zelari-code 1.0.1 → 1.0.3

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.
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __crq } from 'node:module'; const require = __crq(import.meta.url);
3
3
  var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
7
  var __esm = (fn, res) => function __init() {
6
8
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
9
  };
@@ -9,6 +11,15 @@ var __export = (target, all) => {
9
11
  for (var name in all)
10
12
  __defProp(target, name, { get: all[name], enumerable: true });
11
13
  };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
12
23
 
13
24
  // src/cli/grokOAuth.ts
14
25
  var grokOAuth_exports = {};
@@ -1522,10 +1533,10 @@ function mergeDefs(...defs) {
1522
1533
  function cloneDef(schema) {
1523
1534
  return mergeDefs(schema._zod.def);
1524
1535
  }
1525
- function getElementAtPath(obj, path24) {
1526
- if (!path24)
1536
+ function getElementAtPath(obj, path25) {
1537
+ if (!path25)
1527
1538
  return obj;
1528
- return path24.reduce((acc, key) => acc?.[key], obj);
1539
+ return path25.reduce((acc, key) => acc?.[key], obj);
1529
1540
  }
1530
1541
  function promiseAllObject(promisesObj) {
1531
1542
  const keys = Object.keys(promisesObj);
@@ -1853,11 +1864,11 @@ function explicitlyAborted(x, startIndex = 0) {
1853
1864
  }
1854
1865
  return false;
1855
1866
  }
1856
- function prefixIssues(path24, issues) {
1867
+ function prefixIssues(path25, issues) {
1857
1868
  return issues.map((iss) => {
1858
1869
  var _a3;
1859
1870
  (_a3 = iss).path ?? (_a3.path = []);
1860
- iss.path.unshift(path24);
1871
+ iss.path.unshift(path25);
1861
1872
  return iss;
1862
1873
  });
1863
1874
  }
@@ -2075,16 +2086,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2075
2086
  }
2076
2087
  function formatError(error51, mapper = (issue2) => issue2.message) {
2077
2088
  const fieldErrors = { _errors: [] };
2078
- const processError = (error52, path24 = []) => {
2089
+ const processError = (error52, path25 = []) => {
2079
2090
  for (const issue2 of error52.issues) {
2080
2091
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2081
- issue2.errors.map((issues) => processError({ issues }, [...path24, ...issue2.path]));
2092
+ issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
2082
2093
  } else if (issue2.code === "invalid_key") {
2083
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
2094
+ processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2084
2095
  } else if (issue2.code === "invalid_element") {
2085
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
2096
+ processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2086
2097
  } else {
2087
- const fullpath = [...path24, ...issue2.path];
2098
+ const fullpath = [...path25, ...issue2.path];
2088
2099
  if (fullpath.length === 0) {
2089
2100
  fieldErrors._errors.push(mapper(issue2));
2090
2101
  } else {
@@ -2111,17 +2122,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2111
2122
  }
2112
2123
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2113
2124
  const result = { errors: [] };
2114
- const processError = (error52, path24 = []) => {
2125
+ const processError = (error52, path25 = []) => {
2115
2126
  var _a3, _b;
2116
2127
  for (const issue2 of error52.issues) {
2117
2128
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2118
- issue2.errors.map((issues) => processError({ issues }, [...path24, ...issue2.path]));
2129
+ issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
2119
2130
  } else if (issue2.code === "invalid_key") {
2120
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
2131
+ processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2121
2132
  } else if (issue2.code === "invalid_element") {
2122
- processError({ issues: issue2.issues }, [...path24, ...issue2.path]);
2133
+ processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
2123
2134
  } else {
2124
- const fullpath = [...path24, ...issue2.path];
2135
+ const fullpath = [...path25, ...issue2.path];
2125
2136
  if (fullpath.length === 0) {
2126
2137
  result.errors.push(mapper(issue2));
2127
2138
  continue;
@@ -2153,8 +2164,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2153
2164
  }
2154
2165
  function toDotPath(_path) {
2155
2166
  const segs = [];
2156
- const path24 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2157
- for (const seg of path24) {
2167
+ const path25 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2168
+ for (const seg of path25) {
2158
2169
  if (typeof seg === "number")
2159
2170
  segs.push(`[${seg}]`);
2160
2171
  else if (typeof seg === "symbol")
@@ -15657,13 +15668,13 @@ function resolveRef(ref, ctx) {
15657
15668
  if (!ref.startsWith("#")) {
15658
15669
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15659
15670
  }
15660
- const path24 = ref.slice(1).split("/").filter(Boolean);
15661
- if (path24.length === 0) {
15671
+ const path25 = ref.slice(1).split("/").filter(Boolean);
15672
+ if (path25.length === 0) {
15662
15673
  return ctx.rootSchema;
15663
15674
  }
15664
15675
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15665
- if (path24[0] === defsKey) {
15666
- const key = path24[1];
15676
+ if (path25[0] === defsKey) {
15677
+ const key = path25[1];
15667
15678
  if (!key || !ctx.defs[key]) {
15668
15679
  throw new Error(`Reference not found: ${ref}`);
15669
15680
  }
@@ -17693,11 +17704,11 @@ var init_tools = __esm({
17693
17704
  if (!ctx.addDocument)
17694
17705
  return "Knowledge vault tool not available.";
17695
17706
  const title = args["title"] || "New Document";
17696
- const path24 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17707
+ const path25 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17697
17708
  const content = args["content"] || "";
17698
17709
  const tags = args["tags"] || [];
17699
17710
  ctx.addDocument({
17700
- path: path24,
17711
+ path: path25,
17701
17712
  title,
17702
17713
  content,
17703
17714
  format: "markdown",
@@ -17706,7 +17717,7 @@ var init_tools = __esm({
17706
17717
  workspaceId: ctx.workspaceId
17707
17718
  });
17708
17719
  ctx.addActivity("vault", "created document", title);
17709
- return `Document "${title}" created at "${path24}".`;
17720
+ return `Document "${title}" created at "${path25}".`;
17710
17721
  }
17711
17722
  }
17712
17723
  ];
@@ -20212,11 +20223,11 @@ var init_synthesisAudit = __esm({
20212
20223
  import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
20213
20224
  import { join as join2 } from "node:path";
20214
20225
  function loadNfrSpec(zelariRoot) {
20215
- const path24 = join2(zelariRoot, "nfr-spec.json");
20216
- if (!existsSync8(path24))
20226
+ const path25 = join2(zelariRoot, "nfr-spec.json");
20227
+ if (!existsSync8(path25))
20217
20228
  return null;
20218
20229
  try {
20219
- const raw = JSON.parse(readFileSync6(path24, "utf8"));
20230
+ const raw = JSON.parse(readFileSync6(path25, "utf8"));
20220
20231
  if (raw.version !== 1 || !Array.isArray(raw.targets))
20221
20232
  return null;
20222
20233
  return raw;
@@ -22405,9 +22416,9 @@ var init_types2 = __esm({
22405
22416
  import { readFileSync as readFileSync11 } from "node:fs";
22406
22417
  import { join as join8 } from "node:path";
22407
22418
  function readLessonsDeduped(zelariRoot) {
22408
- const path24 = join8(zelariRoot, LESSONS_FILE);
22419
+ const path25 = join8(zelariRoot, LESSONS_FILE);
22409
22420
  try {
22410
- const raw = readFileSync11(path24, "utf8");
22421
+ const raw = readFileSync11(path25, "utf8");
22411
22422
  const byId = /* @__PURE__ */ new Map();
22412
22423
  for (const line of raw.split(/\r?\n/)) {
22413
22424
  if (!line.trim())
@@ -22508,8 +22519,8 @@ function keywordsFrom(check2, signature) {
22508
22519
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
22509
22520
  }
22510
22521
  function writeLesson(zelariRoot, lesson) {
22511
- const path24 = join9(zelariRoot, LESSONS_FILE);
22512
- appendFileSync2(path24, `${JSON.stringify(lesson)}
22522
+ const path25 = join9(zelariRoot, LESSONS_FILE);
22523
+ appendFileSync2(path25, `${JSON.stringify(lesson)}
22513
22524
  `, "utf8");
22514
22525
  }
22515
22526
  function findSimilar(lessons, signature) {
@@ -23649,28 +23660,28 @@ var init_storage = __esm({
23649
23660
  VALID_SCALARS = /^(true|false|null|~)$/i;
23650
23661
  Storage = class {
23651
23662
  /** Read a Markdown file with frontmatter. Throws if not found. */
23652
- read(path24) {
23653
- if (!existsSync13(path24)) {
23654
- throw new Error(`File not found: ${path24}`);
23663
+ read(path25) {
23664
+ if (!existsSync13(path25)) {
23665
+ throw new Error(`File not found: ${path25}`);
23655
23666
  }
23656
- const md = readFileSync13(path24, "utf8");
23667
+ const md = readFileSync13(path25, "utf8");
23657
23668
  return parseFrontmatter(md);
23658
23669
  }
23659
23670
  /** Read a Markdown file; returns null if not found. */
23660
- readIfExists(path24) {
23661
- if (!existsSync13(path24)) return null;
23662
- return this.read(path24);
23671
+ readIfExists(path25) {
23672
+ if (!existsSync13(path25)) return null;
23673
+ return this.read(path25);
23663
23674
  }
23664
23675
  /**
23665
23676
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
23666
23677
  * The meta object is serialized as YAML frontmatter; body as Markdown.
23667
23678
  */
23668
- write(path24, meta3, body) {
23669
- mkdirSync7(dirname(path24), { recursive: true });
23670
- const tmp = path24 + ".tmp-" + process.pid;
23679
+ write(path25, meta3, body) {
23680
+ mkdirSync7(dirname(path25), { recursive: true });
23681
+ const tmp = path25 + ".tmp-" + process.pid;
23671
23682
  const md = serializeFrontmatter(meta3, body);
23672
23683
  writeFileSync11(tmp, md, "utf8");
23673
- renameSync2(tmp, path24);
23684
+ renameSync2(tmp, path25);
23674
23685
  }
23675
23686
  /** List all .md files in a directory (non-recursive). */
23676
23687
  listMarkdown(dir) {
@@ -23748,8 +23759,8 @@ function readPlan(ctx) {
23748
23759
  } catch {
23749
23760
  }
23750
23761
  }
23751
- const path24 = workspaceFile(ctx.rootDir, "plan");
23752
- const doc = ctx.storage.readIfExists(path24);
23762
+ const path25 = workspaceFile(ctx.rootDir, "plan");
23763
+ const doc = ctx.storage.readIfExists(path25);
23753
23764
  if (!doc) return { phases: [], tasks: [], milestones: [] };
23754
23765
  const meta3 = doc.meta;
23755
23766
  return {
@@ -23914,7 +23925,7 @@ function addMilestoneRecord(ctx, summary, input) {
23914
23925
  dueDate: input.dueDate,
23915
23926
  targetVersion: version2
23916
23927
  });
23917
- const path24 = join14(ctx.rootDir, "milestones", `${id}.md`);
23928
+ const path25 = join14(ctx.rootDir, "milestones", `${id}.md`);
23918
23929
  const meta3 = {
23919
23930
  kind: "milestone",
23920
23931
  id,
@@ -23931,7 +23942,7 @@ function addMilestoneRecord(ctx, summary, input) {
23931
23942
  `Target version: ${version2}`,
23932
23943
  ""
23933
23944
  ].join("\n");
23934
- ctx.storage.write(path24, meta3, body);
23945
+ ctx.storage.write(path25, meta3, body);
23935
23946
  return { id, created: true };
23936
23947
  }
23937
23948
  function readPlanSummary(ctx) {
@@ -24135,7 +24146,7 @@ function addIdeaStub(ctx) {
24135
24146
  const tags = args["tags"] ?? [];
24136
24147
  const category = args["category"] ?? "General";
24137
24148
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
24138
- const path24 = workspaceArtifact(ctx.rootDir, "decisions", id);
24149
+ const path25 = workspaceArtifact(ctx.rootDir, "decisions", id);
24139
24150
  const meta3 = {
24140
24151
  kind: "adr",
24141
24152
  status: "proposed",
@@ -24161,7 +24172,7 @@ function addIdeaStub(ctx) {
24161
24172
  ...consequences.map((c) => `- ${c}`),
24162
24173
  ""
24163
24174
  ].join("\n");
24164
- ctx.storage.write(path24, meta3, body);
24175
+ ctx.storage.write(path25, meta3, body);
24165
24176
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
24166
24177
  });
24167
24178
  }
@@ -24243,14 +24254,14 @@ function createDocumentStub(ctx) {
24243
24254
  ctx.storage.write(risksPath, riskMeta, content);
24244
24255
  return `Document "${title}" created at risks.md (workspace root).`;
24245
24256
  }
24246
- const path24 = workspaceArtifact(ctx.rootDir, "docs", slug);
24257
+ const path25 = workspaceArtifact(ctx.rootDir, "docs", slug);
24247
24258
  const meta3 = {
24248
24259
  kind: "doc",
24249
24260
  id: slug,
24250
24261
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
24251
24262
  tags
24252
24263
  };
24253
- ctx.storage.write(path24, meta3, content);
24264
+ ctx.storage.write(path25, meta3, content);
24254
24265
  return `Document "${title}" created at docs/${slug}.md.`;
24255
24266
  });
24256
24267
  }
@@ -24469,13 +24480,133 @@ var init_cmdline = __esm({
24469
24480
  }
24470
24481
  });
24471
24482
 
24472
- // src/cli/mcp/mcpClient.ts
24483
+ // src/cli/updater.ts
24484
+ var updater_exports = {};
24485
+ __export(updater_exports, {
24486
+ REGISTRY_URL: () => REGISTRY_URL,
24487
+ checkForUpdate: () => checkForUpdate,
24488
+ compareSemver: () => compareSemver,
24489
+ fetchLatestVersion: () => fetchLatestVersion,
24490
+ getCurrentVersion: () => getCurrentVersion,
24491
+ performUpdate: () => performUpdate
24492
+ });
24493
+ import { createRequire } from "node:module";
24473
24494
  import { spawn as spawn2 } from "node:child_process";
24495
+ import path15 from "node:path";
24496
+ import { fileURLToPath } from "node:url";
24497
+ function getCurrentVersion() {
24498
+ try {
24499
+ const pkgPath = path15.resolve(__dirname2, "..", "..", "package.json");
24500
+ const pkg = require2(pkgPath);
24501
+ return pkg.version;
24502
+ } catch {
24503
+ return "0.0.0";
24504
+ }
24505
+ }
24506
+ function compareSemver(a, b) {
24507
+ const parse3 = (v) => {
24508
+ const m = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
24509
+ if (!m) return [0, 0, 0, null];
24510
+ return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
24511
+ };
24512
+ const [a1, a2, a3, aPre] = parse3(a);
24513
+ const [b1, b2, b3, bPre] = parse3(b);
24514
+ if (a1 !== b1) return a1 < b1 ? -1 : 1;
24515
+ if (a2 !== b2) return a2 < b2 ? -1 : 1;
24516
+ if (a3 !== b3) return a3 < b3 ? -1 : 1;
24517
+ if (aPre === bPre) return 0;
24518
+ if (aPre === null) return 1;
24519
+ if (bPre === null) return -1;
24520
+ return aPre < bPre ? -1 : 1;
24521
+ }
24522
+ async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs = 5e3) {
24523
+ try {
24524
+ const controller = new AbortController();
24525
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
24526
+ const response = await fetcher(registryUrl, { signal: controller.signal });
24527
+ clearTimeout(timer);
24528
+ if (!response.ok) {
24529
+ return { error: `Registry responded ${response.status}` };
24530
+ }
24531
+ const data = await response.json();
24532
+ if (!data.version || typeof data.version !== "string") {
24533
+ return { error: "Registry response missing version field" };
24534
+ }
24535
+ return { version: data.version };
24536
+ } catch (err) {
24537
+ const message = err instanceof Error ? err.message : String(err);
24538
+ return { error: message };
24539
+ }
24540
+ }
24541
+ async function checkForUpdate(fetcher = fetch, registryUrl) {
24542
+ const currentVersion = getCurrentVersion();
24543
+ const latest = await fetchLatestVersion(fetcher, registryUrl);
24544
+ if ("error" in latest) {
24545
+ return {
24546
+ currentVersion,
24547
+ latestVersion: currentVersion,
24548
+ updateAvailable: false,
24549
+ error: latest.error
24550
+ };
24551
+ }
24552
+ const cmp = compareSemver(currentVersion, latest.version);
24553
+ return {
24554
+ currentVersion,
24555
+ latestVersion: latest.version,
24556
+ updateAvailable: cmp < 0
24557
+ };
24558
+ }
24559
+ async function performUpdate(packageName = "zelari-code", executor = spawn2) {
24560
+ return new Promise((resolve) => {
24561
+ const args = ["install", "-g", `${packageName}@latest`];
24562
+ let stdout = "";
24563
+ let stderr = "";
24564
+ const stdio = ["ignore", "pipe", "pipe"];
24565
+ const child = process.platform === "win32" ? executor(buildCmdLine("npm", args), { stdio, shell: true }) : executor("npm", args, { stdio });
24566
+ child.stdout?.on("data", (chunk) => {
24567
+ stdout += chunk.toString();
24568
+ });
24569
+ child.stderr?.on("data", (chunk) => {
24570
+ stderr += chunk.toString();
24571
+ });
24572
+ child.on("error", (err) => {
24573
+ resolve({
24574
+ ok: false,
24575
+ output: stdout + stderr,
24576
+ error: err.message,
24577
+ exitCode: null
24578
+ });
24579
+ });
24580
+ child.on("close", (code) => {
24581
+ const ok = code === 0;
24582
+ resolve({
24583
+ ok,
24584
+ output: stdout + stderr,
24585
+ error: ok ? void 0 : `npm exited with code ${code}`,
24586
+ exitCode: code
24587
+ });
24588
+ });
24589
+ });
24590
+ }
24591
+ var require2, __dirname2, REGISTRY_URL;
24592
+ var init_updater = __esm({
24593
+ "src/cli/updater.ts"() {
24594
+ "use strict";
24595
+ init_cmdline();
24596
+ require2 = createRequire(import.meta.url);
24597
+ __dirname2 = path15.dirname(fileURLToPath(import.meta.url));
24598
+ REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
24599
+ }
24600
+ });
24601
+
24602
+ // src/cli/mcp/mcpClient.ts
24603
+ import { spawn as spawn3 } from "node:child_process";
24474
24604
  var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
24475
24605
  var init_mcpClient = __esm({
24476
24606
  "src/cli/mcp/mcpClient.ts"() {
24477
24607
  "use strict";
24478
24608
  init_cmdline();
24609
+ init_updater();
24479
24610
  DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
24480
24611
  INIT_TIMEOUT_MS = 15e3;
24481
24612
  MCP_PROTOCOL_VERSION = "2025-03-26";
@@ -24497,17 +24628,26 @@ var init_mcpClient = __esm({
24497
24628
  env: { ...process.env, ...this.config.env ?? {} },
24498
24629
  windowsHide: true
24499
24630
  };
24500
- const child = process.platform === "win32" ? spawn2(buildCmdLine(this.config.command, this.config.args ?? []), {
24631
+ const child = process.platform === "win32" ? spawn3(buildCmdLine(this.config.command, this.config.args ?? []), {
24501
24632
  ...spawnOpts,
24502
24633
  shell: true
24503
- }) : spawn2(this.config.command, this.config.args ?? [], spawnOpts);
24634
+ }) : spawn3(this.config.command, this.config.args ?? [], spawnOpts);
24504
24635
  this.child = child;
24505
24636
  child.stdout.setEncoding("utf8");
24506
24637
  child.stdout.on("data", (chunk) => this.onStdout(chunk));
24507
- child.on("error", (err) => this.failAll(new Error(`[mcp:${this.serverName}] spawn failed: ${err.message}`)));
24638
+ child.on(
24639
+ "error",
24640
+ (err) => this.failAll(
24641
+ new Error(`[mcp:${this.serverName}] spawn failed: ${err.message}`)
24642
+ )
24643
+ );
24508
24644
  child.on("exit", (code) => {
24509
24645
  if (!this.closed) {
24510
- this.failAll(new Error(`[mcp:${this.serverName}] server exited (code ${code ?? "null"})`));
24646
+ this.failAll(
24647
+ new Error(
24648
+ `[mcp:${this.serverName}] server exited (code ${code ?? "null"})`
24649
+ )
24650
+ );
24511
24651
  }
24512
24652
  });
24513
24653
  await this.request(
@@ -24515,7 +24655,7 @@ var init_mcpClient = __esm({
24515
24655
  {
24516
24656
  protocolVersion: MCP_PROTOCOL_VERSION,
24517
24657
  capabilities: {},
24518
- clientInfo: { name: "zelari-code", version: "0.7.9" }
24658
+ clientInfo: { name: "zelari-code", version: getCurrentVersion() }
24519
24659
  },
24520
24660
  INIT_TIMEOUT_MS
24521
24661
  );
@@ -24524,7 +24664,9 @@ var init_mcpClient = __esm({
24524
24664
  /** Discover the server's tools. */
24525
24665
  async listTools() {
24526
24666
  const res = await this.request("tools/list", {});
24527
- return (res.tools ?? []).filter((t) => !!t.name).map((t) => ({
24667
+ return (res.tools ?? []).filter(
24668
+ (t) => !!t.name
24669
+ ).map((t) => ({
24528
24670
  name: t.name,
24529
24671
  description: t.description ?? "",
24530
24672
  inputSchema: t.inputSchema ?? { type: "object", properties: {} }
@@ -24535,9 +24677,16 @@ var init_mcpClient = __esm({
24535
24677
  * items are summarized by type. Throws when the server flags isError.
24536
24678
  */
24537
24679
  async callTool(name, args, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
24538
- const res = await this.request("tools/call", { name, arguments: args }, timeoutMs);
24539
- const text = (res.content ?? []).map((c) => c.type === "text" && typeof c.text === "string" ? c.text : `[${c.type ?? "unknown"} content]`).join("\n");
24540
- if (res.isError) throw new Error(text || `tool "${name}" reported an error`);
24680
+ const res = await this.request(
24681
+ "tools/call",
24682
+ { name, arguments: args },
24683
+ timeoutMs
24684
+ );
24685
+ const text = (res.content ?? []).map(
24686
+ (c) => c.type === "text" && typeof c.text === "string" ? c.text : `[${c.type ?? "unknown"} content]`
24687
+ ).join("\n");
24688
+ if (res.isError)
24689
+ throw new Error(text || `tool "${name}" reported an error`);
24541
24690
  return text;
24542
24691
  }
24543
24692
  /** Terminate the server process and reject all in-flight requests. */
@@ -24550,13 +24699,18 @@ var init_mcpClient = __esm({
24550
24699
  // ── JSON-RPC plumbing ────────────────────────────────────────────────
24551
24700
  request(method, params, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
24552
24701
  const child = this.child;
24553
- if (!child) return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
24702
+ if (!child)
24703
+ return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
24554
24704
  const id = this.nextId++;
24555
24705
  const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params });
24556
24706
  return new Promise((resolve, reject) => {
24557
24707
  const timer = setTimeout(() => {
24558
24708
  this.pending.delete(id);
24559
- reject(new Error(`[mcp:${this.serverName}] ${method} timed out after ${timeoutMs}ms`));
24709
+ reject(
24710
+ new Error(
24711
+ `[mcp:${this.serverName}] ${method} timed out after ${timeoutMs}ms`
24712
+ )
24713
+ );
24560
24714
  }, timeoutMs);
24561
24715
  this.pending.set(id, { resolve, reject, timer });
24562
24716
  child.stdin.write(payload + "\n", (err) => {
@@ -24569,7 +24723,9 @@ var init_mcpClient = __esm({
24569
24723
  });
24570
24724
  }
24571
24725
  notify(method, params) {
24572
- this.child?.stdin.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n");
24726
+ this.child?.stdin.write(
24727
+ JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n"
24728
+ );
24573
24729
  }
24574
24730
  onStdout(chunk) {
24575
24731
  this.stdoutBuffer += chunk;
@@ -24590,7 +24746,11 @@ var init_mcpClient = __esm({
24590
24746
  this.pending.delete(msg.id);
24591
24747
  clearTimeout(pending.timer);
24592
24748
  if (msg.error) {
24593
- pending.reject(new Error(`[mcp:${this.serverName}] ${msg.error.message ?? "JSON-RPC error"} (code ${msg.error.code ?? "?"})`));
24749
+ pending.reject(
24750
+ new Error(
24751
+ `[mcp:${this.serverName}] ${msg.error.message ?? "JSON-RPC error"} (code ${msg.error.code ?? "?"})`
24752
+ )
24753
+ );
24594
24754
  } else {
24595
24755
  pending.resolve(msg.result);
24596
24756
  }
@@ -24868,10 +25028,10 @@ import { createHash as createHash2 } from "node:crypto";
24868
25028
  import { join as join17 } from "node:path";
24869
25029
  import { readFile } from "node:fs/promises";
24870
25030
  async function readPackageJson2(projectRoot) {
24871
- const path24 = join17(projectRoot, "package.json");
24872
- if (!existsSync17(path24)) return null;
25031
+ const path25 = join17(projectRoot, "package.json");
25032
+ if (!existsSync17(path25)) return null;
24873
25033
  try {
24874
- return JSON.parse(await readFile(path24, "utf8"));
25034
+ return JSON.parse(await readFile(path25, "utf8"));
24875
25035
  } catch {
24876
25036
  return null;
24877
25037
  }
@@ -24953,9 +25113,9 @@ async function genBuild(ctx) {
24953
25113
  ].join("\n");
24954
25114
  }
24955
25115
  async function genOpenQuestions(ctx) {
24956
- const path24 = join17(ctx.rootDir, "risks.md");
24957
- if (!existsSync17(path24)) return "_No open questions._";
24958
- const content = readFileSync17(path24, "utf8");
25116
+ const path25 = join17(ctx.rootDir, "risks.md");
25117
+ if (!existsSync17(path25)) return "_No open questions._";
25118
+ const content = readFileSync17(path25, "utf8");
24959
25119
  const lines = content.split("\n");
24960
25120
  const questions = [];
24961
25121
  let currentTitle = "";
@@ -25200,7 +25360,7 @@ var init_completeDesign = __esm({
25200
25360
  });
25201
25361
 
25202
25362
  // src/cli/workspace/projectSmoke.ts
25203
- import { spawn as spawn3 } from "node:child_process";
25363
+ import { spawn as spawn4 } from "node:child_process";
25204
25364
  import { existsSync as existsSync18, readFileSync as readFileSync18 } from "node:fs";
25205
25365
  import { join as join18 } from "node:path";
25206
25366
  function pickSmokeScript(scripts) {
@@ -25231,7 +25391,7 @@ async function runProjectSmoke(projectRoot, timeoutMs = DEFAULT_TIMEOUT_MS) {
25231
25391
  }
25232
25392
  return await new Promise((resolveRun) => {
25233
25393
  const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
25234
- const child = spawn3(npmCmd, ["run", script], {
25394
+ const child = spawn4(npmCmd, ["run", script], {
25235
25395
  cwd: projectRoot,
25236
25396
  stdio: ["ignore", "pipe", "pipe"],
25237
25397
  env: process.env,
@@ -25303,7 +25463,7 @@ __export(postCouncilHook_exports, {
25303
25463
  runImplementationVerificationHook: () => runImplementationVerificationHook,
25304
25464
  runPostCouncilHook: () => runPostCouncilHook
25305
25465
  });
25306
- import { spawn as spawn4 } from "node:child_process";
25466
+ import { spawn as spawn5 } from "node:child_process";
25307
25467
  import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
25308
25468
  import { join as join19 } from "node:path";
25309
25469
  async function runCompleteDesignPostProcessor(ctx, options) {
@@ -25352,7 +25512,7 @@ async function runCompleteDesignPostProcessor(ctx, options) {
25352
25512
  }
25353
25513
  }
25354
25514
  return await new Promise((resolveRun) => {
25355
- const child = spawn4(process.execPath, [scriptPath], {
25515
+ const child = spawn5(process.execPath, [scriptPath], {
25356
25516
  cwd: ctx.projectRoot,
25357
25517
  stdio: ["ignore", "pipe", "pipe"],
25358
25518
  env: process.env
@@ -25518,8 +25678,8 @@ async function runPostCouncilHook(ctx, options) {
25518
25678
  sources: scope.sources
25519
25679
  } : void 0
25520
25680
  });
25521
- const path24 = writeCouncilCompletion(ctx.rootDir, completion);
25522
- completionHook = { ran: true, path: path24, completion };
25681
+ const path25 = writeCouncilCompletion(ctx.rootDir, completion);
25682
+ completionHook = { ran: true, path: path25, completion };
25523
25683
  } catch (err) {
25524
25684
  completionHook = {
25525
25685
  ran: true,
@@ -25588,7 +25748,7 @@ import {
25588
25748
  writeFileSync as writeFileSync14,
25589
25749
  mkdirSync as mkdirSync9
25590
25750
  } from "node:fs";
25591
- import path15 from "node:path";
25751
+ import path16 from "node:path";
25592
25752
  import os7 from "node:os";
25593
25753
  var FeedbackStore;
25594
25754
  var init_councilFeedback = __esm({
@@ -25599,7 +25759,7 @@ var init_councilFeedback = __esm({
25599
25759
  now;
25600
25760
  entries = [];
25601
25761
  constructor(options = {}) {
25602
- this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path15.join(os7.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
25762
+ this.file = options.file ?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE ?? path16.join(os7.homedir(), ".tmp", "zelari-code", "council-feedback.json"));
25603
25763
  this.now = options.now ?? Date.now;
25604
25764
  this.load();
25605
25765
  }
@@ -25705,7 +25865,7 @@ var init_councilFeedback = __esm({
25705
25865
  }
25706
25866
  }
25707
25867
  save() {
25708
- mkdirSync9(path15.dirname(this.file), { recursive: true });
25868
+ mkdirSync9(path16.dirname(this.file), { recursive: true });
25709
25869
  writeFileSync14(
25710
25870
  this.file,
25711
25871
  JSON.stringify({ entries: this.entries }, null, 2),
@@ -25740,7 +25900,7 @@ __export(fileBackend_exports, {
25740
25900
  });
25741
25901
  import { randomUUID as randomUUID2 } from "node:crypto";
25742
25902
  import { promises as fs13 } from "node:fs";
25743
- import * as path16 from "node:path";
25903
+ import * as path17 from "node:path";
25744
25904
  function tokenize(text) {
25745
25905
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
25746
25906
  }
@@ -25778,8 +25938,8 @@ var init_fileBackend = __esm({
25778
25938
  logPath = "";
25779
25939
  memoryDir = "";
25780
25940
  async init(projectRoot) {
25781
- this.memoryDir = path16.join(projectRoot, ".zelari", "memory");
25782
- this.logPath = path16.join(this.memoryDir, "log.jsonl");
25941
+ this.memoryDir = path17.join(projectRoot, ".zelari", "memory");
25942
+ this.logPath = path17.join(this.memoryDir, "log.jsonl");
25783
25943
  await fs13.mkdir(this.memoryDir, { recursive: true });
25784
25944
  }
25785
25945
  async add(content, metadata = {}, graph) {
@@ -25860,7 +26020,7 @@ __export(zelariMission_exports, {
25860
26020
  });
25861
26021
  import { randomUUID as randomUUID3 } from "node:crypto";
25862
26022
  import { promises as fs14 } from "node:fs";
25863
- import * as path17 from "node:path";
26023
+ import * as path18 from "node:path";
25864
26024
  function resolveMaxIterations(env = process.env) {
25865
26025
  const raw = env.ZELARI_MISSION_MAX_ITER;
25866
26026
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -25876,10 +26036,10 @@ function isMissionAutoStart(env = process.env) {
25876
26036
  return env.ZELARI_MISSION_AUTO === "1";
25877
26037
  }
25878
26038
  async function writeMissionState(projectRoot, state2) {
25879
- const dir = path17.join(projectRoot, ".zelari");
26039
+ const dir = path18.join(projectRoot, ".zelari");
25880
26040
  await fs14.mkdir(dir, { recursive: true });
25881
26041
  await fs14.writeFile(
25882
- path17.join(dir, "mission-state.json"),
26042
+ path18.join(dir, "mission-state.json"),
25883
26043
  JSON.stringify(state2, null, 2) + "\n",
25884
26044
  "utf8"
25885
26045
  );
@@ -26007,122 +26167,249 @@ var init_zelariMission = __esm({
26007
26167
  }
26008
26168
  });
26009
26169
 
26010
- // src/cli/updater.ts
26011
- var updater_exports = {};
26012
- __export(updater_exports, {
26013
- REGISTRY_URL: () => REGISTRY_URL,
26014
- checkForUpdate: () => checkForUpdate,
26015
- compareSemver: () => compareSemver,
26016
- fetchLatestVersion: () => fetchLatestVersion,
26017
- getCurrentVersion: () => getCurrentVersion,
26018
- performUpdate: () => performUpdate
26170
+ // src/cli/utils/doctor.ts
26171
+ var doctor_exports = {};
26172
+ __export(doctor_exports, {
26173
+ runDoctor: () => runDoctor
26019
26174
  });
26020
- import { createRequire } from "node:module";
26021
- import { spawn as spawn5 } from "node:child_process";
26022
- import path19 from "node:path";
26023
- import { fileURLToPath } from "node:url";
26024
- function getCurrentVersion() {
26175
+ import { execSync } from "node:child_process";
26176
+ import { existsSync as existsSync26, readFileSync as readFileSync23, readlinkSync, statSync as statSync6 } from "node:fs";
26177
+ import { createRequire as createRequire2 } from "node:module";
26178
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
26179
+ import path24 from "node:path";
26180
+ function findPackageRoot(start) {
26181
+ let dir = start;
26182
+ for (let i = 0; i < 6; i += 1) {
26183
+ const candidate = path24.join(dir, "package.json");
26184
+ if (existsSync26(candidate)) {
26185
+ try {
26186
+ const pkg = JSON.parse(readFileSync23(candidate, "utf8"));
26187
+ if (pkg.name === "zelari-code") return dir;
26188
+ } catch {
26189
+ }
26190
+ }
26191
+ const parent = path24.dirname(dir);
26192
+ if (parent === dir) break;
26193
+ dir = parent;
26194
+ }
26195
+ return path24.resolve(__dirname3, "..", "..", "..");
26196
+ }
26197
+ function tryExec(cmd) {
26025
26198
  try {
26026
- const pkgPath = path19.resolve(__dirname2, "..", "..", "package.json");
26027
- const pkg = require2(pkgPath);
26028
- return pkg.version;
26199
+ return execSync(cmd, {
26200
+ encoding: "utf8",
26201
+ stdio: ["ignore", "pipe", "ignore"]
26202
+ }).trim();
26029
26203
  } catch {
26030
- return "0.0.0";
26204
+ return "";
26031
26205
  }
26032
26206
  }
26033
- function compareSemver(a, b) {
26034
- const parse3 = (v) => {
26035
- const m = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
26036
- if (!m) return [0, 0, 0, null];
26037
- return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
26038
- };
26039
- const [a1, a2, a3, aPre] = parse3(a);
26040
- const [b1, b2, b3, bPre] = parse3(b);
26041
- if (a1 !== b1) return a1 < b1 ? -1 : 1;
26042
- if (a2 !== b2) return a2 < b2 ? -1 : 1;
26043
- if (a3 !== b3) return a3 < b3 ? -1 : 1;
26044
- if (aPre === bPre) return 0;
26045
- if (aPre === null) return 1;
26046
- if (bPre === null) return -1;
26047
- return aPre < bPre ? -1 : 1;
26207
+ function readPackageJson3() {
26208
+ try {
26209
+ const pkgPath = path24.join(packageRoot, "package.json");
26210
+ return JSON.parse(readFileSync23(pkgPath, "utf8"));
26211
+ } catch {
26212
+ return null;
26213
+ }
26048
26214
  }
26049
- async function fetchLatestVersion(fetcher = fetch, registryUrl = REGISTRY_URL, timeoutMs = 5e3) {
26215
+ function getGlobalPrefix() {
26216
+ return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim() || tryExec("npm prefix -g");
26217
+ }
26218
+ function checkShim(pkgName) {
26219
+ const prefix = getGlobalPrefix();
26220
+ if (!prefix) {
26221
+ return WARN("npm global prefix not detectable (run inside an npm context)");
26222
+ }
26223
+ const isWin = process.platform === "win32";
26224
+ const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
26225
+ const shimPath = path24.join(prefix, shimName);
26226
+ if (!existsSync26(shimPath)) {
26227
+ return FAIL(
26228
+ `shim not found at ${shimPath}
26229
+ fix: npm install -g ${pkgName}@latest --force`
26230
+ );
26231
+ }
26050
26232
  try {
26051
- const controller = new AbortController();
26052
- const timer = setTimeout(() => controller.abort(), timeoutMs);
26053
- const response = await fetcher(registryUrl, { signal: controller.signal });
26054
- clearTimeout(timer);
26055
- if (!response.ok) {
26056
- return { error: `Registry responded ${response.status}` };
26233
+ const st = statSync6(shimPath);
26234
+ if (isWin) {
26235
+ const content = readFileSync23(shimPath, "utf8");
26236
+ if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
26237
+ return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
26238
+ }
26239
+ return FAIL(
26240
+ `shim at ${shimPath} does not reference ${pkgName}/bin/
26241
+ fix: npm install -g ${pkgName}@latest --force`
26242
+ );
26057
26243
  }
26058
- const data = await response.json();
26059
- if (!data.version || typeof data.version !== "string") {
26060
- return { error: "Registry response missing version field" };
26244
+ let target;
26245
+ try {
26246
+ target = readlinkSync(shimPath);
26247
+ } catch {
26248
+ return FAIL(
26249
+ `shim at ${shimPath} is not a symlink (POSIX global installs should be symlinks)
26250
+ fix: npm install -g ${pkgName}@latest --force`
26251
+ );
26061
26252
  }
26062
- return { version: data.version };
26253
+ const resolved = path24.resolve(path24.dirname(shimPath), target);
26254
+ const expected = path24.join(
26255
+ prefix,
26256
+ "node_modules",
26257
+ pkgName,
26258
+ "bin",
26259
+ "zelari-code.js"
26260
+ );
26261
+ if (resolved === expected) {
26262
+ return OK(`shim OK at ${shimPath} \u2192 ${resolved}`);
26263
+ }
26264
+ return FAIL(
26265
+ `shim at ${shimPath} points to ${resolved}
26266
+ expected ${expected}
26267
+ fix: npm install -g ${pkgName}@latest --force`
26268
+ );
26063
26269
  } catch (err) {
26064
- const message = err instanceof Error ? err.message : String(err);
26065
- return { error: message };
26270
+ return FAIL(
26271
+ `could not inspect shim: ${err instanceof Error ? err.message : String(err)}`
26272
+ );
26066
26273
  }
26067
26274
  }
26068
- async function checkForUpdate(fetcher = fetch, registryUrl) {
26069
- const currentVersion = getCurrentVersion();
26070
- const latest = await fetchLatestVersion(fetcher, registryUrl);
26071
- if ("error" in latest) {
26072
- return {
26073
- currentVersion,
26074
- latestVersion: currentVersion,
26075
- updateAvailable: false,
26076
- error: latest.error
26077
- };
26275
+ function checkNode(pkg) {
26276
+ const raw = tryExec("node --version");
26277
+ if (!raw) {
26278
+ return FAIL("`node` not found on PATH");
26078
26279
  }
26079
- const cmp = compareSemver(currentVersion, latest.version);
26080
- return {
26081
- currentVersion,
26082
- latestVersion: latest.version,
26083
- updateAvailable: cmp < 0
26084
- };
26280
+ const m = raw.match(/^v?(\d+)\.(\d+)\.(\d+)/);
26281
+ if (!m) {
26282
+ return WARN(`could not parse node version: ${raw}`);
26283
+ }
26284
+ const major = Number(m[1]);
26285
+ if (major < 20) {
26286
+ return FAIL(
26287
+ `node ${raw} is older than the required engines.node (>= 20.0.0)`
26288
+ );
26289
+ }
26290
+ return OK(`node ${raw}`);
26085
26291
  }
26086
- async function performUpdate(packageName = "zelari-code", executor = spawn5) {
26087
- return new Promise((resolve) => {
26088
- const args = ["install", "-g", `${packageName}@latest`];
26089
- let stdout = "";
26090
- let stderr = "";
26091
- const stdio = ["ignore", "pipe", "pipe"];
26092
- const child = process.platform === "win32" ? executor(buildCmdLine("npm", args), { stdio, shell: true }) : executor("npm", args, { stdio });
26093
- child.stdout?.on("data", (chunk) => {
26094
- stdout += chunk.toString();
26095
- });
26096
- child.stderr?.on("data", (chunk) => {
26097
- stderr += chunk.toString();
26292
+ function checkBundle() {
26293
+ const bundle = path24.join(packageRoot, "dist", "cli", "main.bundled.js");
26294
+ if (!existsSync26(bundle)) {
26295
+ return FAIL(
26296
+ `dist/cli/main.bundled.js missing at ${bundle}
26297
+ fix: npm run build:cli (then reinstall or run via tsx)`
26298
+ );
26299
+ }
26300
+ try {
26301
+ const st = statSync6(bundle);
26302
+ return OK(`bundle OK (${(st.size / 1024 / 1024).toFixed(2)} MB)`);
26303
+ } catch (err) {
26304
+ return FAIL(
26305
+ `could not stat bundle: ${err instanceof Error ? err.message : String(err)}`
26306
+ );
26307
+ }
26308
+ }
26309
+ function checkRuntimeDeps() {
26310
+ const required2 = ["react", "react-dom", "ink", "ink-text-input", "zod"];
26311
+ const missing = [];
26312
+ for (const dep of required2) {
26313
+ try {
26314
+ const localReq = createRequire2(path24.join(packageRoot, "package.json"));
26315
+ localReq.resolve(dep);
26316
+ } catch {
26317
+ missing.push(dep);
26318
+ }
26319
+ }
26320
+ if (missing.length === 0) {
26321
+ return OK(
26322
+ "runtime deps resolvable (react, react-dom, ink, ink-text-input, zod)"
26323
+ );
26324
+ }
26325
+ return FAIL(
26326
+ `missing runtime deps: ${missing.join(", ")}
26327
+ fix: npm install -g ${"<pkg>"} --force (then reopen the terminal)`
26328
+ );
26329
+ }
26330
+ function checkPath() {
26331
+ const prefix = getGlobalPrefix();
26332
+ if (!prefix) return WARN("npm global prefix not detectable");
26333
+ const pathSep = process.platform === "win32" ? ";" : ":";
26334
+ const pathDirs = (process.env.PATH || "").split(pathSep);
26335
+ if (pathDirs.includes(prefix)) {
26336
+ return OK(`PATH includes npm prefix (${prefix})`);
26337
+ }
26338
+ return WARN(
26339
+ `PATH does not include npm prefix (${prefix})
26340
+ symptom: "zelari-code: command not found" after install
26341
+ fix (POSIX): export PATH="$(npm prefix -g)/bin:$PATH"
26342
+ fix (Windows): $env:Path = "$(npm prefix -g);$env:Path"`
26343
+ );
26344
+ }
26345
+ function runDoctor() {
26346
+ const pkg = readPackageJson3();
26347
+ const pkgName = pkg?.name ?? "zelari-code";
26348
+ const checks = [
26349
+ { name: "node", run: () => checkNode(pkg) },
26350
+ { name: "bin shim", run: () => checkShim(pkgName) },
26351
+ { name: "cli bundle", run: () => checkBundle() },
26352
+ { name: "runtime deps", run: () => checkRuntimeDeps() },
26353
+ { name: "PATH", run: () => checkPath() }
26354
+ ];
26355
+ console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
26356
+ console.log("platform:", process.platform, process.arch);
26357
+ console.log("node: ", process.version);
26358
+ console.log("prefix: ", getGlobalPrefix() || "(unknown)");
26359
+ console.log("root: ", packageRoot);
26360
+ console.log("");
26361
+ let criticalFails = 0;
26362
+ let warns = 0;
26363
+ for (const c of checks) {
26364
+ let result;
26365
+ try {
26366
+ result = c.run();
26367
+ } catch (err) {
26368
+ result = FAIL(
26369
+ `unexpected error: ${err instanceof Error ? err.message : String(err)}`
26370
+ );
26371
+ }
26372
+ const tag = result.ok ? "OK " : result.severity === "critical" ? "FAIL" : "WARN";
26373
+ const color = result.ok ? "\x1B[32m" : result.severity === "critical" ? "\x1B[31m" : "\x1B[33m";
26374
+ const reset = "\x1B[0m";
26375
+ console.log(
26376
+ ` ${color}${tag}${reset} ${c.name.padEnd(14)}${result.message.replace(/\n/g, "\n ")}`
26377
+ );
26378
+ if (!result.ok) {
26379
+ if (result.severity === "critical") criticalFails += 1;
26380
+ else warns += 1;
26381
+ }
26382
+ }
26383
+ console.log("");
26384
+ if (criticalFails === 0 && warns === 0) {
26385
+ console.log("\x1B[32m\u2714 all checks passed\x1B[0m");
26386
+ return true;
26387
+ }
26388
+ console.log(`\u2717 ${criticalFails} critical failure(s), ${warns} warning(s)`);
26389
+ return criticalFails === 0;
26390
+ }
26391
+ var require3, __dirname3, packageRoot, OK, FAIL, WARN;
26392
+ var init_doctor = __esm({
26393
+ "src/cli/utils/doctor.ts"() {
26394
+ "use strict";
26395
+ require3 = createRequire2(import.meta.url);
26396
+ __dirname3 = path24.dirname(fileURLToPath2(import.meta.url));
26397
+ packageRoot = findPackageRoot(__dirname3);
26398
+ OK = (message) => ({
26399
+ ok: true,
26400
+ message,
26401
+ severity: "critical"
26098
26402
  });
26099
- child.on("error", (err) => {
26100
- resolve({
26101
- ok: false,
26102
- output: stdout + stderr,
26103
- error: err.message,
26104
- exitCode: null
26105
- });
26403
+ FAIL = (message, severity = "critical") => ({
26404
+ ok: false,
26405
+ message,
26406
+ severity
26106
26407
  });
26107
- child.on("close", (code) => {
26108
- const ok = code === 0;
26109
- resolve({
26110
- ok,
26111
- output: stdout + stderr,
26112
- error: ok ? void 0 : `npm exited with code ${code}`,
26113
- exitCode: code
26114
- });
26408
+ WARN = (message) => ({
26409
+ ok: false,
26410
+ message,
26411
+ severity: "warn"
26115
26412
  });
26116
- });
26117
- }
26118
- var require2, __dirname2, REGISTRY_URL;
26119
- var init_updater = __esm({
26120
- "src/cli/updater.ts"() {
26121
- "use strict";
26122
- init_cmdline();
26123
- require2 = createRequire(import.meta.url);
26124
- __dirname2 = path19.dirname(fileURLToPath(import.meta.url));
26125
- REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
26126
26413
  }
26127
26414
  });
26128
26415
 
@@ -31448,7 +31735,7 @@ ${formatSkillList(availableSkills)}`
31448
31735
  // src/cli/gitOps.ts
31449
31736
  import { execFile as execFile2 } from "node:child_process";
31450
31737
  import { promisify } from "node:util";
31451
- import path18 from "node:path";
31738
+ import path19 from "node:path";
31452
31739
  var execFileAsync = promisify(execFile2);
31453
31740
  async function git(cwd, args) {
31454
31741
  try {
@@ -31494,7 +31781,7 @@ async function undoWorkingChanges(opts = {}) {
31494
31781
  };
31495
31782
  }
31496
31783
  function defaultProjectRoot() {
31497
- return path18.resolve(__dirname, "..", "..", "..");
31784
+ return path19.resolve(__dirname, "..", "..", "..");
31498
31785
  }
31499
31786
 
31500
31787
  // src/cli/slashHandlers/git.ts
@@ -31599,14 +31886,23 @@ async function handleUpdateCheck(ctx) {
31599
31886
  Run \`/update --yes\` to install. You'll need to restart manually after.`
31600
31887
  );
31601
31888
  } else {
31602
- appendSystem(ctx.setMessages, `[update] up to date (${info.currentVersion})`);
31889
+ appendSystem(
31890
+ ctx.setMessages,
31891
+ `[update] up to date (${info.currentVersion})`
31892
+ );
31603
31893
  }
31604
31894
  } catch (err) {
31605
- appendSystem(ctx.setMessages, `[update error] ${err instanceof Error ? err.message : String(err)}`);
31895
+ appendSystem(
31896
+ ctx.setMessages,
31897
+ `[update error] ${err instanceof Error ? err.message : String(err)}`
31898
+ );
31606
31899
  }
31607
31900
  }
31608
31901
  async function handleUpdatePerform(ctx) {
31609
- appendSystem(ctx.setMessages, "[update] running `npm install -g zelari-code@latest`...");
31902
+ appendSystem(
31903
+ ctx.setMessages,
31904
+ "[update] running `npm install -g zelari-code@latest`..."
31905
+ );
31610
31906
  try {
31611
31907
  const { performUpdate: performUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
31612
31908
  const res = await performUpdate2();
@@ -31619,18 +31915,48 @@ Please restart zelari-code manually to use the new version.
31619
31915
  (exit with /exit or Ctrl+C, then run \`zelari-code\` again)`
31620
31916
  );
31621
31917
  } else {
31918
+ const output = res.output?.trim() || "(empty)";
31919
+ const hint = buildUpdateFailureHint(
31920
+ res.error ?? "",
31921
+ output,
31922
+ res.exitCode
31923
+ );
31622
31924
  appendSystem(
31623
31925
  ctx.setMessages,
31624
31926
  `[update] \u274C failed: ${res.error ?? "unknown error"}
31927
+ exit code: ${res.exitCode ?? "n/a"}
31625
31928
 
31626
31929
  npm output:
31627
- ${res.output || "(empty)"}`
31930
+ ${output}
31931
+
31932
+ ` + hint
31628
31933
  );
31629
31934
  }
31630
31935
  } catch (err) {
31631
- appendSystem(ctx.setMessages, `[update error] ${err instanceof Error ? err.message : String(err)}`);
31936
+ appendSystem(
31937
+ ctx.setMessages,
31938
+ `[update error] ${err instanceof Error ? err.message : String(err)}`
31939
+ );
31632
31940
  }
31633
31941
  }
31942
+ function buildUpdateFailureHint(error51, output, exitCode) {
31943
+ const haystack = `${error51}
31944
+ ${output}`.toLowerCase();
31945
+ const isWin = process.platform === "win32";
31946
+ if (haystack.includes("eresolve") || haystack.includes("epeerinvalid") || haystack.includes("peer dep")) {
31947
+ return "\u{1F4A1} hint: peer-dependency conflict. Try:\n npm install -g zelari-code@latest --legacy-peer-deps\n or: npm install -g zelari-code@latest --force";
31948
+ }
31949
+ if (haystack.includes("eacces") || haystack.includes("eperm")) {
31950
+ return "\u{1F4A1} hint: permission denied. On macOS/Linux, avoid sudo with npm:\n https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally\n On Windows, re-run the terminal as Administrator.";
31951
+ }
31952
+ if (haystack.includes("enoent") && haystack.includes("npm")) {
31953
+ return '\u{1F4A1} hint: `npm` was not found on PATH for the spawned shell.\n Re-open the terminal so it picks up your current PATH, or set:\n export PATH="$(npm prefix -g)/bin:$PATH" (POSIX)\n $env:Path = "$(npm prefix -g);$env:Path" (PowerShell)';
31954
+ }
31955
+ if (haystack.includes("zelari-code") && (haystack.includes("not found") || haystack.includes("eexist") || haystack.includes("ebusy") || haystack.includes("shim") || exitCode === 0)) {
31956
+ return "\u{1F4A1} hint: the install may have completed but the global bin shim\n is missing or stale (common on Windows + npm 10/11 after major\n upgrades). To repair:\n npm install -g zelari-code@latest --force\n Then re-open the terminal. If `zelari-code` is still not found,\n run the doctor: `zelari-code doctor`";
31957
+ }
31958
+ return "\u{1F4A1} hint: the install did not succeed. To retry with more detail:\n npm install -g zelari-code@latest --verbose 2>&1 | tail -40\n On Windows, also try: `npm install -g zelari-code@latest --force`\n" + (isWin ? " Run `zelari-code doctor` from a new terminal to diagnose shim / PATH issues.\n" : "");
31959
+ }
31634
31960
 
31635
31961
  // src/cli/slashHandlers/promoteMember.ts
31636
31962
  import { promises as fs15 } from "node:fs";
@@ -33827,7 +34153,8 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
33827
34153
 
33828
34154
  // src/cli/main.ts
33829
34155
  init_skills2();
33830
- var VERSION = "1.0.0";
34156
+ init_updater();
34157
+ var VERSION = getCurrentVersion();
33831
34158
  async function backgroundUpdateCheck() {
33832
34159
  if (process.env.ANATHEMA_DEV === "1") return;
33833
34160
  await new Promise((resolve) => setTimeout(resolve, 3e3));
@@ -33860,9 +34187,14 @@ function pickRootComponent() {
33860
34187
  console.log(`zelari-code v${VERSION}`);
33861
34188
  process.exit(0);
33862
34189
  }
34190
+ if (argv.includes("--doctor") || argv.includes("doctor")) {
34191
+ const { runDoctor: runDoctor2 } = (init_doctor(), __toCommonJS(doctor_exports));
34192
+ const healthy = runDoctor2();
34193
+ process.exit(healthy ? 0 : 1);
34194
+ }
33863
34195
  if (argv.includes("--help") || argv.includes("-h")) {
33864
34196
  console.log(
33865
- "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 --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"
34197
+ "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"
33866
34198
  );
33867
34199
  process.exit(0);
33868
34200
  }
@@ -33887,7 +34219,11 @@ function pickRootComponent() {
33887
34219
  }
33888
34220
  return {
33889
34221
  kind: "app",
33890
- element: React13.createElement(SplashGate, { version: VERSION }, React13.createElement(App))
34222
+ element: React13.createElement(
34223
+ SplashGate,
34224
+ { version: VERSION },
34225
+ React13.createElement(App)
34226
+ )
33891
34227
  };
33892
34228
  }
33893
34229
  function loadUserSkills() {
@@ -33895,7 +34231,9 @@ function loadUserSkills() {
33895
34231
  const existing = new Set(listCodingSkills().map((s) => s.id));
33896
34232
  const summary = loadSkillMdSkills(process.cwd(), { existingIds: existing });
33897
34233
  if (summary.loaded.length > 0) {
33898
- console.error(`[zelari-code] loaded ${summary.loaded.length} SKILL.md skill(s): ${summary.loaded.join(", ")}`);
34234
+ console.error(
34235
+ `[zelari-code] loaded ${summary.loaded.length} SKILL.md skill(s): ${summary.loaded.join(", ")}`
34236
+ );
33899
34237
  }
33900
34238
  for (const s of summary.skipped) {
33901
34239
  console.error(`[zelari-code] skipped SKILL.md at ${s.path}: ${s.reason}`);