@garuda-agent/garuda 0.18.0 → 0.18.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/README.md +3 -2
  3. package/dist/{acpCommand-IUZPLPLI.js → acpCommand-PUJHFWYU.js} +10 -8
  4. package/dist/advise-C777SZ5M.js +433 -0
  5. package/dist/brief-RWKESOPT.js +91 -0
  6. package/dist/buildCheck-HB46NWLX.js +19 -0
  7. package/dist/buildInfo-Z4P7QAQL.js +8 -0
  8. package/dist/chunk-2OYTNNUJ.js +216 -0
  9. package/dist/chunk-4BELTWP4.js +156 -0
  10. package/dist/chunk-4QRTTRVK.js +88 -0
  11. package/dist/chunk-6JQTPW3P.js +88 -0
  12. package/dist/chunk-A5DLAXB4.js +1780 -0
  13. package/dist/{chunk-FARS4R67.js → chunk-AX3A5KPH.js} +18 -1
  14. package/dist/chunk-CFDRP6XW.js +9 -0
  15. package/dist/chunk-E6YRCDSF.js +9 -0
  16. package/dist/{chunk-OQ26W5L4.js → chunk-HP6EGFHV.js} +132 -75
  17. package/dist/chunk-L55VUSX3.js +54 -0
  18. package/dist/{chunk-B4SJY7KT.js → chunk-LMKGSYJC.js} +77 -8
  19. package/dist/{chunk-Q4P3W657.js → chunk-LWNMEGDF.js} +10 -1124
  20. package/dist/{chunk-23FXBNBB.js → chunk-MNE7HHX2.js} +2 -1
  21. package/dist/{chunk-UKIE4AAM.js → chunk-NLS7GMF7.js} +11 -105
  22. package/dist/{chunk-6XHON3Q6.js → chunk-QXTHEFKP.js} +3 -3
  23. package/dist/{chunk-4BVCYCQT.js → chunk-R7RCW4BQ.js} +9 -7
  24. package/dist/{chunk-NHWHG3SW.js → chunk-STVFWSIL.js} +350 -57
  25. package/dist/{chunk-5B5HDTUR.js → chunk-TWQ7V2L6.js} +2 -2
  26. package/dist/chunk-ULAJPMY7.js +107 -0
  27. package/dist/chunk-VISKX36R.js +306 -0
  28. package/dist/{chunk-55CFEEIN.js → chunk-WK5NE6RY.js} +2 -2
  29. package/dist/chunk-Y62J7PTD.js +90 -0
  30. package/dist/chunk-YAQ6QLKB.js +40 -0
  31. package/dist/{chunk-FOPBOB7W.js → chunk-YEH2OONM.js} +6 -4
  32. package/dist/{chunk-7TH3NHCQ.js → chunk-ZKT4P2PC.js} +2 -2
  33. package/dist/cli/index.js +242 -52
  34. package/dist/{create-W6BV75QX.js → create-OACSGJ37.js} +5 -5
  35. package/dist/{evalCommand-SKJQ6CW2.js → evalCommand-MRO6ENCA.js} +77 -39
  36. package/dist/{evalTune-CZRCPSZ3.js → evalTune-VL5RHYOG.js} +24 -18
  37. package/dist/{export-SJ3DUP6W.js → export-ZPF3AMO6.js} +27 -4
  38. package/dist/exportHtml-ISISG5HG.js +21 -0
  39. package/dist/{inkChat-WYYAFZXD.js → inkChat-UDFX6GH6.js} +256 -41
  40. package/dist/{install-TKWN5AYH.js → install-4ST5BNRG.js} +3 -3
  41. package/dist/{jobCommand-2ODQLHB3.js → jobCommand-ILOTYOX3.js} +8 -7
  42. package/dist/{launchd-3A3ONEW5.js → launchd-QG6R5RAK.js} +4 -4
  43. package/dist/{lspCommand-ZZ6R4PA2.js → lspCommand-X4FTTONR.js} +3 -3
  44. package/dist/{manager-TCYTA5XU.js → manager-YH7722C2.js} +3 -3
  45. package/dist/{nightCommand-WJU6VZA6.js → nightCommand-XPWT6CQZ.js} +8 -7
  46. package/dist/receiptView-YFDHV253.js +19 -0
  47. package/dist/{remove-42WQNVPX.js → remove-STX5UO7X.js} +3 -3
  48. package/dist/{run-PTMRQCIU.js → run-W4TK52WT.js} +5 -5
  49. package/dist/runner-EBF6EOYM.js +97 -0
  50. package/dist/{server-VEYIJHEU.js → server-ASM73QVY.js} +3 -3
  51. package/dist/tour-CEVMAC5S.js +18 -0
  52. package/dist/tune-KSNKBPNJ.js +51 -0
  53. package/dist/uiState-YFJQ43D7.js +16 -0
  54. package/package.json +1 -1
  55. package/dist/chunk-4JSUUDFQ.js +0 -118
  56. package/dist/chunk-C57GHLKP.js +0 -9
  57. package/dist/chunk-DOCDCQVM.js +0 -484
  58. package/dist/tune-YVI2KWQF.js +0 -31
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ receiptText,
4
+ screenReaderMode
5
+ } from "./chunk-2OYTNNUJ.js";
2
6
  import {
3
7
  serverCallText,
4
8
  serverResultSummary
@@ -85,6 +89,19 @@ var PlainRenderer = class {
85
89
  info(text) {
86
90
  this.line(this.paint("dim", text));
87
91
  }
92
+ /** UX U1: a box on a terminal of 60 columns or more, else plain lines (stderr, like notes). */
93
+ receipt(receipt) {
94
+ const tty = this.err.isTTY === true;
95
+ const columns = tty ? this.err.columns : void 0;
96
+ this.line(
97
+ `
98
+ ${receiptText(receipt, {
99
+ ...columns === void 0 ? {} : { columns },
100
+ ...this.color ? { paint: (style, text) => this.paint(style, text) } : {},
101
+ screenReader: screenReaderMode()
102
+ })}`
103
+ );
104
+ }
88
105
  warn(text) {
89
106
  this.line(this.paint("yellow", text));
90
107
  }
@@ -203,4 +220,4 @@ export {
203
220
  summariseCall,
204
221
  summariseResult
205
222
  };
206
- //# sourceMappingURL=chunk-FARS4R67.js.map
223
+ //# sourceMappingURL=chunk-AX3A5KPH.js.map
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/version.ts
4
+ var VERSION = "0.18.2";
5
+
6
+ export {
7
+ VERSION
8
+ };
9
+ //# sourceMappingURL=chunk-CFDRP6XW.js.map
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/buildInfo.ts
4
+ var BUILD_COMMIT = true ? "fc82b442119fbfe9c40bd2903b344c7e1166b2a8" : void 0;
5
+
6
+ export {
7
+ BUILD_COMMIT
8
+ };
9
+ //# sourceMappingURL=chunk-E6YRCDSF.js.map
@@ -1,7 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  colorDiff
4
- } from "./chunk-4JSUUDFQ.js";
4
+ } from "./chunk-VISKX36R.js";
5
+ import {
6
+ budgetNote,
7
+ chatStopMessage,
8
+ formatTokens,
9
+ formatUsd,
10
+ stopMessage,
11
+ usageLine
12
+ } from "./chunk-2OYTNNUJ.js";
13
+ import {
14
+ tourChoice
15
+ } from "./chunk-Y62J7PTD.js";
5
16
  import {
6
17
  AuditDirError,
7
18
  SPECIALIST_SPECS,
@@ -18,19 +29,21 @@ import {
18
29
  repoMapText,
19
30
  repoMapTool,
20
31
  verifyAuditDir
21
- } from "./chunk-NHWHG3SW.js";
32
+ } from "./chunk-STVFWSIL.js";
33
+ import {
34
+ hasReceipt
35
+ } from "./chunk-4QRTTRVK.js";
22
36
  import {
23
- budgetSpent,
24
37
  isCostIncomplete,
25
38
  pendingChildTokens
26
- } from "./chunk-UKIE4AAM.js";
39
+ } from "./chunk-NLS7GMF7.js";
27
40
  import {
28
41
  WEB_SEARCH_USD,
29
42
  totalTokens
30
43
  } from "./chunk-7KBOIPUT.js";
