zelari-code 1.8.2 → 1.9.2

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.
@@ -18971,35 +18971,111 @@ function normalizeTextToolArgs(name, args) {
18971
18971
  }
18972
18972
  function parseTextToolCalls(text) {
18973
18973
  const m = /---TOOLS---\s*([\s\S]*?)---END---/.exec(text);
18974
- if (!m || !m[1])
18975
- return [];
18976
- let body = m[1].trim();
18977
- body = body.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
18978
- const candidates = [body];
18979
- if (/\]\s*\[/.test(body)) {
18980
- candidates.push(body.replace(/\]\s*\[/g, ","));
18981
- }
18982
- if (body.includes('\\"')) {
18983
- candidates.push(body.replace(/\\"/g, '"'));
18974
+ if (m?.[1]) {
18975
+ let body = m[1].trim();
18976
+ body = body.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
18977
+ const candidates = [body];
18984
18978
  if (/\]\s*\[/.test(body)) {
18985
- candidates.push(body.replace(/\\"/g, '"').replace(/\]\s*\[/g, ","));
18979
+ candidates.push(body.replace(/\]\s*\[/g, ","));
18980
+ }
18981
+ if (body.includes('\\"')) {
18982
+ candidates.push(body.replace(/\\"/g, '"'));
18983
+ if (/\]\s*\[/.test(body)) {
18984
+ candidates.push(body.replace(/\\"/g, '"').replace(/\]\s*\[/g, ","));
18985
+ }
18986
+ }
18987
+ for (const cand of candidates) {
18988
+ const items = tryParseToolArray(cand);
18989
+ if (items.length > 0)
18990
+ return items;
18991
+ }
18992
+ const arrays = extractJsonArrays(body);
18993
+ if (arrays.length > 1) {
18994
+ const merged = [];
18995
+ for (const a of arrays) {
18996
+ merged.push(...tryParseToolArray(a));
18997
+ }
18998
+ if (merged.length > 0)
18999
+ return merged;
19000
+ }
19001
+ const objs = extractToolObjects(body);
19002
+ if (objs.length > 0)
19003
+ return objs;
19004
+ }
19005
+ const mini = parseMinimaxStyleToolCalls(text);
19006
+ if (mini.length > 0)
19007
+ return mini;
19008
+ return [];
19009
+ }
19010
+ function parseMinimaxStyleToolCalls(text) {
19011
+ const out = [];
19012
+ const normalized = text.replace(/\]\s*<\s*\]\s*minimax\s*\[\s*>\s*\[</gi, "<minimax:").replace(/\]\s*<\s*\]\s*minimax\s*\[\s*>/gi, "<minimax:").replace(/minimax\s*\[\s*>\s*\[</gi, "minimax:").replace(/\]\s*</g, "<");
19013
+ const blockRe = /<(?:minimax:)?tool_call[^>]*>([\s\S]*?)<\/(?:minimax:)?tool_call>|<invoke\s+name=["']([^"']+)["'][^>]*>([\s\S]*?)(?:<\/invoke>|(?=<invoke\s+name=)|$)/gi;
19014
+ let match;
19015
+ while ((match = blockRe.exec(normalized)) !== null) {
19016
+ if (match[2]) {
19017
+ out.push({ name: match[2], args: parseLooseArgs(match[3] ?? "") });
19018
+ } else if (match[1]) {
19019
+ const inner = match[1];
19020
+ const inv = /invoke\s+name=["']([^"']+)["']/i.exec(inner);
19021
+ if (inv) {
19022
+ out.push({ name: inv[1], args: parseLooseArgs(inner) });
19023
+ } else {
19024
+ const jsonish = tryParseToolArray(inner.trim());
19025
+ if (jsonish.length)
19026
+ out.push(...jsonish);
19027
+ else {
19028
+ const objs = extractToolObjects(inner);
19029
+ if (objs.length)
19030
+ out.push(...objs);
19031
+ }
19032
+ }
19033
+ }
19034
+ }
19035
+ if (out.length === 0) {
19036
+ const loose = /invoke\s+name=["']([^"']+)["']([\s\S]*?)(?=invoke\s+name=|$)/gi;
19037
+ let lm;
19038
+ while ((lm = loose.exec(normalized)) !== null) {
19039
+ const name = lm[1];
19040
+ const args = parseLooseArgs(lm[2] ?? "");
19041
+ if (name)
19042
+ out.push({ name, args });
18986
19043
  }
18987
19044
  }
18988
- for (const cand of candidates) {
18989
- const items = tryParseToolArray(cand);
18990
- if (items.length > 0)
18991
- return items;
19045
+ return out;
19046
+ }
19047
+ function parseLooseArgs(body) {
19048
+ const args = {};
19049
+ const paramTag = /(?:parameter\s+name=|<\s*)["']?([a-zA-Z_][\w]*)["']?\s*(?:>|=\s*["']?)([^<\]\n]+)/gi;
19050
+ let m;
19051
+ while ((m = paramTag.exec(body)) !== null) {
19052
+ const key = m[1];
19053
+ if (key === "invoke" || key === "minimax" || key === "tool_call")
19054
+ continue;
19055
+ args[key] = m[2].replace(/^["']|["']$/g, "").trim();
19056
+ }
19057
+ const lineRe = /(?:^|[\s\[>])([a-zA-Z_][\w]*)\s*[>:=]\s*([^\n<\]]+)/g;
19058
+ while ((m = lineRe.exec(body)) !== null) {
19059
+ const key = m[1];
19060
+ if (args[key] !== void 0)
19061
+ continue;
19062
+ if (["invoke", "name", "minimax", "tool_call", "parameter"].includes(key))
19063
+ continue;
19064
+ args[key] = m[2].replace(/^["']|["']$/g, "").trim();
18992
19065
  }
18993
- const arrays = extractJsonArrays(body);
18994
- if (arrays.length > 1) {
18995
- const merged = [];
18996
- for (const a of arrays) {
18997
- merged.push(...tryParseToolArray(a));
19066
+ if (Object.keys(args).length === 0) {
19067
+ const brace = body.indexOf("{");
19068
+ if (brace >= 0) {
19069
+ try {
19070
+ const parsed = JSON.parse(body.slice(brace));
19071
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19072
+ return parsed;
19073
+ }
19074
+ } catch {
19075
+ }
18998
19076
  }
18999
- if (merged.length > 0)
19000
- return merged;
19001
19077
  }
19002
- return extractToolObjects(body);
19078
+ return args;
19003
19079
  }
19004
19080
  function tryParseToolArray(raw) {
19005
19081
  let parsed;
@@ -19105,6 +19181,7 @@ var init_AgentHarness = __esm({
19105
19181
  sessionId;
19106
19182
  maxQueuedIterations;
19107
19183
  maxToolLoopIterations;
19184
+ maxToolLoopHardCap;
19108
19185
  cancelled = false;
19109
19186
  activeController = null;
19110
19187
  queue = [];
@@ -19130,6 +19207,9 @@ var init_AgentHarness = __esm({
19130
19207
  this.sessionId = config2.sessionId ?? crypto.randomUUID();
19131
19208
  this.maxQueuedIterations = config2.maxQueuedIterations ?? 3;
19132
19209
  this.maxToolLoopIterations = config2.maxToolLoopIterations ?? 30;
19210
+ const soft = this.maxToolLoopIterations;
19211
+ const hardCfg = config2.maxToolLoopHardCap;
19212
+ this.maxToolLoopHardCap = typeof hardCfg === "number" && hardCfg > 0 ? Math.max(soft, hardCfg) : Math.max(soft * 3, soft + 60);
19133
19213
  }
19134
19214
  /**
19135
19215
  * Snapshot of the live transcript (`this.config.messages`) as mutated
@@ -19406,7 +19486,11 @@ ${shared2.content}`,
19406
19486
  this.emit(initialMsgEnd);
19407
19487
  yield initialMsgEnd;
19408
19488
  let toolLoopTurns = 0;
19409
- while (!this.cancelled && !hadError && toolLoopTurns < this.maxToolLoopIterations && initialFinishRef.value === "tool_calls") {
19489
+ let softCap = this.maxToolLoopIterations;
19490
+ const hardCap = this.maxToolLoopHardCap;
19491
+ let extensions = 0;
19492
+ const maxExtensions = 8;
19493
+ while (!this.cancelled && !hadError && toolLoopTurns < softCap && initialFinishRef.value === "tool_calls") {
19410
19494
  toolLoopTurns++;
19411
19495
  const turnMessageId = crypto.randomUUID();
19412
19496
  const msgStart = createBrainEvent("message_start", this.sessionId, { messageId: turnMessageId, role: "assistant", ...this.memberFields() });
@@ -19437,9 +19521,25 @@ ${shared2.content}`,
19437
19521
  initialFinishRef.value = turnFinishRef.value;
19438
19522
  if (hadError || this.cancelled)
19439
19523
  break;
19524
+ if (initialFinishRef.value === "tool_calls" && toolLoopTurns >= softCap && toolLoopTurns < hardCap && extensions < maxExtensions) {
19525
+ extensions++;
19526
+ const chunk = this.maxToolLoopIterations;
19527
+ softCap = Math.min(hardCap, softCap + chunk);
19528
+ this.config.messages.push({
19529
+ role: "user",
19530
+ content: `[system] Soft tool budget reached (${toolLoopTurns}/${hardCap} hard). Work may be incomplete \u2014 CONTINUE with tools as needed to finish remaining steps. Prefer concrete progress over summarizing. Do not apologize for budgets.`
19531
+ });
19532
+ const note = createBrainEvent("error", this.sessionId, {
19533
+ severity: "recoverable",
19534
+ message: `Tool budget extended (${toolLoopTurns}\u2192${softCap}, hard ${hardCap}) to allow completion.`,
19535
+ code: "tool_budget_extended"
19536
+ });
19537
+ this.emit(note);
19538
+ yield note;
19539
+ }
19440
19540
  }
19441
- const hitIterationCap = toolLoopTurns >= this.maxToolLoopIterations;
19442
- if (!this.cancelled && !hadError && initialFinishRef.value === "tool_calls" && hitIterationCap) {
19541
+ const hitHardCap = toolLoopTurns >= hardCap || toolLoopTurns >= softCap && softCap >= hardCap;
19542
+ if (!this.cancelled && !hadError && initialFinishRef.value === "tool_calls" && hitHardCap) {
19443
19543
  yield* this.runFinalAnswerTurn();
19444
19544
  }
19445
19545
  let turns = 0;
@@ -19603,13 +19703,14 @@ ${cached2}`
19603
19703
  pendingNativeTools.length = 0;
19604
19704
  }
19605
19705
  const textTools = parseTextToolCalls(turnText);
19606
- const nativeWriteRan = turnToolCalls.some((t) => t.name === "write_file" || t.name === "edit_file");
19607
- const textWriteTools = textTools.filter((t) => t.name === "write_file" || t.name === "edit_file");
19608
- const toolsToRun = nativeWriteRan ? [] : turnToolCalls.length === 0 ? textTools : textWriteTools;
19609
- if (/---TOOLS---/.test(turnText) && textTools.length === 0) {
19706
+ const toolsToRun = textTools.filter((tt) => {
19707
+ const key = hashToolCall(tt.name, tt.args);
19708
+ return !turnToolCalls.some((n) => hashToolCall(n.name, n.args) === key);
19709
+ });
19710
+ if ((/---TOOLS---/.test(turnText) || /minimax|invoke\s+name=/i.test(turnText)) && textTools.length === 0) {
19610
19711
  const parseErr = createBrainEvent("error", this.sessionId, {
19611
19712
  severity: "recoverable",
19612
- message: "Found ---TOOLS--- block but JSON parse failed; tool calls were not executed. Emit valid JSON (escape newlines as \\n inside strings).",
19713
+ message: "Found text-format tool block but parse failed; tool calls were not executed. Prefer native tool_call, or ---TOOLS--- with ONE valid JSON array.",
19613
19714
  code: "text_tools_parse_failed"
19614
19715
  });
19615
19716
  this.emit(parseErr);
@@ -19741,7 +19842,7 @@ ${cached2}`
19741
19842
  yield msgStart;
19742
19843
  this.config.messages.push({
19743
19844
  role: "user",
19744
- content: "[system] Tool iteration budget exhausted. Stop calling tools and answer the original request now using the information you have already gathered. Do not apologize for the tools."
19845
+ content: "[system] Hard tool-iteration ceiling reached. Stop calling tools and give a clear status: what is DONE, what remains, and the exact next steps. Use what you already gathered. Do not apologize for the tools."
19745
19846
  });
19746
19847
  let totalLength = 0;
19747
19848
  const finishRef = { value: "stop" };
@@ -19940,6 +20041,7 @@ __export(harness_exports, {
19940
20041
  SessionJsonlWriter: () => SessionJsonlWriter,
19941
20042
  hashToolCall: () => hashToolCall,
19942
20043
  normalizeTextToolArgs: () => normalizeTextToolArgs,
20044
+ parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
19943
20045
  parseTextToolCalls: () => parseTextToolCalls,
19944
20046
  readSession: () => readSession,
19945
20047
  wrapLegacyStream: () => wrapLegacyStream
@@ -30652,12 +30754,12 @@ var init_fixPath = __esm({
30652
30754
  });
30653
30755
 
30654
30756
  // src/cli/main.ts
30655
- import React14 from "react";
30757
+ import React15 from "react";
30656
30758
  import { render } from "ink";
30657
30759
 
30658
30760
  // src/cli/app.tsx
30659
- import React9, { useState as useState8, useMemo, useCallback as useCallback5, useEffect as useEffect7, useRef as useRef6 } from "react";
30660
- import { Box as Box8, Static, useInput as useInput2, useStdin as useStdin2 } from "ink";
30761
+ import React10, { useState as useState8, useMemo, useCallback as useCallback5, useEffect as useEffect7 } from "react";
30762
+ import { Box as Box9, Static, useInput as useInput2, useStdin as useStdin2 } from "ink";
30661
30763
 
30662
30764
  // src/cli/components/InputBar.tsx
30663
30765
  import React from "react";
@@ -31171,8 +31273,7 @@ function StatusBar({
31171
31273
  costUsd = 0,
31172
31274
  cachedTokens = 0,
31173
31275
  contextUsed = 0,
31174
- contextLimit = 0,
31175
- brandVersion
31276
+ contextLimit = 0
31176
31277
  }) {
31177
31278
  const ctxLabel = contextLimit > 0 ? `${formatTokens(contextUsed)}/${formatTokens(contextLimit)}` : contextUsed > 0 ? formatTokens(contextUsed) : null;
31178
31279
  return /* @__PURE__ */ React6.createElement(Box5, { paddingX: 1, width: "100%", justifyContent: "space-between", gap: 2 }, /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 2 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, /* @__PURE__ */ React6.createElement(Text6, { color: sessionActive ? "green" : "gray" }, sessionActive ? "\u25CF" : "\u25CB"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: phase2 === "plan" ? "yellow" : "green" }, phase2 === "plan" ? "\u25C7 plan" : "\u25C6 build"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(
@@ -31182,7 +31283,7 @@ function StatusBar({
31182
31283
  color: mode === "council" ? "magenta" : mode === "zelari" ? "green" : "cyan"
31183
31284
  },
31184
31285
  mode === "council" ? "council" : mode === "zelari" ? "zelari" : "agent"
31185
- ), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, model), cwd ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, cwd)) : null)), /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 1 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, busy && elapsedMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Spinner, { color: "yellow" }), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " ", formatDuration(elapsedMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : lastMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "last ", formatDuration(lastMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, queueCount > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "queue ", queueCount), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, ctxLabel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, ctxLabel), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, costUsd > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, formatCost(costUsd)), cachedTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", formatTokens(cachedTokens), " cached)") : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, brandVersion ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "white" }, "ZELARI"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " v", brandVersion), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId))));
31286
+ ), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, model), cwd ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, cwd)) : null)), /* @__PURE__ */ React6.createElement(Box5, { flexShrink: 1 }, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, busy && elapsedMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Spinner, { color: "yellow" }), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " ", formatDuration(elapsedMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : lastMs !== null ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "last ", formatDuration(lastMs)), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, queueCount > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, "queue ", queueCount), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, ctxLabel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, ctxLabel), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, costUsd > 0 ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, formatCost(costUsd)), cachedTokens > 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (", formatTokens(cachedTokens), " cached)") : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \xB7 ")) : null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "session ", sessionId))));
31186
31287
  }
