@nexus-cortex/cli 4.36.2 → 4.37.1

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/.env.example ADDED
@@ -0,0 +1,358 @@
1
+ # Nexus Cortex — Complete Environment Configuration Reference
2
+ # Copy to .env and fill in the API keys for the providers you use. Every other value
3
+ # has a proven-optimal default.
4
+ #
5
+ # WARNING: The Settings Configurator (/config) regenerates .env from SettingsSchema.
6
+ # Runtime-only vars (YOLO, SERVER_IDLE_TIMEOUT, WEB_TOOLS_MODEL, etc.) are NOT in the
7
+ # schema — they will be silently dropped unless the SettingsWriter preserves them.
8
+ #
9
+ # Keep this file and .env structurally in sync: same comments + ordering, only the
10
+ # values differ (.env = your real config, this file = template/defaults).
11
+
12
+ # ============================================
13
+ # API KEYS
14
+ # ============================================
15
+ # A model is only available if its provider's key is set. Leave the rest blank.
16
+
17
+ # Claude (Anthropic) — Fable / Opus / Sonnet / Haiku
18
+ ANTHROPIC_API_KEY=
19
+ # OpenAI — GPT / o-series
20
+ OPENAI_API_KEY=
21
+ # Gemini (legacy key; GEMINI_API_KEY takes priority when both are set)
22
+ GOOGLE_API_KEY=
23
+ # Gemini (preferred key; falls back to GOOGLE_API_KEY)
24
+ GEMINI_API_KEY=
25
+ # xAI — Grok
26
+ XAI_API_KEY=
27
+ # DeepSeek (the default model's provider)
28
+ DEEPSEEK_API_KEY=
29
+ # Nvidia (no standalone NVIDIA cards registered yet — currently reached via Cloudflare)
30
+ NVIDIA_API_KEY=
31
+ # Inception Labs (Mercury diffusion models — mercury-2)
32
+ INCEPTION_API_KEY=
33
+ # Cloudflare Workers AI (@cf/* models). Requires CLOUDFLARE_ACCOUNT_ID.
34
+ CLOUDFLARE_API_TOKEN=
35
+ # Cloudflare account id (paired with the token above)
36
+ CLOUDFLARE_ACCOUNT_ID=
37
+ # Alibaba Qwen (DashScope) — qwen-* models
38
+ DASHSCOPE_API_KEY=
39
+ # MiniMax — minimax-* models
40
+ MINIMAX_API_KEY=
41
+ # Moonshot AI (Kimi) — moonshot-* / kimi-* models
42
+ MOONSHOT_API_KEY=
43
+ # Zhipu AI (GLM) — glm-* models
44
+ ZHIPU_API_KEY=
45
+ # Hugging Face Inference (no HF models registered yet; HUGGINGFACE_TOKEN is also accepted)
46
+ HUGGINGFACE_API_KEY=
47
+ HUGGINGFACE_TOKEN=
48
+
49
+ # Authentication method for Anthropic: auto (try OAuth first, fall back to API key),
50
+ # oauth (OAuth only), api-key (API key only). Options: auto, oauth, api-key
51
+ ANTHROPIC_AUTH_METHOD=api-key
52
+ # OAuth token override (alternative to ~/.claude/.credentials.json)
53
+ CLAUDE_CODE_OAUTH_TOKEN=
54
+
55
+ # ============================================
56
+ # MODEL CONFIGURATION
57
+ # ============================================
58
+
59
+ # Default model for new sessions (any registry ID or alias — `cortex models list`)
60
+ DEFAULT_MODEL_ID=deepseek-v4-pro
61
+ # Cheaper model for context compaction & mentorship
62
+ HELPER_MODEL_ID=deepseek-v4-flash
63
+
64
+ # Model that backs WebSearch / WebFetch tools. Provider auto-detected from ID prefix.
65
+ # When unset, auto-picks based on available API keys (Gemini > Anthropic > XAI),
66
+ # falling back to DuckDuckGo HTML scrape if no provider key configured.
67
+ #
68
+ # Gemini models (uses @google/genai SDK — googleSearch grounding + urlContext):
69
+ # gemini-2.5-flash — fast, free-tier friendly, good grounding with citations
70
+ # gemini-2.5-flash-lite — cheapest, fewer grounding chunks but still functional
71
+ # gemini-2.5-pro — best grounding quality (most chunks/supports), slower
72
+ # gemini-3.5-flash — latest GA flash, strong agentic grounding, search + url context
73
+ # gemini-3.1-pro-preview — latest pro preview, rich grounding, slowest
74
+ # gemini-3-flash-preview — preview alias for gemini-3.5-flash
75
+ #
76
+ # Other providers (provider-native search APIs with citations):
77
+ # claude-haiku-4-5 — Anthropic web_search server tool (inline source citations)
78
+ # grok-4-fast-non-reasoning — XAI Responses API web_search (URL citations)
79
+ # grok-4.3 — XAI Responses API web_search (reasoning model, URL citations)
80
+ WEB_TOOLS_MODEL=gemini-2.5-flash
81
+
82
+ # ============================================
83
+ # SYSTEM SETTINGS
84
+ # ============================================
85
+
86
+ # Verbose debug logging (system-message assembly, routes). Toggle at runtime via /debug.
87
+ DEBUG=false
88
+ # Allow emoji in CLI output (false = plain text)
89
+ USE_EMOJI=false
90
+ # Project root the tools operate on. Leave EMPTY to use the launch cwd (the cwd
91
+ # model, like Claude Code): cd into your project and run cortex. Only set this for
92
+ # headless setups where the server's cwd is not the project. When set, it becomes
93
+ # canonical and PROJECT_ROOT is derived from it (they can never diverge).
94
+ PROJECT_PATH=
95
+
96
+ # ============================================
97
+ # REACTIVE MENTORSHIP (AI-to-AI self-improvement)
98
+ # ============================================
99
+
100
+ # Master switch for the mentorship system
101
+ MENTORSHIP_ENABLED=false
102
+ # Trigger helper-model review on tool errors (only applies when mentorship is enabled)
103
+ MENTORSHIP_TRIGGER_ON_ERROR=false
104
+ # Minimum error severity to trigger. Options: low, medium, high
105
+ MENTORSHIP_ERROR_THRESHOLD=medium
106
+ # React to @ultrathink / @analyze / @rethink keywords
107
+ MENTORSHIP_KEYWORDS_ENABLED=false
108
+ # Additional trigger keywords (comma-separated, e.g. @help,@stuck)
109
+ MENTORSHIP_CUSTOM_KEYWORDS=
110
+ # Model used for mentorship guidance (overrides HELPER_MODEL_ID)
111
+ MENTORSHIP_HELPER_MODEL=@cf/google/gemma-4-26b-a4b-it
112
+ # Periodic mentorship review every N turns
113
+ MENTORSHIP_TURN_BASED_ENABLED=false
114
+ # Turns between periodic reviews (1-50)
115
+ MENTORSHIP_TURN_INTERVAL=10
116
+ # Inject thinking assistance for non-reasoning models
117
+ MENTORSHIP_INTERLEAVED_THINKING=false
118
+ # Detect and alert on repeated failure patterns
119
+ MENTORSHIP_PATTERN_DETECTION=false
120
+ # Number of similar errors to trigger pattern detection (2-10)
121
+ MENTORSHIP_PATTERN_THRESHOLD=3
122
+ # Post-turn summary + next-action prediction via the helper model
123
+ TURN_SUMMARY_PREDICTION=false
124
+
125
+ # ============================================
126
+ # CONTEXT MANAGEMENT
127
+ # ============================================
128
+
129
+ # Anthropic prompt caching (up to ~90% input-token savings)
130
+ ANTHROPIC_PROMPT_CACHING=true
131
+ # Strategy when context overflows: priority-based preserves critical context + tool pairs;
132
+ # sliding-window is dumb recency and can orphan tool_use -> Anthropic 400.
133
+ # Options: sliding-window, priority-based
134
+ CONTEXT_BUDGET_STRATEGY=priority-based
135
+
136
+ # ============================================
137
+ # SESSION CONFIGURATION
138
+ # ============================================
139
+
140
+ # Where JSONL session files are written
141
+ SESSION_STORAGE_DIR=.cortex/sessions
142
+ # Auto-inject connected MCP servers' tools into every turn
143
+ MCP_AUTO_INJECT=false
144
+ # Auto-research subagents. off = disabled (PM never told; main context stays clean).
145
+ # native = PM delegates to autoresearch-agent subagents that run experiments with the
146
+ # internal tools. mcp = the subagents route experiment-running to the external
147
+ # configured auto-research MCP server instead (MCP integration ships separately). Options: off, native, mcp
148
+ AUTORESEARCH_AGENTS=off
149
+ # Cap on bytes per injected project doc (CORTEX.md, MEMORY.md, etc.). 0 = unlimited.
150
+ SYSTEM_MESSAGE_DOC_MAX_BYTES=0
151
+
152
+ # ============================================
153
+ # LOOP CONTROL
154
+ # ============================================
155
+
156
+ # Maximum tool executions per turn
157
+ MAX_TOOL_ITERATIONS=50
158
+ # Stop the turn after this many consecutive all-error iterations
159
+ MAX_CONSECUTIVE_ERRORS=3
160
+ # Soft per-turn tool-call budget (R29b brake: escalating reminders, hard cap at 2x)
161
+ TOOL_BUDGET_SOFT=15
162
+ # Per-tool execution timeout in milliseconds (2 minutes)
163
+ TOOL_TIMEOUT_MS=120000
164
+ # Identical tool calls before loop detection breaks the turn
165
+ MAX_LOOP_REPETITIONS=5
166
+
167
+ # ============================================
168
+ # PROVIDER TOOLING (server-side tools / API surface)
169
+ # ============================================
170
+
171
+ # Inject server-side tools (web_search, x_search, code_execution) into XAI/OpenAI requests.
172
+ # When true, overrides to the Responses API at request time regardless of XAI_API_MODE.
173
+ # R37/R38 benchmarks: hybrid (true) is 20-26% faster than client-only with equal accuracy.
174
+ ENABLE_SERVER_SIDE_TOOLS=true
175
+ # Default API pattern for XAI models at registration time (ENABLE_SERVER_SIDE_TOOLS=true
176
+ # overrides at request time). Options: messages | responses
177
+ XAI_API_MODE=messages
178
+ # OpenAI request surface. Options: chat/completions | responses (opt into hosted tools)
179
+ OPENAI_API_MODE=
180
+ # Only load essential tools initially; the model discovers the rest via SearchTools.
181
+ # R43 benchmark: 77% input-token reduction on the first turn.
182
+ ENABLE_DEFERRED_TOOL_LOADING=true
183
+ # Programmatic Tool Calling (compose tool calls in a script) — experimental
184
+ ENABLE_PTC=false
185
+ # Allow local code-execution tooling
186
+ ENABLE_LOCAL_CODE_EXECUTION=false
187
+
188
+ # ============================================
189
+ # MODEL ROUTER (auto model selection)
190
+ # ============================================
191
+
192
+ # Auto-select a model based on task type + benchmark history (routes model="auto")
193
+ MODEL_ROUTER_ENABLED=false
194
+ # Routing strategy: auto (classify the prompt) | matrix-only (require explicit task type)
195
+ MODEL_ROUTER_STRATEGY=auto
196
+ # Record turn metrics to .cortex/router-matrix.jsonl (independent of routing being enabled)
197
+ MODEL_ROUTER_RECORD=true
198
+ # Models the router must NEVER auto-select (cost/policy bans), comma-separated.
199
+ # A trailing '*' is a prefix wildcard: 'grok*' excludes EVERY xAI model (present and
200
+ # future) so exploration can never route a sub-agent to a grok model — honors the
201
+ # standing cost constraint.
202
+ MODEL_ROUTER_EXCLUDE=grok*
203
+ # Opt-in explore/exploit (Thompson sampling) for model='auto'. OFF = conservative greedy
204
+ # trust-gated routing. ON = posterior-sample each model and route to the sampled-argmax,
205
+ # so thinly-sampled models get a chance and the matrix stops being self-confirming.
206
+ MODEL_ROUTER_EXPLORATION=false
207
+ # Trust gate for sub-agent model='auto' dispatch (works even when ENABLED=false — passing
208
+ # 'auto' is the orchestrator's explicit per-dispatch opt-in):
209
+ # Min task-classification confidence (0-1) before 'auto' routes; else inherits parent model
210
+ ROUTER_MIN_CONFIDENCE=0.3
211
+ # Min real benchmark samples a task type needs before 'auto' trusts the matrix
212
+ # recommendation; else inherits parent. Met as MODEL_ROUTER_RECORD accumulates data.
213
+ ROUTER_MIN_SAMPLES=3
214
+
215
+ # ============================================
216
+ # END-OF-TURN AUDIT / TRAINING SUBSTRATE (opt-in)
217
+ # ============================================
218
+
219
+ # Mandatory EndTurn pre-delivery self-audit + Stage 2/3 verifiers. Default OFF; ON =
220
+ # graded cortex-channel training records (the tool is hidden when off).
221
+ CORTEX_ENDTURN_GATE=false
222
+
223
+ # ============================================
224
+ # DECISION STORE (prior-recall + recording)
225
+ # ============================================
226
+
227
+ # Append each tool decision to <PROJECT_ROOT>/.cortex/decisions.jsonl (default ON)
228
+ CORTEX_RECORD_DECISIONS=true
229
+ # Inject prior decisions as a <system-reminder> before tool use (default ON)
230
+ CORTEX_LOOKUP_PRIOR_DECISIONS=true
231
+ # decisions.jsonl self-rotates at this byte cap (default 2 MB)
232
+ CORTEX_DECISIONS_MAX_BYTES=2097152
233
+ # Per-turn "Repository State" harness-note: git branch, uncommitted changes in the project
234
+ # subtree, recent commits, and a cross-agent staleness warning (files you read that another
235
+ # agent/the user has since changed on disk). Lets two agents share one working tree safely.
236
+ # Default ON; set "false" to disable.
237
+ CORTEX_GIT_CONTEXT=true
238
+
239
+ # ============================================
240
+ # ORCHESTRATOR MODE
241
+ # ============================================
242
+
243
+ # Orchestrator lifecycle: persistent (default) | stateless (clean per request) | server
244
+ CORTEX_MODE=persistent
245
+ # Server URL when CORTEX_MODE=server (HTTP client mode)
246
+ CORTEX_SERVER_URL=http://localhost:4000
247
+
248
+ # ============================================
249
+ # AGENT WORKSPACE
250
+ # ============================================
251
+
252
+ # tmux visual monitoring for parallel agent teams (one live pane per agent)
253
+ AGENT_TMUX_MONITOR=false
254
+
255
+ # ============================================
256
+ # TESTING
257
+ # ============================================
258
+
259
+ # Run real-API smoke tests instead of mocked ones
260
+ ENABLE_SMOKE_TESTS=false
261
+
262
+ # Run the Chromium browser-integration test suite (drives a real browser,
263
+ # downloads http-server via npx — opt-in; default npm test skips it)
264
+ ENABLE_BROWSER_TESTS=false
265
+
266
+ # ============================================
267
+ # DEBUG FLAGS
268
+ # ============================================
269
+
270
+ # Log raw API request/response payloads
271
+ DEBUG_PAYLOAD=false
272
+ # Verbose system-message assembly logging (also enabled by DEBUG=true)
273
+ DEBUG_SYSTEM_MESSAGES=false
274
+ # Show thinking/reasoning content in the CLI. For Anthropic Opus 4.7/4.8 (adaptive
275
+ # thinking) 'true' requests display:'summarized', which BILLS extra output tokens
276
+ # ($25/1M on Opus 4.8); 'false' keeps reasoning omitted (empty thinking blocks, $0).
277
+ # Other providers (XAI/DeepSeek/GPT) stream reasoning for free regardless.
278
+ DEBUG_THINKING=false
279
+
280
+ # ============================================
281
+ # TOOL & PATH OVERRIDES
282
+ # ============================================
283
+
284
+ # Override the Chromium binary used by web/browse tools (auto-detected if unset)
285
+ CHROMIUM_BIN=
286
+ # Alternate Chromium path honored by Playwright
287
+ PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=
288
+ # Override the tmux binary used by visual agent monitoring (auto-detected if unset)
289
+ TMUX_BIN=
290
+ # Google Vertex AI project (only when using Vertex instead of the Gemini API)
291
+ GOOGLE_CLOUD_PROJECT=
292
+ # Extra directories to grant tool access to, OUTSIDE the project root (the --add-dir
293
+ # mechanism — your explicit permission). Path-delimiter separated (':' on Linux/macOS).
294
+ # Example: /data/shared:/opt/reference. Prefer the --add-dir flag for one-offs.
295
+ CORTEX_ADD_DIRS=
296
+
297
+ # ============================================
298
+ # GIT / PR ACCESS CONTROL
299
+ # ============================================
300
+ # Controls the git/PR tools (PRAgent, WorkspaceManager) and the /v1/pr/* server routes.
301
+ # Input-format validation (which blocks shell/argument injection) is ALWAYS on; the
302
+ # allow-lists below are opt-in defense-in-depth.
303
+ #
304
+ # Comma list of repos the tools may touch: exact "owner/repo", "owner/*", or "*".
305
+ # Unset = ALL repos are permitted — the convenient default for single-user/local use.
306
+ # For shared or multi-tenant deployments, RESTRICT this to the repos you trust the tools
307
+ # with, e.g. GIT_ALLOWED_REPOS=me/app,me/*
308
+ GIT_ALLOWED_REPOS=
309
+ # Comma list of allowed actions: review,list,create,post-review,clone,worktree,diff,cleanup,status
310
+ # Unset or * = all actions allowed.
311
+ GIT_ALLOWED_ACTIONS=
312
+ # Token for gh/git. Injected into the subprocess env as GH_TOKEN/GITHUB_TOKEN only —
313
+ # never placed on argv or in a clone URL. Leave unset to use gh's own auth.
314
+ GIT_AUTH_TOKEN=
315
+ # GitHub (Enterprise) host for git/PR tools. Default: github.com
316
+ GIT_HOST=github.com
317
+ # HMAC secret for the /v1/pr/webhook endpoint (GitHub X-Hub-Signature-256).
318
+ # Unset = the webhook is DISABLED (returns 401) rather than accepting unsigned calls.
319
+ GITHUB_WEBHOOK_SECRET=
320
+
321
+ # ============================================
322
+ # SERVER LIFECYCLE (startup / shutdown / resume)
323
+ # ============================================
324
+
325
+ # HTTP server port (falls back to the next free port if taken)
326
+ PORT=4000
327
+ # Startup resume: load the most recent session on boot — e.g. after an idle-shutdown ->
328
+ # wake cycle. For a specific session set RESUME_SESSION_ID instead. Default: fresh session.
329
+ AUTO_RESUME=false
330
+ # Resume a specific session UUID on boot (overrides AUTO_RESUME)
331
+ RESUME_SESSION_ID=
332
+ # Inactivity shutdown: auto-stop after N seconds with no requests.
333
+ # 0 = never (always-on daemon)
334
+ # N>0 = stop after N seconds idle. Pair with a process manager (systemd, pm2) +
335
+ # AUTO_RESUME=true for "sleep when idle, resume on wake" — cost-efficient
336
+ # on-demand use without losing conversation state.
337
+ SERVER_IDLE_TIMEOUT=0
338
+ # Graceful shutdown: max ms to let in-flight connections drain before forcing exit
339
+ # (0 = wait indefinitely). On shutdown the dashboard view server, idle timer, and
340
+ # cleanup intervals are all closed cleanly first.
341
+ SHUTDOWN_GRACE_MS=10000
342
+ # Opt-in: start the sandbox+tmux dashboard (default off; binds an extra port).
343
+ # MASTER SWITCH for the tmux/sandbox web dashboard (the 4001 view server).
344
+ # false = the dashboard NEVER starts (not at boot, not on TmuxSession/CreateArtifact
345
+ # use) and tools return guidance instead of view URLs. Set true to enable; if it
346
+ # fails to start, check for a port conflict on DASHBOARD_PORT (retries 10 ports).
347
+ ENABLE_DASHBOARD=false
348
+ # Dashboard port — honored whether started eagerly (above) or lazily by a tool
349
+ DASHBOARD_PORT=4001
350
+
351
+ # ============================================
352
+ # RUNTIME FLAGS
353
+ # ============================================
354
+
355
+ # Auto-approve ALL tool executions (bypasses the permission system). Use with caution.
356
+ YOLO=false
357
+ # Proactive mentorship discovery (runtime flag)
358
+ MENTORSHIP_ACTIVE_DISCOVERY=false
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, mkdirSync, openSync } from 'fs';
28
+ import { existsSync, readFileSync, realpathSync, mkdirSync, openSync, writeFileSync, copyFileSync } from 'fs';
29
29
  import { homedir } from 'os';
