@garuda-agent/garuda 0.15.0 → 0.16.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 (33) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +30 -17
  3. package/dist/{acpCommand-FZ2P5JFO.js → acpCommand-AJJBKAE5.js} +64 -26
  4. package/dist/chunk-3EQU2GUX.js +27 -0
  5. package/dist/{chunk-K3SIUOAG.js → chunk-44QN5SWZ.js} +2 -2
  6. package/dist/{chunk-INRZR772.js → chunk-B6FA4I7Q.js} +2 -2
  7. package/dist/chunk-EIXUSVFX.js +116 -0
  8. package/dist/{chunk-PQNT34LY.js → chunk-ENLDPN4S.js} +4 -4
  9. package/dist/{chunk-SI2QJYCR.js → chunk-GXZACJUI.js} +6 -25
  10. package/dist/{chunk-2S62ZNTC.js → chunk-HE2K657U.js} +9 -7
  11. package/dist/chunk-NP6RIC25.js +331 -0
  12. package/dist/chunk-R7I2NBPX.js +71 -0
  13. package/dist/{chunk-2TAXSFWN.js → chunk-TUUEBHG2.js} +2 -2
  14. package/dist/{chunk-HRGX53VC.js → chunk-ULAJPMY7.js} +1 -67
  15. package/dist/{chunk-S77XDSD4.js → chunk-USCUXCAU.js} +2 -2
  16. package/dist/{chunk-K7KK7U44.js → chunk-WIJL72ZE.js} +125 -226
  17. package/dist/chunk-XOK5OZOC.js +9 -0
  18. package/dist/cli/index.js +50 -33
  19. package/dist/{create-N4I7TIZE.js → create-VXIH2GUZ.js} +6 -5
  20. package/dist/{evalCommand-3SSFMZQD.js → evalCommand-OX2LQYEK.js} +31 -17
  21. package/dist/{inkChat-34BTFG2K.js → inkChat-YMMMGU7Y.js} +10 -7
  22. package/dist/{install-VRPBZY5H.js → install-EWDSPFFZ.js} +4 -3
  23. package/dist/{jobCommand-XJ5GULS5.js → jobCommand-ACSGSBQ4.js} +9 -7
  24. package/dist/{launchd-QVHQ3VNN.js → launchd-SSX362VG.js} +5 -4
  25. package/dist/{lspCommand-XNTBHXLA.js → lspCommand-JRXKRSL7.js} +4 -3
  26. package/dist/{manager-CSI76AWF.js → manager-AWU4NHHK.js} +4 -3
  27. package/dist/{nightCommand-CR6ZHYGP.js → nightCommand-65MUEV32.js} +9 -7
  28. package/dist/{remove-LE7Q7K46.js → remove-6GDSTAIO.js} +4 -3
  29. package/dist/{server-R3GNIC33.js → server-KHHNHUYV.js} +24 -6
  30. package/dist/setupCommand-VA7D5MZK.js +303 -0
  31. package/package.json +1 -1
  32. package/dist/chunk-EPKWDCV4.js +0 -9
  33. package/dist/chunk-YFQJXRIJ.js +0 -193
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0 (2026-10-06)
4
+
5
+ Setup for editors and the ACP Registry (design: [docs/lld/setup.md](docs/lld/setup.md)).
6
+
7
+ - Stored provider keys: Garuda reads `~/.garuda/credentials` (JSON, key name → key) when no
8
+ environment variable sets the key. It reads the file only when it is a regular file (not a link),
9
+ owned by you, with mode 0600; else it ignores the file with a warning. The key never enters
10
+ Garuda's environment, and the redactor knows it. `garuda setup` writes this file.
11
+ - Commands in the OS sandbox cannot read `~/.garuda/credentials` (macOS and Linux).
12
+ - `"default"` in `~/.garuda/models.json`: the model when neither `--model` nor `GARUDA_MODEL` names
13
+ one.
14
+ - The redactor removes every provider key that Garuda keeps, also one whose variable name does not
15
+ look secret (a custom provider's `apiKeyEnv`).
16
+ - `garuda setup`: choose a provider and a model, enter the key once (hidden input), check it with
17
+ one free request to the provider, then store the model as the default and the key in
18
+ `~/.garuda/credentials`. `--show` shows where each key comes from (masked); `--forget [NAME]`
19
+ removes stored keys. With no terminal, it stops with a message.
20
+ - The messages for a missing model or key name `garuda setup`.
21
+ - `garuda acp`: Terminal Auth. An editor that can run it (`auth.terminal`) gets a "Set up Garuda"
22
+ method; it runs `garuda acp setup` in a terminal. A session with no model or no key fails with
23
+ ACP's "auth required", so the editor offers the setup. The setup is read again for each new
24
+ session: no restart after `garuda setup`.
25
+ - Native Windows: `garuda acp` sessions and `garuda setup` stop with a message to use WSL.
26
+ - Docs: `garuda setup` in the quick start, the landing page, the README and the Editors page; editor
27
+ entries need no API key in their settings. Tested live in Zed 1.22: with no model and no key, Zed
28
+ offered the setup, ran it in a terminal, and the next thread worked with no restart.
29
+ - `-p --output-format stream-json`: `apiKeySource` in `system/init` is `credentials` for a stored
30
+ Claude key.
31
+
3
32
  ## 0.15.0 (2026-10-06)
4
33
 
5
34
  Garuda in your editor. Version 0.15.0 is not the old "0.15" branch label of the status table (code
package/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
 
5
5
  Website and docs: https://madhu-sv.github.io/garuda/
6
6
 
7
- Garuda is a terminal coding agent. This is version 0.15.0: Garuda in your editor over the Agent Client
8
- Protocol (`garuda acp`), on top of 0.14.1 (the 0.14–0.17 branches after the merge gate and Garuda's own
7
+ Garuda is a terminal coding agent. This is version 0.16.0: `garuda setup` (choose a model and store
8
+ its key once) and the sign-in from editors, on top of 0.15.0 (Garuda in your editor over the Agent
9
+ Client Protocol, `garuda acp`) and 0.14.1 (the 0.14–0.17 branches after the merge gate and Garuda's own
9
10
  review; see the notes under the status table). Release notes: [CHANGELOG.md](CHANGELOG.md).
10
11
  Design documents: [docs/](docs/README.md) (architecture, high-level design, low-level design per component).
11
12
  The requirements doc defines the scope. Code, tests and commits refer to its IDs (F1–F26, N1–N8).
@@ -36,9 +37,10 @@ The requirements doc defines the scope. Code, tests and commits refer to its IDs
36
37
  | 0.16: language plugins (Go and Rust built in, user plugins in ~/.garuda/languages, /languages; project plugins not loaded) | Done (0.14.0) |
37
38
  | 0.17: Mixture-of-Experts subagents (opt-in), team policy and audit log (W5) | Done (0.14.0) |
38
39
  | Editors over the Agent Client Protocol (`garuda acp`: VS Code with an extension, Zed, JetBrains, Neovim, Emacs) | Done (0.15.0) |
40
+ | `garuda setup` (model and stored key) and the editor sign-in (ACP Terminal Auth) | Done (0.16.0) |
39
41
 
40
- The rows labelled 0.14–0.17 are branch labels, not versions: the "0.15" branch (code intelligence)
41
- shipped in 0.14.0. The editors row is version 0.15.0.
42
+ The rows labelled 0.14–0.17 are branch labels, not versions: the "0.15" and "0.16" branches (code
43
+ intelligence, language plugins) shipped in 0.14.0. The last two rows are versions 0.15.0 and 0.16.0.
42
44
 
43
45
  The 0.14–0.17 rows were built on stacked branches that were never released. They ship together as
44
46
  0.14.0 after the merge gate: the high findings of the branch review are fixed (policy source, audit
@@ -50,7 +52,7 @@ headings below keep the branch labels (0.14–0.17).
50
52
  Before 0.14.0, Garuda reviewed its own code with Fable 5.1, one area at a time (knowledge,
51
53
  permissions, tools, sandbox, audit, loop, agents), in plan mode. 0.14.1 adds the cli and extensions
52
54
  areas and fixes the open findings of all nine areas (see [CHANGELOG.md](CHANGELOG.md)). Known limits
53
- in 0.15.0:
55
+ in 0.16.0:
54
56
 
55
57
  - G09: project language plugins are not loaded (no consent flow for their imports yet).
56
58
  - Subagents: each child has its own step and token budget (capped by the team policy); it does not
@@ -65,6 +67,9 @@ in 0.15.0:
65
67
  - Editors (`garuda acp`): Garuda's session history is not in the editor (no `session/load`), the
66
68
  editor's MCP servers are not started, Garuda reads saved files (not unsaved buffers), the terminal's
67
69
  own commands (`/undo`, `/diff` …) are not available, and an edit is approved as a whole (no hunks).
70
+ - Stored keys are in a file (`~/.garuda/credentials`, mode 0600, hidden from the sandbox), not in the
71
+ OS keychain yet. Keep the file out of backups and dotfile sync.
72
+ - Native Windows is not supported; use WSL.
68
73
 
69
74
  ## Use
70
75
 
@@ -98,8 +103,7 @@ pnpm link --global # optional: puts `garuda` on your PATH (run `pnpm setup` onc
98
103
  Then:
99
104
 
100
105
  ```sh
101
- export ANTHROPIC_API_KEY=...
102
- export GARUDA_MODEL=<model-id>
106
+ garuda setup # once: pick a provider and model, enter the key
103
107
  garuda # chat in the current folder
104
108
  garuda -p "Explain what this repo does" # run one task and exit
105
109
  echo "Explain this repo" | garuda # the same, from stdin
@@ -107,6 +111,13 @@ echo "Explain this repo" | garuda # the same, from stdin
107
111
 
108
112
  From a source build without `pnpm link`, use `node dist/cli/index.js` in place of `garuda`.
109
113
 
114
+ `garuda setup` (0.16) asks for the provider, the model and the key (hidden input), checks the key
115
+ with one free request to the provider, and stores the model as `"default"` in
116
+ `~/.garuda/models.json` and the key in `~/.garuda/credentials` (mode 0600; commands in the sandbox
117
+ cannot read it). `garuda setup --show` shows the model and where each key comes from (masked);
118
+ `garuda setup --forget [NAME]` removes stored keys. Environment variables still work and win:
119
+ `ANTHROPIC_API_KEY`, `GARUDA_MODEL`. Do not sync or back up `~/.garuda/credentials` to other places.
120
+
110
121
  Tools: read_file, glob and grep run with no question.
111
122
  write_file, edit_file and bash show a diff or the command first. You pick: allow once, allow for this session, or deny.
112
123
  Commands run in an OS sandbox when your machine has one (see Sandbox below). Without a sandbox,
@@ -150,13 +161,15 @@ Set the context window, price and output limit per model, and add providers, in
150
161
  ```json
151
162
  {
152
163
  "providers": { "lab": { "type": "openai-compatible", "baseUrl": "https://llm.example.com/v1", "apiKeyEnv": "LAB_KEY" } },
153
- "models": { "ollama/qwen3-coder:30b": { "contextWindow": 65536, "maxTokens": 8192 } }
164
+ "models": { "ollama/qwen3-coder:30b": { "contextWindow": 65536, "maxTokens": 8192 } },
165
+ "default": "ollama/qwen3-coder:30b"
154
166
  }
155
167
  ```
156
168
 
157
169
  - Only this file in your home folder can define providers; a project cannot send your code elsewhere.
158
170
  Keys come from environment variables. Plain http works only to this machine (or with
159
171
  `"allowInsecureHttp": true`).
172
+ - `"default"` is the model when neither `--model` nor `GARUDA_MODEL` names one.
160
173
  - Some small models write a tool call as JSON text. When the whole reply is such a call to a known
161
174
  tool, Garuda runs it as a real call, with the usual approvals. For a model that also writes
162
175
  sentences around the call (for example `qwen2.5-coder:7b`), set `"textToolCalls": "lines"` for that
@@ -252,12 +265,14 @@ Set the context window, price and output limit per model, and add providers, in
252
265
 
253
266
  Model text goes to stdout; tool activity and notes go to stderr. So `garuda -p "…" > answer.md` keeps only the answer.
254
267
 
255
- ## Editors (ACP, 0.15)
268
+ ## Editors (ACP, 0.15; sign-in 0.16)
256
269
 
257
270
  `garuda acp` runs Garuda as an agent for editors that speak the Agent Client Protocol. The editor
258
271
  starts it, shows the conversation, the tool calls and the questions, and Garuda does the work with
259
272
  the same permission engine, OS sandbox, team policy, hooks and audit log as in the terminal.
260
273
 
274
+ Run `garuda setup` once in a terminal (see Use): then the editor needs no key in its settings.
275
+
261
276
  VS Code, with the extension "ACP Client" (`formulahendry.acp-client`), in the user settings
262
277
  (Cmd+Shift+P, **Preferences: Open User Settings (JSON)**). Use the full path from `which garuda`:
263
278
  VS Code started from the Dock does not get your shell's `PATH`.
@@ -265,18 +280,16 @@ VS Code started from the Dock does not get your shell's `PATH`.
265
280
  ```json
266
281
  {
267
282
  "acp.agents": {
268
- "Garuda": {
269
- "command": "/opt/homebrew/bin/garuda",
270
- "args": ["acp"],
271
- "env": { "GARUDA_MODEL": "claude-sonnet-5", "ANTHROPIC_API_KEY": "..." }
272
- }
283
+ "Garuda": { "command": "/opt/homebrew/bin/garuda", "args": ["acp"] }
273
284
  }
274
285
  }
275
286
  ```
276
287
 
277
- Zed: the same command, argument and environment under `agent_servers` (with `"type": "custom"`).
278
- The website's [Editors](https://madhu-sv.github.io/garuda/docs/editors/) page has the steps, a way
279
- to keep the API key out of `settings.json`, and what the editor shows.
288
+ Zed: the same command and argument under `agent_servers` (with `"type": "custom"`). With no model
289
+ or key, Zed offers **Set up Garuda** (ACP Terminal Auth, 0.16): it runs `garuda acp setup` in a
290
+ terminal, and the next thread works. The website's
291
+ [Editors](https://madhu-sv.github.io/garuda/docs/editors/) page has the steps and what the editor
292
+ shows.
280
293
 
281
294
  - Each edit asks in the editor, with the editor's diff view; "Allow for this session" lasts for this
282
295
  session only. Keep an extension's auto-approve off: it answers Garuda's questions for you.
@@ -1,46 +1,65 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- keepProviderKey,
4
- loadModelsConfig,
5
- resolveModel
6
- } from "./chunk-YFQJXRIJ.js";
7
2
  import {
8
3
  FileSessionStore,
9
4
  Runtime,
10
5
  auditDirFor
11
- } from "./chunk-K7KK7U44.js";
12
- import "./chunk-A2QAIRQL.js";
13
- import "./chunk-HRGX53VC.js";
6
+ } from "./chunk-WIJL72ZE.js";
14
7
  import "./chunk-G2AX7MGS.js";
8
+ import "./chunk-A2QAIRQL.js";
9
+ import "./chunk-ULAJPMY7.js";
10
+ import {
11
+ chooseModel,
12
+ keepProviderKeys,
13
+ loadModelsConfig,
14
+ providerKeySource,
15
+ resolveModel
16
+ } from "./chunk-NP6RIC25.js";
17
+ import "./chunk-EIXUSVFX.js";
18
+ import "./chunk-R7I2NBPX.js";
15
19
  import {
16
20
  loadSearchConfig
17
21
  } from "./chunk-B25VL4RU.js";
18
22
  import "./chunk-23FXBNBB.js";
19
23
  import {
20
24
  VERSION
21
- } from "./chunk-EPKWDCV4.js";
25
+ } from "./chunk-XOK5OZOC.js";
22
26
  import "./chunk-5DEWP3YX.js";
23
27
  import "./chunk-ZAE25JM2.js";
24
28
  import "./chunk-ATW7BNIG.js";
25
29
  import "./chunk-PZS3V6P3.js";
26
30
  import "./chunk-OUCV3FCT.js";
27
- import "./chunk-SI2QJYCR.js";
31
+ import "./chunk-GXZACJUI.js";
28
32
  import "./chunk-WMOLXTBU.js";
33
+ import "./chunk-3EQU2GUX.js";
29
34
  import "./chunk-HUIK3WXO.js";
30
35
  import "./chunk-3NJNVZTK.js";
31
36
 
32
37
  // src/cli/acpCommand.ts
33
38
  import { homedir } from "os";
34
39
  import { Readable } from "stream";
40
+ var WINDOWS_MESSAGE = "Garuda supports macOS and Linux. On Windows, run Garuda in WSL (Windows Subsystem for Linux).";
35
41
  async function acpCommand(options) {
42
+ if (options.action !== void 0) {
43
+ if (options.action !== "setup") {
44
+ process.stderr.write(`Unknown argument "${options.action}". Use: garuda acp [setup]
45
+ `);
46
+ return 1;
47
+ }
48
+ const { setupCommand, terminalIO } = await import("./setupCommand-VA7D5MZK.js");
49
+ return setupCommand({}, { io: await terminalIO() });
50
+ }
36
51
  const output = protocolOutput(process.stdout, process.stderr);
37
52
  const input = Readable.toWeb(process.stdin);
38
- const setup = await prepare(options);
39
- const { acpServer } = await import("./server-R3GNIC33.js");
53
+ const { acpServer, setupNeeded } = await import("./server-KHHNHUYV.js");
40
54
  const server = acpServer({
41
55
  version: VERSION,
56
+ // Not on native Windows: the setup there could not keep a key private (no 0600).
57
+ ...process.platform === "win32" ? {} : { terminalAuth: { args: ["setup"] } },
42
58
  createRuntime: async ({ root, approver, onEvent, onNotice }) => {
43
- if (typeof setup === "string") throw new Error(setup);
59
+ const setup = await prepare(options);
60
+ if ("problem" in setup) {
61
+ throw setup.setupNeeded ? setupNeeded(setup.problem) : new Error(setup.problem);
62
+ }
44
63
  return createRuntime(setup, { root, approver, onEvent, onNotice });
45
64
  }
46
65
  });
@@ -64,11 +83,15 @@ function protocolOutput(stdout, stderr) {
64
83
  })
65
84
  });
66
85
  }
67
- async function prepare(options) {
68
- const spec = options.model ?? process.env.GARUDA_MODEL;
69
- if (!spec) return "Set a model with --model <id> or the GARUDA_MODEL variable.";
70
- const models = await loadModelsConfig();
71
- if (models.problem !== void 0) return models.problem;
86
+ var SETUP_HINT = "Run `garuda setup` in a terminal, or use the editor's sign-in for Garuda.";
87
+ async function prepare(options, from = {}) {
88
+ const fail = (problem, setupNeeded = false) => ({ problem, setupNeeded });
89
+ if ((from.platform ?? process.platform) === "win32") return fail(WINDOWS_MESSAGE);
90
+ const home = from.home ?? homedir();
91
+ const models = await loadModelsConfig(home);
92
+ if (models.problem !== void 0) return fail(models.problem);
93
+ const spec = chooseModel(models.config, options.model, process.env.GARUDA_MODEL);
94
+ if (!spec) return fail(`Garuda has no model. ${SETUP_HINT}`, true);
72
95
  let resolved;
73
96
  let sub;
74
97
  try {
@@ -76,25 +99,35 @@ async function prepare(options) {
76
99
  const subSpec = process.env.GARUDA_SUBAGENT_MODEL;
77
100
  if (subSpec) sub = resolveModel(subSpec, models.config);
78
101
  } catch (error) {
79
- return error.message;
102
+ return fail(error.message);
80
103
  }
81
- const search = await loadSearchConfig();
82
- for (const name of [resolved.def.apiKeyEnv, sub?.def.apiKeyEnv, "ANTHROPIC_API_KEY"]) {
83
- if (name !== void 0) keepProviderKey(name);
104
+ const search = await loadSearchConfig(home);
105
+ const credentialsWarning = await keepProviderKeys(
106
+ [resolved.def.apiKeyEnv, sub?.def.apiKeyEnv, "ANTHROPIC_API_KEY"],
107
+ home
108
+ );
109
+ if (credentialsWarning !== void 0) process.stderr.write(`garuda acp: ${credentialsWarning}
110
+ `);
111
+ const keyName = resolved.def.type === "anthropic" ? resolved.def.apiKeyEnv ?? "ANTHROPIC_API_KEY" : resolved.def.apiKeyEnv;
112
+ const sdkToken = resolved.def.type === "anthropic" && (process.env.ANTHROPIC_AUTH_TOKEN ?? "") !== "";
113
+ if (keyName !== void 0 && providerKeySource(keyName) === void 0 && !sdkToken) {
114
+ const why = credentialsWarning === void 0 ? "" : ` ${credentialsWarning}`;
115
+ return fail(`Garuda has no key for ${resolved.spec} (${keyName}).${why} ${SETUP_HINT}`, true);
84
116
  }
85
117
  const { loadTeamPolicy } = await import("./policy-EJPLEEXP.js");
86
118
  let team;
87
119
  try {
88
- team = await loadTeamPolicy();
120
+ team = await loadTeamPolicy("managed" in from ? { home, managed: from.managed } : { home });
89
121
  } catch (error) {
90
- return `Team policy: ${error.message}`;
122
+ return fail(`Team policy: ${error.message}`);
91
123
  }
92
124
  return {
93
125
  resolved,
94
126
  ...sub === void 0 ? {} : { sub },
95
127
  models: models.config,
96
128
  ...team === void 0 ? {} : { team: { policy: team.policy, sources: team.sources } },
97
- search
129
+ search,
130
+ ...credentialsWarning === void 0 ? {} : { credentialsWarning }
98
131
  };
99
132
  }
100
133
  function createRuntime(setup, input) {
@@ -144,10 +177,15 @@ function createRuntime(setup, input) {
144
177
  },
145
178
  ...input.onEvent === void 0 ? {} : { onEvent: input.onEvent },
146
179
  onNotice: input.onNotice
180
+ }).then((runtime) => {
181
+ if (setup.credentialsWarning !== void 0) input.onNotice(setup.credentialsWarning);
182
+ return runtime;
147
183
  });
148
184
  }
149
185
  export {
186
+ WINDOWS_MESSAGE,
150
187
  acpCommand,
188
+ prepare,
151
189
  protocolOutput
152
190
  };
153
- //# sourceMappingURL=acpCommand-FZ2P5JFO.js.map
191
+ //# sourceMappingURL=acpCommand-AJJBKAE5.js.map
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/model/types.ts
4
+ var EFFORTS = ["low", "medium", "high", "xhigh", "max"];
5
+ var ZERO_USAGE = {
6
+ inputTokens: 0,
7
+ outputTokens: 0,
8
+ cacheReadTokens: 0,
9
+ cacheWriteTokens: 0
10
+ };
11
+ function addUsage(a, b) {
12
+ const searches = (a.webSearches ?? 0) + (b.webSearches ?? 0);
13
+ return {
14
+ inputTokens: a.inputTokens + b.inputTokens,
15
+ outputTokens: a.outputTokens + b.outputTokens,
16
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
17
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
18
+ ...searches === 0 ? {} : { webSearches: searches }
19
+ };
20
+ }
21
+
22
+ export {
23
+ EFFORTS,
24
+ ZERO_USAGE,
25
+ addUsage
26
+ };
27
+ //# sourceMappingURL=chunk-3EQU2GUX.js.map
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  JobGitError,
4
4
  git
5
- } from "./chunk-2TAXSFWN.js";
5
+ } from "./chunk-TUUEBHG2.js";
6
6
 
7
7
  // src/jobs/worktree.ts
8
8
  import { createHash } from "crypto";
@@ -135,4 +135,4 @@ export {
135
135
  commitJob,
136
136
  jobChanges
137
137
  };
138
- //# sourceMappingURL=chunk-K3SIUOAG.js.map
138
+ //# sourceMappingURL=chunk-44QN5SWZ.js.map
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-EU7PA45O.js";
7
7
  import {
8
8
  DEFAULT_ENV_ALLOWLIST
9
- } from "./chunk-SI2QJYCR.js";
9
+ } from "./chunk-GXZACJUI.js";
10
10
 
11
11
  // src/lsp/install.ts
12
12
  import { mkdir } from "fs/promises";
@@ -70,4 +70,4 @@ export {
70
70
  installCommand,
71
71
  installServer
72
72
  };
73
- //# sourceMappingURL=chunk-INRZR772.js.map
73
+ //# sourceMappingURL=chunk-B6FA4I7Q.js.map
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/session/redact.ts
4
+ var REDACTED = "[REDACTED]";
5
+ var PATTERNS = [
6
+ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g,
7
+ /sk-ant-[A-Za-z0-9_-]{16,}/g,
8
+ /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}/g,
9
+ /\bAKIA[0-9A-Z]{16}\b/g,
10
+ /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
11
+ /\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
12
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}/g,
13
+ /\bAIza[0-9A-Za-z_-]{35}\b/g,
14
+ /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
15
+ ];
16
+ var ASSIGNMENT = /\b([A-Za-z0-9_]*(?:api[_-]?key|secret|token|password|passwd)[A-Za-z0-9_]*)(["']?\s*[:=]\s*["']?)([^\s"'`,;]{8,})/gi;
17
+ var OPAQUE_KEYS = /* @__PURE__ */ new Set(["encrypted_content", "encrypted_index", "signature"]);
18
+ function looksLikeCode(name, value, next) {
19
+ if (/^[\d_.]+$/.test(value)) return !/pass|secret/i.test(name);
20
+ if (value.includes("(") || next === "(") return true;
21
+ return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value) && !/\d{4,}/.test(value);
22
+ }
23
+ var SECRET_ENV_NAME = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL)/i;
24
+ var movedSecrets = /* @__PURE__ */ new Map();
25
+ function keepSecretForRedaction(name, value) {
26
+ movedSecrets.set(name, value);
27
+ }
28
+ var Redactor = class {
29
+ literals;
30
+ /**
31
+ * `env`: variables whose names look secret have their values removed wherever they appear.
32
+ * Default: this process's environment and the secrets moved out of it.
33
+ */
34
+ constructor(env = { ...process.env, ...Object.fromEntries(movedSecrets) }) {
35
+ this.literals = Object.entries(env).filter(
36
+ ([name, value]) => (SECRET_ENV_NAME.test(name) || value !== void 0 && movedSecrets.get(name) === value) && (value?.length ?? 0) >= 8
37
+ ).map(([, value]) => value).sort((a, b) => b.length - a.length);
38
+ }
39
+ text(input) {
40
+ let out = input;
41
+ for (const literal of this.literals) out = out.split(literal).join(REDACTED);
42
+ for (const pattern of PATTERNS) out = out.replace(pattern, REDACTED);
43
+ return out.replace(
44
+ ASSIGNMENT,
45
+ (all, name, sep, value, at) => looksLikeCode(name, value, out[at + all.length]) ? all : `${name}${sep}${REDACTED}`
46
+ );
47
+ }
48
+ /** A copy of `value` with every string redacted. */
49
+ value(value) {
50
+ return this.walk(value);
51
+ }
52
+ walk(value) {
53
+ if (typeof value === "string") return this.text(value);
54
+ if (Array.isArray(value)) return value.map((item) => this.walk(item));
55
+ if (value !== null && typeof value === "object") {
56
+ const redactedThinking = value.type === "redacted_thinking";
57
+ return Object.fromEntries(
58
+ Object.entries(value).map(([k, v]) => [
59
+ k,
60
+ (OPAQUE_KEYS.has(k) || redactedThinking && k === "data") && typeof v === "string" ? v : this.walk(v)
61
+ ])
62
+ );
63
+ }
64
+ return value;
65
+ }
66
+ };
67
+
68
+ // src/tools/atomicWrite.ts
69
+ import { randomBytes } from "crypto";
70
+ import { chmod, link, realpath, rename, stat, unlink, writeFile } from "fs/promises";
71
+ import { basename, dirname, join } from "path";
72
+ async function writeFileAtomic(path, content, { createOnly }) {
73
+ const target = createOnly ? path : await realpath(path);
74
+ const temp = join(
75
+ dirname(target),
76
+ `.${basename(target)}.garuda-${randomBytes(4).toString("hex")}.tmp`
77
+ );
78
+ const mode = createOnly ? void 0 : (await stat(target)).mode & 4095;
79
+ await writeFile(temp, content, { flag: "wx", ...mode === void 0 ? {} : { mode } });
80
+ try {
81
+ if (createOnly) {
82
+ try {
83
+ await link(temp, target);
84
+ } catch (error) {
85
+ const code = error.code;
86
+ if (code !== "EPERM" && code !== "ENOTSUP" && code !== "EXDEV") throw error;
87
+ if (await exists(target)) throw Object.assign(new Error("EEXIST"), { code: "EEXIST" });
88
+ await rename(temp, target);
89
+ return;
90
+ }
91
+ } else {
92
+ await chmod(temp, mode ?? 420);
93
+ await rename(temp, target);
94
+ return;
95
+ }
96
+ } finally {
97
+ await unlink(temp).catch(() => {
98
+ });
99
+ }
100
+ }
101
+ async function exists(path) {
102
+ try {
103
+ await stat(path);
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ export {
111
+ REDACTED,
112
+ keepSecretForRedaction,
113
+ Redactor,
114
+ writeFileAtomic
115
+ };
116
+ //# sourceMappingURL=chunk-EIXUSVFX.js.map
@@ -17,11 +17,11 @@ import {
17
17
  repoMapText,
18
18
  repoMapTool,
19
19
  verifyAuditFile
20
- } from "./chunk-K7KK7U44.js";
20
+ } from "./chunk-WIJL72ZE.js";
21
21
  import {
22
22
  WEB_SEARCH_USD,
23
23
  totalTokens
24
- } from "./chunk-HRGX53VC.js";
24
+ } from "./chunk-R7I2NBPX.js";
25
25
  import {
26
26
  BUILTIN_COMMANDS
27
27
  } from "./chunk-23FXBNBB.js";
