@nexus-cortex/cli 4.30.0 → 4.31.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.
Files changed (2) hide show
  1. package/bin/cortex.js +77 -30
  2. package/package.json +2 -2
package/bin/cortex.js CHANGED
@@ -25,7 +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, statSync, mkdirSync, writeFileSync } from 'fs';
28
+ import { existsSync, readFileSync, realpathSync, mkdirSync, writeFileSync } from 'fs';
29
29
  import { homedir } from 'os';
30
30
  import { createRequire } from 'module';
31
31
 
@@ -62,36 +62,64 @@ if (!process.env.CORTEX_ROOT) {
62
62
  const BASE_PORT = process.env.PORT || '4000';
63
63
  const BASE_URL = process.env.CORTEX_URL || `http://localhost:${BASE_PORT}`;
64
64
 
65
- // ── Background auto-update (opt-out) ──────────────────────────────
66
- // On by default; set CORTEX_AUTO_UPDATE=false (or pass --no-auto-update) to disable.
67
- // Fire-and-forget: spawns a DETACHED `npm i -g nexus-cortex@latest` that the current
68
- // run does NOT wait on and that does NOT hot-swap the running process — the update
69
- // lands on the NEXT launch. Throttled to once per 24h via a marker file. Skipped for
70
- // dev/source checkouts (only updates an actual global npm install). Never blocks or
71
- // throws into startup any failure is swallowed.
72
- function maybeAutoUpdate() {
73
- try {
74
- const optOut = String(process.env.CORTEX_AUTO_UPDATE).toLowerCase() === 'false'
75
- || process.argv.includes('--no-auto-update');
76
- if (optOut) return;
77
- // Only auto-update a real installed package, never a git/source checkout.
78
- if (!__cortex_dirname.includes('node_modules')) return;
79
-
80
- const marker = join(homedir(), '.cortex', '.update-check');
81
- const DAY_MS = 24 * 60 * 60 * 1000;
82
- try {
83
- if (Date.now() - statSync(marker).mtimeMs < DAY_MS) return; // throttled
84
- } catch { /* no marker yet — proceed */ }
85
- try { mkdirSync(dirname(marker), { recursive: true }); writeFileSync(marker, String(Date.now())); } catch {}
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
+ }
86
85
 
87
- const child = spawn('npm', ['install', '-g', 'nexus-cortex@latest'], {
88
- detached: true,
89
- stdio: 'ignore',
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); });
90
97
  });
91
- child.unref();
92
- } catch { /* never let auto-update affect the CLI */ }
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 */ }
93
122
  }
94
- maybeAutoUpdate();
95
123
 
96
124
  let serverProcess = null;
97
125
 
@@ -112,6 +140,25 @@ if (args.includes('--version') || args.includes('-v')) {
112
140
  process.exit(0);
113
141
  }
114
142
 
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
+ // Update-available notice (skip for machine-readable / quiet output).
158
+ if (!args.includes('--json') && !args.includes('--quiet') && !args.includes('-q')) {
159
+ notifyUpdateAvailable();
160
+ }
161
+
115
162
  // ── Setup wizard (interactive API-key onboarding) ─────────────────
116
163
  // Global config lives at ~/.cortex/.env so a global npm install works from ANY
117
164
  // folder (the server loads it). Runs on `cortex config init` and on first run
@@ -322,8 +369,8 @@ FLAGS:
322
369
  --timeout MS Request timeout in ms (default: 600000 = 10 min)
323
370
  --idle-timeout SECS Auto-shutdown server after N seconds of inactivity
324
371
  --cwd DIR (agent) Run in DIR — the agent's file tools operate there
325
- --no-auto-update Skip the background update check for this run
326
- (also: export CORTEX_AUTO_UPDATE=false to disable it entirely)
372
+ --update Update nexus-cortex to the latest version (npm i -g)
373
+ (a notice appears when you're behind; CORTEX_NO_UPDATE_NOTICE=true to silence)
327
374
  --shutdown Stop the running server and exit
328
375
  --tmux List active tmux sessions with dashboard URLs
329
376
  --stats Show current session statistics
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexus-cortex/cli",
3
- "version": "4.30.0",
3
+ "version": "4.31.0",
4
4
  "description": "Nexus Cortex CLI - Terminal interface for multi-provider LLM orchestration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "prepack": "node ../../scripts/copy-pkg-cortex-scaffold.mjs"
20
20
  },
21
21
  "dependencies": {
22
- "@nexus-cortex/core": "^4.30.0",
22
+ "@nexus-cortex/core": "^4.31.0",
23
23
  "chalk": "^4.1.2",
24
24
  "cli-spinners": "^2.9.0",
25
25
  "commander": "^11.0.0",