30
30
  import { createRequire } from 'module';
31
31
 
@@ -354,8 +354,95 @@ async function startServer() {
354
354
  // Where the auto-started background server logs to. Honors CORTEX_HOME/HOME so
355
355
  // it lands beside the rest of the global config (~/.cortex/server.log).
356
356
  function getServerLogPath() {
357
+ return join(getGlobalCortexDir(), 'server.log');
358
+ }
359
+
360
+ function getGlobalCortexDir() {
357
361
  const base = process.env.CORTEX_HOME || homedir() || process.cwd();
358
- return join(base, '.cortex', 'server.log');
362
+ return join(base, '.cortex');
363
+ }
364
+
365
+ // Is at least one provider API key resolvable — from the environment/secrets store
366
+ // OR from a .env file (cwd or global)? A BLANK value in a .env (e.g. `DEEPSEEK_API_KEY=`)
367
+ // does NOT count: the loader treats it as falsy and falls through to process.env, which is
368
+ // exactly how the secrets-store model works (blank .env + key injected via env). So we only
369
+ // count a non-empty, non-commented assignment, or a non-empty process.env var.
370
+ function hasAnyApiKey() {
371
+ const isKeyName = (k) => /(_API_KEY|_API_TOKEN|_OAUTH_TOKEN)$/.test(k);
372
+ for (const [k, v] of Object.entries(process.env)) {
373
+ if (isKeyName(k) && String(v || '').trim()) return true;
374
+ }
375
+ for (const p of [join(process.cwd(), '.env'), join(getGlobalCortexDir(), '.env')]) {
376
+ try {
377
+ if (!existsSync(p)) continue;
378
+ for (const line of readFileSync(p, 'utf8').split('\n')) {
379
+ if (/^\s*#/.test(line)) continue;
380
+ const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(\S.*?)\s*$/);
381
+ if (m && isKeyName(m[1]) && m[2].trim()) return true;
382
+ }
383
+ } catch { /* unreadable .env — ignore */ }
384
+ }
385
+ return false;
386
+ }
387
+
388
+ // A blank-value config template — mirrors the project's own .env: every key declared but
389
+ // EMPTY, so the loader falls through to the environment/secrets store. NEVER put a non-empty
390
+ // placeholder here — it would override a real secret in process.env.
391
+ const BLANK_ENV_TEMPLATE = `# Nexus Cortex configuration.
392
+ # Blank values are read from your environment / secrets store (a value set in the
393
+ # environment wins when the line below is left blank). Fill a key here for local use,
394
+ # or leave blank and inject it via your secrets store. Run "cortex config init --force"
395
+ # for the full, commented template.
396
+
397
+ DEFAULT_MODEL_ID=deepseek-v4-pro
398
+
399
+ ANTHROPIC_API_KEY=
400
+ OPENAI_API_KEY=
401
+ GEMINI_API_KEY=
402
+ DEEPSEEK_API_KEY=
403
+ XAI_API_KEY=
404
+ `;
405
+
406
+ // Preflight for the prompt path: a model run needs a key. If none is resolvable, seed a
407
+ // blank ~/.cortex/.env so there's a findable file to edit (it didn't exist before the first
408
+ // run), print clear guidance, and EXIT WITHOUT starting a server — so we never leave a
409
+ // keyless server running that would force a --shutdown + re-invoke. When a key IS present
410
+ // (a filled .env, or secrets in the environment), this is a no-op and the run proceeds —
411
+ // one-shot. The container case (secrets in env) hits the no-op path and writes nothing.
412
+ async function ensureKeysOrExit() {
413
+ if (hasAnyApiKey()) return;
414
+ // No key resolvable. If a server is already running, it was started with no key and has
415
+ // cached that empty config — so even after you add a key it would keep failing until a
416
+ // restart. Drain it now (graceful) so the NEXT run (with your key) starts a fresh server
417
+ // that picks the key up — no manual --shutdown dance.
418
+ try {
419
+ if (await isServerUp()) {
420
+ try { await fetch(`${BASE_URL}/shutdown`, { method: 'POST', signal: AbortSignal.timeout(5000) }); } catch { /* best-effort */ }
421
+ process.stderr.write('[cortex] Stopped the running server so it will pick up your key on the next run.\n');
422
+ }
423
+ } catch { /* never block on the drain */ }
424
+ const dir = getGlobalCortexDir();
425
+ const envPath = join(dir, '.env');
426
+ let seeded = false;
427
+ if (!existsSync(envPath)) {
428
+ try {
429
+ mkdirSync(dir, { recursive: true });
430
+ // Prefer the shipped .env.example (the full, canonical blank-value template) and
431
+ // copy it to ~/.cortex/.env — i.e. .env.example becomes .env, no codegen. Fall back
432
+ // to the minimal inline template only if the example isn't found in the install.
433
+ const example = [join(CLI_PKG_ROOT, '.env.example'), join(MONOREPO_ROOT, '.env.example')]
434
+ .find((p) => existsSync(p));
435
+ if (example) copyFileSync(example, envPath);
436
+ else writeFileSync(envPath, BLANK_ENV_TEMPLATE);
437
+ seeded = true;
438
+ } catch { /* read-only home — still print guidance below */ }
439
+ }
440
+ process.stderr.write('\n[cortex] No API key found — a model run needs one.\n');
441
+ if (seeded) process.stderr.write(`[cortex] Created ${envPath}\n`);
442
+ process.stderr.write('[cortex] Add a provider key, then run again:\n');
443
+ process.stderr.write(`[cortex] - edit ${envPath} (e.g. DEEPSEEK_API_KEY=sk-...)\n`);
444
+ process.stderr.write('[cortex] - or inject it via your environment / secrets store (read from there too)\n');
445
+ process.exit(1);
359
446
  }
360
447
 
361
448
  async function ensureServer() {
@@ -406,6 +493,14 @@ async function run() {
406
493
  await checkForUpdate();
407
494
  } catch { /* dist absent in a source checkout, or check failed — never block */ }
408
495
 
496
+ // A prompt/agent run needs a model key. Check BEFORE starting the server so we never
497
+ // leave a keyless server running (which forced the old --shutdown + re-invoke dance).
498
+ // Read-only ops (--stats/--sessions/--tmux/--pr, no prompt) don't need a key and are
499
+ // fine against a keyless server, so only gate the actual prompt path.
500
+ if (prompt) {
501
+ await ensureKeysOrExit();
502
+ }
503
+
409
504
  await ensureServer();
410
505
 
411
506
  // --tmux: list active tmux sessions (served by SandboxViewServer on port+1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexus-cortex/cli",
3
- "version": "4.36.2",
3
+ "version": "4.37.1",
4
4
  "description": "Nexus Cortex CLI - Terminal interface for multi-provider LLM orchestration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -77,6 +77,7 @@
77
77
  "bin",
78
78
  "README.md",
79
79
  "LICENSE",
80
- "NOTICE"
80
+ "NOTICE",
81
+ ".env.example"
81
82
  ]
82
83
  }