subconscious-cli 4.0.13 → 4.0.15

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
@@ -7,6 +7,7 @@ Log in to Subconscious, then run coding agents against the Subconscious gateway.
7
7
  ```bash
8
8
  npm install -g subconscious-cli
9
9
  subc login
10
+ subc sc
10
11
  subc claude
11
12
  ```
12
13
 
@@ -21,7 +22,7 @@ never block the requested command. Set `SUBC_DISABLE_UPDATE_CHECK=1` to suppress
21
22
  the check in offline automation.
22
23
 
23
24
  Login creates both the saved credential and a ready-to-use `default` profile,
24
- so Claude Code, Codex, OpenCode, and DeepSeek Harness can launch immediately.
25
+ so Subconscious Code, Claude Code, Codex, OpenCode, and DeepSeek Harness can launch immediately.
25
26
  Persistent editor and Pi integrations are installed per agent:
26
27
 
27
28
  ```bash
@@ -52,6 +53,7 @@ profile; they do not authenticate, install, configure, or launch anything:
52
53
 
53
54
  ```bash
54
55
  subc claude help
56
+ subc sc help
55
57
  subc codex help
56
58
  subc cursor help
57
59
  subc config help
@@ -66,6 +68,7 @@ gateway environment applied for that process. Arguments pass through as usual:
66
68
 
67
69
  ```bash
68
70
  subc claude --continue
71
+ subc sc -- -p "fix the tests"
69
72
  subc codex exec "write a test"
70
73
  subc opencode
71
74
  subc pi
@@ -78,6 +81,7 @@ The packaged integrations live in `cli/bin/runbook`.
78
81
 
79
82
  | Command | Behavior |
80
83
  | --- | --- |
84
+ | `subc sc` | Launch Subconscious Code with the active gateway, key, model, and DLR transport |
81
85
  | `subc claude` | Launch Claude Code with the runbook environment, context limits, subagent limits, and OTEL usage reporting |
82
86
  | `subc codex` | Launch Codex with the runbook provider, temporary model catalog, and surgically merged compaction hooks |
83
87
  | `subc opencode` | Launch OpenCode with the runbook provider, client header, and context/output limits |
@@ -86,11 +90,16 @@ The packaged integrations live in `cli/bin/runbook`.
86
90
  | `subc pi` | Refresh the Pi provider from the live catalog, then launch |
87
91
  | `subc dsh` | Launch the DeepSeek Harness Web UI with a temporary provider populated from the live catalog |
88
92
 
89
- If Claude Code, Codex, OpenCode, or DeepSeek Harness is missing, an interactive terminal offers
93
+ If Subconscious Code, Claude Code, Codex, OpenCode, or DeepSeek Harness is missing, an interactive terminal offers
90
94
  to install it before launching. Pi refreshes its Subconscious provider on every
91
95
  `subc pi` launch while preserving all other providers in `models.json`; its
92
96
  executable must already be installed.
93
97
 
98
+ `subc sc install` downloads and checksum-verifies the latest static Linux release
99
+ from `subconscious-systems/subconscious-code`. The upstream release currently
100
+ publishes x86_64 and ARM64 Linux assets. On macOS, the installer uses Cargo to
101
+ build the tagged release until Darwin assets are published.
102
+
94
103
  `subc codex` disables Codex apps and plugin tools for that launch by default so
95
104
  requests remain below the gateway's 128-tool limit. Core coding tools remain
96
105
  available. Use `subc codex --external-tools` to opt back in when targeting a
@@ -145,7 +154,7 @@ rewritten. Run a one-shot task with `subc dsh headless "PROMPT"`.
145
154
  ```
146
155
 
147
156
  The file is mode `600` and contains the shared gateway URL, API key, model,
148
- optional per-agent key overrides, and all Claude/Codex/OpenCode/Pi/Copilot/
157
+ optional per-agent key overrides, and all Subconscious Code/Claude/Codex/OpenCode/Pi/Copilot/
149
158
  DeepSeek Harness context and output settings used by the packaged runbook scripts.
150
159
 
