pi matugen theme

This commit is contained in:
2026-02-19 23:59:42 +00:00
parent 371ab008ae
commit a9362d767e
4 changed files with 239 additions and 3 deletions

View File

@@ -46,3 +46,8 @@ output_path = '~/.config/jjui/themes/matugen.toml'
input_path = '~/.config/matugen/templates/neovim.lua' input_path = '~/.config/matugen/templates/neovim.lua'
output_path = '~/.config/nvim/lua/plugins/dankcolors.lua' output_path = '~/.config/nvim/lua/plugins/dankcolors.lua'
post_hook = 'nohup ~/.config/matugen/scripts/sync-nvim-mac.sh >/dev/null 2>&1 &' post_hook = 'nohup ~/.config/matugen/scripts/sync-nvim-mac.sh >/dev/null 2>&1 &'
[templates.pi]
input_path = '~/.config/matugen/templates/pi-theme.json'
output_path = '~/.pi/agent/themes/matugen.json.tmp'
post_hook = 'cat ~/.pi/agent/themes/matugen.json.tmp > ~/.pi/agent/themes/matugen.json'

View File

@@ -0,0 +1,88 @@
{
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "matugen",
"vars": {
"bg": "{{colors.surface.default.hex}}",
"bgAlt": "{{colors.surface_container.default.hex}}",
"fg": "{{colors.on_surface.default.hex}}",
"muted": "{{colors.on_surface_variant.default.hex}}",
"dim": "{{colors.outline.default.hex}}",
"accent": "{{colors.primary.default.hex}}",
"border": "{{colors.primary.default.hex}}",
"borderAccent": "{{colors.secondary.default.hex}}",
"borderMuted": "{{colors.outline_variant.default.hex}}",
"success": "{{colors.tertiary.default.hex}}",
"error": "{{colors.error.default.hex}}",
"warning": "{{colors.primary_fixed.default.hex}}",
"selectedBg": "{{colors.surface_container_high.default.hex}}",
"userMsgBg": "{{colors.surface_container_high.default.hex}}",
"toolPendingBg": "{{colors.surface_container.default.hex}}",
"toolSuccessBg": "{{colors.tertiary_container.default.hex}}",
"toolErrorBg": "{{colors.error_container.default.hex}}",
"customMsgBg": "{{colors.surface_container_high.default.hex}}"
},
"colors": {
"accent": "accent",
"border": "border",
"borderAccent": "borderAccent",
"borderMuted": "borderMuted",
"success": "success",
"error": "error",
"warning": "warning",
"muted": "muted",
"dim": "dim",
"text": "",
"thinkingText": "muted",
"selectedBg": "selectedBg",
"userMessageBg": "userMsgBg",
"userMessageText": "",
"customMessageBg": "customMsgBg",
"customMessageText": "",
"customMessageLabel": "{{colors.secondary.default.hex}}",
"toolPendingBg": "toolPendingBg",
"toolSuccessBg": "toolSuccessBg",
"toolErrorBg": "toolErrorBg",
"toolTitle": "",
"toolOutput": "muted",
"mdHeading": "{{colors.primary.default.hex}}",
"mdLink": "{{colors.secondary.default.hex}}",
"mdLinkUrl": "muted",
"mdCode": "accent",
"mdCodeBlock": "{{colors.tertiary.default.hex}}",
"mdCodeBlockBorder": "muted",
"mdQuote": "muted",
"mdQuoteBorder": "muted",
"mdHr": "muted",
"mdListBullet": "accent",
"toolDiffAdded": "{{colors.tertiary.default.hex}}",
"toolDiffRemoved": "{{colors.error.default.hex}}",
"toolDiffContext": "muted",
"syntaxComment": "muted",
"syntaxKeyword": "{{colors.primary.default.hex}}",
"syntaxFunction": "{{colors.secondary.default.hex}}",
"syntaxVariable": "fg",
"syntaxString": "{{colors.tertiary.default.hex}}",
"syntaxNumber": "{{colors.primary_fixed.default.hex}}",
"syntaxType": "{{colors.secondary.default.hex}}",
"syntaxOperator": "{{colors.on_surface_variant.default.hex}}",
"syntaxPunctuation": "{{colors.on_surface_variant.default.hex}}",
"thinkingOff": "borderMuted",
"thinkingMinimal": "dim",
"thinkingLow": "{{colors.primary.default.hex}}",
"thinkingMedium": "{{colors.secondary.default.hex}}",
"thinkingHigh": "{{colors.tertiary.default.hex}}",
"thinkingXhigh": "{{colors.error.default.hex}}",
"bashMode": "success"
},
"export": {
"pageBg": "{{colors.surface.default.hex}}",
"cardBg": "{{colors.surface_container.default.hex}}",
"infoBg": "{{colors.surface_container_high.default.hex}}"
}
}

