subconscious-cli 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -2
- package/bin/agent-data.js +242 -0
- package/bin/agent-help.js +106 -0
- package/bin/agent-install.js +354 -0
- package/bin/agent-launch.js +199 -0
- package/bin/agent-spawn.js +137 -0
- package/bin/agents.js +284 -840
- package/bin/auth.js +89 -33
- package/bin/branding.js +3 -1
- package/bin/claude-picker.js +143 -0
- package/bin/cli.js +87 -20
- package/bin/clipboard-image.js +24 -5
- package/bin/colors.js +1 -1
- package/bin/feedback.js +71 -21
- package/bin/harness-manifest.js +286 -0
- package/bin/headless-args.js +35 -0
- package/bin/launch-values.js +25 -0
- package/bin/model-capabilities.js +12 -8
- package/bin/models.js +35 -19
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/opencode-provider.js +48 -5
- package/bin/profile-settings.js +129 -0
- package/bin/profiles.js +136 -449
- package/bin/runbook/README.md +66 -23
- package/bin/runbook/claude-code/agent.json +300 -0
- package/bin/runbook/claude-code/install.sh +9 -252
- package/bin/runbook/codex/agent.json +461 -0
- package/bin/runbook/codex/hooks-lib.sh +17 -4
- package/bin/runbook/codex/install.sh +0 -4
- package/bin/runbook/codex/run.sh +16 -235
- package/bin/runbook/copilot/agent.json +185 -0
- package/bin/runbook/copilot/install.sh +45 -63
- package/bin/runbook/cursor/agent.json +137 -0
- package/bin/runbook/cursor/install.sh +8 -35
- package/bin/runbook/deepseek-harness/agent.json +163 -0
- package/bin/runbook/deepseek-harness/run.sh +20 -59
- package/bin/runbook/image-window/index.ts +14 -0
- package/bin/runbook/image-window/window.d.ts +19 -0
- package/bin/runbook/image-window/window.js +140 -0
- package/bin/runbook/lib.sh +41 -0
- package/bin/runbook/opencode/agent.json +158 -0
- package/bin/runbook/opencode/install.sh +9 -183
- package/bin/runbook/opencode/opencode-plugin.d.ts +16 -0
- package/bin/runbook/opencode/subconscious-image-window.ts +24 -0
- package/bin/runbook/pi/agent.json +216 -0
- package/bin/runbook/pi/install.sh +18 -44
- package/bin/runbook/pi/run.sh +6 -18
- package/bin/runbook/pi/subconscious-compaction.ts +71 -49
- package/bin/runbook/shared.json +58 -0
- package/bin/runbook/subconscious-code/agent.json +156 -0
- package/bin/sessions.js +173 -58
- package/bin/tui.js +104 -32
- package/bin/update-check.js +54 -21
- package/bin/upgrade.js +67 -43
- package/bin/usage.js +47 -24
- package/bin/windows/agents.js +141 -37
- package/bin/windows/common.js +36 -25
- package/bin/windows/install.js +116 -34
- package/bin/windows/launch.js +260 -77
- package/bin/windows/pi-compaction.ts +68 -13
- package/bin/windows/process.js +210 -57
- package/bin/windows/setup.js +251 -56
- package/package.json +9 -4
- package/bin/registry.generated.json +0 -406
- package/bin/runbook/claude-code/run.sh +0 -254
- package/bin/runbook/model-capabilities.generated.sh +0 -9
- package/bin/runbook/opencode/provider.sh +0 -93
- package/bin/runbook/opencode/run.sh +0 -99
- package/bin/runbook/opencode/subconscious-compaction.ts +0 -100
- package/bin/runbook/subconscious-code/run.sh +0 -21
- package/bin/windows/claude.js +0 -71
package/bin/profiles.js
CHANGED
|
@@ -6,24 +6,29 @@
|
|
|
6
6
|
* ~/.subconscious/profiles/<name>.env
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { windowsEditor, runWindows } from './windows/process.js';
|
|
9
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
11
10
|
import fs from 'node:fs/promises';
|
|
12
11
|
import os from 'node:os';
|
|
13
12
|
import path from 'node:path';
|
|
14
|
-
import { readFileSync } from 'node:fs';
|
|
15
13
|
import { c } from './colors.js';
|
|
16
14
|
import { PUBLIC_CATALOG_FALLBACK_MESSAGE } from './models.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
import {
|
|
16
|
+
profileTemplate,
|
|
17
|
+
RUNBOOK_DEFAULTS,
|
|
18
|
+
SUPPORTED_MODELS,
|
|
19
|
+
} from './profile-settings.js';
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
PROFILE_SETTING_GROUPS,
|
|
23
|
+
profileSettingsForAgent,
|
|
24
|
+
RUNBOOK_DEFAULTS,
|
|
25
|
+
resolvedProfileValues,
|
|
26
|
+
SUPPORTED_MODELS,
|
|
27
|
+
} from './profile-settings.js';
|
|
28
|
+
|
|
29
|
+
import { runWindows, windowsEditor } from './windows/process.js';
|
|
21
30
|
|
|
22
31
|
export const DEFAULT_PROFILE = 'default';
|
|
23
|
-
export const SUPPORTED_MODELS =
|
|
24
|
-
Array.isArray(registry.defaults.models) && registry.defaults.models.length
|
|
25
|
-
? registry.defaults.models
|
|
26
|
-
: [registry.defaults.model];
|
|
27
32
|
const PREVIOUS_DEFAULT_GATEWAYS = new Set([
|
|
28
33
|
'https://api.subconscious.dev',
|
|
29
34
|
'https://api.subconscious.dev/',
|
|
@@ -35,407 +40,11 @@ const LEGACY_PROFILES_DIR = CONFIG_OVERRIDE
|
|
|
35
40
|
? null
|
|
36
41
|
: path.join(os.homedir(), '.subcon', 'profiles');
|
|
37
42
|
|
|
38
|
-
export const RUNBOOK_DEFAULTS = {
|
|
39
|
-
GATEWAY_URL: registry.defaults.baseUrl,
|
|
40
|
-
API_KEY: '',
|
|
41
|
-
MODEL: registry.defaults.model,
|
|
42
|
-
SC_API_KEY: '',
|
|
43
|
-
CLAUDE_CODE_SUBAGENT_MODEL: '',
|
|
44
|
-
CLAUDE_GATEWAY_URL: '',
|
|
45
|
-
CLAUDE_CODE_API_KEY: '',
|
|
46
|
-
CODEX_API_KEY: '',
|
|
47
|
-
OPENCODE_API_KEY: '',
|
|
48
|
-
CURSOR_API_KEY: '',
|
|
49
|
-
COPILOT_API_KEY: '',
|
|
50
|
-
PI_API_KEY: '',
|
|
51
|
-
DEEPSEEK_HARNESS_API_KEY: '',
|
|
52
|
-
CLAUDE_CODE_AUTO_COMPACT_WINDOW: '1000000',
|
|
53
|
-
CLAUDE_CODE_MAX_CONTEXT_TOKENS: '3000000',
|
|
54
|
-
MAX_CONCURRENT_SUBAGENTS: '4',
|
|
55
|
-
MAX_SUBAGENT_SPAWN_DEPTH: '1',
|
|
56
|
-
CODEX_CONTEXT_WINDOW: '5000000',
|
|
57
|
-
CODEX_MAX_CONTEXT_WINDOW: '5000000',
|
|
58
|
-
CODEX_AUTO_COMPACT_TOKEN_LIMIT: '4500000',
|
|
59
|
-
CODEX_REASONING_EFFORT: 'max',
|
|
60
|
-
CODEX_EXTERNAL_TOOLS: 'false',
|
|
61
|
-
OPENCODE_CONTEXT_LIMIT: '5000000',
|
|
62
|
-
OPENCODE_OUTPUT_LIMIT: '65536',
|
|
63
|
-
PI_CONTEXT_WINDOW: '5000000',
|
|
64
|
-
PI_MAX_TOKENS: '65536',
|
|
65
|
-
DEEPSEEK_HARNESS_CONTEXT_WINDOW: '5000000',
|
|
66
|
-
DEEPSEEK_HARNESS_MAX_TOKENS: '65536',
|
|
67
|
-
COPILOT_MAX_INPUT_TOKENS: '5000000',
|
|
68
|
-
COPILOT_MAX_OUTPUT_TOKENS: '65536',
|
|
69
|
-
VSCODE_APP: '',
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const SETTINGS = {
|
|
73
|
-
GATEWAY_URL: {
|
|
74
|
-
key: 'GATEWAY_URL',
|
|
75
|
-
label: 'Gateway URL',
|
|
76
|
-
description: 'Subconscious gateway origin shared by every integration.',
|
|
77
|
-
type: 'url',
|
|
78
|
-
required: true,
|
|
79
|
-
},
|
|
80
|
-
PLATFORM_URL: {
|
|
81
|
-
key: 'PLATFORM_URL',
|
|
82
|
-
label: 'Platform URL',
|
|
83
|
-
description: 'Subconscious platform origin for login, whoami, and usage.',
|
|
84
|
-
type: 'url',
|
|
85
|
-
},
|
|
86
|
-
API_KEY: {
|
|
87
|
-
key: 'API_KEY',
|
|
88
|
-
label: 'Shared API key',
|
|
89
|
-
description: 'Default credential used when an agent-specific key is blank.',
|
|
90
|
-
type: 'secret',
|
|
91
|
-
},
|
|
92
|
-
MODEL: {
|
|
93
|
-
key: 'MODEL',
|
|
94
|
-
label: 'Default model',
|
|
95
|
-
description:
|
|
96
|
-
'Initial model used by coding-agent launches and setup. UNSET uses the first live catalog model.',
|
|
97
|
-
type: 'choice',
|
|
98
|
-
choices: SUPPORTED_MODELS,
|
|
99
|
-
},
|
|
100
|
-
SC_API_KEY: {
|
|
101
|
-
key: 'SC_API_KEY',
|
|
102
|
-
label: 'Subconscious Code API key',
|
|
103
|
-
description: 'Subconscious Code credential override; blank uses API_KEY.',
|
|
104
|
-
type: 'secret',
|
|
105
|
-
},
|
|
106
|
-
CLAUDE_CODE_SUBAGENT_MODEL: {
|
|
107
|
-
key: 'CLAUDE_CODE_SUBAGENT_MODEL',
|
|
108
|
-
label: 'Subagent model',
|
|
109
|
-
description: 'Model used by Claude Code subagents. UNSET follows the default model.',
|
|
110
|
-
type: 'choice',
|
|
111
|
-
choices: SUPPORTED_MODELS,
|
|
112
|
-
},
|
|
113
|
-
CLAUDE_GATEWAY_URL: {
|
|
114
|
-
key: 'CLAUDE_GATEWAY_URL',
|
|
115
|
-
label: 'Claude gateway override',
|
|
116
|
-
description: 'Optional Claude-only gateway origin; blank uses GATEWAY_URL.',
|
|
117
|
-
type: 'url',
|
|
118
|
-
},
|
|
119
|
-
CLAUDE_CODE_API_KEY: {
|
|
120
|
-
key: 'CLAUDE_CODE_API_KEY',
|
|
121
|
-
label: 'Claude API key',
|
|
122
|
-
description: 'Claude-specific credential override; blank uses API_KEY.',
|
|
123
|
-
type: 'secret',
|
|
124
|
-
},
|
|
125
|
-
CLAUDE_CODE_AUTO_COMPACT_WINDOW: {
|
|
126
|
-
key: 'CLAUDE_CODE_AUTO_COMPACT_WINDOW',
|
|
127
|
-
label: 'Auto-compact window',
|
|
128
|
-
description: 'Claude token window before compaction (100000–1000000).',
|
|
129
|
-
type: 'integer',
|
|
130
|
-
min: 100000,
|
|
131
|
-
max: 1000000,
|
|
132
|
-
},
|
|
133
|
-
CLAUDE_CODE_MAX_CONTEXT_TOKENS: {
|
|
134
|
-
key: 'CLAUDE_CODE_MAX_CONTEXT_TOKENS',
|
|
135
|
-
label: 'Maximum context tokens',
|
|
136
|
-
description: 'Maximum context advertised to Claude Code.',
|
|
137
|
-
type: 'integer',
|
|
138
|
-
min: 1,
|
|
139
|
-
},
|
|
140
|
-
MAX_CONCURRENT_SUBAGENTS: {
|
|
141
|
-
key: 'MAX_CONCURRENT_SUBAGENTS',
|
|
142
|
-
label: 'Concurrent subagents',
|
|
143
|
-
description: 'Maximum concurrent Claude/Codex subagent threads.',
|
|
144
|
-
type: 'integer',
|
|
145
|
-
min: 1,
|
|
146
|
-
},
|
|
147
|
-
CODEX_SUBAGENT_REASONING_EFFORT: {
|
|
148
|
-
key: 'CODEX_SUBAGENT_REASONING_EFFORT',
|
|
149
|
-
label: 'Subagent reasoning effort',
|
|
150
|
-
description:
|
|
151
|
-
'Effort for Codex subagents, set separately from the parent: a marathon model at max thinks for minutes without emitting a token, and a subagent turn is one uninterrupted think, so inheriting max makes every delegated subtask look like a hang.',
|
|
152
|
-
type: 'text',
|
|
153
|
-
},
|
|
154
|
-
CODEX_MULTI_AGENT_VERSION: {
|
|
155
|
-
key: 'CODEX_MULTI_AGENT_VERSION',
|
|
156
|
-
label: 'Codex multi-agent version',
|
|
157
|
-
description:
|
|
158
|
-
'Which subagent implementation Codex offers: v2 gives the collaboration tools. Leave empty to turn subagents off entirely.',
|
|
159
|
-
type: 'text',
|
|
160
|
-
},
|
|
161
|
-
CODEX_STREAM_IDLE_TIMEOUT_MS: {
|
|
162
|
-
key: 'CODEX_STREAM_IDLE_TIMEOUT_MS',
|
|
163
|
-
label: 'Stream idle timeout (ms)',
|
|
164
|
-
description:
|
|
165
|
-
'How long Codex waits on a silent stream before giving up. Needs to exceed the longest think the model will take.',
|
|
166
|
-
type: 'integer',
|
|
167
|
-
min: 1,
|
|
168
|
-
},
|
|
169
|
-
MAX_SUBAGENT_SPAWN_DEPTH: {
|
|
170
|
-
key: 'MAX_SUBAGENT_SPAWN_DEPTH',
|
|
171
|
-
label: 'Subagent spawn depth',
|
|
172
|
-
description: 'Maximum Claude subagent nesting depth.',
|
|
173
|
-
type: 'integer',
|
|
174
|
-
min: 0,
|
|
175
|
-
},
|
|
176
|
-
CODEX_API_KEY: {
|
|
177
|
-
key: 'CODEX_API_KEY',
|
|
178
|
-
label: 'Codex API key',
|
|
179
|
-
description: 'Codex-specific credential override; blank uses API_KEY.',
|
|
180
|
-
type: 'secret',
|
|
181
|
-
},
|
|
182
|
-
CODEX_CONTEXT_WINDOW: {
|
|
183
|
-
key: 'CODEX_CONTEXT_WINDOW',
|
|
184
|
-
label: 'Context window',
|
|
185
|
-
description: 'Context window written into the Codex model catalog.',
|
|
186
|
-
type: 'integer',
|
|
187
|
-
min: 1,
|
|
188
|
-
},
|
|
189
|
-
CODEX_MAX_CONTEXT_WINDOW: {
|
|
190
|
-
key: 'CODEX_MAX_CONTEXT_WINDOW',
|
|
191
|
-
label: 'Maximum context window',
|
|
192
|
-
description: 'Maximum context window written into the Codex catalog.',
|
|
193
|
-
type: 'integer',
|
|
194
|
-
min: 1,
|
|
195
|
-
},
|
|
196
|
-
CODEX_AUTO_COMPACT_TOKEN_LIMIT: {
|
|
197
|
-
key: 'CODEX_AUTO_COMPACT_TOKEN_LIMIT',
|
|
198
|
-
label: 'Auto-compact token limit',
|
|
199
|
-
description: 'Codex token threshold that triggers automatic compaction.',
|
|
200
|
-
type: 'integer',
|
|
201
|
-
min: 1,
|
|
202
|
-
},
|
|
203
|
-
CODEX_REASONING_EFFORT: {
|
|
204
|
-
key: 'CODEX_REASONING_EFFORT',
|
|
205
|
-
label: 'Reasoning effort',
|
|
206
|
-
description: 'Default Codex reasoning effort.',
|
|
207
|
-
type: 'choice',
|
|
208
|
-
choices: ['none', 'low', 'medium', 'high', 'max'],
|
|
209
|
-
},
|
|
210
|
-
CODEX_EXTERNAL_TOOLS: {
|
|
211
|
-
key: 'CODEX_EXTERNAL_TOOLS',
|
|
212
|
-
label: 'External tools',
|
|
213
|
-
description: 'Enable Codex apps/plugins; may exceed the gateway tool limit.',
|
|
214
|
-
type: 'choice',
|
|
215
|
-
choices: ['false', 'true'],
|
|
216
|
-
},
|
|
217
|
-
OPENCODE_API_KEY: {
|
|
218
|
-
key: 'OPENCODE_API_KEY',
|
|
219
|
-
label: 'OpenCode API key',
|
|
220
|
-
description: 'OpenCode-specific credential override; blank uses API_KEY.',
|
|
221
|
-
type: 'secret',
|
|
222
|
-
},
|
|
223
|
-
OPENCODE_CONTEXT_LIMIT: {
|
|
224
|
-
key: 'OPENCODE_CONTEXT_LIMIT',
|
|
225
|
-
label: 'Context limit',
|
|
226
|
-
description: 'OpenCode provider context limit used for compaction.',
|
|
227
|
-
type: 'integer',
|
|
228
|
-
min: 1,
|
|
229
|
-
},
|
|
230
|
-
OPENCODE_OUTPUT_LIMIT: {
|
|
231
|
-
key: 'OPENCODE_OUTPUT_LIMIT',
|
|
232
|
-
label: 'Output limit',
|
|
233
|
-
description: 'Maximum output tokens advertised to OpenCode.',
|
|
234
|
-
type: 'integer',
|
|
235
|
-
min: 1,
|
|
236
|
-
},
|
|
237
|
-
CURSOR_API_KEY: {
|
|
238
|
-
key: 'CURSOR_API_KEY',
|
|
239
|
-
label: 'Cursor hook API key',
|
|
240
|
-
description: 'Cursor hook credential override; blank uses API_KEY.',
|
|
241
|
-
type: 'secret',
|
|
242
|
-
},
|
|
243
|
-
COPILOT_API_KEY: {
|
|
244
|
-
key: 'COPILOT_API_KEY',
|
|
245
|
-
label: 'Copilot hook API key',
|
|
246
|
-
description: 'Copilot hook credential override; blank uses API_KEY.',
|
|
247
|
-
type: 'secret',
|
|
248
|
-
},
|
|
249
|
-
COPILOT_MAX_INPUT_TOKENS: {
|
|
250
|
-
key: 'COPILOT_MAX_INPUT_TOKENS',
|
|
251
|
-
label: 'Maximum input tokens',
|
|
252
|
-
description: 'Input-token capacity advertised to VS Code.',
|
|
253
|
-
type: 'integer',
|
|
254
|
-
min: 1,
|
|
255
|
-
},
|
|
256
|
-
COPILOT_MAX_OUTPUT_TOKENS: {
|
|
257
|
-
key: 'COPILOT_MAX_OUTPUT_TOKENS',
|
|
258
|
-
label: 'Maximum output tokens',
|
|
259
|
-
description: 'Output-token capacity advertised to VS Code.',
|
|
260
|
-
type: 'integer',
|
|
261
|
-
min: 1,
|
|
262
|
-
},
|
|
263
|
-
VSCODE_APP: {
|
|
264
|
-
key: 'VSCODE_APP',
|
|
265
|
-
label: 'VS Code application',
|
|
266
|
-
description: 'Optional application override; blank enables auto-detection.',
|
|
267
|
-
type: 'choice',
|
|
268
|
-
choices: ['', 'Code', 'Code - Insiders', 'VSCodium'],
|
|
269
|
-
},
|
|
270
|
-
PI_API_KEY: {
|
|
271
|
-
key: 'PI_API_KEY',
|
|
272
|
-
label: 'Pi API key',
|
|
273
|
-
description: 'Pi-specific credential override; blank uses API_KEY.',
|
|
274
|
-
type: 'secret',
|
|
275
|
-
},
|
|
276
|
-
PI_CONTEXT_WINDOW: {
|
|
277
|
-
key: 'PI_CONTEXT_WINDOW',
|
|
278
|
-
label: 'Context window',
|
|
279
|
-
description: 'Pi context window used to determine compaction.',
|
|
280
|
-
type: 'integer',
|
|
281
|
-
min: 1,
|
|
282
|
-
},
|
|
283
|
-
PI_MAX_TOKENS: {
|
|
284
|
-
key: 'PI_MAX_TOKENS',
|
|
285
|
-
label: 'Maximum output tokens',
|
|
286
|
-
description: 'Maximum output tokens advertised to Pi.',
|
|
287
|
-
type: 'integer',
|
|
288
|
-
min: 1,
|
|
289
|
-
},
|
|
290
|
-
DEEPSEEK_HARNESS_API_KEY: {
|
|
291
|
-
key: 'DEEPSEEK_HARNESS_API_KEY',
|
|
292
|
-
label: 'DeepSeek Harness API key',
|
|
293
|
-
description: 'DeepSeek Harness credential override; blank uses API_KEY.',
|
|
294
|
-
type: 'secret',
|
|
295
|
-
},
|
|
296
|
-
DEEPSEEK_HARNESS_CONTEXT_WINDOW: {
|
|
297
|
-
key: 'DEEPSEEK_HARNESS_CONTEXT_WINDOW',
|
|
298
|
-
label: 'Context window',
|
|
299
|
-
description: 'Context capacity advertised to DeepSeek Harness.',
|
|
300
|
-
type: 'integer',
|
|
301
|
-
min: 1,
|
|
302
|
-
},
|
|
303
|
-
DEEPSEEK_HARNESS_MAX_TOKENS: {
|
|
304
|
-
key: 'DEEPSEEK_HARNESS_MAX_TOKENS',
|
|
305
|
-
label: 'Maximum output tokens',
|
|
306
|
-
description: 'Output capacity advertised to DeepSeek Harness.',
|
|
307
|
-
type: 'integer',
|
|
308
|
-
min: 1,
|
|
309
|
-
},
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const AGENT_SETTING_KEYS = {
|
|
313
|
-
'subconscious-code': ['SC_API_KEY'],
|
|
314
|
-
'claude-code': [
|
|
315
|
-
'CLAUDE_GATEWAY_URL',
|
|
316
|
-
'CLAUDE_CODE_API_KEY',
|
|
317
|
-
'CLAUDE_CODE_SUBAGENT_MODEL',
|
|
318
|
-
'CLAUDE_CODE_AUTO_COMPACT_WINDOW',
|
|
319
|
-
'CLAUDE_CODE_MAX_CONTEXT_TOKENS',
|
|
320
|
-
'MAX_CONCURRENT_SUBAGENTS',
|
|
321
|
-
'MAX_SUBAGENT_SPAWN_DEPTH',
|
|
322
|
-
],
|
|
323
|
-
codex: [
|
|
324
|
-
'CODEX_API_KEY',
|
|
325
|
-
'CODEX_CONTEXT_WINDOW',
|
|
326
|
-
'CODEX_MAX_CONTEXT_WINDOW',
|
|
327
|
-
'CODEX_AUTO_COMPACT_TOKEN_LIMIT',
|
|
328
|
-
'CODEX_REASONING_EFFORT',
|
|
329
|
-
'CODEX_EXTERNAL_TOOLS',
|
|
330
|
-
'CODEX_STREAM_IDLE_TIMEOUT_MS',
|
|
331
|
-
'CODEX_MULTI_AGENT_VERSION',
|
|
332
|
-
'CODEX_SUBAGENT_REASONING_EFFORT',
|
|
333
|
-
'MAX_CONCURRENT_SUBAGENTS',
|
|
334
|
-
],
|
|
335
|
-
opencode: ['OPENCODE_API_KEY', 'OPENCODE_CONTEXT_LIMIT', 'OPENCODE_OUTPUT_LIMIT'],
|
|
336
|
-
cursor: ['CURSOR_API_KEY'],
|
|
337
|
-
copilot: [
|
|
338
|
-
'COPILOT_API_KEY',
|
|
339
|
-
'COPILOT_MAX_INPUT_TOKENS',
|
|
340
|
-
'COPILOT_MAX_OUTPUT_TOKENS',
|
|
341
|
-
'VSCODE_APP',
|
|
342
|
-
],
|
|
343
|
-
pi: ['PI_API_KEY', 'PI_CONTEXT_WINDOW', 'PI_MAX_TOKENS'],
|
|
344
|
-
'deepseek-harness': [
|
|
345
|
-
'DEEPSEEK_HARNESS_API_KEY',
|
|
346
|
-
'DEEPSEEK_HARNESS_CONTEXT_WINDOW',
|
|
347
|
-
'DEEPSEEK_HARNESS_MAX_TOKENS',
|
|
348
|
-
],
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
const GROUP_KEYS = [
|
|
352
|
-
{ id: 'shared', label: 'Shared settings', keys: ['GATEWAY_URL', 'API_KEY', 'MODEL'] },
|
|
353
|
-
...registry.agents
|
|
354
|
-
.filter((agent) => agent.cli !== false)
|
|
355
|
-
.map((agent) => ({
|
|
356
|
-
id: agent.id,
|
|
357
|
-
label: agent.name,
|
|
358
|
-
keys: AGENT_SETTING_KEYS[agent.id] || [],
|
|
359
|
-
})),
|
|
360
|
-
];
|
|
361
|
-
|
|
362
|
-
export const PROFILE_SETTING_GROUPS = GROUP_KEYS.map((group) => ({
|
|
363
|
-
...group,
|
|
364
|
-
settings: group.keys.map((key) => SETTINGS[key]),
|
|
365
|
-
}));
|
|
366
|
-
|
|
367
|
-
export function profileSettingsForAgent(agentId) {
|
|
368
|
-
const shared = PROFILE_SETTING_GROUPS.find((group) => group.id === 'shared');
|
|
369
|
-
const agent = PROFILE_SETTING_GROUPS.find((group) => group.id === agentId);
|
|
370
|
-
return [...(shared?.settings || []), ...(agent?.settings || [])];
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export function resolvedProfileValues(profile) {
|
|
374
|
-
return { ...RUNBOOK_DEFAULTS, ...(profile?.values || {}) };
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function profileTemplate() {
|
|
378
|
-
const modelComments = SUPPORTED_MODELS.map((model) => `# ${model}`).join('\n');
|
|
379
|
-
return `# Subconscious coding-agent profile
|
|
380
|
-
# Generated by subc. Extra KEY=value lines are passed through to launches.
|
|
381
|
-
|
|
382
|
-
GATEWAY_URL={GATEWAY_URL}
|
|
383
|
-
API_KEY={API_KEY}
|
|
384
|
-
# Available models (MODEL=UNSET uses the first live catalog model):
|
|
385
|
-
${modelComments}
|
|
386
|
-
MODEL={MODEL}
|
|
387
|
-
|
|
388
|
-
# Optional per-agent keys. Leave blank to use API_KEY above.
|
|
389
|
-
SC_API_KEY={SC_API_KEY}
|
|
390
|
-
CLAUDE_CODE_API_KEY={CLAUDE_CODE_API_KEY}
|
|
391
|
-
CODEX_API_KEY={CODEX_API_KEY}
|
|
392
|
-
OPENCODE_API_KEY={OPENCODE_API_KEY}
|
|
393
|
-
CURSOR_API_KEY={CURSOR_API_KEY}
|
|
394
|
-
COPILOT_API_KEY={COPILOT_API_KEY}
|
|
395
|
-
PI_API_KEY={PI_API_KEY}
|
|
396
|
-
DEEPSEEK_HARNESS_API_KEY={DEEPSEEK_HARNESS_API_KEY}
|
|
397
|
-
|
|
398
|
-
# Claude Code
|
|
399
|
-
# Leave CLAUDE_GATEWAY_URL blank to use GATEWAY_URL above.
|
|
400
|
-
CLAUDE_GATEWAY_URL={CLAUDE_GATEWAY_URL}
|
|
401
|
-
# CLAUDE_CODE_SUBAGENT_MODEL=UNSET follows MODEL above.
|
|
402
|
-
CLAUDE_CODE_SUBAGENT_MODEL={CLAUDE_CODE_SUBAGENT_MODEL}
|
|
403
|
-
CLAUDE_CODE_AUTO_COMPACT_WINDOW={CLAUDE_CODE_AUTO_COMPACT_WINDOW}
|
|
404
|
-
CLAUDE_CODE_MAX_CONTEXT_TOKENS={CLAUDE_CODE_MAX_CONTEXT_TOKENS}
|
|
405
|
-
MAX_CONCURRENT_SUBAGENTS={MAX_CONCURRENT_SUBAGENTS}
|
|
406
|
-
MAX_SUBAGENT_SPAWN_DEPTH={MAX_SUBAGENT_SPAWN_DEPTH}
|
|
407
|
-
|
|
408
|
-
# Codex
|
|
409
|
-
CODEX_CONTEXT_WINDOW={CODEX_CONTEXT_WINDOW}
|
|
410
|
-
CODEX_MAX_CONTEXT_WINDOW={CODEX_MAX_CONTEXT_WINDOW}
|
|
411
|
-
CODEX_AUTO_COMPACT_TOKEN_LIMIT={CODEX_AUTO_COMPACT_TOKEN_LIMIT}
|
|
412
|
-
CODEX_REASONING_EFFORT={CODEX_REASONING_EFFORT}
|
|
413
|
-
CODEX_EXTERNAL_TOOLS={CODEX_EXTERNAL_TOOLS}
|
|
414
|
-
|
|
415
|
-
# OpenCode
|
|
416
|
-
OPENCODE_CONTEXT_LIMIT={OPENCODE_CONTEXT_LIMIT}
|
|
417
|
-
OPENCODE_OUTPUT_LIMIT={OPENCODE_OUTPUT_LIMIT}
|
|
418
|
-
|
|
419
|
-
# Pi
|
|
420
|
-
PI_CONTEXT_WINDOW={PI_CONTEXT_WINDOW}
|
|
421
|
-
PI_MAX_TOKENS={PI_MAX_TOKENS}
|
|
422
|
-
|
|
423
|
-
# DeepSeek Harness
|
|
424
|
-
DEEPSEEK_HARNESS_CONTEXT_WINDOW={DEEPSEEK_HARNESS_CONTEXT_WINDOW}
|
|
425
|
-
DEEPSEEK_HARNESS_MAX_TOKENS={DEEPSEEK_HARNESS_MAX_TOKENS}
|
|
426
|
-
|
|
427
|
-
# GitHub Copilot
|
|
428
|
-
COPILOT_MAX_INPUT_TOKENS={COPILOT_MAX_INPUT_TOKENS}
|
|
429
|
-
COPILOT_MAX_OUTPUT_TOKENS={COPILOT_MAX_OUTPUT_TOKENS}
|
|
430
|
-
VSCODE_APP={VSCODE_APP}
|
|
431
|
-
|
|
432
|
-
# Extra KEY=value lines are passed through to launches.
|
|
433
|
-
`;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
43
|
export function validateProfileName(name) {
|
|
437
44
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name)) {
|
|
438
|
-
throw new Error(
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Invalid profile name '${name}' (use letters, digits, _ or -)`,
|
|
47
|
+
);
|
|
439
48
|
}
|
|
440
49
|
return name;
|
|
441
50
|
}
|
|
@@ -462,7 +71,9 @@ export function parseProfile(text) {
|
|
|
462
71
|
for (const line of text.split('\n')) {
|
|
463
72
|
const trimmed = line.trim();
|
|
464
73
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
465
|
-
const match = trimmed.match(
|
|
74
|
+
const match = trimmed.match(
|
|
75
|
+
/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/,
|
|
76
|
+
);
|
|
466
77
|
if (!match) continue;
|
|
467
78
|
values[match[1]] = decodeValue(match[2]);
|
|
468
79
|
}
|
|
@@ -478,7 +89,9 @@ function encodeValue(value) {
|
|
|
478
89
|
}
|
|
479
90
|
|
|
480
91
|
function renderTemplate(values) {
|
|
481
|
-
return profileTemplate().replace(/\{([A-Z0-9_]+)\}/g, (_, key) =>
|
|
92
|
+
return profileTemplate().replace(/\{([A-Z0-9_]+)\}/g, (_, key) =>
|
|
93
|
+
encodeValue(values[key]),
|
|
94
|
+
);
|
|
482
95
|
}
|
|
483
96
|
|
|
484
97
|
function upsertValues(text, updates) {
|
|
@@ -491,7 +104,8 @@ function upsertValues(text, updates) {
|
|
|
491
104
|
remaining.delete(match[2]);
|
|
492
105
|
return `${match[1]}${match[2]}=${encodeValue(value)}`;
|
|
493
106
|
});
|
|
494
|
-
for (const [key, value] of remaining)
|
|
107
|
+
for (const [key, value] of remaining)
|
|
108
|
+
output.push(`${key}=${encodeValue(value)}`);
|
|
495
109
|
return `${output.join('\n')}\n`;
|
|
496
110
|
}
|
|
497
111
|
|
|
@@ -506,10 +120,12 @@ function migrateCopilotTokenDefaults(text) {
|
|
|
506
120
|
const values = parseProfile(text);
|
|
507
121
|
const updates = {};
|
|
508
122
|
if (values.COPILOT_MAX_INPUT_TOKENS === '12288') {
|
|
509
|
-
updates.COPILOT_MAX_INPUT_TOKENS =
|
|
123
|
+
updates.COPILOT_MAX_INPUT_TOKENS =
|
|
124
|
+
RUNBOOK_DEFAULTS.COPILOT_MAX_INPUT_TOKENS;
|
|
510
125
|
}
|
|
511
126
|
if (values.COPILOT_MAX_OUTPUT_TOKENS === '4096') {
|
|
512
|
-
updates.COPILOT_MAX_OUTPUT_TOKENS =
|
|
127
|
+
updates.COPILOT_MAX_OUTPUT_TOKENS =
|
|
128
|
+
RUNBOOK_DEFAULTS.COPILOT_MAX_OUTPUT_TOKENS;
|
|
513
129
|
}
|
|
514
130
|
return Object.keys(updates).length ? upsertValues(text, updates) : text;
|
|
515
131
|
}
|
|
@@ -530,7 +146,12 @@ export async function loadProfile(name = DEFAULT_PROFILE) {
|
|
|
530
146
|
const text = await fs.readFile(file, 'utf-8');
|
|
531
147
|
const migratedText = migrateDefaults(text);
|
|
532
148
|
if (migratedText !== text) await writeProfile(file, migratedText);
|
|
533
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
name,
|
|
151
|
+
path: file,
|
|
152
|
+
exists: true,
|
|
153
|
+
values: parseProfile(migratedText),
|
|
154
|
+
};
|
|
534
155
|
} catch (error) {
|
|
535
156
|
if (error.code !== 'ENOENT') throw error;
|
|
536
157
|
}
|
|
@@ -539,7 +160,10 @@ export async function loadProfile(name = DEFAULT_PROFILE) {
|
|
|
539
160
|
if (LEGACY_PROFILES_DIR) {
|
|
540
161
|
try {
|
|
541
162
|
const text = migrateDefaults(
|
|
542
|
-
await fs.readFile(
|
|
163
|
+
await fs.readFile(
|
|
164
|
+
path.join(LEGACY_PROFILES_DIR, `${name}.env`),
|
|
165
|
+
'utf-8',
|
|
166
|
+
),
|
|
543
167
|
);
|
|
544
168
|
await writeProfile(file, text);
|
|
545
169
|
return { name, path: file, exists: true, values: parseProfile(text) };
|
|
@@ -562,7 +186,8 @@ export async function ensureProfile(name = DEFAULT_PROFILE, apiKey) {
|
|
|
562
186
|
Object.fromEntries(
|
|
563
187
|
Object.entries(values).filter(
|
|
564
188
|
([key]) =>
|
|
565
|
-
profile.values[key] === undefined ||
|
|
189
|
+
profile.values[key] === undefined ||
|
|
190
|
+
(key === 'API_KEY' && apiKey !== undefined),
|
|
566
191
|
),
|
|
567
192
|
),
|
|
568
193
|
)
|
|
@@ -586,11 +211,21 @@ export async function clearProfileApiKey(name = DEFAULT_PROFILE) {
|
|
|
586
211
|
return true;
|
|
587
212
|
}
|
|
588
213
|
|
|
589
|
-
|
|
214
|
+
// PI_MAX_TOKENS=65536 is a budget, not a credential. Anything else under a
|
|
215
|
+
// *_TOKENS name, such as a key pasted onto the wrong line, stays redacted.
|
|
216
|
+
function isTokenBudget(key, value) {
|
|
217
|
+
return (
|
|
218
|
+
/_TOKENS$|_TOKEN_LIMIT$/i.test(key) &&
|
|
219
|
+
/^\d+$/.test(value) &&
|
|
220
|
+
Number.isSafeInteger(Number(value))
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isSecretKey(key, value) {
|
|
590
225
|
const upper = key.toUpperCase();
|
|
591
226
|
return (
|
|
592
227
|
upper.endsWith('API_KEY') ||
|
|
593
|
-
upper.includes('TOKEN') ||
|
|
228
|
+
(upper.includes('TOKEN') && !isTokenBudget(key, value)) ||
|
|
594
229
|
upper.includes('SECRET') ||
|
|
595
230
|
upper.includes('PASSWORD') ||
|
|
596
231
|
upper.includes('AUTH')
|
|
@@ -598,16 +233,20 @@ function isSecretKey(key) {
|
|
|
598
233
|
}
|
|
599
234
|
|
|
600
235
|
function redact(key, value) {
|
|
601
|
-
if (!isSecretKey(key
|
|
602
|
-
return value.length <= 8
|
|
236
|
+
if (!value || !isSecretKey(key, value)) return value;
|
|
237
|
+
return value.length <= 8
|
|
238
|
+
? '********'
|
|
239
|
+
: `${value.slice(0, 4)}…${value.slice(-4)}`;
|
|
603
240
|
}
|
|
604
241
|
|
|
605
242
|
function redactEnvLine(line) {
|
|
606
|
-
const match = line.match(
|
|
243
|
+
const match = line.match(
|
|
244
|
+
/^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)=(.*)$/,
|
|
245
|
+
);
|
|
607
246
|
if (!match) return line;
|
|
608
247
|
const [, prefix, key, raw] = match;
|
|
609
248
|
const value = decodeValue(raw);
|
|
610
|
-
if (!isSecretKey(key
|
|
249
|
+
if (!value || !isSecretKey(key, value)) return line;
|
|
611
250
|
return `${prefix}${key}=${redact(key, value)}`;
|
|
612
251
|
}
|
|
613
252
|
|
|
@@ -633,18 +272,20 @@ async function printProfile(profile) {
|
|
|
633
272
|
}
|
|
634
273
|
const text = await fs.readFile(profile.path, 'utf-8');
|
|
635
274
|
console.log(
|
|
636
|
-
text
|
|
637
|
-
.replace(/\n$/, '')
|
|
638
|
-
.split('\n')
|
|
639
|
-
.map(redactEnvLine)
|
|
640
|
-
.join('\n'),
|
|
275
|
+
text.replace(/\n$/, '').split('\n').map(redactEnvLine).join('\n'),
|
|
641
276
|
);
|
|
642
277
|
console.log();
|
|
643
278
|
}
|
|
644
279
|
|
|
645
280
|
export function isUnsetSetting(value) {
|
|
646
|
-
const normalized = String(value ?? '')
|
|
647
|
-
|
|
281
|
+
const normalized = String(value ?? '')
|
|
282
|
+
.trim()
|
|
283
|
+
.toUpperCase();
|
|
284
|
+
return (
|
|
285
|
+
normalized === 'UNSET' ||
|
|
286
|
+
normalized === 'FOLLOW-GATEWAY' ||
|
|
287
|
+
normalized === 'FOLLOW-DEFAULT'
|
|
288
|
+
);
|
|
648
289
|
}
|
|
649
290
|
|
|
650
291
|
export function resolvedModelSetting(value) {
|
|
@@ -684,7 +325,11 @@ export function validateSettingValue(setting, value) {
|
|
|
684
325
|
return null;
|
|
685
326
|
}
|
|
686
327
|
|
|
687
|
-
const ALLOWED_EDITORS = new Set(
|
|
328
|
+
const ALLOWED_EDITORS = new Set(
|
|
329
|
+
process.platform === 'win32'
|
|
330
|
+
? ['notepad', 'code', 'code-insiders', 'vim', 'nano']
|
|
331
|
+
: ['vim', 'nano'],
|
|
332
|
+
);
|
|
688
333
|
|
|
689
334
|
function commandExists(bin) {
|
|
690
335
|
try {
|
|
@@ -709,7 +354,9 @@ function resolveEditor(requested) {
|
|
|
709
354
|
if (fromEnv) return parseEditorCommand(fromEnv);
|
|
710
355
|
if (commandExists('vim')) return { cmd: 'vim', args: [] };
|
|
711
356
|
if (commandExists('nano')) return { cmd: 'nano', args: [] };
|
|
712
|
-
throw new Error(
|
|
357
|
+
throw new Error(
|
|
358
|
+
'No editor found. Install vim or nano, or set $VISUAL / $EDITOR.',
|
|
359
|
+
);
|
|
713
360
|
}
|
|
714
361
|
|
|
715
362
|
async function editProfile(profileName, requestedEditor) {
|
|
@@ -776,12 +423,18 @@ async function printProfileList(activeName) {
|
|
|
776
423
|
console.log(`\n ${c.bold}Profiles${c.reset}`);
|
|
777
424
|
for (const name of profiles) {
|
|
778
425
|
const active = name === activeName ? ` ${c.green}(active)${c.reset}` : '';
|
|
779
|
-
console.log(
|
|
426
|
+
console.log(
|
|
427
|
+
` ${c.cyan}${name}${c.reset}${active} ${c.dim}${profilePath(name)}${c.reset}`,
|
|
428
|
+
);
|
|
780
429
|
}
|
|
781
430
|
console.log();
|
|
782
431
|
}
|
|
783
432
|
|
|
784
|
-
export async function configCommand(
|
|
433
|
+
export async function configCommand(
|
|
434
|
+
argv,
|
|
435
|
+
profileName = DEFAULT_PROFILE,
|
|
436
|
+
options = {},
|
|
437
|
+
) {
|
|
785
438
|
let action = 'default';
|
|
786
439
|
let editor;
|
|
787
440
|
const updates = {};
|
|
@@ -791,7 +444,17 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
|
|
|
791
444
|
if (arg === 'help' || arg === '-h' || arg === '--help') {
|
|
792
445
|
printConfigHelp();
|
|
793
446
|
return;
|
|
794
|
-
} else if (
|
|
447
|
+
} else if (
|
|
448
|
+
[
|
|
449
|
+
'show',
|
|
450
|
+
'path',
|
|
451
|
+
'list',
|
|
452
|
+
'create',
|
|
453
|
+
'delete',
|
|
454
|
+
'edit',
|
|
455
|
+
'interactive',
|
|
456
|
+
].includes(arg)
|
|
457
|
+
) {
|
|
795
458
|
action = arg === 'interactive' ? 'edit' : arg;
|
|
796
459
|
} else if (
|
|
797
460
|
arg === '--gateway-url' ||
|
|
@@ -810,12 +473,17 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
|
|
|
810
473
|
'--subagent-model': 'CLAUDE_CODE_SUBAGENT_MODEL',
|
|
811
474
|
}[arg];
|
|
812
475
|
updates[key] =
|
|
813
|
-
(arg === '--model' || arg === '--subagent-model') &&
|
|
476
|
+
(arg === '--model' || arg === '--subagent-model') &&
|
|
477
|
+
isUnsetSetting(value)
|
|
478
|
+
? ''
|
|
479
|
+
: value;
|
|
814
480
|
} else if (action === 'edit' && ALLOWED_EDITORS.has(arg)) {
|
|
815
|
-
if (editor) throw new Error('Specify only one editor
|
|
481
|
+
if (editor) throw new Error('Specify only one editor');
|
|
816
482
|
editor = arg;
|
|
817
483
|
} else if (action === 'edit') {
|
|
818
|
-
throw new Error(
|
|
484
|
+
throw new Error(
|
|
485
|
+
`Unknown editor '${arg}' (use ${[...ALLOWED_EDITORS].join(', ')})`,
|
|
486
|
+
);
|
|
819
487
|
} else {
|
|
820
488
|
throw new Error(`Unknown config argument: ${arg}`);
|
|
821
489
|
}
|
|
@@ -831,7 +499,12 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
|
|
|
831
499
|
return;
|
|
832
500
|
}
|
|
833
501
|
|
|
834
|
-
if (
|
|
502
|
+
if (
|
|
503
|
+
action === 'list' ||
|
|
504
|
+
(action === 'default' &&
|
|
505
|
+
!options.profileExplicit &&
|
|
506
|
+
!Object.keys(updates).length)
|
|
507
|
+
) {
|
|
835
508
|
await printProfileList(profileName);
|
|
836
509
|
return;
|
|
837
510
|
}
|
|
@@ -846,7 +519,8 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
|
|
|
846
519
|
throw new Error('config create cannot be combined with profile updates');
|
|
847
520
|
}
|
|
848
521
|
const existing = await loadProfile(profileName);
|
|
849
|
-
if (existing.exists)
|
|
522
|
+
if (existing.exists)
|
|
523
|
+
throw new Error(`Profile '${profileName}' already exists`);
|
|
850
524
|
const profile = await ensureProfile(profileName);
|
|
851
525
|
console.log(`Created profile '${profileName}'.`);
|
|
852
526
|
await printProfile(profile);
|
|
@@ -855,7 +529,9 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
|
|
|
855
529
|
|
|
856
530
|
if (action === 'delete') {
|
|
857
531
|
if (profileName === DEFAULT_PROFILE) {
|
|
858
|
-
throw new Error(
|
|
532
|
+
throw new Error(
|
|
533
|
+
'Refusing to delete the default profile; use subc logout to clear its key',
|
|
534
|
+
);
|
|
859
535
|
}
|
|
860
536
|
const profile = await loadProfile(profileName);
|
|
861
537
|
if (!profile.exists) {
|
|
@@ -878,10 +554,12 @@ export async function configCommand(argv, profileName = DEFAULT_PROFILE, options
|
|
|
878
554
|
}
|
|
879
555
|
|
|
880
556
|
export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
|
|
881
|
-
const selectedModel =
|
|
557
|
+
const selectedModel =
|
|
558
|
+
options.selectedModel || models[0] || RUNBOOK_DEFAULTS.MODEL;
|
|
882
559
|
console.log(`\n ${c.bold}Available models${c.reset}\n`);
|
|
883
560
|
for (const model of models) {
|
|
884
|
-
const suffix =
|
|
561
|
+
const suffix =
|
|
562
|
+
model === selectedModel ? ` ${c.dim}(default)${c.reset}` : '';
|
|
885
563
|
console.log(` ${c.cyan}${model}${c.reset}${suffix}`);
|
|
886
564
|
}
|
|
887
565
|
if (options.source === 'public' && options.hasApiKey) {
|
|
@@ -896,7 +574,9 @@ export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
|
|
|
896
574
|
}
|
|
897
575
|
|
|
898
576
|
export function validateOriginUrl(rawUrl, label = 'URL') {
|
|
899
|
-
const normalized = String(rawUrl ?? '')
|
|
577
|
+
const normalized = String(rawUrl ?? '')
|
|
578
|
+
.trim()
|
|
579
|
+
.replace(/\/+$/, '');
|
|
900
580
|
if (!normalized) {
|
|
901
581
|
throw new Error(`${label} cannot be blank`);
|
|
902
582
|
}
|
|
@@ -929,7 +609,9 @@ export async function updateUrlCommand(argv = [], options = {}) {
|
|
|
929
609
|
const profile = await updateProfile(profileName, { GATEWAY_URL: gatewayUrl });
|
|
930
610
|
|
|
931
611
|
console.log(`\n ${c.green}${c.bold}✓ Gateway URL updated.${c.reset}`);
|
|
932
|
-
console.log(
|
|
612
|
+
console.log(
|
|
613
|
+
` ${c.dim}Updated profile automatically: ${profile.path}${c.reset}`,
|
|
614
|
+
);
|
|
933
615
|
console.log(` ${c.dim}URL: ${gatewayUrl}${c.reset}`);
|
|
934
616
|
if (process.env.SUBCONSCIOUS_BASE_URL?.trim()) {
|
|
935
617
|
console.log(
|
|
@@ -937,7 +619,8 @@ export async function updateUrlCommand(argv = [], options = {}) {
|
|
|
937
619
|
);
|
|
938
620
|
}
|
|
939
621
|
const claudeOverride =
|
|
940
|
-
process.env.CLAUDE_GATEWAY_URL?.trim() ||
|
|
622
|
+
process.env.CLAUDE_GATEWAY_URL?.trim() ||
|
|
623
|
+
profile.values.CLAUDE_GATEWAY_URL?.trim();
|
|
941
624
|
if (claudeOverride) {
|
|
942
625
|
console.log(
|
|
943
626
|
` ${c.yellow}CLAUDE_GATEWAY_URL is set, so Claude Code will continue using ${claudeOverride}.${c.reset}`,
|
|
@@ -954,10 +637,14 @@ export async function updatePlatformUrlCommand(argv = [], options = {}) {
|
|
|
954
637
|
const platformUrl = validateOriginUrl(argv[0], 'Platform URL');
|
|
955
638
|
|
|
956
639
|
const profileName = options.profileName || DEFAULT_PROFILE;
|
|
957
|
-
const profile = await updateProfile(profileName, {
|
|
640
|
+
const profile = await updateProfile(profileName, {
|
|
641
|
+
PLATFORM_URL: platformUrl,
|
|
642
|
+
});
|
|
958
643
|
|
|
959
644
|
console.log(`\n ${c.green}${c.bold}✓ Platform URL updated.${c.reset}`);
|
|
960
|
-
console.log(
|
|
645
|
+
console.log(
|
|
646
|
+
` ${c.dim}Updated profile automatically: ${profile.path}${c.reset}`,
|
|
647
|
+
);
|
|
961
648
|
console.log(` ${c.dim}URL: ${platformUrl}${c.reset}`);
|
|
962
649
|
if (process.env.SUBCONSCIOUS_URL?.trim()) {
|
|
963
650
|
console.log(
|