299 lines
9.2 KiB
TypeScript
299 lines
9.2 KiB
TypeScript
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<string, Theme> = {
|
|
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 <name>|create <name>|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 <name> to apply a theme.`,
|
|
"info",
|
|
);
|
|
break;
|
|
}
|
|
case "set":
|
|
case "apply": {
|
|
const name = parts[1];
|
|
if (!name) {
|
|
ctx.ui.notify("Usage: /theme set <name>\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 <name>\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 <name>", "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 <name> - Apply a theme\n" +
|
|
" /theme create <name>- Create custom theme\n" +
|
|
" /theme delete <name>- Delete custom theme\n" +
|
|
" /theme reset - Reset to dragon (default)",
|
|
"info",
|
|
);
|
|
}
|
|
},
|
|
});
|
|
}
|