zelari-code 1.3.0 → 1.4.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/README.md CHANGED
@@ -37,6 +37,30 @@
37
37
  > `src/main/core/`, `src/agents/`, `src/shared/`, `src/types/` paths no longer
38
38
  > exist. The published core package is `@zelari/core` (MIT). 9 subpath exports.
39
39
 
40
+ ## Prerequisites
41
+
42
+ | Requirement | Version | Notes |
43
+ |---|---|---|
44
+ | **Node.js** | **≥ 20 LTS** | Earlier versions lack stable `fetch`, `AbortController.timeout`, and `node:test`. Works on 20.x and 22.x. |
45
+ | **npm** | **≥ 10** | Ships with Node 20 LTS; tested with npm 10 and 11. |
46
+ | **OS** | Linux, macOS, Windows 10/11 | Tested on Pop!_OS 24.04, macOS 15, Windows 11. Windows requires Git Bash (auto-detected). |
47
+ | **Disk** | ~50 MB for the CLI + `@zelari/core` | Models are not bundled — provider APIs are remote. |
48
+ | **Account + API key** | 1 of: xAI Grok, OpenAI-compatible, GLM/Z.AI, MiniMax, DeepSeek | OAuth Grok supported via `/login grok`. |
49
+
50
+ ### Optional (for v1.3.0 advanced tools)
51
+
52
+ These are **opt-in** — the CLI runs fine without them. The agent auto-skips a tool if its dependency is missing.
53
+
54
+ | Tool group | Dependency | Used by |
55
+ |---|---|---|
56
+ | `lsp_*` | Language server on PATH (e.g. `typescript-language-server`, `pyright-langserver`) + Node/Python LSP client libs | `lsp_definition`, `lsp_references`, `lsp_hover`, `lsp_symbols`, `lsp_rename` |
57
+ | `ast_*` | *(none)* | `ast_outline`, `ast_find_symbol` — TypeScript Compiler API, no LSP needed |
58
+ | `semantic_search` | Local embedding model (default `Xenova/all-MiniLM-L6-v2` via `@xenova/transformers`, downloads on first use) | `semantic_search`, `/index` |
59
+ | `browser_check` | Playwright (`npx playwright install chromium` once, ~150 MB) | `browser_check` |
60
+ | diagnostics loop | `eslint` and/or `ruff` on PATH (project-local preferred) | post-edit compile/lint feedback |
61
+
62
+ Disable any tool group: set `ZELARI_LSP=0`, `ZELARI_AST=0`, `ZELARI_SEMANTIC=0`, `ZELARI_BROWSER=0`, `ZELARI_DIAGNOSTICS=0`.
63
+
40
64
  ## Install
41
65
 
42
66
  ```bash
@@ -44,7 +68,23 @@ npm install -g zelari-code
44
68
  zelari-code
45
69
  ```
46
70
 
47
- Requires **Node.js ≥ 20**.
71
+ **Prerequisites:**
72
+ - **Node.js ≥ 20** — required. Without it the agent cannot run `npm`/`tsc`/builds, so zelari-code refuses to boot.
73
+ - **Git** — recommended. Without it, `/diff`, `/undo` and the git sidebar are disabled. Install from <https://git-scm.com>.
74
+ - **Git Bash** (Windows only) — recommended. The agent's `bash` tool needs real POSIX semantics (`ls`, `which`, `$VAR`, `&&`). Ships with Git for Windows.
75
+
76
+ After install, verify your environment:
77
+
78
+ ```bash
79
+ zelari-code --doctor # checks shim, bundle, PATH, node/git/bash in the agent shell
80
+ ```
81
+
82
+ > **Why `--doctor` matters on Windows:** the agent runs commands through Git
83
+ > Bash, which inherits a different `PATH` than the Node process. Node can be
84
+ > visible to PowerShell yet invisible to Git Bash (typical when Node was
85
+ > installed for "current user" only). `--doctor`'s `node (agent shell)` row
86
+ > catches this; the boot-time preflight (`runPreflight`) blocks the launch
87
+ > with an actionable message instead of letting the agent fail mid-task.
48
88
 
49
89
  ### `zelari-code: command not found` (Windows)
50
90
 
@@ -64,6 +104,8 @@ source ~/.bashrc
64
104
 
65
105
  Verify the fix: `where zelari-code` (CMD) or `which zelari-code` (Bash) should print a path.
66
106
 
