subconscious-cli 6.3.1 → 7.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 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. Launch those agents with `subc claude` /
355
- `subc opencode`; they do not need install.
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
@@ -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 { configDir, isUnsetSetting } from './profiles.js';
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/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
 
@@ -372,7 +372,7 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
372
372
  console.error(
373
373
  ` ${c.yellow}Could not fetch the live model catalog; using packaged defaults.${c.reset}`,
374
374
  );
375
- console.error(` ${c.dim}${catalog.error.message}${c.reset}\n`);
375
+ console.error(` ${c.dim}${describeFailure(catalog.error)}${c.reset}\n`);
376
376
  }
377
377
  return catalog;
378
378
  }
@@ -546,22 +546,5 @@ async function launchAgent(agent, argv, profile, headless) {
546
546
  // The headless endpoint is the one endpoint for every harness, Claude too.
547
547
  const launchEnv = headless ? { ...env, CLAUDE_GATEWAY_URL: '' } : env;
548
548
  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
549
  return runCommand(planCommand(agent, plan, binDir));
567
550
  }
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(platformUrl, fetchImpl = fetch) {
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 = fetch,
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 || fetch;
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
- console.error(` ${c.red}✗ ${error.message}${c.reset}`);
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
- console.error(` ${c.red}✗ ${error.message}${c.reset}`);
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 fetch(
407
+ const res = await proxyAwareFetch(
384
408
  `${getPlatformUrl(options.profile)}/api/cli/whoami`,
385
409
  {
386
410
  headers: { Authorization: `Bearer ${key}` },