@@ -489,7 +489,7 @@ async function jobsCommand(runtime, renderer, id) {
489
489
  return;
490
490
  }
491
491
  const jobs = await listJobs2(runtime.root);
492
- const { defaultAgentEnv, nightAgentTime } = await import("./launchd-QVHQ3VNN.js");
492
+ const { defaultAgentEnv, nightAgentTime } = await import("./launchd-SSX362VG.js");
493
493
  const night = nightAgentTime(runtime.root, defaultAgentEnv());
494
494
  const nightLine = night === void 0 ? [] : [
495
495
  `Night shift: launchd starts the queue at ${night.toTimeString().slice(0, 5)} on ${night.toDateString()} (/jobs cancel night removes it).`
@@ -1244,4 +1244,4 @@ export {
1244
1244
  statusOf,
1245
1245
  CommandArgs
1246
1246
  };
1247
- //# sourceMappingURL=chunk-PQNT34LY.js.map
1247
+ //# sourceMappingURL=chunk-ENLDPN4S.js.map
@@ -2,6 +2,9 @@
2
2
  import {
3
3
  realRelative
4
4
  } from "./chunk-WMOLXTBU.js";
5
+ import {
6
+ EFFORTS
7
+ } from "./chunk-3EQU2GUX.js";
5
8
  import {
6
9
  isCommandDisallowedByPolicy,
7
10
  isHostBlockedByPolicy,
@@ -86,6 +89,8 @@ var DENY_READ_IN_HOME = [
86
89
  // Garuda's OAuth tokens for remote MCP servers (0.4). The rest of ~/.garuda stays readable:
87
90
  // skills and their scripts live there (0.5).
88
91
  ".garuda/mcp-auth.json",
92
+ // Provider keys that `garuda setup` stored (0.16).
93
+ ".garuda/credentials",
89
94
  // Claude Code: its config can hold MCP server tokens in env values; its credentials on Linux.
90
95
  ".claude.json",
91
96
  ".claude/.credentials.json"
@@ -698,27 +703,6 @@ function onPath(program) {
698
703
  import { readFile } from "fs/promises";
699
704
  import { join as join3 } from "path";
700
705
  import { z } from "zod";
701
-
702
- // src/model/types.ts
703
- var EFFORTS = ["low", "medium", "high", "xhigh", "max"];
704
- var ZERO_USAGE = {
705
- inputTokens: 0,
706
- outputTokens: 0,
707
- cacheReadTokens: 0,
708
- cacheWriteTokens: 0
709
- };
710
- function addUsage(a, b) {
711
- const searches = (a.webSearches ?? 0) + (b.webSearches ?? 0);
712
- return {
713
- inputTokens: a.inputTokens + b.inputTokens,
714
- outputTokens: a.outputTokens + b.outputTokens,
715
- cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
716
- cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
717
- ...searches === 0 ? {} : { webSearches: searches }
718
- };
719
- }
720
-
721
- // src/permissions/settings.ts
722
706
  var SETTINGS_FILE = join3(".garuda", "settings.json");
723
707
  var NOTIFY_CHOICES = ["auto", "osc9", "bell", "off"];
724
708
  var schema = z.strictObject({
@@ -1320,9 +1304,6 @@ function describeTarget(target) {
1320
1304
  }
1321
1305
 
1322
1306
  export {
1323
- EFFORTS,
1324
- ZERO_USAGE,
1325
- addUsage,
1326
1307
  isSensitive,
1327
1308
  NETWORK_PORTS,
1328
1309
  expandAllowlist,
@@ -1338,4 +1319,4 @@ export {
1338
1319
  JOB_DENIAL,
1339
1320
  PermissionEngine
1340
1321
  };
1341
- //# sourceMappingURL=chunk-SI2QJYCR.js.map
1322
+ //# sourceMappingURL=chunk-GXZACJUI.js.map
@@ -4,9 +4,11 @@ import {
4
4
  pickChannel
5
5
  } from "./chunk-N4I6YS3E.js";
6
6
  import {
7
- gateProjectSettings,
7
+ gateProjectSettings
8
+ } from "./chunk-ULAJPMY7.js";
9
+ import {
8
10
  totalTokens
9
- } from "./chunk-HRGX53VC.js";
11
+ } from "./chunk-R7I2NBPX.js";
10
12
  import {
11
13
  TrustStore
12
14
  } from "./chunk-5DEWP3YX.js";
@@ -15,11 +17,11 @@ import {
15
17
  commitJob,
16
18
  jobChanges,
17
19
  prepareWorktree
18
- } from "./chunk-K3SIUOAG.js";
20
+ } from "./chunk-44QN5SWZ.js";
19
21
  import {
20
22
  defaultAgentEnv,
21
23
  removeAgent
22
- } from "./chunk-S77XDSD4.js";
24
+ } from "./chunk-USCUXCAU.js";
23
25
  import {
24
26
  jobReport
25
27
  } from "./chunk-YQTGMCU6.js";
@@ -36,11 +38,11 @@ import {
36
38
  JobGitError,
37
39
  git,
38
40
  hostCommand
39
- } from "./chunk-2TAXSFWN.js";
41
+ } from "./chunk-TUUEBHG2.js";
40
42
  import {
41
43
  createExecutor,
42
44
  loadSettings
43
- } from "./chunk-SI2QJYCR.js";
45
+ } from "./chunk-GXZACJUI.js";
44
46
  import {
45
47
  JOBS_DIR,
46
48
  loadJob,
@@ -317,4 +319,4 @@ export {
317
319
  msUntil,
318
320
  finishJob
319
321
  };
320
- //# sourceMappingURL=chunk-2S62ZNTC.js.map
322
+ //# sourceMappingURL=chunk-HE2K657U.js.map