zelari-code 1.23.0 → 1.24.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.
@@ -21,6 +21,87 @@ var __copyProps = (to, from, except, desc) => {
21
21
  };
22
22
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
23
 
24
+ // src/cli/modelPricing.ts
25
+ function envOverride(model) {
26
+ const key = `ANATHEMA_PRICE_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
27
+ const raw = process.env[key] ?? process.env.ANATHEMA_PRICE_DEFAULT;
28
+ return parseRateOverride(raw);
29
+ }
30
+ function parseRateOverride(raw) {
31
+ if (!raw) return null;
32
+ const [inStr, outStr] = raw.split("/");
33
+ const input = Number.parseFloat(inStr);
34
+ const output = outStr !== void 0 ? Number.parseFloat(outStr) : input;
35
+ if (!Number.isFinite(input) || !Number.isFinite(output)) return null;
36
+ return { input, output };
37
+ }
38
+ function getModelRate(model) {
39
+ if (!model) return DEFAULT_RATE;
40
+ return envOverride(model) ?? PRICES_PER_MILLION[model] ?? DEFAULT_RATE;
41
+ }
42
+ function calculateCost(model, promptTokens, completionTokens, cachedPromptTokens = 0) {
43
+ if (!Number.isFinite(promptTokens) || promptTokens < 0) return 0;
44
+ if (!Number.isFinite(completionTokens) || completionTokens < 0) return 0;
45
+ const rate = getModelRate(model);
46
+ const cached2 = Number.isFinite(cachedPromptTokens) && cachedPromptTokens > 0 ? Math.min(cachedPromptTokens, promptTokens) : 0;
47
+ const uncachedPrompt = promptTokens - cached2;
48
+ const cachedRate = rate.cachedInput ?? rate.input * DEFAULT_CACHE_DISCOUNT;
49
+ const inputCost = uncachedPrompt / 1e6 * rate.input + cached2 / 1e6 * cachedRate;
50
+ const outputCost = completionTokens / 1e6 * rate.output;
51
+ return Number((inputCost + outputCost).toFixed(6));
52
+ }
53
+ function formatCost(usd) {
54
+ if (!Number.isFinite(usd) || usd < 0) return "$0.0000";
55
+ if (usd === 0) return "$0.0000";
56
+ if (usd < 1e-4) return "<$0.0001";
57
+ return `$${usd.toFixed(4)}`;
58
+ }
59
+ function formatTokens(tokens) {
60
+ if (!Number.isFinite(tokens) || tokens < 0) return "0";
61
+ if (tokens < 1e3) return `${tokens}`;
62
+ if (tokens < 1e6) return `${(tokens / 1e3).toFixed(1)}k`;
63
+ if (tokens < 1e9) return `${(tokens / 1e6).toFixed(2)}M`;
64
+ return `${(tokens / 1e9).toFixed(2)}B`;
65
+ }
66
+ var PRICES_PER_MILLION, DEFAULT_RATE, DEFAULT_CACHE_DISCOUNT;
67
+ var init_modelPricing = __esm({
68
+ "src/cli/modelPricing.ts"() {
69
+ "use strict";
70
+ PRICES_PER_MILLION = {
71
+ // xAI Grok (list prices; grok-4.5 default reasoning_effort is "high")
72
+ "grok-4.5": { input: 2, output: 6, cachedInput: 0.5 },
73
+ "grok-4.3": { input: 1.25, output: 2.5, cachedInput: 0.2 },
74
+ "grok-4": { input: 3, output: 15 },
75
+ "grok-4-fast": { input: 0.2, output: 0.5 },
76
+ "grok-3": { input: 3, output: 15 },
77
+ "grok-3-mini": { input: 0.3, output: 0.5 },
78
+ "grok-2-vision": { input: 2, output: 10 },
79
+ // GLM / Z.AI
80
+ "glm-4.6": { input: 0.6, output: 2.2 },
81
+ "glm-4.5": { input: 0.5, output: 2 },
82
+ "glm-4.5-air": { input: 0.1, output: 0.6 },
83
+ "glm-z1": { input: 0.5, output: 2 },
84
+ // MiniMax
85
+ "MiniMax-M2.5": { input: 0.2, output: 1.1 },
86
+ "MiniMax-M2": { input: 0.2, output: 1.1 },
87
+ "MiniMax-M2-her": { input: 0.3, output: 1.2 },
88
+ // DeepSeek (global platform) — estimated list prices; override via
89
+ // ANATHEMA_PRICE_DEEPSEEK_V4_FLASH / ANATHEMA_PRICE_DEEPSEEK_V4_PRO.
90
+ // DeepSeek prompt-cache hits are ~10× cheaper than a cache miss.
91
+ "deepseek-v4-flash": { input: 0.14, output: 0.28, cachedInput: 0.014 },
92
+ "deepseek-v4-pro": { input: 0.55, output: 2.19, cachedInput: 0.055 },
93
+ // OpenAI (for openai-compatible fallback)
94
+ "gpt-4o": { input: 2.5, output: 10 },
95
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
96
+ "gpt-4-turbo": { input: 10, output: 30 },
97
+ "o1-preview": { input: 15, output: 60 },
98
+ "o1-mini": { input: 3, output: 12 }
99
+ };
100
+ DEFAULT_RATE = { input: 1, output: 3 };
101
+ DEFAULT_CACHE_DISCOUNT = 0.25;
102
+ }
103
+ });
104
+
24
105
  // src/cli/grokOAuth.ts
25
106
  var grokOAuth_exports = {};
26
107
  __export(grokOAuth_exports, {
@@ -1543,10 +1624,10 @@ function mergeDefs(...defs) {
1543
1624
  function cloneDef(schema) {
1544
1625
  return mergeDefs(schema._zod.def);
1545
1626
  }
1546
- function getElementAtPath(obj, path38) {
1547
- if (!path38)
1627
+ function getElementAtPath(obj, path40) {
1628
+ if (!path40)
1548
1629
  return obj;
1549
- return path38.reduce((acc, key) => acc?.[key], obj);
1630
+ return path40.reduce((acc, key) => acc?.[key], obj);
1550
1631
  }
1551
1632
  function promiseAllObject(promisesObj) {
1552
1633
  const keys = Object.keys(promisesObj);
@@ -1874,11 +1955,11 @@ function explicitlyAborted(x, startIndex = 0) {
1874
1955
  }
1875
1956
  return false;
1876
1957
  }
1877
- function prefixIssues(path38, issues) {
1958
+ function prefixIssues(path40, issues) {
1878
1959
  return issues.map((iss) => {
1879
1960
  var _a3;
1880
1961
  (_a3 = iss).path ?? (_a3.path = []);
1881
- iss.path.unshift(path38);
1962
+ iss.path.unshift(path40);
1882
1963
  return iss;
1883
1964
  });
1884
1965
  }
@@ -2096,16 +2177,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2096
2177
  }
2097
2178
  function formatError(error51, mapper = (issue2) => issue2.message) {
2098
2179
  const fieldErrors = { _errors: [] };
2099
- const processError = (error52, path38 = []) => {
2180
+ const processError = (error52, path40 = []) => {
2100
2181
  for (const issue2 of error52.issues) {
2101
2182
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2102
- issue2.errors.map((issues) => processError({ issues }, [...path38, ...issue2.path]));
2183
+ issue2.errors.map((issues) => processError({ issues }, [...path40, ...issue2.path]));
2103
2184
  } else if (issue2.code === "invalid_key") {
2104
- processError({ issues: issue2.issues }, [...path38, ...issue2.path]);
2185
+ processError({ issues: issue2.issues }, [...path40, ...issue2.path]);
2105
2186
  } else if (issue2.code === "invalid_element") {
2106
- processError({ issues: issue2.issues }, [...path38, ...issue2.path]);
2187
+ processError({ issues: issue2.issues }, [...path40, ...issue2.path]);
2107
2188
  } else {
2108
- const fullpath = [...path38, ...issue2.path];
2189
+ const fullpath = [...path40, ...issue2.path];
2109
2190
  if (fullpath.length === 0) {
2110
2191
  fieldErrors._errors.push(mapper(issue2));
2111
2192
  } else {
@@ -2132,17 +2213,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2132
2213
  }
2133
2214
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2134
2215
  const result = { errors: [] };
2135
- const processError = (error52, path38 = []) => {
2216
+ const processError = (error52, path40 = []) => {
2136
2217
  var _a3, _b;
2137
2218
  for (const issue2 of error52.issues) {
2138
2219
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2139
- issue2.errors.map((issues) => processError({ issues }, [...path38, ...issue2.path]));
2220
+ issue2.errors.map((issues) => processError({ issues }, [...path40, ...issue2.path]));
2140
2221
  } else if (issue2.code === "invalid_key") {
2141
- processError({ issues: issue2.issues }, [...path38, ...issue2.path]);
2222
+ processError({ issues: issue2.issues }, [...path40, ...issue2.path]);
2142
2223
  } else if (issue2.code === "invalid_element") {
2143
- processError({ issues: issue2.issues }, [...path38, ...issue2.path]);
2224
+ processError({ issues: issue2.issues }, [...path40, ...issue2.path]);
2144
2225
  } else {
2145
- const fullpath = [...path38, ...issue2.path];
2226
+ const fullpath = [...path40, ...issue2.path];
2146
2227
  if (fullpath.length === 0) {
2147
2228
  result.errors.push(mapper(issue2));
2148
2229
  continue;
@@ -2174,8 +2255,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2174
2255
  }
2175
2256
  function toDotPath(_path) {
2176
2257
  const segs = [];
2177
- const path38 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2178
- for (const seg of path38) {
2258
+ const path40 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2259
+ for (const seg of path40) {
2179
2260
  if (typeof seg === "number")
2180
2261
  segs.push(`[${seg}]`);
2181
2262
  else if (typeof seg === "symbol")
@@ -15678,13 +15759,13 @@ function resolveRef(ref, ctx) {
15678
15759
  if (!ref.startsWith("#")) {
15679
15760
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15680
15761
  }
15681
- const path38 = ref.slice(1).split("/").filter(Boolean);
15682
- if (path38.length === 0) {
15762
+ const path40 = ref.slice(1).split("/").filter(Boolean);
15763
+ if (path40.length === 0) {
15683
15764
  return ctx.rootSchema;
15684
15765
  }
15685
15766
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15686
- if (path38[0] === defsKey) {
15687
- const key = path38[1];
15767
+ if (path40[0] === defsKey) {
15768
+ const key = path40[1];
15688
15769
  if (!key || !ctx.defs[key]) {
15689
15770
  throw new Error(`Reference not found: ${ref}`);
15690
15771
  }
@@ -17848,11 +17929,11 @@ var init_tools = __esm({
17848
17929
  if (!ctx.addDocument)
17849
17930
  return "Knowledge vault tool not available.";
17850
17931
  const title = args["title"] || "New Document";
17851
- const path38 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17932
+ const path40 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
17852
17933
  const content = args["content"] || "";
17853
17934
  const tags = args["tags"] || [];
17854
17935
  ctx.addDocument({
17855
- path: path38,
17936
+ path: path40,
17856
17937
  title,
17857
17938
  content,
17858
17939
  format: "markdown",
@@ -17861,7 +17942,7 @@ var init_tools = __esm({
17861
17942
  workspaceId: ctx.workspaceId
17862
17943
  });
17863
17944
  ctx.addActivity("vault", "created document", title);
17864
- return `Document "${title}" created at "${path38}".`;
17945
+ return `Document "${title}" created at "${path40}".`;
17865
17946
  }
17866
17947
  }
17867
17948
  ];
@@ -21059,9 +21140,9 @@ function spillToolOutput(fullText, meta3) {
21059
21140
  const rnd = randomBytes(3).toString("hex");
21060
21141
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
21061
21142
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
21062
- const path38 = join(dir, file2);
21063
- writeFileSync5(path38, fullText, "utf8");
21064
- return path38;
21143
+ const path40 = join(dir, file2);
21144
+ writeFileSync5(path40, fullText, "utf8");
21145
+ return path40;
21065
21146
  } catch {
21066
21147
  return null;
21067
21148
  }
@@ -21107,10 +21188,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
21107
21188
  ${tail}`;
21108
21189
  }
