@asathinkeroops/nova-code 0.1.0-beta.6 → 0.1.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +439 -230
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1395,6 +1395,20 @@ var settingsSchema = z2.object({
1395
1395
  additionalDirectories: [],
1396
1396
  autoMode: { llmClassifier: true, classifierTimeoutMs: 8e3 }
1397
1397
  }),
1398
+ // Workspace trust. On startup nova checks whether the workspace it was
1399
+ // launched in has been granted file access; an untrusted workspace prompts
1400
+ // the user to confirm, and declining exits. Trust is recorded HERE (in the
1401
+ // user-global config, which a project cannot write) rather than in any
1402
+ // project-checked-in file — mirroring Claude Code's `~/.claude.json` model,
1403
+ // so a cloned repo can never mark itself trusted. `trustedRoots` are absolute
1404
+ // canonical paths; a workspace equal to or nested under any of them is
1405
+ // trusted (so trusting a repo root covers its subdirectories). Users may
1406
+ // pre-seed roots by hand. Set `enabled: false` to restore the legacy
1407
+ // "workspace is always trusted on launch" behavior.
1408
+ trust: z2.object({
1409
+ enabled: z2.boolean().default(true),
1410
+ trustedRoots: z2.array(z2.string().min(1)).default([])
1411
+ }).default({ enabled: true, trustedRoots: [] }),
1398
1412
  transcript: z2.object({
1399
1413
  enabled: z2.boolean().default(true)
1400
1414
  }).default({ enabled: true }),
