import type { ExtensionAPI, ExtensionContext } from "@jaeswift/jae-coding-agent"; import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; interface ThemeColors { primary: string; secondary: string; accent: string; bg: string; surface: string; border: string; text: string; muted: string; } interface Theme { name: string; description: string; colors: ThemeColors; } interface ThemeConfig { active: string; custom: Theme[]; } export default function (pi: ExtensionAPI) { const THEMES: Record = { dragon: { name: "dragon", description: "Default JAE theme — black with orange accents", colors: { primary: "#FF6B00", secondary: "#FF8C38", accent: "#FFA500", bg: "#0a0a0a", surface: "#111111", border: "#1e1e1e", text: "#f8f8f8", muted: "#888888", }, }, midnight: { name: "midnight", description: "Deep dark blue — calm and focused", colors: { primary: "#4A9EFF", secondary: "#6BB5FF", accent: "#80CAFF", bg: "#0a0e1a", surface: "#111827", border: "#1e293b", text: "#e2e8f0", muted: "#64748b", }, }, matrix: { name: "matrix", description: "Green on black — classic hacker aesthetic", colors: { primary: "#00FF41", secondary: "#00CC33", accent: "#33FF77", bg: "#000000", surface: "#0a0a0a", border: "#0f3d0f", text: "#00FF41", muted: "#00802a", }, }, frost: { name: "frost", description: "Light cool tones — easy on the eyes", colors: { primary: "#5B8DEF", secondary: "#7AA8F2", accent: "#98C1FF", bg: "#0c1220", surface: "#141e30", border: "#1f2d42", text: "#dce6f5", muted: "#7b8da6", }, }, sunset: { name: "sunset", description: "Warm oranges and reds — vibrant energy", colors: { primary: "#FF4500", secondary: "#FF6347", accent: "#FF8C00", bg: "#0d0806", surface: "#1a0f0a", border: "#2d1810", text: "#fce4d6", muted: "#a0735c", }, }, }; const configDir = join(process.env.HOME || "/root", ".jae"); const configPath = join(configDir, "theme.json"); function loadConfig(): ThemeConfig { try { if (existsSync(configPath)) { return JSON.parse(readFileSync(configPath, "utf-8")); } } catch { /* ignore */ } return { active: "dragon", custom: [] }; } function saveConfig(config: ThemeConfig): void { if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true }); writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8"); } function getTheme(name: string, config: ThemeConfig): Theme | undefined { if (THEMES[name]) return THEMES[name]; return config.custom.find((t) => t.name === name); } function getAllThemes(config: ThemeConfig): Theme[] { return [...Object.values(THEMES), ...config.custom]; } function applyTheme(theme: Theme, ctx: ExtensionContext): void { const c = theme.colors; ctx.ui.setStatus( "theme", `\u{1F3A8} ${theme.name}`, ); // Apply theme colors via footer display ctx.ui.setFooter( "theme", `\u{1F3A8} Theme: ${theme.name} | ${c.primary} ${c.secondary}`, ); } function formatThemePreview(theme: Theme): string { const c = theme.colors; return ( ` \u{1F3A8} ${theme.name} — ${theme.description}\n` + ` Primary: ${c.primary} Secondary: ${c.secondary}\n` + ` Accent: ${c.accent} Background: ${c.bg}\n` + ` Surface: ${c.surface} Border: ${c.border}\n` + ` Text: ${c.text} Muted: ${c.muted}` ); } // Apply theme on session start pi.on("session_start", async (_event, ctx) => { const config = loadConfig(); const theme = getTheme(config.active, config) || THEMES.dragon; applyTheme(theme, ctx); }); pi.registerCommand("theme", { description: "Manage themes: /theme [list|set |create |show]", handler: async (args, ctx) => { const parts = args.trim().split(/\s+/); const subcommand = (parts[0] || "show").toLowerCase(); const config = loadConfig(); switch (subcommand) { case "show": case "": { const theme = getTheme(config.active, config) || THEMES.dragon; ctx.ui.notify( `\u{1F3A8} Current Theme\n${'\u2500'.repeat(40)}\n\n${formatThemePreview(theme)}`, "info", ); break; } case "list": case "ls": { const all = getAllThemes(config); const list = all .map((t) => { const active = t.name === config.active ? " \u2705 (active)" : ""; const isCustom = !THEMES[t.name] ? " [custom]" : ""; return ` \u{25CF} ${t.name}${active}${isCustom} — ${t.description}`; }) .join("\n"); ctx.ui.notify( `\u{1F3A8} Available Themes\n${'\u2500'.repeat(40)}\n\n${list}\n\nUse /theme set to apply a theme.`, "info", ); break; } case "set": case "apply": { const name = parts[1]; if (!name) { ctx.ui.notify("Usage: /theme set \n\nUse /theme list to see available themes.", "warning"); return; } const theme = getTheme(name.toLowerCase(), config); if (!theme) { ctx.ui.notify( `\u274C Theme '${name}' not found.\n\nAvailable: ${getAllThemes(config).map((t) => t.name).join(", ")}`, "warning", ); return; } config.active = theme.name; saveConfig(config); applyTheme(theme, ctx); ctx.ui.notify( `\u2705 Theme set to '${theme.name}'\n\n${formatThemePreview(theme)}`, "info", ); break; } case "create": { const name = parts[1]; if (!name) { ctx.ui.notify( "Usage: /theme create \n\n" + "Creates a custom theme based on the current theme.\n" + "Edit ~/.jae/theme.json to customize colors.", "warning", ); return; } const themeName = name.toLowerCase().replace(/[^a-z0-9_-]/g, "-"); if (THEMES[themeName]) { ctx.ui.notify(`\u274C Cannot override built-in theme '${themeName}'.`, "warning"); return; } const existing = config.custom.find((t) => t.name === themeName); if (existing) { ctx.ui.notify(`\u274C Custom theme '${themeName}' already exists. Edit ~/.jae/theme.json to modify.`, "warning"); return; } // Clone current theme as base const base = getTheme(config.active, config) || THEMES.dragon; const newTheme: Theme = { name: themeName, description: `Custom theme based on ${base.name}`, colors: { ...base.colors }, }; config.custom.push(newTheme); config.active = themeName; saveConfig(config); applyTheme(newTheme, ctx); ctx.ui.notify( `\u2705 Custom theme '${themeName}' created and activated!\n\n${formatThemePreview(newTheme)}\n\n\u{1F4DD} Edit colors in ~/.jae/theme.json`, "info", ); break; } case "reset": { config.active = "dragon"; saveConfig(config); applyTheme(THEMES.dragon, ctx); ctx.ui.notify("\u2705 Theme reset to dragon (default).", "info"); break; } case "delete": case "remove": { const name = parts[1]; if (!name) { ctx.ui.notify("Usage: /theme delete ", "warning"); return; } const themeName = name.toLowerCase(); if (THEMES[themeName]) { ctx.ui.notify(`\u274C Cannot delete built-in theme '${themeName}'.`, "warning"); return; } const idx = config.custom.findIndex((t) => t.name === themeName); if (idx === -1) { ctx.ui.notify(`\u274C Custom theme '${themeName}' not found.`, "warning"); return; } config.custom.splice(idx, 1); if (config.active === themeName) config.active = "dragon"; saveConfig(config); ctx.ui.notify(`\u2705 Custom theme '${themeName}' deleted.`, "info"); break; } default: ctx.ui.notify( `\u{1F3A8} Theme System\n${'\u2500'.repeat(30)}\n\n` + "Commands:\n" + " /theme - Show current theme\n" + " /theme list - List all themes\n" + " /theme set - Apply a theme\n" + " /theme create - Create custom theme\n" + " /theme delete - Delete custom theme\n" + " /theme reset - Reset to dragon (default)", "info", ); } }, }); }