31187
31288
 
31188
31289
  // src/cli/components/SelectList.tsx
@@ -31230,14 +31331,47 @@ function SelectList({
31230
31331
  // src/cli/components/Sidebar.tsx
31231
31332
  import React8 from "react";
31232
31333
  import { Box as Box7, Text as Text8 } from "ink";
31334
+
31335
+ // src/cli/components/brandArt.ts
31336
+ var BRAND_LOGO_ASCII = [
31337
+ " -#%=",
31338
+ " +%%%%#.",
31339
+ " :#%%%%%%%-",
31340
+ ":%%@@@@@@@@=",
31341
+ "-%@@@@@@@@@@@+",
31342
+ ":%@@@@@@%@@@@@@=",
31343
+ "-%%@@@@@.+%@@%@+",
31344
+ " -@@@@@@:%++%@=",
31345
+ "=*%@@@@@@:@@%=*@#+.",
31346
+ "%%%%%@@@@:+++-*%@@*",
31347
+ ".%%%%%@@@@@+%+=:%@@@@:",
31348
+ "*@@@@@@@@@@@@@%@@@@@@*"
31349
+ ].join("\n");
31350
+ var BRAND_LOGO_COMPACT = [
31351
+ " -#%=",
31352
+ " +%%%%#.",
31353
+ ":%%@@@@=",
31354
+ "*@@@@@@*"
31355
+ ].join("\n");
31356
+
31357
+ // src/cli/components/Sidebar.tsx
31358
+ var EMBLEM_BRAILLE = `\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28F4\u28FF\u28F7\u28C6\u2840
31359
+ \u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28F6\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6\u2840
31360
+ \u2800\u2800\u2800\u2800\u2800\u2800\u28F0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6
31361
+ \u2800\u2800\u2800\u2800\u28A0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28F7\u2844
31362
+ \u2800\u2800\u2800\u2800\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2819\u283B\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2806
31363
+ \u2800\u2800\u2800\u2800\u28C8\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2800\u28F7\u28EC\u285B\u28BF\u28FF\u28DF\u28C1
31364
+ \u2800\u2880\u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2800\u287F\u28BF\u287F\u2883\u28C8\u28FB\u28FF\u28FF\u28F6
31365
+ \u2800\u28FC\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E0\u28F4\u28C6\u283B\u280E\u28BF\u28FF\u28FF\u28FF\u28FF\u28E7
31366
+ \u28F0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2847`;
31233
31367
  var SIDEBAR_WIDTH = 28;
31234
31368
  var SIDEBAR_MIN_COLUMNS = 96;
31235
31369
  var SIDEBAR_HIDE_COLUMNS = 88;
31236
31370
  var SIDEBAR_MIN_ROWS = 16;
31237
31371
  var SIDEBAR_HIDE_ROWS = 14;
31238
- var MAX_FILES_SHORT = 4;
31239
- var MAX_FILES_TALL = 8;
31240
- var SIDEBAR_CHROME_LINES = 4;
31372
+ var BRAILLE_MIN_ROWS = 28;
31373
+ var MAX_FILES_SHORT = 6;
31374
+ var MAX_FILES_TALL = 10;
31241
31375
  function shouldShowSidebar(columns, rows) {
31242
31376
  return columns >= SIDEBAR_MIN_COLUMNS && rows >= SIDEBAR_MIN_ROWS;
31243
31377
  }
@@ -31247,18 +31381,19 @@ function sidebarVisibility(columns, rows, currentlyVisible) {
31247
31381
  }
31248
31382
  return shouldShowSidebar(columns, rows);
31249
31383
  }
31250
- function maxSidebarFiles(rows) {
31251
- const budget = Math.max(2, rows - 14 - SIDEBAR_CHROME_LINES);
31252
- const cap = rows >= 28 ? MAX_FILES_TALL : MAX_FILES_SHORT;
31253
- return Math.min(cap, budget);
31254
- }
31255
31384
  function truncatePath(p3, max) {
31256
31385
  if (p3.length <= max) return p3;
31257
31386
  return `\u2026${p3.slice(-(max - 1))}`;
31258
31387
  }
31388
+ function preferAsciiLogo() {
31389
+ if (process.env.ZELARI_ASCII_LOGO === "1") return true;
31390
+ if (process.env.ZELARI_ASCII_LOGO === "0") return false;
31391
+ return process.platform === "win32";
31392
+ }
31259
31393
  function Sidebar({ version: version2, changes, rows }) {
31260
- void version2;
31261
- const maxFiles = maxSidebarFiles(rows);
31394
+ const useBraille = !preferAsciiLogo() && rows >= BRAILLE_MIN_ROWS;
31395
+ const logoLines = useBraille ? EMBLEM_BRAILLE.split("\n") : BRAND_LOGO_COMPACT.split("\n");
31396
+ const maxFiles = rows >= BRAILLE_MIN_ROWS + 8 ? MAX_FILES_TALL : MAX_FILES_SHORT;
31262
31397
  const visible = changes.files.slice(0, maxFiles);
31263
31398
  const hidden = changes.files.length - visible.length;
31264
31399
  const innerWidth = SIDEBAR_WIDTH - 4;
@@ -31270,65 +31405,42 @@ function Sidebar({ version: version2, changes, rows }) {
31270
31405
  borderStyle: "single",
31271
31406
  borderColor: "gray",
31272
31407
  paddingX: 1,
31273
- flexShrink: 0,
31274
- height: Math.min(rows - 8, SIDEBAR_CHROME_LINES + maxFiles + 2),
31275
- overflow: "hidden"
31408
+ flexShrink: 0
31276
31409
  },
31277
- /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, changes.branch ? truncatePath(changes.branch, 18) : "git")),
31410
+ /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column", alignItems: "center" }, logoLines.map((line, i) => /* @__PURE__ */ React8.createElement(Text8, { key: i, color: "cyan" }, line))),
31411
+ /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "white" }, "ZELARI CODE")),
31412
+ /* @__PURE__ */ React8.createElement(Box7, { justifyContent: "center" }, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "v", version2, changes.branch ? ` \xB7 ${truncatePath(changes.branch, 12)}` : "")),
31278
31413
  /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "\u2500".repeat(innerWidth)),
