@nexus-cortex/cli 4.33.0 → 4.34.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/cortex.js CHANGED
@@ -25,8 +25,7 @@
25
25
  import { spawn, spawnSync } from 'child_process';
26
26
  import { fileURLToPath } from 'url';
27
27
  import { dirname, join, resolve } from 'path';
28
- import { existsSync, readFileSync, realpathSync, mkdirSync, writeFileSync } from 'fs';
29
- import { homedir } from 'os';
28
+ import { existsSync, readFileSync, realpathSync } from 'fs';
30
29
  import { createRequire } from 'module';
31
30
 
32
31
  const __cortex_require = createRequire(import.meta.url);
@@ -62,65 +61,6 @@ if (!process.env.CORTEX_ROOT) {
62
61
  const BASE_PORT = process.env.PORT || '4000';
63
62
  const BASE_URL = process.env.CORTEX_URL || `http://localhost:${BASE_PORT}`;
64
63
 
65
- // ── Self-update (transparent) ─────────────────────────────────────
66
- // No silent background install (the old approach failed invisibly and locked itself).
67
- // Instead: a one-line "update available" notice when a newer release exists (read from
68
- // a tiny cached version check), and `cortex --update` to update on the spot with visible
69
- // npm output. Silence the notice with CORTEX_NO_UPDATE_NOTICE=true.
70
- const UPDATE_CACHE = join(homedir(), '.cortex', '.update-check');
71
- const PKG_VERSION = (() => {
72
- try { return JSON.parse(readFileSync(join(__cortex_dirname, '..', 'package.json'), 'utf8')).version; }
73
- catch { return null; }
74
- })();
75
-
76
- function semverGt(a, b) {
77
- const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
78
- const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
79
- for (let i = 0; i < 3; i++) {
80
- if ((pa[i] || 0) > (pb[i] || 0)) return true;
81
- if ((pa[i] || 0) < (pb[i] || 0)) return false;
82
- }
83
- return false;
84
- }
85
-
86
- async function fetchLatestVersion() {
87
- try {
88
- const https = await import('node:https');
89
- return await new Promise((resolve) => {
90
- const req = https.get('https://registry.npmjs.org/nexus-cortex/latest', { timeout: 3000 }, (res) => {
91
- let body = '';
92
- res.on('data', (c) => { body += c; });
93
- res.on('end', () => { try { resolve(JSON.parse(body).version); } catch { resolve(null); } });
94
- });
95
- req.on('error', () => resolve(null));
96
- req.on('timeout', () => { req.destroy(); resolve(null); });
97
- });
98
- } catch { return null; }
99
- }
100
-
101
- // Show the notice from cache (instant, no network); refresh the cache in the background
102
- // when stale — a tiny registry GET, NEVER an install. Best-effort; never blocks/throws.
103
- function notifyUpdateAvailable() {
104
- try {
105
- if (String(process.env.CORTEX_NO_UPDATE_NOTICE).toLowerCase() === 'true') return;
106
- if (!PKG_VERSION || !__cortex_dirname.includes('node_modules')) return;
107
- let cache = {};
108
- try { cache = JSON.parse(readFileSync(UPDATE_CACHE, 'utf8')); } catch { /* none/legacy — refresh below */ }
109
- if (cache.latest && semverGt(cache.latest, PKG_VERSION)) {
110
- process.stderr.write(`\n ↑ Update available: ${PKG_VERSION} → ${cache.latest} · run \`cortex --update\`\n\n`);
111
- }
112
- const THROTTLE = 12 * 60 * 60 * 1000;
113
- if (!cache.lastCheck || Date.now() - cache.lastCheck > THROTTLE) {
114
- fetchLatestVersion().then((latest) => {
115
- try {
116
- mkdirSync(dirname(UPDATE_CACHE), { recursive: true });
117
- writeFileSync(UPDATE_CACHE, JSON.stringify({ lastCheck: Date.now(), latest: latest || cache.latest || null }));
118
- } catch { /* ignore */ }
119
- }).catch(() => {});
120
- }
121
- } catch { /* never let the notice affect the CLI */ }
122
- }
123
-
124
64
  let serverProcess = null;
125
65
 
126
66
  // ── Argument parsing ──────────────────────────────────────────────
@@ -140,129 +80,6 @@ if (args.includes('--version') || args.includes('-v')) {
140
80
  process.exit(0);
141
81
  }
142
82
 
143
- // ── Self-update command ───────────────────────────────────────────
144
- // `cortex --update` — update the global install on the spot, with visible npm output.
145
- if (args.includes('--update')) {
146
- process.stdout.write(`Updating nexus-cortex (current: ${PKG_VERSION || 'unknown'})…\n\n`);
147
- const res = spawnSync('npm', ['install', '-g', 'nexus-cortex@latest'], { stdio: 'inherit' });
148
- if (res.status === 0) {
149
- try { mkdirSync(dirname(UPDATE_CACHE), { recursive: true }); writeFileSync(UPDATE_CACHE, JSON.stringify({ lastCheck: Date.now(), latest: null })); } catch { /* ignore */ }
150
- process.stdout.write('\n✓ Updated. Run `cortex --version` to confirm.\n');
151
- process.exit(0);
152
- }
153
- process.stderr.write('\nUpdate failed — see the npm output above. If it is a permissions error, try:\n sudo npm install -g nexus-cortex@latest\n');
154
- process.exit(res.status || 1);
155
- }
156
-
157
- // `cortex --uninstall` — remove the global install. Config/keys at ~/.cortex are LEFT
158
- // in place (so you don't lose your API key by accident); the message says how to remove them.
159
- if (args.includes('--uninstall')) {
160
- process.stdout.write('Uninstalling nexus-cortex…\n\n');
161
- const res = spawnSync('npm', ['uninstall', '-g', 'nexus-cortex'], { stdio: 'inherit' });
162
- if (res.status === 0) {
163
- process.stdout.write('\n✓ Uninstalled. Your config and API key remain at ~/.cortex — to remove those too:\n rm -rf ~/.cortex\n');
164
- process.exit(0);
165
- }
166
- process.stderr.write('\nUninstall failed — see the npm output above. If it is a permissions error, try:\n sudo npm uninstall -g nexus-cortex\n');
167
- process.exit(res.status || 1);
168
- }
169
-
170
- // Update-available notice (skip for machine-readable / quiet output).
171
- if (!args.includes('--json') && !args.includes('--quiet') && !args.includes('-q')) {
172
- notifyUpdateAvailable();
173
- }
174
-
175
- // ── Setup wizard (interactive API-key onboarding) ─────────────────
176
- // Global config lives at ~/.cortex/.env so a global npm install works from ANY
177
- // folder (the server loads it). Runs on `cortex config init` and on first run
178
- // when no key is configured anywhere.
179
- const PROVIDERS = [
180
- { name: 'Anthropic (Claude)', keyVar: 'ANTHROPIC_API_KEY', model: 'claude-sonnet-4-6', hint: 'sk-ant-…' },
181
- { name: 'OpenAI (GPT)', keyVar: 'OPENAI_API_KEY', model: 'gpt-5-mini', hint: 'sk-…' },
182
- { name: 'Google (Gemini)', keyVar: 'GEMINI_API_KEY', model: 'gemini-2.5-flash', hint: 'AIza…' },
183
- { name: 'DeepSeek', keyVar: 'DEEPSEEK_API_KEY', model: 'deepseek-v4-pro', hint: 'sk-…' },
184
- { name: 'xAI (Grok)', keyVar: 'XAI_API_KEY', model: 'grok-4.3', hint: 'xai-…' },
185
- ];
186
- const KEY_VARS = [...PROVIDERS.map((p) => p.keyVar), 'GOOGLE_API_KEY'];
187
- const GLOBAL_ENV = join(homedir(), '.cortex', '.env');
188
-
189
- function hasApiKey() {
190
- if (KEY_VARS.some((k) => (process.env[k] || '').trim())) return true;
191
- for (const f of [GLOBAL_ENV, join(process.cwd(), '.env')]) {
192
- try {
193
- const txt = readFileSync(f, 'utf8');
194
- if (KEY_VARS.some((k) => new RegExp('^' + k + '=\\S', 'm').test(txt))) return true;
195
- } catch { /* file absent */ }
196
- }
197
- return false;
198
- }
199
-
200
- function writeGlobalEnv(kv) {
201
- mkdirSync(dirname(GLOBAL_ENV), { recursive: true });
202
- let lines = [];
203
- try { lines = readFileSync(GLOBAL_ENV, 'utf8').split('\n').filter((l) => l.length); } catch { /* new file */ }
204
- for (const [k, v] of Object.entries(kv)) {
205
- const i = lines.findIndex((l) => l.startsWith(k + '='));
206
- if (i >= 0) lines[i] = `${k}=${v}`; else lines.push(`${k}=${v}`);
207
- }
208
- writeFileSync(GLOBAL_ENV, lines.join('\n') + '\n', { mode: 0o600 }); // user-only — holds a secret
209
- }
210
-
211
- async function runSetupWizard() {
212
- if (!process.stdin.isTTY) {
213
- process.stderr.write(
214
- '\n No API key configured. Run `cortex config init` for interactive setup, or set one:\n' +
215
- ' export ANTHROPIC_API_KEY=sk-ant-…\n' +
216
- ' export DEFAULT_MODEL_ID=claude-sonnet-4-6\n\n',
217
- );
218
- process.exit(1);
219
- }
220
- const { createInterface } = await import('node:readline/promises');
221
- const rl = createInterface({ input: process.stdin, output: process.stdout });
222
- try {
223
- process.stdout.write('\n Welcome to Nexus Cortex — quick setup (~30s).\n\n Which AI provider?\n');
224
- PROVIDERS.forEach((p, i) => process.stdout.write(` ${i + 1}) ${p.name}\n`));
225
- let choice;
226
- for (;;) {
227
- const n = parseInt((await rl.question(`\n Choose 1-${PROVIDERS.length}: `)).trim(), 10);
228
- if (n >= 1 && n <= PROVIDERS.length) { choice = PROVIDERS[n - 1]; break; }
229
- process.stdout.write(` Please enter a number 1-${PROVIDERS.length}.\n`);
230
- }
231
- let key = '';
232
- while (!key) {
233
- key = (await rl.question(`\n Paste your ${choice.name} API key (${choice.hint}): `)).trim();
234
- if (!key) process.stdout.write(' An API key is required.\n');
235
- }
236
- const model = (await rl.question(`\n Default model [${choice.model}]: `)).trim() || choice.model;
237
- writeGlobalEnv({ [choice.keyVar]: key, DEFAULT_MODEL_ID: model });
238
- process.stdout.write(`\n ✓ Saved to ${GLOBAL_ENV}\n You're set — try: cortex "what is 2 + 2?"\n\n`);
239
- } catch {
240
- // Ctrl-C / Ctrl-D / closed input — exit cleanly instead of dumping a stack trace.
241
- process.stdout.write('\n Setup cancelled. Run `cortex config init` any time to finish.\n');
242
- rl.close();
243
- process.exit(1);
244
- } finally {
245
- rl.close();
246
- }
247
- }
248
-
249
- // `cortex config init` — interactive setup (intercept before the Commander handoff).
250
- if (args[0] === 'config' && args[1] === 'init') {
251
- await runSetupWizard();
252
- process.exit(0);
253
- }
254
- // `cortex config` (no subcommand) — interactive config panel. `config get/set/...` still
255
- // delegate to the Commander CLI below.
256
- if (args[0] === 'config' && !args[1]) {
257
- await runConfigPanel();
258
- process.exit(0);
259
- }
260
- // `cortex docs [name]` — print the docs (the same ones the agent can read).
261
- if (args[0] === 'docs') {
262
- await showDocs(args.slice(1).join(' '));
263
- process.exit(0);
264
- }
265
-
266
83
  // ── Headless Commander delegation ─────────────────────────────────
267
84
  // `cortex` is primarily the HTTP chat/PR client; the headless Commander program
268
85
  // (autoresearch, models, message, mcp, config, …) lives in dist/index.js. If the
@@ -272,6 +89,7 @@ if (args[0] === 'docs') {
272
89
  const COMMANDER_SUBCOMMANDS = new Set([
273
90
  'autoresearch', 'models', 'message', 'mcp', 'config', 'permissions', 'context',
274
91
  'tools', 'middleware', 'artifact', 'cache', 'system-messages', 'tmux',
92
+ 'update', 'uninstall',
275
93
  ]);
276
94
  const __firstPositional = args.find((a) => !a.startsWith('-'));
277
95
  if (__firstPositional && COMMANDER_SUBCOMMANDS.has(__firstPositional)) {
@@ -360,10 +178,7 @@ if (showHelp) {
360
178
  cortex — Natural language interface to the Nexus Cortex library
361
179
 
362
180
  USAGE:
363
- cortex Interactive chat (slash commands: /help /config /docs /model)
364
181
  cortex "your prompt here" Send a message (continues current session)
365
- cortex config Interactive config panel (keys, model, settings)
366
- cortex docs [name] Print the docs (list, or e.g. cortex docs authentication)
367
182
  cortex agent "<task>" Autonomous one-shot agent run (see below)
368
183
  cortex --new "start fresh" Start new session, then send message
369
184
  cortex --model MODEL_ID "prompt" Use a specific model
@@ -396,9 +211,6 @@ FLAGS:
396
211
  --timeout MS Request timeout in ms (default: 600000 = 10 min)
397
212
  --idle-timeout SECS Auto-shutdown server after N seconds of inactivity
398
213
  --cwd DIR (agent) Run in DIR — the agent's file tools operate there
399
- --update Update nexus-cortex to the latest version (npm i -g)
400
- (a notice appears when you're behind; CORTEX_NO_UPDATE_NOTICE=true to silence)
401
- --uninstall Remove the global nexus-cortex install (keeps ~/.cortex config)
402
214
  --shutdown Stop the running server and exit
403
215
  --tmux List active tmux sessions with dashboard URLs
404
216
  --stats Show current session statistics
@@ -538,168 +350,6 @@ async function fetchJSON(path, options = {}, timeoutMs = 30000) {
538
350
 
539
351
  // ── Commands ──────────────────────────────────────────────────────
540
352
 
541
- // Interactive chat REPL — `cortex` with no message drops you here so you can talk
542
- // line-by-line without the shell mangling ?/*/quotes. The server keeps the session,
543
- // so it's multi-turn. Reuses the same /v1/messages send as the one-shot path.
544
- // ── Config panel + docs (shared by /config & /docs and `cortex config`/`cortex docs`) ──
545
- // Docs ship alongside the install; dev runs from the monorepo. CORTEX_ROOT covers the
546
- // npm-install case (set to the package root that holds the .cortex scaffold + docs).
547
- function resolveDocsDir() {
548
- for (const d of [join(MONOREPO_ROOT, 'docs'), join(CLI_PKG_ROOT, 'docs'), join(process.env.CORTEX_ROOT || '.', 'docs')]) {
549
- try { if (existsSync(d)) return d; } catch { /* ignore */ }
550
- }
551
- return null;
552
- }
553
-
554
- function readGlobalEnvMap() {
555
- const m = {};
556
- try {
557
- for (const l of readFileSync(GLOBAL_ENV, 'utf8').split('\n')) {
558
- if (!l || l.startsWith('#')) continue;
559
- const i = l.indexOf('=');
560
- if (i > 0) m[l.slice(0, i).trim()] = l.slice(i + 1).trim();
561
- }
562
- } catch { /* none yet */ }
563
- return m;
564
- }
565
-
566
- function maskSecret(v) { return !v ? '' : (v.length <= 10 ? '••••' : `${v.slice(0, 6)}…${v.slice(-4)}`); }
567
-
568
- async function showDocs(name) {
569
- const dir = resolveDocsDir();
570
- if (!dir) { process.stdout.write(' No docs found in this install.\n'); return; }
571
- const { readdirSync } = await import('node:fs');
572
- let files = [];
573
- try { files = readdirSync(dir).filter((f) => f.endsWith('.md')); } catch { /* ignore */ }
574
- if (!name) {
575
- process.stdout.write(`\n Docs (${dir}):\n`);
576
- files.forEach((f) => process.stdout.write(` - ${f.replace(/\.md$/, '')}\n`));
577
- process.stdout.write('\n Read one: /docs <name> (e.g. /docs authentication)\n\n');
578
- return;
579
- }
580
- const match = files.find((f) => f.toLowerCase().includes(name.toLowerCase()));
581
- if (!match) { process.stdout.write(` No doc matching "${name}". Available: ${files.map((f) => f.replace(/\.md$/, '')).join(', ')}\n`); return; }
582
- try { process.stdout.write('\n' + readFileSync(join(dir, match), 'utf8') + '\n'); }
583
- catch (e) { process.stdout.write(` Could not read ${match}: ${e.message}\n`); }
584
- }
585
-
586
- // Interactive config panel. Reuses the caller's readline (from chat) or creates its own
587
- // (from the `cortex config` shell command). Writes ~/.cortex/.env.
588
- async function runConfigPanel(existingRl) {
589
- let rl = existingRl;
590
- if (!rl) {
591
- if (!process.stdin.isTTY) { process.stderr.write(' `cortex config` needs an interactive terminal. Use `cortex config set <key> <value>` non-interactively.\n'); process.exit(1); }
592
- const { createInterface } = await import('node:readline/promises');
593
- rl = createInterface({ input: process.stdin, output: process.stdout });
594
- }
595
- try {
596
- for (;;) {
597
- const env = readGlobalEnvMap();
598
- process.stdout.write(`\n Nexus Cortex config — ${GLOBAL_ENV}\n\n`);
599
- for (const p of PROVIDERS) {
600
- const v = env[p.keyVar] || process.env[p.keyVar];
601
- process.stdout.write(` ${p.keyVar.padEnd(20)} ${v ? '✓ ' + maskSecret(v) : '— not set'}\n`);
602
- }
603
- const model = env.DEFAULT_MODEL_ID || process.env.DEFAULT_MODEL_ID;
604
- process.stdout.write(` ${'DEFAULT_MODEL_ID'.padEnd(20)} ${model || '— not set'}\n`);
605
- process.stdout.write('\n 1) set / change an API key\n 2) change the default model\n 3) set any variable (KEY=value)\n 4) view docs\n q) done\n');
606
- const c = (await rl.question('\n > ')).trim().toLowerCase();
607
- if (c === 'q' || c === 'done' || c === 'quit' || c === '') break;
608
- if (c === '1') {
609
- PROVIDERS.forEach((p, i) => process.stdout.write(` ${i + 1}) ${p.name}\n`));
610
- const p = PROVIDERS[parseInt((await rl.question(` provider 1-${PROVIDERS.length}: `)).trim(), 10) - 1];
611
- if (!p) { process.stdout.write(' cancelled.\n'); continue; }
612
- const key = (await rl.question(` ${p.name} key (${p.hint}): `)).trim();
613
- if (key) {
614
- const set = { [p.keyVar]: key };
615
- if (!env.DEFAULT_MODEL_ID && !process.env.DEFAULT_MODEL_ID) set.DEFAULT_MODEL_ID = p.model;
616
- writeGlobalEnv(set);
617
- process.stdout.write(` ✓ saved ${p.keyVar}${set.DEFAULT_MODEL_ID ? ` + DEFAULT_MODEL_ID=${set.DEFAULT_MODEL_ID}` : ''}\n`);
618
- }
619
- } else if (c === '2') {
620
- const m = (await rl.question(' default model id (e.g. claude-sonnet-4-6): ')).trim();
621
- if (m) { writeGlobalEnv({ DEFAULT_MODEL_ID: m }); process.stdout.write(` ✓ DEFAULT_MODEL_ID=${m}\n`); }
622
- } else if (c === '3') {
623
- const kv = (await rl.question(' KEY=value: ')).trim();
624
- const i = kv.indexOf('=');
625
- if (i > 0) { writeGlobalEnv({ [kv.slice(0, i).trim()]: kv.slice(i + 1).trim() }); process.stdout.write(' ✓ saved\n'); }
626
- else process.stdout.write(' expected KEY=value\n');
627
- } else if (c === '4') {
628
- await showDocs();
629
- }
630
- }
631
- } finally {
632
- if (!existingRl) rl.close();
633
- }
634
- process.stdout.write(' Config saved to ~/.cortex/.env. New keys take effect on the next server start (`cortex --shutdown`, then run again).\n');
635
- }
636
-
637
- function printSlashHelp() {
638
- process.stdout.write(
639
- '\n Slash commands (anything without a leading / is sent to the model):\n' +
640
- ' /help this list\n' +
641
- ' /config view & change API keys, model, settings\n' +
642
- ' /docs [name] list docs, or print one (e.g. /docs authentication)\n' +
643
- ' /model [id] show or switch the model for this session\n' +
644
- ' /new start a fresh session\n' +
645
- ' /exit leave chat (or Ctrl-D)\n\n',
646
- );
647
- }
648
-
649
- async function runInteractiveChat() {
650
- if (newSession) {
651
- try { await fetchJSON('/sessions/new', { method: 'POST' }); } catch { /* fresh session is best-effort */ }
652
- }
653
- let currentModel = modelId;
654
- const { createInterface } = await import('node:readline/promises');
655
- const rl = createInterface({ input: process.stdin, output: process.stdout });
656
- process.stdout.write(
657
- '\n Nexus Cortex — interactive chat. Type a message and press Enter.\n' +
658
- ' Slash commands: /help /config /docs /model /new /exit · Ctrl-D to quit.\n\n',
659
- );
660
- try {
661
- for (;;) {
662
- let line;
663
- try { line = (await rl.question('cortex> ')).trim(); }
664
- catch { break; } // Ctrl-D / closed input
665
- if (!line) continue;
666
- if (line.startsWith('/')) {
667
- const parts = line.slice(1).split(/\s+/);
668
- const cmd = (parts.shift() || '').toLowerCase();
669
- const arg = parts.join(' ');
670
- if (cmd === 'exit' || cmd === 'quit' || cmd === 'q') break;
671
- if (cmd === 'help' || cmd === '?') { printSlashHelp(); continue; }
672
- if (cmd === 'config') { await runConfigPanel(rl); continue; }
673
- if (cmd === 'docs') { await showDocs(arg); continue; }
674
- if (cmd === 'new') {
675
- try { await fetchJSON('/sessions/new', { method: 'POST' }); process.stdout.write(' ✓ new session\n'); }
676
- catch (e) { process.stderr.write(` [error] ${e.message}\n`); }
677
- continue;
678
- }
679
- if (cmd === 'model') {
680
- if (arg) { currentModel = arg; process.stdout.write(` ✓ model → ${arg}\n`); }
681
- else process.stdout.write(` model: ${currentModel || '(server default)'}\n`);
682
- continue;
683
- }
684
- process.stdout.write(` unknown command: /${cmd} — type /help\n`);
685
- continue;
686
- }
687
- const payload = { messages: [{ role: 'user', content: line }] };
688
- if (currentModel) payload.model = currentModel;
689
- try {
690
- const data = await fetchJSON('/v1/messages', { method: 'POST', body: JSON.stringify(payload) }, messageTimeoutMs);
691
- const text = (data.content || []).filter((b) => b.type === 'text').map((b) => b.text).join('\n');
692
- process.stdout.write('\n' + (text || '(no text response)') + '\n\n');
693
- } catch (err) {
694
- process.stderr.write(`\n [error] ${err.message}\n\n`);
695
- }
696
- }
697
- } finally {
698
- rl.close();
699
- }
700
- process.stdout.write(' Bye.\n');
701
- }
702
-
703
353
  async function run() {
704
354
  // --shutdown: stop the server and exit
705
355
  if (doShutdown) {
@@ -707,12 +357,13 @@ async function run() {
707
357
  return;
708
358
  }
709
359
 
710
- // First-run onboarding: no API key anywhere walk the user through setup before
711
- // starting the server. Skipped for pure server-management invocations.
712
- const SKIP_SETUP = ['--help', '-h', '--stats', '--sessions', '--tmux'].some((f) => args.includes(f));
713
- if (!SKIP_SETUP && !hasApiKey()) {
714
- await runSetupWizard();
715
- }
360
+ // Update check for the direct path (`cortex "<prompt>"`, `cortex agent`). Commander
361
+ // subcommands are delegated earlier and run this same check via the CLI's preAction
362
+ // hook; this covers the non-delegated chat/agent path. Canonical service, best-effort.
363
+ try {
364
+ const { checkForUpdate } = await import('../dist/lifecycle/updateCheck.js');
365
+ await checkForUpdate();
366
+ } catch { /* dist absent in a source checkout, or check failed — never block */ }
716
367
 
717
368
  await ensureServer();
718
369
 
@@ -834,17 +485,9 @@ async function run() {
834
485
  }
835
486
  }
836
487
 
837
- // No message provided. In an interactive terminal (and NOT agent/run mode), drop
838
- // into the chat REPL so the shell never mangles ?/*/quotes. agent/run with no task,
839
- // or any non-TTY (scripted) use, still gets the usage error — unchanged.
488
+ // Need a prompt for message commands
840
489
  if (!prompt) {
841
- if (!__agentMode && process.stdin.isTTY) {
842
- await runInteractiveChat();
843
- return;
844
- }
845
- console.error(__agentMode
846
- ? 'Usage: cortex agent "<task>" (or cortex run "<task>")'
847
- : 'Usage: cortex "your prompt here"');
490
+ console.error('Usage: cortex "your prompt here"');
848
491
  console.error(' cortex --help for more options');
849
492
  process.exit(1);
850
493
  }
@@ -0,0 +1,4 @@
1
+ export declare function configInit(options?: {
2
+ force?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/config/init.ts"],"names":[],"mappings":"AAiBA,wBAAsB,UAAU,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCjF"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Initialize the global user config file (~/.cortex/.env).
3
+ *
4
+ * Creates a schema-templated .env in the user's home directory — the canonical,
5
+ * findable, editable config location — so a globally-installed CLI can be configured
6
+ * by opening one file, regardless of where npm placed the binary. With --force it
7
+ * regenerates the template while preserving any values you've already set.
8
+ */
9
+ import { existsSync, mkdirSync } from 'fs';
10
+ import { createDefaultEnvFile, updateEnvFile, getGlobalConfigDir, getGlobalEnvPath, } from '@nexus-cortex/core';
11
+ import { ThemeManager } from '../../themes/ThemeManager.js';
12
+ export async function configInit(options = {}) {
13
+ const theme = ThemeManager.getTheme();
14
+ const dir = getGlobalConfigDir();
15
+ const envPath = getGlobalEnvPath();
16
+ try {
17
+ mkdirSync(dir, { recursive: true });
18
+ const exists = existsSync(envPath);
19
+ if (exists && !options.force) {
20
+ console.log(theme.colors.warning(`[skip] config already exists: ${envPath}`));
21
+ console.log(theme.colors.muted(' Open it to edit your settings, or run "cortex config set KEY VALUE".'));
22
+ console.log(theme.colors.muted(' Use "cortex config init --force" to refresh the template (your values are preserved).'));
23
+ return;
24
+ }
25
+ if (exists) {
26
+ // Refresh template/comments without discarding existing values.
27
+ updateEnvFile(dir, {});
28
+ }
29
+ else {
30
+ createDefaultEnvFile(dir);
31
+ }
32
+ console.log(theme.colors.success(`[OK] ${exists ? 'refreshed' : 'created'} ${envPath}`));
33
+ console.log();
34
+ console.log(theme.colors.muted(' Set your keys one of two ways:'));
35
+ console.log(theme.colors.highlight(` • open ${envPath} and edit it`));
36
+ console.log(theme.colors.highlight(' • or run: cortex config set ANTHROPIC_API_KEY sk-ant-...'));
37
+ console.log();
38
+ }
39
+ catch (error) {
40
+ console.error(theme.colors.error(`Failed to initialize config: ${error.message}`));
41
+ process.exit(1);
42
+ }
43
+ }
44
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/config/init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAA+B,EAAE;IAChE,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IAEnC,IAAI,CAAC;QACH,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAEnC,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,iCAAiC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC9E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC,CAAC;YAC1G,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,yFAAyF,CAAC,CAAC,CAAC;YAC3H,OAAO;QACT,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,gEAAgE;YAChE,aAAa,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,oBAAoB,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC;QACzF,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,OAAO,cAAc,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,8DAA8D,CAAC,CAAC,CAAC;QACpG,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/commands/config/set.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAmDf"}
1
+ {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/commands/config/set.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAuDf"}
@@ -4,9 +4,8 @@
4
4
  * Unified config: writes to .env via SettingsLoader.
5
5
  * Validates against SettingsSchema metadata.
6
6
  */
7
- import { SettingsLoader, SETTINGS_METADATA, validateSetting, isLiveToggleable, } from '@nexus-cortex/core';
7
+ import { SettingsLoader, SETTINGS_METADATA, validateSetting, isLiveToggleable, getGlobalConfigDir, getGlobalEnvPath, } from '@nexus-cortex/core';
8
8
  import { ThemeManager } from '../../themes/ThemeManager.js';
9
- import { findProjectRoot } from './utils.js';
10
9
  /**
11
10
  * Set configuration value
12
11
  * Validates the key and value before saving to .env
@@ -35,8 +34,11 @@ export async function configSet(key, value) {
35
34
  process.exit(1);
36
35
  return; // guard: do not write an invalid value if exit is mocked
37
36
  }
38
- const projectPath = findProjectRoot();
39
- const loader = new SettingsLoader(projectPath);
37
+ // Write to the global user config (~/.cortex/.env) so the setting applies from
38
+ // any directory and survives package updates — regardless of where npm installed
39
+ // the binary. A project-local ./.env still overrides this when present.
40
+ const writeDir = getGlobalConfigDir();
41
+ const loader = new SettingsLoader(writeDir);
40
42
  const result = loader.set(key, value);
41
43
  if (!result.success) {
42
44
  console.error(theme.colors.error(`Failed to set ${key}: ${result.error}`));
@@ -50,6 +52,7 @@ export async function configSet(key, value) {
50
52
  console.log(theme.colors.muted(` was: ${result.previousValue}`));
51
53
  }
52
54
  console.log(theme.colors.highlight(` now: ${value}`));
55
+ console.log(theme.colors.muted(` saved to: ${getGlobalEnvPath()}`));
53
56
  console.log();
54
57
  }
55
58
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"set.js","sourceRoot":"","sources":["../../../src/commands/config/set.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"set.js","sourceRoot":"","sources":["../../../src/commands/config/set.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function uninstallCli(options?: {
2
+ purge?: boolean;
3
+ yes?: boolean;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=uninstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"AAyBA,wBAAsB,YAAY,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ClG"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `cortex-cli uninstall` — remove the global install.
3
+ *
4
+ * By default your config + API keys at ~/.cortex are LEFT in place (so you don't lose
5
+ * keys by accident); --purge removes them too. Interactive runs confirm first;
6
+ * non-interactive runs require --yes so a stray invocation can't wipe an install.
7
+ */
8
+ import { spawnSync } from 'child_process';
9
+ import { rmSync, existsSync } from 'fs';
10
+ import { createInterface } from 'readline';
11
+ import { getGlobalConfigDir } from '@nexus-cortex/core';
12
+ import { ThemeManager } from '../themes/ThemeManager.js';
13
+ const PKG_NAME = 'nexus-cortex';
14
+ function askYesNo(question) {
15
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
16
+ return new Promise((resolve) => {
17
+ rl.question(question, (answer) => {
18
+ rl.close();
19
+ resolve(/^y(es)?$/i.test(answer.trim()));
20
+ });
21
+ });
22
+ }
23
+ export async function uninstallCli(options = {}) {
24
+ const theme = ThemeManager.getTheme();
25
+ const dir = getGlobalConfigDir();
26
+ if (!options.yes) {
27
+ if (!process.stdin.isTTY) {
28
+ console.error(theme.colors.error('Refusing to uninstall non-interactively without --yes.'));
29
+ process.exit(1);
30
+ return;
31
+ }
32
+ const ok = await askYesNo(theme.colors.warning(`Uninstall nexus-cortex globally${options.purge ? ` and delete ${dir}` : ''}? [y/N] `));
33
+ if (!ok) {
34
+ console.log(theme.colors.muted('Cancelled.'));
35
+ return;
36
+ }
37
+ }
38
+ console.log(theme.colors.highlight('Uninstalling nexus-cortex…'));
39
+ console.log();
40
+ const res = spawnSync('npm', ['uninstall', '-g', PKG_NAME], { stdio: 'inherit' });
41
+ if (res.status !== 0) {
42
+ console.error(theme.colors.error('\nUninstall failed — see the npm output above.'));
43
+ console.error(theme.colors.muted('If it is a permissions error: sudo npm uninstall -g nexus-cortex'));
44
+ process.exit(res.status || 1);
45
+ return;
46
+ }
47
+ if (options.purge) {
48
+ try {
49
+ if (existsSync(dir)) {
50
+ rmSync(dir, { recursive: true, force: true });
51
+ console.log(theme.colors.muted(`Removed ${dir}`));
52
+ }
53
+ console.log(theme.colors.success('\n[OK] Uninstalled and purged.'));
54
+ }
55
+ catch (e) {
56
+ console.error(theme.colors.error(`Uninstalled, but could not remove ${dir}: ${e.message}`));
57
+ }
58
+ }
59
+ else {
60
+ console.log(theme.colors.success('\n[OK] Uninstalled.'));
61
+ console.log(theme.colors.muted(`Your config + API keys remain at ${dir} — to remove them too: rm -rf ${dir}`));
62
+ }
63
+ }
64
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,QAAQ,GAAG,cAAc,CAAC;AAEhC,SAAS,QAAQ,CAAC,QAAgB;IAChC,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;QACtC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;YAC/B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAA8C,EAAE;IACjF,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;IAEjC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC,CAAC;YAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,QAAQ,CACvB,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,kCAAkC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAC5G,CAAC;QACF,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAClF,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC,CAAC;QACpF,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAC,CAAC;QACtG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpB,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC;YACpD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,GAAG,iCAAiC,GAAG,EAAE,CAAC,CAAC,CAAC;IACjH,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function updateCli(): Promise<void>;
2
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAQA,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAc/C"}