subconscious-cli 6.0.1 → 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.
Files changed (73) hide show
  1. package/README.md +167 -43
  2. package/bin/agent-data.js +278 -0
  3. package/bin/agent-help.js +114 -0
  4. package/bin/agent-install.js +238 -0
  5. package/bin/agent-launch.js +199 -0
  6. package/bin/agent-spawn.js +137 -0
  7. package/bin/agents.js +236 -931
  8. package/bin/auth.js +9 -1
  9. package/bin/claude-picker.js +143 -0
  10. package/bin/cli.js +62 -2
  11. package/bin/clipboard-image.js +2 -0
  12. package/bin/feedback.js +14 -3
  13. package/bin/harness-manifest.js +286 -0
  14. package/bin/headless-args.js +35 -0
  15. package/bin/launch-values.js +25 -0
  16. package/bin/model-capabilities.js +10 -9
  17. package/bin/models.js +25 -13
  18. package/bin/native/subc-tui-darwin-amd64 +0 -0
  19. package/bin/native/subc-tui-darwin-arm64 +0 -0
  20. package/bin/native/subc-tui-linux-amd64 +0 -0
  21. package/bin/native/subc-tui-linux-arm64 +0 -0
  22. package/bin/native/subc-tui-windows-amd64.exe +0 -0
  23. package/bin/native/subc-tui-windows-arm64.exe +0 -0
  24. package/bin/opencode-provider.js +21 -0
  25. package/bin/profile-settings.js +129 -0
  26. package/bin/profiles.js +29 -424
  27. package/bin/runbook/README.md +77 -23
  28. package/bin/runbook/claude-code/agent.json +300 -0
  29. package/bin/runbook/claude-code/install.sh +9 -252
  30. package/bin/runbook/codex/agent.json +462 -0
  31. package/bin/runbook/codex/hooks-lib.sh +17 -4
  32. package/bin/runbook/codex/install.sh +0 -4
  33. package/bin/runbook/codex/run.sh +16 -235
  34. package/bin/runbook/copilot/agent.json +186 -0
  35. package/bin/runbook/copilot/install.sh +45 -63
  36. package/bin/runbook/cursor/agent.json +138 -0
  37. package/bin/runbook/cursor/install.sh +8 -35
  38. package/bin/runbook/deepseek-harness/agent.json +164 -0
  39. package/bin/runbook/deepseek-harness/run.sh +20 -59
  40. package/bin/runbook/image-window/index.ts +14 -0
  41. package/bin/runbook/image-window/window.d.ts +19 -0
  42. package/bin/runbook/image-window/window.js +140 -0
  43. package/bin/runbook/lib.sh +41 -0
  44. package/bin/runbook/opencode/agent.json +159 -0
  45. package/bin/runbook/opencode/install.sh +9 -183
  46. package/bin/runbook/opencode/opencode-plugin.d.ts +10 -3
  47. package/bin/runbook/opencode/subconscious-image-window.ts +24 -0
  48. package/bin/runbook/pi/agent.json +217 -0
  49. package/bin/runbook/pi/install.sh +18 -44
  50. package/bin/runbook/pi/run.sh +6 -18
  51. package/bin/runbook/pi/subconscious-compaction.ts +3 -1
  52. package/bin/runbook/shared.json +57 -0
  53. package/bin/sessions.js +53 -48
  54. package/bin/tui.js +13 -0
  55. package/bin/update-check.js +35 -1
  56. package/bin/upgrade.js +27 -34
  57. package/bin/usage.js +21 -10
  58. package/bin/windows/agents.js +28 -60
  59. package/bin/windows/common.js +2 -28
  60. package/bin/windows/launch.js +252 -231
  61. package/bin/windows/process.js +12 -1
  62. package/bin/windows/setup.js +29 -9
  63. package/package.json +2 -4
  64. package/bin/registry.generated.json +0 -406
  65. package/bin/runbook/claude-code/run.sh +0 -254
  66. package/bin/runbook/model-capabilities.generated.sh +0 -9
  67. package/bin/runbook/opencode/provider.sh +0 -93
  68. package/bin/runbook/opencode/run.sh +0 -99
  69. package/bin/runbook/opencode/subconscious-compaction.ts +0 -100
  70. package/bin/runbook/subconscious-code/install.sh +0 -154
  71. package/bin/runbook/subconscious-code/run.sh +0 -21
  72. package/bin/windows/claude.js +0 -100
  73. package/bin/windows/install.js +0 -188
@@ -1,14 +1,15 @@
1
- import { readFileSync } from 'node:fs';
2
-
3
- // The models endpoint currently supplies IDs, not modality metadata. Keep
4
- // explicit capabilities in the same registry used to generate the Unix lookup.
5
- const { modelCapabilities = {} } = JSON.parse(
6
- readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf8'),
7
- );
1
+ import { MODEL_CAPABILITIES } from './agent-data.js';
8
2
 
