@garuda-agent/garuda 0.0.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +1040 -0
  5. package/SECURITY.md +32 -0
  6. package/dist/anthropic-NQDBCHJU.js +327 -0
  7. package/dist/chunk-35GMLLJW.js +157 -0
  8. package/dist/chunk-36GQIVGT.js +526 -0
  9. package/dist/chunk-3CEPHS4C.js +231 -0
  10. package/dist/chunk-3NJNVZTK.js +123 -0
  11. package/dist/chunk-42DSA4EA.js +116 -0
  12. package/dist/chunk-4P26OKTP.js +100 -0
  13. package/dist/chunk-5DEWP3YX.js +139 -0
  14. package/dist/chunk-6TWBYMY7.js +9 -0
  15. package/dist/chunk-A2QAIRQL.js +45 -0
  16. package/dist/chunk-ATW7BNIG.js +31 -0
  17. package/dist/chunk-D5O5CIWK.js +1339 -0
  18. package/dist/chunk-EU7PA45O.js +324 -0
  19. package/dist/chunk-FARS4R67.js +206 -0
  20. package/dist/chunk-G2AX7MGS.js +36 -0
  21. package/dist/chunk-HUIK3WXO.js +234 -0
  22. package/dist/chunk-ITITLNAR.js +9341 -0
  23. package/dist/chunk-KKB5W47S.js +231 -0
  24. package/dist/chunk-KWRJREUH.js +283 -0
  25. package/dist/chunk-N4I6YS3E.js +73 -0
  26. package/dist/chunk-OUCV3FCT.js +205 -0
  27. package/dist/chunk-PZS3V6P3.js +46 -0
  28. package/dist/chunk-QOW5TK4L.js +320 -0
  29. package/dist/chunk-U4OQVT2T.js +73 -0
  30. package/dist/chunk-V7WPGQLB.js +207 -0
  31. package/dist/chunk-W7BPYQMW.js +173 -0
  32. package/dist/chunk-WMOLXTBU.js +61 -0
  33. package/dist/chunk-YIZWYUBG.js +138 -0
  34. package/dist/chunk-YM6T4J5V.js +1350 -0
  35. package/dist/chunk-YQTGMCU6.js +111 -0
  36. package/dist/chunk-ZAE25JM2.js +158 -0
  37. package/dist/chunk-ZGZ2ZADL.js +49 -0
  38. package/dist/cli/index.js +1191 -0
  39. package/dist/create-Y5SXJUXO.js +223 -0
  40. package/dist/deadline-F5RJJ2QW.js +86 -0
  41. package/dist/detect-LARDSLLZ.js +10 -0
  42. package/dist/evalCommand-5TG2CKZY.js +2795 -0
  43. package/dist/export-HN3CT7AV.js +158 -0
  44. package/dist/inkChat-3J26R24L.js +1190 -0
  45. package/dist/install-XWLU22OH.js +17 -0
  46. package/dist/job-3NYDMP2M.js +31 -0
  47. package/dist/jobCommand-IYWZWTSI.js +30 -0
  48. package/dist/launchd-2FL5UN2U.js +40 -0
  49. package/dist/lspCommand-TJEZBV2E.js +99 -0
  50. package/dist/manager-4PMSXT47.js +14 -0
  51. package/dist/manager-EBTYO2GR.js +847 -0
  52. package/dist/nightCommand-DUNWJASI.js +247 -0
  53. package/dist/oauth-W6OK6XXO.js +23 -0
  54. package/dist/openaiCompatible-CJWVJP5M.js +501 -0
  55. package/dist/policy-EJPLEEXP.js +31 -0
  56. package/dist/proxy-XJAHCZB2.js +192 -0
  57. package/dist/remove-AENULZZE.js +59 -0
  58. package/dist/run-66Y6R2KU.js +886 -0
  59. package/dist/servers-6YDGWOZ6.js +24 -0
  60. package/dist/text-J2GD647J.js +11 -0
  61. package/package.json +83 -3
