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 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: "#00FFFF", children: SPINNER_FRAMES[frame] }), " ", _jsx(Text, { bold: true, color: "#FF00FF", children: message }), " ", _jsxs(Text, { color: "#008B8B", children: ["[", elapsed, "s]"] })] }));
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: "#008B8B", children: STREAM_DOTS[frame] }), " ", _jsx(Text, { color: "#008B8B", children: "streaming" })] }));
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: "#00FFFF", paddingX: 1, children: [codeLines.map((line, i) => (_jsx(Text, { color: "#00FFFF", children: line }, `c${i}`))), maxxingLines.map((line, i) => (_jsx(Text, { color: i === maxxingLines.length - 1 ? "#CC00CC" : "#FF00FF", children: line }, `m${i}`))), _jsxs(Text, { children: [_jsx(Text, { color: "#008B8B", children: " v" + VERSION }), " ", _jsx(Text, { color: "#00FFFF", children: "\uD83D\uDCAA" }), " ", _jsx(Text, { dimColor: true, children: "your code. your model. no excuses." })] }), _jsxs(Text, { dimColor: true, children: [" Type ", _jsx(Text, { color: "#008B8B", children: "/help" }), " for commands · ", _jsx(Text, { color: "#008B8B", children: "Ctrl+C" }), " twice to exit"] })] }), connectionInfo.length > 0 && (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: "#008B8B", paddingX: 1, marginBottom: 1, children: connectionInfo.map((line, i) => (_jsx(Text, { color: line.startsWith("✔") ? "#00FFFF" : line.startsWith("✗") ? "red" : "#008B8B", children: line }, i))) })), messages.map((msg) => {
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: "#008B8B", children: [" > ", msg.text] }) }, msg.id));
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: "#00FFFF", children: "\u25CF " }) : _jsx(Text, { children: " " }), l.startsWith("```") ? _jsx(Text, { color: "#008B8B", children: l }) :
585
- l.startsWith("# ") || l.startsWith("## ") ? _jsx(Text, { bold: true, color: "#FF00FF", children: l }) :
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: "#00FFFF", children: " \u25CF " }), _jsx(Text, { bold: true, color: "#FF00FF", children: msg.text })] }) }, msg.id));
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: "#008B8B", children: [" ", msg.text] }, msg.id);
611
+ return _jsxs(Text, { color: theme.colors.toolResult, children: [" ", msg.text] }, msg.id);
592
612
  case "error":
593
- return _jsxs(Text, { color: "red", children: [" ", msg.text] }, msg.id);
613
+ return _jsxs(Text, { color: theme.colors.error, children: [" ", msg.text] }, msg.id);
594
614
  case "info":
