@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,320 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ notificationBytes,
4
+ pickChannel
5
+ } from "./chunk-N4I6YS3E.js";
6
+ import {
7
+ gateProjectSettings,
8
+ totalTokens
9
+ } from "./chunk-HRGX53VC.js";
10
+ import {
11
+ TrustStore
12
+ } from "./chunk-5DEWP3YX.js";
13
+ import {
14
+ cleanArgs,
15
+ commitJob,
16
+ jobChanges,
17
+ prepareWorktree
18
+ } from "./chunk-K3SIUOAG.js";
19
+ import {
20
+ defaultAgentEnv,
21
+ removeAgent
22
+ } from "./chunk-S77XDSD4.js";
23
+ import {
24
+ jobReport
25
+ } from "./chunk-YQTGMCU6.js";
26
+ import {
27
+ jobVerdict,
28
+ parseReview,
29
+ reviewPrompt,
30
+ reviewerSystem,
31
+ riskFlags,
32
+ stackOf,
33
+ testSummary
34
+ } from "./chunk-OUCV3FCT.js";
35
+ import {
36
+ JobGitError,
37
+ git,
38
+ hostCommand
39
+ } from "./chunk-2TAXSFWN.js";
40
+ import {
41
+ createExecutor,
42
+ loadSettings
43
+ } from "./chunk-SI2QJYCR.js";
44
+ import {
45
+ JOBS_DIR,
46
+ loadJob,
47
+ saveJob
48
+ } from "./chunk-KKB5W47S.js";
49
+ import {
50
+ parseRule
51
+ } from "./chunk-3NJNVZTK.js";
52
+
53
+ // src/cli/jobCommand.ts
54
+ import { writeFile } from "fs/promises";
55
+ import { join } from "path";
56
+ var JOB_TEST_TIMEOUT_MS = 10 * 6e4;
57
+ async function testsBefore(prepared, runtime, renderer, git2 = createExecutor("host").executor) {
58
+ const { job } = prepared;
59
+ if (job.test === void 0) return;
60
+ renderer.info(`Tests before the job: ${job.test}`);
61
+ prepared.testsBefore = await runtime.runCheck(
62
+ job.test,
63
+ JOB_TEST_TIMEOUT_MS,
64
+ new AbortController().signal
65
+ );
66
+ renderer.info(`Tests before the job: ${testSummary(prepared.testsBefore)}`);
67
+ await git(git2, job.worktree, ["checkout", "--", "."], { check: false });
68
+ await git(git2, job.worktree, cleanArgs(job.links), { check: false });
69
+ }
70
+ async function proveJob(prepared, result, runtime, renderer, git2) {
71
+ const { job } = prepared;
72
+ const signal = new AbortController().signal;
73
+ let after;
74
+ if (job.test !== void 0) {
75
+ after = await runtime.runCheck(job.test, JOB_TEST_TIMEOUT_MS, signal);
76
+ renderer.info(`Tests after the job: ${testSummary(after)}`);
77
+ }
78
+ const before = prepared.testsBefore;
79
+ const flags = riskFlags({
80
+ result,
81
+ ...job.test === void 0 ? {} : { test: job.test },
82
+ ...before === void 0 ? {} : { before },
83
+ ...after === void 0 ? {} : { after }
84
+ });
85
+ if (result.commit === void 0) flags.push({ level: "look", text: "No file changed." });
86
+ const proof = {
87
+ verdict: "needs-look",
88
+ ...before === void 0 ? {} : { before },
89
+ ...after === void 0 ? {} : { after },
90
+ flags
91
+ };
92
+ if (job.review !== false && result.commit !== void 0) {
93
+ renderer.info("Reviewing the diff (a principal engineer's review)\u2026");
94
+ try {
95
+ const diff = (await git(git2, job.root, ["diff", "--no-renames", job.base, job.branch], {
96
+ check: false
97
+ })).stdout;
98
+ const answer = await runtime.askModel(
99
+ reviewerSystem(stackOf(result.files, runtime.profiles)),
100
+ reviewPrompt({
101
+ prompt: job.prompt,
102
+ files: result.files,
103
+ ...job.test === void 0 ? {} : { test: job.test },
104
+ ...before === void 0 ? {} : { before },
105
+ ...after === void 0 ? {} : { after },
106
+ flags,
107
+ diff
108
+ }),
109
+ signal
110
+ );
111
+ const parsed = parseReview(answer.text);
112
+ proof.review = {
113
+ ...parsed,
114
+ ...answer.costUsd === void 0 ? {} : { costUsd: answer.costUsd }
115
+ };
116
+ } catch (error) {
117
+ proof.reviewError = error.message;
118
+ }
119
+ }
120
+ proof.verdict = jobVerdict(flags, proof.review?.verdict);
121
+ return proof;
122
+ }
123
+ async function prepareJob(mainRoot, id, at, renderer, git2 = createExecutor("host").executor, launchd = {}, trustHome) {
124
+ const prepared = await prepare(mainRoot, id, at, renderer, git2, trustHome);
125
+ if (typeof prepared === "number" && launchd.fromLaunchd === true) {
126
+ const job = await loadJob(mainRoot, id).catch(() => void 0);
127
+ if (job !== void 0) await removeAgent(git2, job, launchd.env ?? defaultAgentEnv());
128
+ }
129
+ return prepared;
130
+ }
131
+ async function prepare(mainRoot, id, at, renderer, git2, trustHome) {
132
+ let job;
133
+ try {
134
+ job = await loadJob(mainRoot, id);
135
+ } catch (error) {
136
+ renderer.error(error.message);
137
+ return 1;
138
+ }
139
+ if (job.status === "running") {
140
+ renderer.error(
141
+ `Job ${id} is marked as running. If no other garuda runs it, set "status" to "stopped" in ${JOBS_DIR}/${id}.json and try again.`
142
+ );
143
+ return 1;
144
+ }
145
+ if (job.status === "done") {
146
+ renderer.error(`Job ${id} is done. Its report: ${join(JOBS_DIR, `${id}.md`)}`);
147
+ return 1;
148
+ }
149
+ if (at !== void 0) {
150
+ const waited = await waitUntil(at, renderer, job);
151
+ if (waited !== 0) return waited;
152
+ }
153
+ try {
154
+ await prepareWorktree(git2, job);
155
+ } catch (error) {
156
+ if (!(error instanceof JobGitError)) throw error;
157
+ renderer.error(`The job's worktree failed: ${error.message}`);
158
+ return 1;
159
+ }
160
+ const gate = await gateProjectSettings({
161
+ root: job.root,
162
+ settings: await loadSettings(job.worktree),
163
+ trust: trustHome === void 0 ? void 0 : await TrustStore.open(trustHome)
164
+ });
165
+ if (gate.notice !== void 0) renderer.info(gate.notice);
166
+ const project = gate.settings;
167
+ const links = job.links.map((name) => join(job.root, name));
168
+ const { network: _projectNetwork, ...projectRest } = project;
169
+ const settings = {
170
+ ...projectRest,
171
+ ...job.network === void 0 ? {} : { network: { allow: job.network } },
172
+ allow: [...project.allow, ...job.allow.map((rule) => parseRule(rule))],
173
+ maxSteps: job.maxSteps,
174
+ sandbox: {
175
+ ...project.sandbox,
176
+ // Linked folders (node_modules …) live in the checkout: tools may write their caches there.
177
+ writePaths: [...project.sandbox?.writePaths ?? [], ...links]
178
+ }
179
+ };
180
+ job.status = "running";
181
+ job.startedAt = (/* @__PURE__ */ new Date()).toISOString();
182
+ await saveJob(job);
183
+ renderer.info(
184
+ `Job ${job.id}: ${job.title}
185
+ Branch ${job.branch} \xB7 worktree ${job.worktree}
186
+ Approved: ${job.allow.length === 0 ? "nothing beyond the sandbox" : job.allow.join(", ")}`
187
+ );
188
+ return { job, root: job.worktree, settings, denied: [], startedAt: Date.now() };
189
+ }
190
+ async function waitUntil(at, renderer, job) {
191
+ const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(at);
192
+ if (match === null) {
193
+ renderer.error(`"${at}" is not a time. Use HH:MM, for example 01:00.`);
194
+ return 1;
195
+ }
196
+ const ms = msUntil(Number(match[1]), Number(match[2]));
197
+ const hours = Math.floor(ms / 36e5);
198
+ const minutes = Math.round(ms % 36e5 / 6e4);
199
+ renderer.info(
200
+ `Job ${job.id} waits until ${at} (in ${hours} h ${minutes} min). Keep this terminal open and the Mac awake and on power. Ctrl-C cancels the wait.`
201
+ );
202
+ const cancelled = await new Promise((resolve) => {
203
+ const timer = setTimeout(() => {
204
+ process.off("SIGINT", cancel);
205
+ resolve(false);
206
+ }, ms);
207
+ const cancel = () => {
208
+ clearTimeout(timer);
209
+ resolve(true);
210
+ };
211
+ process.once("SIGINT", cancel);
212
+ });
213
+ if (cancelled) {
214
+ renderer.warn(`The wait was cancelled. Job ${job.id} is still scheduled.`);
215
+ return 130;
216
+ }
217
+ return 0;
218
+ }
219
+ function msUntil(hour, minute, now = /* @__PURE__ */ new Date()) {
220
+ const next = new Date(now);
221
+ next.setHours(hour, minute, 0, 0);
222
+ if (next.getTime() <= now.getTime()) next.setDate(next.getDate() + 1);
223
+ return next.getTime() - now.getTime();
224
+ }
225
+ async function finishJob(prepared, outcome, runtime, renderer, git2 = createExecutor("host").executor, launchd = {}) {
226
+ const { job } = prepared;
227
+ const hadAgent = job.launchd !== void 0 || launchd.fromLaunchd === true;
228
+ delete job.launchd;
229
+ const result = {
230
+ stopReason: outcome.kind === "done" ? outcome.result.stopReason : outcome.kind,
231
+ steps: outcome.kind === "done" ? outcome.result.steps : 0,
232
+ tokens: runtime.session === void 0 ? 0 : totalTokens(runtime.session.usage),
233
+ ...runtime.session?.costUsd === void 0 ? {} : { costUsd: runtime.session.costUsd },
234
+ durationMs: Date.now() - prepared.startedAt,
235
+ ...runtime.session === void 0 ? {} : { sessionId: runtime.session.id },
236
+ files: [],
237
+ denied: prepared.denied,
238
+ answer: lastAnswer(runtime),
239
+ ...prepared.deadline === void 0 ? {} : {
240
+ modelCalls: {
241
+ batch: prepared.deadline.calls.primary,
242
+ normal: prepared.deadline.calls.fallback,
243
+ slow: prepared.deadline.calls.slow,
244
+ ...prepared.deadline.calls.switchedAt === void 0 ? {} : { switchedAt: prepared.deadline.calls.switchedAt.toISOString() }
245
+ }
246
+ },
247
+ ...outcome.kind === "error" ? { error: outcome.message } : {}
248
+ };
249
+ try {
250
+ const commit = await commitJob(
251
+ git2,
252
+ job,
253
+ `Garuda job ${job.id}: ${job.title}${outcome.kind === "done" && outcome.result.stopReason === "done" ? "" : ` (${result.stopReason})`}`
254
+ );
255
+ if (commit !== void 0) result.commit = commit;
256
+ result.files = await jobChanges(git2, job);
257
+ } catch (error) {
258
+ if (!(error instanceof JobGitError)) throw error;
259
+ result.error = [result.error, `commit: ${error.message}`].filter(Boolean).join("; ");
260
+ }
261
+ if (outcome.kind !== "interrupted") {
262
+ result.proof = await proveJob(prepared, result, runtime, renderer, git2);
263
+ }
264
+ job.status = outcome.kind === "error" ? "failed" : outcome.kind === "interrupted" ? "stopped" : "done";
265
+ job.endedAt = (/* @__PURE__ */ new Date()).toISOString();
266
+ job.result = result;
267
+ await saveJob(job);
268
+ const report = jobReport(job);
269
+ const file = join(job.root, JOBS_DIR, `${job.id}.md`);
270
+ await writeFile(file, `${report}
271
+ `, { mode: 384 });
272
+ renderer.info(`
273
+ ${report}
274
+
275
+ Report: ${file}`);
276
+ const verdict = result.proof === void 0 ? "" : result.proof.verdict === "ready" ? ", ready to merge" : ", needs a look";
277
+ const note = `Garuda: job ${job.id} ${job.status}${verdict} (${result.files.length} file(s) changed)`;
278
+ if (process.stdout.isTTY) {
279
+ process.stdout.write(
280
+ notificationBytes(pickChannel(runtime.notificationSettings?.channel), note)
281
+ );
282
+ } else if (launchd.fromLaunchd === true && process.platform === "darwin") {
283
+ await hostCommand(
284
+ git2,
285
+ job.root,
286
+ [
287
+ "osascript",
288
+ "-e",
289
+ "on run argv",
290
+ "-e",
291
+ 'display notification (item 1 of argv) with title "Garuda"',
292
+ "-e",
293
+ "end run",
294
+ note
295
+ ],
296
+ { check: false }
297
+ );
298
+ }
299
+ if (hadAgent) await removeAgent(git2, job, launchd.env ?? defaultAgentEnv());
300
+ }
301
+ function lastAnswer(runtime) {
302
+ const messages = runtime.session?.messages ?? [];
303
+ for (let i = messages.length - 1; i >= 0; i--) {
304
+ const m = messages[i];
305
+ if (m?.role !== "assistant") continue;
306
+ const text = m.content.map((b) => b.type === "text" ? b.text : "").join("").trim();
307
+ if (text !== "") return text.length <= 8e3 ? text : `${text.slice(0, 8e3)}
308
+ \u2026 [cut]`;
309
+ }
310
+ return "";
311
+ }
312
+
313
+ export {
314
+ JOB_TEST_TIMEOUT_MS,
315
+ testsBefore,
316
+ prepareJob,
317
+ msUntil,
318
+ finishJob
319
+ };
320
+ //# sourceMappingURL=chunk-2S62ZNTC.js.map
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_ENV_ALLOWLIST
4
+ } from "./chunk-SI2QJYCR.js";
5
+
6
+ // src/jobs/git.ts
7
+ var GIT_TIMEOUT_MS = 12e4;
8
+ var JobGitError = class extends Error {
9
+ };
10
+ function shellWord(text) {
11
+ return /^[A-Za-z0-9_./:@=+-]+$/.test(text) ? text : `'${text.replace(/'/g, "'\\''")}'`;
12
+ }
13
+ function git(executor, cwd, args, options = {}) {
14
+ return hostCommand(executor, cwd, ["git", "-c", "core.hooksPath=/dev/null", ...args], options);
15
+ }
16
+ async function hostCommand(executor, cwd, argv, options = {}) {
17
+ const policy = {
18
+ root: cwd,
19
+ sandbox: false,
20
+ writePaths: [],
21
+ denyWritePaths: [],
22
+ denyReadPaths: [],
23
+ network: false,
24
+ envAllowlist: [...DEFAULT_ENV_ALLOWLIST],
25
+ timeoutMs: GIT_TIMEOUT_MS,
26
+ maxOutputBytes: 5e6
27
+ };
28
+ const command = argv.map(shellWord).join(" ");
29
+ const result = await executor.run(command, policy, {
30
+ ...options.signal === void 0 ? {} : { signal: options.signal },
31
+ env: { GIT_TERMINAL_PROMPT: "0" }
32
+ });
33
+ if (result.aborted) throw options.signal?.reason ?? new Error("stopped");
34
+ const out = { exitCode: result.exitCode, stdout: result.stdout.text, stderr: result.stderr.text };
35
+ if (options.check !== false && result.exitCode !== 0) {
36
+ const why = (out.stderr || out.stdout).trim().split("\n").slice(-3).join(" ");
37
+ const name = argv[0] === "git" ? `git ${argv.find((a, i) => i > 2 && !a.startsWith("-")) ?? ""}`.trim() : argv[0] ?? "command";
38
+ throw new JobGitError(result.timedOut ? `${name} took too long.` : `${name} failed: ${why}`);
39
+ }
40
+ return out;
41
+ }
42
+
43
+ export {
44
+ JobGitError,
45
+ shellWord,
46
+ git,
47
+ hostCommand
48
+ };
49
+ //# sourceMappingURL=chunk-2TAXSFWN.js.map