subconscious-cli 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,25 +4,59 @@ Log in to Subconscious, then run coding agents against the Subconscious gateway.
4
4
 
5
5
  ## Quick start
6
6
 
7
+ Install with npm:
8
+
7
9
  ```bash
8
10
  npm install -g subconscious-cli
11
+ ```
12
+
13
+ Or install on macOS or Linux without npm:
14
+
15
+ ```bash
16
+ curl -fsSL https://raw.githubusercontent.com/subconscious-systems/subconscious/main/scripts/install.sh | bash
17
+ ```
18
+
19
+ Then log in and launch an agent:
20
+
21
+ ```bash
9
22
  subc login
10
- subc marathon
23
+ subc opencode
11
24
  subc claude
12
25
  ```
13
26
 
27
+ The curl installer downloads the `subconscious-cli` tarball from the npm
28
+ registry, checks it against the registry's integrity hash, and installs it in
29
+ `~/.local/share/subconscious-cli` with a `subc` link in `~/.local/bin`. It does
30
+ not need npm, so a registry policy that blocks `npm install -g` does not stop
31
+ it. Node.js 18 or newer must already be installed, because `subc` runs on it.
32
+ If `~/.local/bin` is not on your `PATH`, the installer prints the line to add.
33
+ Run it again to reinstall or update. These variables change its defaults:
34
+
35
+ | Variable | Default |
36
+ | --- | --- |
37
+ | `SUBC_VERSION` | `latest` (a version such as `6.1.0`, or a dist-tag) |
38
+ | `SUBC_INSTALL_DIR` | `~/.local/share/subconscious-cli` |
39
+ | `SUBC_BIN_DIR` | `~/.local/bin` |
40
+ | `SUBC_REGISTRY` | `https://registry.npmjs.org` |
41
+
42
+ On Windows, use npm (see [Native Windows support](#native-windows-support)).
43
+
44
+ subc does not install the coding agents. Install each agent you use from its
45
+ vendor. If an agent is missing, `subc <agent>` prints its install command and
46
+ official install page, then exits with status 127.
47
+
14
48
  Every interactive `subc` command checks npm for a newer CLI release. When an
15
49
  update is available, a notice shows the installed and latest versions and lets
16
50
  you select **Update now** or **Skip for now** with the arrow keys and Enter.
17
51
  Each option describes what it will do, and the active option is highlighted.
18
- Update runs `npm install -g subconscious-cli@latest`; Skip continues the
19
- requested command.
52
+ Update runs `npm install -g subconscious-cli@latest`, or the curl installer
53
+ again if that is how subc was installed; Skip continues the requested command.
20
54
  Non-interactive commands automatically skip, and registry errors and timeouts
21
55
  never block the requested command. Set `SUBC_DISABLE_UPDATE_CHECK=1` to suppress
22
56
  the check in offline automation.
23
57
 
24
58
  Login creates both the saved credential and a ready-to-use `default` profile,
25
- so Subconscious Code, Claude Code, Codex, OpenCode, and DeepSeek Harness can launch immediately.
59
+ so OpenCode, Claude Code, Codex, and DeepSeek Harness can launch immediately.
26
60
  Persistent editor and Pi integrations are installed per agent:
27
61
 
28
62
  ```bash
@@ -53,8 +87,8 @@ Every command accepts `help` as a subcommand. These only read the selected
53
87
  profile; they do not authenticate, install, configure, or launch anything:
54
88
 
55
89
  ```bash
90
+ subc opencode help
56
91
  subc claude help
57
- subc marathon help
58
92
  subc codex help
59
93
  subc cursor help
60
94
  subc config help
@@ -69,7 +103,6 @@ gateway environment applied for that process. Arguments pass through as usual:
69
103
 
70
104
  ```bash
71
105
  subc claude --continue
72
- subc marathon -- -p "fix the tests"
73
106
  subc codex exec "write a test"
74
107
  subc opencode
75
108
  subc pi
@@ -105,8 +138,9 @@ Update with `npm.cmd install -g subconscious-cli@latest` or `subc.cmd upgrade --
105
138
 
106
139
  If PowerShell's execution policy blocks npm-generated `.ps1` entry points, use
107
140
  `npm.cmd` and `subc.cmd` or run the same commands in Command Prompt. No policy
108
- change is required. Pi must already be installed; other terminal agents offer
109
- their Windows installer when missing in an interactive terminal.
141
+ change is required. subc does not install the agents on Windows either. If an
142
+ agent is missing, subc prints its install command and official install page,
143
+ then exits with status 127.
110
144
 
111
145
  Native executables and standard npm `.cmd` shims are supported. The launcher
112
146
  invokes the underlying executable or Node entry point directly, preserving
@@ -129,19 +163,6 @@ VS Code's secret store and prompts for its key. Its configuration goes under
129
163
  `%APPDATA%\Code\User` (or Code - Insiders/VSCodium). Rerun install after moving
130
164
  your Node installation, because hooks record the absolute Node executable path.
131
165
 
132
- To install the native x64 `marathon.exe` (stable releases starting with 0.1.4), clear
133
- any preview version pin from the current PowerShell session:
134
-
135
- ```powershell
136
- Remove-Item Env:SC_CODE_VERSION -ErrorAction SilentlyContinue
137
- subc.cmd marathon install
138
- subc.cmd marathon
139
- ```
140
-
141
- The installer requires the selected release's Windows asset and SHA-256
142
- checksum; it reports a clear error if unavailable and never downloads a Unix
143
- binary as a fallback. Windows ARM64 binaries are not currently published.
144
-
145
166
  Run `npm run test:windows` from the repository root for the separate Windows suite. The
146
167
  Windows CI job also builds/tests the Go TUI. Existing Unix tests and runbooks
147
168
  remain separate and unchanged.
@@ -152,18 +173,17 @@ The packaged integrations live in `bin/runbook`.
152
173
 
153
174
  | Command | Behavior |
154
175
  | --- | --- |
155
- | `subc marathon` | Launch Subconscious Code with the active gateway, key, model, and DLR transport |
176
+ | `subc opencode` | Launch OpenCode with the runbook provider, client header, and context/output limits |
156
177
  | `subc claude` | Launch Claude Code with the runbook environment, context limits, subagent limits, and OTEL usage reporting |
157
178
  | `subc codex` | Launch Codex with the runbook provider, temporary model catalog, and surgically merged compaction hooks |
158
- | `subc opencode` | Launch OpenCode with the runbook provider, client header, and context/output limits |
159
- | `subc cursor install` | Install/update Cursor conversation and compaction hooks |
179
+ | `subc cursor install` | Install/update Cursor conversation and compaction hooks (Cursor desktop app and IDE only — the Cursor CLI is not supported) |
160
180
  | `subc copilot install` | Install/update the VS Code custom endpoint and Copilot hooks |
161
181
  | `subc pi` | Refresh the Pi provider from the live catalog, then launch |
162
182
  | `subc dsh` | Launch the DeepSeek Harness Web UI with a temporary provider populated from the live catalog |
163
183
 
164
184
  ## Headless runs
165
185
 
166
- `subc <agent> headless PROMPT [args...]` runs one task without a terminal UI and exits with the agent's status. It works for `claude`, `codex`, `opencode`, `pi`, `marathon`, and `dsh`, and uses the same setup as an interactive launch. It never prompts: there is no update check, a missing agent fails with its install command, stdin is not read, and stdout carries only the agent's own output. Remaining arguments go to the agent, for example `subc claude headless "fix the failing test" --output-format stream-json --verbose`. subc reads `-p`/`--profile` and `--model` itself; put agent options with those names after `--`.
186
+ `subc <agent> headless PROMPT [args...]` runs one task without a terminal UI and exits with the agent's status. It works for `claude`, `codex`, `opencode`, `pi`, and `dsh`, and uses the same setup as an interactive launch. It never prompts: there is no update check, a missing agent fails with its install command, stdin is not read, and stdout carries only the agent's own output. Remaining arguments go to the agent, for example `subc claude headless "fix the failing test" --output-format stream-json --verbose`. subc reads `-p`/`--profile` and `--model` itself; put agent options with those names after `--`.
167
187
 
168
188
  The agents keep their own permission defaults, so a run that should edit files usually needs the agent's permission flags in `[args...]`. For example, `codex exec` uses a read-only sandbox and requires a git repository unless given `--skip-git-repo-check`.
169
189
 
@@ -188,7 +208,6 @@ The endpoint must speak the same APIs as the Subconscious gateway: Anthropic Mes
188
208
  | `codex` | `codex exec -- PROMPT` |
189
209
  | `opencode` | `opencode run`, with the prompt on stdin |
190
210
  | `pi` | `pi --print`, with the prompt on stdin |
191
- | `marathon` | `marathon --print=PROMPT` |
192
211
  | `dsh` | `dsh --profile headless`, with the prompt on stdin |
193
212
 
194
213
  The prompt is always the argument after `headless`, including for `dsh`, which no longer reads a task piped into subc. Where the table says stdin, subc passes the prompt that way so it reaches the agent unchanged even when it starts with `-` or `@`. On Windows only `dsh` supports headless runs.
@@ -222,7 +241,7 @@ Each `harnesses.<id>` entry has:
222
241
 
223
242
  | Field | Meaning |
224
243
  | --- | --- |
225
- | `install` | `method`, `package` or `repository`, `version` or `channel`, `minimum_version`, release `targets`, and the per-OS `commands`. |
244
+ | `install` | `method`, `package` or `repository`, `version` or `channel`, `minimum_version`, release `targets`, the per-OS `commands`, and `url`, the vendor's official install page. subc shows `commands` and `url` and never runs them. |
226
245
  | `prerequisites` | Commands the runbook needs, such as `jq`, with what needs them and whether they are required. `a\|b` means either one. |
227
246
  | `binary` | Executable name, or `null` for IDE integrations. |
228
247
  | `launch` | `argv` template, `headless_argv` for `subc <agent> headless` (absent when unsupported), `headless_stdin` when the prompt is passed on stdin instead, `headless_platforms` (`darwin`, `linux`, `win32`) where it works, `resume` and `handoff` argv for `subc sessions`, Codex's `config_flag`, and whether a launch also writes persistent files. |
@@ -240,7 +259,7 @@ Enumerated values:
240
259
 
241
260
  | Field | Values |
242
261
  | --- | --- |
243
- | `install.method` | `npm`, `script`, `github-release`, `user`, `null` |
262
+ | `install.method` | `npm`, `user`, `null` |
244
263
  | `compaction.default` | `on`, `unknown` |
245
264
  | knob `kind` | `env`, `cli-config`, `config-field`, `model-catalog-field`, `null` |
246
265
  | `threshold.semantics` | `window`, `trigger` |
@@ -293,7 +312,7 @@ subc sessions resume codex:SESSION_ID --harness opencode
293
312
  ```
294
313
 
295
314
  The catalog discovers recent sessions written locally by Claude Code, Codex,
296
- OpenCode, Pi, and Subconscious Code. It shows each session's originating
315
+ OpenCode, and Pi. It shows each session's originating
297
316
  harness, title, last activity, project directory, and model when the harness
298
317
  records one. Selecting the original harness uses its native resume mechanism.
299
318
 
@@ -309,26 +328,12 @@ conversation stores do not expose a stable local resume interface. A session
309
328
  whose local transcript is missing remains available for native resume but does
310
329
  not offer cross-harness destinations.
311
330
 
312
- If Subconscious Code, Claude Code, Codex, OpenCode, or DeepSeek Harness is missing, an interactive terminal offers
313
- to install it before launching. Pi refreshes its Subconscious provider on every
331
+ subc never installs OpenCode, Claude Code, Codex, DeepSeek Harness, or Pi. If
332
+ one is missing, subc prints its install command and official install page, then
333
+ exits with status 127, in interactive and headless runs alike. Pi refreshes its Subconscious provider on every
314
334
  `subc pi` launch while preserving all other providers in `models.json`; its
315
335
  executable must already be installed.
316
336
 
317
- `subc marathon install` detects the operating system and architecture, then downloads
318
- and checksum-verifies the matching precompiled release from
319
- `subconscious-systems/subconscious-code`. Apple Silicon and Intel macOS plus
320
- x86_64 and ARM64 Linux and Windows x64 are supported; Cargo is not required.
321
-
322
- Starting with CLI 4.1.1, `subc marathon` is the native agent command. On Windows,
323
- the installer writes `marathon.exe`, avoiding the system `sc.exe` command.
324
- `subc sc` remains a compatibility alias but never launches Windows service
325
- control. Older pinned release binaries are also installed as `marathon.exe`.
326
- Starting with CLI 4.1.2 and native agent 0.1.6, macOS and Linux also install
327
- and launch `marathon`. Existing `.sc` settings, sessions, and `SC_*` variables
328
- are preserved on every platform. Shell behavior is unchanged. Older Unix
329
- release archives are installed under the new name; existing `sc` files are
330
- left untouched. `subc sc` remains a compatibility alias on every platform.
331
-
332
337
  `subc codex` disables Codex apps and plugin tools for that launch by default so
333
338
  requests remain below the gateway's 128-tool limit. Core coding tools remain
334
339
  available. Use `subc codex --external-tools` to opt back in when targeting a
@@ -383,7 +388,7 @@ rewritten. Run a one-shot task with `subc dsh headless "PROMPT"`.
383
388
  ```
384
389
 
385
390
  The file is mode `600` and contains the shared gateway URL, API key, model,
386
- optional per-agent key overrides, and all Subconscious Code/Claude/Codex/OpenCode/Pi/Copilot/
391
+ optional per-agent key overrides, and all Claude/Codex/OpenCode/Pi/Copilot/
387
392
  DeepSeek Harness context and output settings used by the packaged runbook scripts.
388
393
 
389
394
  ```bash
package/bin/agent-data.js CHANGED
@@ -77,6 +77,30 @@ function validateLaunch(launch, where) {
77
77
  }
78
78
  }
79
79
 
80
+ function validateInstall(install, where) {
81
+ check(isObject(install), where, 'install block is required');
82
+ check(
83
+ typeof install.url === 'string' && install.url.startsWith('https://'),
84
+ where,
85
+ 'install.url must be an https:// link to the official install page',
86
+ );
87
+ const { commands } = install;
88
+ if (commands === null) return;
89
+ check(
90
+ isObject(commands),
91
+ where,
92
+ 'install.commands must be an object or null',
93
+ );
94
+ for (const [os, command] of Object.entries(commands)) {
95
+ check(PLATFORMS.has(os), where, `install.commands.${os} is not an OS`);
96
+ check(
97
+ typeof command === 'string' && command,
98
+ where,
99
+ `install.commands.${os}`,
100
+ );
101
+ }
102
+ }
103
+
80
104
  function validateRunbook(runbook, where) {
81
105
  check(isObject(runbook), where, 'runbook block is required');
82
106
  check(MODES.has(runbook.mode), where, `bad runbook.mode ${runbook.mode}`);
@@ -101,7 +125,7 @@ export function validateAgent(agent, id) {
101
125
  for (const key of ['command', 'name', 'description', 'protocol'])
102
126
  check(typeof agent[key] === 'string' && agent[key], where, `${key}`);
103
127
  check(isStringList(agent.aliases), where, 'aliases must be a list');
104
- check(isObject(agent.install), where, 'install block is required');
128
+ validateInstall(agent.install, where);
105
129
  check(isObject(agent.help), where, 'help block is required');
106
130
  check(Array.isArray(agent.help.options), where, 'help.options');
107
131
  validateRunbook(agent.runbook, where);
@@ -176,6 +200,18 @@ export function installCommands(agent) {
176
200
  );
177
201
  }
178
202
 
203
+ /**
204
+ * The install command and official install page to show when the agent's
205
+ * binary is missing. subc prints these and never runs them. Windows never
206
+ * borrows the Linux command.
207
+ */
208
+ export function installAdvice(agent, platform = process.platform) {
209
+ const commands = installCommands(agent);
210
+ const command =
211
+ commands?.[platform] ?? (platform === 'win32' ? null : commands?.linux);
212
+ return { command: command ?? null, url: agent.install.url };
213
+ }
214
+
179
215
  /** The API key input that overrides API_KEY for this agent, if any. */
180
216
  export function agentKeyName(agent) {
181
217
  return (
package/bin/agent-help.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
+ agentBinary,
2
3
  agentCommandName,
3
4
  agentInputs,
4
5
  agentSetupActions,
6
+ installAdvice,
5
7
  } from './agent-data.js';
6
8
  import { c } from './colors.js';
7
9
  import { profileSettingsForAgent, resolvedProfileValues } from './profiles.js';
@@ -72,12 +74,18 @@ function printSetupHints(agent, profile) {
72
74
  console.log(
73
75
  `\n Edit the env file with ${c.cyan}subc -p ${profile?.name || 'default'} config edit${c.reset}.`,
74
76
  );
77
+ if (agentBinary(agent)) {
78
+ const { command: install, url } = installAdvice(agent);
79
+ const how = install
80
+ ? `with ${c.cyan}${install}${c.reset}, or see ${c.cyan}${url}${c.reset}`
81
+ : `from ${c.cyan}${url}${c.reset}`;
82
+ console.log(` subc doesn't install ${agent.name}. Install it ${how}.`);
83
+ }
75
84
  const actions = agentSetupActions(agent);
76
85
  if (actions.includes('install')) {
77
- const what = agent.runbook.binary_install_script
78
- ? 'Install the agent binary with'
79
- : 'Install the persistent integration with';
80
- console.log(` ${what} ${c.cyan}subc ${command} install${c.reset}.`);
86
+ console.log(
87
+ ` Install the persistent integration with ${c.cyan}subc ${command} install${c.reset}.`,
88
+ );
81
89
  }
82
90
  if (actions.includes('uninstall')) {
83
91
  console.log(
@@ -1,4 +1,4 @@
1
- import { execFileSync, spawn } from 'node:child_process';
1
+ import { execFileSync } from 'node:child_process';
2
2
  import { constants as fsConstants } from 'node:fs';
3
3
  import fs from 'node:fs/promises';
4
4
  import os from 'node:os';
@@ -9,26 +9,11 @@ import {
9
9
  agentById,
10
10
  agentCommandName,
11
11
  agentInstallDirs,
12
- installCommands,
12
+ installAdvice,
13
13
  } from './agent-data.js';
14
- import { runbookScriptPath } from './agent-spawn.js';
15
14
  import { c } from './colors.js';
16
15
  import { compareVersions } from './update-check.js';
17
16
 
18
- /**
19
- * The install command for this OS, falling back to the Linux one, then any
20
- * command present; plus the optional fallback command.
21
- */
22
- export function resolveInstall(agent) {
23
- const commands = installCommands(agent);
24
- if (!commands) return { command: undefined, fallback: undefined };
25
- const command =
26
- commands[process.platform] ||
27
- commands.linux ||
28
- Object.entries(commands).find(([os]) => os !== 'fallback')?.[1];
29
- return { command, fallback: commands.fallback };
30
- }
31
-
32
17
  /**
33
18
  * Common locations a freshly-installed coding-agent binary lands in but which
34
19
  * are often NOT on the current process's PATH (e.g. aider/claude install into
@@ -139,21 +124,6 @@ export function augmentPath(
139
124
  ].join(path.delimiter);
140
125
  }
141
126
 
142
- /** Ask a yes/no question on the TTY. Empty answer counts as yes. */
143
- function askYesNo(question) {
144
- return new Promise((resolve) => {
145
- const rl = readline.createInterface({
146
- input: process.stdin,
147
- output: process.stdout,
148
- });
149
- rl.question(question, (answer) => {
150
- rl.close();
151
- const a = answer.trim().toLowerCase();
152
- resolve(a === '' || a === 'y' || a === 'yes');
153
- });
154
- });
155
- }
156
-
157
127
  function waitForEnter(prompt) {
158
128
  return new Promise((resolve) => {
159
129
  const rl = readline.createInterface({
@@ -167,113 +137,33 @@ function waitForEnter(prompt) {
167
137
  });
168
138
  }
169
139
 
170
- /** Run the agent's packaged binary installer, or its shell install command. */
171
- function runInstaller(
172
- agent,
173
- install = resolveInstall(agent).command,
174
- usePackagedScript = true,
175
- ) {
176
- return new Promise((resolve) => {
177
- const installScript =
178
- usePackagedScript && agent.runbook.binary_install_script;
179
- const child = installScript
180
- ? spawn('bash', [runbookScriptPath(agent, installScript), 'install'], {
181
- stdio: 'inherit',
182
- })
183
- : spawn(install, { shell: true, stdio: 'inherit' });
184
- child.on('error', () => resolve(false));
185
- child.on('exit', (code) => resolve(code === 0));
186
- });
187
- }
188
-
189
- /** Print the resolved install command (plus any fallback) for an agent. */
190
- function printInstallCommands(agent) {
191
- const { command, fallback } = resolveInstall(agent);
192
- console.error(` ${c.cyan}${command}${c.reset}`);
193
- if (fallback) {
194
- console.error(` ${c.dim}or, as a fallback:${c.reset}`);
195
- console.error(` ${c.cyan}${fallback}${c.reset}`);
196
- }
197
- console.error('');
140
+ /** Print the install command and official install page; subc runs neither. */
141
+ function printInstallAdvice(agent) {
142
+ const { command, url } = installAdvice(agent);
143
+ if (command) console.error(` ${c.cyan}${command}${c.reset}`);
144
+ console.error(
145
+ ` ${c.dim}Official install page:${c.reset} ${c.cyan}${url}${c.reset}\n`,
146
+ );
198
147
  }
199
148
 
200
149
  /**
201
- * Ensure the agent's binary is resolvable. If missing:
202
- * - interactive TTY: offer to run the per-OS installer (with fallback), then
203
- * re-resolve against PATH + candidate dirs.
204
- * - non-interactive: print the resolved install command (+ fallback) and
205
- * exit 127 without running anything.
206
- *
207
- * Returns the directory containing the bin (to prepend to the child's PATH) on
208
- * success. May exit the process on failure or when manual action is needed.
150
+ * Return the directory that holds the agent's binary. subc never installs a
151
+ * third-party agent: when the binary is missing it prints how to install it
152
+ * and exits 127, in every shell.
209
153
  */
210
- export async function ensureInstalled(agent, { mayPrompt = true } = {}) {
154
+ export async function ensureInstalled(agent) {
211
155
  const bin = agentBinary(agent);
212
- const { command: install, fallback } = resolveInstall(agent);
213
- const preferredDirs = preferredBinDirsForAgent(agent);
214
- const existing = await resolveBinPath(bin, preferredDirs);
156
+ const existing = await resolveBinPath(bin, preferredBinDirsForAgent(agent));
215
157
  if (existing) return existing;
216
158
 
217
- // Agents without an installer are launch-only. Their setup integration may
218
- // configure the provider, but `subc <agent>` must never install the binary.
219
- if (!install) {
220
- console.error(
221
- `\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${bin}\` not found on PATH).${c.reset}`,
222
- );
223
- console.error(
224
- ` Install ${agent.name} separately, then re-run ${c.cyan}subc ${agentCommandName(agent)}${c.reset}.\n`,
225
- );
226
- process.exit(127);
227
- }
228
-
229
- const interactive = mayPrompt && process.stdin.isTTY && process.stdout.isTTY;
230
-
231
- if (!interactive) {
232
- console.error(
233
- `\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${bin}\` not found on PATH).${c.reset}`,
234
- );
235
- console.error(` Install it with:\n`);
236
- printInstallCommands(agent);
237
- process.exit(127);
238
- }
239
-
240
- console.error(`\n ${c.bold}${agent.name}${c.reset} isn't installed.`);
241
- const ok = await askYesNo(` Install it now? ${c.dim}[Y/n]${c.reset} `);
242
- if (!ok) {
243
- console.error(`\n No problem. Install it yourself with:\n`);
244
- printInstallCommands(agent);
245
- process.exit(127);
246
- }
247
-
248
159
  console.error(
249
- `\n ${c.dim}Running ${c.reset}${c.cyan}${install}${c.reset}\n`,
160
+ `\n ${c.red}${agent.name} isn't installed${c.reset} ${c.dim}(\`${bin}\` not found on PATH).${c.reset}`,
250
161
  );
251
- let installed = await runInstaller(agent);
252
-
253
- // Primary failed and a fallback exists — try it once.
254
- if (!installed && fallback) {
255
- console.error(
256
- `\n ${c.dim}That didn't work. Trying the fallback: ${c.reset}${c.cyan}${fallback}${c.reset}\n`,
257
- );
258
- installed = await runInstaller(agent, fallback, false);
259
- }
260
-
261
- if (!installed) {
262
- console.error(`\n ${c.red}Install failed.${c.reset} Try it manually:\n`);
263
- printInstallCommands(agent);
264
- process.exit(127);
265
- }
266
-
267
- // PATH hardening: the freshly-installed binary is often not on the current
268
- // process's PATH. Re-resolve against PATH + candidate dirs.
269
- const found = await resolveBinPath(bin, preferredDirs);
270
- if (found) return found;
271
-
272
162
  console.error(
273
- `\n ${c.dim}Installed ${agent.name}, but it isn't on this shell's PATH yet. ` +
274
- `Open a new terminal (or add a bin dir to PATH) and re-run \`subc ${agentCommandName(agent)}\`.${c.reset}\n`,
163
+ ` subc doesn't install ${agent.name}. Install it yourself, then re-run ${c.cyan}subc ${agentCommandName(agent)}${c.reset}.\n`,
275
164
  );
276
- process.exit(0);
165
+ printInstallAdvice(agent);
166
+ process.exit(127);
277
167
  }
278
168
 
279
169
  export function parseClaudeVersion(text) {
@@ -335,14 +225,8 @@ export async function ensureMinimumVersion(
335
225
  console.error(
336
226
  `\n Minimum supported ${agent.name} version is ${minimum}. Your version is ${version}. Upgrade to get the best experience.\n`,
337
227
  );
338
- const { command, fallback } = resolveInstall(agent);
339
228
  console.error(` Upgrade it with:`);
340
- console.error(` ${c.cyan}${command}${c.reset}`);
341
- if (fallback) {
342
- console.error(` or`);
343
- console.error(` ${c.cyan}${fallback}${c.reset}`);
344
- }
345
- console.error('');
229
+ printInstallAdvice(agent);
346
230
  if (
347
231
  mayPrompt &&
348
232
  process.stdin.isTTY === true &&
package/bin/agents.js CHANGED
@@ -283,7 +283,8 @@ export const RUNBOOK_ENV_DESCRIPTION = Object.freeze({
283
283
  {
284
284
  name: 'SUBCONSCIOUS_MODELS',
285
285
  value: '{catalog}',
286
- description: 'Newline-separated live model catalog.',
286
+ description:
287
+ 'Newline-separated model catalog. Set by the caller, it pins the catalog and skips live discovery; otherwise it is the live catalog.',
287
288
  },
288
289
  {
289
290
  name: 'PATH',
@@ -362,6 +363,7 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
362
363
  apiKey,
363
364
  selectedModel,
364
365
  fallbackModels: PACKAGED_MODELS,
366
+ pinnedModels: process.env.SUBCONSCIOUS_MODELS,
365
367
  });
366
368
  if (catalog.source === 'public' && apiKey) {
367
369
  console.error(` ${c.dim}${PUBLIC_CATALOG_FALLBACK_MESSAGE}${c.reset}\n`);
@@ -390,7 +392,7 @@ export function selectLaunchModel(requestedModel, modelSource, catalog) {
390
392
  async function runRunbookSetup(agent, argv, profile) {
391
393
  const script = agent.runbook.setup_script;
392
394
  if (isSetupWithoutAuth(argv) || agent.runbook.setup_needs_auth === false) {
393
- // Status, uninstall, and Marathon's binary install read no model or
395
+ // Status and uninstall read no model or
394
396
  // agent setting, so an invalid saved one must not block them.
395
397
  const lenient = { strict: false };
396
398
  const base = { ...(profile?.values || {}), ...process.env };
@@ -521,9 +523,8 @@ async function launchAgent(agent, argv, profile, headless) {
521
523
  const apiKey = await requireApiKey(profile, agent);
522
524
  if (!apiKey) return 1;
523
525
 
524
- const mayPrompt = !headless;
525
- const binDir = await ensureInstalled(agent, { mayPrompt });
526
- await ensureMinimumVersion(agent, binDir, { mayPrompt });
526
+ const binDir = await ensureInstalled(agent);
527
+ await ensureMinimumVersion(agent, binDir, { mayPrompt: !headless });
527
528
  const catalog = await resolvedModelsForLaunch(
528
529
  profile,
529
530
  apiKey,
package/bin/cli.js CHANGED
@@ -159,7 +159,8 @@ Usage:
159
159
  subc upgrade --latest
160
160
  subc upgrade help
161
161
 
162
- Upgrade subconscious-cli to the latest published version.
162
+ Upgrade subconscious-cli to the latest published version, the same way it
163
+ was installed: npm (or pnpm, yarn, bun), or the curl installer.
163
164
 
164
165
  subc upgrade Prompt "Do you want to upgrade?" then install @latest
165
166
  subc upgrade --latest Skip the prompt and install @latest
package/bin/models.js CHANGED
@@ -175,10 +175,22 @@ export async function resolveModelCatalog({
175
175
  apiKey,
176
176
  selectedModel,
177
177
  fallbackModels = [],
178
+ pinnedModels,
178
179
  fetchImpl = globalThis.fetch,
179
180
  timeoutMs = DEFAULT_MODEL_FETCH_TIMEOUT_MS,
180
181
  signal,
181
182
  }) {
183
+ // A caller that names its catalog (a benchmark run, a CI job) needs the same
184
+ // models on every launch, whatever the gateway lists that day.
185
+ const pinned = pinnedModels?.split(/\r?\n/).filter((id) => id.trim());
186
+ if (pinned?.length) {
187
+ return {
188
+ models: normalizeModelIds(pinned, selectedModel),
189
+ source: 'pinned',
190
+ error: null,
191
+ };
192
+ }
193
+
182
194
  const key = apiKey?.trim() || '';
183
195
 
184
196
  if (key) {
Binary file
Binary file
Binary file
Binary file
@@ -8,7 +8,6 @@ the default gateway, model capabilities, and the settings every agent uses
8
8
 
9
9
  | Agent | Launch | Setup |
10
10
  | --- | --- | --- |
11
- | `subconscious-code` (Marathon) | binary | `install.sh` (binary installer) |
12
11
  | `claude-code` | binary | `install.sh` (leftover status/uninstall) |
13
12
  | `codex` | `run.sh` | `install.sh` (hooks) |
14
13
  | `opencode` | binary | `install.sh` (leftover status/uninstall) |
@@ -22,7 +21,7 @@ the default gateway, model capabilities, and the settings every agent uses
22
21
  | Field | Read by |
23
22
  | --- | --- |
24
23
  | `id`, `command`, `aliases`, `name`, `description` | routing, `subc help`, the TUI menu |
25
- | `install` | the installer prompt, `--version` warning (`minimum_version`), Windows installs; `{package}` fills in the commands |
24
+ | `install` | the missing-agent message, agent help, and the upgrade warning (`minimum_version`); `{package}` fills in `commands` |
26
25
  | `runbook` | which script launches the agent (none means the binary is run directly) and which setup actions exist |
27
26
  | `help` | `subc <agent> help`; flags with a `help` text are added from `inputs` |
28
27
  | `sessions`, `launch.resume`, `launch.handoff` | `subc sessions resume` |
@@ -32,6 +31,18 @@ the default gateway, model capabilities, and the settings every agent uses
32
31
  | `config` | `cli-config` entries become `config_flag NAME=VALUE` (Codex `-c`) when `when` holds; other entries describe files the runbooks write |
33
32
  | `capabilities`, `prerequisites` | the harness manifest only |
34
33
 
34
+ ## Install data is display-only
35
+
36
+ subc never installs an agent. When an agent's binary is missing, subc prints
37
+ `install.commands` for the current OS and `install.url`, then exits with
38
+ status 127. Windows never shows the Linux command. Rules for `install`, checked
39
+ by `bin/agent-data.js` and `test/agent-install-policy.test.js`:
40
+
41
+ - `url` is required: an `https://` link to the vendor's official install page.
42
+ - `commands` is `null`, or an object keyed by `darwin`, `linux`, and `win32`.
43
+ - A command is one the user can paste, such as `npm i -g {package}`. It never
44
+ pipes a downloaded script into a shell.
45
+
35
46
  Values may use `{baseUrl}`, `{baseUrlV1}`, `{apiKey}`, `{model}`, `{prompt}`,
36
47
  `{args}`, `{config}`, and `${NAME}` for another input or env entry.
37
48
  `subc harness-manifest --json` prints every agent file with `${NAME}` replaced
@@ -8,15 +8,15 @@
8
8
  "homepage": "https://docs.anthropic.com/en/docs/claude-code",
9
9
  "install": {
10
10
  "commands": {
11
- "darwin": "curl -fsSL https://claude.ai/install.sh | bash",
12
- "linux": "curl -fsSL https://claude.ai/install.sh | bash",
13
- "win32": "powershell -ExecutionPolicy Bypass -Command \"irm https://claude.ai/install.ps1 | iex\"",
14
- "fallback": "npm i -g {package}"
11
+ "darwin": "npm i -g {package}",
12
+ "linux": "npm i -g {package}",
13
+ "win32": "npm i -g {package}"
15
14
  },
16
- "method": "script",
15
+ "url": "https://code.claude.com/docs/en/setup",
16
+ "method": "npm",
17
17
  "package": "@anthropic-ai/claude-code",
18
18
  "version": null,
19
- "channel": "installer default (unpinned)",
19
+ "channel": "latest (unpinned)",
20
20
  "minimum_version": "2.1.242",
21
21
  "verified": true,
22
22
  "note": "subc warns, and does not block, below minimum_version."
@@ -12,6 +12,7 @@
12
12
  "linux": "npm i -g {package}",
13
13
  "win32": "npm i -g {package}"
14
14
  },
15
+ "url": "https://developers.openai.com/codex/cli",
15
16
  "method": "npm",
16
17
  "package": "@openai/codex",
17
18
  "version": null,