codemaxxing 0.1.5 → 0.1.8

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/dist/auth-cli.js CHANGED
@@ -259,10 +259,8 @@ Examples:
259
259
  process.exit(1);
260
260
  }
261
261
  }
262
- // Run main if this is the main module
263
- if (typeof require !== "undefined" && require.main === module) {
264
- main().catch((err) => {
265
- console.error(`Error: ${err.message}`);
266
- process.exit(1);
267
- });
268
- }
262
+ // Always run main this module is either imported and main() called, or run directly
263
+ main().catch((err) => {
264
+ console.error(`Error: ${err.message}`);
265
+ process.exit(1);
266
+ });
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  const subcmd = process.argv[2];
12
12
  if (subcmd === "login" || subcmd === "auth") {
13
- // Route to auth CLI
13
+ // Route to auth CLI (spawn is fine here — no TUI/raw mode needed)
14
14
  const authScript = join(__dirname, "auth-cli.js");
15
15
  const args = subcmd === "login"
16
16
  ? [authScript, "login", ...process.argv.slice(3)]
@@ -22,11 +22,6 @@ if (subcmd === "login" || subcmd === "auth") {
22
22
  child.on("exit", (code) => process.exit(code ?? 0));
23
23
  }
24
24
  else {
25
- // Route to TUI
26
- const tuiScript = join(__dirname, "index.js");
27
- const child = spawn(process.execPath, [tuiScript, ...process.argv.slice(2)], {
28
- stdio: "inherit",
29
- cwd: process.cwd(),
30
- });
31
- child.on("exit", (code) => process.exit(code ?? 0));
25
+ // TUI mode — import directly (not spawn) to preserve raw stdin
26
+ await import("./index.js");
32
27
  }
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ 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
12
  import { getTheme, listThemes, THEMES, DEFAULT_THEME } from "./themes.js";
13
- import { PROVIDERS, openRouterOAuth, anthropicSetupToken, importCodexToken, importQwenToken, copilotDeviceFlow } from "./utils/auth.js";
13
+ import { PROVIDERS, getCredentials, openRouterOAuth, anthropicSetupToken, importCodexToken, importQwenToken, copilotDeviceFlow } from "./utils/auth.js";
14
14
  const VERSION = "0.1.5";
15
15
  // ── Helpers ──
16
16
  function formatTimeAgo(date) {
@@ -30,7 +30,6 @@ function formatTimeAgo(date) {
30
30
  const SLASH_COMMANDS = [
31
31
  { cmd: "/help", desc: "show commands" },
32
32
  { cmd: "/login", desc: "set up authentication" },
33
- { cmd: "/login", desc: "set up authentication" },
34
33
  { cmd: "/map", desc: "show repository map" },
35
34
  { cmd: "/reset", desc: "clear conversation" },
36
35
  { cmd: "/context", desc: "show message count" },
@@ -109,6 +108,8 @@ function App() {
109
108
  const [themePickerIndex, setThemePickerIndex] = useState(0);
110
109
  const [loginPicker, setLoginPicker] = useState(false);
111
110
  const [loginPickerIndex, setLoginPickerIndex] = useState(0);
111
+ const [loginMethodPicker, setLoginMethodPicker] = useState(null);
112
+ const [loginMethodIndex, setLoginMethodIndex] = useState(0);
112
113
  const [approval, setApproval] = useState(null);
113
114
  // Listen for paste events from stdin interceptor
114
115
  useEffect(() => {
@@ -498,83 +499,127 @@ function App() {
498
499
  return;
499
500
  }
500
501
  }
501
- // Login picker navigation
502
- if (loginPicker) {
503
- const loginProviders = PROVIDERS.filter((p) => p.id !== "local");
502
+ // Login method picker navigation (second level — pick auth method)
503
+ if (loginMethodPicker) {
504
+ const methods = loginMethodPicker.methods;
504
505
  if (key.upArrow) {
505
- setLoginPickerIndex((prev) => (prev - 1 + loginProviders.length) % loginProviders.length);
506
+ setLoginMethodIndex((prev) => (prev - 1 + methods.length) % methods.length);
506
507
  return;
507
508
  }
508
509
  if (key.downArrow) {
509
- setLoginPickerIndex((prev) => (prev + 1) % loginProviders.length);
510
+ setLoginMethodIndex((prev) => (prev + 1) % methods.length);
511
+ return;
512
+ }
513
+ if (key.escape) {
514
+ setLoginMethodPicker(null);
515
+ setLoginPicker(true); // go back to provider picker
510
516
  return;
511
517
  }
512
518
  if (key.return) {
513
- const selected = loginProviders[loginPickerIndex];
514
- setLoginPicker(false);
515
- if (selected.id === "openrouter") {
519
+ const method = methods[loginMethodIndex];
520
+ const providerId = loginMethodPicker.provider;
521
+ setLoginMethodPicker(null);
522
+ if (method === "oauth" && providerId === "openrouter") {
516
523
  addMsg("info", "Starting OpenRouter OAuth — opening browser...");
517
524
  setLoading(true);
518
525
  setSpinnerMsg("Waiting for authorization...");
519
526
  openRouterOAuth((msg) => addMsg("info", msg))
520
- .then((cred) => {
521
- addMsg("info", `✅ OpenRouter authenticated! You now have access to 200+ models.`);
522
- addMsg("info", `Switch with: /model anthropic/claude-sonnet-4`);
527
+ .then(() => {
528
+ addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`);
523
529
  setLoading(false);
524
530
  })
525
- .catch((err) => {
526
- addMsg("error", `OAuth failed: ${err.message}`);
527
- setLoading(false);
528
- });
531
+ .catch((err) => { addMsg("error", `OAuth failed: ${err.message}`); setLoading(false); });
529
532
  }
530
- else if (selected.id === "anthropic") {
531
- addMsg("info", "Starting Anthropic setup-token flow...");
533
+ else if (method === "setup-token") {
534
+ addMsg("info", "Starting setup-token flow — browser will open...");
532
535
  setLoading(true);
533
536
  setSpinnerMsg("Waiting for Claude Code auth...");
534
537
  anthropicSetupToken((msg) => addMsg("info", msg))
535
- .then((cred) => {
536
- addMsg("info", `✅ Anthropic authenticated! (${cred.label})`);
537
- setLoading(false);
538
- })
539
- .catch((err) => {
540
- addMsg("error", `Anthropic auth failed: ${err.message}`);
541
- setLoading(false);
542
- });
538
+ .then((cred) => { addMsg("info", `✅ Anthropic authenticated! (${cred.label})`); setLoading(false); })
539
+ .catch((err) => { addMsg("error", `Auth failed: ${err.message}`); setLoading(false); });
543
540
  }
544
- else if (selected.id === "openai") {
541
+ else if (method === "cached-token" && providerId === "openai") {
545
542
  const imported = importCodexToken((msg) => addMsg("info", msg));
546
543
  if (imported) {
547
544
  addMsg("info", `✅ Imported Codex credentials! (${imported.label})`);
548
545
  }
549
546
  else {
550
- addMsg("info", "No Codex CLI found. Run: codemaxxing auth api-key openai <your-key>");
547
+ addMsg("info", "No Codex CLI found. Install Codex CLI and sign in first.");
551
548
  }
552
549
  }
553
- else if (selected.id === "qwen") {
550
+ else if (method === "cached-token" && providerId === "qwen") {
554
551
  const imported = importQwenToken((msg) => addMsg("info", msg));
555
552
  if (imported) {
556
553
  addMsg("info", `✅ Imported Qwen credentials! (${imported.label})`);
557
554
  }
558
555
  else {
559
- addMsg("info", "No Qwen CLI found. Run: codemaxxing auth api-key qwen <your-key>");
556
+ addMsg("info", "No Qwen CLI found. Install Qwen CLI and sign in first.");
560
557
  }
561
558
  }
562
- else if (selected.id === "copilot") {
559
+ else if (method === "device-flow") {
563
560
  addMsg("info", "Starting GitHub Copilot device flow...");
564
561
  setLoading(true);
565
562
  setSpinnerMsg("Waiting for GitHub authorization...");
566
563
  copilotDeviceFlow((msg) => addMsg("info", msg))
567
- .then(() => {
568
- addMsg("info", `✅ GitHub Copilot authenticated!`);
569
- setLoading(false);
570
- })
571
- .catch((err) => {
572
- addMsg("error", `Copilot auth failed: ${err.message}`);
573
- setLoading(false);
574
- });
564
+ .then(() => { addMsg("info", `✅ GitHub Copilot authenticated!`); setLoading(false); })
565
+ .catch((err) => { addMsg("error", `Copilot auth failed: ${err.message}`); setLoading(false); });
566
+ }
567
+ else if (method === "api-key") {
568
+ const provider = PROVIDERS.find((p) => p.id === providerId);
569
+ addMsg("info", `Enter your API key via CLI:\n codemaxxing auth api-key ${providerId} <your-key>\n Get key at: ${provider?.consoleUrl ?? "your provider's dashboard"}`);
570
+ }
571
+ return;
572
+ }
573
+ return;
574
+ }
575
+ // Login picker navigation (first level — pick provider)
576
+ if (loginPicker) {
577
+ const loginProviders = PROVIDERS.filter((p) => p.id !== "local");
578
+ if (key.upArrow) {
579
+ setLoginPickerIndex((prev) => (prev - 1 + loginProviders.length) % loginProviders.length);
580
+ return;
581
+ }
582
+ if (key.downArrow) {
583
+ setLoginPickerIndex((prev) => (prev + 1) % loginProviders.length);
584
+ return;
585
+ }
586
+ if (key.return) {
587
+ const selected = loginProviders[loginPickerIndex];
588
+ setLoginPicker(false);
589
+ // Get available methods for this provider (filter out 'none')
590
+ const methods = selected.methods.filter((m) => m !== "none");
591
+ if (methods.length === 1) {
592
+ // Only one method — execute it directly
593
+ setLoginMethodPicker({ provider: selected.id, methods });
594
+ setLoginMethodIndex(0);
595
+ // Simulate Enter press on the single method
596
+ if (methods[0] === "oauth" && selected.id === "openrouter") {
597
+ setLoginMethodPicker(null);
598
+ addMsg("info", "Starting OpenRouter OAuth — opening browser...");
599
+ setLoading(true);
600
+ setSpinnerMsg("Waiting for authorization...");
601
+ openRouterOAuth((msg) => addMsg("info", msg))
602
+ .then(() => { addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`); setLoading(false); })
603
+ .catch((err) => { addMsg("error", `OAuth failed: ${err.message}`); setLoading(false); });
604
+ }
605
+ else if (methods[0] === "device-flow") {
606
+ setLoginMethodPicker(null);
607
+ addMsg("info", "Starting GitHub Copilot device flow...");
608
+ setLoading(true);
609
+ setSpinnerMsg("Waiting for GitHub authorization...");
610
+ copilotDeviceFlow((msg) => addMsg("info", msg))
611
+ .then(() => { addMsg("info", `✅ GitHub Copilot authenticated!`); setLoading(false); })
612
+ .catch((err) => { addMsg("error", `Copilot auth failed: ${err.message}`); setLoading(false); });
613
+ }
614
+ else if (methods[0] === "api-key") {
615
+ setLoginMethodPicker(null);
616
+ addMsg("info", `Enter your API key via CLI:\n codemaxxing auth api-key ${selected.id} <your-key>\n Get key at: ${selected.consoleUrl ?? "your provider's dashboard"}`);
617
+ }
575
618
  }
576
619
  else {
577
- addMsg("info", `Run: codemaxxing auth api-key ${selected.id} <your-key>\n Get key at: ${selected.consoleUrl ?? selected.baseUrl}`);
620
+ // Multiple methods show submenu
621
+ setLoginMethodPicker({ provider: selected.id, methods });
622
+ setLoginMethodIndex(0);
578
623
  }
579
624
  return;
580
625
  }
@@ -739,7 +784,16 @@ function App() {
739
784
  default:
740
785
  return _jsx(Text, { children: msg.text }, msg.id);
741
786
  }
742
- }), 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" })] })] })), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), 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 · ~", (() => {
787
+ }), 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" })] })] })), loginPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "\uD83D\uDCAA Choose a provider:" }), PROVIDERS.filter((p) => p.id !== "local").map((p, i) => (_jsxs(Text, { children: [i === loginPickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === loginPickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: p.name }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", p.description] }), getCredentials().some((c) => c.provider === p.id) ? _jsx(Text, { color: theme.colors.success, children: " \u2713" }) : null] }, p.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), loginMethodPicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "How do you want to authenticate?" }), loginMethodPicker.methods.map((method, i) => {
788
+ const labels = {
789
+ "oauth": "🌐 Browser login (OAuth)",
790
+ "setup-token": "🔑 Link subscription (via Claude Code CLI)",
791
+ "cached-token": "📦 Import from existing CLI",
792
+ "api-key": "🔒 Enter API key manually",
793
+ "device-flow": "📱 Device flow (GitHub)",
794
+ };
795
+ return (_jsxs(Text, { children: [i === loginMethodIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === loginMethodIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: labels[method] ?? method })] }, method));
796
+ }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc back" })] })), themePicker && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Choose a theme:" }), listThemes().map((key, i) => (_jsxs(Text, { children: [i === themePickerIndex ? _jsx(Text, { color: theme.colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === themePickerIndex ? theme.colors.suggestion : theme.colors.primary, bold: true, children: key }), _jsxs(Text, { color: theme.colors.muted, children: [" — ", THEMES[key].description] }), key === theme.name.toLowerCase() ? _jsx(Text, { color: theme.colors.muted, children: " (current)" }) : null] }, key))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] })), 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 · ~", (() => {
743
797
  const tokens = agent.estimateTokens();
