zelari-code 1.0.2 → 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
  }
@@ -25017,10 +25028,10 @@ import { createHash as createHash2 } from "node:crypto";
25017
25028
  import { join as join17 } from "node:path";
25018
25029
  import { readFile } from "node:fs/promises";
25019
25030
  async function readPackageJson2(projectRoot) {
25020
- const path24 = join17(projectRoot, "package.json");
25021
- if (!existsSync17(path24)) return null;
25031
+ const path25 = join17(projectRoot, "package.json");
25032
+ if (!existsSync17(path25)) return null;
25022
25033
  try {
25023
- return JSON.parse(await readFile(path24, "utf8"));
25034
+ return JSON.parse(await readFile(path25, "utf8"));
25024
25035
  } catch {
25025
25036
  return null;
25026
25037
  }
@@ -25102,9 +25113,9 @@ async function genBuild(ctx) {
25102
25113
  ].join("\n");
25103
25114
  }
25104
25115
  async function genOpenQuestions(ctx) {
25105
- const path24 = join17(ctx.rootDir, "risks.md");
25106
- if (!existsSync17(path24)) return "_No open questions._";
25107
- 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");
25108
25119
  const lines = content.split("\n");
25109
25120
  const questions = [];
25110
25121
  let currentTitle = "";
@@ -25667,8 +25678,8 @@ async function runPostCouncilHook(ctx, options) {
25667
25678
  sources: scope.sources
25668
25679
  } : void 0
25669
25680
  });
25670
- const path24 = writeCouncilCompletion(ctx.rootDir, completion);
25671
- completionHook = { ran: true, path: path24, completion };
25681
+ const path25 = writeCouncilCompletion(ctx.rootDir, completion);
25682
+ completionHook = { ran: true, path: path25, completion };
25672
25683
  } catch (err) {
25673
25684
  completionHook = {
25674
25685
  ran: true,
@@ -26156,6 +26167,252 @@ var init_zelariMission = __esm({
26156
26167
  }
26157
26168
  });
26158
26169
 
26170
+ // src/cli/utils/doctor.ts
26171
+ var doctor_exports = {};
26172
+ __export(doctor_exports, {
26173
+ runDoctor: () => runDoctor
26174
+ });
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) {
26198
+ try {
26199
+ return execSync(cmd, {
26200
+ encoding: "utf8",
26201
+ stdio: ["ignore", "pipe", "ignore"]
26202
+ }).trim();
26203
+ } catch {
26204
+ return "";
26205
+ }
26206
+ }
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
+ }
26214
+ }
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
+ }
26232
+ try {
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
+ );
26243
+ }
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
+ );
26252
+ }
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
+ );
26269
+ } catch (err) {
26270
+ return FAIL(
26271
+ `could not inspect shim: ${err instanceof Error ? err.message : String(err)}`
26272
+ );
26273
+ }
26274
+ }
26275
+ function checkNode(pkg) {
26276
+ const raw = tryExec("node --version");
26277
+ if (!raw) {
26278
+ return FAIL("`node` not found on PATH");
26279
+ }
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}`);
26291
+ }
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"
26402
+ });
26403
+ FAIL = (message, severity = "critical") => ({
26404
+ ok: false,
26405
+ message,
26406
+ severity
26407
+ });
26408
+ WARN = (message) => ({
26409
+ ok: false,
26410
+ message,
26411
+ severity: "warn"
26412
+ });
26413
+ }
26414
+ });
26415
+
26159
26416
  // src/cli/main.ts
26160
26417
  import React13 from "react";
26161
26418
  import { render } from "ink";
@@ -31629,14 +31886,23 @@ async function handleUpdateCheck(ctx) {
31629
31886
  Run \`/update --yes\` to install. You'll need to restart manually after.`
31630
31887
  );
31631
31888
  } else {
31632
- appendSystem(ctx.setMessages, `[update] up to date (${info.currentVersion})`);
31889
+ appendSystem(
31890
+ ctx.setMessages,
31891
+ `[update] up to date (${info.currentVersion})`
31892
+ );
31633
31893
  }
31634
31894
  } catch (err) {
31635
- 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
+ );
31636
31899
  }
31637
31900
  }
31638
31901
  async function handleUpdatePerform(ctx) {
31639
- 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
+ );
31640
31906
  try {
31641
31907
  const { performUpdate: performUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
31642
31908
  const res = await performUpdate2();
@@ -31649,18 +31915,48 @@ Please restart zelari-code manually to use the new version.
31649
31915
  (exit with /exit or Ctrl+C, then run \`zelari-code\` again)`
31650
31916
  );
31651
31917
  } else {
31918
+ const output = res.output?.trim() || "(empty)";
31919
+ const hint = buildUpdateFailureHint(
31920
+ res.error ?? "",
31921
+ output,
31922
+ res.exitCode
31923
+ );
31652
31924
  appendSystem(
31653
31925
  ctx.setMessages,
31654
31926
  `[update] \u274C failed: ${res.error ?? "unknown error"}
31927
+ exit code: ${res.exitCode ?? "n/a"}
31655
31928
 
31656
31929
  npm output:
31657
- ${res.output || "(empty)"}`
31930
+ ${output}
31931
+
31932
+ ` + hint
31658
31933
  );
31659
31934
  }
31660
31935
  } catch (err) {
31661
- 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
+ );
31662
31940
  }
31663
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
+ }
31664
31960
 
31665
31961
  // src/cli/slashHandlers/promoteMember.ts
31666
31962
  import { promises as fs15 } from "node:fs";
@@ -33891,9 +34187,14 @@ function pickRootComponent() {
33891
34187
  console.log(`zelari-code v${VERSION}`);
33892
34188
  process.exit(0);
33893
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
+ }
33894
34195
  if (argv.includes("--help") || argv.includes("-h")) {
33895
34196
  console.log(
33896
- "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"
33897
34198
  );
33898
34199
  process.exit(0);
33899
34200
  }