codemaxxing 1.0.0 → 1.0.2

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.
Files changed (47) hide show
  1. package/README.md +18 -12
  2. package/dist/agent.d.ts +4 -0
  3. package/dist/agent.js +91 -16
  4. package/dist/commands/git.d.ts +2 -0
  5. package/dist/commands/git.js +50 -0
  6. package/dist/commands/ollama.d.ts +27 -0
  7. package/dist/commands/ollama.js +171 -0
  8. package/dist/commands/output.d.ts +2 -0
  9. package/dist/commands/output.js +18 -0
  10. package/dist/commands/registry.d.ts +2 -0
  11. package/dist/commands/registry.js +8 -0
  12. package/dist/commands/skills.d.ts +18 -0
  13. package/dist/commands/skills.js +121 -0
  14. package/dist/commands/types.d.ts +5 -0
  15. package/dist/commands/types.js +1 -0
  16. package/dist/commands/ui.d.ts +16 -0
  17. package/dist/commands/ui.js +79 -0
  18. package/dist/config.d.ts +9 -0
  19. package/dist/config.js +13 -3
  20. package/dist/exec.js +4 -1
  21. package/dist/index.js +75 -401
  22. package/dist/tools/files.js +58 -3
  23. package/dist/utils/context.js +6 -0
  24. package/dist/utils/mcp.d.ts +7 -2
  25. package/dist/utils/mcp.js +34 -6
  26. package/package.json +8 -5
  27. package/src/agent.ts +0 -894
  28. package/src/auth-cli.ts +0 -287
  29. package/src/cli.ts +0 -37
  30. package/src/config.ts +0 -352
  31. package/src/exec.ts +0 -183
  32. package/src/index.tsx +0 -2647
  33. package/src/skills/registry.ts +0 -1436
  34. package/src/themes.ts +0 -335
  35. package/src/tools/files.ts +0 -374
  36. package/src/utils/auth.ts +0 -606
  37. package/src/utils/context.ts +0 -174
  38. package/src/utils/git.ts +0 -117
  39. package/src/utils/hardware.ts +0 -131
  40. package/src/utils/lint.ts +0 -116
  41. package/src/utils/mcp.ts +0 -307
  42. package/src/utils/models.ts +0 -218
  43. package/src/utils/ollama.ts +0 -352
  44. package/src/utils/repomap.ts +0 -220
  45. package/src/utils/sessions.ts +0 -254
  46. package/src/utils/skills.ts +0 -241
  47. package/tsconfig.json +0 -16
