subconscious-cli 0.2.1 → 0.3.1

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/bin/auth.js CHANGED
@@ -8,7 +8,8 @@
8
8
  * 3. The web app authenticates the user, generates an API key, and
9
9
  * delivers it back to the CLI via a cross-origin fetch to
10
10
  * localhost:{port}/callback?token=...&state=...
11
- * 4. CLI verifies the `state` matches, saves the key to ~/.subcon/config.json.
11
+ * 4. CLI verifies the `state`, saves the key to ~/.subconscious/config.json,
12
+ * and creates a coding-agent profile under ~/.subconscious/profiles/.
12
13
  *
13
14
  * Override SUBCONSCIOUS_URL env var for local development.
14
15
  */
@@ -20,18 +21,55 @@ import fs from 'node:fs/promises';
20
21
  import os from 'node:os';
21
22
  import path from 'node:path';
22
23
  import { c } from './colors.js';
24
+ import { clearProfileApiKey, DEFAULT_PROFILE, ensureProfile } from './profiles.js';
23
25
 
24
- const CONFIG_DIR = path.join(os.homedir(), '.subcon');
26
+ const CONFIG_OVERRIDE = process.env.SUBC_CONFIG_DIR?.trim();
27
+ const CONFIG_DIR = CONFIG_OVERRIDE || path.join(os.homedir(), '.subconscious');
25
28
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
29
+ const LEGACY_CONFIG_FILE = CONFIG_OVERRIDE
30
+ ? null
31
+ : path.join(os.homedir(), '.subcon', 'config.json');
26
32
  // Defaults to production. Developers set SUBCONSCIOUS_URL=http://localhost:3000 for local dev.
27
33
  const PLATFORM_URL = process.env.SUBCONSCIOUS_URL || 'https://www.subconscious.dev';
28
34
 