744
798
  return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
745
799
  })(), " tokens", modelName ? ` · 🤖 ${modelName}` : ""] }) }))] }));
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "codemaxxing",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "Open-source terminal coding agent. Connect any LLM. Max your code.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "codemaxxing": "./dist/cli.js",
8
- "codemaxxing-auth": "./dist/auth-cli.js"
7
+ "codemaxxing": "dist/cli.js",
8
+ "codemaxxing-auth": "dist/auth-cli.js"
9
9
  },
10
10
  "type": "module",
11
11
  "scripts": {
package/src/auth-cli.ts CHANGED
@@ -280,10 +280,8 @@ Examples:
280
280
  }
281
281
  }
282
282
 
283
- // Run main if this is the main module
284
- if (typeof require !== "undefined" && require.main === module) {
285
- main().catch((err) => {
286
- console.error(`Error: ${err.message}`);
287
- process.exit(1);
288
- });
289
- }
283
+ // Always run main this module is either imported and main() called, or run directly
284
+ main().catch((err) => {
285
+ console.error(`Error: ${err.message}`);
286
+ process.exit(1);
287
+ });
package/src/cli.ts CHANGED
@@ -15,7 +15,7 @@ const __dirname = dirname(__filename);
15
15
  const subcmd = process.argv[2];
