@yemi33/minions 0.1.1587 → 0.1.1589
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/CHANGELOG.md +10 -0
- package/bin/minions.js +5 -3
- package/dashboard/js/settings.js +216 -22
- package/dashboard.js +136 -8
- package/docs/copilot-cli-schema.md +637 -0
- package/docs/copilot-output-sample-claude.jsonl +72 -0
- package/docs/copilot-output-sample-default.jsonl +26 -0
- package/docs/copilot-output-sample-gpt4o.jsonl +23 -0
- package/engine/cli.js +250 -18
- package/engine/lifecycle.js +14 -9
- package/engine/llm.js +346 -94
- package/engine/model-discovery.js +167 -0
- package/engine/preflight.js +247 -19
- package/engine/runtimes/claude.js +413 -0
- package/engine/runtimes/copilot.js +566 -0
- package/engine/runtimes/index.js +61 -0
- package/engine/shared.js +299 -63
- package/engine/spawn-agent.js +265 -181
- package/engine.js +118 -31
- package/package.json +1 -1
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/runtimes/claude.js — Claude Code CLI runtime adapter.
|
|
3
|
+
*
|
|
4
|
+
* Foundation extracted from spawn-agent.js, engine.js, llm.js, and shared.js
|
|
5
|
+
* with zero behavioral change. This adapter is the single source of truth for
|
|
6
|
+
* everything Claude-CLI-specific: binary resolution, arg construction, prompt
|
|
7
|
+
* preparation, output parsing, and error normalization.
|
|
8
|
+
*
|
|
9
|
+
* Adapter contract (all runtimes must implement):
|
|
10
|
+
* - name: string
|
|
11
|
+
* - capabilities: { ... } feature flags consumed by engine code
|
|
12
|
+
* - resolveBinary() → { bin, native, leadingArgs }
|
|
13
|
+
* - capsFile: absolute path of the binary-resolution cache for this runtime
|
|
14
|
+
* - listModels() → Promise<{id,name,provider}[] | null>
|
|
15
|
+
* - modelsCache: absolute path of the model-list cache for this runtime
|
|
16
|
+
* - spawnScript: absolute path of the spawn wrapper (or null if direct-only)
|
|
17
|
+
* - buildArgs(opts) → string[] — CLI args excluding the binary
|
|
18
|
+
* - buildPrompt(promptText, sysPromptText) → string — final prompt delivered
|
|
19
|
+
* - resolveModel(input) → string|undefined — shorthand expansion / passthrough
|
|
20
|
+
* - parseOutput(raw) → { text, usage, sessionId, model }
|
|
21
|
+
* - parseStreamChunk(line) → parsed event object or null
|
|
22
|
+
* - parseError(rawOutput) → { message, code, retriable }
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const os = require('os');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const ENGINE_DIR = __dirname.replace(/[\\/]runtimes$/, '');
|
|
30
|
+
const MINIONS_DIR = path.resolve(ENGINE_DIR, '..');
|
|
31
|
+
|
|
32
|
+
const isWin = process.platform === 'win32';
|
|
33
|
+
|
|
34
|
+
// ── Binary Resolution ────────────────────────────────────────────────────────
|
|
35
|
+
// Mirrors engine/spawn-agent.js:26-91. Cached at engine/claude-caps.json so the
|
|
36
|
+
// repeated path-probe (PATH / npm-global / npm-root-g) only happens once per
|
|
37
|
+
// install.
|
|
38
|
+
|
|
39
|
+
const CAPS_FILE = path.join(ENGINE_DIR, 'claude-caps.json');
|
|
40
|
+
|
|
41
|
+
function _safeJson(p) {
|
|
42
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function _safeWriteJson(p, obj) {
|
|
46
|
+
try { fs.writeFileSync(p, JSON.stringify(obj, null, 2)); } catch { /* best effort */ }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function _probeClaudePackage(pkgDir) {
|
|
50
|
+
const nativeBin = path.join(pkgDir, 'bin', isWin ? 'claude.exe' : 'claude');
|
|
51
|
+
if (fs.existsSync(nativeBin)) return { bin: nativeBin, native: true };
|
|
52
|
+
const cliJs = path.join(pkgDir, 'cli.js');
|
|
53
|
+
if (fs.existsSync(cliJs)) return { bin: cliJs, native: false };
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _execSyncCapture(cmd, env) {
|
|
58
|
+
const { execSync } = require('child_process');
|
|
59
|
+
return execSync(cmd, { encoding: 'utf8', env, timeout: 10000, windowsHide: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the Claude CLI binary. Returns { bin, native, leadingArgs } or null.
|
|
64
|
+
* `leadingArgs` is always [] for Claude (the binary is invoked directly with no
|
|
65
|
+
* subcommand prefix). Reserved on the contract for runtimes like `gh copilot`
|
|
66
|
+
* where the runtime is a subcommand of another binary.
|
|
67
|
+
*
|
|
68
|
+
* Backwards-compat: honors `config.claude.binary` from minions config when set
|
|
69
|
+
* and the resulting path exists on disk.
|
|
70
|
+
*/
|
|
71
|
+
function resolveBinary({ env = process.env, config = null } = {}) {
|
|
72
|
+
// 0. Honor explicit override from config.claude.binary (legacy field)
|
|
73
|
+
const overridePath = config?.claude?.binary && config.claude.binary !== 'claude'
|
|
74
|
+
? config.claude.binary
|
|
75
|
+
: null;
|
|
76
|
+
if (overridePath && fs.existsSync(overridePath)) {
|
|
77
|
+
// If the override points at an npm package dir, probe it — otherwise treat
|
|
78
|
+
// as a direct binary path.
|
|
79
|
+
const probed = _probeClaudePackage(overridePath);
|
|
80
|
+
if (probed) return { bin: probed.bin, native: probed.native, leadingArgs: [] };
|
|
81
|
+
const native = !isWin || path.extname(overridePath).toLowerCase() === '.exe';
|
|
82
|
+
return { bin: overridePath, native, leadingArgs: [] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 1. Cache hit — fastest path
|
|
86
|
+
const cached = _safeJson(CAPS_FILE);
|
|
87
|
+
if (cached?.claudeBin && fs.existsSync(cached.claudeBin)) {
|
|
88
|
+
return { bin: cached.claudeBin, native: !!cached.claudeIsNative, leadingArgs: [] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2. PATH lookup → probe the resolved path's neighbouring node_modules dir
|
|
92
|
+
let bin = null;
|
|
93
|
+
let native = false;
|
|
94
|
+
try {
|
|
95
|
+
const cmd = isWin ? 'where claude 2>NUL' : 'which claude 2>/dev/null';
|
|
96
|
+
const which = _execSyncCapture(cmd, env).trim().split('\n')[0].trim();
|
|
97
|
+
if (which) {
|
|
98
|
+
const whichNative = isWin
|
|
99
|
+
? which
|
|
100
|
+
: which.replace(/^\/([a-zA-Z])\//, (_, d) => d.toUpperCase() + ':/').replace(/\//g, path.sep);
|
|
101
|
+
const ccPkg = path.join(path.dirname(whichNative), 'node_modules', '@anthropic-ai', 'claude-code');
|
|
102
|
+
const found = _probeClaudePackage(ccPkg);
|
|
103
|
+
if (found) {
|
|
104
|
+
bin = found.bin;
|
|
105
|
+
native = found.native;
|
|
106
|
+
} else if (!isWin || path.extname(whichNative).toLowerCase() === '.exe') {
|
|
107
|
+
bin = whichNative;
|
|
108
|
+
native = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch { /* PATH probe is optional */ }
|
|
112
|
+
|
|
113
|
+
// 3. Known npm-global locations
|
|
114
|
+
if (!bin) {
|
|
115
|
+
const prefixes = [
|
|
116
|
+
env.npm_config_prefix ? path.join(env.npm_config_prefix, 'node_modules', '@anthropic-ai', 'claude-code') : '',
|
|
117
|
+
env.APPDATA ? path.join(env.APPDATA, 'npm', 'node_modules', '@anthropic-ai', 'claude-code') : '',
|
|
118
|
+
'/usr/local/lib/node_modules/@anthropic-ai/claude-code',
|
|
119
|
+
'/usr/lib/node_modules/@anthropic-ai/claude-code',
|
|
120
|
+
'/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code',
|
|
121
|
+
path.join(path.dirname(process.execPath), '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code'),
|
|
122
|
+
path.join(path.dirname(process.execPath), 'node_modules', '@anthropic-ai', 'claude-code'),
|
|
123
|
+
path.join(MINIONS_DIR, 'node_modules', '@anthropic-ai', 'claude-code'),
|
|
124
|
+
].filter(Boolean);
|
|
125
|
+
for (const pkg of prefixes) {
|
|
126
|
+
try {
|
|
127
|
+
const found = _probeClaudePackage(pkg);
|
|
128
|
+
if (found) { bin = found.bin; native = found.native; break; }
|
|
129
|
+
} catch {}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 4. `npm root -g` fallback
|
|
134
|
+
if (!bin) {
|
|
135
|
+
try {
|
|
136
|
+
const globalRoot = _execSyncCapture('npm root -g', env).trim();
|
|
137
|
+
const found = _probeClaudePackage(path.join(globalRoot, '@anthropic-ai', 'claude-code'));
|
|
138
|
+
if (found) { bin = found.bin; native = found.native; }
|
|
139
|
+
} catch { /* optional */ }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!bin) return null;
|
|
143
|
+
|
|
144
|
+
// Persist cache for the next spawn
|
|
145
|
+
_safeWriteJson(CAPS_FILE, { claudeBin: bin, claudeIsNative: native });
|
|
146
|
+
return { bin, native, leadingArgs: [] };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Model Resolution ─────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
const _CLAUDE_SHORTHANDS = new Set(['sonnet', 'opus', 'haiku']);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Pass through Claude model strings verbatim — including the family
|
|
155
|
+
* shorthands `sonnet`, `opus`, `haiku`, which Claude CLI itself expands.
|
|
156
|
+
* Returns `undefined` for nullish input so the caller omits `--model`.
|
|
157
|
+
*/
|
|
158
|
+
function resolveModel(input) {
|
|
159
|
+
if (input == null || input === '') return undefined;
|
|
160
|
+
return String(input);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Claude has no public model-enumeration mechanism — the CLI bakes the model
|
|
165
|
+
* list internally and the Anthropic API doesn't expose it. Returning null
|
|
166
|
+
* tells the dashboard to fall back to a free-text input.
|
|
167
|
+
*/
|
|
168
|
+
function listModels() {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const MODELS_CACHE = path.join(ENGINE_DIR, 'claude-models.json');
|
|
173
|
+
|
|
174
|
+
// ── Argument Construction ────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build the CLI args (excluding the binary itself) for a Claude invocation.
|
|
178
|
+
*
|
|
179
|
+
* Mirrors the union of:
|
|
180
|
+
* - engine.js:817-844 (agent dispatch)
|
|
181
|
+
* - engine/llm.js:68-76 (CC / doc-chat direct spawn)
|
|
182
|
+
* - spawn-agent.js:118-128 (--system-prompt-file injection on first turn)
|
|
183
|
+
*
|
|
184
|
+
* Per the plan: emits `--dangerously-skip-permissions` (the modern Claude flag)
|
|
185
|
+
* instead of the old `--permission-mode bypassPermissions`. The adapter owns
|
|
186
|
+
* `--add-dir` injection too (when `opts.addDirs` is supplied); spawn-agent.js
|
|
187
|
+
* just hands the dirs over so the wrapper itself stays runtime-agnostic.
|
|
188
|
+
*
|
|
189
|
+
* Conditional flags are emitted ONLY when their corresponding capability is
|
|
190
|
+
* truthy. Copilot-only flags (`stream`, `disableBuiltinMcps`,
|
|
191
|
+
* `suppressAgentsMd`, `reasoningSummaries`) are silently ignored on the Claude
|
|
192
|
+
* path — runtime adapters MUST be tolerant of unknown opts so engine code can
|
|
193
|
+
* pass the same option bag to every adapter without branching.
|
|
194
|
+
*/
|
|
195
|
+
function buildArgs(opts = {}) {
|
|
196
|
+
const {
|
|
197
|
+
model,
|
|
198
|
+
maxTurns,
|
|
199
|
+
allowedTools,
|
|
200
|
+
effort,
|
|
201
|
+
sessionId,
|
|
202
|
+
sysPromptFile,
|
|
203
|
+
addDirs,
|
|
204
|
+
outputFormat = 'stream-json',
|
|
205
|
+
verbose = true,
|
|
206
|
+
maxBudget,
|
|
207
|
+
bare = false,
|
|
208
|
+
fallbackModel,
|
|
209
|
+
} = opts;
|
|
210
|
+
|
|
211
|
+
const args = ['-p', '--output-format', outputFormat];
|
|
212
|
+
if (maxTurns != null) args.push('--max-turns', String(maxTurns));
|
|
213
|
+
if (model) args.push('--model', String(model));
|
|
214
|
+
if (verbose) args.push('--verbose');
|
|
215
|
+
if (sysPromptFile) args.push('--system-prompt-file', sysPromptFile);
|
|
216
|
+
if (Array.isArray(addDirs)) {
|
|
217
|
+
for (const d of addDirs) {
|
|
218
|
+
if (d) args.push('--add-dir', String(d));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (allowedTools) args.push('--allowedTools', allowedTools);
|
|
222
|
+
if (effort) args.push('--effort', String(effort));
|
|
223
|
+
args.push('--dangerously-skip-permissions');
|
|
224
|
+
if (maxBudget != null) args.push('--max-budget-usd', String(maxBudget));
|
|
225
|
+
if (bare === true) args.push('--bare');
|
|
226
|
+
if (fallbackModel) args.push('--fallback-model', String(fallbackModel));
|
|
227
|
+
if (sessionId) args.push('--resume', String(sessionId));
|
|
228
|
+
return args;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build the final prompt text delivered to the Claude CLI. Claude takes the
|
|
233
|
+
* system prompt via `--system-prompt-file` and the user prompt via stdin, so
|
|
234
|
+
* `buildPrompt()` is a passthrough — `sysPromptText` is delivered separately
|
|
235
|
+
* by the spawn wrapper, not embedded in the user prompt.
|
|
236
|
+
*/
|
|
237
|
+
function buildPrompt(promptText, /* sysPromptText */ _sys) {
|
|
238
|
+
return String(promptText == null ? '' : promptText);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Output Parsing ───────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Parse the full stream-json output of a Claude CLI invocation.
|
|
245
|
+
* Returns { text, usage, sessionId, model } — same shape as the legacy
|
|
246
|
+
* `shared.parseStreamJsonOutput`.
|
|
247
|
+
*
|
|
248
|
+
* Tail-slices `text` when `maxTextLength` is set — VERDICTs, completion blocks,
|
|
249
|
+
* and PR URLs live at the END of agent output (regression #1234).
|
|
250
|
+
*/
|
|
251
|
+
function parseOutput(raw, { maxTextLength = 0 } = {}) {
|
|
252
|
+
let text = '';
|
|
253
|
+
let usage = null;
|
|
254
|
+
let sessionId = null;
|
|
255
|
+
let model = null;
|
|
256
|
+
|
|
257
|
+
function extractResult(obj) {
|
|
258
|
+
if (!obj || obj.type !== 'result') return false;
|
|
259
|
+
if (obj.result) text = maxTextLength ? obj.result.slice(-maxTextLength) : obj.result;
|
|
260
|
+
if (obj.session_id) sessionId = obj.session_id;
|
|
261
|
+
if (obj.total_cost_usd || obj.usage) {
|
|
262
|
+
usage = {
|
|
263
|
+
costUsd: obj.total_cost_usd || 0,
|
|
264
|
+
inputTokens: obj.usage?.input_tokens || 0,
|
|
265
|
+
outputTokens: obj.usage?.output_tokens || 0,
|
|
266
|
+
cacheRead: obj.usage?.cache_read_input_tokens || obj.usage?.cacheReadInputTokens || 0,
|
|
267
|
+
cacheCreation: obj.usage?.cache_creation_input_tokens || obj.usage?.cacheCreationInputTokens || 0,
|
|
268
|
+
durationMs: obj.duration_ms || 0,
|
|
269
|
+
numTurns: obj.num_turns || 0,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const safeRaw = raw == null ? '' : String(raw);
|
|
276
|
+
const lines = safeRaw.split('\n');
|
|
277
|
+
|
|
278
|
+
// Forward-scan for the init message (always near the top)
|
|
279
|
+
for (let i = 0; i < Math.min(lines.length, 10); i++) {
|
|
280
|
+
const line = lines[i].trim();
|
|
281
|
+
if (!line || !line.startsWith('{')) continue;
|
|
282
|
+
try {
|
|
283
|
+
const obj = JSON.parse(line);
|
|
284
|
+
if (obj.type === 'system' && obj.subtype === 'init' && obj.model) { model = obj.model; break; }
|
|
285
|
+
} catch {}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Backward-scan for the result message (always at the tail)
|
|
289
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
290
|
+
const line = lines[i].trim();
|
|
291
|
+
if (!line) continue;
|
|
292
|
+
if (line.startsWith('[')) {
|
|
293
|
+
try {
|
|
294
|
+
const arr = JSON.parse(line);
|
|
295
|
+
for (let j = arr.length - 1; j >= 0; j--) {
|
|
296
|
+
if (extractResult(arr[j])) break;
|
|
297
|
+
}
|
|
298
|
+
if (text || usage) break;
|
|
299
|
+
} catch {}
|
|
300
|
+
}
|
|
301
|
+
if (line.startsWith('{')) {
|
|
302
|
+
try {
|
|
303
|
+
if (extractResult(JSON.parse(line))) break;
|
|
304
|
+
} catch {}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { text, usage, sessionId, model };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Parse a single line from the stream-json stdout. Returns the parsed event
|
|
313
|
+
* object, or null when the line is empty / non-JSON.
|
|
314
|
+
*
|
|
315
|
+
* Used by the streaming accumulator in engine/llm.js to react to assistant
|
|
316
|
+
* text blocks, tool-use blocks, and the terminal `result` event without
|
|
317
|
+
* waiting for the whole process to exit.
|
|
318
|
+
*/
|
|
319
|
+
function parseStreamChunk(line) {
|
|
320
|
+
const trimmed = line == null ? '' : String(line).trim();
|
|
321
|
+
if (!trimmed || !trimmed.startsWith('{')) return null;
|
|
322
|
+
try { return JSON.parse(trimmed); } catch { return null; }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── Error Normalization ──────────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Inspect raw agent output (stdout/stderr concatenated by the caller) and map
|
|
329
|
+
* common Claude error patterns onto a normalized shape:
|
|
330
|
+
* { message, code, retriable }
|
|
331
|
+
*
|
|
332
|
+
* `code` values are stable identifiers consumed by retry/escalation logic:
|
|
333
|
+
* - 'auth-failure' — invalid API key / credit-card / org-blocked
|
|
334
|
+
* - 'context-limit' — context window exhausted
|
|
335
|
+
* - 'budget-exceeded' — `--max-budget-usd` ceiling hit
|
|
336
|
+
* - 'crash' — CLI crashed (segfault, panic, "Internal error")
|
|
337
|
+
* - null — no recognised pattern
|
|
338
|
+
*
|
|
339
|
+
* Returns `{ message: '', code: null, retriable: true }` when input is empty
|
|
340
|
+
* (no signal — let upstream classification have the final word).
|
|
341
|
+
*/
|
|
342
|
+
function parseError(rawOutput) {
|
|
343
|
+
const text = rawOutput == null ? '' : String(rawOutput);
|
|
344
|
+
if (!text) return { message: '', code: null, retriable: true };
|
|
345
|
+
const lower = text.toLowerCase();
|
|
346
|
+
|
|
347
|
+
if (/invalid api key|api key.*invalid|authentication.*fail|unauthorized|401|403 forbidden|please.*log.*in|claude\.ai\/login/i.test(text)) {
|
|
348
|
+
return { message: 'Claude authentication failed', code: 'auth-failure', retriable: false };
|
|
349
|
+
}
|
|
350
|
+
if (/prompt is too long|context window|context.*length.*exceeded|token limit|conversation.*too long/i.test(text)) {
|
|
351
|
+
return { message: 'Claude context window exhausted', code: 'context-limit', retriable: false };
|
|
352
|
+
}
|
|
353
|
+
if (/budget.*exceed|max.budget.usd.*reach|cost.*limit.*exceed/i.test(lower)) {
|
|
354
|
+
return { message: 'Claude budget cap exceeded', code: 'budget-exceeded', retriable: false };
|
|
355
|
+
}
|
|
356
|
+
if (/internal error|panic|segmentation fault|claude.*crashed|fatal: claude/i.test(lower)) {
|
|
357
|
+
return { message: 'Claude CLI crashed', code: 'crash', retriable: true };
|
|
358
|
+
}
|
|
359
|
+
return { message: '', code: null, retriable: true };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ── Capability Block ────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
const capabilities = {
|
|
365
|
+
// Streaming JSONL events on stdout
|
|
366
|
+
streaming: true,
|
|
367
|
+
// `--resume <session-id>` resumes a previous turn
|
|
368
|
+
sessionResume: true,
|
|
369
|
+
// Accepts the system prompt via `--system-prompt-file`
|
|
370
|
+
systemPromptFile: true,
|
|
371
|
+
// Honours `--effort low|medium|high|xhigh`
|
|
372
|
+
effortLevels: true,
|
|
373
|
+
// Emits `total_cost_usd` and detailed token usage in the result event
|
|
374
|
+
costTracking: true,
|
|
375
|
+
// Family shorthands (`sonnet` / `opus` / `haiku`) are accepted by the CLI
|
|
376
|
+
modelShorthands: true,
|
|
377
|
+
// No public model enumeration mechanism — settings UI uses free-text
|
|
378
|
+
modelDiscovery: false,
|
|
379
|
+
// Prompt is delivered via stdin (`-p` mode), NOT via `--prompt <text>`
|
|
380
|
+
promptViaArg: false,
|
|
381
|
+
// Supports `--max-budget-usd <n>`
|
|
382
|
+
budgetCap: true,
|
|
383
|
+
// Supports `--bare` (suppress CLAUDE.md auto-discovery)
|
|
384
|
+
bareMode: true,
|
|
385
|
+
// Supports `--fallback-model <id>`
|
|
386
|
+
fallbackModel: true,
|
|
387
|
+
// Engine controls session persistence (writes session.json on completion)
|
|
388
|
+
sessionPersistenceControl: true,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Install hint surfaced when `resolveBinary()` returns null. Consumed by
|
|
392
|
+
// `engine/preflight.js` (per-runtime binary check) and `engine/spawn-agent.js`
|
|
393
|
+
// (fatal error message). Multi-line so all platforms see actionable guidance.
|
|
394
|
+
const INSTALL_HINT = 'install from https://claude.ai/download or: npm install -g @anthropic-ai/claude-code';
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
name: 'claude',
|
|
398
|
+
capabilities,
|
|
399
|
+
resolveBinary,
|
|
400
|
+
capsFile: CAPS_FILE,
|
|
401
|
+
listModels,
|
|
402
|
+
modelsCache: MODELS_CACHE,
|
|
403
|
+
spawnScript: path.join(ENGINE_DIR, 'spawn-agent.js'),
|
|
404
|
+
installHint: INSTALL_HINT,
|
|
405
|
+
buildArgs,
|
|
406
|
+
buildPrompt,
|
|
407
|
+
resolveModel,
|
|
408
|
+
parseOutput,
|
|
409
|
+
parseStreamChunk,
|
|
410
|
+
parseError,
|
|
411
|
+
// Exposed for unit tests — never imported by engine code
|
|
412
|
+
_CLAUDE_SHORTHANDS,
|
|
413
|
+
};
|