35
+ // Login callback CORS. After the marketing/platform split, www 307s /cli/auth
36
+ // to the platform host, so the browser Origin is platform even when the CLI
37
+ // still opened www. Keep www so a non-redirected tab still works.
38
+ const CALLBACK_ORIGINS = new Set([
39
+ 'https://www.subconscious.dev',
40
+ 'https://platform.subconscious.dev',
41
+ 'https://dev.subconscious.dev',
42
+ 'https://platform-dev.subconscious.dev',
43
+ ]);
44
+
45
+ export function isAllowedCallbackOrigin(origin, platformUrl = PLATFORM_URL) {
46
+ if (!origin) return false;
47
+ if (origin === platformUrl || CALLBACK_ORIGINS.has(origin)) return true;
48
+ try {
49
+ const { protocol, hostname } = new URL(origin);
50
+ return protocol === 'http:' && (hostname === 'localhost' || hostname === '127.0.0.1');
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
29
56
  // ── Config helpers ──────────────────────────────────────────────────────
30
57
 
31
58
  async function loadConfig() {
32
59
  try {
33
60
  const content = await fs.readFile(CONFIG_FILE, 'utf-8');
34
61
  return JSON.parse(content);
62
+ } catch (error) {
63
+ if (error.code !== 'ENOENT' || !LEGACY_CONFIG_FILE) return {};
64
+ }
65
+
66
+ // One-way compatibility migration. Keep the old file untouched so this is
67
+ // recoverable, but all future writes go to ~/.subconscious.
68
+ try {
69
+ const content = await fs.readFile(LEGACY_CONFIG_FILE, 'utf-8');
70
+ const config = JSON.parse(content);
71
+ await saveConfig(config);
72
+ return config;
35
73
  } catch {
36
74
  return {};
37
75
  }
@@ -48,13 +86,20 @@ async function saveConfig(config) {
48
86
  * Resolve the active API key. The env var takes precedence over the saved
49
87
  * config so CI and per-shell overrides win. Returns null when unauthenticated.
50
88
  */
51
- export async function getApiKey() {
89
+ export async function getApiKey(profile) {
52
90
  const envKey = process.env.SUBCONSCIOUS_API_KEY?.trim();
53
91
  if (envKey) return { key: envKey, source: 'SUBCONSCIOUS_API_KEY env var' };
54
92
 
93
+ const profileKey = profile?.values?.API_KEY?.trim();
94
+ if (profileKey) return { key: profileKey, source: profile.path };
95
+
96
+ // Named profiles are isolated: an empty/missing key must not silently fall
97
+ // back to the default credential and send traffic to the wrong account.
98
+ if (profile?.name && profile.name !== DEFAULT_PROFILE) return null;
99
+
55
100
  const config = await loadConfig();
56
101
  if (config.subconscious_api_key) {
57
- return { key: config.subconscious_api_key, source: '~/.subcon/config.json' };
102
+ return { key: config.subconscious_api_key, source: '~/.subconscious/config.json' };
58
103
  }
59
104
  return null;
60
105
  }
@@ -90,12 +135,10 @@ function startCallbackServer(expectedState) {
90
135
  });
91
136
 
92
137
  const server = http.createServer((req, res) => {
93
- // CORS: only allow the web app's origin (production or localhost dev).
138
+ // CORS: only allow known web-app origins (or localhost dev).
94
139
  // This prevents arbitrary websites from hitting this callback.
95
140
  const origin = req.headers.origin || '';
96
- const allowed =
97
- origin === PLATFORM_URL ||
98
- origin.startsWith('http://localhost:');
141
+ const allowed = isAllowedCallbackOrigin(origin);
99
142
  res.setHeader(
100
143
  'Access-Control-Allow-Origin',
101
144
  allowed ? origin : PLATFORM_URL,
@@ -231,15 +274,20 @@ h1{font-size:15px;font-weight:600;margin-bottom:4px;letter-spacing:-.01em}
231
274
 
232
275
  // ── Commands ────────────────────────────────────────────────────────────
233
276
 
234
- export async function loginCommand() {
235
- const existing = await getApiKey();
277
+ export async function loginCommand(_argv = [], options = {}) {
278
+ const profileName = options.profileName || DEFAULT_PROFILE;
279
+ const existing = await getApiKey(options.profile);
236
280
 
237
281
  if (existing) {
282
+ const profile = await ensureProfile(profileName, existing.key);
283
+ const logout =
284
+ profileName === DEFAULT_PROFILE ? 'subc logout' : `subc --profile ${profileName} logout`;
238
285
  const masked = existing.key.slice(0, 8) + '...' + existing.key.slice(-4);
239
286
  console.log(`\n${c.yellow}Already logged in.${c.reset}`);
240
287
  console.log(` Key: ${c.dim}${masked}${c.reset}`);
288
+ console.log(` Profile: ${c.dim}${profile.path}${c.reset}`);
241
289
  console.log(
242
- `\n Run ${c.cyan}subconscious logout${c.reset} first to switch accounts.\n`,
290
+ `\n Run ${c.cyan}${logout}${c.reset} first to switch accounts.\n`,
243
291
  );
244
292
  return;
245
293
  }
@@ -283,14 +331,22 @@ export async function loginCommand() {
283
331
  clearInterval(spinner);
284
332
  process.stdout.write('\r' + ' '.repeat(50) + '\r');
285
333
 
286
- const config = await loadConfig();
287
- config.subconscious_api_key = result.token;
288
- await saveConfig(config);
334
+ if (profileName === DEFAULT_PROFILE) {
335
+ const config = await loadConfig();
336
+ config.subconscious_api_key = result.token;
337
+ await saveConfig(config);
338
+ }
339
+ const profile = await ensureProfile(profileName, result.token);
289
340
 
290
341
  const masked = result.token.slice(0, 8) + '...' + result.token.slice(-4);
291
342
  console.log(` ${c.green}${c.bold}✓ Logged in successfully!${c.reset}`);
292
343
  console.log(` ${c.dim}Key: ${masked}${c.reset}`);
293
- console.log(` ${c.dim}Saved to ~/.subcon/config.json${c.reset}`);
344
+ if (profileName === DEFAULT_PROFILE) {
345
+ console.log(` ${c.dim}Saved to ~/.subconscious/config.json${c.reset}`);
346
+ }
347
+ console.log(` ${c.dim}Runbook profile: ${profile.path}${c.reset}`);
348
+ console.log(` ${c.dim}Launch a terminal agent with ${c.reset}${c.cyan}subc claude${c.reset}${c.dim}, or install editor hooks with ${c.reset}${c.cyan}subc cursor install${c.reset}${c.dim}.${c.reset}`);
349
+ console.log(` ${c.dim}Pi needs ${c.reset}${c.cyan}subc pi install${c.reset}${c.dim} first. List profiles with ${c.reset}${c.cyan}subc config${c.reset}${c.dim}.${c.reset}`);
294
350
  console.log();
295
351
  } catch (error) {
296
352
  clearInterval(spinner);
@@ -300,29 +356,65 @@ export async function loginCommand() {
300
356
  }
301
357
  }
302
358
 
303
- export async function logoutCommand() {
359
+ export async function updateApiKeyCommand(argv = [], options = {}) {
360
+ if (argv.length !== 1 || !argv[0]?.trim()) {
361
+ throw new Error('Usage: subc update-key <api-key>');
362
+ }
363
+
364
+ const key = argv[0].trim();
365
+ const profileName = options.profileName || DEFAULT_PROFILE;
366
+ const profile = await ensureProfile(profileName, key);
367
+
368
+ if (profileName === DEFAULT_PROFILE) {
369
+ const config = await loadConfig();
370
+ config.subconscious_api_key = key;
371
+ await saveConfig(config);
372
+ }
373
+
374
+ const masked = key.length <= 12 ? '********' : `${key.slice(0, 8)}...${key.slice(-4)}`;
375
+ console.log(`\n ${c.green}${c.bold}✓ API key updated.${c.reset}`);
376
+ console.log(` ${c.dim}Profile: ${profile.path}${c.reset}`);
377
+ console.log(` ${c.dim}Key: ${masked}${c.reset}`);
378
+ if (process.env.SUBCONSCIOUS_API_KEY?.trim()) {
379
+ console.log(
380
+ `\n ${c.yellow}SUBCONSCIOUS_API_KEY is set and will override this saved key.${c.reset}`,
381
+ );
382
+ }
383
+ console.log();
384
+ }
385
+
386
+ export async function logoutCommand(_argv = [], options = {}) {
387
+ const profileName = options.profileName || DEFAULT_PROFILE;
304
388
  const config = await loadConfig();
389
+ const clearedProfile = await clearProfileApiKey(profileName);
390
+ const clearSavedConfig = profileName === DEFAULT_PROFILE && config.subconscious_api_key;
305
391
 
306
- if (!config.subconscious_api_key) {
392
+ if (!clearSavedConfig && !clearedProfile) {
307
393
  console.log(`\n ${c.dim}Not logged in.${c.reset}\n`);
308
394
  return;
309
395
  }
310
396
 
311
- delete config.subconscious_api_key;
312
- await saveConfig(config);
397
+ if (clearSavedConfig) {
398
+ delete config.subconscious_api_key;
399
+ await saveConfig(config);
400
+ }
313
401
 
314
402
  console.log(
315
- `\n ${c.green}✓${c.reset} Logged out. API key removed from ${c.dim}~/.subcon/config.json${c.reset}\n`,
403
+ `\n ${c.green}✓${c.reset} Logged out of profile '${profileName}'.${c.reset}\n`,
316
404
  );
317
405
  }
318
406
 
319
- export async function whoamiCommand() {
320
- const auth = await getApiKey();
407
+ export async function whoamiCommand(_argv = [], options = {}) {
408
+ const auth = await getApiKey(options.profile);
409
+ const profileFlag =
410
+ options.profileName && options.profileName !== DEFAULT_PROFILE
411
+ ? `--profile ${options.profileName} `
412
+ : '';
321
413
 
322
414
  if (!auth) {
323
415
  console.log(`\n ${c.dim}Not logged in.${c.reset}`);
324
416
  console.log(
325
- ` Run ${c.cyan}subconscious login${c.reset} to get started.\n`,
417
+ ` Run ${c.cyan}subc ${profileFlag}login${c.reset} to get started.\n`,
326
418
  );
327
419
  return;
328
420
  }
@@ -353,7 +445,7 @@ export async function whoamiCommand() {
353
445
  console.log(` ${c.dim}Source: ${source}${c.reset}`);
354
446
  console.log();
355
447
  console.log(
356
- ` Run ${c.cyan}subconscious logout${c.reset} then ${c.cyan}subconscious login${c.reset} to re-authenticate.`,
448
+ ` Run ${c.cyan}subc ${profileFlag}logout${c.reset} then ${c.cyan}subc ${profileFlag}login${c.reset} to re-authenticate.`,
357
449
  );
358
450
  }
359
451
  } catch {
@@ -0,0 +1,39 @@
1
+ import { colorEnabled } from './colors.js';
2
+
3
+ // Pre-rendered from assets/imgs/logo.png as portable, 7-bit ASCII art.
4
+ const LOGO = [
5
+ ' ##### #####',
6
+ ' ####### #######',
7
+ ' ######## ########',
8
+ ' ######## ########',
9
+ ' ######',
10
+ ' ##',
11
+ ' ###### #### ######',
12
+ '########## ######## ##########',
13
+ '########## ######## ##########',
14
+ ' ###### #### ######',
15
+ ' ##',
16
+ ' ######',
17
+ ' ####### #######',
18
+ ' ####### #######',
19
+ ' ####### #######',
20
+ ' ###### #####',
21
+ ].join('\n');
22
+
23
+ const ORANGE = '\x1b[38;2;255;92;40m';
24
+ const BOLD = '\x1b[1m';
25
+ const RESET = '\x1b[0m';
26
+
27
+ export function renderBanner(options = {}) {
28
+ const isTTY = options.isTTY ?? process.stdout.isTTY === true;
29
+ const term = options.term ?? process.env.TERM;
30
+ const color = options.color ?? colorEnabled;
31
+ const title = color ? `${BOLD}Subconscious CLI${RESET}` : 'Subconscious CLI';
32
+
33
+ // Avoid multi-line art in logs and pipes. Interactive NO_COLOR sessions
34
+ // still get the same logo without ANSI styling.
35
+ if (!isTTY || term === 'dumb') return ` ${title}`;
36
+
37
+ const logo = color ? `${ORANGE}${LOGO}${RESET}` : LOGO;
38
+ return `${logo}\n\n ${title}`;
39
+ }
package/bin/cli.js CHANGED
@@ -3,61 +3,229 @@
3
3
  /**
4
4
  * Subconscious CLI — log in, then launch coding agents on your hosted models.
5
5
  *
6
- * subconscious login | logout | whoami — manage your API key
7
- * subconscious <agent> [...args] — launch a coding agent
6
+ * subc login | update-key | logout | whoami — manage your API key
7
+ * subc <agent> [...args] — launch or configure a coding agent
8
8
  *
9
9
  * Auth lives in ./auth.js, the agent launcher + registry in ./agents.js.
10
10
  */
11
11
 
12
12
  import fs from 'node:fs/promises';
13
13
  import { c } from './colors.js';
14
- import { loginCommand, logoutCommand, whoamiCommand } from './auth.js';
15
- import { resolveAgent, runAgent, agentList } from './agents.js';
14
+ import { renderBanner } from './branding.js';
15
+ import {
16
+ loginCommand,
17
+ logoutCommand,
18
+ updateApiKeyCommand,
19
+ whoamiCommand,
20
+ } from './auth.js';
21
+ import {
22
+ resolveAgent,
23
+ runAgent,
24
+ agentList,
25
+ agentCommandName,
26
+ isAgentHelpRequest,
27
+ parseAgentAction,
28
+ } from './agents.js';
29
+ import {
30
+ configCommand,
31
+ DEFAULT_PROFILE,
32
+ loadProfile,
33
+ modelsCommand,
34
+ printConfigHelp,
35
+ updateUrlCommand,
36
+ validateProfileName,
37
+ } from './profiles.js';
38
+
39
+ function isHelpArg(arg) {
40
+ return arg === 'help' || arg === '-h' || arg === '--help';
41
+ }
16
42
 
17
43
  function printHelp() {
18
44
  const agents = agentList()
19
- .map(({ name, alias }) => ` ${c.cyan}${alias.padEnd(13)}${c.reset}${c.dim}Launch ${name}${c.reset}`)
45
+ .map(({ name, alias, action }) => ` ${c.cyan}${alias.padEnd(13)}${c.reset}${c.dim}${action} ${name}${c.reset}`)
20
46
  .join('\n');
21
47
 
22
- console.log(`
23
- ${c.magenta}${c.bold}Subconscious CLI${c.reset}
48
+ console.log(`${renderBanner()}
24
49
 
25
50
  ${c.bold}Usage${c.reset}
26
- ${c.cyan}subconscious${c.reset} <command> [...args]
51
+ ${c.cyan}subc${c.reset} <command> [...args]
52
+ ${c.cyan}subc${c.reset} <command> help
27
53
 
28
54
  ${c.bold}Auth${c.reset}
29
55
  ${c.cyan}login${c.reset} Authenticate and save your API key
56
+ ${c.cyan}update-key${c.reset} Replace the selected profile's API key
57
+ ${c.cyan}update-url${c.reset} Update the active profile's gateway URL automatically
30
58
  ${c.cyan}logout${c.reset} Remove saved credentials
31
59
  ${c.cyan}whoami${c.reset} Show current authentication status
32
60
 
61
+ ${c.bold}Profiles${c.reset}
62
+ ${c.cyan}config${c.reset} List profiles, or show/edit one with ${c.dim}-p${c.reset}
63
+ ${c.cyan}models${c.reset} List available Subconscious models
64
+
33
65
  ${c.bold}Coding agents${c.reset}
34
66
  ${agents}
35
67
 
36
68
  ${c.bold}Options${c.reset}
37
- ${c.dim}--model <id>${c.reset} Model to use (default subconscious/tim-qwen3.6-27b)
69
+ ${c.dim}--model <id>${c.reset} Model to use (default subconscious/glm-5.2)
70
+ ${c.dim}-p, --profile${c.reset} Select a profile (default: default)
38
71
  ${c.dim}-h, --help${c.reset} Show this help
39
72
  ${c.dim}-v, --version${c.reset} Show version
40
73
 
41
74
  ${c.bold}Examples${c.reset}
42
- ${c.dim}$${c.reset} subconscious login
43
- ${c.dim}$${c.reset} subconscious claude-code
44
- ${c.dim}$${c.reset} subconscious open-code --model subconscious/tim-qwen3.6-27b
75
+ ${c.dim}$${c.reset} subc login
76
+ ${c.dim}$${c.reset} subc config
77
+ ${c.dim}$${c.reset} subc config help
78
+ ${c.dim}$${c.reset} subc -p staging config
79
+ ${c.dim}$${c.reset} subc config edit vim
80
+ ${c.dim}$${c.reset} subc claude
81
+ ${c.dim}$${c.reset} subc claude help
82
+ ${c.dim}$${c.reset} subc cursor install
83
+ ${c.dim}$${c.reset} subc cursor uninstall
84
+ ${c.dim}$${c.reset} subc pi install
85
+ ${c.dim}$${c.reset} subc -p staging codex
45
86
 
46
- ${c.dim}Anything after the agent name is forwarded to the underlying CLI.${c.reset}
87
+ ${c.dim}Use subc <command> help for command-specific usage.${c.reset}
47
88
  `);
48
89
  }
49
90
 
50
- const authCommands = { login: loginCommand, logout: logoutCommand, whoami: whoamiCommand };
91
+ const COMMAND_HELP = {
92
+ login: `
93
+ Usage:
94
+ subc login
95
+ subc -p NAME login
96
+ subc login help
97
+
98
+ Authenticate and save an API key to the selected profile.
99
+ `,
100
+ logout: `
101
+ Usage:
102
+ subc logout
103
+ subc -p NAME logout
104
+ subc logout help
105
+
106
+ Remove the selected profile's saved API key. Non-secret settings are kept.
107
+ `,
108
+ whoami: `
109
+ Usage:
110
+ subc whoami
111
+ subc -p NAME whoami
112
+ subc whoami help
113
+
114
+ Show the current authentication status for the selected profile.
115
+ `,
116
+ 'update-key': `
117
+ Usage:
118
+ subc update-key <api-key>
119
+ subc -p NAME update-key <api-key>
120
+ subc update-key help
121
+
122
+ Replace the selected profile's shared API key.
123
+ `,
124
+ 'update-url': `
125
+ Usage:
126
+ subc update-url <gateway-url>
127
+ subc update-url help
128
+
129
+ Update the active profile's gateway URL.
130
+ `,
131
+ models: `
132
+ Usage:
133
+ subc models
134
+ subc models help
135
+
136
+ List available Subconscious models.
137
+ `,
138
+ };
139
+
140
+ function printRetiredSetup(args = []) {
141
+ const maybeAgent = resolveAgent(args[0]);
142
+ const command = maybeAgent ? agentCommandName(maybeAgent) : 'cursor';
143
+ console.error(`
144
+ ${c.red}subc setup is no longer used.${c.reset} Use the agent command instead:
145
+
146
+ ${c.cyan}subc ${command} install${c.reset}
147
+ ${c.cyan}subc ${command} uninstall${c.reset}
148
+ ${c.cyan}subc ${command} help${c.reset}
149
+ `);
150
+ }
151
+
152
+ function printRetiredSettings() {
153
+ console.error(`
154
+ ${c.red}subc settings is no longer used.${c.reset} Use:
155
+
156
+ ${c.cyan}subc config${c.reset} List profiles
157
+ ${c.cyan}subc -p NAME config${c.reset} Show a profile
158
+ ${c.cyan}subc -p NAME config edit${c.reset} Open the profile env file
159
+ ${c.cyan}subc config help${c.reset}
160
+ `);
161
+ }
162
+
163
+ const authCommands = {
164
+ login: loginCommand,
165
+ 'update-key': updateApiKeyCommand,
166
+ logout: logoutCommand,
167
+ whoami: whoamiCommand,
168
+ };
169
+
170
+ function extractProfile(argv) {
171
+ let profileName = process.env.SUBC_PROFILE?.trim() || DEFAULT_PROFILE;
172
+ let profileExplicit = false;
173
+ const args = [];
174
+ for (let i = 0; i < argv.length; i++) {
175
+ const arg = argv[i];
176
+ if (arg === '--') {
177
+ args.push(...argv.slice(i));
178
+ break;
179
+ }
180
+ if (arg === '--profile' || arg === '-p') {
181
+ const value = argv[++i];
182
+ if (!value) throw new Error(`${arg} requires a profile name`);
183
+ profileName = value;
184
+ profileExplicit = true;
185
+ continue;
186
+ }
187
+ if (arg.startsWith('--profile=')) {
188
+ profileName = arg.slice('--profile='.length);
189
+ profileExplicit = true;
190
+ continue;
191
+ }
192
+ args.push(arg);
193
+ }
194
+ validateProfileName(profileName);
195
+ return { args, profileName, profileExplicit };
196
+ }
197
+
198
+ function requireNamedProfile(profile) {
199
+ if (profile.name !== DEFAULT_PROFILE && !profile.exists) {
200
+ throw new Error(
201
+ `Profile '${profile.name}' does not exist. Create it with ` +
202
+ `subc -p ${profile.name} config --api-key KEY`,
203
+ );
204
+ }
205
+ }
51
206
 
52
207
  async function main() {
53
- const args = process.argv.slice(2);
208
+ const parsed = extractProfile(process.argv.slice(2));
209
+ const { args, profileName, profileExplicit } = parsed;
54
210
  const command = args[0];
55
211
 
56
- if (!command || command === '--help' || command === '-h') {
212
+ if (!command || command === '--help' || command === '-h' || (command === 'help' && !args[1])) {
57
213
  printHelp();
58
214
  return;
59
215
  }
60
216
 
217
+ if (command === 'help') {
218
+ if (!args[1] || isHelpArg(args[1])) {
219
+ printHelp();
220
+ return;
221
+ }
222
+ const agent = resolveAgent(args[1]);
223
+ if (!agent) throw new Error(`Unknown coding agent: ${args[1]}`);
224
+ const profile = await loadProfile(profileName);
225
+ await runAgent(agent, ['help'], { profile });
226
+ return;
227
+ }
228
+
61
229
  if (command === '--version' || command === '-v') {
62
230
  const pkgPath = new URL('../package.json', import.meta.url);
63
231
  const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
@@ -65,15 +233,67 @@ async function main() {
65
233
  return;
66
234
  }
67
235
 
236
+ if (command === 'setup') {
237
+ printRetiredSetup(args.slice(1));
238
+ process.exitCode = 1;
239
+ return;
240
+ }
241
+
242
+ if (command === 'settings') {
243
+ printRetiredSettings();
244
+ process.exitCode = 1;
245
+ return;
246
+ }
247
+
68
248
  const authHandler = authCommands[command];
69
249
  if (authHandler) {
70
- await authHandler(args.slice(1));
250
+ if (isHelpArg(args[1])) {
251
+ console.log(COMMAND_HELP[command]);
252
+ return;
253
+ }
254
+ const profile = await loadProfile(profileName);
255
+ await authHandler(args.slice(1), { profile, profileName });
256
+ return;
257
+ }
258
+
259
+ if (command === 'config') {
260
+ if (isHelpArg(args[1])) {
261
+ printConfigHelp();
262
+ return;
263
+ }
264
+ await configCommand(args.slice(1), profileName, { profileExplicit });
265
+ return;
266
+ }
267
+
268
+ if (command === 'models') {
269
+ if (isHelpArg(args[1])) {
270
+ console.log(COMMAND_HELP.models);
271
+ return;
272
+ }
273
+ modelsCommand();
274
+ return;
275
+ }
276
+
277
+ if (command === 'update-url') {
278
+ if (isHelpArg(args[1])) {
279
+ console.log(COMMAND_HELP['update-url']);
280
+ return;
281
+ }
282
+ await updateUrlCommand(args.slice(1), { profileName });
71
283
  return;
72
284
  }
73
285
 
74
286
  const agent = resolveAgent(command);
75
287
  if (agent) {
76
- await runAgent(agent, args.slice(1));
288
+ const agentArgs = args.slice(1);
289
+ const profile = await loadProfile(profileName);
290
+ if (!isAgentHelpRequest(agentArgs)) {
291
+ const action = parseAgentAction(agent, agentArgs);
292
+ if (action.action !== 'status' && action.action !== 'uninstall') {
293
+ requireNamedProfile(profile);
294
+ }
295
+ }
296
+ await runAgent(agent, agentArgs, { profile });
77
297
  return;
78
298
  }
79
299
 
package/bin/colors.js CHANGED
@@ -1,12 +1,24 @@
1
- // ANSI color helpers shared across the CLI.
1
+ // ANSI color helpers shared across the CLI. Keep redirected output machine
2
+ // readable and honor the standard NO_COLOR opt-out.
3
+ const noColor = Object.prototype.hasOwnProperty.call(process.env, 'NO_COLOR');
4
+ const forceColor =
5
+ process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== '0';
6
+
7
+ export const colorEnabled =
8
+ !noColor &&
9
+ process.env.TERM !== 'dumb' &&
10
+ (forceColor || process.stdout.isTTY === true);
11
+
12
+ const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : '');
13
+
2
14
  export const c = {
3
- reset: '\x1b[0m',
4
- bold: '\x1b[1m',
5
- dim: '\x1b[2m',
6
- cyan: '\x1b[36m',
7
- green: '\x1b[32m',
8
- red: '\x1b[31m',
9
- yellow: '\x1b[33m',
10
- magenta: '\x1b[35m',
11
- underline: '\x1b[4m',
15
+ reset: ansi(0),
16
+ bold: ansi(1),
17
+ dim: ansi(2),
18
+ cyan: ansi(36),
19
+ green: ansi(32),
20
+ red: ansi(31),
21
+ yellow: ansi(33),
22
+ magenta: ansi(35),
23
+ underline: ansi(4),
12
24
  };