subconscious-cli 6.3.1 → 8.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/README.md +5 -2
- package/bin/agent-launch.js +1 -25
- package/bin/agent-spawn.js +2 -2
- package/bin/agents.js +27 -22
- package/bin/auth.js +31 -7
- package/bin/claude-config.js +13 -629
- package/bin/cli.js +43 -0
- package/bin/feedback.js +2 -1
- package/bin/model-capabilities.js +45 -5
- package/bin/models.js +126 -36
- 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/network.js +710 -0
- package/bin/opencode-provider.js +17 -5
- package/bin/profiles.js +46 -2
- package/bin/runbook/claude-code/agent.json +6 -27
- package/bin/runbook/claude-code/install.sh +3 -2
- package/bin/runbook/shared.json +10 -0
- package/bin/tui.js +1 -1
- package/bin/update-check.js +2 -1
- package/bin/upgrade.js +2 -1
- package/bin/usage.js +85 -62
- package/bin/windows/launch.js +5 -17
- package/package.json +1 -1
- package/bin/claude-config-drift.js +0 -58
- package/bin/claude-config-plugins.js +0 -103
package/README.md
CHANGED
|
@@ -351,8 +351,11 @@ subc opencode uninstall
|
|
|
351
351
|
```
|
|
352
352
|
|
|
353
353
|
`subc claude uninstall` and `subc opencode uninstall` only clean leftover files
|
|
354
|
-
from older overwrite-style setup.
|
|
355
|
-
|
|
354
|
+
from older overwrite-style setup. `subc claude` writes nothing persistent: it
|
|
355
|
+
passes routing, credentials and the model picker through the launch env and
|
|
356
|
+
`--settings`, and sessions use your own Claude config (~/.claude, or your
|
|
357
|
+
CLAUDE_CONFIG_DIR). Launch those agents with `subc claude` / `subc opencode`;
|
|
358
|
+
they do not need install.
|
|
356
359
|
|
|
357
360
|
Inspect a persistent integration with `subc <agent> status`. A one-off
|
|
358
361
|
`--api-key` passed to install takes precedence for that command but is not
|
package/bin/agent-launch.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { agentInputs, agentKeyName } from './agent-data.js';
|
|
3
2
|
import { openCodeConfigFromEnv } from './opencode-provider.js';
|
|
4
|
-
import {
|
|
3
|
+
import { isUnsetSetting } from './profiles.js';
|
|
5
4
|
|
|
6
5
|
// One pass, so a substituted value (a prompt, a settings document) is never
|
|
7
6
|
// scanned again for placeholders.
|
|
@@ -9,9 +8,6 @@ const PLACEHOLDER = /\$\{([A-Z_][A-Z0-9_]*)\}(\/)?|\{([A-Za-z][A-Za-z0-9]*)\}/g;
|
|
|
9
8
|
const present = (value) =>
|
|
10
9
|
value !== undefined && value !== null && value !== '';
|
|
11
10
|
|
|
12
|
-
// CLAUDE_CODE_ISOLATE values that stop Claude Code config isolation.
|
|
13
|
-
const ISOLATE_OFF = new Set(['off', '0', 'false', 'no']);
|
|
14
|
-
|
|
15
11
|
function tokensFor(agent, env, extra) {
|
|
16
12
|
const baseUrl = String(env.GATEWAY_URL ?? '').replace(/\/+$/, '');
|
|
17
13
|
const keyName = agentKeyName(agent);
|
|
@@ -23,26 +19,6 @@ function tokensFor(agent, env, extra) {
|
|
|
23
19
|
// Left for the runbook, which writes the file and substitutes its path.
|
|
24
20
|
tempFile: '{tempFile}',
|
|
25
21
|
opencodeConfig: () => JSON.stringify(openCodeConfigFromEnv(env)),
|
|
26
|
-
// Claude Code config isolation: subc sessions keep their Claude config
|
|
27
|
-
// (settings, sessions, plugins, credentials) under the subc config dir
|
|
28
|
-
// instead of ~/.claude, so session state cannot bleed into a plain
|
|
29
|
-
// `claude` installation. Empty when CLAUDE_CODE_ISOLATE opts out.
|
|
30
|
-
claudeConfigDir: () => {
|
|
31
|
-
const setting = String(env.CLAUDE_CODE_ISOLATE ?? '')
|
|
32
|
-
.trim()
|
|
33
|
-
.toLowerCase();
|
|
34
|
-
// Opted out: the user's own folder, as plain `claude` would use it.
|
|
35
|
-
if (ISOLATE_OFF.has(setting)) {
|
|
36
|
-
return String(
|
|
37
|
-
env.SUBC_CLAUDE_CONFIG_SOURCE || env.CLAUDE_CONFIG_DIR || '',
|
|
38
|
-
).trim();
|
|
39
|
-
}
|
|
40
|
-
const base = String(env.SUBC_CONFIG_DIR ?? '').trim() || configDir();
|
|
41
|
-
// A relative base would put the config, MCP tokens included, in the
|
|
42
|
-
// current directory; use ~/.claude unchanged instead.
|
|
43
|
-
if (!path.isAbsolute(base)) return '';
|
|
44
|
-
return path.join(base, 'claude-code');
|
|
45
|
-
},
|
|
46
22
|
...extra,
|
|
47
23
|
};
|
|
48
24
|
}
|
package/bin/agent-spawn.js
CHANGED
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { RUNBOOK_DIR } from './agent-data.js';
|
|
5
5
|
import { modelIds } from './launch-values.js';
|
|
6
|
-
import {
|
|
6
|
+
import { visionModelListFromMetadata } from './model-capabilities.js';
|
|
7
7
|
|
|
8
8
|
/** Resolve a script inside the agent's own runbook directory. */
|
|
9
9
|
export function runbookScriptPath(agent, relativeScript) {
|
|
@@ -95,7 +95,7 @@ export function scriptEnv(env, { strict = true } = {}) {
|
|
|
95
95
|
return {
|
|
96
96
|
...env,
|
|
97
97
|
...(ids === undefined ? {} : { SUBC_MODEL_IDS: ids }),
|
|
98
|
-
SUBC_VISION_MODELS:
|
|
98
|
+
SUBC_VISION_MODELS: visionModelListFromMetadata(env.SUBC_MODEL_METADATA),
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
|
package/bin/agents.js
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
import { launchPlan, resolveInputs } from './agent-launch.js';
|
|
20
20
|
import { planCommand, runCommand, scriptCommand } from './agent-spawn.js';
|
|
21
21
|
import { getApiKey } from './auth.js';
|
|
22
|
-
import { prepareClaudeLaunch } from './claude-config.js';
|
|
23
22
|
import {
|
|
24
23
|
applyClaudePickerCatalog,
|
|
25
24
|
CLAUDE_PICKER_ENV_DESCRIPTION,
|
|
@@ -37,6 +36,7 @@ import {
|
|
|
37
36
|
PUBLIC_CATALOG_FALLBACK_MESSAGE,
|
|
38
37
|
resolveModelCatalog,
|
|
39
38
|
} from './models.js';
|
|
39
|
+
import { describeFailure } from './network.js';
|
|
40
40
|
import { isUnsetSetting, resolvedModelSetting } from './profiles.js';
|
|
41
41
|
import { runWindowsAgent } from './windows/agents.js';
|
|
42
42
|
|
|
@@ -327,6 +327,7 @@ export function runbookEnv(
|
|
|
327
327
|
profile,
|
|
328
328
|
agent,
|
|
329
329
|
models = PACKAGED_MODELS,
|
|
330
|
+
entries = [],
|
|
330
331
|
) {
|
|
331
332
|
const ctx = buildContext(apiKey, model, profile);
|
|
332
333
|
const extraDirs = [binDir, ...candidateBinDirs()].filter(Boolean);
|
|
@@ -343,6 +344,7 @@ export function runbookEnv(
|
|
|
343
344
|
...(keyName ? { [keyName]: apiKey } : {}),
|
|
344
345
|
MODEL: model,
|
|
345
346
|
SUBCONSCIOUS_MODELS: models.join('\n'),
|
|
347
|
+
SUBC_MODEL_METADATA: JSON.stringify(entries),
|
|
346
348
|
...(claude
|
|
347
349
|
? {
|
|
348
350
|
SUBC_CLAUDE_SETTINGS: JSON.stringify(
|
|
@@ -372,13 +374,17 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
|
|
|
372
374
|
console.error(
|
|
373
375
|
` ${c.yellow}Could not fetch the live model catalog; using packaged defaults.${c.reset}`,
|
|
374
376
|
);
|
|
375
|
-
console.error(` ${c.dim}${catalog.error
|
|
377
|
+
console.error(` ${c.dim}${describeFailure(catalog.error)}${c.reset}\n`);
|
|
376
378
|
}
|
|
377
379
|
return catalog;
|
|
378
380
|
}
|
|
379
381
|
|
|
380
382
|
export function selectLaunchModel(requestedModel, modelSource, catalog) {
|
|
381
|
-
const first =
|
|
383
|
+
const first =
|
|
384
|
+
catalog.defaultModels?.[0] ||
|
|
385
|
+
catalog.models[0] ||
|
|
386
|
+
requestedModel ||
|
|
387
|
+
DEFAULTS.model;
|
|
382
388
|
if (modelSource === 'catalog' || !requestedModel) {
|
|
383
389
|
return first;
|
|
384
390
|
}
|
|
@@ -427,7 +433,15 @@ async function runRunbookSetup(agent, argv, profile) {
|
|
|
427
433
|
// Setup scripts read no strict-choice setting; only the model list matters.
|
|
428
434
|
const env = resolveInputs(
|
|
429
435
|
agent,
|
|
430
|
-
runbookEnv(
|
|
436
|
+
runbookEnv(
|
|
437
|
+
apiKey,
|
|
438
|
+
model,
|
|
439
|
+
undefined,
|
|
440
|
+
profile,
|
|
441
|
+
agent,
|
|
442
|
+
catalog.defaultModels,
|
|
443
|
+
catalog.entries,
|
|
444
|
+
),
|
|
431
445
|
{},
|
|
432
446
|
{ strict: false },
|
|
433
447
|
);
|
|
@@ -542,26 +556,17 @@ async function launchAgent(agent, argv, profile, headless) {
|
|
|
542
556
|
(headless ? console.error : console.log)(
|
|
543
557
|
` ${c.dim}Launching ${c.reset}${c.bold}${agent.name}${c.reset} ${c.dim}on Subconscious ${c.reset}${c.dim}(${model})${c.reset}\n`,
|
|
544
558
|
);
|
|
545
|
-
const env = runbookEnv(
|
|
559
|
+
const env = runbookEnv(
|
|
560
|
+
apiKey,
|
|
561
|
+
model,
|
|
562
|
+
binDir,
|
|
563
|
+
profile,
|
|
564
|
+
agent,
|
|
565
|
+
catalog.defaultModels,
|
|
566
|
+
catalog.entries,
|
|
567
|
+
);
|
|
546
568
|
// The headless endpoint is the one endpoint for every harness, Claude too.
|
|
547
569
|
const launchEnv = headless ? { ...env, CLAUDE_GATEWAY_URL: '' } : env;
|
|
548
570
|
const plan = launchPlan(agent, { env: launchEnv, ...headlessParts(rest) });
|
|
549
|
-
if (agent.id === CLAUDE_CODE) {
|
|
550
|
-
const {
|
|
551
|
-
warnings,
|
|
552
|
-
refused,
|
|
553
|
-
env: sourceEnv,
|
|
554
|
-
} = prepareClaudeLaunch(agent, plan.env, launchEnv);
|
|
555
|
-
const color = refused ? c.red : c.yellow;
|
|
556
|
-
for (const warning of warnings) {
|
|
557
|
-
console.error(` ${color}${warning}${c.reset}`);
|
|
558
|
-
}
|
|
559
|
-
if (refused) {
|
|
560
|
-
process.exitCode = 1;
|
|
561
|
-
return 1;
|
|
562
|
-
}
|
|
563
|
-
const prepared = { ...plan, env: { ...plan.env, ...sourceEnv } };
|
|
564
|
-
return runCommand(planCommand(agent, prepared, binDir));
|
|
565
|
-
}
|
|
566
571
|
return runCommand(planCommand(agent, plan, binDir));
|
|
567
572
|
}
|
package/bin/auth.js
CHANGED
|
@@ -16,6 +16,12 @@ import fs from 'node:fs/promises';
|
|
|
16
16
|
import os from 'node:os';
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import { c } from './colors.js';
|
|
19
|
+
import {
|
|
20
|
+
describeFetchError,
|
|
21
|
+
proxyAwareFetch,
|
|
22
|
+
proxyHint,
|
|
23
|
+
tlsTrustHint,
|
|
24
|
+
} from './network.js';
|
|
19
25
|
import {
|
|
20
26
|
clearProfileApiKey,
|
|
21
27
|
DEFAULT_PROFILE,
|
|
@@ -128,7 +134,10 @@ function terminalLink(url) {
|
|
|
128
134
|
return `\u001b]8;;${url}\u0007${url}\u001b]8;;\u0007`;
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
export async function registerDeviceLogin(
|
|
137
|
+
export async function registerDeviceLogin(
|
|
138
|
+
platformUrl,
|
|
139
|
+
fetchImpl = proxyAwareFetch,
|
|
140
|
+
) {
|
|
132
141
|
const res = await fetchImpl(`${platformUrl}/api/cli/device/register`, {
|
|
133
142
|
method: 'POST',
|
|
134
143
|
signal: AbortSignal.timeout(8000),
|
|
@@ -146,7 +155,7 @@ export async function registerDeviceLogin(platformUrl, fetchImpl = fetch) {
|
|
|
146
155
|
export async function pollDeviceLogin(
|
|
147
156
|
platformUrl,
|
|
148
157
|
deviceCode,
|
|
149
|
-
fetchImpl =
|
|
158
|
+
fetchImpl = proxyAwareFetch,
|
|
150
159
|
) {
|
|
151
160
|
const res = await fetchImpl(`${platformUrl}/api/cli/device/token`, {
|
|
152
161
|
method: 'POST',
|
|
@@ -180,10 +189,25 @@ function printDeviceInstructions(platformUrl, userCode) {
|
|
|
180
189
|
|
|
181
190
|
function printLoginFallback() {
|
|
182
191
|
console.error(
|
|
183
|
-
` ${c.dim}You can also copy an API key from the dashboard and run${c.reset} ${c.cyan}subc update-key <your-api-key>${c.reset}${c.dim}.${c.reset}`,
|
|
192
|
+
` ${c.dim}You can also copy an API key from the dashboard and run${c.reset} ${c.cyan}subc update-key <your-api-key>${c.reset}${c.dim}, which needs no network.${c.reset}`,
|
|
184
193
|
);
|
|
185
194
|
}
|
|
186
195
|
|
|
196
|
+
// Render a login failure with the real underlying network cause. Node's fetch
|
|
197
|
+
// reports every connection problem as a bare "fetch failed"; the diagnostic
|
|
198
|
+
// (`ENOTFOUND`, `ECONNREFUSED`, `ETIMEDOUT`, TLS, proxy) lives on `cause`.
|
|
199
|
+
function printLoginFailure(platformUrl, error) {
|
|
200
|
+
const message = describeFetchError(error);
|
|
201
|
+
console.error(` ${c.red}✗ ${message}${c.reset}`);
|
|
202
|
+
const hint = tlsTrustHint(error);
|
|
203
|
+
if (hint) {
|
|
204
|
+
console.error(` ${c.yellow}${hint}${c.reset}`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const proxy = proxyHint(error, platformUrl);
|
|
208
|
+
if (proxy) console.error(` ${c.dim}${proxy}${c.reset}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
187
211
|
async function saveLoginKey(profileName, token) {
|
|
188
212
|
if (profileName === DEFAULT_PROFILE) {
|
|
189
213
|
const config = await loadConfig();
|
|
@@ -196,7 +220,7 @@ async function saveLoginKey(profileName, token) {
|
|
|
196
220
|
export async function loginCommand(_argv = [], options = {}) {
|
|
197
221
|
const profileName = options.profileName || DEFAULT_PROFILE;
|
|
198
222
|
const existing = await getApiKey(options.profile);
|
|
199
|
-
const fetchImpl = options.fetchImpl ||
|
|
223
|
+
const fetchImpl = options.fetchImpl || proxyAwareFetch;
|
|
200
224
|
|
|
201
225
|
if (existing) {
|
|
202
226
|
const profile = await ensureProfile(profileName, existing.key);
|
|
@@ -225,7 +249,7 @@ export async function loginCommand(_argv = [], options = {}) {
|
|
|
225
249
|
try {
|
|
226
250
|
registered = await registerDeviceLogin(platformUrl, fetchImpl);
|
|
227
251
|
} catch (error) {
|
|
228
|
-
|
|
252
|
+
printLoginFailure(platformUrl, error);
|
|
229
253
|
printLoginFallback();
|
|
230
254
|
console.log();
|
|
231
255
|
process.exitCode = 1;
|
|
@@ -299,7 +323,7 @@ export async function loginCommand(_argv = [], options = {}) {
|
|
|
299
323
|
} catch (error) {
|
|
300
324
|
clearInterval(spinner);
|
|
301
325
|
process.stdout.write(`\r${' '.repeat(50)}\r`);
|
|
302
|
-
|
|
326
|
+
printLoginFailure(platformUrl, error);
|
|
303
327
|
printLoginFallback();
|
|
304
328
|
console.log();
|
|
305
329
|
process.exitCode = 1;
|
|
@@ -380,7 +404,7 @@ export async function whoamiCommand(_argv = [], options = {}) {
|
|
|
380
404
|
|
|
381
405
|
// Validate the key against the server; falls back to offline display if unreachable
|
|
382
406
|
try {
|
|
383
|
-
const res = await
|
|
407
|
+
const res = await proxyAwareFetch(
|
|
384
408
|
`${getPlatformUrl(options.profile)}/api/cli/whoami`,
|
|
385
409
|
{
|
|
386
410
|
headers: { Authorization: `Bearer ${key}` },
|