31279
31414
  !changes.isRepo ? /* @__PURE__ */ React8.createElement(Text8, { dimColor: true, italic: true }, "not a git repo") : changes.files.length === 0 ? /* @__PURE__ */ React8.createElement(Text8, { dimColor: true, italic: true }, "no changes") : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "changes (", changes.files.length, ")"), visible.map((f) => /* @__PURE__ */ React8.createElement(FileRow, { key: f.path, file: f, pathWidth: innerWidth - 10 })), hidden > 0 && /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, " +", hidden, " more\u2026"))
31280
31415
  );
31281
31416
  }
31282
- function FileRow({ file: file2, pathWidth }) {
31417
+ function FileRow({
31418
+ file: file2,
31419
+ pathWidth
31420
+ }) {
31283
31421
  const name = truncatePath(file2.path, Math.max(6, pathWidth));
31284
31422
  return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(Text8, { wrap: "truncate" }, /* @__PURE__ */ React8.createElement(Text8, { color: file2.untracked ? "yellow" : "white" }, name), file2.untracked ? /* @__PURE__ */ React8.createElement(Text8, { color: "yellow" }, " new") : /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Text8, { color: "green" }, " +", file2.added ?? "\xB7"), /* @__PURE__ */ React8.createElement(Text8, { color: "red" }, " -", file2.removed ?? "\xB7"))));
31285
31423
  }
31286
31424
 
31287
- // src/cli/components/brandArt.ts
31288
- var BRAND_LOGO_ASCII = [
31289
- " -#%=",
31290
- " +%%%%#.",
31291
- " :#%%%%%%%-",
31292
- ":%%@@@@@@@@=",
31293
- "-%@@@@@@@@@@@+",
31294
- ":%@@@@@@%@@@@@@=",
31295
- "-%%@@@@@.+%@@%@+",
31296
- " -@@@@@@:%++%@=",
31297
- "=*%@@@@@@:@@%=*@#+.",
31298
- "%%%%%@@@@:+++-*%@@*",
31299
- ".%%%%%@@@@@+%+=:%@@@@:",
31300
- "*@@@@@@@@@@@@@%@@@@@@*"
31301
- ].join("\n");
31302
- var BRAND_LOGO_COMPACT = [
31303
- " -#%=",
31304
- " +%%%%#.",
31305
- ":%%@@@@=",
31306
- "*@@@@@@*"
31307
- ].join("\n");
31308
- function formatBannerWithLogoRight(opts) {
31309
- const logoSrc = opts.compact ? BRAND_LOGO_COMPACT : BRAND_LOGO_ASCII;
31310
- const logoLines = logoSrc.split("\n");
31311
- const logoWidth = Math.max(...logoLines.map((l) => l.length));
31312
- const cols = Math.max(logoWidth + 20, Math.min(opts.columns, 120));
31313
- const left = [...opts.leftLines];
31314
- const wordmark = `ZELARI CODE v${opts.version}`;
31315
- const rightExtra = [wordmark];
31316
- const rightBlock = [...logoLines, ...rightExtra];
31317
- const rows = Math.max(left.length, rightBlock.length);
31318
- const out = [];
31319
- for (let i = 0; i < rows; i++) {
31320
- const L = left[i] ?? "";
31321
- const R = rightBlock[i] ?? "";
31322
- if (!R) {
31323
- out.push(L);
31324
- continue;
31325
- }
31326
- const maxLeft = Math.max(0, cols - R.length - 2);
31327
- const leftClipped = L.length > maxLeft ? L.slice(0, Math.max(0, maxLeft - 1)) + "\u2026" : L;
31328
- const pad = Math.max(2, cols - leftClipped.length - R.length);
31329
- out.push(leftClipped + " ".repeat(pad) + R);
31330
- }
31331
- return out.join("\n");
31425
+ // src/cli/components/StartupBanner.tsx
31426
+ import React9 from "react";
31427
+ import { Box as Box8, Text as Text9 } from "ink";
31428
+ function StartupBanner({
31429
+ version: version2,
31430
+ providerId,
31431
+ model,
31432
+ cwd,
31433
+ columns,
31434
+ rows
31435
+ }) {
31436
+ const compact = columns < 72 || rows < 20;
31437
+ const logo = (compact ? BRAND_LOGO_COMPACT : BRAND_LOGO_ASCII).split("\n");
31438
+ const leftLines = [
31439
+ `zelari-code v${version2} \u2014 ${providerId}/${model}`,
31440
+ `cwd: ${cwd}`,
31441
+ `/help \xB7 /plan \xB7 /build \xB7 /view-plan \xB7 shift+tab mode`
31442
+ ];
31443
+ return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, width: "100%" }, /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", flexGrow: 1, marginRight: 2 }, leftLines.map((line, i) => /* @__PURE__ */ React9.createElement(Text9, { key: i, color: i === 0 ? "cyan" : void 0, dimColor: i > 0 }, line))), /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", flexShrink: 0, alignItems: "flex-end" }, logo.map((line, i) => /* @__PURE__ */ React9.createElement(Text9, { key: i, color: "cyan" }, line)), /* @__PURE__ */ React9.createElement(Text9, { bold: true, color: "white" }, "ZELARI CODE"), /* @__PURE__ */ React9.createElement(Text9, { dimColor: true }, "v", version2)));
31332
31444
  }
31333
31445
 
31334
31446
  // src/cli/modelDiscovery.ts
@@ -34315,10 +34427,12 @@ function computeSessionStatsDelta(realUsage, userText, assistantContent, model,
34315
34427
  const completionTokens = realUsage ? realUsage.completionTokens : Math.ceil(assistantContent.length / 4);
34316
34428
  const cachedPromptTokens = realUsage?.cachedPromptTokens ?? 0;
34317
34429
  const turnCost = calculateCost(model, promptTokens, completionTokens, cachedPromptTokens);
34430
+ const contextTokens = realUsage ? realUsage.totalTokens || promptTokens + completionTokens : promptTokens + completionTokens;
34318
34431
  return {
34319
34432
  totalTokens: prev2.totalTokens + promptTokens + completionTokens,
34320
34433
  totalCostUsd: prev2.totalCostUsd + turnCost,
34321
- cachedTokens: (prev2.cachedTokens ?? 0) + cachedPromptTokens
34434
+ cachedTokens: (prev2.cachedTokens ?? 0) + cachedPromptTokens,
34435
+ contextTokens
34322
34436
  };
34323
34437
  }
34324
34438
 
@@ -34604,6 +34718,11 @@ function useChatTurn(params) {
34604
34718
  min: 1
34605
34719
  });
34606
34720
  const maxToolLoopIterations = budget.maxToolLoopIterations;
