several changes

This commit is contained in:
2026-02-20 15:36:45 +00:00
parent cb1f893e3d
commit 6a82a3c5d2
7 changed files with 3487 additions and 11 deletions

View File

@@ -0,0 +1,144 @@
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 matugenThemeWatch(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 () => {
// i wish this fucking worked
// pi.sendUserMessage("/reload", { deliverAs: "followUp" });
const theme = await loadThemeFromPath(THEME_PATH);
const res = ctx.ui.setTheme(theme);
if (res.success) {
ctx.ui.notify("Background changed. Colors: ", "info");
} else {
ctx.ui.notify("Theme update failed, fuck", "error");
}
}, 150);
},
);
} catch (error) {
ctx.ui.notify(`Theme watch failed: ${String(error)}`, "error");
}
});
pi.on("session_shutdown", () => {
if (watcher) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- weird types
fs.unwatchFile(THEME_PATH, watcher as any);
watcher = null;
}
if (debounce) {
clearTimeout(debounce);
debounce = null;
}
});
}