View File

@@ -0,0 +1,143 @@
import fs from "node:fs";
import fsp from "node:fs/promises";
import path from "node:path";
import type {
ExtensionAPI,
Theme as PiTheme,
} from "@mariozechner/pi-coding-agent";
import { Theme } from "@mariozechner/pi-coding-agent";
const THEME_NAME = "matugen";
const THEME_PATH = path.join(
process.env.HOME ?? "",
".pi/agent/themes/matugen.json",
);
const POLL_INTERVAL_MS = 500;
type ColorValue = string | number;
type ThemeJson = {
name: string;
vars?: Record<string, ColorValue>;
colors: Record<string, ColorValue>;
};
const BG_KEYS = new Set([
"selectedBg",
"userMessageBg",
"customMessageBg",
"toolPendingBg",
"toolSuccessBg",
"toolErrorBg",
]);
function detectColorMode(): "truecolor" | "256color" {
const colorterm = process.env.COLORTERM;
if (colorterm === "truecolor" || colorterm === "24bit") return "truecolor";
if (process.env.WT_SESSION) return "truecolor";
const term = process.env.TERM || "";
if (term === "dumb" || term === "" || term === "linux") return "256color";
if (process.env.TERM_PROGRAM === "Apple_Terminal") return "256color";
return "truecolor";
}
function resolveVarRefs(
value: ColorValue,
vars: Record<string, ColorValue>,
visited = new Set<string>(),
): ColorValue {
if (
typeof value === "number" ||
value === "" ||
(typeof value === "string" && value.startsWith("#"))
) {
return value;
}
if (typeof value !== "string") {
throw new Error(`Invalid color value: ${String(value)}`);
}
if (visited.has(value)) {
throw new Error(`Circular variable reference: ${value}`);
}
if (!(value in vars)) {
throw new Error(`Variable reference not found: ${value}`);
}
visited.add(value);
return resolveVarRefs(vars[value], vars, visited);
}
function resolveThemeColors(
colors: Record<string, ColorValue>,
vars: Record<string, ColorValue> = {},
): Record<string, ColorValue> {
const resolved: Record<string, ColorValue> = {};
for (const [key, value] of Object.entries(colors)) {
resolved[key] = resolveVarRefs(value, vars);
}
return resolved;
}
async function loadThemeFromPath(filePath: string): Promise<PiTheme> {
const content = await fsp.readFile(filePath, "utf-8");
const parsed = JSON.parse(content) as ThemeJson;
const resolved = resolveThemeColors(parsed.colors, parsed.vars ?? {});
const fgColors: Record<string, ColorValue> = {};
const bgColors: Record<string, ColorValue> = {};
for (const [key, value] of Object.entries(resolved)) {
if (BG_KEYS.has(key)) {
bgColors[key] = value;
} else {
fgColors[key] = value;
}
}
return new Theme(fgColors as never, bgColors as never, detectColorMode(), {
name: parsed.name ?? THEME_NAME,
sourcePath: filePath,
});
}
export default function (pi: ExtensionAPI) {
let watcher: fs.StatWatcher | null = null;
let debounce: NodeJS.Timeout | null = null;
pi.on("session_start", (_event, ctx) => {
try {
watcher = fs.watchFile(
THEME_PATH,
{ interval: POLL_INTERVAL_MS },
(curr, prev) => {
if (curr.mtimeMs === prev.mtimeMs) return;
if (debounce) {
clearTimeout(debounce);
}
debounce = setTimeout(async () => {
try {
const theme = await loadThemeFromPath(THEME_PATH);
const result = ctx.ui.setTheme(theme);
if (!result.success && result.error) {
ctx.ui.notify(`Theme reload failed: ${result.error}`, "error");
}
} catch (error) {
ctx.ui.notify(`Theme reload failed: ${String(error)}`, "error");
}
}, 150);
},
);
} catch (error) {
ctx.ui.notify(`Theme watch failed: ${String(error)}`, "error");
}
});
pi.on("session_shutdown", () => {
if (watcher) {
fs.unwatchFile(THEME_PATH, watcher);
watcher = null;
}
if (debounce) {
clearTimeout(debounce);
debounce = null;
}
});
}

View File

@@ -1,7 +1,7 @@
{ {
"lastChangelogVersion": "0.53.0", "lastChangelogVersion": "0.54.0",
"defaultProvider": "openrouter", "defaultProvider": "openrouter",
"defaultModel": "openai/gpt-5.2-codex", "defaultModel": "openai/gpt-5.2-codex",
"defaultThinkingLevel": "minimal", "defaultThinkingLevel": "high",
"theme": "dark" "theme": "matugen"
} }