107
+ **Node visible to PowerShell but not to Git Bash?** This is the dual-PATH problem: Node installed for "current user" only reaches the user shell, while Git Bash inherits the system `Path`. Fix: reinstall Node with "Add to PATH for **all users**", or add `C:\Program Files\nodejs\` to the **System** `Path` (not User). Confirm with `zelari-code --doctor` — the `node (agent shell)` row must read OK.
108
+
67
109
  ## First Run
68
110
 
69
111
  The first time you run `zelari-code` (or whenever your provider config
@@ -72,7 +114,7 @@ the regular TUI:
72
114
 
73
115
  ```
74
116
  ╭─────────────────────────────────────────────────╮
75
- │ zelari-code v0.6.0 — first-time setup │
117
+ │ zelari-code v1.3.0 — first-time setup │
76
118
  │ 1/welcome 2/provider 3/model 4/apikey 5/...│
77
119
  │ │
78
120
  │ Welcome! Let's get you coding in under 2 min. │
@@ -146,8 +188,12 @@ Full reference: **[docs/GUIDA.md](./docs/GUIDA.md#comandi-slash)** (all flags, e
146
188
  | `/steer <text>`, `/steer --interrupt <text>` | Queue follow-up during a run |
147
189
  | `/workspace …` | `.zelari/` artifacts + `AGENTS.MD` |
148
190
  | `/update`, `/update --yes` | Check / install CLI updates |
191
+ | `/mode [agent\|council\|zelari]` | Switch dispatch mode (shift+tab fallback) |
192
+ | `/checkpoint [label]` | Snapshot the working tree (rollback target) |
193
+ | `/rollback [id\|latest]` | Restore a checkpoint (revert / restore files atomically) |
194
+ | `/index` | Build / refresh the semantic search index |
149
195
 
150
- **TUI:** `shift+tab` cycles **agent** → **council** → **zelari** mode for free-form prompts.
196
+ **TUI:** `shift+tab` cycles **agent** → **council** → **zelari** mode for free-form prompts (with terminal-fallback hardening since v1.3.0). The equivalent command `/mode [agent|council|zelari]` works in any terminal.
151
197
 
152
198
  ## Headless Mode
153
199
 
@@ -185,6 +231,14 @@ Disable auto-check: `ANATHEMA_DEV=1 zelari-code`
185
231
  - ⏱️ **Execution timer** — elapsed time of the in-flight turn in the status line (`⏱ 12s`), frozen as `last 34s` when the run completes
186
232
  - 🧠 **Provider-agnostic** — OpenAI-compatible APIs (OpenAI, Together, Groq, custom), xAI Grok with OAuth refresh, GLM/Z.AI
187
233
  - 🛠️ **Built-in tools** — filesystem (read/write/edit), shell (bash), search (grep), web fetch/search
234
+ - 🧠 **LSP code intelligence** (`lsp_*` tools) — go-to-definition, find references, hover type, document symbols, rename symbol via real language servers (tsserver, pyright, …)
235
+ - 🌲 **AST structural tools** (`ast_*` tools) — symbol outline + find-by-name via the TypeScript compiler API, no language server needed
236
+ - 🔎 **Semantic search** (`semantic_search` + `/index`) — concept-level code search via embeddings, local-first
237
+ - 🌐 **Browser verification** (`browser_check`) — headless browser with click/fill/goto/wait actions, console + network + screenshot capture for visual verification of web work
238
+ - 🔁 **Post-edit diagnostics loop** — after every `edit_file`/`write_file` the harness runs project lint/compile (`eslint`, `ruff`, LSP-pluggable) and surfaces the errors to the model in the same turn (opt out: `ZELARI_DIAGNOSTICS=0`)
239
+ - 💾 **Prompt-cache accounting** — tracks prompt-cache hit rate per provider/model and surfaces it in the status bar (`cache 73%`) so you can see when a session is amortizing its prefix
240
+ - 🧷 **Workspace checkpoints** — `/checkpoint [label]` + `/rollback [id|latest]` use git plumbing to snapshot the working tree (tracked + untracked) and restore it atomically; every zelari-mode mission takes one before starting
241
+ - 🤝 **Sub-agent delegation** (`task` tool) — delegate a focused read-only research sub-task to an isolated sub-agent with its own fresh context, max 12 tool turns, no write/bash/recursion
188
242
  - 📚 **23 coding skills** (+ user `SKILL.md` from `.zelari/skills/`, `.claude/skills/`, …)
189
243
  - 🔄 **Cross-provider failover** — automatic retry with provider swap on transient errors
190
244
  - 📊 **Metrics + skill history** — fire-and-forget logging to `~/.tmp/zelari-code/`
@@ -232,6 +286,16 @@ zelari-code (CLI, proprietary)
232
286
  | `ZELARI_MISSION_AUTO=1` | Auto-start Zelari missions (skip the brief confirmation) |
233
287
  | `ZELARI_MISSION_MAX_ITER` | Max Zelari mission iterations (default 10) |
234
288
  | `ZELARI_MODE_MAX_TOOLS_LUCIFER` | Chairman (Lucifero) tool budget in zelari-mode (default 30) |
289
+ | `ZELARI_DIAGNOSTICS=0` | Disable the post-edit compiler/lint diagnostics loop |
290
+ | `ZELARI_DIAGNOSTICS_TIMEOUT_MS` | Timeout of the diagnostics loop (default 5000) |
291
+ | `ZELARI_AST=0` | Disable AST structural tools (`ast_*`) |
292
+ | `ZELARI_SEMANTIC=0` | Disable semantic search + `/index` |
293
+ | `ZELARI_SEMANTIC_FILE` | Override the embeddings store path |
294
+ | `ZELARI_EMBED_MODEL` | Embedding model id (default `Xenova/all-MiniLM-L6-v2`) |
295
+ | `ZELARI_BROWSER=0` | Disable `browser_check` |
296
+ | `ZELARI_LSP=0` | Disable LSP tools (`lsp_*`) |
297
+ | `ZELARI_CHECKPOINT=0` | Disable automatic workspace checkpoints in zelari-mode |
298
+ | `ZELARI_TOOL_OUTPUT_LINES` | Lines of tool output shown in the TUI (default 8) |
235
299
 
236
300
  See **[docs/GUIDA.md](./docs/GUIDA.md#variabili-dambiente)** for the full list.
237
301
 
@@ -26534,13 +26534,273 @@ var init_zelariMission = __esm({
26534
26534
  }
26535
26535
  });
26536
26536
 
26537
+ // src/cli/utils/prereqChecks.ts
26538
+ var prereqChecks_exports = {};
26539
+ __export(prereqChecks_exports, {
26540
+ checkAgentBash: () => checkAgentBash,
26541
+ checkAgentGit: () => checkAgentGit,
26542
+ checkAgentNode: () => checkAgentNode,
26543
+ checkMainNode: () => checkMainNode,
26544
+ runPrereqChecks: () => runPrereqChecks
26545
+ });
26546
+ import { execSync, spawnSync as spawnSync2 } from "node:child_process";
26547
+ import { existsSync as existsSync25 } from "node:fs";
26548
+ function resolveAgentShellSync() {
26549
+ if (process.platform !== "win32") {
26550
+ return { bashPath: null, isBash: true, via: "/bin/sh" };
26551
+ }
26552
+ const envShell = process.env.ZELARI_SHELL;
26553
+ if (envShell && envShell.trim().length > 0 && existsSyncSafe2(envShell)) {
26554
+ return { bashPath: envShell, isBash: true, via: `bash (${envShell})` };
26555
+ }
26556
+ const sessionShell = process.env.SHELL;
26557
+ if (sessionShell && sessionShell.trim().length > 0 && existsSyncSafe2(sessionShell)) {
26558
+ return {
26559
+ bashPath: sessionShell,
26560
+ isBash: true,
26561
+ via: `bash (${sessionShell})`
26562
+ };
26563
+ }
26564
+ for (const p3 of STANDARD_BASH_PATHS2) {
26565
+ if (existsSyncSafe2(p3)) {
26566
+ return { bashPath: p3, isBash: true, via: `bash (${p3})` };
26567
+ }
26568
+ }
26569
+ try {
26570
+ const result = spawnSync2("where", ["bash"], {
26571
+ encoding: "utf8",
26572
+ windowsHide: true
26573
+ });
26574
+ if (result.status === 0 && result.stdout) {
26575
+ const first = result.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
26576
+ if (first && existsSyncSafe2(first)) {
26577
+ const trimmed = first.trim();
26578
+ return { bashPath: trimmed, isBash: true, via: `bash (${trimmed})` };
26579
+ }
26580
+ }
26581
+ } catch {
26582
+ }
26583
+ return { bashPath: null, isBash: false, via: "cmd.exe" };
26584
+ }
26585
+ function existsSyncSafe2(p3) {
26586
+ try {
26587
+ return existsSync25(p3);
26588
+ } catch {
26589
+ return false;
26590
+ }
26591
+ }
26592
+ function probeTool(tool) {
26593
+ const shell = resolveAgentShellSync();
26594
+ let stdout = "";
26595
+ if (shell.bashPath) {
26596
+ try {
26597
+ const r = spawnSync2(shell.bashPath, ["-c", `${tool} --version`], {
26598
+ encoding: "utf8",
26599
+ stdio: ["ignore", "pipe", "ignore"],
26600
+ windowsHide: true
26601
+ });
26602
+ if (r.status === 0) stdout = (r.stdout || "").trim();
26603
+ } catch {
26604
+ }
26605
+ } else if (process.platform === "win32") {
26606
+ try {
26607
+ stdout = execSync(`${tool} --version`, {
26608
+ encoding: "utf8",
26609
+ stdio: ["ignore", "pipe", "ignore"]
26610
+ }).trim();
26611
+ } catch {
26612
+ }
26613
+ } else {
26614
+ try {
26615
+ stdout = execSync(`${tool} --version`, {
26616
+ encoding: "utf8",
26617
+ stdio: ["ignore", "pipe", "ignore"]
26618
+ }).trim();
26619
+ } catch {
26620
+ }
26621
+ }
26622
+ const m = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
26623
+ return {
26624
+ found: stdout.length > 0,
26625
+ version: m ? `${m[1]}.${m[2]}.${m[3]}` : "",
26626
+ raw: stdout
26627
+ };
26628
+ }
26629
+ function nodeMissingHint() {
26630
+ const shell = resolveAgentShellSync();
26631
+ if (process.platform === "win32") {
26632
+ return `node is not reachable from the agent's shell (${shell.via}).
26633
+ This usually means Node was installed for "current user" only,
26634
+ while Git Bash inherits the SYSTEM Path. Fix (pick one):
26635
+ - Reinstall Node (https://nodejs.org) and choose
26636
+ "Add to PATH for all users", OR
26637
+ - Add C:\\Program Files\\nodejs\\ to the SYSTEM Path
26638
+ (System Properties \u2192 Environment Variables \u2192 Path), OR
26639
+ - Set ZELARI_SHELL to a bash that already sees node.`;
26640
+ }
26641
+ return `node is not on the agent's shell PATH.
26642
+ Install Node >= ${MIN_NODE_MAJOR} (https://nodejs.org) or, if you use
26643
+ nvm, run \`nvm use <version>\` and reopen this terminal.`;
26644
+ }
26645
+ function checkAgentNode() {
26646
+ const probe = probeTool("node");
26647
+ if (!probe.found) {
26648
+ return {
26649
+ ok: false,
26650
+ severity: "critical",
26651
+ tool: "node",
26652
+ message: nodeMissingHint()
26653
+ };
26654
+ }
26655
+ const major = Number(probe.version.split(".")[0]);
26656
+ if (!probe.version || Number.isNaN(major)) {
26657
+ return {
26658
+ ok: false,
26659
+ severity: "warn",
26660
+ tool: "node",
26661
+ message: `could not parse node version from "${probe.raw}" (node is reachable, but version check skipped)`
26662
+ };
26663
+ }
26664
+ if (major < MIN_NODE_MAJOR) {
26665
+ return {
26666
+ ok: false,
26667
+ severity: "critical",
26668
+ tool: "node",
26669
+ message: `node ${probe.version} is older than the required >= ${MIN_NODE_MAJOR}.0.0 (from engines.node). Upgrade: https://nodejs.org`
26670
+ };
26671
+ }
26672
+ return {
26673
+ ok: true,
26674
+ severity: "critical",
26675
+ tool: "node",
26676
+ message: `node ${probe.version} (agent shell)`
26677
+ };
26678
+ }
26679
+ function checkAgentGit() {
26680
+ const probe = probeTool("git");
26681
+ if (!probe.found) {
26682
+ const hint = process.platform === "win32" ? `Install Git for Windows: https://git-scm.com/download/win` : `Install git (e.g. \`brew install git\` on macOS, \`apt install git\` on Debian)`;
26683
+ return {
26684
+ ok: false,
26685
+ severity: "warn",
26686
+ tool: "git",
26687
+ message: `git not found on the agent's shell PATH \u2014 /diff, /undo and the git sidebar will be disabled. ${hint}`
26688
+ };
26689
+ }
26690
+ return {
26691
+ ok: true,
26692
+ severity: "warn",
26693
+ tool: "git",
26694
+ message: `git ${probe.version} (agent shell)`
26695
+ };
26696
+ }
26697
+ function checkAgentBash() {
26698
+ if (process.platform !== "win32") {
26699
+ return {
26700
+ ok: true,
26701
+ severity: "warn",
26702
+ tool: "bash",
26703
+ message: "POSIX shell always available on this platform"
26704
+ };
26705
+ }
26706
+ const shell = resolveAgentShellSync();
26707
+ if (shell.isBash) {
26708
+ return {
26709
+ ok: true,
26710
+ severity: "warn",
26711
+ tool: "bash",
26712
+ message: `real bash available (${shell.via})`
26713
+ };
26714
+ }
26715
+ return {
26716
+ ok: false,
26717
+ severity: "warn",
26718
+ tool: "bash",
26719
+ message: `no Git Bash found \u2014 the agent's \`bash\` tool falls back to cmd.exe,
26720
+ where POSIX commands (ls, which, $VAR, &&) may fail. Install Git
26721
+ for Windows (https://git-scm.com/download/win) or set ZELARI_SHELL
26722
+ to your bash binary.`
26723
+ };
26724
+ }
26725
+ function checkMainNode() {
26726
+ try {
26727
+ const raw = execSync("node --version", {
26728
+ encoding: "utf8",
26729
+ stdio: ["ignore", "pipe", "ignore"]
26730
+ }).trim();
26731
+ const m = raw.match(/(\d+)\.(\d+)\.(\d+)/);
26732
+ const version2 = m ? `${m[1]}.${m[2]}.${m[3]}` : "";
26733
+ const major = Number(version2.split(".")[0]);
26734
+ if (version2 && !Number.isNaN(major) && major >= MIN_NODE_MAJOR) {
26735
+ return {
26736
+ ok: true,
26737
+ severity: "critical",
26738
+ tool: "node",
26739
+ message: `node ${version2} (main process)`
26740
+ };
26741
+ }
26742
+ return {
26743
+ ok: false,
26744
+ severity: "warn",
26745
+ tool: "node",
26746
+ message: `node present in main process but version unparseable or < ${MIN_NODE_MAJOR}: "${raw}"`
26747
+ };
26748
+ } catch {
26749
+ return {
26750
+ ok: false,
26751
+ severity: "critical",
26752
+ tool: "node",
26753
+ message: "node not found on the main process PATH (zelari-code itself runs on node \u2014 this is unexpected)"
26754
+ };
26755
+ }
26756
+ }
26757
+ function runPrereqChecks(opts = { mode: "preflight" }) {
26758
+ const checks = [
26759
+ () => checkAgentNode(),
26760
+ () => checkAgentGit(),
26761
+ () => checkAgentBash()
26762
+ ];
26763
+ const results = [];
26764
+ for (const run of checks) {
26765
+ try {
26766
+ results.push(run());
26767
+ } catch (err) {
26768
+ results.push({
26769
+ ok: false,
26770
+ severity: "warn",
26771
+ tool: "node",
26772
+ message: `prereq check crashed: ${err instanceof Error ? err.message : String(err)}`
26773
+ });
26774
+ }
26775
+ }
26776
+ const hasCriticalFail = results.some(
26777
+ (r) => !r.ok && r.severity === "critical"
26778
+ );
26779
+ const warnings = results.filter((r) => !r.ok && r.severity === "warn");
26780
+ void opts.mode;
26781
+ return { results, hasCriticalFail, warnings };
26782
+ }
26783
+ var MIN_NODE_MAJOR, STANDARD_BASH_PATHS2;
26784
+ var init_prereqChecks = __esm({
26785
+ "src/cli/utils/prereqChecks.ts"() {
26786
+ "use strict";
26787
+ MIN_NODE_MAJOR = 20;
26788
+ STANDARD_BASH_PATHS2 = [
26789
+ "C:\\Program Files\\Git\\bin\\bash.exe",
26790
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
26791
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
26792
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
26793
+ ];
26794
+ }
26795
+ });
26796
+
26537
26797
  // src/cli/utils/doctor.ts
26538
26798
  var doctor_exports = {};
26539
26799
  __export(doctor_exports, {
26540
26800
  runDoctor: () => runDoctor
26541
26801
  });
26542
- import { execSync } from "node:child_process";
26543
- import { existsSync as existsSync29, readFileSync as readFileSync25, readlinkSync, statSync as statSync6 } from "node:fs";
26802
+ import { execSync as execSync2 } from "node:child_process";
26803
+ import { existsSync as existsSync30, readFileSync as readFileSync25, readlinkSync, statSync as statSync6 } from "node:fs";
26544
26804
  import { createRequire as createRequire2 } from "node:module";
26545
26805
  import { fileURLToPath as fileURLToPath2 } from "node:url";
26546
26806
  import path32 from "node:path";
@@ -26548,7 +26808,7 @@ function findPackageRoot(start) {
26548
26808
  let dir = start;
26549
26809
  for (let i = 0; i < 6; i += 1) {
26550
26810
  const candidate = path32.join(dir, "package.json");
26551
- if (existsSync29(candidate)) {
26811
+ if (existsSync30(candidate)) {
26552
26812
  try {
26553
26813
  const pkg = JSON.parse(readFileSync25(candidate, "utf8"));
26554
26814
  if (pkg.name === "zelari-code") return dir;
@@ -26563,7 +26823,7 @@ function findPackageRoot(start) {
26563
26823
  }
26564
26824
  function tryExec(cmd) {
26565
26825
  try {
26566
- return execSync(cmd, {
26826
+ return execSync2(cmd, {
26567
26827
  encoding: "utf8",
26568
26828
  stdio: ["ignore", "pipe", "ignore"]
26569
26829
  }).trim();
@@ -26590,7 +26850,7 @@ function checkShim(pkgName) {
26590
26850
  const isWin = process.platform === "win32";
26591
26851
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
26592
26852
  const shimPath = path32.join(prefix, shimName);
26593
- if (!existsSync29(shimPath)) {
26853
+ if (!existsSync30(shimPath)) {
26594
26854
  return FAIL(
26595
26855
  `shim not found at ${shimPath}
26596
26856
  fix: npm install -g ${pkgName}@latest --force`
@@ -26658,7 +26918,7 @@ function checkNode(pkg) {
26658
26918
  }
26659
26919
  function checkBundle() {
26660
26920
  const bundle = path32.join(packageRoot, "dist", "cli", "main.bundled.js");
26661
- if (!existsSync29(bundle)) {
26921
+ if (!existsSync30(bundle)) {
26662
26922
  return FAIL(
26663
26923
  `dist/cli/main.bundled.js missing at ${bundle}
26664
26924
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -26709,15 +26969,28 @@ function checkPath() {
26709
26969
  fix (Windows): $env:Path = "$(npm prefix -g);$env:Path"`
26710
26970
  );
26711
26971
  }
26972
+ function prereqToCheckResult(r) {
26973
+ if (r.ok) return OK(r.message);
26974
+ return r.severity === "critical" ? FAIL(r.message, "critical") : WARN(r.message);
26975
+ }
26712
26976
  function runDoctor() {
26713
26977
  const pkg = readPackageJson3();
26714
26978
  const pkgName = pkg?.name ?? "zelari-code";
26715
26979
  const checks = [
26980
+ // --- install-health checks (main-process probes) ---
26716
26981
  { name: "node", run: () => checkNode(pkg) },
26717
26982
  { name: "bin shim", run: () => checkShim(pkgName) },
26718
26983
  { name: "cli bundle", run: () => checkBundle() },
26719
26984
  { name: "runtime deps", run: () => checkRuntimeDeps() },
26720
- { name: "PATH", run: () => checkPath() }
26985
+ { name: "PATH", run: () => checkPath() },
26986
+ // --- agent-shell checks (v1.4.0) ---
26987
+ // These probe node/git/bash THROUGH the resolved shell the agent uses.
26988
+ // A pass on "node" + a FAIL on "node (agent shell)" is the tell-tale
26989
+ // signature of the PATH-mismatch bug (node visible to the main process,
26990
+ // invisible to Git Bash) that silently breaks council builds.
26991
+ { name: "node (agent shell)", run: () => prereqToCheckResult(checkAgentNode()) },
26992
+ { name: "git (agent shell)", run: () => prereqToCheckResult(checkAgentGit()) },
26993
+ { name: "bash", run: () => prereqToCheckResult(checkAgentBash()) }
26721
26994
  ];
26722
26995
  console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
26723
26996
  console.log("platform:", process.platform, process.arch);
@@ -26759,6 +27032,7 @@ var require3, __dirname3, packageRoot, OK, FAIL, WARN;
26759
27032
  var init_doctor = __esm({
26760
27033
  "src/cli/utils/doctor.ts"() {
26761
27034
  "use strict";
27035
+ init_prereqChecks();
26762
27036
  require3 = createRequire2(import.meta.url);
26763
27037
  __dirname3 = path32.dirname(fileURLToPath2(import.meta.url));
26764
27038
  packageRoot = findPackageRoot(__dirname3);
@@ -33931,12 +34205,18 @@ async function handleUpdatePerform(ctx) {
33931
34205
  const { performUpdate: performUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
33932
34206
  const res = await performUpdate2();
33933
34207
  if (res.ok) {
34208
+ let prereqBlock = "";
34209
+ try {
34210
+ const { runPrereqChecks: runPrereqChecks2 } = await Promise.resolve().then(() => (init_prereqChecks(), prereqChecks_exports));
34211
+ const { warnings } = runPrereqChecks2({ mode: "preflight" });
34212
+ if (warnings.length > 0) {
34213
+ prereqBlock = "\n\n\u26A0 Prerequisite warnings (the agent may be limited after restart):\n" + warnings.map((w) => ` - ${w.tool}: ${w.message.replace(/\n/g, "\n ")}`).join("\n") + "\n Run `zelari-code --doctor` after restart for the full report.";
34214
+ }
34215
+ } catch {
34216
+ }
33934
34217
  appendSystem(
33935
34218
  ctx.setMessages,
33936
- `[update] \u2705 installed successfully
33937
-
33938
- Please restart zelari-code manually to use the new version.
33939
- (exit with /exit or Ctrl+C, then run \`zelari-code\` again)`
34219
+ "[update] \u2705 installed successfully\n\nPlease restart zelari-code manually to use the new version.\n(exit with /exit or Ctrl+C, then run `zelari-code` again)" + prereqBlock
33940
34220
  );
33941
34221
  } else {
33942
34222
  const output = res.output?.trim() || "(empty)";
@@ -34012,7 +34292,7 @@ async function handlePromoteMember(ctx, memberId) {
34012
34292
  }
34013
34293
 
34014
34294
  // src/cli/branchManager.ts
34015
- import { promises as fs17, existsSync as existsSync25, readFileSync as readFileSync23, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
34295
+ import { promises as fs17, existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
34016
34296
  import path29 from "node:path";
34017
34297
  import os10 from "node:os";
34018
34298
  var META_FILENAME = "meta.json";
@@ -34034,7 +34314,7 @@ function sessionsPathFor(name, baseDir) {
34034
34314
  }
34035
34315
  function readBranchMeta(name, baseDir) {
34036
34316
  const metaPath = metaPathFor(name, baseDir);
34037
- if (!existsSync25(metaPath)) {
34317
+ if (!existsSync26(metaPath)) {
34038
34318
  throw new BranchNotFoundError(`Branch "${name}" not found`);
34039
34319
  }
34040
34320
  try {
@@ -34094,7 +34374,7 @@ var SessionNotFoundError = class extends Error {
34094
34374
  };
34095
34375
  function branchExists(name, baseDir = getBranchesBaseDir()) {
34096
34376
  const bp = branchPathFor(name, baseDir);
34097
- return existsSync25(bp) && existsSync25(metaPathFor(name, baseDir));
34377
+ return existsSync26(bp) && existsSync26(metaPathFor(name, baseDir));
34098
34378
  }
34099
34379
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
34100
34380
  if (!name || name.trim().length === 0) {
@@ -34107,7 +34387,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
34107
34387
  throw new BranchAlreadyExistsError(name);
34108
34388
  }
34109
34389
  const sourcePath = path29.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
34110
- if (!existsSync25(sourcePath)) {
34390
+ if (!existsSync26(sourcePath)) {
34111
34391
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
34112
34392
  }
34113
34393
  const branchPath = branchPathFor(name, baseDir);
@@ -34140,7 +34420,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
34140
34420
  const results = [];
34141
34421
  for (const entry of entries) {
34142
34422
  const metaPath = metaPathFor(entry, baseDir);
34143
- if (!existsSync25(metaPath)) continue;
34423
+ if (!existsSync26(metaPath)) continue;
34144
34424
  try {
34145
34425
  const meta3 = readBranchMeta(entry, baseDir);
34146
34426
  const sessionCount = await countSessions(entry, baseDir);
@@ -34676,7 +34956,7 @@ import path31 from "node:path";
34676
34956
  import os11 from "node:os";
34677
34957
 
34678
34958
  // src/cli/skillHistory.ts
34679
- import { promises as fs19, existsSync as existsSync26, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync11 } from "node:fs";
34959
+ import { promises as fs19, existsSync as existsSync27, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync11 } from "node:fs";
34680
34960
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
34681
34961
  async function readSkillHistory(file2) {
34682
34962
  let raw = "";
@@ -35620,9 +35900,9 @@ function SplashGate({
35620
35900
  init_providerConfig();
35621
35901
 
35622
35902
  // src/cli/wizard/firstRun.ts
35623
- import { existsSync as existsSync27 } from "node:fs";
35903
+ import { existsSync as existsSync28 } from "node:fs";
35624
35904
  function shouldRunWizard(input) {
35625
- const exists = input.exists ?? existsSync27;
35905
+ const exists = input.exists ?? existsSync28;
35626
35906
  if (input.hasResetConfigFlag) {
35627
35907
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
35628
35908
  }
@@ -36112,7 +36392,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
36112
36392
 
36113
36393
  // src/cli/skillsMd.ts
36114
36394
  init_skills2();
36115
- import { existsSync as existsSync28, readdirSync as readdirSync4, readFileSync as readFileSync24 } from "node:fs";
36395
+ import { existsSync as existsSync29, readdirSync as readdirSync4, readFileSync as readFileSync24 } from "node:fs";
36116
36396
  import { join as join23 } from "node:path";
36117
36397
  import { homedir as homedir6 } from "node:os";
36118
36398
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
@@ -36190,7 +36470,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36190
36470
  const summary = { loaded: [], skipped: [] };
36191
36471
  const seen = new Set(options.existingIds ?? []);
36192
36472
  for (const dir of skillMdSearchDirs(projectRoot)) {
36193
- if (!existsSync28(dir)) continue;
36473
+ if (!existsSync29(dir)) continue;
36194
36474
  let entries;
36195
36475
  try {
36196
36476
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -36199,7 +36479,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36199
36479
  }
36200
36480
  for (const entry of entries) {
36201
36481
  const skillPath = join23(dir, entry, "SKILL.md");
36202
- if (!existsSync28(skillPath)) continue;
36482
+ if (!existsSync29(skillPath)) continue;
36203
36483
  try {
36204
36484
  const parsed = parseSkillMd(readFileSync24(skillPath, "utf8"), skillPath);
36205
36485
  if (!parsed) {
@@ -36225,6 +36505,38 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36225
36505
  init_skills2();
36226
36506
  init_updater();
36227
36507
  var VERSION = getCurrentVersion();
36508
+ function runPreflight() {
36509
+ if (process.env.ZELARI_SKIP_PREFLIGHT === "1") return;
36510
+ if (process.argv.includes("--skip-checks")) return;
36511
+ if (process.env.ANATHEMA_DEV === "1") return;
36512
+ const { runPrereqChecks: runPrereqChecks2 } = (
36513
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
36514
+ (init_prereqChecks(), __toCommonJS(prereqChecks_exports))
36515
+ );
36516
+ const { results, hasCriticalFail, warnings } = runPrereqChecks2({
36517
+ mode: "preflight"
36518
+ });
36519
+ for (const w of warnings) {
36520
+ console.error(`\x1B[33m[zelari-code] \u26A0 ${w.tool}: ${w.message}\x1B[0m`);
36521
+ }
36522
+ if (hasCriticalFail) {
36523
+ const critical = results.find(
36524
+ (r) => !r.ok && r.severity === "critical"
36525
+ );
36526
+ console.error("");
36527
+ console.error(
36528
+ "\x1B[31m==============================================================\n zelari-code cannot start: a critical prerequisite is missing.\n==============================================================\x1B[0m"
36529
+ );
36530
+ if (critical) {
36531
+ console.error(`
36532
+ ${critical.tool}: ${critical.message}`);
36533
+ }
36534
+ console.error(
36535
+ "\n Run `zelari-code --doctor` for the full diagnostic report.\n Bypass this check with ZELARI_SKIP_PREFLIGHT=1 (NOT recommended \u2014\n the agent will still fail when it tries to run npm/build/tsc)."
36536
+ );
36537
+ process.exit(1);
36538
+ }
36539
+ }
36228
36540
  async function backgroundUpdateCheck() {
36229
36541
  if (process.env.ANATHEMA_DEV === "1") return;
36230
36542
  await new Promise((resolve) => setTimeout(resolve, 3e3));
@@ -36264,7 +36576,7 @@ function pickRootComponent() {
36264
36576
  }
36265
36577
  if (argv.includes("--help") || argv.includes("-h")) {
36266
36578
  console.log(
36267
- "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --council Use the 6-member council pipeline\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ANATHEMA_DEV=1 Disable background update check\n"
36579
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --council Use the 6-member council pipeline\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
36268
36580
  );
36269
36581
  process.exit(0);
36270
36582
  }
@@ -36314,6 +36626,7 @@ function loadUserSkills() {
36314
36626
  function main() {
36315
36627
  const picked = pickRootComponent();
36316
36628
  if (picked.kind === "done") return;
36629
+ runPreflight();
36317
36630
  loadUserSkills();
36318
36631
  if (picked.kind === "headless") {
36319
36632
  void runHeadless(picked.headlessOpts).then((code) => {