151
160
  ```bash
@@ -153,8 +162,9 @@ subc config # list every profile and its file path
153
162
  subc -p staging config create # create a profile with default settings
154
163
  subc -p staging config # print that path and env file
155
164
  subc -p staging config --model subconscious/glm-5.2
165
+ subc -p staging config --model UNSET
156
166
  subc -p staging config --subagent-model subconscious/deepseek-v4-flash-marathon
157
- subc -p staging config --subagent-model follow-default
167
+ subc -p staging config --subagent-model UNSET
158
168
  subc config --gateway-url https://gateway.example
159
169
  subc config path
160
170
  subc config edit # open the selected profile in $VISUAL, $EDITOR, vim, or nano
@@ -170,9 +180,12 @@ Subconscious-injected defaults; resolved login identity (`GATEWAY_URL`,
170
180
  In scripts and CI, keep using `subc config --gateway-url`, `--api-key`,
171
181
  `--model`, and `--subagent-model`.
172
182
 
183
+ Use `UNSET` to clear the profile default so launches use the first model in
184
+ the live catalog (gateway priority order).
185
+
173
186
  The subagent setting controls `CLAUDE_CODE_SUBAGENT_MODEL` for Claude Code.
174
- Use `follow-default` to clear the override so subagents automatically follow
175
- the profile's default model.
187
+ Use `UNSET` to clear the override so subagents automatically follow the
188
+ profile's default model.
176
189
 
177
190
  Each agent section can hold its own API key. An agent-specific key takes
178
191
  precedence over the shared profile key and can be used on its own, so profiles
@@ -224,11 +237,15 @@ list. The selected profile model is listed first only while the gateway still
224
237
  advertises it.
225
238
  OpenCode rebuilds its isolated runtime provider on every `subc opencode` launch,
226
239
  so models left in older OpenCode configuration files do not leak into the list.
227
- If a saved profile default has been removed, launches use the first live model;
228
- an explicit `--model` or `SUBCONSCIOUS_MODEL` override is always preserved.
240
+ If a saved profile default is UNSET or has been removed, launches use the first
241
+ live model; an explicit `--model` or `SUBCONSCIOUS_MODEL` override is always
242
+ preserved.
229
243
  Claude Code exposes four native Opus/Sonnet/Haiku/Fable picker slots plus one
230
- custom model option, so the CLI maps the first five models into those slots; any other
231
- model can still be selected with `subc claude --model MODEL`. Cursor requires adding
244
+ custom model option. The CLI remaps those aliases to the live catalog, then
245
+ replaces the built-in `/model` lineup (`modelPicker.replaceBuiltInOptions`) and
246
+ allowlists only those models (`availableModels`) so Anthropic Fable and
247
+ ungranted slugs do not appear. Any other model can still be selected with
248
+ `subc claude --model MODEL`. Cursor requires adding
232
249
  the printed model IDs in its OpenAI API Key Override settings. Use the printed
233
250
  `/v1` Base URL in Cursor Settings; the profile itself stores the gateway origin
234
251
  so correlation hooks can post to `/v1/agent-hooks`.
package/bin/agents.js CHANGED
@@ -20,8 +20,16 @@ import readline from 'node:readline';
20
20
  import { fileURLToPath } from 'node:url';
21
21
  import { c } from './colors.js';
22
22
  import { getApiKey } from './auth.js';
23
- import { profileSettingsForAgent, resolvedProfileValues } from './profiles.js';
23
+ import {
24
+ isUnsetSetting,
25
+ profileSettingsForAgent,
26
+ resolvedModelSetting,
27
+ resolvedProfileValues,
28
+ } from './profiles.js';
24
29
  import { isLiveModelSource, PUBLIC_CATALOG_FALLBACK_MESSAGE, resolveModelCatalog } from './models.js';
30
+ import { compareVersions } from './update-check.js';
31
+
32
+ export const MIN_CLAUDE_CODE_VERSION = '2.1.242';
25
33
 
26
34
  // --- Registry (single source of truth, generated copy shipped in the package).
27
35
  const registry = JSON.parse(
@@ -156,6 +164,17 @@ export function parseAgentAction(agent, argv = []) {
156
164
  }
157
165
 
158
166
  const AGENT_HELP = {
167
+ 'subconscious-code': {
168
+ usage: 'subc [-p NAME] sc [help|install] [Subconscious Code arguments...]',
169
+ behavior:
170
+ 'Launches the native Subconscious coding agent with the selected profile gateway, credential, and model.',
171
+ options: [
172
+ ['help', 'Show this help'],
173
+ ['install', 'Install the latest Linux release, or build from source on macOS'],
174
+ ['--model MODEL', 'Override the profile model for this launch'],
175
+ ['ARGS...', 'Pass arguments directly to Subconscious Code'],
176
+ ],
177
+ },
159
178
  'claude-code': {
160
179
  usage: 'subc [-p NAME] claude [help|status|uninstall] [Claude arguments...]',
161
180
  behavior:
@@ -258,6 +277,12 @@ function displayProfileValue(setting, value, values) {
258
277
  if (value) return '(set)';
259
278
  return setting.key !== 'API_KEY' && values.API_KEY ? '(shared key)' : '(not set)';
260
279
  }
280
+ if (
281
+ !value &&
282
+ (setting.key === 'MODEL' || setting.key === 'CLAUDE_CODE_SUBAGENT_MODEL')
283
+ ) {
284
+ return 'UNSET';
285
+ }
261
286
  return value || '(auto)';
262
287
  }
263
288
 
@@ -295,9 +320,10 @@ export function printAgentHelp(agent, profile) {
295
320
  );
296
321
  const actions = agentSetupActions(agent);
297
322
  if (actions.includes('install')) {
298
- console.log(
299
- ` Install the persistent integration with ${c.cyan}subc ${command} install${c.reset}.`,
300
- );
323
+ const installDescription = agent.runbook?.binaryInstallScript
324
+ ? 'Install the agent binary with'
325
+ : 'Install the persistent integration with';
326
+ console.log(` ${installDescription} ${c.cyan}subc ${command} install${c.reset}.`);
301
327
  }
302
328
  if (actions.includes('uninstall')) {
303
329
  console.log(
@@ -325,28 +351,40 @@ function buildContext(apiKey, model, profile) {
325
351
  /**
326
352
  * Pull a `--model <value>` / `--model=<value>` flag out of the passthrough
327
353
  * args (so it sets the Subconscious model rather than reaching the agent).
328
- * Falls back to SUBCONSCIOUS_MODEL, then the registry default.
354
+ * Falls back to SUBCONSCIOUS_MODEL, then the profile MODEL. UNSET or a blank
355
+ * profile model means "use the first live catalog entry" (gateway priority).
329
356
  */
330
- function extractModel(argv, profile) {
331
- const environmentModel = process.env.SUBCONSCIOUS_MODEL?.trim();
332
- const profileModel = profile?.values?.MODEL?.trim();
333
- let model = environmentModel || profileModel || DEFAULTS.model;
334
- let modelSource = environmentModel ? 'environment' : profileModel ? 'profile' : 'default';
357
+ export function extractModel(argv, profile) {
358
+ const environmentModel = resolvedModelSetting(process.env.SUBCONSCIOUS_MODEL);
359
+ const profileModel = resolvedModelSetting(profile?.values?.MODEL);
360
+ let model = environmentModel || profileModel || '';
361
+ let modelSource = environmentModel ? 'environment' : profileModel ? 'profile' : 'catalog';
335
362
  const rest = [];
336
363
  for (let i = 0; i < argv.length; i++) {
337
364
  const a = argv[i];
338
365
  if (a === '--model') {
339
366
  const v = argv[i + 1];
340
367
  if (v && !v.startsWith('-')) {
341
- model = v;
342
- modelSource = 'command';
368
+ if (isUnsetSetting(v)) {
369
+ model = '';
370
+ modelSource = 'catalog';
371
+ } else {
372
+ model = v;
373
+ modelSource = 'command';
374
+ }
343
375
  i++;
344
376
  }
345
377
  continue;
346
378
  }
347
379
  if (a.startsWith('--model=')) {
348
- model = a.slice('--model='.length);
349
- modelSource = 'command';
380
+ const v = a.slice('--model='.length);
381
+ if (isUnsetSetting(v)) {
382
+ model = '';
383
+ modelSource = 'catalog';
384
+ } else {
385
+ model = v;
386
+ modelSource = 'command';
387
+ }
350
388
  continue;
351
389
  }
352
390
  rest.push(a);
@@ -371,6 +409,7 @@ function candidateBinDirs() {
371
409
  if (home) dirs.push(path.join(home, '.local', 'bin'));
372
410
  } else {
373
411
  dirs.push(path.join(home, '.local', 'bin'));
412
+ dirs.push(path.join(home, '.cargo', 'bin'));
374
413
  dirs.push('/opt/homebrew/bin');
375
414
  dirs.push('/usr/local/bin');
376
415
  }
@@ -445,10 +484,25 @@ function askYesNo(question) {
445
484
  });
446
485
  }
447
486
 
448
- /** Run the agent's install command (may contain `&&`, so shell:true). */
449
- function runInstaller(install) {
487
+ function waitForEnter(prompt) {
488
+ return new Promise((resolve) => {
489
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
490
+ rl.question(prompt, () => {
491
+ rl.close();
492
+ resolve();
493
+ });
494
+ });
495
+ }
496
+
497
+ /** Run the agent's packaged binary installer, or its shell install command. */
498
+ function runInstaller(agent, install = agent.install, usePackagedScript = true) {
450
499
  return new Promise((resolve) => {
451
- const child = spawn(install, { shell: true, stdio: 'inherit' });
500
+ const installScript = usePackagedScript && agent.runbook?.binaryInstallScript;
501
+ const child = installScript
502
+ ? spawn('bash', [runbookScriptPath(agent, installScript), 'install'], {
503
+ stdio: 'inherit',
504
+ })
505
+ : spawn(install, { shell: true, stdio: 'inherit' });
452
506
  child.on('error', () => resolve(false));
453
507
  child.on('exit', (code) => resolve(code === 0));
454
508
  });
@@ -508,14 +562,14 @@ async function ensureInstalled(agent) {
508
562
  }
509
563
 
510
564
  console.error(`\n ${c.dim}Running ${c.reset}${c.cyan}${agent.install}${c.reset}\n`);
511
- let installed = await runInstaller(agent.install);
565
+ let installed = await runInstaller(agent);
512
566
 
513
567
  // Primary failed and a fallback exists — try it once.
514
568
  if (!installed && agent.installFallback) {
515
569
  console.error(
516
570
  `\n ${c.dim}That didn't work. Trying the fallback: ${c.reset}${c.cyan}${agent.installFallback}${c.reset}\n`,
517
571
  );
518
- installed = await runInstaller(agent.installFallback);
572
+ installed = await runInstaller(agent, agent.installFallback, false);
519
573
  }
520
574
 