@@ -2417,7 +2431,7 @@ function buildSystemPrompt(workspace, memory, sessionId, skillsBlock = "", langu
2417
2431
  - Run long-lived commands (dev servers, watchers, builds) with runInBackground; it returns immediately and its output is delivered to you automatically when the command finishes \u2014 no need to poll.
2418
2432
  - Load specialized knowledge with loadSkill.
2419
2433
  - Delegate focused subtasks to parallel sub-agents with createSubAgent (type: explore = read-only retrieval, plan = read-only planning, general-purpose = full tools).
2420
- - Don't guess file paths. When unsure whether a file exists or where it lives, locate it with glob/grep before read \u2014 a read on a wrong path just wastes a turn.
2434
+ - Don't guess file paths. When unsure whether a file or directory exists, locate it with glob/grep/ls before acting \u2014 a read, write, edit, or mkdir on a wrong path just wastes a turn or causes damage.
2421
2435
  - Respond in ${language} by default, even when a tool or sub-agent returns content in another language \u2014 relay and summarize it in ${language}. Preserve that exact script and regional variant; do not switch it.
2422
2436
 
2423
2437
  When I ask you to commit, follow the repo's own version-control conventions:
@@ -5298,13 +5312,15 @@ ${xmlEscape(payload)}
5298
5312
 
5299
5313
  // ../../packages/tools/src/builtin/read.ts
5300
5314
  import { readFile as readFile10 } from "fs/promises";
5301
- import { extname as extname2, resolve as resolve9 } from "path";
5315
+ import { basename as basename3, extname as extname2, resolve as resolve9 } from "path";
5302
5316
  import { z as z13 } from "zod";
5303
5317
  import * as XLSX from "xlsx";
5318
+ import { extractText as extractText2 } from "unpdf";
5304
5319
  var MAX_CHARS = 2e5;
5305
5320
  var MAX_LINE_CHARS = 16e3;
5306
5321
  var LINE_NO_WIDTH = 6;
5307
5322
  var EXCEL_EXTENSIONS = /* @__PURE__ */ new Set([".xlsx", ".xls", ".xlsm", ".xlsb", ".ods"]);
5323
+ var MAX_PDF_BYTES = 3e7;
5308
5324
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
5309
5325
  var MAX_IMAGE_BYTES = 2e7;
5310
5326
  var IMAGE_MAGIC = {
@@ -5352,6 +5368,28 @@ function renderLines(lines, total, startIdx, endIdx, path3) {
5352
5368
  }
5353
5369
  return body;
5354
5370
  }
5371
+ function paginateLines(raw, input, path3) {
5372
+ const lines = raw.match(/[^\n]*\n|[^\n]+$/g) ?? [];
5373
+ const total = lines.length;
5374
+ const startLine = input.offset ?? 1;
5375
+ const startIdx = startLine - 1;
5376
+ if (startIdx >= total && total > 0) {
5377
+ return {
5378
+ output: `read: offset ${startLine} is past end of file (it has ${total} lines)`,
5379
+ isError: true
5380
+ };
5381
+ }
5382
+ let endIdx = startIdx;
5383
+ let chars = 0;
5384
+ while (endIdx < total) {
5385
+ if (input.limit !== void 0 && endIdx - startIdx >= input.limit) break;
5386
+ const cost = Math.min(lines[endIdx].length, MAX_LINE_CHARS);
5387
+ if (chars > 0 && chars + cost > MAX_CHARS) break;
5388
+ chars += cost;
5389
+ endIdx += 1;
5390
+ }
5391
+ return { output: renderLines(lines, total, startIdx, endIdx, path3) };
5392
+ }
5355
5393
  async function readText(abs, input, path3) {
5356
5394
  let raw;
5357
5395
  try {
@@ -5379,26 +5417,60 @@ async function readText(abs, input, path3) {
5379
5417
  isError: true
5380
5418
  };
5381
5419
  }
5382
- const lines = raw.match(/[^\n]*\n|[^\n]+$/g) ?? [];
5383
- const total = lines.length;
5384
- const startLine = input.offset ?? 1;
5385
- const startIdx = startLine - 1;
5386
- if (startIdx >= total && total > 0) {
5420
+ return paginateLines(raw, input, path3);
5421
+ }
5422
+ async function readPdf(abs, input, path3) {
5423
+ let buf;
5424
+ try {
5425
+ buf = await readFile10(abs);
5426
+ } catch (err) {
5427
+ const code = err.code;
5428
+ if (code === "ENOENT") {
5429
+ return {
5430
+ output: `read failed: no such file: ${path3}. It may not exist at this path (or anywhere) \u2014 use glob/grep to locate it rather than guessing another path.`,
5431
+ isError: true
5432
+ };
5433
+ }
5434
+ if (code === "EISDIR") {
5435
+ return {
5436
+ output: `read failed: ${path3} is a directory, not a file. Use glob to list its contents or grep to search inside it.`,
5437
+ isError: true
5438
+ };
5439
+ }
5440
+ const msg = err instanceof Error ? err.message : String(err);
5441
+ return { output: `read failed: ${msg}`, isError: true };
5442
+ }
5443
+ if (buf.length > MAX_PDF_BYTES) {
5444
+ const mb = (buf.length / 1e6).toFixed(1);
5445
+ const cap = (MAX_PDF_BYTES / 1e6).toFixed(0);
5387
5446
  return {
5388
- output: `read: offset ${startLine} is past end of file (it has ${total} lines)`,
5447
+ output: `read failed: ${path3} is ${mb} MB (PDF cap is ${cap} MB). Use bash with a command-line tool (e.g. \`pdftotext\`, \`qpdf\`) to split or extract it.`,
5389
5448
  isError: true
5390
5449
  };
5391
5450
  }
5392
- let endIdx = startIdx;
5393
- let chars = 0;
5394
- while (endIdx < total) {
5395
- if (input.limit !== void 0 && endIdx - startIdx >= input.limit) break;
5396
- const cost = Math.min(lines[endIdx].length, MAX_LINE_CHARS);
5397
- if (chars > 0 && chars + cost > MAX_CHARS) break;
5398
- chars += cost;
5399
- endIdx += 1;
5451
+ let totalPages;
5452
+ let pages;
5453
+ try {
5454
+ const extracted = await extractText2(new Uint8Array(buf), { mergePages: false });
5455
+ totalPages = extracted.totalPages;
5456
+ pages = extracted.text;
5457
+ } catch (err) {
5458
+ const msg = err instanceof Error ? err.message : String(err);
5459
+ return { output: `read failed: cannot parse ${path3} as a PDF: ${msg}`, isError: true };
5400
5460
  }
5401
- return { output: renderLines(lines, total, startIdx, endIdx, path3) };
5461
+ const meta = `PDF "${basename3(path3)}" \u2014 ${totalPages} page${totalPages === 1 ? "" : "s"}`;
5462
+ if (pages.every((p) => p.trim() === "")) {
5463
+ return {
5464
+ output: `${meta}
5465
+ read: no extractable text \u2014 this PDF is likely scanned or image-only. Use bash with an OCR tool (e.g. \`ocrmypdf\`, \`tesseract\`) to extract its text.`
5466
+ };
5467
+ }
5468
+ const combined = pages.map((p, i) => `[Page ${i + 1}]
5469
+ ${p.trim()}`).join("\n\n");
5470
+ const result = paginateLines(combined, input, path3);
5471
+ if (result.isError) return result;
5472
+ return { output: `${meta}
5473
+ ${result.output}` };
5402
5474
  }
5403
5475
  function resolveSheet(sheetNames, spec) {
5404
5476
  if (spec === void 0) return sheetNames[0] ?? null;
@@ -5564,7 +5636,7 @@ async function readImage(abs, ext, path3) {
5564
5636
  var readTool = {
5565
5637
  definition: {
5566
5638
  name: "read",
5567
- description: "Read a text file, spreadsheet, or image from disk. For text files, output is line-numbered (`<line>\\t<text>`, `cat -n` style, 1-based); returns up to `limit` lines (and at most ~200K characters) per call. If more remains, the result tells you the exact read(path, offset) call to continue from. The line-number prefix is display only \u2014 strip it before passing text to `edit`. For Excel files (.xlsx/.xls/.xlsm/.xlsb/.ods), each row is rendered as a TSV line with a metadata header; use the optional `sheet` parameter to select a sheet. For image files (.png/.jpg/.jpeg/.gif/.webp), returns the image as a base64 block alongside metadata \u2014 only when the active model supports image input; capped at 20 MB.",
5639
+ description: "Read a text file, spreadsheet, PDF, or image from disk. For text files, output is line-numbered (`<line>\\t<text>`, `cat -n` style, 1-based); returns up to `limit` lines (and at most ~200K characters) per call. If more remains, the result tells you the exact read(path, offset) call to continue from. The line-number prefix is display only \u2014 strip it before passing text to `edit`. For Excel files (.xlsx/.xls/.xlsm/.xlsb/.ods), each row is rendered as a TSV line with a metadata header; use the optional `sheet` parameter to select a sheet. For PDF files (.pdf), extracted text is returned line-numbered with a `[Page N]` marker before each page and the same offset/limit paging; scanned/image-only PDFs have no extractable text (capped at 30 MB). For image files (.png/.jpg/.jpeg/.gif/.webp), returns the image as a base64 block alongside metadata \u2014 only when the active model supports image input; capped at 20 MB.",
5568
5640
  inputSchema: inputSchema8
5569
5641
  },
5570
5642
  async run(rawInput, ctx) {
@@ -5583,6 +5655,9 @@ var readTool = {
5583
5655
  if (EXCEL_EXTENSIONS.has(ext)) {
5584
5656
  return readExcel(abs, input, input.path);
5585
5657
  }
5658
+ if (ext === ".pdf") {
5659
+ return readPdf(abs, input, input.path);
5660
+ }
5586
5661
  return readText(abs, input, input.path);
5587
5662
  }
5588
5663
  };
@@ -5649,11 +5724,14 @@ function parseSkillFile(text) {
5649
5724
  if (typeof descRaw !== "string" || descRaw.trim().length === 0) {
5650
5725
  return { error: "missing description" };
5651
5726
  }
5652
- const description = descRaw.length > DESCRIPTION_MAX ? descRaw.slice(0, DESCRIPTION_MAX) : descRaw;
5653
- const triggersRaw = meta["triggers"];
5654
- const triggers = Array.isArray(triggersRaw) ? triggersRaw.filter((x) => typeof x === "string") : [];
5727
+ const whenRaw = meta["when_to_use"];
5728
+ const whenToUse = typeof whenRaw === "string" ? whenRaw.trim() : "";
5729
+ const combined = whenToUse ? `${descRaw} ${whenToUse}` : descRaw;
5730
+ const description = combined.length > DESCRIPTION_MAX ? combined.slice(0, DESCRIPTION_MAX) : combined;
5731
+ const disableModelInvocation = parseBool(meta["disable-model-invocation"], false);
5732
+ const userInvocable = parseBool(meta["user-invocable"], true);
5655
5733
  const body = normalized.slice(fm[0].length).trimStart();
5656
- return { ok: { name, description, triggers, body } };
5734
+ return { ok: { name, description, disableModelInvocation, userInvocable, body } };
5657
5735
  }
5658
5736
  function parseFrontMatter2(src) {
5659
5737
  const out = {};
@@ -5698,6 +5776,13 @@ function parseScalar2(v) {
5698
5776
  }
5699
5777
  return v;
5700
5778
  }
5779
+ function parseBool(v, dflt) {
5780
+ if (typeof v !== "string") return dflt;
5781
+ const s = v.trim().toLowerCase();
5782
+ if (s === "true") return true;
5783
+ if (s === "false") return false;
5784
+ return dflt;
5785
+ }
5701
5786
  function scan(r, logger) {
5702
5787
  const targets = [];
5703
5788
  for (const d of r.projectDirs) targets.push({ kind: "project", root: resolve10(r.cwd, d) });
@@ -5734,7 +5819,8 @@ function scan(r, logger) {
5734
5819
  list.push({
5735
5820
  name: parsed.ok.name,
5736
5821
  description: parsed.ok.description,
5737
- triggers: parsed.ok.triggers,
5822
+ disableModelInvocation: parsed.ok.disableModelInvocation,
5823
+ userInvocable: parsed.ok.userInvocable,
5738
5824
  location: dir
5739
5825
  });
5740
5826
  seen.add(parsed.ok.name);
@@ -8226,7 +8312,7 @@ Working directory: ${workspace}
8226
8312
  - Use tools to complete the task yourself. Work autonomously; do not ask the parent for clarification unless you are truly blocked.
8227
8313
  - You run in isolation. The parent sees ONLY your final message \u2014 it cannot see your intermediate steps or tool output. End by reporting results concisely: what you did, key findings, and concrete file paths / follow-ups.
8228
8314
  - Stay within the assigned task. Do not spawn further sub-agents.
8229
- - Don't guess file paths. When unsure whether a file exists or where it lives, locate it with glob/grep before read.
8315
+ - Don't guess file paths. When unsure whether a file or directory exists, locate it with glob/grep/ls before acting \u2014 read, write, edit, or mkdir.
8230
8316
  - Write your final report in the language of the task you were given, matching its exact script and regional variant (e.g. Simplified vs Traditional Chinese \u2014 don't switch a Simplified-Chinese task to Traditional), not the language of these instructions.
8231
8317
  ${extra}
8232
8318
  Act, don't explain.
@@ -9833,7 +9919,16 @@ async function writeStamp(dir) {
9833
9919
  async function git(cwd, args, logger) {
9834
9920
  logger?.debug({ args }, "nova-code-guide: git");
9835
9921
  try {
9836
- await execFileP2("git", [...args], { cwd });
9922
+ const p = execFileP2("git", [...args], { cwd });
9923
+ const child = p.child;
9924
+ if (child) {
9925
+ const unref = (s) => s?.unref?.();
9926
+ child.unref();
9927
+ unref(child.stdin);
9928
+ unref(child.stdout);
9929
+ unref(child.stderr);
9930
+ }
9931
+ await p;
9837
9932
  } catch (err) {
9838
9933
  if (isEnoent(err)) {
9839
9934
  throw new GuideProvisionError(
@@ -10732,6 +10827,7 @@ function loadSkillCommandsInto(registry, opts) {
10732
10827
  const taken = new Set(registry.list().map((c) => c.name));
10733
10828
  let added = 0;
10734
10829
  for (const item of items) {
10830
+ if (!item.userInvocable) continue;
10735
10831
  const cmd = {
10736
10832
  name: item.name,
10737
10833
  description: item.description,
@@ -10965,7 +11061,7 @@ import { render } from "ink";
10965
11061
 
10966
11062
  // src/ui/app.tsx
10967
11063
  import React12 from "react";
10968
- import { Box as Box14, Text as Text14 } from "ink";
11064
+ import { Box as Box15, Text as Text15 } from "ink";
10969
11065
  import { useShallow as useShallow3 } from "zustand/react/shallow";
10970
11066
 
10971
11067
  // src/ui/input-box.tsx
@@ -11834,20 +11930,24 @@ import { Box as Box4, Text as Text4 } from "ink";
11834
11930
 
11835
11931
  // src/ui/logo.ts
11836
11932
  var LOGO = [
11837
- "\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588",
11838
- "\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
11839
- "\u2588\u2588 \u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 ",
11840
- "\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
11841
- "\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588"
11933
+ " \xB7 \u22C6 \u2726 \u22C6 \xB7 ",
11934
+ " \u22C6 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u22C6 ",
11935
+ " \u2727 \xB7 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \xB7 \u2727 ",
11936
+ "\u2726 \u207A \u2588\u2588 \u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u207A \u2726",
11937
+ " \xB7 \u2727 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2727 \xB7 ",
11938
+ " \u22C6 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u22C6 ",
11939
+ " \u207A \xB7 \u22C6 \xB7 \u2726 \u207A "
11842
11940
  ];
11843
11941
  var LOGO_GRADIENT = [
11942
+ [140, 246, 255],
11844
11943
  [0, 238, 255],
11845
11944
  [90, 160, 255],
11846
11945
  [170, 110, 245],
11847
11946
  [230, 80, 210],
11848
- [255, 60, 170]
11947
+ [255, 60, 170],
11948
+ [255, 140, 215]
11849
11949
  ];
11850
- var LOGO_FALLBACK = [accent, blue, magenta, magenta, magenta];
11950
+ var LOGO_FALLBACK = [accent, accent, blue, magenta, magenta, magenta, magenta];
11851
11951
  function bannerLine(line, row) {
11852
11952
  if (useTruecolor) {
11853
11953
  const stop = LOGO_GRADIENT[row];
@@ -11951,10 +12051,28 @@ function SetupView({ state }) {
11951
12051
  ] });
11952
12052
  }
11953
12053
 
12054
+ // src/ui/trust-view.tsx
12055
+ import { Box as Box5, Text as Text5 } from "ink";
12056
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
12057
+ function TrustView({ state }) {
12058
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
12059
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text5, { children: [
12060
+ /* @__PURE__ */ jsx5(Text5, { color: ACCENT_HEX, children: ">_" }),
12061
+ " Nova Code",
12062
+ " ",
12063
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `(v${state.version})` })
12064
+ ] }) }),
12065
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: LOGO.map((line, i) => /* @__PURE__ */ jsx5(Text5, { color: LOGO_ROW_HEX[i] ?? ACCENT_HEX, children: line }, i)) }),
12066
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: ACCENT_HEX, children: "Do you trust the files in this folder?" }) }),
12067
+ /* @__PURE__ */ jsx5(Text5, { children: state.workspace }),
12068
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: state.lines.map((line, i) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: line }, i)) })
12069
+ ] });
12070
+ }
12071
+
11954
12072
  // src/ui/status-line.tsx
11955
12073
  import React3 from "react";
11956
- import { Box as Box5, Text as Text5 } from "ink";
11957
- import { basename as basename3 } from "path";
12074
+ import { Box as Box6, Text as Text6 } from "ink";
12075
+ import { basename as basename4 } from "path";
11958
12076
  import { useShallow } from "zustand/react/shallow";
11959
12077
 
11960
12078
  // src/ui/status-format.ts
@@ -12032,17 +12150,17 @@ function fitSegments(segments, maxWidth, sep3 = " | ") {
12032
12150
  }
12033
12151
 
12034
12152
  // src/ui/status-line.tsx
