lazyclaw 6.1.0 → 6.2.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/package.json +1 -1
- package/providers/codex_cli.mjs +17 -1
- package/providers/compat_vendors.mjs +161 -0
- package/providers/gemini_cli.mjs +19 -3
- package/providers/model_catalogue.mjs +142 -9
- package/providers/registry.mjs +31 -162
- package/tui/pickers.mjs +3 -3
- package/tui/provider_families.mjs +8 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazyclaw",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "Lazy, elegant terminal CLI for chatting with Claude / OpenAI / Gemini / Ollama, orchestrating multi-step LLM workflows, and running multi-agent Slack teams with cross-task memory. Banner-on-launch, slash-command ghost autocomplete, persistent sessions, local HTTP gateway.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/providers/codex_cli.mjs
CHANGED
|
@@ -144,6 +144,16 @@ export const codexCliProvider = {
|
|
|
144
144
|
const onAbort = () => { try { proc.kill('SIGTERM'); } catch (_) { /* ignore */ } };
|
|
145
145
|
if (opts.signal) opts.signal.addEventListener('abort', onAbort);
|
|
146
146
|
|
|
147
|
+
// A missing binary surfaces as an ASYNC ChildProcess 'error' event on
|
|
148
|
+
// some platforms (the sync try/catch above doesn't see it). Without a
|
|
149
|
+
// listener that's an uncaughtException that kills the WHOLE process mid
|
|
150
|
+
// `providers test` — capture it and surface a per-provider CliMissingError
|
|
151
|
+
// instead (same fix claude_cli.mjs received in F8).
|
|
152
|
+
let spawnError = null;
|
|
153
|
+
const spawnErrorPromise = new Promise((resolve) => {
|
|
154
|
+
proc.once('error', (err) => { spawnError = err; resolve(); });
|
|
155
|
+
});
|
|
156
|
+
|
|
147
157
|
let stderr = '';
|
|
148
158
|
proc.stderr.setEncoding('utf8');
|
|
149
159
|
proc.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
@@ -184,7 +194,13 @@ export const codexCliProvider = {
|
|
|
184
194
|
if (text) yield text;
|
|
185
195
|
} catch (_) { /* incomplete tail — drop */ }
|
|
186
196
|
}
|
|
187
|
-
|
|
197
|
+
// Wait for either a clean exit or an async spawn error. On ENOENT the
|
|
198
|
+
// process never starts, so 'close' never fires — racing against
|
|
199
|
+
// spawnErrorPromise keeps this from hanging forever.
|
|
200
|
+
await Promise.race([exitPromise, spawnErrorPromise]);
|
|
201
|
+
if (spawnError) {
|
|
202
|
+
throw spawnError.code === 'ENOENT' ? new CliMissingError() : spawnError;
|
|
203
|
+
}
|
|
188
204
|
if (exitInfo && exitInfo.code !== 0 && !opts.signal?.aborted) {
|
|
189
205
|
throw new CliExitError(exitInfo.code, exitInfo.signal, stderr);
|
|
190
206
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// providers/compat_vendors.mjs — the built-in OpenAI-compatible vendor
|
|
2
|
+
// catalogue, split out of registry.mjs (file-size ratchet). Pure data:
|
|
3
|
+
// registry.mjs imports this and expands it into PROVIDERS / PROVIDER_INFO.
|
|
4
|
+
|
|
5
|
+
// Built-in OpenAI-compatible vendors. Same wire format → one factory call
|
|
6
|
+
// each. The picker treats these like first-class providers so users don't
|
|
7
|
+
// have to walk through "+ Add a custom endpoint" for the popular ones.
|
|
8
|
+
//
|
|
9
|
+
// Each entry must define baseUrl + envKey (the env var the chat path
|
|
10
|
+
// consults when no api-key is configured) + suggestedModels (curated list
|
|
11
|
+
// shown before the user fetches the live /v1/models catalogue).
|
|
12
|
+
//
|
|
13
|
+
// Adding a new vendor: drop a row here. The PROVIDERS / PROVIDER_INFO loops
|
|
14
|
+
// below pick it up automatically.
|
|
15
|
+
export const OPENAI_COMPAT_BUILTINS = {
|
|
16
|
+
nim: {
|
|
17
|
+
label: 'NVIDIA NIM',
|
|
18
|
+
baseUrl: 'https://integrate.api.nvidia.com/v1',
|
|
19
|
+
envKey: 'NVIDIA_API_KEY',
|
|
20
|
+
altEnvKeys: ['NIM_API_KEY'],
|
|
21
|
+
keyPrefix: 'nvapi-',
|
|
22
|
+
docs: 'NVIDIA NIM hosted catalogue (Llama 3.x, Nemotron, DeepSeek-R1, Mixtral, Phi-3, Qwen, etc.). Auth: NVIDIA_API_KEY env var or in-app api-key. Endpoint speaks the OpenAI v1 wire format.',
|
|
23
|
+
defaultModel: 'meta/llama-3.1-405b-instruct',
|
|
24
|
+
suggestedModels: [
|
|
25
|
+
'meta/llama-3.1-405b-instruct',
|
|
26
|
+
'meta/llama-3.1-70b-instruct',
|
|
27
|
+
'meta/llama-3.1-8b-instruct',
|
|
28
|
+
'nvidia/llama-3.1-nemotron-70b-instruct',
|
|
29
|
+
'nvidia/nemotron-mini-4b-instruct',
|
|
30
|
+
'nvidia/llama-3.3-nemotron-super-49b-v1',
|
|
31
|
+
'mistralai/mistral-nemo-12b-instruct',
|
|
32
|
+
'mistralai/mixtral-8x22b-instruct-v0.1',
|
|
33
|
+
'microsoft/phi-3-medium-4k-instruct',
|
|
34
|
+
'deepseek-ai/deepseek-r1',
|
|
35
|
+
'qwen/qwen2.5-7b-instruct',
|
|
36
|
+
'qwen/qwen2.5-coder-32b-instruct',
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
openrouter: {
|
|
40
|
+
label: 'OpenRouter',
|
|
41
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
42
|
+
envKey: 'OPENROUTER_API_KEY',
|
|
43
|
+
keyPrefix: 'sk-or-',
|
|
44
|
+
docs: 'OpenRouter unified gateway — 200+ models behind one OpenAI-compatible endpoint. Auth: OPENROUTER_API_KEY env var or in-app api-key. Uses x-title/HTTP-Referer headers for attribution.',
|
|
45
|
+
defaultModel: 'anthropic/claude-3.5-sonnet',
|
|
46
|
+
headers: { 'http-referer': 'https://github.com/cmblir/lazyclaude', 'x-title': 'lazyclaw' },
|
|
47
|
+
suggestedModels: [
|
|
48
|
+
'anthropic/claude-3.5-sonnet',
|
|
49
|
+
'anthropic/claude-3-opus',
|
|
50
|
+
'openai/gpt-4o',
|
|
51
|
+
'openai/gpt-4o-mini',
|
|
52
|
+
'openai/o1-preview',
|
|
53
|
+
'meta-llama/llama-3.1-405b-instruct',
|
|
54
|
+
'meta-llama/llama-3.3-70b-instruct',
|
|
55
|
+
'google/gemini-2.0-flash-exp:free',
|
|
56
|
+
'google/gemini-pro-1.5',
|
|
57
|
+
'deepseek/deepseek-chat',
|
|
58
|
+
'deepseek/deepseek-r1',
|
|
59
|
+
'qwen/qwen-2.5-coder-32b-instruct',
|
|
60
|
+
'mistralai/mistral-large',
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
groq: {
|
|
64
|
+
label: 'Groq',
|
|
65
|
+
baseUrl: 'https://api.groq.com/openai/v1',
|
|
66
|
+
envKey: 'GROQ_API_KEY',
|
|
67
|
+
keyPrefix: 'gsk_',
|
|
68
|
+
docs: 'Groq LPU inference — fastest-token-per-second tier for Llama / Mixtral / Gemma. Auth: GROQ_API_KEY env var or in-app api-key.',
|
|
69
|
+
defaultModel: 'llama-3.3-70b-versatile',
|
|
70
|
+
suggestedModels: [
|
|
71
|
+
'llama-3.3-70b-versatile',
|
|
72
|
+
'llama-3.1-70b-versatile',
|
|
73
|
+
'llama-3.1-8b-instant',
|
|
74
|
+
'llama-3.2-90b-vision-preview',
|
|
75
|
+
'mixtral-8x7b-32768',
|
|
76
|
+
'gemma2-9b-it',
|
|
77
|
+
'qwen-2.5-coder-32b',
|
|
78
|
+
'qwen-2.5-32b',
|
|
79
|
+
'deepseek-r1-distill-llama-70b',
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
together: {
|
|
83
|
+
label: 'Together AI',
|
|
84
|
+
baseUrl: 'https://api.together.xyz/v1',
|
|
85
|
+
envKey: 'TOGETHER_API_KEY',
|
|
86
|
+
docs: 'Together AI hosted inference for open-weight models (Llama, Mixtral, Qwen, DeepSeek, etc.). Auth: TOGETHER_API_KEY env var or in-app api-key.',
|
|
87
|
+
defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
|
|
88
|
+
suggestedModels: [
|
|
89
|
+
'meta-llama/Llama-3.3-70B-Instruct-Turbo',
|
|
90
|
+
'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
|
|
91
|
+
'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
|
|
92
|
+
'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo',
|
|
93
|
+
'mistralai/Mixtral-8x22B-Instruct-v0.1',
|
|
94
|
+
'mistralai/Mixtral-8x7B-Instruct-v0.1',
|
|
95
|
+
'Qwen/Qwen2.5-72B-Instruct-Turbo',
|
|
96
|
+
'Qwen/Qwen2.5-Coder-32B-Instruct',
|
|
97
|
+
'deepseek-ai/DeepSeek-V3',
|
|
98
|
+
'deepseek-ai/DeepSeek-R1',
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
xai: {
|
|
102
|
+
label: 'xAI (Grok)',
|
|
103
|
+
baseUrl: 'https://api.x.ai/v1',
|
|
104
|
+
envKey: 'XAI_API_KEY',
|
|
105
|
+
altEnvKeys: ['GROK_API_KEY'],
|
|
106
|
+
keyPrefix: 'xai-',
|
|
107
|
+
docs: 'xAI Grok models. Auth: XAI_API_KEY env var or in-app api-key.',
|
|
108
|
+
defaultModel: 'grok-2-latest',
|
|
109
|
+
suggestedModels: [
|
|
110
|
+
'grok-2-latest',
|
|
111
|
+
'grok-2-1212',
|
|
112
|
+
'grok-2-vision-1212',
|
|
113
|
+
'grok-beta',
|
|
114
|
+
'grok-vision-beta',
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
deepseek: {
|
|
118
|
+
label: 'DeepSeek',
|
|
119
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
120
|
+
envKey: 'DEEPSEEK_API_KEY',
|
|
121
|
+
keyPrefix: 'sk-',
|
|
122
|
+
docs: 'DeepSeek (deepseek-chat / deepseek-reasoner). Auth: DEEPSEEK_API_KEY env var or in-app api-key.',
|
|
123
|
+
defaultModel: 'deepseek-chat',
|
|
124
|
+
suggestedModels: [
|
|
125
|
+
'deepseek-chat',
|
|
126
|
+
'deepseek-reasoner',
|
|
127
|
+
'deepseek-coder',
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
mistral: {
|
|
131
|
+
label: 'Mistral La Plateforme',
|
|
132
|
+
baseUrl: 'https://api.mistral.ai/v1',
|
|
133
|
+
envKey: 'MISTRAL_API_KEY',
|
|
134
|
+
docs: 'Mistral La Plateforme (mistral-large, codestral, ministral, pixtral). Auth: MISTRAL_API_KEY env var or in-app api-key.',
|
|
135
|
+
defaultModel: 'mistral-large-latest',
|
|
136
|
+
suggestedModels: [
|
|
137
|
+
'mistral-large-latest',
|
|
138
|
+
'mistral-small-latest',
|
|
139
|
+
'codestral-latest',
|
|
140
|
+
'ministral-8b-latest',
|
|
141
|
+
'ministral-3b-latest',
|
|
142
|
+
'pixtral-large-latest',
|
|
143
|
+
'open-mistral-nemo',
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
fireworks: {
|
|
147
|
+
label: 'Fireworks AI',
|
|
148
|
+
baseUrl: 'https://api.fireworks.ai/inference/v1',
|
|
149
|
+
envKey: 'FIREWORKS_API_KEY',
|
|
150
|
+
docs: 'Fireworks AI hosted models. Auth: FIREWORKS_API_KEY env var or in-app api-key.',
|
|
151
|
+
defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',
|
|
152
|
+
suggestedModels: [
|
|
153
|
+
'accounts/fireworks/models/llama-v3p3-70b-instruct',
|
|
154
|
+
'accounts/fireworks/models/llama-v3p1-405b-instruct',
|
|
155
|
+
'accounts/fireworks/models/qwen2p5-coder-32b-instruct',
|
|
156
|
+
'accounts/fireworks/models/deepseek-r1',
|
|
157
|
+
'accounts/fireworks/models/deepseek-v3',
|
|
158
|
+
'accounts/fireworks/models/mixtral-8x22b-instruct',
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
};
|
package/providers/gemini_cli.mjs
CHANGED
|
@@ -133,6 +133,16 @@ export const geminiCliProvider = {
|
|
|
133
133
|
const onAbort = () => { try { proc.kill('SIGTERM'); } catch (_) { /* ignore */ } };
|
|
134
134
|
if (opts.signal) opts.signal.addEventListener('abort', onAbort);
|
|
135
135
|
|
|
136
|
+
// A missing binary surfaces as an ASYNC ChildProcess 'error' event on
|
|
137
|
+
// some platforms (the sync try/catch above doesn't see it). Without a
|
|
138
|
+
// listener that's an uncaughtException that kills the WHOLE process mid
|
|
139
|
+
// `providers test` — capture it and surface a per-provider CliMissingError
|
|
140
|
+
// instead (same fix claude_cli.mjs received in F8).
|
|
141
|
+
let spawnError = null;
|
|
142
|
+
const spawnErrorPromise = new Promise((resolve) => {
|
|
143
|
+
proc.once('error', (err) => { spawnError = err; resolve(); });
|
|
144
|
+
});
|
|
145
|
+
|
|
136
146
|
let stdout = '';
|
|
137
147
|
let stderr = '';
|
|
138
148
|
proc.stdout.setEncoding('utf8');
|
|
@@ -140,9 +150,15 @@ export const geminiCliProvider = {
|
|
|
140
150
|
proc.stdout.on('data', (c) => { stdout += c; });
|
|
141
151
|
proc.stderr.on('data', (c) => { stderr += c; });
|
|
142
152
|
|
|
143
|
-
const exitInfo = await
|
|
144
|
-
proc.on('close', (code, signal) => resolve({ code, signal }));
|
|
145
|
-
|
|
153
|
+
const exitInfo = await Promise.race([
|
|
154
|
+
new Promise((resolve) => { proc.on('close', (code, signal) => resolve({ code, signal })); }),
|
|
155
|
+
spawnErrorPromise.then(() => null),
|
|
156
|
+
]);
|
|
157
|
+
if (spawnError) {
|
|
158
|
+
if (opts.signal) opts.signal.removeEventListener('abort', onAbort);
|
|
159
|
+
if (spawnError.code === 'ENOENT') throw new CliMissingError();
|
|
160
|
+
throw spawnError;
|
|
161
|
+
}
|
|
146
162
|
|
|
147
163
|
try {
|
|
148
164
|
if (opts.signal?.aborted) throw new AbortError('aborted mid-run');
|
|
@@ -11,13 +11,18 @@
|
|
|
11
11
|
// Dependency-injected (no cli.mjs internals) so it stays import-light and
|
|
12
12
|
// unit-testable with no network.
|
|
13
13
|
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
|
-
* Whether a provider exposes
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* endpoints
|
|
20
|
-
*
|
|
19
|
+
* Whether a provider exposes a model catalogue we can live-fetch. True for
|
|
20
|
+
* openai, ollama, any builtin OpenAI-compat vendor (nim / openrouter / groq /
|
|
21
|
+
* together / xai / deepseek / mistral / fireworks), any provider carrying an
|
|
22
|
+
* explicit `baseUrl` (custom endpoints), and — via their NATIVE list
|
|
23
|
+
* endpoints — anthropic (`GET /v1/models`) and gemini
|
|
24
|
+
* (`GET /v1beta/models`). False for claude-cli (keyless subprocess, no
|
|
25
|
+
* catalogue endpoint) / mock / orchestrator.
|
|
21
26
|
*
|
|
22
27
|
* @param {object} meta PROVIDER_INFO[providerId]
|
|
23
28
|
* @param {string} providerId
|
|
@@ -28,9 +33,70 @@ export function supportsLiveFetch(meta, providerId) {
|
|
|
28
33
|
return !!m.baseUrl
|
|
29
34
|
|| providerId === 'openai'
|
|
30
35
|
|| providerId === 'ollama'
|
|
36
|
+
|| providerId === 'anthropic'
|
|
37
|
+
|| providerId === 'gemini'
|
|
38
|
+
// Keyless CLI providers borrow the credential their vendor accepts
|
|
39
|
+
// (anthropic key / Claude Code OAuth token; gemini key; openai key or
|
|
40
|
+
// a plain key stored in ~/.codex/auth.json) — best-effort with an
|
|
41
|
+
// honest, actionable error when none is available.
|
|
42
|
+
|| providerId === 'claude-cli'
|
|
43
|
+
|| providerId === 'gemini-cli'
|
|
44
|
+
|| providerId === 'codex-cli'
|
|
31
45
|
|| !!m.builtinOpenAICompat;
|
|
32
46
|
}
|
|
33
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Live-list Anthropic models via the native Models API. Surfaces newly
|
|
50
|
+
* released models (e.g. claude-fable-5) the day they ship instead of waiting
|
|
51
|
+
* for a curated-list update. Sorted, deduped.
|
|
52
|
+
*
|
|
53
|
+
* @param {{apiKey:string, fetchImpl?:typeof fetch}} opts
|
|
54
|
+
* @returns {Promise<string[]>}
|
|
55
|
+
*/
|
|
56
|
+
export async function fetchAnthropicModels({ apiKey, oauthToken, fetchImpl } = {}) {
|
|
57
|
+
if (!apiKey && !oauthToken) throw new Error('anthropic model listing requires an api key (set ANTHROPIC_API_KEY or configure the provider)');
|
|
58
|
+
const f = fetchImpl || globalThis.fetch;
|
|
59
|
+
// OAuth tokens (Claude Code subscription login) authenticate with a Bearer
|
|
60
|
+
// header plus the documented oauth beta header; api keys use x-api-key.
|
|
61
|
+
const auth = apiKey
|
|
62
|
+
? { 'x-api-key': apiKey }
|
|
63
|
+
: { 'authorization': `Bearer ${oauthToken}`, 'anthropic-beta': 'oauth-2025-04-20' };
|
|
64
|
+
const res = await f('https://api.anthropic.com/v1/models?limit=1000', {
|
|
65
|
+
method: 'GET',
|
|
66
|
+
headers: { ...auth, 'anthropic-version': '2023-06-01', 'accept': 'application/json' },
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok) throw new Error(`anthropic /v1/models returned HTTP ${res.status}`);
|
|
69
|
+
const obj = await res.json();
|
|
70
|
+
const ids = (Array.isArray(obj?.data) ? obj.data : [])
|
|
71
|
+
.map((m) => m && m.id)
|
|
72
|
+
.filter((id) => typeof id === 'string');
|
|
73
|
+
return Array.from(new Set(ids)).sort((a, b) => a.localeCompare(b));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Live-list Gemini models via the Generative Language API, keeping only
|
|
78
|
+
* chat-capable entries (supportedGenerationMethods includes
|
|
79
|
+
* generateContent) and stripping the `models/` resource prefix.
|
|
80
|
+
*
|
|
81
|
+
* @param {{apiKey:string, fetchImpl?:typeof fetch}} opts
|
|
82
|
+
* @returns {Promise<string[]>}
|
|
83
|
+
*/
|
|
84
|
+
export async function fetchGeminiModels({ apiKey, fetchImpl } = {}) {
|
|
85
|
+
if (!apiKey) throw new Error('gemini model listing requires an api key (set GEMINI_API_KEY or configure the provider)');
|
|
86
|
+
const f = fetchImpl || globalThis.fetch;
|
|
87
|
+
const res = await f(`https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000&key=${encodeURIComponent(apiKey)}`, {
|
|
88
|
+
method: 'GET',
|
|
89
|
+
headers: { 'accept': 'application/json' },
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok) throw new Error(`gemini models list returned HTTP ${res.status}`);
|
|
92
|
+
const obj = await res.json();
|
|
93
|
+
const ids = (Array.isArray(obj?.models) ? obj.models : [])
|
|
94
|
+
.filter((m) => Array.isArray(m?.supportedGenerationMethods) && m.supportedGenerationMethods.includes('generateContent'))
|
|
95
|
+
.map((m) => String(m.name || '').replace(/^models\//, ''))
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
return Array.from(new Set(ids)).sort((a, b) => a.localeCompare(b));
|
|
98
|
+
}
|
|
99
|
+
|
|
34
100
|
/**
|
|
35
101
|
* Resolve `{ baseUrl, apiKey }` for a provider's OpenAI-compatible
|
|
36
102
|
* `/v1/models` endpoint. Returns `null` when the provider has no such
|
|
@@ -75,12 +141,79 @@ export function modelCatalogueFor({ cfg, registryMod, resolveAuthKey, providerId
|
|
|
75
141
|
* @param {object} deps same shape as {@link modelCatalogueFor}
|
|
76
142
|
* @returns {Promise<string[]>}
|
|
77
143
|
*/
|
|
144
|
+
// Claude Code OAuth token from the credential store `claude login` writes on
|
|
145
|
+
// Linux / non-keychain setups. On macOS the token lives in the OS Keychain
|
|
146
|
+
// (no file), so this returns null there — the caller falls through to its
|
|
147
|
+
// honest "set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN" error. Read-only;
|
|
148
|
+
// the token is only ever sent to api.anthropic.com, never logged.
|
|
149
|
+
export function _claudeCodeOAuthToken({ home, readFileSync } = {}) {
|
|
150
|
+
const h = home || os.homedir();
|
|
151
|
+
const read = readFileSync || fs.readFileSync;
|
|
152
|
+
for (const rel of ['.claude/.credentials.json', '.config/claude/.credentials.json']) {
|
|
153
|
+
try {
|
|
154
|
+
const j = JSON.parse(read(path.join(h, rel), 'utf8'));
|
|
155
|
+
const tok = j?.claudeAiOauth?.accessToken || j?.accessToken || j?.access_token;
|
|
156
|
+
if (typeof tok === 'string' && tok) return tok;
|
|
157
|
+
} catch { /* missing / unreadable / not JSON — try the next location */ }
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// A plain API key stored by `codex login --api-key` in ~/.codex/auth.json.
|
|
163
|
+
// ChatGPT-OAuth logins store an empty OPENAI_API_KEY object plus OAuth
|
|
164
|
+
// tokens — those do NOT authenticate the platform /v1/models endpoint, so
|
|
165
|
+
// they are deliberately ignored.
|
|
166
|
+
export function _codexStoredApiKey({ home, readFileSync } = {}) {
|
|
167
|
+
const h = home || os.homedir();
|
|
168
|
+
const read = readFileSync || fs.readFileSync;
|
|
169
|
+
try {
|
|
170
|
+
const j = JSON.parse(read(path.join(h, '.codex/auth.json'), 'utf8'));
|
|
171
|
+
const k = j?.OPENAI_API_KEY;
|
|
172
|
+
if (typeof k === 'string' && k) return k;
|
|
173
|
+
if (k && typeof k === 'object' && typeof k.value === 'string' && k.value) return k.value;
|
|
174
|
+
} catch { /* missing / unreadable */ }
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
78
178
|
export async function fetchModelsForProvider(deps) {
|
|
79
|
-
const c = modelCatalogueFor(deps);
|
|
80
179
|
const providerId = deps && deps.providerId;
|
|
180
|
+
const key = (id) => (typeof deps?.resolveAuthKey === 'function' ? deps.resolveAuthKey(id) : '') || '';
|
|
181
|
+
const credReader = deps?._credReader; // test seam: () => token|null per helper
|
|
182
|
+
// Native-API providers list through their own endpoints (they are not
|
|
183
|
+
// OpenAI-compatible). Env fallbacks cover the common keyless-config case.
|
|
184
|
+
if (providerId === 'anthropic') {
|
|
185
|
+
return fetchAnthropicModels({ apiKey: key('anthropic') || process.env.ANTHROPIC_API_KEY || '', fetchImpl: deps?.fetchImpl });
|
|
186
|
+
}
|
|
187
|
+
if (providerId === 'gemini') {
|
|
188
|
+
return fetchGeminiModels({ apiKey: key('gemini') || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || '', fetchImpl: deps?.fetchImpl });
|
|
189
|
+
}
|
|
190
|
+
// Keyless CLI providers: borrow the credential their vendor accepts.
|
|
191
|
+
if (providerId === 'claude-cli') {
|
|
192
|
+
const apiKey = key('claude-cli') || key('anthropic') || process.env.ANTHROPIC_API_KEY || '';
|
|
193
|
+
if (apiKey) return fetchAnthropicModels({ apiKey, fetchImpl: deps?.fetchImpl });
|
|
194
|
+
const oauthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
195
|
+
|| (credReader ? credReader('claude') : _claudeCodeOAuthToken());
|
|
196
|
+
if (oauthToken) return fetchAnthropicModels({ oauthToken, fetchImpl: deps?.fetchImpl });
|
|
197
|
+
throw new Error('claude-cli model listing needs a credential: set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN (on macOS the `claude` login lives in the Keychain, which is not readable here)');
|
|
198
|
+
}
|
|
199
|
+
if (providerId === 'gemini-cli') {
|
|
200
|
+
const apiKey = key('gemini-cli') || key('gemini') || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || '';
|
|
201
|
+
if (apiKey) return fetchGeminiModels({ apiKey, fetchImpl: deps?.fetchImpl });
|
|
202
|
+
throw new Error('gemini-cli model listing needs a credential: set GEMINI_API_KEY or GOOGLE_API_KEY (the `gemini` CLI login token cannot list models)');
|
|
203
|
+
}
|
|
204
|
+
if (providerId === 'codex-cli') {
|
|
205
|
+
const apiKey = key('codex-cli') || key('openai') || process.env.OPENAI_API_KEY
|
|
206
|
+
|| (credReader ? credReader('codex') : _codexStoredApiKey()) || '';
|
|
207
|
+
if (apiKey) {
|
|
208
|
+
const { fetchOpenAICompatModels } = await import('./openai_compat.mjs');
|
|
209
|
+
return fetchOpenAICompatModels({ baseUrl: 'https://api.openai.com/v1', apiKey, fetch: deps?.fetchImpl });
|
|
210
|
+
}
|
|
211
|
+
throw new Error('codex-cli model listing needs a credential: set OPENAI_API_KEY (a ChatGPT-plan `codex` login has no platform API key)');
|
|
212
|
+
}
|
|
213
|
+
const c = modelCatalogueFor(deps);
|
|
81
214
|
if (!c) {
|
|
82
|
-
throw new Error(`provider "${providerId}" does not expose
|
|
215
|
+
throw new Error(`provider "${providerId}" does not expose a model catalogue endpoint`);
|
|
83
216
|
}
|
|
84
217
|
const { fetchOpenAICompatModels } = await import('./openai_compat.mjs');
|
|
85
|
-
return fetchOpenAICompatModels({ baseUrl: c.baseUrl, apiKey: c.apiKey });
|
|
218
|
+
return fetchOpenAICompatModels({ baseUrl: c.baseUrl, apiKey: c.apiKey, fetch: deps?.fetchImpl });
|
|
86
219
|
}
|
package/providers/registry.mjs
CHANGED
|
@@ -12,9 +12,12 @@ import { openaiProvider } from './openai.mjs';
|
|
|
12
12
|
import { ollamaProvider } from './ollama.mjs';
|
|
13
13
|
import { geminiProvider } from './gemini.mjs';
|
|
14
14
|
import { claudeCliProvider } from './claude_cli.mjs';
|
|
15
|
+
import { geminiCliProvider } from './gemini_cli.mjs';
|
|
16
|
+
import { codexCliProvider } from './codex_cli.mjs';
|
|
15
17
|
import { hasClaudeCliSession } from './claude_cli_detect.mjs';
|
|
16
18
|
import { makeOpenAICompatProvider, fetchOpenAICompatModels } from './openai_compat.mjs';
|
|
17
19
|
import { makeOrchestratorProvider } from './orchestrator.mjs';
|
|
20
|
+
import { OPENAI_COMPAT_BUILTINS } from './compat_vendors.mjs';
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* @typedef {{ role: 'user'|'assistant'|'system', content: string }} ChatMessage
|
|
@@ -116,163 +119,10 @@ export function resolveTrainer(cfg, opts = {}) {
|
|
|
116
119
|
return { provider: t.provider, model: t.model || chatModel };
|
|
117
120
|
}
|
|
118
121
|
|
|
119
|
-
// Built-in OpenAI-compatible
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
// Each entry must define baseUrl + envKey (the env var the chat path
|
|
124
|
-
// consults when no api-key is configured) + suggestedModels (curated list
|
|
125
|
-
// shown before the user fetches the live /v1/models catalogue).
|
|
126
|
-
//
|
|
127
|
-
// Adding a new vendor: drop a row here. The PROVIDERS / PROVIDER_INFO loops
|
|
128
|
-
// below pick it up automatically.
|
|
129
|
-
export const OPENAI_COMPAT_BUILTINS = {
|
|
130
|
-
nim: {
|
|
131
|
-
label: 'NVIDIA NIM',
|
|
132
|
-
baseUrl: 'https://integrate.api.nvidia.com/v1',
|
|
133
|
-
envKey: 'NVIDIA_API_KEY',
|
|
134
|
-
altEnvKeys: ['NIM_API_KEY'],
|
|
135
|
-
keyPrefix: 'nvapi-',
|
|
136
|
-
docs: 'NVIDIA NIM hosted catalogue (Llama 3.x, Nemotron, DeepSeek-R1, Mixtral, Phi-3, Qwen, etc.). Auth: NVIDIA_API_KEY env var or in-app api-key. Endpoint speaks the OpenAI v1 wire format.',
|
|
137
|
-
defaultModel: 'meta/llama-3.1-405b-instruct',
|
|
138
|
-
suggestedModels: [
|
|
139
|
-
'meta/llama-3.1-405b-instruct',
|
|
140
|
-
'meta/llama-3.1-70b-instruct',
|
|
141
|
-
'meta/llama-3.1-8b-instruct',
|
|
142
|
-
'nvidia/llama-3.1-nemotron-70b-instruct',
|
|
143
|
-
'nvidia/nemotron-mini-4b-instruct',
|
|
144
|
-
'nvidia/llama-3.3-nemotron-super-49b-v1',
|
|
145
|
-
'mistralai/mistral-nemo-12b-instruct',
|
|
146
|
-
'mistralai/mixtral-8x22b-instruct-v0.1',
|
|
147
|
-
'microsoft/phi-3-medium-4k-instruct',
|
|
148
|
-
'deepseek-ai/deepseek-r1',
|
|
149
|
-
'qwen/qwen2.5-7b-instruct',
|
|
150
|
-
'qwen/qwen2.5-coder-32b-instruct',
|
|
151
|
-
],
|
|
152
|
-
},
|
|
153
|
-
openrouter: {
|
|
154
|
-
label: 'OpenRouter',
|
|
155
|
-
baseUrl: 'https://openrouter.ai/api/v1',
|
|
156
|
-
envKey: 'OPENROUTER_API_KEY',
|
|
157
|
-
keyPrefix: 'sk-or-',
|
|
158
|
-
docs: 'OpenRouter unified gateway — 200+ models behind one OpenAI-compatible endpoint. Auth: OPENROUTER_API_KEY env var or in-app api-key. Uses x-title/HTTP-Referer headers for attribution.',
|
|
159
|
-
defaultModel: 'anthropic/claude-3.5-sonnet',
|
|
160
|
-
headers: { 'http-referer': 'https://github.com/cmblir/lazyclaude', 'x-title': 'lazyclaw' },
|
|
161
|
-
suggestedModels: [
|
|
162
|
-
'anthropic/claude-3.5-sonnet',
|
|
163
|
-
'anthropic/claude-3-opus',
|
|
164
|
-
'openai/gpt-4o',
|
|
165
|
-
'openai/gpt-4o-mini',
|
|
166
|
-
'openai/o1-preview',
|
|
167
|
-
'meta-llama/llama-3.1-405b-instruct',
|
|
168
|
-
'meta-llama/llama-3.3-70b-instruct',
|
|
169
|
-
'google/gemini-2.0-flash-exp:free',
|
|
170
|
-
'google/gemini-pro-1.5',
|
|
171
|
-
'deepseek/deepseek-chat',
|
|
172
|
-
'deepseek/deepseek-r1',
|
|
173
|
-
'qwen/qwen-2.5-coder-32b-instruct',
|
|
174
|
-
'mistralai/mistral-large',
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
groq: {
|
|
178
|
-
label: 'Groq',
|
|
179
|
-
baseUrl: 'https://api.groq.com/openai/v1',
|
|
180
|
-
envKey: 'GROQ_API_KEY',
|
|
181
|
-
keyPrefix: 'gsk_',
|
|
182
|
-
docs: 'Groq LPU inference — fastest-token-per-second tier for Llama / Mixtral / Gemma. Auth: GROQ_API_KEY env var or in-app api-key.',
|
|
183
|
-
defaultModel: 'llama-3.3-70b-versatile',
|
|
184
|
-
suggestedModels: [
|
|
185
|
-
'llama-3.3-70b-versatile',
|
|
186
|
-
'llama-3.1-70b-versatile',
|
|
187
|
-
'llama-3.1-8b-instant',
|
|
188
|
-
'llama-3.2-90b-vision-preview',
|
|
189
|
-
'mixtral-8x7b-32768',
|
|
190
|
-
'gemma2-9b-it',
|
|
191
|
-
'qwen-2.5-coder-32b',
|
|
192
|
-
'qwen-2.5-32b',
|
|
193
|
-
'deepseek-r1-distill-llama-70b',
|
|
194
|
-
],
|
|
195
|
-
},
|
|
196
|
-
together: {
|
|
197
|
-
label: 'Together AI',
|
|
198
|
-
baseUrl: 'https://api.together.xyz/v1',
|
|
199
|
-
envKey: 'TOGETHER_API_KEY',
|
|
200
|
-
docs: 'Together AI hosted inference for open-weight models (Llama, Mixtral, Qwen, DeepSeek, etc.). Auth: TOGETHER_API_KEY env var or in-app api-key.',
|
|
201
|
-
defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
|
|
202
|
-
suggestedModels: [
|
|
203
|
-
'meta-llama/Llama-3.3-70B-Instruct-Turbo',
|
|
204
|
-
'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
|
|
205
|
-
'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
|
|
206
|
-
'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo',
|
|
207
|
-
'mistralai/Mixtral-8x22B-Instruct-v0.1',
|
|
208
|
-
'mistralai/Mixtral-8x7B-Instruct-v0.1',
|
|
209
|
-
'Qwen/Qwen2.5-72B-Instruct-Turbo',
|
|
210
|
-
'Qwen/Qwen2.5-Coder-32B-Instruct',
|
|
211
|
-
'deepseek-ai/DeepSeek-V3',
|
|
212
|
-
'deepseek-ai/DeepSeek-R1',
|
|
213
|
-
],
|
|
214
|
-
},
|
|
215
|
-
xai: {
|
|
216
|
-
label: 'xAI (Grok)',
|
|
217
|
-
baseUrl: 'https://api.x.ai/v1',
|
|
218
|
-
envKey: 'XAI_API_KEY',
|
|
219
|
-
altEnvKeys: ['GROK_API_KEY'],
|
|
220
|
-
keyPrefix: 'xai-',
|
|
221
|
-
docs: 'xAI Grok models. Auth: XAI_API_KEY env var or in-app api-key.',
|
|
222
|
-
defaultModel: 'grok-2-latest',
|
|
223
|
-
suggestedModels: [
|
|
224
|
-
'grok-2-latest',
|
|
225
|
-
'grok-2-1212',
|
|
226
|
-
'grok-2-vision-1212',
|
|
227
|
-
'grok-beta',
|
|
228
|
-
'grok-vision-beta',
|
|
229
|
-
],
|
|
230
|
-
},
|
|
231
|
-
deepseek: {
|
|
232
|
-
label: 'DeepSeek',
|
|
233
|
-
baseUrl: 'https://api.deepseek.com/v1',
|
|
234
|
-
envKey: 'DEEPSEEK_API_KEY',
|
|
235
|
-
keyPrefix: 'sk-',
|
|
236
|
-
docs: 'DeepSeek (deepseek-chat / deepseek-reasoner). Auth: DEEPSEEK_API_KEY env var or in-app api-key.',
|
|
237
|
-
defaultModel: 'deepseek-chat',
|
|
238
|
-
suggestedModels: [
|
|
239
|
-
'deepseek-chat',
|
|
240
|
-
'deepseek-reasoner',
|
|
241
|
-
'deepseek-coder',
|
|
242
|
-
],
|
|
243
|
-
},
|
|
244
|
-
mistral: {
|
|
245
|
-
label: 'Mistral La Plateforme',
|
|
246
|
-
baseUrl: 'https://api.mistral.ai/v1',
|
|
247
|
-
envKey: 'MISTRAL_API_KEY',
|
|
248
|
-
docs: 'Mistral La Plateforme (mistral-large, codestral, ministral, pixtral). Auth: MISTRAL_API_KEY env var or in-app api-key.',
|
|
249
|
-
defaultModel: 'mistral-large-latest',
|
|
250
|
-
suggestedModels: [
|
|
251
|
-
'mistral-large-latest',
|
|
252
|
-
'mistral-small-latest',
|
|
253
|
-
'codestral-latest',
|
|
254
|
-
'ministral-8b-latest',
|
|
255
|
-
'ministral-3b-latest',
|
|
256
|
-
'pixtral-large-latest',
|
|
257
|
-
'open-mistral-nemo',
|
|
258
|
-
],
|
|
259
|
-
},
|
|
260
|
-
fireworks: {
|
|
261
|
-
label: 'Fireworks AI',
|
|
262
|
-
baseUrl: 'https://api.fireworks.ai/inference/v1',
|
|
263
|
-
envKey: 'FIREWORKS_API_KEY',
|
|
264
|
-
docs: 'Fireworks AI hosted models. Auth: FIREWORKS_API_KEY env var or in-app api-key.',
|
|
265
|
-
defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',
|
|
266
|
-
suggestedModels: [
|
|
267
|
-
'accounts/fireworks/models/llama-v3p3-70b-instruct',
|
|
268
|
-
'accounts/fireworks/models/llama-v3p1-405b-instruct',
|
|
269
|
-
'accounts/fireworks/models/qwen2p5-coder-32b-instruct',
|
|
270
|
-
'accounts/fireworks/models/deepseek-r1',
|
|
271
|
-
'accounts/fireworks/models/deepseek-v3',
|
|
272
|
-
'accounts/fireworks/models/mixtral-8x22b-instruct',
|
|
273
|
-
],
|
|
274
|
-
},
|
|
275
|
-
};
|
|
122
|
+
// Built-in OpenAI-compatible vendor catalogue — moved to
|
|
123
|
+
// providers/compat_vendors.mjs (pure data; add new vendors THERE).
|
|
124
|
+
// Re-exported below so existing importers keep working.
|
|
125
|
+
export { OPENAI_COMPAT_BUILTINS } from './compat_vendors.mjs';
|
|
276
126
|
|
|
277
127
|
// Insertion order is the picker order. The list goes first-to-last in
|
|
278
128
|
// rough "user-familiar / popular" order so a first-time onboard lands
|
|
@@ -280,8 +130,11 @@ export const OPENAI_COMPAT_BUILTINS = {
|
|
|
280
130
|
// user feedback ("gemini, codex 이런거 먼저 나오게끔").
|
|
281
131
|
export const PROVIDERS = {
|
|
282
132
|
// Tier 1 — popular / brand-name vendors users come in looking for.
|
|
133
|
+
// Keyless CLI variants sit next to their API siblings.
|
|
283
134
|
gemini: geminiProvider,
|
|
135
|
+
'gemini-cli': geminiCliProvider, // keyless — local `gemini` CLI login
|
|
284
136
|
openai: openaiProvider, // surfaces gpt-5-codex / gpt-5 / o3-pro etc.
|
|
137
|
+
'codex-cli': codexCliProvider, // keyless — local `codex` CLI (ChatGPT plan)
|
|
285
138
|
// Tier 2 — Claude. CLI variant first because it's keyless.
|
|
286
139
|
'claude-cli': claudeCliProvider,
|
|
287
140
|
anthropic: anthropicProvider,
|
|
@@ -340,7 +193,10 @@ export const PROVIDER_INFO = {
|
|
|
340
193
|
endpoint: 'subprocess: claude -p',
|
|
341
194
|
defaultModel: 'claude-opus-4-7',
|
|
342
195
|
suggestedModels: [
|
|
196
|
+
'claude-fable-5',
|
|
197
|
+
'claude-opus-4-8',
|
|
343
198
|
'claude-opus-4-7',
|
|
199
|
+
'claude-opus-4-6',
|
|
344
200
|
'claude-sonnet-4-6',
|
|
345
201
|
'claude-haiku-4-5',
|
|
346
202
|
'opus',
|
|
@@ -348,6 +204,22 @@ export const PROVIDER_INFO = {
|
|
|
348
204
|
'haiku',
|
|
349
205
|
],
|
|
350
206
|
},
|
|
207
|
+
'gemini-cli': {
|
|
208
|
+
name: 'gemini-cli',
|
|
209
|
+
requiresApiKey: false,
|
|
210
|
+
docs: 'Google Gemini via the local `gemini` CLI (free Google-account login). No API key — auth flows through whatever account `gemini` is logged in with. Requires @google/gemini-cli installed.',
|
|
211
|
+
endpoint: 'subprocess: gemini -p',
|
|
212
|
+
defaultModel: 'gemini-2.5-pro',
|
|
213
|
+
suggestedModels: ['gemini-2.5-pro', 'gemini-2.5-flash', 'pro', 'flash'],
|
|
214
|
+
},
|
|
215
|
+
'codex-cli': {
|
|
216
|
+
name: 'codex-cli',
|
|
217
|
+
requiresApiKey: false,
|
|
218
|
+
docs: 'OpenAI via the local `codex` CLI (ChatGPT Plus/Pro subscription). No API key — auth flows through `codex` login. Requires the codex CLI installed.',
|
|
219
|
+
endpoint: 'subprocess: codex exec',
|
|
220
|
+
defaultModel: 'gpt-5-codex',
|
|
221
|
+
suggestedModels: ['gpt-5-codex', 'gpt-5', 'o3'],
|
|
222
|
+
},
|
|
351
223
|
anthropic: {
|
|
352
224
|
name: 'anthropic',
|
|
353
225
|
requiresApiKey: true,
|
|
@@ -356,13 +228,13 @@ export const PROVIDER_INFO = {
|
|
|
356
228
|
endpoint: 'https://api.anthropic.com/v1/messages',
|
|
357
229
|
defaultModel: 'claude-opus-4-7',
|
|
358
230
|
suggestedModels: [
|
|
231
|
+
'claude-fable-5',
|
|
232
|
+
'claude-opus-4-8',
|
|
359
233
|
'claude-opus-4-7',
|
|
360
234
|
'claude-opus-4-6',
|
|
361
235
|
'claude-sonnet-4-6',
|
|
362
236
|
'claude-sonnet-4-5',
|
|
363
237
|
'claude-haiku-4-5',
|
|
364
|
-
'claude-3-5-sonnet-20241022',
|
|
365
|
-
'claude-3-5-haiku-20241022',
|
|
366
238
|
],
|
|
367
239
|
},
|
|
368
240
|
openai: {
|
|
@@ -395,9 +267,6 @@ export const PROVIDER_INFO = {
|
|
|
395
267
|
'gemini-2.5-pro',
|
|
396
268
|
'gemini-2.5-flash',
|
|
397
269
|
'gemini-2.0-flash',
|
|
398
|
-
'gemini-2.0-flash-thinking-exp',
|
|
399
|
-
'gemini-1.5-pro',
|
|
400
|
-
'gemini-1.5-flash',
|
|
401
270
|
],
|
|
402
271
|
},
|
|
403
272
|
ollama: {
|
package/tui/pickers.mjs
CHANGED
|
@@ -417,13 +417,13 @@ export async function _arrowMenu({ title, subtitle, footer, items, defaultIdx =
|
|
|
417
417
|
// here (rather than registry.mjs) because it's a UX concept, not
|
|
418
418
|
// an intrinsic provider attribute.
|
|
419
419
|
export function _providerFamilies() {
|
|
420
|
-
// Membership (api/cli/mock
|
|
421
|
-
//
|
|
422
|
-
// The ANSI tags below are readline-specific, so they're applied here.
|
|
420
|
+
// Membership (api/cli/meta/mock) is shared with the Ink picker via
|
|
421
|
+
// tui/provider_families.mjs; the ANSI tags below are readline-specific.
|
|
423
422
|
const b = _bucketProviders(getRegistry());
|
|
424
423
|
return {
|
|
425
424
|
api: { label: 'API key', desc: 'paste an sk-... key during setup', tag: '\x1b[38;5;245m[needs key]\x1b[0m', members: b.api },
|
|
426
425
|
cli: { label: 'CLI / Local', desc: 'keyless — uses an existing CLI login or a local daemon', tag: '\x1b[38;5;208m[no key]\x1b[0m', members: b.cli },
|
|
426
|
+
meta: { label: 'Multi-agent', desc: 'orchestrator — fan a task out to a planner + workers (advanced)', tag: '\x1b[38;5;245m[meta]\x1b[0m', members: b.meta },
|
|
427
427
|
mock: { label: 'Mock', desc: 'offline echo, only useful for testing', tag: '\x1b[38;5;245m[test]\x1b[0m', members: b.mock },
|
|
428
428
|
};
|
|
429
429
|
}
|
|
@@ -5,17 +5,18 @@
|
|
|
5
5
|
// (API key / CLI-Local / Mock) as shared, unit-testable data so both the
|
|
6
6
|
// readline picker (cli.mjs) and the Ink dispatcher use one bucketing rule.
|
|
7
7
|
|
|
8
|
-
// Bucket every registered provider into one of
|
|
9
|
-
// orchestrator
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// Bucket every registered provider into one of four auth families.
|
|
9
|
+
// orchestrator sits in its own `meta` family — visible and pickable, but
|
|
10
|
+
// never a wizard default (the cursor always starts on api/cli — see the
|
|
11
|
+
// cli.mjs v5.3.2 note). Returns { api:[], cli:[], mock:[], meta:[] } of
|
|
12
|
+
// provider-id strings.
|
|
12
13
|
export function bucketProviders(registry) {
|
|
13
14
|
const info = (registry && registry.PROVIDER_INFO) || {};
|
|
14
15
|
const all = Object.keys((registry && registry.PROVIDERS) || {});
|
|
15
|
-
const out = { api: [], cli: [], mock: [] };
|
|
16
|
+
const out = { api: [], cli: [], mock: [], meta: [] };
|
|
16
17
|
for (const name of all) {
|
|
17
18
|
if (name === 'mock') out.mock.push(name);
|
|
18
|
-
else if (name === 'orchestrator')
|
|
19
|
+
else if (name === 'orchestrator') out.meta.push(name);
|
|
19
20
|
else if ((info[name] || {}).requiresApiKey) out.api.push(name);
|
|
20
21
|
else out.cli.push(name);
|
|
21
22
|
}
|
|
@@ -29,6 +30,7 @@ export function providerFamilies(registry) {
|
|
|
29
30
|
return {
|
|
30
31
|
api: { id: 'api', label: 'API key', desc: 'paste an sk-... key', tag: 'needs key', members: b.api },
|
|
31
32
|
cli: { id: 'cli', label: 'CLI / Local', desc: 'keyless — an existing CLI login or a local daemon', tag: 'no key', members: b.cli },
|
|
33
|
+
meta: { id: 'meta', label: 'Multi-agent', desc: 'orchestrator — fan a task out to a planner + workers (advanced)', tag: 'meta', members: b.meta },
|
|
32
34
|
mock: { id: 'mock', label: 'Mock', desc: 'offline echo, only useful for testing', tag: 'test', members: b.mock },
|
|
33
35
|
};
|
|
34
36
|
}
|