34721
+ const maxToolLoopHardCap = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_HARD, {
34722
+ default: 0,
34723
+ // 0 → harness default (soft×3, min soft+60)
34724
+ min: 0
34725
+ });
34607
34726
  const harness2 = new AgentHarness({
34608
34727
  model: envConfig.model,
34609
34728
  provider: "openai-compatible",
@@ -34624,7 +34743,8 @@ function useChatTurn(params) {
34624
34743
  providerStream,
34625
34744
  cwd,
34626
34745
  maxToolCallsPerTurn,
34627
- maxToolLoopIterations
34746
+ maxToolLoopIterations,
34747
+ ...maxToolLoopHardCap > 0 ? { maxToolLoopHardCap } : {}
34628
34748
  });
34629
34749
  harnessRef.current = harness2;
34630
34750
  setQueueCount(harness2.queueLength);
@@ -34699,7 +34819,15 @@ function useChatTurn(params) {
34699
34819
  }
34700
34820
  } else if (event.type === "error") {
34701
34821
  flushStreaming();
34702
- appendSystem(setMessages, `[error] ${event.message}`, Date.now());
34822
+ if (event.code === "tool_budget_extended") {
34823
+ appendSystem(
34824
+ setMessages,
34825
+ `[budget] ${event.message}`,
34826
+ Date.now()
34827
+ );
34828
+ } else {
34829
+ appendSystem(setMessages, `[error] ${event.message}`, Date.now());
34830
+ }
34703
34831
  } else if (event.type === "tool_execution_start") {
34704
34832
  toolNameById.set(event.toolCallId, event.toolName);
34705
34833
  flushStreaming();
@@ -36193,6 +36321,10 @@ function nextMode(current) {
36193
36321
  const i = MODES.indexOf(current);
36194
36322
  return MODES[(i + 1) % MODES.length] ?? "agent";
36195
36323
  }
36324
+ function parseMode(input) {
36325
+ const v = input.trim().toLowerCase();
36326
+ return MODES.includes(v) ? v : null;
36327
+ }
36196
36328
  function describeMode(mode) {
36197
36329
  switch (mode) {
36198
36330
  case "council":
@@ -37841,7 +37973,12 @@ function App() {
37841
37973
  const [input, setInput] = useState8("");
37842
37974
  const [busy, setBusy] = useState8(false);
37843
37975
  const [providerConfig, setProviderConfig] = useState8(() => getProviderConfig());
37844
- const [sessionStats, setSessionStats] = useState8({ totalTokens: 0, totalCostUsd: 0, cachedTokens: 0 });
37976
+ const [sessionStats, setSessionStats] = useState8({
37977
+ totalTokens: 0,
37978
+ totalCostUsd: 0,
37979
+ cachedTokens: 0,
37980
+ contextTokens: 0
37981
+ });
37845
37982
  const [clearEpoch, setClearEpoch] = useState8(0);
37846
37983
  const [mode, setMode] = useState8("agent");
37847
37984
  const [phase2, setPhaseUi] = useState8("build");
@@ -37942,89 +38079,73 @@ function App() {
37942
38079
  }
37943
38080
  setPicker(null);
37944
38081
  }, [picker]);
37945
- const bannerColsRef = useRef6(null);
37946
- if (bannerColsRef.current === null) {
37947
- bannerColsRef.current = size.columns > 0 ? size.columns : 80;
37948
- }
37949
38082
  const banner = useMemo(() => {
37950
- const cols = bannerColsRef.current ?? 80;
37951
- const content = formatBannerWithLogoRight({
37952
- leftLines: [
37953
- `zelari-code \xB7 ${activeProviderSpec.id}/${activeModel}`,
37954
- `cwd: ${cwd}`,
37955
- `/help \xB7 /plan \xB7 /build \xB7 /view-plan \xB7 shift+tab mode`
37956
- ],
37957
- version: VERSION,
37958
- columns: cols,
37959
- compact: cols < 72
37960
- });
37961
38083
  return {
37962
38084
  id: "banner-once",
37963
38085
  role: "system",
37964
38086
  ts: 0,
37965
- content
38087
+ content: ""
37966
38088
  };
37967
- }, [activeProviderSpec.id, activeModel, cwd]);
38089
+ }, []);
37968
38090
  const [sidebarOpen, setSidebarOpen] = useState8(false);
37969
38091
  useEffect7(() => {
37970
38092
  setSidebarOpen((prev2) => sidebarVisibility(size.columns, size.rows, prev2));
37971
38093
  }, [size.columns, size.rows]);
37972
38094
  const staticKey = `${session.sessionId || "pre-bootstrap"}-${clearEpoch}`;
37973
38095
  const staticItems = session.sessionId ? [banner, ...session.messages] : [];
37974
- const dynamicMaxRows = Math.max(8, Math.min(18, size.rows - 2));
37975
- return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Static, { key: staticKey, items: staticItems }, (item) => renderMessage(item)), /* @__PURE__ */ React9.createElement(
37976
- Box8,
38096
+ return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Static, { key: staticKey, items: staticItems }, (item) => item.id === "banner-once" ? /* @__PURE__ */ React10.createElement(
38097
+ StartupBanner,
37977
38098
  {
37978
- flexDirection: "row",
37979
- width: size.columns > 0 ? size.columns : void 0,
37980
- height: dynamicMaxRows,
37981
- overflow: "hidden"
37982
- },
37983
- /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", flexGrow: 1, paddingX: 1, overflow: "hidden" }, /* @__PURE__ */ React9.createElement(LiveRegion, { live: session.live, busy, elapsedMs: timer.elapsedMs }), picker ? /* @__PURE__ */ React9.createElement(
37984
- SelectList,
37985
- {
37986
- title: picker.title,
37987
- items: picker.items,
37988
- onSelect: onPickerSelect,
37989
- onCancel: onPickerCancel,
37990
- maxVisible: Math.max(3, Math.min(8, size.rows - 12))
37991
- }
37992
- ) : /* @__PURE__ */ React9.createElement(
37993
- InputBar,
37994
- {
37995
- value: input,
37996
- onChange: setInput,
37997
- onSubmit: handleSubmit,
37998
- disabled: busy
37999
- }
38000
- ), /* @__PURE__ */ React9.createElement(
38001
- StatusBar,
38002
- {
38003
- model: activeModel,
38004
- provider: activeProviderSpec.id,
38005
- sessionId: session.sessionId ? session.sessionId.slice(0, 8) : "...",
38006
- sessionActive: session.sessionActive,
38007
- queueCount: chatTurn.queueCount,
38008
- busy,
38009
- mode,
38010
- phase: phase2,
38011
- cwd,
38012
- elapsedMs: timer.elapsedMs,
38013
- lastMs: timer.lastMs,
38014
- costUsd: sessionStats.totalCostUsd,
38015
- cachedTokens: sessionStats.cachedTokens,
38016
- contextUsed: sessionStats.totalTokens,
38017
- contextLimit: Number(process.env.ZELARI_CONTEXT_LIMIT) || 2e5,
38018
- brandVersion: VERSION
38019
- }
38020
- )),
38021
- sidebarOpen && /* @__PURE__ */ React9.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })
38022
- ));
38099
+ key: item.id,
38100
+ version: VERSION,
38101
+ providerId: activeProviderSpec.id,
38102
+ model: activeModel,
38103
+ cwd,
38104
+ columns: size.columns || 80,
38105
+ rows: size.rows || 24
38106
+ }
38107
+ ) : renderMessage(item)), /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "row" }, /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", flexGrow: 1, paddingX: 1 }, /* @__PURE__ */ React10.createElement(LiveRegion, { live: session.live, busy, elapsedMs: timer.elapsedMs }), picker ? /* @__PURE__ */ React10.createElement(
38108
+ SelectList,
38109
+ {
38110
+ title: picker.title,
38111
+ items: picker.items,
38112
+ onSelect: onPickerSelect,
38113
+ onCancel: onPickerCancel,
38114
+ maxVisible: Math.max(4, Math.min(10, size.rows - 10))
38115
+ }
38116
+ ) : /* @__PURE__ */ React10.createElement(
38117
+ InputBar,
38118
+ {
38119
+ value: input,
38120
+ onChange: setInput,
38121
+ onSubmit: handleSubmit,
38122
+ disabled: busy
38123
+ }
38124
+ ), /* @__PURE__ */ React10.createElement(
38125
+ StatusBar,
38126
+ {
38127
+ model: activeModel,
38128
+ provider: activeProviderSpec.id,
38129
+ sessionId: session.sessionId ? session.sessionId.slice(0, 8) : "...",
38130
+ sessionActive: session.sessionActive,
38131
+ queueCount: chatTurn.queueCount,
38132
+ busy,
38133
+ mode,
38134
+ phase: phase2,
38135
+ cwd,
38136
+ elapsedMs: timer.elapsedMs,
38137
+ lastMs: timer.lastMs,
38138
+ costUsd: sessionStats.totalCostUsd,
38139
+ cachedTokens: sessionStats.cachedTokens,
38140
+ contextUsed: sessionStats.contextTokens || 0,
38141
+ contextLimit: Number(process.env.ZELARI_CONTEXT_LIMIT) || 2e5
38142
+ }
38143
+ )), sidebarOpen && /* @__PURE__ */ React10.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })));
38023
38144
  }
38024
38145
 
38025
38146
  // src/cli/components/SplashScreen.tsx
38026
- import React10, { useEffect as useEffect8, useState as useState9 } from "react";
38027
- import { Box as Box9, Text as Text10, useInput as useInput3, useStdin as useStdin3 } from "ink";
38147
+ import React11, { useEffect as useEffect8, useState as useState9 } from "react";
38148
+ import { Box as Box10, Text as Text11, useInput as useInput3, useStdin as useStdin3 } from "ink";
38028
38149
  var SPLASH_DURATION_MS = 2e3;