@@ -0,0 +1,886 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ BUILTIN_COMMANDS,
4
+ loadSearchConfig,
5
+ providerLabel,
6
+ saveSearchUse
7
+ } from "./chunk-KWRJREUH.js";
8
+ import {
9
+ SERVER_NAME,
10
+ checkServerUrl
11
+ } from "./chunk-ZAE25JM2.js";
12
+ import "./chunk-ATW7BNIG.js";
13
+ import {
14
+ folderKind
15
+ } from "./chunk-42DSA4EA.js";
16
+
17
+ // src/init/run.ts
18
+ import { existsSync as existsSync3 } from "fs";
19
+ import { join as join3 } from "path";
20
+
21
+ // src/init/plan.ts
22
+ import { existsSync, readFileSync } from "fs";
23
+ import { mkdir, writeFile } from "fs/promises";
24
+ import { dirname, join } from "path";
25
+ var GITIGNORE_LINES = [".garuda/sessions/", ".garuda/index/", ".garuda/evals/"];
26
+ function buildPlan(items, options) {
27
+ const { root, home } = options;
28
+ const lines = [];
29
+ const writes = [];
30
+ const instructions2 = items.filter((i) => i.kind === "instructions");
31
+ for (const scope of ["user", "project"]) {
32
+ const file = scope === "user" ? join(home, ".garuda", "mcp.json") : join(root, ".garuda", "mcp.json");
33
+ const shown = scope === "user" ? "~/.garuda/mcp.json" : ".garuda/mcp.json";
34
+ const servers = {};
35
+ for (const item of items.filter((i) => i.kind === "mcp" && i.scope === scope)) {
36
+ if (existsSync(file)) {
37
+ lines.push({ item, reason: `${shown} exists; add it there by hand` });
38
+ } else if (item.name in servers) {
39
+ lines.push({ item, reason: `a server named "${item.name}" is already in the list` });
40
+ } else {
41
+ servers[item.name] = item.def;
42
+ lines.push({ item, target: shown });
43
+ }
44
+ }
45
+ if (Object.keys(servers).length > 0) {
46
+ writes.push({
47
+ path: file,
48
+ shown,
49
+ content: `${JSON.stringify({ servers }, null, 2)}
50
+ `,
51
+ mode: "new"
52
+ });
53
+ }
54
+ }
55
+ const taken = /* @__PURE__ */ new Set();
56
+ for (const item of items.filter((i) => i.kind === "command")) {
57
+ const base = item.scope === "user" ? join(home, ".garuda", "commands") : join(root, ".garuda", "commands");
58
+ const rel = `${item.name.split(":").join("/")}.md`;
59
+ const file = join(base, rel);
60
+ const shown = `${item.scope === "user" ? "~/" : ""}.garuda/commands/${rel}`;
61
+ const key = `${item.scope}:${item.name}`;
62
+ if (BUILTIN_COMMANDS.includes(item.name)) {
63
+ lines.push({ item, reason: `/${item.name} is a built-in command` });
64
+ } else if (existsSync(file) || taken.has(key)) {
65
+ lines.push({ item, reason: `${shown} exists` });
66
+ } else {
67
+ taken.add(key);
68
+ writes.push({ path: file, shown, content: item.text, mode: "new" });
69
+ lines.push({ item, target: shown });
70
+ }
71
+ }
72
+ const settingsFile = join(root, ".garuda", "settings.json");
73
+ const rules = items.filter((i) => i.kind === "rule");
74
+ const allow = [];
75
+ const deny = [];
76
+ for (const item of rules) {
77
+ if (existsSync(settingsFile)) {
78
+ lines.push({ item, reason: ".garuda/settings.json exists; add it there by hand" });
79
+ continue;
80
+ }
81
+ const list = item.list === "allow" ? allow : deny;
82
+ if (!list.includes(item.rule)) list.push(item.rule);
83
+ lines.push({ item, target: `.garuda/settings.json (${item.list})` });
84
+ }
85
+ if (!existsSync(settingsFile) && (options.defaults || allow.length + deny.length > 0)) {
86
+ const settings = {
87
+ executor: "auto",
88
+ permissions: { allow, deny },
89
+ undo: { enabled: true },
90
+ lsp: { enabled: false }
91
+ };
92
+ writes.push({
93
+ path: settingsFile,
94
+ shown: ".garuda/settings.json",
95
+ content: `${JSON.stringify(settings, null, 2)}
96
+ `,
97
+ mode: "new"
98
+ });
99
+ }
100
+ if (options.defaults) {
101
+ const file = join(root, ".gitignore");
102
+ const current = existsSync(file) ? readFileSync(file, "utf8") : "";
103
+ const have = new Set(current.split(/\r?\n/).map((l) => l.trim()));
104
+ const missing = GITIGNORE_LINES.filter((l) => !have.has(l));
105
+ if (missing.length > 0) {
106
+ const prefix = current === "" || current.endsWith("\n") ? "" : "\n";
107
+ writes.push({
108
+ path: file,
109
+ shown: ".gitignore",
110
+ content: `${prefix}# Garuda's local state
111
+ ${missing.join("\n")}
112
+ `,
113
+ mode: current === "" ? "new" : "append"
114
+ });
115
+ }
116
+ }
117
+ for (const item of items) {
118
+ if (item.kind === "skipped")
119
+ lines.push({ item, reason: item.notes[0] ?? "not supported" });
120
+ if (item.kind === "instructions") lines.push({ item, target: "read by the init turn" });
121
+ }
122
+ return { writes, lines, instructions: instructions2 };
123
+ }
124
+ function previewText(plan) {
125
+ const out = [];
126
+ const agents = [...new Set(plan.lines.map((l) => l.item.agent))];
127
+ for (const agent of agents) {
128
+ out.push(`From ${agent}:`);
129
+ for (const { item, target, reason } of plan.lines.filter((l) => l.item.agent === agent)) {
130
+ const what = item.kind === "instructions" ? describe(item) : `${describe(item)} (${item.source})`;
131
+ if (target !== void 0) {
132
+ out.push(` + ${what} \u2192 ${target}`);
133
+ for (const note of item.notes) out.push(` ! ${note}`);
134
+ } else {
135
+ out.push(` - ${what}: ${reason}`);
136
+ }
137
+ }
138
+ }
139
+ if (plan.writes.length > 0) {
140
+ out.push("Files:");
141
+ for (const w of plan.writes) out.push(` ${w.mode === "new" ? "new " : "append"} ${w.shown}`);
142
+ }
143
+ return out.join("\n");
144
+ }
145
+ function describe(item) {
146
+ switch (item.kind) {
147
+ case "mcp":
148
+ return `MCP server ${item.name}`;
149
+ case "command":
150
+ return `command /${item.name}`;
151
+ case "rule":
152
+ return `${item.list} rule ${item.rule}`;
153
+ case "instructions":
154
+ return `instructions ${item.path}`;
155
+ case "skipped":
156
+ return item.what;
157
+ }
158
+ }
159
+ async function applyPlan(plan) {
160
+ const done = [];
161
+ for (const w of plan.writes) {
162
+ await mkdir(dirname(w.path), { recursive: true });
163
+ try {
164
+ await writeFile(w.path, w.content, { flag: w.mode === "new" ? "wx" : "a" });
165
+ done.push(w.shown);
166
+ } catch (error) {
167
+ if (error.code !== "EEXIST") throw error;
168
+ }
169
+ }
170
+ return done;
171
+ }
172
+
173
+ // src/init/sources.ts
174
+ import { existsSync as existsSync2, lstatSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
175
+ import { join as join2, relative, sep } from "path";
176
+
177
+ // src/init/convert.ts
178
+ var SECRET_NAME = /KEY|TOKEN|SECRET|PASS|CRED|AUTH/i;
179
+ function serverName(name) {
180
+ const out = name.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/^_+/, "").slice(0, 32);
181
+ return SERVER_NAME.test(out) ? out : void 0;
182
+ }
183
+ function commandPart(name) {
184
+ const out = name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
185
+ return /^[a-z0-9][a-z0-9_-]*$/.test(out) ? out : void 0;
186
+ }
187
+ function convertEnv(env, source, notes) {
188
+ const out = {};
189
+ for (const [key, raw] of Object.entries(env ?? {})) {
190
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
191
+ notes.push(`env "${key}" is not a valid name; left out`);
192
+ continue;
193
+ }
194
+ const value = String(raw ?? "").replace(
195
+ /\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g,
196
+ (_, name) => `\${${name}}`
197
+ );
198
+ if (value.includes("${") || value === "") {
199
+ out[key] = value;
200
+ } else if (SECRET_NAME.test(key)) {
201
+ out[key] = `\${${key}}`;
202
+ notes.push(
203
+ `${key} held a value in ${source}; Garuda does not copy it: set ${key} in your environment`
204
+ );
205
+ } else {
206
+ out[key] = value;
207
+ }
208
+ }
209
+ return out;
210
+ }
211
+ function commandFromMarkdown(text, notes) {
212
+ let body = text;
213
+ const meta = {};
214
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
215
+ if (fm !== null) {
216
+ body = text.slice(fm[0].length);
217
+ for (const line of (fm[1] ?? "").split(/\r?\n/)) {
218
+ const m = /^([A-Za-z-]+):\s*(.*)$/.exec(line);
219
+ if (m === null) continue;
220
+ const key = m[1].toLowerCase();
221
+ const value = m[2].trim().replace(/^["']|["']$/g, "");
222
+ if (key === "description" || key === "argument-hint") meta[key] = value;
223
+ else notes.push(`"${key}" in the front matter is left out (Garuda has no such option)`);
224
+ }
225
+ }
226
+ return withMeta(meta, body, notes);
227
+ }
228
+ function commandFromToml(data, notes) {
229
+ if (typeof data.prompt !== "string" || data.prompt.trim() === "") return void 0;
230
+ const meta = {};
231
+ if (typeof data.description === "string") meta.description = data.description;
232
+ return withMeta(meta, data.prompt.replaceAll("{{args}}", "$ARGUMENTS"), notes);
233
+ }
234
+ function withMeta(meta, body, notes) {
235
+ if (/!`[^`]*`|!\{[^}]*\}/.test(body)) {
236
+ notes.push("it runs shell commands in the prompt (!`\u2026`); Garuda sends that text as it is");
237
+ }
238
+ if (/(^|\s)@\{?[\w./-]+/.test(body)) {
239
+ notes.push("it includes files with @\u2026; Garuda sends that text as it is");
240
+ }
241
+ const keys = Object.entries(meta).filter(([, v]) => v !== "");
242
+ const head = keys.length === 0 ? "" : `---
243
+ ${keys.map(([k, v]) => `${k}: ${v.replace(/\n/g, " ")}`).join("\n")}
244
+ ---
245
+ `;
246
+ return `${head}${body.trim()}
247
+ `;
248
+ }
249
+ function claudeRule(text) {
250
+ const t = text.trim();
251
+ if (/^mcp__[\w-]+(__[\w*-]+)?$/.test(t)) return [t];
252
+ const m = /^([A-Za-z]+)(?:\((.*)\))?$/.exec(t);
253
+ if (m === null) return void 0;
254
+ const tool = m[1];
255
+ const arg = m[2]?.trim();
256
+ const path = (p) => p === void 0 || p === "" ? "" : `(${p.replace(/^\.\//, "")})`;
257
+ switch (tool) {
258
+ case "Bash": {
259
+ if (arg === void 0 || arg === "") return ["bash"];
260
+ return [`bash(${arg.replace(/:\*$/, "*").replace(/ \*$/, "*")})`];
261
+ }
262
+ case "Read":
263
+ return [`read_file${path(arg)}`];
264
+ case "Edit":
265
+ case "Write":
266
+ case "MultiEdit":
267
+ return [`edit_file${path(arg)}`, `write_file${path(arg)}`];
268
+ case "WebFetch": {
269
+ const host = arg?.startsWith("domain:") ? arg.slice(7) : void 0;
270
+ return host === void 0 ? arg === void 0 ? ["web_fetch"] : void 0 : [`web_fetch(${host})`];
271
+ }
272
+ default:
273
+ return void 0;
274
+ }
275
+ }
276
+ function openCodeRules(permission) {
277
+ if (permission === null || typeof permission !== "object") return [];
278
+ const out = [];
279
+ const tools = {
280
+ bash: ["bash"],
281
+ edit: ["edit_file", "write_file"],
282
+ webfetch: ["web_fetch"],
283
+ read: ["read_file"]
284
+ };
285
+ for (const [key, value] of Object.entries(permission)) {
286
+ const targets = tools[key];
287
+ if (targets === void 0) continue;
288
+ const add = (action, pattern) => {
289
+ if (action !== "allow" && action !== "deny") return;
290
+ for (const tool of targets) {
291
+ out.push({
292
+ list: action,
293
+ rule: pattern === void 0 || pattern === "*" ? tool : `${tool}(${pattern})`
294
+ });
295
+ }
296
+ };
297
+ if (typeof value === "string") add(value);
298
+ else if (value !== null && typeof value === "object") {
299
+ for (const [pattern, action] of Object.entries(value)) {
300
+ add(action, key === "bash" ? pattern.replace(/ \*$/, "*") : pattern);
301
+ }
302
+ }
303
+ }
304
+ return out;
305
+ }
306
+ function parseJsonc(text) {
307
+ let out = "";
308
+ let inString = false;
309
+ for (let i = 0; i < text.length; i++) {
310
+ const c = text[i];
311
+ if (inString) {
312
+ out += c;
313
+ if (c === "\\") out += text[++i] ?? "";
314
+ else if (c === '"') inString = false;
315
+ } else if (c === '"') {
316
+ inString = true;
317
+ out += c;
318
+ } else if (c === "/" && text[i + 1] === "/") {
319
+ while (i < text.length && text[i] !== "\n") i++;
320
+ out += "\n";
321
+ } else if (c === "/" && text[i + 1] === "*") {
322
+ i = text.indexOf("*/", i + 2);
323
+ if (i === -1) break;
324
+ i++;
325
+ } else {
326
+ out += c;
327
+ }
328
+ }
329
+ return JSON.parse(out.replace(/,(\s*[}\]])/g, "$1"));
330
+ }
331
+
332
+ // src/init/sources.ts
333
+ var MAX_FILES = 200;
334
+ var SECRET_ARG = /(token|api[-_]?key|secret|password)|^(sk-|ghp_|xox[bp]-)/i;
335
+ function readAllSources(options) {
336
+ return [
337
+ ...claudeCode(options),
338
+ ...openCode(options),
339
+ ...codex(options),
340
+ ...geminiLike("Gemini CLI", options, {
341
+ settings: [
342
+ ["project", ".gemini/settings.json"],
343
+ ["user", ".gemini/settings.json"]
344
+ ],
345
+ commands: [
346
+ ["project", ".gemini/commands"],
347
+ ["user", ".gemini/commands"]
348
+ ],
349
+ instructions: ["GEMINI.md"]
350
+ }),
351
+ ...geminiLike("Tabnine", options, {
352
+ settings: [
353
+ ["project", ".tabnine/agent/settings.json"],
354
+ ["project", ".tabnine/mcp_servers.json"],
355
+ ["user", ".tabnine/agent/settings.json"]
356
+ ],
357
+ commands: [
358
+ ["project", ".tabnine/agent/commands"],
359
+ ["user", ".tabnine/agent/commands"]
360
+ ],
361
+ instructions: ["TABNINE.md", ".tabnine/guidelines"]
362
+ }),
363
+ ...cursor(options),
364
+ ...instructions("Copilot", options, [
365
+ ".github/copilot-instructions.md",
366
+ ".github/instructions"
367
+ ])
368
+ ];
369
+ }
370
+ function claudeCode({ root, home }) {
371
+ const agent = "Claude Code";
372
+ const out = [];
373
+ const mcp = readJsonFile(join2(root, ".mcp.json"), agent, out);
374
+ out.push(...mcpServers(agent, ".mcp.json", "project", field(mcp, "mcpServers"), "claude"));
375
+ const global = readJsonFile(join2(home, ".claude.json"), agent, out);
376
+ out.push(...mcpServers(agent, "~/.claude.json", "user", field(global, "mcpServers"), "claude"));
377
+ const perProject = field(field(global, "projects"), root);
378
+ out.push(
379
+ ...mcpServers(
380
+ agent,
381
+ "~/.claude.json (this project)",
382
+ "project",
383
+ field(perProject, "mcpServers"),
384
+ "claude"
385
+ )
386
+ );
387
+ out.push(...markdownCommands(agent, "project", root, ".claude/commands"));
388
+ out.push(...markdownCommands(agent, "user", home, ".claude/commands"));
389
+ for (const file of [".claude/settings.json", ".claude/settings.local.json"]) {
390
+ const settings = readJsonFile(join2(root, file), agent, out);
391
+ const permissions = field(settings, "permissions");
392
+ for (const list of ["allow", "deny"]) {
393
+ for (const text of arrayOf(field(permissions, list))) {
394
+ const rules = claudeRule(String(text));
395
+ if (rules === void 0) {
396
+ out.push(skipped(agent, file, `the rule ${String(text)}`, "Garuda has no such tool"));
397
+ } else {
398
+ for (const rule of rules)
399
+ out.push({ kind: "rule", agent, source: file, notes: [], list, rule });
400
+ }
401
+ }
402
+ }
403
+ if (field(settings, "hooks") !== void 0) {
404
+ out.push(skipped(agent, file, "hooks", "their format differs; see ~/.garuda/hooks.json"));
405
+ }
406
+ }
407
+ const userSettings = readJsonFile(join2(home, ".claude/settings.json"), agent, out);
408
+ if (field(userSettings, "permissions") !== void 0) {
409
+ out.push(
410
+ skipped(
411
+ agent,
412
+ "~/.claude/settings.json",
413
+ "your personal permission rules",
414
+ "Garuda has project settings only"
415
+ )
416
+ );
417
+ }
418
+ return out;
419
+ }
420
+ function openCode({ root, home }) {
421
+ const agent = "OpenCode";
422
+ const out = [];
423
+ const files = [
424
+ ["project", root, "opencode.json"],
425
+ ["project", root, "opencode.jsonc"],
426
+ ["user", home, ".config/opencode/opencode.json"],
427
+ ["user", home, ".config/opencode/opencode.jsonc"]
428
+ ];
429
+ for (const [scope, base, file] of files) {
430
+ const shown = scope === "user" ? `~/${file}` : file;
431
+ const config = readJsonFile(join2(base, file), agent, out);
432
+ if (config === void 0) continue;
433
+ for (const [name, raw] of Object.entries(objectOf(field(config, "mcp")))) {
434
+ const def = objectOf(raw);
435
+ if (def.type === "local") {
436
+ const cmd = arrayOf(def.command).map(String);
437
+ out.push(
438
+ ...mcpServers(agent, shown, scope, {
439
+ [name]: {
440
+ command: cmd[0],
441
+ args: cmd.slice(1),
442
+ env: def.environment,
443
+ enabled: def.enabled
444
+ }
445
+ })
446
+ );
447
+ } else if (def.type === "remote") {
448
+ out.push(
449
+ ...mcpServers(agent, shown, scope, {
450
+ [name]: { url: def.url, headers: def.headers, enabled: def.enabled }
451
+ })
452
+ );
453
+ }
454
+ }
455
+ for (const [name, raw] of Object.entries(objectOf(field(config, "command")))) {
456
+ const def = objectOf(raw);
457
+ const notes = [];
458
+ const md = `${typeof def.description === "string" ? `---
459
+ description: ${def.description}
460
+ ---
461
+ ` : ""}${String(def.template ?? "")}`;
462
+ const part = commandPart(name);
463
+ if (part === void 0 || typeof def.template !== "string") {
464
+ out.push(skipped(agent, shown, `the command ${name}`, "no valid name or template"));
465
+ continue;
466
+ }
467
+ out.push({
468
+ kind: "command",
469
+ agent,
470
+ source: shown,
471
+ notes,
472
+ scope,
473
+ name: part,
474
+ text: commandFromMarkdown(md, notes)
475
+ });
476
+ }
477
+ if (scope === "project") {
478
+ for (const { list, rule } of openCodeRules(field(config, "permission"))) {
479
+ out.push({ kind: "rule", agent, source: shown, notes: [], list, rule });
480
+ }
481
+ } else if (field(config, "permission") !== void 0) {
482
+ out.push(
483
+ skipped(agent, shown, "your personal permission rules", "Garuda has project settings only")
484
+ );
485
+ }
486
+ }
487
+ for (const dir of [".opencode/command", ".opencode/commands"])
488
+ out.push(...markdownCommands(agent, "project", root, dir));
489
+ for (const dir of [".config/opencode/command", ".config/opencode/commands"]) {
490
+ out.push(...markdownCommands(agent, "user", home, dir));
491
+ }
492
+ return out;
493
+ }
494
+ function codex({ root, home }) {
495
+ const agent = "Codex";
496
+ const out = [];
497
+ for (const [scope, base, shown] of [
498
+ ["project", root, ".codex/config.toml"],
499
+ ["user", home, "~/.codex/config.toml"]
500
+ ]) {
501
+ const config = readTomlFile(join2(base, ".codex/config.toml"), agent, out);
502
+ for (const [name, raw] of Object.entries(objectOf(field(config, "mcp_servers")))) {
503
+ const def = objectOf(raw);
504
+ const env = { ...objectOf(def.env) };
505
+ for (const v of arrayOf(def.env_vars)) env[String(v)] = `\${${String(v)}}`;
506
+ const headers = def.http_headers ?? (def.bearer_token_env_var === void 0 ? void 0 : { Authorization: "\u2026" });
507
+ out.push(...mcpServers(agent, shown, scope, { [name]: { ...def, env, headers } }));
508
+ }
509
+ }
510
+ out.push(...markdownCommands(agent, "user", home, ".codex/prompts"));
511
+ return out;
512
+ }
513
+ function geminiLike(agent, { root, home }, paths) {
514
+ const out = [];
515
+ for (const [scope, file] of paths.settings) {
516
+ const base = scope === "user" ? home : root;
517
+ const settings = readJsonFile(join2(base, file), agent, out);
518
+ const shown = scope === "user" ? `~/${file}` : file;
519
+ out.push(...mcpServers(agent, shown, scope, field(settings, "mcpServers"), "gemini"));
520
+ }
521
+ for (const [scope, dir] of paths.commands) {
522
+ const base = scope === "user" ? home : root;
523
+ for (const file of listFiles(join2(base, dir), ".toml", scope === "user")) {
524
+ const shown = `${scope === "user" ? "~/" : ""}${dir}/${file}`;
525
+ const name = commandName(file, ".toml");
526
+ const data = readTomlFile(join2(base, dir, file), agent, out);
527
+ const notes = [];
528
+ const text = data === void 0 ? void 0 : commandFromToml(data, notes);
529
+ if (name === void 0 || text === void 0) {
530
+ out.push(skipped(agent, shown, "a command", "no valid name or prompt"));
531
+ continue;
532
+ }
533
+ out.push({ kind: "command", agent, source: shown, notes, scope, name, text });
534
+ }
535
+ }
536
+ out.push(...instructions(agent, { root, home }, paths.instructions));
537
+ return out;
538
+ }
539
+ function cursor({ root, home }) {
540
+ const agent = "Cursor";
541
+ const out = [];
542
+ const project = readJsonFile(join2(root, ".cursor/mcp.json"), agent, out);
543
+ out.push(...mcpServers(agent, ".cursor/mcp.json", "project", field(project, "mcpServers")));
544
+ const user = readJsonFile(join2(home, ".cursor/mcp.json"), agent, out);
545
+ out.push(...mcpServers(agent, "~/.cursor/mcp.json", "user", field(user, "mcpServers")));
546
+ out.push(...instructions(agent, { root, home }, [".cursorrules", ".cursor/rules"]));
547
+ return out;
548
+ }
549
+ function mcpServers(agent, source, scope, servers, dialect = "plain") {
550
+ const out = [];
551
+ for (const [rawName, raw] of Object.entries(objectOf(servers))) {
552
+ const def = objectOf(raw);
553
+ const name = serverName(rawName);
554
+ if (name === void 0) {
555
+ out.push(
556
+ skipped(
557
+ agent,
558
+ source,
559
+ `the MCP server "${rawName}"`,
560
+ "its name does not fit Garuda's rules"
561
+ )
562
+ );
563
+ continue;
564
+ }
565
+ const notes = [];
566
+ if (def.enabled === false || def.disabled === true) notes.push("it was off; it stays off");
567
+ const enabled = !(def.enabled === false || def.disabled === true);
568
+ const sse = dialect === "claude" && def.type === "sse" || dialect === "gemini" && def.url !== void 0 && def.httpUrl === void 0;
569
+ const url = dialect === "gemini" ? def.httpUrl : def.url;
570
+ if (sse) {
571
+ out.push(
572
+ skipped(
573
+ agent,
574
+ source,
575
+ `the MCP server "${rawName}"`,
576
+ "it uses SSE; Garuda supports Streamable HTTP only"
577
+ )
578
+ );
579
+ continue;
580
+ }
581
+ if (typeof url === "string") {
582
+ const problem = checkServerUrl(url, scope);
583
+ if (problem !== void 0) {
584
+ out.push(skipped(agent, source, `the MCP server "${rawName}"`, problem));
585
+ continue;
586
+ }
587
+ if (def.headers !== void 0 && Object.keys(objectOf(def.headers)).length > 0) {
588
+ notes.push(
589
+ "it sent HTTP headers (for example a token); Garuda sends none and signs in with OAuth if the server asks"
590
+ );
591
+ }
592
+ out.push({
593
+ kind: "mcp",
594
+ agent,
595
+ source,
596
+ notes,
597
+ scope,
598
+ name,
599
+ def: { url, ...enabled ? {} : { enabled: false } }
600
+ });
601
+ continue;
602
+ }
603
+ if (typeof def.command !== "string" || def.command === "") {
604
+ out.push(skipped(agent, source, `the MCP server "${rawName}"`, "no command and no URL"));
605
+ continue;
606
+ }
607
+ if (arrayOf(def.args).some((a) => SECRET_ARG.test(String(a)))) {
608
+ notes.push("its arguments may hold a secret; check them in the new file");
609
+ }
610
+ if (def.cwd !== void 0)
611
+ notes.push("its working folder (cwd) is left out; Garuda starts it in the project");
612
+ notes.push(
613
+ 'it runs in the sandbox with no network; add "network": true if it needs the internet'
614
+ );
615
+ out.push({
616
+ kind: "mcp",
617
+ agent,
618
+ source,
619
+ notes,
620
+ scope,
621
+ name,
622
+ def: {
623
+ command: def.command,
624
+ args: arrayOf(def.args).map(String),
625
+ env: convertEnv(objectOf(def.env), source, notes),
626
+ ...enabled ? {} : { enabled: false }
627
+ }
628
+ });
629
+ }
630
+ return out;
631
+ }
632
+ function markdownCommands(agent, scope, base, dir) {
633
+ const out = [];
634
+ for (const file of listFiles(join2(base, dir), ".md", scope === "user")) {
635
+ const shown = `${scope === "user" ? "~/" : ""}${dir}/${file}`;
636
+ const name = commandName(file, ".md");
637
+ if (name === void 0) {
638
+ out.push(skipped(agent, shown, "a command", "its name does not fit Garuda's rules"));
639
+ continue;
640
+ }
641
+ const notes = [];
642
+ const text = commandFromMarkdown(readFileSync2(join2(base, dir, file), "utf8"), notes);
643
+ out.push({ kind: "command", agent, source: shown, notes, scope, name, text });
644
+ }
645
+ return out;
646
+ }
647
+ function instructions(agent, { root }, paths) {
648
+ const out = [];
649
+ for (const path of paths) {
650
+ const full = join2(root, path);
651
+ if (!existsSync2(full)) continue;
652
+ const files = statSync(full).isDirectory() ? listFiles(full, [".md", ".mdc"]).map((f) => `${path}/${f}`) : [path];
653
+ for (const file of files)
654
+ out.push({ kind: "instructions", agent, source: file, notes: [], path: file });
655
+ }
656
+ return out;
657
+ }
658
+ function commandName(file, ext) {
659
+ const parts = file.slice(0, -ext.length).split("/").map(commandPart);
660
+ return parts.every((p) => p !== void 0) ? parts.join(":") : void 0;
661
+ }
662
+ function listFiles(dir, ext, followLinks = false) {
663
+ const exts = Array.isArray(ext) ? ext : [ext];
664
+ const out = [];
665
+ const walk = (current) => {
666
+ let entries;
667
+ try {
668
+ entries = readdirSync(current).sort();
669
+ } catch {
670
+ return;
671
+ }
672
+ for (const name of entries) {
673
+ if (out.length >= MAX_FILES) return;
674
+ const full = join2(current, name);
675
+ let stat;
676
+ try {
677
+ stat = followLinks ? statSync(full) : lstatSync(full);
678
+ } catch {
679
+ continue;
680
+ }
681
+ if (stat.isDirectory()) walk(full);
682
+ else if (stat.isFile() && exts.some((e) => name.endsWith(e))) {
683
+ out.push(relative(dir, full).split(sep).join("/"));
684
+ }
685
+ }
686
+ };
687
+ walk(dir);
688
+ return out;
689
+ }
690
+ function readJsonFile(path, agent, out) {
691
+ if (!existsSync2(path)) return void 0;
692
+ try {
693
+ return objectOf(parseJsonc(readFileSync2(path, "utf8")));
694
+ } catch (error) {
695
+ out.push(
696
+ skipped(
697
+ agent,
698
+ path,
699
+ "a settings file",
700
+ `it does not parse: ${error.message.slice(0, 80)}`
701
+ )
702
+ );
703
+ return void 0;
704
+ }
705
+ }
706
+ var toml;
707
+ function readTomlFile(path, agent, out) {
708
+ if (!existsSync2(path)) return void 0;
709
+ if (toml === void 0) {
710
+ out.push(skipped(agent, path, "a TOML file", "the TOML reader is not loaded"));
711
+ return void 0;
712
+ }
713
+ try {
714
+ return objectOf(toml.parse(readFileSync2(path, "utf8")));
715
+ } catch (error) {
716
+ out.push(
717
+ skipped(
718
+ agent,
719
+ path,
720
+ "a TOML file",
721
+ `it does not parse: ${error.message.split("\n")[0]}`
722
+ )
723
+ );
724
+ return void 0;
725
+ }
726
+ }
727
+ async function loadToml() {
728
+ toml ??= await import("smol-toml");
729
+ }
730
+ function skipped(agent, source, what, why) {
731
+ return { kind: "skipped", agent, source, notes: [why], what };
732
+ }
733
+ function field(value, key) {
734
+ return value !== null && typeof value === "object" ? value[key] : void 0;
735
+ }
736
+ function objectOf(value) {
737
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
738
+ }
739
+ function arrayOf(value) {
740
+ return Array.isArray(value) ? value : [];
741
+ }
742
+
743
+ // src/init/run.ts
744
+ async function runInit(context) {
745
+ const { root, home, approver, executor, signal } = context;
746
+ const kind = folderKind(root);
747
+ const report = [];
748
+ await loadToml();
749
+ const items = readAllSources({ root, home });
750
+ const plan = buildPlan(items, { root, home, defaults: true });
751
+ if (plan.writes.length > 0 || plan.lines.some((l) => l.target === void 0)) {
752
+ const found = new Set(items.map((i) => i.agent));
753
+ const question = {
754
+ tool: "init",
755
+ target: { kind: "input", json: "{}" },
756
+ preview: previewText(plan),
757
+ isolation: executor.isolation,
758
+ title: found.size > 0 ? `Set up Garuda here, with what ${[...found].join(", ")} left?` : "Set up Garuda in this folder?",
759
+ question: "Write these files?",
760
+ choices: ["once", "deny"],
761
+ labels: { once: "Yes, write the new files", deny: "No, write nothing" }
762
+ };
763
+ if (plan.writes.length === 0) {
764
+ report.push(
765
+ "Nothing to write:",
766
+ ...previewText(plan).split("\n").map((l) => ` ${l}`)
767
+ );
768
+ } else if (await approver.ask(question, signal) === "once") {
769
+ const written = await applyPlan(plan);
770
+ report.push(`Wrote ${written.length} file(s): ${written.join(", ")}.`);
771
+ const imported = plan.lines.filter((l) => l.target !== void 0 && l.item.kind === "mcp");
772
+ if (imported.some((l) => l.target?.startsWith(".garuda"))) {
773
+ report.push(
774
+ "Project MCP servers ask for your consent the first time, like any project server."
775
+ );
776
+ }
777
+ } else {
778
+ report.push("Nothing written.");
779
+ }
780
+ }
781
+ if (!existsSync3(join3(root, ".git"))) {
782
+ const choice = await approver.ask(
783
+ {
784
+ tool: "init",
785
+ target: { kind: "command", command: "git init" },
786
+ preview: "This folder is not a git repository. Git keeps the history of your changes.",
787
+ isolation: executor.isolation,
788
+ title: "Run git init?",
789
+ question: "Run it?",
790
+ choices: ["once", "deny"],
791
+ labels: { once: "Yes, run git init", deny: "No" }
792
+ },
793
+ signal
794
+ );
795
+ if (choice === "once") {
796
+ const result = await executor.run(
797
+ "git init -q",
798
+ {
799
+ root,
800
+ sandbox: false,
801
+ writePaths: [],
802
+ denyWritePaths: [],
803
+ denyReadPaths: [],
804
+ network: false,
805
+ envAllowlist: ["PATH", "HOME"],
806
+ timeoutMs: 3e4,
807
+ maxOutputBytes: 5e3
808
+ },
809
+ { signal }
810
+ );
811
+ report.push(
812
+ result.exitCode === 0 ? "Ran git init." : `git init failed: ${result.stderr.text.trim()}`
813
+ );
814
+ }
815
+ }
816
+ const search = context.searchHome === void 0 ? void 0 : await searchStep(context.searchHome, approver, executor, signal);
817
+ if (search !== void 0) report.push(search.text);
818
+ return {
819
+ kind,
820
+ report,
821
+ prompt: initPrompt(
822
+ kind,
823
+ plan.instructions.map((i) => i.path)
824
+ ),
825
+ ...search === void 0 ? {} : { searchUse: search.use }
826
+ };
827
+ }
828
+ async function searchStep(home, approver, executor, signal) {
829
+ const loaded = await loadSearchConfig(home);
830
+ if (loaded.claude === void 0 || loaded.use !== void 0) return void 0;
831
+ const provider = loaded.config === void 0 ? void 0 : providerLabel(loaded.config);
832
+ const choice = await approver.ask(
833
+ {
834
+ tool: "init",
835
+ target: { kind: "input", json: "{}" },
836
+ preview: [
837
+ "Claude can search the web itself, on Anthropic's servers, when a task needs it",
838
+ `($10 per 1,000 searches).${provider === void 0 ? "" : ` Your other provider: ${provider}; it asks before each search.`}`,
839
+ 'Garuda saves your choice in ~/.garuda/search.json ("use"), for every project. Change it later with /search.'
840
+ ].join("\n"),
841
+ isolation: executor.isolation,
842
+ title: "Web search",
843
+ question: "Which web search?",
844
+ choices: provider === void 0 ? ["once", "deny"] : ["once", "session", "deny"],
845
+ labels: {
846
+ once: "Claude's search, when a task needs it",
847
+ session: "My other provider only",
848
+ deny: "No web search"
849
+ }
850
+ },
851
+ signal
852
+ );
853
+ const use = choice === "once" ? "claude" : choice === "session" ? "provider" : "off";
854
+ const file = await saveSearchUse(home, use);
855
+ return { use, text: `Web search: ${use} (saved in ${file}).` };
856
+ }
857
+ function initPrompt(kind, otherFiles) {
858
+ const others = otherFiles.length === 0 ? "" : `
859
+ These files hold instructions for other coding agents: ${otherFiles.join(", ")}. Read them and carry over what still applies.`;
860
+ if (kind === "code") {
861
+ return [
862
+ "Set up this project for coding agents (/init).",
863
+ "Read the repository with glob, grep and read_file. Do not change any code.",
864
+ "Then create AGENTS.md at the root, or improve it if it exists (keep what is still correct), with these sections:",
865
+ "## Build and test: the exact commands.",
866
+ "## Structure: the main folders and what they hold.",
867
+ "## Conventions: style, naming and patterns that the code follows.",
868
+ "## Notes: what an agent must know (generated files, slow tests, environment variables).",
869
+ `Be short and specific. Write only facts that you checked in the files. Never copy secrets.${others}`,
870
+ "At the end, say in two or three lines what you wrote."
871
+ ].join("\n");
872
+ }
873
+ const what = kind === "empty" ? "This folder is empty." : "This folder has files, but no code yet.";
874
+ return [
875
+ `${what} The user wants to start a project here (/init).`,
876
+ "Ask the user, in one short message, what they want to build, and which language, build tool and test tool to use.",
877
+ "Suggest a default for each. Do not create files in this turn.",
878
+ "When the user answers, create AGENTS.md with their choices (## Stack, ## Build and test, ## Conventions),",
879
+ `and offer to create the project skeleton.${others}`
880
+ ].join("\n");
881
+ }
882
+ export {
883
+ initPrompt,
884
+ runInit
885
+ };
886
+ //# sourceMappingURL=run-66Y6R2KU.js.map