@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.
- package/CHANGELOG.md +58 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +1040 -0
- package/SECURITY.md +32 -0
- package/dist/anthropic-NQDBCHJU.js +327 -0
- package/dist/chunk-35GMLLJW.js +157 -0
- package/dist/chunk-36GQIVGT.js +526 -0
- package/dist/chunk-3CEPHS4C.js +231 -0
- package/dist/chunk-3NJNVZTK.js +123 -0
- package/dist/chunk-42DSA4EA.js +116 -0
- package/dist/chunk-4P26OKTP.js +100 -0
- package/dist/chunk-5DEWP3YX.js +139 -0
- package/dist/chunk-6TWBYMY7.js +9 -0
- package/dist/chunk-A2QAIRQL.js +45 -0
- package/dist/chunk-ATW7BNIG.js +31 -0
- package/dist/chunk-D5O5CIWK.js +1339 -0
- package/dist/chunk-EU7PA45O.js +324 -0
- package/dist/chunk-FARS4R67.js +206 -0
- package/dist/chunk-G2AX7MGS.js +36 -0
- package/dist/chunk-HUIK3WXO.js +234 -0
- package/dist/chunk-ITITLNAR.js +9341 -0
- package/dist/chunk-KKB5W47S.js +231 -0
- package/dist/chunk-KWRJREUH.js +283 -0
- package/dist/chunk-N4I6YS3E.js +73 -0
- package/dist/chunk-OUCV3FCT.js +205 -0
- package/dist/chunk-PZS3V6P3.js +46 -0
- package/dist/chunk-QOW5TK4L.js +320 -0
- package/dist/chunk-U4OQVT2T.js +73 -0
- package/dist/chunk-V7WPGQLB.js +207 -0
- package/dist/chunk-W7BPYQMW.js +173 -0
- package/dist/chunk-WMOLXTBU.js +61 -0
- package/dist/chunk-YIZWYUBG.js +138 -0
- package/dist/chunk-YM6T4J5V.js +1350 -0
- package/dist/chunk-YQTGMCU6.js +111 -0
- package/dist/chunk-ZAE25JM2.js +158 -0
- package/dist/chunk-ZGZ2ZADL.js +49 -0
- package/dist/cli/index.js +1191 -0
- package/dist/create-Y5SXJUXO.js +223 -0
- package/dist/deadline-F5RJJ2QW.js +86 -0
- package/dist/detect-LARDSLLZ.js +10 -0
- package/dist/evalCommand-5TG2CKZY.js +2795 -0
- package/dist/export-HN3CT7AV.js +158 -0
- package/dist/inkChat-3J26R24L.js +1190 -0
- package/dist/install-XWLU22OH.js +17 -0
- package/dist/job-3NYDMP2M.js +31 -0
- package/dist/jobCommand-IYWZWTSI.js +30 -0
- package/dist/launchd-2FL5UN2U.js +40 -0
- package/dist/lspCommand-TJEZBV2E.js +99 -0
- package/dist/manager-4PMSXT47.js +14 -0
- package/dist/manager-EBTYO2GR.js +847 -0
- package/dist/nightCommand-DUNWJASI.js +247 -0
- package/dist/oauth-W6OK6XXO.js +23 -0
- package/dist/openaiCompatible-CJWVJP5M.js +501 -0
- package/dist/policy-EJPLEEXP.js +31 -0
- package/dist/proxy-XJAHCZB2.js +192 -0
- package/dist/remove-AENULZZE.js +59 -0
- package/dist/run-66Y6R2KU.js +886 -0
- package/dist/servers-6YDGWOZ6.js +24 -0
- package/dist/text-J2GD647J.js +11 -0
- package/package.json +83 -3
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
passed,
|
|
4
|
+
testSummary
|
|
5
|
+
} from "./chunk-OUCV3FCT.js";
|
|
6
|
+
|
|
7
|
+
// src/jobs/text.ts
|
|
8
|
+
function jobPrompt(request, plan) {
|
|
9
|
+
return [
|
|
10
|
+
"Carry out the plan below. It was made in an earlier session; you now run as a scheduled job, and nobody can answer questions or approve calls.",
|
|
11
|
+
"- Work as a staff engineer: small, focused changes that follow the plan and the project's conventions, with tests that prove each change. A principal engineer reviews your diff before the user merges it.",
|
|
12
|
+
"- Calls outside the job's approved list are denied. Do not retry a denied call: continue without it, and list it at the end.",
|
|
13
|
+
"- Do not commit, push or change branches: Garuda commits your changes to the job's branch when you finish.",
|
|
14
|
+
"- Run the project's tests when the plan says how. End with a short report: what you changed, the test result, and what is left for the user.",
|
|
15
|
+
"",
|
|
16
|
+
"<request>",
|
|
17
|
+
request.trim(),
|
|
18
|
+
"</request>",
|
|
19
|
+
"",
|
|
20
|
+
"<plan>",
|
|
21
|
+
plan.trim(),
|
|
22
|
+
"</plan>"
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
var cost = (usd) => usd === void 0 ? "unknown" : `$${usd.toFixed(4)}`;
|
|
26
|
+
function jobReport(job) {
|
|
27
|
+
const r = job.result;
|
|
28
|
+
const lines = [`# Garuda job ${job.id}: ${job.title}`, ""];
|
|
29
|
+
lines.push(
|
|
30
|
+
`- Status: ${job.status}${r === void 0 ? "" : ` (${r.stopReason})`}`,
|
|
31
|
+
`- Branch: ${job.branch} (worktree ${job.worktree})`,
|
|
32
|
+
`- Base: ${job.base.slice(0, 12)}`,
|
|
33
|
+
`- Approved: ${job.allow.length === 0 ? "nothing beyond the sandbox" : job.allow.join(", ")}`
|
|
34
|
+
);
|
|
35
|
+
if (job.batch === true) {
|
|
36
|
+
lines.push(
|
|
37
|
+
`- Batch API: yes, finish by ${job.finishBy ?? "07:00"}, step limit ${job.stepLimitMinutes ?? 20} min`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (job.startedAt !== void 0) lines.push(`- Started: ${job.startedAt}`);
|
|
41
|
+
if (r === void 0) return lines.join("\n");
|
|
42
|
+
lines.push(
|
|
43
|
+
`- Steps: ${r.steps} \xB7 tokens: ${Math.round(r.tokens / 100) / 10}k \xB7 cost: ${cost(r.costUsd)} \xB7 time: ${Math.round(r.durationMs / 1e3)} s`
|
|
44
|
+
);
|
|
45
|
+
if (r.sessionId !== void 0) lines.push(`- Session: ${r.sessionId}`);
|
|
46
|
+
if (r.modelCalls !== void 0) {
|
|
47
|
+
const m = r.modelCalls;
|
|
48
|
+
lines.push(
|
|
49
|
+
`- Model calls: ${m.batch} through the Batch API, ${m.normal} normal${m.slow ? ` (${m.slow} after a batch waited too long)` : ""}${m.switchedAt === void 0 ? "" : `; the rest normal from ${m.switchedAt}`}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (r.error !== void 0) lines.push(`- Error: ${r.error}`);
|
|
53
|
+
if (r.proof !== void 0) lines.push("", ...proofLines(job, r.proof));
|
|
54
|
+
lines.push("", "## Files");
|
|
55
|
+
if (r.files.length === 0) lines.push("", "No file changed.");
|
|
56
|
+
else {
|
|
57
|
+
lines.push("");
|
|
58
|
+
for (const f of r.files) {
|
|
59
|
+
const counts = f.added === void 0 ? " (binary)" : ` (+${f.added} \u2212${f.removed ?? 0})`;
|
|
60
|
+
lines.push(`- ${f.status} ${f.path}${counts}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (r.denied.length > 0) {
|
|
64
|
+
lines.push(
|
|
65
|
+
"",
|
|
66
|
+
"## Denied calls",
|
|
67
|
+
"",
|
|
68
|
+
"The job's list did not cover these; do them yourself or add rules and run again:",
|
|
69
|
+
""
|
|
70
|
+
);
|
|
71
|
+
for (const d of r.denied) lines.push(`- ${d.tool}: ${d.target}`);
|
|
72
|
+
}
|
|
73
|
+
if (r.answer.trim() !== "") lines.push("", "## The agent's report", "", r.answer.trim());
|
|
74
|
+
lines.push(
|
|
75
|
+
"",
|
|
76
|
+
"## Next",
|
|
77
|
+
"",
|
|
78
|
+
r.commit === void 0 ? "Nothing to merge." : `Review: \`git diff ${job.base.slice(0, 12)} ${job.branch}\` \xB7 merge: \`git merge ${job.branch}\` \xB7 clean up: \`git worktree remove ${job.worktree}\``
|
|
79
|
+
);
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
function proofLines(job, proof) {
|
|
83
|
+
const out = [
|
|
84
|
+
"## Verdict",
|
|
85
|
+
"",
|
|
86
|
+
proof.verdict === "ready" ? "**Ready to merge**: the tests pass, nothing stops it, and the review found no blocker." : "**Needs a look**: see the flags and the review below before you merge.",
|
|
87
|
+
"",
|
|
88
|
+
"## Tests",
|
|
89
|
+
"",
|
|
90
|
+
job.test === void 0 ? 'No test command (set "test" in the job file).' : `\`${job.test}\`: before the job ${testSummary(proof.before)}; after it ${testSummary(proof.after)}.`
|
|
91
|
+
];
|
|
92
|
+
if (proof.after !== void 0 && !passed(proof.after)) {
|
|
93
|
+
out.push("", "```text", proof.after.tail, "```");
|
|
94
|
+
}
|
|
95
|
+
out.push("", "## Risk flags", "");
|
|
96
|
+
if (proof.flags.length === 0) out.push("None.");
|
|
97
|
+
for (const f of proof.flags) out.push(`- ${f.level === "stop" ? "STOP" : "look"}: ${f.text}`);
|
|
98
|
+
if (proof.review !== void 0) {
|
|
99
|
+
const cost2 = proof.review.costUsd === void 0 ? "" : ` (${`$${proof.review.costUsd.toFixed(4)}`})`;
|
|
100
|
+
out.push("", `## Review by a principal engineer${cost2}`, "", proof.review.text);
|
|
101
|
+
} else if (proof.reviewError !== void 0) {
|
|
102
|
+
out.push("", "## Review", "", `The review failed: ${proof.reviewError}`);
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
jobPrompt,
|
|
109
|
+
jobReport
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=chunk-YQTGMCU6.js.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkAddress,
|
|
4
|
+
isIpLiteral,
|
|
5
|
+
isLoopbackHost
|
|
6
|
+
} from "./chunk-ATW7BNIG.js";
|
|
7
|
+
|
|
8
|
+
// src/mcp/config.ts
|
|
9
|
+
import { createHash } from "crypto";
|
|
10
|
+
import { readFile } from "fs/promises";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
var MCP_FILE = join(".garuda", "mcp.json");
|
|
15
|
+
var SERVER_NAME = /^[a-z0-9][a-z0-9_]{0,31}$/;
|
|
16
|
+
var stdioSchema = z.strictObject({
|
|
17
|
+
command: z.string().min(1),
|
|
18
|
+
args: z.array(z.string()).default([]),
|
|
19
|
+
env: z.record(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), z.string()).default({}),
|
|
20
|
+
/** The server may use the network. Default: no network (it runs in the OS sandbox). */
|
|
21
|
+
network: z.boolean().default(false),
|
|
22
|
+
/** Extra writable paths. `~/` is the home folder; relative paths start at the root. */
|
|
23
|
+
writePaths: z.array(z.string()).default([]),
|
|
24
|
+
/** Time limit for one tool call. */
|
|
25
|
+
timeoutMs: z.number().int().min(1e3).max(6e5).default(6e4),
|
|
26
|
+
enabled: z.boolean().default(true)
|
|
27
|
+
});
|
|
28
|
+
var httpSchema = z.strictObject({
|
|
29
|
+
type: z.literal("http").optional(),
|
|
30
|
+
url: z.string().min(1),
|
|
31
|
+
/** Time limit for one tool call. */
|
|
32
|
+
timeoutMs: z.number().int().min(1e3).max(6e5).default(6e4),
|
|
33
|
+
enabled: z.boolean().default(true)
|
|
34
|
+
});
|
|
35
|
+
var serverSchema = z.union([httpSchema, stdioSchema]);
|
|
36
|
+
var fileSchema = z.strictObject({
|
|
37
|
+
servers: z.record(z.string(), serverSchema).default({})
|
|
38
|
+
});
|
|
39
|
+
function isHttp(def) {
|
|
40
|
+
return "url" in def;
|
|
41
|
+
}
|
|
42
|
+
async function loadMcpConfig({ home, root } = { home: homedir(), root: process.cwd() }) {
|
|
43
|
+
const problems = [];
|
|
44
|
+
const read = async (source, file) => {
|
|
45
|
+
let text;
|
|
46
|
+
try {
|
|
47
|
+
text = await readFile(file, "utf8");
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error.code === "ENOENT") return [];
|
|
50
|
+
problems.push(`${file}: ${error.message}`);
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
let json;
|
|
54
|
+
try {
|
|
55
|
+
json = JSON.parse(text);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
problems.push(`${file}: invalid JSON: ${error.message}`);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const parsed = fileSchema.safeParse(json);
|
|
61
|
+
if (!parsed.success) {
|
|
62
|
+
problems.push(`${file}: ${z.prettifyError(parsed.error)}`);
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const [name, def] of Object.entries(parsed.data.servers)) {
|
|
67
|
+
if (!SERVER_NAME.test(name)) {
|
|
68
|
+
problems.push(
|
|
69
|
+
`${file}: server name "${name}" is not valid. Use lowercase letters, digits and _ (up to 32).`
|
|
70
|
+
);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (isHttp(def)) {
|
|
74
|
+
const problem = checkServerUrl(def.url, source);
|
|
75
|
+
if (problem !== void 0) {
|
|
76
|
+
problems.push(`${file}: server "${name}": ${problem}`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
out.push({ name, source, file, def });
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
};
|
|
84
|
+
const user = await read("user", join(home, MCP_FILE));
|
|
85
|
+
const project = home === root ? [] : await read("project", join(root, MCP_FILE));
|
|
86
|
+
const userNames = new Set(user.map((s) => s.name));
|
|
87
|
+
for (const s of project) {
|
|
88
|
+
if (userNames.has(s.name)) {
|
|
89
|
+
problems.push(
|
|
90
|
+
`${s.file}: server "${s.name}" is ignored: a server with this name is in your own ${join("~", MCP_FILE)}.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { servers: [...user, ...project.filter((s) => !userNames.has(s.name))], problems };
|
|
95
|
+
}
|
|
96
|
+
function checkServerUrl(raw, source) {
|
|
97
|
+
let url;
|
|
98
|
+
try {
|
|
99
|
+
url = new URL(raw);
|
|
100
|
+
} catch {
|
|
101
|
+
return `"${raw.slice(0, 200)}" is not a valid URL.`;
|
|
102
|
+
}
|
|
103
|
+
if (url.username !== "" || url.password !== "")
|
|
104
|
+
return "a URL with a user name or password is not allowed.";
|
|
105
|
+
if (url.hash !== "") return "a URL with a #fragment is not allowed.";
|
|
106
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
107
|
+
const loopback = isLoopbackHost(host);
|
|
108
|
+
if (url.protocol === "http:") {
|
|
109
|
+
if (!loopback || source === "project")
|
|
110
|
+
return "use https (http only for localhost, in your own ~/.garuda/mcp.json).";
|
|
111
|
+
} else if (url.protocol !== "https:") {
|
|
112
|
+
return `only https URLs are allowed, not ${url.protocol}`;
|
|
113
|
+
}
|
|
114
|
+
if (source === "project" && (loopback || isIpLiteral(host) && !checkAddress(host, false).ok)) {
|
|
115
|
+
return "a project server must be on a public address.";
|
|
116
|
+
}
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
function defHash(def) {
|
|
120
|
+
if (isHttp(def)) {
|
|
121
|
+
return createHash("sha256").update(JSON.stringify({ type: "http", url: def.url })).digest("hex");
|
|
122
|
+
}
|
|
123
|
+
const canonical = JSON.stringify({
|
|
124
|
+
command: def.command,
|
|
125
|
+
args: def.args,
|
|
126
|
+
env: Object.fromEntries(Object.entries(def.env).sort(([a], [b]) => a.localeCompare(b))),
|
|
127
|
+
network: def.network,
|
|
128
|
+
writePaths: def.writePaths
|
|
129
|
+
});
|
|
130
|
+
return createHash("sha256").update(canonical).digest("hex");
|
|
131
|
+
}
|
|
132
|
+
function expandEnv(env, source = process.env) {
|
|
133
|
+
const missing = [];
|
|
134
|
+
const out = {};
|
|
135
|
+
for (const [key, value] of Object.entries(env)) {
|
|
136
|
+
out[key] = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) => {
|
|
137
|
+
const v = source[name];
|
|
138
|
+
if (v === void 0) missing.push(name);
|
|
139
|
+
return v ?? "";
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return { env: out, missing };
|
|
143
|
+
}
|
|
144
|
+
function commandLine(def) {
|
|
145
|
+
const quote = (s) => /^[\w@%+=:,./-]+$/.test(s) ? s : `'${s.replaceAll("'", `'\\''`)}'`;
|
|
146
|
+
return [def.command, ...def.args].map(quote).join(" ");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
SERVER_NAME,
|
|
151
|
+
isHttp,
|
|
152
|
+
loadMcpConfig,
|
|
153
|
+
checkServerUrl,
|
|
154
|
+
defHash,
|
|
155
|
+
expandEnv,
|
|
156
|
+
commandLine
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=chunk-ZAE25JM2.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_ENV_ALLOWLIST
|
|
4
|
+
} from "./chunk-D5O5CIWK.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-ZGZ2ZADL.js.map
|