package/src/auth-cli.ts DELETED
@@ -1,287 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * codemaxxing auth CLI
5
- *
6
- * Commands:
7
- * codemaxxing login — Interactive auth setup
8
- * codemaxxing auth list — List saved credentials
9
- * codemaxxing auth remove <name> — Remove a credential
10
- * codemaxxing auth openrouter — Start OpenRouter OAuth
11
- * codemaxxing auth anthropic — Get Anthropic via Claude Code
12
- * codemaxxing auth openai — Import Codex CLI credentials
13
- * codemaxxing auth qwen — Import Qwen CLI credentials
14
- * codemaxxing auth copilot — GitHub Copilot device flow
15
- * codemaxxing auth api-key <name> <key> — Save API key directly
16
- */
17
-
18
- import {
19
- PROVIDERS,
20
- getCredentials,
21
- removeCredential,
22
- openRouterOAuth,
23
- anthropicSetupToken,
24
- importCodexToken,
25
- importQwenToken,
26
- copilotDeviceFlow,
27
- saveApiKey,
28
- detectAvailableAuth,
29
- } from "./utils/auth.js";
30
-
31
- export async function main() {
32
- const command = process.argv[2] ?? "login";
33
-
34
- switch (command) {
35
- case "login": {
36
- console.log("\n💪 Codemaxxing Authentication\n");
37
- console.log("Available providers:\n");
38
-
39
- PROVIDERS.forEach((p, i) => {
40
- const methods = p.methods.filter((m) => m !== "none").join(", ");
41
- console.log(` ${i + 1}. ${p.name}`);
42
- console.log(` ${p.description}`);
43
- console.log(` Methods: ${methods}\n`);
44
- });
45
-
46
- console.log("Detected on this machine:");
47
- const detected = detectAvailableAuth();
48
- if (detected.length === 0) {
49
- console.log(" — No existing CLI credentials found\n");
50
- } else {
51
- detected.forEach((d) => {
52
- console.log(` ⚡ ${d.provider} — ${d.description}`);
53
- });
54
- console.log("");
55
- }
56
-
57
- const readline = await import("readline");
58
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
59
-
60
- const ask = (q: string): Promise<string> =>
61
- new Promise((resolve) => rl.question(q, resolve));
62
-
63
- const choice = await ask("Select a provider (1-" + PROVIDERS.length + ") or name: ");
64
- const providerId = PROVIDERS[parseInt(choice) - 1]?.id ?? choice.toLowerCase();
65
- const provider = PROVIDERS.find((p) => p.id === providerId);
66
-
67
- if (!provider) {
68
- console.log(`Unknown provider: ${providerId}`);
69
- process.exit(1);
70
- }
71
-
72
- console.log(`\nSetting up ${provider.name}...\n`);
73
-
74
- try {
75
- if (providerId === "openrouter") {
76
- const cred = await openRouterOAuth((msg) => console.log(` ${msg}`));
77
- console.log(`\n✅ OpenRouter authenticated! (${cred.label})`);
78
- } else if (providerId === "anthropic") {
79
- if (provider.methods.includes("setup-token")) {
80
- const cred = await anthropicSetupToken((msg) => console.log(` ${msg}`));
81
- console.log(`\n✅ Anthropic authenticated! (${cred.label})`);
82
- } else {
83
- const apiKey = await ask("Enter your Anthropic API key: ");
84
- const cred = saveApiKey(providerId, apiKey);
85
- console.log(`\n✅ Saved API key for ${provider.name}`);
86
- }
87
- } else if (providerId === "openai") {
88
- const imported = importCodexToken((msg) => console.log(` ${msg}`));
89
- if (imported) {
90
- console.log(`\n✅ Imported Codex credentials! (${imported.label})`);
91
- } else {
92
- const apiKey = await ask("Enter your OpenAI API key: ");
93
- const cred = saveApiKey(providerId, apiKey);
94
- console.log(`\n✅ Saved API key for ${provider.name}`);
95
- }
96
- } else if (providerId === "qwen") {
97
- const imported = importQwenToken((msg) => console.log(` ${msg}`));
98
- if (imported) {
99
- console.log(`\n✅ Imported Qwen credentials! (${imported.label})`);
100
- } else {
101
- const apiKey = await ask("Enter your Qwen/DashScope API key: ");
102
- const cred = saveApiKey(providerId, apiKey);
103
- console.log(`\n✅ Saved API key for ${provider.name}`);
104
- }
105
- } else if (providerId === "copilot") {
106
- const cred = await copilotDeviceFlow((msg) => console.log(` ${msg}`));
107
- console.log(`\n✅ GitHub Copilot authenticated!`);
108
- } else if (providerId === "custom") {
109
- const baseUrl = await ask("Enter the base URL: ");
110
- const apiKey = await ask("Enter your API key: ");
111
- const cred = saveApiKey(providerId, apiKey, baseUrl, "Custom provider");
112
- console.log(`\n✅ Saved custom provider`);
113
- } else {
114
- const apiKey = await ask(`Enter your ${provider.name} API key (${provider.consoleUrl}): `);
115
- const cred = saveApiKey(providerId, apiKey);
116
- console.log(`\n✅ Saved API key for ${provider.name}`);
117
- }
118
-
119
- console.log("\nRun 'codemaxxing' to start coding!");
120
- } catch (err: any) {
121
- console.error(`\n❌ Error: ${err.message}`);
122
- process.exit(1);
123
- } finally {
124
- rl.close();
125
- }
126
- break;
127
- }
128
-
129
- case "list":
130
- case "ls": {
131
- const creds = getCredentials();
132
- console.log("\n💪 Saved Credentials\n");
133
-
134
- if (creds.length === 0) {
135
- console.log(" No credentials saved.\n");
136
- console.log(" Run 'codemaxxing login' to set up authentication.\n");
137
- break;
138
- }
139
-
140
- creds.forEach((c) => {
141
- console.log(` ${c.provider}`);
142
- console.log(` Method: ${c.method}`);
143
- console.log(` Label: ${c.label ?? "—"}`);
144
- const maskedKey = c.apiKey.length > 12
145
- ? c.apiKey.slice(0, 4) + "•".repeat(8) + c.apiKey.slice(-4)
146
- : "••••••••";
147
- console.log(` Key: ${maskedKey}`);
148
- console.log(` Base: ${c.baseUrl}`);
149
- console.log("");
150
- });
151
- break;
152
- }
153
-
154
- case "remove":
155
- case "rm":
156
- case "delete": {
157
- const target = process.argv[3];
158
- if (!target) {
159
- console.log("Usage: codemaxxing auth remove <provider-name>");
160
- console.log("\nSaved providers:");
161
- getCredentials().forEach((c) => console.log(` ${c.provider}`));
162
- process.exit(1);
163
- }
164
-
165
- const removed = removeCredential(target);
166
- if (removed) {
167
- console.log(`✅ Removed ${target}`);
168
- } else {
169
- console.log(`❌ No credential found for: ${target}`);
170
- console.log("\nSaved providers:");
171
- getCredentials().forEach((c) => console.log(` ${c.provider}`));
172
- process.exit(1);
173
- }
174
- break;
175
- }
176
-
177
- case "openrouter": {
178
- console.log("Starting OpenRouter OAuth flow...\n");
179
- try {
180
- const cred = await openRouterOAuth((msg) => console.log(` ${msg}`));
181
- console.log(`\n✅ OpenRouter authenticated!`);
182
- } catch (err: any) {
183
- console.error(`\n❌ ${err.message}`);
184
- process.exit(1);
185
- }
186
- break;
187
- }
188
-
189
- case "anthropic": {
190
- console.log("Starting Anthropic setup-token flow...\n");
191
- try {
192
- const cred = await anthropicSetupToken((msg) => console.log(` ${msg}`));
193
- console.log(`\n✅ Anthropic authenticated!`);
194
- } catch (err: any) {
195
- console.error(`\n❌ ${err.message}`);
196
- process.exit(1);
197
- }
198
- break;
199
- }
200
-
201
- case "openai": {
202
- console.log("Checking for Codex CLI credentials...\n");
203
- const imported = importCodexToken((msg) => console.log(` ${msg}`));
204
- if (imported) {
205
- console.log(`\n✅ Imported Codex credentials!`);
206
- } else {
207
- console.log("\n❌ No Codex CLI credentials found.");
208
- console.log("Make sure Codex CLI is installed and you've logged in.");
209
- }
210
- break;
211
- }
212
-
213
- case "qwen": {
214
- console.log("Checking for Qwen CLI credentials...\n");
215
- const imported = importQwenToken((msg) => console.log(` ${msg}`));
216
- if (imported) {
217
- console.log(`\n✅ Imported Qwen credentials!`);
218
- } else {
219
- console.log("\n❌ No Qwen CLI credentials found.");
220
- console.log("Make sure Qwen CLI is installed and you've logged in.");
221
- }
222
- break;
223
- }
224
-
225
- case "copilot": {
226
- console.log("Starting GitHub Copilot device flow...\n");
227
- try {
228
- const cred = await copilotDeviceFlow((msg) => console.log(` ${msg}`));
229
- console.log(`\n✅ GitHub Copilot authenticated!`);
230
- } catch (err: any) {
231
- console.error(`\n❌ ${err.message}`);
232
- process.exit(1);
233
- }
234
- break;
235
- }
236
-
237
- case "api-key": {
238
- const providerId = process.argv[3];
239
- const apiKey = process.argv[4];
240
- if (!providerId || !apiKey) {
241
- console.log("Usage: codemaxxing auth api-key <provider-id> <api-key>");
242
- process.exit(1);
243
- }
244
- const cred = saveApiKey(providerId, apiKey);
245
- console.log(`\n✅ Saved API key for ${cred.provider}`);
246
- break;
247
- }
248
-
249
- case "help":
250
- case "--help":
251
- case "-h": {
252
- console.log(`
253
- 💪 Codemaxxing Auth
254
-
255
- Commands:
256
- codemaxxing login Interactive authentication setup
257
- codemaxxing auth list List saved credentials
258
- codemaxxing auth remove <name> Remove a credential
259
- codemaxxing auth openrouter Start OpenRouter OAuth flow
260
- codemaxxing auth anthropic Get Anthropic via Claude Code CLI
261
- codemaxxing auth openai Import Codex CLI credentials
262
- codemaxxing auth qwen Import Qwen CLI credentials
263
- codemaxxing auth copilot GitHub Copilot device flow
264
- codemaxxing auth api-key <id> <key> Save API key directly
265
- codemaxxing auth help Show this help
266
-
267
- Examples:
268
- codemaxxing login # Interactive provider picker
269
- codemaxxing auth openrouter # One browser login, access to 200+ models
270
- codemaxxing auth anthropic # Use your Claude subscription via Claude Code
271
- codemaxxing auth list # See what's saved
272
- `);
273
- break;
274
- }
275
-
276
- default:
277
- console.log(`Unknown command: ${command}`);
278
- console.log("Run 'codemaxxing auth help' for available commands.");
279
- process.exit(1);
280
- }
281
- }
282
-
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 DELETED
@@ -1,37 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Codemaxxing CLI entry point
5
- * Routes subcommands (login, auth, exec) to handlers, everything else to the TUI
6
- */
7
-
8
- import { spawn } from "node:child_process";
9
- import { fileURLToPath } from "node:url";
10
- import { dirname, join } from "node:path";
11
-
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = dirname(__filename);
14
-
15
- const subcmd = process.argv[2];
16
-
17
- if (subcmd === "login" || subcmd === "auth") {
18
- // Route to auth CLI (spawn is fine here — no TUI/raw mode needed)
19
- const authScript = join(__dirname, "auth-cli.js");
20
- const args = subcmd === "login"
21
- ? [authScript, "login", ...process.argv.slice(3)]
22
- : [authScript, ...process.argv.slice(3)];
23
-
24
- const child = spawn(process.execPath, args, {
25
- stdio: "inherit",
26
- cwd: process.cwd(),
27
- });
28
-
29
- child.on("exit", (code) => process.exit(code ?? 0));
30
- } else if (subcmd === "exec") {
31
- // Headless/CI mode — no TUI
32
- const { runExec } = await import("./exec.js");
33
- await runExec(process.argv.slice(3));
34
- } else {
35
- // TUI mode — import directly (not spawn) to preserve raw stdin
36
- await import("./index.js");
37
- }
package/src/config.ts DELETED
@@ -1,352 +0,0 @@
1
- import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
2
- import { homedir } from "os";
3
- import { join } from "path";
4
- import { getCredential, type AuthCredential } from "./utils/auth.js";
5
-
6
- export interface ProviderConfig {
7
- baseUrl: string;
8
- apiKey: string;
9
- model: string;
10
- type?: "openai" | "anthropic";
11
- }
12
-
13
- export interface ProviderProfile extends ProviderConfig {
14
- name: string;
15
- }
16
-
17
- export interface CodemaxxingConfig {
18
- provider: ProviderConfig;
19
- providers?: Record<string, ProviderProfile>;
20
- defaults: {
21
- autoApprove: boolean;
22
- contextFiles: number;
23
- maxTokens: number;
24
- contextCompressionThreshold?: number;
25
- architectModel?: string;
26
- autoLint?: boolean;
27
- stopOllamaOnExit?: boolean;
28
- };
29
- }
30
-
31
- export interface CLIArgs {
32
- model?: string;
33
- provider?: string;
34
- apiKey?: string;
35
- baseUrl?: string;
36
- }
37
-
38
- const CONFIG_DIR = join(homedir(), ".codemaxxing");
39
- const CONFIG_FILE = join(CONFIG_DIR, "settings.json");
40
-
41
- const DEFAULT_CONFIG: CodemaxxingConfig = {
42
- provider: {
43
- baseUrl: "http://localhost:1234/v1",
44
- apiKey: "not-needed",
45
- model: "auto",
46
- },
47
- providers: {
48
- local: {
49
- name: "Local (LM Studio/Ollama)",
50
- baseUrl: "http://localhost:1234/v1",
51
- apiKey: "not-needed",
52
- model: "auto",
53
- },
54
- },
55
- defaults: {
56
- autoApprove: false,
57
- contextFiles: 20,
58
- maxTokens: 8192,
59
- },
60
- };
61
-
62
- /**
63
- * Parse CLI arguments
64
- */
65
- export function parseCLIArgs(): CLIArgs {
66
- const args: CLIArgs = {};
67
- const argv = process.argv.slice(2);
68
-
69
- for (let i = 0; i < argv.length; i++) {
70
- const arg = argv[i];
71
- const next = argv[i + 1];
72
-
73
- if ((arg === "--model" || arg === "-m") && next) {
74
- args.model = next;
75
- i++;
76
- } else if ((arg === "--provider" || arg === "-p") && next) {
77
- args.provider = next;
78
- i++;
79
- } else if ((arg === "--api-key" || arg === "-k") && next) {
80
- args.apiKey = next;
81
- i++;
82
- } else if ((arg === "--base-url" || arg === "-u") && next) {
83
- args.baseUrl = next;
84
- i++;
85
- } else if (arg === "--help" || arg === "-h") {
86
- console.log(`
87
- codemaxxing — your code. your model. no excuses.
88
-
89
- Usage:
90
- codemaxxing [options]
91
- codemaxxing exec "prompt" [exec-options]
92
-
93
- Options:
94
- -m, --model <model> Model name to use
95
- -p, --provider <name> Provider profile from config (e.g. local, openrouter)
96
- -k, --api-key <key> API key for the provider
97
- -u, --base-url <url> Base URL for the provider API
98
- -h, --help Show this help
99
-
100
- Exec options (headless/CI mode):
101
- --auto-approve Skip tool approval prompts
102
- --json Output JSON instead of streaming text
103
- -m, --model <model> Model to use
104
- -p, --provider <name> Provider profile
105
-
106
- Examples:
107
- codemaxxing # Auto-detect local LLM
108
- codemaxxing -m gpt-4o -u https://api.openai.com/v1 -k sk-...
109
- codemaxxing -p openrouter # Use saved provider profile
110
- codemaxxing -m qwen3.5-35b # Override model only
111
- codemaxxing exec "fix the failing tests" # Headless mode
112
- echo "explain this code" | codemaxxing exec # Pipe input
113
-
114
- Config: ~/.codemaxxing/settings.json
115
- `);
116
- process.exit(0);
117
- }
118
- }
119
-
120
- return args;
121
- }
122
-
123
- export function loadConfig(): CodemaxxingConfig {
124
- if (!existsSync(CONFIG_DIR)) {
125
- mkdirSync(CONFIG_DIR, { recursive: true });
126
- }
127
-
128
- if (!existsSync(CONFIG_FILE)) {
129
- writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2));
130
- return DEFAULT_CONFIG;
131
- }
132
-
133
- try {
134
- const raw = readFileSync(CONFIG_FILE, "utf-8");
135
- const parsed = JSON.parse(raw);
136
- return { ...DEFAULT_CONFIG, ...parsed };
137
- } catch {
138
- return DEFAULT_CONFIG;
139
- }
140
- }
141
-
142
- /** Save config to disk (merges with existing) */
143
- export function saveConfig(updates: Partial<CodemaxxingConfig>): void {
144
- const current = loadConfig();
145
- const merged = {
146
- ...current,
147
- ...updates,
148
- defaults: { ...current.defaults, ...(updates.defaults ?? {}) },
149
- };
150
- if (!existsSync(CONFIG_DIR)) {
151
- mkdirSync(CONFIG_DIR, { recursive: true });
152
- }
153
- writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
154
- }
155
-
156
- /**
157
- * Apply CLI overrides to a provider config
158
- */
159
- export function applyOverrides(config: CodemaxxingConfig, args: CLIArgs): CodemaxxingConfig {
160
- const result = { ...config, provider: { ...config.provider } };
161
-
162
- // If a named provider profile is specified, use it as the base
163
- if (args.provider && config.providers?.[args.provider]) {
164
- const profile = config.providers[args.provider];
165
- result.provider = { ...profile };
166
- // Also check auth store for this provider
167
- const authCred = getCredential(args.provider);
168
- if (authCred) {
169
- result.provider.baseUrl = authCred.baseUrl;
170
- result.provider.apiKey = authCred.apiKey;
171
- }
172
- // Detect provider type
173
- result.provider.type = detectProviderType(args.provider, result.provider.baseUrl);
174
- }
175
-
176
- // CLI flags override everything
177
- if (args.model) result.provider.model = args.model;
178
- if (args.apiKey) result.provider.apiKey = args.apiKey;
179
- if (args.baseUrl) result.provider.baseUrl = args.baseUrl;
180
-
181
- // Auto-detect type from baseUrl if not set
182
- if (!result.provider.type && result.provider.baseUrl) {
183
- result.provider.type = detectProviderType(args.provider || "", result.provider.baseUrl);
184
- }
185
-
186
- return result;
187
- }
188
-
189
- export function getConfigPath(): string {
190
- return CONFIG_FILE;
191
- }
192
-
193
- /**
194
- * Auto-detect local LLM servers
195
- */
196
- export type DetectionResult =
197
- | { status: "connected"; provider: ProviderConfig }
198
- | { status: "no-models"; serverName: string; baseUrl: string }
199
- | { status: "no-server" };
200
-
201
- export async function detectLocalProviderDetailed(): Promise<DetectionResult> {
202
- const endpoints = [
203
- { name: "LM Studio", url: "http://localhost:1234/v1" },
204
- { name: "Ollama", url: "http://localhost:11434/v1" },
205
- { name: "vLLM", url: "http://localhost:8000/v1" },
206
- ];
207
-
208
- let serverFound: { name: string; url: string } | null = null;
209
-
210
- for (const endpoint of endpoints) {
211
- try {
212
- const controller = new AbortController();
213
- const timeout = setTimeout(() => controller.abort(), 2000);
214
- const res = await fetch(`${endpoint.url}/models`, {
215
- signal: controller.signal,
216
- });
217
- clearTimeout(timeout);
218
-
219
- if (res.ok) {
220
- const data = (await res.json()) as { data?: Array<{ id: string }> };
221
- const models = data.data ?? [];
222
- if (models.length === 0) {
223
- // Server is up but no models — remember it but keep looking
224
- if (!serverFound) serverFound = endpoint;
225
- continue;
226
- }
227
- return {
228
- status: "connected",
229
- provider: {
230
- baseUrl: endpoint.url,
231
- apiKey: "not-needed",
232
- model: models[0]!.id,
233
- },
234
- };
235
- }
236
- } catch {
237
- // Server not running, try next
238
- }
239
- }
240
-
241
- if (serverFound) {
242
- return { status: "no-models", serverName: serverFound.name, baseUrl: serverFound.url };
243
- }
244
- return { status: "no-server" };
245
- }
246
-
247
- export async function detectLocalProvider(): Promise<ProviderConfig | null> {
248
- const endpoints = [
249
- { name: "LM Studio", url: "http://localhost:1234/v1" },
250
- { name: "Ollama", url: "http://localhost:11434/v1" },
251
- { name: "vLLM", url: "http://localhost:8000/v1" },
252
- ];
253
-
254
- for (const endpoint of endpoints) {
255
- try {
256
- const controller = new AbortController();
257
- const timeout = setTimeout(() => controller.abort(), 2000);
258
- const res = await fetch(`${endpoint.url}/models`, {
259
- signal: controller.signal,
260
- });
261
- clearTimeout(timeout);
262
-
263
- if (res.ok) {
264
- const data = (await res.json()) as { data?: Array<{ id: string }> };
265
- const models = data.data ?? [];
266
- if (models.length === 0) {
267
- // Server is up but no models available — don't fake a connection
268
- continue;
269
- }
270
- const model = models[0]!.id;
271
- return {
272
- baseUrl: endpoint.url,
273
- apiKey: "not-needed",
274
- model,
275
- };
276
- }
277
- } catch {
278
- // Server not running, try next
279
- }
280
- }
281
-
282
- return null;
283
- }
284
-
285
- /**
286
- * List available models from a provider endpoint
287
- */
288
- export async function listModels(baseUrl: string, apiKey: string): Promise<string[]> {
289
- try {
290
- const controller = new AbortController();
291
- const timeout = setTimeout(() => controller.abort(), 5000);
292
- const headers: Record<string, string> = {};
293
- if (apiKey && apiKey !== "not-needed") {
294
- headers["Authorization"] = `Bearer ${apiKey}`;
295
- }
296
- const res = await fetch(`${baseUrl}/models`, {
297
- signal: controller.signal,
298
- headers,
299
- });
300
- clearTimeout(timeout);
301
-
302
- if (res.ok) {
303
- const data = (await res.json()) as { data?: Array<{ id: string }> };
304
- return (data.data ?? []).map(m => m.id);
305
- }
306
- } catch { /* ignore */ }
307
- return [];
308
- }
309
-
310
- /**
311
- * Resolve provider configuration from auth store or config file
312
- * Priority: CLI args > auth store > config file > auto-detect
313
- */
314
- export function resolveProvider(
315
- providerId: string,
316
- cliArgs: CLIArgs
317
- ): ProviderConfig | null {
318
- // Check auth store first
319
- const authCred = getCredential(providerId);
320
- if (authCred) {
321
- return {
322
- baseUrl: authCred.baseUrl,
323
- apiKey: authCred.apiKey,
324
- model: cliArgs.model || "auto",
325
- type: detectProviderType(providerId, authCred.baseUrl),
326
- };
327
- }
328
-
329
- // Fall back to config file
330
- const config = loadConfig();
331
- const provider = config.providers?.[providerId];
332
- if (provider) {
333
- return {
334
- baseUrl: provider.baseUrl,
335
- apiKey: cliArgs.apiKey || provider.apiKey,
336
- model: cliArgs.model || provider.model,
337
- type: detectProviderType(providerId, provider.baseUrl),
338
- };
339
- }
340
-
341
- return null;
342
- }
343
-
344
- /**
345
- * Detect provider type from ID or base URL
346
- */
347
- function detectProviderType(providerId: string, baseUrl: string): "openai" | "anthropic" {
348
- if (providerId === "anthropic" || baseUrl.includes("anthropic.com")) {
349
- return "anthropic";
350
- }
351
- return "openai";
352
- }