subconscious-cli 6.0.0 → 6.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 +135 -2
- package/bin/agent-data.js +242 -0
- package/bin/agent-help.js +106 -0
- package/bin/agent-install.js +354 -0
- package/bin/agent-launch.js +199 -0
- package/bin/agent-spawn.js +137 -0
- package/bin/agents.js +284 -840
- package/bin/auth.js +89 -33
- package/bin/branding.js +3 -1
- package/bin/claude-picker.js +143 -0
- package/bin/cli.js +87 -20
- package/bin/clipboard-image.js +24 -5
- package/bin/colors.js +1 -1
- package/bin/feedback.js +71 -21
- package/bin/harness-manifest.js +286 -0
- package/bin/headless-args.js +35 -0
- package/bin/launch-values.js +25 -0
- package/bin/model-capabilities.js +12 -8
- package/bin/models.js +35 -19
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/opencode-provider.js +48 -5
- package/bin/profile-settings.js +129 -0
- package/bin/profiles.js +136 -449
- package/bin/runbook/README.md +66 -23
- package/bin/runbook/claude-code/agent.json +300 -0
- package/bin/runbook/claude-code/install.sh +9 -252
- package/bin/runbook/codex/agent.json +461 -0
- package/bin/runbook/codex/hooks-lib.sh +17 -4
- package/bin/runbook/codex/install.sh +0 -4
- package/bin/runbook/codex/run.sh +16 -235
- package/bin/runbook/copilot/agent.json +185 -0
- package/bin/runbook/copilot/install.sh +45 -63
- package/bin/runbook/cursor/agent.json +137 -0
- package/bin/runbook/cursor/install.sh +8 -35
- package/bin/runbook/deepseek-harness/agent.json +163 -0
- package/bin/runbook/deepseek-harness/run.sh +20 -59
- package/bin/runbook/image-window/index.ts +14 -0
- package/bin/runbook/image-window/window.d.ts +19 -0
- package/bin/runbook/image-window/window.js +140 -0
- package/bin/runbook/lib.sh +41 -0
- package/bin/runbook/opencode/agent.json +158 -0
- package/bin/runbook/opencode/install.sh +9 -183
- package/bin/runbook/opencode/opencode-plugin.d.ts +16 -0
- package/bin/runbook/opencode/subconscious-image-window.ts +24 -0
- package/bin/runbook/pi/agent.json +216 -0
- package/bin/runbook/pi/install.sh +18 -44
- package/bin/runbook/pi/run.sh +6 -18
- package/bin/runbook/pi/subconscious-compaction.ts +71 -49
- package/bin/runbook/shared.json +58 -0
- package/bin/runbook/subconscious-code/agent.json +156 -0
- package/bin/sessions.js +173 -58
- package/bin/tui.js +104 -32
- package/bin/update-check.js +54 -21
- package/bin/upgrade.js +67 -43
- package/bin/usage.js +47 -24
- package/bin/windows/agents.js +141 -37
- package/bin/windows/common.js +36 -25
- package/bin/windows/install.js +116 -34
- package/bin/windows/launch.js +260 -77
- package/bin/windows/pi-compaction.ts +68 -13
- package/bin/windows/process.js +210 -57
- package/bin/windows/setup.js +251 -56
- package/package.json +9 -4
- package/bin/registry.generated.json +0 -406
- package/bin/runbook/claude-code/run.sh +0 -254
- package/bin/runbook/model-capabilities.generated.sh +0 -9
- package/bin/runbook/opencode/provider.sh +0 -93
- package/bin/runbook/opencode/run.sh +0 -99
- package/bin/runbook/opencode/subconscious-compaction.ts +0 -100
- package/bin/runbook/subconscious-code/run.sh +0 -21
- package/bin/windows/claude.js +0 -71
package/bin/agents.js
CHANGED
|
@@ -1,115 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
21
|
-
import { c } from './colors.js';
|
|
22
|
-
import { runWindowsAgent } from './windows/agents.js';
|
|
1
|
+
import {
|
|
2
|
+
AGENTS,
|
|
3
|
+
agentCommandName,
|
|
4
|
+
agentKeyName,
|
|
5
|
+
agentSetupActions,
|
|
6
|
+
DEFAULTS,
|
|
7
|
+
PACKAGED_MODELS,
|
|
8
|
+
} from './agent-data.js';
|
|
9
|
+
import { isAgentHelpRequest, printAgentHelp } from './agent-help.js';
|
|
10
|
+
import {
|
|
11
|
+
augmentPath,
|
|
12
|
+
candidateBinDirs,
|
|
13
|
+
claudeVersionNeedsUpgrade,
|
|
14
|
+
ensureInstalled,
|
|
15
|
+
ensureMinimumVersion,
|
|
16
|
+
MIN_CLAUDE_CODE_VERSION,
|
|
17
|
+
parseClaudeVersion,
|
|
18
|
+
} from './agent-install.js';
|
|
19
|
+
import { launchPlan, resolveInputs } from './agent-launch.js';
|
|
20
|
+
import { planCommand, runCommand, scriptCommand } from './agent-spawn.js';
|
|
23
21
|
import { getApiKey } from './auth.js';
|
|
24
22
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from './
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// --- Registry (single source of truth, generated copy shipped in the package).
|
|
45
|
-
const registry = JSON.parse(
|
|
46
|
-
readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf-8'),
|
|
47
|
-
);
|
|
48
|
-
const DEFAULTS = registry.defaults;
|
|
49
|
-
const PACKAGED_MODELS =
|
|
50
|
-
Array.isArray(DEFAULTS.models) && DEFAULTS.models.length
|
|
51
|
-
? DEFAULTS.models
|
|
52
|
-
: [DEFAULTS.model];
|
|
53
|
-
const RUNBOOK_DIR = fileURLToPath(new URL('./runbook/', import.meta.url));
|
|
54
|
-
|
|
55
|
-
// --- Token substitution — same rules as scripts/lib/registry.js.
|
|
56
|
-
// Replaces {apiKey}, {model}, {baseUrl}, {baseUrlV1}. NEVER touches {env:...}.
|
|
57
|
-
const TOKENS = ['apiKey', 'model', 'baseUrl', 'baseUrlV1'];
|
|
58
|
-
|
|
59
|
-
function substituteString(str, ctx) {
|
|
60
|
-
let out = str;
|
|
61
|
-
for (const token of TOKENS) {
|
|
62
|
-
if (ctx[token] === undefined) continue;
|
|
63
|
-
out = out.split(`{${token}}`).join(ctx[token]);
|
|
64
|
-
}
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function substitute(value, ctx) {
|
|
69
|
-
if (typeof value === 'string') return substituteString(value, ctx);
|
|
70
|
-
if (Array.isArray(value)) return value.map((v) => substitute(v, ctx));
|
|
71
|
-
if (value && typeof value === 'object') {
|
|
72
|
-
const keys = Object.keys(value);
|
|
73
|
-
if (keys.length === 1 && keys[0] === '$json') {
|
|
74
|
-
return JSON.stringify(substitute(value.$json, ctx));
|
|
75
|
-
}
|
|
76
|
-
const out = {};
|
|
77
|
-
for (const key of keys) out[substituteString(key, ctx)] = substitute(value[key], ctx);
|
|
78
|
-
return out;
|
|
79
|
-
}
|
|
80
|
-
return value;
|
|
81
|
-
}
|
|
23
|
+
applyClaudePickerCatalog,
|
|
24
|
+
CLAUDE_PICKER_ENV_DESCRIPTION,
|
|
25
|
+
claudeModelPickerEnv,
|
|
26
|
+
claudePickerSettings,
|
|
27
|
+
} from './claude-picker.js';
|
|
28
|
+
import { c } from './colors.js';
|
|
29
|
+
import {
|
|
30
|
+
headlessPromptIndex,
|
|
31
|
+
separatorIndex,
|
|
32
|
+
takesModelValue,
|
|
33
|
+
} from './headless-args.js';
|
|
34
|
+
import {
|
|
35
|
+
isLiveModelSource,
|
|
36
|
+
PUBLIC_CATALOG_FALLBACK_MESSAGE,
|
|
37
|
+
resolveModelCatalog,
|
|
38
|
+
} from './models.js';
|
|
39
|
+
import { isUnsetSetting, resolvedModelSetting } from './profiles.js';
|
|
40
|
+
import { runWindowsAgent } from './windows/agents.js';
|
|
82
41
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
42
|
+
export { agentCommandName, agentSetupActions } from './agent-data.js';
|
|
43
|
+
export { isAgentHelpRequest, printAgentHelp } from './agent-help.js';
|
|
44
|
+
export {
|
|
45
|
+
augmentPath,
|
|
46
|
+
claudeVersionNeedsUpgrade,
|
|
47
|
+
MIN_CLAUDE_CODE_VERSION,
|
|
48
|
+
MIN_CODEX_VERSION,
|
|
49
|
+
parseClaudeVersion,
|
|
50
|
+
preferredBinDirsForAgent,
|
|
51
|
+
readClaudeVersion,
|
|
52
|
+
versionNeedsUpgrade,
|
|
53
|
+
} from './agent-install.js';
|
|
54
|
+
export { claudePickerSettings } from './claude-picker.js';
|
|
55
|
+
|
|
56
|
+
const CLAUDE_CODE = 'claude-code';
|
|
98
57
|
|
|
99
|
-
// --- Build the in-memory registry + alias index.
|
|
100
|
-
// Each agent gets a resolved per-OS `install` (string) plus optional
|
|
101
|
-
// `installFallback`, while keeping the original per-OS object available.
|
|
102
|
-
const AGENTS = registry.agents
|
|
103
|
-
.filter((agent) => agent.cli !== false)
|
|
104
|
-
.map((agent) => {
|
|
105
|
-
const { command, fallback } = resolveInstall(agent.install);
|
|
106
|
-
return { ...agent, bin: agent.binByPlatform?.[process.platform] || agent.bin, install: command, installFallback: fallback };
|
|
107
|
-
});
|
|
108
58
|
const BY_ALIAS = new Map();
|
|
109
59
|
for (const agent of AGENTS) {
|
|
110
60
|
BY_ALIAS.set(agent.id, agent);
|
|
111
|
-
|
|
112
|
-
for (const alias of agent.aliases
|
|
61
|
+
BY_ALIAS.set(agentCommandName(agent), agent);
|
|
62
|
+
for (const alias of agent.aliases) BY_ALIAS.set(alias, agent);
|
|
113
63
|
}
|
|
114
64
|
|
|
115
65
|
export function resolveAgent(name) {
|
|
@@ -120,24 +70,16 @@ export function agentList() {
|
|
|
120
70
|
return AGENTS.map((a) => ({
|
|
121
71
|
id: a.id,
|
|
122
72
|
name: a.name,
|
|
123
|
-
alias: a
|
|
124
|
-
action: a.runbook
|
|
125
|
-
description: a.description
|
|
126
|
-
launch: a.runbook
|
|
73
|
+
alias: agentCommandName(a),
|
|
74
|
+
action: a.runbook.mode === 'setup' ? 'Configure' : 'Launch',
|
|
75
|
+
description: a.description,
|
|
76
|
+
launch: a.runbook.mode !== 'setup',
|
|
127
77
|
}));
|
|
128
78
|
}
|
|
129
79
|
|
|
130
80
|
const SETUP_ACTIONS = new Set(['install', 'status', 'uninstall']);
|
|
131
81
|
const DROPPED_SETUP_HELPERS = new Set(['use', 'env', 'unset']);
|
|
132
82
|
|
|
133
|
-
export function agentSetupActions(agent) {
|
|
134
|
-
return Array.isArray(agent.runbook?.setupActions) ? agent.runbook.setupActions : [];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function agentCommandName(agent) {
|
|
138
|
-
return agent.command || agent.id;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
83
|
export function parseAgentAction(agent, argv = []) {
|
|
142
84
|
const command = agentCommandName(agent);
|
|
143
85
|
const first = argv[0];
|
|
@@ -150,7 +92,7 @@ export function parseAgentAction(agent, argv = []) {
|
|
|
150
92
|
}
|
|
151
93
|
|
|
152
94
|
if (SETUP_ACTIONS.has(first)) {
|
|
153
|
-
if (!agent.runbook
|
|
95
|
+
if (!agent.runbook.setup_script || !actions.includes(first)) {
|
|
154
96
|
if (first === 'install') {
|
|
155
97
|
const uninstallHint = actions.includes('uninstall')
|
|
156
98
|
? ` To remove leftover files: subc ${command} uninstall.`
|
|
@@ -166,190 +108,37 @@ export function parseAgentAction(agent, argv = []) {
|
|
|
166
108
|
return { action: first, args: argv };
|
|
167
109
|
}
|
|
168
110
|
|
|
169
|
-
if (
|
|
111
|
+
if (first === 'headless') {
|
|
112
|
+
if (!agent.launch?.headless_argv) {
|
|
113
|
+
throw new Error(`${agent.name} does not support headless mode.`);
|
|
114
|
+
}
|
|
115
|
+
if (!argv[1]?.trim() || ['-h', '--help'].includes(argv[1])) {
|
|
116
|
+
throw new Error(`Usage: subc ${command} headless PROMPT [args...]`);
|
|
117
|
+
}
|
|
118
|
+
if (argv.slice(2).some((arg) => arg === '-h' || arg === '--help')) {
|
|
119
|
+
throw new Error('help is not available in a headless run');
|
|
120
|
+
}
|
|
121
|
+
return { action: 'headless', args: argv };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (agent.runbook.mode === 'setup') {
|
|
170
125
|
return { action: 'install', args: argv };
|
|
171
126
|
}
|
|
172
127
|
|
|
173
128
|
return { action: 'launch', args: argv };
|
|
174
129
|
}
|
|
175
130
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
['help', 'Show this help'],
|
|
183
|
-
['install', process.platform === 'win32' ? 'Install a verified Windows release, when published upstream' : 'Install the latest precompiled Linux or macOS release'],
|
|
184
|
-
['--model MODEL', 'Override the profile model for this launch'],
|
|
185
|
-
['ARGS...', 'Pass arguments directly to Marathon'],
|
|
186
|
-
],
|
|
187
|
-
},
|
|
188
|
-
'claude-code': {
|
|
189
|
-
usage: 'subc [-p NAME] claude [help|status|uninstall] [Claude arguments...]',
|
|
190
|
-
behavior:
|
|
191
|
-
'Launches Claude Code with the active Subconscious profile and model picker. Persistent files are leftover-only: subc claude uninstall.',
|
|
192
|
-
options: [
|
|
193
|
-
['help', 'Show this help'],
|
|
194
|
-
['status', 'Inspect leftover ~/.claude/subconscious-gateway.env'],
|
|
195
|
-
['uninstall', 'Remove leftover ~/.claude/subconscious-gateway.env'],
|
|
196
|
-
['--model MODEL', 'Override the profile model for this launch'],
|
|
197
|
-
['--compact-window N', 'Override the Claude auto-compact window'],
|
|
198
|
-
['--max-context-tokens N', 'Override the maximum context tokens'],
|
|
199
|
-
['-- ARGS...', 'Pass remaining arguments to Claude Code'],
|
|
200
|
-
],
|
|
201
|
-
},
|
|
202
|
-
codex: {
|
|
203
|
-
usage: 'subc [-p NAME] codex [help|install|status|uninstall] [Codex arguments...]',
|
|
204
|
-
behavior:
|
|
205
|
-
'Launches Codex with a temporary Subconscious provider catalog. Compaction hooks are merged into ~/.codex/hooks.json on first launch and removed with subc codex uninstall; Codex will not run them until you trust them with /hooks once.',
|
|
206
|
-
options: [
|
|
207
|
-
['help', 'Show this help'],
|
|
208
|
-
['install', 'Install the Subconscious compaction hooks (then trust with /hooks)'],
|
|
209
|
-
['status', 'Inspect the installed compaction hooks'],
|
|
210
|
-
['uninstall', 'Remove only the Subconscious Codex hooks'],
|
|
211
|
-
['--model MODEL', 'Override the profile model for this launch'],
|
|
212
|
-
['--context-window N', 'Override catalog context_window'],
|
|
213
|
-
['--max-context-window N', 'Override catalog max_context_window'],
|
|
214
|
-
['--auto-compact-token-limit N', 'Override the automatic compaction threshold'],
|
|
215
|
-
['--reasoning-effort LEVEL', 'Use none, low, medium, high, or max'],
|
|
216
|
-
['--external-tools', 'Enable Codex apps/plugins for this launch'],
|
|
217
|
-
['--stream-idle-timeout MS', 'Allow a longer silent think before timing out'],
|
|
218
|
-
['--max-subagents N', 'Subagents allowed to run at once (default 4)'],
|
|
219
|
-
['--subagent-effort LEVEL', 'Effort for subagents, separate from the parent'],
|
|
220
|
-
['-- ARGS...', 'Pass remaining arguments to Codex'],
|
|
221
|
-
],
|
|
222
|
-
},
|
|
223
|
-
opencode: {
|
|
224
|
-
usage: 'subc [-p NAME] opencode [help|status|uninstall] [OpenCode arguments...]',
|
|
225
|
-
behavior:
|
|
226
|
-
'Launches OpenCode with an ephemeral provider containing every Subconscious model. Persistent files are leftover-only: subc opencode uninstall.',
|
|
227
|
-
options: [
|
|
228
|
-
['help', 'Show this help'],
|
|
229
|
-
['status', 'Inspect leftover OpenCode Subconscious config'],
|
|
230
|
-
['uninstall', 'Remove only the Subconscious OpenCode provider and plugin'],
|
|
231
|
-
['--model MODEL', 'Override the profile model for this launch'],
|
|
232
|
-
['ARGS...', 'Pass arguments directly to OpenCode'],
|
|
233
|
-
],
|
|
234
|
-
},
|
|
235
|
-
cursor: {
|
|
236
|
-
usage: 'subc [-p NAME] cursor [help|install|status|uninstall]',
|
|
237
|
-
behavior:
|
|
238
|
-
'Manages Cursor correlation hooks; model endpoint setup is completed in Cursor Settings.',
|
|
239
|
-
options: [
|
|
240
|
-
['help', 'Show this help'],
|
|
241
|
-
['install', 'Install or update the Cursor hooks (default action)'],
|
|
242
|
-
['status', 'Inspect the installed hook configuration'],
|
|
243
|
-
['uninstall', 'Remove only the Subconscious Cursor hooks'],
|
|
244
|
-
],
|
|
245
|
-
},
|
|
246
|
-
copilot: {
|
|
247
|
-
usage: 'subc [-p NAME] copilot [help|install|status|uninstall]',
|
|
248
|
-
behavior: 'Manages the VS Code model provider and Copilot correlation hooks.',
|
|
249
|
-
options: [
|
|
250
|
-
['help', 'Show this help'],
|
|
251
|
-
['install', 'Install or update the provider and hooks (default action)'],
|
|
252
|
-
['status', 'Inspect the installed provider and hooks'],
|
|
253
|
-
['uninstall', 'Remove the Subconscious provider and hooks'],
|
|
254
|
-
],
|
|
255
|
-
},
|
|
256
|
-
pi: {
|
|
257
|
-
usage: 'subc [-p NAME] pi [help|install|status|uninstall] [Pi arguments...]',
|
|
258
|
-
behavior:
|
|
259
|
-
'Refreshes the persistent Subconscious provider from the live catalog, then launches Pi.',
|
|
260
|
-
options: [
|
|
261
|
-
['help', 'Show this help'],
|
|
262
|
-
['install', 'Refresh the Subconscious provider without launching Pi'],
|
|
263
|
-
['status', 'Inspect the persistent Pi provider'],
|
|
264
|
-
['uninstall', 'Remove only the Subconscious Pi provider and extension'],
|
|
265
|
-
['--model MODEL', 'Override the profile model for this launch'],
|
|
266
|
-
['ARGS...', 'Pass arguments directly to Pi'],
|
|
267
|
-
],
|
|
268
|
-
},
|
|
269
|
-
'deepseek-harness': {
|
|
270
|
-
usage: 'subc [-p NAME] dsh [web|headless] [--model MODEL] [Harness arguments...]',
|
|
271
|
-
behavior:
|
|
272
|
-
'Launches DeepSeek Harness with a temporary Subconscious provider containing every live gateway model. Web mode is the default.',
|
|
273
|
-
options: [
|
|
274
|
-
['help', 'Show this help'],
|
|
275
|
-
['web', 'Launch the DeepSeek Harness Web UI (default)'],
|
|
276
|
-
['headless PROMPT', 'Run one headless task and exit'],
|
|
277
|
-
['--model MODEL', 'Set the initial model for new Harness sessions'],
|
|
278
|
-
['ARGS...', 'Pass remaining arguments to the selected Harness mode'],
|
|
279
|
-
],
|
|
280
|
-
},
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
export function isAgentHelpRequest(argv = []) {
|
|
284
|
-
return ['help', '-h', '--help'].includes(argv[0]);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function displayProfileValue(setting, value, values) {
|
|
288
|
-
if (setting.type === 'secret') {
|
|
289
|
-
if (value) return '(set)';
|
|
290
|
-
return setting.key !== 'API_KEY' && values.API_KEY ? '(shared key)' : '(not set)';
|
|
291
|
-
}
|
|
292
|
-
if (
|
|
293
|
-
!value &&
|
|
294
|
-
(setting.key === 'MODEL' || setting.key === 'CLAUDE_CODE_SUBAGENT_MODEL')
|
|
295
|
-
) {
|
|
296
|
-
return 'UNSET';
|
|
297
|
-
}
|
|
298
|
-
return value || '(auto)';
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
export function printAgentHelp(agent, profile) {
|
|
302
|
-
const details = AGENT_HELP[agent.id] || {
|
|
303
|
-
usage: `subc [--profile NAME] ${agent.command || agent.id} [arguments...]`,
|
|
304
|
-
behavior: agent.description,
|
|
305
|
-
options: [],
|
|
306
|
-
};
|
|
307
|
-
const settings = profileSettingsForAgent(agent.id);
|
|
308
|
-
const values = resolvedProfileValues(profile);
|
|
309
|
-
const optionWidth = Math.max(0, ...details.options.map(([option]) => option.length));
|
|
310
|
-
const settingWidth = Math.max(0, ...settings.map((setting) => setting.key.length));
|
|
311
|
-
|
|
312
|
-
console.log(`\n ${c.bold}${agent.name} + Subconscious${c.reset}\n`);
|
|
313
|
-
console.log(` ${details.behavior}\n`);
|
|
314
|
-
console.log(` ${c.bold}Usage${c.reset}\n ${details.usage}\n`);
|
|
315
|
-
if (details.options.length) {
|
|
316
|
-
console.log(` ${c.bold}Commands and options${c.reset}`);
|
|
317
|
-
for (const [option, description] of details.options) {
|
|
318
|
-
console.log(` ${c.cyan}${option.padEnd(optionWidth)}${c.reset} ${description}`);
|
|
319
|
-
}
|
|
320
|
-
console.log();
|
|
321
|
-
}
|
|
322
|
-
console.log(` ${c.bold}Profile settings${c.reset} ${c.dim}(${profile?.name || 'default'})${c.reset}`);
|
|
323
|
-
for (const setting of settings) {
|
|
324
|
-
const value = displayProfileValue(setting, values[setting.key], values);
|
|
325
|
-
console.log(` ${c.cyan}${setting.key.padEnd(settingWidth)}${c.reset} ${value}`);
|
|
326
|
-
console.log(` ${' '.repeat(settingWidth)} ${c.dim}${setting.description}${c.reset}`);
|
|
327
|
-
}
|
|
328
|
-
const command = agentCommandName(agent);
|
|
329
|
-
const profileFlag = profile?.name || 'default';
|
|
330
|
-
console.log(
|
|
331
|
-
`\n Edit the env file with ${c.cyan}subc -p ${profileFlag} config edit${c.reset}.`,
|
|
332
|
-
);
|
|
333
|
-
const actions = agentSetupActions(agent);
|
|
334
|
-
if (actions.includes('install')) {
|
|
335
|
-
const installDescription = agent.runbook?.binaryInstallScript
|
|
336
|
-
? 'Install the agent binary with'
|
|
337
|
-
: 'Install the persistent integration with';
|
|
338
|
-
console.log(` ${installDescription} ${c.cyan}subc ${command} install${c.reset}.`);
|
|
339
|
-
}
|
|
340
|
-
if (actions.includes('uninstall')) {
|
|
341
|
-
console.log(
|
|
342
|
-
` Remove it with ${c.cyan}subc ${command} uninstall${c.reset}.`,
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
console.log();
|
|
131
|
+
/** True when argv asks for a headless run; throws when that run is invalid. */
|
|
132
|
+
export function isHeadlessRequest(agent, argv = []) {
|
|
133
|
+
const prompt = headlessPromptIndex(argv);
|
|
134
|
+
if (prompt < 0) return false;
|
|
135
|
+
parseAgentAction(agent, argv.slice(prompt - 1));
|
|
136
|
+
return true;
|
|
346
137
|
}
|
|
347
138
|
|
|
348
139
|
/**
|
|
349
|
-
* Resolve the
|
|
350
|
-
*
|
|
351
|
-
* baseUrl — SUBCONSCIOUS_BASE_URL → registry default
|
|
352
|
-
* baseUrlV1 — `${baseUrl}/v1` (so an override flows to both)
|
|
140
|
+
* Resolve the gateway for a launch: SUBCONSCIOUS_BASE_URL, then the profile
|
|
141
|
+
* GATEWAY_URL, then the packaged default, without trailing slashes.
|
|
353
142
|
*/
|
|
354
143
|
function buildContext(apiKey, model, profile) {
|
|
355
144
|
const baseUrl = (
|
|
@@ -357,7 +146,7 @@ function buildContext(apiKey, model, profile) {
|
|
|
357
146
|
profile?.values?.GATEWAY_URL?.trim() ||
|
|
358
147
|
DEFAULTS.baseUrl
|
|
359
148
|
).replace(/\/+$/, '');
|
|
360
|
-
return { apiKey, model, baseUrl
|
|
149
|
+
return { apiKey, model, baseUrl };
|
|
361
150
|
}
|
|
362
151
|
|
|
363
152
|
/**
|
|
@@ -370,14 +159,24 @@ export function extractModel(argv, profile) {
|
|
|
370
159
|
const environmentModel = resolvedModelSetting(process.env.SUBCONSCIOUS_MODEL);
|
|
371
160
|
const profileModel = resolvedModelSetting(profile?.values?.MODEL);
|
|
372
161
|
let model = environmentModel || profileModel || '';
|
|
373
|
-
let modelSource = environmentModel
|
|
162
|
+
let modelSource = environmentModel
|
|
163
|
+
? 'environment'
|
|
164
|
+
: profileModel
|
|
165
|
+
? 'profile'
|
|
166
|
+
: 'catalog';
|
|
374
167
|
const rest = [];
|
|
168
|
+
// The headless prompt is opaque, even when it looks like --model.
|
|
169
|
+
const promptIndex = headlessPromptIndex(argv);
|
|
375
170
|
for (let i = 0; i < argv.length; i++) {
|
|
376
171
|
const a = argv[i];
|
|
172
|
+
if (i === promptIndex) {
|
|
173
|
+
rest.push(a);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
377
176
|
if (a === '--model') {
|
|
378
177
|
const v = argv[i + 1];
|
|
379
|
-
if (v
|
|
380
|
-
if (isUnsetSetting(v)) {
|
|
178
|
+
if (takesModelValue(v)) {
|
|
179
|
+
if (v === '' || isUnsetSetting(v)) {
|
|
381
180
|
model = '';
|
|
382
181
|
modelSource = 'catalog';
|
|
383
182
|
} else {
|
|
@@ -404,331 +203,6 @@ export function extractModel(argv, profile) {
|
|
|
404
203
|
return { model, modelSource, rest };
|
|
405
204
|
}
|
|
406
205
|
|
|
407
|
-
/**
|
|
408
|
-
* Common locations a freshly-installed coding-agent binary lands in but which
|
|
409
|
-
* are often NOT on the current process's PATH (e.g. aider/claude install into
|
|
410
|
-
* `~/.local/bin`; npm globals into the npm prefix bin). Best-effort, deduped.
|
|
411
|
-
*/
|
|
412
|
-
function candidateBinDirs() {
|
|
413
|
-
const home = os.homedir();
|
|
414
|
-
const dirs = [];
|
|
415
|
-
|
|
416
|
-
if (process.platform === 'win32') {
|
|
417
|
-
if (process.env.APPDATA) dirs.push(path.join(process.env.APPDATA, 'npm'));
|
|
418
|
-
if (process.env.USERPROFILE) {
|
|
419
|
-
dirs.push(path.join(process.env.USERPROFILE, '.local', 'bin'));
|
|
420
|
-
}
|
|
421
|
-
if (home) dirs.push(path.join(home, '.local', 'bin'));
|
|
422
|
-
} else {
|
|
423
|
-
dirs.push(path.join(home, '.local', 'bin'));
|
|
424
|
-
dirs.push(path.join(home, '.cargo', 'bin'));
|
|
425
|
-
dirs.push('/opt/homebrew/bin');
|
|
426
|
-
dirs.push('/usr/local/bin');
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// npm global bin (best-effort — npm may be absent).
|
|
430
|
-
try {
|
|
431
|
-
const prefix = execFileSync('npm', ['prefix', '-g'], {
|
|
432
|
-
encoding: 'utf-8',
|
|
433
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
434
|
-
}).trim();
|
|
435
|
-
if (prefix) {
|
|
436
|
-
dirs.push(process.platform === 'win32' ? prefix : path.join(prefix, 'bin'));
|
|
437
|
-
}
|
|
438
|
-
} catch {
|
|
439
|
-
// npm not available — skip.
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Dedupe, drop empties.
|
|
443
|
-
return [...new Set(dirs.filter(Boolean))];
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/** Resolve an agent-managed binary directory declared by the registry. */
|
|
447
|
-
export function preferredBinDirsForAgent(
|
|
448
|
-
agent,
|
|
449
|
-
environment = process.env,
|
|
450
|
-
home = os.homedir(),
|
|
451
|
-
) {
|
|
452
|
-
const envName = agent.runbook?.installDirEnv;
|
|
453
|
-
const configured = envName ? environment[envName]?.trim() : '';
|
|
454
|
-
if (configured) return [path.resolve(configured)];
|
|
455
|
-
|
|
456
|
-
const installDir = agent.runbook?.installDir?.trim();
|
|
457
|
-
return installDir ? [path.resolve(home, installDir)] : [];
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/** Executable extensions to probe (Windows uses PATHEXT). */
|
|
461
|
-
function binExts() {
|
|
462
|
-
return process.platform === 'win32'
|
|
463
|
-
? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';')
|
|
464
|
-
: [''];
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Resolve `bin` against PATH plus the candidate bin dirs. Returns the directory
|
|
469
|
-
* containing the executable if found, otherwise null. Searching the candidate
|
|
470
|
-
* dirs lets us find binaries installed this session that aren't on PATH yet.
|
|
471
|
-
*/
|
|
472
|
-
async function resolveBinPath(bin, preferredDirs = []) {
|
|
473
|
-
const pathDirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
474
|
-
const dirs = [...new Set([...preferredDirs, ...pathDirs, ...candidateBinDirs()])];
|
|
475
|
-
const exts = binExts();
|
|
476
|
-
for (const dir of dirs) {
|
|
477
|
-
for (const ext of exts) {
|
|
478
|
-
const candidate = path.join(dir, bin + ext);
|
|
479
|
-
try {
|
|
480
|
-
await fs.access(candidate, fsConstants.F_OK);
|
|
481
|
-
return dir;
|
|
482
|
-
} catch {
|
|
483
|
-
// keep scanning
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
return null;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Build a PATH string with `extraDirs` prepended (deduped against PATH).
|
|
492
|
-
* Returns the augmented PATH value for use in a child env.
|
|
493
|
-
*/
|
|
494
|
-
export function augmentPath(
|
|
495
|
-
extraDirs,
|
|
496
|
-
preferredDir,
|
|
497
|
-
currentPath = process.env.PATH || '',
|
|
498
|
-
) {
|
|
499
|
-
const current = currentPath.split(path.delimiter).filter(Boolean);
|
|
500
|
-
const seen = new Set(current);
|
|
501
|
-
const prepend = extraDirs.filter(
|
|
502
|
-
(dir, index) =>
|
|
503
|
-
dir &&
|
|
504
|
-
dir !== preferredDir &&
|
|
505
|
-
!seen.has(dir) &&
|
|
506
|
-
extraDirs.indexOf(dir) === index,
|
|
507
|
-
);
|
|
508
|
-
const remaining = current.filter((dir) => dir !== preferredDir);
|
|
509
|
-
return [...(preferredDir ? [preferredDir] : []), ...prepend, ...remaining].join(
|
|
510
|
-
path.delimiter,
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/** Ask a yes/no question on the TTY. Empty answer counts as yes. */
|
|
515
|
-
function askYesNo(question) {
|
|
516
|
-
return new Promise((resolve) => {
|
|
517
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
518
|
-
rl.question(question, (answer) => {
|
|
519
|
-
rl.close();
|
|
520
|
-
const a = answer.trim().toLowerCase();
|
|
521
|
-
resolve(a === '' || a === 'y' || a === 'yes');
|
|
522
|
-
});
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function waitForEnter(prompt) {
|
|
527
|
-
return new Promise((resolve) => {
|
|
528
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
529
|
-
rl.question(prompt, () => {
|
|
530
|
-
rl.close();
|
|
531
|
-
resolve();
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/** Run the agent's packaged binary installer, or its shell install command. */
|
|
537
|
-
function runInstaller(agent, install = agent.install, usePackagedScript = true) {
|
|
538
|
-
return new Promise((resolve) => {
|
|
539
|
-
const installScript = usePackagedScript && agent.runbook?.binaryInstallScript;
|
|
540
|
-
const child = installScript
|
|
541
|
-
? spawn('bash', [runbookScriptPath(agent, installScript), 'install'], {
|
|
542
|
-
stdio: 'inherit',
|
|
543
|
-
})
|
|
544
|
-
: spawn(install, { shell: true, stdio: 'inherit' });
|
|
545
|
-
child.on('error', () => resolve(false));
|
|
546
|
-
child.on('exit', (code) => resolve(code === 0));
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/** Print the resolved install command (plus any fallback) for an agent. */
|
|
551
|
-
function printInstallCommands(agent) {
|
|
552
|
-
console.error(` ${c.cyan}${agent.install}${c.reset}`);
|
|
553
|
-
if (agent.installFallback) {
|
|
554
|
-
console.error(` ${c.dim}or, as a fallback:${c.reset}`);
|
|
555
|
-
console.error(` ${c.cyan}${agent.installFallback}${c.reset}`);
|
|
556
|
-
}
|
|
557
|
-
console.error('');
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Ensure the agent's binary is resolvable. If missing:
|
|
562
|
-
* - interactive TTY: offer to run the per-OS installer (with fallback), then
|
|
563
|
-
* re-resolve against PATH + candidate dirs.
|
|
564
|
-
* - non-interactive: print the resolved install command (+ fallback) and
|
|
565
|
-
* exit 127 without running anything.
|
|
566
|
-
*
|
|
567
|
-
* Returns the directory containing the bin (to prepend to the child's PATH) on
|
|
568
|
-
* success. May exit the process on failure or when manual action is needed.
|
|
569
|
-
*/
|
|
570
|
-
async function ensureInstalled(agent) {
|
|
571
|
-
const preferredDirs = preferredBinDirsForAgent(agent);
|
|
572
|
-
const existing = await resolveBinPath(agent.bin, preferredDirs);
|
|
573
|
-
if (existing) return existing;
|
|
574
|
-
|
|
575
|
-
// Agents without an installer are launch-only. Their setup integration may
|
|
576
|
-
// configure the provider, but `subc <agent>` must never install the binary.
|
|
577
|
-
if (!agent.install) {
|
|
578
|
-
console.error(
|
|
579
|
-
`\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${agent.bin}\` not found on PATH).${c.reset}`,
|
|
580
|
-
);
|
|
581
|
-
console.error(` Install ${agent.name} separately, then re-run ${c.cyan}subc ${agent.command || agent.id}${c.reset}.\n`);
|
|
582
|
-
process.exit(127);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
586
|
-
|
|
587
|
-
if (!interactive) {
|
|
588
|
-
console.error(
|
|
589
|
-
`\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${agent.bin}\` not found on PATH).${c.reset}`,
|
|
590
|
-
);
|
|
591
|
-
console.error(` Install it with:\n`);
|
|
592
|
-
printInstallCommands(agent);
|
|
593
|
-
process.exit(127);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
console.error(`\n ${c.bold}${agent.name}${c.reset} isn't installed.`);
|
|
597
|
-
const ok = await askYesNo(` Install it now? ${c.dim}[Y/n]${c.reset} `);
|
|
598
|
-
if (!ok) {
|
|
599
|
-
console.error(`\n No problem. Install it yourself with:\n`);
|
|
600
|
-
printInstallCommands(agent);
|
|
601
|
-
process.exit(127);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
console.error(`\n ${c.dim}Running ${c.reset}${c.cyan}${agent.install}${c.reset}\n`);
|
|
605
|
-
let installed = await runInstaller(agent);
|
|
606
|
-
|
|
607
|
-
// Primary failed and a fallback exists — try it once.
|
|
608
|
-
if (!installed && agent.installFallback) {
|
|
609
|
-
console.error(
|
|
610
|
-
`\n ${c.dim}That didn't work. Trying the fallback: ${c.reset}${c.cyan}${agent.installFallback}${c.reset}\n`,
|
|
611
|
-
);
|
|
612
|
-
installed = await runInstaller(agent, agent.installFallback, false);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
if (!installed) {
|
|
616
|
-
console.error(`\n ${c.red}Install failed.${c.reset} Try it manually:\n`);
|
|
617
|
-
printInstallCommands(agent);
|
|
618
|
-
process.exit(127);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// PATH hardening: the freshly-installed binary is often not on the current
|
|
622
|
-
// process's PATH. Re-resolve against PATH + candidate dirs.
|
|
623
|
-
const found = await resolveBinPath(agent.bin, preferredDirs);
|
|
624
|
-
if (found) return found;
|
|
625
|
-
|
|
626
|
-
console.error(
|
|
627
|
-
`\n ${c.dim}Installed ${agent.name}, but it isn't on this shell's PATH yet. ` +
|
|
628
|
-
`Open a new terminal (or add a bin dir to PATH) and re-run \`subc ${agent.command || agent.id}\`.${c.reset}\n`,
|
|
629
|
-
);
|
|
630
|
-
process.exit(0);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
export function parseClaudeVersion(text) {
|
|
634
|
-
const match = String(text ?? '').match(/v?(\d+\.\d+\.\d+)/);
|
|
635
|
-
return match?.[1] ?? null;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
export function claudeVersionNeedsUpgrade(installed, minimum = MIN_CLAUDE_CODE_VERSION) {
|
|
639
|
-
return versionNeedsUpgrade(installed, minimum);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
export function versionNeedsUpgrade(installed, minimum) {
|
|
643
|
-
return Boolean(installed && minimum && compareVersions(installed, minimum) < 0);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
export function readClaudeVersion(bin, binDir, options = {}) {
|
|
647
|
-
const execFile = options.execFileSync || execFileSync;
|
|
648
|
-
try {
|
|
649
|
-
const stdout = execFile(bin, ['--version'], {
|
|
650
|
-
encoding: 'utf-8',
|
|
651
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
652
|
-
env: {
|
|
653
|
-
...process.env,
|
|
654
|
-
PATH: augmentPath(
|
|
655
|
-
[binDir, ...candidateBinDirs()].filter(Boolean),
|
|
656
|
-
binDir,
|
|
657
|
-
),
|
|
658
|
-
},
|
|
659
|
-
timeout: options.timeoutMs ?? 3000,
|
|
660
|
-
});
|
|
661
|
-
return parseClaudeVersion(typeof stdout === 'string' ? stdout : stdout?.toString?.());
|
|
662
|
-
} catch {
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
const MINIMUM_VERSIONS = {
|
|
668
|
-
'claude-code': { minimum: MIN_CLAUDE_CODE_VERSION, label: 'Claude Code' },
|
|
669
|
-
// Below this, subagents are silently absent rather than broken.
|
|
670
|
-
codex: { minimum: MIN_CODEX_VERSION, label: 'Codex' },
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
async function ensureClaudeCompatible(agent, binDir) {
|
|
674
|
-
const requirement = MINIMUM_VERSIONS[agent.id];
|
|
675
|
-
if (!requirement) return;
|
|
676
|
-
const version = readClaudeVersion(agent.bin, binDir);
|
|
677
|
-
if (!versionNeedsUpgrade(version, requirement.minimum)) return;
|
|
678
|
-
|
|
679
|
-
console.error(
|
|
680
|
-
`\n Minimum supported ${requirement.label} version is ${requirement.minimum}. Your version is ${version}. Upgrade to get the best experience.\n`,
|
|
681
|
-
);
|
|
682
|
-
console.error(` Upgrade it with:`);
|
|
683
|
-
console.error(` ${c.cyan}${agent.install}${c.reset}`);
|
|
684
|
-
if (agent.installFallback) {
|
|
685
|
-
console.error(` or`);
|
|
686
|
-
console.error(` ${c.cyan}${agent.installFallback}${c.reset}`);
|
|
687
|
-
}
|
|
688
|
-
console.error('');
|
|
689
|
-
if (process.stdin.isTTY === true && process.stdout.isTTY === true) {
|
|
690
|
-
await waitForEnter(` ${c.dim}Press Enter to continue.${c.reset} `);
|
|
691
|
-
console.error('');
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/** Resolve and validate a script inside the packaged runbook directory. */
|
|
696
|
-
function runbookScriptPath(agent, relativeScript = agent.runbook.script) {
|
|
697
|
-
const script = path.resolve(RUNBOOK_DIR, relativeScript);
|
|
698
|
-
const relative = path.relative(RUNBOOK_DIR, script);
|
|
699
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
700
|
-
throw new Error(`Invalid runbook script path for ${agent.name}`);
|
|
701
|
-
}
|
|
702
|
-
return script;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/** Spawn a runbook script and mirror its exit status/signals. */
|
|
706
|
-
function spawnRunbook(agent, args, env, relativeScript) {
|
|
707
|
-
return new Promise((resolve, reject) => {
|
|
708
|
-
const script = runbookScriptPath(agent, relativeScript);
|
|
709
|
-
const child = spawn('bash', [script, ...args], { stdio: 'inherit', env });
|
|
710
|
-
|
|
711
|
-
child.on('error', (error) => {
|
|
712
|
-
if (error.code === 'ENOENT') {
|
|
713
|
-
reject(
|
|
714
|
-
new Error('These coding-agent integrations require `bash`, but it was not found on PATH.'),
|
|
715
|
-
);
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
reject(error);
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
child.on('exit', (code, signal) => {
|
|
722
|
-
if (signal) {
|
|
723
|
-
process.kill(process.pid, signal);
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
if (code) process.exitCode = code;
|
|
727
|
-
resolve(code ?? 0);
|
|
728
|
-
});
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
|
|
732
206
|
function isSetupWithoutAuth(argv) {
|
|
733
207
|
return ['status', 'uninstall', '-h', '--help', 'help'].includes(argv[0]);
|
|
734
208
|
}
|
|
@@ -738,17 +212,11 @@ function optionValue(argv, name) {
|
|
|
738
212
|
return index >= 0 ? argv[index + 1]?.trim() || null : null;
|
|
739
213
|
}
|
|
740
214
|
|
|
741
|
-
function agentApiKeySetting(agent) {
|
|
742
|
-
return profileSettingsForAgent(agent.id).find(
|
|
743
|
-
(setting) => setting.key !== 'API_KEY' && setting.key.endsWith('_API_KEY'),
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
215
|
export async function getAgentApiKey(profile, agent) {
|
|
748
|
-
const
|
|
749
|
-
const specificKey = specificSetting?.key;
|
|
216
|
+
const specificKey = agentKeyName(agent);
|
|
750
217
|
const specificEnvKey = specificKey && process.env[specificKey]?.trim();
|
|
751
|
-
if (specificEnvKey)
|
|
218
|
+
if (specificEnvKey)
|
|
219
|
+
return { key: specificEnvKey, source: `${specificKey} env var` };
|
|
752
220
|
|
|
753
221
|
const sharedEnvKey = process.env.SUBCONSCIOUS_API_KEY?.trim();
|
|
754
222
|
if (sharedEnvKey) {
|
|
@@ -777,109 +245,79 @@ async function requireApiKey(profile, agent) {
|
|
|
777
245
|
return null;
|
|
778
246
|
}
|
|
779
247
|
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
'ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME',
|
|
789
|
-
'ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION',
|
|
790
|
-
'ANTHROPIC_DEFAULT_FABLE_MODEL',
|
|
791
|
-
'ANTHROPIC_DEFAULT_FABLE_MODEL_NAME',
|
|
792
|
-
'ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION',
|
|
793
|
-
'ANTHROPIC_CUSTOM_MODEL_OPTION',
|
|
794
|
-
'ANTHROPIC_CUSTOM_MODEL_OPTION_NAME',
|
|
795
|
-
'ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION',
|
|
796
|
-
];
|
|
797
|
-
|
|
798
|
-
const CLAUDE_MODEL_PICKER_ID_KEYS = [
|
|
799
|
-
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
800
|
-
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
801
|
-
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
802
|
-
'ANTHROPIC_DEFAULT_FABLE_MODEL',
|
|
803
|
-
'ANTHROPIC_CUSTOM_MODEL_OPTION',
|
|
804
|
-
];
|
|
805
|
-
|
|
806
|
-
function uniqueCatalogModels(models, fallbackModel) {
|
|
807
|
-
const unique = [];
|
|
808
|
-
for (const model of models) {
|
|
809
|
-
if (model && !unique.includes(model)) unique.push(model);
|
|
810
|
-
}
|
|
811
|
-
if (unique.length === 0 && fallbackModel) unique.push(fallbackModel);
|
|
812
|
-
return unique;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
export function claudePickerSettings(models, fallbackModel) {
|
|
816
|
-
const unique = uniqueCatalogModels(models, fallbackModel);
|
|
817
|
-
return {
|
|
818
|
-
availableModels: unique,
|
|
819
|
-
modelPicker: {
|
|
820
|
-
replaceBuiltInOptions: true,
|
|
821
|
-
options: unique.map((model) => ({
|
|
822
|
-
model,
|
|
823
|
-
label: model,
|
|
824
|
-
description: `Subconscious model ${model}`,
|
|
825
|
-
})),
|
|
248
|
+
export const RUNBOOK_ENV_DESCRIPTION = Object.freeze({
|
|
249
|
+
order:
|
|
250
|
+
'Later layers win: Claude picker env (claude-code only), then profile values, then process.env, then the fixed values. For claude-code, picker slots whose value is not in the live catalog are then reset to the catalog. Each agent then applies its own inputs and env entries.',
|
|
251
|
+
inherited: [
|
|
252
|
+
{
|
|
253
|
+
source: 'profile',
|
|
254
|
+
description:
|
|
255
|
+
'Every key in the selected profile env file, for example CODEX_CONTEXT_WINDOW.',
|
|
826
256
|
},
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
}
|
|
257
|
+
{
|
|
258
|
+
source: 'process.env',
|
|
259
|
+
description:
|
|
260
|
+
"The caller's whole environment. It overrides profile values.",
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
fixed: [
|
|
264
|
+
{
|
|
265
|
+
name: 'GATEWAY_URL',
|
|
266
|
+
value: '{baseUrl}',
|
|
267
|
+
description:
|
|
268
|
+
'SUBCONSCIOUS_BASE_URL, then profile GATEWAY_URL, then the packaged default. Trailing slashes are removed.',
|
|
269
|
+
},
|
|
270
|
+
{ name: 'API_KEY', value: '{apiKey}', description: 'Gateway credential.' },
|
|
271
|
+
{
|
|
272
|
+
name: '<agent key>',
|
|
273
|
+
value: '{apiKey}',
|
|
274
|
+
description:
|
|
275
|
+
'The agent-specific key input (for example CODEX_API_KEY), set to the same credential.',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: 'MODEL',
|
|
279
|
+
value: '{model}',
|
|
280
|
+
description:
|
|
281
|
+
'--model, then SUBCONSCIOUS_MODEL, then profile MODEL; UNSET or blank uses the first catalog model. A profile or default model missing from a live catalog is replaced by the first live model.',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'SUBCONSCIOUS_MODELS',
|
|
285
|
+
value: '{catalog}',
|
|
286
|
+
description: 'Newline-separated live model catalog.',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'PATH',
|
|
290
|
+
value: '{binDir}:<install dirs>:{PATH}',
|
|
291
|
+
description:
|
|
292
|
+
"The caller's PATH with the agent's install directory and common install directories prepended.",
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
runbook_scripts: [
|
|
296
|
+
{
|
|
297
|
+
name: 'SUBC_MODEL_IDS',
|
|
298
|
+
value: '{model} then {catalog}',
|
|
299
|
+
description: 'The launch model first, then the catalog, deduplicated.',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: 'SUBC_VISION_MODELS',
|
|
303
|
+
value: 'model IDs',
|
|
304
|
+
description: 'Models that accept images, newline-separated.',
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: 'SUBC_TEMPLATE_WORDS',
|
|
308
|
+
value: 'count',
|
|
309
|
+
description:
|
|
310
|
+
'How many leading argv words came from the argv template; only those may hold {tempFile}.',
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
per_harness: { 'claude-code': CLAUDE_PICKER_ENV_DESCRIPTION },
|
|
314
|
+
});
|
|
882
315
|
|
|
316
|
+
/**
|
|
317
|
+
* The env every launch and setup starts from: profile values, then the
|
|
318
|
+
* caller's env, then the resolved gateway, key, model, and catalog. Claude
|
|
319
|
+
* Code also gets its model picker, kept inside the live catalog.
|
|
320
|
+
*/
|
|
883
321
|
export function runbookEnv(
|
|
884
322
|
apiKey,
|
|
885
323
|
model,
|
|
@@ -890,8 +328,9 @@ export function runbookEnv(
|
|
|
890
328
|
) {
|
|
891
329
|
const ctx = buildContext(apiKey, model, profile);
|
|
892
330
|
const extraDirs = [binDir, ...candidateBinDirs()].filter(Boolean);
|
|
893
|
-
const
|
|
894
|
-
const
|
|
331
|
+
const keyName = agentKeyName(agent);
|
|
332
|
+
const claude = agent.id === CLAUDE_CODE;
|
|
333
|
+
const picker = claude ? claudeModelPickerEnv(model, models) : {};
|
|
895
334
|
return applyClaudePickerCatalog(
|
|
896
335
|
{
|
|
897
336
|
...picker,
|
|
@@ -899,17 +338,16 @@ export function runbookEnv(
|
|
|
899
338
|
...process.env,
|
|
900
339
|
GATEWAY_URL: ctx.baseUrl,
|
|
901
340
|
API_KEY: apiKey,
|
|
902
|
-
...(
|
|
341
|
+
...(keyName ? { [keyName]: apiKey } : {}),
|
|
903
342
|
MODEL: model,
|
|
904
343
|
SUBCONSCIOUS_MODELS: models.join('\n'),
|
|
905
|
-
...(
|
|
344
|
+
...(claude
|
|
906
345
|
? {
|
|
907
|
-
SUBC_CLAUDE_SETTINGS: JSON.stringify(
|
|
908
|
-
|
|
909
|
-
|
|
346
|
+
SUBC_CLAUDE_SETTINGS: JSON.stringify(
|
|
347
|
+
claudePickerSettings(models, model),
|
|
348
|
+
),
|
|
910
349
|
}
|
|
911
350
|
: {}),
|
|
912
|
-
SUBC_ENV_FILE: os.devNull,
|
|
913
351
|
PATH: augmentPath(extraDirs, binDir),
|
|
914
352
|
},
|
|
915
353
|
picker,
|
|
@@ -949,46 +387,86 @@ export function selectLaunchModel(requestedModel, modelSource, catalog) {
|
|
|
949
387
|
return useLiveDefault ? first : requestedModel;
|
|
950
388
|
}
|
|
951
389
|
|
|
952
|
-
async function runRunbookSetup(agent, argv, profile
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
},
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
390
|
+
async function runRunbookSetup(agent, argv, profile) {
|
|
391
|
+
const script = agent.runbook.setup_script;
|
|
392
|
+
if (isSetupWithoutAuth(argv) || agent.runbook.setup_needs_auth === false) {
|
|
393
|
+
// Status, uninstall, and Marathon's binary install read no model or
|
|
394
|
+
// agent setting, so an invalid saved one must not block them.
|
|
395
|
+
const lenient = { strict: false };
|
|
396
|
+
const base = { ...(profile?.values || {}), ...process.env };
|
|
397
|
+
const env = resolveInputs(agent, base, {}, lenient);
|
|
398
|
+
return runCommand(scriptCommand(agent, script, argv, env, lenient));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const {
|
|
402
|
+
model: requestedModel,
|
|
403
|
+
modelSource,
|
|
404
|
+
rest,
|
|
405
|
+
} = extractModel(argv, profile);
|
|
406
|
+
const apiKey =
|
|
407
|
+
optionValue(rest, '--api-key') || (await requireApiKey(profile, agent));
|
|
963
408
|
if (!apiKey) return 1;
|
|
964
|
-
const catalog = await resolvedModelsForLaunch(
|
|
409
|
+
const catalog = await resolvedModelsForLaunch(
|
|
410
|
+
profile,
|
|
411
|
+
apiKey,
|
|
412
|
+
requestedModel,
|
|
413
|
+
);
|
|
965
414
|
const model = selectLaunchModel(requestedModel, modelSource, catalog);
|
|
966
415
|
if (requestedModel && model !== requestedModel) {
|
|
967
416
|
console.error(
|
|
968
417
|
` ${c.yellow}Configured model ${requestedModel} is not in the live catalog; using ${model}.${c.reset}\n`,
|
|
969
418
|
);
|
|
970
419
|
}
|
|
971
|
-
const ctx = buildContext(apiKey, model, profile);
|
|
972
|
-
const authArgs = substitute(agent.runbook.authArgs || [], ctx);
|
|
973
420
|
|
|
974
421
|
console.log(
|
|
975
422
|
` ${c.dim}Configuring ${c.reset}${c.bold}${agent.name}${c.reset} ${c.dim}for Subconscious (${model})${c.reset}\n`,
|
|
976
423
|
);
|
|
977
|
-
|
|
424
|
+
// Setup scripts read no strict-choice setting; only the model list matters.
|
|
425
|
+
const env = resolveInputs(
|
|
978
426
|
agent,
|
|
979
|
-
[...authArgs, ...rest],
|
|
980
427
|
runbookEnv(apiKey, model, undefined, profile, agent, catalog.models),
|
|
981
|
-
|
|
428
|
+
{},
|
|
429
|
+
{ strict: false },
|
|
982
430
|
);
|
|
431
|
+
const code = await runCommand(scriptCommand(agent, script, rest, env));
|
|
983
432
|
const installed = !['status', 'uninstall'].includes(rest[0]);
|
|
984
433
|
if (code !== 0 || !installed) return code;
|
|
985
434
|
|
|
986
|
-
if (agent.
|
|
987
|
-
console.log(`\n ${c.dim}
|
|
435
|
+
if (agent.runbook.after_install) {
|
|
436
|
+
console.log(`\n ${c.dim}${agent.runbook.after_install}${c.reset}\n`);
|
|
988
437
|
}
|
|
989
438
|
return code;
|
|
990
439
|
}
|
|
991
440
|
|
|
441
|
+
async function runSetupAction(agent, parsed, profile) {
|
|
442
|
+
if (!agent.runbook.setup_script) {
|
|
443
|
+
throw new Error(`No persistent integration is available for ${agent.name}`);
|
|
444
|
+
}
|
|
445
|
+
const setupArgs =
|
|
446
|
+
parsed.args[0] === parsed.action
|
|
447
|
+
? parsed.args
|
|
448
|
+
: [parsed.action, ...parsed.args];
|
|
449
|
+
const code = await runRunbookSetup(agent, setupArgs, profile);
|
|
450
|
+
if (code === 0) {
|
|
451
|
+
const message =
|
|
452
|
+
parsed.action === 'status'
|
|
453
|
+
? `${agent.name} status check complete.`
|
|
454
|
+
: parsed.action === 'uninstall'
|
|
455
|
+
? `${agent.name} integration removed.`
|
|
456
|
+
: `${agent.name} setup complete.`;
|
|
457
|
+
console.log(`\n ${c.green}${c.bold}✓ ${message}${c.reset}\n`);
|
|
458
|
+
}
|
|
459
|
+
return code;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Split `headless PROMPT args...` from the words after subc's own flags. */
|
|
463
|
+
function headlessParts(rest) {
|
|
464
|
+
const index = headlessPromptIndex(rest);
|
|
465
|
+
return index < 0
|
|
466
|
+
? { args: rest, prompt: undefined }
|
|
467
|
+
: { args: rest.slice(index + 1), prompt: rest[index] };
|
|
468
|
+
}
|
|
469
|
+
|
|
992
470
|
/**
|
|
993
471
|
* Launch a coding agent against Subconscious. `argv` is everything after the
|
|
994
472
|
* agent name; unknown flags pass straight through to the underlying CLI.
|
|
@@ -1000,46 +478,57 @@ export async function runAgent(agent, argv, options = {}) {
|
|
|
1000
478
|
return 0;
|
|
1001
479
|
}
|
|
1002
480
|
|
|
481
|
+
const headless = isHeadlessRequest(agent, argv);
|
|
482
|
+
if (headless && !agent.launch.headless_platforms.includes(process.platform)) {
|
|
483
|
+
throw new Error(
|
|
484
|
+
`${agent.name} headless mode is not available on ${process.platform === 'win32' ? 'Windows' : process.platform} yet.`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
1003
488
|
if (process.platform === 'win32') {
|
|
1004
489
|
return runWindowsAgent(agent, argv, {
|
|
1005
|
-
profile,
|
|
1006
|
-
|
|
1007
|
-
|
|
490
|
+
profile,
|
|
491
|
+
parseAgentAction,
|
|
492
|
+
extractModel,
|
|
493
|
+
requireApiKey,
|
|
494
|
+
resolvedModelsForLaunch,
|
|
495
|
+
selectLaunchModel,
|
|
496
|
+
runbookEnv,
|
|
497
|
+
minimumClaudeVersion: MIN_CLAUDE_CODE_VERSION,
|
|
498
|
+
headless,
|
|
499
|
+
parseClaudeVersion,
|
|
500
|
+
claudeVersionNeedsUpgrade,
|
|
1008
501
|
});
|
|
1009
502
|
}
|
|
1010
503
|
|
|
1011
504
|
const parsed = parseAgentAction(agent, argv);
|
|
1012
|
-
if (parsed.action !== 'launch') {
|
|
1013
|
-
|
|
1014
|
-
throw new Error(`No persistent integration is available for ${agent.name}`);
|
|
1015
|
-
}
|
|
1016
|
-
const setupArgs =
|
|
1017
|
-
parsed.args[0] === parsed.action ? parsed.args : [parsed.action, ...parsed.args];
|
|
1018
|
-
const code = await runRunbookSetup(
|
|
1019
|
-
agent,
|
|
1020
|
-
setupArgs,
|
|
1021
|
-
profile,
|
|
1022
|
-
agent.runbook.setupScript,
|
|
1023
|
-
);
|
|
1024
|
-
if (code === 0) {
|
|
1025
|
-
const message =
|
|
1026
|
-
parsed.action === 'status'
|
|
1027
|
-
? `${agent.name} status check complete.`
|
|
1028
|
-
: parsed.action === 'uninstall'
|
|
1029
|
-
? `${agent.name} integration removed.`
|
|
1030
|
-
: `${agent.name} setup complete.`;
|
|
1031
|
-
console.log(`\n ${c.green}${c.bold}✓ ${message}${c.reset}\n`);
|
|
1032
|
-
}
|
|
1033
|
-
return code;
|
|
505
|
+
if (parsed.action !== 'launch' && !headless) {
|
|
506
|
+
return runSetupAction(agent, parsed, profile);
|
|
1034
507
|
}
|
|
1035
508
|
|
|
1036
|
-
|
|
509
|
+
return launchAgent(agent, argv, profile, headless);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function launchAgent(agent, argv, profile, headless) {
|
|
513
|
+
// Arguments after -- belong to the agent, including its own --model.
|
|
514
|
+
const boundary = separatorIndex(argv);
|
|
515
|
+
const {
|
|
516
|
+
model: requestedModel,
|
|
517
|
+
modelSource,
|
|
518
|
+
rest: subcRest,
|
|
519
|
+
} = extractModel(boundary < 0 ? argv : argv.slice(0, boundary), profile);
|
|
520
|
+
const rest = boundary < 0 ? subcRest : [...subcRest, ...argv.slice(boundary)];
|
|
1037
521
|
const apiKey = await requireApiKey(profile, agent);
|
|
1038
522
|
if (!apiKey) return 1;
|
|
1039
523
|
|
|
1040
|
-
const
|
|
1041
|
-
await
|
|
1042
|
-
|
|
524
|
+
const mayPrompt = !headless;
|
|
525
|
+
const binDir = await ensureInstalled(agent, { mayPrompt });
|
|
526
|
+
await ensureMinimumVersion(agent, binDir, { mayPrompt });
|
|
527
|
+
const catalog = await resolvedModelsForLaunch(
|
|
528
|
+
profile,
|
|
529
|
+
apiKey,
|
|
530
|
+
requestedModel,
|
|
531
|
+
);
|
|
1043
532
|
const model = selectLaunchModel(requestedModel, modelSource, catalog);
|
|
1044
533
|
if (requestedModel && model !== requestedModel) {
|
|
1045
534
|
console.error(
|
|
@@ -1047,58 +536,13 @@ export async function runAgent(agent, argv, options = {}) {
|
|
|
1047
536
|
);
|
|
1048
537
|
}
|
|
1049
538
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
` ${c.dim}Launching ${c.reset}${c.bold}${agent.name}${c.reset} ${c.dim}on Subconscious ${c.reset}${c.dim}(${model})${c.reset}\n`,
|
|
1053
|
-
);
|
|
1054
|
-
return spawnRunbook(
|
|
1055
|
-
agent,
|
|
1056
|
-
rest,
|
|
1057
|
-
runbookEnv(apiKey, model, binDir, profile, agent, catalog.models),
|
|
1058
|
-
);
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
const ctx = buildContext(apiKey, model, profile);
|
|
1062
|
-
const launch = substituteString(agent.launch, ctx);
|
|
1063
|
-
const [bin, ...launchArgs] = launch.split(' ').filter(Boolean);
|
|
1064
|
-
const envMap = substitute(agent.env, ctx);
|
|
1065
|
-
|
|
1066
|
-
// Prepend the resolved bin dir + candidate dirs to the child's PATH so the
|
|
1067
|
-
// agent (and any subprocess it spawns) resolves correctly this session, even
|
|
1068
|
-
// if it was installed into a dir not yet on the parent shell's PATH.
|
|
1069
|
-
const extraDirs = [binDir, ...candidateBinDirs()].filter(Boolean);
|
|
1070
|
-
const env = {
|
|
1071
|
-
...envMap,
|
|
1072
|
-
...(profile?.values || {}),
|
|
1073
|
-
...process.env,
|
|
1074
|
-
PATH: augmentPath(extraDirs, binDir),
|
|
1075
|
-
};
|
|
1076
|
-
const args = [...launchArgs, ...rest];
|
|
1077
|
-
|
|
1078
|
-
console.log(
|
|
539
|
+
// Headless stdout belongs to the harness alone.
|
|
540
|
+
(headless ? console.error : console.log)(
|
|
1079
541
|
` ${c.dim}Launching ${c.reset}${c.bold}${agent.name}${c.reset} ${c.dim}on Subconscious ${c.reset}${c.dim}(${model})${c.reset}\n`,
|
|
1080
542
|
);
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
console.error(
|
|
1087
|
-
`\n ${c.red}Could not launch \`${bin}\`.${c.reset} Install it with:\n`,
|
|
1088
|
-
);
|
|
1089
|
-
printInstallCommands(agent);
|
|
1090
|
-
process.exit(127);
|
|
1091
|
-
}
|
|
1092
|
-
console.error(`\n ${c.red}${err.message}${c.reset}\n`);
|
|
1093
|
-
process.exit(1);
|
|
1094
|
-
});
|
|
1095
|
-
|
|
1096
|
-
// Mirror the child's exit status so callers/scripts see the real result.
|
|
1097
|
-
child.on('exit', (code, signal) => {
|
|
1098
|
-
if (signal) {
|
|
1099
|
-
process.kill(process.pid, signal);
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
process.exit(code ?? 0);
|
|
1103
|
-
});
|
|
543
|
+
const env = runbookEnv(apiKey, model, binDir, profile, agent, catalog.models);
|
|
544
|
+
// The headless endpoint is the one endpoint for every harness, Claude too.
|
|
545
|
+
const launchEnv = headless ? { ...env, CLAUDE_GATEWAY_URL: '' } : env;
|
|
546
|
+
const plan = launchPlan(agent, { env: launchEnv, ...headlessParts(rest) });
|
|
547
|
+
return runCommand(planCommand(agent, plan, binDir));
|
|
1104
548
|
}
|