21109
21190
  if (doSpill) {
21110
- const path38 = spillToolOutput(text, { toolName: opts.toolName });
21111
- if (path38) {
21191
+ const path40 = spillToolOutput(text, { toolName: opts.toolName });
21192
+ if (path40) {
21112
21193
  const spillNote = `
21113
- \u2026 [full output spilled to: ${path38} \u2014 re-read with read_file if you need the complete text] \u2026`;
21194
+ \u2026 [full output spilled to: ${path40} \u2014 re-read with read_file if you need the complete text] \u2026`;
21114
21195
  if (preview.includes("] \u2026\n")) {
21115
21196
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
21116
21197
  `);
@@ -23542,21 +23623,21 @@ function normalizeAuth(auth) {
23542
23623
  return "agent";
23543
23624
  }
23544
23625
  function readSecrets() {
23545
- const path38 = getSshSecretsPath();
23546
- if (!existsSync11(path38)) return {};
23626
+ const path40 = getSshSecretsPath();
23627
+ if (!existsSync11(path40)) return {};
23547
23628
  try {
23548
- return JSON.parse(readFileSync8(path38, "utf8"));
23629
+ return JSON.parse(readFileSync8(path40, "utf8"));
23549
23630
  } catch {
23550
23631
  return {};
23551
23632
  }
23552
23633
  }
23553
23634
  function writeSecrets(data) {
23554
- const path38 = getSshSecretsPath();
23555
- mkdirSync7(dirname2(path38), { recursive: true });
23556
- writeFileSync6(path38, `${JSON.stringify(data, null, 2)}
23635
+ const path40 = getSshSecretsPath();
23636
+ mkdirSync7(dirname2(path40), { recursive: true });
23637
+ writeFileSync6(path40, `${JSON.stringify(data, null, 2)}
23557
23638
  `, "utf8");
23558
23639
  try {
23559
- chmodSync(path38, 384);
23640
+ chmodSync(path40, 384);
23560
23641
  } catch {
23561
23642
  }
23562
23643
  }
@@ -23585,10 +23666,10 @@ function deleteSshPassword(id) {
23585
23666
  writeSecrets({ passwords });
23586
23667
  }
23587
23668
  function readStore2() {
23588
- const path38 = getSshTargetsPath();
23589
- if (!existsSync11(path38)) return [];
23669
+ const path40 = getSshTargetsPath();
23670
+ if (!existsSync11(path40)) return [];
23590
23671
  try {
23591
- const parsed = JSON.parse(readFileSync8(path38, "utf8"));
23672
+ const parsed = JSON.parse(readFileSync8(path40, "utf8"));
23592
23673
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
23593
23674
  return list.filter(
23594
23675
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -23603,11 +23684,11 @@ function readStore2() {
23603
23684
  }
23604
23685
  }
23605
23686
  function writeStore2(targets) {
23606
- const path38 = getSshTargetsPath();
23607
- mkdirSync7(dirname2(path38), { recursive: true });
23687
+ const path40 = getSshTargetsPath();
23688
+ mkdirSync7(dirname2(path40), { recursive: true });
23608
23689
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
23609
23690
  writeFileSync6(
23610
- path38,
23691
+ path40,
23611
23692
  `${JSON.stringify({ targets: clean }, null, 2)}
23612
23693
  `,
23613
23694
  "utf8"
@@ -23853,11 +23934,11 @@ function formatSshTargetsForPrompt() {
23853
23934
  ];
23854
23935
  for (const t of targets) {
23855
23936
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
23856
- const path38 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
23937
+ const path40 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
23857
23938
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
23858
23939
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
23859
23940
  lines.push(
23860
- `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path38}${tags}${allow}`
23941
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path40}${tags}${allow}`
23861
23942
  );
23862
23943
  }
23863
23944
  return lines.join("\n");
@@ -25857,11 +25938,11 @@ var init_synthesisAudit = __esm({
25857
25938
  import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
25858
25939
  import { join as join6 } from "node:path";
25859
25940
  function loadNfrSpec(zelariRoot) {
25860
- const path38 = join6(zelariRoot, "nfr-spec.json");
25861
- if (!existsSync13(path38))
25941
+ const path40 = join6(zelariRoot, "nfr-spec.json");
25942
+ if (!existsSync13(path40))
25862
25943
  return null;
25863
25944
  try {
25864
- const raw = JSON.parse(readFileSync10(path38, "utf8"));
25945
+ const raw = JSON.parse(readFileSync10(path40, "utf8"));
25865
25946
  if (raw.version !== 1 || !Array.isArray(raw.targets))
25866
25947
  return null;
25867
25948
  return raw;
@@ -28167,9 +28248,9 @@ var init_types3 = __esm({
28167
28248
  import { readFileSync as readFileSync15 } from "node:fs";
28168
28249
  import { join as join12 } from "node:path";
28169
28250
  function readLessonsDeduped(zelariRoot) {
28170
- const path38 = join12(zelariRoot, LESSONS_FILE);
28251
+ const path40 = join12(zelariRoot, LESSONS_FILE);
28171
28252
  try {
28172
- const raw = readFileSync15(path38, "utf8");
28253
+ const raw = readFileSync15(path40, "utf8");
28173
28254
  const byId = /* @__PURE__ */ new Map();
28174
28255
  for (const line of raw.split(/\r?\n/)) {
28175
28256
  if (!line.trim())
@@ -28270,8 +28351,8 @@ function keywordsFrom(check2, signature) {
28270
28351
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
28271
28352
  }
28272
28353
  function writeLesson(zelariRoot, lesson) {
28273
- const path38 = join13(zelariRoot, LESSONS_FILE);
28274
- appendFileSync2(path38, `${JSON.stringify(lesson)}
28354
+ const path40 = join13(zelariRoot, LESSONS_FILE);
28355
+ appendFileSync2(path40, `${JSON.stringify(lesson)}
28275
28356
  `, "utf8");
28276
28357
  }
28277
28358
  function findSimilar(lessons, signature) {
@@ -30428,28 +30509,28 @@ var init_storage = __esm({
30428
30509
  VALID_SCALARS = /^(true|false|null|~)$/i;
30429
30510
  Storage = class {
30430
30511
  /** Read a Markdown file with frontmatter. Throws if not found. */
30431
- read(path38) {
30432
- if (!existsSync23(path38)) {
30433
- throw new Error(`File not found: ${path38}`);
30512
+ read(path40) {
30513
+ if (!existsSync23(path40)) {
30514
+ throw new Error(`File not found: ${path40}`);
30434
30515
  }
30435
- const md = readFileSync20(path38, "utf8");
30516
+ const md = readFileSync20(path40, "utf8");
30436
30517
  return parseFrontmatter(md);
30437
30518
  }
30438
30519
  /** Read a Markdown file; returns null if not found. */
30439
- readIfExists(path38) {
30440
- if (!existsSync23(path38)) return null;
30441
- return this.read(path38);
30520
+ readIfExists(path40) {
30521
+ if (!existsSync23(path40)) return null;
30522
+ return this.read(path40);
30442
30523
  }
30443
30524
  /**
30444
30525
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
30445
30526
  * The meta object is serialized as YAML frontmatter; body as Markdown.
30446
30527
  */
30447
- write(path38, meta3, body) {
30448
- mkdirSync9(dirname4(path38), { recursive: true });
30449
- const tmp = path38 + ".tmp-" + process.pid;
30528
+ write(path40, meta3, body) {
30529
+ mkdirSync9(dirname4(path40), { recursive: true });
30530
+ const tmp = path40 + ".tmp-" + process.pid;
30450
30531
  const md = serializeFrontmatter(meta3, body);
30451
30532
  writeFileSync13(tmp, md, "utf8");
30452
- renameSync2(tmp, path38);
30533
+ renameSync2(tmp, path40);
30453
30534
  }
30454
30535
  /** List all .md files in a directory (non-recursive). */
30455
30536
  listMarkdown(dir) {
@@ -30527,8 +30608,8 @@ function readPlan(ctx) {
30527
30608
  } catch {
30528
30609
  }
30529
30610
  }
30530
- const path38 = workspaceFile(ctx.rootDir, "plan");
30531
- const doc = ctx.storage.readIfExists(path38);
30611
+ const path40 = workspaceFile(ctx.rootDir, "plan");
30612
+ const doc = ctx.storage.readIfExists(path40);
30532
30613
  if (!doc) return { phases: [], tasks: [], milestones: [] };
30533
30614
  const meta3 = doc.meta;
30534
30615
  return {
@@ -30693,7 +30774,7 @@ function addMilestoneRecord(ctx, summary, input) {
30693
30774
  dueDate: input.dueDate,
30694
30775
  targetVersion: version2
30695
30776
  });
30696
- const path38 = join23(ctx.rootDir, "milestones", `${id}.md`);
30777
+ const path40 = join23(ctx.rootDir, "milestones", `${id}.md`);
30697
30778
  const meta3 = {
30698
30779
  kind: "milestone",
30699
30780
  id,
@@ -30710,7 +30791,7 @@ function addMilestoneRecord(ctx, summary, input) {
30710
30791
  `Target version: ${version2}`,
30711
30792
  ""
30712
30793
  ].join("\n");
30713
- ctx.storage.write(path38, meta3, body);
30794
+ ctx.storage.write(path40, meta3, body);
30714
30795
  return { id, created: true };
30715
30796
  }
30716
30797
  function readPlanSummary(ctx) {
@@ -30914,7 +30995,7 @@ function addIdeaStub(ctx) {
30914
30995
  const tags = args["tags"] ?? [];
30915
30996
  const category = args["category"] ?? "General";
30916
30997
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
30917
- const path38 = workspaceArtifact(ctx.rootDir, "decisions", id);
30998
+ const path40 = workspaceArtifact(ctx.rootDir, "decisions", id);
30918
30999
  const meta3 = {
30919
31000
  kind: "adr",
30920
31001
  status: "proposed",
@@ -30940,7 +31021,7 @@ function addIdeaStub(ctx) {
30940
31021
  ...consequences.map((c) => `- ${c}`),
30941
31022
  ""
30942
31023
  ].join("\n");
30943
- ctx.storage.write(path38, meta3, body);
31024
+ ctx.storage.write(path40, meta3, body);
30944
31025
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
30945
31026
  });
30946
31027
  }
@@ -31022,14 +31103,14 @@ function createDocumentStub(ctx) {
31022
31103
  ctx.storage.write(risksPath, riskMeta, content);
31023
31104
  return `Document "${title}" created at risks.md (workspace root).`;
31024
31105
  }
31025
- const path38 = workspaceArtifact(ctx.rootDir, "docs", slug);
31106
+ const path40 = workspaceArtifact(ctx.rootDir, "docs", slug);
31026
31107
  const meta3 = {
31027
31108
  kind: "doc",
31028
31109
  id: slug,
31029
31110
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
31030
31111
  tags
31031
31112
  };
31032
- ctx.storage.write(path38, meta3, content);
31113
+ ctx.storage.write(path40, meta3, content);
31033
31114
  return `Document "${title}" created at docs/${slug}.md.`;
31034
31115
  });
31035
31116
  }
@@ -31573,10 +31654,10 @@ function getUserMcpPath() {
31573
31654
  function getProjectMcpPath(projectRoot) {
31574
31655
  return join24(projectRoot, ".zelari", "mcp.json");
31575
31656
  }
31576
- function readFile2(path38) {
31577
- if (!existsSync26(path38)) return {};
31657
+ function readFile2(path40) {
31658
+ if (!existsSync26(path40)) return {};
31578
31659
  try {
31579
- const parsed = JSON.parse(readFileSync22(path38, "utf8"));
31660
+ const parsed = JSON.parse(readFileSync22(path40, "utf8"));
31580
31661
  const out = {};
31581
31662
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
31582
31663
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -31592,10 +31673,10 @@ function readFile2(path38) {
31592
31673
  return {};
31593
31674
  }
31594
31675
  }
31595
- function writeFile(path38, servers) {
31596
- mkdirSync11(dirname6(path38), { recursive: true });
31676
+ function writeFile(path40, servers) {
31677
+ mkdirSync11(dirname6(path40), { recursive: true });
31597
31678
  const body = { mcpServers: servers };
31598
- writeFileSync15(path38, `${JSON.stringify(body, null, 2)}
31679
+ writeFileSync15(path40, `${JSON.stringify(body, null, 2)}
31599
31680
  `, "utf8");
31600
31681
  }
31601
31682
  function listMcpServers(projectRoot) {
@@ -31628,9 +31709,9 @@ function upsertMcpServer(opts) {
31628
31709
  if (!opts.config.command?.trim()) {
31629
31710
  return { ok: false, error: "command is required" };
31630
31711
  }
31631
- let path38;
31712
+ let path40;
31632
31713
  if (opts.scope === "user") {
31633
- path38 = getUserMcpPath();
31714
+ path40 = getUserMcpPath();
31634
31715
  } else {
31635
31716
  const root = opts.projectRoot?.trim();
31636
31717
  if (!root) {
@@ -31639,30 +31720,30 @@ function upsertMcpServer(opts) {
31639
31720
  error: "projectRoot required for project scope (Open Folder first)"
31640
31721
  };
31641
31722
  }
31642
- path38 = getProjectMcpPath(root);
31723
+ path40 = getProjectMcpPath(root);
31643
31724
  }
31644
- const current = readFile2(path38);
31725
+ const current = readFile2(path40);
31645
31726
  current[name] = {
31646
31727
  command: opts.config.command.trim(),
31647
31728
  args: opts.config.args,
31648
31729
  env: opts.config.env,
31649
31730
  enabled: opts.config.enabled !== false
31650
31731
  };
31651
- writeFile(path38, current);
31652
- return { ok: true, path: path38 };
31732
+ writeFile(path40, current);
31733
+ return { ok: true, path: path40 };
31653
31734
  }
31654
31735
  function removeMcpServer(opts) {
31655
- const path38 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
31656
- if (!path38) {
31736
+ const path40 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
31737
+ if (!path40) {
31657
31738
  return { ok: false, error: "projectRoot required for project scope" };
31658
31739
  }
31659
- const current = readFile2(path38);
31740
+ const current = readFile2(path40);
31660
31741
  if (!(opts.name in current)) {
31661
- return { ok: false, error: `Server "${opts.name}" not found in ${path38}` };
31742
+ return { ok: false, error: `Server "${opts.name}" not found in ${path40}` };
31662
31743
  }
31663
31744
  delete current[opts.name];
31664
- writeFile(path38, current);
31665
- return { ok: true, path: path38 };
31745
+ writeFile(path40, current);
31746
+ return { ok: true, path: path40 };
31666
31747
  }
31667
31748
  var init_mcpConfigIo = __esm({
31668
31749
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -31999,10 +32080,10 @@ import { createHash as createHash5 } from "node:crypto";
31999
32080
  import { join as join26 } from "node:path";
32000
32081
  import { readFile as readFile3 } from "node:fs/promises";
32001
32082
  async function readPackageJson2(projectRoot) {
32002
- const path38 = join26(projectRoot, "package.json");
32003
- if (!existsSync28(path38)) return null;
32083
+ const path40 = join26(projectRoot, "package.json");
32084
+ if (!existsSync28(path40)) return null;
32004
32085
  try {
32005
- return JSON.parse(await readFile3(path38, "utf8"));
32086
+ return JSON.parse(await readFile3(path40, "utf8"));
32006
32087
  } catch {
32007
32088
  return null;
32008
32089
  }
@@ -32084,9 +32165,9 @@ async function genBuild(ctx) {
32084
32165
  ].join("\n");
32085
32166
  }
32086
32167
  async function genOpenQuestions(ctx) {
32087
- const path38 = join26(ctx.rootDir, "risks.md");
32088
- if (!existsSync28(path38)) return "_No open questions._";
32089
- const content = readFileSync24(path38, "utf8");
32168
+ const path40 = join26(ctx.rootDir, "risks.md");
32169
+ if (!existsSync28(path40)) return "_No open questions._";
32170
+ const content = readFileSync24(path40, "utf8");
32090
32171
  const lines = content.split("\n");
32091
32172
  const questions = [];
32092
32173
  let currentTitle = "";
@@ -32653,8 +32734,8 @@ async function runPostCouncilHook(ctx, options) {
32653
32734
  sources: scope.sources
32654
32735
  } : void 0
32655
32736
  });
32656
- const path38 = writeCouncilCompletion(ctx.rootDir, completion);
32657
- completionHook = { ran: true, path: path38, completion };
32737
+ const path40 = writeCouncilCompletion(ctx.rootDir, completion);
32738
+ completionHook = { ran: true, path: path40, completion };
32658
32739
  } catch (err) {
32659
32740
  completionHook = {
32660
32741
  ran: true,
@@ -33211,18 +33292,49 @@ var init_fileBackend = __esm({
33211
33292
  }
33212
33293
  });
33213
33294
 
33295
+ // src/cli/traceStore.ts
33296
+ import { promises as fs17 } from "node:fs";
33297
+ import * as path29 from "node:path";
33298
+ function traceDir(projectRoot) {
33299
+ return path29.join(projectRoot, ".zelari", "trace");
33300
+ }
33301
+ function tracePath(projectRoot, missionId) {
33302
+ return path29.join(traceDir(projectRoot), `${missionId}.json`);
33303
+ }
33304
+ async function saveTrace(projectRoot, missionId, entries) {
33305
+ const dir = traceDir(projectRoot);
33306
+ await fs17.mkdir(dir, { recursive: true });
33307
+ const payload = {
33308
+ missionId,
33309
+ ts: Date.now(),
33310
+ entries
33311
+ };
33312
+ await fs17.writeFile(
33313
+ tracePath(projectRoot, missionId),
33314
+ JSON.stringify(payload, null, 2) + "\n",
33315
+ "utf8"
33316
+ );
33317
+ }
33318
+ var init_traceStore = __esm({
33319
+ "src/cli/traceStore.ts"() {
33320
+ "use strict";
33321
+ }
33322
+ });
33323
+
33214
33324
  // src/cli/zelariMission.ts
33215
33325
  var zelariMission_exports = {};
33216
33326
  __export(zelariMission_exports, {
33217
33327
  formatBriefForChat: () => formatBriefForChat,
33218
33328
  isMissionAutoStart: () => isMissionAutoStart,
33329
+ resolveMaxCost: () => resolveMaxCost,
33219
33330
  resolveMaxIterations: () => resolveMaxIterations,
33220
33331
  resolveMaxStall: () => resolveMaxStall,
33332
+ resolveMaxTokens: () => resolveMaxTokens,
33221
33333
  runZelariMission: () => runZelariMission
33222
33334
  });
33223
33335
  import { randomUUID as randomUUID5 } from "node:crypto";
33224
- import { promises as fs17 } from "node:fs";
33225
- import * as path29 from "node:path";
33336
+ import { promises as fs18 } from "node:fs";
33337
+ import * as path30 from "node:path";
33226
33338
  function resolveMaxIterations(env = process.env) {
33227
33339
  const raw = env.ZELARI_MISSION_MAX_ITER;
33228
33340
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -33234,17 +33346,35 @@ function resolveMaxStall(env = process.env) {
33234
33346
  const n = Number.parseInt(raw, 10);
33235
33347
  return Number.isFinite(n) && n >= 0 ? n : DEFAULT_MAX_STALL;
33236
33348
  }
33349
+ function resolveMaxCost(env = process.env) {
33350
+ const raw = env.ZELARI_MISSION_MAX_COST;
33351
+ if (!raw) return void 0;
33352
+ const n = Number.parseFloat(raw);
33353
+ return Number.isFinite(n) && n > 0 ? n : void 0;
33354
+ }
33355
+ function resolveMaxTokens(env = process.env) {
33356
+ const raw = env.ZELARI_MISSION_MAX_TOKENS;
33357
+ if (!raw) return void 0;
33358
+ const n = Number.parseInt(raw, 10);
33359
+ return Number.isFinite(n) && n > 0 ? n : void 0;
33360
+ }
33237
33361
  function isMissionAutoStart(env = process.env) {
33238
33362
  return env.ZELARI_MISSION_AUTO === "1";
33239
33363
  }
33240
33364
  async function writeMissionState(projectRoot, state2) {
33241
- const dir = path29.join(projectRoot, ".zelari");
33242
- await fs17.mkdir(dir, { recursive: true });
33243
- await fs17.writeFile(
33244
- path29.join(dir, "mission-state.json"),
33365
+ const dir = path30.join(projectRoot, ".zelari");
33366
+ await fs18.mkdir(dir, { recursive: true });
33367
+ await fs18.writeFile(
33368
+ path30.join(dir, "mission-state.json"),
33245
33369
  JSON.stringify(state2, null, 2) + "\n",
33246
33370
  "utf8"
33247
33371
  );
33372
+ if (state2.trace?.length) {
33373
+ try {
33374
+ await saveTrace(projectRoot, state2.missionId, state2.trace);
33375
+ } catch {
33376
+ }
33377
+ }
33248
33378
  }
33249
33379
  function buildSlicePrompt(brief, userMessage, runMode, iteration) {
33250
33380
  if (runMode === "design-phase") {
@@ -33282,6 +33412,8 @@ async function runZelariMission(userMessage, brief, deps) {
33282
33412
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
33283
33413
  const maxIter = deps.maxIterations ?? resolveMaxIterations(deps.env);
33284
33414
  const maxStall = resolveMaxStall(deps.env);
33415
+ const maxCost = resolveMaxCost(deps.env);
33416
+ const maxTokens = resolveMaxTokens(deps.env);
33285
33417
  const missionId = deps.missionId ?? `m_${randomUUID5().slice(0, 8)}`;
33286
33418
  const startedAt = now().toISOString();
33287
33419
  const state2 = {
@@ -33317,6 +33449,8 @@ async function runZelariMission(userMessage, brief, deps) {
33317
33449
  let step = 0;
33318
33450
  let implStep = 0;
33319
33451
  let pendingDesign = designFirst;
33452
+ let cumulativeCostUsd = 0;
33453
+ let cumulativeTokens = 0;
33320
33454
  while (true) {
33321
33455
  const runMode = pendingDesign ? "design-phase" : "implementation";
33322
33456
  if (runMode === "implementation") {
@@ -33333,6 +33467,8 @@ async function runZelariMission(userMessage, brief, deps) {
33333
33467
  const promptIter = runMode === "implementation" ? implStep : 1;
33334
33468
  const slicePrompt = buildSlicePrompt(brief, userMessage, runMode, promptIter);
33335
33469
  const implementerRetry = runMode === "implementation" && implStep > 1;
33470
+ const sliceStartedAt = now().toISOString();
33471
+ const sliceStartMs = now().getTime();
33336
33472
  if (runMode === "design-phase") {
33337
33473
  deps.emit(
33338
33474
  `[zelari] design-phase (fuori budget) \xB7 step ${step} \xB7 slice ${brief.sliceMvp.id}`
@@ -33369,6 +33505,8 @@ async function runZelariMission(userMessage, brief, deps) {
33369
33505
  );
33370
33506
  return state2;
33371
33507
  }
33508
+ if (typeof result.costUsd === "number") cumulativeCostUsd += result.costUsd;
33509
+ if (typeof result.costTokens === "number") cumulativeTokens += result.costTokens;
33372
33510
  await deps.memory.add(
33373
33511
  JSON.stringify({
33374
33512
  iteration: step,
@@ -33394,6 +33532,20 @@ async function runZelariMission(userMessage, brief, deps) {
33394
33532
  }
33395
33533
  state2.lastCompletionOk = completionOk;
33396
33534
  state2.updatedAt = now().toISOString();
33535
+ state2.cumulativeCostUsd = cumulativeCostUsd;
33536
+ state2.cumulativeTokens = cumulativeTokens;
33537
+ if (!state2.trace) state2.trace = [];
33538
+ state2.trace.push({
33539
+ sliceId: brief.sliceMvp.id,
33540
+ iteration: step,
33541
+ runMode,
33542
+ completionOk,
33543
+ degraded: result.degraded,
33544
+ costTokens: typeof result.costTokens === "number" ? result.costTokens : void 0,
33545
+ costUsd: typeof result.costUsd === "number" ? result.costUsd : void 0,
33546
+ startedAt: sliceStartedAt,
33547
+ durationMs: now().getTime() - sliceStartMs
33548
+ });
33397
33549
  if (runMode === "design-phase") {
33398
33550
  pendingDesign = false;
33399
33551
  await writeMissionState(deps.projectRoot, state2);
@@ -33457,6 +33609,16 @@ async function runZelariMission(userMessage, brief, deps) {
33457
33609
  return state2;
33458
33610
  }
33459
33611
  }
33612
+ if (maxCost !== void 0 && cumulativeCostUsd >= maxCost || maxTokens !== void 0 && cumulativeTokens >= maxTokens) {
33613
+ state2.status = "stopped";
33614
+ state2.updatedAt = now().toISOString();
33615
+ await writeMissionState(deps.projectRoot, state2);
33616
+ const reason = maxCost !== void 0 && cumulativeCostUsd >= maxCost ? `budget USD ${formatCost(cumulativeCostUsd)} \u2265 ${formatCost(maxCost)}` : `token ${formatTokens(cumulativeTokens)} \u2265 ${formatTokens(maxTokens)}`;
33617
+ deps.emit(
33618
+ `[zelari] fermata: ${reason}. Imposta ZELARI_MISSION_MAX_COST / ZELARI_MISSION_MAX_TOKENS pi\xF9 alto, o usa un modello pi\xF9 economico. Stato salvato in .zelari/mission-state.json`
33619
+ );
33620
+ return state2;
33621
+ }
33460
33622
  await writeMissionState(deps.projectRoot, state2);
33461
33623
  }
33462
33624
  state2.status = "stopped";
@@ -33475,6 +33637,8 @@ var init_zelariMission = __esm({
33475
33637
  init_checkpointManager();
33476
33638
  init_fileStateStore();
33477
33639
  init_commitHelpers();
33640
+ init_modelPricing();
33641
+ init_traceStore();
33478
33642
  DEFAULT_MAX_ITER = 6;
33479
33643
  DEFAULT_MAX_STALL = 2;
33480
33644
  }
@@ -33632,6 +33796,8 @@ async function runAgentMissionSlice(deps) {
33632
33796
  });
33633
33797
  let text = "";
33634
33798
  let errored2 = false;
33799
+ let promptTokens = 0;
33800
+ let completionTokens = 0;
33635
33801
  try {
33636
33802
  for await (const event of harness.run()) {
33637
33803
  counter.onEvent(event);
@@ -33642,6 +33808,11 @@ async function runAgentMissionSlice(deps) {
33642
33808
  if (event.type === "agent_end" && event.reason === "error") {
33643
33809
  errored2 = true;
33644
33810
  }
33811
+ if (event.type === "message_end" && event.usage) {
33812
+ const u = event.usage;
33813
+ promptTokens += u.promptTokens ?? 0;
33814
+ completionTokens += u.completionTokens ?? 0;
33815
+ }
33645
33816
  if (event.type === "error" && event.severity === "fatal") {
33646
33817
  errored2 = true;
33647
33818
  }
@@ -33664,7 +33835,9 @@ async function runAgentMissionSlice(deps) {
33664
33835
  successfulWrites: counter.state.successfulWrites,
33665
33836
  emittedWrites: counter.state.emittedWrites,
33666
33837
  errored: errored2,
33667
- messages: harness.getMessages()
33838
+ messages: harness.getMessages(),
33839
+ promptTokens,
33840
+ completionTokens
33668
33841
  };
33669
33842
  }
33670
33843
  const initial = [
@@ -33676,6 +33849,8 @@ async function runAgentMissionSlice(deps) {
33676
33849
  let totalEmitted = pass.emittedWrites;
33677
33850
  let synthesisText = pass.text;
33678
33851
  let errored = pass.errored;
33852
+ let totalPromptTokens = pass.promptTokens;
33853
+ let totalCompletionTokens = pass.completionTokens;
33679
33854
  if (writeRetry && totalWrites === 0 && !errored) {
33680
33855
  deps.emit?.(
33681
33856
  "[zelari] build@agent: 0 write \u2014 forcing implementation retry"
@@ -33695,6 +33870,8 @@ async function runAgentMissionSlice(deps) {
33695
33870
  ${retry.text}` : retry.text;
33696
33871
  }
33697
33872
  errored = errored || retry.errored;
33873
+ totalPromptTokens += retry.promptTokens;
33874
+ totalCompletionTokens += retry.completionTokens;
33698
33875
  }
33699
33876
  const cleaned = cleanAgentContent(synthesisText, {
33700
33877
  stripQuestion: false,
@@ -33741,12 +33918,16 @@ ${retry.text}` : retry.text;
33741
33918
  else degraded = true;
33742
33919
  }
33743
33920
  void totalEmitted;
33921
+ const costTokens = totalPromptTokens + totalCompletionTokens;
33922
+ const costUsd = calculateCost(deps.model, totalPromptTokens, totalCompletionTokens);
33744
33923
  return {
33745
33924
  completionOk,
33746
33925
  ran: true,
33747
33926
  synthesisText: cleaned || void 0,
33748
33927
  writeCount: totalWrites,
33749
- degraded
33928
+ degraded,
33929
+ costTokens,
33930
+ costUsd
33750
33931
  };
33751
33932
  }
33752
33933
  var MUTATING, AGENT_MISSION_IMPLEMENTER_PREAMBLE;
@@ -33759,6 +33940,7 @@ var init_missionSlice = __esm({
33759
33940
  init_dist();
33760
33941
  init_buildPolicy();
33761
33942
  init_envNumber();
33943
+ init_modelPricing();
33762
33944
  MUTATING = /* @__PURE__ */ new Set(["write_file", "edit_file", "apply_diff"]);
33763
33945
  AGENT_MISSION_IMPLEMENTER_PREAMBLE = "You are the sole implementer for this Zelari mission slice. A multi-agent council may already have produced a plan under `.zelari/` \u2014 treat it as a SPEC to apply on disk. You MUST create or modify real project files with write_file / edit_file. Prose without successful writes is a failed slice.";
33764
33946
  }
@@ -34148,10 +34330,10 @@ var init_prereqChecks = __esm({
34148
34330
 
34149
34331
  // src/cli/plugins/prefs.ts
34150
34332
  import { existsSync as existsSync33, readFileSync as readFileSync28, writeFileSync as writeFileSync18, mkdirSync as mkdirSync13 } from "node:fs";
34151
- import path31 from "node:path";
34333
+ import path32 from "node:path";
34152
34334
  import os9 from "node:os";
34153
34335
  function getPluginPrefsPath() {
34154
- return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path31.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
34336
+ return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path32.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
34155
34337
  }
34156
34338
  function getPluginPrefs() {
34157
34339
  const file2 = getPluginPrefsPath();
@@ -34172,7 +34354,7 @@ function getPluginPrefs() {
34172
34354
  }
34173
34355
  function writePluginPrefs(prefs) {
34174
34356
  const file2 = getPluginPrefsPath();
34175
- mkdirSync13(path31.dirname(file2), { recursive: true });
34357
+ mkdirSync13(path32.dirname(file2), { recursive: true });
34176
34358
  writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
34177
34359
  encoding: "utf-8",
34178
34360
  mode: 384
@@ -34209,7 +34391,7 @@ __export(registry_exports, {
34209
34391
  isBinaryOnPath: () => isBinaryOnPath
34210
34392
  });
34211
34393
  import { existsSync as existsSync34 } from "node:fs";
34212
- import path32 from "node:path";
34394
+ import path33 from "node:path";
34213
34395
  function detectLocalBin(bin) {
34214
34396
  return (cwd) => {
34215
34397
  try {
@@ -34227,7 +34409,7 @@ function isBinaryOnPath(bin, opts = {}) {
34227
34409
  const platform = opts.platform ?? process.platform;
34228
34410
  const exists = opts.exists ?? existsSync34;
34229
34411
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
34230
- const pathMod = platform === "win32" ? path32.win32 : path32.posix;
34412
+ const pathMod = platform === "win32" ? path33.win32 : path33.posix;
34231
34413
  const sep = platform === "win32" ? ";" : ":";
34232
34414
  const dirs = pathEnv.split(sep).filter((d) => d.length > 0);
34233
34415
  const candidates = [bin];
@@ -34357,6 +34539,59 @@ var init_registry2 = __esm({
34357
34539
  }
34358
34540
  });
34359
34541
 
34542
+ // src/cli/triggerLock.ts
34543
+ var triggerLock_exports = {};
34544
+ __export(triggerLock_exports, {
34545
+ acquireLock: () => acquireLock,
34546
+ lockPath: () => lockPath,
34547
+ releaseLock: () => releaseLock
34548
+ });
34549
+ import { promises as fs23 } from "node:fs";
34550
+ import * as path38 from "node:path";
34551
+ function lockPath(projectRoot) {
34552
+ return path38.join(projectRoot, ".zelari", "trigger.lock");
34553
+ }
34554
+ function isPidAlive(pid) {
34555
+ try {
34556
+ process.kill(pid, 0);
34557
+ return true;
34558
+ } catch (err) {
34559
+ const code = err.code;
34560
+ return code === "EPERM";
34561
+ }
34562
+ }
34563
+ async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
34564
+ const lp = lockPath(projectRoot);
34565
+ const dir = path38.dirname(lp);
34566
+ await fs23.mkdir(dir, { recursive: true });
34567
+ try {
34568
+ const raw = await fs23.readFile(lp, "utf8");
34569
+ const existing = JSON.parse(raw);
34570
+ if (existing.pid && isPidAlive(existing.pid)) {
34571
+ return { acquired: false, heldBy: existing.pid, lockPath: lp };
34572
+ }
34573
+ } catch {
34574
+ }
34575
+ const payload = {
34576
+ pid: process.pid,
34577
+ acquiredAt: now().toISOString()
34578
+ };
34579
+ await fs23.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
34580
+ return { acquired: true, lockPath: lp };
34581
+ }
34582
+ async function releaseLock(projectRoot) {
34583
+ const lp = lockPath(projectRoot);
34584
+ try {
34585
+ await fs23.unlink(lp);
34586
+ } catch {
34587
+ }
34588
+ }
34589
+ var init_triggerLock = __esm({
34590
+ "src/cli/triggerLock.ts"() {
34591
+ "use strict";
34592
+ }
34593
+ });
34594
+
34360
34595
  // src/cli/utils/doctor.ts
34361
34596
  var doctor_exports = {};
34362
34597
  __export(doctor_exports, {
@@ -34366,11 +34601,11 @@ import { execSync as execSync2 } from "node:child_process";
34366
34601
  import { existsSync as existsSync38, readFileSync as readFileSync31, readlinkSync, statSync as statSync6 } from "node:fs";
34367
34602
  import { createRequire as createRequire3 } from "node:module";
34368
34603
  import { fileURLToPath as fileURLToPath2 } from "node:url";
34369
- import path37 from "node:path";
34604
+ import path39 from "node:path";
34370
34605
  function findPackageRoot(start) {
34371
34606
  let dir = start;
34372
34607
  for (let i = 0; i < 6; i += 1) {
34373
- const candidate = path37.join(dir, "package.json");
34608
+ const candidate = path39.join(dir, "package.json");
34374
34609
  if (existsSync38(candidate)) {
34375
34610
  try {
34376
34611
  const pkg = JSON.parse(readFileSync31(candidate, "utf8"));
@@ -34378,11 +34613,11 @@ function findPackageRoot(start) {
34378
34613
  } catch {
34379
34614
  }
34380
34615
  }
34381
- const parent = path37.dirname(dir);
34616
+ const parent = path39.dirname(dir);
34382
34617
  if (parent === dir) break;
34383
34618
  dir = parent;
34384
34619
  }
34385
- return path37.resolve(__dirname3, "..", "..", "..");
34620
+ return path39.resolve(__dirname3, "..", "..", "..");
34386
34621
  }
34387
34622
  function tryExec(cmd) {
34388
34623
  try {
@@ -34396,7 +34631,7 @@ function tryExec(cmd) {
34396
34631
  }
34397
34632
  function readPackageJson3() {
34398
34633
  try {
34399
- const pkgPath = path37.join(packageRoot, "package.json");
34634
+ const pkgPath = path39.join(packageRoot, "package.json");
34400
34635
  return JSON.parse(readFileSync31(pkgPath, "utf8"));
34401
34636
  } catch {
34402
34637
  return null;
@@ -34412,7 +34647,7 @@ function checkShim(pkgName) {
34412
34647
  }
34413
34648
  const isWin = process.platform === "win32";
34414
34649
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
34415
- const shimPath = path37.join(prefix, shimName);
34650
+ const shimPath = path39.join(prefix, shimName);
34416
34651
  if (!existsSync38(shimPath)) {
34417
34652
  return FAIL(
34418
34653
  `shim not found at ${shimPath}
@@ -34440,8 +34675,8 @@ function checkShim(pkgName) {
34440
34675
  fix: npm install -g ${pkgName}@latest --force`
34441
34676
  );
34442
34677
  }
34443
- const resolved = path37.resolve(path37.dirname(shimPath), target);
34444
- const expected = path37.join(
34678
+ const resolved = path39.resolve(path39.dirname(shimPath), target);
34679
+ const expected = path39.join(
34445
34680
  prefix,
34446
34681
  "node_modules",
34447
34682
  pkgName,
@@ -34480,7 +34715,7 @@ function checkNode(pkg) {
34480
34715
  return OK(`node ${raw}`);
34481
34716
  }
34482
34717
  function checkBundle() {
34483
- const bundle = path37.join(packageRoot, "dist", "cli", "main.bundled.js");
34718
+ const bundle = path39.join(packageRoot, "dist", "cli", "main.bundled.js");
34484
34719
  if (!existsSync38(bundle)) {
34485
34720
  return FAIL(
34486
34721
  `dist/cli/main.bundled.js missing at ${bundle}
@@ -34501,7 +34736,7 @@ function checkRuntimeDeps() {
34501
34736
  const missing = [];
34502
34737
  for (const dep of required2) {
34503
34738
  try {
34504
- const localReq = createRequire3(path37.join(packageRoot, "package.json"));
34739
+ const localReq = createRequire3(path39.join(packageRoot, "package.json"));
34505
34740
  localReq.resolve(dep);
34506
34741
  } catch {
34507
34742
  missing.push(dep);
@@ -34677,7 +34912,7 @@ var init_doctor = __esm({
34677
34912
  "use strict";
34678
34913
  init_prereqChecks();
34679
34914
  require3 = createRequire3(import.meta.url);
34680
- __dirname3 = path37.dirname(fileURLToPath2(import.meta.url));
34915
+ __dirname3 = path39.dirname(fileURLToPath2(import.meta.url));
34681
34916
  packageRoot = findPackageRoot(__dirname3);
34682
34917
  OK = (message) => ({
34683
34918
  ok: true,
@@ -35285,83 +35520,7 @@ function StreamingTail(m) {
35285
35520
  // src/cli/components/StatusBar.tsx
35286
35521
  import React6 from "react";
35287
35522
  import { Box as Box5, Text as Text6 } from "ink";
35288
-
35289
- // src/cli/modelPricing.ts
35290
- var PRICES_PER_MILLION = {
35291
- // xAI Grok (list prices; grok-4.5 default reasoning_effort is "high")
35292
- "grok-4.5": { input: 2, output: 6, cachedInput: 0.5 },
35293
- "grok-4.3": { input: 1.25, output: 2.5, cachedInput: 0.2 },
35294
- "grok-4": { input: 3, output: 15 },
35295
- "grok-4-fast": { input: 0.2, output: 0.5 },
35296
- "grok-3": { input: 3, output: 15 },
35297
- "grok-3-mini": { input: 0.3, output: 0.5 },
35298
- "grok-2-vision": { input: 2, output: 10 },
35299
- // GLM / Z.AI
35300
- "glm-4.6": { input: 0.6, output: 2.2 },
35301
- "glm-4.5": { input: 0.5, output: 2 },
35302
- "glm-4.5-air": { input: 0.1, output: 0.6 },
35303
- "glm-z1": { input: 0.5, output: 2 },
35304
- // MiniMax
35305
- "MiniMax-M2.5": { input: 0.2, output: 1.1 },
35306
- "MiniMax-M2": { input: 0.2, output: 1.1 },
35307
- "MiniMax-M2-her": { input: 0.3, output: 1.2 },
35308
- // DeepSeek (global platform) — estimated list prices; override via
35309
- // ANATHEMA_PRICE_DEEPSEEK_V4_FLASH / ANATHEMA_PRICE_DEEPSEEK_V4_PRO.
35310
- // DeepSeek prompt-cache hits are ~10× cheaper than a cache miss.
35311
- "deepseek-v4-flash": { input: 0.14, output: 0.28, cachedInput: 0.014 },
35312
- "deepseek-v4-pro": { input: 0.55, output: 2.19, cachedInput: 0.055 },
35313
- // OpenAI (for openai-compatible fallback)
35314
- "gpt-4o": { input: 2.5, output: 10 },
35315
- "gpt-4o-mini": { input: 0.15, output: 0.6 },
35316
- "gpt-4-turbo": { input: 10, output: 30 },
35317
- "o1-preview": { input: 15, output: 60 },
35318
- "o1-mini": { input: 3, output: 12 }
35319
- };
35320
- var DEFAULT_RATE = { input: 1, output: 3 };
35321
- var DEFAULT_CACHE_DISCOUNT = 0.25;
35322
- function envOverride(model) {
35323
- const key = `ANATHEMA_PRICE_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
35324
- const raw = process.env[key] ?? process.env.ANATHEMA_PRICE_DEFAULT;
35325
- return parseRateOverride(raw);
35326
- }
35327
- function parseRateOverride(raw) {
35328
- if (!raw) return null;
35329
- const [inStr, outStr] = raw.split("/");
35330
- const input = Number.parseFloat(inStr);
35331
- const output = outStr !== void 0 ? Number.parseFloat(outStr) : input;
35332
- if (!Number.isFinite(input) || !Number.isFinite(output)) return null;
35333
- return { input, output };
35334
- }
35335
- function getModelRate(model) {
35336
- if (!model) return DEFAULT_RATE;
35337
- return envOverride(model) ?? PRICES_PER_MILLION[model] ?? DEFAULT_RATE;
35338
- }
35339
- function calculateCost(model, promptTokens, completionTokens, cachedPromptTokens = 0) {
35340
- if (!Number.isFinite(promptTokens) || promptTokens < 0) return 0;
35341
- if (!Number.isFinite(completionTokens) || completionTokens < 0) return 0;
35342
- const rate = getModelRate(model);
35343
- const cached2 = Number.isFinite(cachedPromptTokens) && cachedPromptTokens > 0 ? Math.min(cachedPromptTokens, promptTokens) : 0;
35344
- const uncachedPrompt = promptTokens - cached2;
35345
- const cachedRate = rate.cachedInput ?? rate.input * DEFAULT_CACHE_DISCOUNT;
35346
- const inputCost = uncachedPrompt / 1e6 * rate.input + cached2 / 1e6 * cachedRate;
35347
- const outputCost = completionTokens / 1e6 * rate.output;
35348
- return Number((inputCost + outputCost).toFixed(6));
35349
- }
35350
- function formatCost(usd) {
35351
- if (!Number.isFinite(usd) || usd < 0) return "$0.0000";
35352
- if (usd === 0) return "$0.0000";
35353
- if (usd < 1e-4) return "<$0.0001";
35354
- return `$${usd.toFixed(4)}`;
35355
- }
35356
- function formatTokens(tokens) {
35357
- if (!Number.isFinite(tokens) || tokens < 0) return "0";
35358
- if (tokens < 1e3) return `${tokens}`;
35359
- if (tokens < 1e6) return `${(tokens / 1e3).toFixed(1)}k`;
35360
- if (tokens < 1e9) return `${(tokens / 1e6).toFixed(2)}M`;
35361
- return `${(tokens / 1e9).toFixed(2)}B`;
35362
- }
35363
-
35364
- // src/cli/components/StatusBar.tsx
35523
+ init_modelPricing();
35365
35524
  function StatusBar({
35366
35525
  model,
35367
35526
  provider,
@@ -38705,6 +38864,9 @@ function finalizeStreamingAssistant(setMessages) {
38705
38864
  // src/cli/hooks/useChatTurn.ts
38706
38865
  init_conversationContext();
38707
38866
 
38867
+ // src/cli/hooks/chatStats.ts
38868
+ init_modelPricing();
38869
+
38708
38870
  // src/cli/state/promptCacheStats.ts
38709
38871
  function emptyPromptCacheStats() {
38710
38872
  return {
@@ -40904,7 +41066,7 @@ ${formatSkillList(availableSkills)}`
40904
41066
  // src/cli/gitOps.ts
40905
41067
  import { execFile as execFile3 } from "node:child_process";
40906
41068
  import { promisify as promisify2 } from "node:util";
40907
- import path30 from "node:path";
41069
+ import path31 from "node:path";
40908
41070
  var execFileAsync2 = promisify2(execFile3);
40909
41071
  async function git2(cwd, args) {
40910
41072
  try {
@@ -40950,7 +41112,7 @@ async function undoWorkingChanges(opts = {}) {
40950
41112
  };
40951
41113
  }
40952
41114
  function defaultProjectRoot() {
40953
- return path30.resolve(__dirname, "..", "..", "..");
41115
+ return path31.resolve(__dirname, "..", "..", "..");
40954
41116
  }
40955
41117
 
40956
41118
  // src/cli/slashHandlers/git.ts
@@ -41590,17 +41752,17 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
41590
41752
  }
41591
41753
 
41592
41754
  // src/cli/slashHandlers/promoteMember.ts
41593
- import { promises as fs18 } from "node:fs";
41594
- import path33 from "node:path";
41755
+ import { promises as fs19 } from "node:fs";
41756
+ import path34 from "node:path";
41595
41757
  import os10 from "node:os";
41596
41758
  async function handlePromoteMember(ctx, memberId) {
41597
41759
  try {
41598
41760
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
41599
41761
  const { skill, markdown } = promoteMember2(memberId);
41600
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path33.join(os10.homedir(), ".tmp", "zelari-code", "skills");
41601
- await fs18.mkdir(skillDir, { recursive: true });
41602
- const filePath = path33.join(skillDir, `${skill.id}.md`);
41603
- await fs18.writeFile(filePath, markdown, "utf8");
41762
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path34.join(os10.homedir(), ".tmp", "zelari-code", "skills");
41763
+ await fs19.mkdir(skillDir, { recursive: true });
41764
+ const filePath = path34.join(skillDir, `${skill.id}.md`);
41765
+ await fs19.writeFile(filePath, markdown, "utf8");
41604
41766
  appendSystem(
41605
41767
  ctx.setMessages,
41606
41768
  `[promote-member] ${skill.name} (${memberId}) \u2192 ${filePath}
@@ -41616,25 +41778,25 @@ async function handlePromoteMember(ctx, memberId) {
41616
41778
  }
41617
41779
 
41618
41780
  // src/cli/branchManager.ts
41619
- import { promises as fs19, existsSync as existsSync35, readFileSync as readFileSync29, writeFileSync as writeFileSync19, mkdirSync as mkdirSync14, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
41620
- import path34 from "node:path";
41781
+ import { promises as fs20, existsSync as existsSync35, readFileSync as readFileSync29, writeFileSync as writeFileSync19, mkdirSync as mkdirSync14, statSync as statSync4, rmSync as rmSync2 } from "node:fs";
41782
+ import path35 from "node:path";
41621
41783
  import os11 from "node:os";
41622
41784
  var META_FILENAME = "meta.json";
41623
41785
  var SESSIONS_SUBDIR = "sessions";
41624
41786
  function getBranchesBaseDir() {
41625
- return process.env.ANATHEMA_BRANCHES_DIR ?? path34.join(os11.homedir(), ".tmp", "zelari-code", "branches");
41787
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path35.join(os11.homedir(), ".tmp", "zelari-code", "branches");
41626
41788
  }
41627
41789
  function getSessionsBaseDir() {
41628
- return process.env.ANATHEMA_SESSIONS_DIR ?? path34.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
41790
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path35.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
41629
41791
  }
41630
41792
  function branchPathFor(name, baseDir) {
41631
- return path34.join(baseDir, name);
41793
+ return path35.join(baseDir, name);
41632
41794
  }
41633
41795
  function metaPathFor(name, baseDir) {
41634
- return path34.join(baseDir, name, META_FILENAME);
41796
+ return path35.join(baseDir, name, META_FILENAME);
41635
41797
  }
41636
41798
  function sessionsPathFor(name, baseDir) {
41637
- return path34.join(baseDir, name, SESSIONS_SUBDIR);
41799
+ return path35.join(baseDir, name, SESSIONS_SUBDIR);
41638
41800
  }
41639
41801
  function readBranchMeta(name, baseDir) {
41640
41802
  const metaPath = metaPathFor(name, baseDir);
@@ -41659,13 +41821,13 @@ function readBranchMeta(name, baseDir) {
41659
41821
  }
41660
41822
  function writeBranchMeta(name, baseDir, meta3) {
41661
41823
  const metaPath = metaPathFor(name, baseDir);
41662
- mkdirSync14(path34.dirname(metaPath), { recursive: true });
41824
+ mkdirSync14(path35.dirname(metaPath), { recursive: true });
41663
41825
  writeFileSync19(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
41664
41826
  }
41665
41827
  async function countSessions(name, baseDir) {
41666
41828
  const sessionsPath = sessionsPathFor(name, baseDir);
41667
41829
  try {
41668
- const entries = await fs19.readdir(sessionsPath);
41830
+ const entries = await fs20.readdir(sessionsPath);
41669
41831
  return entries.filter((e) => e.endsWith(".jsonl")).length;
41670
41832
  } catch (err) {
41671
41833
  if (err.code === "ENOENT") return 0;
@@ -41710,15 +41872,15 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
41710
41872
  if (branchExists(name, baseDir)) {
41711
41873
  throw new BranchAlreadyExistsError(name);
41712
41874
  }
41713
- const sourcePath = path34.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
41875
+ const sourcePath = path35.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
41714
41876
  if (!existsSync35(sourcePath)) {
41715
41877
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
41716
41878
  }
41717
41879
  const branchPath = branchPathFor(name, baseDir);
41718
41880
  const branchSessionsPath = sessionsPathFor(name, baseDir);
41719
41881
  mkdirSync14(branchSessionsPath, { recursive: true });
41720
- const destPath = path34.join(branchSessionsPath, `${fromSessionId}.jsonl`);
41721
- await fs19.copyFile(sourcePath, destPath);
41882
+ const destPath = path35.join(branchSessionsPath, `${fromSessionId}.jsonl`);
41883
+ await fs20.copyFile(sourcePath, destPath);
41722
41884
  const meta3 = {
41723
41885
  name,
41724
41886
  createdAt: Date.now(),
@@ -41736,7 +41898,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
41736
41898
  async function listBranches(baseDir = getBranchesBaseDir()) {
41737
41899
  let entries;
41738
41900
  try {
41739
- entries = await fs19.readdir(baseDir);
41901
+ entries = await fs20.readdir(baseDir);
41740
41902
  } catch (err) {
41741
41903
  if (err.code === "ENOENT") return [];
41742
41904
  throw err;
@@ -41817,26 +41979,26 @@ async function handleBranchCheckout(ctx, branchName) {
41817
41979
  }
41818
41980
 
41819
41981
  // src/cli/slashHandlers/workspace.ts
41820
- import { promises as fs20 } from "node:fs";
41821
- import path35 from "node:path";
41982
+ import { promises as fs21 } from "node:fs";
41983
+ import path36 from "node:path";
41822
41984
  async function handleWorkspaceShow(ctx, what) {
41823
41985
  try {
41824
- const zelari = path35.join(process.cwd(), ".zelari");
41986
+ const zelari = path36.join(process.cwd(), ".zelari");
41825
41987
  let content;
41826
41988
  switch (what) {
41827
41989
  case "plan": {
41828
- const planPath = path35.join(zelari, "plan.md");
41990
+ const planPath = path36.join(zelari, "plan.md");
41829
41991
  try {
41830
- content = await fs20.readFile(planPath, "utf-8");
41992
+ content = await fs21.readFile(planPath, "utf-8");
41831
41993
  } catch {
41832
41994
  content = "(no plan.md yet \u2014 run a council session first)";
41833
41995
  }
41834
41996
  break;
41835
41997
  }
41836
41998
  case "decisions": {
41837
- const decisionsDir = path35.join(zelari, "decisions");
41999
+ const decisionsDir = path36.join(zelari, "decisions");
41838
42000
  try {
41839
- const files = (await fs20.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
42001
+ const files = (await fs21.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
41840
42002
  if (files.length === 0) {
41841
42003
  content = "(no ADRs yet \u2014 invoke /council to generate some)";
41842
42004
  } else {
@@ -41844,7 +42006,7 @@ async function handleWorkspaceShow(ctx, what) {
41844
42006
  `];
41845
42007
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
41846
42008
  for (const f of files) {
41847
- const raw = await fs20.readFile(path35.join(decisionsDir, f), "utf-8");
42009
+ const raw = await fs21.readFile(path36.join(decisionsDir, f), "utf-8");
41848
42010
  const { meta: meta3, body } = parseFrontmatter2(raw);
41849
42011
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
41850
42012
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -41857,27 +42019,27 @@ async function handleWorkspaceShow(ctx, what) {
41857
42019
  break;
41858
42020
  }
41859
42021
  case "risks": {
41860
- const risksPath = path35.join(zelari, "risks.md");
42022
+ const risksPath = path36.join(zelari, "risks.md");
41861
42023
  try {
41862
- content = await fs20.readFile(risksPath, "utf-8");
42024
+ content = await fs21.readFile(risksPath, "utf-8");
41863
42025
  } catch {
41864
42026
  content = "(no risks.md yet)";
41865
42027
  }
41866
42028
  break;
41867
42029
  }
41868
42030
  case "agents": {
41869
- const agentsPath = path35.join(process.cwd(), "AGENTS.MD");
42031
+ const agentsPath = path36.join(process.cwd(), "AGENTS.MD");
41870
42032
  try {
41871
- content = await fs20.readFile(agentsPath, "utf-8");
42033
+ content = await fs21.readFile(agentsPath, "utf-8");
41872
42034
  } catch {
41873
42035
  content = "(no AGENTS.MD yet at project root \u2014 run `/workspace sync` after a council session)";
41874
42036
  }
41875
42037
  break;
41876
42038
  }
41877
42039
  case "docs": {
41878
- const docsDir = path35.join(zelari, "docs");
42040
+ const docsDir = path36.join(zelari, "docs");
41879
42041
  try {
41880
- const files = (await fs20.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
42042
+ const files = (await fs21.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
41881
42043
  content = files.length ? `# Docs (${files.length})
41882
42044
 
41883
42045
  ` + files.map((f) => `- ${f}`).join("\n") : "(no docs drafts yet)";
@@ -41917,8 +42079,8 @@ async function handleWorkspaceReset(ctx, force) {
41917
42079
  return;
41918
42080
  }
41919
42081
  try {
41920
- const target = path35.join(process.cwd(), ".zelari");
41921
- await fs20.rm(target, { recursive: true, force: true });
42082
+ const target = path36.join(process.cwd(), ".zelari");
42083
+ await fs21.rm(target, { recursive: true, force: true });
41922
42084
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
41923
42085
  } catch (err) {
41924
42086
  appendSystem(ctx.setMessages, `[workspace reset error] ${err instanceof Error ? err.message : String(err)}`);
@@ -42276,16 +42438,16 @@ function handleModelsRefresh(ctx) {
42276
42438
  }
42277
42439
 
42278
42440
  // src/cli/slashHandlers/skills.ts
42279
- import path36 from "node:path";
42441
+ import path37 from "node:path";
42280
42442
  import os12 from "node:os";
42281
42443
 
42282
42444
  // src/cli/skillHistory.ts
42283
- import { promises as fs21, existsSync as existsSync36, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync15 } from "node:fs";
42445
+ import { promises as fs22, existsSync as existsSync36, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync15 } from "node:fs";
42284
42446
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
42285
42447
  async function readSkillHistory(file2) {
42286
42448
  let raw = "";
42287
42449
  try {
42288
- raw = await fs21.readFile(file2, "utf-8");
42450
+ raw = await fs22.readFile(file2, "utf-8");
42289
42451
  } catch {
42290
42452
  return [];
42291
42453
  }
@@ -42378,7 +42540,7 @@ async function applySteerInterrupt(options) {
42378
42540
 
42379
42541
  // src/cli/slashHandlers/skills.ts
42380
42542
  async function handleSkillStats(ctx, skillId) {
42381
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path36.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
42543
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path37.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
42382
42544
  try {
42383
42545
  const records = await readSkillHistory(historyFile);
42384
42546
  const stats = getSkillStats(records, skillId);
@@ -42394,7 +42556,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
42394
42556
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
42395
42557
  return;
42396
42558
  }
42397
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path36.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
42559
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path37.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
42398
42560
  try {
42399
42561
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
42400
42562
  appendSystem(ctx.setMessages, formatted);
@@ -43808,6 +43970,7 @@ function parseHeadlessFlags(argv) {
43808
43970
  let provider;
43809
43971
  let model;
43810
43972
  let history2;
43973
+ let once = false;
43811
43974
  for (let i = 0; i < argv.length; i++) {
43812
43975
  const arg = argv[i];
43813
43976
  if (arg === "--headless") continue;
@@ -43896,6 +44059,8 @@ function parseHeadlessFlags(argv) {
43896
44059
  }
43897
44060
  i++;
43898
44061
  }
44062
+ } else if (arg === "--once") {
44063
+ once = true;
43899
44064
  }
43900
44065
  }
43901
44066
  if (councilFlag && !modeExplicit) {
@@ -43918,7 +44083,8 @@ function parseHeadlessFlags(argv) {
43918
44083
  useCouncil: mode === "council",
43919
44084
  provider,
43920
44085
  model,
43921
- ...history2 && history2.length > 0 ? { history: history2 } : {}
44086
+ ...history2 && history2.length > 0 ? { history: history2 } : {},
44087
+ ...once ? { once: true } : {}
43922
44088
  }
43923
44089
  };
43924
44090
  }
@@ -44601,6 +44767,22 @@ ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMv
44601
44767
  }
44602
44768
  let exitCode = 0;
44603
44769
  let lastMissionAssistant = "";
44770
+ let lockAcquired = false;
44771
+ if (opts.once) {
44772
+ const { acquireLock: acquireLock2 } = await Promise.resolve().then(() => (init_triggerLock(), triggerLock_exports));
44773
+ const lockRes = await acquireLock2(projectRoot);
44774
+ if (!lockRes.acquired) {
44775
+ process.stderr.write(
44776
+ `[zelari-code --once] skip: another mission is running (pid ${lockRes.heldBy}).
44777
+ `
44778
+ );
44779
+ return 0;
44780
+ }
44781
+ lockAcquired = true;
44782
+ if (!process.env.ZELARI_MISSION_MAX_ITER) {
44783
+ process.env.ZELARI_MISSION_MAX_ITER = "1";
44784
+ }
44785
+ }
44604
44786
  try {
44605
44787
  const state2 = await runZelariMission2(missionTask, brief, {
44606
44788
  projectRoot,
@@ -44819,6 +45001,10 @@ ${ragContext}` : slicePrompt;
44819
45001
  );
44820
45002
  return 2;
44821
45003
  } finally {
45004
+ if (lockAcquired) {
45005
+ const { releaseLock: releaseLock2 } = await Promise.resolve().then(() => (init_triggerLock(), triggerLock_exports));
45006
+ await releaseLock2(projectRoot);
45007
+ }
44822
45008
  await memory.close().catch(() => void 0);
44823
45009
  }
44824
45010
  if (opts.output === "json") {