subconscious-cli 0.2.1 → 0.3.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 +228 -76
- package/bin/agents.js +400 -25
- package/bin/auth.js +94 -20
- package/bin/branding.js +39 -0
- package/bin/cli.js +187 -17
- package/bin/colors.js +22 -10
- package/bin/profiles.js +852 -0
- package/bin/registry.generated.json +154 -44
- package/bin/runbook/README.md +25 -0
- package/bin/runbook/claude-code/install.sh +316 -0
- package/bin/runbook/claude-code/run.sh +164 -0
- package/bin/runbook/codex/hook.sh +112 -0
- package/bin/runbook/codex/hooks.json +29 -0
- package/bin/runbook/codex/install.sh +524 -0
- package/bin/runbook/codex/run.sh +264 -0
- package/bin/runbook/copilot/hook.sh +173 -0
- package/bin/runbook/copilot/hooks.json +19 -0
- package/bin/runbook/copilot/install.sh +469 -0
- package/bin/runbook/cursor/hook.sh +145 -0
- package/bin/runbook/cursor/hooks.json +17 -0
- package/bin/runbook/cursor/install.sh +259 -0
- package/bin/runbook/opencode/install.sh +298 -0
- package/bin/runbook/opencode/run.sh +107 -0
- package/bin/runbook/opencode/subconscious-compaction.ts +100 -0
- package/bin/runbook/pi/install.sh +282 -0
- package/bin/runbook/pi/run.sh +25 -0
- package/bin/runbook/pi/subconscious-compaction.ts +151 -0
- package/package.json +10 -5
package/bin/agents.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coding-agent launcher.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `subc <agent>` resolves your saved API key and dispatches the vendored
|
|
5
|
+
* ol-runbook integration. Terminal agents launch ephemerally; IDE/config-based
|
|
6
|
+
* agents run their setup scripts.
|
|
7
7
|
*
|
|
8
8
|
* There is NO hardcoded agent data here: everything is read from
|
|
9
9
|
* `registry.generated.json` (shipped under `bin/`), which is generated from the
|
|
@@ -17,14 +17,21 @@ import { constants as fsConstants } from 'node:fs';
|
|
|
17
17
|
import os from 'node:os';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import readline from 'node:readline';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
20
21
|
import { c } from './colors.js';
|
|
21
22
|
import { getApiKey } from './auth.js';
|
|
23
|
+
import { profileSettingsForAgent, resolvedProfileValues } from './profiles.js';
|
|
22
24
|
|
|
23
25
|
// --- Registry (single source of truth, generated copy shipped in the package).
|
|
24
26
|
const registry = JSON.parse(
|
|
25
27
|
readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf-8'),
|
|
26
28
|
);
|
|
27
29
|
const DEFAULTS = registry.defaults;
|
|
30
|
+
const SUPPORTED_MODELS =
|
|
31
|
+
Array.isArray(DEFAULTS.models) && DEFAULTS.models.length
|
|
32
|
+
? DEFAULTS.models
|
|
33
|
+
: [DEFAULTS.model];
|
|
34
|
+
const RUNBOOK_DIR = fileURLToPath(new URL('./runbook/', import.meta.url));
|
|
28
35
|
|
|
29
36
|
// --- Token substitution — same rules as scripts/lib/registry.js.
|
|
30
37
|
// Replaces {apiKey}, {model}, {baseUrl}, {baseUrlV1}. NEVER touches {env:...}.
|
|
@@ -73,13 +80,16 @@ function resolveInstall(install) {
|
|
|
73
80
|
// --- Build the in-memory registry + alias index.
|
|
74
81
|
// Each agent gets a resolved per-OS `install` (string) plus optional
|
|
75
82
|
// `installFallback`, while keeping the original per-OS object available.
|
|
76
|
-
const AGENTS = registry.agents
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
83
|
+
const AGENTS = registry.agents
|
|
84
|
+
.filter((agent) => agent.cli !== false)
|
|
85
|
+
.map((agent) => {
|
|
86
|
+
const { command, fallback } = resolveInstall(agent.install);
|
|
87
|
+
return { ...agent, install: command, installFallback: fallback };
|
|
88
|
+
});
|
|
80
89
|
const BY_ALIAS = new Map();
|
|
81
90
|
for (const agent of AGENTS) {
|
|
82
91
|
BY_ALIAS.set(agent.id, agent);
|
|
92
|
+
if (agent.command) BY_ALIAS.set(agent.command, agent);
|
|
83
93
|
for (const alias of agent.aliases || []) BY_ALIAS.set(alias, agent);
|
|
84
94
|
}
|
|
85
95
|
|
|
@@ -88,7 +98,169 @@ export function resolveAgent(name) {
|
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
export function agentList() {
|
|
91
|
-
return AGENTS.map((a) => ({
|
|
101
|
+
return AGENTS.map((a) => ({
|
|
102
|
+
name: a.name,
|
|
103
|
+
alias: a.command || a.id,
|
|
104
|
+
action: a.runbook?.mode === 'setup' ? 'Configure' : 'Launch',
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function setupAgentList() {
|
|
109
|
+
return AGENTS.filter((agent) => agent.runbook?.setupScript);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const SETUP_ACTIONS = new Set(['install', 'status', 'uninstall']);
|
|
113
|
+
const SETUP_HELPER_ACTIONS = new Set(['use', 'env', 'unset']);
|
|
114
|
+
const SETUP_HELPER_AGENT_IDS = new Set(['claude-code', 'codex']);
|
|
115
|
+
|
|
116
|
+
export function parseSetupRequest(argv = []) {
|
|
117
|
+
const [first, ...rest] = argv;
|
|
118
|
+
if (!first || SETUP_ACTIONS.has(first)) {
|
|
119
|
+
if (rest.length) {
|
|
120
|
+
throw new Error('Agent-specific setup options require a target agent');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
agents: setupAgentList(),
|
|
124
|
+
args: first ? [first] : [],
|
|
125
|
+
action: first || 'install',
|
|
126
|
+
targeted: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const agent = resolveAgent(first);
|
|
131
|
+
if (!agent) throw new Error(`Unknown coding agent or setup action: ${first}`);
|
|
132
|
+
if (!agent.runbook?.setupScript) {
|
|
133
|
+
throw new Error(`No persistent setup integration is available for ${agent.name}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const requestedAction = rest[0];
|
|
137
|
+
const helperAction = SETUP_HELPER_ACTIONS.has(requestedAction);
|
|
138
|
+
if (helperAction && !SETUP_HELPER_AGENT_IDS.has(agent.id)) {
|
|
139
|
+
throw new Error(`${agent.name} does not support the '${requestedAction}' setup action`);
|
|
140
|
+
}
|
|
141
|
+
if (
|
|
142
|
+
requestedAction &&
|
|
143
|
+
!requestedAction.startsWith('-') &&
|
|
144
|
+
!SETUP_ACTIONS.has(requestedAction) &&
|
|
145
|
+
!helperAction
|
|
146
|
+
) {
|
|
147
|
+
throw new Error(`Unknown setup action for ${agent.name}: ${requestedAction}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
agents: [agent],
|
|
152
|
+
args: rest,
|
|
153
|
+
action:
|
|
154
|
+
SETUP_ACTIONS.has(requestedAction) || helperAction ? requestedAction : 'install',
|
|
155
|
+
targeted: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const AGENT_HELP = {
|
|
160
|
+
'claude-code': {
|
|
161
|
+
usage: 'subc [--profile NAME] claude [integration options] [Claude arguments...]',
|
|
162
|
+
behavior: 'Launches Claude Code with the active Subconscious profile and model picker.',
|
|
163
|
+
options: [
|
|
164
|
+
['--model MODEL', 'Override the profile model for this launch'],
|
|
165
|
+
['--compact-window N', 'Override the Claude auto-compact window'],
|
|
166
|
+
['--max-context-tokens N', 'Override the maximum context tokens'],
|
|
167
|
+
['-- ARGS...', 'Pass remaining arguments to Claude Code'],
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
codex: {
|
|
171
|
+
usage: 'subc [--profile NAME] codex [integration options] [Codex arguments...]',
|
|
172
|
+
behavior: 'Launches Codex with a temporary Subconscious provider catalog and compaction hooks.',
|
|
173
|
+
options: [
|
|
174
|
+
['--model MODEL', 'Override the profile model for this launch'],
|
|
175
|
+
['--context-window N', 'Override catalog context_window'],
|
|
176
|
+
['--max-context-window N', 'Override catalog max_context_window'],
|
|
177
|
+
['--auto-compact-token-limit N', 'Override the automatic compaction threshold'],
|
|
178
|
+
['--reasoning-effort LEVEL', 'Use none, low, medium, high, or max'],
|
|
179
|
+
['--external-tools', 'Enable Codex apps/plugins for this launch'],
|
|
180
|
+
['--subagents', 'Use the pinned legacy Codex subagent mode'],
|
|
181
|
+
['-- ARGS...', 'Pass remaining arguments to Codex'],
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
opencode: {
|
|
185
|
+
usage: 'subc [--profile NAME] opencode [OpenCode arguments...]',
|
|
186
|
+
behavior: 'Launches OpenCode with an ephemeral provider containing every Subconscious model.',
|
|
187
|
+
options: [
|
|
188
|
+
['--model MODEL', 'Override the profile model for this launch'],
|
|
189
|
+
['ARGS...', 'Pass arguments directly to OpenCode'],
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
cursor: {
|
|
193
|
+
usage: 'subc [--profile NAME] cursor [install|status|uninstall]',
|
|
194
|
+
behavior: 'Manages Cursor correlation hooks; model endpoint setup is completed in Cursor Settings.',
|
|
195
|
+
options: [
|
|
196
|
+
['install', 'Install or update the Cursor hooks (default action)'],
|
|
197
|
+
['status', 'Inspect the installed hook configuration'],
|
|
198
|
+
['uninstall', 'Remove only the Subconscious Cursor hooks'],
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
copilot: {
|
|
202
|
+
usage: 'subc [--profile NAME] copilot [install|status|uninstall]',
|
|
203
|
+
behavior: 'Manages the VS Code model provider and Copilot correlation hooks.',
|
|
204
|
+
options: [
|
|
205
|
+
['install', 'Install or update the provider and hooks (default action)'],
|
|
206
|
+
['status', 'Inspect the installed provider and hooks'],
|
|
207
|
+
['uninstall', 'Remove the Subconscious provider and hooks'],
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
pi: {
|
|
211
|
+
usage: 'subc [--profile NAME] pi [Pi arguments...]',
|
|
212
|
+
behavior: 'Launches Pi read-only against the provider previously configured by subc setup.',
|
|
213
|
+
options: [
|
|
214
|
+
['--model MODEL', 'Override the profile model for this launch'],
|
|
215
|
+
['ARGS...', 'Pass arguments directly to Pi'],
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export function isAgentHelpRequest(argv = []) {
|
|
221
|
+
return ['help', '-h', '--help'].includes(argv[0]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function displayProfileValue(setting, value, values) {
|
|
225
|
+
if (setting.type === 'secret') {
|
|
226
|
+
if (value) return '(set)';
|
|
227
|
+
return setting.key !== 'API_KEY' && values.API_KEY ? '(shared key)' : '(not set)';
|
|
228
|
+
}
|
|
229
|
+
return value || '(auto)';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function printAgentHelp(agent, profile) {
|
|
233
|
+
const details = AGENT_HELP[agent.id] || {
|
|
234
|
+
usage: `subc [--profile NAME] ${agent.command || agent.id} [arguments...]`,
|
|
235
|
+
behavior: agent.description,
|
|
236
|
+
options: [],
|
|
237
|
+
};
|
|
238
|
+
const settings = profileSettingsForAgent(agent.id);
|
|
239
|
+
const values = resolvedProfileValues(profile);
|
|
240
|
+
const optionWidth = Math.max(0, ...details.options.map(([option]) => option.length));
|
|
241
|
+
const settingWidth = Math.max(0, ...settings.map((setting) => setting.key.length));
|
|
242
|
+
|
|
243
|
+
console.log(`\n ${c.bold}${agent.name} + Subconscious${c.reset}\n`);
|
|
244
|
+
console.log(` ${details.behavior}\n`);
|
|
245
|
+
console.log(` ${c.bold}Usage${c.reset}\n ${details.usage}\n`);
|
|
246
|
+
if (details.options.length) {
|
|
247
|
+
console.log(` ${c.bold}Commands and options${c.reset}`);
|
|
248
|
+
for (const [option, description] of details.options) {
|
|
249
|
+
console.log(` ${c.cyan}${option.padEnd(optionWidth)}${c.reset} ${description}`);
|
|
250
|
+
}
|
|
251
|
+
console.log();
|
|
252
|
+
}
|
|
253
|
+
console.log(` ${c.bold}Profile settings${c.reset} ${c.dim}(${profile?.name || 'default'})${c.reset}`);
|
|
254
|
+
for (const setting of settings) {
|
|
255
|
+
const value = displayProfileValue(setting, values[setting.key], values);
|
|
256
|
+
console.log(` ${c.cyan}${setting.key.padEnd(settingWidth)}${c.reset} ${value}`);
|
|
257
|
+
console.log(` ${' '.repeat(settingWidth)} ${c.dim}${setting.description}${c.reset}`);
|
|
258
|
+
}
|
|
259
|
+
console.log(`\n Edit these interactively with ${c.cyan}subc --profile ${profile?.name || 'default'} settings${c.reset}.`);
|
|
260
|
+
if (agent.runbook?.setupScript) {
|
|
261
|
+
console.log(` Apply persistent integration setup with ${c.cyan}subc setup ${agent.command || agent.id}${c.reset}.`);
|
|
262
|
+
}
|
|
263
|
+
console.log();
|
|
92
264
|
}
|
|
93
265
|
|
|
94
266
|
/**
|
|
@@ -97,8 +269,12 @@ export function agentList() {
|
|
|
97
269
|
* baseUrl — SUBCONSCIOUS_BASE_URL → registry default
|
|
98
270
|
* baseUrlV1 — `${baseUrl}/v1` (so an override flows to both)
|
|
99
271
|
*/
|
|
100
|
-
function buildContext(apiKey, model) {
|
|
101
|
-
const baseUrl =
|
|
272
|
+
function buildContext(apiKey, model, profile) {
|
|
273
|
+
const baseUrl = (
|
|
274
|
+
process.env.SUBCONSCIOUS_BASE_URL?.trim() ||
|
|
275
|
+
profile?.values?.GATEWAY_URL?.trim() ||
|
|
276
|
+
DEFAULTS.baseUrl
|
|
277
|
+
).replace(/\/+$/, '');
|
|
102
278
|
return { apiKey, model, baseUrl, baseUrlV1: `${baseUrl}/v1` };
|
|
103
279
|
}
|
|
104
280
|
|
|
@@ -107,8 +283,9 @@ function buildContext(apiKey, model) {
|
|
|
107
283
|
* args (so it sets the Subconscious model rather than reaching the agent).
|
|
108
284
|
* Falls back to SUBCONSCIOUS_MODEL, then the registry default.
|
|
109
285
|
*/
|
|
110
|
-
function extractModel(argv) {
|
|
111
|
-
let model =
|
|
286
|
+
function extractModel(argv, profile) {
|
|
287
|
+
let model =
|
|
288
|
+
process.env.SUBCONSCIOUS_MODEL?.trim() || profile?.values?.MODEL?.trim() || DEFAULTS.model;
|
|
112
289
|
const rest = [];
|
|
113
290
|
for (let i = 0; i < argv.length; i++) {
|
|
114
291
|
const a = argv[i];
|
|
@@ -253,6 +430,16 @@ async function ensureInstalled(agent) {
|
|
|
253
430
|
const existing = await resolveBinPath(agent.bin);
|
|
254
431
|
if (existing) return existing;
|
|
255
432
|
|
|
433
|
+
// Agents without an installer are launch-only. Their setup integration may
|
|
434
|
+
// configure the provider, but `subc <agent>` must never install the binary.
|
|
435
|
+
if (!agent.install) {
|
|
436
|
+
console.error(
|
|
437
|
+
`\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${agent.bin}\` not found on PATH).${c.reset}`,
|
|
438
|
+
);
|
|
439
|
+
console.error(` Install ${agent.name} separately, then re-run ${c.cyan}subc ${agent.command || agent.id}${c.reset}.\n`);
|
|
440
|
+
process.exit(127);
|
|
441
|
+
}
|
|
442
|
+
|
|
256
443
|
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
257
444
|
|
|
258
445
|
if (!interactive) {
|
|
@@ -296,30 +483,213 @@ async function ensureInstalled(agent) {
|
|
|
296
483
|
|
|
297
484
|
console.error(
|
|
298
485
|
`\n ${c.dim}Installed ${agent.name}, but it isn't on this shell's PATH yet. ` +
|
|
299
|
-
`Open a new terminal (or add a bin dir to PATH) and re-run \`
|
|
486
|
+
`Open a new terminal (or add a bin dir to PATH) and re-run \`subc ${agent.command || agent.id}\`.${c.reset}\n`,
|
|
300
487
|
);
|
|
301
488
|
process.exit(0);
|
|
302
489
|
}
|
|
303
490
|
|
|
491
|
+
/** Resolve and validate a script inside the packaged ol-runbook snapshot. */
|
|
492
|
+
function runbookScriptPath(agent, relativeScript = agent.runbook.script) {
|
|
493
|
+
const script = path.resolve(RUNBOOK_DIR, relativeScript);
|
|
494
|
+
const relative = path.relative(RUNBOOK_DIR, script);
|
|
495
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
496
|
+
throw new Error(`Invalid runbook script path for ${agent.name}`);
|
|
497
|
+
}
|
|
498
|
+
return script;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** Spawn a runbook script and mirror its exit status/signals. */
|
|
502
|
+
function spawnRunbook(agent, args, env, relativeScript) {
|
|
503
|
+
return new Promise((resolve, reject) => {
|
|
504
|
+
const script = runbookScriptPath(agent, relativeScript);
|
|
505
|
+
const child = spawn('bash', [script, ...args], { stdio: 'inherit', env });
|
|
506
|
+
|
|
507
|
+
child.on('error', (error) => {
|
|
508
|
+
if (error.code === 'ENOENT') {
|
|
509
|
+
reject(
|
|
510
|
+
new Error('The ol-runbook integrations require `bash`, but it was not found on PATH.'),
|
|
511
|
+
);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
reject(error);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
child.on('exit', (code, signal) => {
|
|
518
|
+
if (signal) {
|
|
519
|
+
process.kill(process.pid, signal);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (code) process.exitCode = code;
|
|
523
|
+
resolve(code ?? 0);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function isSetupWithoutAuth(argv) {
|
|
529
|
+
return ['status', 'uninstall', 'use', 'env', 'unset', '-h', '--help'].includes(argv[0]);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function optionValue(argv, name) {
|
|
533
|
+
const index = argv.indexOf(name);
|
|
534
|
+
return index >= 0 ? argv[index + 1]?.trim() || null : null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function agentApiKeySetting(agent) {
|
|
538
|
+
return profileSettingsForAgent(agent.id).find(
|
|
539
|
+
(setting) => setting.key !== 'API_KEY' && setting.key.endsWith('_API_KEY'),
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export async function getAgentApiKey(profile, agent) {
|
|
544
|
+
const specificSetting = agentApiKeySetting(agent);
|
|
545
|
+
const specificKey = specificSetting?.key;
|
|
546
|
+
const specificEnvKey = specificKey && process.env[specificKey]?.trim();
|
|
547
|
+
if (specificEnvKey) return { key: specificEnvKey, source: `${specificKey} env var` };
|
|
548
|
+
|
|
549
|
+
const sharedEnvKey = process.env.SUBCONSCIOUS_API_KEY?.trim();
|
|
550
|
+
if (sharedEnvKey) {
|
|
551
|
+
return { key: sharedEnvKey, source: 'SUBCONSCIOUS_API_KEY env var' };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const profileKey = specificKey && profile?.values?.[specificKey]?.trim();
|
|
555
|
+
if (profileKey) return { key: profileKey, source: profile.path };
|
|
556
|
+
|
|
557
|
+
return getApiKey(profile);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function requireApiKey(profile, agent) {
|
|
561
|
+
const auth = await getAgentApiKey(profile, agent);
|
|
562
|
+
if (auth) return auth.key;
|
|
563
|
+
const login =
|
|
564
|
+
profile?.name && profile.name !== 'default'
|
|
565
|
+
? `subc --profile ${profile.name} login`
|
|
566
|
+
: 'subc login';
|
|
567
|
+
|
|
568
|
+
console.error(`\n ${c.red}Not logged in.${c.reset}`);
|
|
569
|
+
console.error(
|
|
570
|
+
` Run ${c.cyan}${login}${c.reset} (or set ${c.dim}SUBCONSCIOUS_API_KEY${c.reset}) first.\n`,
|
|
571
|
+
);
|
|
572
|
+
process.exitCode = 1;
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const CLAUDE_MODEL_PICKER_KEYS = [
|
|
577
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
578
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL_NAME',
|
|
579
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION',
|
|
580
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
581
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL_NAME',
|
|
582
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION',
|
|
583
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
584
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME',
|
|
585
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION',
|
|
586
|
+
];
|
|
587
|
+
|
|
588
|
+
function claudeModelPickerEnv(agent, ctx) {
|
|
589
|
+
if (agent.id !== 'claude-code') return {};
|
|
590
|
+
const configured = substitute(agent.env || {}, ctx);
|
|
591
|
+
return Object.fromEntries(
|
|
592
|
+
CLAUDE_MODEL_PICKER_KEYS.map((key) => [key, configured[key]]).filter(([, value]) => value),
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function runbookEnv(apiKey, model, binDir, profile, agent) {
|
|
597
|
+
const ctx = buildContext(apiKey, model, profile);
|
|
598
|
+
const extraDirs = [binDir, ...candidateBinDirs()].filter(Boolean);
|
|
599
|
+
const specificApiKey = agentApiKeySetting(agent)?.key;
|
|
600
|
+
return {
|
|
601
|
+
...(profile?.values || {}),
|
|
602
|
+
...process.env,
|
|
603
|
+
...claudeModelPickerEnv(agent, ctx),
|
|
604
|
+
GATEWAY_URL: ctx.baseUrl,
|
|
605
|
+
API_KEY: apiKey,
|
|
606
|
+
...(specificApiKey ? { [specificApiKey]: apiKey } : {}),
|
|
607
|
+
MODEL: model,
|
|
608
|
+
SUBCONSCIOUS_MODELS: SUPPORTED_MODELS.join('\n'),
|
|
609
|
+
MBTA_ENV_FILE: os.devNull,
|
|
610
|
+
PATH: augmentPath(extraDirs),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function runRunbookSetup(agent, argv, profile, relativeScript = agent.runbook.script) {
|
|
615
|
+
if (isSetupWithoutAuth(argv)) {
|
|
616
|
+
return spawnRunbook(agent, argv, {
|
|
617
|
+
...(profile?.values || {}),
|
|
618
|
+
...process.env,
|
|
619
|
+
MBTA_ENV_FILE: os.devNull,
|
|
620
|
+
}, relativeScript);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const { model, rest } = extractModel(argv, profile);
|
|
624
|
+
const apiKey = optionValue(rest, '--api-key') || (await requireApiKey(profile, agent));
|
|
625
|
+
if (!apiKey) return 1;
|
|
626
|
+
const ctx = buildContext(apiKey, model, profile);
|
|
627
|
+
const authArgs = substitute(agent.runbook.authArgs || [], ctx);
|
|
628
|
+
|
|
629
|
+
console.log(
|
|
630
|
+
` ${c.dim}Configuring ${c.reset}${c.bold}${agent.name}${c.reset} ${c.dim}for Subconscious (${model})${c.reset}\n`,
|
|
631
|
+
);
|
|
632
|
+
const code = await spawnRunbook(
|
|
633
|
+
agent,
|
|
634
|
+
[...authArgs, ...rest],
|
|
635
|
+
runbookEnv(apiKey, model, undefined, profile, agent),
|
|
636
|
+
relativeScript,
|
|
637
|
+
);
|
|
638
|
+
const installed = !['status', 'uninstall'].includes(rest[0]);
|
|
639
|
+
if (code !== 0 || !installed) return code;
|
|
640
|
+
|
|
641
|
+
if (agent.id === 'cursor') {
|
|
642
|
+
const modelList = SUPPORTED_MODELS.map(
|
|
643
|
+
(supportedModel) => ` ${c.cyan}${supportedModel}${c.reset}`,
|
|
644
|
+
).join('\n');
|
|
645
|
+
console.log(
|
|
646
|
+
`\n ${c.bold}Finish in Cursor Settings${c.reset}\n` +
|
|
647
|
+
` Enable OpenAI API Key Override, then use:\n` +
|
|
648
|
+
` Base URL: ${c.cyan}${ctx.baseUrl}${c.reset}\n` +
|
|
649
|
+
` Add the available custom models:\n${modelList}\n` +
|
|
650
|
+
` Select ${c.cyan}${model}${c.reset} for this profile.\n` +
|
|
651
|
+
` Paste your Subconscious API key there, then fully restart Cursor.\n`,
|
|
652
|
+
);
|
|
653
|
+
} else if (agent.id === 'pi') {
|
|
654
|
+
console.log(`\n ${c.dim}Start a fresh session with ${c.reset}${c.cyan}subc pi${c.reset}${c.dim}.${c.reset}\n`);
|
|
655
|
+
}
|
|
656
|
+
return code;
|
|
657
|
+
}
|
|
658
|
+
|
|
304
659
|
/**
|
|
305
660
|
* Launch a coding agent against Subconscious. `argv` is everything after the
|
|
306
661
|
* agent name; unknown flags pass straight through to the underlying CLI.
|
|
307
662
|
*/
|
|
308
|
-
export async function runAgent(agent, argv) {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
663
|
+
export async function runAgent(agent, argv, options = {}) {
|
|
664
|
+
const profile = options.profile;
|
|
665
|
+
if (options.setup) {
|
|
666
|
+
if (!agent.runbook?.setupScript) {
|
|
667
|
+
throw new Error(`No setup integration is available for ${agent.name}`);
|
|
668
|
+
}
|
|
669
|
+
return runRunbookSetup(agent, argv, profile, agent.runbook.setupScript);
|
|
670
|
+
}
|
|
671
|
+
if (isAgentHelpRequest(argv)) {
|
|
672
|
+
printAgentHelp(agent, profile);
|
|
673
|
+
return 0;
|
|
318
674
|
}
|
|
675
|
+
if (agent.runbook?.mode === 'setup') {
|
|
676
|
+
return runRunbookSetup(agent, argv, profile);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const { model, rest } = extractModel(argv, profile);
|
|
680
|
+
const apiKey = await requireApiKey(profile, agent);
|
|
681
|
+
if (!apiKey) return 1;
|
|
319
682
|
|
|
320
683
|
const binDir = await ensureInstalled(agent);
|
|
321
684
|
|
|
322
|
-
|
|
685
|
+
if (agent.runbook?.mode === 'launch') {
|
|
686
|
+
console.log(
|
|
687
|
+
` ${c.dim}Launching ${c.reset}${c.bold}${agent.name}${c.reset} ${c.dim}on Subconscious ${c.reset}${c.dim}(${model})${c.reset}\n`,
|
|
688
|
+
);
|
|
689
|
+
return spawnRunbook(agent, rest, runbookEnv(apiKey, model, binDir, profile, agent));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const ctx = buildContext(apiKey, model, profile);
|
|
323
693
|
const launch = substituteString(agent.launch, ctx);
|
|
324
694
|
const [bin, ...launchArgs] = launch.split(' ').filter(Boolean);
|
|
325
695
|
const envMap = substitute(agent.env, ctx);
|
|
@@ -328,7 +698,12 @@ export async function runAgent(agent, argv) {
|
|
|
328
698
|
// agent (and any subprocess it spawns) resolves correctly this session, even
|
|
329
699
|
// if it was installed into a dir not yet on the parent shell's PATH.
|
|
330
700
|
const extraDirs = [binDir, ...candidateBinDirs()].filter(Boolean);
|
|
331
|
-
const env = {
|
|
701
|
+
const env = {
|
|
702
|
+
...(profile?.values || {}),
|
|
703
|
+
...process.env,
|
|
704
|
+
...envMap,
|
|
705
|
+
PATH: augmentPath(extraDirs),
|
|
706
|
+
};
|
|
332
707
|
const args = [...launchArgs, ...rest];
|
|
333
708
|
|
|
334
709
|
console.log(
|