31
44
  import {
32
45
  BUILTIN_COMMANDS
33
- } from "./chunk-23FXBNBB.js";
46
+ } from "./chunk-MNE7HHX2.js";
34
47
  import {
35
48
  LSP_LANGUAGES
36
49
  } from "./chunk-EU7PA45O.js";
@@ -76,70 +89,19 @@ function focusEvent(input) {
76
89
  return void 0;
77
90
  }
78
91
 
79
- // src/cli/report.ts
80
- function formatTokens(n) {
81
- if (n < 1e3) return String(n);
82
- if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
83
- return `${(n / 1e6).toFixed(1)}M`;
84
- }
85
- function formatUsd(usd, incomplete = false) {
86
- if (usd === void 0) return "cost unknown";
87
- const amount = usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
88
- return incomplete ? `\u2265 ${amount} (incomplete)` : amount;
89
- }
90
- function budgetNote(session) {
91
- const unreported = session.unreportedTokens;
92
- const children = pendingChildTokens(session);
93
- if (unreported <= 0 && children <= 0) return void 0;
94
- const parts = [
95
- ...unreported > 0 ? [`${formatTokens(unreported)} reserved without reported usage`] : [],
96
- ...children > 0 ? [`${formatTokens(children)} from subagents with no report`] : []
97
- ];
98
- return `budget ${formatTokens(budgetSpent(session))} tokens (${parts.join(", ")})`;
99
- }
100
- function usageLine(result, session, runCostUsd, contextWindow, runCostIncomplete = false) {
101
- const u = result.usage;
102
- const input = u.inputTokens + u.cacheReadTokens + u.cacheWriteTokens;
103
- const percent = Math.round(session.contextTokens / contextWindow * 100);
104
- return [
105
- `${result.stopReason} \xB7 ${result.steps} step(s)`,
106
- `${budgetNote(session) !== void 0 ? "reported " : ""}${formatTokens(input)} in (${formatTokens(u.cacheReadTokens)} cached) / ${formatTokens(u.outputTokens)} out`,
107
- formatUsd(runCostUsd, runCostIncomplete),
108
- `context ${percent}% of ${formatTokens(contextWindow)}`,
109
- `session ${formatTokens(totalTokens(session.usage))} tokens, ${formatUsd(session.costUsd, isCostIncomplete(session))}`,
110
- ...budgetNote(session) === void 0 ? [] : [budgetNote(session)]
111
- ].join(" \xB7 ");
112
- }
113
- function stopMessage(reason, limits) {
114
- switch (reason) {
115
- case "done":
116
- return void 0;
117
- case "max_steps":
118
- return `Stopped: the run reached the step limit (${limits.maxSteps}). Use --resume to continue, or raise limits.maxSteps in .garuda/settings.json.`;
119
- case "token_budget":
120
- return `Stopped: the session used its token budget (${formatTokens(limits.tokenBudget)} tokens). Raise limits.tokenBudget in .garuda/settings.json to continue.`;
121
- case "repeated_calls":
122
- return "Stopped: the agent made the same tool call 3 times in a row, so it may be stuck. Use --resume with a hint to continue.";
123
- case "max_tokens":
124
- return "Stopped: the response reached the output token limit.";
125
- case "refusal":
126
- return "Stopped: the model refused the request.";
127
- }
128
- }
129
-
130
92
  // src/cli/turn.ts
