subconscious-cli 0.2.0 → 0.3.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 +237 -54
- package/bin/agents.js +634 -114
- package/bin/auth.js +94 -20
- package/bin/branding.js +39 -0
- package/bin/cli.js +187 -17
- package/bin/colors.js +22 -10
- package/bin/profiles.js +852 -0
- package/bin/registry.generated.json +268 -0
- package/bin/runbook/README.md +25 -0
- package/bin/runbook/claude-code/install.sh +316 -0
- package/bin/runbook/claude-code/run.sh +164 -0
- package/bin/runbook/codex/hook.sh +112 -0
- package/bin/runbook/codex/hooks.json +29 -0
- package/bin/runbook/codex/install.sh +524 -0
- package/bin/runbook/codex/run.sh +264 -0
- package/bin/runbook/copilot/hook.sh +173 -0
- package/bin/runbook/copilot/hooks.json +19 -0
- package/bin/runbook/copilot/install.sh +469 -0
- package/bin/runbook/cursor/hook.sh +145 -0
- package/bin/runbook/cursor/hooks.json +17 -0
- package/bin/runbook/cursor/install.sh +259 -0
- package/bin/runbook/opencode/install.sh +298 -0
- package/bin/runbook/opencode/run.sh +107 -0
- package/bin/runbook/opencode/subconscious-compaction.ts +100 -0
- package/bin/runbook/pi/install.sh +282 -0
- package/bin/runbook/pi/run.sh +25 -0
- package/bin/runbook/pi/subconscious-compaction.ts +151 -0
- package/package.json +10 -5
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
|
|
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,9 +21,14 @@ 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
|
|
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
|
|
|
@@ -32,6 +38,17 @@ async function loadConfig() {
|
|
|
32
38
|
try {
|
|
33
39
|
const content = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
34
40
|
return JSON.parse(content);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code !== 'ENOENT' || !LEGACY_CONFIG_FILE) return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// One-way compatibility migration. Keep the old file untouched so this is
|
|
46
|
+
// recoverable, but all future writes go to ~/.subconscious.
|
|
47
|
+
try {
|
|
48
|
+
const content = await fs.readFile(LEGACY_CONFIG_FILE, 'utf-8');
|
|
49
|
+
const config = JSON.parse(content);
|
|
50
|
+
await saveConfig(config);
|
|
51
|
+
return config;
|
|
35
52
|
} catch {
|
|
36
53
|
return {};
|
|
37
54
|
}
|
|
@@ -48,13 +65,20 @@ async function saveConfig(config) {
|
|
|
48
65
|
* Resolve the active API key. The env var takes precedence over the saved
|
|
49
66
|
* config so CI and per-shell overrides win. Returns null when unauthenticated.
|
|
50
67
|
*/
|
|
51
|
-
export async function getApiKey() {
|
|
68
|
+
export async function getApiKey(profile) {
|
|
52
69
|
const envKey = process.env.SUBCONSCIOUS_API_KEY?.trim();
|
|
53
70
|
if (envKey) return { key: envKey, source: 'SUBCONSCIOUS_API_KEY env var' };
|
|
54
71
|
|
|
72
|
+
const profileKey = profile?.values?.API_KEY?.trim();
|
|
73
|
+
if (profileKey) return { key: profileKey, source: profile.path };
|
|
74
|
+
|
|
75
|
+
// Named profiles are isolated: an empty/missing key must not silently fall
|
|
76
|
+
// back to the default credential and send traffic to the wrong account.
|
|
77
|
+
if (profile?.name && profile.name !== DEFAULT_PROFILE) return null;
|
|
78
|
+
|
|
55
79
|
const config = await loadConfig();
|
|
56
80
|
if (config.subconscious_api_key) {
|
|
57
|
-
return { key: config.subconscious_api_key, source: '~/.
|
|
81
|
+
return { key: config.subconscious_api_key, source: '~/.subconscious/config.json' };
|
|
58
82
|
}
|
|
59
83
|
return null;
|
|
60
84
|
}
|
|
@@ -231,15 +255,20 @@ h1{font-size:15px;font-weight:600;margin-bottom:4px;letter-spacing:-.01em}
|
|
|
231
255
|
|
|
232
256
|
// ── Commands ────────────────────────────────────────────────────────────
|
|
233
257
|
|
|
234
|
-
export async function loginCommand() {
|
|
235
|
-
const
|
|
258
|
+
export async function loginCommand(_argv = [], options = {}) {
|
|
259
|
+
const profileName = options.profileName || DEFAULT_PROFILE;
|
|
260
|
+
const existing = await getApiKey(options.profile);
|
|
236
261
|
|
|
237
262
|
if (existing) {
|
|
263
|
+
const profile = await ensureProfile(profileName, existing.key);
|
|
264
|
+
const logout =
|
|
265
|
+
profileName === DEFAULT_PROFILE ? 'subc logout' : `subc --profile ${profileName} logout`;
|
|
238
266
|
const masked = existing.key.slice(0, 8) + '...' + existing.key.slice(-4);
|
|
239
267
|
console.log(`\n${c.yellow}Already logged in.${c.reset}`);
|
|
240
268
|
console.log(` Key: ${c.dim}${masked}${c.reset}`);
|
|
269
|
+
console.log(` Profile: ${c.dim}${profile.path}${c.reset}`);
|
|
241
270
|
console.log(
|
|
242
|
-
`\n Run ${c.cyan}
|
|
271
|
+
`\n Run ${c.cyan}${logout}${c.reset} first to switch accounts.\n`,
|
|
243
272
|
);
|
|
244
273
|
return;
|
|
245
274
|
}
|
|
@@ -283,14 +312,23 @@ export async function loginCommand() {
|
|
|
283
312
|
clearInterval(spinner);
|
|
284
313
|
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
285
314
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
if (profileName === DEFAULT_PROFILE) {
|
|
316
|
+
const config = await loadConfig();
|
|
317
|
+
config.subconscious_api_key = result.token;
|
|
318
|
+
await saveConfig(config);
|
|
319
|
+
}
|
|
320
|
+
const profile = await ensureProfile(profileName, result.token);
|
|
289
321
|
|
|
290
322
|
const masked = result.token.slice(0, 8) + '...' + result.token.slice(-4);
|
|
291
323
|
console.log(` ${c.green}${c.bold}✓ Logged in successfully!${c.reset}`);
|
|
292
324
|
console.log(` ${c.dim}Key: ${masked}${c.reset}`);
|
|
293
|
-
|
|
325
|
+
if (profileName === DEFAULT_PROFILE) {
|
|
326
|
+
console.log(` ${c.dim}Saved to ~/.subconscious/config.json${c.reset}`);
|
|
327
|
+
}
|
|
328
|
+
console.log(` ${c.dim}Runbook profile: ${profile.path}${c.reset}`);
|
|
329
|
+
const setup =
|
|
330
|
+
profileName === DEFAULT_PROFILE ? 'subc setup' : `subc --profile ${profileName} setup`;
|
|
331
|
+
console.log(` ${c.dim}Run ${setup} once to configure all coding agents.${c.reset}`);
|
|
294
332
|
console.log();
|
|
295
333
|
} catch (error) {
|
|
296
334
|
clearInterval(spinner);
|
|
@@ -300,29 +338,65 @@ export async function loginCommand() {
|
|
|
300
338
|
}
|
|
301
339
|
}
|
|
302
340
|
|
|
303
|
-
export async function
|
|
341
|
+
export async function updateApiKeyCommand(argv = [], options = {}) {
|
|
342
|
+
if (argv.length !== 1 || !argv[0]?.trim()) {
|
|
343
|
+
throw new Error('Usage: subc update-key <api-key>');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const key = argv[0].trim();
|
|
347
|
+
const profileName = options.profileName || DEFAULT_PROFILE;
|
|
348
|
+
const profile = await ensureProfile(profileName, key);
|
|
349
|
+
|
|
350
|
+
if (profileName === DEFAULT_PROFILE) {
|
|
351
|
+
const config = await loadConfig();
|
|
352
|
+
config.subconscious_api_key = key;
|
|
353
|
+
await saveConfig(config);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const masked = key.length <= 12 ? '********' : `${key.slice(0, 8)}...${key.slice(-4)}`;
|
|
357
|
+
console.log(`\n ${c.green}${c.bold}✓ API key updated.${c.reset}`);
|
|
358
|
+
console.log(` ${c.dim}Profile: ${profile.path}${c.reset}`);
|
|
359
|
+
console.log(` ${c.dim}Key: ${masked}${c.reset}`);
|
|
360
|
+
if (process.env.SUBCONSCIOUS_API_KEY?.trim()) {
|
|
361
|
+
console.log(
|
|
362
|
+
`\n ${c.yellow}SUBCONSCIOUS_API_KEY is set and will override this saved key.${c.reset}`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
console.log();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function logoutCommand(_argv = [], options = {}) {
|
|
369
|
+
const profileName = options.profileName || DEFAULT_PROFILE;
|
|
304
370
|
const config = await loadConfig();
|
|
371
|
+
const clearedProfile = await clearProfileApiKey(profileName);
|
|
372
|
+
const clearSavedConfig = profileName === DEFAULT_PROFILE && config.subconscious_api_key;
|
|
305
373
|
|
|
306
|
-
if (!
|
|
374
|
+
if (!clearSavedConfig && !clearedProfile) {
|
|
307
375
|
console.log(`\n ${c.dim}Not logged in.${c.reset}\n`);
|
|
308
376
|
return;
|
|
309
377
|
}
|
|
310
378
|
|
|
311
|
-
|
|
312
|
-
|
|
379
|
+
if (clearSavedConfig) {
|
|
380
|
+
delete config.subconscious_api_key;
|
|
381
|
+
await saveConfig(config);
|
|
382
|
+
}
|
|
313
383
|
|
|
314
384
|
console.log(
|
|
315
|
-
`\n ${c.green}✓${c.reset} Logged out
|
|
385
|
+
`\n ${c.green}✓${c.reset} Logged out of profile '${profileName}'.${c.reset}\n`,
|
|
316
386
|
);
|
|
317
387
|
}
|
|
318
388
|
|
|
319
|
-
export async function whoamiCommand() {
|
|
320
|
-
const auth = await getApiKey();
|
|
389
|
+
export async function whoamiCommand(_argv = [], options = {}) {
|
|
390
|
+
const auth = await getApiKey(options.profile);
|
|
391
|
+
const profileFlag =
|
|
392
|
+
options.profileName && options.profileName !== DEFAULT_PROFILE
|
|
393
|
+
? `--profile ${options.profileName} `
|
|
394
|
+
: '';
|
|
321
395
|
|
|
322
396
|
if (!auth) {
|
|
323
397
|
console.log(`\n ${c.dim}Not logged in.${c.reset}`);
|
|
324
398
|
console.log(
|
|
325
|
-
` Run ${c.cyan}
|
|
399
|
+
` Run ${c.cyan}subc ${profileFlag}login${c.reset} to get started.\n`,
|
|
326
400
|
);
|
|
327
401
|
return;
|
|
328
402
|
}
|
|
@@ -353,7 +427,7 @@ export async function whoamiCommand() {
|
|
|
353
427
|
console.log(` ${c.dim}Source: ${source}${c.reset}`);
|
|
354
428
|
console.log();
|
|
355
429
|
console.log(
|
|
356
|
-
` Run ${c.cyan}
|
|
430
|
+
` Run ${c.cyan}subc ${profileFlag}logout${c.reset} then ${c.cyan}subc ${profileFlag}login${c.reset} to re-authenticate.`,
|
|
357
431
|
);
|
|
358
432
|
}
|
|
359
433
|
} catch {
|
package/bin/branding.js
ADDED
|
@@ -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,54 +3,133 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Subconscious CLI — log in, then launch coding agents on your hosted models.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
15
|
-
import {
|
|
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
|
+
parseSetupRequest,
|
|
26
|
+
isAgentHelpRequest,
|
|
27
|
+
} from './agents.js';
|
|
28
|
+
import {
|
|
29
|
+
configCommand,
|
|
30
|
+
DEFAULT_PROFILE,
|
|
31
|
+
loadProfile,
|
|
32
|
+
modelsCommand,
|
|
33
|
+
updateUrlCommand,
|
|
34
|
+
validateProfileName,
|
|
35
|
+
} from './profiles.js';
|
|
16
36
|
|
|
17
37
|
function printHelp() {
|
|
18
38
|
const agents = agentList()
|
|
19
|
-
.map(({ name, alias }) => ` ${c.cyan}${alias.padEnd(13)}${c.reset}${c.dim}
|
|
39
|
+
.map(({ name, alias, action }) => ` ${c.cyan}${alias.padEnd(13)}${c.reset}${c.dim}${action} ${name}${c.reset}`)
|
|
20
40
|
.join('\n');
|
|
21
41
|
|
|
22
|
-
console.log(
|
|
23
|
-
${c.magenta}${c.bold}Subconscious CLI${c.reset}
|
|
42
|
+
console.log(`${renderBanner()}
|
|
24
43
|
|
|
25
44
|
${c.bold}Usage${c.reset}
|
|
26
|
-
${c.cyan}
|
|
45
|
+
${c.cyan}subc${c.reset} <command> [...args]
|
|
27
46
|
|
|
28
47
|
${c.bold}Auth${c.reset}
|
|
29
48
|
${c.cyan}login${c.reset} Authenticate and save your API key
|
|
49
|
+
${c.cyan}update-key${c.reset} Replace the selected profile's API key
|
|
50
|
+
${c.cyan}update-url${c.reset} Update the active profile's gateway URL automatically
|
|
30
51
|
${c.cyan}logout${c.reset} Remove saved credentials
|
|
31
52
|
${c.cyan}whoami${c.reset} Show current authentication status
|
|
32
53
|
|
|
54
|
+
${c.bold}Setup and profiles${c.reset}
|
|
55
|
+
${c.cyan}help <agent>${c.reset} Show coding-agent integration help and settings
|
|
56
|
+
${c.cyan}setup${c.reset} Configure all or one coding-agent integration
|
|
57
|
+
${c.cyan}config${c.reset} Show or update the selected runbook profile
|
|
58
|
+
${c.cyan}settings${c.reset} Edit profile and per-agent settings interactively
|
|
59
|
+
${c.cyan}models${c.reset} List available Subconscious models
|
|
60
|
+
|
|
33
61
|
${c.bold}Coding agents${c.reset}
|
|
34
62
|
${agents}
|
|
35
63
|
|
|
36
64
|
${c.bold}Options${c.reset}
|
|
37
|
-
${c.dim}--model <id>${c.reset} Model to use (default subconscious/
|
|
65
|
+
${c.dim}--model <id>${c.reset} Model to use (default subconscious/glm-5.2)
|
|
66
|
+
${c.dim}-p, --profile${c.reset} Select a profile (default: default)
|
|
38
67
|
${c.dim}-h, --help${c.reset} Show this help
|
|
39
68
|
${c.dim}-v, --version${c.reset} Show version
|
|
40
69
|
|
|
41
70
|
${c.bold}Examples${c.reset}
|
|
42
|
-
${c.dim}$${c.reset}
|
|
43
|
-
${c.dim}$${c.reset}
|
|
44
|
-
${c.dim}$${c.reset}
|
|
71
|
+
${c.dim}$${c.reset} subc login
|
|
72
|
+
${c.dim}$${c.reset} subc update-key sk-...
|
|
73
|
+
${c.dim}$${c.reset} subc update-url https://api.subconscious.dev
|
|
74
|
+
${c.dim}$${c.reset} subc setup
|
|
75
|
+
${c.dim}$${c.reset} subc settings
|
|
76
|
+
${c.dim}$${c.reset} subc models
|
|
77
|
+
${c.dim}$${c.reset} subc help codex
|
|
78
|
+
${c.dim}$${c.reset} subc claude
|
|
79
|
+
${c.dim}$${c.reset} subc --profile staging codex
|
|
80
|
+
${c.dim}$${c.reset} subc codex --model subconscious/glm-5.2
|
|
45
81
|
|
|
46
|
-
${c.dim}
|
|
82
|
+
${c.dim}Arguments are forwarded to terminal agents or their runbook setup.${c.reset}
|
|
47
83
|
`);
|
|
48
84
|
}
|
|
49
85
|
|
|
50
|
-
const authCommands = {
|
|
86
|
+
const authCommands = {
|
|
87
|
+
login: loginCommand,
|
|
88
|
+
'update-key': updateApiKeyCommand,
|
|
89
|
+
logout: logoutCommand,
|
|
90
|
+
whoami: whoamiCommand,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function extractProfile(argv) {
|
|
94
|
+
let profileName =
|
|
95
|
+
process.env.SUBC_PROFILE?.trim() ||
|
|
96
|
+
process.env.MBTA_PROFILE?.trim() ||
|
|
97
|
+
DEFAULT_PROFILE;
|
|
98
|
+
const args = [];
|
|
99
|
+
for (let i = 0; i < argv.length; i++) {
|
|
100
|
+
const arg = argv[i];
|
|
101
|
+
if (arg === '--') {
|
|
102
|
+
args.push(...argv.slice(i));
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
if (arg === '--profile' || arg === '-p') {
|
|
106
|
+
const value = argv[++i];
|
|
107
|
+
if (!value) throw new Error(`${arg} requires a profile name`);
|
|
108
|
+
profileName = value;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (arg.startsWith('--profile=')) {
|
|
112
|
+
profileName = arg.slice('--profile='.length);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
args.push(arg);
|
|
116
|
+
}
|
|
117
|
+
validateProfileName(profileName);
|
|
118
|
+
return { args, profileName };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function requireNamedProfile(profile) {
|
|
122
|
+
if (profile.name !== DEFAULT_PROFILE && !profile.exists) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Profile '${profile.name}' does not exist. Create it with ` +
|
|
125
|
+
`subc --profile ${profile.name} config --api-key KEY`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
51
129
|
|
|
52
130
|
async function main() {
|
|
53
|
-
const
|
|
131
|
+
const parsed = extractProfile(process.argv.slice(2));
|
|
132
|
+
const { args, profileName } = parsed;
|
|
54
133
|
const command = args[0];
|
|
55
134
|
|
|
56
135
|
if (!command || command === '--help' || command === '-h') {
|
|
@@ -58,6 +137,18 @@ async function main() {
|
|
|
58
137
|
return;
|
|
59
138
|
}
|
|
60
139
|
|
|
140
|
+
if (command === 'help') {
|
|
141
|
+
if (!args[1]) {
|
|
142
|
+
printHelp();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const agent = resolveAgent(args[1]);
|
|
146
|
+
if (!agent) throw new Error(`Unknown coding agent: ${args[1]}`);
|
|
147
|
+
const profile = await loadProfile(profileName);
|
|
148
|
+
await runAgent(agent, ['help'], { profile });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
61
152
|
if (command === '--version' || command === '-v') {
|
|
62
153
|
const pkgPath = new URL('../package.json', import.meta.url);
|
|
63
154
|
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
@@ -67,13 +158,92 @@ async function main() {
|
|
|
67
158
|
|
|
68
159
|
const authHandler = authCommands[command];
|
|
69
160
|
if (authHandler) {
|
|
70
|
-
await
|
|
161
|
+
const profile = await loadProfile(profileName);
|
|
162
|
+
await authHandler(args.slice(1), { profile, profileName });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (command === 'config') {
|
|
167
|
+
await configCommand(args.slice(1), profileName);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (command === 'settings') {
|
|
172
|
+
if (['help', '-h', '--help'].includes(args[1])) {
|
|
173
|
+
console.log(`
|
|
174
|
+
Usage:
|
|
175
|
+
subc settings
|
|
176
|
+
subc --profile NAME settings
|
|
177
|
+
subc --profile NAME config interactive
|
|
178
|
+
|
|
179
|
+
Interactively choose or create a profile, then edit shared or per-agent settings.
|
|
180
|
+
`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (args.length > 1) throw new Error('Usage: subc [--profile NAME] settings');
|
|
184
|
+
await configCommand(['interactive'], profileName);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (command === 'models') {
|
|
189
|
+
modelsCommand();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (command === 'update-url') {
|
|
194
|
+
await updateUrlCommand(args.slice(1), { profileName });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (command === 'setup') {
|
|
199
|
+
const setupArgs = args.slice(1);
|
|
200
|
+
if (setupArgs[0] === '-h' || setupArgs[0] === '--help') {
|
|
201
|
+
console.log(`
|
|
202
|
+
Usage:
|
|
203
|
+
subc setup [install|status|uninstall]
|
|
204
|
+
subc setup AGENT [install|status|uninstall] [agent options]
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
subc setup Configure every coding-agent integration
|
|
208
|
+
subc setup status Show every integration's setup status
|
|
209
|
+
subc setup codex Configure only Codex
|
|
210
|
+
subc setup codex status Show only Codex's setup status
|
|
211
|
+
subc setup codex --subagents Configure Codex's legacy subagent mode
|
|
212
|
+
subc setup codex env Print persistent Codex exports for sourcing
|
|
213
|
+
`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const request = parseSetupRequest(setupArgs);
|
|
217
|
+
const profile = await loadProfile(profileName);
|
|
218
|
+
const targetHelp = request.targeted && isAgentHelpRequest(request.args);
|
|
219
|
+
const persistentHelper = ['use', 'env', 'unset'].includes(request.action);
|
|
220
|
+
const oneOffApiKey = request.targeted && request.args.includes('--api-key');
|
|
221
|
+
if (!targetHelp && !persistentHelper && !oneOffApiKey) requireNamedProfile(profile);
|
|
222
|
+
const failures = [];
|
|
223
|
+
for (const agent of request.agents) {
|
|
224
|
+
const code = await runAgent(agent, request.args, { profile, setup: true });
|
|
225
|
+
if (code) failures.push(agent.name);
|
|
226
|
+
}
|
|
227
|
+
if (failures.length) {
|
|
228
|
+
throw new Error(`Setup failed for: ${failures.join(', ')}`);
|
|
229
|
+
}
|
|
230
|
+
if (targetHelp || persistentHelper) return;
|
|
231
|
+
const subject = request.targeted ? request.agents[0].name : 'Coding-agent';
|
|
232
|
+
const message =
|
|
233
|
+
request.action === 'status'
|
|
234
|
+
? `${subject} status check complete.`
|
|
235
|
+
: request.action === 'uninstall'
|
|
236
|
+
? `${subject} integration${request.targeted ? '' : 's'} removed.`
|
|
237
|
+
: `${subject} setup complete.`;
|
|
238
|
+
console.log(`\n ${c.green}${c.bold}✓ ${message}${c.reset}\n`);
|
|
71
239
|
return;
|
|
72
240
|
}
|
|
73
241
|
|
|
74
242
|
const agent = resolveAgent(command);
|
|
75
243
|
if (agent) {
|
|
76
|
-
await
|
|
244
|
+
const profile = await loadProfile(profileName);
|
|
245
|
+
if (!isAgentHelpRequest(args.slice(1))) requireNamedProfile(profile);
|
|
246
|
+
await runAgent(agent, args.slice(1), { profile });
|
|
77
247
|
return;
|
|
78
248
|
}
|
|
79
249
|
|
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:
|
|
4
|
-
bold:
|
|
5
|
-
dim:
|
|
6
|
-
cyan:
|
|
7
|
-
green:
|
|
8
|
-
red:
|
|
9
|
-
yellow:
|
|
10
|
-
magenta:
|
|
11
|
-
underline:
|
|
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
|
};
|