codemaxxing 1.0.17 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -16
- package/dist/agent.d.ts +12 -1
- package/dist/agent.js +296 -31
- package/dist/index.js +140 -43
- package/dist/ui/input-router.d.ts +42 -2
- package/dist/ui/input-router.js +70 -17
- package/dist/ui/pickers.d.ts +23 -2
- package/dist/ui/pickers.js +12 -3
- package/dist/utils/anthropic-oauth.d.ts +13 -0
- package/dist/utils/anthropic-oauth.js +171 -0
- package/dist/utils/auth.d.ts +2 -0
- package/dist/utils/auth.js +42 -3
- package/dist/utils/ollama.js +6 -1
- package/dist/utils/openai-oauth.d.ts +19 -0
- package/dist/utils/openai-oauth.js +233 -0
- package/dist/utils/responses-api.d.ts +40 -0
- package/dist/utils/responses-api.js +264 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -16,10 +16,11 @@ import { listServers, addServer, removeServer, getConnectedServers } from "./uti
|
|
|
16
16
|
import { tryHandleSkillsCommand } from "./commands/skills.js";
|
|
17
17
|
import { isOllamaRunning, stopOllama, listInstalledModelsDetailed } from "./utils/ollama.js";
|
|
18
18
|
import { routeKeyPress } from "./ui/input-router.js";
|
|
19
|
+
import { getCredential } from "./utils/auth.js";
|
|
19
20
|
import { Banner, ConnectionInfo } from "./ui/banner.js";
|
|
20
21
|
import { StatusBar } from "./ui/status-bar.js";
|
|
21
22
|
import { refreshConnectionBanner as refreshConnectionBannerImpl, connectToProvider as connectToProviderImpl, } from "./ui/connection.js";
|
|
22
|
-
import { CommandSuggestions, LoginPicker, LoginMethodPickerUI, SkillsMenu, SkillsBrowse, SkillsInstalled, SkillsRemove, ThemePickerUI, SessionPicker, DeleteSessionPicker, DeleteSessionConfirm, ModelPicker, OllamaDeletePicker, OllamaPullPicker, OllamaDeleteConfirm, OllamaPullProgress, OllamaExitPrompt, ApprovalPrompt, WizardConnection, WizardModels, WizardInstallOllama, WizardPulling, } from "./ui/pickers.js";
|
|
23
|
+
import { CommandSuggestions, LoginPicker, LoginMethodPickerUI, SkillsMenu, SkillsBrowse, SkillsInstalled, SkillsRemove, ThemePickerUI, SessionPicker, DeleteSessionPicker, DeleteSessionConfirm, ProviderPicker, ModelPicker, OllamaDeletePicker, OllamaPullPicker, OllamaDeleteConfirm, OllamaPullProgress, OllamaExitPrompt, ApprovalPrompt, WizardConnection, WizardModels, WizardInstallOllama, WizardPulling, } from "./ui/pickers.js";
|
|
23
24
|
import { createRequire } from "module";
|
|
24
25
|
const _require = createRequire(import.meta.url);
|
|
25
26
|
const VERSION = _require("../package.json").version;
|
|
@@ -51,9 +52,8 @@ const SLASH_COMMANDS = [
|
|
|
51
52
|
{ cmd: "/push", desc: "push to remote" },
|
|
52
53
|
{ cmd: "/git on", desc: "enable auto-commits" },
|
|
53
54
|
{ cmd: "/git off", desc: "disable auto-commits" },
|
|
54
|
-
{ cmd: "/models", desc: "
|
|
55
|
+
{ cmd: "/models", desc: "switch model" },
|
|
55
56
|
{ cmd: "/theme", desc: "switch color theme" },
|
|
56
|
-
{ cmd: "/model", desc: "switch model mid-session" },
|
|
57
57
|
{ cmd: "/sessions", desc: "list past sessions" },
|
|
58
58
|
{ cmd: "/session delete", desc: "delete a session" },
|
|
59
59
|
{ cmd: "/resume", desc: "resume a past session" },
|
|
@@ -182,8 +182,12 @@ function App() {
|
|
|
182
182
|
const [ollamaDeletePickerIndex, setOllamaDeletePickerIndex] = useState(0);
|
|
183
183
|
const [ollamaPullPicker, setOllamaPullPicker] = useState(false);
|
|
184
184
|
const [ollamaPullPickerIndex, setOllamaPullPickerIndex] = useState(0);
|
|
185
|
-
const [
|
|
185
|
+
const [modelPickerGroups, setModelPickerGroups] = useState(null);
|
|
186
186
|
const [modelPickerIndex, setModelPickerIndex] = useState(0);
|
|
187
|
+
const [flatModelList, setFlatModelList] = useState([]);
|
|
188
|
+
const [providerPicker, setProviderPicker] = useState(null);
|
|
189
|
+
const [providerPickerIndex, setProviderPickerIndex] = useState(0);
|
|
190
|
+
const [selectedProvider, setSelectedProvider] = useState(null);
|
|
187
191
|
// ── Setup Wizard State ──
|
|
188
192
|
const [wizardScreen, setWizardScreen] = useState(null);
|
|
189
193
|
const [wizardIndex, setWizardIndex] = useState(0);
|
|
@@ -259,7 +263,7 @@ function App() {
|
|
|
259
263
|
const selected = matches[idx];
|
|
260
264
|
if (selected) {
|
|
261
265
|
// Commands that need args (like /commit, /model) — fill input instead of executing
|
|
262
|
-
if (selected.cmd === "/commit" || selected.cmd === "/
|
|
266
|
+
if (selected.cmd === "/commit" || selected.cmd === "/session delete" ||
|
|
263
267
|
selected.cmd === "/architect") {
|
|
264
268
|
setInput(selected.cmd + " ");
|
|
265
269
|
setCmdIndex(0);
|
|
@@ -320,8 +324,7 @@ function App() {
|
|
|
320
324
|
" /help — show this",
|
|
321
325
|
" /connect — retry LLM connection",
|
|
322
326
|
" /login — authentication setup (run codemaxxing login in terminal)",
|
|
323
|
-
" /
|
|
324
|
-
" /models — list available models",
|
|
327
|
+
" /models — switch model",
|
|
325
328
|
" /map — show repository map",
|
|
326
329
|
" /sessions — list past sessions",
|
|
327
330
|
" /session delete — delete a session",
|
|
@@ -471,54 +474,140 @@ function App() {
|
|
|
471
474
|
addMsg("info", `Messages in context: ${agent.getContextLength()}`);
|
|
472
475
|
return;
|
|
473
476
|
}
|
|
474
|
-
if (trimmed === "/models") {
|
|
477
|
+
if (trimmed === "/models" || trimmed === "/model") {
|
|
475
478
|
addMsg("info", "Fetching available models...");
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
479
|
+
const groups = {};
|
|
480
|
+
const providerEntries = [];
|
|
481
|
+
// Local LLM (Ollama/LM Studio) — always show, auto-detect
|
|
482
|
+
let localFound = false;
|
|
483
|
+
// Check common local LLM endpoints
|
|
484
|
+
const localEndpoints = [
|
|
485
|
+
{ name: "LM Studio", port: 1234 },
|
|
486
|
+
{ name: "Ollama", port: 11434 },
|
|
487
|
+
{ name: "vLLM", port: 8000 },
|
|
488
|
+
{ name: "LocalAI", port: 8080 },
|
|
489
|
+
];
|
|
490
|
+
for (const endpoint of localEndpoints) {
|
|
491
|
+
if (localFound)
|
|
492
|
+
break;
|
|
493
|
+
try {
|
|
494
|
+
const url = `http://localhost:${endpoint.port}/v1`;
|
|
495
|
+
const models = await listModels(url, "local");
|
|
496
|
+
if (models.length > 0) {
|
|
497
|
+
groups["Local LLM"] = models.map(m => ({
|
|
498
|
+
name: m,
|
|
499
|
+
baseUrl: url,
|
|
500
|
+
apiKey: "local",
|
|
501
|
+
providerType: "openai",
|
|
502
|
+
}));
|
|
503
|
+
localFound = true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch { /* not running */ }
|
|
483
507
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
508
|
+
// Also check Ollama native API
|
|
509
|
+
if (!localFound) {
|
|
510
|
+
try {
|
|
511
|
+
const ollamaModels = await listInstalledModelsDetailed();
|
|
512
|
+
if (ollamaModels.length > 0) {
|
|
513
|
+
groups["Local LLM"] = ollamaModels.map(m => ({
|
|
514
|
+
name: m.name,
|
|
515
|
+
baseUrl: "http://localhost:11434/v1",
|
|
516
|
+
apiKey: "ollama",
|
|
517
|
+
providerType: "openai",
|
|
518
|
+
}));
|
|
519
|
+
localFound = true;
|
|
520
|
+
}
|
|
495
521
|
}
|
|
522
|
+
catch { /* Ollama not running */ }
|
|
523
|
+
}
|
|
524
|
+
if (localFound) {
|
|
525
|
+
providerEntries.push({ name: "Local LLM", description: "No auth needed — auto-detected", authed: true });
|
|
496
526
|
}
|
|
497
|
-
|
|
498
|
-
|
|
527
|
+
// Anthropic
|
|
528
|
+
const anthropicCred = getCredential("anthropic");
|
|
529
|
+
const claudeModels = ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5-20251001"];
|
|
530
|
+
if (anthropicCred) {
|
|
531
|
+
groups["Anthropic (Claude)"] = claudeModels.map(m => ({
|
|
532
|
+
name: m,
|
|
533
|
+
baseUrl: "https://api.anthropic.com",
|
|
534
|
+
apiKey: anthropicCred.apiKey,
|
|
535
|
+
providerType: "anthropic",
|
|
536
|
+
}));
|
|
499
537
|
}
|
|
500
|
-
|
|
501
|
-
|
|
538
|
+
providerEntries.push({ name: "Anthropic (Claude)", description: "Claude Opus, Sonnet, Haiku — use your subscription or API key", authed: !!anthropicCred });
|
|
539
|
+
// OpenAI
|
|
540
|
+
const openaiCred = getCredential("openai");
|
|
541
|
+
const openaiModels = ["gpt-5.4", "gpt-5.4-pro", "gpt-5", "gpt-5-mini", "gpt-4.1", "gpt-4.1-mini", "o3", "o4-mini", "gpt-4o"];
|
|
542
|
+
if (openaiCred) {
|
|
543
|
+
// OAuth tokens (non sk- keys) must use ChatGPT backend, not api.openai.com
|
|
544
|
+
const isOAuthToken = openaiCred.method === "oauth" || openaiCred.method === "cached-token" ||
|
|
545
|
+
(!openaiCred.apiKey.startsWith("sk-") && !openaiCred.apiKey.startsWith("sess-"));
|
|
546
|
+
const baseUrl = isOAuthToken
|
|
547
|
+
? "https://chatgpt.com/backend-api"
|
|
548
|
+
: (openaiCred.baseUrl || "https://api.openai.com/v1");
|
|
549
|
+
groups["OpenAI (ChatGPT)"] = openaiModels.map(m => ({
|
|
550
|
+
name: m,
|
|
551
|
+
baseUrl,
|
|
552
|
+
apiKey: openaiCred.apiKey,
|
|
553
|
+
providerType: "openai",
|
|
554
|
+
}));
|
|
555
|
+
}
|
|
556
|
+
providerEntries.push({ name: "OpenAI (ChatGPT)", description: "GPT-5, GPT-4.1, o3 — use your ChatGPT subscription or API key", authed: !!openaiCred });
|
|
557
|
+
// OpenRouter
|
|
558
|
+
const openrouterCred = getCredential("openrouter");
|
|
559
|
+
if (openrouterCred) {
|
|
502
560
|
try {
|
|
503
|
-
const
|
|
504
|
-
if (
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
561
|
+
const orModels = await listModels(openrouterCred.baseUrl || "https://openrouter.ai/api/v1", openrouterCred.apiKey);
|
|
562
|
+
if (orModels.length > 0) {
|
|
563
|
+
groups["OpenRouter"] = orModels.slice(0, 20).map(m => ({
|
|
564
|
+
name: m,
|
|
565
|
+
baseUrl: openrouterCred.baseUrl || "https://openrouter.ai/api/v1",
|
|
566
|
+
apiKey: openrouterCred.apiKey,
|
|
567
|
+
providerType: "openai",
|
|
568
|
+
}));
|
|
508
569
|
}
|
|
509
570
|
}
|
|
510
|
-
catch
|
|
511
|
-
|
|
512
|
-
|
|
571
|
+
catch { /* skip */ }
|
|
572
|
+
}
|
|
573
|
+
providerEntries.push({ name: "OpenRouter", description: "200+ models (Claude, GPT, Gemini, Llama, etc.) — one login", authed: !!openrouterCred });
|
|
574
|
+
// Qwen
|
|
575
|
+
const qwenCred = getCredential("qwen");
|
|
576
|
+
if (qwenCred) {
|
|
577
|
+
groups["Qwen"] = ["qwen-max", "qwen-plus", "qwen-turbo"].map(m => ({
|
|
578
|
+
name: m,
|
|
579
|
+
baseUrl: qwenCred.baseUrl || "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
580
|
+
apiKey: qwenCred.apiKey,
|
|
581
|
+
providerType: "openai",
|
|
582
|
+
}));
|
|
583
|
+
}
|
|
584
|
+
providerEntries.push({ name: "Qwen", description: "Qwen 3.5, Qwen Coder — use your Qwen CLI login or API key", authed: !!qwenCred });
|
|
585
|
+
// GitHub Copilot
|
|
586
|
+
const copilotCred = getCredential("copilot");
|
|
587
|
+
if (copilotCred) {
|
|
588
|
+
groups["GitHub Copilot"] = ["gpt-4o", "claude-3.5-sonnet"].map(m => ({
|
|
589
|
+
name: m,
|
|
590
|
+
baseUrl: copilotCred.baseUrl || "https://api.githubcopilot.com",
|
|
591
|
+
apiKey: copilotCred.apiKey,
|
|
592
|
+
providerType: "openai",
|
|
593
|
+
}));
|
|
594
|
+
}
|
|
595
|
+
providerEntries.push({ name: "GitHub Copilot", description: "Use your GitHub Copilot subscription", authed: !!copilotCred });
|
|
596
|
+
// Show provider picker (step 1)
|
|
597
|
+
if (providerEntries.length > 0) {
|
|
598
|
+
setModelPickerGroups(groups);
|
|
599
|
+
setProviderPicker(providerEntries);
|
|
600
|
+
setProviderPickerIndex(0);
|
|
601
|
+
setSelectedProvider(null);
|
|
602
|
+
return;
|
|
513
603
|
}
|
|
514
|
-
// No models found anywhere
|
|
515
604
|
addMsg("error", "No models available. Download one with /ollama pull or configure a provider.");
|
|
516
605
|
return;
|
|
517
606
|
}
|
|
518
607
|
if (trimmed.startsWith("/model ")) {
|
|
519
608
|
const newModel = trimmed.replace("/model ", "").trim();
|
|
520
609
|
if (!newModel) {
|
|
521
|
-
addMsg("info", `Current model: ${modelName}\n Usage: /
|
|
610
|
+
addMsg("info", `Current model: ${modelName}\n Usage: /models`);
|
|
522
611
|
return;
|
|
523
612
|
}
|
|
524
613
|
agent.switchModel(newModel);
|
|
@@ -670,10 +759,18 @@ function App() {
|
|
|
670
759
|
setSkillsPicker,
|
|
671
760
|
sessionDisabledSkills,
|
|
672
761
|
setSessionDisabledSkills,
|
|
673
|
-
|
|
762
|
+
modelPickerGroups,
|
|
674
763
|
modelPickerIndex,
|
|
675
764
|
setModelPickerIndex,
|
|
676
|
-
|
|
765
|
+
setModelPickerGroups,
|
|
766
|
+
flatModelList,
|
|
767
|
+
setFlatModelList,
|
|
768
|
+
providerPicker,
|
|
769
|
+
providerPickerIndex,
|
|
770
|
+
setProviderPickerIndex,
|
|
771
|
+
setProviderPicker,
|
|
772
|
+
selectedProvider,
|
|
773
|
+
setSelectedProvider,
|
|
677
774
|
ollamaDeletePicker,
|
|
678
775
|
ollamaDeletePickerIndex,
|
|
679
776
|
setOllamaDeletePickerIndex,
|
|
@@ -754,7 +851,7 @@ function App() {
|
|
|
754
851
|
default:
|
|
755
852
|
return _jsx(Text, { children: msg.text }, msg.id);
|
|
756
853
|
}
|
|
757
|
-
}), loading && !approval && !streaming && _jsx(NeonSpinner, { message: spinnerMsg, colors: theme.colors }), streaming && !loading && _jsx(StreamingIndicator, { colors: theme.colors }), approval && (_jsx(ApprovalPrompt, { approval: approval, colors: theme.colors })), loginPicker && (_jsx(LoginPicker, { loginPickerIndex: loginPickerIndex, colors: theme.colors })), loginMethodPicker && (_jsx(LoginMethodPickerUI, { loginMethodPicker: loginMethodPicker, loginMethodIndex: loginMethodIndex, colors: theme.colors })), skillsPicker === "menu" && (_jsx(SkillsMenu, { skillsPickerIndex: skillsPickerIndex, colors: theme.colors })), skillsPicker === "browse" && (_jsx(SkillsBrowse, { skillsPickerIndex: skillsPickerIndex, colors: theme.colors })), skillsPicker === "installed" && (_jsx(SkillsInstalled, { skillsPickerIndex: skillsPickerIndex, sessionDisabledSkills: sessionDisabledSkills, colors: theme.colors })), skillsPicker === "remove" && (_jsx(SkillsRemove, { skillsPickerIndex: skillsPickerIndex, colors: theme.colors })), themePicker && (_jsx(ThemePickerUI, { themePickerIndex: themePickerIndex, theme: theme })), sessionPicker && (_jsx(SessionPicker, { sessions: sessionPicker, selectedIndex: sessionPickerIndex, colors: theme.colors })), deleteSessionPicker && (_jsx(DeleteSessionPicker, { sessions: deleteSessionPicker, selectedIndex: deleteSessionPickerIndex, colors: theme.colors })), deleteSessionConfirm && (_jsx(DeleteSessionConfirm, { session: deleteSessionConfirm, colors: theme.colors })),
|
|
854
|
+
}), loading && !approval && !streaming && _jsx(NeonSpinner, { message: spinnerMsg, colors: theme.colors }), streaming && !loading && _jsx(StreamingIndicator, { colors: theme.colors }), approval && (_jsx(ApprovalPrompt, { approval: approval, colors: theme.colors })), loginPicker && (_jsx(LoginPicker, { loginPickerIndex: loginPickerIndex, colors: theme.colors })), loginMethodPicker && (_jsx(LoginMethodPickerUI, { loginMethodPicker: loginMethodPicker, loginMethodIndex: loginMethodIndex, colors: theme.colors })), skillsPicker === "menu" && (_jsx(SkillsMenu, { skillsPickerIndex: skillsPickerIndex, colors: theme.colors })), skillsPicker === "browse" && (_jsx(SkillsBrowse, { skillsPickerIndex: skillsPickerIndex, colors: theme.colors })), skillsPicker === "installed" && (_jsx(SkillsInstalled, { skillsPickerIndex: skillsPickerIndex, sessionDisabledSkills: sessionDisabledSkills, colors: theme.colors })), skillsPicker === "remove" && (_jsx(SkillsRemove, { skillsPickerIndex: skillsPickerIndex, colors: theme.colors })), themePicker && (_jsx(ThemePickerUI, { themePickerIndex: themePickerIndex, theme: theme })), sessionPicker && (_jsx(SessionPicker, { sessions: sessionPicker, selectedIndex: sessionPickerIndex, colors: theme.colors })), deleteSessionPicker && (_jsx(DeleteSessionPicker, { sessions: deleteSessionPicker, selectedIndex: deleteSessionPickerIndex, colors: theme.colors })), deleteSessionConfirm && (_jsx(DeleteSessionConfirm, { session: deleteSessionConfirm, colors: theme.colors })), providerPicker && !selectedProvider && (_jsx(ProviderPicker, { providers: providerPicker, selectedIndex: providerPickerIndex, colors: theme.colors })), selectedProvider && modelPickerGroups && modelPickerGroups[selectedProvider] && (_jsx(ModelPicker, { providerName: selectedProvider, models: modelPickerGroups[selectedProvider], selectedIndex: modelPickerIndex, activeModel: modelName, colors: theme.colors })), ollamaDeletePicker && (_jsx(OllamaDeletePicker, { models: ollamaDeletePicker.models, selectedIndex: ollamaDeletePickerIndex, colors: theme.colors })), ollamaPullPicker && (_jsx(OllamaPullPicker, { selectedIndex: ollamaPullPickerIndex, colors: theme.colors })), ollamaDeleteConfirm && (_jsx(OllamaDeleteConfirm, { model: ollamaDeleteConfirm.model, size: ollamaDeleteConfirm.size, colors: theme.colors })), ollamaPulling && (_jsx(OllamaPullProgress, { model: ollamaPulling.model, progress: ollamaPulling.progress, colors: theme.colors })), ollamaExitPrompt && (_jsx(OllamaExitPrompt, { colors: theme.colors })), wizardScreen === "connection" && (_jsx(WizardConnection, { wizardIndex: wizardIndex, colors: theme.colors })), wizardScreen === "models" && wizardHardware && (_jsx(WizardModels, { wizardIndex: wizardIndex, wizardHardware: wizardHardware, wizardModels: wizardModels, colors: theme.colors })), wizardScreen === "install-ollama" && (_jsx(WizardInstallOllama, { wizardHardware: wizardHardware, colors: theme.colors })), wizardScreen === "pulling" && (wizardSelectedModel || wizardPullProgress) && (_jsx(WizardPulling, { wizardSelectedModel: wizardSelectedModel, wizardPullProgress: wizardPullProgress, wizardPullError: wizardPullError, colors: theme.colors })), showSuggestions && (_jsx(CommandSuggestions, { cmdMatches: cmdMatches, cmdIndex: cmdIndex, colors: theme.colors })), _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 && !wizardScreen ? (_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(sanitizeInputArtifacts(v)); setCmdIndex(0); }, onSubmit: handleSubmit }, inputKey)] })) : (_jsx(Text, { dimColor: true, children: loading ? "waiting for response..." : "initializing..." }))] }), agent && (_jsx(StatusBar, { agent: agent, modelName: modelName, sessionDisabledSkills: sessionDisabledSkills }))] }));
|
|
758
855
|
}
|
|
759
856
|
// Clear screen before render
|
|
760
857
|
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
@@ -36,10 +36,50 @@ export interface InputRouterContext extends WizardContext {
|
|
|
36
36
|
setSkillsPicker: (val: "menu" | "browse" | "installed" | "remove" | null) => void;
|
|
37
37
|
sessionDisabledSkills: Set<string>;
|
|
38
38
|
setSessionDisabledSkills: (fn: (prev: Set<string>) => Set<string>) => void;
|
|
39
|
-
|
|
39
|
+
providerPicker: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
authed: boolean;
|
|
43
|
+
}> | null;
|
|
44
|
+
providerPickerIndex: number;
|
|
45
|
+
setProviderPickerIndex: (fn: (prev: number) => number) => void;
|
|
46
|
+
setProviderPicker: (val: Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
description: string;
|
|
49
|
+
authed: boolean;
|
|
50
|
+
}> | null) => void;
|
|
51
|
+
selectedProvider: string | null;
|
|
52
|
+
setSelectedProvider: (val: string | null) => void;
|
|
53
|
+
modelPickerGroups: {
|
|
54
|
+
[providerName: string]: Array<{
|
|
55
|
+
name: string;
|
|
56
|
+
baseUrl: string;
|
|
57
|
+
apiKey: string;
|
|
58
|
+
providerType: "openai" | "anthropic";
|
|
59
|
+
}>;
|
|
60
|
+
} | null;
|
|
40
61
|
modelPickerIndex: number;
|
|
41
62
|
setModelPickerIndex: (fn: (prev: number) => number) => void;
|
|
42
|
-
|
|
63
|
+
setModelPickerGroups: (val: {
|
|
64
|
+
[providerName: string]: Array<{
|
|
65
|
+
name: string;
|
|
66
|
+
baseUrl: string;
|
|
67
|
+
apiKey: string;
|
|
68
|
+
providerType: "openai" | "anthropic";
|
|
69
|
+
}>;
|
|
70
|
+
} | null) => void;
|
|
71
|
+
flatModelList: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
baseUrl: string;
|
|
74
|
+
apiKey: string;
|
|
75
|
+
providerType: "openai" | "anthropic";
|
|
76
|
+
}>;
|
|
77
|
+
setFlatModelList: (val: Array<{
|
|
78
|
+
name: string;
|
|
79
|
+
baseUrl: string;
|
|
80
|
+
apiKey: string;
|
|
81
|
+
providerType: "openai" | "anthropic";
|
|
82
|
+
}>) => void;
|
|
43
83
|
ollamaDeletePicker: {
|
|
44
84
|
models: {
|
|
45
85
|
name: string;
|
package/dist/ui/input-router.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { PROVIDERS, openRouterOAuth,
|
|
1
|
+
import { PROVIDERS, openRouterOAuth, importCodexToken, importQwenToken, copilotDeviceFlow } from "../utils/auth.js";
|
|
2
|
+
import { loginOpenAICodexOAuth } from "../utils/openai-oauth.js";
|
|
3
|
+
import { loginAnthropicOAuth } from "../utils/anthropic-oauth.js";
|
|
2
4
|
import { listInstalledSkills, installSkill, removeSkill, getRegistrySkills } from "../utils/skills.js";
|
|
3
5
|
import { listThemes, getTheme, THEMES } from "../themes.js";
|
|
4
6
|
import { getSession, loadMessages, deleteSession } from "../utils/sessions.js";
|
|
@@ -15,6 +17,8 @@ export function routeKeyPress(inputChar, key, ctx) {
|
|
|
15
17
|
return true;
|
|
16
18
|
if (handleSkillsPicker(inputChar, key, ctx))
|
|
17
19
|
return true;
|
|
20
|
+
if (handleProviderPicker(inputChar, key, ctx))
|
|
21
|
+
return true;
|
|
18
22
|
if (handleModelPicker(inputChar, key, ctx))
|
|
19
23
|
return true;
|
|
20
24
|
if (handleOllamaDeletePicker(inputChar, key, ctx))
|
|
@@ -99,21 +103,37 @@ function handleLoginMethodPicker(inputChar, key, ctx) {
|
|
|
99
103
|
})
|
|
100
104
|
.catch((err) => { ctx.addMsg("error", `OAuth failed: ${err.message}`); ctx.setLoading(false); });
|
|
101
105
|
}
|
|
102
|
-
else if (method === "
|
|
103
|
-
ctx.addMsg("info", "
|
|
106
|
+
else if (method === "oauth" && providerId === "anthropic") {
|
|
107
|
+
ctx.addMsg("info", "Opening browser for Claude login...");
|
|
104
108
|
ctx.setLoading(true);
|
|
105
|
-
ctx.setSpinnerMsg("Waiting for
|
|
106
|
-
|
|
109
|
+
ctx.setSpinnerMsg("Waiting for Anthropic authorization...");
|
|
110
|
+
loginAnthropicOAuth((msg) => ctx.addMsg("info", msg))
|
|
107
111
|
.then((cred) => { ctx.addMsg("info", `✅ Anthropic authenticated! (${cred.label})`); ctx.setLoading(false); })
|
|
108
|
-
.catch((err) => {
|
|
112
|
+
.catch((err) => {
|
|
113
|
+
ctx.addMsg("error", `OAuth failed: ${err.message}\n Fallback: set your key via CLI: codemaxxing auth api-key anthropic <your-key>\n Or set ANTHROPIC_API_KEY env var and restart.\n Get key at: console.anthropic.com/settings/keys`);
|
|
114
|
+
ctx.setLoading(false);
|
|
115
|
+
});
|
|
109
116
|
}
|
|
110
|
-
else if (method === "
|
|
117
|
+
else if (method === "oauth" && providerId === "openai") {
|
|
118
|
+
// Try cached Codex token first as a quick path
|
|
111
119
|
const imported = importCodexToken((msg) => ctx.addMsg("info", msg));
|
|
112
120
|
if (imported) {
|
|
113
121
|
ctx.addMsg("info", `✅ Imported Codex credentials! (${imported.label})`);
|
|
114
122
|
}
|
|
115
123
|
else {
|
|
116
|
-
|
|
124
|
+
// Primary flow: browser OAuth
|
|
125
|
+
ctx.addMsg("info", "Opening browser for ChatGPT login...");
|
|
126
|
+
ctx.setLoading(true);
|
|
127
|
+
ctx.setSpinnerMsg("Waiting for OpenAI authorization...");
|
|
128
|
+
loginOpenAICodexOAuth((msg) => ctx.addMsg("info", msg))
|
|
129
|
+
.then((cred) => {
|
|
130
|
+
ctx.addMsg("info", `✅ OpenAI authenticated! (${cred.label})`);
|
|
131
|
+
ctx.setLoading(false);
|
|
132
|
+
})
|
|
133
|
+
.catch((err) => {
|
|
134
|
+
ctx.addMsg("error", `OAuth failed: ${err.message}\n Fallback: set your key via CLI: codemaxxing auth api-key openai <your-key>\n Or set OPENAI_API_KEY env var and restart.\n Get key at: platform.openai.com/api-keys`);
|
|
135
|
+
ctx.setLoading(false);
|
|
136
|
+
});
|
|
117
137
|
}
|
|
118
138
|
}
|
|
119
139
|
else if (method === "cached-token" && providerId === "qwen") {
|
|
@@ -343,30 +363,63 @@ function handleSkillsPicker(_inputChar, key, ctx) {
|
|
|
343
363
|
}
|
|
344
364
|
return true; // absorb all keys when skills picker is active
|
|
345
365
|
}
|
|
366
|
+
function handleProviderPicker(_inputChar, key, ctx) {
|
|
367
|
+
if (!ctx.providerPicker || ctx.selectedProvider)
|
|
368
|
+
return false;
|
|
369
|
+
const len = ctx.providerPicker.length;
|
|
370
|
+
if (key.upArrow) {
|
|
371
|
+
ctx.setProviderPickerIndex((prev) => (prev - 1 + len) % len);
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
if (key.downArrow) {
|
|
375
|
+
ctx.setProviderPickerIndex((prev) => (prev + 1) % len);
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
if (key.escape) {
|
|
379
|
+
ctx.setProviderPicker(null);
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
if (key.return) {
|
|
383
|
+
const selected = ctx.providerPicker[ctx.providerPickerIndex];
|
|
384
|
+
ctx.setSelectedProvider(selected.name);
|
|
385
|
+
ctx.setModelPickerIndex(() => 0);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
346
390
|
function handleModelPicker(_inputChar, key, ctx) {
|
|
347
|
-
if (!ctx.
|
|
391
|
+
if (!ctx.selectedProvider || !ctx.modelPickerGroups)
|
|
392
|
+
return false;
|
|
393
|
+
const models = ctx.modelPickerGroups[ctx.selectedProvider];
|
|
394
|
+
if (!models)
|
|
348
395
|
return false;
|
|
396
|
+
const len = models.length;
|
|
349
397
|
if (key.upArrow) {
|
|
350
|
-
ctx.setModelPickerIndex((prev) => (prev - 1 +
|
|
398
|
+
ctx.setModelPickerIndex((prev) => (prev - 1 + len) % len);
|
|
351
399
|
return true;
|
|
352
400
|
}
|
|
353
401
|
if (key.downArrow) {
|
|
354
|
-
ctx.setModelPickerIndex((prev) => (prev + 1) %
|
|
402
|
+
ctx.setModelPickerIndex((prev) => (prev + 1) % len);
|
|
355
403
|
return true;
|
|
356
404
|
}
|
|
357
405
|
if (key.escape) {
|
|
358
|
-
ctx.
|
|
406
|
+
ctx.setSelectedProvider(null);
|
|
407
|
+
ctx.setModelPickerIndex(() => 0);
|
|
359
408
|
return true;
|
|
360
409
|
}
|
|
361
410
|
if (key.return) {
|
|
362
|
-
const selected =
|
|
411
|
+
const selected = models[ctx.modelPickerIndex];
|
|
363
412
|
if (selected && ctx.agent) {
|
|
364
|
-
ctx.agent.switchModel(selected);
|
|
365
|
-
ctx.setModelName(selected);
|
|
366
|
-
ctx.addMsg("info", `✅ Switched to: ${selected}`);
|
|
413
|
+
ctx.agent.switchModel(selected.name, selected.baseUrl, selected.apiKey, selected.providerType);
|
|
414
|
+
ctx.setModelName(selected.name);
|
|
415
|
+
ctx.addMsg("info", `✅ Switched to: ${selected.name}`);
|
|
367
416
|
ctx.refreshConnectionBanner();
|
|
368
417
|
}
|
|
369
|
-
ctx.
|
|
418
|
+
ctx.setModelPickerGroups(null);
|
|
419
|
+
ctx.setProviderPicker(null);
|
|
420
|
+
ctx.setSelectedProvider(null);
|
|
421
|
+
ctx.setModelPickerIndex(() => 0);
|
|
422
|
+
ctx.setProviderPickerIndex(() => 0);
|
|
370
423
|
return true;
|
|
371
424
|
}
|
|
372
425
|
return true;
|
package/dist/ui/pickers.d.ts
CHANGED
|
@@ -77,13 +77,34 @@ interface DeleteSessionConfirmProps {
|
|
|
77
77
|
colors: Theme["colors"];
|
|
78
78
|
}
|
|
79
79
|
export declare function DeleteSessionConfirm({ session, colors }: DeleteSessionConfirmProps): import("react/jsx-runtime").JSX.Element;
|
|
80
|
+
export interface ModelEntry {
|
|
81
|
+
name: string;
|
|
82
|
+
baseUrl: string;
|
|
83
|
+
apiKey: string;
|
|
84
|
+
providerType: "openai" | "anthropic";
|
|
85
|
+
}
|
|
86
|
+
export interface GroupedModels {
|
|
87
|
+
[providerName: string]: ModelEntry[];
|
|
88
|
+
}
|
|
89
|
+
export interface ProviderPickerEntry {
|
|
90
|
+
name: string;
|
|
91
|
+
description: string;
|
|
92
|
+
authed: boolean;
|
|
93
|
+
}
|
|
94
|
+
interface ProviderPickerProps {
|
|
95
|
+
providers: ProviderPickerEntry[];
|
|
96
|
+
selectedIndex: number;
|
|
97
|
+
colors: Theme["colors"];
|
|
98
|
+
}
|
|
99
|
+
export declare function ProviderPicker({ providers, selectedIndex, colors }: ProviderPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
80
100
|
interface ModelPickerProps {
|
|
81
|
-
|
|
101
|
+
providerName: string;
|
|
102
|
+
models: ModelEntry[];
|
|
82
103
|
selectedIndex: number;
|
|
83
104
|
activeModel: string;
|
|
84
105
|
colors: Theme["colors"];
|
|
85
106
|
}
|
|
86
|
-
export declare function ModelPicker({ models, selectedIndex, activeModel, colors }: ModelPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
107
|
+
export declare function ModelPicker({ providerName, models, selectedIndex, activeModel, colors }: ModelPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
87
108
|
interface OllamaDeletePickerProps {
|
|
88
109
|
models: Array<{
|
|
89
110
|
name: string;
|
package/dist/ui/pickers.js
CHANGED
|
@@ -13,6 +13,11 @@ export function LoginPicker({ loginPickerIndex, colors }) {
|
|
|
13
13
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: 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: colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === loginPickerIndex ? colors.suggestion : colors.primary, bold: true, children: p.name }), _jsxs(Text, { color: colors.muted, children: [" — ", p.description] }), getCredentials().some((c) => c.provider === p.id) ? _jsx(Text, { color: colors.success, children: " \u2713" }) : null] }, p.id))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] }));
|
|
14
14
|
}
|
|
15
15
|
export function LoginMethodPickerUI({ loginMethodPicker, loginMethodIndex, colors }) {
|
|
16
|
+
// Provider-specific label overrides
|
|
17
|
+
const providerLabels = {
|
|
18
|
+
anthropic: { "oauth": "🔐 Login with Claude Pro/Max (browser)" },
|
|
19
|
+
openai: { "oauth": "🔐 Login with ChatGPT (browser)" },
|
|
20
|
+
};
|
|
16
21
|
const labels = {
|
|
17
22
|
"oauth": "🌐 Browser login (OAuth)",
|
|
18
23
|
"setup-token": "🔑 Link subscription (via Claude Code CLI)",
|
|
@@ -20,7 +25,8 @@ export function LoginMethodPickerUI({ loginMethodPicker, loginMethodIndex, color
|
|
|
20
25
|
"api-key": "🔒 Enter API key manually",
|
|
21
26
|
"device-flow": "📱 Device flow (GitHub)",
|
|
22
27
|
};
|
|
23
|
-
|
|
28
|
+
const getLabel = (method) => providerLabels[loginMethodPicker.provider]?.[method] ?? labels[method] ?? method;
|
|
29
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.secondary, children: "How do you want to authenticate?" }), loginMethodPicker.methods.map((method, i) => (_jsxs(Text, { children: [i === loginMethodIndex ? _jsx(Text, { color: colors.suggestion, bold: true, children: "▸ " }) : _jsx(Text, { children: " " }), _jsx(Text, { color: i === loginMethodIndex ? colors.suggestion : colors.primary, bold: true, children: getLabel(method) })] }, method))), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc back" })] }));
|
|
24
30
|
}
|
|
25
31
|
export function SkillsMenu({ skillsPickerIndex, colors }) {
|
|
26
32
|
const items = [
|
|
@@ -57,8 +63,11 @@ export function DeleteSessionPicker({ sessions, selectedIndex, colors }) {
|
|
|
57
63
|
export function DeleteSessionConfirm({ session, colors }) {
|
|
58
64
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.warning, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: colors.warning, children: ["Delete session ", session.id, "?"] }), _jsxs(Text, { color: colors.muted, children: [" ", session.display] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.error, bold: true, children: " [y]" }), _jsx(Text, { children: "es " }), _jsx(Text, { color: colors.success, bold: true, children: "[n]" }), _jsx(Text, { children: "o" })] })] }));
|
|
59
65
|
}
|
|
60
|
-
export function
|
|
61
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.secondary, children: "
|
|
66
|
+
export function ProviderPicker({ providers, selectedIndex, colors }) {
|
|
67
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.secondary, children: "Choose a provider:" }), _jsx(Text, { children: "" }), providers.map((p, i) => (_jsxs(Text, { children: [i === selectedIndex ? _jsx(Text, { color: colors.primary, bold: true, children: "▸ " }) : " ", _jsx(Text, { color: i === selectedIndex ? colors.primary : undefined, bold: true, children: p.name }), _jsxs(Text, { dimColor: true, children: [" — ", p.description] }), p.authed ? _jsx(Text, { color: colors.success, children: " ✓" }) : null] }, p.name))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter select · Esc cancel" })] }));
|
|
68
|
+
}
|
|
69
|
+
export function ModelPicker({ providerName, models, selectedIndex, activeModel, colors }) {
|
|
70
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsxs(Text, { bold: true, color: colors.secondary, children: ["── ", providerName, " ──"] }), _jsx(Text, { children: "" }), models.map((entry, i) => (_jsxs(Text, { children: [" ", i === selectedIndex ? _jsx(Text, { color: colors.primary, bold: true, children: "▸ " }) : " ", _jsx(Text, { color: i === selectedIndex ? colors.primary : undefined, children: entry.name }), entry.name === activeModel ? _jsx(Text, { color: colors.success, children: " (active)" }) : null] }, entry.name))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter switch · Esc back" })] }));
|
|
62
71
|
}
|
|
63
72
|
export function OllamaDeletePicker({ models, selectedIndex, colors }) {
|
|
64
73
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, marginBottom: 0, children: [_jsx(Text, { bold: true, color: colors.secondary, children: "Delete which model?" }), _jsx(Text, { children: "" }), models.map((m, i) => (_jsxs(Text, { children: [" ", i === selectedIndex ? _jsx(Text, { color: colors.primary, bold: true, children: "▸ " }) : " ", _jsx(Text, { color: i === selectedIndex ? colors.primary : undefined, children: m.name }), _jsxs(Text, { color: colors.muted, children: [" (", (m.size / (1024 * 1024 * 1024)).toFixed(1), " GB)"] })] }, m.name))), _jsx(Text, { children: "" }), _jsx(Text, { dimColor: true, children: " ↑↓ navigate · Enter to delete · Esc cancel" })] }));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic OAuth PKCE flow
|
|
3
|
+
*
|
|
4
|
+
* Lets users log in with their Claude Pro/Max subscription (no API key needed).
|
|
5
|
+
* Uses the same OAuth flow as Claude Code CLI.
|
|
6
|
+
*/
|
|
7
|
+
import { type AuthCredential } from "./auth.js";
|
|
8
|
+
export declare function refreshAnthropicOAuthToken(refreshToken: string): Promise<{
|
|
9
|
+
access: string;
|
|
10
|
+
refresh: string;
|
|
11
|
+
expires: number;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function loginAnthropicOAuth(onStatus?: (msg: string) => void): Promise<AuthCredential>;
|