codemaxxing 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.js +33 -13
- package/dist/themes.d.ts +24 -0
- package/dist/themes.js +149 -0
- package/package.json +1 -1
- package/src/index.tsx +65 -45
- package/src/themes.ts +173 -0
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> your code. your model. no excuses.
|
|
4
4
|
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/screenshot.jpg" alt="codemaxxing terminal UI" width="700">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
5
9
|
Open-source terminal coding agent. Connect **any** LLM — local or remote — and start building. Like Claude Code, but you bring your own model.
|
|
6
10
|
|
|
7
11
|
## Why?
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { loadConfig, detectLocalProvider, parseCLIArgs, applyOverrides, listMode
|
|
|
9
9
|
import { listSessions, getSession, loadMessages } from "./utils/sessions.js";
|
|
10
10
|
import { execSync } from "child_process";
|
|
11
11
|
import { isGitRepo, getBranch, getStatus, getDiff, undoLastCommit } from "./utils/git.js";
|
|
12
|
+
import { getTheme, THEMES, DEFAULT_THEME } from "./themes.js";
|
|
12
13
|
const VERSION = "0.1.0";
|
|
13
14
|
// ── Helpers ──
|
|
14
15
|
function formatTimeAgo(date) {
|
|
@@ -37,6 +38,7 @@ const SLASH_COMMANDS = [
|
|
|
37
38
|
{ cmd: "/git on", desc: "enable auto-commits" },
|
|
38
39
|
{ cmd: "/git off", desc: "disable auto-commits" },
|
|
39
40
|
{ cmd: "/models", desc: "list available models" },
|
|
41
|
+
{ cmd: "/theme", desc: "switch color theme" },
|
|
40
42
|
{ cmd: "/model", desc: "switch model mid-session" },
|
|
41
43
|
{ cmd: "/sessions", desc: "list past sessions" },
|
|
42
44
|
{ cmd: "/resume", desc: "resume a past session" },
|
|
@@ -51,7 +53,7 @@ const SPINNER_MESSAGES = [
|
|
|
51
53
|
"Hacking the main frame...", "Codemaxxing...", "Vibe coding...", "Running a marathon...",
|
|
52
54
|
];
|
|
53
55
|
// ── Neon Spinner ──
|
|
54
|
-
function NeonSpinner({ message }) {
|
|
56
|
+
function NeonSpinner({ message, colors }) {
|
|
55
57
|
const [frame, setFrame] = useState(0);
|
|
56
58
|
const [elapsed, setElapsed] = useState(0);
|
|
57
59
|
useEffect(() => {
|
|
@@ -62,11 +64,11 @@ function NeonSpinner({ message }) {
|
|
|
62
64
|
}, 80);
|
|
63
65
|
return () => clearInterval(interval);
|
|
64
66
|
}, []);
|
|
65
|
-
return (_jsxs(Text, { children: [" ", _jsx(Text, { color:
|
|
67
|
+
return (_jsxs(Text, { children: [" ", _jsx(Text, { color: colors.spinner, children: SPINNER_FRAMES[frame] }), " ", _jsx(Text, { bold: true, color: colors.secondary, children: message }), " ", _jsxs(Text, { color: colors.muted, children: ["[", elapsed, "s]"] })] }));
|
|
66
68
|
}
|
|
67
69
|
// ── Streaming Indicator (subtle, shows model is still working) ──
|
|
68
70
|
const STREAM_DOTS = ["· ", "·· ", "···", " ··", " ·", " "];
|
|
69
|
-
function StreamingIndicator() {
|
|
71
|
+
function StreamingIndicator({ colors }) {
|
|
70
72
|
const [frame, setFrame] = useState(0);
|
|
71
73
|
useEffect(() => {
|
|
72
74
|
const interval = setInterval(() => {
|
|
@@ -74,7 +76,7 @@ function StreamingIndicator() {
|
|
|
74
76
|
}, 300);
|
|
75
77
|
return () => clearInterval(interval);
|
|
76
78
|
}, []);
|
|
77
|
-
return (_jsxs(Text, { dimColor: true, children: [" ", _jsx(Text, { color:
|
|
79
|
+
return (_jsxs(Text, { dimColor: true, children: [" ", _jsx(Text, { color: colors.spinner, children: STREAM_DOTS[frame] }), " ", _jsx(Text, { color: colors.muted, children: "streaming" })] }));
|
|
78
80
|
}
|
|
79
81
|
let msgId = 0;
|
|
80
82
|
// ── Main App ──
|
|
@@ -91,6 +93,7 @@ function App() {
|
|
|
91
93
|
const [spinnerMsg, setSpinnerMsg] = useState("");
|
|
92
94
|
const [agent, setAgent] = useState(null);
|
|
93
95
|
const [modelName, setModelName] = useState("");
|
|
96
|
+
const [theme, setTheme] = useState(getTheme(DEFAULT_THEME));
|
|
94
97
|
const providerRef = React.useRef({ baseUrl: "", apiKey: "" });
|
|
95
98
|
const [ready, setReady] = useState(false);
|
|
96
99
|
const [connectionInfo, setConnectionInfo] = useState([]);
|
|
@@ -323,6 +326,23 @@ function App() {
|
|
|
323
326
|
addMsg("info", `✅ Switched to model: ${newModel}`);
|
|
324
327
|
return;
|
|
325
328
|
}
|
|
329
|
+
if (trimmed.startsWith("/theme")) {
|
|
330
|
+
const themeName = trimmed.replace("/theme", "").trim();
|
|
331
|
+
if (!themeName) {
|
|
332
|
+
const available = Object.entries(THEMES)
|
|
333
|
+
.map(([key, t]) => ` ${key === theme.name.toLowerCase() ? "▸ " : " "}${key} — ${t.description}`)
|
|
334
|
+
.join("\n");
|
|
335
|
+
addMsg("info", `Current theme: ${theme.name}\n\nAvailable themes:\n${available}\n\n Usage: /theme <name>`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (!THEMES[themeName]) {
|
|
339
|
+
addMsg("error", `Theme "${themeName}" not found. Use /theme to see available themes.`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
setTheme(getTheme(themeName));
|
|
343
|
+
addMsg("info", `✅ Switched to theme: ${THEMES[themeName].name}`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
326
346
|
if (trimmed === "/map") {
|
|
327
347
|
const map = agent.getRepoMap();
|
|
328
348
|
if (map) {
|
|
@@ -576,27 +596,27 @@ function App() {
|
|
|
576
596
|
"| | | | | | | | / .'. \\ / .'. \\ | |'->| | \\ | | '-' | ",
|
|
577
597
|
"`--' `--' `--' `--'`--' '--'`--' '--'`--' `--' `--' `-----' ",
|
|
578
598
|
];
|
|
579
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
599
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.border, paddingX: 1, children: [codeLines.map((line, i) => (_jsx(Text, { color: theme.colors.primary, children: line }, `c${i}`))), maxxingLines.map((line, i) => (_jsx(Text, { color: theme.colors.secondary, children: line }, `m${i}`))), _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.muted, children: " v" + VERSION }), " ", _jsx(Text, { color: theme.colors.primary, children: "\uD83D\uDCAA" }), " ", _jsx(Text, { dimColor: true, children: "your code. your model. no excuses." })] }), _jsxs(Text, { dimColor: true, children: [" Type ", _jsx(Text, { color: theme.colors.muted, children: "/help" }), " for commands · ", _jsx(Text, { color: theme.colors.muted, children: "Ctrl+C" }), " twice to exit"] })] }), connectionInfo.length > 0 && (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.muted, paddingX: 1, marginBottom: 1, children: connectionInfo.map((line, i) => (_jsx(Text, { color: line.startsWith("✔") ? theme.colors.primary : line.startsWith("✗") ? theme.colors.error : theme.colors.muted, children: line }, i))) })), messages.map((msg) => {
|
|
580
600
|
switch (msg.type) {
|
|
581
601
|
case "user":
|
|
582
|
-
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color:
|
|
602
|
+
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.colors.userInput, children: [" > ", msg.text] }) }, msg.id));
|
|
583
603
|
case "response":
|
|
584
|
-
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: msg.text.split("\n").map((l, i) => (_jsxs(Text, { wrap: "wrap", children: [i === 0 ? _jsx(Text, { color:
|
|
585
|
-
l.startsWith("# ") || l.startsWith("## ") ? _jsx(Text, { bold: true, color:
|
|
604
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: msg.text.split("\n").map((l, i) => (_jsxs(Text, { wrap: "wrap", children: [i === 0 ? _jsx(Text, { color: theme.colors.response, children: "\u25CF " }) : _jsx(Text, { children: " " }), l.startsWith("```") ? _jsx(Text, { color: theme.colors.muted, children: l }) :
|
|
605
|
+
l.startsWith("# ") || l.startsWith("## ") ? _jsx(Text, { bold: true, color: theme.colors.secondary, children: l }) :
|
|
586
606
|
l.startsWith("**") ? _jsx(Text, { bold: true, children: l }) :
|
|
587
607
|
_jsx(Text, { children: l })] }, i))) }, msg.id));
|
|
588
608
|
case "tool":
|
|
589
|
-
return (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color:
|
|
609
|
+
return (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.response, children: " \u25CF " }), _jsx(Text, { bold: true, color: theme.colors.tool, children: msg.text })] }) }, msg.id));
|
|
590
610
|
case "tool-result":
|
|
591
|
-
return _jsxs(Text, { color:
|
|
611
|
+
return _jsxs(Text, { color: theme.colors.toolResult, children: [" ", msg.text] }, msg.id);
|
|
592
612
|
case "error":
|
|
593
|
-
return _jsxs(Text, { color:
|
|
613
|
+
return _jsxs(Text, { color: theme.colors.error, children: [" ", msg.text] }, msg.id);
|
|
594
614
|
case "info":
|
|
595
|
-
return _jsxs(Text, { color:
|
|
615
|
+
return _jsxs(Text, { color: theme.colors.muted, children: [" ", msg.text] }, msg.id);
|
|
596
616
|
default:
|
|
597
617
|
return _jsx(Text, { children: msg.text }, msg.id);
|
|
598
618
|
}
|
|
599
|
-
}), loading && !approval && !streaming && _jsx(NeonSpinner, { message: spinnerMsg }), streaming && !loading && _jsx(StreamingIndicator, {}), approval && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor:
|
|
619
|
+
}), loading && !approval && !streaming && _jsx(NeonSpinner, { message: spinnerMsg, colors: theme.colors }), streaming && !loading && _jsx(StreamingIndicator, { colors: theme.colors }), approval && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.warning, paddingX: 1, marginTop: 1, children: [_jsxs(Text, { bold: true, color: theme.colors.warning, children: ["\u26A0 Approve ", approval.tool, "?"] }), approval.tool === "write_file" && approval.args.path ? (_jsxs(Text, { color: theme.colors.muted, children: [" 📄 ", String(approval.args.path)] })) : null, approval.tool === "write_file" && approval.args.content ? (_jsxs(Text, { color: theme.colors.muted, children: [" ", String(approval.args.content).split("\n").length, " lines, ", String(approval.args.content).length, "B"] })) : null, approval.tool === "run_command" && approval.args.command ? (_jsxs(Text, { color: theme.colors.muted, children: [" $ ", String(approval.args.command)] })) : null, _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.success, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: theme.colors.error, bold: true, children: "[n]" }), _jsx(Text, { children: "o " }), _jsx(Text, { color: theme.colors.primary, bold: true, children: "[a]" }), _jsx(Text, { children: "lways" })] })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted, children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), showSuggestions && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.muted, paddingX: 1, marginBottom: 0, children: [cmdMatches.slice(0, 6).map((c, i) => (_jsxs(Text, { children: [i === cmdIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === cmdIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: c.cmd }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", c.desc] })] }, i))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Tab select" })] })), _jsxs(Box, { borderStyle: "single", borderColor: approval ? theme.colors.warning : theme.colors.border, paddingX: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "> " }), approval ? (_jsx(Text, { color: theme.colors.warning, children: "waiting for approval..." })) : ready && !loading ? (_jsxs(Box, { children: [pastedChunks.map((p) => (_jsxs(Text, { color: theme.colors.muted, children: ["[Pasted text #", p.id, " +", p.lines, " lines]"] }, p.id))), _jsx(TextInput, { value: input, onChange: (v) => { setInput(v); setCmdIndex(0); }, onSubmit: handleSubmit }, inputKey)] })) : (_jsx(Text, { dimColor: true, children: loading ? "waiting for response..." : "initializing..." }))] }), agent && (_jsx(Box, { paddingX: 2, children: _jsxs(Text, { dimColor: true, children: ["💬 ", agent.getContextLength(), " messages · ~", (() => {
|
|
600
620
|
const tokens = agent.estimateTokens();
|
|
601
621
|
return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
|
|
602
622
|
})(), " tokens", modelName ? ` · 🤖 ${modelName}` : ""] }) }))] }));
|
package/dist/themes.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Theme {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
colors: {
|
|
5
|
+
primary: string;
|
|
6
|
+
secondary: string;
|
|
7
|
+
muted: string;
|
|
8
|
+
text: string;
|
|
9
|
+
userInput: string;
|
|
10
|
+
response: string;
|
|
11
|
+
tool: string;
|
|
12
|
+
toolResult: string;
|
|
13
|
+
error: string;
|
|
14
|
+
success: string;
|
|
15
|
+
warning: string;
|
|
16
|
+
spinner: string;
|
|
17
|
+
border: string;
|
|
18
|
+
suggestion: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare const THEMES: Record<string, Theme>;
|
|
22
|
+
export declare const DEFAULT_THEME = "neon";
|
|
23
|
+
export declare function getTheme(name: string): Theme;
|
|
24
|
+
export declare function listThemes(): string[];
|
package/dist/themes.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export const THEMES = {
|
|
2
|
+
neon: {
|
|
3
|
+
name: "Neon",
|
|
4
|
+
description: "Cyberpunk neon (default)",
|
|
5
|
+
colors: {
|
|
6
|
+
primary: "#00FFFF",
|
|
7
|
+
secondary: "#FF00FF",
|
|
8
|
+
muted: "#008B8B",
|
|
9
|
+
text: "",
|
|
10
|
+
userInput: "#008B8B",
|
|
11
|
+
response: "#00FFFF",
|
|
12
|
+
tool: "#FF00FF",
|
|
13
|
+
toolResult: "#008B8B",
|
|
14
|
+
error: "red",
|
|
15
|
+
success: "#00FF00",
|
|
16
|
+
warning: "#FF8C00",
|
|
17
|
+
spinner: "#00FFFF",
|
|
18
|
+
border: "#00FFFF",
|
|
19
|
+
suggestion: "#FF00FF",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
dracula: {
|
|
23
|
+
name: "Dracula",
|
|
24
|
+
description: "Dark purple tones",
|
|
25
|
+
colors: {
|
|
26
|
+
primary: "#BD93F9",
|
|
27
|
+
secondary: "#FF79C6",
|
|
28
|
+
muted: "#6272A4",
|
|
29
|
+
text: "#F8F8F2",
|
|
30
|
+
userInput: "#8BE9FD",
|
|
31
|
+
response: "#BD93F9",
|
|
32
|
+
tool: "#FF79C6",
|
|
33
|
+
toolResult: "#6272A4",
|
|
34
|
+
error: "#FF5555",
|
|
35
|
+
success: "#50FA7B",
|
|
36
|
+
warning: "#FFB86C",
|
|
37
|
+
spinner: "#BD93F9",
|
|
38
|
+
border: "#BD93F9",
|
|
39
|
+
suggestion: "#FF79C6",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
gruvbox: {
|
|
43
|
+
name: "Gruvbox",
|
|
44
|
+
description: "Warm retro tones",
|
|
45
|
+
colors: {
|
|
46
|
+
primary: "#FE8019",
|
|
47
|
+
secondary: "#FABD2F",
|
|
48
|
+
muted: "#928374",
|
|
49
|
+
text: "#EBDBB2",
|
|
50
|
+
userInput: "#83A598",
|
|
51
|
+
response: "#FE8019",
|
|
52
|
+
tool: "#FABD2F",
|
|
53
|
+
toolResult: "#928374",
|
|
54
|
+
error: "#FB4934",
|
|
55
|
+
success: "#B8BB26",
|
|
56
|
+
warning: "#FABD2F",
|
|
57
|
+
spinner: "#FE8019",
|
|
58
|
+
border: "#FE8019",
|
|
59
|
+
suggestion: "#FABD2F",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
nord: {
|
|
63
|
+
name: "Nord",
|
|
64
|
+
description: "Cool arctic blues",
|
|
65
|
+
colors: {
|
|
66
|
+
primary: "#88C0D0",
|
|
67
|
+
secondary: "#81A1C1",
|
|
68
|
+
muted: "#4C566A",
|
|
69
|
+
text: "#ECEFF4",
|
|
70
|
+
userInput: "#88C0D0",
|
|
71
|
+
response: "#81A1C1",
|
|
72
|
+
tool: "#5E81AC",
|
|
73
|
+
toolResult: "#4C566A",
|
|
74
|
+
error: "#BF616A",
|
|
75
|
+
success: "#A3BE8C",
|
|
76
|
+
warning: "#EBCB8B",
|
|
77
|
+
spinner: "#88C0D0",
|
|
78
|
+
border: "#81A1C1",
|
|
79
|
+
suggestion: "#88C0D0",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
mono: {
|
|
83
|
+
name: "Mono",
|
|
84
|
+
description: "Clean monochrome — easy on the eyes",
|
|
85
|
+
colors: {
|
|
86
|
+
primary: "#AAAAAA",
|
|
87
|
+
secondary: "#FFFFFF",
|
|
88
|
+
muted: "#666666",
|
|
89
|
+
text: "#CCCCCC",
|
|
90
|
+
userInput: "#AAAAAA",
|
|
91
|
+
response: "#FFFFFF",
|
|
92
|
+
tool: "#CCCCCC",
|
|
93
|
+
toolResult: "#666666",
|
|
94
|
+
error: "#FF6666",
|
|
95
|
+
success: "#66FF66",
|
|
96
|
+
warning: "#FFAA66",
|
|
97
|
+
spinner: "#AAAAAA",
|
|
98
|
+
border: "#888888",
|
|
99
|
+
suggestion: "#FFFFFF",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
solarized: {
|
|
103
|
+
name: "Solarized",
|
|
104
|
+
description: "Solarized dark",
|
|
105
|
+
colors: {
|
|
106
|
+
primary: "#268BD2",
|
|
107
|
+
secondary: "#2AA198",
|
|
108
|
+
muted: "#586E75",
|
|
109
|
+
text: "#839496",
|
|
110
|
+
userInput: "#2AA198",
|
|
111
|
+
response: "#268BD2",
|
|
112
|
+
tool: "#B58900",
|
|
113
|
+
toolResult: "#586E75",
|
|
114
|
+
error: "#DC322F",
|
|
115
|
+
success: "#859900",
|
|
116
|
+
warning: "#CB4B16",
|
|
117
|
+
spinner: "#268BD2",
|
|
118
|
+
border: "#268BD2",
|
|
119
|
+
suggestion: "#2AA198",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
hacker: {
|
|
123
|
+
name: "Hacker",
|
|
124
|
+
description: "Green on black — classic terminal",
|
|
125
|
+
colors: {
|
|
126
|
+
primary: "#00FF00",
|
|
127
|
+
secondary: "#00CC00",
|
|
128
|
+
muted: "#006600",
|
|
129
|
+
text: "#00DD00",
|
|
130
|
+
userInput: "#00FF00",
|
|
131
|
+
response: "#00FF00",
|
|
132
|
+
tool: "#00CC00",
|
|
133
|
+
toolResult: "#006600",
|
|
134
|
+
error: "#FF0000",
|
|
135
|
+
success: "#00FF00",
|
|
136
|
+
warning: "#FFFF00",
|
|
137
|
+
spinner: "#00FF00",
|
|
138
|
+
border: "#00FF00",
|
|
139
|
+
suggestion: "#00CC00",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
export const DEFAULT_THEME = "neon";
|
|
144
|
+
export function getTheme(name) {
|
|
145
|
+
return THEMES[name] ?? THEMES[DEFAULT_THEME];
|
|
146
|
+
}
|
|
147
|
+
export function listThemes() {
|
|
148
|
+
return Object.keys(THEMES);
|
|
149
|
+
}
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { loadConfig, detectLocalProvider, parseCLIArgs, applyOverrides, listMode
|
|
|
9
9
|
import { listSessions, getSession, loadMessages } from "./utils/sessions.js";
|
|
10
10
|
import { execSync } from "child_process";
|
|
11
11
|
import { isGitRepo, getBranch, getStatus, getDiff, undoLastCommit } from "./utils/git.js";
|
|
12
|
+
import { getTheme, listThemes, THEMES, DEFAULT_THEME, type Theme } from "./themes.js";
|
|
12
13
|
|
|
13
14
|
const VERSION = "0.1.0";
|
|
14
15
|
|
|
@@ -37,6 +38,7 @@ const SLASH_COMMANDS = [
|
|
|
37
38
|
{ cmd: "/git on", desc: "enable auto-commits" },
|
|
38
39
|
{ cmd: "/git off", desc: "disable auto-commits" },
|
|
39
40
|
{ cmd: "/models", desc: "list available models" },
|
|
41
|
+
{ cmd: "/theme", desc: "switch color theme" },
|
|
40
42
|
{ cmd: "/model", desc: "switch model mid-session" },
|
|
41
43
|
{ cmd: "/sessions", desc: "list past sessions" },
|
|
42
44
|
{ cmd: "/resume", desc: "resume a past session" },
|
|
@@ -54,7 +56,7 @@ const SPINNER_MESSAGES = [
|
|
|
54
56
|
];
|
|
55
57
|
|
|
56
58
|
// ── Neon Spinner ──
|
|
57
|
-
function NeonSpinner({ message }: { message: string }) {
|
|
59
|
+
function NeonSpinner({ message, colors }: { message: string; colors: Theme['colors'] }) {
|
|
58
60
|
const [frame, setFrame] = useState(0);
|
|
59
61
|
const [elapsed, setElapsed] = useState(0);
|
|
60
62
|
|
|
@@ -69,16 +71,16 @@ function NeonSpinner({ message }: { message: string }) {
|
|
|
69
71
|
|
|
70
72
|
return (
|
|
71
73
|
<Text>
|
|
72
|
-
{" "}<Text color=
|
|
73
|
-
{" "}<Text bold color=
|
|
74
|
-
{" "}<Text color=
|
|
74
|
+
{" "}<Text color={colors.spinner}>{SPINNER_FRAMES[frame]}</Text>
|
|
75
|
+
{" "}<Text bold color={colors.secondary}>{message}</Text>
|
|
76
|
+
{" "}<Text color={colors.muted}>[{elapsed}s]</Text>
|
|
75
77
|
</Text>
|
|
76
78
|
);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// ── Streaming Indicator (subtle, shows model is still working) ──
|
|
80
82
|
const STREAM_DOTS = ["· ", "·· ", "···", " ··", " ·", " "];
|
|
81
|
-
function StreamingIndicator() {
|
|
83
|
+
function StreamingIndicator({ colors }: { colors: Theme['colors'] }) {
|
|
82
84
|
const [frame, setFrame] = useState(0);
|
|
83
85
|
|
|
84
86
|
useEffect(() => {
|
|
@@ -90,8 +92,8 @@ function StreamingIndicator() {
|
|
|
90
92
|
|
|
91
93
|
return (
|
|
92
94
|
<Text dimColor>
|
|
93
|
-
{" "}<Text color=
|
|
94
|
-
{" "}<Text color=
|
|
95
|
+
{" "}<Text color={colors.spinner}>{STREAM_DOTS[frame]}</Text>
|
|
96
|
+
{" "}<Text color={colors.muted}>streaming</Text>
|
|
95
97
|
</Text>
|
|
96
98
|
);
|
|
97
99
|
}
|
|
@@ -120,6 +122,7 @@ function App() {
|
|
|
120
122
|
const [spinnerMsg, setSpinnerMsg] = useState("");
|
|
121
123
|
const [agent, setAgent] = useState<CodingAgent | null>(null);
|
|
122
124
|
const [modelName, setModelName] = useState("");
|
|
125
|
+
const [theme, setTheme] = useState<Theme>(getTheme(DEFAULT_THEME));
|
|
123
126
|
const providerRef = React.useRef<{ baseUrl: string; apiKey: string }>({ baseUrl: "", apiKey: "" });
|
|
124
127
|
const [ready, setReady] = useState(false);
|
|
125
128
|
const [connectionInfo, setConnectionInfo] = useState<string[]>([]);
|
|
@@ -370,6 +373,23 @@ function App() {
|
|
|
370
373
|
addMsg("info", `✅ Switched to model: ${newModel}`);
|
|
371
374
|
return;
|
|
372
375
|
}
|
|
376
|
+
if (trimmed.startsWith("/theme")) {
|
|
377
|
+
const themeName = trimmed.replace("/theme", "").trim();
|
|
378
|
+
if (!themeName) {
|
|
379
|
+
const available = Object.entries(THEMES)
|
|
380
|
+
.map(([key, t]) => ` ${key === theme.name.toLowerCase() ? "▸ " : " "}${key} — ${t.description}`)
|
|
381
|
+
.join("\n");
|
|
382
|
+
addMsg("info", `Current theme: ${theme.name}\n\nAvailable themes:\n${available}\n\n Usage: /theme <name>`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (!THEMES[themeName]) {
|
|
386
|
+
addMsg("error", `Theme "${themeName}" not found. Use /theme to see available themes.`);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
setTheme(getTheme(themeName));
|
|
390
|
+
addMsg("info", `✅ Switched to theme: ${THEMES[themeName].name}`);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
373
393
|
if (trimmed === "/map") {
|
|
374
394
|
const map = agent.getRepoMap();
|
|
375
395
|
if (map) {
|
|
@@ -628,26 +648,26 @@ function App() {
|
|
|
628
648
|
return (
|
|
629
649
|
<Box flexDirection="column">
|
|
630
650
|
{/* ═══ BANNER BOX ═══ */}
|
|
631
|
-
<Box flexDirection="column" borderStyle="round" borderColor=
|
|
651
|
+
<Box flexDirection="column" borderStyle="round" borderColor={theme.colors.border} paddingX={1}>
|
|
632
652
|
{codeLines.map((line, i) => (
|
|
633
|
-
<Text key={`c${i}`} color=
|
|
653
|
+
<Text key={`c${i}`} color={theme.colors.primary}>{line}</Text>
|
|
634
654
|
))}
|
|
635
655
|
{maxxingLines.map((line, i) => (
|
|
636
|
-
<Text key={`m${i}`} color={
|
|
656
|
+
<Text key={`m${i}`} color={theme.colors.secondary}>{line}</Text>
|
|
637
657
|
))}
|
|
638
658
|
<Text>
|
|
639
|
-
<Text color=
|
|
640
|
-
{" "}<Text color=
|
|
659
|
+
<Text color={theme.colors.muted}>{" v" + VERSION}</Text>
|
|
660
|
+
{" "}<Text color={theme.colors.primary}>💪</Text>
|
|
641
661
|
{" "}<Text dimColor>your code. your model. no excuses.</Text>
|
|
642
662
|
</Text>
|
|
643
|
-
<Text dimColor>{" Type "}<Text color=
|
|
663
|
+
<Text dimColor>{" Type "}<Text color={theme.colors.muted}>/help</Text>{" for commands · "}<Text color={theme.colors.muted}>Ctrl+C</Text>{" twice to exit"}</Text>
|
|
644
664
|
</Box>
|
|
645
665
|
|
|
646
666
|
{/* ═══ CONNECTION INFO BOX ═══ */}
|
|
647
667
|
{connectionInfo.length > 0 && (
|
|
648
|
-
<Box flexDirection="column" borderStyle="single" borderColor=
|
|
668
|
+
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.muted} paddingX={1} marginBottom={1}>
|
|
649
669
|
{connectionInfo.map((line, i) => (
|
|
650
|
-
<Text key={i} color={line.startsWith("✔") ?
|
|
670
|
+
<Text key={i} color={line.startsWith("✔") ? theme.colors.primary : line.startsWith("✗") ? theme.colors.error : theme.colors.muted}>{line}</Text>
|
|
651
671
|
))}
|
|
652
672
|
</Box>
|
|
653
673
|
)}
|
|
@@ -658,7 +678,7 @@ function App() {
|
|
|
658
678
|
case "user":
|
|
659
679
|
return (
|
|
660
680
|
<Box key={msg.id} marginTop={1}>
|
|
661
|
-
<Text color=
|
|
681
|
+
<Text color={theme.colors.userInput}>{" > "}{msg.text}</Text>
|
|
662
682
|
</Box>
|
|
663
683
|
);
|
|
664
684
|
case "response":
|
|
@@ -666,9 +686,9 @@ function App() {
|
|
|
666
686
|
<Box key={msg.id} flexDirection="column" marginLeft={2} marginBottom={1}>
|
|
667
687
|
{msg.text.split("\n").map((l, i) => (
|
|
668
688
|
<Text key={i} wrap="wrap">
|
|
669
|
-
{i === 0 ? <Text color=
|
|
670
|
-
{l.startsWith("```") ? <Text color=
|
|
671
|
-
l.startsWith("# ") || l.startsWith("## ") ? <Text bold color=
|
|
689
|
+
{i === 0 ? <Text color={theme.colors.response}>● </Text> : <Text> </Text>}
|
|
690
|
+
{l.startsWith("```") ? <Text color={theme.colors.muted}>{l}</Text> :
|
|
691
|
+
l.startsWith("# ") || l.startsWith("## ") ? <Text bold color={theme.colors.secondary}>{l}</Text> :
|
|
672
692
|
l.startsWith("**") ? <Text bold>{l}</Text> :
|
|
673
693
|
<Text>{l}</Text>}
|
|
674
694
|
</Text>
|
|
@@ -678,53 +698,53 @@ function App() {
|
|
|
678
698
|
case "tool":
|
|
679
699
|
return (
|
|
680
700
|
<Box key={msg.id}>
|
|
681
|
-
<Text><Text color=
|
|
701
|
+
<Text><Text color={theme.colors.response}> ● </Text><Text bold color={theme.colors.tool}>{msg.text}</Text></Text>
|
|
682
702
|
</Box>
|
|
683
703
|
);
|
|
684
704
|
case "tool-result":
|
|
685
|
-
return <Text key={msg.id} color=
|
|
705
|
+
return <Text key={msg.id} color={theme.colors.toolResult}> {msg.text}</Text>;
|
|
686
706
|
case "error":
|
|
687
|
-
return <Text key={msg.id} color=
|
|
707
|
+
return <Text key={msg.id} color={theme.colors.error}> {msg.text}</Text>;
|
|
688
708
|
case "info":
|
|
689
|
-
return <Text key={msg.id} color=
|
|
709
|
+
return <Text key={msg.id} color={theme.colors.muted}> {msg.text}</Text>;
|
|
690
710
|
default:
|
|
691
711
|
return <Text key={msg.id}>{msg.text}</Text>;
|
|
692
712
|
}
|
|
693
713
|
})}
|
|
694
714
|
|
|
695
715
|
{/* ═══ SPINNER ═══ */}
|
|
696
|
-
{loading && !approval && !streaming && <NeonSpinner message={spinnerMsg} />}
|
|
697
|
-
{streaming && !loading && <StreamingIndicator />}
|
|
716
|
+
{loading && !approval && !streaming && <NeonSpinner message={spinnerMsg} colors={theme.colors} />}
|
|
717
|
+
{streaming && !loading && <StreamingIndicator colors={theme.colors} />}
|
|
698
718
|
|
|
699
719
|
{/* ═══ APPROVAL PROMPT ═══ */}
|
|
700
720
|
{approval && (
|
|
701
|
-
<Box flexDirection="column" borderStyle="single" borderColor=
|
|
702
|
-
<Text bold color=
|
|
721
|
+
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.warning} paddingX={1} marginTop={1}>
|
|
722
|
+
<Text bold color={theme.colors.warning}>⚠ Approve {approval.tool}?</Text>
|
|
703
723
|
{approval.tool === "write_file" && approval.args.path ? (
|
|
704
|
-
<Text color=
|
|
724
|
+
<Text color={theme.colors.muted}>{" 📄 "}{String(approval.args.path)}</Text>
|
|
705
725
|
) : null}
|
|
706
726
|
{approval.tool === "write_file" && approval.args.content ? (
|
|
707
|
-
<Text color=
|
|
727
|
+
<Text color={theme.colors.muted}>{" "}{String(approval.args.content).split("\n").length}{" lines, "}{String(approval.args.content).length}{"B"}</Text>
|
|
708
728
|
) : null}
|
|
709
729
|
{approval.tool === "run_command" && approval.args.command ? (
|
|
710
|
-
<Text color=
|
|
730
|
+
<Text color={theme.colors.muted}>{" $ "}{String(approval.args.command)}</Text>
|
|
711
731
|
) : null}
|
|
712
732
|
<Text>
|
|
713
|
-
<Text color=
|
|
714
|
-
<Text color=
|
|
715
|
-
<Text color=
|
|
733
|
+
<Text color={theme.colors.success} bold> [y]</Text><Text>es </Text>
|
|
734
|
+
<Text color={theme.colors.error} bold>[n]</Text><Text>o </Text>
|
|
735
|
+
<Text color={theme.colors.primary} bold>[a]</Text><Text>lways</Text>
|
|
716
736
|
</Text>
|
|
717
737
|
</Box>
|
|
718
738
|
)}
|
|
719
739
|
|
|
720
740
|
{/* ═══ SESSION PICKER ═══ */}
|
|
721
741
|
{sessionPicker && (
|
|
722
|
-
<Box flexDirection="column" borderStyle="single" borderColor=
|
|
723
|
-
<Text bold color=
|
|
742
|
+
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.secondary} paddingX={1} marginBottom={0}>
|
|
743
|
+
<Text bold color={theme.colors.secondary}>Resume a session:</Text>
|
|
724
744
|
{sessionPicker.map((s, i) => (
|
|
725
745
|
<Text key={s.id}>
|
|
726
|
-
{i === sessionPickerIndex ? <Text color=
|
|
727
|
-
<Text color={i === sessionPickerIndex ?
|
|
746
|
+
{i === sessionPickerIndex ? <Text color={theme.colors.suggestion} bold>{"▸ "}</Text> : <Text>{" "}</Text>}
|
|
747
|
+
<Text color={i === sessionPickerIndex ? theme.colors.suggestion : theme.colors.muted}>{s.display}</Text>
|
|
728
748
|
</Text>
|
|
729
749
|
))}
|
|
730
750
|
<Text dimColor>{" ↑↓ navigate · Enter select · Esc cancel"}</Text>
|
|
@@ -733,12 +753,12 @@ function App() {
|
|
|
733
753
|
|
|
734
754
|
{/* ═══ COMMAND SUGGESTIONS ═══ */}
|
|
735
755
|
{showSuggestions && (
|
|
736
|
-
<Box flexDirection="column" borderStyle="single" borderColor=
|
|
756
|
+
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.muted} paddingX={1} marginBottom={0}>
|
|
737
757
|
{cmdMatches.slice(0, 6).map((c, i) => (
|
|
738
758
|
<Text key={i}>
|
|
739
|
-
{i === cmdIndex ? <Text color=
|
|
740
|
-
<Text color={i === cmdIndex ?
|
|
741
|
-
<Text color=
|
|
759
|
+
{i === cmdIndex ? <Text color={theme.colors.suggestion} bold>{"▸ "}</Text> : <Text>{" "}</Text>}
|
|
760
|
+
<Text color={i === cmdIndex ? theme.colors.suggestion : theme.colors.primary} bold>{c.cmd}</Text>
|
|
761
|
+
<Text color={theme.colors.muted}>{" — "}{c.desc}</Text>
|
|
742
762
|
</Text>
|
|
743
763
|
))}
|
|
744
764
|
<Text dimColor>{" ↑↓ navigate · Tab select"}</Text>
|
|
@@ -746,14 +766,14 @@ function App() {
|
|
|
746
766
|
)}
|
|
747
767
|
|
|
748
768
|
{/* ═══ INPUT BOX (always at bottom) ═══ */}
|
|
749
|
-
<Box borderStyle="single" borderColor={approval ?
|
|
750
|
-
<Text color=
|
|
769
|
+
<Box borderStyle="single" borderColor={approval ? theme.colors.warning : theme.colors.border} paddingX={1}>
|
|
770
|
+
<Text color={theme.colors.secondary} bold>{"> "}</Text>
|
|
751
771
|
{approval ? (
|
|
752
|
-
<Text color=
|
|
772
|
+
<Text color={theme.colors.warning}>waiting for approval...</Text>
|
|
753
773
|
) : ready && !loading ? (
|
|
754
774
|
<Box>
|
|
755
775
|
{pastedChunks.map((p) => (
|
|
756
|
-
<Text key={p.id} color=
|
|
776
|
+
<Text key={p.id} color={theme.colors.muted}>[Pasted text #{p.id} +{p.lines} lines]</Text>
|
|
757
777
|
))}
|
|
758
778
|
<TextInput
|
|
759
779
|
key={inputKey}
|
package/src/themes.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export interface Theme {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
colors: {
|
|
5
|
+
primary: string; // Main accent (borders, highlights)
|
|
6
|
+
secondary: string; // Secondary accent (banner, headings)
|
|
7
|
+
muted: string; // Dimmed text (hints, timestamps)
|
|
8
|
+
text: string; // Normal text
|
|
9
|
+
userInput: string; // User input text
|
|
10
|
+
response: string; // AI response marker
|
|
11
|
+
tool: string; // Tool call text
|
|
12
|
+
toolResult: string; // Tool result text
|
|
13
|
+
error: string; // Error messages
|
|
14
|
+
success: string; // Success messages
|
|
15
|
+
warning: string; // Warning/approval prompts
|
|
16
|
+
spinner: string; // Spinner color
|
|
17
|
+
border: string; // Border color
|
|
18
|
+
suggestion: string; // Highlighted suggestion
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const THEMES: Record<string, Theme> = {
|
|
23
|
+
neon: {
|
|
24
|
+
name: "Neon",
|
|
25
|
+
description: "Cyberpunk neon (default)",
|
|
26
|
+
colors: {
|
|
27
|
+
primary: "#00FFFF",
|
|
28
|
+
secondary: "#FF00FF",
|
|
29
|
+
muted: "#008B8B",
|
|
30
|
+
text: "",
|
|
31
|
+
userInput: "#008B8B",
|
|
32
|
+
response: "#00FFFF",
|
|
33
|
+
tool: "#FF00FF",
|
|
34
|
+
toolResult: "#008B8B",
|
|
35
|
+
error: "red",
|
|
36
|
+
success: "#00FF00",
|
|
37
|
+
warning: "#FF8C00",
|
|
38
|
+
spinner: "#00FFFF",
|
|
39
|
+
border: "#00FFFF",
|
|
40
|
+
suggestion: "#FF00FF",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
dracula: {
|
|
44
|
+
name: "Dracula",
|
|
45
|
+
description: "Dark purple tones",
|
|
46
|
+
colors: {
|
|
47
|
+
primary: "#BD93F9",
|
|
48
|
+
secondary: "#FF79C6",
|
|
49
|
+
muted: "#6272A4",
|
|
50
|
+
text: "#F8F8F2",
|
|
51
|
+
userInput: "#8BE9FD",
|
|
52
|
+
response: "#BD93F9",
|
|
53
|
+
tool: "#FF79C6",
|
|
54
|
+
toolResult: "#6272A4",
|
|
55
|
+
error: "#FF5555",
|
|
56
|
+
success: "#50FA7B",
|
|
57
|
+
warning: "#FFB86C",
|
|
58
|
+
spinner: "#BD93F9",
|
|
59
|
+
border: "#BD93F9",
|
|
60
|
+
suggestion: "#FF79C6",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
gruvbox: {
|
|
64
|
+
name: "Gruvbox",
|
|
65
|
+
description: "Warm retro tones",
|
|
66
|
+
colors: {
|
|
67
|
+
primary: "#FE8019",
|
|
68
|
+
secondary: "#FABD2F",
|
|
69
|
+
muted: "#928374",
|
|
70
|
+
text: "#EBDBB2",
|
|
71
|
+
userInput: "#83A598",
|
|
72
|
+
response: "#FE8019",
|
|
73
|
+
tool: "#FABD2F",
|
|
74
|
+
toolResult: "#928374",
|
|
75
|
+
error: "#FB4934",
|
|
76
|
+
success: "#B8BB26",
|
|
77
|
+
warning: "#FABD2F",
|
|
78
|
+
spinner: "#FE8019",
|
|
79
|
+
border: "#FE8019",
|
|
80
|
+
suggestion: "#FABD2F",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
nord: {
|
|
84
|
+
name: "Nord",
|
|
85
|
+
description: "Cool arctic blues",
|
|
86
|
+
colors: {
|
|
87
|
+
primary: "#88C0D0",
|
|
88
|
+
secondary: "#81A1C1",
|
|
89
|
+
muted: "#4C566A",
|
|
90
|
+
text: "#ECEFF4",
|
|
91
|
+
userInput: "#88C0D0",
|
|
92
|
+
response: "#81A1C1",
|
|
93
|
+
tool: "#5E81AC",
|
|
94
|
+
toolResult: "#4C566A",
|
|
95
|
+
error: "#BF616A",
|
|
96
|
+
success: "#A3BE8C",
|
|
97
|
+
warning: "#EBCB8B",
|
|
98
|
+
spinner: "#88C0D0",
|
|
99
|
+
border: "#81A1C1",
|
|
100
|
+
suggestion: "#88C0D0",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
mono: {
|
|
104
|
+
name: "Mono",
|
|
105
|
+
description: "Clean monochrome — easy on the eyes",
|
|
106
|
+
colors: {
|
|
107
|
+
primary: "#AAAAAA",
|
|
108
|
+
secondary: "#FFFFFF",
|
|
109
|
+
muted: "#666666",
|
|
110
|
+
text: "#CCCCCC",
|
|
111
|
+
userInput: "#AAAAAA",
|
|
112
|
+
response: "#FFFFFF",
|
|
113
|
+
tool: "#CCCCCC",
|
|
114
|
+
toolResult: "#666666",
|
|
115
|
+
error: "#FF6666",
|
|
116
|
+
success: "#66FF66",
|
|
117
|
+
warning: "#FFAA66",
|
|
118
|
+
spinner: "#AAAAAA",
|
|
119
|
+
border: "#888888",
|
|
120
|
+
suggestion: "#FFFFFF",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
solarized: {
|
|
124
|
+
name: "Solarized",
|
|
125
|
+
description: "Solarized dark",
|
|
126
|
+
colors: {
|
|
127
|
+
primary: "#268BD2",
|
|
128
|
+
secondary: "#2AA198",
|
|
129
|
+
muted: "#586E75",
|
|
130
|
+
text: "#839496",
|
|
131
|
+
userInput: "#2AA198",
|
|
132
|
+
response: "#268BD2",
|
|
133
|
+
tool: "#B58900",
|
|
134
|
+
toolResult: "#586E75",
|
|
135
|
+
error: "#DC322F",
|
|
136
|
+
success: "#859900",
|
|
137
|
+
warning: "#CB4B16",
|
|
138
|
+
spinner: "#268BD2",
|
|
139
|
+
border: "#268BD2",
|
|
140
|
+
suggestion: "#2AA198",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
hacker: {
|
|
144
|
+
name: "Hacker",
|
|
145
|
+
description: "Green on black — classic terminal",
|
|
146
|
+
colors: {
|
|
147
|
+
primary: "#00FF00",
|
|
148
|
+
secondary: "#00CC00",
|
|
149
|
+
muted: "#006600",
|
|
150
|
+
text: "#00DD00",
|
|
151
|
+
userInput: "#00FF00",
|
|
152
|
+
response: "#00FF00",
|
|
153
|
+
tool: "#00CC00",
|
|
154
|
+
toolResult: "#006600",
|
|
155
|
+
error: "#FF0000",
|
|
156
|
+
success: "#00FF00",
|
|
157
|
+
warning: "#FFFF00",
|
|
158
|
+
spinner: "#00FF00",
|
|
159
|
+
border: "#00FF00",
|
|
160
|
+
suggestion: "#00CC00",
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const DEFAULT_THEME = "neon";
|
|
166
|
+
|
|
167
|
+
export function getTheme(name: string): Theme {
|
|
168
|
+
return THEMES[name] ?? THEMES[DEFAULT_THEME];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function listThemes(): string[] {
|
|
172
|
+
return Object.keys(THEMES);
|
|
173
|
+
}
|