@garuda-agent/garuda 0.18.0 → 0.18.1

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 (61) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +3 -2
  3. package/dist/{acpCommand-IUZPLPLI.js → acpCommand-JAYNMIMP.js} +17 -14
  4. package/dist/advise-HDKJGNBL.js +195 -0
  5. package/dist/brief-RWKESOPT.js +91 -0
  6. package/dist/buildCheck-HB46NWLX.js +19 -0
  7. package/dist/buildInfo-K73FFI5S.js +8 -0
  8. package/dist/chunk-2OYTNNUJ.js +216 -0
  9. package/dist/{chunk-OQ26W5L4.js → chunk-34WBDQBL.js} +134 -77
  10. package/dist/chunk-4QRTTRVK.js +88 -0
  11. package/dist/{chunk-NHWHG3SW.js → chunk-5UDYQ5MK.js} +354 -59
  12. package/dist/chunk-CWVXOSAA.js +894 -0
  13. package/dist/{chunk-FARS4R67.js → chunk-DQSCCE77.js} +18 -1
  14. package/dist/chunk-DUYLVGPY.js +821 -0
  15. package/dist/{chunk-Q4P3W657.js → chunk-GKU62MHQ.js} +10 -1124
  16. package/dist/chunk-H7LIBGZT.js +9 -0
  17. package/dist/{chunk-FOPBOB7W.js → chunk-IKPGHM54.js} +6 -4
  18. package/dist/chunk-IYGJWX65.js +9 -0
  19. package/dist/{chunk-65NUEOI7.js → chunk-JMH5W5DG.js} +1 -44
  20. package/dist/chunk-L55VUSX3.js +54 -0
  21. package/dist/{chunk-B4SJY7KT.js → chunk-LMKGSYJC.js} +77 -8
  22. package/dist/{chunk-23FXBNBB.js → chunk-MNE7HHX2.js} +2 -1
  23. package/dist/chunk-NH72AELP.js +48 -0
  24. package/dist/{chunk-UKIE4AAM.js → chunk-NLS7GMF7.js} +11 -105
  25. package/dist/{chunk-2FIV2VZP.js → chunk-QLBCM2YQ.js} +2 -2
  26. package/dist/{chunk-6XHON3Q6.js → chunk-QXTHEFKP.js} +3 -3
  27. package/dist/{chunk-4BVCYCQT.js → chunk-R7RCW4BQ.js} +9 -7
  28. package/dist/chunk-TLA2PDGM.js +156 -0
  29. package/dist/{chunk-5B5HDTUR.js → chunk-TWQ7V2L6.js} +2 -2
  30. package/dist/chunk-ULAJPMY7.js +107 -0
  31. package/dist/chunk-VISKX36R.js +306 -0
  32. package/dist/{chunk-55CFEEIN.js → chunk-WK5NE6RY.js} +2 -2
  33. package/dist/chunk-Y62J7PTD.js +90 -0
  34. package/dist/chunk-YAQ6QLKB.js +40 -0
  35. package/dist/{chunk-7TH3NHCQ.js → chunk-ZKT4P2PC.js} +2 -2
  36. package/dist/cli/index.js +252 -61
  37. package/dist/{create-W6BV75QX.js → create-OACSGJ37.js} +5 -5
  38. package/dist/{evalCommand-SKJQ6CW2.js → evalCommand-BLJJ4G76.js} +43 -33
  39. package/dist/{evalTune-CZRCPSZ3.js → evalTune-W53KAD4V.js} +25 -18
  40. package/dist/{export-SJ3DUP6W.js → export-E677HIQS.js} +27 -4
  41. package/dist/exportHtml-BMV73YRO.js +21 -0
  42. package/dist/{inkChat-WYYAFZXD.js → inkChat-EVLYDC3K.js} +258 -42
  43. package/dist/{install-TKWN5AYH.js → install-4ST5BNRG.js} +3 -3
  44. package/dist/{jobCommand-2ODQLHB3.js → jobCommand-ILOTYOX3.js} +8 -7
  45. package/dist/{launchd-3A3ONEW5.js → launchd-QG6R5RAK.js} +4 -4
  46. package/dist/{lspCommand-ZZ6R4PA2.js → lspCommand-X4FTTONR.js} +3 -3
  47. package/dist/{manager-TCYTA5XU.js → manager-L25IUXHW.js} +3 -3
  48. package/dist/{nightCommand-WJU6VZA6.js → nightCommand-XPWT6CQZ.js} +8 -7
  49. package/dist/receiptView-YFDHV253.js +19 -0
  50. package/dist/{remove-42WQNVPX.js → remove-STX5UO7X.js} +3 -3
  51. package/dist/{run-PTMRQCIU.js → run-W4TK52WT.js} +5 -5
  52. package/dist/{server-VEYIJHEU.js → server-ASM73QVY.js} +3 -3
  53. package/dist/{setupCommand-YMF6I3MD.js → setupCommand-OSOMFHDQ.js} +9 -8
  54. package/dist/tour-CEVMAC5S.js +18 -0
  55. package/dist/tune-VX2WQTHS.js +52 -0
  56. package/dist/uiState-YFJQ43D7.js +16 -0
  57. package/package.json +1 -1
  58. package/dist/chunk-4JSUUDFQ.js +0 -118
  59. package/dist/chunk-C57GHLKP.js +0 -9
  60. package/dist/chunk-DOCDCQVM.js +0 -484
  61. package/dist/tune-YVI2KWQF.js +0 -31
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ summariseCall,
4
+ summariseResult
5
+ } from "./chunk-DQSCCE77.js";
6
+ import {
7
+ hasReceipt
8
+ } from "./chunk-4QRTTRVK.js";
9
+ import {
10
+ serverCallText,
11
+ serverResultSummary
12
+ } from "./chunk-A2QAIRQL.js";
13
+ import {
14
+ receiptText
15
+ } from "./chunk-2OYTNNUJ.js";
16
+
17
+ // src/cli/exportHtml.ts
18
+ function escapeHtml(text) {
19
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
20
+ }
21
+ var OUTPUT_CHARS = 4e3;
22
+ var cutOutput = (text) => text.length <= OUTPUT_CHARS ? text : `${text.slice(0, OUTPUT_CHARS)}
23
+ \u2026 ${text.length - OUTPUT_CHARS} more characters in the session file.`;
24
+ function callChange(call) {
25
+ const input = call.input ?? {};
26
+ const lines = (text, sign) => typeof text === "string" ? text.split("\n").map((l) => `${sign}${l}`) : [];
27
+ if (call.name === "edit_file") {
28
+ return [...lines(input.old_string, "-"), ...lines(input.new_string, "+")].join("\n");
29
+ }
30
+ if (call.name === "write_file") return lines(input.content, "+").join("\n");
31
+ return void 0;
32
+ }
33
+ function diffHtml(text) {
34
+ return text.split("\n").map((line) => {
35
+ const cls = line.startsWith("+") ? "add" : line.startsWith("-") ? "del" : "ctx";
36
+ return `<span class="${cls}">${escapeHtml(line)}</span>`;
37
+ }).join("\n");
38
+ }
39
+ var STYLE = `
40
+ :root { --bg: #fbf7ef; --fg: #222; --dim: #6b6b6b; --line: #e4dccb; --accent: #d97706;
41
+ --add: #e6f4e6; --addfg: #17602a; --del: #fbe7e7; --delfg: #8f1d1d; --you: #0e7490; }
42
+ @media (prefers-color-scheme: dark) { :root { --bg: #1d1d1b; --fg: #e8e4dc; --dim: #a19c92;
43
+ --line: #3a3833; --add: #183522; --addfg: #9ee2ad; --del: #3b1d1d; --delfg: #f4a9a9; } }
44
+ body { background: var(--bg); color: var(--fg); font: 15px/1.55 system-ui, sans-serif;
45
+ max-width: 920px; margin: 32px auto; padding: 0 16px; }
46
+ h1 { font-size: 20px; } .meta { color: var(--dim); font-size: 13px; }
47
+ .you { border-left: 4px solid var(--you); padding: 4px 12px; margin: 24px 0 8px; font-weight: 600;
48
+ white-space: pre-wrap; }
49
+ .answer { white-space: pre-wrap; margin: 8px 0; }
50
+ .answer::before { content: "\u2726 "; color: var(--accent); }
51
+ details { border: 1px solid var(--line); border-radius: 6px; margin: 6px 0; padding: 4px 10px; }
52
+ summary { cursor: pointer; font-family: ui-monospace, monospace; font-size: 13px; }
53
+ pre { font: 12.5px/1.45 ui-monospace, monospace; white-space: pre-wrap; overflow-wrap: anywhere;
54
+ margin: 6px 0; }
55
+ .add { background: var(--add); color: var(--addfg); display: inline-block; width: 100%; }
56
+ .del { background: var(--del); color: var(--delfg); display: inline-block; width: 100%; }
57
+ .receipt { border: 1px solid var(--accent); border-radius: 6px; padding: 6px 10px; }
58
+ .note { color: var(--dim); font-style: italic; }
59
+ .failed summary { color: var(--delfg); }
60
+ `;
61
+ function sessionHtml(records, sessionId) {
62
+ const body = [];
63
+ const start = records.find((r) => r.type === "start");
64
+ body.push(`<h1>Garuda session ${escapeHtml(sessionId)}</h1>`);
65
+ if (start?.type === "start") {
66
+ body.push(
67
+ `<p class="meta">${escapeHtml(start.root)} \xB7 ${escapeHtml(start.model)} \xB7 ${escapeHtml(start.t)}</p>`
68
+ );
69
+ }
70
+ const calls = /* @__PURE__ */ new Map();
71
+ for (const record of records) {
72
+ switch (record.type) {
73
+ case "user": {
74
+ const prompt = record.message.content[0];
75
+ body.push(
76
+ `<div class="you">${escapeHtml(prompt?.type === "text" ? prompt.text : "")}</div>`
77
+ );
78
+ break;
79
+ }
80
+ case "assistant":
81
+ for (const block of record.response.content) {
82
+ if (block.type === "text" && block.text.trim() !== "") {
83
+ body.push(`<div class="answer">${escapeHtml(block.text.trim())}</div>`);
84
+ }
85
+ if (block.type === "tool_use") calls.set(block.id, block);
86
+ if (block.type === "server_tool_result") {
87
+ const call = record.response.content.find(
88
+ (b) => b.type === "server_tool_use" && b.id === block.toolUseId
89
+ );
90
+ const query = call?.type === "server_tool_use" ? ` ${serverCallText(call)}` : "";
91
+ const pages = block.results.map((r) => `${r.title}
92
+ ${r.url}`).join("\n");
93
+ body.push(
94
+ `<details><summary>${escapeHtml(`\u25CF ${block.name} (Claude)${query} \u2192 ${serverResultSummary(block)}`)}</summary><pre>${escapeHtml(pages)}</pre></details>`
95
+ );
96
+ }
97
+ }
98
+ break;
99
+ case "tool_results":
100
+ for (const block of record.message.content) {
101
+ if (block.type !== "tool_result") continue;
102
+ const call = calls.get(block.toolUseId);
103
+ if (call === void 0) continue;
104
+ const summary = summariseResult(call, { content: block.content, isError: block.isError });
105
+ const head = `\u25CF ${[call.name, summariseCall(call)].filter((p) => p !== "").join(" ")} \u2192 ${block.isError ? `failed: ${summary}` : summary}`;
106
+ const change = callChange(call);
107
+ const inner = change === void 0 ? `<pre>${escapeHtml(cutOutput(block.content))}</pre>` : `<pre>${diffHtml(cutOutput(change))}</pre>`;
108
+ body.push(
109
+ `<details${block.isError ? ' class="failed"' : ""}><summary>${escapeHtml(head)}</summary>${inner}</details>`
110
+ );
111
+ }
112
+ break;
113
+ case "receipt":
114
+ if (hasReceipt(record.receipt)) {
115
+ body.push(`<pre class="receipt">${escapeHtml(receiptText(record.receipt))}</pre>`);
116
+ }
117
+ break;
118
+ case "undo":
119
+ body.push('<p class="note">/undo: the last turn was taken back.</p>');
120
+ break;
121
+ case "redo":
122
+ body.push('<p class="note">/redo: the turn came back.</p>');
123
+ break;
124
+ case "compaction":
125
+ body.push('<p class="note">The conversation was compacted here.</p>');
126
+ break;
127
+ case "fork":
128
+ body.push(
129
+ `<p class="note">This session starts as a fork of session ${escapeHtml(record.from.sessionId)}, at turn ${record.from.turn}.</p>`
130
+ );
131
+ break;
132
+ default:
133
+ break;
134
+ }
135
+ }
136
+ return [
137
+ "<!doctype html>",
138
+ '<html lang="en"><head><meta charset="utf-8">',
139
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
140
+ // No script may run, and nothing loads from outside: the page's own policy says so too.
141
+ `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">`,
142
+ `<title>Garuda session ${escapeHtml(sessionId)}</title>`,
143
+ `<style>${STYLE}</style></head><body>`,
144
+ ...body,
145
+ "</body></html>",
146
+ ""
147
+ ].join("\n");
148
+ }
149
+
150
+ export {
151
+ escapeHtml,
152
+ OUTPUT_CHARS,
153
+ callChange,
154
+ sessionHtml
155
+ };
156
+ //# sourceMappingURL=chunk-TLA2PDGM.js.map
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  hostCommand,
4
4
  shellWord
