subconscious-cli 0.2.1 → 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/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,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 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
 
@@ -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: '~/.subcon/config.json' };
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 existing = await getApiKey();
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}subconscious logout${c.reset} first to switch accounts.\n`,
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
- const config = await loadConfig();
287
- config.subconscious_api_key = result.token;
288
- await saveConfig(config);
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
- console.log(` ${c.dim}Saved to ~/.subcon/config.json${c.reset}`);
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 logoutCommand() {
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 (!config.subconscious_api_key) {
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
- delete config.subconscious_api_key;
312
- await saveConfig(config);
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. API key removed from ${c.dim}~/.subcon/config.json${c.reset}\n`,
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}subconscious login${c.reset} to get started.\n`,
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}subconscious logout${c.reset} then ${c.cyan}subconscious login${c.reset} to re-authenticate.`,
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 {
@@ -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
- * 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
+ 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}Launch ${name}${c.reset}`)
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}subconscious${c.reset} <command> [...args]
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/tim-qwen3.6-27b)
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} subconscious login
43
- ${c.dim}$${c.reset} subconscious claude-code
44
- ${c.dim}$${c.reset} subconscious open-code --model subconscious/tim-qwen3.6-27b
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}Anything after the agent name is forwarded to the underlying CLI.${c.reset}
82
+ ${c.dim}Arguments are forwarded to terminal agents or their runbook setup.${c.reset}
47
83
  `);
48
84
  }
49
85
 
50
- const authCommands = { login: loginCommand, logout: logoutCommand, whoami: whoamiCommand };
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 args = process.argv.slice(2);
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 authHandler(args.slice(1));
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 runAgent(agent, args.slice(1));
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: '\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
  };