38029
38150
  var EMBLEM_LARGE = ` =%@*-
38030
38151
  =%@@@@@#:
@@ -38160,8 +38281,8 @@ function Splash({ onDone, version: version2 }) {
38160
38281
  { isActive: isRawModeSupported === true }
38161
38282
  );
38162
38283
  if (!picked) return null;
38163
- return /* @__PURE__ */ React10.createElement(
38164
- Box9,
38284
+ return /* @__PURE__ */ React11.createElement(
38285
+ Box10,
38165
38286
  {
38166
38287
  flexDirection: "column",
38167
38288
  alignItems: "center",
@@ -38169,10 +38290,10 @@ function Splash({ onDone, version: version2 }) {
38169
38290
  width: columns,
38170
38291
  height: rows - 1
38171
38292
  },
38172
- /* @__PURE__ */ React10.createElement(Text10, { color: "cyan" }, picked.art),
38173
- /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text10, { bold: true, color: "white" }, "Z E L A R I C O D E")),
38174
- !picked.compact && /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, `${version2 ? `v${version2} \u2014 ` : ""}N-THEM Studio`),
38175
- !picked.compact && /* @__PURE__ */ React10.createElement(Text10, { dimColor: true, italic: true }, "press any key to skip")
38293
+ /* @__PURE__ */ React11.createElement(Text11, { color: "cyan" }, picked.art),
38294
+ /* @__PURE__ */ React11.createElement(Box10, { marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text11, { bold: true, color: "white" }, "Z E L A R I C O D E")),
38295
+ !picked.compact && /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, `${version2 ? `v${version2} \u2014 ` : ""}N-THEM Studio`),
38296
+ !picked.compact && /* @__PURE__ */ React11.createElement(Text11, { dimColor: true, italic: true }, "press any key to skip")
38176
38297
  );
38177
38298
  }
38178
38299
  function SplashGate({
@@ -38188,14 +38309,14 @@ function SplashGate({
38188
38309
  })
38189
38310
  );
38190
38311
  if (show) {
38191
- return /* @__PURE__ */ React10.createElement(Splash, { onDone: () => setShow(false), version: version2 });
38312
+ return /* @__PURE__ */ React11.createElement(Splash, { onDone: () => setShow(false), version: version2 });
38192
38313
  }
38193
- return /* @__PURE__ */ React10.createElement(React10.Fragment, null, children);
38314
+ return /* @__PURE__ */ React11.createElement(React11.Fragment, null, children);
38194
38315
  }
38195
38316
 
38196
38317
  // src/cli/components/PluginGate.tsx
38197
- import React11, { useEffect as useEffect9, useState as useState10, useCallback as useCallback6 } from "react";
38198
- import { Box as Box10, Text as Text11, useStdin as useStdin4 } from "ink";
38318
+ import React12, { useEffect as useEffect9, useState as useState10, useCallback as useCallback6 } from "react";
38319
+ import { Box as Box11, Text as Text12, useStdin as useStdin4 } from "ink";
38199
38320
  init_registry2();
38200
38321
  init_prefs();
38201
38322
  var CHOICE_INSTALL = "__install__";
@@ -38284,20 +38405,20 @@ function PluginGate({ cwd, children }) {
38284
38405
  [current, cwd, advance]
38285
38406
  );
38286
38407
  if (phase2 === "done") {
38287
- return /* @__PURE__ */ React11.createElement(React11.Fragment, null, children);
38408
+ return /* @__PURE__ */ React12.createElement(React12.Fragment, null, children);
38288
38409
  }
38289
38410
  if (phase2 === "detecting") {
38290
- return /* @__PURE__ */ React11.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React11.createElement(Text11, { dimColor: true }, "Checking for optional tool plugins\u2026"));
38411
+ return /* @__PURE__ */ React12.createElement(Box11, { paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "Checking for optional tool plugins\u2026"));
38291
38412
  }
38292
38413
  if (!current) {
38293
- return /* @__PURE__ */ React11.createElement(React11.Fragment, null, children);
38414
+ return /* @__PURE__ */ React12.createElement(React12.Fragment, null, children);
38294
38415
  }
38295
38416
  if (phase2 === "installing") {
38296
- 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));
38417
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, "\u23F3 Installing ", current.label, "\u2026"), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "npm install ", current.installScope === "global" ? "-g" : "-D", " ", current.npmPackage));
38297
38418
  }
38298
38419
  if (phase2 === "result") {
38299
38420
  const ok = result?.ok === true;
38300
- 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 }));
38421
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text12, { color: ok ? "green" : "red" }, ok ? "\u2713" : "\u2717", " ", ok ? "Installed" : "Install failed", ": ", current.label), !ok && result?.error ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, result.error) : null, ok && current.postInstallHint ? /* @__PURE__ */ React12.createElement(Text12, { color: "yellow" }, " \u2192 ", current.postInstallHint) : null, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "Press any key to continue\u2026"), /* @__PURE__ */ React12.createElement(ContinueKey, { onContinue: advance }));
38301
38422
  }
38302
38423
  const items = [
38303
38424
  {
@@ -38308,7 +38429,7 @@ function PluginGate({ cwd, children }) {
38308
38429
  { value: CHOICE_LATER, label: "Maybe later" },
38309
38430
  { value: CHOICE_NEVER, label: "Don't ask again" }
38310
38431
  ];
38311
- return /* @__PURE__ */ React11.createElement(
38432
+ return /* @__PURE__ */ React12.createElement(
38312
38433
  SelectList,
38313
38434
  {
38314
38435
  title: `Optional plugin missing: ${current.label}`,
@@ -38370,15 +38491,15 @@ function parseWizardFlags(argv) {
38370
38491
  }
38371
38492
 
38372
38493
  // src/cli/wizard/runWizard.tsx
38373
- import React13, { useEffect as useEffect10, useState as useState11 } from "react";
38374
- import { Box as Box12, Text as Text13 } from "ink";
38494
+ import React14, { useEffect as useEffect10, useState as useState11 } from "react";
38495
+ import { Box as Box13, Text as Text14 } from "ink";
38375
38496
  import { useInput as useInput4 } from "ink";
38376
38497
  init_keyStore();
38377
38498
  init_providerConfig();
38378
38499
 
38379
38500
  // src/cli/wizard/index.tsx
38380
- import React12 from "react";
38381
- import { Box as Box11, Text as Text12 } from "ink";
38501
+ import React13 from "react";
38502
+ import { Box as Box12, Text as Text13 } from "ink";
38382
38503
 
38383
38504
  // src/cli/wizard/useWizardState.ts
38384
38505
  init_providerConfig();
@@ -38494,16 +38615,16 @@ var API_KEY_OPTIONS = ["env", "keystore", "skip"];
38494
38615
 
38495
38616
  // src/cli/wizard/index.tsx
38496
38617
  function Frame({ children }) {
38497
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 }, children);
38618
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 }, children);
38498
38619
  }
38499
38620
  function Step(props) {
38500
- return /* @__PURE__ */ React12.createElement(Box11, null, /* @__PURE__ */ React12.createElement(Text12, { color: props.active ? "cyan" : "gray", inverse: props.active }, " ", props.index, "/", props.total, " ", props.name, " "));
38621
+ return /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Text13, { color: props.active ? "cyan" : "gray", inverse: props.active }, " ", props.index, "/", props.total, " ", props.name, " "));
38501
38622
  }
38502
38623
  function renderProviderList(providers, cursor) {
38503
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, providers.map((p3, i) => {
38624
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, providers.map((p3, i) => {
38504
38625
  const arrow = i === cursor ? "\u279C " : " ";
38505
38626
  const color = i === cursor ? "cyan" : void 0;
38506
- return /* @__PURE__ */ React12.createElement(Text12, { key: p3.id, color }, arrow, p3.displayName, " ", /* @__PURE__ */ React12.createElement(Text12, { color: "gray" }, "(", p3.id, ", uses env ", p3.envVar, ")"));
38627
+ return /* @__PURE__ */ React13.createElement(Text13, { key: p3.id, color }, arrow, p3.displayName, " ", /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "(", p3.id, ", uses env ", p3.envVar, ")"));
38507
38628
  }));
38508
38629
  }
38509
38630
  function renderApiKeyOptions(cursor) {
@@ -38512,18 +38633,18 @@ function renderApiKeyOptions(cursor) {
38512
38633
  keystore: "Save to local keyStore (encrypted)",
38513
38634
  skip: "Skip for now (chat will fail until key is set)"
38514
38635
  };
38515
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, API_KEY_OPTIONS.map((choice, i) => {
38636
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, API_KEY_OPTIONS.map((choice, i) => {
38516
38637
  const arrow = i === cursor ? "\u279C " : " ";
38517
38638
  const color = i === cursor ? "cyan" : void 0;
38518
- return /* @__PURE__ */ React12.createElement(Text12, { key: choice, color }, arrow, labels[choice]);
38639
+ return /* @__PURE__ */ React13.createElement(Text13, { key: choice, color }, arrow, labels[choice]);
38519
38640
  }));
38520
38641
  }
38521
38642
  function Wizard({ state: state2, providers }) {
38522
38643
  const s = state2.state;
38523
38644
  if (s.committed) {
38524
- 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)")));
38645
+ return /* @__PURE__ */ React13.createElement(Frame, null, /* @__PURE__ */ React13.createElement(Text13, { color: "green" }, "\u2713 Setup complete!"), /* @__PURE__ */ React13.createElement(Text13, null, "Provider: ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan" }, s.providerId), " | ", "Model: ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan" }, s.model), " | ", "API key: ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan" }, s.apiKeyChoice ?? "n/a")), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "Launching zelari-code\u2026 (press any key)")));
38525
38646
  }
38526
- 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."))));
38647
+ return /* @__PURE__ */ React13.createElement(Frame, null, /* @__PURE__ */ React13.createElement(Text13, { color: "cyan", bold: true }, "zelari-code v", VERSION, " \u2014 first-time setup"), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1, marginBottom: 1, flexDirection: "row" }, /* @__PURE__ */ React13.createElement(Step, { index: 1, total: 5, name: "welcome", active: s.step === "welcome" }), /* @__PURE__ */ React13.createElement(Step, { index: 2, total: 5, name: "provider", active: s.step === "provider" }), /* @__PURE__ */ React13.createElement(Step, { index: 3, total: 5, name: "model", active: s.step === "model" }), /* @__PURE__ */ React13.createElement(Step, { index: 4, total: 5, name: "apikey", active: s.step === "apikey" }), /* @__PURE__ */ React13.createElement(Step, { index: 5, total: 5, name: "confirm", active: s.step === "confirm" })), s.step === "welcome" && /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text13, null, "Welcome! Let's get you coding in under two minutes."), /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "We'll pick a provider, default model, and how to handle your API key."), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text13, null, "Press "), /* @__PURE__ */ React13.createElement(Text13, { color: "cyan", inverse: true }, " Enter "), /* @__PURE__ */ React13.createElement(Text13, null, " to continue, or "), /* @__PURE__ */ React13.createElement(Text13, { color: "red", inverse: true }, " Q "), /* @__PURE__ */ React13.createElement(Text13, null, " to quit (re-run with "), /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "--no-wizard"), /* @__PURE__ */ React13.createElement(Text13, null, " to skip later)."))), s.step === "provider" && /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text13, null, "Choose your LLM provider:"), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, renderProviderList(providers, s.providerCursor)), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "model" && /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text13, null, "Model for ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan" }, s.providerId), ":"), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React13.createElement(Text13, null, s.model ?? "(empty)")), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text13, null, "Press ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan", inverse: true }, " Enter "), "to accept, or type a new name. ", /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "(default kept on empty input)")))), s.step === "apikey" && /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text13, null, "How should we handle the API key?"), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, renderApiKeyOptions(s.apiKeyCursor)), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text13, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "confirm" && /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text13, null, "Confirm your setup:"), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React13.createElement(Text13, null, "Provider: ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan" }, s.providerId)), /* @__PURE__ */ React13.createElement(Text13, null, "Model: ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan" }, s.model)), /* @__PURE__ */ React13.createElement(Text13, null, "API key: ", /* @__PURE__ */ React13.createElement(Text13, { color: "cyan" }, s.apiKeyChoice ?? "(unset)"))), /* @__PURE__ */ React13.createElement(Box12, { marginTop: 1 }, /* @__PURE__ */ React13.createElement(Text13, null, "Press ", /* @__PURE__ */ React13.createElement(Text13, { color: "green", inverse: true }, " Enter "), "to save and launch, or ", /* @__PURE__ */ React13.createElement(Text13, { color: "yellow", inverse: true }, " B "), "to go back."))));
38527
38648
  }
38528
38649
 
38529
38650
  // src/cli/wizard/runWizard.tsx
@@ -38599,9 +38720,9 @@ function RunWizard(_props) {
38599
38720
  }
38600
38721
  });
38601
38722
  if (wiz.state.committed) {
38602
- return /* @__PURE__ */ React13.createElement(PostCommitBridge, null);
38723
+ return /* @__PURE__ */ React14.createElement(PostCommitBridge, null);
38603
38724
  }
38604
- return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React13.createElement(Wizard, { state: wiz, providers: PROVIDERS }));
38725
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React14.createElement(Wizard, { state: wiz, providers: PROVIDERS }));
38605
38726
  }
38606
38727
  function PostCommitBridge() {
38607
38728
  const [showApp, setShowApp] = useState11(false);
@@ -38610,22 +38731,26 @@ function PostCommitBridge() {
38610
38731
  return () => clearTimeout(t);
38611
38732
  }, []);
38612
38733
  if (showApp) {
38613
- return /* @__PURE__ */ React13.createElement(App, null);
38734
+ return /* @__PURE__ */ React14.createElement(App, null);
38614
38735
  }
38615
- 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.")));
38736
+ return /* @__PURE__ */ React14.createElement(Box13, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React14.createElement(Box13, { borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, flexDirection: "column" }, /* @__PURE__ */ React14.createElement(Text14, { color: "green", bold: true }, "\u2713 Setup complete! Launching zelari-code\u2026"), /* @__PURE__ */ React14.createElement(Text14, { color: "gray" }, "Press Ctrl+C any time to exit.")));
38616
38737
  }
38617
38738
 
38618
38739
  // src/cli/headless.ts
38619
38740
  init_keyStore();
38620
38741
  init_providerConfig();
38621
38742
  init_openai_compatible();
38743
+ init_phase();
38622
38744
  function parseHeadlessFlags(argv) {
38623
38745
  if (!argv.includes("--headless")) {
38624
38746
  return { options: null };
38625
38747
  }
38626
38748
  let task;
38627
38749
  let output = "json";
38628
- let useCouncil = false;
38750
+ let mode = "agent";
38751
+ let phase2 = "build";
38752
+ let modeExplicit = false;
38753
+ let councilFlag = false;
38629
38754
  let provider;
38630
38755
  let model;
38631
38756
  for (let i = 0; i < argv.length; i++) {
@@ -38646,7 +38771,30 @@ function parseHeadlessFlags(argv) {
38646
38771
  task = argv[i + 1];
38647
38772
  i++;
38648
38773
  } else if (arg === "--council") {
38649
- useCouncil = true;
38774
+ councilFlag = true;
38775
+ } else if (arg === "--mode") {
38776
+ const next = argv[i + 1];
38777
+ const parsed = next ? parseMode(next) : null;
38778
+ if (!parsed) {
38779
+ return {
38780
+ options: null,
38781
+ error: `--mode requires 'agent', 'council', or 'zelari', got '${next ?? "(missing)"}'`
38782
+ };
38783
+ }
38784
+ mode = parsed;
38785
+ modeExplicit = true;
38786
+ i++;
38787
+ } else if (arg === "--phase") {
38788
+ const next = argv[i + 1];
38789
+ const parsed = next ? parsePhase(next) : null;
38790
+ if (!parsed) {
38791
+ return {
38792
+ options: null,
38793
+ error: `--phase requires 'plan' or 'build', got '${next ?? "(missing)"}'`
38794
+ };
38795
+ }
38796
+ phase2 = parsed;
38797
+ i++;
38650
38798
  } else if (arg === "--provider") {
38651
38799
  provider = argv[i + 1];
38652
38800
  i++;
@@ -38655,11 +38803,27 @@ function parseHeadlessFlags(argv) {
38655
38803
  i++;
38656
38804
  }
38657
38805
  }
38806
+ if (councilFlag && !modeExplicit) {
38807
+ mode = "council";
38808
+ } else if (councilFlag && modeExplicit && mode !== "council") {
38809
+ return {
38810
+ options: null,
38811
+ error: `--council conflicts with --mode ${mode}`
38812
+ };
38813
+ }
38658
38814
  if (!task || task.trim().length === 0) {
38659
38815
  return { options: null, error: "--headless requires --task <prompt>" };
38660
38816
  }
38661
38817
  return {
38662
- options: { task, output, useCouncil, provider, model }
38818
+ options: {
38819
+ task,
38820
+ output,
38821
+ mode,
38822
+ phase: phase2,
38823
+ useCouncil: mode === "council",
38824
+ provider,
38825
+ model
38826
+ }
38663
38827
  };
38664
38828
  }
38665
38829
  async function resolveHeadlessKey(providerId) {
@@ -38694,7 +38858,10 @@ init_harness();
38694
38858
  init_toolRegistry();
38695
38859
  init_skills2();
38696
38860
  init_envNumber();
38861
+ init_phaseState();
38862
+ init_phase();
38697
38863
  async function runHeadless(opts) {
38864
+ setPhase(opts.phase ?? "build");
38698
38865
  const { provider, model } = resolveHeadlessProvider(opts);
38699
38866
  const key = await resolveHeadlessKey(provider);
38700
38867
  if ("error" in key) {
@@ -38708,14 +38875,34 @@ async function runHeadless(opts) {
38708
38875
  baseUrl: key.baseUrl,
38709
38876
  model
38710
38877
  });
38711
- if (opts.useCouncil) {
38878
+ const mode = opts.mode ?? (opts.useCouncil ? "council" : "agent");
38879
+ if (opts.output === "json") {
38880
+ emitEvent({
38881
+ type: "log",
38882
+ message: `[headless] mode=${mode} phase=${opts.phase ?? "build"} provider=${provider} model=${model}`
38883
+ });
38884
+ } else {
38885
+ process.stderr.write(
38886
+ `[zelari-code --headless] mode=${mode} phase=${describePhase(opts.phase ?? "build")}
38887
+ `
38888
+ );
38889
+ }
38890
+ if (mode === "zelari") {
38891
+ return runHeadlessZelari(opts, provider, model, providerStream);
38892
+ }
38893
+ if (mode === "council" || opts.useCouncil) {
38712
38894
  return runHeadlessCouncil(opts, provider, model, providerStream);
38713
38895
  }
38714
38896
  return runHeadlessSingle(opts, provider, model, providerStream);
38715
38897
  }
38898
+ function planModeFromOpts(opts) {
38899
+ return (opts.phase ?? "build") === "plan";
38900
+ }
38716
38901
  async function runHeadlessSingle(opts, provider, model, providerStream) {
38717
38902
  const sessionId = crypto.randomUUID();
38718
- const { registry: toolRegistry } = createBuiltinToolRegistry();
38903
+ const { registry: toolRegistry } = createBuiltinToolRegistry({
38904
+ planMode: planModeFromOpts(opts)
38905
+ });
38719
38906
  const tools = toolRegistry.toOpenAITools().map((t) => ({
38720
38907
  name: t.function.name,
38721
38908
  description: t.function.description,
@@ -38738,10 +38925,6 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
38738
38925
  color: "#00d9a3",
38739
38926
  avatar: "\u25C6",
38740
38927
  tools: toolNames,
38741
- // v1.7.0 fix (agy audit): include platform/shell context in the role
38742
- // prompt so the agent knows its cwd and shell — previously this was
38743
- // empty (systemPrompt: '') and the agent lacked the platform block
38744
- // the TUI single-agent path gets.
38745
38928
  systemPrompt: [
38746
38929
  "# Platform",
38747
38930
  `platform: ${process.platform}`,
@@ -38750,7 +38933,10 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
38750
38933
  "# Working Directory",
38751
38934
  `You are running in: ${process.cwd()}`,
38752
38935
  "All relative file paths are resolved against this directory.",
38753
- "The shell is NON-INTERACTIVE (stdin closed): pass non-interactive flags (--yes, --force, --template)."
38936
+ "The shell is NON-INTERACTIVE (stdin closed): pass non-interactive flags (--yes, --force, --template).",
38937
+ "",
38938
+ `# Work phase: ${opts.phase ?? "build"}`,
38939
+ (opts.phase ?? "build") === "plan" ? "PLAN phase: explore and design only. Do not write project source files (write_file/edit_file/bash blocked). Plan artifacts under .zelari are allowed." : "BUILD phase: full tools; implement changes."
38754
38940
  ].join("\n")
