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 +5 -7
- package/dist/cli.js +3 -8
- package/dist/index.js +96 -42
- package/package.json +3 -3
- package/src/auth-cli.ts +5 -7
- package/src/cli.ts +3 -9
- package/src/index.tsx +126 -48
package/dist/auth-cli.js
CHANGED
|
@@ -259,10 +259,8 @@ Examples:
|
|
|
259
259
|
process.exit(1);
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
//
|
|
26
|
-
|
|
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 (
|
|
503
|
-
const
|
|
502
|
+
// Login method picker navigation (second level — pick auth method)
|
|
503
|
+
if (loginMethodPicker) {
|
|
504
|
+
const methods = loginMethodPicker.methods;
|
|
504
505
|
if (key.upArrow) {
|
|
505
|
-
|
|
506
|
+
setLoginMethodIndex((prev) => (prev - 1 + methods.length) % methods.length);
|
|
506
507
|
return;
|
|
507
508
|
}
|
|
508
509
|
if (key.downArrow) {
|
|
509
|
-
|
|
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
|
|
514
|
-
|
|
515
|
-
|
|
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((
|
|
521
|
-
addMsg("info", `✅ OpenRouter authenticated!
|
|
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 (
|
|
531
|
-
addMsg("info", "Starting
|
|
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("
|
|
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 (
|
|
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.
|
|
547
|
+
addMsg("info", "No Codex CLI found. Install Codex CLI and sign in first.");
|
|
551
548
|
}
|
|
552
549
|
}
|
|
553
|
-
else if (
|
|
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.
|
|
556
|
+
addMsg("info", "No Qwen CLI found. Install Qwen CLI and sign in first.");
|
|
560
557
|
}
|
|
561
558
|
}
|
|
562
|
-
else if (
|
|
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("
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
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" })] })] })),
|
|
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.
|
|
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": "
|
|
8
|
-
"codemaxxing-auth": "
|
|
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
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
//
|
|
32
|
-
|
|
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 (
|
|
549
|
-
const
|
|
548
|
+
// Login method picker navigation (second level — pick auth method)
|
|
549
|
+
if (loginMethodPicker) {
|
|
550
|
+
const methods = loginMethodPicker.methods;
|
|
550
551
|
if (key.upArrow) {
|
|
551
|
-
|
|
552
|
+
setLoginMethodIndex((prev: number) => (prev - 1 + methods.length) % methods.length);
|
|
552
553
|
return;
|
|
553
554
|
}
|
|
554
555
|
if (key.downArrow) {
|
|
555
|
-
|
|
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
|
|
560
|
-
|
|
565
|
+
const method = methods[loginMethodIndex];
|
|
566
|
+
const providerId = loginMethodPicker.provider;
|
|
567
|
+
setLoginMethodPicker(null);
|
|
561
568
|
|
|
562
|
-
if (
|
|
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((
|
|
568
|
-
addMsg("info", `✅ OpenRouter authenticated!
|
|
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
|
-
|
|
574
|
-
|
|
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
|
-
|
|
583
|
-
|
|
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
|
-
|
|
593
|
-
|
|
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
|
-
|
|
600
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
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}>
|