521
575
  if (!installed) {
@@ -536,6 +590,51 @@ async function ensureInstalled(agent) {
536
590
  process.exit(0);
537
591
  }
538
592
 
593
+ export function parseClaudeVersion(text) {
594
+ const match = String(text ?? '').match(/v?(\d+\.\d+\.\d+)/);
595
+ return match?.[1] ?? null;
596
+ }
597
+
598
+ export function claudeVersionNeedsUpgrade(installed, minimum = MIN_CLAUDE_CODE_VERSION) {
599
+ return Boolean(installed && minimum && compareVersions(installed, minimum) < 0);
600
+ }
601
+
602
+ export function readClaudeVersion(bin, binDir, options = {}) {
603
+ const execFile = options.execFileSync || execFileSync;
604
+ try {
605
+ const stdout = execFile(bin, ['--version'], {
606
+ encoding: 'utf-8',
607
+ stdio: ['ignore', 'pipe', 'pipe'],
608
+ env: { ...process.env, PATH: augmentPath([binDir, ...candidateBinDirs()].filter(Boolean)) },
609
+ timeout: options.timeoutMs ?? 3000,
610
+ });
611
+ return parseClaudeVersion(typeof stdout === 'string' ? stdout : stdout?.toString?.());
612
+ } catch {
613
+ return null;
614
+ }
615
+ }
616
+
617
+ async function ensureClaudeCompatible(agent, binDir) {
618
+ if (agent.id !== 'claude-code') return;
619
+ const version = readClaudeVersion(agent.bin, binDir);
620
+ if (!claudeVersionNeedsUpgrade(version)) return;
621
+
622
+ console.error(
623
+ `\n Minimum supported Claude Code version is ${MIN_CLAUDE_CODE_VERSION}. Your version is ${version}. Upgrade to get the best experience.\n`,
624
+ );
625
+ console.error(` Upgrade it with:`);
626
+ console.error(` ${c.cyan}${agent.install}${c.reset}`);
627
+ if (agent.installFallback) {
628
+ console.error(` or`);
629
+ console.error(` ${c.cyan}${agent.installFallback}${c.reset}`);
630
+ }
631
+ console.error('');
632
+ if (process.stdin.isTTY === true && process.stdout.isTTY === true) {
633
+ await waitForEnter(` ${c.dim}Press Enter to continue.${c.reset} `);
634
+ console.error('');
635
+ }
636
+ }
637
+
539
638
  /** Resolve and validate a script inside the packaged runbook directory. */
540
639
  function runbookScriptPath(agent, relativeScript = agent.runbook.script) {
541
640
  const script = path.resolve(RUNBOOK_DIR, relativeScript);
@@ -639,21 +738,48 @@ const CLAUDE_MODEL_PICKER_KEYS = [
639
738
  'ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION',
640
739
  ];
641
740
 
741
+ const CLAUDE_MODEL_PICKER_ID_KEYS = [
742
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL',
743
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL',
744
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
745
+ 'ANTHROPIC_DEFAULT_FABLE_MODEL',
746
+ 'ANTHROPIC_CUSTOM_MODEL_OPTION',
747
+ ];
748
+
749
+ function uniqueCatalogModels(models, fallbackModel) {
750
+ const unique = [];
751
+ for (const model of models) {
752
+ if (model && !unique.includes(model)) unique.push(model);
753
+ }
754
+ if (unique.length === 0 && fallbackModel) unique.push(fallbackModel);
755
+ return unique;
756
+ }
757
+
758
+ export function claudePickerSettings(models, fallbackModel) {
759
+ const unique = uniqueCatalogModels(models, fallbackModel);
760
+ return {
761
+ availableModels: unique,
762
+ modelPicker: {
763
+ replaceBuiltInOptions: true,
764
+ options: unique.map((model) => ({
765
+ model,
766
+ label: model,
767
+ description: `Subconscious model ${model}`,
768
+ })),
769
+ },
770
+ };
771
+ }
772
+
642
773
  function claudeModelPickerEnv(agent, ctx, models) {
643
774
  if (agent.id !== 'claude-code') return {};
644
- const configured = substitute(agent.env || {}, ctx);
645
- const configuredModels = [
646
- configured.ANTHROPIC_DEFAULT_OPUS_MODEL,
647
- configured.ANTHROPIC_DEFAULT_SONNET_MODEL,
648
- configured.ANTHROPIC_DEFAULT_HAIKU_MODEL,
649
- configured.ANTHROPIC_DEFAULT_FABLE_MODEL,
650
- configured.ANTHROPIC_CUSTOM_MODEL_OPTION,
651
- ];
652
- const pickerModels = [];
653
- for (const model of [...models, ...configuredModels]) {
654
- if (model && !pickerModels.includes(model)) pickerModels.push(model);
655
- }
656
- while (pickerModels.length < 3) pickerModels.push(pickerModels.at(-1) || ctx.model);
775
+ // Only the live catalog belongs in Claude's picker. Packaged registry slots
776
+ // (Haiku → DeepSeek, etc.) must not be unioned back in — that advertised
777
+ // models the key cannot call.
778
+ //
779
+ // Pad through Fable so the built-in `fable` alias cannot resolve to Anthropic
780
+ // Fable 5. The /model menu itself is replaced via claudePickerSettings.
781
+ const pickerModels = uniqueCatalogModels(models, ctx.model);
782
+ while (pickerModels.length < 4) pickerModels.push(pickerModels.at(-1) || ctx.model);
657
783
 
658
784
  const roles = ['OPUS', 'SONNET', 'HAIKU', 'FABLE'];
659
785
  const env = {};
@@ -676,6 +802,27 @@ function claudeModelPickerEnv(agent, ctx, models) {
676
802
  );
677
803
  }
678
804
 
805
+ function applyClaudePickerCatalog(env, picker, models) {
806
+ if (Object.keys(picker).length === 0) return env;
807
+ const allowed = new Set(models);
808
+
809
+ for (const key of CLAUDE_MODEL_PICKER_KEYS) {
810
+ if (!picker[key]) {
811
+ delete env[key];
812
+ }
813
+ }
814
+
815
+ for (const key of CLAUDE_MODEL_PICKER_ID_KEYS) {
816
+ if (!picker[key] || !env[key] || allowed.has(env[key])) continue;
817
+ env[key] = picker[key];
818
+ const nameKey = `${key}_NAME`;
819
+ const descriptionKey = `${key}_DESCRIPTION`;
820
+ if (picker[nameKey]) env[nameKey] = picker[nameKey];
821
+ if (picker[descriptionKey]) env[descriptionKey] = picker[descriptionKey];
822
+ }
823
+ return env;
824
+ }
825
+
679
826
  export function runbookEnv(
680
827
  apiKey,
681
828
  model,
@@ -687,18 +834,29 @@ export function runbookEnv(
687
834
  const ctx = buildContext(apiKey, model, profile);
688
835
  const extraDirs = [binDir, ...candidateBinDirs()].filter(Boolean);
689
836
  const specificApiKey = agentApiKeySetting(agent)?.key;
690
- return {
691
- ...claudeModelPickerEnv(agent, ctx, models),
692
- ...(profile?.values || {}),
693
- ...process.env,
694
- GATEWAY_URL: ctx.baseUrl,
695
- API_KEY: apiKey,
696
- ...(specificApiKey ? { [specificApiKey]: apiKey } : {}),
697
- MODEL: model,
698
- SUBCONSCIOUS_MODELS: models.join('\n'),
699
- SUBC_ENV_FILE: os.devNull,
700
- PATH: augmentPath(extraDirs),
701
- };
837
+ const picker = claudeModelPickerEnv(agent, ctx, models);
838
+ return applyClaudePickerCatalog(
839
+ {
840
+ ...picker,
841
+ ...(profile?.values || {}),
842
+ ...process.env,
843
+ GATEWAY_URL: ctx.baseUrl,
844
+ API_KEY: apiKey,
845
+ ...(specificApiKey ? { [specificApiKey]: apiKey } : {}),
846
+ MODEL: model,
847
+ SUBCONSCIOUS_MODELS: models.join('\n'),
848
+ ...(agent.id === 'claude-code'
849
+ ? {
850
+ SUBC_CLAUDE_SETTINGS: JSON.stringify(claudePickerSettings(models, model)),
851
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: '0',
852
+ }
853
+ : {}),
854
+ SUBC_ENV_FILE: os.devNull,
855
+ PATH: augmentPath(extraDirs),
856
+ },
857
+ picker,
858
+ models,
859
+ );
702
860
  }
703
861
 
704
862
  async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
@@ -721,16 +879,20 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
721
879
  }
722
880
 
723
881
  export function selectLaunchModel(requestedModel, modelSource, catalog) {
882
+ const first = catalog.models[0] || requestedModel || DEFAULTS.model;
883
+ if (modelSource === 'catalog' || !requestedModel) {
884
+ return first;
885
+ }
724
886
  const useLiveDefault =
725
887
  isLiveModelSource(catalog.source) &&
726
888
  catalog.models.length > 0 &&
727
889
  !catalog.models.includes(requestedModel) &&
728
890
  (modelSource === 'profile' || modelSource === 'default');
729
- return useLiveDefault ? catalog.models[0] : requestedModel;
891
+ return useLiveDefault ? first : requestedModel;
730
892
  }
731
893
 
732
894
  async function runRunbookSetup(agent, argv, profile, relativeScript = agent.runbook.script) {
733
- if (isSetupWithoutAuth(argv)) {
895
+ if (isSetupWithoutAuth(argv) || agent.runbook?.setupNeedsAuth === false) {
734
896
  return spawnRunbook(agent, argv, {
735
897
  ...(profile?.values || {}),
736
898
  ...process.env,
@@ -743,7 +905,7 @@ async function runRunbookSetup(agent, argv, profile, relativeScript = agent.runb
743
905
  if (!apiKey) return 1;
744
906
  const catalog = await resolvedModelsForLaunch(profile, apiKey, requestedModel);
745
907
  const model = selectLaunchModel(requestedModel, modelSource, catalog);
746
- if (model !== requestedModel) {
908
+ if (requestedModel && model !== requestedModel) {
747
909
  console.error(
748
910
  ` ${c.yellow}Configured model ${requestedModel} is not in the live catalog; using ${model}.${c.reset}\n`,
749
911
  );
@@ -810,9 +972,10 @@ export async function runAgent(agent, argv, options = {}) {
810
972
  if (!apiKey) return 1;
811
973
 
812
974
  const binDir = await ensureInstalled(agent);
975
+ await ensureClaudeCompatible(agent, binDir);
813
976
  const catalog = await resolvedModelsForLaunch(profile, apiKey, requestedModel);
814
977
  const model = selectLaunchModel(requestedModel, modelSource, catalog);
815
- if (model !== requestedModel) {
978
+ if (requestedModel && model !== requestedModel) {
816
979
  console.error(
817
980
  ` ${c.yellow}Configured model ${requestedModel} is not in the live catalog; using ${model}.${c.reset}\n`,
818
981
  );
package/bin/cli.js CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  loadProfile,
35
35
  modelsCommand,
36
36
  printConfigHelp,
37
+ resolvedModelSetting,
37
38
  RUNBOOK_DEFAULTS,
38
39
  SUPPORTED_MODELS as PACKAGED_MODELS,
39
40
  updateUrlCommand,
@@ -74,7 +75,7 @@ function printHelp() {
74
75
  ${agents}
75
76
 
76
77
  ${c.bold}Options${c.reset}
77
- ${c.dim}--model <id>${c.reset} Model to use (default subconscious/glm-5.2)
78
+ ${c.dim}--model <id>${c.reset} Model to use (profile MODEL, or first live catalog model if UNSET)
78
79
  ${c.dim}-p, --profile${c.reset} Select a profile (default: default)
79
80
  ${c.dim}-h, --help${c.reset} Show this help
80
81
  ${c.dim}-v, --version${c.reset} Show version
@@ -154,7 +155,8 @@ Usage:
154
155
  subc models
155
156
  subc models help
156
157
 
157
- List available Subconscious models.
158
+ List available Subconscious models. The default is the profile MODEL,
159
+ or the first live catalog model if MODEL is UNSET.
158
160
  `,
159
161
  };
160
162
 
@@ -309,10 +311,9 @@ async function main() {
309
311
  }
310
312
  const profile = await loadProfile(profileName);
311
313
  const auth = await getApiKey(profile);
312
- const selectedModel =
313
- process.env.SUBCONSCIOUS_MODEL?.trim() ||
314
- profile.values.MODEL?.trim() ||
315
- RUNBOOK_DEFAULTS.MODEL;
314
+ const selectedModel = resolvedModelSetting(
315
+ process.env.SUBCONSCIOUS_MODEL || profile.values.MODEL,
316
+ );
316
317
  const baseUrl =
317
318
  process.env.SUBCONSCIOUS_BASE_URL?.trim() ||
318
319
  profile.values.GATEWAY_URL?.trim() ||
Binary file
Binary file
Binary file
Binary file
package/bin/profiles.js CHANGED
@@ -38,6 +38,7 @@ export const RUNBOOK_DEFAULTS = {
38
38
  GATEWAY_URL: registry.defaults.baseUrl,
39
39
  API_KEY: '',
40
40
  MODEL: registry.defaults.model,
41
+ SC_API_KEY: '',
41
42
  CLAUDE_CODE_SUBAGENT_MODEL: '',
42
43
  CLAUDE_GATEWAY_URL: '',
43
44
  CLAUDE_CODE_API_KEY: '',
@@ -84,14 +85,21 @@ const SETTINGS = {
84
85
  MODEL: {
85
86
  key: 'MODEL',
86
87
  label: 'Default model',
87
- description: 'Initial model used by coding-agent launches and setup.',
88
+ description:
89
+ 'Initial model used by coding-agent launches and setup. UNSET uses the first live catalog model.',
88
90
  type: 'choice',
89
91
  choices: SUPPORTED_MODELS,
90
92
  },
93
+ SC_API_KEY: {
94
+ key: 'SC_API_KEY',
95
+ label: 'Subconscious Code API key',
96
+ description: 'Subconscious Code credential override; blank uses API_KEY.',
97
+ type: 'secret',
98
+ },
91
99
  CLAUDE_CODE_SUBAGENT_MODEL: {
92
100
  key: 'CLAUDE_CODE_SUBAGENT_MODEL',
93
101
  label: 'Subagent model',
94
- description: 'Model used by Claude Code subagents; blank follows the default model.',
102
+ description: 'Model used by Claude Code subagents. UNSET follows the default model.',
95
103
  type: 'choice',
96
104
  choices: SUPPORTED_MODELS,
97
105
  },
@@ -273,6 +281,7 @@ const SETTINGS = {
273
281
  };
274
282
 
275
283
  const AGENT_SETTING_KEYS = {
284
+ 'subconscious-code': ['SC_API_KEY'],
276
285
  'claude-code': [
277
286
  'CLAUDE_GATEWAY_URL',
278
287
  'CLAUDE_CODE_API_KEY',
@@ -340,11 +349,12 @@ function profileTemplate() {
340
349
 
341
350
  GATEWAY_URL={GATEWAY_URL}
342
351
  API_KEY={API_KEY}
343
- # Available models:
352
+ # Available models (MODEL=UNSET uses the first live catalog model):
344
353
  ${modelComments}
345
354
  MODEL={MODEL}
346
355
 
347
356
  # Optional per-agent keys. Leave blank to use API_KEY above.
357
+ SC_API_KEY={SC_API_KEY}
348
358
  CLAUDE_CODE_API_KEY={CLAUDE_CODE_API_KEY}
349
359
  CODEX_API_KEY={CODEX_API_KEY}
350
360
  OPENCODE_API_KEY={OPENCODE_API_KEY}
@@ -356,7 +366,7 @@ DEEPSEEK_HARNESS_API_KEY={DEEPSEEK_HARNESS_API_KEY}
356
366
  # Claude Code
357
367
  # Leave CLAUDE_GATEWAY_URL blank to use GATEWAY_URL above.
358
368
  CLAUDE_GATEWAY_URL={CLAUDE_GATEWAY_URL}
359
- # Leave blank to use MODEL above for Claude Code subagents.
369
+ # CLAUDE_CODE_SUBAGENT_MODEL=UNSET follows MODEL above.
360
370
  CLAUDE_CODE_SUBAGENT_MODEL={CLAUDE_CODE_SUBAGENT_MODEL}
361
371
  CLAUDE_CODE_AUTO_COMPACT_WINDOW={CLAUDE_CODE_AUTO_COMPACT_WINDOW}
362
372
  CLAUDE_CODE_MAX_CONTEXT_TOKENS={CLAUDE_CODE_MAX_CONTEXT_TOKENS}
@@ -600,6 +610,16 @@ async function printProfile(profile) {
600
610
  console.log();
601
611
  }
602
612
 
613
+ export function isUnsetSetting(value) {
614
+ const normalized = String(value ?? '').trim().toUpperCase();
615
+ return normalized === 'UNSET' || normalized === 'FOLLOW-GATEWAY' || normalized === 'FOLLOW-DEFAULT';
616
+ }
617
+
618
+ export function resolvedModelSetting(value) {
619
+ const trimmed = String(value ?? '').trim();
620
+ return isUnsetSetting(trimmed) ? '' : trimmed;
621
+ }
622
+
603
623
  export function validateSettingValue(setting, value) {
604
624
  if (!value) {
605
625
  if (setting.required) return `${setting.label} cannot be blank`;
@@ -691,8 +711,9 @@ Usage:
691
711
  subc config edit [vim|nano]
692
712
  subc -p NAME config edit [vim|nano]
693
713
  subc config [show|path|list|create|delete]
694
- [--gateway-url URL] [--api-key KEY] [--model MODEL]
695
- [--subagent-model MODEL|follow-default]
714
+ [--gateway-url URL] [--api-key KEY]
715
+ [--model MODEL|UNSET]
716
+ [--subagent-model MODEL|UNSET]
696
717
 
697
718
  subc config List every profile and its file path
698
719
  subc -p NAME config Print that profile's path and env file
@@ -702,6 +723,9 @@ Usage:
702
723
  subc config path Print the selected profile path
703
724
  subc -p NAME config create Create a new profile with default settings
704
725
  subc -p NAME config delete Delete a non-default profile
726
+
727
+ --model UNSET Clear MODEL so launches use the first live catalog model
728
+ --subagent-model UNSET Clear the Claude subagent override so it follows MODEL
705
729
  `);
706
730
  }
707
731
 
@@ -745,7 +769,8 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
745
769
  '--model': 'MODEL',
746
770
  '--subagent-model': 'CLAUDE_CODE_SUBAGENT_MODEL',
747
771
  }[arg];
748
- updates[key] = arg === '--subagent-model' && value === 'follow-default' ? '' : value;
772
+ updates[key] =
773
+ (arg === '--model' || arg === '--subagent-model') && isUnsetSetting(value) ? '' : value;
749
774
  } else if (action === 'edit' && ALLOWED_EDITORS.has(arg)) {
750
775
  if (editor) throw new Error('Specify only one editor (vim or nano)');
751
776
  editor = arg;
@@ -813,7 +838,7 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
813
838
  }
814
839
 
815
840
  export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
816
- const selectedModel = options.selectedModel || registry.defaults.model;
841
+ const selectedModel = options.selectedModel || models[0] || registry.defaults.model;
817
842
  console.log(`\n ${c.bold}Available models${c.reset}\n`);
818
843
  for (const model of models) {
819
844
  const suffix = model === selectedModel ? ` ${c.dim}(default)${c.reset}` : '';
@@ -13,6 +13,49 @@
13
13
  "platformUrl": "https://subconscious.dev/platform"
14
14
  },
15
15
  "agents": [
16
+ {
17
+ "id": "subconscious-code",
18
+ "command": "sc",
19
+ "name": "Subconscious Code",
20
+ "aliases": [
21
+ "subconscious-code"
22
+ ],
23
+ "description": "Launch the native Subconscious coding agent with the active profile, gateway, and model.",
24
+ "framework": "Subconscious Code",
25
+ "language": "rust",
26
+ "category": "coding-agent",
27
+ "tags": [
28
+ "local",
29
+ "cli",
30
+ "agents"
31
+ ],
32
+ "protocol": "openai",
33
+ "homepage": "https://github.com/subconscious-systems/subconscious-code",
34
+ "bin": "sc",
35
+ "install": {
36
+ "darwin": "cargo install --locked --git https://github.com/subconscious-systems/subconscious-code --bin sc rc-cli",
37
+ "linux": "subc sc install",
38
+ "fallback": "cargo install --locked --git https://github.com/subconscious-systems/subconscious-code --bin sc rc-cli"
39
+ },
40
+ "runbook": {
41
+ "mode": "launch",
42
+ "script": "subconscious-code/run.sh",
43
+ "setupScript": "subconscious-code/install.sh",
44
+ "binaryInstallScript": "subconscious-code/install.sh",
45
+ "setupActions": [
46
+ "install"
47
+ ],
48
+ "setupNeedsAuth": false
49
+ },
50
+ "launch": "sc",
51
+ "env": {
52
+ "SC_BASE_URL": "{baseUrlV1}",
53
+ "SC_DLR_URL": "{baseUrl}",
54
+ "SC_DLR_ENABLED": "true",
55
+ "SC_API_KEY": "{apiKey}",
56
+ "SC_MODEL": "{model}"
57
+ }
58
+ },
16
59
  {
17
60
  "id": "claude-code",
18
61
  "command": "claude",
@@ -1,7 +1,7 @@
1
1
  # Coding-agent integrations
2
2
 
3
3
  These scripts are part of `subc`. They launch or surgically install Claude Code,
4
- Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi against the
4
+ Subconscious Code, Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi against the
5
5
  Subconscious gateway.
6
6
 
7
7
  `subc` reads `~/.subconscious/profiles/<name>.env` and injects those values into
@@ -10,6 +10,7 @@ directly, `SUBC_ENV_FILE` (or `cli/bin/runbook/.env`) can supply the same keys.
10
10
 
11
11
  Included integrations:
12
12
 
13
+ - `subconscious-code/install.sh` and `run.sh`
13
14
  - `claude-code/install.sh` and `run.sh`
14
15
  - `codex/install.sh`, `run.sh`, `hook.sh`, `hooks.json`, and `hooks-lib.sh`
15
16
  - `opencode/install.sh`, `run.sh`, and `subconscious-compaction.ts`
@@ -62,9 +62,6 @@ DEFAULT_COMPACT_WINDOW="1000000"
62
62
  DEFAULT_MAX_CONTEXT_TOKENS="3000000"
63
63
  DEFAULT_MAX_CONCURRENT_SUBAGENTS="4"
64
64
  DEFAULT_MAX_SUBAGENT_SPAWN_DEPTH="1"
65
- DEFAULT_OPUS_MODEL="subconscious/glm-5.2"
66
- DEFAULT_SONNET_MODEL="subconscious/tim-qwen3.6-27b"
67
- DEFAULT_HAIKU_MODEL="subconscious/deepseek-v4-flash-marathon"
68
65
 
69
66
  GATEWAY_URL="${GATEWAY_URL:-}"
70
67
  API_KEY="${CLAUDE_CODE_API_KEY:-${API_KEY:-}}"
@@ -73,15 +70,55 @@ COMPACT_WINDOW="${CLAUDE_CODE_AUTO_COMPACT_WINDOW:-${COMPACT_WINDOW:-$DEFAULT_CO
73
70
  MAX_CONTEXT_TOKENS="${CLAUDE_CODE_MAX_CONTEXT_TOKENS:-${MAX_CONTEXT_TOKENS:-$DEFAULT_MAX_CONTEXT_TOKENS}}"
74
71
  MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-$DEFAULT_MAX_CONCURRENT_SUBAGENTS}"
75
72
  MAX_SUBAGENT_SPAWN_DEPTH="${MAX_SUBAGENT_SPAWN_DEPTH:-$DEFAULT_MAX_SUBAGENT_SPAWN_DEPTH}"
73
+
74
+ PICKER_MODELS=()
75
+ if [[ -n "${SUBCONSCIOUS_MODELS:-}" ]]; then
76
+ while IFS= read -r picker_model; do
77
+ [[ -n "$picker_model" ]] && PICKER_MODELS+=("$picker_model")
78
+ done <<< "$SUBCONSCIOUS_MODELS"
79
+ fi
80
+
81
+ picker_model_at() {
82
+ local index="$1"
83
+ local count="${#PICKER_MODELS[@]}"
84
+ if [[ "$count" -eq 0 ]]; then
85
+ printf '%s' "$MODEL"
86
+ return
87
+ fi
88
+ if [[ "$index" -ge "$count" ]]; then
89
+ index=$((count - 1))
90
+ fi
91
+ printf '%s' "${PICKER_MODELS[$index]}"
92
+ }
93
+
94
+ in_picker_catalog() {
95
+ local candidate="$1" existing
96
+ [[ -z "$candidate" ]] && return 1
97
+ [[ "${#PICKER_MODELS[@]}" -eq 0 ]] && return 0
98
+ for existing in "${PICKER_MODELS[@]}"; do
99
+ [[ "$existing" == "$candidate" ]] && return 0
100
+ done
101
+ return 1
102
+ }
103
+
104
+ if [[ "${#PICKER_MODELS[@]}" -gt 0 ]]; then
105
+ in_picker_catalog "${ANTHROPIC_DEFAULT_OPUS_MODEL:-}" || ANTHROPIC_DEFAULT_OPUS_MODEL=""
106
+ in_picker_catalog "${ANTHROPIC_DEFAULT_SONNET_MODEL:-}" || ANTHROPIC_DEFAULT_SONNET_MODEL=""
107
+ in_picker_catalog "${ANTHROPIC_DEFAULT_HAIKU_MODEL:-}" || ANTHROPIC_DEFAULT_HAIKU_MODEL=""
108
+ fi
109
+
110
+ DEFAULT_OPUS_MODEL="$(picker_model_at 0)"
111
+ DEFAULT_SONNET_MODEL="$(picker_model_at 1)"
112
+ DEFAULT_HAIKU_MODEL="$(picker_model_at 2)"
76
113
  OPUS_MODEL="${ANTHROPIC_DEFAULT_OPUS_MODEL:-$DEFAULT_OPUS_MODEL}"
77
114
  OPUS_MODEL_NAME="${ANTHROPIC_DEFAULT_OPUS_MODEL_NAME:-$OPUS_MODEL}"
78
- OPUS_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION:-Subconscious default model}"
115
+ OPUS_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION:-Subconscious model ${OPUS_MODEL}}"
79
116
  SONNET_MODEL="${ANTHROPIC_DEFAULT_SONNET_MODEL:-$DEFAULT_SONNET_MODEL}"
80
117
  SONNET_MODEL_NAME="${ANTHROPIC_DEFAULT_SONNET_MODEL_NAME:-$SONNET_MODEL}"
81
- SONNET_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION:-Subconscious TIM Qwen 3.6 27B}"
118
+ SONNET_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION:-Subconscious model ${SONNET_MODEL}}"
82
119
  HAIKU_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL:-$DEFAULT_HAIKU_MODEL}"
83
120
  HAIKU_MODEL_NAME="${ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME:-$HAIKU_MODEL}"
84
- HAIKU_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION:-Subconscious DeepSeek V4 Flash Marathon}"
121
+ HAIKU_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION:-Subconscious model ${HAIKU_MODEL}}"
85
122
  COMMAND="install"
86
123
 
87
124
  usage() {
@@ -30,9 +30,6 @@ DEFAULT_COMPACT_WINDOW="1000000"
30
30
  DEFAULT_MAX_CONTEXT_TOKENS="3000000"
31
31
  DEFAULT_MAX_CONCURRENT_SUBAGENTS="4"
32
32
  DEFAULT_MAX_SUBAGENT_SPAWN_DEPTH="1"
33
- DEFAULT_OPUS_MODEL="subconscious/glm-5.2"
34
- DEFAULT_SONNET_MODEL="subconscious/tim-qwen3.6-27b"
35
- DEFAULT_HAIKU_MODEL="subconscious/deepseek-v4-flash-marathon"
36
33
 
37
34
  GATEWAY_URL="${GATEWAY_URL:-}"
38
35
  API_KEY="${CLAUDE_CODE_API_KEY:-${API_KEY:-}}"
@@ -41,15 +38,94 @@ COMPACT_WINDOW="${CLAUDE_CODE_AUTO_COMPACT_WINDOW:-${COMPACT_WINDOW:-$DEFAULT_CO
41
38
  MAX_CONTEXT_TOKENS="${CLAUDE_CODE_MAX_CONTEXT_TOKENS:-${MAX_CONTEXT_TOKENS:-$DEFAULT_MAX_CONTEXT_TOKENS}}"
42
39
  MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-$DEFAULT_MAX_CONCURRENT_SUBAGENTS}"
43
40
  MAX_SUBAGENT_SPAWN_DEPTH="${MAX_SUBAGENT_SPAWN_DEPTH:-$DEFAULT_MAX_SUBAGENT_SPAWN_DEPTH}"
41
+
42
+ # Claude's Opus/Sonnet/Haiku/Fable/custom slots must follow the live catalog
43
+ # (SUBCONSCIOUS_MODELS). Packaged DeepSeek/GLM defaults are not a capability.
44
+ PICKER_MODELS=()
45
+ if [[ -n "${SUBCONSCIOUS_MODELS:-}" ]]; then
46
+ while IFS= read -r picker_model; do
47
+ [[ -n "$picker_model" ]] && PICKER_MODELS+=("$picker_model")
48
+ done <<< "$SUBCONSCIOUS_MODELS"
49
+ fi
50
+
51
+ picker_model_at() {
52
+ local index="$1"
53
+ local count="${#PICKER_MODELS[@]}"
54
+ if [[ "$count" -eq 0 ]]; then
55
+ printf '%s' "$MODEL"
56
+ return
57
+ fi
58
+ if [[ "$index" -ge "$count" ]]; then
59
+ index=$((count - 1))
60
+ fi
61
+ printf '%s' "${PICKER_MODELS[$index]}"
62
+ }
63
+
64
+ in_picker_catalog() {
65
+ local candidate="$1" existing
66
+ [[ -z "$candidate" ]] && return 1
67
+ [[ "${#PICKER_MODELS[@]}" -eq 0 ]] && return 0
68
+ for existing in "${PICKER_MODELS[@]}"; do
69
+ [[ "$existing" == "$candidate" ]] && return 0
70
+ done
71
+ return 1
72
+ }
73
+
74
+ if [[ "${#PICKER_MODELS[@]}" -gt 0 ]]; then
75
+ in_picker_catalog "${ANTHROPIC_DEFAULT_OPUS_MODEL:-}" || ANTHROPIC_DEFAULT_OPUS_MODEL=""
76
+ in_picker_catalog "${ANTHROPIC_DEFAULT_SONNET_MODEL:-}" || ANTHROPIC_DEFAULT_SONNET_MODEL=""
77
+ in_picker_catalog "${ANTHROPIC_DEFAULT_HAIKU_MODEL:-}" || ANTHROPIC_DEFAULT_HAIKU_MODEL=""
78
+ in_picker_catalog "${ANTHROPIC_DEFAULT_FABLE_MODEL:-}" || unset ANTHROPIC_DEFAULT_FABLE_MODEL ANTHROPIC_DEFAULT_FABLE_MODEL_NAME ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION
79
+ if [[ "${#PICKER_MODELS[@]}" -lt 5 ]] || ! in_picker_catalog "${ANTHROPIC_CUSTOM_MODEL_OPTION:-}"; then
80
+ unset ANTHROPIC_CUSTOM_MODEL_OPTION ANTHROPIC_CUSTOM_MODEL_OPTION_NAME ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION
81
+ fi
82
+ fi
83
+
84
+ DEFAULT_OPUS_MODEL="$(picker_model_at 0)"
85
+ DEFAULT_SONNET_MODEL="$(picker_model_at 1)"
86
+ DEFAULT_HAIKU_MODEL="$(picker_model_at 2)"
87
+ DEFAULT_FABLE_MODEL="$(picker_model_at 3)"
44
88
  OPUS_MODEL="${ANTHROPIC_DEFAULT_OPUS_MODEL:-$DEFAULT_OPUS_MODEL}"
45
89
  OPUS_MODEL_NAME="${ANTHROPIC_DEFAULT_OPUS_MODEL_NAME:-$OPUS_MODEL}"
46
- OPUS_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION:-Subconscious default model}"
90
+ OPUS_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION:-Subconscious model ${OPUS_MODEL}}"
47
91
  SONNET_MODEL="${ANTHROPIC_DEFAULT_SONNET_MODEL:-$DEFAULT_SONNET_MODEL}"
48
92
  SONNET_MODEL_NAME="${ANTHROPIC_DEFAULT_SONNET_MODEL_NAME:-$SONNET_MODEL}"
49
- SONNET_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION:-Subconscious TIM Qwen 3.6 27B}"
93
+ SONNET_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION:-Subconscious model ${SONNET_MODEL}}"
50
94
  HAIKU_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL:-$DEFAULT_HAIKU_MODEL}"