38755
38941
  };
38756
38942
  systemPrompt = buildSystemPrompt(headlessRole, {
@@ -38829,18 +39015,11 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
38829
39015
  if (finalReason === "error") return 3;
38830
39016
  return exitCode;
38831
39017
  }
38832
- async function runHeadlessCouncil(opts, provider, model, providerStream) {
38833
- const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
38834
- const sessionId = crypto.randomUUID();
38835
- const { registry: toolRegistry } = createBuiltinToolRegistry();
38836
- const workspaceCtx = {
38837
- projectRoot: process.cwd(),
38838
- getActiveBranch: () => null
38839
- };
39018
+ async function buildCouncilToolRegistry(planMode) {
39019
+ const { registry: toolRegistry } = createBuiltinToolRegistry({ planMode });
38840
39020
  const { createWorkspaceContext: createWorkspaceContext2, createWorkspaceStubs: createWorkspaceStubs2 } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
38841
39021
  const { createWorkspaceToolRegistry: createWorkspaceToolRegistry2 } = await Promise.resolve().then(() => (init_toolRegistry2(), toolRegistry_exports2));
38842
39022
  const { setWorkspaceStubs: setWorkspaceStubs2 } = await Promise.resolve().then(() => (init_skills2(), skills_exports));
38843
- const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
38844
39023
  const realCtx = createWorkspaceContext2();
38845
39024
  const realReg = createWorkspaceToolRegistry2(realCtx);
38846
39025
  for (const name of realReg.list()) {
@@ -38848,6 +39027,13 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
38848
39027
  if (td) toolRegistry.register(td);
38849
39028
  }
38850
39029
  setWorkspaceStubs2(createWorkspaceStubs2(realCtx));
39030
+ return { toolRegistry, workspaceCtx: realCtx };
39031
+ }
39032
+ async function runHeadlessCouncil(opts, provider, model, providerStream) {
39033
+ const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
39034
+ const sessionId = crypto.randomUUID();
39035
+ const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts));
39036
+ const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
38851
39037
  const feedbackStore = new FeedbackStore2();
38852
39038
  let exitCode = 0;
38853
39039
  try {
@@ -38858,7 +39044,8 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
38858
39044
  providerStream,
38859
39045
  sessionId,
38860
39046
  tools: toolRegistry,
38861
- feedbackStore
39047
+ feedbackStore,
39048
+ runMode: planModeFromOpts(opts) ? "design-phase" : "implementation"
38862
39049
  })) {
38863
39050
  if (opts.output === "json") {
38864
39051
  emitEvent(event);
@@ -38879,6 +39066,378 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
38879
39066
  }
38880
39067
  return exitCode;
38881
39068
  }
