@garuda-agent/garuda 0.0.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +1097 -0
  5. package/SECURITY.md +32 -0
  6. package/dist/acpCommand-FZ2P5JFO.js +153 -0
  7. package/dist/anthropic-NQDBCHJU.js +327 -0
  8. package/dist/chunk-23FXBNBB.js +46 -0
  9. package/dist/chunk-2S62ZNTC.js +320 -0
  10. package/dist/chunk-2TAXSFWN.js +49 -0
  11. package/dist/chunk-36GQIVGT.js +526 -0
  12. package/dist/chunk-3CEPHS4C.js +231 -0
  13. package/dist/chunk-3NJNVZTK.js +123 -0
  14. package/dist/chunk-42DSA4EA.js +116 -0
  15. package/dist/chunk-4JSUUDFQ.js +118 -0
  16. package/dist/chunk-4P26OKTP.js +100 -0
  17. package/dist/chunk-5DEWP3YX.js +139 -0
  18. package/dist/chunk-A2QAIRQL.js +45 -0
  19. package/dist/chunk-ATW7BNIG.js +31 -0
  20. package/dist/chunk-B25VL4RU.js +242 -0
  21. package/dist/chunk-EPKWDCV4.js +9 -0
  22. package/dist/chunk-EU7PA45O.js +324 -0
  23. package/dist/chunk-FARS4R67.js +206 -0
  24. package/dist/chunk-G2AX7MGS.js +36 -0
  25. package/dist/chunk-HRGX53VC.js +173 -0
  26. package/dist/chunk-HUIK3WXO.js +234 -0
  27. package/dist/chunk-INRZR772.js +73 -0
  28. package/dist/chunk-K3SIUOAG.js +138 -0
  29. package/dist/chunk-K7KK7U44.js +9358 -0
  30. package/dist/chunk-KKB5W47S.js +231 -0
  31. package/dist/chunk-N4I6YS3E.js +73 -0
  32. package/dist/chunk-NXJ4XLB5.js +19 -0
  33. package/dist/chunk-OUCV3FCT.js +205 -0
  34. package/dist/chunk-PQNT34LY.js +1247 -0
  35. package/dist/chunk-PZS3V6P3.js +46 -0
  36. package/dist/chunk-S77XDSD4.js +157 -0
  37. package/dist/chunk-SI2QJYCR.js +1341 -0
  38. package/dist/chunk-WMOLXTBU.js +61 -0
  39. package/dist/chunk-YFQJXRIJ.js +193 -0
  40. package/dist/chunk-YQTGMCU6.js +111 -0
  41. package/dist/chunk-ZAE25JM2.js +158 -0
  42. package/dist/cli/index.js +1203 -0
  43. package/dist/create-N4I7TIZE.js +223 -0
  44. package/dist/deadline-F5RJJ2QW.js +86 -0
  45. package/dist/detect-LARDSLLZ.js +10 -0
  46. package/dist/evalCommand-3SSFMZQD.js +2798 -0
  47. package/dist/export-HN3CT7AV.js +158 -0
  48. package/dist/inkChat-34BTFG2K.js +1193 -0
  49. package/dist/install-VRPBZY5H.js +17 -0
  50. package/dist/job-3NYDMP2M.js +31 -0
  51. package/dist/jobCommand-XJ5GULS5.js +30 -0
  52. package/dist/launchd-QVHQ3VNN.js +40 -0
  53. package/dist/lspCommand-XNTBHXLA.js +99 -0
  54. package/dist/manager-4PMSXT47.js +14 -0
  55. package/dist/manager-CSI76AWF.js +847 -0
  56. package/dist/nightCommand-CR6ZHYGP.js +247 -0
  57. package/dist/oauth-W6OK6XXO.js +23 -0
  58. package/dist/openaiCompatible-CJWVJP5M.js +501 -0
  59. package/dist/policy-EJPLEEXP.js +31 -0
  60. package/dist/proxy-XJAHCZB2.js +192 -0
  61. package/dist/remove-LE7Q7K46.js +59 -0
  62. package/dist/run-ML6GSPN4.js +888 -0
  63. package/dist/server-R3GNIC33.js +676 -0
  64. package/dist/servers-6YDGWOZ6.js +24 -0
  65. package/dist/text-J2GD647J.js +11 -0
  66. package/package.json +84 -3
