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
@@ -2,256 +2,277 @@
2
2
  import fs from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
- import { modelSupportsVision } from '../model-capabilities.js';
6
- import { buildOpenCodeConfig } from '../opencode-provider.js';
7
- import { claudeNativeLaunch } from './claude.js';
5
+ import { agentById, agentKeyName } from '../agent-data.js';
8
6
  import {
9
- defaults,
10
- modelIds,
11
- origin,
12
- parseOptions,
13
- positiveInteger,
14
- value,
15
- writeJson,
16
- } from './common.js';
7
+ launchPlan,
8
+ resolveAgentEnv,
9
+ resolveInputs,
10
+ takeFlags,
11
+ } from '../agent-launch.js';
12
+ import { claudePickerSettings } from '../claude-picker.js';
13
+ import { modelIds, positiveInteger } from '../launch-values.js';
14
+ import { modelSupportsVision } from '../model-capabilities.js';
15
+ import { defaults, origin, value, writeJson } from './common.js';
17
16
 
18
- function connection(env, keyName) {
17
+ const PLANNED = new Set(['claude-code', 'pi', 'opencode']);
18
+
19
+ // Windows trims a configured gateway to its origin and gives every agent but
20
+ // Claude the gateway and key as SUBCONSCIOUS_*. Claude has always been given
21
+ // the gateway as configured.
22
+ function windowsEnv(id, env) {
23
+ const model = value(env, 'MODEL', defaults.model);
24
+ const gateway = value(env, 'GATEWAY_URL', defaults.baseUrl);
25
+ if (id === 'claude-code')
26
+ return { ...env, MODEL: model, GATEWAY_URL: gateway };
27
+ const keyName = agentKeyName(agentById(id));
19
28
  return {
20
- model: value(env, 'MODEL', defaults.model),
21
- base: origin(value(env, 'GATEWAY_URL', defaults.baseUrl)),
22
- key: value(env, keyName, value(env, 'API_KEY')),
29
+ ...env,
30
+ MODEL: model,
31
+ GATEWAY_URL: origin(gateway),
32
+ SUBCONSCIOUS_API_KEY: value(env, keyName, value(env, 'API_KEY')),
33
+ SUBCONSCIOUS_GATEWAY_URL: origin(gateway),
23
34
  };
24
35
  }
25
36
 
26
- export async function windowsLaunch(
27
- id,
28
- argv,
29
- env,
30
- { tempRoot = os.tmpdir() } = {},
31
- ) {
32
- if (id === 'claude-code')
33
- return { command: 'claude', ...claudeNativeLaunch(argv, env) };
34
- const keyName = {
35
- codex: 'CODEX_API_KEY',
36
- opencode: 'OPENCODE_API_KEY',
37
- pi: 'PI_API_KEY',
38
- 'subconscious-code': 'SC_API_KEY',
39
- 'deepseek-harness': 'DEEPSEEK_HARNESS_API_KEY',
40
- }[id];
41
- const { model, base, key } = connection(env, keyName);
42
- const childEnv = {
43
- ...env,
44
- SUBCONSCIOUS_API_KEY: key,
45
- SUBCONSCIOUS_GATEWAY_URL: base,
37
+ function claudeLaunch(agent, argv, env) {
38
+ const settings =
39
+ value(env, 'SUBC_CLAUDE_SETTINGS') ||
40
+ JSON.stringify(claudePickerSettings([env.MODEL], env.MODEL));
41
+ const plan = launchPlan(agent, {
42
+ env: { ...env, SUBC_CLAUDE_SETTINGS: settings },
43
+ args: argv,
44
+ });
45
+ if (!plan.env.ANTHROPIC_BASE_URL || !plan.env.ANTHROPIC_AUTH_TOKEN) {
46
+ throw new Error(
47
+ 'GATEWAY_URL and API_KEY are required to launch Claude Code',
48
+ );
49
+ }
50
+ positiveInteger(plan.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW, 'compact-window');
51
+ positiveInteger(
52
+ plan.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS,
53
+ 'max-context-tokens',
54
+ );
55
+ return plan;
56
+ }
57
+
58
+ function plannedLaunch(agent, argv, env) {
59
+ const plan =
60
+ agent.id === 'claude-code'
61
+ ? claudeLaunch(agent, argv, env)
62
+ : launchPlan(agent, { env, args: argv });
63
+ return { command: plan.argv[0], args: plan.argv.slice(1), env: plan.env };
64
+ }
65
+
66
+ function codexCatalog(env, context, maxContext, compact) {
67
+ return {
68
+ models: modelIds(env).map((id) => ({
69
+ slug: id,
70
+ display_name: id,
71
+ description: `Subconscious model ${id}`,
72
+ context_window: context,
73
+ max_context_window: maxContext,
74
+ auto_compact_token_limit: compact,
75
+ effective_context_window_percent: 95,
76
+ supported_reasoning_levels: [],
77
+ shell_type: 'shell_command',
78
+ visibility: 'list',
79
+ supported_in_api: true,
80
+ priority: 0,
81
+ service_tiers: [
82
+ {
83
+ id: 'priority',
84
+ name: 'Priority',
85
+ description:
86
+ 'Route requests through the configured priority service tier',
87
+ },
88
+ ],
89
+ availability_nux: null,
90
+ upgrade: null,
91
+ base_instructions: 'You are Codex, a coding agent.',
92
+ supports_reasoning_summaries: false,
93
+ support_verbosity: false,
94
+ default_verbosity: null,
95
+ apply_patch_tool_type: 'freeform',
96
+ truncation_policy: { mode: 'tokens', limit: 10000 },
97
+ supports_parallel_tool_calls: true,
98
+ experimental_supported_tools: [],
99
+ // Codex assumes ['text', 'image'] when the field is missing.
100
+ input_modalities: modelSupportsVision(id) ? ['text', 'image'] : ['text'],
101
+ })),
46
102
  };
47
- if (id === 'subconscious-code') {
48
- return {
49
- command: 'marathon',
50
- args: argv,
51
- env: {
52
- ...childEnv,
53
- SC_API_KEY: key,
54
- SC_BASE_URL: `${base}/v1`,
55
- SC_DLR_URL: base,
56
- SC_DLR_ENABLED: 'true',
57
- SC_MODEL: model,
58
- },
59
- };
103
+ }
104
+
105
+ // --subagents is Windows-only: it runs a pinned older Codex instead.
106
+ function splitSubagents(argv) {
107
+ const boundary = argv.indexOf('--');
108
+ const head = boundary < 0 ? argv : argv.slice(0, boundary);
109
+ const tail = boundary < 0 ? [] : argv.slice(boundary);
110
+ for (const arg of head)
111
+ if (arg.startsWith('--subagents='))
112
+ throw new Error('--subagents does not take a value');
113
+ return {
114
+ subagents: head.includes('--subagents'),
115
+ words: [...head.filter((arg) => arg !== '--subagents'), ...tail],
116
+ };
117
+ }
118
+
119
+ function codexConfig(env, file) {
120
+ const config = {
121
+ model: env.MODEL,
122
+ model_provider: 'subconscious',
123
+ model_catalog_json: file,
124
+ model_reasoning_effort: env.CODEX_REASONING_EFFORT,
125
+ web_search: 'disabled',
126
+ 'model_providers.subconscious.name': 'Subconscious',
127
+ 'model_providers.subconscious.base_url': `${env.GATEWAY_URL}/v1`,
128
+ 'model_providers.subconscious.wire_api': 'responses',
129
+ 'model_providers.subconscious.env_key': 'SUBCONSCIOUS_API_KEY',
130
+ 'model_providers.subconscious.stream_idle_timeout_ms': Number(
131
+ env.CODEX_STREAM_IDLE_TIMEOUT_MS,
132
+ ),
133
+ };
134
+ if (env.CODEX_EXTERNAL_TOOLS !== 'true') {
135
+ Object.assign(config, {
136
+ 'features.apps': false,
137
+ 'features.plugins': false,
138
+ 'apps._default.enabled': false,
139
+ });
60
140
  }
61
- if (id === 'pi')
62
- return {
63
- command: 'pi',
64
- args: ['--provider', 'subconscious', '--model', model, ...argv],
65
- env: childEnv,
66
- };
67
- if (id === 'opencode') {
68
- const context = positiveInteger(
69
- value(env, 'OPENCODE_CONTEXT_LIMIT', '5000000'),
70
- 'OPENCODE_CONTEXT_LIMIT',
71
- );
72
- const output = positiveInteger(
73
- value(env, 'OPENCODE_OUTPUT_LIMIT', '65536'),
74
- 'OPENCODE_OUTPUT_LIMIT',
75
- );
76
- childEnv.OPENCODE_CONFIG_CONTENT = JSON.stringify(
77
- buildOpenCodeConfig({
78
- baseUrl: base,
79
- model,
80
- modelIds: modelIds(env),
81
- context,
82
- output,
83
- }),
84
- );
141
+ return config;
142
+ }
143
+
144
+ async function codexLaunch(agent, argv, env, tempRoot) {
145
+ const { subagents, words } = splitSubagents(argv);
146
+ // Windows Codex sets no subagent effort, and uses the subagent cap only
147
+ // with --subagents; otherwise those flags still reach Codex unchanged.
148
+ const { values, rest } = takeFlags(agent, words, {
149
+ skip: [
150
+ 'CODEX_SUBAGENT_REASONING_EFFORT',
151
+ ...(subagents ? [] : ['MAX_CONCURRENT_SUBAGENTS']),
152
+ ],
153
+ });
154
+ const resolved = resolveAgentEnv(agent, resolveInputs(agent, env, values));
155
+ const context = positiveInteger(
156
+ resolved.CODEX_CONTEXT_WINDOW,
157
+ 'context-window',
158
+ );
159
+ const maxContext = positiveInteger(
160
+ resolved.CODEX_MAX_CONTEXT_WINDOW,
161
+ 'max-context-window',
162
+ );
163
+ const compact = positiveInteger(
164
+ resolved.CODEX_AUTO_COMPACT_TOKEN_LIMIT,
165
+ 'auto-compact-token-limit',
166
+ );
167
+ const catalog = codexCatalog(resolved, context, maxContext, compact);
168
+ // Validate all flags before creating a temporary directory.
169
+ positiveInteger(resolved.CODEX_STREAM_IDLE_TIMEOUT_MS, 'stream-idle-timeout');
170
+ const threads = positiveInteger(
171
+ resolved.MAX_CONCURRENT_SUBAGENTS,
172
+ 'MAX_CONCURRENT_SUBAGENTS',
173
+ );
174
+ const dir = await fs.mkdtemp(path.join(tempRoot, 'subc-codex-'));
175
+ const cleanup = () => fs.rm(dir, { recursive: true, force: true });
176
+ try {
177
+ const file = path.join(dir, 'models.json');
178
+ await writeJson(file, catalog);
179
+ const config = codexConfig(resolved, file);
180
+ if (subagents)
181
+ Object.assign(config, {
182
+ 'features.multi_agent': true,
183
+ 'agents.max_threads': threads,
184
+ 'agents.max_depth': 1,
185
+ 'agents.interrupt_message': true,
186
+ });
187
+ const args = Object.entries(config).flatMap(([k, v]) => [
188
+ '-c',
189
+ `${k}=${JSON.stringify(v)}`,
190
+ ]);
85
191
  return {
86
- command: 'opencode',
87
- args: parseOptions(argv, {}).rest,
88
- env: childEnv,
192
+ command: subagents ? 'npx' : 'codex',
193
+ args: [
194
+ ...(subagents ? ['-y', '@openai/codex@0.132.0'] : []),
195
+ ...args,
196
+ ...rest,
197
+ ],
198
+ env: resolved,
199
+ cleanup,
89
200
  };
201
+ } catch (error) {
202
+ await cleanup();
203
+ throw error;
90
204
  }
91
- if (id === 'codex') {
92
- const { options: opts, rest } = parseOptions(argv, {
93
- '--context-window': 1,
94
- '--max-context-window': 1,
95
- '--auto-compact-token-limit': 1,
96
- '--reasoning-effort': 1,
97
- '--subagents': true,
98
- '--external-tools': true,
99
- });
100
- const context = positiveInteger(
101
- opts['--context-window'] || value(env, 'CODEX_CONTEXT_WINDOW', '5000000'),
102
- 'context-window',
103
- );
104
- const maxContext = positiveInteger(
105
- opts['--max-context-window'] ||
106
- value(env, 'CODEX_MAX_CONTEXT_WINDOW', String(context)),
107
- 'max-context-window',
108
- );
109
- const compact = positiveInteger(
110
- opts['--auto-compact-token-limit'] ||
111
- value(env, 'CODEX_AUTO_COMPACT_TOKEN_LIMIT', '4500000'),
112
- 'auto-compact-token-limit',
205
+ }
206
+
207
+ function dshOverlay(env, context, maxTokens) {
208
+ const models = modelIds(env)
209
+ .map(
210
+ (id) =>
211
+ ` - id: '${id}'\n name: '${id}'\n${modelSupportsVision(id) ? ' input: [text, image]\n' : ''} contextWindow: ${context}\n maxTokens: ${maxTokens}`,
212
+ )
213
+ .join('\n');
214
+ return `- id: llm-pi-ai\n config:\n providers:\n subconscious:\n apiKeyEnv: SUBCONSCIOUS_API_KEY\n displayName: Subconscious Gateway\n api: openai-completions\n baseURL: !!js process.env.SUBCONSCIOUS_DSH_BASE_URL\n headers:\n x-subconscious-client: deepseek-harness\n compat:\n supportsDeveloperRole: false\n maxTokensField: max_tokens\n defaultContextWindow: ${context}\n defaultMaxTokens: ${maxTokens}\n models:\n${models}\n- id: agent-default-model\n config:\n provider: subconscious\n model: '${env.MODEL}'\n`;
215
+ }
216
+
217
+ async function dshLaunch(agent, argv, env, tempRoot) {
218
+ const headless = argv[0] === 'headless';
219
+ const plan = launchPlan(agent, {
220
+ env,
221
+ args: headless ? argv.slice(2) : argv,
222
+ prompt: headless ? argv[1] : undefined,
223
+ });
224
+ const context = positiveInteger(
225
+ plan.env.DEEPSEEK_HARNESS_CONTEXT_WINDOW,
226
+ 'DEEPSEEK_HARNESS_CONTEXT_WINDOW',
227
+ );
228
+ const maxTokens = positiveInteger(
229
+ plan.env.DEEPSEEK_HARNESS_MAX_TOKENS,
230
+ 'DEEPSEEK_HARNESS_MAX_TOKENS',
231
+ );
232
+ const text = dshOverlay(plan.env, context, maxTokens);
233
+ const dir = await fs.mkdtemp(path.join(tempRoot, 'subc-dsh-'));
234
+ const cleanup = () => fs.rm(dir, { recursive: true, force: true });
235
+ try {
236
+ const file = path.join(dir, 'subconscious.cordis.yml');
237
+ await fs.writeFile(file, text, { mode: 0o600, flag: 'wx' });
238
+ const argvWithFile = plan.argv.map((word, index) =>
239
+ index < plan.templateWords ? word.replaceAll('{tempFile}', file) : word,
113
240
  );
114
- const reasoning =
115
- opts['--reasoning-effort'] || value(env, 'CODEX_REASONING_EFFORT', 'max');
116
- if (!['none', 'low', 'medium', 'high', 'max'].includes(reasoning))
117
- throw new Error(
118
- 'reasoning-effort must be none, low, medium, high, or max',
119
- );
120
- const catalog = {
121
- models: modelIds(env).map((id) => ({
122
- slug: id,
123
- display_name: id,
124
- description: `Subconscious model ${id}`,
125
- context_window: context,
126
- max_context_window: maxContext,
127
- auto_compact_token_limit: compact,
128
- effective_context_window_percent: 95,
129
- supported_reasoning_levels: [],
130
- shell_type: 'shell_command',
131
- visibility: 'list',
132
- supported_in_api: true,
133
- priority: 0,
134
- service_tiers: [
135
- {
136
- id: 'priority',
137
- name: 'Priority',
138
- description:
139
- 'Route requests through the configured priority service tier',
140
- },
141
- ],
142
- availability_nux: null,
143
- upgrade: null,
144
- base_instructions: 'You are Codex, a coding agent.',
145
- supports_reasoning_summaries: false,
146
- support_verbosity: false,
147
- default_verbosity: null,
148
- apply_patch_tool_type: 'freeform',
149
- truncation_policy: { mode: 'tokens', limit: 10000 },
150
- supports_parallel_tool_calls: true,
151
- experimental_supported_tools: [],
152
- ...(modelSupportsVision(id)
153
- ? { input_modalities: ['text', 'image'] }
154
- : {}),
155
- })),
241
+ return {
242
+ command: argvWithFile[0],
243
+ args: argvWithFile.slice(1),
244
+ env: plan.env,
245
+ ...(plan.input === undefined ? {} : { input: plan.input }),
246
+ cleanup,
156
247
  };
157
- // Validate all flags before creating a temporary directory.
158
- const threads = positiveInteger(
159
- value(env, 'MAX_CONCURRENT_SUBAGENTS', '4'),
160
- 'MAX_CONCURRENT_SUBAGENTS',
161
- );
162
- const dir = await fs.mkdtemp(path.join(tempRoot, 'subc-codex-'));
163
- const cleanup = () => fs.rm(dir, { recursive: true, force: true });
164
- try {
165
- const file = path.join(dir, 'models.json');
166
- await writeJson(file, catalog);
167
- const config = {
168
- model,
169
- model_provider: 'subconscious',
170
- model_catalog_json: file,
171
- model_reasoning_effort: reasoning,
172
- web_search: 'disabled',
173
- 'model_providers.subconscious.name': 'Subconscious',
174
- 'model_providers.subconscious.base_url': `${base}/v1`,
175
- 'model_providers.subconscious.wire_api': 'responses',
176
- 'model_providers.subconscious.env_key': 'SUBCONSCIOUS_API_KEY',
177
- 'model_providers.subconscious.stream_idle_timeout_ms': 300000,
178
- };
179
- if (
180
- !opts['--external-tools'] &&
181
- value(env, 'CODEX_EXTERNAL_TOOLS', 'false') !== 'true'
182
- ) {
183
- Object.assign(config, {
184
- 'features.apps': false,
185
- 'features.plugins': false,
186
- 'apps._default.enabled': false,
187
- });
188
- }
189
- if (opts['--subagents'])
190
- Object.assign(config, {
191
- 'features.multi_agent': true,
192
- 'agents.max_threads': threads,
193
- 'agents.max_depth': 1,
194
- 'agents.interrupt_message': true,
195
- });
196
- const args = Object.entries(config).flatMap(([k, v]) => [
197
- '-c',
198
- `${k}=${JSON.stringify(v)}`,
199
- ]);
200
- return {
201
- command: opts['--subagents'] ? 'npx' : 'codex',
202
- args: [
203
- ...(opts['--subagents'] ? ['-y', '@openai/codex@0.132.0'] : []),
204
- ...args,
205
- ...rest,
206
- ],
207
- env: childEnv,
208
- cleanup,
209
- };
210
- } catch (error) {
211
- await cleanup();
212
- throw error;
213
- }
214
- }
215
- if (id === 'deepseek-harness') {
216
- const context = positiveInteger(
217
- value(env, 'DEEPSEEK_HARNESS_CONTEXT_WINDOW', '5000000'),
218
- 'DEEPSEEK_HARNESS_CONTEXT_WINDOW',
219
- );
220
- const maxTokens = positiveInteger(
221
- value(env, 'DEEPSEEK_HARNESS_MAX_TOKENS', '65536'),
222
- 'DEEPSEEK_HARNESS_MAX_TOKENS',
223
- );
224
- const models = modelIds(env);
225
- const dir = await fs.mkdtemp(path.join(tempRoot, 'subc-dsh-'));
226
- const cleanup = () => fs.rm(dir, { recursive: true, force: true });
227
- try {
228
- const file = path.join(dir, 'subconscious.cordis.yml');
229
- const text = `- id: llm-pi-ai\n config:\n providers:\n subconscious:\n apiKeyEnv: SUBCONSCIOUS_API_KEY\n displayName: Subconscious Gateway\n api: openai-completions\n baseURL: !!js process.env.SUBCONSCIOUS_DSH_BASE_URL\n headers:\n x-subconscious-client: deepseek-harness\n compat:\n supportsDeveloperRole: false\n maxTokensField: max_tokens\n defaultContextWindow: ${context}\n defaultMaxTokens: ${maxTokens}\n models:\n${models.map((id) => ` - id: '${id}'\n name: '${id}'\n${modelSupportsVision(id) ? ' input: [text, image]\n' : ''} contextWindow: ${context}\n maxTokens: ${maxTokens}`).join('\n')}\n- id: agent-default-model\n config:\n provider: subconscious\n model: '${model}'\n`;
230
- await fs.writeFile(file, text, { mode: 0o600, flag: 'wx' });
231
- const mode = ['web', 'headless'].includes(argv[0]) ? argv[0] : 'web';
232
- const rest = ['web', 'headless'].includes(argv[0]) ? argv.slice(1) : argv;
233
- return {
234
- command: 'dsh',
235
- args: [
236
- ...(mode === 'web' ? ['web'] : ['--profile', 'headless']),
237
- '--patch',
238
- file,
239
- ...rest,
240
- ],
241
- env: { ...childEnv, SUBCONSCIOUS_DSH_BASE_URL: `${base}/v1` },
242
- cleanup,
243
- };
244
- } catch (error) {
245
- await cleanup();
246
- throw error;
247
- }
248
+ } catch (error) {
249
+ await cleanup();
250
+ throw error;
248
251
  }
252
+ }
253
+
254
+ export async function windowsLaunch(
255
+ id,
256
+ argv,
257
+ env,
258
+ { tempRoot = os.tmpdir() } = {},
259
+ ) {
260
+ const agent = agentById(id);
261
+ const launchEnv = windowsEnv(id, env);
262
+ if (PLANNED.has(id)) return plannedLaunch(agent, argv, launchEnv);
263
+ if (id === 'codex') return codexLaunch(agent, argv, launchEnv, tempRoot);
264
+ if (id === 'deepseek-harness')
265
+ return dshLaunch(agent, argv, launchEnv, tempRoot);
249
266
  throw new Error(`No native Windows launcher is available for ${id}`);
250
267
  }
251
268
 
252
269
  export async function executeWindowsLaunch(spec, run) {
253
270
  try {
254
- return await run(spec.command, spec.args, { env: spec.env });
271
+ const options =
272
+ spec.input === undefined
273
+ ? { env: spec.env }
274
+ : { env: spec.env, input: spec.input };
275
+ return await run(spec.command, spec.args, options);
255
276
  } finally {
256
277
  await spec.cleanup?.();
257
278
  }
@@ -207,8 +207,19 @@ export function spawnWindowsSync(command, args, options = {}) {
207
207
  }
208
208
 
209
209
  export function runWindows(command, args, options = {}) {
210
+ const { input, ...spawnOptions } = options;
210
211
  return new Promise((resolve, reject) => {
211
- const child = spawnWindows(command, args, { stdio: 'inherit', ...options });
212
+ const child = spawnWindows(command, args, {
213
+ stdio: input === undefined ? 'inherit' : ['pipe', 'inherit', 'inherit'],
214
+ ...spawnOptions,
215
+ });
216
+ if (input !== undefined) {
217
+ // EPIPE means the agent exited without reading; its exit code reports why.
218
+ child.stdin.on('error', (error) => {
219
+ if (error.code !== 'EPIPE') reject(error);
220
+ });
221
+ child.stdin.end(input);
222
+ }
212
223
  const forward = (signal) => {
213
224
  try {
214
225
  child.kill(signal);
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
+ import { agentById } from '../agent-data.js';
5
+ import { resolveInputs } from '../agent-launch.js';
4
6
  import { modelSupportsVision } from '../model-capabilities.js';
5
7
  import {
6
8
  defaults,
@@ -118,10 +120,16 @@ export async function windowsSetup(
118
120
  if (id === 'pi')
119
121
  Object.assign(schema, { '--context-window': 1, '--max-tokens': 1 });
120
122
  const { options } = parseOptions(argv, schema, { strict: true });
121
- const env = {
122
- ...environment,
123
- ...(options['--model'] ? { MODEL: options['--model'] } : {}),
124
- };
123
+ const agent = agentById(id);
124
+ const env = resolveInputs(
125
+ agent,
126
+ {
127
+ ...environment,
128
+ ...(options['--model'] ? { MODEL: options['--model'] } : {}),
129
+ },
130
+ {},
131
+ { strict: false },
132
+ );
125
133
  const gatewayUrl = origin(
126
134
  options['--gateway-url'] || value(env, 'GATEWAY_URL', defaults.baseUrl),
127
135
  );
@@ -179,12 +187,12 @@ export async function windowsSetup(
179
187
  if (installing) {
180
188
  const context = positiveInteger(
181
189
  options['--max-input-tokens'] ||
182
- value(env, 'COPILOT_MAX_INPUT_TOKENS', '5000000'),
190
+ value(env, 'COPILOT_MAX_INPUT_TOKENS'),
183
191
  'max-input-tokens',
184
192
  );
185
193
  const maxTokens = positiveInteger(
186
194
  options['--max-output-tokens'] ||
187
- value(env, 'COPILOT_MAX_OUTPUT_TOKENS', '65536'),
195
+ value(env, 'COPILOT_MAX_OUTPUT_TOKENS'),
188
196
  'max-output-tokens',
189
197
  );
190
198
  providers.push({
@@ -262,15 +270,20 @@ export async function windowsSetup(
262
270
  'extensions',
263
271
  'subconscious-compaction.ts',
264
272
  );
273
+ // Keeps requests at the newest 30 images; same files as the shell runbook.
274
+ const imageWindow = path.join(
275
+ dir,
276
+ 'extensions',
277
+ 'subconscious-image-window',
278
+ );
265
279
  const config = path.join(dir, 'subconscious-windows.json');
266
280
  if (installing) {
267
281
  const context = positiveInteger(
268
- options['--context-window'] ||
269
- value(env, 'PI_CONTEXT_WINDOW', '5000000'),
282
+ options['--context-window'] || value(env, 'PI_CONTEXT_WINDOW'),
270
283
  'context-window',
271
284
  );
272
285
  const maxTokens = positiveInteger(
273
- options['--max-tokens'] || value(env, 'PI_MAX_TOKENS', '65536'),
286
+ options['--max-tokens'] || value(env, 'PI_MAX_TOKENS'),
274
287
  'max-tokens',
275
288
  );
276
289
  providers.subconscious = {
@@ -294,10 +307,17 @@ export async function windowsSetup(
294
307
  new URL('./pi-compaction.ts', import.meta.url),
295
308
  extension,
296
309
  );
310
+ await fs.rm(imageWindow, { recursive: true, force: true });
311
+ await fs.cp(
312
+ new URL('../runbook/image-window', import.meta.url),
313
+ imageWindow,
314
+ { recursive: true },
315
+ );
297
316
  await writeJson(config, { gatewayUrl, apiKey });
298
317
  } else {
299
318
  delete providers.subconscious;
300
319
  await fs.rm(extension, { force: true });
320
+ await fs.rm(imageWindow, { recursive: true, force: true });
301
321
  await fs.rm(config, { force: true });
302
322
  }
303
323
  if (installing || (await exists(modelsFile)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subconscious-cli",
3
- "version": "6.0.1",
3
+ "version": "6.2.0",
4
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"
@@ -9,9 +9,8 @@
9
9
  "build:tui": "node scripts/build-tui.js",
10
10
  "build:tui:host": "node scripts/build-tui.js --host",
11
11
  "test": "node --test",
12
- "test:windows": "node --test test/windows.test.js test/models.test.js test/model-capabilities.test.js test/auth.test.js test/tui.test.js test/update-check.test.js test/sessions.test.js test/branding.test.js test/claude-version.test.js test/usage.test.js test/feedback.test.js test/release.test.js test/github-govuln-issue.test.js",
12
+ "test:windows": "node --test test/windows.test.js test/models.test.js test/model-capabilities.test.js test/auth.test.js test/tui.test.js test/update-check.test.js test/sessions.test.js test/branding.test.js test/claude-version.test.js test/usage.test.js test/feedback.test.js test/release.test.js test/github-govuln-issue.test.js test/harness-manifest.test.js test/agent-files.test.js test/agent-launch.test.js test/agent-install-policy.test.js",
13
13
  "test:tui": "go -C tui test ./...",
14
- "generate": "node scripts/generate-agents.js",
15
14
  "lint": "biome ci .",
16
15
  "format": "biome check --write .",
17
16
  "typecheck": "tsc --noEmit",
@@ -38,7 +37,6 @@
38
37
  "authentication",
39
38
  "profiles",
40
39
  "coding-agent",
41
- "subconscious-code",
42
40
  "claude-code",
43
41
  "opencode",
44
42
  "codex",