codemaxxing 0.1.7 → 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/index.js +96 -41
- package/package.json +1 -1
- package/src/index.tsx +126 -47
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) {
|
|
@@ -108,6 +108,8 @@ function App() {
|
|
|
108
108
|
const [themePickerIndex, setThemePickerIndex] = useState(0);
|
|
109
109
|
const [loginPicker, setLoginPicker] = useState(false);
|
|
110
110
|
const [loginPickerIndex, setLoginPickerIndex] = useState(0);
|
|
111
|
+
const [loginMethodPicker, setLoginMethodPicker] = useState(null);
|
|
112
|
+
const [loginMethodIndex, setLoginMethodIndex] = useState(0);
|
|
111
113
|
const [approval, setApproval] = useState(null);
|
|
112
114
|
// Listen for paste events from stdin interceptor
|
|
113
115
|
useEffect(() => {
|
|
@@ -497,83 +499,127 @@ function App() {
|
|
|
497
499
|
return;
|
|
498
500
|
}
|
|
499
501
|
}
|
|
500
|
-
// Login picker navigation
|
|
501
|
-
if (
|
|
502
|
-
const
|
|
502
|
+
// Login method picker navigation (second level — pick auth method)
|
|
503
|
+
if (loginMethodPicker) {
|
|
504
|
+
const methods = loginMethodPicker.methods;
|
|
503
505
|
if (key.upArrow) {
|
|
504
|
-
|
|
506
|
+
setLoginMethodIndex((prev) => (prev - 1 + methods.length) % methods.length);
|
|
505
507
|
return;
|
|
506
508
|
}
|
|
507
509
|
if (key.downArrow) {
|
|
508
|
-
|
|
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
|
|
509
516
|
return;
|
|
510
517
|
}
|
|
511
518
|
if (key.return) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
519
|
+
const method = methods[loginMethodIndex];
|
|
520
|
+
const providerId = loginMethodPicker.provider;
|
|
521
|
+
setLoginMethodPicker(null);
|
|
522
|
+
if (method === "oauth" && providerId === "openrouter") {
|
|
515
523
|
addMsg("info", "Starting OpenRouter OAuth — opening browser...");
|
|
516
524
|
setLoading(true);
|
|
517
525
|
setSpinnerMsg("Waiting for authorization...");
|
|
518
526
|
openRouterOAuth((msg) => addMsg("info", msg))
|
|
519
|
-
.then((
|
|
520
|
-
addMsg("info", `✅ OpenRouter authenticated!
|
|
521
|
-
addMsg("info", `Switch with: /model anthropic/claude-sonnet-4`);
|
|
527
|
+
.then(() => {
|
|
528
|
+
addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`);
|
|
522
529
|
setLoading(false);
|
|
523
530
|
})
|
|
524
|
-
.catch((err) => {
|
|
525
|
-
addMsg("error", `OAuth failed: ${err.message}`);
|
|
526
|
-
setLoading(false);
|
|
527
|
-
});
|
|
531
|
+
.catch((err) => { addMsg("error", `OAuth failed: ${err.message}`); setLoading(false); });
|
|
528
532
|
}
|
|
529
|
-
else if (
|
|
530
|
-
addMsg("info", "Starting
|
|
533
|
+
else if (method === "setup-token") {
|
|
534
|
+
addMsg("info", "Starting setup-token flow — browser will open...");
|
|
531
535
|
setLoading(true);
|
|
532
536
|
setSpinnerMsg("Waiting for Claude Code auth...");
|
|
533
537
|
anthropicSetupToken((msg) => addMsg("info", msg))
|
|
534
|
-
.then((cred) => {
|
|
535
|
-
addMsg("
|
|
536
|
-
setLoading(false);
|
|
537
|
-
})
|
|
538
|
-
.catch((err) => {
|
|
539
|
-
addMsg("error", `Anthropic auth failed: ${err.message}`);
|
|
540
|
-
setLoading(false);
|
|
541
|
-
});
|
|
538
|
+
.then((cred) => { addMsg("info", `✅ Anthropic authenticated! (${cred.label})`); setLoading(false); })
|
|
539
|
+
.catch((err) => { addMsg("error", `Auth failed: ${err.message}`); setLoading(false); });
|
|
542
540
|
}
|
|
543
|
-
else if (
|
|
541
|
+
else if (method === "cached-token" && providerId === "openai") {
|
|
544
542
|
const imported = importCodexToken((msg) => addMsg("info", msg));
|
|
545
543
|
if (imported) {
|
|
546
544
|
addMsg("info", `✅ Imported Codex credentials! (${imported.label})`);
|
|
547
545
|
}
|
|
548
546
|
else {
|
|
549
|
-
addMsg("info", "No Codex CLI found.
|
|
547
|
+
addMsg("info", "No Codex CLI found. Install Codex CLI and sign in first.");
|
|
550
548
|
}
|
|
551
549
|
}
|
|
552
|
-
else if (
|
|
550
|
+
else if (method === "cached-token" && providerId === "qwen") {
|
|
553
551
|
const imported = importQwenToken((msg) => addMsg("info", msg));
|
|
554
552
|
if (imported) {
|
|
555
553
|
addMsg("info", `✅ Imported Qwen credentials! (${imported.label})`);
|
|
556
554
|
}
|
|
557
555
|
else {
|
|
558
|
-
addMsg("info", "No Qwen CLI found.
|
|
556
|
+
addMsg("info", "No Qwen CLI found. Install Qwen CLI and sign in first.");
|
|
559
557
|
}
|
|
560
558
|
}
|
|
561
|
-
else if (
|
|
559
|
+
else if (method === "device-flow") {
|
|
562
560
|
addMsg("info", "Starting GitHub Copilot device flow...");
|
|
563
561
|
setLoading(true);
|
|
564
562
|
setSpinnerMsg("Waiting for GitHub authorization...");
|
|
565
563
|
copilotDeviceFlow((msg) => addMsg("info", msg))
|
|
566
|
-
.then(() => {
|
|
567
|
-
addMsg("
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
+
}
|
|
574
618
|
}
|
|
575
619
|
else {
|
|
576
|
-
|
|
620
|
+
// Multiple methods — show submenu
|
|
621
|
+
setLoginMethodPicker({ provider: selected.id, methods });
|
|
622
|
+
setLoginMethodIndex(0);
|
|
577
623
|
}
|
|
578
624
|
return;
|
|
579
625
|
}
|
|
@@ -738,7 +784,16 @@ function App() {
|
|
|
738
784
|
default:
|
|
739
785
|
return _jsx(Text, { children: msg.text }, msg.id);
|
|
740
786
|
}
|
|
741
|
-
}), 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 · ~", (() => {
|
|
742
797
|
const tokens = agent.estimateTokens();
|
|
743
798
|
return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
|
|
744
799
|
})(), " tokens", modelName ? ` · 🤖 ${modelName}` : ""] }) }))] }));
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -137,6 +137,8 @@ function App() {
|
|
|
137
137
|
const [themePickerIndex, setThemePickerIndex] = useState(0);
|
|
138
138
|
const [loginPicker, setLoginPicker] = useState(false);
|
|
139
139
|
const [loginPickerIndex, setLoginPickerIndex] = useState(0);
|
|
140
|
+
const [loginMethodPicker, setLoginMethodPicker] = useState<{ provider: string; methods: string[] } | null>(null);
|
|
141
|
+
const [loginMethodIndex, setLoginMethodIndex] = useState(0);
|
|
140
142
|
const [approval, setApproval] = useState<{
|
|
141
143
|
tool: string;
|
|
142
144
|
args: Record<string, unknown>;
|
|
@@ -543,77 +545,115 @@ function App() {
|
|
|
543
545
|
}
|
|
544
546
|
}
|
|
545
547
|
|
|
546
|
-
// Login picker navigation
|
|
547
|
-
if (
|
|
548
|
-
const
|
|
548
|
+
// Login method picker navigation (second level — pick auth method)
|
|
549
|
+
if (loginMethodPicker) {
|
|
550
|
+
const methods = loginMethodPicker.methods;
|
|
549
551
|
if (key.upArrow) {
|
|
550
|
-
|
|
552
|
+
setLoginMethodIndex((prev: number) => (prev - 1 + methods.length) % methods.length);
|
|
551
553
|
return;
|
|
552
554
|
}
|
|
553
555
|
if (key.downArrow) {
|
|
554
|
-
|
|
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
|
|
555
562
|
return;
|
|
556
563
|
}
|
|
557
564
|
if (key.return) {
|
|
558
|
-
const
|
|
559
|
-
|
|
565
|
+
const method = methods[loginMethodIndex];
|
|
566
|
+
const providerId = loginMethodPicker.provider;
|
|
567
|
+
setLoginMethodPicker(null);
|
|
560
568
|
|
|
561
|
-
if (
|
|
569
|
+
if (method === "oauth" && providerId === "openrouter") {
|
|
562
570
|
addMsg("info", "Starting OpenRouter OAuth — opening browser...");
|
|
563
571
|
setLoading(true);
|
|
564
572
|
setSpinnerMsg("Waiting for authorization...");
|
|
565
573
|
openRouterOAuth((msg: string) => addMsg("info", msg))
|
|
566
|
-
.then((
|
|
567
|
-
addMsg("info", `✅ OpenRouter authenticated!
|
|
568
|
-
addMsg("info", `Switch with: /model anthropic/claude-sonnet-4`);
|
|
574
|
+
.then(() => {
|
|
575
|
+
addMsg("info", `✅ OpenRouter authenticated! Access to 200+ models.`);
|
|
569
576
|
setLoading(false);
|
|
570
577
|
})
|
|
571
|
-
.catch((err: any) => {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
});
|
|
575
|
-
} else if (selected.id === "anthropic") {
|
|
576
|
-
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...");
|
|
577
581
|
setLoading(true);
|
|
578
582
|
setSpinnerMsg("Waiting for Claude Code auth...");
|
|
579
583
|
anthropicSetupToken((msg: string) => addMsg("info", msg))
|
|
580
|
-
.then((cred) => {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
})
|
|
584
|
-
.catch((err: any) => {
|
|
585
|
-
addMsg("error", `Anthropic auth failed: ${err.message}`);
|
|
586
|
-
setLoading(false);
|
|
587
|
-
});
|
|
588
|
-
} 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") {
|
|
589
587
|
const imported = importCodexToken((msg: string) => addMsg("info", msg));
|
|
590
|
-
if (imported) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
addMsg("info", "No Codex CLI found. Run: codemaxxing auth api-key openai <your-key>");
|
|
594
|
-
}
|
|
595
|
-
} 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") {
|
|
596
591
|
const imported = importQwenToken((msg: string) => addMsg("info", msg));
|
|
597
|
-
if (imported) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
addMsg("info", "No Qwen CLI found. Run: codemaxxing auth api-key qwen <your-key>");
|
|
601
|
-
}
|
|
602
|
-
} 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") {
|
|
603
595
|
addMsg("info", "Starting GitHub Copilot device flow...");
|
|
604
596
|
setLoading(true);
|
|
605
597
|
setSpinnerMsg("Waiting for GitHub authorization...");
|
|
606
598
|
copilotDeviceFlow((msg: string) => addMsg("info", msg))
|
|
607
|
-
.then(() => {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
+
}
|
|
615
653
|
} else {
|
|
616
|
-
|
|
654
|
+
// Multiple methods — show submenu
|
|
655
|
+
setLoginMethodPicker({ provider: selected.id, methods });
|
|
656
|
+
setLoginMethodIndex(0);
|
|
617
657
|
}
|
|
618
658
|
return;
|
|
619
659
|
}
|
|
@@ -855,6 +895,45 @@ function App() {
|
|
|
855
895
|
</Box>
|
|
856
896
|
)}
|
|
857
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
|
+
|
|
858
937
|
{/* ═══ THEME PICKER ═══ */}
|
|
859
938
|
{themePicker && (
|
|
860
939
|
<Box flexDirection="column" borderStyle="single" borderColor={theme.colors.border} paddingX={1} marginBottom={0}>
|