595
- return _jsxs(Text, { color: "#008B8B", children: [" ", msg.text] }, msg.id);
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: "#FF8C00", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { bold: true, color: "#FF8C00", children: ["\u26A0 Approve ", approval.tool, "?"] }), approval.tool === "write_file" && approval.args.path ? (_jsxs(Text, { color: "#008B8B", children: [" 📄 ", String(approval.args.path)] })) : null, approval.tool === "write_file" && approval.args.content ? (_jsxs(Text, { color: "#008B8B", 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: "#008B8B", children: [" $ ", String(approval.args.command)] })) : null, _jsxs(Text, { children: [_jsx(Text, { color: "#00FF00", bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: "#FF0000", bold: true, children: "[n]" }), _jsx(Text, { children: "o " }), _jsx(Text, { color: "#00FFFF", bold: true, children: "[a]" }), _jsx(Text, { children: "lways" })] })] })), sessionPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "#FF00FF", paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: "#FF00FF", children: "Resume a session:" }), sessionPicker.map((s, i) => (_jsxs(Text, { children: [i === sessionPickerIndex ? _jsx(Text, { color: "#FF00FF", bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === sessionPickerIndex ? "#FF00FF" : "#008B8B", children: s.display })] }, s.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), showSuggestions && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "#008B8B", paddingX: 1, marginBottom: 0, children: [cmdMatches.slice(0, 6).map((c, i) => (_jsxs(Text, { children: [i === cmdIndex ? _jsx(Text, { color: "#FF00FF", bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === cmdIndex ? "#FF00FF" : "#00FFFF", bold: true, children: c.cmd }), _jsxs(Text, { color: "#008B8B", children: [" — ", c.desc] })] }, i))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Tab select" })] })), _jsxs(Box, { borderStyle: "single", borderColor: approval ? "#FF8C00" : "#00FFFF", paddingX: 1, children: [_jsx(Text, { color: "#FF00FF", bold: true, children: "> " }), approval ? (_jsx(Text, { color: "#FF8C00", children: "waiting for approval..." })) : ready && !loading ? (_jsxs(Box, { children: [pastedChunks.map((p) => (_jsxs(Text, { color: "#008B8B", 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 · ~", (() => {
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}` : ""] }) }))] }));
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemaxxing",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Open-source terminal coding agent. Connect any LLM. Max your code.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
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="#00FFFF">{SPINNER_FRAMES[frame]}</Text>
73
- {" "}<Text bold color="#FF00FF">{message}</Text>
74
- {" "}<Text color="#008B8B">[{elapsed}s]</Text>
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="#008B8B">{STREAM_DOTS[frame]}</Text>
94
- {" "}<Text color="#008B8B">streaming</Text>
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="#00FFFF" paddingX={1}>
651
+ <Box flexDirection="column" borderStyle="round" borderColor={theme.colors.border} paddingX={1}>
632
652
  {codeLines.map((line, i) => (
633
- <Text key={`c${i}`} color="#00FFFF">{line}</Text>
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={i === maxxingLines.length - 1 ? "#CC00CC" : "#FF00FF"}>{line}</Text>
656
+ <Text key={`m${i}`} color={theme.colors.secondary}>{line}</Text>
637
657
  ))}
638
658
  <Text>
639
- <Text color="#008B8B">{" v" + VERSION}</Text>
640
- {" "}<Text color="#00FFFF">💪</Text>
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="#008B8B">/help</Text>{" for commands · "}<Text color="#008B8B">Ctrl+C</Text>{" twice to exit"}</Text>
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="#008B8B" paddingX={1} marginBottom={1}>
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("✔") ? "#00FFFF" : line.startsWith("✗") ? "red" : "#008B8B"}>{line}</Text>
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="#008B8B">{" > "}{msg.text}</Text>
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="#00FFFF">● </Text> : <Text> </Text>}
670
- {l.startsWith("```") ? <Text color="#008B8B">{l}</Text> :
671
- l.startsWith("# ") || l.startsWith("## ") ? <Text bold color="#FF00FF">{l}</Text> :
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="#00FFFF"> ● </Text><Text bold color="#FF00FF">{msg.text}</Text></Text>
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="#008B8B"> {msg.text}</Text>;
705
+ return <Text key={msg.id} color={theme.colors.toolResult}> {msg.text}</Text>;
686
706
  case "error":
687
- return <Text key={msg.id} color="red"> {msg.text}</Text>;
707
+ return <Text key={msg.id} color={theme.colors.error}> {msg.text}</Text>;
688
708
  case "info":
689
- return <Text key={msg.id} color="#008B8B"> {msg.text}</Text>;
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="#FF8C00" paddingX={1} marginTop={1}>
702
- <Text bold color="#FF8C00">⚠ Approve {approval.tool}?</Text>
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="#008B8B">{" 📄 "}{String(approval.args.path)}</Text>
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="#008B8B">{" "}{String(approval.args.content).split("\n").length}{" lines, "}{String(approval.args.content).length}{"B"}</Text>
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="#008B8B">{" $ "}{String(approval.args.command)}</Text>
730
+ <Text color={theme.colors.muted}>{" $ "}{String(approval.args.command)}</Text>
711
731
  ) : null}
712
732
  <Text>
713
- <Text color="#00FF00" bold> [y]</Text><Text>es </Text>
714
- <Text color="#FF0000" bold>[n]</Text><Text>o </Text>
715
- <Text color="#00FFFF" bold>[a]</Text><Text>lways</Text>
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="#FF00FF" paddingX={1} marginBottom={0}>
723
- <Text bold color="#FF00FF">Resume a session:</Text>
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="#FF00FF" bold>{"▸ "}</Text> : <Text>{" "}</Text>}
727
- <Text color={i === sessionPickerIndex ? "#FF00FF" : "#008B8B"}>{s.display}</Text>
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="#008B8B" paddingX={1} marginBottom={0}>
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="#FF00FF" bold>{"▸ "}</Text> : <Text>{" "}</Text>}
740
- <Text color={i === cmdIndex ? "#FF00FF" : "#00FFFF"} bold>{c.cmd}</Text>
741
- <Text color="#008B8B">{" — "}{c.desc}</Text>
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 ? "#FF8C00" : "#00FFFF"} paddingX={1}>
750
- <Text color="#FF00FF" bold>{"> "}</Text>
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="#FF8C00">waiting for approval...</Text>
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="#008B8B">[Pasted text #{p.id} +{p.lines} lines]</Text>
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
+ }