39069
+ async function runHeadlessZelari(opts, provider, model, providerStream) {
39070
+ const projectRoot = process.cwd();
39071
+ const { buildMissionBrief: buildMissionBrief2 } = await Promise.resolve().then(() => (init_council(), council_exports));
39072
+ const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
39073
+ const { getMemoryBackend: getMemoryBackend2 } = await Promise.resolve().then(() => (init_fileBackend(), fileBackend_exports));
39074
+ const { runZelariMission: runZelariMission2 } = await Promise.resolve().then(() => (init_zelariMission(), zelariMission_exports));
39075
+ const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
39076
+ const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
39077
+ const { runPostCouncilHook: runPostCouncilHook2 } = await Promise.resolve().then(() => (init_postCouncilHook(), postCouncilHook_exports));
39078
+ const brief = buildMissionBrief2({
39079
+ userMessage: opts.task,
39080
+ hasPlan: hasWorkspacePlan2(projectRoot)
39081
+ });
39082
+ const memory = await getMemoryBackend2(projectRoot);
39083
+ const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(planModeFromOpts(opts));
39084
+ const feedbackStore = new FeedbackStore2();
39085
+ const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
39086
+ default: 30,
39087
+ min: 1
39088
+ });
39089
+ const emit = (message) => {
39090
+ if (opts.output === "json") {
39091
+ emitEvent({ type: "log", message });
39092
+ } else {
39093
+ process.stderr.write(message + "\n");
39094
+ }
39095
+ };
39096
+ emit(`[zelari] mission brief
39097
+ ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMvp?.title }, null, 0)}`);
39098
+ let exitCode = 0;
39099
+ try {
39100
+ const state2 = await runZelariMission2(opts.task, brief, {
39101
+ projectRoot,
39102
+ memory,
39103
+ emit,
39104
+ runSlice: async ({ userMessage: slicePrompt, runMode, ragContext }) => {
39105
+ const sessionId = crypto.randomUUID();
39106
+ const fullPrompt = ragContext ? `${slicePrompt}
39107
+
39108
+ ## Memory context
39109
+ ${ragContext}` : slicePrompt;
39110
+ let synthesisText = "";
39111
+ let writeCount = 0;
39112
+ let chairmanErrored = false;
39113
+ let membersCompleted = 0;
39114
+ for await (const event of dispatchCouncil2(fullPrompt, {
39115
+ apiKey: "REDACTED",
39116
+ model,
39117
+ provider: "openai-compatible",
39118
+ providerStream,
39119
+ sessionId,
39120
+ tools: toolRegistry,
39121
+ feedbackStore,
39122
+ runMode: planModeFromOpts(opts) ? "design-phase" : runMode,
39123
+ maxToolCallsChairman: chairmanBudget
39124
+ })) {
39125
+ if (opts.output === "json") {
39126
+ emitEvent(event);
39127
+ } else if (event.type === "message_delta") {
39128
+ process.stdout.write(event.delta);
39129
+ }
39130
+ if (event.type === "message_delta" && typeof event.delta === "string") {
39131
+ synthesisText += event.delta;
39132
+ }
39133
+ if (event.type === "tool_execution_end") {
39134
+ const name = event.toolName ?? event.name ?? "";
39135
+ if (name === "write_file" || name === "edit_file" || name === "apply_diff") {
39136
+ writeCount += 1;
39137
+ }
39138
+ }
39139
+ if (event.type === "agent_end") {
39140
+ membersCompleted += 1;
39141
+ if (event.reason === "error") chairmanErrored = true;
39142
+ }
39143
+ if (event.type === "error" && event.severity === "fatal") {
39144
+ chairmanErrored = true;
39145
+ exitCode = 2;
39146
+ }
39147
+ }
39148
+ let completionOk = false;
39149
+ let degraded = false;
39150
+ try {
39151
+ const { detectDegradedRun: detectDegradedRun2 } = await Promise.resolve().then(() => (init_council(), council_exports));
39152
+ const d = detectDegradedRun2({
39153
+ chairmanErrored,
39154
+ councilAborted: false,
39155
+ luciferWriteCount: writeCount,
39156
+ synthesisText,
39157
+ runMode
39158
+ });
39159
+ degraded = d.degraded;
39160
+ const hook = await runPostCouncilHook2(workspaceCtx, {
39161
+ runMode,
39162
+ userMessage: opts.task,
39163
+ synthesisText: synthesisText || void 0,
39164
+ degradedRun: d.degraded,
39165
+ degradedReasons: d.reasons
39166
+ });
39167
+ completionOk = hook.completion?.completion?.ok ?? false;
39168
+ if (completionOk) {
39169
+ emit(`[zelari] slice completion ok`);
39170
+ }
39171
+ } catch {
39172
+ }
39173
+ return {
39174
+ completionOk,
39175
+ ran: membersCompleted > 0 || synthesisText.length > 0,
39176
+ synthesisText: synthesisText || void 0,
39177
+ writeCount,
39178
+ degraded
39179
+ };
39180
+ }
39181
+ });
39182
+ if (state2.status === "error") exitCode = exitCode || 3;
39183
+ else if (state2.status === "success") exitCode = 0;
39184
+ else if (state2.status === "stalled" || state2.status === "stopped") exitCode = exitCode || 0;
39185
+ } catch (err) {
39186
+ process.stderr.write(
39187
+ `[zelari-code --headless] zelari error: ${err instanceof Error ? err.message : String(err)}
39188
+ `
39189
+ );
39190
+ return 2;
39191
+ } finally {
39192
+ await memory.close().catch(() => void 0);
39193
+ }
39194
+ return exitCode;
39195
+ }
39196
+
39197
+ // src/cli/desktopConfig.ts
39198
+ init_keyStore();
39199
+ init_providerConfig();
39200
+ init_updater();
39201
+ function wantsPrintConfig(argv) {
39202
+ return argv.includes("--print-config");
39203
+ }
39204
+ function wantsSetKey(argv) {
39205
+ return argv.includes("--set-key");
39206
+ }
39207
+ function wantsDiscoverModels(argv) {
39208
+ return argv.includes("--discover-models");
39209
+ }
39210
+ function parseSetConfigFlags(argv) {
39211
+ if (!argv.includes("--set-config")) {
39212
+ return { request: null };
39213
+ }
39214
+ let provider;
39215
+ let model;
39216
+ let endpoint;
39217
+ let endpointClear = false;
39218
+ for (let i = 0; i < argv.length; i++) {
39219
+ const arg = argv[i];
39220
+ if (arg === "--provider") {
39221
+ provider = argv[i + 1];
39222
+ i++;
39223
+ } else if (arg === "--model") {
39224
+ model = argv[i + 1];
39225
+ i++;
39226
+ } else if (arg === "--endpoint") {
39227
+ endpoint = argv[i + 1];
39228
+ i++;
39229
+ } else if (arg === "--endpoint-clear") {
39230
+ endpointClear = true;
39231
+ }
39232
+ }
39233
+ if (!provider && !model && !endpoint && !endpointClear) {
39234
+ return {
39235
+ request: null,
39236
+ error: "--set-config requires --provider, --model, --endpoint, and/or --endpoint-clear"
39237
+ };
39238
+ }
39239
+ if (provider !== void 0 && provider.trim().length === 0) {
39240
+ return { request: null, error: "--provider cannot be empty" };
39241
+ }
39242
+ if (model !== void 0 && model.trim().length === 0) {
39243
+ return { request: null, error: "--model cannot be empty" };
39244
+ }
39245
+ if (endpoint !== void 0 && endpoint.trim().length === 0) {
39246
+ return { request: null, error: "--endpoint cannot be empty" };
39247
+ }
39248
+ if (endpoint && endpointClear) {
39249
+ return { request: null, error: "--endpoint and --endpoint-clear conflict" };
39250
+ }
39251
+ return {
39252
+ request: {
39253
+ provider: provider?.trim(),
39254
+ model: model?.trim(),
39255
+ endpoint: endpoint?.trim(),
39256
+ endpointClear: endpointClear || void 0
39257
+ }
39258
+ };
39259
+ }
39260
+ function parseSetKeyFlags(argv) {
39261
+ if (!argv.includes("--set-key")) {
39262
+ return { request: null };
39263
+ }
39264
+ let provider;
39265
+ let key;
39266
+ for (let i = 0; i < argv.length; i++) {
39267
+ const arg = argv[i];
39268
+ if (arg === "--provider") {
39269
+ provider = argv[i + 1];
39270
+ i++;
39271
+ } else if (arg === "--key") {
39272
+ key = argv[i + 1];
39273
+ i++;
39274
+ }
39275
+ }
39276
+ if (!provider || provider.trim().length === 0) {
39277
+ return { request: null, error: "--set-key requires --provider <id>" };
39278
+ }
39279
+ if (!key || key.trim().length === 0) {
39280
+ return { request: null, error: "--set-key requires --key <secret>" };
39281
+ }
39282
+ return {
39283
+ request: {
39284
+ provider: provider.trim(),
39285
+ key: key.trim()
39286
+ }
39287
+ };
39288
+ }
39289
+ function parseDiscoverModelsFlags(argv) {
39290
+ if (!argv.includes("--discover-models")) {
39291
+ return { provider: null, present: false };
39292
+ }
39293
+ let provider;
39294
+ for (let i = 0; i < argv.length; i++) {
39295
+ if (argv[i] === "--provider") {
39296
+ provider = argv[i + 1];
39297
+ i++;
39298
+ }
39299
+ }
39300
+ return {
39301
+ present: true,
39302
+ provider: provider?.trim() || null
39303
+ };
39304
+ }
39305
+ function buildDesktopConfigSnapshot() {
39306
+ const config2 = getProviderConfig();
39307
+ const providers = PROVIDERS.map((p3) => {
39308
+ const cached2 = getCachedModels(p3.id);
39309
+ const models = cached2?.models.map((m) => m.id) ?? [];
39310
+ const defaultModel = config2.modelByProvider[p3.id] ?? "";
39311
+ if (defaultModel && !models.includes(defaultModel)) {
39312
+ models.unshift(defaultModel);
39313
+ }
39314
+ const custom2 = getCustomEndpoint(p3.id);
39315
+ const builtin = p3.baseUrl ?? null;
39316
+ return {
39317
+ id: p3.id,
39318
+ displayName: p3.displayName,
39319
+ hasKey: !!resolveApiKey(p3.id),
39320
+ envVar: p3.envVar,
39321
+ models,
39322
+ defaultModel,
39323
+ endpoint: custom2 ?? null,
39324
+ baseUrl: custom2 ?? builtin
39325
+ };
39326
+ });
39327
+ return {
39328
+ activeProviderId: config2.activeProviderId,
39329
+ modelByProvider: { ...config2.modelByProvider },
39330
+ providers,
39331
+ cliVersion: getCurrentVersion(),
39332
+ configPaths: {
39333
+ provider: getProviderConfigPath(),
39334
+ keys: getKeyStorePath()
39335
+ }
39336
+ };
39337
+ }
39338
+ function printDesktopConfig() {
39339
+ const snap = buildDesktopConfigSnapshot();
39340
+ process.stdout.write(JSON.stringify(snap, null, 2) + "\n");
39341
+ }
39342
+ function applySetConfig(req) {
39343
+ try {
39344
+ const config2 = getProviderConfig();
39345
+ let targetProvider = req.provider ?? config2.activeProviderId;
39346
+ if (req.provider) {
39347
+ const exists = PROVIDERS.some((p3) => p3.id === req.provider);
39348
+ if (!exists) {
39349
+ return {
39350
+ ok: false,
39351
+ error: `unknown provider '${req.provider}'. Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`
39352
+ };
39353
+ }
39354
+ setActiveProviderId(req.provider);
39355
+ targetProvider = req.provider;
39356
+ }
39357
+ if (req.endpointClear) {
39358
+ clearCustomEndpoint(targetProvider);
39359
+ }
39360
+ if (req.endpoint) {
39361
+ setCustomEndpoint(targetProvider, req.endpoint);
39362
+ }
39363
+ if (req.model) {
39364
+ setModelForProvider(targetProvider, req.model);
39365
+ }
39366
+ const after = getProviderConfig();
39367
+ const ep = getCustomEndpoint(after.activeProviderId);
39368
+ return {
39369
+ ok: true,
39370
+ message: `activeProvider=${after.activeProviderId} model=${after.modelByProvider[after.activeProviderId]}` + (ep ? ` endpoint=${ep}` : "")
39371
+ };
39372
+ } catch (err) {
39373
+ return {
39374
+ ok: false,
39375
+ error: err instanceof Error ? err.message : String(err)
39376
+ };
39377
+ }
39378
+ }
39379
+ function applySetKey(req) {
39380
+ try {
39381
+ const exists = PROVIDERS.some((p3) => p3.id === req.provider);
39382
+ if (!exists) {
39383
+ return {
39384
+ ok: false,
39385
+ error: `unknown provider '${req.provider}'. Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`
39386
+ };
39387
+ }
39388
+ setApiKey(req.provider, req.key);
39389
+ setActiveProviderId(req.provider);
39390
+ return {
39391
+ ok: true,
39392
+ provider: req.provider,
39393
+ masked: maskKey(req.key)
39394
+ };
39395
+ } catch (err) {
39396
+ return {
39397
+ ok: false,
39398
+ error: err instanceof Error ? err.message : String(err)
39399
+ };
39400
+ }
39401
+ }
39402
+ var DISCOVERABLE = [
39403
+ "grok",
39404
+ "glm",
39405
+ "minimax",
39406
+ "deepseek",
39407
+ "openai-compatible"
39408
+ ];
39409
+ async function runDiscoverModels(providerArg) {
39410
+ const config2 = getProviderConfig();
39411
+ const providerId = (providerArg ?? config2.activeProviderId).trim();
39412
+ if (!DISCOVERABLE.includes(providerId)) {
39413
+ if (providerId === "custom") {
39414
+ } else {
39415
+ return {
39416
+ ok: false,
39417
+ error: `provider '${providerId}' is not discoverable. Use: ${DISCOVERABLE.join(", ")}`
39418
+ };
39419
+ }
39420
+ }
39421
+ const discoveryId = providerId === "custom" ? "openai-compatible" : providerId;
39422
+ try {
39423
+ const entry = await discoverModelsForProvider(discoveryId);
39424
+ return {
39425
+ ok: true,
39426
+ payload: {
39427
+ ok: true,
39428
+ provider: providerId,
39429
+ models: entry.models.map((m) => m.id),
39430
+ fetchedAt: entry.fetchedAt,
39431
+ baseUrl: entry.baseUrl
39432
+ }
39433
+ };
39434
+ } catch (err) {
39435
+ return {
39436
+ ok: false,
39437
+ error: err instanceof Error ? err.message : String(err)
39438
+ };
39439
+ }
39440
+ }
38882
39441
 