51
95
  HAIKU_MODEL_NAME="${ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME:-$HAIKU_MODEL}"
52
- HAIKU_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION:-Subconscious DeepSeek V4 Flash Marathon}"
96
+ HAIKU_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION:-Subconscious model ${HAIKU_MODEL}}"
97
+ FABLE_MODEL="${ANTHROPIC_DEFAULT_FABLE_MODEL:-$DEFAULT_FABLE_MODEL}"
98
+ FABLE_MODEL_NAME="${ANTHROPIC_DEFAULT_FABLE_MODEL_NAME:-$FABLE_MODEL}"
99
+ FABLE_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION:-Subconscious model ${FABLE_MODEL}}"
100
+
101
+ # Model IDs are already restricted to [-A-Za-z0-9._:/+], so they are JSON-safe.
102
+ build_claude_picker_settings() {
103
+ local ids=() model_id first options allowlist
104
+ if [[ "${#PICKER_MODELS[@]}" -gt 0 ]]; then
105
+ ids=("${PICKER_MODELS[@]}")
106
+ else
107
+ ids=("$MODEL")
108
+ fi
109
+ options=""
110
+ allowlist=""
111
+ first=1
112
+ for model_id in "${ids[@]}"; do
113
+ [[ -n "$model_id" ]] || continue
114
+ if [[ "$first" -eq 1 ]]; then
115
+ first=0
116
+ else
117
+ options="${options},"
118
+ allowlist="${allowlist},"
119
+ fi
120
+ options="${options}{\"model\":\"${model_id}\",\"label\":\"${model_id}\",\"description\":\"Subconscious model ${model_id}\"}"
121
+ allowlist="${allowlist}\"${model_id}\""
122
+ done
123
+ printf '{"availableModels":[%s],"modelPicker":{"replaceBuiltInOptions":true,"options":[%s]}}' "$allowlist" "$options"
124
+ }
125
+
126
+ if [[ -z "${SUBC_CLAUDE_SETTINGS:-}" ]]; then
127
+ SUBC_CLAUDE_SETTINGS="$(build_claude_picker_settings)"
128
+ fi
53
129
 
