subconscious-cli 0.3.0 → 4.0.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/bin/profiles.js CHANGED
@@ -1,17 +1,16 @@
1
1
  /**
2
- * Runbook-compatible profiles stored independently of the installed package.
2
+ * Named .env profiles stored independently of the installed package.
3
3
  *
4
4
  * Profiles use a small .env format so users can inspect or edit them directly:
5
5
  * ~/.subconscious/profiles/default.env
6
6
  * ~/.subconscious/profiles/<name>.env
7
7
  */
8
8
 
9
+ import { spawn, execFileSync } from 'node:child_process';
9
10
  import fs from 'node:fs/promises';
10
11
  import os from 'node:os';
11
12
  import path from 'node:path';
12
13
  import { readFileSync } from 'node:fs';
13
- import readline from 'node:readline';
14
- import { Writable } from 'node:stream';
15
14
  import { c } from './colors.js';
16
15
 
17
16
  const registry = JSON.parse(
@@ -299,7 +298,7 @@ export function resolvedProfileValues(profile) {
299
298
  function profileTemplate() {
300
299
  const modelComments = SUPPORTED_MODELS.map((model) => `# ${model}`).join('\n');
301
300
  return `# Subconscious coding-agent profile
302
- # Generated by subc. Values are shared by the packaged ol-runbook integrations.
301
+ # Generated by subc. Extra KEY=value lines are passed through to launches.
303
302
 
304
303
  GATEWAY_URL={GATEWAY_URL}
305
304
  API_KEY={API_KEY}
@@ -308,7 +307,6 @@ ${modelComments}
308
307
  MODEL={MODEL}
309
308
 
310
309
  # Optional per-agent keys. Leave blank to use API_KEY above.
311
- CLAUDE_GATEWAY_URL={CLAUDE_GATEWAY_URL}
312
310
  CLAUDE_CODE_API_KEY={CLAUDE_CODE_API_KEY}
313
311
  CODEX_API_KEY={CODEX_API_KEY}
314
312
  OPENCODE_API_KEY={OPENCODE_API_KEY}
@@ -317,6 +315,8 @@ COPILOT_API_KEY={COPILOT_API_KEY}
317
315
  PI_API_KEY={PI_API_KEY}
318
316
 
319
317
  # Claude Code
318
+ # Leave CLAUDE_GATEWAY_URL blank to use GATEWAY_URL above.
319
+ CLAUDE_GATEWAY_URL={CLAUDE_GATEWAY_URL}
320
320
  CLAUDE_CODE_AUTO_COMPACT_WINDOW={CLAUDE_CODE_AUTO_COMPACT_WINDOW}
321
321
  CLAUDE_CODE_MAX_CONTEXT_TOKENS={CLAUDE_CODE_MAX_CONTEXT_TOKENS}
322
322
  MAX_CONCURRENT_SUBAGENTS={MAX_CONCURRENT_SUBAGENTS}
@@ -341,6 +341,8 @@ PI_MAX_TOKENS={PI_MAX_TOKENS}
341
341
  COPILOT_MAX_INPUT_TOKENS={COPILOT_MAX_INPUT_TOKENS}
342
342
  COPILOT_MAX_OUTPUT_TOKENS={COPILOT_MAX_OUTPUT_TOKENS}
343
343
  VSCODE_APP={VSCODE_APP}
344
+
345
+ # Extra KEY=value lines are passed through to launches.
344
346
  `;
345
347
  }
346
348
 
@@ -481,11 +483,31 @@ export async function clearProfileApiKey(name = DEFAULT_PROFILE) {
481
483
  return true;
482
484
  }
483
485
 
486
+ function isSecretKey(key) {
487
+ const upper = key.toUpperCase();
488
+ return (
489
+ upper.endsWith('API_KEY') ||
490
+ upper.includes('TOKEN') ||
491
+ upper.includes('SECRET') ||
492
+ upper.includes('PASSWORD') ||
493
+ upper.includes('AUTH')
494
+ );
495
+ }
496
+
484
497
  function redact(key, value) {
485
- if (!key.endsWith('API_KEY') || !value) return value;
498
+ if (!isSecretKey(key) || !value) return value;
486
499
  return value.length <= 8 ? '********' : `${value.slice(0, 4)}…${value.slice(-4)}`;
487
500
  }
488
501
 
502
+ function redactEnvLine(line) {
503
+ const match = line.match(/^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
504
+ if (!match) return line;
505
+ const [, prefix, key, raw] = match;
506
+ const value = decodeValue(raw);
507
+ if (!isSecretKey(key) || !value) return line;
508
+ return `${prefix}${key}=${redact(key, value)}`;
509
+ }
510
+
489
511
  export async function listProfiles() {
490
512
  try {
491
513
  const entries = await fs.readdir(PROFILES_DIR, { withFileTypes: true });
@@ -499,62 +521,24 @@ export async function listProfiles() {
499
521
  }
500
522
  }
501
523
 
502
- function printProfile(profile) {
524
+ async function printProfile(profile) {
503
525
  console.log(`\n ${c.bold}Profile: ${profile.name}${c.reset}`);
504
- console.log(` ${c.dim}Path: ${profile.path}${c.reset}`);
526
+ console.log(` ${c.dim}${profile.path}${c.reset}\n`);
505
527
  if (!profile.exists) {
506
528
  console.log(` ${c.yellow}Not configured.${c.reset}\n`);
507
529
  return;
508
530
  }
509
- for (const key of ['GATEWAY_URL', 'API_KEY', 'MODEL']) {
510
- console.log(` ${c.dim}${key.padEnd(12)}${c.reset}${redact(key, profile.values[key] || '')}`);
511
- }
531
+ const text = await fs.readFile(profile.path, 'utf-8');
532
+ console.log(
533
+ text
534
+ .replace(/\n$/, '')
535
+ .split('\n')
536
+ .map(redactEnvLine)
537
+ .join('\n'),
538
+ );
512
539
  console.log();
513
540
  }
514
541
 
515
- function createPrompter() {
516
- let muted = false;
517
- let rejectPending = null;
518
- const output = new Writable({
519
- write(chunk, _encoding, callback) {
520
- if (!muted) process.stdout.write(chunk);
521
- callback();
522
- },
523
- });
524
- const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
525
-
526
- rl.on('SIGINT', () => {
527
- const error = new Error('Interactive configuration cancelled.');
528
- error.code = 'SUBC_CANCELLED';
529
- rejectPending?.(error);
530
- });
531
-
532
- return {
533
- question(prompt, options = {}) {
534
- return new Promise((resolve, reject) => {
535
- rejectPending = reject;
536
- if (options.secret) {
537
- process.stdout.write(prompt);
538
- muted = true;
539
- }
540
- rl.question(options.secret ? '' : prompt, (answer) => {
541
- if (options.secret) {
542
- muted = false;
543
- process.stdout.write('\n');
544
- }
545
- rejectPending = null;
546
- resolve(answer);
547
- });
548
- });
549
- },
550
- close() {
551
- muted = false;
552
- rejectPending = null;
553
- rl.close();
554
- },
555
- };
556
- }
557
-
558
542
  export function validateSettingValue(setting, value) {
559
543
  if (!value) {
560
544
  if (setting.required) return `${setting.label} cannot be blank`;
@@ -587,146 +571,103 @@ export function validateSettingValue(setting, value) {
587
571
  return null;
588
572
  }
589
573
 
590
- async function promptSetting(prompter, setting, current) {
591
- console.log(`\n ${c.bold}${setting.label}${c.reset} ${c.dim}(${setting.key})${c.reset}`);
592
- console.log(` ${c.dim}${setting.description}${c.reset}`);
574
+ const ALLOWED_EDITORS = new Set(['vim', 'nano']);
593
575
 
594
- if (setting.type === 'choice') {
595
- setting.choices.forEach((choice, index) => {
596
- const label = choice || '(auto-detect)';
597
- const selected = choice === current ? ` ${c.green}current${c.reset}` : '';
598
- console.log(` ${index + 1}. ${label}${selected}`);
599
- });
600
- while (true) {
601
- const answer = (await prompter.question(' Select a number (Enter keeps current): ')).trim();
602
- if (!answer) return current;
603
- const index = Number(answer) - 1;
604
- if (Number.isInteger(index) && setting.choices[index] !== undefined) {
605
- return setting.choices[index];
606
- }
607
- if (setting.choices.includes(answer)) return answer;
608
- console.log(` ${c.yellow}Choose one of the listed values.${c.reset}`);
609
- }
576
+ function commandExists(bin) {
577
+ try {
578
+ execFileSync('which', [bin], { stdio: 'ignore' });
579
+ return true;
580
+ } catch {
581
+ return false;
610
582
  }
583
+ }
611
584
 
612
- const currentLabel = setting.type === 'secret' ? (current ? 'set' : 'not set') : current || 'blank';
613
- const suffix = setting.required ? 'Enter keeps current' : 'Enter keeps current; - clears';
614
- while (true) {
615
- const answer = (
616
- await prompter.question(
617
- ` Value [${currentLabel}] (${suffix}): `,
618
- { secret: setting.type === 'secret' },
619
- )
620
- ).trim();
621
- if (!answer) return current;
622
- const value = answer === '-' && !setting.required ? '' : answer;
623
- const error = validateSettingValue(setting, value);
624
- if (!error) return value;
625
- console.log(` ${c.yellow}${error}.${c.reset}`);
626
- }
585
+ function parseEditorCommand(editor) {
586
+ const parts = String(editor || '')
587
+ .split(/\s+/)
588
+ .filter(Boolean);
589
+ if (!parts.length) throw new Error('Editor is empty');
590
+ return { cmd: parts[0], args: parts.slice(1) };
627
591
  }
628
592
 
629
- function uniqueSettings(groups) {
630
- const settings = new Map();
631
- for (const group of groups) {
632
- for (const setting of group.settings) settings.set(setting.key, setting);
633
- }
634
- return [...settings.values()];
593
+ function resolveEditor(requested) {
594
+ if (requested) return { cmd: requested, args: [] };
595
+ const fromEnv = process.env.VISUAL?.trim() || process.env.EDITOR?.trim();
596
+ if (fromEnv) return parseEditorCommand(fromEnv);
597
+ if (commandExists('vim')) return { cmd: 'vim', args: [] };
598
+ if (commandExists('nano')) return { cmd: 'nano', args: [] };
599
+ throw new Error('No editor found. Install vim or nano, or set $VISUAL / $EDITOR.');
635
600
  }
636
601
 
637
- async function interactiveConfig(profileName) {
602
+ async function editProfile(profileName, requestedEditor) {
638
603
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
639
- throw new Error('Interactive settings require a terminal. Use subc config flags in scripts.');
604
+ console.error('Editing a profile requires a terminal.');
605
+ console.error(profilePath(profileName));
606
+ process.exitCode = 1;
607
+ return;
640
608
  }
641
609
 
642
- const prompter = createPrompter();
643
- try {
644
- const profiles = await listProfiles();
645
- console.log(`\n ${c.bold}Interactive coding-agent settings${c.reset}`);
646
- if (profiles.length) {
647
- console.log(` ${c.dim}Existing profiles: ${profiles.join(', ')}${c.reset}`);
648
- }
649
- const requestedName = (
650
- await prompter.question(` Profile name [${profileName}]: `)
651
- ).trim();
652
- const targetName = validateProfileName(requestedName || profileName);
653
- const profile = await loadProfile(targetName);
654
- const baseValues = resolvedProfileValues(profile);
655
- const updates = {};
656
-
657
- while (true) {
658
- console.log(`\n ${c.bold}Profile: ${targetName}${c.reset}`);
659
- PROFILE_SETTING_GROUPS.forEach((group, index) => {
660
- console.log(` ${index + 1}. ${group.label}`);
661
- });
662
- const allIndex = PROFILE_SETTING_GROUPS.length + 1;
663
- const saveIndex = allIndex + 1;
664
- console.log(` ${allIndex}. All settings`);
665
- console.log(` ${saveIndex}. Save and exit`);
666
- console.log(' 0. Cancel');
667
- if (Object.keys(updates).length) {
668
- console.log(` ${c.dim}${Object.keys(updates).length} pending change(s)${c.reset}`);
669
- }
610
+ const profile = await ensureProfile(profileName);
611
+ const { cmd, args } = resolveEditor(requestedEditor);
612
+ await new Promise((resolve, reject) => {
613
+ const child = spawn(cmd, [...args, profile.path], { stdio: 'inherit' });
614
+ child.on('error', (error) => {
615
+ reject(new Error(`Failed to launch editor '${cmd}': ${error.message}`));
616
+ });
617
+ child.on('close', (code) => {
618
+ if (code && code !== 0) process.exitCode = code;
619
+ resolve();
620
+ });
621
+ });
622
+ }
670
623
 
671
- const answer = (await prompter.question(' Choose a section: ')).trim();
672
- if (answer === '0') {
673
- console.log(`\n ${c.dim}No settings were written.${c.reset}\n`);
674
- return;
675
- }
676
- if (answer === String(saveIndex)) {
677
- const confirmation = (
678
- await prompter.question(` Save profile '${targetName}'? [Y/n] `)
679
- ).trim().toLowerCase();
680
- if (confirmation && !['y', 'yes'].includes(confirmation)) continue;
681
- const saved = Object.keys(updates).length
682
- ? await updateProfile(targetName, updates)
683
- : await ensureProfile(targetName);
684
- console.log(`\n ${c.green}${c.bold}✓ Saved profile '${targetName}'.${c.reset}`);
685
- console.log(` ${c.dim}${saved.path}${c.reset}\n`);
686
- return;
687
- }
624
+ export function printConfigHelp() {
625
+ console.log(`
626
+ Usage:
627
+ subc config
628
+ subc config help
629
+ subc -p NAME config
630
+ subc config edit [vim|nano]
631
+ subc -p NAME config edit [vim|nano]
632
+ subc config [show|path|list|delete]
633
+ [--gateway-url URL] [--api-key KEY] [--model MODEL]
688
634
 
689
- let selectedSettings;
690
- if (answer === String(allIndex)) {
691
- selectedSettings = uniqueSettings(PROFILE_SETTING_GROUPS);
692
- } else {
693
- const group = PROFILE_SETTING_GROUPS[Number(answer) - 1];
694
- if (!group) {
695
- console.log(` ${c.yellow}Choose a listed section.${c.reset}`);
696
- continue;
697
- }
698
- selectedSettings = group.settings;
699
- }
635
+ subc config List every profile and its file path
636
+ subc -p NAME config Print that profile's path and env file
637
+ subc config edit Open the selected profile in $VISUAL, $EDITOR, vim, or nano
638
+ subc config edit vim Open the selected profile in vim
639
+ subc config edit nano Open the selected profile in nano
640
+ subc config path Print the selected profile path
641
+ subc -p NAME config delete Delete a non-default profile
642
+ `);
643
+ }
700
644
 
701
- for (const setting of selectedSettings) {
702
- const current = updates[setting.key] ?? baseValues[setting.key] ?? '';
703
- const value = await promptSetting(prompter, setting, current);
704
- if (value === (baseValues[setting.key] ?? '')) {
705
- delete updates[setting.key];
706
- } else if (value !== current) {
707
- updates[setting.key] = value;
708
- }
709
- }
710
- }
711
- } catch (error) {
712
- if (error.code === 'SUBC_CANCELLED') {
713
- console.log(`\n ${c.dim}No settings were written.${c.reset}\n`);
714
- return;
715
- }
716
- throw error;
717
- } finally {
718
- prompter.close();
645
+ async function printProfileList(activeName) {
646
+ const profiles = await listProfiles();
647
+ if (!profiles.length) {
648
+ console.log(`\n ${c.dim}No profiles yet. Run subc login.${c.reset}\n`);
649
+ return;
650
+ }
651
+ console.log(`\n ${c.bold}Profiles${c.reset}`);
652
+ for (const name of profiles) {
653
+ const active = name === activeName ? ` ${c.green}(active)${c.reset}` : '';
654
+ console.log(` ${c.cyan}${name}${c.reset}${active} ${c.dim}${profilePath(name)}${c.reset}`);
719
655
  }
656
+ console.log();
720
657
  }
721
658
 
722
- export async function configCommand(argv, profileName = DEFAULT_PROFILE) {
723
- let action = 'show';
659
+ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options = {}) {
660
+ let action = 'default';
661
+ let editor;
724
662
  const updates = {};
725
663
 
726
664
  for (let i = 0; i < argv.length; i++) {
727
665
  const arg = argv[i];
728
- if (['show', 'path', 'list', 'delete', 'interactive'].includes(arg)) {
729
- action = arg;
666
+ if (arg === 'help' || arg === '-h' || arg === '--help') {
667
+ printConfigHelp();
668
+ return;
669
+ } else if (['show', 'path', 'list', 'delete', 'edit', 'interactive'].includes(arg)) {
670
+ action = arg === 'interactive' ? 'edit' : arg;
730
671
  } else if (arg === '--gateway-url' || arg === '--api-key' || arg === '--model') {
731
672
  const value = argv[++i];
732
673
  if (!value) throw new Error(`${arg} requires a value`);
@@ -736,37 +677,26 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE) {
736
677
  '--model': 'MODEL',
737
678
  }[arg];
738
679
  updates[key] = value;
739
- } else if (arg === '-h' || arg === '--help') {
740
- console.log(`
741
- Usage:
742
- subc config [show|path|list|delete|interactive]
743
- [--gateway-url URL] [--api-key KEY] [--model MODEL]
744
- subc --profile NAME config [...]
745
-
746
- Interactive wizard:
747
- subc settings
748
- subc --profile NAME config interactive
749
- `);
750
- return;
680
+ } else if (action === 'edit' && ALLOWED_EDITORS.has(arg)) {
681
+ if (editor) throw new Error('Specify only one editor (vim or nano)');
682
+ editor = arg;
683
+ } else if (action === 'edit') {
684
+ throw new Error(`Unknown editor '${arg}' (use vim or nano)`);
751
685
  } else {
752
686
  throw new Error(`Unknown config argument: ${arg}`);
753
687
  }
754
688
  }
755
689
 
756
- if (action === 'interactive') {
757
- await interactiveConfig(profileName);
690
+ if (action === 'edit') {
691
+ if (Object.keys(updates).length) {
692
+ throw new Error('config edit cannot be combined with --gateway-url, --api-key, or --model');
693
+ }
694
+ await editProfile(profileName, editor);
758
695
  return;
759
696
  }
760
697
 
761
- if (action === 'list') {
762
- const profiles = await listProfiles();
763
- if (!profiles.length) {
764
- console.log(`\n ${c.dim}No profiles yet. Run subc login.${c.reset}\n`);
765
- return;
766
- }
767
- console.log();
768
- for (const name of profiles) console.log(` ${name} ${profilePath(name)}`);
769
- console.log();
698
+ if (action === 'list' || (action === 'default' && !options.profileExplicit && !Object.keys(updates).length)) {
699
+ await printProfileList(profileName);
770
700
  return;
771
701
  }
772
702
 
@@ -792,11 +722,11 @@ Interactive wizard:
792
722
  if (Object.keys(updates).length) {
793
723
  const profile = await updateProfile(profileName, updates);
794
724
  console.log(`Updated profile '${profileName}'.`);
795
- printProfile(profile);
725
+ await printProfile(profile);
796
726
  return;
797
727
  }
798
728
 
799
- printProfile(await loadProfile(profileName));
729
+ await printProfile(await loadProfile(profileName));
800
730
  }
801
731
 
802
732
  export function modelsCommand() {
@@ -23,7 +23,7 @@
23
23
  "claudecode"
24
24
  ],
25
25
  "displayName": "Claude Code + Subconscious",
26
- "description": "Run Claude Code on your hosted Subconscious model using the ol-runbook environment and telemetry setup.",
26
+ "description": "Run Claude Code on your hosted Subconscious model with gateway env, context limits, and telemetry.",
27
27
  "framework": "Claude Code",
28
28
  "language": "shell",
29
29
  "category": "coding-agent",
@@ -44,7 +44,11 @@
44
44
  "runbook": {
45
45
  "mode": "launch",
46
46
  "script": "claude-code/run.sh",
47
- "setupScript": "claude-code/install.sh"
47
+ "setupScript": "claude-code/install.sh",
48
+ "setupActions": [
49
+ "status",
50
+ "uninstall"
51
+ ]
48
52
  },
49
53
  "launch": "claude",
50
54
  "env": {
@@ -83,7 +87,7 @@
83
87
  "name": "Codex CLI",
84
88
  "aliases": [],
85
89
  "displayName": "Codex + Subconscious",
86
- "description": "Run Codex on your hosted Subconscious model using the ol-runbook provider, model catalog, and compaction hooks.",
90
+ "description": "Run Codex on your hosted Subconscious model with a temporary provider catalog and compaction hooks.",
87
91
  "framework": "Codex",
88
92
  "language": "shell",
89
93
  "category": "coding-agent",
@@ -103,7 +107,12 @@
103
107
  "runbook": {
104
108
  "mode": "launch",
105
109
  "script": "codex/run.sh",
106
- "setupScript": "codex/install.sh"
110
+ "setupScript": "codex/install.sh",
111
+ "setupActions": [
112
+ "install",
113
+ "status",
114
+ "uninstall"
115
+ ]
107
116
  },
108
117
  "launch": "codex -c model={model} -c model_provider=subconscious -c web_search=disabled -c model_providers.subconscious.name=Subconscious -c model_providers.subconscious.base_url={baseUrlV1} -c model_providers.subconscious.wire_api=responses -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY -c model_providers.subconscious.stream_idle_timeout_ms=300000",
109
118
  "env": {
@@ -120,7 +129,7 @@
120
129
  "open-code"
121
130
  ],
122
131
  "displayName": "OpenCode + Subconscious",
123
- "description": "Run OpenCode on your hosted Subconscious model using the ol-runbook provider and context settings.",
132
+ "description": "Run OpenCode on your hosted Subconscious model with the Subconscious provider and context settings.",
124
133
  "framework": "OpenCode",
125
134
  "language": "shell",
126
135
  "category": "coding-agent",
@@ -140,7 +149,11 @@
140
149
  "runbook": {
141
150
  "mode": "launch",
142
151
  "script": "opencode/run.sh",
143
- "setupScript": "opencode/install.sh"
152
+ "setupScript": "opencode/install.sh",
153
+ "setupActions": [
154
+ "status",
155
+ "uninstall"
156
+ ]
144
157
  },
145
158
  "launch": "opencode",
146
159
  "configEnv": "OPENCODE_CONFIG_CONTENT",
@@ -199,7 +212,7 @@
199
212
  "command": "cursor",
200
213
  "name": "Cursor",
201
214
  "aliases": [],
202
- "description": "Install the ol-runbook Cursor conversation and compaction hooks, then finish model setup in Cursor Settings.",
215
+ "description": "Install Cursor conversation and compaction hooks, then finish model setup in Cursor Settings.",
203
216
  "framework": "Cursor",
204
217
  "language": "shell",
205
218
  "category": "coding-agent",
@@ -213,7 +226,12 @@
213
226
  "runbook": {
214
227
  "mode": "setup",
215
228
  "script": "cursor/install.sh",
216
- "setupScript": "cursor/install.sh"
229
+ "setupScript": "cursor/install.sh",
230
+ "setupActions": [
231
+ "install",
232
+ "status",
233
+ "uninstall"
234
+ ]
217
235
  }
218
236
  },
219
237
  {
@@ -224,7 +242,7 @@
224
242
  "vscode",
225
243
  "vs-code"
226
244
  ],
227
- "description": "Install the ol-runbook VS Code custom endpoint and Copilot conversation/compaction hooks.",
245
+ "description": "Install the VS Code custom endpoint and Copilot conversation/compaction hooks.",
228
246
  "framework": "GitHub Copilot",
229
247
  "language": "shell",
230
248
  "category": "coding-agent",
@@ -238,7 +256,12 @@
238
256
  "runbook": {
239
257
  "mode": "setup",
240
258
  "script": "copilot/install.sh",
241
- "setupScript": "copilot/install.sh"
259
+ "setupScript": "copilot/install.sh",
260
+ "setupActions": [
261
+ "install",
262
+ "status",
263
+ "uninstall"
264
+ ]
242
265
  }
243
266
  },
244
267
  {
@@ -246,7 +269,7 @@
246
269
  "command": "pi",
247
270
  "name": "Pi",
248
271
  "aliases": [],
249
- "description": "Launch Pi with the Subconscious provider and active profile model configured by subc setup.",
272
+ "description": "Launch Pi with the Subconscious provider and active profile model configured by subc pi install.",
250
273
  "framework": "Pi",
251
274
  "language": "shell",
252
275
  "category": "coding-agent",
@@ -261,7 +284,12 @@
261
284
  "runbook": {
262
285
  "mode": "launch",
263
286
  "script": "pi/run.sh",
264
- "setupScript": "pi/install.sh"
287
+ "setupScript": "pi/install.sh",
288
+ "setupActions": [
289
+ "install",
290
+ "status",
291
+ "uninstall"
292
+ ]
265
293
  }
266
294
  }
267
295
  ]
@@ -1,25 +1,22 @@
1
- # ol-runbook coding-agent integrations
1
+ # Coding-agent integrations
2
2
 
3
- These runtime files are vendored from `ol-runbook/coding-agents` so the npm
4
- package can run without a separate runbook checkout. The snapshot was synced
5
- from `ol-runbook` commit `2236390` (latest `main` on 2026-08-13), with Claude
6
- Code model-picker aliases and complete Codex, OpenCode, Pi, and Copilot model
7
- catalogs added for the CLI's full Subconscious catalog. Namespaced
8
- `subconscious/...` IDs are supported throughout. Codex app/plugin tools are
9
- also disabled by default to honor the gateway's 128-tool request limit.
3
+ These scripts are part of `subc`. They launch or surgically install Claude Code,
4
+ Codex, OpenCode, Cursor, Copilot, and Pi against the Subconscious gateway.
5
+
6
+ `subc` reads `~/.subconscious/profiles/<name>.env` and injects those values into
7
+ the scripts, so users do not need a sibling `.env` file. When a script is run
8
+ directly, `SUBC_ENV_FILE` (or `cli/bin/runbook/.env`) can supply the same keys.
10
9
 
11
10
  Included integrations:
12
11
 
13
12
  - `claude-code/install.sh` and `run.sh`
14
- - `codex/install.sh`, `run.sh`, `hook.sh`, and `hooks.json`
13
+ - `codex/install.sh`, `run.sh`, `hook.sh`, `hooks.json`, and `hooks-lib.sh`
15
14
  - `opencode/install.sh`, `run.sh`, and `subconscious-compaction.ts`
16
15
  - `cursor/install.sh`, `hook.sh`, and `hooks.json`
17
16
  - `copilot/install.sh`, `hook.sh`, and `hooks.json`
18
17
  - `pi/install.sh`, `run.sh`, and `subconscious-compaction.ts`
19
18
 
20
- When the upstream setup changes, update this snapshot and the corresponding
21
- entries in `agents/registry.json` together.
22
-
23
- At runtime `subc` reads `~/.subconscious/profiles/<name>.env` and injects those values
24
- into these scripts, so users do not need to create a runbook checkout or source
25
- an env file manually.
19
+ Each CLI-enabled entry in `agents/registry.json` points here with a `runbook`
20
+ block. Persistent writes are merge/unmerge only: they never replace a user's
21
+ `config.toml`, `opencode.json`, `models.json`, `hooks.json`, or VS Code
22
+ provider list.