@@ -0,0 +1,1203 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ BUILD_PROMPT,
4
+ CommandArgs,
5
+ FOCUS_REPORTING_OFF,
6
+ FocusTracker,
7
+ commandNames,
8
+ complete,
9
+ describeError,
10
+ formatTokens,
11
+ planHandoff,
12
+ reportsFocus,
13
+ rootFiles,
14
+ rootLister,
15
+ runCommand,
16
+ runTurnInTerminal,
17
+ stopMessage
18
+ } from "../chunk-PQNT34LY.js";
19
+ import {
20
+ PlainRenderer
21
+ } from "../chunk-FARS4R67.js";
22
+ import {
23
+ AutoApprover
24
+ } from "../chunk-NXJ4XLB5.js";
25
+ import {
26
+ Notifier,
27
+ pickChannel
28
+ } from "../chunk-N4I6YS3E.js";
29
+ import {
30
+ SwitchApprover,
31
+ TerminalApprover
32
+ } from "../chunk-4JSUUDFQ.js";
33
+ import {
34
+ hasProviderKey,
35
+ keepProviderKey,
36
+ loadModelsConfig,
37
+ resolveModel
38
+ } from "../chunk-YFQJXRIJ.js";
39
+ import {
40
+ DEFAULT_MAX_TOKENS,
41
+ FileSessionStore,
42
+ Runtime,
43
+ ToolRegistry,
44
+ auditDirFor,
45
+ closeOpenToolCalls,
46
+ createSession,
47
+ defaultTools,
48
+ parseRecords,
49
+ popPoint,
50
+ pushPoint,
51
+ rebuildState,
52
+ redoPoint,
53
+ runAgent,
54
+ signature
55
+ } from "../chunk-K7KK7U44.js";
56
+ import "../chunk-A2QAIRQL.js";
57
+ import "../chunk-HRGX53VC.js";
58
+ import "../chunk-G2AX7MGS.js";
59
+ import {
60
+ loadSearchConfig
61
+ } from "../chunk-B25VL4RU.js";
62
+ import {
63
+ BUILTIN_COMMANDS
64
+ } from "../chunk-23FXBNBB.js";
65
+ import "../chunk-EU7PA45O.js";
66
+ import {
67
+ VERSION
68
+ } from "../chunk-EPKWDCV4.js";
69
+ import "../chunk-5DEWP3YX.js";
70
+ import "../chunk-ZAE25JM2.js";
71
+ import "../chunk-ATW7BNIG.js";
72
+ import "../chunk-PZS3V6P3.js";
73
+ import "../chunk-OUCV3FCT.js";
74
+ import {
75
+ JOB_DENIAL
76
+ } from "../chunk-SI2QJYCR.js";
77
+ import "../chunk-WMOLXTBU.js";
78
+ import "../chunk-HUIK3WXO.js";
79
+ import "../chunk-KKB5W47S.js";
80
+ import "../chunk-3NJNVZTK.js";
81
+
82
+ // src/cli/index.ts
83
+ import { existsSync, readFileSync, realpathSync } from "fs";
84
+ import { homedir as homedir2 } from "os";
85
+ import { join } from "path";
86
+ import { Writable } from "stream";
87
+ import { Command, Option } from "commander";
88
+
89
+ // src/loop/replay.ts
90
+ import { isDeepStrictEqual } from "util";
91
+
92
+ // src/model/fake.ts
93
+ var ScriptExhaustedError = class extends Error {
94
+ constructor(calls) {
95
+ super(`Fake model script has no step for call ${calls}.`);
96
+ this.name = "ScriptExhaustedError";
97
+ }
98
+ };
99
+ var FakeModelClient = class {
100
+ requests = [];
101
+ /** Server tools the fake accepts, like a Claude model (0.6). Default: none. */
102
+ serverTools;
103
+ steps;
104
+ constructor(steps, options = {}) {
105
+ this.steps = [...steps];
106
+ if (options.serverTools !== void 0) this.serverTools = options.serverTools;
107
+ }
108
+ get remaining() {
109
+ return this.steps.length;
110
+ }
111
+ async *stream(request, options) {
112
+ options?.signal?.throwIfAborted();
113
+ this.requests.push(structuredClone(request));
114
+ const step = this.steps.shift();
115
+ if (step === void 0) throw new ScriptExhaustedError(this.requests.length);
116
+ const response = typeof step === "function" ? step(request) : step;
117
+ for (const block of response.content) {
118
+ if (block.type === "text") yield { type: "text_delta", text: block.text };
119
+ if (block.type === "thinking" && block.text !== "") {
120
+ yield { type: "thinking_delta", text: block.text };
121
+ }
122
+ }
123
+ yield { type: "response", response };
124
+ }
125
+ };
126
+
127
+ // src/loop/replay.ts
128
+ async function replaySession(records, tools) {
129
+ const problems = [];
130
+ const responses = [];
131
+ const outcomes = /* @__PURE__ */ new Map();
132
+ const expectedCalls = [];
133
+ for (const record of records) {
134
+ if (record.type === "assistant") {
135
+ responses.push(record.response);
136
+ for (const block of record.response.content) {
137
+ if (block.type === "tool_use") expectedCalls.push(signature(block));
138
+ }
139
+ }
140
+ if (record.type === "compaction" && record.summary !== void 0)
141
+ responses.push(record.summary);
142
+ if (record.type === "tool_results" && record.synthetic !== true) {
143
+ const reports = new Map(record.calls.map((call) => [call.toolUseId, call.subagent]));
144
+ for (const block of record.message.content) {
145
+ if (block.type === "tool_result") {
146
+ const subagent = reports.get(block.toolUseId);
147
+ outcomes.set(block.toolUseId, {
148
+ content: block.content,
149
+ isError: block.isError,
150
+ // Replay does not run the subagent again; its recorded usage still counts.
151
+ ...subagent === void 0 ? {} : { subagent }
152
+ });
153
+ }
154
+ }
155
+ }
156
+ }
157
+ const model = new FakeModelClient(responses);
158
+ const recorded = new RecordedTools(tools, outcomes, problems);
159
+ const first = records.find((r) => r.type === "start");
160
+ const session = createSession(first?.root ?? "/replay", first?.sessionId ?? "replay");
161
+ let limits = first?.limits;
162
+ let runs = 0;
163
+ let steps = 0;
164
+ for (let i = 0; i < records.length; i++) {
165
+ const record = records[i];
166
+ if (record === void 0) break;
167
+ if (record.type === "start" || record.type === "resume" || record.type === "model")
168
+ limits = record.limits;
169
+ if (record.type === "snapshot") {
170
+ pushPoint(session.undo, record.tree, record.messages, record.prompt);
171
+ } else if (record.type === "undo") {
172
+ popPoint(session.undo, session.messages, record.after);
173
+ } else if (record.type === "redo") {
174
+ redoPoint(session.undo, session.messages);
175
+ }
176
+ if (record.type !== "user") continue;
177
+ const endIndex = records.findIndex((r, j) => j > i && (r.type === "end" || r.type === "user"));
178
+ const end = endIndex === -1 ? void 0 : records[endIndex];
179
+ if (end?.type !== "end" || end.stopReason === "interrupted" || end.stopReason === "error") {
180
+ problems.push(
181
+ `Run ${runs + 1} did not finish in the recording (it was interrupted). Replay stops there.`
182
+ );
183
+ break;
184
+ }
185
+ runs++;
186
+ closeOpenToolCalls(session);
187
+ const texts = record.message.content.flatMap((b) => b.type === "text" ? [b.text] : []);
188
+ session.messages.push({ role: "user", content: texts.map((t) => ({ type: "text", text: t })) });
189
+ const result = await runAgent(session, {
190
+ model,
191
+ tools: recorded,
192
+ system: "replay",
193
+ permissions: NO_PERMISSIONS,
194
+ ...limits === void 0 ? {} : {
195
+ maxSteps: limits.maxSteps,
196
+ tokenBudget: limits.tokenBudget,
197
+ contextWindow: limits.contextWindow
198
+ }
199
+ });
200
+ steps += result.steps;
201
+ if (result.stopReason !== end.stopReason || result.steps !== end.steps) {
202
+ problems.push(
203
+ `Run ${runs}: the recording stopped with ${end.stopReason} after ${end.steps} steps; the replay stopped with ${result.stopReason} after ${result.steps} steps.`
204
+ );
205
+ }
206
+ const expected = rebuildState(records.slice(0, endIndex + 1)).messages;
207
+ if (!isDeepStrictEqual(session.messages, expected)) {
208
+ problems.push(`Run ${runs}: the conversation differs from the recording.`);
209
+ }
210
+ }
211
+ if (model.remaining > 0)
212
+ problems.push(`${model.remaining} recorded model responses were not used.`);
213
+ const actualCalls = recorded.calls;
214
+ if (problems.length === 0 && !isDeepStrictEqual(actualCalls, expectedCalls.slice(0, actualCalls.length))) {
215
+ problems.push("The tool calls differ from the recording.");
216
+ }
217
+ return { matches: problems.length === 0, runs, steps, toolCalls: actualCalls.length, problems };
218
+ }
219
+ var RecordedTools = class {
220
+ constructor(real, outcomes, problems) {
221
+ this.real = real;
222
+ this.outcomes = outcomes;
223
+ this.problems = problems;
224
+ }
225
+ real;
226
+ outcomes;
227
+ problems;
228
+ calls = [];
229
+ specs() {
230
+ return this.real.specs();
231
+ }
232
+ isReadOnly(name) {
233
+ return this.real.isReadOnly(name);
234
+ }
235
+ runsCommands(name) {
236
+ return this.real.runsCommands(name);
237
+ }
238
+ async execute(call, _context) {
239
+ this.calls.push(signature(call));
240
+ const outcome = this.outcomes.get(call.id);
241
+ if (outcome !== void 0) return outcome;
242
+ this.problems.push(`No recorded result for ${call.name} call ${call.id}.`);
243
+ return { content: "Error: no recorded result.", isError: true };
244
+ }
245
+ };
246
+ var NO_PERMISSIONS = {
247
+ check: async () => ({ allowed: false, by: "rule", reason: "Replay runs no tools." }),
248
+ execPolicy: () => {
249
+ throw new Error("Replay runs no commands.");
250
+ },
251
+ deniedByPolicy: () => true,
252
+ commandPolicyDenial: () => "Replay runs no commands."
253
+ };
254
+
255
+ // src/cli/banner.ts
256
+ import { homedir } from "os";
257
+ var WORDMARK = [
258
+ " \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 ",
259
+ "\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
260
+ "\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",
261
+ "\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
262
+ " \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"
263
+ ];
264
+ var WORDMARK_MIN_COLUMNS = 60;
265
+ var FROM = [255, 122, 24];
266
+ var TO = [255, 209, 102];
267
+ function colorLevel(stream = process.stdout, env = process.env) {
268
+ if (env.NO_COLOR !== void 0 || stream.isTTY !== true || env.TERM === "dumb") return "none";
269
+ return /truecolor|24bit/i.test(env.COLORTERM ?? "") ? "truecolor" : "basic";
270
+ }
271
+ function banner(info, options) {
272
+ const c = paint(options.color);
273
+ const lines = [];
274
+ if (options.columns >= WORDMARK_MIN_COLUMNS) {
275
+ const width = WORDMARK[0]?.length ?? 1;
276
+ for (const row of WORDMARK) lines.push(c.gradient(row, width));
277
+ lines.push("");
278
+ }
279
+ lines.push(...card(info, options, c));
280
+ const tips = info.ink ? "/help \xB7 Esc stops a task \xB7 \\ then Enter: new line \xB7 Ctrl-G editor \xB7 Ctrl-P commands" : "/help \xB7 Ctrl-C stops a task \xB7 \\ at the end: new line";
281
+ lines.push(c.dim(` ${tips}`), "");
282
+ return lines.join("\n");
283
+ }
284
+ function card(info, options, c) {
285
+ const home = options.home ?? homedir();
286
+ const folder = info.root === home || info.root.startsWith(`${home}/`) ? `~${info.root.slice(home.length)}` : info.root;
287
+ const title = `\u2726 Garuda ${info.version} \xB7 a terminal coding agent`;
288
+ const rows = [
289
+ ["model", info.model],
290
+ ["sandbox", info.sandbox],
291
+ ["folder", folder]
292
+ ];
293
+ const hasCategorized = info.subagents !== void 0 && info.subagents.length > 0 || info.agents !== void 0 && info.agents.length > 0 || info.skills !== void 0 && info.skills.length > 0 || info.tools !== void 0 && info.tools.length > 0;
294
+ if (hasCategorized) {
295
+ if (info.subagents !== void 0 && info.subagents.length > 0) {
296
+ rows.push(["subagents", info.subagents.join(" \xB7 ")]);
297
+ }
298
+ if (info.agents !== void 0 && info.agents.length > 0) {
299
+ rows.push(["agents", info.agents.join(" \xB7 ")]);
300
+ }
301
+ if (info.skills !== void 0 && info.skills.length > 0) {
302
+ rows.push(["skills", info.skills.join(" \xB7 ")]);
303
+ }
304
+ if (info.tools !== void 0 && info.tools.length > 0) {
305
+ rows.push(["tools", info.tools.join(" \xB7 ")]);
306
+ }
307
+ } else if (info.extras.length > 0) {
308
+ rows.push(["extras", info.extras.join(" \xB7 ")]);
309
+ }
310
+ const maxLabel = Math.max(...rows.map(([label]) => label.length));
311
+ const labelWidth = Math.max(9, maxLabel + 2);
312
+ const prefix = 2 + labelWidth;
313
+ const natural = Math.max(title.length, ...rows.map(([, v]) => prefix + v.length));
314
+ const inner = Math.max(20, Math.min(natural + 1, options.columns - 4));
315
+ const cutStart = (text, max) => text.length <= max ? text : `\u2026${text.slice(text.length - max + 1)}`;
316
+ const border = (left, right) => c.dim(`${left}${"\u2500".repeat(inner + 2)}${right}`);
317
+ const line = (plain, styled) => `${c.dim("\u2502")} ${styled}${" ".repeat(Math.max(0, inner - plain.length))} ${c.dim("\u2502")}`;
318
+ const titleText = cutStart(title, inner);
319
+ const out = [
320
+ border("\u256D", "\u256E"),
321
+ line(
322
+ titleText,
323
+ titleText.startsWith("\u2726") ? `${c.gold("\u2726")}${c.bold(titleText.slice(1))}` : c.bold(titleText)
324
+ ),
325
+ line("", "")
326
+ ];
327
+ for (const [label, value] of rows) {
328
+ const shown = cutStart(value, inner - prefix);
329
+ const plain = ` ${label.padEnd(labelWidth)}${shown}`;
330
+ out.push(line(plain, ` ${c.dim(label.padEnd(labelWidth))}${shown}`));
331
+ }
332
+ out.push(border("\u2570", "\u256F"));
333
+ return out;
334
+ }
335
+ function paint(level) {
336
+ const esc = (code, text, reset) => `\x1B[${code}m${text}\x1B[${reset}m`;
337
+ if (level === "none") {
338
+ return { gradient: (t) => t, gold: (t) => t, bold: (t) => t, dim: (t) => t };
339
+ }
340
+ const bold = (t) => esc("1", t, "22");
341
+ const dim = (t) => esc("2", t, "22");
342
+ if (level === "basic") {
343
+ return { gradient: (t) => esc("33", t, "39"), gold: (t) => esc("33", t, "39"), bold, dim };
344
+ }
345
+ const rgb = ([r, g, b]) => `38;2;${r};${g};${b}`;
346
+ return {
347
+ gradient: (text, width) => [...text].map((ch, i) => {
348
+ if (ch === " ") return ch;
349
+ const t = width <= 1 ? 0 : i / (width - 1);
350
+ const mix = FROM.map((f, k) => Math.round(f + ((TO[k] ?? f) - f) * t));
351
+ return esc(rgb(mix), ch, "39");
352
+ }).join(""),
353
+ gold: (t) => esc(rgb(TO), t, "39"),
354
+ bold,
355
+ dim
356
+ };
357
+ }
358
+
359
+ // src/cli/jsonOutput.ts
360
+ import { randomUUID } from "crypto";
361
+ var OUTPUT_FORMATS = ["text", "json", "stream-json"];
362
+ var JsonOutput = class {
363
+ constructor(options) {
364
+ this.options = options;
365
+ this.started = this.now();
366
+ }
367
+ options;
368
+ started;
369
+ initDone = false;
370
+ lastText = "";
371
+ denials = [];
372
+ event(event) {
373
+ if (this.options.verbose) this.options.log.event(event);
374
+ switch (event.type) {
375
+ case "step_end":
376
+ this.lastText = textOf(event.response);
377
+ this.stream(() => assistantLine(event.response, this.run().model));
378
+ return;
379
+ case "tool_result":
380
+ if (event.outcome.denied === true) {
381
+ this.denials.push({
382
+ tool_name: event.call.name,
383
+ tool_use_id: event.call.id,
384
+ tool_input: event.call.input
385
+ });
386
+ }
387
+ this.stream(() => ({
388
+ type: "user",
389
+ message: {
390
+ role: "user",
391
+ content: [
392
+ {
393
+ type: "tool_result",
394
+ tool_use_id: event.call.id,
395
+ content: event.outcome.content,
396
+ is_error: event.outcome.isError
397
+ }
398
+ ]
399
+ },
400
+ parent_tool_use_id: null
401
+ }));
402
+ return;
403
+ case "model_retry":
404
+ this.stream(() => ({
405
+ type: "system",
406
+ subtype: "api_retry",
407
+ attempt: event.attempt,
408
+ max_retries: event.maxRetries,
409
+ retry_delay_ms: event.delayMs,
410
+ error_status: null,
411
+ error: "unknown",
412
+ message: event.reason
413
+ }));
414
+ return;
415
+ case "compaction":
416
+ this.stream(() => ({
417
+ type: "system",
418
+ subtype: "compact_boundary",
419
+ compact_metadata: {
420
+ trigger: "auto",
421
+ pre_tokens: event.result.beforeTokens,
422
+ post_tokens: event.result.afterTokens
423
+ }
424
+ }));
425
+ return;
426
+ default:
427
+ return;
428
+ }
429
+ }
430
+ info(text) {
431
+ if (this.options.verbose) this.options.log.info(text);
432
+ }
433
+ warn(text) {
434
+ this.options.log.warn(text);
435
+ }
436
+ error(text) {
437
+ this.options.log.error(text);
438
+ }
439
+ /** Write the `result` line (and `system/init` first, if no line came yet). */
440
+ finish(outcome, info) {
441
+ const line = resultLine(outcome, {
442
+ ...info,
443
+ model: this.run().model,
444
+ durationMs: Math.round(this.now() - this.started),
445
+ lastText: this.lastText,
446
+ denials: this.denials
447
+ });
448
+ if (this.options.format === "stream-json") this.stream(() => line);
449
+ else this.writeLine(line);
450
+ }
451
+ stream(make) {
452
+ if (this.options.format !== "stream-json") return;
453
+ if (!this.initDone) {
454
+ this.initDone = true;
455
+ this.writeLine(initLine(this.run()));
456
+ }
457
+ this.writeLine(make());
458
+ }
459
+ writeLine(line) {
460
+ this.options.write(
461
+ JSON.stringify({ ...line, session_id: this.options.sessionId(), uuid: randomUUID() })
462
+ );
463
+ }
464
+ run() {
465
+ return this.options.runInfo();
466
+ }
467
+ now() {
468
+ return (this.options.now ?? performance.now.bind(performance))();
469
+ }
470
+ };
471
+ function initLine(info) {
472
+ return {
473
+ type: "system",
474
+ subtype: "init",
475
+ cwd: info.cwd,
476
+ tools: info.tools,
477
+ mcp_servers: info.mcpServers.map((s) => ({ name: s.name, status: mcpStatus(s.state) })),
478
+ model: info.model,
479
+ permissionMode: info.permissionMode,
480
+ slash_commands: info.slashCommands,
481
+ apiKeySource: info.apiKeySource,
482
+ output_style: "default",
483
+ skills: info.skills ?? [],
484
+ agents: info.agents ?? [],
485
+ plugins: [],
486
+ garuda_version: VERSION
487
+ };
488
+ }
489
+ function mcpStatus(state) {
490
+ if (state === "connected" || state === "failed") return state;
491
+ return state === "stopped" ? "failed" : "disabled";
492
+ }
493
+ function assistantLine(response, model) {
494
+ return {
495
+ type: "assistant",
496
+ message: {
497
+ id: `msg_${randomUUID().replaceAll("-", "")}`,
498
+ type: "message",
499
+ role: "assistant",
500
+ model,
501
+ content: response.content.map((block) => {
502
+ switch (block.type) {
503
+ case "text":
504
+ return block.citations === void 0 ? { type: "text", text: block.text } : { type: "text", text: block.text, citations: block.citations };
505
+ case "tool_use":
506
+ return { type: "tool_use", id: block.id, name: block.name, input: block.input };
507
+ // Server tool blocks (0.6) as the API gave them, as Claude Code shows them.
508
+ default:
509
+ return block.wire;
510
+ }
511
+ }),
512
+ stop_reason: response.stopReason,
513
+ stop_sequence: null,
514
+ usage: apiUsage(response.usage)
515
+ },
516
+ parent_tool_use_id: null
517
+ };
518
+ }
519
+ function apiUsage(u) {
520
+ return {
521
+ input_tokens: u.inputTokens,
522
+ cache_creation_input_tokens: u.cacheWriteTokens,
523
+ cache_read_input_tokens: u.cacheReadTokens,
524
+ output_tokens: u.outputTokens,
525
+ ...u.webSearches === void 0 ? {} : { server_tool_use: { web_search_requests: u.webSearches, web_fetch_requests: 0 } }
526
+ };
527
+ }
528
+ function textOf(response) {
529
+ return response.content.filter((b) => b.type === "text").map((b) => b.type === "text" ? b.text : "").join("");
530
+ }
531
+ var STOPS = {
532
+ done: { subtype: "success", terminal: "completed" },
533
+ // The model stopped by itself; Claude Code reports these as success with the stop_reason.
534
+ max_tokens: { subtype: "success", terminal: "completed" },
535
+ refusal: { subtype: "success", terminal: "completed" },
536
+ max_steps: { subtype: "error_max_turns", terminal: "max_turns" },
537
+ // Garuda's budget counts tokens, not dollars, so error_max_budget_usd does not fit.
538
+ token_budget: { subtype: "error_during_execution", terminal: "budget_exhausted" },
539
+ repeated_calls: { subtype: "error_during_execution" }
540
+ };
541
+ function resultLine(outcome, info) {
542
+ const result = outcome.kind === "done" ? outcome.result : void 0;
543
+ const usage = result?.usage ?? {
544
+ inputTokens: 0,
545
+ outputTokens: 0,
546
+ cacheReadTokens: 0,
547
+ cacheWriteTokens: 0
548
+ };
549
+ const stop = result === void 0 ? void 0 : STOPS[result.stopReason];
550
+ const subtype = stop?.subtype ?? "error_during_execution";
551
+ const errors = outcome.kind === "error" ? [outcome.message] : outcome.kind === "interrupted" ? ["Interrupted."] : subtype === "success" ? void 0 : [info.stopMessage ?? `Stopped: ${result?.stopReason}`];
552
+ return {
553
+ type: "result",
554
+ subtype,
555
+ is_error: outcome.kind === "error",
556
+ duration_ms: info.durationMs,
557
+ duration_api_ms: Math.round(result?.apiMs ?? 0),
558
+ num_turns: result?.steps ?? 0,
559
+ ...subtype === "success" ? { result: info.lastText } : {},
560
+ stop_reason: result?.modelStopReason ?? null,
561
+ total_cost_usd: info.totalCostUsd,
562
+ usage: apiUsage(usage),
563
+ modelUsage: {
564
+ [info.model]: {
565
+ inputTokens: usage.inputTokens,
566
+ outputTokens: usage.outputTokens,
567
+ cacheReadInputTokens: usage.cacheReadTokens,
568
+ cacheCreationInputTokens: usage.cacheWriteTokens,
569
+ webSearchRequests: usage.webSearches ?? 0,
570
+ costUSD: info.runCostUsd ?? 0,
571
+ contextWindow: info.contextWindow,
572
+ maxOutputTokens: info.maxOutputTokens
573
+ }
574
+ },
575
+ permission_denials: info.denials,
576
+ ...errors === void 0 ? {} : { errors },
577
+ ...stop?.terminal === void 0 ? {} : { terminal_reason: stop.terminal }
578
+ };
579
+ }
580
+
581
+ // src/cli/repl.ts
582
+ import { createInterface } from "readline";
583
+ async function runRepl(runtime, approver, renderer, sessionPath, exitNow, io = { input: process.stdin, output: process.stderr }, firstInput, notifier) {
584
+ const history = [];
585
+ const list = rootLister(runtime.root);
586
+ const args = new CommandArgs(runtime);
587
+ const files = rootFiles(runtime.root);
588
+ const completer = (line) => {
589
+ const result = complete(line, line.length, {
590
+ commands: commandNames(runtime),
591
+ list,
592
+ args: (command, before) => args.choices(command, before),
593
+ files
594
+ });
595
+ if (result === void 0) return [[], line];
596
+ const word = line.slice(line.search(/\S*$/));
597
+ const done = result.text.slice(line.search(/\S*$/));
598
+ const words = result.candidates.map(
599
+ (c) => result.lines === true ? c.split(" ")[0] ?? c : c
600
+ );
601
+ return [words.length > 0 ? words : [done], word];
602
+ };
603
+ const ask = lineSource(io, completer);
604
+ let lastInterrupt = 0;
605
+ let first = firstInput;
606
+ for (; ; ) {
607
+ void args.refresh();
608
+ const input = first !== void 0 ? { kind: "line", text: first } : await ask(runtime.mode === "plan" ? "plan\u203A " : "\u203A ", history);
609
+ first = void 0;
610
+ if (input.kind === "eof") return;
611
+ if (input.kind === "interrupt") {
612
+ if (Date.now() - lastInterrupt < 2e3) return;
613
+ lastInterrupt = Date.now();
614
+ renderer.info("Press Ctrl-C again to exit, or type /exit.");
615
+ continue;
616
+ }
617
+ let raw = input.text;
618
+ while (raw.endsWith("\\")) {
619
+ const more = await ask("\u2026 ", []);
620
+ if (more.kind !== "line") break;
621
+ raw = `${raw.slice(0, -1)}
622
+ ${more.text}`;
623
+ }
624
+ const text = raw.trim();
625
+ if (text === "") continue;
626
+ history.unshift(raw);
627
+ if (text.startsWith("!") && text.length > 1) {
628
+ const controller = new AbortController();
629
+ approver.onInterrupt = () => controller.abort();
630
+ const stop = () => controller.abort();
631
+ process.on("SIGINT", stop);
632
+ try {
633
+ const result = await runtime.runUserCommand(text.slice(1).trim(), controller.signal);
634
+ renderer.info(`${result.text}
635
+ (The output goes to the model with your next message.)`);
636
+ } catch (error) {
637
+ renderer.warn(controller.signal.aborted ? "Command stopped." : error.message);
638
+ } finally {
639
+ process.off("SIGINT", stop);
640
+ approver.onInterrupt = () => {
641
+ };
642
+ }
643
+ continue;
644
+ }
645
+ let prompt = text;
646
+ if (text.startsWith("/")) {
647
+ const result = await runCommand(text, { runtime, renderer, sessionPath });
648
+ if (result === "exit") return;
649
+ if (result === "done") continue;
650
+ prompt = result.prompt;
651
+ }
652
+ for (; ; ) {
653
+ const planning = runtime.mode === "plan";
654
+ const outcome = await runTurnInTerminal(
655
+ runtime,
656
+ approver,
657
+ renderer,
658
+ prompt,
659
+ exitNow,
660
+ notifier
661
+ );
662
+ if (!planning || outcome.kind !== "done" || outcome.result.stopReason !== "done") break;
663
+ if (await planHandoff(runtime, approver) !== "now") break;
664
+ renderer.info(`\u203A ${BUILD_PROMPT}`);
665
+ prompt = BUILD_PROMPT;
666
+ }
667
+ }
668
+ }
669
+ function lineSource(io, completer) {
670
+ if (io.input.isTTY === true) {
671
+ return (prompt, history) => askTerminal(prompt, history, io, completer);
672
+ }
673
+ const rl = createInterface({ input: io.input, terminal: false });
674
+ const queue = [];
675
+ let waiting;
676
+ const push = (input) => {
677
+ if (waiting !== void 0) {
678
+ const resolve = waiting;
679
+ waiting = void 0;
680
+ resolve(input);
681
+ } else queue.push(input);
682
+ };
683
+ rl.on("line", (text) => push({ kind: "line", text }));
684
+ rl.on("close", () => push({ kind: "eof" }));
685
+ return (prompt) => {
686
+ io.output.write(prompt);
687
+ const next = queue.shift();
688
+ if (next !== void 0) {
689
+ if (next.kind === "eof") queue.unshift(next);
690
+ return Promise.resolve(next);
691
+ }
692
+ return new Promise((resolve) => {
693
+ waiting = resolve;
694
+ });
695
+ };
696
+ }
697
+ function askTerminal(prompt, history, io, completer) {
698
+ return new Promise((resolve) => {
699
+ const rl = createInterface({
700
+ input: io.input,
701
+ output: io.output,
702
+ terminal: true,
703
+ history: [...history],
704
+ historySize: 100,
705
+ ...completer === void 0 ? {} : { completer }
706
+ });
707
+ let done = false;
708
+ const finish = (input) => {
709
+ if (done) return;
710
+ done = true;
711
+ rl.close();
712
+ resolve(input);
713
+ };
714
+ rl.on("line", (text) => finish({ kind: "line", text }));
715
+ rl.on("SIGINT", () => {
716
+ io.output.write("\n");
717
+ finish({ kind: "interrupt" });
718
+ });
719
+ rl.on("close", () => finish({ kind: "eof" }));
720
+ rl.setPrompt(prompt);
721
+ rl.prompt();
722
+ });
723
+ }
724
+
725
+ // src/cli/index.ts
726
+ async function main() {
727
+ const program = new Command().enablePositionalOptions().name("garuda").description("A terminal coding agent. With no task, it starts a chat in the current folder.").version(VERSION).option("-p, --prompt <task>", "run one task and exit").option("-m, --model <id>", "model id (or set GARUDA_MODEL)").option(
728
+ "--subagent-model <id>",
729
+ "model of the explore subagent (or set GARUDA_SUBAGENT_MODEL); default: the main model"
730
+ ).option("--plan", "start in plan mode: read and plan, change nothing").option("--lsp", "add language server errors to edit results (TS/JS, Python, Java)").option("-r, --resume [session-id]", "continue the last session, or the given one").option("--replay <session-id-or-file>", "replay a recorded session with no API calls").addOption(
731
+ new Option("--output-format <format>", "with a task: text (default), json or stream-json").choices(OUTPUT_FORMATS).default("text")
732
+ ).option("--verbose", "with json output: also show the tool activity on stderr").action(async (options) => {
733
+ process.exitCode = await start(options, program);
734
+ });
735
+ program.command("eval").description("run the eval tasks in scratch folders and report pass/fail, steps and cost").option("-m, --model <id>", "model id (or set GARUDA_MODEL)").option("-s, --suite <name>", "basic (default), hard, java, python, all or repo").option("-t, --task <ids...>", "run only these tasks (from any suite)").option("--max-steps <n>", "step limit per task", (v) => Number.parseInt(v, 10)).option(
736
+ "--repeat <n>",
737
+ "run each task n times and show the mean",
738
+ (v) => Number.parseInt(v, 10)
739
+ ).option("--index <mode>", "code index tools for the model: off (default), lookup or all").option("--executor <name>", "auto (default), os or host").option("--keep", "keep the scratch folders").option("--list", "list the tasks and exit").option("--subagents <mode>", "the explore subagent: off (default) or on, for A/B runs").option("--subagent-model <id>", "model of the explore subagent; default: the main model").option("--todo <mode>", "the todo_write tool: off (default) or on, for A/B runs").option("--lsp <mode>", "language server errors in edit results: off (default) or on").option(
740
+ "--format <mode>",
741
+ "the project's formatter after edits: off (default) or on, for A/B runs"
742
+ ).option(
743
+ "--keep-thinking <mode>",
744
+ "send Claude's thinking blocks back: on (default) or off, for A/B runs"
745
+ ).option(
746
+ "--batch <mode>",
747
+ "model calls through the Batch API (half price, slow): off (default) or on"
748
+ ).option(
749
+ "--parallel <n>",
750
+ "tasks at the same time; default 1, with --batch on all (up to 20)",
751
+ (v) => Number.parseInt(v, 10)
752
+ ).option(
753
+ "--prepare <toolchain>",
754
+ "java: download Maven plugins and JUnit once; python: check pytest"
755
+ ).option("--from-git", "build the repo suite from this project's recent commits, then exit").option(
756
+ "--commits <n>",
757
+ "with --from-git: recent commits to look at (default 200)",
758
+ (v) => Number.parseInt(v, 10)
759
+ ).option("--since <date>", "with --from-git: only commits after this date").option(
760
+ "--max-tasks <n>",
761
+ "with --from-git: most tasks in the suite (default 30)",
762
+ (v) => Number.parseInt(v, 10)
763
+ ).option(
764
+ "--network <entries...>",
765
+ "presets (npm, pypi, maven, go, cargo, github) or hosts that sandboxed commands may reach"
766
+ ).option(
767
+ "--test-command <cmd>",
768
+ "with --from-git: the test command; {files} takes the test files (default: detected)"
769
+ ).action(async (options) => {
770
+ const { runEvalCommand } = await import("../evalCommand-3SSFMZQD.js");
771
+ process.exitCode = await runEvalCommand({
772
+ ...options,
773
+ model: options.model ?? process.env.GARUDA_MODEL,
774
+ subagentModel: options.subagentModel ?? process.env.GARUDA_SUBAGENT_MODEL
775
+ });
776
+ });
777
+ program.command("init").description(
778
+ "set up this folder (AGENTS.md; commands, MCP servers and rules from Claude Code, OpenCode, Codex, Gemini CLI, Tabnine, Cursor, Copilot), then chat"
779
+ ).option("-m, --model <id>", "model id (or set GARUDA_MODEL)").action(async (options) => {
780
+ process.exitCode = await start({ ...options, firstInput: "/init" }, program);
781
+ });
782
+ program.command("run").description(
783
+ "run a scheduled job (made with /schedule) in its own worktree, with its approval list and nobody to ask"
784
+ ).argument("<job-id>", "the job id, from /schedule or /jobs").option("--at <HH:MM>", "wait until this local time first (keep the terminal open)").option("-m, --model <id>", "model id; default: the job's model").addOption(new Option("--from-launchd").hideHelp()).action(
785
+ async (job, options) => {
786
+ process.exitCode = await start({ ...options, job }, program);
787
+ }
788
+ );
789
+ program.command("night").description(
790
+ "run this project's night queue (jobs from /schedule), up to 3 at a time, then write one digest"
791
+ ).option("--at <HH:MM>", "wait until this local time first (keep the terminal open)").option("--parallel <n>", "jobs at the same time (1 to 10, default 3)").addOption(new Option("--from-launchd").hideHelp()).action(async (options) => {
792
+ const { nightCommand } = await import("../nightCommand-CR6ZHYGP.js");
793
+ process.exitCode = await nightCommand(options, new PlainRenderer());
794
+ });
795
+ program.command("acp").description(
796
+ "run as an agent for an editor over the Agent Client Protocol (VS Code with an extension, Zed, JetBrains, Neovim, Emacs); the editor starts it"
797
+ ).option("-m, --model <id>", "model id (or set GARUDA_MODEL)").action(async (options) => {
798
+ const { acpCommand } = await import("../acpCommand-FZ2P5JFO.js");
799
+ process.exitCode = await acpCommand(options);
800
+ });
801
+ const lsp = program.command("lsp").description("show the language servers for diagnostics (TS/JS, Python, Java)").action(async () => {
802
+ const { lspStatusCommand } = await import("../lspCommand-XNTBHXLA.js");
803
+ process.exitCode = await lspStatusCommand();
804
+ });
805
+ lsp.command("install").description("install a pinned language server into ~/.garuda/lsp (needs the network)").argument("<language>", "typescript, python or java").action(async (language) => {
806
+ const { lspInstallCommand } = await import("../lspCommand-XNTBHXLA.js");
807
+ process.exitCode = await lspInstallCommand(language);
808
+ });
809
+ await program.parseAsync();
810
+ }
811
+ async function start(options, program) {
812
+ const mainRoot = realpathSync(process.cwd());
813
+ const store = new FileSessionStore(mainRoot);
814
+ if (options.replay !== void 0) return replay(options.replay, store);
815
+ let job;
816
+ let jobGuard;
817
+ if (options.job !== void 0) {
818
+ const { prepareJob } = await import("../jobCommand-XJ5GULS5.js");
819
+ const prepared = await prepareJob(
820
+ mainRoot,
821
+ options.job,
822
+ options.at,
823
+ new PlainRenderer(),
824
+ void 0,
825
+ { fromLaunchd: options.fromLaunchd === true },
826
+ homedir2()
827
+ );
828
+ if (typeof prepared === "number") return prepared;
829
+ job = prepared;
830
+ const { failJobSync } = await import("../job-3NYDMP2M.js");
831
+ const preparedJob = prepared;
832
+ jobGuard = () => {
833
+ if (preparedJob.job.status === "running") {
834
+ failJobSync(
835
+ preparedJob.job,
836
+ "Garuda stopped before the job's turn finished. The error is in the job's log or on the terminal."
837
+ );
838
+ }
839
+ };
840
+ process.once("exit", jobGuard);
841
+ }
842
+ const root = job?.root ?? mainRoot;
843
+ let prompt = job?.job.prompt ?? options.prompt;
844
+ if (prompt === void 0 && !process.stdin.isTTY && job === void 0)
845
+ prompt = readFileSync(0, "utf8").trim();
846
+ if (prompt === "") program.error("The task is empty.");
847
+ const format = options.outputFormat ?? "text";
848
+ if (format !== "text" && prompt === void 0) {
849
+ program.error(`--output-format ${format} needs a task: use -p "task" or give it on stdin.`);
850
+ }
851
+ const spec = options.model ?? job?.job.model ?? process.env.GARUDA_MODEL;
852
+ if (!spec) program.error("Set a model with --model <id> or the GARUDA_MODEL variable.");
853
+ const models = await loadModelsConfig();
854
+ if (models.problem !== void 0) program.error(models.problem);
855
+ let resolved;
856
+ try {
857
+ resolved = resolveModel(spec, models.config);
858
+ } catch (error) {
859
+ program.error(error.message);
860
+ }
861
+ const modelId = resolved.spec;
862
+ const subSpec = options.subagentModel ?? process.env.GARUDA_SUBAGENT_MODEL;
863
+ let sub;
864
+ if (subSpec) {
865
+ try {
866
+ sub = resolveModel(subSpec, models.config);
867
+ } catch (error) {
868
+ program.error(error.message);
869
+ }
870
+ }
871
+ const searchConfig = await loadSearchConfig();
872
+ const hasApiKey = hasProviderKey("ANTHROPIC_API_KEY");
873
+ for (const name of [resolved.def.apiKeyEnv, sub?.def.apiKeyEnv, "ANTHROPIC_API_KEY"]) {
874
+ if (name !== void 0) keepProviderKey(name);
875
+ }
876
+ const { loadTeamPolicy } = await import("../policy-EJPLEEXP.js");
877
+ let team;
878
+ try {
879
+ team = await loadTeamPolicy();
880
+ } catch (error) {
881
+ program.error(`Team policy: ${error.message}`);
882
+ }
883
+ const renderer = new PlainRenderer();
884
+ const terminalApprover = new TerminalApprover();
885
+ const approver = new SwitchApprover(
886
+ job === void 0 ? terminalApprover : new AutoApprover("deny")
887
+ );
888
+ let events = renderer;
889
+ const runtime = await Runtime.create({
890
+ root,
891
+ modelId,
892
+ ...team === void 0 ? {} : { policy: team.policy, policySources: team.sources },
893
+ // The audit log lives in ~/.garuda/audit, keyed by the project (a job: its main checkout, not
894
+ // the worktree that is deleted after the job).
895
+ audit: { dir: auditDirFor(job?.job.root ?? root) },
896
+ model: job?.job.batch === true && resolved.createBatch !== void 0 ? () => jobBatchClient(job, resolved, renderer) : () => resolved.create(),
897
+ modelInfo: resolved.info,
898
+ ...resolved.maxTokens === void 0 ? {} : { maxTokens: resolved.maxTokens },
899
+ ...sub === void 0 ? {} : { subagentModel: { spec: sub.spec, model: () => sub.create(), info: sub.info } },
900
+ approver,
901
+ store,
902
+ ...job === void 0 ? {} : {
903
+ settings: job.settings,
904
+ // The job's network list was approved with the job (0.13).
905
+ network: { approved: true },
906
+ unattended: {
907
+ reason: JOB_DENIAL,
908
+ onDeny: (tool, target) => recordJobDenial(job, tool, target)
909
+ }
910
+ },
911
+ ...options.plan === true ? { mode: "plan" } : {},
912
+ ...options.lsp === true ? { lsp: { enabled: true } } : {},
913
+ // Undo snapshots are on for the chat and for -p (a later chat can undo the task). A job has its
914
+ // own branch instead.
915
+ ...job === void 0 ? { undo: {} } : {},
916
+ // Skills from ~/.garuda/skills, ~/.claude/skills and the project (0.5).
917
+ skills: {},
918
+ // User language plugins from ~/.garuda/languages (0.16); project plugins stay off.
919
+ languages: {},
920
+ // The project's settings that loosen safety: asked at startup when there is a terminal,
921
+ // pinned in ~/.garuda/trust.json (a job passes its own settings).
922
+ projectSettings: { home: homedir2(), ask: process.stdin.isTTY === true },
923
+ // Web search (0.5) and Claude's own search (0.6): only the user's search.json and environment.
924
+ ...searchConfig.config === void 0 && searchConfig.claude === void 0 ? {} : {
925
+ search: {
926
+ ...searchConfig.config === void 0 ? {} : { config: searchConfig.config },
927
+ ...searchConfig.claude === void 0 ? {} : { claude: searchConfig.claude },
928
+ // The saved choice (0.14); the first-time answer is saved in ~/.garuda/search.json.
929
+ ...searchConfig.use === void 0 ? {} : { use: searchConfig.use },
930
+ home: homedir2()
931
+ }
932
+ },
933
+ // Custom agents from ~/.garuda/agents, ~/.claude/agents and the project (0.5). A model id in a
934
+ // user agent file goes through the same providers as -m (only ~/.garuda/models.json).
935
+ agents: {
936
+ resolveModel: (spec2) => {
937
+ const r = resolveModel(spec2, models.config);
938
+ return { spec: r.spec, model: () => r.create(), info: r.info };
939
+ }
940
+ },
941
+ // /models (0.6): the same providers as -m; the list adds the user's configured models.
942
+ models: {
943
+ resolve: (spec2) => {
944
+ const r = resolveModel(spec2, models.config);
945
+ return {
946
+ spec: r.spec,
947
+ model: () => r.create(),
948
+ info: r.info,
949
+ ...r.maxTokens === void 0 ? {} : { maxTokens: r.maxTokens }
950
+ };
951
+ },
952
+ configured: Object.keys(models.config.models)
953
+ },
954
+ ...options.resume === void 0 ? {} : { resume: options.resume },
955
+ onEvent: (event) => events.event(event),
956
+ onNotice: (text) => events.warn(text)
957
+ });
958
+ process.on("exit", () => runtime.executor.shutdown());
959
+ const exitNow = () => {
960
+ runtime.executor.shutdown();
961
+ process.exit(130);
962
+ };
963
+ if (runtime.executorNotice !== void 0) renderer.warn(runtime.executorNotice);
964
+ if (job !== void 0 && runtime.executor.isolation === "none") {
965
+ renderer.error(
966
+ "A job needs the OS sandbox (Seatbelt or bubblewrap): without it, every command would need an approval, and nobody is there to give it."
967
+ );
968
+ await runtime.close();
969
+ const { saveJob } = await import("../job-3NYDMP2M.js");
970
+ job.job.status = "failed";
971
+ await saveJob(job.job);
972
+ if (options.fromLaunchd === true || job.job.launchd !== void 0) {
973
+ const { removeAgent, defaultAgentEnv } = await import("../launchd-QVHQ3VNN.js");
974
+ await removeAgent(runtime.executor, job.job, defaultAgentEnv());
975
+ }
976
+ return 1;
977
+ }
978
+ if (searchConfig.problem !== void 0) {
979
+ renderer.warn(
980
+ searchConfig.claude === void 0 ? `Web search is off: ${searchConfig.problem}` : `The fallback web search is off (Claude's search still works): ${searchConfig.problem}`
981
+ );
982
+ }
983
+ const session = runtime.session;
984
+ if (session !== void 0) {
985
+ renderer.info(
986
+ `Resumed session ${session.id} (${session.messages.length} messages, ${formatTokens(session.contextTokens)} tokens of context).`
987
+ );
988
+ }
989
+ for (const note of resolved.notes) renderer.info(note);
990
+ if (runtime.price === void 0) {
991
+ renderer.warn(
992
+ `Garuda has no price for ${modelId}. Set "price" for it in ~/.garuda/models.json to see cost.`
993
+ );
994
+ }
995
+ if (prompt !== void 0) {
996
+ const output = format === "text" ? void 0 : new JsonOutput({
997
+ format,
998
+ write: (line) => process.stdout.write(`${line}
999
+ `),
1000
+ // stdout holds only JSON: model text never goes there.
1001
+ log: new PlainRenderer({ out: nullStream(), err: process.stderr }),
1002
+ verbose: options.verbose === true,
1003
+ sessionId: () => runtime.session?.id ?? "",
1004
+ runInfo: () => ({
1005
+ cwd: root,
1006
+ model: modelId,
1007
+ tools: runtime.toolNames(),
1008
+ mcpServers: runtime.mcpStatus(),
1009
+ permissionMode: runtime.mode === "plan" ? "plan" : "default",
1010
+ slashCommands: [
1011
+ ...BUILTIN_COMMANDS,
1012
+ ...runtime.commands.map((c) => c.name),
1013
+ ...runtime.skills.filter((k) => k.userInvocable).map((k) => k.name)
1014
+ ],
1015
+ skills: runtime.skills.map((k) => k.name),
1016
+ agents: runtime.agents.map((a) => a.name),
1017
+ apiKeySource: !modelId.includes("/") && hasApiKey ? "ANTHROPIC_API_KEY" : "none"
1018
+ })
1019
+ });
1020
+ if (output !== void 0) events = output;
1021
+ const costBefore = runtime.session?.costUsd ?? 0;
1022
+ const finish = (outcome2) => {
1023
+ const cost = runtime.session?.costUsd;
1024
+ output?.finish(outcome2, {
1025
+ totalCostUsd: cost ?? 0,
1026
+ runCostUsd: cost === void 0 ? void 0 : cost - costBefore,
1027
+ contextWindow: runtime.limits.contextWindow,
1028
+ maxOutputTokens: resolved.maxTokens ?? DEFAULT_MAX_TOKENS,
1029
+ ...outcome2.kind === "done" ? { stopMessage: stopMessage(outcome2.result.stopReason, runtime.limits) } : {}
1030
+ });
1031
+ };
1032
+ if (prompt.startsWith("/")) {
1033
+ const resolved2 = await runtime.resolveCommand(prompt, new AbortController().signal);
1034
+ if (resolved2.kind === "denied") {
1035
+ (output ?? renderer).warn(resolved2.message);
1036
+ await runtime.close();
1037
+ finish({ kind: "error", message: resolved2.message });
1038
+ return 1;
1039
+ }
1040
+ if (resolved2.kind === "prompt") prompt = resolved2.prompt;
1041
+ }
1042
+ if (job !== void 0) {
1043
+ const { testsBefore } = await import("../jobCommand-XJ5GULS5.js");
1044
+ await testsBefore(job, runtime, renderer);
1045
+ }
1046
+ const outcome = await runTurnInTerminal(
1047
+ runtime,
1048
+ terminalApprover,
1049
+ output ?? renderer,
1050
+ prompt,
1051
+ exitNow
1052
+ );
1053
+ await runtime.close();
1054
+ finish(outcome);
1055
+ if (job !== void 0) {
1056
+ const { finishJob } = await import("../jobCommand-XJ5GULS5.js");
1057
+ await finishJob(job, outcome, runtime, renderer, void 0, {
1058
+ fromLaunchd: options.fromLaunchd === true
1059
+ });
1060
+ }
1061
+ if (outcome.kind === "interrupted") return 130;
1062
+ if (outcome.kind === "error") return 1;
1063
+ return outcome.result.stopReason === "done" ? 0 : 2;
1064
+ }
1065
+ const ink = wantsInk() ? await loadInk(renderer) : void 0;
1066
+ const bannerInfo = {
1067
+ version: VERSION,
1068
+ model: modelId,
1069
+ sandbox: runtime.executor.isolation === "none" ? "none: each command asks first" : `${runtime.executor.name} \xB7 no network`,
1070
+ root,
1071
+ extras: runtime.extras(),
1072
+ subagents: runtime.subagentsSummary(),
1073
+ agents: runtime.agentsSummary(),
1074
+ skills: runtime.skillsSummary(),
1075
+ tools: runtime.toolsSummary(),
1076
+ ink: ink !== void 0
1077
+ };
1078
+ let startBanner = banner(bannerInfo, {
1079
+ columns: process.stdout.columns || 80,
1080
+ color: colorLevel(process.stdout)
1081
+ });
1082
+ if (options.firstInput === void 0) {
1083
+ const { initTip } = await import("../detect-LARDSLLZ.js");
1084
+ const tip = initTip(root);
1085
+ if (tip !== void 0) startBanner += `
1086
+ ${tip}`;
1087
+ }
1088
+ const sessionPath = (id2) => join(store.dir, `${id2}.jsonl`);
1089
+ const notify = runtime.notificationSettings;
1090
+ const channel = pickChannel(notify?.channel);
1091
+ const focus = ink !== void 0 && channel !== "off" && reportsFocus() ? new FocusTracker() : void 0;
1092
+ if (focus !== void 0) process.on("exit", () => process.stdout.write(FOCUS_REPORTING_OFF));
1093
+ const notifier = process.stdout.isTTY ? new Notifier(
1094
+ channel,
1095
+ (bytes) => process.stdout.write(bytes),
1096
+ notify?.afterSeconds,
1097
+ () => focus?.focused
1098
+ ) : void 0;
1099
+ if (notifier !== void 0) approver.onAsk = (request) => notifier.approval(request);
1100
+ if (ink !== void 0) {
1101
+ const setEventTarget = (target) => {
1102
+ events = target;
1103
+ };
1104
+ await ink.runInkChat(
1105
+ runtime,
1106
+ approver,
1107
+ setEventTarget,
1108
+ startBanner,
1109
+ sessionPath,
1110
+ exitNow,
1111
+ options.firstInput,
1112
+ notifier,
1113
+ focus
1114
+ );
1115
+ } else {
1116
+ process.stderr.write(`${startBanner}
1117
+ `);
1118
+ await runRepl(
1119
+ runtime,
1120
+ terminalApprover,
1121
+ renderer,
1122
+ sessionPath,
1123
+ exitNow,
1124
+ void 0,
1125
+ options.firstInput,
1126
+ notifier
1127
+ );
1128
+ }
1129
+ await runtime.close();
1130
+ const id = runtime.session?.id;
1131
+ if (id !== void 0) renderer.info(`Session ${id}. Continue it with: garuda --resume ${id}`);
1132
+ return 0;
1133
+ }
1134
+ function nullStream() {
1135
+ return new Writable({ write: (_chunk, _encoding, done) => done() });
1136
+ }
1137
+ function wantsInk() {
1138
+ return process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.GARUDA_PLAIN !== "1" && process.env.TERM !== "dumb";
1139
+ }
1140
+ async function loadInk(renderer) {
1141
+ try {
1142
+ return await import("../inkChat-34BTFG2K.js");
1143
+ } catch (error) {
1144
+ renderer.info(`Plain chat (Ink is not available: ${error.message.split("\n")[0]}).`);
1145
+ return void 0;
1146
+ }
1147
+ }
1148
+ async function replay(target, store) {
1149
+ const records = existsSync(target) ? parseRecords(readFileSync(target, "utf8")) : await store.read(target);
1150
+ const report = await replaySession(records, new ToolRegistry(defaultTools({ daemons: true })));
1151
+ const summary = `${report.runs} run(s), ${report.steps} step(s), ${report.toolCalls} tool call(s)`;
1152
+ if (report.matches) {
1153
+ process.stdout.write(`Replay matches the recording: ${summary}.
1154
+ `);
1155
+ return 0;
1156
+ }
1157
+ process.stdout.write(`Replay does not match the recording (${summary}):
1158
+ `);
1159
+ for (const problem of report.problems) process.stdout.write(` - ${problem}
1160
+ `);
1161
+ return 1;
1162
+ }
1163
+ main().catch((error) => {
1164
+ process.stderr.write(`Error: ${describeError(error)}
1165
+ `);
1166
+ process.exitCode = 1;
1167
+ });
1168
+ function recordJobDenial(job, tool, target) {
1169
+ const shown = target.kind === "path" ? target.path : target.kind === "command" ? target.command.split("\n")[0] ?? "" : target.kind === "url" ? target.host : target.json.slice(0, 200);
1170
+ job.denied.push({ tool, target: shown });
1171
+ }
1172
+ async function jobBatchClient(job, resolved, renderer) {
1173
+ const { DeadlineClient } = await import("../deadline-F5RJJ2QW.js");
1174
+ const { nextTime } = await import("../launchd-QVHQ3VNN.js");
1175
+ const { DEFAULT_FINISH_BY, DEFAULT_STEP_LIMIT_MINUTES, SWITCH_BEFORE_MS } = await import("../create-N4I7TIZE.js");
1176
+ const finishBy = job.job.finishBy ?? DEFAULT_FINISH_BY;
1177
+ const stepLimit = job.job.stepLimitMinutes ?? DEFAULT_STEP_LIMIT_MINUTES;
1178
+ const switchAt = new Date(nextTime(finishBy).getTime() - SWITCH_BEFORE_MS);
1179
+ const batch = await resolved.createBatch(
1180
+ process.env,
1181
+ {
1182
+ // A quiet terminal must not look stuck: the batch id, then a line every minute.
1183
+ onWait: ({ batchId, waitedMs, status }) => renderer.info(
1184
+ waitedMs === 0 ? `Waiting for batch ${batchId} \u2026` : ` still waiting for ${batchId}: ${Math.round(waitedMs / 6e4)} min (${status})`
1185
+ )
1186
+ }
1187
+ );
1188
+ job.deadline = new DeadlineClient(batch, () => resolved.create(), switchAt, {
1189
+ stepLimitMs: stepLimit * 6e4
1190
+ });
1191
+ const when = switchAt.toLocaleString(void 0, {
1192
+ weekday: "short",
1193
+ day: "numeric",
1194
+ month: "short",
1195
+ hour: "2-digit",
1196
+ minute: "2-digit"
1197
+ });
1198
+ renderer.info(
1199
+ `Batch API (half price) until ${when}, then the normal API. A step that waits more than ${stepLimit} min runs on the normal API.`
1200
+ );
1201
+ return job.deadline;
1202
+ }
1203
+ //# sourceMappingURL=index.js.map