12035
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
12153
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
12036
12154
  function SegmentRow({
12037
12155
  segments,
12038
12156
  termCols
12039
12157
  }) {
12040
12158
  const shown = fitSegments(segments, Math.max(0, termCols - 2));
12041
- return /* @__PURE__ */ jsxs4(Box5, { children: [
12042
- /* @__PURE__ */ jsx5(Text5, { children: " " }),
12043
- shown.map((seg, i) => /* @__PURE__ */ jsxs4(React3.Fragment, { children: [
12044
- i > 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " | " }) : null,
12045
- /* @__PURE__ */ jsxs4(Text5, { color: seg.color, children: [
12159
+ return /* @__PURE__ */ jsxs5(Box6, { children: [
12160
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
12161
+ shown.map((seg, i) => /* @__PURE__ */ jsxs5(React3.Fragment, { children: [
12162
+ i > 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " | " }) : null,
12163
+ /* @__PURE__ */ jsxs5(Text6, { color: seg.color, children: [
12046
12164
  seg.icon,
12047
12165
  " ",
12048
12166
  seg.text
@@ -12083,15 +12201,15 @@ function StatusLine({ store, shellMode = false }) {
12083
12201
  }))
12084
12202
  );
12085
12203
  if (shellMode) {
12086
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12087
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { color: SHELL_MODE_INDICATOR.color, children: ` ${SHELL_MODE_INDICATOR.label}` }) }),
12088
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { children: " " }) })
12204
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12205
+ /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text6, { color: SHELL_MODE_INDICATOR.color, children: ` ${SHELL_MODE_INDICATOR.label}` }) }),
12206
+ /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text6, { children: " " }) })
12089
12207
  ] });
12090
12208
  }
12091
12209
  if (copyNotice) {
12092
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12093
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { color: copyNoticeTone === "warn" ? "red" : "green", children: ` ${copyNotice}` }) }),
12094
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { children: " " }) })
12210
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12211
+ /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text6, { color: copyNoticeTone === "warn" ? "red" : "green", children: ` ${copyNotice}` }) }),
12212
+ /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text6, { children: " " }) })
12095
12213
  ] });
12096
12214
  }
12097
12215
  const main = [];
@@ -12106,7 +12224,7 @@ function StatusLine({ store, shellMode = false }) {
12106
12224
  main.push({ icon: "\u25CB", text: `${contextBar(pct)} ${pct}%`, color: "yellow" });
12107
12225
  }
12108
12226
  if (banner?.cwd) {
12109
- main.push({ icon: "\u25C8", text: basename3(banner.cwd) || banner.cwd, color: "green" });
12227
+ main.push({ icon: "\u25C8", text: basename4(banner.cwd) || banner.cwd, color: "green" });
12110
12228
  }
12111
12229
  if (gitBranch) {
12112
12230
  main.push({ icon: "\u2387", text: gitBranch, color: "blue" });
@@ -12152,15 +12270,15 @@ function StatusLine({ store, shellMode = false }) {
12152
12270
  color: "#d97706"
12153
12271
  });
12154
12272
  }
12155
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12156
- /* @__PURE__ */ jsx5(SegmentRow, { segments: main, termCols }),
12157
- /* @__PURE__ */ jsx5(SegmentRow, { segments: usage, termCols })
12273
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12274
+ /* @__PURE__ */ jsx6(SegmentRow, { segments: main, termCols }),
12275
+ /* @__PURE__ */ jsx6(SegmentRow, { segments: usage, termCols })
12158
12276
  ] });
12159
12277
  }
12160
12278
 
12161
12279
  // src/ui/picker.tsx
12162
12280
  import { useState as useState2 } from "react";
12163
- import { Box as Box6, Text as Text6, useInput as useInput2, useStdout as useStdout2 } from "ink";
12281
+ import { Box as Box7, Text as Text7, useInput as useInput2, useStdout as useStdout2 } from "ink";
12164
12282
 
12165
12283
  // src/ui/selection.ts