16
16
 
17
17
  if (subcmd === "login" || subcmd === "auth") {
18
- // Route to auth CLI
18
+ // Route to auth CLI (spawn is fine here — no TUI/raw mode needed)
19
19
  const authScript = join(__dirname, "auth-cli.js");
20
20
  const args = subcmd === "login"
21
21
  ? [authScript, "login", ...process.argv.slice(3)]
@@ -28,12 +28,6 @@ if (subcmd === "login" || subcmd === "auth") {
28
28
 
29
29
  child.on("exit", (code) => process.exit(code ?? 0));
30
30
  } else {
31
- // Route to TUI
32
- const tuiScript = join(__dirname, "index.js");
33
- const child = spawn(process.execPath, [tuiScript, ...process.argv.slice(2)], {
34
- stdio: "inherit",
35
- cwd: process.cwd(),
36
- });
37
-
38
- child.on("exit", (code) => process.exit(code ?? 0));
31
+ // TUI mode — import directly (not spawn) to preserve raw stdin
32
+ await import("./index.js");
39
33
  }
package/src/index.tsx CHANGED
@@ -30,7 +30,6 @@ function formatTimeAgo(date: Date): string {
30
30
  const SLASH_COMMANDS = [
31
31
  { cmd: "/help", desc: "show commands" },
32
32
  { cmd: "/login", desc: "set up authentication" },
33
- { cmd: "/login", desc: "set up authentication" },
34
33
  { cmd: "/map", desc: "show repository map" },
35
34
  { cmd: "/reset", desc: "clear conversation" },
36
35
  { cmd: "/context", desc: "show message count" },
@@ -138,6 +137,8 @@ function App() {
138
137
  const [themePickerIndex, setThemePickerIndex] = useState(0);
139
138
  const [loginPicker, setLoginPicker] = useState(false);
140
139
  const [loginPickerIndex, setLoginPickerIndex] = useState(0);
140
+ const [loginMethodPicker, setLoginMethodPicker] = useState<{ provider: string; methods: string[] } | null>(null);
141
+ const [loginMethodIndex, setLoginMethodIndex] = useState(0);
141
142
  const [approval, setApproval] = useState<{
142
143
  tool: string;
143
144
  args: Record<string, unknown>;
@@ -544,77 +545,115 @@ function App() {
544
545
  }
545
546
  }
546
547
 
547
- // Login picker navigation
548
- if (loginPicker) {
549
- const loginProviders = PROVIDERS.filter((p) => p.id !== "local");
548
+ // Login method picker navigation (second level — pick auth method)
549
+ if (loginMethodPicker) {
550
+ const methods = loginMethodPicker.methods;
550
551
  if (key.upArrow) {
551
- setLoginPickerIndex((prev: number) => (prev - 1 + loginProviders.length) % loginProviders.length);
552
+ setLoginMethodIndex((prev: number) => (prev - 1 + methods.length) % methods.length);
552
553
  return;
553
554
  }
554
555
  if (key.downArrow) {
555
- setLoginPickerIndex((prev: number) => (prev + 1) % loginProviders.length);
556
+ setLoginMethodIndex((prev: number) => (prev + 1) % methods.length);
557
+ return;
558
+ }
559
+ if (key.escape) {
560
+ setLoginMethodPicker(null);
561
+ setLoginPicker(true); // go back to provider picker
556
562
  return;
557
563
  }
558
564
  if (key.return) {
559
- const selected = loginProviders[loginPickerIndex];
560
- setLoginPicker(false);
565
+ const method = methods[loginMethodIndex];
566
+ const providerId = loginMethodPicker.provider;
567
+ setLoginMethodPicker(null);
561
568
 
562
- if (selected.id === "openrouter") {
569
+ if (method === "oauth" && providerId === "openrouter") {
563
570
  addMsg("info", "Starting OpenRouter OAuth — opening browser...");
564
571
  setLoading(true);
565
572
  setSpinnerMsg("Waiting for authorization...");
566
573
  openRouterOAuth((msg: string) => addMsg("info", msg))
567
- .then((cred) => {
568
- addMsg("info", `✅ OpenRouter authenticated! You now have access to 200+ models.`);
569
- addMsg("info", `Switch with: /model anthropic/claude-sonnet-4`);
574
+ .then(() => {
575
+ addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`);
570
576
  setLoading(false);
571
577
  })
572
- .catch((err: any) => {
573
- addMsg("error", `OAuth failed: ${err.message}`);
574
- setLoading(false);
575
- });
576
- } else if (selected.id === "anthropic") {
577
- addMsg("info", "Starting Anthropic setup-token flow...");
578
+ .catch((err: any) => { addMsg("error", `OAuth failed: ${err.message}`); setLoading(false); });
579
+ } else if (method === "setup-token") {
580
+ addMsg("info", "Starting setup-token flow — browser will open...");
578
581
  setLoading(true);
579
582
  setSpinnerMsg("Waiting for Claude Code auth...");
580
583
  anthropicSetupToken((msg: string) => addMsg("info", msg))
581
- .then((cred) => {
582
- addMsg("info", `✅ Anthropic authenticated! (${cred.label})`);
583
- setLoading(false);
584
- })
585
- .catch((err: any) => {
586
- addMsg("error", `Anthropic auth failed: ${err.message}`);
587
- setLoading(false);
588
- });
589
- } else if (selected.id === "openai") {
584
+ .then((cred) => { addMsg("info", `✅ Anthropic authenticated! (${cred.label})`); setLoading(false); })
585
+ .catch((err: any) => { addMsg("error", `Auth failed: ${err.message}`); setLoading(false); });
586
+ } else if (method === "cached-token" && providerId === "openai") {
590
587
  const imported = importCodexToken((msg: string) => addMsg("info", msg));
591
- if (imported) {
592
- addMsg("info", `✅ Imported Codex credentials! (${imported.label})`);
593
- } else {
594
- addMsg("info", "No Codex CLI found. Run: codemaxxing auth api-key openai <your-key>");
595
- }
596
- } else if (selected.id === "qwen") {
588
+ if (imported) { addMsg("info", `✅ Imported Codex credentials! (${imported.label})`); }
589
+ else { addMsg("info", "No Codex CLI found. Install Codex CLI and sign in first."); }
590
+ } else if (method === "cached-token" && providerId === "qwen") {
597
591
  const imported = importQwenToken((msg: string) => addMsg("info", msg));
598
- if (imported) {
599
- addMsg("info", `✅ Imported Qwen credentials! (${imported.label})`);
600
- } else {
601
- addMsg("info", "No Qwen CLI found. Run: codemaxxing auth api-key qwen <your-key>");
602
- }
603
- } else if (selected.id === "copilot") {
592
+ if (imported) { addMsg("info", `✅ Imported Qwen credentials! (${imported.label})`); }
593
+ else { addMsg("info", "No Qwen CLI found. Install Qwen CLI and sign in first."); }
594
+ } else if (method === "device-flow") {
604
595
  addMsg("info", "Starting GitHub Copilot device flow...");
605
596
  setLoading(true);
606
597
  setSpinnerMsg("Waiting for GitHub authorization...");
607
598
  copilotDeviceFlow((msg: string) => addMsg("info", msg))
608
- .then(() => {
609
- addMsg("info", `✅ GitHub Copilot authenticated!`);
610
- setLoading(false);
611
- })
612
- .catch((err: any) => {
613
- addMsg("error", `Copilot auth failed: ${err.message}`);
614
- setLoading(false);
615
- });
599
+ .then(() => { addMsg("info", `✅ GitHub Copilot authenticated!`); setLoading(false); })
600
+ .catch((err: any) => { addMsg("error", `Copilot auth failed: ${err.message}`); setLoading(false); });
601
+ } else if (method === "api-key") {
602
+ const provider = PROVIDERS.find((p) => p.id === providerId);
603
+ addMsg("info", `Enter your API key via CLI:\n codemaxxing auth api-key ${providerId} <your-key>\n Get key at: ${provider?.consoleUrl ?? "your provider's dashboard"}`);
604
+ }
605
+ return;
606
+ }
607
+ return;
608
+ }
609
+
610
+ // Login picker navigation (first level — pick provider)
611
+ if (loginPicker) {
612
+ const loginProviders = PROVIDERS.filter((p) => p.id !== "local");
613
+ if (key.upArrow) {
614
+ setLoginPickerIndex((prev: number) => (prev - 1 + loginProviders.length) % loginProviders.length);
615
+ return;
616
+ }
617
+ if (key.downArrow) {
618
+ setLoginPickerIndex((prev: number) => (prev + 1) % loginProviders.length);
619
+ return;
620
+ }
621
+ if (key.return) {
622
+ const selected = loginProviders[loginPickerIndex];
623
+ setLoginPicker(false);
624
+
625
+ // Get available methods for this provider (filter out 'none')
626
+ const methods = selected.methods.filter((m) => m !== "none");
627
+
628
+ if (methods.length === 1) {
629
+ // Only one method — execute it directly
630
+ setLoginMethodPicker({ provider: selected.id, methods });
631
+ setLoginMethodIndex(0);
632
+ // Simulate Enter press on the single method
633
+ if (methods[0] === "oauth" && selected.id === "openrouter") {
634
+ setLoginMethodPicker(null);
635
+ addMsg("info", "Starting OpenRouter OAuth — opening browser...");
636
+ setLoading(true);
637
+ setSpinnerMsg("Waiting for authorization...");
638
+ openRouterOAuth((msg: string) => addMsg("info", msg))
639
+ .then(() => { addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`); setLoading(false); })
640
+ .catch((err: any) => { addMsg("error", `OAuth failed: ${err.message}`); setLoading(false); });
641
+ } else if (methods[0] === "device-flow") {
642
+ setLoginMethodPicker(null);
643
+ addMsg("info", "Starting GitHub Copilot device flow...");
644
+ setLoading(true);
645
+ setSpinnerMsg("Waiting for GitHub authorization...");
646
+ copilotDeviceFlow((msg: string) => addMsg("info", msg))
647
+ .then(() => { addMsg("info", `✅ GitHub Copilot authenticated!`); setLoading(false); })
648
+ .catch((err: any) => { addMsg("error", `Copilot auth failed: ${err.message}`); setLoading(false); });
649
+ } else if (methods[0] === "api-key") {
650
+ setLoginMethodPicker(null);
651
+ addMsg("info", `Enter your API key via CLI:\n codemaxxing auth api-key ${selected.id} <your-key>\n Get key at: ${selected.consoleUrl ?? "your provider's dashboard"}`);
652
+ }
616
653
  } else {
617
- addMsg("info", `Run: codemaxxing auth api-key ${selected.id} <your-key>\n Get key at: ${selected.consoleUrl ?? selected.baseUrl}`);
654
+ // Multiple methods show submenu
655
+ setLoginMethodPicker({ provider: selected.id, methods });
656
+ setLoginMethodIndex(0);
618
657
  }
619
658
  return;
620
659
  }
@@ -856,6 +895,45 @@ function App() {
856
895
  </Box>
857
896
  )}
858
897
 
898
+ {/* ═══ LOGIN PICKER ═══ */}
899
+ {loginPicker && (
900
+ <Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
901
+ <Text bold color={theme.colors.secondary}>💪 Choose a provider:</Text>
902
+ {PROVIDERS.filter((p) => p.id !== "local").map((p, i) => (
903
+ <Text key={p.id}>
904
+ {i === loginPickerIndex ? <Text color={theme.colors.suggestion} bold>{"▸ "}</Text> : <Text>{" "}</Text>}
905
+ <Text color={i === loginPickerIndex ? theme.colors.suggestion : theme.colors.primary} bold>{p.name}</Text>
906
+ <Text color={theme.colors.muted}>{" — "}{p.description}</Text>
907
+ {getCredentials().some((c) => c.provider === p.id) ? <Text color={theme.colors.success}> ✓</Text> : null}
908
+ </Text>
909
+ ))}
910
+ <Text dimColor>{" ↑↓ navigate · Enter select · Esc cancel"}</Text>
911
+ </Box>
912
+ )}
913
+
914
+ {/* ═══ LOGIN METHOD PICKER ═══ */}
915
+ {loginMethodPicker && (
916
+ <Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
917
+ <Text bold color={theme.colors.secondary}>How do you want to authenticate?</Text>
918
+ {loginMethodPicker.methods.map((method, i) => {
919
+ const labels: Record<string, string> = {
920
+ "oauth": "🌐 Browser login (OAuth)",
921
+ "setup-token": "🔑 Link subscription (via Claude Code CLI)",
922
+ "cached-token": "📦 Import from existing CLI",
923
+ "api-key": "🔒 Enter API key manually",
924
+ "device-flow": "📱 Device flow (GitHub)",
925
+ };
926
+ return (
927
+ <Text key={method}>
928
+ {i === loginMethodIndex ? <Text color={theme.colors.suggestion} bold>{"▸ "}</Text> : <Text>{" "}</Text>}
929
+ <Text color={i === loginMethodIndex ? theme.colors.suggestion : theme.colors.primary} bold>{labels[method] ?? method}</Text>
930
+ </Text>
931
+ );
932
+ })}
933
+ <Text dimColor>{" ↑↓ navigate · Enter select · Esc back"}</Text>
934
+ </Box>
935
+ )}
936
+
859
937
  {/* ═══ THEME PICKER ═══ */}
860
938
  {themePicker && (
861
939
  <Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>