zelari-code 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +67 -3
  2. package/dist/cli/components/PluginGate.js +195 -0
  3. package/dist/cli/components/PluginGate.js.map +1 -0
  4. package/dist/cli/diagnostics/engine.js +2 -10
  5. package/dist/cli/diagnostics/engine.js.map +1 -1
  6. package/dist/cli/hooks/useSlashDispatch.js +17 -0
  7. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  8. package/dist/cli/lsp/tools.js +3 -12
  9. package/dist/cli/lsp/tools.js.map +1 -1
  10. package/dist/cli/main.bundled.js +1460 -551
  11. package/dist/cli/main.bundled.js.map +4 -4
  12. package/dist/cli/main.js +124 -6
  13. package/dist/cli/main.js.map +1 -1
  14. package/dist/cli/plugins/installer.js +103 -0
  15. package/dist/cli/plugins/installer.js.map +1 -0
  16. package/dist/cli/plugins/prefs.js +96 -0
  17. package/dist/cli/plugins/prefs.js.map +1 -0
  18. package/dist/cli/plugins/registry.js +209 -0
  19. package/dist/cli/plugins/registry.js.map +1 -0
  20. package/dist/cli/slashCommands.js +18 -1
  21. package/dist/cli/slashCommands.js.map +1 -1
  22. package/dist/cli/slashHandlers/plugins.js +75 -0
  23. package/dist/cli/slashHandlers/plugins.js.map +1 -0
  24. package/dist/cli/slashHandlers/updater.js +27 -3
  25. package/dist/cli/slashHandlers/updater.js.map +1 -1
  26. package/dist/cli/updater.js +1 -1
  27. package/dist/cli/updater.js.map +1 -1
  28. package/dist/cli/utils/doctor.js +66 -4
  29. package/dist/cli/utils/doctor.js.map +1 -1
  30. package/dist/cli/utils/fixPath.js +119 -0
  31. package/dist/cli/utils/fixPath.js.map +1 -0
  32. package/dist/cli/utils/paths.js +47 -0
  33. package/dist/cli/utils/paths.js.map +1 -1
  34. package/dist/cli/utils/prereqChecks.js +362 -0
  35. package/dist/cli/utils/prereqChecks.js.map +1 -0
  36. package/package.json +4 -2
  37. package/scripts/diagnose-path.ps1 +22 -0
  38. package/scripts/dump-path.ps1 +13 -0
  39. package/scripts/fix-path.ps1 +26 -0
  40. package/scripts/path-length.ps1 +15 -0
  41. package/scripts/postinstall.mjs +78 -1
  42. package/scripts/repair-path.mjs +104 -0
@@ -308,7 +308,7 @@ async function refreshGrokToken(options) {
308
308
  return parseTokenResponseBody(obj, accessToken);
309
309
  }