5
- } from "./chunk-6XHON3Q6.js";
5
+ } from "./chunk-QXTHEFKP.js";
6
6
  import {
7
7
  JOBS_DIR
8
8
  } from "./chunk-VTMD5IHD.js";
@@ -154,4 +154,4 @@ export {
154
154
  nightAgentTime,
155
155
  removeAgent
156
156
  };
157
- //# sourceMappingURL=chunk-5B5HDTUR.js.map
157
+ //# sourceMappingURL=chunk-TWQ7V2L6.js.map
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ cleanLine
4
+ } from "./chunk-PZS3V6P3.js";
5
+ import {
6
+ formatRule
7
+ } from "./chunk-3NJNVZTK.js";
8
+
9
+ // src/permissions/projectSettings.ts
10
+ import { createHash } from "crypto";
11
+ function settingsRisk(settings) {
12
+ const risk = {
13
+ executorHost: settings.executor === "host",
14
+ allow: settings.allow.map(formatRule),
15
+ writePaths: [...settings.sandbox?.writePaths ?? []],
16
+ envAllow: [...settings.envAllow],
17
+ allowLocalhost: settings.web?.allowLocalhost === true
18
+ };
19
+ const formatterCommands = Object.entries(settings.formatters?.commands ?? {}).flatMap(
20
+ ([name, f]) => f === false ? [] : [`${name}: ${f.command.join(" ")}`]
21
+ );
22
+ if (formatterCommands.length > 0) risk.formatterCommands = formatterCommands;
23
+ const none = !risk.executorHost && risk.allow.length === 0 && risk.writePaths.length === 0 && risk.envAllow.length === 0 && !risk.allowLocalhost && risk.formatterCommands === void 0;
24
+ return none ? void 0 : risk;
25
+ }
26
+ function settingsRiskHash(risk) {
27
+ return createHash("sha256").update(JSON.stringify(risk)).digest("hex");
28
+ }
29
+ function settingsRiskLines(risk) {
30
+ const lines = [];
31
+ if (risk.executorHost) lines.push('executor: "host" (commands run with no OS sandbox)');
32
+ if (risk.allow.length > 0) {
33
+ lines.push(`permissions.allow (no question for these): ${risk.allow.join(", ")}`);
34
+ }
35
+ if (risk.writePaths.length > 0) {
36
+ lines.push(`sandbox.writePaths (commands may write there): ${risk.writePaths.join(", ")}`);
37
+ }
38
+ if (risk.envAllow.length > 0) {
39
+ lines.push(`env.allow (commands see these variables): ${risk.envAllow.join(", ")}`);
40
+ }
41
+ if (risk.allowLocalhost) lines.push("web.allowLocalhost (web_fetch may reach this machine)");
42
+ if (risk.formatterCommands !== void 0) {
43
+ lines.push(
44
+ `formatters.commands (run after each edit): ${risk.formatterCommands.map(cleanLine).join("; ")}`
45
+ );
46
+ }
47
+ return lines;
48
+ }
49
+ function withoutRisk(settings) {
50
+ const { sandbox, web, formatters, ...rest } = settings;
51
+ const { writePaths: _writePaths, ...sandboxRest } = sandbox ?? {};
52
+ const keptCommands = Object.fromEntries(
53
+ Object.entries(formatters?.commands ?? {}).filter(([, f]) => f === false)
54
+ );
55
+ return {
56
+ ...rest,
57
+ ...formatters === void 0 ? {} : { formatters: { ...formatters, commands: keptCommands } },
58
+ executor: settings.executor === "host" ? "auto" : settings.executor,
59
+ allow: [],
60
+ envAllow: [],
61
+ ...sandbox === void 0 ? {} : { sandbox: sandboxRest },
62
+ ...web === void 0 ? {} : { web: { ...web, allowLocalhost: false } }
63
+ };
64
+ }
65
+ async function gateProjectSettings(options) {
66
+ const risk = settingsRisk(options.settings);
67
+ if (risk === void 0) return { settings: options.settings };
68
+ const hash = settingsRiskHash(risk);
69
+ const known = options.trust?.settingsHash(options.root);
70
+ if (known === hash) return { settings: options.settings };
71
+ const lines = settingsRiskLines(risk);
72
+ if (options.ask !== void 0) {
73
+ const choice = await options.ask({
74
+ tool: "settings",
75
+ target: { kind: "input", json: "" },
76
+ title: "Project settings that loosen safety",
77
+ preview: [
78
+ `This project's .garuda/settings.json ${known === void 0 ? "asks for" : "changed and now asks for"}:`,
79
+ ...lines.map((line) => ` - ${line}`),
80
+ "A cloned repository can ship this file. Without your yes, Garuda uses safe defaults for these",
81
+ "parts; the rest of the file applies."
82
+ ].join("\n"),
83
+ isolation: options.isolation ?? "os",
84
+ question: "Use these project settings?",
85
+ choices: ["once", "session", "deny"],
86
+ labels: {
87
+ once: "Yes, for this run",
88
+ session: "Yes, and remember (asks again when they change)",
89
+ deny: "No, use safe defaults"
90
+ }
91
+ });
92
+ if (choice === "session") await options.trust?.setSettingsHash(options.root, hash);
93
+ if (choice !== "deny") return { settings: options.settings };
94
+ }
95
+ return {
96
+ settings: withoutRisk(options.settings),
97
+ notice: [
98
+ `Project settings not applied (not approved): ${lines.join("; ")}.`,
99
+ "Start the chat in a terminal and answer yes to use them."
100
+ ].join(" ")
101
+ };
102
+ }
103
+
104
+ export {
105
+ gateProjectSettings
106
+ };
107
+ //# sourceMappingURL=chunk-ULAJPMY7.js.map
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/approver.ts
4
+ import { styleText } from "util";
5
+
6
+ // src/cli/diffView.ts
7
+ var SIDE_BY_SIDE_COLUMNS = 140;
8
+ function tokens(line) {
9
+ return line.match(/\s+|[\p{L}\p{N}_]+|[^\s\p{L}\p{N}_]/gu) ?? [];
10
+ }
11
+ var MAX_CELLS = 4e4;
12
+ function wordDiff(a, b) {
13
+ const x = tokens(a);
14
+ const y = tokens(b);
15
+ if (x.length * y.length > MAX_CELLS || x.length + y.length === 0) return void 0;
16
+ const n = x.length;
17
+ const m = y.length;
18
+ const table = new Uint16Array((n + 1) * (m + 1));
19
+ const at = (i2, j2) => i2 * (m + 1) + j2;
20
+ for (let i2 = n - 1; i2 >= 0; i2--) {
21
+ for (let j2 = m - 1; j2 >= 0; j2--) {
22
+ table[at(i2, j2)] = x[i2] === y[j2] ? (table[at(i2 + 1, j2 + 1)] ?? 0) + 1 : Math.max(table[at(i2 + 1, j2)] ?? 0, table[at(i2, j2 + 1)] ?? 0);
23
+ }
24
+ }
25
+ const common = table[at(0, 0)] ?? 0;
26
+ const solid = (t) => t.filter((s) => s.trim() !== "").length;
27
+ if (common === 0 || common / Math.max(1, Math.max(solid(x), solid(y))) < 0.4) return void 0;
28
+ const outA = [];
29
+ const outB = [];
30
+ let i = 0;
31
+ let j = 0;
32
+ while (i < n || j < m) {
33
+ if (i < n && j < m && x[i] === y[j]) {
34
+ outA.push({ text: x[i], changed: false });
35
+ outB.push({ text: y[j], changed: false });
36
+ i++;
37
+ j++;
38
+ } else if (j < m && (i >= n || (table[at(i, j + 1)] ?? 0) >= (table[at(i + 1, j)] ?? 0))) {
39
+ outB.push({ text: y[j], changed: true });
40
+ j++;
41
+ } else {
42
+ outA.push({ text: x[i], changed: true });
43
+ i++;
44
+ }
45
+ }
46
+ return { a: outA, b: outB };
47
+ }
48
+ function paintParts(parts, base, paint) {
49
+ const runs = [];
50
+ for (const p of parts) {
51
+ const last = runs.at(-1);
52
+ if (last !== void 0 && last.changed === p.changed) last.text += p.text;
53
+ else runs.push({ ...p });
54
+ }
55
+ return runs.map(
56
+ (r) => r.changed && r.text.trim() !== "" ? paint([base, "inverse"], r.text) : paint([base], r.text)
57
+ ).join("");
58
+ }
59
+ function unifiedDiff(text, paint) {
60
+ const lines = text.split("\n");
61
+ const out = [];
62
+ let i = 0;
63
+ while (i < lines.length) {
64
+ const line = lines[i];
65
+ if (isFileHeader(line)) {
66
+ out.push(paint(["bold"], line));
67
+ i++;
68
+ continue;
69
+ }
70
+ if (line.startsWith("@@")) {
71
+ out.push(paint(["cyan"], line));
72
+ i++;
73
+ continue;
74
+ }
75
+ if (line.startsWith("-")) {
76
+ const removed = [];
77
+ while (i < lines.length && isRemoved(lines[i])) removed.push(lines[i++]);
78
+ const added = [];
79
+ while (i < lines.length && isAdded(lines[i])) added.push(lines[i++]);
80
+ const marks = removed.map((r, k) => {
81
+ const a = added[k];
82
+ return a === void 0 ? void 0 : wordDiff(r.slice(1), a.slice(1));
83
+ });
84
+ removed.forEach((r, k) => {
85
+ const m = marks[k];
86
+ out.push(
87
+ m === void 0 ? paint(["red"], r) : paint(["red"], "-") + paintParts(m.a, "red", paint)
88
+ );
89
+ });
90
+ added.forEach((a, k) => {
91
+ const m = marks[k];
92
+ out.push(
93
+ m === void 0 ? paint(["green"], a) : paint(["green"], "+") + paintParts(m.b, "green", paint)
94
+ );
95
+ });
96
+ continue;
97
+ }
98
+ out.push(line.startsWith("+") ? paint(["green"], line) : line);
99
+ i++;
100
+ }
101
+ return out.join("\n");
102
+ }
103
+ var isFileHeader = (line) => line.startsWith("+++") || line.startsWith("---") || line.startsWith("diff --git");
104
+ var isRemoved = (line) => line.startsWith("-") && !line.startsWith("---");
105
+ var isAdded = (line) => line.startsWith("+") && !line.startsWith("+++");
106
+ function fitWidth(text, width) {
107
+ const chars = [...text];
108
+ if (chars.length > width) return `${chars.slice(0, Math.max(0, width - 1)).join("")}\u2026`;
109
+ return text + " ".repeat(width - chars.length);
110
+ }
111
+ function sideBySide(text, columns, paint) {
112
+ const half = Math.floor((columns - 3) / 2);
113
+ const body = half - 6;
114
+ const out = [];
115
+ let oldNo = 0;
116
+ let newNo = 0;
117
+ const cell = (no, mark, content, style) => {
118
+ const head = no === void 0 ? " " : String(no).padStart(4);
119
+ const shown = fitWidth(`${mark}${content}`, body + 1);
120
+ return `${paint(["dim"], head)} ${style === void 0 ? shown : paint([style], shown)}`;
121
+ };
122
+ const markedCell = (no, sign, parts, style) => {
123
+ let room = body;
124
+ const kept = [];
125
+ for (const p of parts) {
126
+ if (room <= 0) break;
127
+ const chars = [...p.text];
128
+ const piece = chars.length > room ? `${chars.slice(0, Math.max(0, room - 1)).join("")}\u2026` : p.text;
129
+ kept.push({ text: piece, changed: p.changed });
130
+ room -= [...piece].length;
131
+ }
132
+ const pad = " ".repeat(Math.max(0, room));
133
+ return `${paint(["dim"], String(no).padStart(4))} ${paint([style], sign)}${paintParts(kept, style, paint)}${pad}`;
134
+ };
135
+ const empty = " ".repeat(half);
136
+ const lines = text.split("\n");
137
+ let i = 0;
138
+ while (i < lines.length) {
139
+ const line = lines[i];
140
+ if (isFileHeader(line)) {
141
+ out.push(paint(["bold"], line));
142
+ i++;
143
+ continue;
144
+ }
145
+ const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
146
+ if (hunk !== null) {
147
+ oldNo = Number(hunk[1]);
148
+ newNo = Number(hunk[2]);
149
+ out.push(paint(["cyan"], line));
150
+ i++;
151
+ continue;
152
+ }
153
+ if (isRemoved(line) || isAdded(line)) {
154
+ const removed = [];
155
+ while (i < lines.length && isRemoved(lines[i])) removed.push(lines[i++]);
156
+ const added = [];
157
+ while (i < lines.length && isAdded(lines[i])) added.push(lines[i++]);
158
+ for (let k = 0; k < Math.max(removed.length, added.length); k++) {
159
+ const r = removed[k];
160
+ const a = added[k];
161
+ const m = r !== void 0 && a !== void 0 ? wordDiff(r.slice(1), a.slice(1)) : void 0;
162
+ const left = r === void 0 ? empty : m !== void 0 ? markedCell(oldNo++, "-", m.a, "red") : cell(oldNo++, "-", r.slice(1), "red");
163
+ const right = a === void 0 ? "" : m !== void 0 ? markedCell(newNo++, "+", m.b, "green") : cell(newNo++, "+", a.slice(1), "green");
164
+ out.push(`${left} ${paint(["dim"], "\u2502")} ${right}`.trimEnd());
165
+ }
166
+ continue;
167
+ }
168
+ if (line.startsWith("\\")) {
169
+ out.push(paint(["dim"], line));
170
+ i++;
171
+ continue;
172
+ }
173
+ if (i === lines.length - 1 && line === "") break;
174
+ const content = line.startsWith(" ") ? line.slice(1) : line;
175
+ out.push(
176
+ `${cell(oldNo++, " ", content)} ${paint(["dim"], "\u2502")} ${cell(newNo++, " ", content)}`.trimEnd()
177
+ );
178
+ i++;
179
+ }
180
+ return out.join("\n");
181
+ }
182
+ function terminalDiff(text, columns, paint) {
183
+ if (hunkCount(text) === 0) return plainColours(text, paint);
184
+ return columns !== void 0 && columns >= SIDE_BY_SIDE_COLUMNS ? sideBySide(text, columns, paint) : unifiedDiff(text, paint);
185
+ }
186
+ function plainColours(text, paint) {
187
+ return text.split("\n").map(
188
+ (line) => isFileHeader(line) ? paint(["bold"], line) : line.startsWith("+") ? paint(["green"], line) : line.startsWith("-") ? paint(["red"], line) : line
189
+ ).join("\n");
190
+ }
191
+ function hunkCount(text) {
192
+ return text.split("\n").filter((line) => line.startsWith("@@")).length;
193
+ }
194
+
195
+ // src/cli/approver.ts
196
+ var TerminalApprover = class {
197
+ /** Set by the CLI: it aborts the current turn. */
198
+ onInterrupt = () => {
199
+ };
200
+ /** One question at a time (0.14.1, review): two prompts at once mixed their keys. */
201
+ ask(request, signal) {
202
+ const next = this.asking === 0 ? this.askNow(request, signal) : this.queue.then(() => this.askNow(request, signal));
203
+ this.asking++;
204
+ this.queue = next.catch(() => {
205
+ }).finally(() => {
206
+ this.asking--;
207
+ });
208
+ return next;
209
+ }
210
+ asking = 0;
211
+ queue = Promise.resolve();
212
+ async askNow(request, signal) {
213
+ signal.throwIfAborted();
214
+ const out = process.stderr;
215
+ out.write(`
216
+ ${header(request)}
217
+ ${colorPreview(request, out.columns)}
218
+ `);
219
+ if (!process.stdin.isTTY) {
220
+ out.write("No terminal to ask for approval, so Garuda denies this call.\n");
221
+ return "deny";
222
+ }
223
+ const sessionLabel = request.labels?.session ?? (request.target.kind === "command" ? "Yes, and allow this exact command for this session" : request.target.kind === "url" ? `Yes, and allow ${request.target.host} for this session` : `Yes, and allow all ${request.tool} calls for this session`);
224
+ const { select } = await import("@inquirer/prompts");
225
+ try {
226
+ return await select(
227
+ {
228
+ message: request.question ?? "Allow?",
229
+ choices: [
230
+ { name: request.labels?.once ?? "Yes, once", value: "once" },
231
+ { name: sessionLabel, value: "session" },
232
+ { name: request.labels?.deny ?? "No, deny", value: "deny" }
233
+ ].filter((c) => request.choices?.includes(c.value) ?? true)
234
+ },
235
+ { signal, output: out }
236
+ );
237
+ } catch (error) {
238
+ if (error.name === "ExitPromptError") this.onInterrupt();
239
+ throw error;
240
+ }
241
+ }
242
+ };
243
+ var SwitchApprover = class {
244
+ constructor(current) {
245
+ this.current = current;
246
+ }
247
+ current;
248
+ /** Called before each question: the chat's notifier (0.6). */
249
+ onAsk = () => {
250
+ };
251
+ ask(request, signal) {
252
+ this.onAsk(request);
253
+ return this.current.ask(request, signal);
254
+ }
255
+ };
256
+ function visible(text) {
257
+ return text.replace(/[\u0000-\u0008\u000b-\u001f]/g, (c) => String.fromCodePoint(9216 + c.charCodeAt(0))).replace(/\u007f/g, "\u2421").replace(
258
+ /[\u0080-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/gu,
259
+ (c) => `[U+${(c.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}]`
260
+ );
261
+ }
262
+ function hasHidden(text) {
263
+ return visible(text) !== text;
264
+ }
265
+ var HIDDEN_WARNING = "! This holds hidden or control characters, shown as \u240D, \u241B or [U+\u2026]. Read it with care.";
266
+ function headerText(request) {
267
+ const { tool, target, isolation } = request;
268
+ if (request.title !== void 0) return visible(request.title);
269
+ if (target.kind === "command") {
270
+ const where = isolation === "none" ? "on your machine, no sandbox" : target.outsideSandbox ? "OUTSIDE the sandbox: network on, writes anywhere" : `sandbox: ${isolation}`;
271
+ return `${tool} wants to run a command (${where}):`;
272
+ }
273
+ if (target.kind === "input") return `${tool} wants to run with this input:`;
274
+ if (target.kind === "url") return `${tool} wants to fetch from ${visible(target.host)}:`;
275
+ return `${tool} wants to change ${visible(target.path)}:`;
276
+ }
277
+ function header(request) {
278
+ return styleText("bold", headerText(request));
279
+ }
280
+ function colorPreview({ target, preview }, columns) {
281
+ const warning = hasHidden(preview) ? `
282
+ ${styleText("yellow", ` ${HIDDEN_WARNING}`)}` : "";
283
+ if (target.kind === "command") return `${styleText("cyan", ` $ ${visible(preview)}`)}${warning}`;
284
+ if (target.kind === "url") return `${visible(preview)}${warning}`;
285
+ return `${colorDiff(visible(preview), columns)}${warning}`;
286
+ }
287
+ var terminalPaint = (styles, text) => styles.length === 0 ? text : styleText([...styles], text);
288
+ function colorDiff(text, columns, paint = terminalPaint) {
289
+ return terminalDiff(text, columns, paint);
290
+ }
291
+
292
+ export {
293
+ unifiedDiff,
294
+ hunkCount,
295
+ TerminalApprover,
296
+ SwitchApprover,
297
+ visible,
298
+ hasHidden,
299
+ HIDDEN_WARNING,
300
+ headerText,
301
+ header,
302
+ colorPreview,
303
+ terminalPaint,
304
+ colorDiff
305
+ };
306
+ //# sourceMappingURL=chunk-VISKX36R.js.map
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  JobGitError,
4
4
  git
5
- } from "./chunk-6XHON3Q6.js";
5
+ } from "./chunk-QXTHEFKP.js";
6
6
  import {
7
7
  inWriteScope
8
8
  } from "./chunk-UIRJOQQS.js";
