@garuda-agent/garuda 0.16.1 → 0.18.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 (63) hide show
  1. package/CHANGELOG.md +319 -0
  2. package/README.md +56 -1074
  3. package/SECURITY.md +9 -2
  4. package/dist/{acpCommand-OTAYW7BS.js → acpCommand-IUZPLPLI.js} +25 -23
  5. package/dist/{anthropic-NQDBCHJU.js → anthropic-NQIZLD64.js} +12 -6
  6. package/dist/{chunk-NP6RIC25.js → chunk-2FIV2VZP.js} +11 -7
  7. package/dist/{chunk-YQTGMCU6.js → chunk-3MRQJKUO.js} +29 -5
  8. package/dist/{chunk-HE2K657U.js → chunk-4BVCYCQT.js} +47 -16
  9. package/dist/{chunk-WMOLXTBU.js → chunk-4LFJFDCF.js} +2 -1
  10. package/dist/{chunk-44QN5SWZ.js → chunk-55CFEEIN.js} +22 -2
  11. package/dist/{chunk-USCUXCAU.js → chunk-5B5HDTUR.js} +3 -3
  12. package/dist/chunk-65NUEOI7.js +143 -0
  13. package/dist/{chunk-TUUEBHG2.js → chunk-6XHON3Q6.js} +2 -2
  14. package/dist/chunk-7KBOIPUT.js +157 -0
  15. package/dist/{chunk-B6FA4I7Q.js → chunk-7TH3NHCQ.js} +2 -2
  16. package/dist/{chunk-OUCV3FCT.js → chunk-AOGFYUKG.js} +3 -3
  17. package/dist/{chunk-GXZACJUI.js → chunk-B4SJY7KT.js} +322 -39
  18. package/dist/{chunk-3EQU2GUX.js → chunk-BHZ6G65V.js} +5 -1
  19. package/dist/chunk-C57GHLKP.js +9 -0
  20. package/dist/chunk-DOCDCQVM.js +484 -0
  21. package/dist/chunk-FOPBOB7W.js +332 -0
  22. package/dist/{chunk-TILFPYWO.js → chunk-NHWHG3SW.js} +1930 -992
  23. package/dist/{chunk-BQTUOO4G.js → chunk-OQ26W5L4.js} +81 -32
  24. package/dist/{chunk-HUIK3WXO.js → chunk-P6XWWIEK.js} +20 -3
  25. package/dist/chunk-Q4P3W657.js +6372 -0
  26. package/dist/{chunk-ZAE25JM2.js → chunk-QANRZW7W.js} +8 -1
  27. package/dist/chunk-UIRJOQQS.js +118 -0
  28. package/dist/chunk-UKIE4AAM.js +472 -0
  29. package/dist/{chunk-KKB5W47S.js → chunk-VTMD5IHD.js} +26 -2
  30. package/dist/cli/index.js +148 -57
  31. package/dist/config-WV2H64TF.js +23 -0
  32. package/dist/{create-VXIH2GUZ.js → create-W6BV75QX.js} +18 -12
  33. package/dist/{deadline-F5RJJ2QW.js → deadline-TP7ZGLT2.js} +4 -2
  34. package/dist/evalCommand-SKJQ6CW2.js +686 -0
  35. package/dist/evalTune-CZRCPSZ3.js +295 -0
  36. package/dist/{export-HN3CT7AV.js → export-SJ3DUP6W.js} +2 -2
  37. package/dist/{inkChat-WEDGQMCY.js → inkChat-WYYAFZXD.js} +20 -19
  38. package/dist/install-TKWN5AYH.js +19 -0
  39. package/dist/{job-3NYDMP2M.js → job-7JOI4YIQ.js} +6 -2
  40. package/dist/jobCommand-2ODQLHB3.js +33 -0
  41. package/dist/{launchd-SSX362VG.js → launchd-3A3ONEW5.js} +9 -8
  42. package/dist/{lspCommand-JRXKRSL7.js → lspCommand-ZZ6R4PA2.js} +7 -6
  43. package/dist/{manager-773PUDFE.js → manager-TCYTA5XU.js} +15 -14
  44. package/dist/{nightCommand-65MUEV32.js → nightCommand-WJU6VZA6.js} +26 -20
  45. package/dist/{openaiCompatible-CJWVJP5M.js → openaiCompatible-6TLDDW5U.js} +29 -15
  46. package/dist/{policy-EJPLEEXP.js → policy-ZJ7RMOFN.js} +10 -4
  47. package/dist/{remove-6GDSTAIO.js → remove-42WQNVPX.js} +8 -7
  48. package/dist/{run-ML6GSPN4.js → run-PTMRQCIU.js} +5 -5
  49. package/dist/{server-NGKVIQEV.js → server-VEYIJHEU.js} +2 -2
  50. package/dist/{setupCommand-VA7D5MZK.js → setupCommand-YMF6I3MD.js} +45 -13
  51. package/dist/text-LS35WSRL.js +17 -0
  52. package/dist/tune-YVI2KWQF.js +31 -0
  53. package/native/bin/garuda-reap +0 -0
  54. package/native/garuda-reap.c +113 -0
  55. package/package.json +39 -13
  56. package/dist/chunk-EIXUSVFX.js +0 -116
  57. package/dist/chunk-R7I2NBPX.js +0 -71
  58. package/dist/chunk-ULAJPMY7.js +0 -107
  59. package/dist/chunk-VBZCNMUD.js +0 -9
  60. package/dist/evalCommand-RGMZRYDR.js +0 -2812
  61. package/dist/install-EWDSPFFZ.js +0 -18
  62. package/dist/jobCommand-ACSGSBQ4.js +0 -32
  63. package/dist/text-J2GD647J.js +0 -11