131
- async function runTurnInTerminal(runtime, approver, renderer, prompt, exitNow, watcher) {
93
+ async function runTurnInTerminal(runtime, approver, renderer, prompt, exitNow, watcher, options = {}) {
132
94
  const started = performance.now();
133
95
  watcher?.turnStarted();
134
96
  let outcome;
135
97
  try {
136
- outcome = await runTurn(runtime, approver, renderer, prompt, exitNow);
98
+ outcome = await runTurn(runtime, approver, renderer, prompt, exitNow, options);
137
99
  return outcome;
138
100
  } finally {
139
101
  watcher?.turnEnded(outcome, performance.now() - started);
140
102
  }
141
103
  }
142
- async function runTurn(runtime, approver, renderer, prompt, exitNow) {
104
+ async function runTurn(runtime, approver, renderer, prompt, exitNow, options) {
143
105
  const controller = new AbortController();
144
106
  const stop = () => {
145
107
  if (controller.signal.aborted) exitNow();
@@ -158,12 +120,20 @@ async function runTurn(runtime, approver, renderer, prompt, exitNow) {
158
120
  if (session !== void 0) {
159
121
  const runCost = session.costUsd === void 0 ? void 0 : session.costUsd - costBefore;
160
122
  const runIncomplete = session.unreportedTokens > unreportedBefore || pendingChildTokens(session) > pendingBefore || session.costIncomplete && !incompleteBefore;
161
- renderer.info(
162
- `
123
+ const receipt = runtime.lastReceipt;
124
+ if (receipt !== void 0 && hasReceipt(receipt) && renderer.receipt !== void 0) {
125
+ renderer.receipt(receipt);
126
+ } else {
127
+ renderer.info(
128
+ `
163
129
  [${usageLine(result, session, runCost, runtime.limits.contextWindow, runIncomplete)}]`
164
- );
130
+ );
131
+ }
165
132
  }
166
- const message = stopMessage(result.stopReason, runtime.limits);
133
+ const message = (options.chat === true ? chatStopMessage : stopMessage)(
134
+ result.stopReason,
135
+ runtime.limits
136
+ );
167
137
  if (message !== void 0) renderer.warn(message);
168
138
  return { kind: "done", result };
169
139
  } catch (error) {
@@ -208,6 +178,34 @@ function modeText(runtime) {
208
178
  return runtime.mode === "plan" ? "Plan mode: the agent reads and plans. It cannot change files; commands run read-only in the sandbox. /build (or Shift+Tab) switches back." : "Build mode: the agent can change files and run commands, with the usual approvals.";
209
179
  }
210
180
 
181
+ // src/cli/chat/rewind.ts
182
+ function ago(at, now) {
183
+ if (at === void 0) return "";
184
+ const minutes = Math.floor(Math.max(0, now - at) / 6e4);
185
+ if (minutes < 1) return "<1 min";
186
+ if (minutes < 60) return `${minutes} min`;
187
+ const hours = Math.floor(minutes / 60);
188
+ return hours < 48 ? `${hours} h` : `${Math.floor(hours / 24)} d`;
189
+ }
190
+ function rewindList(points, now = Date.now()) {
191
+ if (points.length === 0) return "There is no turn to rewind to yet.";
192
+ const width = Math.min(40, Math.max(...points.map((p) => p.prompt.length + 2)));
193
+ const rows = points.map((p, i) => {
194
+ const prompt = `"${p.prompt}"`;
195
+ const shown = prompt.length > width ? `${prompt.slice(0, width - 2)}\u2026"` : prompt;
196
+ const files = p.changedFiles === void 0 ? "" : p.changedFiles === 0 ? "no files" : `${p.changedFiles} file${p.changedFiles === 1 ? "" : "s"}`;
197
+ const cost = p.costUsd === void 0 ? "" : formatUsd(p.costUsd);
198
+ const note = p.conversation ? "" : " (before a compaction: files only)";
199
+ return ` ${String(i + 1).padStart(3)} ${shown.padEnd(width)} ${files.padEnd(8)} ${cost.padEnd(8)} ${ago(p.at, now)}${note}`.trimEnd();
200
+ }).reverse();
201
+ return [
202
+ "Rewind to the start of a turn (newest first):",
203
+ ...rows,
204
+ "/rewind <n> goes back to the start of turn n (/redo brings the turns back).",
205
+ "/rewind fork <n> starts a new session from there; this one stays as it is."
206
+ ].join("\n");
207
+ }
208
+
211
209
  // src/cli/chat/commands.ts
212
210
  var HELP = [
213
211
  "Type a task and press Enter. @path attaches a file or folder, !command runs a command, Tab completes.",
@@ -217,7 +215,7 @@ var HELP = [
217
215
  " /session the session id and file",
218
216
  " /sessions this project's sessions; /sessions <n|id> continues one; rename, delete",
219
217
  " /models the models; /models <n|id|opus|sonnet|haiku> switches for this chat",
220
- " /export write this conversation as Markdown; /export <file>",
218
+ " /export write this conversation as Markdown; /export <file>; /export --html [file] as one page",
221
219
  " /compact summarise the older turns now; /compact <what to keep>",
222
220
  " /diff file changes in this session; /diff last (last turn); /diff [last] <path>",
223
221
  " /schedule make the last plan a job that runs later: /schedule [HH:MM], then garuda run <id>",
@@ -240,6 +238,7 @@ var HELP = [
240
238
  " /build build mode: change files and run commands (the default); /build <task> runs it",
241
239
  " /undo take back the last turn: its file changes and its messages",
242
240
  " /redo bring back the last undone turn",
241
+ " /rewind the turns of this session; /rewind <n> goes back to turn n, /rewind fork <n> forks",
243
242
  " /init set up this folder: AGENTS.md, and files from other agents (Claude Code, OpenCode \u2026)",
244
243
  " /editor write the next prompt in $VISUAL or $EDITOR (also Ctrl-G)",
245
244
  " /details show or hide the result line under each tool call; /details on|off",
@@ -362,6 +361,18 @@ async function runCommand(text, { runtime, renderer, sessionPath, output, signal
362
361
  await expertsCommand(runtime, renderer);
363
362
  } else if (command === "/audit") {
364
363
  await auditCommand(runtime, renderer, text.slice(command.length).trim());
364
+ } else if (command === "/rewind") {
365
+ const args = text.slice(command.length).trim().split(/\s+/).filter((a) => a !== "");
366
+ const signal2 = new AbortController().signal;
367
+ const fork = args[0] === "fork";
368
+ const number = fork ? args[1] : args[0];
369
+ if (number === void 0 && !fork) renderer.info(rewindList(runtime.rewindPoints));
370
+ else if (number === void 0 || !/^\d+$/.test(number) || args.length > (fork ? 2 : 1)) {
371
+ renderer.info("Use: /rewind, /rewind <n> or /rewind fork <n>.");
372
+ } else {
373
+ const turn = Number(number);
374
+ renderer.info(await (fork ? runtime.fork(turn, signal2) : runtime.rewind(turn, signal2)));
375
+ }
365
376
  } else if (command === "/undo" || command === "/redo") {
366
377
  const signal2 = new AbortController().signal;
367
378
  renderer.info(await (command === "/undo" ? runtime.undo(signal2) : runtime.redo(signal2)));
@@ -484,10 +495,10 @@ async function diffCommand(runtime, renderer, arg, output) {
484
495
  `Changes${where} ${since} (${result.files.length} file${result.files.length === 1 ? "" : "s"}, +${plus} \u2212${minus}). Changes you made yourself count too.`,
485
496
  ...rows,
486
497
  "",
487
- colorDiff(lines.slice(0, DIFF_LINES).join("\n")),
498
+ colorDiff(lines.slice(0, DIFF_LINES).join("\n"), process.stdout.columns),
488
499
  ...cut ? [`\u2026 ${lines.length - DIFF_LINES} more lines. ${more}`] : []
489
500
  ].join("\n"),
490
- cut ? { title: `diff ${since}${where}`, text: colorDiff(result.patch) } : void 0
501
+ cut ? { title: `diff ${since}${where}`, text: colorDiff(result.patch, process.stdout.columns) } : void 0
491
502
  );
492
503
  }
493
504
  async function jobsCommand(runtime, renderer, id) {
@@ -513,7 +524,7 @@ async function jobsCommand(runtime, renderer, id) {
513
524
  return;
514
525
  }
515
526
  const jobs = await listJobs2(runtime.root);
516
- const { defaultAgentEnv, nightAgentTime } = await import("./launchd-3A3ONEW5.js");
527
+ const { defaultAgentEnv, nightAgentTime } = await import("./launchd-QG6R5RAK.js");
517
528
  const night = nightAgentTime(runtime.root, defaultAgentEnv());
518
529
  const nightLine = night === void 0 ? [] : [
519
530
  `Night shift: launchd starts the queue at ${night.toTimeString().slice(0, 5)} on ${night.toDateString()} (/jobs cancel night removes it).`
@@ -551,8 +562,11 @@ async function exportCommand(runtime, renderer, file) {
551
562
  renderer.info("There is no conversation to export yet.");
552
563
  return;
553
564
  }
554
- const { sessionMarkdown, writeExport } = await import("./export-SJ3DUP6W.js");
555
- const result = await writeExport(runtime.root, id, sessionMarkdown(records, id), file);
565
+ const { sessionMarkdown, writeExport } = await import("./export-ZPF3AMO6.js");
566
+ const html = /^--html(\s|$)/.test(file);
567
+ const name = html ? file.slice("--html".length).trim() : file;
568
+ const text = html ? (await import("./exportHtml-ISISG5HG.js")).sessionHtml(records, id) : sessionMarkdown(records, id);
569
+ const result = await writeExport(runtime.root, id, text, name, html ? ".html" : ".md");
556
570
  if ("problem" in result) renderer.warn(result.problem);
557
571
  else
558
572
  renderer.info(
@@ -896,6 +910,7 @@ var RUN_AT_ONCE = /* @__PURE__ */ new Set([
896
910
  "build",
897
911
  "undo",
898
912
  "redo",
913
+ "rewind",
899
914
  "init",
900
915
  "editor",
901
916
  "details",
@@ -1097,8 +1112,30 @@ function rootFiles(root, now = Date.now) {
1097
1112
  };
1098
1113
  }
1099
1114
 
1115
+ // src/cli/chat/stop.ts
1116
+ var CONTINUE_PROMPT = "Go on with the task from where you stopped. You reached the step limit; the conversation above has your work so far.";
1117
+ var SUMMARY_PROMPT = "Stop the work here and do not call tools. Say what is done, what is left, and the next step.";
1118
+ async function stopHandoff(approver, maxSteps, isolation, signal = new AbortController().signal) {
1119
+ const request = {
1120
+ tool: "step_limit",
1121
+ target: { kind: "input", json: "{}" },
1122
+ preview: `The turn used its ${maxSteps} steps. The next turn gets ${maxSteps} more.`,
1123
+ isolation,
1124
+ title: "The task is not finished.",
1125
+ question: "Go on?",
1126
+ labels: {
1127
+ once: `Go on for ${maxSteps} more steps`,
1128
+ session: "Summarise what is done and what is left",
1129
+ deny: "Stop here"
1130
+ }
1131
+ };
1132
+ const choice = await approver.ask(request, signal);
1133
+ return choice === "once" ? "more" : choice === "session" ? "summary" : "stop";
1134
+ }
1135
+
1100
1136
  // src/cli/chat/controller.ts
1101
- async function runChat(runtime, store, sessionPath, exitNow, notifier) {
1137
+ async function runChat(runtime, store, sessionPath, exitNow, notifier, tour) {
1138
+ let tourTasks = tour;
1102
1139
  store.onToggleMode = () => {
1103
1140
  runtime.setMode(runtime.mode === "plan" ? "build" : "plan");
1104
1141
  return statusOf(runtime);
@@ -1117,7 +1154,8 @@ async function runChat(runtime, store, sessionPath, exitNow, notifier) {
1117
1154
  void args.refresh();
1118
1155
  const input = await store.nextInput();
1119
1156
  if (input === void 0) return;
1120
- const text = input.trim();
1157
+ const text = tourChoice(input.trim(), tourTasks);
1158
+ tourTasks = void 0;
1121
1159
  let prompt = text;
1122
1160
  if (text.startsWith("!") && text.length > 1) {
1123
1161
  await runShell(runtime, store, text.slice(1).trim());
@@ -1167,10 +1205,20 @@ async function runChat(runtime, store, sessionPath, exitNow, notifier) {
1167
1205
  store.begin(display, prompt === display);
1168
1206
  let outcome;
1169
1207
  try {
1170
- outcome = await runTurnInTerminal(runtime, store, store, prompt, exitNow, notifier);
1208
+ outcome = await runTurnInTerminal(runtime, store, store, prompt, exitNow, notifier, {
1209
+ chat: true
1210
+ });
1171
1211
  } finally {
1172
1212
  store.end(statusOf(runtime));
1173
1213
  }
1214
+ if (outcome.kind === "done" && outcome.result.stopReason === "max_steps") {
1215
+ const next2 = await stopHandoff(store, runtime.limits.maxSteps, runtime.executor.isolation);
1216
+ store.refreshStatus(statusOf(runtime));
1217
+ if (next2 === "stop") break;
1218
+ prompt = next2 === "more" ? CONTINUE_PROMPT : SUMMARY_PROMPT;
1219
+ display = prompt;
1220
+ continue;
1221
+ }
1174
1222
  if (!planning || outcome.kind !== "done" || outcome.result.stopReason !== "done") break;
1175
1223
  const next = await planHandoff(runtime, store);
1176
1224
  store.refreshStatus(statusOf(runtime));
@@ -1252,6 +1300,14 @@ var CommandArgs = class {
1252
1300
  return (first === "cancel" || first === "delete") && second === void 0 ? this.jobs : [];
1253
1301
  case "diff":
1254
1302
  return first === void 0 ? words("last") : [];
1303
+ case "rewind": {
1304
+ const turns = this.runtime.rewindPoints.map((p, i) => ({
1305
+ value: String(i + 1),
1306
+ hint: p.prompt
1307
+ }));
1308
+ if (first === void 0) return [...words("fork"), ...turns];
1309
+ return first === "fork" && second === void 0 ? turns : [];
1310
+ }
1255
1311
  case "mcp":
1256
1312
  if (first === void 0) return words("logout");
1257
1313
  return first === "logout" && second === void 0 ? this.runtime.mcpStatus().map((s) => ({ value: s.name })) : [];
@@ -1278,8 +1334,6 @@ export {
1278
1334
  reportsFocus,
1279
1335
  FocusTracker,
1280
1336
  focusEvent,
1281
- formatTokens,
1282
- stopMessage,
1283
1337
  BUILD_PROMPT,
1284
1338
  planHandoff,
1285
1339
  runCommand,
@@ -1288,9 +1342,12 @@ export {
1288
1342
  fuzzyFiles,
1289
1343
  rootFiles,
1290
1344
  runTurnInTerminal,
1345
+ CONTINUE_PROMPT,
1346
+ SUMMARY_PROMPT,
1347
+ stopHandoff,
1291
1348
  runChat,
1292
1349
  commandNames,
1293
1350
  statusOf,
1294
1351
  CommandArgs
1295
1352
  };
1296
- //# sourceMappingURL=chunk-OQ26W5L4.js.map
1353
+ //# sourceMappingURL=chunk-HP6EGFHV.js.map
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/uiState.ts
4
+ import { mkdir, readFile, writeFile } from "fs/promises";
5
+ import { join } from "path";
6
+ var KEY_HINT_CHATS = 5;
7
+ async function countChat(home) {
8
+ const dir = join(home, ".garuda");
9
+ const file = join(dir, "ui.json");
10
+ let chats = 0;
11
+ let data = {};
12
+ try {
13
+ const read = JSON.parse(await readFile(file, "utf8"));
14
+ if (typeof read === "object" && read !== null && !Array.isArray(read)) {
15
+ data = read;
16
+ }
17
+ if (typeof data.chats === "number" && Number.isInteger(data.chats) && data.chats >= 0) {
18
+ chats = data.chats;
19
+ }
20
+ } catch {
21
+ }
22
+ chats++;
23
+ try {
24
+ await mkdir(dir, { recursive: true, mode: 448 });
25
+ await writeFile(file, `${JSON.stringify({ ...data, chats })}
26
+ `, { mode: 384 });
27
+ } catch {
28
+ }
29
+ return chats;
30
+ }
31
+ var KEYS_HELP = [
32
+ "Keys:",
33
+ " Enter sends \xB7 \\ then Enter, or Alt+Enter: a new line \xB7 \u2191\u2193 earlier inputs",
34
+ " Tab completes /commands and @paths \xB7 Shift+Tab plan mode \xB7 Ctrl-P commands",
35
+ " Ctrl-G edits the prompt in your editor \xB7 Ctrl-O shows the last output in full",
36
+ " Esc or Ctrl-C stops a turn \xB7 Ctrl-C clears the line \xB7 Ctrl-D exits",
37
+ " !command runs a command yourself \xB7 @path attaches a file"
38
+ ].join("\n");
39
+ function contextBar(percent) {
40
+ const full = percent <= 0 ? 0 : Math.max(1, Math.min(10, Math.round(percent / 10)));
41
+ return `${"\u2593".repeat(full)}${"\u2591".repeat(10 - full)}`;
42
+ }
43
+ function contextLevel(percent) {
44
+ return percent >= 80 ? "full" : percent >= 70 ? "high" : "ok";
45
+ }
46
+
47
+ export {
48
+ KEY_HINT_CHATS,
49
+ countChat,
50
+ KEYS_HELP,
51
+ contextBar,
52
+ contextLevel
53
+ };
54
+ //# sourceMappingURL=chunk-L55VUSX3.js.map
@@ -42,6 +42,10 @@ var NETWORK_PRESETS = {
42
42
  ]
43
43
  };
44
44
  var PRESET_NAMES = Object.keys(NETWORK_PRESETS);
45
+ function presetFor(host) {
46
+ const bare = host.toLowerCase();
47
+ return PRESET_NAMES.find((name) => NETWORK_PRESETS[name]?.includes(bare));
48
+ }
45
49
  var NETWORK_PORTS = [80, 443];
46
50
  var HOST = /^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,63}$/;
47
51
  function expandAllowlist(entries) {
@@ -74,9 +78,9 @@ function describeAllowlist(entries) {
74
78
  }
75
79
 
76
80
  // src/permissions/sandboxPaths.ts
77
- import { realpathSync } from "fs";
81
+ import { readdirSync, realpathSync } from "fs";
78
82
  import { homedir, tmpdir } from "os";
79
- import { isAbsolute, join, resolve } from "path";
83
+ import { isAbsolute, join, resolve, sep } from "path";
80
84
  import { globbySync } from "globby";
81
85
  var DENY_READ_IN_HOME = [
82
86
  ".ssh",
@@ -101,6 +105,49 @@ var DENY_READ_IN_HOME = [
101
105
  ".claude.json",
102
106
  ".claude/.credentials.json"
103
107
  ];
108
+ var PERSONAL_IN_HOME = [
109
+ "Desktop",
110
+ "Documents",
111
+ "Downloads",
112
+ "Movies",
113
+ "Music",
114
+ "Pictures",
115
+ "Public",
116
+ "Library/Mail",
117
+ "Library/Messages",
118
+ "Library/Safari",
119
+ "Library/Calendars",
120
+ "Library/Application Support/AddressBook",
121
+ "Library/Mobile Documents",
122
+ "Library/CloudStorage"
123
+ ];
124
+ function denyAround(base, keep, keepHidden = false) {
125
+ let names;
126
+ try {
127
+ names = readdirSync(base);
128
+ } catch {
129
+ return [];
130
+ }
131
+ const out = [];
132
+ for (const name of names) {
133
+ const path = join(base, name);
134
+ if (keep.includes(path)) continue;
135
+ if (keep.some((k) => k.startsWith(`${path}${sep}`))) {
136
+ out.push(...denyAround(path, keep));
137
+ continue;
138
+ }
139
+ if (keepHidden && name.startsWith(".")) continue;
140
+ out.push(path);
141
+ }
142
+ return out;
143
+ }
144
+ function personalDenials(home, root) {
145
+ return PERSONAL_IN_HOME.flatMap((name) => {
146
+ const folder = join(home, name);
147
+ if (root === folder) return [];
148
+ return root.startsWith(`${folder}${sep}`) ? denyAround(folder, [root]) : [folder];
149
+ });
150
+ }
104
151
  var PROTECTED_IN_ROOT = [".git/hooks", ".git/config", ".garuda"];
105
152
  var WRITE_IN_HOME = [
106
153
  ".cache",
@@ -122,6 +169,7 @@ function sandboxPaths(root, settings = {}, home = homedir()) {
122
169
  denyWritePaths: PROTECTED_IN_ROOT.map((p) => join(root, p)),
123
170
  denyReadPaths: unique([
124
171
  ...DENY_READ_IN_HOME.map((p) => join(home, p)),
172
+ ...personalDenials(home, root),
125
173
  ...(settings.denyRead ?? []).map(expand)
126
174
  ])
127
175
  };
@@ -817,10 +865,10 @@ function seatbeltProfile(policy, extra = []) {
817
865
  }
818
866
  }
819
867
  if (policy.denyWritePaths.length > 0) {
820
- lines.push(`(deny file-write* ${paths(policy.denyWritePaths)})`);
868
+ lines.push(`(deny file-write* ${paths(withDataVolume(policy.denyWritePaths))})`);
821
869
  }
822
870
  if (policy.denyReadPaths.length > 0) {
823
- lines.push(`(deny file-read* ${paths(policy.denyReadPaths)})`);
871
+ lines.push(`(deny file-read* ${paths(withDataVolume(policy.denyReadPaths))})`);
824
872
  }
825
873
  lines.push("(deny lsopen)", "(deny appleevent-send)");
826
874
  if (!policy.network) {
@@ -836,6 +884,14 @@ function seatbeltProfile(policy, extra = []) {
836
884
  lines.push(...extra);
837
885
  return lines.join("\n");
838
886
  }
887
+ function withDataVolume(list) {
888
+ const out = [];
889
+ for (const path of list) {
890
+ out.push(path);
891
+ if (path.startsWith("/Users/")) out.push(`/System/Volumes/Data${path}`);
892
+ }
893
+ return out;
894
+ }
839
895
  function quote2(path) {
840
896
  return `"${path.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
841
897
  }
@@ -976,6 +1032,8 @@ var schema = z.strictObject({
976
1032
  }).optional(),
977
1033
  /** Language server diagnostics after edits (0.4). enabled: default false. */
978
1034
  lsp: z.strictObject({ enabled: z.boolean().optional() }).optional(),
1035
+ /** The chat's look (0.18.1). clearOnStart: default true (the Ink chat starts on a clear screen). */
1036
+ ui: z.strictObject({ clearOnStart: z.boolean().optional() }).optional(),
979
1037
  /**
980
1038
  * The project's formatter after edits (0.10). enabled: default false (A/B first). commands: by
981
1039
  * name, false turns a detected formatter off; a command adds one or replaces a detected one.
@@ -1065,6 +1123,7 @@ function parseSettings(json, source = SETTINGS_FILE) {
1065
1123
  skills,
1066
1124
  agents,
1067
1125
  notifications,
1126
+ ui,
1068
1127
  thinking,
1069
1128
  formatters,
1070
1129
  network
@@ -1102,6 +1161,7 @@ function parseSettings(json, source = SETTINGS_FILE) {
1102
1161
  ...commands?.mode === void 0 ? {} : { commands: { mode: commands.mode } },
1103
1162
  ...todo?.enabled === void 0 ? {} : { todo: { enabled: todo.enabled } },
1104
1163
  ...lsp?.enabled === void 0 ? {} : { lsp: { enabled: lsp.enabled } },
1164
+ ...ui?.clearOnStart === void 0 ? {} : { ui: { clearOnStart: ui.clearOnStart } },
1105
1165
  ...undo?.enabled === void 0 ? {} : { undo: { enabled: undo.enabled } },
1106
1166
  ...skills?.enabled === void 0 ? {} : { skills: { enabled: skills.enabled } },
1107
1167
  ...agents?.enabled === void 0 ? {} : { agents: { enabled: agents.enabled } },
@@ -1160,7 +1220,7 @@ async function loadSettings(root) {
1160
1220
  }
1161
1221
 
1162
1222
  // src/permissions/engine.ts
1163
- import { sep } from "path";
1223
+ import { sep as sep2 } from "path";
1164
1224
 
1165
1225
  // src/permissions/sensitive.ts
1166
1226
  var SENSITIVE_PATTERNS = [
@@ -1430,7 +1490,14 @@ var PermissionEngine = class {
1430
1490
  if (choice === "once" && accepted !== void 0) {
1431
1491
  return { decision: { allowed: true, by: "user", hunks: accepted }, userChoice: choice };
1432
1492
  }
1433
- return { decision: { allowed: true, by: "user" }, userChoice: choice };
1493
+ return {
1494
+ decision: {
1495
+ allowed: true,
1496
+ by: "user",
1497
+ ...choice === "session" ? { remembered: true } : {}
1498
+ },
1499
+ userChoice: choice
1500
+ };
1434
1501
  }
1435
1502
  /**
1436
1503
  * Plan mode: file changes and memory are always denied; a command runs only in the OS sandbox
@@ -1518,7 +1585,7 @@ var PermissionEngine = class {
1518
1585
  const denied = policyDeniedPaths(this.root, this.policy?.denyPaths ?? []);
1519
1586
  let writePaths = [.../* @__PURE__ */ new Set([...paths.writePaths, ...this.access.writePaths])];
1520
1587
  let denyWritePaths = [...paths.denyWritePaths, ...denied];
1521
- const inRoot = (p) => p === this.root || p.startsWith(`${this.root}${sep}`);
1588
+ const inRoot = (p) => p === this.root || p.startsWith(`${this.root}${sep2}`);
1522
1589
  if (readOnlyRoot) {
1523
1590
  writePaths = writePaths.filter((p) => !inRoot(p));
1524
1591
  denyWritePaths = [this.root, ...denyWritePaths];
@@ -1588,10 +1655,12 @@ function describeTarget(target) {
1588
1655
 
1589
1656
  export {
1590
1657
  isSensitive,
1658
+ presetFor,
1591
1659
  NETWORK_PORTS,
1592
1660
  expandAllowlist,
1593
1661
  hostAllowed,
1594
1662
  describeAllowlist,
1663
+ denyAround,
1595
1664
  sandboxPaths,
1596
1665
  HostExecutor,
1597
1666
  EXECUTOR_NAMES,
@@ -1602,4 +1671,4 @@ export {
1602
1671
  JOB_DENIAL,
1603
1672
  PermissionEngine
1604
1673
  };
1605
- //# sourceMappingURL=chunk-B4SJY7KT.js.map
1674
+ //# sourceMappingURL=chunk-LMKGSYJC.js.map