12166
12284
  function sliceVisualCols(line, startCol, endCol) {
@@ -12256,7 +12374,7 @@ function highlightLines(lines, rect) {
12256
12374
  }
12257
12375
 
12258
12376
  // src/ui/picker.tsx
12259
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
12377
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
12260
12378
  function highlightRow(text, width) {
12261
12379
  const tw = visibleWidth(text);
12262
12380
  const padded = tw < width ? text + " ".repeat(width - tw) : text;
@@ -12359,12 +12477,12 @@ function PickList({ opts, onResolve, panelWidth }) {
12359
12477
  const text = rendered[i - windowStart];
12360
12478
  const body = i === selected && useColor ? highlightRow(text, barWidth) : text;
12361
12479
  rows.push(
12362
- /* @__PURE__ */ jsx6(Text6, { ...fullWidth ? { wrap: "truncate-end" } : {}, children: body }, i)
12480
+ /* @__PURE__ */ jsx7(Text7, { ...fullWidth ? { wrap: "truncate-end" } : {}, children: body }, i)
12363
12481
  );
12364
12482
  }
12365
12483
  const borderProps = overlayBorderProps(bordered, opts.topRuleColor);
12366
- return /* @__PURE__ */ jsxs5(
12367
- Box6,
12484
+ return /* @__PURE__ */ jsxs6(
12485
+ Box7,
12368
12486
  {
12369
12487
  flexDirection: "column",
12370
12488
  marginTop: 1,
@@ -12372,10 +12490,10 @@ function PickList({ opts, onResolve, panelWidth }) {
12372
12490
  ...fullWidth ? { width: cols } : {},
12373
12491
  ...borderProps,
12374
12492
  children: [
12375
- opts.header ? /* @__PURE__ */ jsx6(Text6, { children: opts.header }) : null,
12493
+ opts.header ? /* @__PURE__ */ jsx7(Text7, { children: opts.header }) : null,
12376
12494
  rows,
12377
- indicator ? /* @__PURE__ */ jsx6(Text6, { children: indicator }) : null,
12378
- opts.footer ? /* @__PURE__ */ jsx6(Text6, { children: opts.footer }) : null
12495
+ indicator ? /* @__PURE__ */ jsx7(Text7, { children: indicator }) : null,
12496
+ opts.footer ? /* @__PURE__ */ jsx7(Text7, { children: opts.footer }) : null
12379
12497
  ]
12380
12498
  }
12381
12499
  );
@@ -12431,13 +12549,13 @@ function ScrollViewer({ opts, onResolve, panelWidth }) {
12431
12549
  for (let i = 0; i < visible.length; i++) {
12432
12550
  const line = visible[i];
12433
12551
  const body = line.bg && useColor ? tintRow(line.text, barWidth, line.bg) : line.text;
12434
- rows.push(/* @__PURE__ */ jsx6(Text6, { children: (line.gutter ?? "") + body }, start + i));
12552
+ rows.push(/* @__PURE__ */ jsx7(Text7, { children: (line.gutter ?? "") + body }, start + i));
12435
12553
  }
12436
12554
  const indicator = lines.length > pageSize ? useColor ? dim(` (${start + 1}\u2013${end}/${lines.length})`) : ` (${start + 1}\u2013${end}/${lines.length})` : null;
12437
12555
  const fullWidth = !bordered && !!opts.topRuleColor;
12438
12556
  const borderProps = overlayBorderProps(bordered, opts.topRuleColor);
12439
- return /* @__PURE__ */ jsxs5(
12440
- Box6,
12557
+ return /* @__PURE__ */ jsxs6(
12558
+ Box7,
12441
12559
  {
12442
12560
  flexDirection: "column",
12443
12561
  marginTop: 1,
@@ -12445,10 +12563,10 @@ function ScrollViewer({ opts, onResolve, panelWidth }) {
12445
12563
  ...fullWidth ? { width: cols } : {},
12446
12564
  ...borderProps,
12447
12565
  children: [
12448
- opts.header ? /* @__PURE__ */ jsx6(Text6, { children: opts.header }) : null,
12566
+ opts.header ? /* @__PURE__ */ jsx7(Text7, { children: opts.header }) : null,
12449
12567
  rows,
12450
- indicator ? /* @__PURE__ */ jsx6(Text6, { children: indicator }) : null,
12451
- opts.footer ? /* @__PURE__ */ jsx6(Text6, { children: opts.footer }) : null
12568
+ indicator ? /* @__PURE__ */ jsx7(Text7, { children: indicator }) : null,
12569
+ opts.footer ? /* @__PURE__ */ jsx7(Text7, { children: opts.footer }) : null
12452
12570
  ]
12453
12571
  }
12454
12572
  );
@@ -12496,11 +12614,11 @@ function PickHorizontal({ opts, onResolve, panelWidth }) {
12496
12614
  items.forEach((item, i) => {
12497
12615
  const badge = opts.badge?.(item) ?? null;
12498
12616
  const label = ` ${opts.label(item)} `;
12499
- if (i > 0) cells.push(/* @__PURE__ */ jsx6(Text6, { children: separator }, `sep-${i}`));
12617
+ if (i > 0) cells.push(/* @__PURE__ */ jsx7(Text7, { children: separator }, `sep-${i}`));
12500
12618
  cells.push(
12501
- /* @__PURE__ */ jsxs5(Text6, { children: [
12502
- /* @__PURE__ */ jsx6(Text6, { inverse: i === selected, dimColor: i !== selected, children: label }),
12503
- badge ? /* @__PURE__ */ jsx6(Text6, { color: ACCENT_HEX, bold: true, children: ` ${badge} ` }) : null
12619
+ /* @__PURE__ */ jsxs6(Text7, { children: [
12620
+ /* @__PURE__ */ jsx7(Text7, { inverse: i === selected, dimColor: i !== selected, children: label }),
12621
+ badge ? /* @__PURE__ */ jsx7(Text7, { color: ACCENT_HEX, bold: true, children: ` ${badge} ` }) : null
12504
12622
  ] }, i)
12505
12623
  );
12506
12624
  });
@@ -12508,8 +12626,8 @@ function PickHorizontal({ opts, onResolve, panelWidth }) {
12508
12626
  const fullWidth = !bordered && !!opts.topRuleColor;
12509
12627
  const cols = panelWidth ?? stdout?.columns ?? 80;
12510
12628
  const borderProps = overlayBorderProps(bordered, opts.topRuleColor);
12511
- return /* @__PURE__ */ jsxs5(
12512
- Box6,
12629
+ return /* @__PURE__ */ jsxs6(
12630
+ Box7,
12513
12631
  {
12514
12632
  flexDirection: "column",
12515
12633
  marginTop: 1,
@@ -12518,11 +12636,11 @@ function PickHorizontal({ opts, onResolve, panelWidth }) {
12518
12636
  ...fullWidth ? { width: cols } : {},
12519
12637
  ...borderProps,
12520
12638
  children: [
12521
- opts.header ? /* @__PURE__ */ jsx6(Text6, { children: opts.header }) : null,
12522
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
12523
- /* @__PURE__ */ jsx6(Box6, { children: cells }),
12524
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
12525
- opts.footer ? /* @__PURE__ */ jsx6(Text6, { children: opts.footer }) : null
12639
+ opts.header ? /* @__PURE__ */ jsx7(Text7, { children: opts.header }) : null,
12640
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
12641
+ /* @__PURE__ */ jsx7(Box7, { children: cells }),
12642
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
12643
+ opts.footer ? /* @__PURE__ */ jsx7(Text7, { children: opts.footer }) : null
12526
12644
  ]
12527
12645
  }
12528
12646
  );
@@ -12530,12 +12648,12 @@ function PickHorizontal({ opts, onResolve, panelWidth }) {
12530
12648
 
12531
12649
  // src/ui/viewport.tsx
12532
12650
  import React11 from "react";
12533
- import { Box as Box13, Text as Text13 } from "ink";
12651
+ import { Box as Box14, Text as Text14 } from "ink";
12534
12652
  import { useShallow as useShallow2 } from "zustand/react/shallow";
12535
12653
 
12536
12654
  // src/ui/approval.tsx
12537
12655
  import { useState as useState3 } from "react";
12538
- import { Box as Box7, Text as Text7, useInput as useInput3 } from "ink";
12656
+ import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
12539
12657
 
12540
12658
  // src/ui/measure.ts
12541
12659
  import wrapAnsi2 from "wrap-ansi";
@@ -13092,7 +13210,7 @@ function wrapThinkingBody(text, width) {
13092
13210
  return wrapAnsi(trimmed, bodyWidth, { hard: true, wordWrap: false, trim: false }).split("\n");
13093
13211
  }
13094
13212
  function renderThinking(text, label, width, collapsed = false, expanded = false) {
13095
- const head = `${magenta("\u273B")} ${dim(`thinking${label ? ` \xB7 ${label}` : ""}`)}`;
13213
+ const head = `${magenta("\u2726")} ${dim(`thinking${label ? ` \xB7 ${label}` : ""}`)}`;
13096
13214
  const lines = wrapThinkingBody(text, width);
13097
13215
  if (lines.length === 0) return head;
13098
13216
  const overflow = collapsed ? Math.max(0, lines.length - THINKING_COLLAPSED_MAX_LINES) : 0;
@@ -13115,7 +13233,7 @@ function thinkingToggleLineIndex(item, width) {
13115
13233
  return 1 + shown;
13116
13234
  }
13117
13235
  function renderRedactedThinking(label) {
13118
- return `${magenta("\u273B")} ${dim(`thinking${label ? ` \xB7 ${label}` : ""} (redacted)`)}`;
13236
+ return `${magenta("\u2726")} ${dim(`thinking${label ? ` \xB7 ${label}` : ""} (redacted)`)}`;
13119
13237
  }
13120
13238
  function renderCard(card) {
13121
13239
  const color = cardColor[card.kind];
@@ -13387,7 +13505,7 @@ function genericUseHeader(use) {
13387
13505
  return header(use.name, dim(trim(compact)));
13388
13506
  }
13389
13507
  var DETAIL_MARK = {
13390
- thinking: "\u273B",
13508
+ thinking: "\u2726",
13391
13509
  tool_use: "\u2692",
13392
13510
  final: "\u2192"
13393
13511
  };
@@ -13586,7 +13704,7 @@ function sliceLines(items, width, offset, viewportRows) {
13586
13704
  }
13587
13705
 
13588
13706
  // src/ui/approval.tsx
13589
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
13707
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
13590
13708
  var TOOL_PROMPTS = {
13591
13709
  read: "Allow reading this file?",
13592
13710
  write: "Allow writing this file?",
@@ -13703,8 +13821,8 @@ function ApprovalPrompt({
13703
13821
  });
13704
13822
  const inner = approvalInnerWidth(width);
13705
13823
  const { text: detail, truncated } = clampDetail(describeToolInput(input.input));
13706
- return /* @__PURE__ */ jsxs6(
13707
- Box7,
13824
+ return /* @__PURE__ */ jsxs7(
13825
+ Box8,
13708
13826
  {
13709
13827
  flexDirection: "column",
13710
13828
  marginTop: 1,
@@ -13713,31 +13831,31 @@ function ApprovalPrompt({
13713
13831
  borderStyle: "round",
13714
13832
  borderColor: "gray",
13715
13833
  children: [
13716
- /* @__PURE__ */ jsx7(Text7, { children: promptFor(input.tool) }),
13717
- /* @__PURE__ */ jsx7(Text7, { children: " " }),
13834
+ /* @__PURE__ */ jsx8(Text8, { children: promptFor(input.tool) }),
13835
+ /* @__PURE__ */ jsx8(Text8, { children: " " }),
13718
13836
  input.tool === "bash" ? (
13719
13837
  // Mirror the message-stream bash rendering: `● bash` header with the
13720
13838
  // command under a `⎿` gutter, so heredocs/long one-liners preview the
13721
13839
  // way they'll print in the transcript instead of as a flat line.
13722
- /* @__PURE__ */ jsxs6(Fragment2, { children: [
13723
- /* @__PURE__ */ jsx7(Text7, { children: BASH_HEADER }),
13724
- /* @__PURE__ */ jsx7(Text7, { children: renderCommandBody(detail, inner) }),
13725
- truncated ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: `${BASH_BODY_INDENT}\u2026 (truncated)` }) : null
13840
+ /* @__PURE__ */ jsxs7(Fragment2, { children: [
13841
+ /* @__PURE__ */ jsx8(Text8, { children: BASH_HEADER }),
13842
+ /* @__PURE__ */ jsx8(Text8, { children: renderCommandBody(detail, inner) }),
13843
+ truncated ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: `${BASH_BODY_INDENT}\u2026 (truncated)` }) : null
13726
13844
  ] })
13727
- ) : /* @__PURE__ */ jsxs6(Text7, { children: [
13728
- /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
13845
+ ) : /* @__PURE__ */ jsxs7(Text8, { children: [
13846
+ /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
13729
13847
  input.tool,
13730
13848
  " "
13731
13849
  ] }),
13732
- /* @__PURE__ */ jsx7(Text7, { color: ACCENT_HEX, children: detail }),
13733
- truncated ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " \u2026 (truncated)" }) : null
13850
+ /* @__PURE__ */ jsx8(Text8, { color: ACCENT_HEX, children: detail }),
13851
+ truncated ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \u2026 (truncated)" }) : null
13734
13852
  ] }),
13735
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: OPTIONS.map((opt, i) => {
13853
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", marginTop: 1, children: OPTIONS.map((opt, i) => {
13736
13854
  const active = i === cursor;
13737
- return /* @__PURE__ */ jsxs6(Text7, { color: active ? opt.color : void 0, children: [
13855
+ return /* @__PURE__ */ jsxs7(Text8, { color: active ? opt.color : void 0, children: [
13738
13856
  active ? "\u276F " : " ",
13739
- /* @__PURE__ */ jsx7(Text7, { children: opt.label }),
13740
- /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
13857
+ /* @__PURE__ */ jsx8(Text8, { children: opt.label }),
13858
+ /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
13741
13859
  " (",
13742
13860
  opt.hint,
13743
13861
  ")"
@@ -13751,8 +13869,8 @@ function ApprovalPrompt({
13751
13869
 
13752
13870
  // src/ui/ask-user.tsx
13753
13871
  import { useState as useState4 } from "react";
13754
- import { Box as Box8, Text as Text8, useInput as useInput4 } from "ink";
13755
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
13872
+ import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
13873
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
13756
13874
  var OTHER_LABEL = "Other";
13757
13875
  var CONFIRM_HEADER = "Confirm";
13758
13876
  var CONFIRM_SUBMIT = 0;
@@ -14023,25 +14141,25 @@ function AskPanel({ req, onResolve }) {
14023
14141
  });
14024
14142
  const labelWidth = q ? Math.max(...q.options.map((o) => visibleWidth(o.label))) : 0;
14025
14143
  const everyAnswered = allAnswered(states);
14026
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", padding: 1, marginTop: 1, marginBottom: 1, borderStyle: "round", children: [
14027
- /* @__PURE__ */ jsxs7(Text8, { children: [
14028
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: ACCENT_HEX, children: "?" }),
14144
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", padding: 1, marginTop: 1, marginBottom: 1, borderStyle: "round", children: [
14145
+ /* @__PURE__ */ jsxs8(Text9, { children: [
14146
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: ACCENT_HEX, children: "?" }),
14029
14147
  " ",
14030
14148
  isConfirm ? "Review your answers and submit." : q?.spec.question
14031
14149
  ] }),
14032
- /* @__PURE__ */ jsxs7(Box8, { children: [
14150
+ /* @__PURE__ */ jsxs8(Box9, { children: [
14033
14151
  states.map((s, i) => {
14034
14152
  const status = isAnswered(s) ? "\u2713" : i === tab ? "\u25CF" : "\u25CB";
14035
14153
  const text = ` ${status} ${s.spec.header} `;
14036
- return /* @__PURE__ */ jsxs7(Text8, { color: i === tab ? ACCENT_HEX : void 0, dimColor: i !== tab, children: [
14154
+ return /* @__PURE__ */ jsxs8(Text9, { color: i === tab ? ACCENT_HEX : void 0, dimColor: i !== tab, children: [
14037
14155
  i > 0 ? " " : "",
14038
14156
  "[",
14039
14157
  text,
14040
14158
  "]"
14041
14159
  ] }, i);
14042
14160
  }),
14043
- /* @__PURE__ */ jsxs7(
14044
- Text8,
14161
+ /* @__PURE__ */ jsxs8(
14162
+ Text9,
14045
14163
  {
14046
14164
  color: isConfirm ? ACCENT_HEX : everyAnswered ? "green" : void 0,
14047
14165
  dimColor: !isConfirm,
@@ -14056,34 +14174,34 @@ function AskPanel({ req, onResolve }) {
14056
14174
  }
14057
14175
  )
14058
14176
  ] }),
14059
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
14060
- isConfirm ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
14177
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
14178
+ isConfirm ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
14061
14179
  states.map((s, i) => {
14062
14180
  const labels = answerLabels(s);
14063
14181
  const val = labels.length > 0 ? labels.join(", ") : "(no answer)";
14064
- return /* @__PURE__ */ jsxs7(Text8, { children: [
14182
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
14065
14183
  " ",
14066
- /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
14184
+ /* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
14067
14185
  s.spec.header,
14068
14186
  ":"
14069
14187
  ] }),
14070
14188
  " ",
14071
- /* @__PURE__ */ jsx8(Text8, { color: labels.length > 0 ? void 0 : "yellow", children: val })
14189
+ /* @__PURE__ */ jsx9(Text9, { color: labels.length > 0 ? void 0 : "yellow", children: val })
14072
14190
  ] }, i);
14073
14191
  }),
14074
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
14192
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
14075
14193
  [
14076
14194
  { idx: CONFIRM_SUBMIT, label: "Submit", color: "green" },
14077
14195
  { idx: CONFIRM_CANCEL, label: "Cancel", color: "red" }
14078
14196
  ].map((b) => {
14079
14197
  const isCur = confirmIndex === b.idx;
14080
14198
  const disabled = b.idx === CONFIRM_SUBMIT && !everyAnswered;
14081
- return /* @__PURE__ */ jsxs7(Text8, { children: [
14199
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
14082
14200
  " ",
14083
- /* @__PURE__ */ jsx8(Text8, { color: isCur ? ACCENT_HEX : void 0, children: isCur ? "\u276F" : " " }),
14201
+ /* @__PURE__ */ jsx9(Text9, { color: isCur ? ACCENT_HEX : void 0, children: isCur ? "\u276F" : " " }),
14084
14202
  " ",
14085
- /* @__PURE__ */ jsx8(Text8, { color: isCur ? b.color : void 0, dimColor: disabled && !isCur, children: b.label }),
14086
- disabled ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " (answer all questions first)" }) : null
14203
+ /* @__PURE__ */ jsx9(Text9, { color: isCur ? b.color : void 0, dimColor: disabled && !isCur, children: b.label }),
14204
+ disabled ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " (answer all questions first)" }) : null
14087
14205
  ] }, b.idx);
14088
14206
  })
14089
14207
  ] }) : q?.options.map((o, i) => {
@@ -14092,34 +14210,34 @@ function AskPanel({ req, onResolve }) {
14092
14210
  const marker2 = q.spec.multiSelect ? isSelected ? "[x]" : "[ ]" : isSelected ? "\u25CF" : "\u25CB";
14093
14211
  const cur = isCur ? "\u276F" : " ";
14094
14212
  const pad = " ".repeat(Math.max(0, labelWidth - visibleWidth(o.label)));
14095
- return /* @__PURE__ */ jsxs7(Text8, { children: [
14213
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
14096
14214
  " ",
14097
- /* @__PURE__ */ jsx8(Text8, { color: isCur ? ACCENT_HEX : void 0, children: cur }),
14215
+ /* @__PURE__ */ jsx9(Text9, { color: isCur ? ACCENT_HEX : void 0, children: cur }),
14098
14216
  " ",
14099
14217
  marker2,
14100
14218
  " ",
14101
- /* @__PURE__ */ jsx8(Text8, { color: isCur ? ACCENT_HEX : void 0, children: o.label }),
14219
+ /* @__PURE__ */ jsx9(Text9, { color: isCur ? ACCENT_HEX : void 0, children: o.label }),
14102
14220
  pad,
14103
- o.description ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
14221
+ o.description ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
14104
14222
  " ",
14105
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: o.description })
14223
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: o.description })
14106
14224
  ] }) : null
14107
14225
  ] }, i);
14108
14226
  }),
14109
- phase === "freeform" ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
14110
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
14111
- /* @__PURE__ */ jsxs7(Box8, { children: [
14112
- /* @__PURE__ */ jsx8(Text8, { color: ACCENT_HEX, children: " \u203A " }),
14113
- /* @__PURE__ */ jsx8(Text8, { children: freeformBuffer }),
14114
- /* @__PURE__ */ jsx8(Text8, { inverse: true, children: " " }),
14115
- freeformBuffer.length === 0 ? /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
14227
+ phase === "freeform" ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
14228
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
14229
+ /* @__PURE__ */ jsxs8(Box9, { children: [
14230
+ /* @__PURE__ */ jsx9(Text9, { color: ACCENT_HEX, children: " \u203A " }),
14231
+ /* @__PURE__ */ jsx9(Text9, { children: freeformBuffer }),
14232
+ /* @__PURE__ */ jsx9(Text9, { inverse: true, children: " " }),
14233
+ freeformBuffer.length === 0 ? /* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
14116
14234
  " ",
14117
14235
  "type your custom answer, Enter to confirm, Esc to cancel"
14118
14236
  ] }) : null
14119
14237
  ] })
14120
14238
  ] }) : null,
14121
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
14122
- /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: [
14239
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
14240
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: [
14123
14241
  "\u2190/\u2192 tab",
14124
14242
  isConfirm ? "\u2191/\u2193 button" : "\u2191/\u2193 option",
14125
14243
  !isConfirm && q?.spec.multiSelect ? "space toggle" : "",
@@ -14131,8 +14249,8 @@ function AskPanel({ req, onResolve }) {
14131
14249
 
14132
14250
  // src/ui/slider.tsx
14133
14251
  import { useEffect as useEffect2, useState as useState5 } from "react";
14134
- import { Box as Box9, Text as Text9, useInput as useInput5, useStdout as useStdout3 } from "ink";
14135
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
14252
+ import { Box as Box10, Text as Text10, useInput as useInput5, useStdout as useStdout3 } from "ink";
14253
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
14136
14254
  var SEP = 3;
14137
14255
  function hslToRgb(h, s, l) {
14138
14256
  const c = (1 - Math.abs(2 * l - 1)) * s;
@@ -14234,8 +14352,8 @@ function SliderPicker({ opts, onResolve, panelWidth }) {
14234
14352
  const fullWidth = !!opts.topRuleColor;
14235
14353
  const cols = panelWidth ?? stdout?.columns ?? 80;
14236
14354
  const borderProps = overlayBorderProps(false, opts.topRuleColor);
14237
- return /* @__PURE__ */ jsxs8(
14238
- Box9,
14355
+ return /* @__PURE__ */ jsxs9(
14356
+ Box10,
14239
14357
  {
14240
14358
  flexDirection: "column",
14241
14359
  marginTop: 1,
@@ -14243,15 +14361,15 @@ function SliderPicker({ opts, onResolve, panelWidth }) {
14243
14361
  ...fullWidth ? { width: cols } : {},
14244
14362
  ...borderProps,
14245
14363
  children: [
14246
- /* @__PURE__ */ jsx9(Text9, { children: scaleLine }),
14247
- /* @__PURE__ */ jsx9(Text9, { children: itemLine }),
14248
- description ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
14249
- /* @__PURE__ */ jsx9(Text9, { children: " " }),
14250
- /* @__PURE__ */ jsx9(Text9, { children: dim(description) })
14364
+ /* @__PURE__ */ jsx10(Text10, { children: scaleLine }),
14365
+ /* @__PURE__ */ jsx10(Text10, { children: itemLine }),
14366
+ description ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
14367
+ /* @__PURE__ */ jsx10(Text10, { children: " " }),
14368
+ /* @__PURE__ */ jsx10(Text10, { children: dim(description) })
14251
14369
  ] }) : null,
14252
- opts.footer ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
14253
- /* @__PURE__ */ jsx9(Text9, { children: " " }),
14254
- /* @__PURE__ */ jsx9(Text9, { children: opts.footer })
14370
+ opts.footer ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
14371
+ /* @__PURE__ */ jsx10(Text10, { children: " " }),
14372
+ /* @__PURE__ */ jsx10(Text10, { children: opts.footer })
14255
14373
  ] }) : null
14256
14374
  ]
14257
14375
  }
@@ -14478,9 +14596,10 @@ function appendAssistantItems(items, msg, resultIndex, thinkingLabel, nextKey, t
14478
14596
 
14479
14597
  // src/ui/spinner.tsx
14480
14598
  import { useEffect as useEffect3, useState as useState6 } from "react";
14481
- import { Box as Box10, Text as Text10 } from "ink";
14482
- import { jsx as jsx10 } from "react/jsx-runtime";
14483
- var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
14599
+ import { Box as Box11, Text as Text11 } from "ink";
14600
+ import { jsx as jsx11 } from "react/jsx-runtime";
14601
+ var FRAMES = ["\xB7", "\u2726", "\u2727", "\u2736", "\u2738", "\u273A", "\u2738", "\u2736", "\u2727", "\u2726"];
14602
+ var ANIM_SLOWDOWN = 2;
14484
14603
  function shimmer(text, frame, [r, g, b]) {
14485
14604
  let out = "\x1B[1m";
14486
14605
  for (let i = 0; i < text.length; i++) {
@@ -14511,28 +14630,29 @@ function Spinner({ spec }) {
14511
14630
  return () => clearInterval(id);
14512
14631
  }, [tickMs]);
14513
14632
  const elapsed = formatElapsed(Date.now() - spec.startedAt);
14514
- const frameChar = FRAMES[frame % FRAMES.length] ?? "";
14633
+ const phase = Math.floor(frame / ANIM_SLOWDOWN);
14634
+ const frameChar = FRAMES[phase % FRAMES.length] ?? "";
14515
14635
  const upStr = spec.inputTokens != null ? ` \xB7 \u2191 ${formatTokenCount(spec.inputTokens)} tok` : "";
14516
14636
  const downStr = spec.tokens != null ? ` \xB7 \u2193 ~${formatTokenCount(spec.tokens)} tok` : "";
14517
14637
  const tokenStr = `${upStr}${downStr}`;
14518
14638
  const hintStr = spec.hint ? ` \xB7 ${spec.hint}` : "";
14519
14639
  let line;
14520
14640
  if (canShimmer && tint) {
14521
- const head = shimmer(frameChar, frame + 1, tint);
14522
- const word = shimmer(spec.activeWord, frame, tint);
14641
+ const head = shimmer(frameChar, phase + 1, tint);
14642
+ const word = shimmer(spec.activeWord, phase, tint);
14523
14643
  line = `${head} ${word} \xB7 ${elapsed}${tokenStr}${hintStr}`;
14524
14644
  } else {
14525
14645
  const renderedFrame = isStatic ? frameChar : bold(colorize(frameChar));
14526
14646
  const word = isStatic ? spec.activeWord : bold(colorize(spec.activeWord));
14527
14647
  line = `${renderedFrame} ${word} \xB7 ${elapsed}${tokenStr}${hintStr}`;
14528
14648
  }
14529
- return /* @__PURE__ */ jsx10(Box10, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx10(Text10, { children: line }) });
14649
+ return /* @__PURE__ */ jsx11(Box11, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(Text11, { children: line }) });
14530
14650
  }
14531
14651
 
14532
14652
  // src/ui/task-footer.tsx
14533
14653
  import { useEffect as useEffect4, useState as useState7 } from "react";
14534
- import { Box as Box11, Text as Text11 } from "ink";
14535
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
14654
+ import { Box as Box12, Text as Text12 } from "ink";
14655
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
14536
14656
  var MAX_VISIBLE = 5;
14537
14657
  var MIN_VISIBLE_TASKS = 2;
14538
14658
  function taskFooterVisible(tasks) {
@@ -14544,28 +14664,28 @@ var STATUS_RANK = {
14544
14664
  completed: 2
14545
14665
  };
14546
14666
  function TaskRow({ task, isFirst }) {
14547
- const prefix = isFirst ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: " \u23BF " }) : /* @__PURE__ */ jsx11(Text11, { children: " " });
14548
- const suffix = task.blockedBy.length > 0 ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: ` \u27F5 ${task.blockedBy.length}` }) : null;
14667
+ const prefix = isFirst ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: " \u23BF " }) : /* @__PURE__ */ jsx12(Text12, { children: " " });
14668
+ const suffix = task.blockedBy.length > 0 ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: ` \u27F5 ${task.blockedBy.length}` }) : null;
14549
14669
  switch (task.status) {
14550
14670
  case "completed":
14551
- return /* @__PURE__ */ jsxs9(Text11, { children: [
14671
+ return /* @__PURE__ */ jsxs10(Text12, { children: [
14552
14672
  prefix,
14553
- /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" }),
14673
+ /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
14554
14674
  " ",
14555
- /* @__PURE__ */ jsx11(Text11, { color: "gray", strikethrough: true, children: task.description }),
14675
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", strikethrough: true, children: task.description }),
14556
14676
  suffix
14557
14677
  ] });
14558
14678
  case "in_progress":
14559
- return /* @__PURE__ */ jsxs9(Text11, { children: [
14679
+ return /* @__PURE__ */ jsxs10(Text12, { children: [
14560
14680
  prefix,
14561
- /* @__PURE__ */ jsx11(Text11, { color: ACCENT_HEX, children: "\u25A0" }),
14681
+ /* @__PURE__ */ jsx12(Text12, { color: ACCENT_HEX, children: "\u25A0" }),
14562
14682
  " ",
14563
- /* @__PURE__ */ jsx11(Text11, { color: ACCENT_HEX, bold: true, children: task.description }),
14683
+ /* @__PURE__ */ jsx12(Text12, { color: ACCENT_HEX, bold: true, children: task.description }),
14564
14684
  suffix
14565
14685
  ] });
14566
14686
  case "pending":
14567
14687
  default:
14568
- return /* @__PURE__ */ jsxs9(Text11, { children: [
14688
+ return /* @__PURE__ */ jsxs10(Text12, { children: [
14569
14689
  prefix,
14570
14690
  "\u25A1 ",
14571
14691
  task.description,
@@ -14577,9 +14697,9 @@ function SummaryRow({
14577
14697
  hidden,
14578
14698
  counts
14579
14699
  }) {
14580
- return /* @__PURE__ */ jsxs9(Text11, { children: [
14700
+ return /* @__PURE__ */ jsxs10(Text12, { children: [
14581
14701
  " ",
14582
- /* @__PURE__ */ jsxs9(Text11, { dimColor: true, children: [
14702
+ /* @__PURE__ */ jsxs10(Text12, { dimColor: true, children: [
14583
14703
  "... ",
14584
14704
  hidden,
14585
14705
  " More, ",
@@ -14628,17 +14748,17 @@ function TaskFooter({ tasks }) {
14628
14748
  startedAt,
14629
14749
  activeWord: `TASK: ${spinnerTask.description}...`
14630
14750
  } : null;
14631
- return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", marginTop: 1, children: [
14632
- spinnerSpec ? /* @__PURE__ */ jsx11(Spinner, { spec: spinnerSpec }) : null,
14633
- visible.map((t, i) => /* @__PURE__ */ jsx11(TaskRow, { task: t, isFirst: i === 0 }, t.id)),
14634
- hidden > 0 ? /* @__PURE__ */ jsx11(SummaryRow, { hidden, counts }) : null
14751
+ return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginTop: 1, children: [
14752
+ spinnerSpec ? /* @__PURE__ */ jsx12(Spinner, { spec: spinnerSpec }) : null,
14753
+ visible.map((t, i) => /* @__PURE__ */ jsx12(TaskRow, { task: t, isFirst: i === 0 }, t.id)),
14754
+ hidden > 0 ? /* @__PURE__ */ jsx12(SummaryRow, { hidden, counts }) : null
14635
14755
  ] });
14636
14756
  }
14637
14757
 
14638
14758
  // src/ui/todo-footer.tsx
14639
14759
  import { useEffect as useEffect5, useState as useState8 } from "react";
14640
- import { Box as Box12, Text as Text12 } from "ink";
14641
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
14760
+ import { Box as Box13, Text as Text13 } from "ink";
14761
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
14642
14762
  var MAX_VISIBLE2 = 5;
14643
14763
  var MIN_VISIBLE_TODOS = 2;
14644
14764
  function todoFooterVisible(todos) {
@@ -14650,25 +14770,25 @@ var STATUS_RANK2 = {
14650
14770
  completed: 2
14651
14771
  };
14652
14772
  function TodoRow({ todo, isFirst }) {
14653
- const prefix = isFirst ? /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: " \u23BF " }) : /* @__PURE__ */ jsx12(Text12, { children: " " });
14773
+ const prefix = isFirst ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \u23BF " }) : /* @__PURE__ */ jsx13(Text13, { children: " " });
14654
14774
  switch (todo.status) {
14655
14775
  case "completed":
14656
- return /* @__PURE__ */ jsxs10(Text12, { children: [
14776
+ return /* @__PURE__ */ jsxs11(Text13, { children: [
14657
14777
  prefix,
14658
- /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
14778
+ /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
14659
14779
  " ",
14660
- /* @__PURE__ */ jsx12(Text12, { color: "gray", strikethrough: true, children: todo.description })
14780
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", strikethrough: true, children: todo.description })
14661
14781
  ] });
14662
14782
  case "in_progress":
14663
- return /* @__PURE__ */ jsxs10(Text12, { children: [
14783
+ return /* @__PURE__ */ jsxs11(Text13, { children: [
14664
14784
  prefix,
14665
- /* @__PURE__ */ jsx12(Text12, { color: "blue", children: "\u25A0" }),
14785
+ /* @__PURE__ */ jsx13(Text13, { color: "blue", children: "\u25A0" }),
14666
14786
  " ",
14667
- /* @__PURE__ */ jsx12(Text12, { color: "blue", bold: true, children: todo.description })
14787
+ /* @__PURE__ */ jsx13(Text13, { color: "blue", bold: true, children: todo.description })
14668
14788
  ] });
14669
14789
  case "pending":
14670
14790
  default:
14671
- return /* @__PURE__ */ jsxs10(Text12, { children: [
14791
+ return /* @__PURE__ */ jsxs11(Text13, { children: [
14672
14792
  prefix,
14673
14793
  "\u25A1 ",
14674
14794
  todo.description
@@ -14679,9 +14799,9 @@ function SummaryRow2({
14679
14799
  hidden,
14680
14800
  counts
14681
14801
  }) {
14682
- return /* @__PURE__ */ jsxs10(Text12, { children: [
14802
+ return /* @__PURE__ */ jsxs11(Text13, { children: [
14683
14803
  " ",
14684
- /* @__PURE__ */ jsxs10(Text12, { dimColor: true, children: [
14804
+ /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
14685
14805
  "... ",
14686
14806
  hidden,
14687
14807
  " More, ",
@@ -14730,15 +14850,15 @@ function TodoFooter({ todos }) {
14730
14850
  startedAt,
14731
14851
  activeWord: `TODO: ${spinnerTodo.description}...`
14732
14852
  } : null;
14733
- return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginTop: 1, children: [
14734
- spinnerSpec ? /* @__PURE__ */ jsx12(Spinner, { spec: spinnerSpec }) : null,
14735
- visible.map((t, i) => /* @__PURE__ */ jsx12(TodoRow, { todo: t, isFirst: i === 0 }, t.id)),
14736
- hidden > 0 ? /* @__PURE__ */ jsx12(SummaryRow2, { hidden, counts }) : null
14853
+ return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", marginTop: 1, children: [
14854
+ spinnerSpec ? /* @__PURE__ */ jsx13(Spinner, { spec: spinnerSpec }) : null,
14855
+ visible.map((t, i) => /* @__PURE__ */ jsx13(TodoRow, { todo: t, isFirst: i === 0 }, t.id)),
14856
+ hidden > 0 ? /* @__PURE__ */ jsx13(SummaryRow2, { hidden, counts }) : null
14737
14857
  ] });
14738
14858
  }
14739
14859
 
14740
14860
  // src/ui/viewport.tsx
14741
- import { Fragment as Fragment5, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
14861
+ import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
14742
14862
  var MIN_ROWS = 3;
14743
14863
  var H_PAD = 2;
14744
14864
  function Viewport({ store, rows, resolveModal }) {
@@ -14852,7 +14972,7 @@ function Viewport({ store, rows, resolveModal }) {
14852
14972
  }, [hasPending]);
14853
14973
  const displayLines = hasPending && !blinkOn ? highlighted.map((l) => blinkPendingOff(l)) : highlighted;
14854
14974
  if (inStreamModal) {
14855
- const modalEl = /* @__PURE__ */ jsx13(
14975
+ const modalEl = /* @__PURE__ */ jsx14(
14856
14976
  InStreamModal,
14857
14977
  {
14858
14978
  modal: inStreamModal,
@@ -14862,26 +14982,26 @@ function Viewport({ store, rows, resolveModal }) {
14862
14982
  }
14863
14983
  );
14864
14984
  if (inStreamModal.kind === "approval") {
14865
- return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", paddingX: H_PAD, children: [
14866
- displayLines.length > 0 ? /* @__PURE__ */ jsx13(Text13, { children: displayLines.join("\n") }) : null,
14985
+ return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", paddingX: H_PAD, children: [
14986
+ displayLines.length > 0 ? /* @__PURE__ */ jsx14(Text14, { children: displayLines.join("\n") }) : null,
14867
14987
  modalEl,
14868
- /* @__PURE__ */ jsx13(Box13, { flexGrow: 1 })
14988
+ /* @__PURE__ */ jsx14(Box14, { flexGrow: 1 })
14869
14989
  ] });
14870
14990
  }
14871
- return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", paddingX: H_PAD, children: [
14872
- displayLines.length > 0 ? /* @__PURE__ */ jsx13(Text13, { children: displayLines.join("\n") }) : null,
14873
- /* @__PURE__ */ jsx13(Box13, { flexGrow: 1 }),
14991
+ return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", paddingX: H_PAD, children: [
14992
+ displayLines.length > 0 ? /* @__PURE__ */ jsx14(Text14, { children: displayLines.join("\n") }) : null,
14993
+ /* @__PURE__ */ jsx14(Box14, { flexGrow: 1 }),
14874
14994
  modalEl
14875
14995
  ] });
14876
14996
  }
14877
- return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", paddingX: H_PAD, children: [
14878
- displayLines.length > 0 ? /* @__PURE__ */ jsx13(Text13, { children: displayLines.join("\n") }) : null,
14879
- hasSpinner && !anyFooterVisible ? /* @__PURE__ */ jsx13(SpinnerWrapper, { store }) : null,
14880
- hasSpinner && anyFooterVisible ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
14881
- /* @__PURE__ */ jsx13(TaskFooter, { tasks }),
14882
- /* @__PURE__ */ jsx13(TodoFooter, { todos })
14997
+ return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", paddingX: H_PAD, children: [
14998
+ displayLines.length > 0 ? /* @__PURE__ */ jsx14(Text14, { children: displayLines.join("\n") }) : null,
14999
+ hasSpinner && !anyFooterVisible ? /* @__PURE__ */ jsx14(SpinnerWrapper, { store }) : null,
15000
+ hasSpinner && anyFooterVisible ? /* @__PURE__ */ jsxs12(Fragment5, { children: [
15001
+ /* @__PURE__ */ jsx14(TaskFooter, { tasks }),
15002
+ /* @__PURE__ */ jsx14(TodoFooter, { todos })
14883
15003
  ] }) : null,
14884
- /* @__PURE__ */ jsx13(Box13, { flexGrow: 1 })
15004
+ /* @__PURE__ */ jsx14(Box14, { flexGrow: 1 })
14885
15005
  ] });
14886
15006
  }
14887
15007
  function SpinnerWrapper({
@@ -14889,7 +15009,7 @@ function SpinnerWrapper({
14889
15009
  }) {
14890
15010
  const spec = store((s) => s.spinner);
14891
15011
  if (!spec) return null;
14892
- return /* @__PURE__ */ jsx13(Spinner, { spec });
15012
+ return /* @__PURE__ */ jsx14(Spinner, { spec });
14893
15013
  }
14894
15014
  function chromeRowsFor(hasSpinner, modal, todos, tasks, cols) {
14895
15015
  if (modal) {
@@ -14964,7 +15084,7 @@ function InStreamModal({
14964
15084
  }) {
14965
15085
  switch (modal.kind) {
14966
15086
  case "approval":
14967
- return /* @__PURE__ */ jsx13(
15087
+ return /* @__PURE__ */ jsx14(
14968
15088
  ApprovalPrompt,
14969
15089
  {
14970
15090
  decision: modal.decision,
@@ -14976,9 +15096,9 @@ function InStreamModal({
14976
15096
  }
14977
15097
  );
14978
15098
  case "ask":
14979
- return /* @__PURE__ */ jsx13(AskPanel, { req: modal.req, onResolve: (value) => resolveModal(value) });
15099
+ return /* @__PURE__ */ jsx14(AskPanel, { req: modal.req, onResolve: (value) => resolveModal(value) });
14980
15100
  case "pick":
14981
- return /* @__PURE__ */ jsx13(
15101
+ return /* @__PURE__ */ jsx14(
14982
15102
  PickList,
14983
15103
  {
14984
15104
  opts: modal.opts,
@@ -14987,7 +15107,7 @@ function InStreamModal({
14987
15107
  }
14988
15108
  );
14989
15109
  case "pickH":
14990
- return /* @__PURE__ */ jsx13(
15110
+ return /* @__PURE__ */ jsx14(
14991
15111
  PickHorizontal,
14992
15112
  {
14993
15113
  opts: modal.opts,
@@ -14996,7 +15116,7 @@ function InStreamModal({
14996
15116
  }
14997
15117
  );
14998
15118
  case "slider":
14999
- return /* @__PURE__ */ jsx13(
15119
+ return /* @__PURE__ */ jsx14(
15000
15120
  SliderPicker,
15001
15121
  {
15002
15122
  opts: modal.opts,
@@ -15005,7 +15125,7 @@ function InStreamModal({
15005
15125
  }
15006
15126
  );
15007
15127
  case "viewer":
15008
- return /* @__PURE__ */ jsx13(
15128
+ return /* @__PURE__ */ jsx14(
15009
15129
  ScrollViewer,
15010
15130
  {
15011
15131
  opts: modal.opts,
@@ -15028,7 +15148,7 @@ function hitTestJumpButton(row, col) {
15028
15148
  }
15029
15149
 
15030
15150
  // src/ui/app.tsx
15031
- import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
15151
+ import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
15032
15152
  function pinnedBottomRows(inputRows, extraRows) {
15033
15153
  return STATUS_LINE_ROWS + INPUT_TOP_SPACER_ROWS + inputRows + extraRows;
15034
15154
  }
@@ -15059,12 +15179,13 @@ function JumpToBottomHint({
15059
15179
  store.getState().setJumpButtonHovered(false);
15060
15180
  };
15061
15181
  }, [show, row, colStart, colEnd, store]);
15062
- return /* @__PURE__ */ jsx14(Box14, { flexShrink: 0, height: INPUT_TOP_SPACER_ROWS, justifyContent: "center", children: show ? /* @__PURE__ */ jsx14(Text14, { color: "black", backgroundColor: hovered ? "cyan" : "gray", children: JUMP_TO_BOTTOM_LABEL }) : null });
15182
+ return /* @__PURE__ */ jsx15(Box15, { flexShrink: 0, height: INPUT_TOP_SPACER_ROWS, justifyContent: "center", children: show ? /* @__PURE__ */ jsx15(Text15, { color: "black", backgroundColor: hovered ? "cyan" : "gray", children: JUMP_TO_BOTTOM_LABEL }) : null });
15063
15183
  }
15064
15184
  var MIN_FRAME_ROWS = 4;
15065
15185
  function App({ store }) {
15066
15186
  const {
15067
15187
  setup,
15188
+ trust,
15068
15189
  modal,
15069
15190
  slashCommands,
15070
15191
  mentionFiles,
@@ -15079,6 +15200,7 @@ function App({ store }) {
15079
15200
  } = store(
15080
15201
  useShallow3((s) => ({
15081
15202
  setup: s.setup,
15203
+ trust: s.trust,
15082
15204
  modal: s.modal,
15083
15205
  slashCommands: s.slashCommands,
15084
15206
  mentionFiles: s.mentionFiles,
@@ -15115,12 +15237,13 @@ function App({ store }) {
15115
15237
  [store]
15116
15238
  );
15117
15239
  React12.useEffect(() => {
15118
- if (setup || overlayModal) setCursorTarget(null);
15119
- }, [setup, overlayModal]);
15120
- if (setup) {
15121
- return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", children: [
15122
- /* @__PURE__ */ jsx14(SetupView, { state: setup }),
15123
- modal?.kind === "input" ? /* @__PURE__ */ jsx14(
15240
+ if (setup || trust || overlayModal) setCursorTarget(null);
15241
+ }, [setup, trust, overlayModal]);
15242
+ const fullscreenView = setup ? /* @__PURE__ */ jsx15(SetupView, { state: setup }) : trust ? /* @__PURE__ */ jsx15(TrustView, { state: trust }) : null;
15243
+ if (fullscreenView) {
15244
+ return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", children: [
15245
+ fullscreenView,
15246
+ modal?.kind === "input" ? /* @__PURE__ */ jsx15(
15124
15247
  InputBox,
15125
15248
  {
15126
15249
  options: modal.opts,
@@ -15129,14 +15252,14 @@ function App({ store }) {
15129
15252
  onMeasure: onMeasureInput
15130
15253
  }
15131
15254
  ) : null,
15132
- modal?.kind === "pickH" ? /* @__PURE__ */ jsx14(
15255
+ modal?.kind === "pickH" ? /* @__PURE__ */ jsx15(
15133
15256
  PickHorizontal,
15134
15257
  {
15135
15258
  opts: modal.opts,
15136
15259
  onResolve: (value) => resolveModal(value)
15137
15260
  }
15138
15261
  ) : null,
15139
- modal?.kind === "pick" ? /* @__PURE__ */ jsx14(
15262
+ modal?.kind === "pick" ? /* @__PURE__ */ jsx15(
15140
15263
  PickList,
15141
15264
  {
15142
15265
  opts: modal.opts,
@@ -15149,10 +15272,10 @@ function App({ store }) {
15149
15272
  const indicatorRows = modeIndicator ? 1 : 0;
15150
15273
  const bottomChromeRows = overlayModal ? 0 : pinnedBottomRows(inputRows, indicatorRows);
15151
15274
  const viewportRows = Math.max(3, termRows - bottomChromeRows - 1);
15152
- return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", height: Math.max(MIN_FRAME_ROWS, termRows - 1), overflowY: "hidden", children: [
15153
- /* @__PURE__ */ jsx14(Viewport, { store, rows: viewportRows, resolveModal }),
15154
- overlayModal ? null : /* @__PURE__ */ jsxs12(Fragment6, { children: [
15155
- /* @__PURE__ */ jsx14(
15275
+ return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", height: Math.max(MIN_FRAME_ROWS, termRows - 1), overflowY: "hidden", children: [
15276
+ /* @__PURE__ */ jsx15(Viewport, { store, rows: viewportRows, resolveModal }),
15277
+ overlayModal ? null : /* @__PURE__ */ jsxs13(Fragment6, { children: [
15278
+ /* @__PURE__ */ jsx15(
15156
15279
  JumpToBottomHint,
15157
15280
  {
15158
15281
  store,
@@ -15163,7 +15286,7 @@ function App({ store }) {
15163
15286
  indicatorRows
15164
15287
  }
15165
15288
  ),
15166
- /* @__PURE__ */ jsx14(Box14, { flexShrink: 0, flexDirection: "column", children: /* @__PURE__ */ jsx14(
15289
+ /* @__PURE__ */ jsx15(Box15, { flexShrink: 0, flexDirection: "column", children: /* @__PURE__ */ jsx15(
15167
15290
  InputBox,
15168
15291
  {
15169
15292
  options: {
@@ -15189,11 +15312,11 @@ function App({ store }) {
15189
15312
  onImageAttached: imagePaste?.attached
15190
15313
  }
15191
15314
  ) }),
15192
- /* @__PURE__ */ jsxs12(Box14, { flexShrink: 0, flexDirection: "column", children: [
15193
- /* @__PURE__ */ jsx14(StatusLine, { store, shellMode }),
15194
- modeIndicator ? /* @__PURE__ */ jsxs12(Box14, { children: [
15195
- /* @__PURE__ */ jsx14(Text14, { color: modeIndicator.color, children: ` ${modeIndicator.label}` }),
15196
- /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` ${PERMISSION_MODE_HINT}` })
15315
+ /* @__PURE__ */ jsxs13(Box15, { flexShrink: 0, flexDirection: "column", children: [
15316
+ /* @__PURE__ */ jsx15(StatusLine, { store, shellMode }),
15317
+ modeIndicator ? /* @__PURE__ */ jsxs13(Box15, { children: [
15318
+ /* @__PURE__ */ jsx15(Text15, { color: modeIndicator.color, children: ` ${modeIndicator.label}` }),
15319
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` ${PERMISSION_MODE_HINT}` })
15197
15320
  ] }) : null
15198
15321
  ] })
15199
15322
  ] })
@@ -15567,6 +15690,7 @@ function createAppStore(opts = {}) {
15567
15690
  escHandler: null,
15568
15691
  thinkingLabel: void 0,
15569
15692
  setup: null,
15693
+ trust: null,
15570
15694
  termCols: process.stdout.columns ?? 80,
15571
15695
  termRows: process.stdout.rows ?? 24,
15572
15696
  scrollOffset: 0,
@@ -15708,6 +15832,12 @@ function createAppStore(opts = {}) {
15708
15832
  beginSetup(state) {
15709
15833
  set({ setup: state });
15710
15834
  },
15835
+ beginTrust(state) {
15836
+ set({ trust: state });
15837
+ },
15838
+ endTrust() {
15839
+ set({ trust: null });
15840
+ },
15711
15841
  setSetupPrompt(prompt) {
15712
15842
  const cur = get().setup;
15713
15843
  if (!cur) return;
@@ -16446,6 +16576,12 @@ var Screen = class {
16446
16576
  endSetup() {
16447
16577
  this.store.getState().endSetup();
16448
16578
  }
16579
+ beginTrust(state) {
16580
+ this.store.getState().beginTrust(state);
16581
+ }
16582
+ endTrust() {
16583
+ this.store.getState().endTrust();
16584
+ }
16449
16585
  async promptInput(opts) {
16450
16586
  return this.store.getState().openInputModal(opts);
16451
16587
  }
@@ -17196,7 +17332,7 @@ async function handleDiff(ctx, args) {
17196
17332
 
17197
17333
  // src/doctor.ts
17198
17334
  import { readFile as readFile21 } from "fs/promises";
17199
- import { basename as basename4 } from "path";
17335
+ import { basename as basename5 } from "path";
17200
17336
  import { Command } from "commander";
17201
17337
  function zodIssues(err) {
17202
17338
  if (err && typeof err === "object" && Array.isArray(err.issues)) {
@@ -17314,7 +17450,7 @@ async function diagnoseConfig(opts = {}) {
17314
17450
  for (const e of hooks.errors) {
17315
17451
  issues.push({
17316
17452
  level: "warn",
17317
- title: `invalid hook file: ${basename4(e.source)}`,
17453
+ title: `invalid hook file: ${basename5(e.source)}`,
17318
17454
  detail: e.message,
17319
17455
  hint: "fix or remove it \u2014 nova skips it and continues"
17320
17456
  });
@@ -18611,8 +18747,7 @@ async function handleSkills(ctx) {
18611
18747
  topRuleColor: PURPLE_HEX,
18612
18748
  render: (s, selected) => {
18613
18749
  const name = s.name.padEnd(nameWidth, " ");
18614
- const trig = s.triggers.length > 0 ? ` ${dim(`triggers: ${s.triggers.join(", ")}`)}` : "";
18615
- return `${pickerArrow(selected)} ${name} ${s.description}${trig}`;
18750
+ return `${pickerArrow(selected)} ${name} ${s.description}`;
18616
18751
  }
18617
18752
  });
18618
18753
  }
@@ -19211,7 +19346,8 @@ async function createContext(settings, screen, cliOpts) {
19211
19346
  logger
19212
19347
  } : void 0;
19213
19348
  const skillItems = skillsOpts ? getSkillList(skillsOpts) : [];
19214
- const skillsBlock = skillsOpts ? renderSkillsBlock(skillItems, settings.skills.maxIndexBytes) : "";
19349
+ const modelSkillItems = skillItems.filter((s) => !s.disableModelInvocation);
19350
+ const skillsBlock = skillsOpts ? renderSkillsBlock(modelSkillItems, settings.skills.maxIndexBytes) : "";
19215
19351
  if (skillsOpts) {
19216
19352
  await transcript.append({
19217
19353
  kind: "skills_loaded",
@@ -21578,6 +21714,68 @@ function buildUpgradeCommand() {
21578
21714
  });
21579
21715
  }
21580
21716
 
21717
+ // src/workspace-trust.ts
21718
+ import { homedir as homedir17 } from "os";
21719
+ async function isWorkspaceTrusted(settings, workspace) {
21720
+ if (!settings.trust.enabled) return true;
21721
+ const wsCanon = await canonicalizePath(workspace, ".");
21722
+ for (const root of settings.trust.trustedRoots) {
21723
+ const rootCanon = await canonicalizePath(workspace, root);
21724
+ if (isWithin(rootCanon, wsCanon)) return true;
21725
+ }
21726
+ return false;
21727
+ }
21728
+ async function trustWorkspace(settings, workspace, configPath) {
21729
+ const wsCanon = await canonicalizePath(workspace, ".");
21730
+ const roots = settings.trust.trustedRoots.includes(wsCanon) ? settings.trust.trustedRoots : [...settings.trust.trustedRoots, wsCanon];
21731
+ settings.trust = { ...settings.trust, trustedRoots: roots };
21732
+ const home = await canonicalizePath(homedir17(), ".");
21733
+ if (wsCanon === home) return;
21734
+ await saveSettings({ trust: settings.trust }, configPath);
21735
+ }
21736
+ async function ensureWorkspaceTrust(settings, screen, workspace, configPath) {
21737
+ if (await isWorkspaceTrusted(settings, workspace)) return;
21738
+ const wsCanon = await canonicalizePath(workspace, ".");
21739
+ screen.beginTrust({
21740
+ version: await readCliVersion(),
21741
+ workspace: wsCanon,
21742
+ lines: [
21743
+ "nova has not been granted access to this folder yet. Granting access",
21744
+ "lets it read and edit files here (and in subdirectories) without",
21745
+ "confirming each time, and runs any project hooks the folder defines.",
21746
+ "Only trust folders you recognize \u2014 declining exits without touching",
21747
+ "anything."
21748
+ ]
21749
+ });
21750
+ try {
21751
+ const choice = await screen.pickOne({
21752
+ items: [{ trust: true }, { trust: false }],
21753
+ footer: dim("\u2191/\u2193 choose \xB7 Enter confirm \xB7 Ctrl+C to exit"),
21754
+ border: false,
21755
+ topRuleColor: PURPLE_HEX,
21756
+ render: (it, selected) => {
21757
+ const label = it.trust ? "Yes, trust this folder" : "No, exit";
21758
+ return `${pickerArrow(selected)} ${label}`;
21759
+ }
21760
+ });
21761
+ if (choice === null || !choice.trust) {
21762
+ await fatalExit(screen, `workspace not trusted \u2014 exiting.
21763
+ ${wsCanon}`, 1);
21764
+ }
21765
+ try {
21766
+ await trustWorkspace(settings, workspace, configPath);
21767
+ } catch (err) {
21768
+ const msg = err instanceof Error ? err.message : String(err);
21769
+ screen.card(`could not persist workspace trust: ${msg}`, {
21770
+ kind: "warn",
21771
+ title: "workspace trust"
21772
+ });
21773
+ }
21774
+ } finally {
21775
+ screen.endTrust();
21776
+ }
21777
+ }
21778
+
21581
21779
  // src/index.ts
21582
21780
  function applyCliOverrides(settings, opts) {
21583
21781
  if (opts.model) {
@@ -21651,6 +21849,14 @@ async function runHeadlessMode(settings, positional, opts) {
21651
21849
  dieHeadless(err instanceof Error ? err.message : String(err), 2);
21652
21850
  }
21653
21851
  const approvalPolicy = opts.dangerouslySkipPermissions ? "allow" : "deny";
21852
+ const headlessCwd = opts.cwd ?? process.cwd();
21853
+ if (!opts.dangerouslySkipPermissions && !await isWorkspaceTrusted(settings, headlessCwd)) {
21854
+ dieHeadless(
21855
+ `workspace not trusted: ${headlessCwd}
21856
+ Trust it by running nova interactively here once, by adding it to trust.trustedRoots in nova.config.json, or by passing --dangerously-skip-permissions.`,
21857
+ 1
21858
+ );
21859
+ }
21654
21860
  let code;
21655
21861
  try {
21656
21862
  code = await runHeadless(settings, {
@@ -21721,6 +21927,9 @@ ${formatIssues(report)}
21721
21927
  "apiKey is not set in nova.config.json (or equivalent settings file)."
21722
21928
  );
21723
21929
  }
21930
+ if (!opts.dangerouslySkipPermissions) {
21931
+ await ensureWorkspaceTrust(settings, screen, opts.cwd ?? process.cwd());
21932
+ }
21724
21933
  const ctx = await createContext(settings, screen, {
21725
21934
  ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
21726
21935
  ...opts.resume !== void 0 ? { resume: opts.resume } : {},