54
130
  usage() {
55
131
  cat <<'EOF'
@@ -136,6 +212,10 @@ export ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_SONNET_MO
136
212
  export ANTHROPIC_DEFAULT_HAIKU_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL:-$HAIKU_MODEL}"
137
213
  export ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="${ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME:-$HAIKU_MODEL_NAME}"
138
214
  export ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION:-$HAIKU_MODEL_DESCRIPTION}"
215
+ export ANTHROPIC_DEFAULT_FABLE_MODEL="${ANTHROPIC_DEFAULT_FABLE_MODEL:-$FABLE_MODEL}"
216
+ export ANTHROPIC_DEFAULT_FABLE_MODEL_NAME="${ANTHROPIC_DEFAULT_FABLE_MODEL_NAME:-$FABLE_MODEL_NAME}"
217
+ export ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION:-$FABLE_MODEL_DESCRIPTION}"
218
+ export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY="${CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY:-0}"
139
219
  export CLAUDE_CODE_SUBAGENT_MODEL="${CLAUDE_CODE_SUBAGENT_MODEL:-$MODEL}"
140
220
  export CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS}"
141
221
  export CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH="${MAX_SUBAGENT_SPAWN_DEPTH}"
@@ -164,4 +244,6 @@ if [[ "${BASH_SOURCE[0]:-$0}" != "${0}" ]]; then
164
244
  fi