@@ -215,7 +215,11 @@ var OpenAICompatibleClient = class {
215
215
  fetch;
216
216
  delays;
217
217
  async *stream(request, options) {
218
- const http = await this.post(toWireBody(this.options.model, request), options?.signal);
218
+ const http = await this.post(
219
+ toWireBody(this.options.model, request),
220
+ options?.signal,
221
+ options?.meter
222
+ );
219
223
  const mode = request.tools.length === 0 ? "off" : this.options.textToolCalls ?? "whole";
220
224
  const state = newStreamState();
221
225
  const hold = new StreamHold(mode);
@@ -241,7 +245,7 @@ var OpenAICompatibleClient = class {
241
245
  }
242
246
  yield { type: "response", response };
243
247
  }
244
- async post(body, signal) {
248
+ async post(body, signal, meter) {
245
249
  const url = `${this.options.baseUrl.replace(/\/+$/, "")}/chat/completions`;
246
250
  const headers = {
247
251
  "content-type": "application/json",
@@ -262,6 +266,7 @@ var OpenAICompatibleClient = class {
262
266
  const cause = causeCode(error);
263
267
  if (attempt < this.delays.length && cause !== "ECONNREFUSED") {
264
268
  await sleep(this.delays[attempt] ?? 1e3, signal);
269
+ meter?.retry();
265
270
  continue;
266
271
  }
267
272
  throw new Error(
@@ -274,6 +279,7 @@ var OpenAICompatibleClient = class {
274
279
  const retryAfter = Number(response.headers.get("retry-after")) * 1e3;
275
280
  const wait = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, MAX_RETRY_AFTER_MS) : this.delays[attempt] ?? 1e3;
276
281
  await sleep(wait, signal);
282
+ meter?.retry();
277
283
  continue;
278
284
  }
279
285
  throw new Error(
@@ -378,10 +384,12 @@ function finishResponse(state, request, mode = "whole") {
378
384
  input: parseArguments(call.args)
379
385
  });
380
386
  }
387
+ const reported = providerUsage(state.usage);
381
388
  return {
382
389
  content,
383
390
  stopReason: stopReason(state, content),
384
- usage: usageOf(state, request, content)
391
+ usage: reported ?? estimatedUsage(request, content),
392
+ ...reported === void 0 ? { usageReported: false } : {}
385
393
  };
386
394
  }
387
395
  function toolNames(request) {
@@ -403,17 +411,23 @@ function stopReason(state, content) {
403
411
  return "end_turn";
404
412
  return "other";
405
413
  }
406
- function usageOf(state, request, content) {
407
- const u = state.usage;
408
- if (u?.prompt_tokens !== void 0) {
409
- const cached = u.prompt_tokens_details?.cached_tokens ?? 0;
410
- return {
411
- inputTokens: Math.max(0, u.prompt_tokens - cached),
412
- outputTokens: u.completion_tokens ?? 0,
413
- cacheReadTokens: cached,
414
- cacheWriteTokens: 0
415
- };
416
- }
414
+ function providerUsage(u) {
415
+ const input = u?.prompt_tokens;
416
+ const output = u?.completion_tokens;
417
+ const cached = u?.prompt_tokens_details?.cached_tokens ?? 0;
418
+ if (!tokenCount(input) || !tokenCount(output) || !tokenCount(cached) || cached > input)
419
+ return void 0;
420
+ return {
421
+ inputTokens: input - cached,
422
+ outputTokens: output,
423
+ cacheReadTokens: cached,
424
+ cacheWriteTokens: 0
425
+ };
426
+ }
427
+ function tokenCount(value) {
428
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
429
+ }
430
+ function estimatedUsage(request, content) {
417
431
  const chars = (value) => JSON.stringify(value).length;
418
432
  return {
419
433
  inputTokens: Math.ceil(
@@ -498,4 +512,4 @@ export {
498
512
  toWireMessages,
499
513
  toWireTools
500
514
  };
501
- //# sourceMappingURL=openaiCompatible-CJWVJP5M.js.map
515
+ //# sourceMappingURL=openaiCompatible-6TLDDW5U.js.map
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  POLICY_FILE,
4
+ PROFILES,
4
5
  assertModelAllowedByPolicy,
5
6
  ignoredProjectPolicy,
6
7
  isCommandDisallowedByPolicy,
@@ -8,14 +9,17 @@ import {
8
9
  isModelAllowedByPolicy,
9
10
  isPathDeniedByPolicy,
10
11
  isSandboxRequiredByPolicy,
12
+ isStrict,
11
13
  loadTeamPolicy,
12
14
  managedPolicyPath,
13
15
  mergePolicies,
14
- parsePolicy
15
- } from "./chunk-HUIK3WXO.js";
16
+ parsePolicy,
17
+ withProfile
18
+ } from "./chunk-P6XWWIEK.js";
16
19
  import "./chunk-3NJNVZTK.js";
17
20
  export {
18
21
  POLICY_FILE,
22
+ PROFILES,
19
23
  assertModelAllowedByPolicy,
20
24
  ignoredProjectPolicy,
21
25
  isCommandDisallowedByPolicy,
@@ -23,9 +27,11 @@ export {
23
27
  isModelAllowedByPolicy,
24
28
  isPathDeniedByPolicy,
25
29
  isSandboxRequiredByPolicy,
30
+ isStrict,
26
31
  loadTeamPolicy,
27
32
  managedPolicyPath,
28
33
  mergePolicies,
29
- parsePolicy
34
+ parsePolicy,
35
+ withProfile
30
36
  };
31
- //# sourceMappingURL=policy-EJPLEEXP.js.map
37
+ //# sourceMappingURL=policy-ZJ7RMOFN.js.map
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  git
4
- } from "./chunk-TUUEBHG2.js";
5
- import "./chunk-GXZACJUI.js";
6
- import "./chunk-WMOLXTBU.js";
7
- import "./chunk-3EQU2GUX.js";
8
- import "./chunk-HUIK3WXO.js";
4
+ } from "./chunk-6XHON3Q6.js";
5
+ import "./chunk-B4SJY7KT.js";
6
+ import "./chunk-BHZ6G65V.js";
7
+ import "./chunk-P6XWWIEK.js";
9
8
  import {
10
9
  JOBS_DIR,
11
10
  jobPath
12
- } from "./chunk-KKB5W47S.js";
11
+ } from "./chunk-VTMD5IHD.js";
12
+ import "./chunk-UIRJOQQS.js";
13
+ import "./chunk-4LFJFDCF.js";
13
14
  import "./chunk-3NJNVZTK.js";
14
15
 
15
16
  // src/jobs/remove.ts
@@ -57,4 +58,4 @@ export {
57
58
  jobLeftovers,
58
59
  removeJob
59
60
  };
60
- //# sourceMappingURL=remove-6GDSTAIO.js.map
61
+ //# sourceMappingURL=remove-42WQNVPX.js.map
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ folderKind
4
+ } from "./chunk-42DSA4EA.js";
2
5
  import {
3
6
  loadSearchConfig,
4
7
  providerLabel,
@@ -10,11 +13,8 @@ import {
10
13
  import {
11
14
  SERVER_NAME,
12
15
  checkServerUrl
13
- } from "./chunk-ZAE25JM2.js";
16
+ } from "./chunk-QANRZW7W.js";
14
17
  import "./chunk-ATW7BNIG.js";
15
- import {
16
- folderKind
17
- } from "./chunk-42DSA4EA.js";
18
18
 
19
19
  // src/init/run.ts
20
20
  import { existsSync as existsSync3 } from "fs";
@@ -885,4 +885,4 @@ export {
885
885
  initPrompt,
886
886
  runInit
887
887
  };
888
- //# sourceMappingURL=run-ML6GSPN4.js.map
888
+ //# sourceMappingURL=run-PTMRQCIU.js.map
@@ -467,7 +467,7 @@ function setupNeeded(message) {
467
467
  return Object.assign(new Error(message), { authRequired: true });
468
468
  }
469
469
  var MODES = {
470
- build: { name: "Build", description: "Edits and commands, each one asks first." },
470
+ build: { name: "Build", description: "Edits ask first; commands run in the sandbox." },
471
471
  plan: {
472
472
  name: "Plan",
473
473
  description: "Reads and plans. No edits; commands cannot write the project."
@@ -695,4 +695,4 @@ export {
695
695
  canRunTerminalAuth,
696
696
  setupNeeded
697
697
  };
698
- //# sourceMappingURL=server-NGKVIQEV.js.map
698
+ //# sourceMappingURL=server-VEYIJHEU.js.map
@@ -11,13 +11,16 @@ import {
11
11
  resolveModel,
12
12
  setDefaultModel,
13
13
  writeCredentials
14
- } from "./chunk-NP6RIC25.js";
15
- import "./chunk-EIXUSVFX.js";
14
+ } from "./chunk-2FIV2VZP.js";
16
15
  import {
17
- knownModels
18
- } from "./chunk-R7I2NBPX.js";
16
+ fetchCatalog,
17
+ loadCatalog,
18
+ mergedModels,
19
+ saveCatalog
20
+ } from "./chunk-65NUEOI7.js";
21
+ import "./chunk-7KBOIPUT.js";
19
22
  import "./chunk-ATW7BNIG.js";
20
- import "./chunk-3EQU2GUX.js";
23
+ import "./chunk-BHZ6G65V.js";
21
24
 
22
25
  // src/cli/setupCommand.ts
23
26
  import { homedir } from "os";
@@ -85,7 +88,9 @@ async function setup(io, config, home, env, fetcher) {
85
88
  }))
86
89
  );
87
90
  const def = providers[provider];
88
- const model = await pickModel(io, provider, def, fetcher);
91
+ const anthropicKey = def.type === "anthropic" ? def.apiKeyEnv ?? "ANTHROPIC_API_KEY" : void 0;
92
+ const knownKey = anthropicKey === void 0 ? void 0 : (env[anthropicKey] ?? "") !== "" ? env[anthropicKey] : stored.keys[anthropicKey];
93
+ const model = await pickModel(io, provider, def, fetcher, { home, env, key: knownKey });
89
94
  const spec = provider === "anthropic" ? model : `${provider}/${model}`;
90
95
  try {
91
96
  resolveModel(spec, config);
@@ -111,7 +116,12 @@ async function setup(io, config, home, env, fetcher) {
111
116
  const key = fromEnv !== "" ? fromEnv : newKey ?? old;
112
117
  const result = await checkKey(provider, def, key, env, fetcher);
113
118
  io.print(result.message);
114
- if (result.ok) break;
119
+ if (result.ok) {
120
+ if (def.type === "anthropic" && knownKey === void 0) {
121
+ await refreshCatalog(io, { home, env, key }, fetcher);
122
+ }
123
+ break;
124
+ }
115
125
  const next = await io.select("What now?", [
116
126
  ...fromEnv === "" ? [{ name: "Enter the key again", value: "again" }] : [],
117
127
  { name: "Store it anyway", value: "store" },
@@ -146,15 +156,17 @@ async function setup(io, config, home, env, fetcher) {
146
156
  io.print("Done. Start Garuda with: garuda");
147
157
  return 0;
148
158
  }
149
- async function pickModel(io, provider, def, fetcher) {
159
+ async function pickModel(io, provider, def, fetcher, account) {
150
160
  let listed = [];
151
161
  const notes = /* @__PURE__ */ new Map();
152
162
  if (def.type === "anthropic") {
153
- for (const { id, info } of knownModels()) {
163
+ let catalog = await loadCatalog(account.home);
164
+ if (account.key !== void 0) {
165
+ catalog = await refreshCatalog(io, account, fetcher) ?? catalog;
166
+ }
167
+ for (const { id, info, known } of mergedModels(catalog)) {
154
168
  listed.push(id);
155
- if (info.price !== void 0) {
156
- notes.set(id, `$${info.price.input} in, $${info.price.output} out per million tokens`);
157
- }
169
+ notes.set(id, known && info.price !== void 0 ? priceNote(info.price) : UNKNOWN_PRICE);
158
170
  }
159
171
  } else if (def.local === true && def.baseUrl !== void 0) {
160
172
  listed = await localModels(def.baseUrl, fetcher);
@@ -177,6 +189,26 @@ async function pickModel(io, provider, def, fetcher) {
177
189
  io.print("Type a model name with no spaces.");
178
190
  }
179
191
  }
192
+ var UNKNOWN_PRICE = "price unknown to this Garuda version: costs show as unknown (set model.price, or update Garuda)";
193
+ function priceNote(price) {
194
+ const more = price.over === void 0 ? "" : ` (more for prompts over ${price.over.tokens / 1e3}k tokens)`;
195
+ return `$${price.input} in, $${price.output} out per million tokens${more}`;
196
+ }
197
+ async function refreshCatalog(io, account, fetcher) {
198
+ const base = account.env.ANTHROPIC_BASE_URL ?? "";
199
+ const result = await fetchCatalog({
200
+ key: account.key,
201
+ ...base === "" ? {} : { baseUrl: base },
202
+ fetcher
203
+ });
204
+ if ("problem" in result) {
205
+ io.print(`The list shows Garuda's own table: ${result.problem}.`);
206
+ return void 0;
207
+ }
208
+ await saveCatalog(result.catalog, account.home);
209
+ io.print(`Models of your account: ${result.catalog.models.length}.`);
210
+ return result.catalog;
211
+ }
180
212
  async function localModels(baseUrl, fetcher) {
181
213
  try {
182
214
  const response = await fetcher(`${baseUrl.replace(/\/+$/, "")}/models`, {
@@ -300,4 +332,4 @@ export {
300
332
  setupCommand,
301
333
  terminalIO
302
334
  };
303
- //# sourceMappingURL=setupCommand-VA7D5MZK.js.map
335
+ //# sourceMappingURL=setupCommand-YMF6I3MD.js.map
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ formatCost,
4
+ jobCost,
5
+ jobPrompt,
6
+ jobReport,
7
+ sumCosts
8
+ } from "./chunk-3MRQJKUO.js";
9
+ import "./chunk-AOGFYUKG.js";
10
+ export {
11
+ formatCost,
12
+ jobCost,
13
+ jobPrompt,
14
+ jobReport,
15
+ sumCosts
16
+ };
17
+ //# sourceMappingURL=text-LS35WSRL.js.map
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ADDENDUM_MAX_CHARS,
4
+ PROPOSER_SYSTEM,
5
+ decide,
6
+ parseProposal,
7
+ proposerInput,
8
+ readAddendum,
9
+ rejectReason,
10
+ tune
11
+ } from "./chunk-FOPBOB7W.js";
12
+ import "./chunk-DOCDCQVM.js";
13
+ import "./chunk-C57GHLKP.js";
14
+ import "./chunk-6XHON3Q6.js";
15
+ import "./chunk-B4SJY7KT.js";
16
+ import "./chunk-BHZ6G65V.js";
17
+ import "./chunk-P6XWWIEK.js";
18
+ import "./chunk-UIRJOQQS.js";
19
+ import "./chunk-4LFJFDCF.js";
20
+ import "./chunk-3NJNVZTK.js";
21
+ export {
22
+ ADDENDUM_MAX_CHARS,
23
+ PROPOSER_SYSTEM,
24
+ decide,
25
+ parseProposal,
26
+ proposerInput,
27
+ readAddendum,
28
+ rejectReason,
29
+ tune
30
+ };
31
+ //# sourceMappingURL=tune-YVI2KWQF.js.map
Binary file
@@ -0,0 +1,113 @@
1
+ // garuda-reap (0.17, IPI-04): stop the processes that one sandboxed command left running on macOS.
2
+ //
3
+ // Seatbelt has no process namespace, so a process can leave the command's process group (setsid,
4
+ // setpgid, posix_spawn with POSIX_SPAWN_SETSID or POSIX_SPAWN_SETPGROUP) and outlive the command.
5
+ // It cannot leave its Seatbelt profile. Garuda puts two marker files in each command's profile:
6
+ // reads of <markerA> are denied, reads of <markerB> are allowed. After the command, this helper
7
+ // asks the kernel (sandbox_check) about every process of the current user and sends SIGKILL to
8
+ // each one that has A denied and B allowed. Processes without a sandbox allow both; App Store
9
+ // sandboxes deny both (the markers are outside their containers). So only the command's own
10
+ // processes match. It repeats the scan until a scan finds none (at most 10 scans), so children
11
+ // started during a scan are found in the next one.
12
+ //
13
+ // garuda-reap <markerA> <markerB> stop the matches; prints "killed <n>"
14
+ // garuda-reap --list <markerA> <markerB> only list the matches (tests)
15
+ //
16
+ // Exit codes: 0 done, 2 usage, 3 a marker file is missing, 4 the process list failed.
17
+ #include <errno.h>
18
+ #include <libproc.h>
19
+ #include <signal.h>
20
+ #include <stdio.h>
21
+ #include <stdlib.h>
22
+ #include <string.h>
23
+ #include <sys/proc_info.h>
24
+ #include <sys/stat.h>
25
+ #include <unistd.h>
26
+
27
+ // From libsystem_sandbox (not in the public SDK headers).
28
+ enum sandbox_filter_type { SANDBOX_FILTER_NONE, SANDBOX_FILTER_PATH };
29
+ extern const enum sandbox_filter_type SANDBOX_CHECK_NO_REPORT;
30
+ int sandbox_check(pid_t pid, const char *operation, enum sandbox_filter_type type, ...);
31
+
32
+ #define MAX_SCANS 10
33
+ #define MAX_KILLED 4096
34
+
35
+ // Processes already sent SIGKILL: a killed process can show in the next scan until launchd reaps
36
+ // it, so it is counted once and does not keep the loop going.
37
+ static pid_t killed_pids[MAX_KILLED];
38
+ static int killed_count = 0;
39
+
40
+ static int already_killed(pid_t pid) {
41
+ for (int i = 0; i < killed_count; i++)
42
+ if (killed_pids[i] == pid) return 1;
43
+ return 0;
44
+ }
45
+
46
+ static int denied(pid_t pid, const char *path) {
47
+ return sandbox_check(pid, "file-read-data",
48
+ (enum sandbox_filter_type)(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), path);
49
+ }
50
+
51
+ // One scan. Returns the number of new matches, or -1 when the process list failed.
52
+ static int scan(const char *a, const char *b, int list_only) {
53
+ int n = proc_listallpids(NULL, 0);
54
+ if (n <= 0) return -1;
55
+ size_t cap = (size_t)n + 256;
56
+ pid_t *pids = calloc(cap, sizeof(pid_t));
57
+ if (pids == NULL) return -1;
58
+ n = proc_listallpids(pids, (int)(cap * sizeof(pid_t)));
59
+ if (n <= 0) {
60
+ free(pids);
61
+ return -1;
62
+ }
63
+ uid_t me = getuid();
64
+ pid_t self = getpid(), parent = getppid();
65
+ int matches = 0;
66
+ for (int i = 0; i < n; i++) {
67
+ pid_t pid = pids[i];
68
+ if (pid <= 1 || pid == self || pid == parent) continue;
69
+ struct proc_bsdinfo info;
70
+ if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, sizeof info) != (int)sizeof info) continue;
71
+ if (info.pbi_uid != me) continue;
72
+ if (denied(pid, a) == 1 && denied(pid, b) == 0) {
73
+ if (list_only) {
74
+ matches++;
75
+ printf("match %d %d %s\n", pid, info.pbi_ppid, info.pbi_comm);
76
+ continue;
77
+ }
78
+ kill(pid, SIGKILL);
79
+ if (already_killed(pid)) continue;
80
+ matches++;
81
+ if (killed_count < MAX_KILLED) killed_pids[killed_count++] = pid;
82
+ }
83
+ }
84
+ free(pids);
85
+ return matches;
86
+ }
87
+
88
+ int main(int argc, char **argv) {
89
+ int list_only = argc == 4 && strcmp(argv[1], "--list") == 0;
90
+ if (argc != 3 && !list_only) {
91
+ fprintf(stderr, "usage: garuda-reap [--list] <markerA> <markerB>\n");
92
+ return 2;
93
+ }
94
+ const char *a = argv[argc - 2], *b = argv[argc - 1];
95
+ struct stat st;
96
+ // A missing marker reads as denied for every process, so nothing would match: refuse instead.
97
+ if (stat(a, &st) != 0 || stat(b, &st) != 0) {
98
+ fprintf(stderr, "garuda-reap: a marker file is missing\n");
99
+ return 3;
100
+ }
101
+ if (list_only) return scan(a, b, 1) < 0 ? 4 : 0;
102
+ for (int i = 0; i < MAX_SCANS; i++) {
103
+ int found = scan(a, b, 0);
104
+ if (found < 0) {
105
+ fprintf(stderr, "garuda-reap: the process list failed (%s)\n", strerror(errno));
106
+ return 4;
107
+ }
108
+ if (found == 0) break;
109
+ usleep(10000);
110
+ }
111
+ printf("killed %d\n", killed_count);
112
+ return 0;
113
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuda-agent/garuda",
3
- "version": "0.16.1",
3
+ "version": "0.18.0",
4
4
  "description": "Garuda: a terminal coding agent with an OS sandbox, approvals and an audit log.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Madhusudhan Vasanth Kumar",
@@ -17,7 +17,17 @@
17
17
  "sandbox",
18
18
  "anthropic",
19
19
  "claude",
20
- "mcp"
20
+ "mcp",
21
+ "ai",
22
+ "ai-agent",
23
+ "agent",
24
+ "llm",
25
+ "ollama",
26
+ "openrouter",
27
+ "acp",
28
+ "agent-client-protocol",
29
+ "zed",
30
+ "code-assistant"
21
31
  ],
22
32
  "type": "module",
23
33
  "bin": {
@@ -29,33 +39,40 @@
29
39
  "NOTICE",
30
40
  "README.md",
31
41
  "SECURITY.md",
32
- "CHANGELOG.md"
42
+ "CHANGELOG.md",
43
+ "native/garuda-reap.c",
44
+ "native/bin/garuda-reap"
33
45
  ],
34
46
  "engines": {
35
- "node": ">=22"
47
+ "node": "^22.23.4 || ^24.22.1 || >=26.12.1"
36
48
  },
37
49
  "publishConfig": {
38
50
  "access": "public"
39
51
  },
40
52
  "scripts": {
41
- "build": "tsup",
53
+ "build": "tsup && node scripts/build-native.mjs",
42
54
  "typecheck": "tsc --noEmit",
43
55
  "lint": "biome check .",
44
56
  "format": "biome format --write .",
45
- "test": "vitest run",
57
+ "test": "node scripts/build-native.mjs && vitest run",
46
58
  "check": "pnpm typecheck && pnpm lint && pnpm test",
47
59
  "package": "tsup && node scripts/package.mjs",
48
60
  "startup": "node scripts/startup.mjs",
61
+ "models:check": "node scripts/check-models.mjs",
62
+ "ipi:mutations": "node scripts/check-ipi-mutations.mjs",
49
63
  "eval": "node dist/cli/index.js eval",
50
64
  "baseline:capture": "node scripts/capture-quality-baseline.mjs",
51
- "prepack": "pnpm build"
65
+ "code-intelligence:validate": "vitest run test/lspReferenceSample.test.ts test/codeIntelligenceSample.test.ts --reporter=verbose",
66
+ "code-intelligence:capture": "node scripts/capture-reference-oracle.mjs",
67
+ "prepack": "pnpm build",
68
+ "build:native": "node scripts/build-native.mjs"
52
69
  },
53
70
  "packageManager": "pnpm@10.28.0",
54
71
  "dependencies": {
55
72
  "@agentclientprotocol/sdk": "1.7.0",
56
73
  "@anthropic-ai/sdk": "^0.128.0",
57
74
  "@inquirer/prompts": "^8.7.2",
58
- "@modelcontextprotocol/client": "^2.1.0",
75
+ "@modelcontextprotocol/client": "^2.3.1",
59
76
  "commander": "^15.0.0",
60
77
  "diff": "^9.0.0",
61
78
  "globby": "^16.2.4",
@@ -65,23 +82,32 @@
65
82
  "react": "^19.3.0",
66
83
  "smol-toml": "^1.9.0",
67
84
  "ts6": "npm:typescript@^6.0.3",
68
- "undici": "^7.30.0",
85
+ "undici": "^7.30.1",
69
86
  "vscode-jsonrpc": "^9.0.3",
70
- "zod": "^4.6.5"
87
+ "zod": "^4.6.7"
71
88
  },
72
89
  "devDependencies": {
73
90
  "@biomejs/biome": "^2.5.14",
74
- "@modelcontextprotocol/server": "^2.1.0",
91
+ "@modelcontextprotocol/server": "^2.3.1",
75
92
  "@types/node": "^22.20.4",
76
93
  "@types/react": "^19.3.0",
77
94
  "ink-testing-library": "^4.0.0",
78
95
  "tsup": "^8.5.1",
79
96
  "typescript": "^7.0.2",
80
- "vitest": "^5.0.1"
97
+ "vite": "^8.3.5",
98
+ "vitest": "^5.0.5"
81
99
  },
82
100
  "pnpm": {
83
101
  "onlyBuiltDependencies": [
84
102
  "esbuild"
85
- ]
103
+ ],
104
+ "overrides": {
105
+ "esbuild": "^0.28.2"
106
+ },
107
+ "auditConfig": {
108
+ "ignoreGhsas": [
109
+ "GHSA-vfj7-8cjw-p6xm"
110
+ ]
111
+ }
86
112
  }
87
113
  }