38883
39442
  // src/cli/skillsMd.ts
38884
39443
  init_skills2();
@@ -39088,10 +39647,85 @@ function pickRootComponent() {
39088
39647
  }
39089
39648
  if (argv.includes("--help") || argv.includes("-h")) {
39090
39649
  console.log(
39091
- "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"
39650
+ "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 --mode agent|council|zelari Dispatch mode (default: agent)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --print-config Print provider/model config as JSON (no secrets)\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\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"
39092
39651
  );
39093
39652
  process.exit(0);
39094
39653
  }
39654
+ if (wantsPrintConfig(argv)) {
39655
+ try {
39656
+ printDesktopConfig();
39657
+ process.exit(0);
39658
+ } catch (err) {
39659
+ console.error(
39660
+ `[zelari-code --print-config] ${err instanceof Error ? err.message : String(err)}`
39661
+ );
39662
+ process.exit(1);
39663
+ }
39664
+ }
39665
+ const setConfigParse = parseSetConfigFlags(argv);
39666
+ if (setConfigParse.error) {
39667
+ console.error(`[zelari-code --set-config] ${setConfigParse.error}`);
39668
+ process.exit(1);
39669
+ }
39670
+ if (setConfigParse.request) {
39671
+ const result = applySetConfig(setConfigParse.request);
39672
+ if (!result.ok) {
39673
+ console.error(`[zelari-code --set-config] ${result.error}`);
39674
+ process.exit(1);
39675
+ }
39676
+ console.log(JSON.stringify({ ok: true, message: result.message }));
39677
+ process.exit(0);
39678
+ }
39679
+ if (wantsSetKey(argv)) {
39680
+ const keyParse = parseSetKeyFlags(argv);
39681
+ if (keyParse.error || !keyParse.request) {
39682
+ console.error(
39683
+ `[zelari-code --set-key] ${keyParse.error ?? "invalid arguments"}`
39684
+ );
39685
+ process.exit(1);
39686
+ }
39687
+ const result = applySetKey(keyParse.request);
39688
+ if (!result.ok) {
39689
+ console.error(`[zelari-code --set-key] ${result.error}`);
39690
+ process.exit(1);
39691
+ }
39692
+ console.log(
39693
+ JSON.stringify({
39694
+ ok: true,
39695
+ provider: result.provider,
39696
+ masked: result.masked
39697
+ })
39698
+ );
39699
+ process.exit(0);
39700
+ }
39701
+ if (wantsDiscoverModels(argv)) {
39702
+ const disc = parseDiscoverModelsFlags(argv);
39703
+ void runDiscoverModels(disc.provider).then(async (result) => {
39704
+ if (!result.ok) {
39705
+ console.error(JSON.stringify({ ok: false, error: result.error }));
39706
+ await new Promise((r) => setImmediate(r));
39707
+ process.exit(1);
39708
+ return;
39709
+ }
39710
+ console.log(JSON.stringify(result.payload));
39711
+ try {
39712
+ await getMetricsLogger().flush();
39713
+ } catch {
39714
+ }
39715
+ await new Promise((r) => setImmediate(r));
39716
+ process.exit(0);
39717
+ }).catch(async (err) => {
39718
+ console.error(
39719
+ JSON.stringify({
39720
+ ok: false,
39721
+ error: err instanceof Error ? err.message : String(err)
39722
+ })
39723
+ );
39724
+ await new Promise((r) => setImmediate(r));
39725
+ process.exit(1);
39726
+ });
39727
+ return { kind: "done" };
39728
+ }
39095
39729
  const headlessParse = parseHeadlessFlags(argv);
39096
39730
  if (headlessParse.options !== null) {
39097
39731
  return { kind: "headless", headlessOpts: headlessParse.options };
@@ -39109,16 +39743,16 @@ function pickRootComponent() {
39109
39743
  });
39110
39744
  if (decision.shouldRun) {
39111
39745
  console.error(`[zelari-code] starting wizard: ${decision.reason}`);
39112
- return { kind: "wizard", element: React14.createElement(RunWizard) };
39746
+ return { kind: "wizard", element: React15.createElement(RunWizard) };
39113
39747
  }
39114
39748
  return {
39115
39749
  kind: "app",
39116
- element: React14.createElement(
39750
+ element: React15.createElement(
39117
39751
  SplashGate,
39118
39752
  { version: VERSION },
39119
- React14.createElement(PluginGate, {
39753
+ React15.createElement(PluginGate, {
39120
39754
  cwd: process.cwd(),
39121
- children: React14.createElement(App)
39755
+ children: React15.createElement(App)
39122
39756
  })
39123
39757
  )
39124
39758
  };
@@ -39144,10 +39778,27 @@ function main() {
39144
39778
  runPreflight();
39145
39779
  loadUserSkills();
39146
39780
  if (picked.kind === "headless") {
39147
- void runHeadless(picked.headlessOpts).then((code) => {
39148
- void getMetricsLogger().flush().catch(() => {
39149
- });
39781
+ void runHeadless(picked.headlessOpts).then(async (code) => {
39782
+ try {
39783
+ await getMetricsLogger().flush();
39784
+ } catch {
39785
+ }
39786
+ try {
39787
+ const { closeMcpClients: closeMcpClients2 } = await Promise.resolve().then(() => (init_mcpManager(), mcpManager_exports));
39788
+ closeMcpClients2();
39789
+ } catch {
39790
+ }
39791
+ await new Promise((resolve) => setImmediate(resolve));
39150
39792
  process.exit(code);
39793
+ }).catch(async (err) => {
39794
+ console.error(
39795
+ `[zelari-code --headless] fatal: ${err instanceof Error ? err.message : String(err)}`
39796
+ );
39797
+ try {
39798
+ await getMetricsLogger().flush();
39799
+ } catch {
39800
+ }
39801
+ process.exit(2);
39151
39802
  });
39152
39803
  return;
39153
39804
  }