165
245
 
166
246
  # If executed, launch claude with any passed-through args.
167
- exec claude ${PASSTHRU[@]+"${PASSTHRU[@]}"}
247
+ # --settings is additional JSON: it replaces the built-in Fable/Opus lineup
248
+ # with the live catalog and allowlists only those models.
249
+ exec claude --settings "${SUBC_CLAUDE_SETTINGS}" ${PASSTHRU[@]+"${PASSTHRU[@]}"}
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env bash
2
+ # Install Subconscious Code. Tagged releases currently contain static Linux
3
+ # binaries; macOS builds from the tagged source until Darwin assets are added.
4
+
5
+ set -euo pipefail
6
+
7
+ REPOSITORY="subconscious-systems/subconscious-code"
8
+ REPOSITORY_URL="https://github.com/${REPOSITORY}"
9
+ INSTALL_DIR="${SC_INSTALL_DIR:-${HOME}/.local/bin}"
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Usage: subc sc install
14
+
15
+ Install the latest Subconscious Code release. Linux downloads and verifies the
16
+ published static binary. macOS builds the release from source with Cargo.
17
+ EOF
18
+ }
19
+
20
+ case "${1:-install}" in
21
+ install) ;;
22
+ help|-h|--help)
23
+ usage
24
+ exit 0
25
+ ;;
26
+ *)
27
+ echo "error: unsupported Subconscious Code setup action: $1" >&2
28
+ usage >&2
29
+ exit 2
30
+ ;;
31
+ esac
32
+
33
+ release_tag() {
34
+ if [[ -n "${SC_CODE_VERSION:-}" ]]; then
35
+ printf '%s\n' "${SC_CODE_VERSION#v}"
36
+ return
37
+ fi
38
+ if command -v gh >/dev/null 2>&1; then
39
+ local tag
40
+ tag="$(gh release view --repo "$REPOSITORY" --json tagName --jq '.tagName' 2>/dev/null || true)"
41
+ if [[ -n "$tag" ]]; then
42
+ printf '%s\n' "${tag#v}"
43
+ return
44
+ fi
45
+ fi
46
+ if command -v curl >/dev/null 2>&1; then
47
+ local tag
48
+ tag="$(curl -fsSL "https://api.github.com/repos/${REPOSITORY}/releases/latest" 2>/dev/null \
49
+ | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
50
+ | head -n 1 || true)"
51
+ if [[ -n "$tag" ]]; then
52
+ printf '%s\n' "${tag#v}"
53
+ return
54
+ fi
55
+ fi
56
+ return 1
57
+ }
58
+
59
+ install_from_source() {
60
+ if ! command -v cargo >/dev/null 2>&1; then
61
+ echo "error: macOS installation currently requires Cargo (Rust 1.89 or newer)" >&2
62
+ echo "Install Rust from https://rustup.rs, then run: subc sc install" >&2
63
+ exit 1
64
+ fi
65
+
66
+ local version="${1:-}"
67
+ local args=(install --locked --git "$REPOSITORY_URL" --bin sc)
68
+ if [[ -n "$version" ]]; then
69
+ args+=(--tag "v${version}")
70
+ echo "Building Subconscious Code v${version} from source..."
71
+ else
72
+ echo "No published release is visible yet; building Subconscious Code from the default branch..."
73
+ fi
74
+ args+=(rc-cli)
75
+ CARGO_NET_GIT_FETCH_WITH_CLI=true cargo "${args[@]}"
76
+ }
77
+
78
+ platform="$(uname -s)"
79
+ version="$(release_tag || true)"
80
+
81
+ if [[ "$platform" == "Darwin" ]]; then
82
+ install_from_source "$version"
83
+ exit 0
84
+ fi
85
+
86
+ if [[ "$platform" != "Linux" ]]; then
87
+ echo "error: Subconscious Code automatic installation supports Linux and macOS" >&2
88
+ exit 1
89
+ fi
90
+
91
+ if [[ -z "$version" ]]; then
92
+ echo "error: no published Subconscious Code release is available yet" >&2
93
+ echo "The release may still be building. Retry subc sc install after it finishes." >&2
94
+ exit 1
95
+ fi
96
+
97
+ case "$(uname -m)" in
98
+ x86_64|amd64) target="x86_64-unknown-linux-musl" ;;
99
+ aarch64|arm64) target="aarch64-unknown-linux-musl" ;;
100
+ *)
101
+ echo "error: unsupported Linux architecture: $(uname -m)" >&2
102
+ exit 1
103
+ ;;
104
+ esac
105
+
106
+ asset="sc-${target}.tar.gz"
107
+ checksum="${asset}.sha256"
108
+ work_dir="$(mktemp -d "${TMPDIR:-/tmp}/subc-sc-install.XXXXXX")"
109
+ cleanup() {
110
+ rm -f "$work_dir/$asset" "$work_dir/$checksum" "$work_dir/sc"
111
+ rmdir "$work_dir" 2>/dev/null || true
112
+ }
113
+ trap cleanup EXIT HUP INT TERM
114
+
115
+ echo "Downloading Subconscious Code v${version} for ${target}..."
116
+ if command -v gh >/dev/null 2>&1 \
117
+ && gh release view "v${version}" --repo "$REPOSITORY" >/dev/null 2>&1; then
118
+ gh release download "v${version}" --repo "$REPOSITORY" \
119
+ --pattern "$asset" --pattern "$checksum" --dir "$work_dir"
120
+ else
121
+ command -v curl >/dev/null 2>&1 || {
122
+ echo "error: curl is required to download Subconscious Code" >&2
123
+ exit 1
124
+ }
125
+ release_url="${REPOSITORY_URL}/releases/download/v${version}"
126
+ curl -fL "${release_url}/${asset}" -o "$work_dir/$asset"
127
+ curl -fL "${release_url}/${checksum}" -o "$work_dir/$checksum"
128
+ fi
129
+
130
+ if command -v sha256sum >/dev/null 2>&1; then
131
+ (cd "$work_dir" && sha256sum --check "$checksum")
132
+ elif command -v shasum >/dev/null 2>&1; then
133
+ expected="$(awk '{print $1}' "$work_dir/$checksum")"
134
+ actual="$(shasum -a 256 "$work_dir/$asset" | awk '{print $1}')"
135
+ [[ "$actual" == "$expected" ]] || {
136
+ echo "error: Subconscious Code checksum verification failed" >&2
137
+ exit 1
138
+ }
139
+ else
140
+ echo "error: sha256sum or shasum is required to verify the download" >&2
141
+ exit 1
142
+ fi
143
+
144
+ tar -xzf "$work_dir/$asset" -C "$work_dir" sc
145
+ mkdir -p "$INSTALL_DIR"
146
+ install -m 0755 "$work_dir/sc" "$INSTALL_DIR/sc"
147
+ echo "Installed Subconscious Code v${version} to $INSTALL_DIR/sc"
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # Launch Subconscious Code without changing ~/.sc or project settings.
3
+
4
+ set -euo pipefail
5
+
6
+ GATEWAY_URL="${GATEWAY_URL:-}"
7
+ API_KEY="${SC_API_KEY:-${API_KEY:-}}"
8
+ MODEL="${MODEL:-subconscious/glm-5.2}"
9
+
10
+ if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
11
+ echo "error: GATEWAY_URL and API_KEY are required to launch Subconscious Code" >&2
12
+ exit 1
13
+ fi
14
+
15
+ export SC_API_KEY="$API_KEY"
16
+ export SC_BASE_URL="${GATEWAY_URL%/}/v1"
17
+ export SC_DLR_URL="${GATEWAY_URL%/}"
18
+ export SC_DLR_ENABLED=true
19
+ export SC_MODEL="$MODEL"
20
+
21
+ exec sc "$@"
package/bin/tui.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  DEFAULT_PROFILE,
12
12
  listProfiles,