@@ -155,4 +155,4 @@ export {
155
155
  outsideScope,
156
156
  jobChanges
157
157
  };
158
- //# sourceMappingURL=chunk-55CFEEIN.js.map
158
+ //# sourceMappingURL=chunk-WK5NE6RY.js.map
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ folderKind
4
+ } from "./chunk-42DSA4EA.js";
5
+ import {
6
+ detectTestCommand
7
+ } from "./chunk-AOGFYUKG.js";
8
+
9
+ // src/cli/tour.ts
10
+ import { createHash } from "crypto";
11
+ import { existsSync, readFileSync } from "fs";
12
+ import { mkdir, readFile, writeFile } from "fs/promises";
13
+ import { join } from "path";
14
+ function detectStack(root, profiles) {
15
+ const has = (name) => existsSync(join(root, name));
16
+ const test = detectTestCommand(root, profiles);
17
+ const withTest = (label) => test === void 0 ? { label } : { label, test };
18
+ if (has("package.json")) {
19
+ let deps = {};
20
+ try {
21
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
22
+ deps = { ...pkg.dependencies, ...pkg.devDependencies };
23
+ } catch {
24
+ }
25
+ const language = has("tsconfig.json") || "typescript" in deps ? "TypeScript" : "JavaScript";
26
+ const runner = ["vitest", "jest", "mocha"].find((r) => r in deps);
27
+ const manager = has("pnpm-lock.yaml") ? "pnpm" : has("yarn.lock") ? "yarn" : has("bun.lockb") ? "bun" : "npm";
28
+ const app = "react-native" in deps ? " (React Native)" : "expo" in deps ? " (Expo)" : "";
29
+ return withTest(
30
+ `${language}${app}${runner === void 0 ? "" : ` with ${runner}`} (${manager})`
31
+ );
32
+ }
33
+ const profile = profiles[0];
34
+ if (profile !== void 0) return withTest(profile.label);
35
+ if (has("go.mod")) return withTest("Go");
36
+ if (has("Cargo.toml")) return withTest("Rust");
37
+ return void 0;
38
+ }
39
+ function tourPrompts(stack) {
40
+ return [
41
+ "Explain how this repository is laid out, in 10 lines or fewer.",
42
+ stack.test === void 0 ? "Find the riskiest function in this codebase and say why, in 5 lines." : `Run the tests (${stack.test}) and explain any failure.`,
43
+ "Find one function that has no test, and write a test for it."
44
+ ];
45
+ }
46
+ function tourText(stack, prompts) {
47
+ return [
48
+ `This looks like ${stack.label}. Some first tasks:`,
49
+ ...prompts.map((p, i) => ` ${i + 1}. ${p}`),
50
+ "Type a number, or your own task."
51
+ ].join("\n");
52
+ }
53
+ var rootKey = (root) => createHash("sha256").update(root).digest("hex").slice(0, 16);
54
+ var MAX_TOURED = 200;
55
+ async function firstRunTour(root, home, profiles) {
56
+ if (folderKind(root) !== "code") return void 0;
57
+ const stack = detectStack(root, profiles);
58
+ if (stack === void 0) return void 0;
59
+ const file = join(home, ".garuda", "ui.json");
60
+ let data = {};
61
+ try {
62
+ data = JSON.parse(await readFile(file, "utf8"));
63
+ } catch {
64
+ }
65
+ const toured = Array.isArray(data.toured) ? data.toured.filter((t) => typeof t === "string") : [];
66
+ const key = rootKey(root);
67
+ if (toured.includes(key)) return void 0;
68
+ try {
69
+ await mkdir(join(home, ".garuda"), { recursive: true, mode: 448 });
70
+ const next = { ...data, toured: [...toured, key].slice(-MAX_TOURED) };
71
+ await writeFile(file, `${JSON.stringify(next)}
72
+ `, { mode: 384 });
73
+ } catch {
74
+ }
75
+ const prompts = tourPrompts(stack);
76
+ return { text: tourText(stack, prompts), prompts };
77
+ }
78
+ function tourChoice(input, prompts) {
79
+ const n = /^\s*([1-9])\s*$/.exec(input)?.[1];
80
+ return prompts !== void 0 && n !== void 0 ? prompts[Number(n) - 1] ?? input : input;
81
+ }
82
+
83
+ export {
84
+ detectStack,
85
+ tourPrompts,
86
+ tourText,
87
+ firstRunTour,
88
+ tourChoice
89
+ };
90
+ //# sourceMappingURL=chunk-Y62J7PTD.js.map