310
310
  async function openBrowser(url2) {
311
- const { spawn: spawn8 } = await import("node:child_process");
311
+ const { spawn: spawn9 } = await import("node:child_process");
312
312
  const cmd = (() => {
313
313
  switch (process.platform) {
314
314
  case "darwin":
@@ -321,7 +321,7 @@ async function openBrowser(url2) {
321
321
  })();
322
322
  return new Promise((resolve, reject) => {
323
323
  try {
324
- const child = spawn8(cmd.bin, cmd.args, {
324
+ const child = spawn9(cmd.bin, cmd.args, {
325
325
  stdio: "ignore",
326
326
  detached: true
327
327
  });
@@ -1535,10 +1535,10 @@ function mergeDefs(...defs) {
1535
1535
  function cloneDef(schema) {
1536
1536
  return mergeDefs(schema._zod.def);
1537
1537
  }
1538
- function getElementAtPath(obj, path33) {
1539
- if (!path33)
1538
+ function getElementAtPath(obj, path34) {
1539
+ if (!path34)
1540
1540
  return obj;
1541
- return path33.reduce((acc, key) => acc?.[key], obj);
1541
+ return path34.reduce((acc, key) => acc?.[key], obj);
1542
1542
  }
1543
1543
  function promiseAllObject(promisesObj) {
1544
1544
  const keys = Object.keys(promisesObj);
@@ -1866,11 +1866,11 @@ function explicitlyAborted(x, startIndex = 0) {
1866
1866
  }
1867
1867
  return false;
1868
1868
  }
1869
- function prefixIssues(path33, issues) {
1869
+ function prefixIssues(path34, issues) {
1870
1870
  return issues.map((iss) => {
1871
1871
  var _a3;
1872
1872
  (_a3 = iss).path ?? (_a3.path = []);
1873
- iss.path.unshift(path33);
1873
+ iss.path.unshift(path34);
1874
1874
  return iss;
1875
1875
  });
1876
1876
  }
@@ -2088,16 +2088,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2088
2088
  }
2089
2089
  function formatError(error51, mapper = (issue2) => issue2.message) {
2090
2090
  const fieldErrors = { _errors: [] };
2091
- const processError = (error52, path33 = []) => {
2091
+ const processError = (error52, path34 = []) => {
2092
2092
  for (const issue2 of error52.issues) {
2093
2093
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2094
- issue2.errors.map((issues) => processError({ issues }, [...path33, ...issue2.path]));
2094
+ issue2.errors.map((issues) => processError({ issues }, [...path34, ...issue2.path]));
2095
2095
  } else if (issue2.code === "invalid_key") {
2096
- processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2096
+ processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2097
2097
  } else if (issue2.code === "invalid_element") {
2098
- processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2098
+ processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2099
2099
  } else {
2100
- const fullpath = [...path33, ...issue2.path];
2100
+ const fullpath = [...path34, ...issue2.path];
2101
2101
  if (fullpath.length === 0) {
2102
2102
  fieldErrors._errors.push(mapper(issue2));
2103
2103
  } else {
@@ -2124,17 +2124,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2124
2124
  }
2125
2125
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2126
2126
  const result = { errors: [] };
2127
- const processError = (error52, path33 = []) => {
2127
+ const processError = (error52, path34 = []) => {
2128
2128
  var _a3, _b;
2129
2129
  for (const issue2 of error52.issues) {
2130
2130
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2131
- issue2.errors.map((issues) => processError({ issues }, [...path33, ...issue2.path]));
2131
+ issue2.errors.map((issues) => processError({ issues }, [...path34, ...issue2.path]));
2132
2132
  } else if (issue2.code === "invalid_key") {
2133
- processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2133
+ processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2134
2134
  } else if (issue2.code === "invalid_element") {
2135
- processError({ issues: issue2.issues }, [...path33, ...issue2.path]);
2135
+ processError({ issues: issue2.issues }, [...path34, ...issue2.path]);
2136
2136
  } else {
2137
- const fullpath = [...path33, ...issue2.path];
2137
+ const fullpath = [...path34, ...issue2.path];
2138
2138
  if (fullpath.length === 0) {
2139
2139
  result.errors.push(mapper(issue2));
2140
2140
  continue;
@@ -2166,8 +2166,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2166
2166
  }
2167
2167
  function toDotPath(_path) {
2168
2168
  const segs = [];
2169
- const path33 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
- for (const seg of path33) {
2169
+ const path34 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2170
+ for (const seg of path34) {
2171
2171
  if (typeof seg === "number")
2172
2172
  segs.push(`[${seg}]`);
2173
2173
  else if (typeof seg === "symbol")
@@ -15670,13 +15670,13 @@ function resolveRef(ref, ctx) {
15670
15670
  if (!ref.startsWith("#")) {
15671
15671
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15672
15672
  }
15673
- const path33 = ref.slice(1).split("/").filter(Boolean);
15674
- if (path33.length === 0) {
15673
+ const path34 = ref.slice(1).split("/").filter(Boolean);
15674
+ if (path34.length === 0) {
15675
15675
  return ctx.rootSchema;
15676
15676
  }
15677
15677
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15678
- if (path33[0] === defsKey) {
15679
- const key = path33[1];
15678
+ if (path34[0] === defsKey) {
15679
+ const key = path34[1];
15680
15680
  if (!key || !ctx.defs[key]) {
15681
15681
  throw new Error(`Reference not found: ${ref}`);
15682
15682
  }
@@ -16836,7 +16836,7 @@ var init_walk = __esm({
16836
16836
  // packages/core/dist/core/tools/builtin/search.js
16837
16837
  import { promises as fs6 } from "node:fs";
16838
16838
  import path7 from "node:path";
16839
- async function searchFile(absPath, relPath2, regex, contextLines, remainingSlots) {
16839
+ async function searchFile(absPath, relPath, regex, contextLines, remainingSlots) {
16840
16840
  let buf;
16841
16841
  try {
16842
16842
  buf = await fs6.readFile(absPath, "utf-8");
@@ -16856,7 +16856,7 @@ async function searchFile(absPath, relPath2, regex, contextLines, remainingSlots
16856
16856
  const endAfter = Math.min(lines.length - 1, i + contextLines);
16857
16857
  matches.push({
16858
16858
  file: absPath,
16859
- relPath: relPath2,
16859
+ relPath,
16860
16860
  line: i + 1,
16861
16861
  text: lines[i],
16862
16862
  context: {
@@ -17706,11 +17706,11 @@ var init_tools = __esm({
17706
17706
  if (!ctx.addDocument)
17707
17707
  return "Knowledge vault tool not available.";
17708
17708
  const title = args["title"] || "New Document";
17709
- const path33 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17709
+ const path34 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17710
17710
  const content = args["content"] || "";
17711
17711
  const tags = args["tags"] || [];
17712
17712
  ctx.addDocument({
17713
- path: path33,
17713
+ path: path34,
17714
17714
  title,
17715
17715
  content,
17716
17716
  format: "markdown",
@@ -17719,7 +17719,7 @@ var init_tools = __esm({
17719
17719
  workspaceId: ctx.workspaceId
17720
17720
  });
17721
17721
  ctx.addActivity("vault", "created document", title);
17722
- return `Document "${title}" created at "${path33}".`;
17722
+ return `Document "${title}" created at "${path34}".`;
17723
17723
  }
17724
17724
  }
17725
17725
  ];
@@ -18096,6 +18096,33 @@ var init_skills2 = __esm({
18096
18096
  }
18097
18097
  });
18098
18098
 
18099
+ // src/cli/utils/paths.ts
18100
+ import { homedir as homedir2 } from "node:os";
18101
+ import path10 from "node:path";
18102
+ function shortenCwd(p3, maxLen = 40, home = homedir2()) {
18103
+ let out = p3;
18104
+ if (home && (out === home || out.startsWith(`${home}\\`) || out.startsWith(`${home}/`))) {
18105
+ out = `~${out.slice(home.length)}`;
18106
+ }
18107
+ if (out.length <= maxLen) return out;
18108
+ return `\u2026${out.slice(-(maxLen - 1))}`;
18109
+ }
18110
+ function relativePosix(from, to) {
18111
+ try {
18112
+ const p3 = process.platform === "win32" ? path10.win32 : path10.posix;
18113
+ const rel2 = p3.relative(from, to);
18114
+ if (!rel2 || rel2.startsWith("..")) return to;
18115
+ return rel2.split(/[\\/]/).join("/");
18116
+ } catch {
18117
+ return to;
18118
+ }
18119
+ }
18120
+ var init_paths = __esm({
18121
+ "src/cli/utils/paths.ts"() {
18122
+ "use strict";
18123
+ }
18124
+ });
18125
+
18099
18126
  // packages/core/dist/shared/events.js
18100
18127
  function createBrainEvent(type, sessionId, data) {
18101
18128
  return {
@@ -18802,7 +18829,7 @@ var init_providerStream = __esm({
18802
18829
 
18803
18830
  // packages/core/dist/core/sessionJsonl.js
18804
18831
  import { promises as fs8 } from "node:fs";
18805
- import path10 from "node:path";
18832
+ import path11 from "node:path";
18806
18833
  import os3 from "node:os";
18807
18834
  async function readSession(filePath) {
18808
18835
  try {
@@ -18828,7 +18855,7 @@ async function readSession(filePath) {
18828
18855
  }
18829
18856
  }
18830
18857
  function defaultBaseDir() {
18831
- return path10.join(os3.tmpdir(), "zelari-code", "sessions");
18858
+ return path11.join(os3.tmpdir(), "zelari-code", "sessions");
18832
18859
  }
18833
18860
  var SessionJsonlWriter;
18834
18861
  var init_sessionJsonl = __esm({
@@ -18839,7 +18866,7 @@ var init_sessionJsonl = __esm({
18839
18866
  onError;
18840
18867
  constructor(sessionId, options = {}) {
18841
18868
  const baseDir = options.baseDir ?? defaultBaseDir();
18842
- this.filePath = path10.join(baseDir, `${sessionId}.jsonl`);
18869
+ this.filePath = path11.join(baseDir, `${sessionId}.jsonl`);
18843
18870
  this.onError = options.onError ?? console.error;
18844
18871
  }
18845
18872
  /** Absolute path to the session JSONL file. */
@@ -18849,7 +18876,7 @@ var init_sessionJsonl = __esm({
18849
18876
  /** Append a BrainEvent as one JSON line. Creates the file + parent dirs if missing. */
18850
18877
  async append(event) {
18851
18878
  try {
18852
- await fs8.mkdir(path10.dirname(this.filePath), { recursive: true });
18879
+ await fs8.mkdir(path11.dirname(this.filePath), { recursive: true });
18853
18880
  const line = JSON.stringify({
18854
18881
  ts: event.ts,
18855
18882
  sessionId: event.sessionId,
@@ -19221,6 +19248,346 @@ var init_registry = __esm({
19221
19248
  }
19222
19249
  });
19223
19250
 
19251
+ // src/cli/diagnostics/engine.ts
19252
+ import { spawn as spawn2 } from "node:child_process";
19253
+ import { existsSync as existsSync7 } from "node:fs";
19254
+ import path16 from "node:path";
19255
+ function parseEslintJson(stdout, _file2) {
19256
+ const json2 = safeJson(stdout);
19257
+ if (!Array.isArray(json2)) return [];
19258
+ const out = [];
19259
+ for (const fileResult of json2) {
19260
+ if (!fileResult || typeof fileResult !== "object") continue;
19261
+ const fr = fileResult;
19262
+ const filePath = typeof fr.filePath === "string" ? fr.filePath : _file2;
19263
+ if (!Array.isArray(fr.messages)) continue;
19264
+ for (const m of fr.messages) {
19265
+ if (!m || typeof m !== "object") continue;
19266
+ const msg = m;
19267
+ out.push({
19268
+ file: filePath,
19269
+ line: typeof msg.line === "number" ? msg.line : 0,
19270
+ ...typeof msg.column === "number" ? { column: msg.column } : {},
19271
+ // ESLint severity: 2 = error, 1 = warning.
19272
+ severity: msg.severity === 2 ? "error" : "warning",
19273
+ message: typeof msg.message === "string" ? msg.message : "(no message)",
19274
+ ...typeof msg.ruleId === "string" && msg.ruleId ? { code: msg.ruleId } : {},
19275
+ source: "eslint"
19276
+ });
19277
+ }
19278
+ }
19279
+ return out;
19280
+ }
19281
+ function parseRuffJson(stdout, _file2) {
19282
+ const json2 = safeJson(stdout);
19283
+ if (!Array.isArray(json2)) return [];
19284
+ const out = [];
19285
+ for (const issue2 of json2) {
19286
+ if (!issue2 || typeof issue2 !== "object") continue;
19287
+ const it = issue2;
19288
+ const code = typeof it.code === "string" ? it.code : void 0;
19289
+ out.push({
19290
+ file: typeof it.filename === "string" ? it.filename : _file2,
19291
+ line: typeof it.location?.row === "number" ? it.location.row : 0,
19292
+ ...typeof it.location?.column === "number" ? { column: it.location.column } : {},
19293
+ // Ruff has no severity field; E999 is a syntax error, everything else
19294
+ // is a lint warning.
19295
+ severity: code === "E999" ? "error" : "warning",
19296
+ message: typeof it.message === "string" ? it.message : "(no message)",
19297
+ ...code ? { code } : {},
19298
+ source: "ruff"
19299
+ });
19300
+ }
19301
+ return out;
19302
+ }
19303
+ function safeJson(s) {
19304
+ const trimmed = s.trim();
19305
+ if (!trimmed) return null;
19306
+ try {
19307
+ return JSON.parse(trimmed);
19308
+ } catch {
19309
+ return null;
19310
+ }
19311
+ }
19312
+ function providerForFile(file2, providers = DEFAULT_PROVIDERS) {
19313
+ const ext = path16.extname(file2).toLowerCase();
19314
+ return providers.find((p3) => p3.extensions.includes(ext)) ?? null;
19315
+ }
19316
+ function resolveBin(bin, cwd) {
19317
+ const suffixes = process.platform === "win32" ? [".cmd", ".exe", ""] : [""];
19318
+ let dir = cwd;
19319
+ for (let i = 0; i < 6; i += 1) {
19320
+ for (const suffix of suffixes) {
19321
+ const candidate = path16.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
19322
+ if (existsSync7(candidate)) return candidate;
19323
+ }
19324
+ const parent = path16.dirname(dir);
19325
+ if (parent === dir) break;
19326
+ dir = parent;
19327
+ }
19328
+ return bin;
19329
+ }
19330
+ async function runDiagnosticsForFile(file2, options = {}) {
19331
+ const provider = providerForFile(file2, options.providers);
19332
+ if (!provider) return [];
19333
+ const cwd = options.cwd ?? process.cwd();
19334
+ const timeoutMs = options.timeoutMs ?? 5e3;
19335
+ const runner = options.runner ?? defaultRunner;
19336
+ try {
19337
+ const bin = options.runner ? provider.bin : resolveBin(provider.bin, cwd);
19338
+ const result = await runner(bin, provider.args(file2), { cwd, timeoutMs });
19339
+ return provider.parse(result.stdout, file2);
19340
+ } catch {
19341
+ return [];
19342
+ }
19343
+ }
19344
+ function formatDiagnostics(diagnostics, opts = {}) {
19345
+ if (diagnostics.length === 0) return "";
19346
+ const maxLines = opts.maxLines ?? 20;
19347
+ const rank = { error: 0, warning: 1, info: 2 };
19348
+ const sorted = [...diagnostics].sort(
19349
+ (a, b) => rank[a.severity] - rank[b.severity] || a.line - b.line
19350
+ );
19351
+ const errors = sorted.filter((d) => d.severity === "error").length;
19352
+ const warnings = sorted.filter((d) => d.severity === "warning").length;
19353
+ const header = `\u26A0 ${diagnostics.length} diagnostic${diagnostics.length === 1 ? "" : "s"} (${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"}) \u2014 fix before continuing:`;
19354
+ const shown = sorted.slice(0, maxLines).map((d) => {
19355
+ const loc = opts.relativeTo ? relativePosix(opts.relativeTo, d.file) : d.file;
19356
+ const pos = d.column ? `${d.line}:${d.column}` : `${d.line}`;
19357
+ const tag = d.severity === "error" ? "error" : d.severity === "warning" ? "warn" : "info";
19358
+ const code = d.code ? ` [${d.code}]` : "";
19359
+ return ` ${loc}:${pos} ${tag}${code}: ${d.message} (${d.source})`;
19360
+ });
19361
+ const overflow = sorted.length > maxLines ? [` \u2026 and ${sorted.length - maxLines} more`] : [];
19362
+ return [header, ...shown, ...overflow].join("\n");
19363
+ }
19364
+ var DEFAULT_PROVIDERS, defaultRunner;
19365
+ var init_engine = __esm({
19366
+ "src/cli/diagnostics/engine.ts"() {
19367
+ "use strict";
19368
+ init_paths();
19369
+ DEFAULT_PROVIDERS = [
19370
+ {
19371
+ name: "eslint",
19372
+ bin: "eslint",
19373
+ extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"],
19374
+ args: (file2) => ["--format", "json", file2],
19375
+ parse: parseEslintJson
19376
+ },
19377
+ {
19378
+ name: "ruff",
19379
+ bin: "ruff",
19380
+ extensions: [".py"],
19381
+ args: (file2) => ["check", "--output-format", "json", file2],
19382
+ parse: parseRuffJson
19383
+ }
19384
+ ];
19385
+ defaultRunner = (cmd, args, opts) => new Promise((resolve) => {
19386
+ let stdout = "";
19387
+ let stderr = "";
19388
+ let settled = false;
19389
+ const done = (r) => {
19390
+ if (settled) return;
19391
+ settled = true;
19392
+ resolve(r);
19393
+ };
19394
+ let child;
19395
+ try {
19396
+ child = process.platform === "win32" ? spawn2(`${cmd} ${args.join(" ")}`, { cwd: opts.cwd, shell: true }) : spawn2(cmd, args, { cwd: opts.cwd });
19397
+ } catch {
19398
+ done({ code: null, stdout: "", stderr: "" });
19399
+ return;
19400
+ }
19401
+ const timer = setTimeout(() => {
19402
+ try {
19403
+ child.kill();
19404
+ } catch {
19405
+ }
19406
+ done({ code: null, stdout, stderr });
19407
+ }, opts.timeoutMs);
19408
+ child.stdout?.on("data", (c) => {
19409
+ stdout += c.toString();
19410
+ });
19411
+ child.stderr?.on("data", (c) => {
19412
+ stderr += c.toString();
19413
+ });
19414
+ child.on("error", () => {
19415
+ clearTimeout(timer);
19416
+ done({ code: null, stdout: "", stderr: "" });
19417
+ });
19418
+ child.on("close", (code) => {
19419
+ clearTimeout(timer);
19420
+ done({ code, stdout, stderr });
19421
+ });
19422
+ });
19423
+ }
19424
+ });
19425
+
19426
+ // src/cli/lsp/servers.ts
19427
+ import path17 from "node:path";
19428
+ function languageIdForFile(file2) {
19429
+ const ext = path17.extname(file2).toLowerCase();
19430
+ const map2 = {
19431
+ ".ts": "typescript",
19432
+ ".tsx": "typescriptreact",
19433
+ ".js": "javascript",
19434
+ ".jsx": "javascriptreact",
19435
+ ".mjs": "javascript",
19436
+ ".cjs": "javascript",
19437
+ ".py": "python",
19438
+ ".go": "go",
19439
+ ".rs": "rust"
19440
+ };
19441
+ return map2[ext] ?? "plaintext";
19442
+ }
19443
+ function serverForFile(file2, servers = LSP_SERVERS) {
19444
+ const ext = path17.extname(file2).toLowerCase();
19445
+ return servers.find((s) => s.extensions.includes(ext)) ?? null;
19446
+ }
19447
+ function resolveServerCommand(file2, cwd, servers = LSP_SERVERS) {
19448
+ const spec = serverForFile(file2, servers);
19449
+ if (!spec) return null;
19450
+ const command = resolveBin(spec.bin, cwd);
19451
+ return {
19452
+ language: spec.language,
19453
+ command,
19454
+ args: spec.args,
19455
+ resolved: command !== spec.bin
19456
+ };
19457
+ }
19458
+ var LSP_SERVERS;
19459
+ var init_servers = __esm({
19460
+ "src/cli/lsp/servers.ts"() {
19461
+ "use strict";
19462
+ init_engine();
19463
+ LSP_SERVERS = [
19464
+ {
19465
+ language: "typescript",
19466
+ bin: "typescript-language-server",
19467
+ args: ["--stdio"],
19468
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]
19469
+ },
19470
+ {
19471
+ language: "python",
19472
+ bin: "pyright-langserver",
19473
+ args: ["--stdio"],
19474
+ extensions: [".py"]
19475
+ },
19476
+ {
19477
+ language: "go",
19478
+ bin: "gopls",
19479
+ args: [],
19480
+ extensions: [".go"]
19481
+ },
19482
+ {
19483
+ language: "rust",
19484
+ bin: "rust-analyzer",
19485
+ args: [],
19486
+ extensions: [".rs"]
19487
+ }
19488
+ ];
19489
+ }
19490
+ });
19491
+
19492
+ // src/cli/browser/driver.ts
19493
+ async function runBrowserCheck(options, loader = defaultPlaywrightLoader) {
19494
+ const consoleErrors = [];
19495
+ const pageErrors = [];
19496
+ const failedRequests = [];
19497
+ const base = { ok: false, consoleErrors, pageErrors, failedRequests };
19498
+ const pw = await loader();
19499
+ if (!pw) {
19500
+ return {
19501
+ ...base,
19502
+ error: "browser automation unavailable \u2014 install Playwright (`npm i -D playwright && npx playwright install chromium`) to enable browser_check"
19503
+ };
19504
+ }
19505
+ const timeout = options.timeoutMs ?? 15e3;
19506
+ let browser;
19507
+ try {
19508
+ browser = await pw.chromium.launch({ headless: true });
19509
+ const page = await browser.newPage();
19510
+ page.on("console", (msg) => {
19511
+ if (msg.type() === "error") consoleErrors.push(msg.text());
19512
+ });
19513
+ page.on("pageerror", (err) => pageErrors.push(err.message));
19514
+ page.on("requestfailed", (req) => {
19515
+ const f = req.failure();
19516
+ failedRequests.push(`${req.url()}${f ? ` (${f.errorText})` : ""}`);
19517
+ });
19518
+ await page.goto(options.url, { waitUntil: "load", timeout });
19519
+ for (const action of options.actions ?? []) {
19520
+ switch (action.type) {
19521
+ case "click":
19522
+ await page.click(action.selector, { timeout });
19523
+ break;
19524
+ case "fill":
19525
+ await page.fill(action.selector, action.value, { timeout });
19526
+ break;
19527
+ case "goto":
19528
+ await page.goto(action.url, { waitUntil: "load", timeout });
19529
+ break;
19530
+ case "wait":
19531
+ await page.waitForTimeout(action.ms);
19532
+ break;
19533
+ default:
19534
+ break;
19535
+ }
19536
+ }
19537
+ let selectorFound;
19538
+ if (options.waitForSelector) {
19539
+ try {
19540
+ await page.waitForSelector(options.waitForSelector, { timeout });
19541
+ selectorFound = true;
19542
+ } catch {
19543
+ selectorFound = false;
19544
+ }
19545
+ }
19546
+ let screenshotPath;
19547
+ if (options.screenshotPath) {
19548
+ try {
19549
+ await page.screenshot({ path: options.screenshotPath });
19550
+ screenshotPath = options.screenshotPath;
19551
+ } catch {
19552
+ }
19553
+ }
19554
+ const title = await page.title().catch(() => void 0);
19555
+ return {
19556
+ ok: true,
19557
+ consoleErrors,
19558
+ pageErrors,
19559
+ failedRequests,
19560
+ url: page.url(),
19561
+ ...title !== void 0 ? { title } : {},
19562
+ ...selectorFound !== void 0 ? { selectorFound } : {},
19563
+ ...screenshotPath ? { screenshotPath } : {}
19564
+ };
19565
+ } catch (err) {
19566
+ return { ...base, error: err instanceof Error ? err.message : String(err) };
19567
+ } finally {
19568
+ try {
19569
+ await browser?.close();
19570
+ } catch {
19571
+ }
19572
+ }
19573
+ }
19574
+ var defaultPlaywrightLoader;
19575
+ var init_driver = __esm({
19576
+ "src/cli/browser/driver.ts"() {
19577
+ "use strict";
19578
+ defaultPlaywrightLoader = async () => {
19579
+ try {
19580
+ const pkg = "playwright";
19581
+ const mod = await import(pkg);
19582
+ if (mod && mod.chromium && typeof mod.chromium.launch === "function") return mod;
19583
+ return null;
19584
+ } catch {
19585
+ return null;
19586
+ }
19587
+ };
19588
+ }
19589
+ });
19590
+
19224
19591
  // packages/core/dist/agents/roles.js
19225
19592
  function getAgent(id) {
19226
19593
  return AGENT_ROLES.find((a) => a.id === id);
@@ -20408,11 +20775,11 @@ var init_synthesisAudit = __esm({
20408
20775
  import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
20409
20776
  import { join as join2 } from "node:path";
20410
20777
  function loadNfrSpec(zelariRoot) {
20411
- const path33 = join2(zelariRoot, "nfr-spec.json");
20412
- if (!existsSync10(path33))
20778
+ const path34 = join2(zelariRoot, "nfr-spec.json");
20779
+ if (!existsSync10(path34))
20413
20780
  return null;
20414
20781
  try {
20415
- const raw = JSON.parse(readFileSync8(path33, "utf8"));
20782
+ const raw = JSON.parse(readFileSync8(path34, "utf8"));
20416
20783
  if (raw.version !== 1 || !Array.isArray(raw.targets))
20417
20784
  return null;
20418
20785
  return raw;
@@ -20681,9 +21048,9 @@ function checkDeadHooksInHtml(html) {
20681
21048
  }
20682
21049
  return warnings;
20683
21050
  }
20684
- function runMicroVerificationOnFile(projectRoot, relPath2, zelariRoot) {
20685
- const abs = join3(projectRoot, relPath2);
20686
- if (!existsSync11(abs) || !/\.html?$/i.test(relPath2))
21051
+ function runMicroVerificationOnFile(projectRoot, relPath, zelariRoot) {
21052
+ const abs = join3(projectRoot, relPath);
21053
+ if (!existsSync11(abs) || !/\.html?$/i.test(relPath))
20687
21054
  return [];
20688
21055
  const spec = (zelariRoot ? loadNfrSpec(zelariRoot) : null) ?? DEFAULT_NFR_SPEC;
20689
21056
  const anim = spec.animation ?? { compositorOnly: true, forbidLayoutProps: true };
@@ -20697,7 +21064,7 @@ function runMicroVerificationOnFile(projectRoot, relPath2, zelariRoot) {
20697
21064
  warnings.push({
20698
21065
  id: "motion.keyframes",
20699
21066
  message: `@keyframes uses non-allowed property "${v.property}"`,
20700
- file: relPath2,
21067
+ file: relPath,
20701
21068
  line: v.line
20702
21069
  });
20703
21070
  }
@@ -20705,12 +21072,12 @@ function runMicroVerificationOnFile(projectRoot, relPath2, zelariRoot) {
20705
21072
  warnings.push({
20706
21073
  id: "motion.transitions",
20707
21074
  message: `transition uses non-allowed property "${v.property}"`,
20708
- file: relPath2,
21075
+ file: relPath,
20709
21076
  line: v.line
20710
21077
  });
20711
21078
  }
20712
21079
  for (const w of checkDeadHooksInHtml(html)) {
20713
- warnings.push({ ...w, file: relPath2 });
21080
+ warnings.push({ ...w, file: relPath });
20714
21081
  }
20715
21082
  return warnings;
20716
21083
  }
@@ -22039,8 +22406,8 @@ async function* runChairmanFixLoop(args) {
22039
22406
  break;
22040
22407
  }
22041
22408
  const rescanned = /* @__PURE__ */ new Map();
22042
- for (const relPath2 of args.changedFiles) {
22043
- for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath: relPath2, zelariRoot })) {
22409
+ for (const relPath of args.changedFiles) {
22410
+ for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath, zelariRoot })) {
22044
22411
  rescanned.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
22045
22412
  }
22046
22413
  }
@@ -22601,9 +22968,9 @@ var init_types2 = __esm({
22601
22968
  import { readFileSync as readFileSync13 } from "node:fs";
22602
22969
  import { join as join8 } from "node:path";
22603
22970
  function readLessonsDeduped(zelariRoot) {
22604
- const path33 = join8(zelariRoot, LESSONS_FILE);
22971
+ const path34 = join8(zelariRoot, LESSONS_FILE);
22605
22972
  try {
22606
- const raw = readFileSync13(path33, "utf8");
22973
+ const raw = readFileSync13(path34, "utf8");
22607
22974
  const byId = /* @__PURE__ */ new Map();
22608
22975
  for (const line of raw.split(/\r?\n/)) {
22609
22976
  if (!line.trim())
@@ -22704,8 +23071,8 @@ function keywordsFrom(check2, signature) {
22704
23071
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
22705
23072
  }
22706
23073
  function writeLesson(zelariRoot, lesson) {
22707
- const path33 = join9(zelariRoot, LESSONS_FILE);
22708
- appendFileSync2(path33, `${JSON.stringify(lesson)}
23074
+ const path34 = join9(zelariRoot, LESSONS_FILE);
23075
+ appendFileSync2(path34, `${JSON.stringify(lesson)}
22709
23076
  `, "utf8");
22710
23077
  }
22711
23078
  function findSimilar(lessons, signature) {
@@ -23301,7 +23668,7 @@ function workspaceArtifact(rootDir, subdir, slug) {
23301
23668
  function projectName(projectRoot = process.cwd()) {
23302
23669
  return basename(realpathSync(projectRoot));
23303
23670
  }
23304
- var init_paths = __esm({
23671
+ var init_paths2 = __esm({
23305
23672
  "src/cli/workspace/paths.ts"() {
23306
23673
  "use strict";
23307
23674
  }
@@ -23316,7 +23683,7 @@ __export(workspaceSummary_exports, {
23316
23683
  buildZelariReadHint: () => buildZelariReadHint
23317
23684
  });
23318
23685
  import { existsSync as existsSync14, readFileSync as readFileSync14, readdirSync, statSync as statSync3 } from "node:fs";
23319
- import { join as join12, relative as relative2 } from "node:path";
23686
+ import { join as join12, relative } from "node:path";
23320
23687
  function buildWorkspaceSummary(projectRoot = process.cwd(), options = {}) {
23321
23688
  const { maxEntries = 30 } = options;
23322
23689
  const name = safeProjectName(projectRoot);
@@ -23529,7 +23896,7 @@ function listShallow(projectRoot, maxEntries) {
23529
23896
  out.push(`\u2026 (+${top.length - count} more)`);
23530
23897
  break;
23531
23898
  }
23532
- const rel2 = relative2(projectRoot, join12(projectRoot, entry.name));
23899
+ const rel2 = relative(projectRoot, join12(projectRoot, entry.name));
23533
23900
  if (entry.isDirectory()) {
23534
23901
  let inner = "";
23535
23902
  try {
@@ -23562,7 +23929,7 @@ var init_workspaceSummary = __esm({
23562
23929
  "src/cli/workspace/workspaceSummary.ts"() {
23563
23930
  "use strict";
23564
23931
  init_council();
23565
- init_paths();
23932
+ init_paths2();
23566
23933
  PLAN_SUMMARY_MAX_TASKS = 15;
23567
23934
  PRIORITY_RANK = {
23568
23935
  critical: 3,
@@ -23845,28 +24212,28 @@ var init_storage = __esm({
23845
24212
  VALID_SCALARS = /^(true|false|null|~)$/i;
23846
24213
  Storage = class {
23847
24214
  /** Read a Markdown file with frontmatter. Throws if not found. */
23848
- read(path33) {
23849
- if (!existsSync15(path33)) {
23850
- throw new Error(`File not found: ${path33}`);
24215
+ read(path34) {
24216
+ if (!existsSync15(path34)) {
24217
+ throw new Error(`File not found: ${path34}`);
23851
24218
  }
23852
- const md = readFileSync15(path33, "utf8");
24219
+ const md = readFileSync15(path34, "utf8");
23853
24220
  return parseFrontmatter(md);
23854
24221
  }
23855
24222
  /** Read a Markdown file; returns null if not found. */
23856
- readIfExists(path33) {
23857
- if (!existsSync15(path33)) return null;
23858
- return this.read(path33);
24223
+ readIfExists(path34) {
24224
+ if (!existsSync15(path34)) return null;
24225
+ return this.read(path34);
23859
24226
  }
23860
24227
  /**
23861
24228
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
23862
24229
  * The meta object is serialized as YAML frontmatter; body as Markdown.
23863
24230
  */
23864
- write(path33, meta3, body) {
23865
- mkdirSync7(dirname(path33), { recursive: true });
23866
- const tmp = path33 + ".tmp-" + process.pid;
24231
+ write(path34, meta3, body) {
24232
+ mkdirSync7(dirname(path34), { recursive: true });
24233
+ const tmp = path34 + ".tmp-" + process.pid;
23867
24234
  const md = serializeFrontmatter(meta3, body);
23868
24235
  writeFileSync11(tmp, md, "utf8");
23869
- renameSync2(tmp, path33);
24236
+ renameSync2(tmp, path34);
23870
24237
  }
23871
24238
  /** List all .md files in a directory (non-recursive). */
23872
24239
  listMarkdown(dir) {
@@ -23917,7 +24284,7 @@ import {
23917
24284
  mkdirSync as mkdirSync8,
23918
24285
  renameSync as renameSync3
23919
24286
  } from "node:fs";
23920
- import { join as join14, basename as basename2, dirname as dirname2, relative as relative3 } from "node:path";
24287
+ import { join as join14, basename as basename2, dirname as dirname2, relative as relative2 } from "node:path";
23921
24288
  function createWorkspaceContext(projectRoot = process.cwd()) {
23922
24289
  const rootDir = resolveWorkspaceRoot(projectRoot);
23923
24290
  return {
@@ -23944,8 +24311,8 @@ function readPlan(ctx) {
23944
24311
  } catch {
23945
24312
  }
23946
24313
  }
23947
- const path33 = workspaceFile(ctx.rootDir, "plan");
23948
- const doc = ctx.storage.readIfExists(path33);
24314
+ const path34 = workspaceFile(ctx.rootDir, "plan");
24315
+ const doc = ctx.storage.readIfExists(path34);
23949
24316
  if (!doc) return { phases: [], tasks: [], milestones: [] };
23950
24317
  const meta3 = doc.meta;
23951
24318
  return {
@@ -24110,7 +24477,7 @@ function addMilestoneRecord(ctx, summary, input) {
24110
24477
  dueDate: input.dueDate,
24111
24478
  targetVersion: version2
24112
24479
  });
24113
- const path33 = join14(ctx.rootDir, "milestones", `${id}.md`);
24480
+ const path34 = join14(ctx.rootDir, "milestones", `${id}.md`);
24114
24481
  const meta3 = {
24115
24482
  kind: "milestone",
24116
24483
  id,
@@ -24127,7 +24494,7 @@ function addMilestoneRecord(ctx, summary, input) {
24127
24494
  `Target version: ${version2}`,
24128
24495
  ""
24129
24496
  ].join("\n");
24130
- ctx.storage.write(path33, meta3, body);
24497
+ ctx.storage.write(path34, meta3, body);
24131
24498
  return { id, created: true };
24132
24499
  }
24133
24500
  function readPlanSummary(ctx) {
@@ -24331,7 +24698,7 @@ function addIdeaStub(ctx) {
24331
24698
  const tags = args["tags"] ?? [];
24332
24699
  const category = args["category"] ?? "General";
24333
24700
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
24334
- const path33 = workspaceArtifact(ctx.rootDir, "decisions", id);
24701
+ const path34 = workspaceArtifact(ctx.rootDir, "decisions", id);
24335
24702
  const meta3 = {
24336
24703
  kind: "adr",
24337
24704
  status: "proposed",
@@ -24357,7 +24724,7 @@ function addIdeaStub(ctx) {
24357
24724
  ...consequences.map((c) => `- ${c}`),
24358
24725
  ""
24359
24726
  ].join("\n");
24360
- ctx.storage.write(path33, meta3, body);
24727
+ ctx.storage.write(path34, meta3, body);
24361
24728
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
24362
24729
  });
24363
24730
  }
@@ -24439,14 +24806,14 @@ function createDocumentStub(ctx) {
24439
24806
  ctx.storage.write(risksPath, riskMeta, content);
24440
24807
  return `Document "${title}" created at risks.md (workspace root).`;
24441
24808
  }
24442
- const path33 = workspaceArtifact(ctx.rootDir, "docs", slug);
24809
+ const path34 = workspaceArtifact(ctx.rootDir, "docs", slug);
24443
24810
  const meta3 = {
24444
24811
  kind: "doc",
24445
24812
  id: slug,
24446
24813
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
24447
24814
  tags
24448
24815
  };
24449
- ctx.storage.write(path33, meta3, content);
24816
+ ctx.storage.write(path34, meta3, content);
24450
24817
  return `Document "${title}" created at docs/${slug}.md.`;
24451
24818
  });
24452
24819
  }
@@ -24510,7 +24877,7 @@ function searchDocumentsStub(ctx) {
24510
24877
  const end = Math.min(raw.length, idx + matchLen + 40);
24511
24878
  const snippet = "\u2026" + raw.slice(start, end).replace(/\n/g, " ").trim() + "\u2026";
24512
24879
  results.push({
24513
- path: relative3(ctx.rootDir, file2) || basename2(file2),
24880
+ path: relative2(ctx.rootDir, file2) || basename2(file2),
24514
24881
  snippet
24515
24882
  });
24516
24883
  if (results.length >= limit) break;
@@ -24594,7 +24961,7 @@ var init_stubs = __esm({
24594
24961
  "src/cli/workspace/stubs.ts"() {
24595
24962
  "use strict";
24596
24963
  init_storage();
24597
- init_paths();
24964
+ init_paths2();
24598
24965
  init_storage();
24599
24966
  SEARCH_NUDGE_THRESHOLD = 5;
24600
24967
  }
@@ -24673,6 +25040,7 @@ __export(updater_exports, {
24673
25040
  compareSemver: () => compareSemver,
24674
25041
  fetchLatestVersion: () => fetchLatestVersion,
24675
25042
  getCurrentVersion: () => getCurrentVersion,
25043
+ looksLikeBrokenShim: () => looksLikeBrokenShim,
24676
25044
  performUpdate: () => performUpdate,
24677
25045
  resolveBundledNpmCli: () => resolveBundledNpmCli
24678
25046
  });
@@ -25157,7 +25525,7 @@ function hasWorkspacePlan(projectRoot = process.cwd()) {
25157
25525
  var init_planDetect = __esm({
25158
25526
  "src/cli/workspace/planDetect.ts"() {
25159
25527
  "use strict";
25160
- init_paths();
25528
+ init_paths2();
25161
25529
  }
25162
25530
  });
25163
25531
 
@@ -25250,10 +25618,10 @@ import { createHash as createHash3 } from "node:crypto";
25250
25618
  import { join as join17 } from "node:path";
25251
25619
  import { readFile as readFile2 } from "node:fs/promises";
25252
25620
  async function readPackageJson2(projectRoot) {
25253
- const path33 = join17(projectRoot, "package.json");
25254
- if (!existsSync20(path33)) return null;
25621
+ const path34 = join17(projectRoot, "package.json");
25622
+ if (!existsSync20(path34)) return null;
25255
25623
  try {
25256
- return JSON.parse(await readFile2(path33, "utf8"));
25624
+ return JSON.parse(await readFile2(path34, "utf8"));
25257
25625
  } catch {
25258
25626
  return null;
25259
25627
  }
@@ -25335,9 +25703,9 @@ async function genBuild(ctx) {
25335
25703
  ].join("\n");
25336
25704
  }
25337
25705
  async function genOpenQuestions(ctx) {
25338
- const path33 = join17(ctx.rootDir, "risks.md");
25339
- if (!existsSync20(path33)) return "_No open questions._";
25340
- const content = readFileSync19(path33, "utf8");
25706
+ const path34 = join17(ctx.rootDir, "risks.md");
25707
+ if (!existsSync20(path34)) return "_No open questions._";
25708
+ const content = readFileSync19(path34, "utf8");
25341
25709
  const lines = content.split("\n");
25342
25710
  const questions = [];
25343
25711
  let currentTitle = "";
@@ -25467,7 +25835,7 @@ var AUTO_SECTIONS, MARKER_OPEN, MARKER_CLOSE, GENERATORS;
25467
25835
  var init_agentsMd = __esm({
25468
25836
  "src/cli/workspace/agentsMd.ts"() {
25469
25837
  "use strict";
25470
- init_paths();
25838
+ init_paths2();
25471
25839
  AUTO_SECTIONS = [
25472
25840
  "tech-stack",
25473
25841
  "decisions",
@@ -25900,8 +26268,8 @@ async function runPostCouncilHook(ctx, options) {
25900
26268
  sources: scope.sources
25901
26269
  } : void 0
25902
26270
  });
25903
- const path33 = writeCouncilCompletion(ctx.rootDir, completion);
25904
- completionHook = { ran: true, path: path33, completion };
26271
+ const path34 = writeCouncilCompletion(ctx.rootDir, completion);
26272
+ completionHook = { ran: true, path: path34, completion };
25905
26273
  } catch (err) {
25906
26274
  completionHook = {
25907
26275
  ran: true,
@@ -25954,7 +26322,7 @@ var init_buildLessonsSummary = __esm({
25954
26322
  "src/cli/workspace/buildLessonsSummary.ts"() {
25955
26323
  "use strict";
25956
26324
  init_council();
25957
- init_paths();
26325
+ init_paths2();
25958
26326
  }
25959
26327
  });
25960
26328
 
@@ -26534,36 +26902,478 @@ var init_zelariMission = __esm({
26534
26902
  }
26535
26903
  });
26536
26904
 
26905
+ // src/cli/utils/prereqChecks.ts
26906
+ var prereqChecks_exports = {};
26907
+ __export(prereqChecks_exports, {
26908
+ checkAgentBash: () => checkAgentBash,
26909
+ checkAgentGit: () => checkAgentGit,
26910
+ checkAgentNode: () => checkAgentNode,
26911
+ checkMainNode: () => checkMainNode,
26912
+ runPrereqChecks: () => runPrereqChecks
26913
+ });
26914
+ import { execSync, spawnSync as spawnSync2 } from "node:child_process";
26915
+ import { existsSync as existsSync25 } from "node:fs";
26916
+ function resolveAgentShellSync() {
26917
+ if (process.platform !== "win32") {
26918
+ return { bashPath: null, isBash: true, via: "/bin/sh" };
26919
+ }
26920
+ const envShell = process.env.ZELARI_SHELL;
26921
+ if (envShell && envShell.trim().length > 0 && existsSyncSafe2(envShell)) {
26922
+ return { bashPath: envShell, isBash: true, via: `bash (${envShell})` };
26923
+ }
26924
+ const sessionShell = process.env.SHELL;
26925
+ if (sessionShell && sessionShell.trim().length > 0 && existsSyncSafe2(sessionShell)) {
26926
+ return {
26927
+ bashPath: sessionShell,
26928
+ isBash: true,
26929
+ via: `bash (${sessionShell})`
26930
+ };
26931
+ }
26932
+ for (const p3 of STANDARD_BASH_PATHS2) {
26933
+ if (existsSyncSafe2(p3)) {
26934
+ return { bashPath: p3, isBash: true, via: `bash (${p3})` };
26935
+ }
26936
+ }
26937
+ try {
26938
+ const result = spawnSync2("where", ["bash"], {
26939
+ encoding: "utf8",
26940
+ windowsHide: true
26941
+ });
26942
+ if (result.status === 0 && result.stdout) {
26943
+ const first = result.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
26944
+ if (first && existsSyncSafe2(first)) {
26945
+ const trimmed = first.trim();
26946
+ return { bashPath: trimmed, isBash: true, via: `bash (${trimmed})` };
26947
+ }
26948
+ }
26949
+ } catch {
26950
+ }
26951
+ return { bashPath: null, isBash: false, via: "cmd.exe" };
26952
+ }
26953
+ function existsSyncSafe2(p3) {
26954
+ try {
26955
+ return existsSync25(p3);
26956
+ } catch {
26957
+ return false;
26958
+ }
26959
+ }
26960
+ function probeTool(tool) {
26961
+ const shell = resolveAgentShellSync();
26962
+ let stdout = "";
26963
+ if (shell.bashPath) {
26964
+ try {
26965
+ const r = spawnSync2(shell.bashPath, ["-c", `${tool} --version`], {
26966
+ encoding: "utf8",
26967
+ stdio: ["ignore", "pipe", "ignore"],
26968
+ windowsHide: true
26969
+ });
26970
+ if (r.status === 0) stdout = (r.stdout || "").trim();
26971
+ } catch {
26972
+ }
26973
+ } else if (process.platform === "win32") {
26974
+ try {
26975
+ stdout = execSync(`${tool} --version`, {
26976
+ encoding: "utf8",
26977
+ stdio: ["ignore", "pipe", "ignore"]
26978
+ }).trim();
26979
+ } catch {
26980
+ }
26981
+ } else {
26982
+ try {
26983
+ stdout = execSync(`${tool} --version`, {
26984
+ encoding: "utf8",
26985
+ stdio: ["ignore", "pipe", "ignore"]
26986
+ }).trim();
26987
+ } catch {
26988
+ }
26989
+ }
26990
+ const m = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
26991
+ return {
26992
+ found: stdout.length > 0,
26993
+ version: m ? `${m[1]}.${m[2]}.${m[3]}` : "",
26994
+ raw: stdout
26995
+ };
26996
+ }
26997
+ function nodeMissingHint() {
26998
+ const shell = resolveAgentShellSync();
26999
+ if (process.platform === "win32") {
27000
+ return `node is not reachable from the agent's shell (${shell.via}).
27001
+ This usually means Node was installed for "current user" only,
27002
+ while Git Bash inherits the SYSTEM Path. Fix (pick one):
27003
+ - Reinstall Node (https://nodejs.org) and choose
27004
+ "Add to PATH for all users", OR
27005
+ - Add C:\\Program Files\\nodejs\\ to the SYSTEM Path
27006
+ (System Properties \u2192 Environment Variables \u2192 Path), OR
27007
+ - Set ZELARI_SHELL to a bash that already sees node.`;
27008
+ }
27009
+ return `node is not on the agent's shell PATH.
27010
+ Install Node >= ${MIN_NODE_MAJOR} (https://nodejs.org) or, if you use
27011
+ nvm, run \`nvm use <version>\` and reopen this terminal.`;
27012
+ }
27013
+ function checkAgentNode() {
27014
+ const probe = probeTool("node");
27015
+ if (!probe.found) {
27016
+ return {
27017
+ ok: false,
27018
+ severity: "critical",
27019
+ tool: "node",
27020
+ message: nodeMissingHint()
27021
+ };
27022
+ }
27023
+ const major = Number(probe.version.split(".")[0]);
27024
+ if (!probe.version || Number.isNaN(major)) {
27025
+ return {
27026
+ ok: false,
27027
+ severity: "warn",
27028
+ tool: "node",
27029
+ message: `could not parse node version from "${probe.raw}" (node is reachable, but version check skipped)`
27030
+ };
27031
+ }
27032
+ if (major < MIN_NODE_MAJOR) {
27033
+ return {
27034
+ ok: false,
27035
+ severity: "critical",
27036
+ tool: "node",
27037
+ message: `node ${probe.version} is older than the required >= ${MIN_NODE_MAJOR}.0.0 (from engines.node). Upgrade: https://nodejs.org`
27038
+ };
27039
+ }
27040
+ return {
27041
+ ok: true,
27042
+ severity: "critical",
27043
+ tool: "node",
27044
+ message: `node ${probe.version} (agent shell)`
27045
+ };
27046
+ }
27047
+ function checkAgentGit() {
27048
+ const probe = probeTool("git");
27049
+ if (!probe.found) {
27050
+ 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)`;
27051
+ return {
27052
+ ok: false,
27053
+ severity: "warn",
27054
+ tool: "git",
27055
+ message: `git not found on the agent's shell PATH \u2014 /diff, /undo and the git sidebar will be disabled. ${hint}`
27056
+ };
27057
+ }
27058
+ return {
27059
+ ok: true,
27060
+ severity: "warn",
27061
+ tool: "git",
27062
+ message: `git ${probe.version} (agent shell)`
27063
+ };
27064
+ }
27065
+ function checkAgentBash() {
27066
+ if (process.platform !== "win32") {
27067
+ return {
27068
+ ok: true,
27069
+ severity: "warn",
27070
+ tool: "bash",
27071
+ message: "POSIX shell always available on this platform"
27072
+ };
27073
+ }
27074
+ const shell = resolveAgentShellSync();
27075
+ if (shell.isBash) {
27076
+ return {
27077
+ ok: true,
27078
+ severity: "warn",
27079
+ tool: "bash",
27080
+ message: `real bash available (${shell.via})`
27081
+ };
27082
+ }
27083
+ return {
27084
+ ok: false,
27085
+ severity: "warn",
27086
+ tool: "bash",
27087
+ message: `no Git Bash found \u2014 the agent's \`bash\` tool falls back to cmd.exe,
27088
+ where POSIX commands (ls, which, $VAR, &&) may fail. Install Git
27089
+ for Windows (https://git-scm.com/download/win) or set ZELARI_SHELL
27090
+ to your bash binary.`
27091
+ };
27092
+ }
27093
+ function checkMainNode() {
27094
+ try {
27095
+ const raw = execSync("node --version", {
27096
+ encoding: "utf8",
27097
+ stdio: ["ignore", "pipe", "ignore"]
27098
+ }).trim();
27099
+ const m = raw.match(/(\d+)\.(\d+)\.(\d+)/);
27100
+ const version2 = m ? `${m[1]}.${m[2]}.${m[3]}` : "";
27101
+ const major = Number(version2.split(".")[0]);
27102
+ if (version2 && !Number.isNaN(major) && major >= MIN_NODE_MAJOR) {
27103
+ return {
27104
+ ok: true,
27105
+ severity: "critical",
27106
+ tool: "node",
27107
+ message: `node ${version2} (main process)`
27108
+ };
27109
+ }
27110
+ return {
27111
+ ok: false,
27112
+ severity: "warn",
27113
+ tool: "node",
27114
+ message: `node present in main process but version unparseable or < ${MIN_NODE_MAJOR}: "${raw}"`
27115
+ };
27116
+ } catch {
27117
+ return {
27118
+ ok: false,
27119
+ severity: "critical",
27120
+ tool: "node",
27121
+ message: "node not found on the main process PATH (zelari-code itself runs on node \u2014 this is unexpected)"
27122
+ };
27123
+ }
27124
+ }
27125
+ function runPrereqChecks(opts = { mode: "preflight" }) {
27126
+ const checks = [
27127
+ () => checkAgentNode(),
27128
+ () => checkAgentGit(),
27129
+ () => checkAgentBash()
27130
+ ];
27131
+ const results = [];
27132
+ for (const run of checks) {
27133
+ try {
27134
+ results.push(run());
27135
+ } catch (err) {
27136
+ results.push({
27137
+ ok: false,
27138
+ severity: "warn",
27139
+ tool: "node",
27140
+ message: `prereq check crashed: ${err instanceof Error ? err.message : String(err)}`
27141
+ });
27142
+ }
27143
+ }
27144
+ const hasCriticalFail = results.some(
27145
+ (r) => !r.ok && r.severity === "critical"
27146
+ );
27147
+ const warnings = results.filter((r) => !r.ok && r.severity === "warn");
27148
+ void opts.mode;
27149
+ return { results, hasCriticalFail, warnings };
27150
+ }
27151
+ var MIN_NODE_MAJOR, STANDARD_BASH_PATHS2;
27152
+ var init_prereqChecks = __esm({
27153
+ "src/cli/utils/prereqChecks.ts"() {
27154
+ "use strict";
27155
+ MIN_NODE_MAJOR = 20;
27156
+ STANDARD_BASH_PATHS2 = [
27157
+ "C:\\Program Files\\Git\\bin\\bash.exe",
27158
+ "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
27159
+ "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
27160
+ "C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe"
27161
+ ];
27162
+ }
27163
+ });
27164
+
27165
+ // src/cli/plugins/prefs.ts
27166
+ import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync15, mkdirSync as mkdirSync10 } from "node:fs";
27167
+ import path28 from "node:path";
27168
+ import os9 from "node:os";
27169
+ function getPluginPrefsPath() {
27170
+ return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path28.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
27171
+ }
27172
+ function getPluginPrefs() {
27173
+ const file2 = getPluginPrefsPath();
27174
+ try {
27175
+ if (!existsSync26(file2)) return { ...DEFAULTS2, dontAskAgain: {} };
27176
+ const raw = readFileSync23(file2, "utf-8");
27177
+ const parsed = JSON.parse(raw);
27178
+ if (parsed && typeof parsed === "object" && parsed.dontAskAgain && typeof parsed.dontAskAgain === "object") {
27179
+ const clean = {};
27180
+ for (const [k, v] of Object.entries(parsed.dontAskAgain)) {
27181
+ if (typeof k === "string" && k.length > 0 && v === true) clean[k] = true;
27182
+ }
27183
+ return { version: 1, dontAskAgain: clean };
27184
+ }
27185
+ } catch {
27186
+ }
27187
+ return { ...DEFAULTS2, dontAskAgain: {} };
27188
+ }
27189
+ function writePluginPrefs(prefs) {
27190
+ const file2 = getPluginPrefsPath();
27191
+ mkdirSync10(path28.dirname(file2), { recursive: true });
27192
+ writeFileSync15(file2, JSON.stringify(prefs, null, 2), {
27193
+ encoding: "utf-8",
27194
+ mode: 384
27195
+ });
27196
+ }
27197
+ function markDontAskAgain(pluginId) {
27198
+ try {
27199
+ const prefs = getPluginPrefs();
27200
+ prefs.dontAskAgain[pluginId] = true;
27201
+ writePluginPrefs(prefs);
27202
+ } catch {
27203
+ }
27204
+ }
27205
+ function isMuted(pluginId) {
27206
+ return getPluginPrefs().dontAskAgain[pluginId] === true;
27207
+ }
27208
+ var DEFAULTS2;
27209
+ var init_prefs = __esm({
27210
+ "src/cli/plugins/prefs.ts"() {
27211
+ "use strict";
27212
+ DEFAULTS2 = {
27213
+ version: 1,
27214
+ dontAskAgain: {}
27215
+ };
27216
+ }
27217
+ });
27218
+
27219
+ // src/cli/plugins/registry.ts
27220
+ var registry_exports = {};
27221
+ __export(registry_exports, {
27222
+ PLUGINS: () => PLUGINS,
27223
+ detectMissingPlugins: () => detectMissingPlugins,
27224
+ findPlugin: () => findPlugin
27225
+ });
27226
+ import { spawnSync as spawnSync3 } from "node:child_process";
27227
+ function detectLocalBin(bin) {
27228
+ return (cwd) => {
27229
+ try {
27230
+ const resolved = resolveBin(bin, cwd);
27231
+ return Promise.resolve(resolved !== bin);
27232
+ } catch {
27233
+ return Promise.resolve(false);
27234
+ }
27235
+ };
27236
+ }
27237
+ function detectGlobalBin(bin) {
27238
+ return () => {
27239
+ try {
27240
+ const res = spawnSync3(bin, ["--version"], {
27241
+ stdio: ["ignore", "pipe", "ignore"],
27242
+ shell: process.platform === "win32",
27243
+ timeout: 4e3
27244
+ });
27245
+ return Promise.resolve(res.status === 0 || res.stdout != null && res.stdout.toString().trim().length > 0 && res.error === void 0);
27246
+ } catch {
27247
+ return Promise.resolve(false);
27248
+ }
27249
+ };
27250
+ }
27251
+ async function detectPlaywright() {
27252
+ try {
27253
+ const mod = await defaultPlaywrightLoader();
27254
+ return mod !== null;
27255
+ } catch {
27256
+ return false;
27257
+ }
27258
+ }
27259
+ function binForProvider(name) {
27260
+ const p3 = DEFAULT_PROVIDERS.find((x) => x.name === name);
27261
+ if (!p3) throw new Error(`plugin registry: unknown diagnostic provider '${name}'`);
27262
+ return p3.bin;
27263
+ }
27264
+ function binForLspLanguage(language) {
27265
+ const s = LSP_SERVERS.find((x) => x.language === language);
27266
+ if (!s) throw new Error(`plugin registry: unknown LSP language '${language}'`);
27267
+ return s.bin;
27268
+ }
27269
+ async function detectMissingPlugins(cwd, opts = {}) {
27270
+ const missing = [];
27271
+ for (const spec of PLUGINS) {
27272
+ if (process.env[spec.featureGate] === "0") continue;
27273
+ if (!opts.includeMuted && isMuted(spec.id)) continue;
27274
+ let present = false;
27275
+ try {
27276
+ present = await spec.detect(cwd);
27277
+ } catch {
27278
+ present = false;
27279
+ }
27280
+ if (!present) missing.push(spec);
27281
+ }
27282
+ return missing;
27283
+ }
27284
+ function findPlugin(id) {
27285
+ return PLUGINS.find((p3) => p3.id === id);
27286
+ }
27287
+ var PLUGINS;
27288
+ var init_registry2 = __esm({
27289
+ "src/cli/plugins/registry.ts"() {
27290
+ "use strict";
27291
+ init_engine();
27292
+ init_driver();
27293
+ init_engine();
27294
+ init_servers();
27295
+ init_prefs();
27296
+ PLUGINS = [
27297
+ {
27298
+ id: "eslint",
27299
+ label: "ESLint (diagnostics for JS/TS)",
27300
+ npmPackage: "eslint",
27301
+ installScope: "dev",
27302
+ detect: detectLocalBin(binForProvider("eslint")),
27303
+ featureGate: "ZELARI_DIAGNOSTICS",
27304
+ description: "Enables the post-edit diagnostics loop for .js/.jsx/.ts/.tsx/.mjs/.cjs files."
27305
+ },
27306
+ {
27307
+ id: "ruff",
27308
+ label: "Ruff (diagnostics for Python)",
27309
+ npmPackage: "ruff",
27310
+ installScope: "dev",
27311
+ detect: detectLocalBin(binForProvider("ruff")),
27312
+ featureGate: "ZELARI_DIAGNOSTICS",
27313
+ description: "Enables the post-edit diagnostics loop for .py files."
27314
+ },
27315
+ {
27316
+ id: "playwright",
27317
+ label: "Playwright (browser_check tool)",
27318
+ npmPackage: "playwright",
27319
+ installScope: "dev",
27320
+ detect: () => detectPlaywright(),
27321
+ postInstallHint: "Then fetch the browser binary: `npx playwright install chromium`",
27322
+ featureGate: "ZELARI_BROWSER",
27323
+ description: "Powers the browser_check tool (URL probing, click/fill/wait, screenshots)."
27324
+ },
27325
+ {
27326
+ id: "typescript-language-server",
27327
+ label: "typescript-language-server (LSP for TS/JS)",
27328
+ npmPackage: "typescript-language-server",
27329
+ installScope: "global",
27330
+ detect: detectGlobalBin(binForLspLanguage("typescript")),
27331
+ featureGate: "ZELARI_LSP",
27332
+ description: "Powers go_to_definition / find_references / hover_type / rename_symbol for TS/JS."
27333
+ },
27334
+ {
27335
+ id: "pyright",
27336
+ label: "pyright (LSP for Python)",
27337
+ npmPackage: "pyright",
27338
+ installScope: "global",
27339
+ detect: detectGlobalBin(binForLspLanguage("python")),
27340
+ featureGate: "ZELARI_LSP",
27341
+ description: "Powers go_to_definition / find_references / hover_type / rename_symbol for Python."
27342
+ }
27343
+ ];
27344
+ }
27345
+ });
27346
+
26537
27347
  // src/cli/utils/doctor.ts
26538
27348
  var doctor_exports = {};
26539
27349
  __export(doctor_exports, {
26540
27350
  runDoctor: () => runDoctor
26541
27351
  });
26542
- import { execSync } from "node:child_process";
26543
- import { existsSync as existsSync29, readFileSync as readFileSync25, readlinkSync, statSync as statSync6 } from "node:fs";
27352
+ import { execSync as execSync2 } from "node:child_process";
27353
+ import { existsSync as existsSync31, readFileSync as readFileSync26, readlinkSync, statSync as statSync6 } from "node:fs";
26544
27354
  import { createRequire as createRequire2 } from "node:module";
26545
27355
  import { fileURLToPath as fileURLToPath2 } from "node:url";
26546
- import path32 from "node:path";
27356
+ import path33 from "node:path";
26547
27357
  function findPackageRoot(start) {
26548
27358
  let dir = start;
26549
27359
  for (let i = 0; i < 6; i += 1) {
26550
- const candidate = path32.join(dir, "package.json");
26551
- if (existsSync29(candidate)) {
27360
+ const candidate = path33.join(dir, "package.json");
27361
+ if (existsSync31(candidate)) {
26552
27362
  try {
26553
- const pkg = JSON.parse(readFileSync25(candidate, "utf8"));
27363
+ const pkg = JSON.parse(readFileSync26(candidate, "utf8"));
26554
27364
  if (pkg.name === "zelari-code") return dir;
26555
27365
  } catch {
26556
27366
  }
26557
27367
  }
26558
- const parent = path32.dirname(dir);
27368
+ const parent = path33.dirname(dir);
26559
27369
  if (parent === dir) break;
26560
27370
  dir = parent;
26561
27371
  }
26562
- return path32.resolve(__dirname3, "..", "..", "..");
27372
+ return path33.resolve(__dirname3, "..", "..", "..");
26563
27373
  }
26564
27374
  function tryExec(cmd) {
26565
27375
  try {
26566
- return execSync(cmd, {
27376
+ return execSync2(cmd, {
26567
27377
  encoding: "utf8",
26568
27378
  stdio: ["ignore", "pipe", "ignore"]
26569
27379
  }).trim();
@@ -26573,8 +27383,8 @@ function tryExec(cmd) {
26573
27383
  }
26574
27384
  function readPackageJson3() {
26575
27385
  try {
26576
- const pkgPath = path32.join(packageRoot, "package.json");
26577
- return JSON.parse(readFileSync25(pkgPath, "utf8"));
27386
+ const pkgPath = path33.join(packageRoot, "package.json");
27387
+ return JSON.parse(readFileSync26(pkgPath, "utf8"));
26578
27388
  } catch {
26579
27389
  return null;
26580
27390
  }
@@ -26589,8 +27399,8 @@ function checkShim(pkgName) {
26589
27399
  }
26590
27400
  const isWin = process.platform === "win32";
26591
27401
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
26592
- const shimPath = path32.join(prefix, shimName);
26593
- if (!existsSync29(shimPath)) {
27402
+ const shimPath = path33.join(prefix, shimName);
27403
+ if (!existsSync31(shimPath)) {
26594
27404
  return FAIL(
26595
27405
  `shim not found at ${shimPath}
26596
27406
  fix: npm install -g ${pkgName}@latest --force`
@@ -26599,7 +27409,7 @@ function checkShim(pkgName) {
26599
27409
  try {
26600
27410
  const st = statSync6(shimPath);
26601
27411
  if (isWin) {
26602
- const content = readFileSync25(shimPath, "utf8");
27412
+ const content = readFileSync26(shimPath, "utf8");
26603
27413
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
26604
27414
  return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
26605
27415
  }
@@ -26617,8 +27427,8 @@ function checkShim(pkgName) {
26617
27427
  fix: npm install -g ${pkgName}@latest --force`
26618
27428
  );
26619
27429
  }
26620
- const resolved = path32.resolve(path32.dirname(shimPath), target);
26621
- const expected = path32.join(
27430
+ const resolved = path33.resolve(path33.dirname(shimPath), target);
27431
+ const expected = path33.join(
26622
27432
  prefix,
26623
27433
  "node_modules",
26624
27434
  pkgName,
@@ -26657,8 +27467,8 @@ function checkNode(pkg) {
26657
27467
  return OK(`node ${raw}`);
26658
27468
  }
26659
27469
  function checkBundle() {
26660
- const bundle = path32.join(packageRoot, "dist", "cli", "main.bundled.js");
26661
- if (!existsSync29(bundle)) {
27470
+ const bundle = path33.join(packageRoot, "dist", "cli", "main.bundled.js");
27471
+ if (!existsSync31(bundle)) {
26662
27472
  return FAIL(
26663
27473
  `dist/cli/main.bundled.js missing at ${bundle}
26664
27474
  fix: npm run build:cli (then reinstall or run via tsx)`
@@ -26678,7 +27488,7 @@ function checkRuntimeDeps() {
26678
27488
  const missing = [];
26679
27489
  for (const dep of required2) {
26680
27490
  try {
26681
- const localReq = createRequire2(path32.join(packageRoot, "package.json"));
27491
+ const localReq = createRequire2(path33.join(packageRoot, "package.json"));
26682
27492
  localReq.resolve(dep);
26683
27493
  } catch {
26684
27494
  missing.push(dep);
@@ -26702,22 +27512,60 @@ function checkPath() {
26702
27512
  if (pathDirs.includes(prefix)) {
26703
27513
  return OK(`PATH includes npm prefix (${prefix})`);
26704
27514
  }
27515
+ const winHint = ` fix: zelari-code --fix-path (adds ${prefix} to the user PATH)
27516
+ then open a NEW terminal and run \`zelari-code --version\`
27517
+ if it persists: see scripts/diagnose-path.ps1 in the repo`;
27518
+ const posixHint = ` fix: export PATH="${prefix}/bin:$PATH"
27519
+ (add it to ~/.bashrc / ~/.zshrc to persist)`;
26705
27520
  return WARN(
26706
27521
  `PATH does not include npm prefix (${prefix})
26707
27522
  symptom: "zelari-code: command not found" after install
26708
- fix (POSIX): export PATH="$(npm prefix -g)/bin:$PATH"
26709
- fix (Windows): $env:Path = "$(npm prefix -g);$env:Path"`
27523
+ ` + (process.platform === "win32" ? winHint : posixHint)
26710
27524
  );
26711
27525
  }
26712
- function runDoctor() {
27526
+ function prereqToCheckResult(r) {
27527
+ if (r.ok) return OK(r.message);
27528
+ return r.severity === "critical" ? FAIL(r.message, "critical") : WARN(r.message);
27529
+ }
27530
+ async function checkOptionalPlugins() {
27531
+ const { detectMissingPlugins: detectMissingPlugins2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
27532
+ let missing;
27533
+ try {
27534
+ missing = await detectMissingPlugins2(packageRoot, { includeMuted: true });
27535
+ } catch {
27536
+ return WARN("could not detect optional plugins");
27537
+ }
27538
+ if (missing.length === 0) return OK("all optional plugins available");
27539
+ const lines = missing.map((p3) => `${p3.id} missing \u2014 /plugins install ${p3.id}`);
27540
+ return WARN(
27541
+ `${missing.length} optional plugin(s) missing (features degrade silently without them):
27542
+ ` + lines.map((l) => ` ${l}`).join("\n") + "\n install via: /plugins (or the boot prompt)"
27543
+ );
27544
+ }
27545
+ async function runDoctor() {
26713
27546
  const pkg = readPackageJson3();
26714
27547
  const pkgName = pkg?.name ?? "zelari-code";
26715
27548
  const checks = [
27549
+ // --- install-health checks (main-process probes) ---
26716
27550
  { name: "node", run: () => checkNode(pkg) },
26717
27551
  { name: "bin shim", run: () => checkShim(pkgName) },
26718
27552
  { name: "cli bundle", run: () => checkBundle() },
26719
27553
  { name: "runtime deps", run: () => checkRuntimeDeps() },
26720
- { name: "PATH", run: () => checkPath() }
27554
+ { name: "PATH", run: () => checkPath() },
27555
+ // --- agent-shell checks (v1.4.0) ---
27556
+ // These probe node/git/bash THROUGH the resolved shell the agent uses.
27557
+ // A pass on "node" + a FAIL on "node (agent shell)" is the tell-tale
27558
+ // signature of the PATH-mismatch bug (node visible to the main process,
27559
+ // invisible to Git Bash) that silently breaks council builds.
27560
+ { name: "node (agent shell)", run: () => prereqToCheckResult(checkAgentNode()) },
27561
+ { name: "git (agent shell)", run: () => prereqToCheckResult(checkAgentGit()) },
27562
+ { name: "bash", run: () => prereqToCheckResult(checkAgentBash()) },
27563
+ // --- optional tool plugins (v1.5.0) ---
27564
+ // Playwright / eslint / ruff / LSP servers. WARN-only (never critical):
27565
+ // these power edge features that degrade silently when absent. Surfaced
27566
+ // here so `zelari-code --doctor` tells the user what's missing + how to
27567
+ // install (`/plugins` or the boot PluginGate).
27568
+ { name: "plugins", run: () => checkOptionalPlugins() }
26721
27569
  ];
26722
27570
  console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
26723
27571
  console.log("platform:", process.platform, process.arch);
@@ -26730,7 +27578,7 @@ function runDoctor() {
26730
27578
  for (const c of checks) {
26731
27579
  let result;
26732
27580
  try {
26733
- result = c.run();
27581
+ result = await c.run();
26734
27582
  } catch (err) {
26735
27583
  result = FAIL(
26736
27584
  `unexpected error: ${err instanceof Error ? err.message : String(err)}`
@@ -26759,8 +27607,9 @@ var require3, __dirname3, packageRoot, OK, FAIL, WARN;
26759
27607
  var init_doctor = __esm({
26760
27608
  "src/cli/utils/doctor.ts"() {
26761
27609
  "use strict";
27610
+ init_prereqChecks();
26762
27611
  require3 = createRequire2(import.meta.url);
26763
- __dirname3 = path32.dirname(fileURLToPath2(import.meta.url));
27612
+ __dirname3 = path33.dirname(fileURLToPath2(import.meta.url));
26764
27613
  packageRoot = findPackageRoot(__dirname3);
26765
27614
  OK = (message) => ({
26766
27615
  ok: true,
@@ -26780,8 +27629,87 @@ var init_doctor = __esm({
26780
27629
  }
26781
27630
  });
26782
27631
 
27632
+ // src/cli/utils/fixPath.ts
27633
+ var fixPath_exports = {};
27634
+ __export(fixPath_exports, {
27635
+ repairWindowsUserPath: () => repairWindowsUserPath
27636
+ });
27637
+ import { spawnSync as spawnSync4 } from "node:child_process";
27638
+ function getGlobalPrefix2() {
27639
+ return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim() || (() => {
27640
+ try {
27641
+ return spawnSync4("npm", ["prefix", "-g"], {
27642
+ encoding: "utf8",
27643
+ stdio: ["ignore", "pipe", "ignore"]
27644
+ }).stdout?.trim() ?? "";
27645
+ } catch {
27646
+ return "";
27647
+ }
27648
+ })();
27649
+ }
27650
+ function powershell(script) {
27651
+ try {
27652
+ const res = spawnSync4(
27653
+ "powershell.exe",
27654
+ ["-NoProfile", "-NonInteractive", "-Command", script],
27655
+ { encoding: "utf8" }
27656
+ );
27657
+ if (res.status === 0) return (res.stdout || "").trim();
27658
+ return "";
27659
+ } catch {
27660
+ return "";
27661
+ }
27662
+ }
27663
+ function repairWindowsUserPath() {
27664
+ const prefix = getGlobalPrefix2();
27665
+ if (!prefix) {
27666
+ return {
27667
+ ok: false,
27668
+ prefix: "",
27669
+ error: "could not detect the npm global prefix (run inside an npm context, or set npm_config_prefix)"
27670
+ };
27671
+ }
27672
+ if (process.platform !== "win32") {
27673
+ return {
27674
+ ok: false,
27675
+ prefix,
27676
+ error: `--fix-path is Windows-only. On ${process.platform}, add to your shell profile:
27677
+ export PATH="${prefix}/bin:$PATH"`
27678
+ };
27679
+ }
27680
+ const userPath = powershell(
27681
+ "[Environment]::GetEnvironmentVariable('Path','User')"
27682
+ );
27683
+ const norm = (p3) => p3.toLowerCase().replace(/\/+/g, "\\").replace(/\\+$/, "");
27684
+ const entries = userPath.split(";").map((e) => e.trim()).filter(Boolean);
27685
+ if (entries.some((e) => norm(e) === norm(prefix))) {
27686
+ return { ok: true, alreadyOk: true, prefix };
27687
+ }
27688
+ const updated = userPath === "" || userPath.endsWith(";") ? `${userPath}${prefix}` : `${userPath};${prefix}`;
27689
+ const writeRes = powershell(
27690
+ `[Environment]::SetEnvironmentVariable('Path', ${JSON.stringify(updated)}, 'User')`
27691
+ );
27692
+ const reread = powershell(
27693
+ "[Environment]::GetEnvironmentVariable('Path','User')"
27694
+ );
27695
+ const rereadEntries = reread.split(";").map((e) => e.trim()).filter(Boolean);
27696
+ if (rereadEntries.some((e) => norm(e) === norm(prefix))) {
27697
+ return { ok: true, alreadyOk: false, prefix };
27698
+ }
27699
+ return {
27700
+ ok: false,
27701
+ prefix,
27702
+ error: writeRes === null && reread === userPath ? "PowerShell write did not take effect (permission denied? run as the same user that owns the install)" : "PATH write failed for an unknown reason"
27703
+ };
27704
+ }
27705
+ var init_fixPath = __esm({
27706
+ "src/cli/utils/fixPath.ts"() {
27707
+ "use strict";
27708
+ }
27709
+ });
27710
+
26783
27711
  // src/cli/main.ts
26784
- import React13 from "react";
27712
+ import React14 from "react";
26785
27713
  import { render } from "ink";
26786
27714
 
26787
27715
  // src/cli/app.tsx
@@ -27714,16 +28642,8 @@ function useExecutionTimer(busy, tickMs = 1e3) {
27714
28642
  return { elapsedMs, lastMs };
27715
28643
  }
27716
28644
 
27717
- // src/cli/utils/paths.ts
27718
- import { homedir as homedir2 } from "node:os";
27719
- function shortenCwd(p3, maxLen = 40, home = homedir2()) {
27720
- let out = p3;
27721
- if (home && (out === home || out.startsWith(`${home}\\`) || out.startsWith(`${home}/`))) {
27722
- out = `~${out.slice(home.length)}`;
27723
- }
27724
- if (out.length <= maxLen) return out;
27725
- return `\u2026${out.slice(-(maxLen - 1))}`;
27726
- }
28645
+ // src/cli/app.tsx
28646
+ init_paths();
27727
28647
 
27728
28648
  // packages/core/dist/agents/skills/builtin/debugging.js
27729
28649
  init_skills();
@@ -29717,14 +30637,14 @@ import { useState as useState5, useRef as useRef3, useCallback, useEffect as use
29717
30637
  // src/cli/sessionManager.ts
29718
30638
  init_harness();
29719
30639
  import { promises as fs9, existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync, statSync } from "node:fs";
29720
- import path11 from "node:path";
30640
+ import path12 from "node:path";
29721
30641
  import os4 from "node:os";
29722
30642
  import { randomUUID } from "node:crypto";
29723
30643
  function getSessionBaseDir() {
29724
- return process.env.ANATHEMA_SESSIONS_DIR ?? path11.join(os4.homedir(), ".tmp", "zelari-code", "sessions");
30644
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path12.join(os4.homedir(), ".tmp", "zelari-code", "sessions");
29725
30645
  }
29726
30646
  function getCurrentSessionFile() {
29727
- return process.env.ANATHEMA_CURRENT_SESSION_FILE ?? path11.join(os4.homedir(), ".tmp", "zelari-code", "current.txt");
30647
+ return process.env.ANATHEMA_CURRENT_SESSION_FILE ?? path12.join(os4.homedir(), ".tmp", "zelari-code", "current.txt");
29728
30648
  }
29729
30649
  async function ensureSessionDir() {
29730
30650
  await fs9.mkdir(getSessionBaseDir(), { recursive: true });
@@ -29741,7 +30661,7 @@ function getCurrentSessionId() {
29741
30661
  }
29742
30662
  function setCurrentSessionId(id) {
29743
30663
  const file2 = getCurrentSessionFile();
29744
- mkdirSync4(path11.dirname(file2), { recursive: true });
30664
+ mkdirSync4(path12.dirname(file2), { recursive: true });
29745
30665
  writeFileSync4(file2, id, "utf-8");
29746
30666
  }
29747
30667
  function clearCurrentSessionId() {
@@ -29753,7 +30673,7 @@ function clearCurrentSessionId() {
29753
30673
  }
29754
30674
  }
29755
30675
  function getCurrentBranchFile() {
29756
- return process.env.ANATHEMA_CURRENT_BRANCH_FILE ?? path11.join(path11.dirname(getCurrentSessionFile()), "currentBranch.txt");
30676
+ return process.env.ANATHEMA_CURRENT_BRANCH_FILE ?? path12.join(path12.dirname(getCurrentSessionFile()), "currentBranch.txt");
29757
30677
  }
29758
30678
  function getCurrentBranch() {
29759
30679
  const file2 = getCurrentBranchFile();
@@ -29767,7 +30687,7 @@ function getCurrentBranch() {
29767
30687
  }
29768
30688
  function setCurrentBranch(name) {
29769
30689
  const file2 = getCurrentBranchFile();
29770
- mkdirSync4(path11.dirname(file2), { recursive: true });
30690
+ mkdirSync4(path12.dirname(file2), { recursive: true });
29771
30691
  writeFileSync4(file2, name, "utf-8");
29772
30692
  }
29773
30693
  function newSessionId() {
@@ -29786,7 +30706,7 @@ async function listSessions() {
29786
30706
  for (const entry of entries) {
29787
30707
  if (!entry.endsWith(".jsonl")) continue;
29788
30708
  const id = entry.replace(/\.jsonl$/, "");
29789
- const filePath = path11.join(baseDir, entry);
30709
+ const filePath = path12.join(baseDir, entry);
29790
30710
  try {
29791
30711
  const events = await readSession(filePath);
29792
30712
  let firstTs = 0;
@@ -29833,7 +30753,7 @@ ${lines.join("\n")}`
29833
30753
  return { message: `[${kind}] handled` };
29834
30754
  }
29835
30755
  async function loadSessionEvents(id) {
29836
- const filePath = path11.join(getSessionBaseDir(), `${id}.jsonl`);
30756
+ const filePath = path12.join(getSessionBaseDir(), `${id}.jsonl`);
29837
30757
  return readSession(filePath);
29838
30758
  }
29839
30759
 
@@ -30125,15 +31045,15 @@ import { useState as useState6, useRef as useRef4, useCallback as useCallback2 }
30125
31045
 
30126
31046
  // src/cli/metrics.ts
30127
31047
  import { promises as fs10, existsSync as existsSync6, statSync as statSync2, renameSync, appendFileSync, mkdirSync as mkdirSync5 } from "node:fs";
30128
- import path12 from "node:path";
31048
+ import path13 from "node:path";
30129
31049
  import os5 from "node:os";
30130
31050
  var METRICS_ROTATE_BYTES = 10 * 1024 * 1024;
30131
31051
  var MetricsLogger = class {
30132
31052
  file;
30133
31053
  writeQueue = Promise.resolve();
30134
31054
  constructor(file2) {
30135
- this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path12.join(os5.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
30136
- mkdirSync5(path12.dirname(this.file), { recursive: true });
31055
+ this.file = file2 ?? process.env.ANATHEMA_METRICS_FILE ?? path13.join(os5.homedir(), ".tmp", "zelari-code", "metrics.jsonl");
31056
+ mkdirSync5(path13.dirname(this.file), { recursive: true });
30137
31057
  }
30138
31058
  /** Fire-and-forget record append. */
30139
31059
  record(rec) {
@@ -30298,7 +31218,7 @@ init_diff();
30298
31218
  init_web();
30299
31219
 
30300
31220
  // src/cli/safety/sandboxPath.ts
30301
- import path13 from "node:path";
31221
+ import path14 from "node:path";
30302
31222
  var SandboxViolationError = class extends Error {
30303
31223
  constructor(message, attemptedPath, resolvedPath) {
30304
31224
  super(message);
@@ -30311,9 +31231,9 @@ function resolveSandboxedPath(userPath, options = {}) {
30311
31231
  if (typeof userPath !== "string" || userPath.length === 0) {
30312
31232
  throw new SandboxViolationError("Empty path", userPath, "");
30313
31233
  }
30314
- const root = path13.resolve(options.root ?? process.cwd());
30315
- const resolved = path13.isAbsolute(userPath) ? path13.resolve(userPath) : path13.resolve(root, userPath);
30316
- const rootWithSep = root.endsWith(path13.sep) ? root : root + path13.sep;
31234
+ const root = path14.resolve(options.root ?? process.cwd());
31235
+ const resolved = path14.isAbsolute(userPath) ? path14.resolve(userPath) : path14.resolve(root, userPath);
31236
+ const rootWithSep = root.endsWith(path14.sep) ? root : root + path14.sep;
30317
31237
  if (resolved !== root && !resolved.startsWith(rootWithSep)) {
30318
31238
  throw new SandboxViolationError(
30319
31239
  `Path escapes sandbox root: ${userPath} \u2192 ${resolved} (root: ${root})`,
@@ -30377,7 +31297,7 @@ function assertShellAllowed(command) {
30377
31297
 
30378
31298
  // src/cli/safety/auditLogger.ts
30379
31299
  import { promises as fs11 } from "node:fs";
30380
- import path14 from "node:path";
31300
+ import path15 from "node:path";
30381
31301
  import os6 from "node:os";
30382
31302
  var AuditLogger = class {
30383
31303
  logPath;
@@ -30396,7 +31316,7 @@ var AuditLogger = class {
30396
31316
  async append(entry) {
30397
31317
  const line = JSON.stringify(entry) + "\n";
30398
31318
  this.writeQueue = this.writeQueue.then(async () => {
30399
- await fs11.mkdir(path14.dirname(this.logPath), { recursive: true });
31319
+ await fs11.mkdir(path15.dirname(this.logPath), { recursive: true });
30400
31320
  await fs11.appendFile(this.logPath, line, "utf-8");
30401
31321
  });
30402
31322
  return this.writeQueue;
@@ -30440,7 +31360,7 @@ var AuditLogger = class {
30440
31360
  function defaultAuditPath() {
30441
31361
  const override = process.env.ANATHEMA_AUDIT_LOG;
30442
31362
  if (override && override.trim().length > 0) return override;
30443
- return path14.join(os6.tmpdir(), "zelari-code", "audit.jsonl");
31363
+ return path15.join(os6.tmpdir(), "zelari-code", "audit.jsonl");
30444
31364
  }
30445
31365
  function redactArgs(args) {
30446
31366
  const redacted = {};
@@ -30463,181 +31383,8 @@ function safeStringify(value) {
30463
31383
  }
30464
31384
  }
30465
31385
 
30466
- // src/cli/diagnostics/engine.ts
30467
- import { spawn as spawn2 } from "node:child_process";
30468
- import { existsSync as existsSync7 } from "node:fs";
30469
- import path15 from "node:path";
30470
- function parseEslintJson(stdout, _file2) {
30471
- const json2 = safeJson(stdout);
30472
- if (!Array.isArray(json2)) return [];
30473
- const out = [];
30474
- for (const fileResult of json2) {
30475
- if (!fileResult || typeof fileResult !== "object") continue;
30476
- const fr = fileResult;
30477
- const filePath = typeof fr.filePath === "string" ? fr.filePath : _file2;
30478
- if (!Array.isArray(fr.messages)) continue;
30479
- for (const m of fr.messages) {
30480
- if (!m || typeof m !== "object") continue;
30481
- const msg = m;
30482
- out.push({
30483
- file: filePath,
30484
- line: typeof msg.line === "number" ? msg.line : 0,
30485
- ...typeof msg.column === "number" ? { column: msg.column } : {},
30486
- // ESLint severity: 2 = error, 1 = warning.
30487
- severity: msg.severity === 2 ? "error" : "warning",
30488
- message: typeof msg.message === "string" ? msg.message : "(no message)",
30489
- ...typeof msg.ruleId === "string" && msg.ruleId ? { code: msg.ruleId } : {},
30490
- source: "eslint"
30491
- });
30492
- }
30493
- }
30494
- return out;
30495
- }
30496
- function parseRuffJson(stdout, _file2) {
30497
- const json2 = safeJson(stdout);
30498
- if (!Array.isArray(json2)) return [];
30499
- const out = [];
30500
- for (const issue2 of json2) {
30501
- if (!issue2 || typeof issue2 !== "object") continue;
30502
- const it = issue2;
30503
- const code = typeof it.code === "string" ? it.code : void 0;
30504
- out.push({
30505
- file: typeof it.filename === "string" ? it.filename : _file2,
30506
- line: typeof it.location?.row === "number" ? it.location.row : 0,
30507
- ...typeof it.location?.column === "number" ? { column: it.location.column } : {},
30508
- // Ruff has no severity field; E999 is a syntax error, everything else
30509
- // is a lint warning.
30510
- severity: code === "E999" ? "error" : "warning",
30511
- message: typeof it.message === "string" ? it.message : "(no message)",
30512
- ...code ? { code } : {},
30513
- source: "ruff"
30514
- });
30515
- }
30516
- return out;
30517
- }
30518
- function safeJson(s) {
30519
- const trimmed = s.trim();
30520
- if (!trimmed) return null;
30521
- try {
30522
- return JSON.parse(trimmed);
30523
- } catch {
30524
- return null;
30525
- }
30526
- }
30527
- var DEFAULT_PROVIDERS = [
30528
- {
30529
- name: "eslint",
30530
- bin: "eslint",
30531
- extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"],
30532
- args: (file2) => ["--format", "json", file2],
30533
- parse: parseEslintJson
30534
- },
30535
- {
30536
- name: "ruff",
30537
- bin: "ruff",
30538
- extensions: [".py"],
30539
- args: (file2) => ["check", "--output-format", "json", file2],
30540
- parse: parseRuffJson
30541
- }
30542
- ];
30543
- function providerForFile(file2, providers = DEFAULT_PROVIDERS) {
30544
- const ext = path15.extname(file2).toLowerCase();
30545
- return providers.find((p3) => p3.extensions.includes(ext)) ?? null;
30546
- }
30547
- function resolveBin(bin, cwd) {
30548
- const suffixes = process.platform === "win32" ? [".cmd", ".exe", ""] : [""];
30549
- let dir = cwd;
30550
- for (let i = 0; i < 6; i += 1) {
30551
- for (const suffix of suffixes) {
30552
- const candidate = path15.join(dir, "node_modules", ".bin", `${bin}${suffix}`);
30553
- if (existsSync7(candidate)) return candidate;
30554
- }
30555
- const parent = path15.dirname(dir);
30556
- if (parent === dir) break;
30557
- dir = parent;
30558
- }
30559
- return bin;
30560
- }
30561
- var defaultRunner = (cmd, args, opts) => new Promise((resolve) => {
30562
- let stdout = "";
30563
- let stderr = "";
30564
- let settled = false;
30565
- const done = (r) => {
30566
- if (settled) return;
30567
- settled = true;
30568
- resolve(r);
30569
- };
30570
- let child;
30571
- try {
30572
- child = process.platform === "win32" ? spawn2(`${cmd} ${args.join(" ")}`, { cwd: opts.cwd, shell: true }) : spawn2(cmd, args, { cwd: opts.cwd });
30573
- } catch {
30574
- done({ code: null, stdout: "", stderr: "" });
30575
- return;
30576
- }
30577
- const timer = setTimeout(() => {
30578
- try {
30579
- child.kill();
30580
- } catch {
30581
- }
30582
- done({ code: null, stdout, stderr });
30583
- }, opts.timeoutMs);
30584
- child.stdout?.on("data", (c) => {
30585
- stdout += c.toString();
30586
- });
30587
- child.stderr?.on("data", (c) => {
30588
- stderr += c.toString();
30589
- });
30590
- child.on("error", () => {
30591
- clearTimeout(timer);
30592
- done({ code: null, stdout: "", stderr: "" });
30593
- });
30594
- child.on("close", (code) => {
30595
- clearTimeout(timer);
30596
- done({ code, stdout, stderr });
30597
- });
30598
- });
30599
- async function runDiagnosticsForFile(file2, options = {}) {
30600
- const provider = providerForFile(file2, options.providers);
30601
- if (!provider) return [];
30602
- const cwd = options.cwd ?? process.cwd();
30603
- const timeoutMs = options.timeoutMs ?? 5e3;
30604
- const runner = options.runner ?? defaultRunner;
30605
- try {
30606
- const bin = options.runner ? provider.bin : resolveBin(provider.bin, cwd);
30607
- const result = await runner(bin, provider.args(file2), { cwd, timeoutMs });
30608
- return provider.parse(result.stdout, file2);
30609
- } catch {
30610
- return [];
30611
- }
30612
- }
30613
- function formatDiagnostics(diagnostics, opts = {}) {
30614
- if (diagnostics.length === 0) return "";
30615
- const maxLines = opts.maxLines ?? 20;
30616
- const rank = { error: 0, warning: 1, info: 2 };
30617
- const sorted = [...diagnostics].sort(
30618
- (a, b) => rank[a.severity] - rank[b.severity] || a.line - b.line
30619
- );
30620
- const errors = sorted.filter((d) => d.severity === "error").length;
30621
- const warnings = sorted.filter((d) => d.severity === "warning").length;
30622
- const header = `\u26A0 ${diagnostics.length} diagnostic${diagnostics.length === 1 ? "" : "s"} (${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"}) \u2014 fix before continuing:`;
30623
- const shown = sorted.slice(0, maxLines).map((d) => {
30624
- const loc = opts.relativeTo ? relative(opts.relativeTo, d.file) : d.file;
30625
- const pos = d.column ? `${d.line}:${d.column}` : `${d.line}`;
30626
- const tag = d.severity === "error" ? "error" : d.severity === "warning" ? "warn" : "info";
30627
- const code = d.code ? ` [${d.code}]` : "";
30628
- return ` ${loc}:${pos} ${tag}${code}: ${d.message} (${d.source})`;
30629
- });
30630
- const overflow = sorted.length > maxLines ? [` \u2026 and ${sorted.length - maxLines} more`] : [];
30631
- return [header, ...shown, ...overflow].join("\n");
30632
- }
30633
- function relative(from, to) {
30634
- try {
30635
- const rel2 = path15.relative(from, to);
30636
- return rel2 && !rel2.startsWith("..") ? rel2 : to;
30637
- } catch {
30638
- return to;
30639
- }
30640
- }
31386
+ // src/cli/toolRegistry.ts
31387
+ init_engine();
30641
31388
 
30642
31389
  // src/cli/tools/taskTool.ts
30643
31390
  init_zod();
@@ -30744,7 +31491,6 @@ function createTaskTool(deps) {
30744
31491
  // src/cli/lsp/tools.ts
30745
31492
  init_zod();
30746
31493
  init_toolTypes();
30747
- import path16 from "node:path";
30748
31494
 
30749
31495
  // src/cli/lsp/protocol.ts
30750
31496
  function encodeMessage(message) {
@@ -30797,21 +31543,14 @@ function uriToPath(uri) {
30797
31543
  }
30798
31544
 
30799
31545
  // src/cli/lsp/tools.ts
31546
+ init_paths();
30800
31547
  function fmtLocation(loc, relativeTo) {
30801
31548
  const file2 = uriToPath(loc.uri);
30802
- const rel2 = relativeTo ? relPath(relativeTo, file2) : file2;
31549
+ const rel2 = relativeTo ? relativePosix(relativeTo, file2) : file2;
30803
31550
  const line = (loc.range?.start?.line ?? 0) + 1;
30804
31551
  const col = (loc.range?.start?.character ?? 0) + 1;
30805
31552
  return `${rel2}:${line}:${col}`;
30806
31553
  }
30807
- function relPath(from, to) {
30808
- try {
30809
- const r = path16.relative(from, to);
30810
- return r && !r.startsWith("..") ? r : to;
30811
- } catch {
30812
- return to;
30813
- }
30814
- }
30815
31554
  var PosArgs = external_exports.object({
30816
31555
  path: external_exports.string().min(1).describe("File path (relative to the project root or absolute)."),
30817
31556
  line: external_exports.number().int().positive().describe("1-based line number of the symbol."),
@@ -30888,7 +31627,7 @@ function createLspTools(provider, root = process.cwd()) {
30888
31627
  }
30889
31628
  return typedOk({
30890
31629
  totalEdits: result.totalEdits,
30891
- files: result.files.map((f) => `${relPath(root, f.file)} (${f.count} edit${f.count === 1 ? "" : "s"})`)
31630
+ files: result.files.map((f) => `${relativePosix(root, f.file)} (${f.count} edit${f.count === 1 ? "" : "s"})`)
30892
31631
  });
30893
31632
  }
30894
31633
  };
@@ -30971,66 +31710,8 @@ var LspClient = class {
30971
31710
  }
30972
31711
  };
30973
31712
 
30974
- // src/cli/lsp/servers.ts
30975
- import path17 from "node:path";
30976
- var LSP_SERVERS = [
30977
- {
30978
- language: "typescript",
30979
- bin: "typescript-language-server",
30980
- args: ["--stdio"],
30981
- extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]
30982
- },
30983
- {
30984
- language: "python",
30985
- bin: "pyright-langserver",
30986
- args: ["--stdio"],
30987
- extensions: [".py"]
30988
- },
30989
- {
30990
- language: "go",
30991
- bin: "gopls",
30992
- args: [],
30993
- extensions: [".go"]
30994
- },
30995
- {
30996
- language: "rust",
30997
- bin: "rust-analyzer",
30998
- args: [],
30999
- extensions: [".rs"]
31000
- }
31001
- ];
31002
- function languageIdForFile(file2) {
31003
- const ext = path17.extname(file2).toLowerCase();
31004
- const map2 = {
31005
- ".ts": "typescript",
31006
- ".tsx": "typescriptreact",
31007
- ".js": "javascript",
31008
- ".jsx": "javascriptreact",
31009
- ".mjs": "javascript",
31010
- ".cjs": "javascript",
31011
- ".py": "python",
31012
- ".go": "go",
31013
- ".rs": "rust"
31014
- };
31015
- return map2[ext] ?? "plaintext";
31016
- }
31017
- function serverForFile(file2, servers = LSP_SERVERS) {
31018
- const ext = path17.extname(file2).toLowerCase();
31019
- return servers.find((s) => s.extensions.includes(ext)) ?? null;
31020
- }
31021
- function resolveServerCommand(file2, cwd, servers = LSP_SERVERS) {
31022
- const spec = serverForFile(file2, servers);
31023
- if (!spec) return null;
31024
- const command = resolveBin(spec.bin, cwd);
31025
- return {
31026
- language: spec.language,
31027
- command,
31028
- args: spec.args,
31029
- resolved: command !== spec.bin
31030
- };
31031
- }
31032
-
31033
31713
  // src/cli/lsp/manager.ts
31714
+ init_servers();
31034
31715
  function processTransport(child) {
31035
31716
  return {
31036
31717
  send: (data) => {
@@ -31753,103 +32434,9 @@ function createSemanticTool(deps) {
31753
32434
  // src/cli/browser/tools.ts
31754
32435
  init_zod();
31755
32436
  init_toolTypes();
32437
+ init_driver();
31756
32438
  import path21 from "node:path";
31757
32439
  import os7 from "node:os";
31758
-
31759
- // src/cli/browser/driver.ts
31760
- var defaultPlaywrightLoader = async () => {
31761
- try {
31762
- const pkg = "playwright";
31763
- const mod = await import(pkg);
31764
- if (mod && mod.chromium && typeof mod.chromium.launch === "function") return mod;
31765
- return null;
31766
- } catch {
31767
- return null;
31768
- }
31769
- };
31770
- async function runBrowserCheck(options, loader = defaultPlaywrightLoader) {
31771
- const consoleErrors = [];
31772
- const pageErrors = [];
31773
- const failedRequests = [];
31774
- const base = { ok: false, consoleErrors, pageErrors, failedRequests };
31775
- const pw = await loader();
31776
- if (!pw) {
31777
- return {
31778
- ...base,
31779
- error: "browser automation unavailable \u2014 install Playwright (`npm i -D playwright && npx playwright install chromium`) to enable browser_check"
31780
- };
31781
- }
31782
- const timeout = options.timeoutMs ?? 15e3;
31783
- let browser;
31784
- try {
31785
- browser = await pw.chromium.launch({ headless: true });
31786
- const page = await browser.newPage();
31787
- page.on("console", (msg) => {
31788
- if (msg.type() === "error") consoleErrors.push(msg.text());
31789
- });
31790
- page.on("pageerror", (err) => pageErrors.push(err.message));
31791
- page.on("requestfailed", (req) => {
31792
- const f = req.failure();
31793
- failedRequests.push(`${req.url()}${f ? ` (${f.errorText})` : ""}`);
31794
- });
31795
- await page.goto(options.url, { waitUntil: "load", timeout });
31796
- for (const action of options.actions ?? []) {
31797
- switch (action.type) {
31798
- case "click":
31799
- await page.click(action.selector, { timeout });
31800
- break;
31801
- case "fill":
31802
- await page.fill(action.selector, action.value, { timeout });
31803
- break;
31804
- case "goto":
31805
- await page.goto(action.url, { waitUntil: "load", timeout });
31806
- break;
31807
- case "wait":
31808
- await page.waitForTimeout(action.ms);
31809
- break;
31810
- default:
31811
- break;
31812
- }
31813
- }
31814
- let selectorFound;
31815
- if (options.waitForSelector) {
31816
- try {
31817
- await page.waitForSelector(options.waitForSelector, { timeout });
31818
- selectorFound = true;
31819
- } catch {
31820
- selectorFound = false;
31821
- }
31822
- }
31823
- let screenshotPath;
31824
- if (options.screenshotPath) {
31825
- try {
31826
- await page.screenshot({ path: options.screenshotPath });
31827
- screenshotPath = options.screenshotPath;
31828
- } catch {
31829
- }
31830
- }
31831
- const title = await page.title().catch(() => void 0);
31832
- return {
31833
- ok: true,
31834
- consoleErrors,
31835
- pageErrors,
31836
- failedRequests,
31837
- url: page.url(),
31838
- ...title !== void 0 ? { title } : {},
31839
- ...selectorFound !== void 0 ? { selectorFound } : {},
31840
- ...screenshotPath ? { screenshotPath } : {}
31841
- };
31842
- } catch (err) {
31843
- return { ...base, error: err instanceof Error ? err.message : String(err) };
31844
- } finally {
31845
- try {
31846
- await browser?.close();
31847
- } catch {
31848
- }
31849
- }
31850
- }
31851
-
31852
- // src/cli/browser/tools.ts
31853
32440
  var ActionSchema = external_exports.discriminatedUnion("type", [
31854
32441
  external_exports.object({ type: external_exports.literal("click"), selector: external_exports.string().min(1) }),
31855
32442
  external_exports.object({ type: external_exports.literal("fill"), selector: external_exports.string().min(1), value: external_exports.string() }),
@@ -33198,6 +33785,8 @@ function handleSlashCommand(text, availableSkills) {
33198
33785
  /council-feedback <memberId> <1-5> [note] \u2014 rate a council member for future ranking (Task I.2)
33199
33786
  /promote-member <memberId> \u2014 promote a council member to a standalone skill (v3-K)
33200
33787
  /update [--yes|-y] \u2014 check for zelari-code updates; --yes performs the update (v3-N)
33788
+ /plugins \u2014 list optional tool plugins (Playwright, eslint, ruff, LSP servers)
33789
+ /plugins install <id> \u2014 install a plugin now (e.g. /plugins install eslint)
33201
33790
  /steer <text> \u2014 enqueue a follow-up prompt on the active run (Task 18.2)
33202
33791
  /steer --interrupt <text> \u2014 cancel current run + enqueue <text> for next dispatch (Task C.3.2)
33203
33792
  /compact \u2014 compact the session transcript
@@ -33540,6 +34129,19 @@ ${formatSkillList(availableSkills)}`
33540
34129
  updateForce: force
33541
34130
  };
33542
34131
  }
34132
+ case "plugins": {
34133
+ if (args.length === 0) {
34134
+ return { handled: true, kind: "plugins_list" };
34135
+ }
34136
+ if (args[0] === "install" && args[1]) {
34137
+ return { handled: true, kind: "plugins_install", pluginId: args[1] };
34138
+ }
34139
+ return {
34140
+ handled: true,
34141
+ kind: "plugins_usage",
34142
+ message: "Usage: /plugins \u2014 list optional tool plugins (Playwright, eslint, ruff, LSP servers)\n /plugins install <id> \u2014 install a plugin now (e.g. /plugins install eslint)"
34143
+ };
34144
+ }
33543
34145
  case "undo": {
33544
34146
  const confirmed = args.includes("--yes") || args.includes("-y");
33545
34147
  if (confirmed) {
@@ -33931,12 +34533,18 @@ async function handleUpdatePerform(ctx) {
33931
34533
  const { performUpdate: performUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
33932
34534
  const res = await performUpdate2();
33933
34535
  if (res.ok) {
34536
+ let prereqBlock = "";
34537
+ try {
34538
+ const { runPrereqChecks: runPrereqChecks2 } = await Promise.resolve().then(() => (init_prereqChecks(), prereqChecks_exports));
34539
+ const { warnings } = runPrereqChecks2({ mode: "preflight" });
34540
+ if (warnings.length > 0) {
34541
+ 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.";
34542
+ }
34543
+ } catch {
34544
+ }
33934
34545
  appendSystem(
33935
34546
  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)`
34547
+ "[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
34548
  );
33941
34549
  } else {
33942
34550
  const output = res.output?.trim() || "(empty)";
@@ -33985,17 +34593,124 @@ ${output}`.toLowerCase();
33985
34593
  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" : "");
33986
34594
  }
33987
34595
 
34596
+ // src/cli/slashHandlers/plugins.ts
34597
+ init_registry2();
34598
+
34599
+ // src/cli/plugins/installer.ts
34600
+ init_cmdline();
34601
+ init_updater();
34602
+ import { spawn as spawn8 } from "node:child_process";
34603
+ async function installPlugin(spec, cwd, executor = spawn8) {
34604
+ const scopeFlag = spec.installScope === "global" ? "-g" : "-D";
34605
+ const args = ["install", scopeFlag, spec.npmPackage];
34606
+ const primary = await runNpm2(executor, args, cwd, "shim");
34607
+ if (primary.ok) return primary;
34608
+ const npmCli = resolveBundledNpmCli();
34609
+ if (npmCli && looksLikeBrokenShim(primary.exitCode, primary.output)) {
34610
+ const fallback = await runNpm2(executor, args, cwd, "bundled", npmCli);
34611
+ return {
34612
+ ...fallback,
34613
+ output: `[plugins] npm shim failed (${primary.error ?? "exit " + primary.exitCode}); retried via bundled npm (${npmCli}).
34614
+ ${fallback.output}`
34615
+ };
34616
+ }
34617
+ return primary;
34618
+ }
34619
+ function runNpm2(executor, args, cwd, mode, npmCliPath) {
34620
+ return new Promise((resolve) => {
34621
+ let stdout = "";
34622
+ let stderr = "";
34623
+ const stdio = ["ignore", "pipe", "pipe"];
34624
+ const child = mode === "bundled" && npmCliPath ? executor(process.execPath, [npmCliPath, ...args], { stdio, cwd }) : process.platform === "win32" ? executor(buildCmdLine("npm", args), { stdio, shell: true, cwd }) : executor("npm", args, { stdio, cwd });
34625
+ child.stdout?.on("data", (chunk) => {
34626
+ stdout += chunk.toString();
34627
+ });
34628
+ child.stderr?.on("data", (chunk) => {
34629
+ stderr += chunk.toString();
34630
+ });
34631
+ child.on("error", (err) => {
34632
+ resolve({
34633
+ ok: false,
34634
+ output: stdout + stderr,
34635
+ error: err.message,
34636
+ exitCode: null
34637
+ });
34638
+ });
34639
+ child.on("close", (code) => {
34640
+ const ok = code === 0;
34641
+ resolve({
34642
+ ok,
34643
+ output: stdout + stderr,
34644
+ exitCode: code,
34645
+ error: ok ? void 0 : `npm exited with code ${code}`
34646
+ });
34647
+ });
34648
+ });
34649
+ }
34650
+
34651
+ // src/cli/slashHandlers/plugins.ts
34652
+ async function handlePluginsList(ctx, cwd) {
34653
+ const lines = ["[plugins] optional tool plugins:"];
34654
+ for (const spec of PLUGINS) {
34655
+ let present = false;
34656
+ try {
34657
+ present = await spec.detect(cwd);
34658
+ } catch {
34659
+ present = false;
34660
+ }
34661
+ const mark = present ? "\u2713" : "\u2717";
34662
+ const state2 = present ? "installed" : "missing";
34663
+ const hint = present ? "" : ` \u2014 run \`/plugins install ${spec.id}\``;
34664
+ lines.push(` ${mark} ${spec.label.padEnd(38)} ${state2}${hint}`);
34665
+ }
34666
+ lines.push("Missing plugins are optional \u2014 features degrade silently without them.");
34667
+ appendSystem(ctx.setMessages, lines.join("\n"));
34668
+ }
34669
+ async function handlePluginsInstall(ctx, cwd, pluginId) {
34670
+ const spec = findPlugin(pluginId);
34671
+ if (!spec) {
34672
+ appendSystem(
34673
+ ctx.setMessages,
34674
+ `[plugins] unknown plugin id: '${pluginId}'. Available: ${PLUGINS.map((p3) => p3.id).join(", ")}`
34675
+ );
34676
+ return;
34677
+ }
34678
+ const scopeFlag = spec.installScope === "global" ? "-g" : "-D";
34679
+ appendSystem(
34680
+ ctx.setMessages,
34681
+ `[plugins] installing ${spec.label} (\`npm i ${scopeFlag} ${spec.npmPackage}\`)\u2026`
34682
+ );
34683
+ let result;
34684
+ try {
34685
+ result = await installPlugin(spec, cwd);
34686
+ } catch {
34687
+ result = { ok: false, output: "", exitCode: null, error: "unexpected error" };
34688
+ }
34689
+ if (result.ok) {
34690
+ const post = spec.postInstallHint ? `
34691
+ \u2192 ${spec.postInstallHint}` : "";
34692
+ appendSystem(ctx.setMessages, `[plugins] \u2713 installed ${spec.label}${post}`);
34693
+ } else {
34694
+ const tail = result.output ? `
34695
+ ${result.output.split("\n").slice(-8).join("\n")}` : "";
34696
+ appendSystem(
34697
+ ctx.setMessages,
34698
+ `[plugins] \u2717 install failed for ${spec.label}: ${result.error ?? "unknown error"}${tail}`
34699
+ );
34700
+ }
34701
+ }
34702
+
33988
34703
  // src/cli/slashHandlers/promoteMember.ts
33989
34704
  import { promises as fs16 } from "node:fs";
33990
- import path28 from "node:path";
33991
- import os9 from "node:os";
34705
+ import path29 from "node:path";
34706
+ import os10 from "node:os";
33992
34707
  async function handlePromoteMember(ctx, memberId) {
33993
34708
  try {
33994
34709
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
33995
34710
  const { skill, markdown } = promoteMember2(memberId);
33996
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path28.join(os9.homedir(), ".tmp", "zelari-code", "skills");
34711
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path29.join(os10.homedir(), ".tmp", "zelari-code", "skills");
33997
34712
  await fs16.mkdir(skillDir, { recursive: true });
33998
- const filePath = path28.join(skillDir, `${skill.id}.md`);
34713
+ const filePath = path29.join(skillDir, `${skill.id}.md`);
33999
34714
  await fs16.writeFile(filePath, markdown, "utf8");
34000
34715
  appendSystem(
34001
34716
  ctx.setMessages,
@@ -34012,33 +34727,33 @@ async function handlePromoteMember(ctx, memberId) {
34012
34727
  }
34013
34728
 
34014
34729
  // 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";
34016
- import path29 from "node:path";
34017
- import os10 from "node:os";
34730
+ import { promises as fs17, existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync16, mkdirSync as mkdirSync11, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
34731
+ import path30 from "node:path";
34732
+ import os11 from "node:os";
34018
34733
  var META_FILENAME = "meta.json";
34019
34734
  var SESSIONS_SUBDIR = "sessions";
34020
34735
  function getBranchesBaseDir() {
34021
- return process.env.ANATHEMA_BRANCHES_DIR ?? path29.join(os10.homedir(), ".tmp", "zelari-code", "branches");
34736
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path30.join(os11.homedir(), ".tmp", "zelari-code", "branches");
34022
34737
  }
34023
34738
  function getSessionsBaseDir() {
34024
- return process.env.ANATHEMA_SESSIONS_DIR ?? path29.join(os10.homedir(), ".tmp", "zelari-code", "sessions");
34739
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path30.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
34025
34740
  }
34026
34741
  function branchPathFor(name, baseDir) {
34027
- return path29.join(baseDir, name);
34742
+ return path30.join(baseDir, name);
34028
34743
  }
34029
34744
  function metaPathFor(name, baseDir) {
34030
- return path29.join(baseDir, name, META_FILENAME);
34745
+ return path30.join(baseDir, name, META_FILENAME);
34031
34746
  }
34032
34747
  function sessionsPathFor(name, baseDir) {
34033
- return path29.join(baseDir, name, SESSIONS_SUBDIR);
34748
+ return path30.join(baseDir, name, SESSIONS_SUBDIR);
34034
34749
  }
34035
34750
  function readBranchMeta(name, baseDir) {
34036
34751
  const metaPath = metaPathFor(name, baseDir);
34037
- if (!existsSync25(metaPath)) {
34752
+ if (!existsSync27(metaPath)) {
34038
34753
  throw new BranchNotFoundError(`Branch "${name}" not found`);
34039
34754
  }
34040
34755
  try {
34041
- const raw = readFileSync23(metaPath, "utf-8");
34756
+ const raw = readFileSync24(metaPath, "utf-8");
34042
34757
  const parsed = JSON.parse(raw);
34043
34758
  if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
34044
34759
  throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
@@ -34055,8 +34770,8 @@ function readBranchMeta(name, baseDir) {
34055
34770
  }
34056
34771
  function writeBranchMeta(name, baseDir, meta3) {
34057
34772
  const metaPath = metaPathFor(name, baseDir);
34058
- mkdirSync10(path29.dirname(metaPath), { recursive: true });
34059
- writeFileSync15(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
34773
+ mkdirSync11(path30.dirname(metaPath), { recursive: true });
34774
+ writeFileSync16(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
34060
34775
  }
34061
34776
  async function countSessions(name, baseDir) {
34062
34777
  const sessionsPath = sessionsPathFor(name, baseDir);
@@ -34094,7 +34809,7 @@ var SessionNotFoundError = class extends Error {
34094
34809
  };
34095
34810
  function branchExists(name, baseDir = getBranchesBaseDir()) {
34096
34811
  const bp = branchPathFor(name, baseDir);
34097
- return existsSync25(bp) && existsSync25(metaPathFor(name, baseDir));
34812
+ return existsSync27(bp) && existsSync27(metaPathFor(name, baseDir));
34098
34813
  }
34099
34814
  async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
34100
34815
  if (!name || name.trim().length === 0) {
@@ -34106,14 +34821,14 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
34106
34821
  if (branchExists(name, baseDir)) {
34107
34822
  throw new BranchAlreadyExistsError(name);
34108
34823
  }
34109
- const sourcePath = path29.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
34110
- if (!existsSync25(sourcePath)) {
34824
+ const sourcePath = path30.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
34825
+ if (!existsSync27(sourcePath)) {
34111
34826
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
34112
34827
  }
34113
34828
  const branchPath = branchPathFor(name, baseDir);
34114
34829
  const branchSessionsPath = sessionsPathFor(name, baseDir);
34115
- mkdirSync10(branchSessionsPath, { recursive: true });
34116
- const destPath = path29.join(branchSessionsPath, `${fromSessionId}.jsonl`);
34830
+ mkdirSync11(branchSessionsPath, { recursive: true });
34831
+ const destPath = path30.join(branchSessionsPath, `${fromSessionId}.jsonl`);
34117
34832
  await fs17.copyFile(sourcePath, destPath);
34118
34833
  const meta3 = {
34119
34834
  name,
@@ -34140,7 +34855,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
34140
34855
  const results = [];
34141
34856
  for (const entry of entries) {
34142
34857
  const metaPath = metaPathFor(entry, baseDir);
34143
- if (!existsSync25(metaPath)) continue;
34858
+ if (!existsSync27(metaPath)) continue;
34144
34859
  try {
34145
34860
  const meta3 = readBranchMeta(entry, baseDir);
34146
34861
  const sessionCount = await countSessions(entry, baseDir);
@@ -34214,14 +34929,14 @@ async function handleBranchCheckout(ctx, branchName) {
34214
34929
 
34215
34930
  // src/cli/slashHandlers/workspace.ts
34216
34931
  import { promises as fs18 } from "node:fs";
34217
- import path30 from "node:path";
34932
+ import path31 from "node:path";
34218
34933
  async function handleWorkspaceShow(ctx, what) {
34219
34934
  try {
34220
- const zelari = path30.join(process.cwd(), ".zelari");
34935
+ const zelari = path31.join(process.cwd(), ".zelari");
34221
34936
  let content;
34222
34937
  switch (what) {
34223
34938
  case "plan": {
34224
- const planPath = path30.join(zelari, "plan.md");
34939
+ const planPath = path31.join(zelari, "plan.md");
34225
34940
  try {
34226
34941
  content = await fs18.readFile(planPath, "utf-8");
34227
34942
  } catch {
@@ -34230,7 +34945,7 @@ async function handleWorkspaceShow(ctx, what) {
34230
34945
  break;
34231
34946
  }
34232
34947
  case "decisions": {
34233
- const decisionsDir = path30.join(zelari, "decisions");
34948
+ const decisionsDir = path31.join(zelari, "decisions");
34234
34949
  try {
34235
34950
  const files = (await fs18.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
34236
34951
  if (files.length === 0) {
@@ -34240,7 +34955,7 @@ async function handleWorkspaceShow(ctx, what) {
34240
34955
  `];
34241
34956
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
34242
34957
  for (const f of files) {
34243
- const raw = await fs18.readFile(path30.join(decisionsDir, f), "utf-8");
34958
+ const raw = await fs18.readFile(path31.join(decisionsDir, f), "utf-8");
34244
34959
  const { meta: meta3, body } = parseFrontmatter2(raw);
34245
34960
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
34246
34961
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -34253,7 +34968,7 @@ async function handleWorkspaceShow(ctx, what) {
34253
34968
  break;
34254
34969
  }
34255
34970
  case "risks": {
34256
- const risksPath = path30.join(zelari, "risks.md");
34971
+ const risksPath = path31.join(zelari, "risks.md");
34257
34972
  try {
34258
34973
  content = await fs18.readFile(risksPath, "utf-8");
34259
34974
  } catch {
@@ -34262,7 +34977,7 @@ async function handleWorkspaceShow(ctx, what) {
34262
34977
  break;
34263
34978
  }
34264
34979
  case "agents": {
34265
- const agentsPath = path30.join(process.cwd(), "AGENTS.MD");
34980
+ const agentsPath = path31.join(process.cwd(), "AGENTS.MD");
34266
34981
  try {
34267
34982
  content = await fs18.readFile(agentsPath, "utf-8");
34268
34983
  } catch {
@@ -34271,7 +34986,7 @@ async function handleWorkspaceShow(ctx, what) {
34271
34986
  break;
34272
34987
  }
34273
34988
  case "docs": {
34274
- const docsDir = path30.join(zelari, "docs");
34989
+ const docsDir = path31.join(zelari, "docs");
34275
34990
  try {
34276
34991
  const files = (await fs18.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
34277
34992
  content = files.length ? `# Docs (${files.length})
@@ -34313,7 +35028,7 @@ async function handleWorkspaceReset(ctx, force) {
34313
35028
  return;
34314
35029
  }
34315
35030
  try {
34316
- const target = path30.join(process.cwd(), ".zelari");
35031
+ const target = path31.join(process.cwd(), ".zelari");
34317
35032
  await fs18.rm(target, { recursive: true, force: true });
34318
35033
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
34319
35034
  } catch (err) {
@@ -34672,11 +35387,11 @@ function handleModelsRefresh(ctx) {
34672
35387
  }
34673
35388
 
34674
35389
  // src/cli/slashHandlers/skills.ts
34675
- import path31 from "node:path";
34676
- import os11 from "node:os";
35390
+ import path32 from "node:path";
35391
+ import os12 from "node:os";
34677
35392
 
34678
35393
  // 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";
35394
+ import { promises as fs19, existsSync as existsSync28, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync12 } from "node:fs";
34680
35395
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
34681
35396
  async function readSkillHistory(file2) {
34682
35397
  let raw = "";
@@ -34774,7 +35489,7 @@ async function applySteerInterrupt(options) {
34774
35489
 
34775
35490
  // src/cli/slashHandlers/skills.ts
34776
35491
  async function handleSkillStats(ctx, skillId) {
34777
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path31.join(os11.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
35492
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path32.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
34778
35493
  try {
34779
35494
  const records = await readSkillHistory(historyFile);
34780
35495
  const stats = getSkillStats(records, skillId);
@@ -34790,7 +35505,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
34790
35505
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
34791
35506
  return;
34792
35507
  }
34793
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path31.join(os11.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
35508
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path32.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
34794
35509
  try {
34795
35510
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
34796
35511
  appendSystem(ctx.setMessages, formatted);
@@ -35053,6 +35768,24 @@ function useSlashDispatch(params) {
35053
35768
  setInput("");
35054
35769
  return;
35055
35770
  }
35771
+ if (result.kind === "plugins_list") {
35772
+ await handlePluginsList({ setMessages }, process.cwd());
35773
+ setInput("");
35774
+ return;
35775
+ }
35776
+ if (result.kind === "plugins_install") {
35777
+ await handlePluginsInstall({ setMessages }, process.cwd(), result.pluginId ?? "");
35778
+ setInput("");
35779
+ return;
35780
+ }
35781
+ if (result.kind === "plugins_usage") {
35782
+ appendSystem(
35783
+ setMessages,
35784
+ result.message ?? "Usage: /plugins | /plugins install <id>"
35785
+ );
35786
+ setInput("");
35787
+ return;
35788
+ }
35056
35789
  if (result.kind === "workspace") {
35057
35790
  appendSystem(
35058
35791
  setMessages,
@@ -35616,13 +36349,131 @@ function SplashGate({
35616
36349
  return /* @__PURE__ */ React10.createElement(React10.Fragment, null, children);
35617
36350
  }
35618
36351
 
36352
+ // src/cli/components/PluginGate.tsx
36353
+ import React11, { useEffect as useEffect9, useState as useState10, useCallback as useCallback6 } from "react";
36354
+ import { Box as Box10, Text as Text11, useStdin as useStdin4 } from "ink";
36355
+ init_registry2();
36356
+ init_prefs();
36357
+ var CHOICE_INSTALL = "__install__";
36358
+ var CHOICE_LATER = "__later__";
36359
+ var CHOICE_NEVER = "__never__";
36360
+ function PluginGate({ cwd, children }) {
36361
+ const { isRawModeSupported } = useStdin4();
36362
+ const [phase, setPhase] = useState10("detecting");
36363
+ const [queue, setQueue] = useState10([]);
36364
+ const [current, setCurrent] = useState10(null);
36365
+ const [result, setResult] = useState10(null);
36366
+ const skipGate = process.env.ZELARI_NO_PLUGIN_PROMPT === "1" || isRawModeSupported !== true;
36367
+ useEffect9(() => {
36368
+ if (skipGate) {
36369
+ setPhase("done");
36370
+ return;
36371
+ }
36372
+ let cancelled = false;
36373
+ void detectMissingPlugins(cwd).then((missing) => {
36374
+ if (cancelled) return;
36375
+ if (missing.length === 0) {
36376
+ setPhase("done");
36377
+ return;
36378
+ }
36379
+ setQueue(missing);
36380
+ setCurrent(missing[0] ?? null);
36381
+ setPhase(missing[0] ? "prompting" : "done");
36382
+ }).catch(() => {
36383
+ if (!cancelled) setPhase("done");
36384
+ });
36385
+ return () => {
36386
+ cancelled = true;
36387
+ };
36388
+ }, [cwd, skipGate]);
36389
+ const advance = useCallback6(() => {
36390
+ setQueue((q) => {
36391
+ const rest = q.slice(1);
36392
+ if (rest.length === 0) {
36393
+ setPhase("done");
36394
+ setCurrent(null);
36395
+ return rest;
36396
+ }
36397
+ setCurrent(rest[0] ?? null);
36398
+ setPhase(rest[0] ? "prompting" : "done");
36399
+ setResult(null);
36400
+ return rest;
36401
+ });
36402
+ }, []);
36403
+ const onSelect = useCallback6(
36404
+ (value) => {
36405
+ if (!current) return;
36406
+ if (value === CHOICE_LATER) {
36407
+ advance();
36408
+ } else if (value === CHOICE_NEVER) {
36409
+ markDontAskAgain(current.id);
36410
+ advance();
36411
+ } else if (value === CHOICE_INSTALL) {
36412
+ setPhase("installing");
36413
+ void installPlugin(current, cwd).then((r) => {
36414
+ setResult(r);
36415
+ setPhase("result");
36416
+ }).catch(() => {
36417
+ setResult({ ok: false, output: "", exitCode: null, error: "unexpected error" });
36418
+ setPhase("result");
36419
+ });
36420
+ }
36421
+ },
36422
+ [current, cwd, advance]
36423
+ );
36424
+ if (phase === "done") {
36425
+ return /* @__PURE__ */ React11.createElement(React11.Fragment, null, children);
36426
+ }
36427
+ if (phase === "detecting") {
36428
+ return /* @__PURE__ */ React11.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "Checking for optional tool plugins\u2026"));
36429
+ }
36430
+ if (!current) {
36431
+ return /* @__PURE__ */ React11.createElement(React11.Fragment, null, children);
36432
+ }
36433
+ if (phase === "installing") {
36434
+ return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, "\u23F3 Installing ", current.label, "\u2026"), /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "npm install ", current.installScope === "global" ? "-g" : "-D", " ", current.npmPackage));
36435
+ }
36436
+ if (phase === "result") {
36437
+ const ok = result?.ok === true;
36438
+ return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, { color: ok ? "green" : "red" }, ok ? "\u2713" : "\u2717", " ", ok ? "Installed" : "Install failed", ": ", current.label), !ok && result?.error ? /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, result.error) : null, ok && current.postInstallHint ? /* @__PURE__ */ React11.createElement(Text11, { color: "yellow" }, " \u2192 ", current.postInstallHint) : null, /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "Press any key to continue\u2026"), /* @__PURE__ */ React11.createElement(ContinueKey, { onContinue: advance }));
36439
+ }
36440
+ const items = [
36441
+ {
36442
+ value: CHOICE_INSTALL,
36443
+ label: `Install now`,
36444
+ hint: `npm i ${current.installScope === "global" ? "-g" : "-D"} ${current.npmPackage}`
36445
+ },
36446
+ { value: CHOICE_LATER, label: "Maybe later" },
36447
+ { value: CHOICE_NEVER, label: "Don't ask again" }
36448
+ ];
36449
+ return /* @__PURE__ */ React11.createElement(
36450
+ SelectList,
36451
+ {
36452
+ title: `Optional plugin missing: ${current.label}`,
36453
+ items,
36454
+ onSelect,
36455
+ onCancel: advance,
36456
+ maxVisible: 4
36457
+ }
36458
+ );
36459
+ }
36460
+ function ContinueKey({ onContinue }) {
36461
+ const { isRawModeSupported } = useStdin4();
36462
+ useEffect9(() => {
36463
+ const t = setTimeout(onContinue, 1500);
36464
+ return () => clearTimeout(t);
36465
+ }, [onContinue]);
36466
+ void isRawModeSupported;
36467
+ return null;
36468
+ }
36469
+
35619
36470
  // src/cli/main.ts
35620
36471
  init_providerConfig();
35621
36472
 
35622
36473
  // src/cli/wizard/firstRun.ts
35623
- import { existsSync as existsSync27 } from "node:fs";
36474
+ import { existsSync as existsSync29 } from "node:fs";
35624
36475
  function shouldRunWizard(input) {
35625
- const exists = input.exists ?? existsSync27;
36476
+ const exists = input.exists ?? existsSync29;
35626
36477
  if (input.hasResetConfigFlag) {
35627
36478
  return { shouldRun: true, reason: "--reset-config flag forced wizard" };
35628
36479
  }
@@ -35657,15 +36508,15 @@ function parseWizardFlags(argv) {
35657
36508
  }
35658
36509
 
35659
36510
  // src/cli/wizard/runWizard.tsx
35660
- import React12, { useEffect as useEffect9, useState as useState10 } from "react";
35661
- import { Box as Box11, Text as Text12 } from "ink";
36511
+ import React13, { useEffect as useEffect10, useState as useState11 } from "react";
36512
+ import { Box as Box12, Text as Text13 } from "ink";
35662
36513
  import { useInput as useInput4 } from "ink";
35663
36514
  init_keyStore();
35664
36515
  init_providerConfig();
35665
36516
 
35666
36517
  // src/cli/wizard/index.tsx
35667
- import React11 from "react";
35668
- import { Box as Box10, Text as Text11 } from "ink";
36518
+ import React12 from "react";
36519
+ import { Box as Box11, Text as Text12 } from "ink";
35669
36520
 
35670
36521
  // src/cli/wizard/useWizardState.ts
35671
36522
  init_providerConfig();
@@ -35781,16 +36632,16 @@ var API_KEY_OPTIONS = ["env", "keystore", "skip"];
35781
36632
 
35782
36633
  // src/cli/wizard/index.tsx
35783
36634
  function Frame({ children }) {
35784
- return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 }, children);
36635
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 }, children);
35785
36636
  }
35786
36637
  function Step(props) {
35787
- return /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Text11, { color: props.active ? "cyan" : "gray", inverse: props.active }, " ", props.index, "/", props.total, " ", props.name, " "));
36638
+ return /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text12, { color: props.active ? "cyan" : "gray", inverse: props.active }, " ", props.index, "/", props.total, " ", props.name, " "));
35788
36639
  }
35789
36640
  function renderProviderList(providers, cursor) {
35790
- return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column" }, providers.map((p3, i) => {
36641
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, providers.map((p3, i) => {
35791
36642
  const arrow = i === cursor ? "\u279C " : " ";
35792
36643
  const color = i === cursor ? "cyan" : void 0;
35793
- return /* @__PURE__ */ React11.createElement(Text11, { key: p3.id, color }, arrow, p3.displayName, " ", /* @__PURE__ */ React11.createElement(Text11, { color: "gray" }, "(", p3.id, ", uses env ", p3.envVar, ")"));
36644
+ return /* @__PURE__ */ React12.createElement(Text12, { key: p3.id, color }, arrow, p3.displayName, " ", /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "(", p3.id, ", uses env ", p3.envVar, ")"));
35794
36645
  }));
35795
36646
  }
35796
36647
  function renderApiKeyOptions(cursor) {
@@ -35799,30 +36650,30 @@ function renderApiKeyOptions(cursor) {
35799
36650
  keystore: "Save to local keyStore (encrypted)",
35800
36651
  skip: "Skip for now (chat will fail until key is set)"
35801
36652
  };
35802
- return /* @__PURE__ */ React11.createElement(Box10, { flexDirection: "column" }, API_KEY_OPTIONS.map((choice, i) => {
36653
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, API_KEY_OPTIONS.map((choice, i) => {
35803
36654
  const arrow = i === cursor ? "\u279C " : " ";
35804
36655
  const color = i === cursor ? "cyan" : void 0;
35805
- return /* @__PURE__ */ React11.createElement(Text11, { key: choice, color }, arrow, labels[choice]);
36656
+ return /* @__PURE__ */ React12.createElement(Text12, { key: choice, color }, arrow, labels[choice]);
35806
36657
  }));
35807
36658
  }
35808
36659
  function Wizard({ state: state2, providers }) {
35809
36660
  const s = state2.state;
35810
36661
  if (s.committed) {
35811
- return /* @__PURE__ */ React11.createElement(Frame, null, /* @__PURE__ */ React11.createElement(Text11, { color: "green" }, "\u2713 Setup complete!"), /* @__PURE__ */ React11.createElement(Text11, null, "Provider: ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, s.providerId), " | ", "Model: ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, s.model), " | ", "API key: ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, s.apiKeyChoice ?? "n/a")), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text11, { color: "gray" }, "Launching zelari-code\u2026 (press any key)")));
36662
+ return /* @__PURE__ */ React12.createElement(Frame, null, /* @__PURE__ */ React12.createElement(Text12, { color: "green" }, "\u2713 Setup complete!"), /* @__PURE__ */ React12.createElement(Text12, null, "Provider: ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, s.providerId), " | ", "Model: ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, s.model), " | ", "API key: ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, s.apiKeyChoice ?? "n/a")), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "Launching zelari-code\u2026 (press any key)")));
35812
36663
  }
35813
- return /* @__PURE__ */ React11.createElement(Frame, null, /* @__PURE__ */ React11.createElement(Text11, { color: "cyan", bold: true }, "zelari-code v", VERSION, " \u2014 first-time setup"), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, marginBottom: 1, flexDirection: "row" }, /* @__PURE__ */ React11.createElement(Step, { index: 1, total: 5, name: "welcome", active: s.step === "welcome" }), /* @__PURE__ */ React11.createElement(Step, { index: 2, total: 5, name: "provider", active: s.step === "provider" }), /* @__PURE__ */ React11.createElement(Step, { index: 3, total: 5, name: "model", active: s.step === "model" }), /* @__PURE__ */ React11.createElement(Step, { index: 4, total: 5, name: "apikey", active: s.step === "apikey" }), /* @__PURE__ */ React11.createElement(Step, { index: 5, total: 5, name: "confirm", active: s.step === "confirm" })), s.step === "welcome" && /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Text11, null, "Welcome! Let's get you coding in under two minutes."), /* @__PURE__ */ React11.createElement(Text11, { color: "gray" }, "We'll pick a provider, default model, and how to handle your API key."), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text11, null, "Press "), /* @__PURE__ */ React11.createElement(Text11, { color: "cyan", inverse: true }, " Enter "), /* @__PURE__ */ React11.createElement(Text11, null, " to continue, or "), /* @__PURE__ */ React11.createElement(Text11, { color: "red", inverse: true }, " Q "), /* @__PURE__ */ React11.createElement(Text11, null, " to quit (re-run with "), /* @__PURE__ */ React11.createElement(Text11, { color: "gray" }, "--no-wizard"), /* @__PURE__ */ React11.createElement(Text11, null, " to skip later)."))), s.step === "provider" && /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Text11, null, "Choose your LLM provider:"), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, renderProviderList(providers, s.providerCursor)), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text11, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "model" && /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Text11, null, "Model for ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, s.providerId), ":"), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, null, s.model ?? "(empty)")), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text11, null, "Press ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan", inverse: true }, " Enter "), "to accept, or type a new name. ", /* @__PURE__ */ React11.createElement(Text11, { color: "gray" }, "(default kept on empty input)")))), s.step === "apikey" && /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Text11, null, "How should we handle the API key?"), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, renderApiKeyOptions(s.apiKeyCursor)), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text11, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "confirm" && /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(Text11, null, "Confirm your setup:"), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React11.createElement(Text11, null, "Provider: ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, s.providerId)), /* @__PURE__ */ React11.createElement(Text11, null, "Model: ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, s.model)), /* @__PURE__ */ React11.createElement(Text11, null, "API key: ", /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, s.apiKeyChoice ?? "(unset)"))), /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text11, null, "Press ", /* @__PURE__ */ React11.createElement(Text11, { color: "green", inverse: true }, " Enter "), "to save and launch, or ", /* @__PURE__ */ React11.createElement(Text11, { color: "yellow", inverse: true }, " B "), "to go back."))));
36664
+ return /* @__PURE__ */ React12.createElement(Frame, null, /* @__PURE__ */ React12.createElement(Text12, { color: "cyan", bold: true }, "zelari-code v", VERSION, " \u2014 first-time setup"), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1, marginBottom: 1, flexDirection: "row" }, /* @__PURE__ */ React12.createElement(Step, { index: 1, total: 5, name: "welcome", active: s.step === "welcome" }), /* @__PURE__ */ React12.createElement(Step, { index: 2, total: 5, name: "provider", active: s.step === "provider" }), /* @__PURE__ */ React12.createElement(Step, { index: 3, total: 5, name: "model", active: s.step === "model" }), /* @__PURE__ */ React12.createElement(Step, { index: 4, total: 5, name: "apikey", active: s.step === "apikey" }), /* @__PURE__ */ React12.createElement(Step, { index: 5, total: 5, name: "confirm", active: s.step === "confirm" })), s.step === "welcome" && /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(Text12, null, "Welcome! Let's get you coding in under two minutes."), /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "We'll pick a provider, default model, and how to handle your API key."), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, "Press "), /* @__PURE__ */ React12.createElement(Text12, { color: "cyan", inverse: true }, " Enter "), /* @__PURE__ */ React12.createElement(Text12, null, " to continue, or "), /* @__PURE__ */ React12.createElement(Text12, { color: "red", inverse: true }, " Q "), /* @__PURE__ */ React12.createElement(Text12, null, " to quit (re-run with "), /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "--no-wizard"), /* @__PURE__ */ React12.createElement(Text12, null, " to skip later)."))), s.step === "provider" && /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(Text12, null, "Choose your LLM provider:"), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, renderProviderList(providers, s.providerCursor)), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "model" && /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(Text12, null, "Model for ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, s.providerId), ":"), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, s.model ?? "(empty)")), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, "Press ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan", inverse: true }, " Enter "), "to accept, or type a new name. ", /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "(default kept on empty input)")))), s.step === "apikey" && /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(Text12, null, "How should we handle the API key?"), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, renderApiKeyOptions(s.apiKeyCursor)), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "confirm" && /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(Text12, null, "Confirm your setup:"), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, "Provider: ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, s.providerId)), /* @__PURE__ */ React12.createElement(Text12, null, "Model: ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, s.model)), /* @__PURE__ */ React12.createElement(Text12, null, "API key: ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, s.apiKeyChoice ?? "(unset)"))), /* @__PURE__ */ React12.createElement(Box11, { marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, "Press ", /* @__PURE__ */ React12.createElement(Text12, { color: "green", inverse: true }, " Enter "), "to save and launch, or ", /* @__PURE__ */ React12.createElement(Text12, { color: "yellow", inverse: true }, " B "), "to go back."))));
35814
36665
  }
35815
36666
 
35816
36667
  // src/cli/wizard/runWizard.tsx
35817
36668
  function RunWizard(_props) {
35818
- const [wiz] = useState10(
36669
+ const [wiz] = useState11(
35819
36670
  () => createWizardState({
35820
36671
  providers: PROVIDERS,
35821
36672
  defaultModelFor: (id) => getModelForProvider(id)
35822
36673
  })
35823
36674
  );
35824
- const [, force] = useState10(0);
35825
- useEffect9(() => {
36675
+ const [, force] = useState11(0);
36676
+ useEffect10(() => {
35826
36677
  if (typeof wiz.subscribe === "function") {
35827
36678
  const sub = wiz.subscribe(() => force((n) => n + 1));
35828
36679
  return () => sub();
@@ -35886,20 +36737,20 @@ function RunWizard(_props) {
35886
36737
  }
35887
36738
  });
35888
36739
  if (wiz.state.committed) {
35889
- return /* @__PURE__ */ React12.createElement(PostCommitBridge, null);
36740
+ return /* @__PURE__ */ React13.createElement(PostCommitBridge, null);
35890
36741
  }
35891
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React12.createElement(Wizard, { state: wiz, providers: PROVIDERS }));
36742
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React13.createElement(Wizard, { state: wiz, providers: PROVIDERS }));
35892
36743
  }
35893
36744
  function PostCommitBridge() {
35894
- const [showApp, setShowApp] = useState10(false);
35895
- useEffect9(() => {
36745
+ const [showApp, setShowApp] = useState11(false);
36746
+ useEffect10(() => {
35896
36747
  const t = setTimeout(() => setShowApp(true), 1200);
35897
36748
  return () => clearTimeout(t);
35898
36749
  }, []);
35899
36750
  if (showApp) {
35900
- return /* @__PURE__ */ React12.createElement(App, null);
36751
+ return /* @__PURE__ */ React13.createElement(App, null);
35901
36752
  }
35902
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React12.createElement(Box11, { borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { color: "green", bold: true }, "\u2713 Setup complete! Launching zelari-code\u2026"), /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "Press Ctrl+C any time to exit.")));
36753
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React13.createElement(Box12, { borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, flexDirection: "column" }, /* @__PURE__ */ React13.createElement(Text13, { color: "green", bold: true }, "\u2713 Setup complete! Launching zelari-code\u2026"), /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "Press Ctrl+C any time to exit.")));
35903
36754
  }
35904
36755
 
35905
36756
  // src/cli/headless.ts
@@ -36112,7 +36963,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
36112
36963
 
36113
36964
  // src/cli/skillsMd.ts
36114
36965
  init_skills2();
36115
- import { existsSync as existsSync28, readdirSync as readdirSync4, readFileSync as readFileSync24 } from "node:fs";
36966
+ import { existsSync as existsSync30, readdirSync as readdirSync4, readFileSync as readFileSync25 } from "node:fs";
36116
36967
  import { join as join23 } from "node:path";
36117
36968
  import { homedir as homedir6 } from "node:os";
36118
36969
  var CODING_CATEGORIES = /* @__PURE__ */ new Set([
@@ -36190,7 +37041,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36190
37041
  const summary = { loaded: [], skipped: [] };
36191
37042
  const seen = new Set(options.existingIds ?? []);
36192
37043
  for (const dir of skillMdSearchDirs(projectRoot)) {
36193
- if (!existsSync28(dir)) continue;
37044
+ if (!existsSync30(dir)) continue;
36194
37045
  let entries;
36195
37046
  try {
36196
37047
  entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -36199,9 +37050,9 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36199
37050
  }
36200
37051
  for (const entry of entries) {
36201
37052
  const skillPath = join23(dir, entry, "SKILL.md");
36202
- if (!existsSync28(skillPath)) continue;
37053
+ if (!existsSync30(skillPath)) continue;
36203
37054
  try {
36204
- const parsed = parseSkillMd(readFileSync24(skillPath, "utf8"), skillPath);
37055
+ const parsed = parseSkillMd(readFileSync25(skillPath, "utf8"), skillPath);
36205
37056
  if (!parsed) {
36206
37057
  summary.skipped.push({ path: skillPath, reason: "missing/invalid frontmatter (name, description) or empty body" });
36207
37058
  continue;
@@ -36225,6 +37076,38 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
36225
37076
  init_skills2();
36226
37077
  init_updater();
36227
37078
  var VERSION = getCurrentVersion();
37079
+ function runPreflight() {
37080
+ if (process.env.ZELARI_SKIP_PREFLIGHT === "1") return;
37081
+ if (process.argv.includes("--skip-checks")) return;
37082
+ if (process.env.ANATHEMA_DEV === "1") return;
37083
+ const { runPrereqChecks: runPrereqChecks2 } = (
37084
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
37085
+ (init_prereqChecks(), __toCommonJS(prereqChecks_exports))
37086
+ );
37087
+ const { results, hasCriticalFail, warnings } = runPrereqChecks2({
37088
+ mode: "preflight"
37089
+ });
37090
+ for (const w of warnings) {
37091
+ console.error(`\x1B[33m[zelari-code] \u26A0 ${w.tool}: ${w.message}\x1B[0m`);
37092
+ }
37093
+ if (hasCriticalFail) {
37094
+ const critical = results.find(
37095
+ (r) => !r.ok && r.severity === "critical"
37096
+ );
37097
+ console.error("");
37098
+ console.error(
37099
+ "\x1B[31m==============================================================\n zelari-code cannot start: a critical prerequisite is missing.\n==============================================================\x1B[0m"
37100
+ );
37101
+ if (critical) {
37102
+ console.error(`
37103
+ ${critical.tool}: ${critical.message}`);
37104
+ }
37105
+ console.error(
37106
+ "\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)."
37107
+ );
37108
+ process.exit(1);
37109
+ }
37110
+ }
36228
37111
  async function backgroundUpdateCheck() {
36229
37112
  if (process.env.ANATHEMA_DEV === "1") return;
36230
37113
  await new Promise((resolve) => setTimeout(resolve, 3e3));
@@ -36259,12 +37142,34 @@ function pickRootComponent() {
36259
37142
  }
36260
37143
  if (argv.includes("--doctor") || argv.includes("doctor")) {
36261
37144
  const { runDoctor: runDoctor2 } = (init_doctor(), __toCommonJS(doctor_exports));
36262
- const healthy = runDoctor2();
36263
- process.exit(healthy ? 0 : 1);
37145
+ void runDoctor2().then((healthy) => process.exit(healthy ? 0 : 1));
37146
+ return { kind: "done" };
37147
+ }
37148
+ if (argv.includes("--fix-path") || argv.includes("fix-path")) {
37149
+ const { repairWindowsUserPath: repairWindowsUserPath2 } = (init_fixPath(), __toCommonJS(fixPath_exports));
37150
+ const result = repairWindowsUserPath2();
37151
+ const green = "\x1B[32m";
37152
+ const red = "\x1B[31m";
37153
+ const dim = "\x1B[2m";
37154
+ const reset = "\x1B[0m";
37155
+ if (result.ok) {
37156
+ if (result.alreadyOk) {
37157
+ console.log(`${green}\u2714${reset} npm prefix already on user PATH: ${result.prefix}`);
37158
+ } else {
37159
+ console.log(`${green}\u2714${reset} added npm prefix to user PATH: ${result.prefix}`);
37160
+ console.log(`${dim}open a NEW terminal for the change to take effect, then run: zelari-code --version${reset}`);
37161
+ }
37162
+ process.exit(0);
37163
+ }
37164
+ console.error(`${red}\u2717${reset} ${result.error}`);
37165
+ if (result.prefix) {
37166
+ console.error(`${dim}prefix: ${result.prefix}${reset}`);
37167
+ }
37168
+ process.exit(1);
36264
37169
  }
36265
37170
  if (argv.includes("--help") || argv.includes("-h")) {
36266
37171
  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"
37172
+ "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 --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\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 ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
36268
37173
  );
36269
37174
  process.exit(0);
36270
37175
  }
@@ -36285,14 +37190,17 @@ function pickRootComponent() {
36285
37190
  });
36286
37191
  if (decision.shouldRun) {
36287
37192
  console.error(`[zelari-code] starting wizard: ${decision.reason}`);
36288
- return { kind: "wizard", element: React13.createElement(RunWizard) };
37193
+ return { kind: "wizard", element: React14.createElement(RunWizard) };
36289
37194
  }
36290
37195
  return {
36291
37196
  kind: "app",
36292
- element: React13.createElement(
37197
+ element: React14.createElement(
36293
37198
  SplashGate,
36294
37199
  { version: VERSION },
36295
- React13.createElement(App)
37200
+ React14.createElement(PluginGate, {
37201
+ cwd: process.cwd(),
37202
+ children: React14.createElement(App)
37203
+ })
36296
37204
  )
36297
37205
  };
36298
37206
  }
@@ -36314,6 +37222,7 @@ function loadUserSkills() {
36314
37222
  function main() {
36315
37223
  const picked = pickRootComponent();
36316
37224
  if (picked.kind === "done") return;
37225
+ runPreflight();
36317
37226
  loadUserSkills();
36318
37227
  if (picked.kind === "headless") {
36319
37228
  void runHeadless(picked.headlessOpts).then((code) => {