13
13
  loadProfile,
14
+ resolvedModelSetting,
14
15
  RUNBOOK_DEFAULTS,
15
16
  SUPPORTED_MODELS as PACKAGED_MODELS,
16
17
  } from './profiles.js';
@@ -70,15 +71,11 @@ export async function resolveTuiExecutable(options = {}) {
70
71
  }
71
72
 
72
73
  function selectedModelFor(profile) {
73
- return (
74
- process.env.SUBCONSCIOUS_MODEL?.trim() ||
75
- profile.values.MODEL?.trim() ||
76
- RUNBOOK_DEFAULTS.MODEL
77
- );
74
+ return resolvedModelSetting(process.env.SUBCONSCIOUS_MODEL || profile.values.MODEL);
78
75
  }
79
76
 
80
77
  function subagentModelFor(profile) {
81
- return profile.values.CLAUDE_CODE_SUBAGENT_MODEL?.trim() || '';
78
+ return resolvedModelSetting(profile.values.CLAUDE_CODE_SUBAGENT_MODEL);
82
79
  }
83
80
 
84
81
  function gatewayFor(profile) {
@@ -117,14 +114,15 @@ export async function createTuiState(profileName = DEFAULT_PROFILE, options = {}
117
114
  );
118
115
 
119
116
  const auth = await getApiKey(activeProfile);
120
- const selectedModel = selectedModelFor(activeProfile);
117
+ const requestedModel = selectedModelFor(activeProfile);
121
118
  const gatewayUrl = gatewayFor(activeProfile);
122
119
  const catalog = await resolveModelCatalog({
123
120
  baseUrl: gatewayUrl,
124
121
  apiKey: auth?.key,
125
- selectedModel,
122
+ selectedModel: requestedModel,
126
123
  fallbackModels: PACKAGED_MODELS,
127
124
  });
125
+ const selectedModel = requestedModel;
128
126
 
129
127
  return {
130
128
  version: await packageVersion(),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "subconscious-cli",
3
- "version": "4.0.13",
4
- "description": "CLI for Subconscious — run Claude Code, Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi",
3
+ "version": "4.0.15",
4
+ "description": "CLI for Subconscious — run Subconscious Code, Claude Code, Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi",
5
5
  "bin": {
6
6
  "subc": "bin/cli.js"
7
7
  },
@@ -33,6 +33,7 @@
33
33
  "authentication",
34
34
  "profiles",
35
35
  "coding-agent",
36
+ "subconscious-code",
36
37
  "claude-code",
37
38
  "opencode",
38
39
  "codex",