3
+ // The models endpoint supplies IDs, not modality metadata, so capabilities
4
+ // are listed by exact model ID: similarly named models inherit nothing.
9
5
  export function modelSupportsVision(modelId) {
10
6
  return (
11
- Object.hasOwn(modelCapabilities, modelId) &&
12
- modelCapabilities[modelId].vision === true
7
+ Object.hasOwn(MODEL_CAPABILITIES, modelId) &&
8
+ MODEL_CAPABILITIES[modelId].vision === true
13
9
  );
14
10
  }
11
+
12
+ /** Vision model IDs, newline-separated, for the runbooks' shell lookup. */
13
+ export function visionModelList() {
14
+ return Object.keys(MODEL_CAPABILITIES).filter(modelSupportsVision).join('\n');
15
+ }
package/bin/models.js CHANGED
@@ -106,26 +106,26 @@ export async function fetchGatewayModels({
106
106
  };
107
107
  controller.signal.addEventListener('abort', fail, { once: true });
108
108
  });
109
- const response = await Promise.race([
110
- fetchImpl(endpoint, {
109
+ const discover = async () => {
110
+ const response = await fetchImpl(endpoint, {
111
111
  method: 'GET',
112
112
  headers,
113
113
  cache: 'no-store',
114
114
  signal: controller.signal,
115
- }),
116
- aborted,
117
- ]);
115
+ });
118
116
 
119
- if (!response.ok) {
120
- throw new Error(`Model discovery returned HTTP ${response.status}`);
121
- }
117
+ if (!response.ok) {
118
+ throw new Error(`Model discovery returned HTTP ${response.status}`);
119
+ }
122
120
 
123
- const payload = await response.json();
124
- if (!Array.isArray(payload?.data)) {
125
- throw new Error('Model discovery returned an invalid response');
126
- }
121
+ const payload = await response.json();
122
+ if (!Array.isArray(payload?.data)) {
123
+ throw new Error('Model discovery returned an invalid response');
124
+ }
127
125
 
128
- return normalizeModelIds(payload.data.map((model) => model?.id));
126
+ return normalizeModelIds(payload.data.map((model) => model?.id));
127
+ };
128
+ return await Promise.race([discover(), aborted]);
129
129
  } catch (error) {
130
130
  if (signal?.aborted) throw abortError('Model discovery was cancelled');
131
131
  if (controller.signal.aborted) {
@@ -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
@@ -1,6 +1,11 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { modelIds, positiveInteger } from './launch-values.js';
1
3
  import { modelSupportsVision } from './model-capabilities.js';
2
4
 
3
5
  export const OPENCODE_PROVIDER_ID = 'subconscious';
6
+ const OPENCODE_IMAGE_WINDOW_PLUGIN = fileURLToPath(
7
+ new URL('./runbook/opencode/subconscious-image-window.ts', import.meta.url),
8
+ );
4
9
  export const OPENCODE_PROVIDER_NAME = 'Subconscious Gateway';
5
10
 
6
11
  const ACRONYMS = new Set([
@@ -64,6 +69,8 @@ export function buildOpenCodeConfig({
64
69
  return {
65
70
  $schema: 'https://opencode.ai/config.json',
66
71
  disabled_providers: ['subconscious-cli'],
72
+ // Trims each request to the newest 30 images before upload.
73
+ plugin: [OPENCODE_IMAGE_WINDOW_PLUGIN],
67
74
  provider: {
68
75
  [OPENCODE_PROVIDER_ID]: {
69
76
  npm: '@ai-sdk/openai-compatible',
@@ -81,3 +88,17 @@ export function buildOpenCodeConfig({
81
88
  model: `${OPENCODE_PROVIDER_ID}/${model}`,
82
89
  };
83
90
  }
91
+
92
+ /** The launch config for the env subc passes to OpenCode. */
93
+ export function openCodeConfigFromEnv(env) {
94
+ return buildOpenCodeConfig({
95
+ baseUrl: String(env.GATEWAY_URL ?? '').replace(/\/+$/, ''),
96
+ model: env.MODEL,
97
+ modelIds: modelIds(env),
98
+ context: positiveInteger(
99
+ env.OPENCODE_CONTEXT_LIMIT,
100
+ 'OPENCODE_CONTEXT_LIMIT',
101
+ ),
102
+ output: positiveInteger(env.OPENCODE_OUTPUT_LIMIT, 'OPENCODE_OUTPUT_LIMIT'),
103
+ });
104
+ }
@@ -0,0 +1,129 @@
1
+ import {
2
+ AGENTS,
3
+ agentInputs,
4
+ DEFAULTS,
5
+ PACKAGED_MODELS,
6
+ resolveRefs,
7
+ SHARED_INPUTS,
8
+ staticLookup,
9
+ } from './agent-data.js';
10
+
11
+ export const SUPPORTED_MODELS = Object.freeze([...PACKAGED_MODELS]);
12
+
13
+ const TOKEN = /\{[A-Za-z][A-Za-z0-9]*\}/;
14
+ const shown = (input) => input.label && input.group !== 'shared';
15
+
16
+ function toSetting(input) {
17
+ const model = input.type === 'model';
18
+ const setting = {
19
+ key: input.name,
20
+ label: input.label,
21
+ description: input.description,
22
+ type: model ? 'choice' : input.type || 'text',
23
+ };
24
+ if (model) Object.assign(setting, { choices: SUPPORTED_MODELS, model });
25
+ if (input.choices) setting.choices = input.choices;
26
+ for (const field of ['min', 'max', 'required'])
27
+ if (input[field] !== undefined) setting[field] = input[field];
28
+ return Object.freeze(setting);
29
+ }
30
+
31
+ function group(id, label, inputs) {
32
+ return Object.freeze({
33
+ id,
34
+ label,
35
+ keys: inputs.map((input) => input.name),
36
+ settings: inputs.map(toSetting),
37
+ });
38
+ }
39
+
40
+ export const PROFILE_SETTING_GROUPS = Object.freeze([
41
+ group(
42
+ 'shared',
43
+ 'Shared settings',
44
+ SHARED_INPUTS.filter((input) => input.group === 'shared'),
45
+ ),
46
+ ...AGENTS.map((agent) =>
47
+ group(agent.id, agent.name, agentInputs(agent).filter(shown)),
48
+ ),
49
+ ]);
50
+
51
+ /** A default a profile can hold: literal, with references resolved. */
52
+ function profileDefault(input, lookup) {
53
+ if (input.type === 'secret' || input.default === undefined) return '';
54
+ const value = String(resolveRefs(input.default, lookup));
55
+ return TOKEN.test(value) ? '' : value;
56
+ }
57
+
58
+ function agentDefaults(agent) {
59
+ const lookup = staticLookup(agent);
60
+ // A profile pins every default it holds, so inputs kept out of the
61
+ // template stay out of new and existing profiles.
62
+ return agentInputs(agent)
63
+ .filter((input) => input.label && input.template !== false)
64
+ .map((input) => [input.name, profileDefault(input, lookup)]);
65
+ }
66
+
67
+ export const RUNBOOK_DEFAULTS = Object.freeze({
68
+ ...Object.fromEntries(AGENTS.flatMap(agentDefaults)),
69
+ GATEWAY_URL: DEFAULTS.baseUrl,
70
+ API_KEY: '',
71
+ MODEL: DEFAULTS.model,
72
+ });
73
+
74
+ export function profileSettingsForAgent(agentId) {
75
+ const shared = PROFILE_SETTING_GROUPS.find((item) => item.id === 'shared');
76
+ const agent = PROFILE_SETTING_GROUPS.find((item) => item.id === agentId);
77
+ return [...(shared?.settings || []), ...(agent?.settings || [])];
78
+ }
79
+
80
+ export function resolvedProfileValues(profile) {
81
+ return { ...RUNBOOK_DEFAULTS, ...(profile?.values || {}) };
82
+ }
83
+
84
+ function templateLine(input) {
85
+ const line = `${input.name}={${input.name}}`;
86
+ return input.comment ? [`# ${input.comment}`, line] : [line];
87
+ }
88
+
89
+ function agentSections() {
90
+ const written = new Set();
91
+ return AGENTS.flatMap((agent) => {
92
+ const inputs = agentInputs(agent).filter(
93
+ (input) =>
94
+ shown(input) &&
95
+ input.type !== 'secret' &&
96
+ input.template !== false &&
97
+ !written.has(input.name),
98
+ );
99
+ for (const input of inputs) written.add(input.name);
100
+ if (!inputs.length) return [];
101
+ return ['', `# ${agent.name}`, ...inputs.flatMap(templateLine)];
102
+ });
103
+ }
104
+
105
+ /** The new-profile file, with `{KEY}` where each value goes. */
106
+ export function profileTemplate() {
107
+ const keys = AGENTS.flatMap((agent) =>
108
+ agentInputs(agent).filter(
109
+ (input) => shown(input) && input.type === 'secret',
110
+ ),
111
+ );
112
+ return [
113
+ '# Subconscious coding-agent profile',
114
+ '# Generated by subc. Extra KEY=value lines are passed through to launches.',
115
+ '',
116
+ 'GATEWAY_URL={GATEWAY_URL}',
117
+ 'API_KEY={API_KEY}',
118
+ '# Available models (MODEL=UNSET uses the first live catalog model):',
119
+ ...SUPPORTED_MODELS.map((model) => `# ${model}`),
120
+ 'MODEL={MODEL}',
121
+ '',
122
+ '# Optional per-agent keys. Leave blank to use API_KEY above.',
123
+ ...keys.map((input) => `${input.name}={${input.name}}`),
124
+ ...agentSections(),
125
+ '',
126
+ '# Extra KEY=value lines are passed through to launches.',
127
+ '',
128
+ ].join('\n');
129
+ }