@nexus-cortex/cli 4.29.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.
- package/bin/cortex.js +123 -32
- 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,
|
|
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
|
-
// ──
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
--
|
|
326
|
-
(
|
|
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
|
|
@@ -463,6 +510,42 @@ async function fetchJSON(path, options = {}, timeoutMs = 30000) {
|
|
|
463
510
|
|
|
464
511
|
// ── Commands ──────────────────────────────────────────────────────
|
|
465
512
|
|
|
513
|
+
// Interactive chat REPL — `cortex` with no message drops you here so you can talk
|
|
514
|
+
// line-by-line without the shell mangling ?/*/quotes. The server keeps the session,
|
|
515
|
+
// so it's multi-turn. Reuses the same /v1/messages send as the one-shot path.
|
|
516
|
+
async function runInteractiveChat() {
|
|
517
|
+
if (newSession) {
|
|
518
|
+
try { await fetchJSON('/sessions/new', { method: 'POST' }); } catch { /* fresh session is best-effort */ }
|
|
519
|
+
}
|
|
520
|
+
const { createInterface } = await import('node:readline/promises');
|
|
521
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
522
|
+
process.stdout.write(
|
|
523
|
+
'\n Nexus Cortex — interactive chat. Type a message and press Enter.\n' +
|
|
524
|
+
' The session persists across messages. Type "exit" (or press Ctrl-D) to quit.\n\n',
|
|
525
|
+
);
|
|
526
|
+
try {
|
|
527
|
+
for (;;) {
|
|
528
|
+
let line;
|
|
529
|
+
try { line = (await rl.question('cortex> ')).trim(); }
|
|
530
|
+
catch { break; } // Ctrl-D / closed input
|
|
531
|
+
if (!line) continue;
|
|
532
|
+
if (line === 'exit' || line === 'quit' || line === ':q') break;
|
|
533
|
+
const payload = { messages: [{ role: 'user', content: line }] };
|
|
534
|
+
if (modelId) payload.model = modelId;
|
|
535
|
+
try {
|
|
536
|
+
const data = await fetchJSON('/v1/messages', { method: 'POST', body: JSON.stringify(payload) }, messageTimeoutMs);
|
|
537
|
+
const text = (data.content || []).filter((b) => b.type === 'text').map((b) => b.text).join('\n');
|
|
538
|
+
process.stdout.write('\n' + (text || '(no text response)') + '\n\n');
|
|
539
|
+
} catch (err) {
|
|
540
|
+
process.stderr.write(`\n [error] ${err.message}\n\n`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} finally {
|
|
544
|
+
rl.close();
|
|
545
|
+
}
|
|
546
|
+
process.stdout.write(' Bye.\n');
|
|
547
|
+
}
|
|
548
|
+
|
|
466
549
|
async function run() {
|
|
467
550
|
// --shutdown: stop the server and exit
|
|
468
551
|
if (doShutdown) {
|
|
@@ -597,9 +680,17 @@ async function run() {
|
|
|
597
680
|
}
|
|
598
681
|
}
|
|
599
682
|
|
|
600
|
-
//
|
|
683
|
+
// No message provided. In an interactive terminal (and NOT agent/run mode), drop
|
|
684
|
+
// into the chat REPL so the shell never mangles ?/*/quotes. agent/run with no task,
|
|
685
|
+
// or any non-TTY (scripted) use, still gets the usage error — unchanged.
|
|
601
686
|
if (!prompt) {
|
|
602
|
-
|
|
687
|
+
if (!__agentMode && process.stdin.isTTY) {
|
|
688
|
+
await runInteractiveChat();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
console.error(__agentMode
|
|
692
|
+
? 'Usage: cortex agent "<task>" (or cortex run "<task>")'
|
|
693
|
+
: 'Usage: cortex "your prompt here"');
|
|
603
694
|
console.error(' cortex --help for more options');
|
|
604
695
|
process.exit(1);
|
|
605
696
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexus-cortex/cli",
|
|
3
|
-
"version": "4.
|
|
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.
|
|
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",
|