@garuda-agent/garuda 0.0.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +1097 -0
- package/SECURITY.md +32 -0
- package/dist/acpCommand-FZ2P5JFO.js +153 -0
- package/dist/anthropic-NQDBCHJU.js +327 -0
- package/dist/chunk-23FXBNBB.js +46 -0
- package/dist/chunk-2S62ZNTC.js +320 -0
- package/dist/chunk-2TAXSFWN.js +49 -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-4JSUUDFQ.js +118 -0
- package/dist/chunk-4P26OKTP.js +100 -0
- package/dist/chunk-5DEWP3YX.js +139 -0
- package/dist/chunk-A2QAIRQL.js +45 -0
- package/dist/chunk-ATW7BNIG.js +31 -0
- package/dist/chunk-B25VL4RU.js +242 -0
- package/dist/chunk-EPKWDCV4.js +9 -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-HRGX53VC.js +173 -0
- package/dist/chunk-HUIK3WXO.js +234 -0
- package/dist/chunk-INRZR772.js +73 -0
- package/dist/chunk-K3SIUOAG.js +138 -0
- package/dist/chunk-K7KK7U44.js +9358 -0
- package/dist/chunk-KKB5W47S.js +231 -0
- package/dist/chunk-N4I6YS3E.js +73 -0
- package/dist/chunk-NXJ4XLB5.js +19 -0
- package/dist/chunk-OUCV3FCT.js +205 -0
- package/dist/chunk-PQNT34LY.js +1247 -0
- package/dist/chunk-PZS3V6P3.js +46 -0
- package/dist/chunk-S77XDSD4.js +157 -0
- package/dist/chunk-SI2QJYCR.js +1341 -0
- package/dist/chunk-WMOLXTBU.js +61 -0
- package/dist/chunk-YFQJXRIJ.js +193 -0
- package/dist/chunk-YQTGMCU6.js +111 -0
- package/dist/chunk-ZAE25JM2.js +158 -0
- package/dist/cli/index.js +1203 -0
- package/dist/create-N4I7TIZE.js +223 -0
- package/dist/deadline-F5RJJ2QW.js +86 -0
- package/dist/detect-LARDSLLZ.js +10 -0
- package/dist/evalCommand-3SSFMZQD.js +2798 -0
- package/dist/export-HN3CT7AV.js +158 -0
- package/dist/inkChat-34BTFG2K.js +1193 -0
- package/dist/install-VRPBZY5H.js +17 -0
- package/dist/job-3NYDMP2M.js +31 -0
- package/dist/jobCommand-XJ5GULS5.js +30 -0
- package/dist/launchd-QVHQ3VNN.js +40 -0
- package/dist/lspCommand-XNTBHXLA.js +99 -0
- package/dist/manager-4PMSXT47.js +14 -0
- package/dist/manager-CSI76AWF.js +847 -0
- package/dist/nightCommand-CR6ZHYGP.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-LE7Q7K46.js +59 -0
- package/dist/run-ML6GSPN4.js +888 -0
- package/dist/server-R3GNIC33.js +676 -0
- package/dist/servers-6YDGWOZ6.js +24 -0
- package/dist/text-J2GD647J.js +11 -0
- package/package.json +84 -3
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
cleanText
|
|
4
|
+
} from "./chunk-PZS3V6P3.js";
|
|
5
|
+
|
|
6
|
+
// src/mcp/oauth.ts
|
|
7
|
+
import { randomBytes } from "crypto";
|
|
8
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
9
|
+
import { createServer } from "http";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
var AUTH_FILE = join(".garuda", "mcp-auth.json");
|
|
14
|
+
var SIGN_IN_TIMEOUT_MS = 3e5;
|
|
15
|
+
var entrySchema = z.object({
|
|
16
|
+
client: z.record(z.string(), z.unknown()).optional(),
|
|
17
|
+
tokens: z.record(z.string(), z.unknown()).optional(),
|
|
18
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
19
|
+
/** Where the sign-in happens (authorization server), from discovery (SDK: SEP-2352). */
|
|
20
|
+
discovery: z.record(z.string(), z.unknown()).optional()
|
|
21
|
+
});
|
|
22
|
+
var fileSchema = z.object({
|
|
23
|
+
version: z.literal(1),
|
|
24
|
+
servers: z.record(z.string(), entrySchema).default({})
|
|
25
|
+
});
|
|
26
|
+
var AuthStore = class _AuthStore {
|
|
27
|
+
constructor(file) {
|
|
28
|
+
this.file = file;
|
|
29
|
+
}
|
|
30
|
+
file;
|
|
31
|
+
data = { version: 1, servers: {} };
|
|
32
|
+
static async open(home = homedir()) {
|
|
33
|
+
const store = new _AuthStore(join(home, AUTH_FILE));
|
|
34
|
+
try {
|
|
35
|
+
const parsed = fileSchema.safeParse(JSON.parse(await readFile(store.file, "utf8")));
|
|
36
|
+
if (parsed.success) store.data = parsed.data;
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
return store;
|
|
40
|
+
}
|
|
41
|
+
get(key) {
|
|
42
|
+
return this.data.servers[key] ?? {};
|
|
43
|
+
}
|
|
44
|
+
async update(key, patch) {
|
|
45
|
+
const next = { ...this.get(key) };
|
|
46
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
47
|
+
if (v === void 0) delete next[k];
|
|
48
|
+
else next[k] = v;
|
|
49
|
+
}
|
|
50
|
+
this.data.servers[key] = next;
|
|
51
|
+
await this.save();
|
|
52
|
+
}
|
|
53
|
+
/** Remove every entry of a server (all scopes and URLs). Returns how many were removed. */
|
|
54
|
+
async remove(server) {
|
|
55
|
+
const keys = Object.keys(this.data.servers).filter((k) => k.split("::")[1] === server);
|
|
56
|
+
for (const k of keys) delete this.data.servers[k];
|
|
57
|
+
if (keys.length > 0) await this.save();
|
|
58
|
+
return keys.length;
|
|
59
|
+
}
|
|
60
|
+
hasTokens(key) {
|
|
61
|
+
return this.get(key).tokens !== void 0;
|
|
62
|
+
}
|
|
63
|
+
async save() {
|
|
64
|
+
await mkdir(dirname(this.file), { recursive: true, mode: 448 });
|
|
65
|
+
const temp = `${this.file}.${randomBytes(4).toString("hex")}.tmp`;
|
|
66
|
+
await writeFile(temp, `${JSON.stringify(this.data, null, 2)}
|
|
67
|
+
`, { mode: 384, flag: "wx" });
|
|
68
|
+
await rename(temp, this.file);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function authKey(scope, name, url) {
|
|
72
|
+
return `${scope}::${name}::${url}`;
|
|
73
|
+
}
|
|
74
|
+
var GarudaOAuthProvider = class {
|
|
75
|
+
constructor(store, key, port) {
|
|
76
|
+
this.store = store;
|
|
77
|
+
this.key = key;
|
|
78
|
+
this.port = port;
|
|
79
|
+
}
|
|
80
|
+
store;
|
|
81
|
+
key;
|
|
82
|
+
port;
|
|
83
|
+
/** Set by the SDK when the user must sign in. */
|
|
84
|
+
authorizationUrl;
|
|
85
|
+
verifier;
|
|
86
|
+
expectedState;
|
|
87
|
+
get redirectUrl() {
|
|
88
|
+
return `http://127.0.0.1:${this.port}/callback`;
|
|
89
|
+
}
|
|
90
|
+
get clientMetadata() {
|
|
91
|
+
return {
|
|
92
|
+
client_name: "Garuda",
|
|
93
|
+
redirect_uris: [this.redirectUrl],
|
|
94
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
95
|
+
response_types: ["code"],
|
|
96
|
+
token_endpoint_auth_method: "none"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
state() {
|
|
100
|
+
this.expectedState = randomBytes(16).toString("hex");
|
|
101
|
+
return this.expectedState;
|
|
102
|
+
}
|
|
103
|
+
get stateValue() {
|
|
104
|
+
return this.expectedState;
|
|
105
|
+
}
|
|
106
|
+
clientInformation() {
|
|
107
|
+
return this.store.get(this.key).client;
|
|
108
|
+
}
|
|
109
|
+
async saveClientInformation(info) {
|
|
110
|
+
await this.store.update(this.key, { client: info, port: this.port });
|
|
111
|
+
}
|
|
112
|
+
tokens() {
|
|
113
|
+
return this.store.get(this.key).tokens;
|
|
114
|
+
}
|
|
115
|
+
async saveTokens(tokens) {
|
|
116
|
+
await this.store.update(this.key, { tokens });
|
|
117
|
+
}
|
|
118
|
+
redirectToAuthorization(url) {
|
|
119
|
+
this.authorizationUrl = url;
|
|
120
|
+
}
|
|
121
|
+
saveCodeVerifier(verifier) {
|
|
122
|
+
this.verifier = verifier;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The discovery result, kept with the other sign-in data. The SDK checks at the callback that the
|
|
126
|
+
* authorization server is still the one that it sent the user to (SEP-2352): a code and its PKCE
|
|
127
|
+
* verifier must never go to another server's token endpoint.
|
|
128
|
+
*/
|
|
129
|
+
async saveDiscoveryState(state) {
|
|
130
|
+
await this.store.update(this.key, { discovery: state });
|
|
131
|
+
}
|
|
132
|
+
discoveryState() {
|
|
133
|
+
return this.store.get(this.key).discovery;
|
|
134
|
+
}
|
|
135
|
+
codeVerifier() {
|
|
136
|
+
if (this.verifier === void 0)
|
|
137
|
+
throw new Error("No PKCE code verifier: start the sign-in again.");
|
|
138
|
+
return this.verifier;
|
|
139
|
+
}
|
|
140
|
+
async invalidateCredentials(scope) {
|
|
141
|
+
if (scope === "verifier" || scope === "all") this.verifier = void 0;
|
|
142
|
+
if (scope === "discovery" || scope === "all") {
|
|
143
|
+
await this.store.update(this.key, { discovery: void 0 });
|
|
144
|
+
}
|
|
145
|
+
if (scope === "tokens" || scope === "all")
|
|
146
|
+
await this.store.update(this.key, { tokens: void 0 });
|
|
147
|
+
if (scope === "client" || scope === "all")
|
|
148
|
+
await this.store.update(this.key, { client: void 0 });
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function freePort() {
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
const server = createServer();
|
|
154
|
+
server.once("error", reject);
|
|
155
|
+
server.listen(0, "127.0.0.1", () => {
|
|
156
|
+
const address = server.address();
|
|
157
|
+
server.close(
|
|
158
|
+
() => typeof address === "object" && address !== null ? resolve(address.port) : reject(new Error("no port"))
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
var CallbackError = class extends Error {
|
|
164
|
+
};
|
|
165
|
+
function waitForCallback(port, expectedState, signal) {
|
|
166
|
+
let server;
|
|
167
|
+
let settle;
|
|
168
|
+
const result = new Promise((resolve, reject) => {
|
|
169
|
+
settle = { resolve, reject };
|
|
170
|
+
});
|
|
171
|
+
const finish = (fn) => {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
signal.removeEventListener("abort", onAbort);
|
|
174
|
+
server?.close();
|
|
175
|
+
fn();
|
|
176
|
+
};
|
|
177
|
+
const onAbort = () => finish(() => settle?.reject(signal.reason));
|
|
178
|
+
const timer = setTimeout(
|
|
179
|
+
() => finish(
|
|
180
|
+
() => settle?.reject(
|
|
181
|
+
new CallbackError(`No sign-in within ${SIGN_IN_TIMEOUT_MS / 6e4} minutes.`)
|
|
182
|
+
)
|
|
183
|
+
),
|
|
184
|
+
SIGN_IN_TIMEOUT_MS
|
|
185
|
+
);
|
|
186
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
187
|
+
const listening = new Promise((resolve, reject) => {
|
|
188
|
+
server = createServer((req, res) => {
|
|
189
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
190
|
+
if (url.pathname !== "/callback") {
|
|
191
|
+
res.writeHead(404).end();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const params = url.searchParams;
|
|
195
|
+
const ok = params.get("state") !== null && params.get("state") === expectedState();
|
|
196
|
+
res.writeHead(ok ? 200 : 400, { "content-type": "text/html; charset=utf-8" });
|
|
197
|
+
res.end(
|
|
198
|
+
ok && params.has("code") ? "<p>Signed in. You can close this tab and go back to Garuda.</p>" : "<p>The sign-in did not work. Go back to Garuda.</p>"
|
|
199
|
+
);
|
|
200
|
+
if (!ok) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!params.has("code")) {
|
|
204
|
+
const code = cleanText(params.get("error") ?? "no code").slice(0, 40);
|
|
205
|
+
finish(() => settle?.reject(new CallbackError(`The sign-in failed (${code}).`)));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
finish(() => settle?.resolve(params));
|
|
209
|
+
});
|
|
210
|
+
server.once("error", (error) => {
|
|
211
|
+
finish(() => settle?.reject(error));
|
|
212
|
+
reject(error);
|
|
213
|
+
});
|
|
214
|
+
server.listen(port, "127.0.0.1", () => resolve());
|
|
215
|
+
});
|
|
216
|
+
result.catch(() => {
|
|
217
|
+
});
|
|
218
|
+
return { listening, result };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export {
|
|
222
|
+
AUTH_FILE,
|
|
223
|
+
SIGN_IN_TIMEOUT_MS,
|
|
224
|
+
AuthStore,
|
|
225
|
+
authKey,
|
|
226
|
+
GarudaOAuthProvider,
|
|
227
|
+
freePort,
|
|
228
|
+
CallbackError,
|
|
229
|
+
waitForCallback
|
|
230
|
+
};
|
|
231
|
+
//# sourceMappingURL=chunk-3CEPHS4C.js.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/permissions/rules.ts
|
|
4
|
+
var RULE = /^([a-z_][a-z0-9_-]*\*?)(?:\((.*)\))?$/i;
|
|
5
|
+
function parseRule(text) {
|
|
6
|
+
const match = RULE.exec(text.trim());
|
|
7
|
+
if (match === null || match[1] === void 0) {
|
|
8
|
+
throw new Error(`Invalid permission rule "${text}". Use "tool" or "tool(pattern)".`);
|
|
9
|
+
}
|
|
10
|
+
const pattern = match[2]?.trim();
|
|
11
|
+
return pattern === void 0 || pattern === "" ? { tool: match[1] } : { tool: match[1], pattern };
|
|
12
|
+
}
|
|
13
|
+
function formatRule(rule) {
|
|
14
|
+
return rule.pattern === void 0 ? rule.tool : `${rule.tool}(${rule.pattern})`;
|
|
15
|
+
}
|
|
16
|
+
function ruleMatches(rule, tool, target, mode) {
|
|
17
|
+
if (!toolMatches(rule.tool, tool)) return false;
|
|
18
|
+
if (rule.pattern === void 0) return true;
|
|
19
|
+
if (target === void 0) return false;
|
|
20
|
+
if (target.kind === "path") return pathMatches(rule.pattern, target.path);
|
|
21
|
+
if (target.kind === "input") return false;
|
|
22
|
+
if (target.kind === "url") return hostMatches(rule.pattern, target.host);
|
|
23
|
+
if (rule.exact) return normalize(target.command) === normalize(rule.pattern);
|
|
24
|
+
const parts = commandParts(target.command, mode === "deny");
|
|
25
|
+
if (parts.length === 0) return false;
|
|
26
|
+
const hit = (part) => commandMatches(rule.pattern ?? "", part);
|
|
27
|
+
return mode === "allow" ? parts.every(hit) : parts.some(hit) || hit(target.command);
|
|
28
|
+
}
|
|
29
|
+
function toolMatches(ruleTool, tool) {
|
|
30
|
+
return ruleTool.endsWith("*") ? tool.startsWith(ruleTool.slice(0, -1)) : ruleTool === tool;
|
|
31
|
+
}
|
|
32
|
+
function hostMatches(pattern, host) {
|
|
33
|
+
const p = pattern.toLowerCase();
|
|
34
|
+
const h = host.toLowerCase();
|
|
35
|
+
return p.startsWith("*.") ? h.endsWith(p.slice(1)) && h.length > p.length - 1 : p === h;
|
|
36
|
+
}
|
|
37
|
+
var CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
38
|
+
function pathMatches(pattern, path, ignoreCase = CASE_INSENSITIVE_FS) {
|
|
39
|
+
return globToRegExp(fullGlob(pattern), ignoreCase).test(path);
|
|
40
|
+
}
|
|
41
|
+
function fullGlob(pattern) {
|
|
42
|
+
return pattern.includes("/") ? pattern.replace(/^\.?\//, "") : `**/${pattern}`;
|
|
43
|
+
}
|
|
44
|
+
function globToRegExp(glob, ignoreCase) {
|
|
45
|
+
let out = "";
|
|
46
|
+
for (let i = 0; i < glob.length; i++) {
|
|
47
|
+
const c = glob[i] ?? "";
|
|
48
|
+
if (glob.startsWith("**/", i)) {
|
|
49
|
+
out += "(?:.*/)?";
|
|
50
|
+
i += 2;
|
|
51
|
+
} else if (glob.startsWith("**", i)) {
|
|
52
|
+
out += ".*";
|
|
53
|
+
i += 1;
|
|
54
|
+
} else if (c === "*") out += "[^/]*";
|
|
55
|
+
else if (c === "?") out += "[^/]";
|
|
56
|
+
else out += escapeRegExp(c);
|
|
57
|
+
}
|
|
58
|
+
return new RegExp(`^${out}$`, ignoreCase ? "i" : "");
|
|
59
|
+
}
|
|
60
|
+
function commandMatches(pattern, command) {
|
|
61
|
+
const regex = normalize(pattern).split("*").map(escapeRegExp).join(".*");
|
|
62
|
+
return new RegExp(`^${regex}$`, "s").test(normalize(command));
|
|
63
|
+
}
|
|
64
|
+
function commandParts(command, stripPrefixesToo = true) {
|
|
65
|
+
const parts = [];
|
|
66
|
+
let current = "";
|
|
67
|
+
let quote;
|
|
68
|
+
for (let i = 0; i < command.length; i++) {
|
|
69
|
+
const c = command[i] ?? "";
|
|
70
|
+
if (quote !== void 0) {
|
|
71
|
+
if (c === quote) quote = void 0;
|
|
72
|
+
else if (c === "\\" && quote === '"') {
|
|
73
|
+
current += c + (command[i + 1] ?? "");
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
current += c;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (c === "'" || c === '"') {
|
|
81
|
+
quote = c;
|
|
82
|
+
current += c;
|
|
83
|
+
} else if (c === "\\") {
|
|
84
|
+
current += c + (command[i + 1] ?? "");
|
|
85
|
+
i++;
|
|
86
|
+
} else if (";&|\n`()".includes(c) || c === "$" && command[i + 1] === "(") {
|
|
87
|
+
parts.push(current);
|
|
88
|
+
current = "";
|
|
89
|
+
} else current += c;
|
|
90
|
+
}
|
|
91
|
+
parts.push(current);
|
|
92
|
+
return parts.map((part) => stripPrefixesToo ? stripPrefixes(part) : normalize(part)).filter((part) => part !== "");
|
|
93
|
+
}
|
|
94
|
+
function stripPrefixes(part) {
|
|
95
|
+
let words = normalize(part).replace(/^[{}!\s]+/, "").split(" ");
|
|
96
|
+
for (; ; ) {
|
|
97
|
+
const first = words[0] ?? "";
|
|
98
|
+
if (first === "sudo" || first === "env" || first === "exec" || first === "command") {
|
|
99
|
+
words = words.slice(1);
|
|
100
|
+
} else if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(first)) words = words.slice(1);
|
|
101
|
+
else break;
|
|
102
|
+
}
|
|
103
|
+
return words.join(" ").replace(/[{}\s]+$/, "");
|
|
104
|
+
}
|
|
105
|
+
function normalize(text) {
|
|
106
|
+
return text.trim().replace(/\s+/g, " ");
|
|
107
|
+
}
|
|
108
|
+
function escapeRegExp(text) {
|
|
109
|
+
return text.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
parseRule,
|
|
114
|
+
formatRule,
|
|
115
|
+
ruleMatches,
|
|
116
|
+
hostMatches,
|
|
117
|
+
CASE_INSENSITIVE_FS,
|
|
118
|
+
pathMatches,
|
|
119
|
+
fullGlob,
|
|
120
|
+
commandMatches,
|
|
121
|
+
commandParts
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=chunk-3NJNVZTK.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/init/detect.ts
|
|
4
|
+
import { existsSync, readdirSync, statSync } from "fs";
|
|
5
|
+
import { extname, join } from "path";
|
|
6
|
+
var MARKERS = [
|
|
7
|
+
"package.json",
|
|
8
|
+
"pom.xml",
|
|
9
|
+
"build.gradle",
|
|
10
|
+
"build.gradle.kts",
|
|
11
|
+
"pyproject.toml",
|
|
12
|
+
"setup.py",
|
|
13
|
+
"requirements.txt",
|
|
14
|
+
"Cargo.toml",
|
|
15
|
+
"go.mod",
|
|
16
|
+
"Gemfile",
|
|
17
|
+
"composer.json",
|
|
18
|
+
"CMakeLists.txt",
|
|
19
|
+
"Makefile",
|
|
20
|
+
"deno.json"
|
|
21
|
+
];
|
|
22
|
+
var CODE = /* @__PURE__ */ new Set([
|
|
23
|
+
".ts",
|
|
24
|
+
".tsx",
|
|
25
|
+
".js",
|
|
26
|
+
".jsx",
|
|
27
|
+
".mjs",
|
|
28
|
+
".cjs",
|
|
29
|
+
".py",
|
|
30
|
+
".java",
|
|
31
|
+
".kt",
|
|
32
|
+
".go",
|
|
33
|
+
".rs",
|
|
34
|
+
".rb",
|
|
35
|
+
".php",
|
|
36
|
+
".cs",
|
|
37
|
+
".c",
|
|
38
|
+
".h",
|
|
39
|
+
".cpp",
|
|
40
|
+
".hpp",
|
|
41
|
+
".swift",
|
|
42
|
+
".scala",
|
|
43
|
+
".sh",
|
|
44
|
+
".sql",
|
|
45
|
+
".vue",
|
|
46
|
+
".svelte",
|
|
47
|
+
".dart"
|
|
48
|
+
]);
|
|
49
|
+
var SKIP = /* @__PURE__ */ new Set(["node_modules", "target", "build", "dist", "venv", "__pycache__"]);
|
|
50
|
+
var MAX_ENTRIES = 5e3;
|
|
51
|
+
function folderKind(root) {
|
|
52
|
+
if (MARKERS.some((m) => existsSync(join(root, m)))) return "code";
|
|
53
|
+
let seen = 0;
|
|
54
|
+
let files = 0;
|
|
55
|
+
const walk = (dir, depth) => {
|
|
56
|
+
let names;
|
|
57
|
+
try {
|
|
58
|
+
names = readdirSync(dir);
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
for (const name of names) {
|
|
63
|
+
if (++seen > MAX_ENTRIES) return false;
|
|
64
|
+
if (name.startsWith(".") || SKIP.has(name)) continue;
|
|
65
|
+
const full = join(dir, name);
|
|
66
|
+
let isDir = false;
|
|
67
|
+
try {
|
|
68
|
+
isDir = statSync(full).isDirectory();
|
|
69
|
+
} catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (isDir) {
|
|
73
|
+
if (depth < 3 && walk(full, depth + 1)) return true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
files++;
|
|
77
|
+
if (CODE.has(extname(name).toLowerCase())) return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
};
|
|
81
|
+
if (walk(root, 1)) return "code";
|
|
82
|
+
return files === 0 ? "empty" : "notes";
|
|
83
|
+
}
|
|
84
|
+
var OTHER_AGENTS = [
|
|
85
|
+
[".claude", "Claude Code"],
|
|
86
|
+
[".mcp.json", "Claude Code"],
|
|
87
|
+
["opencode.json", "OpenCode"],
|
|
88
|
+
[".opencode", "OpenCode"],
|
|
89
|
+
[".codex", "Codex"],
|
|
90
|
+
[".gemini", "Gemini CLI"],
|
|
91
|
+
["GEMINI.md", "Gemini CLI"],
|
|
92
|
+
[".tabnine", "Tabnine"],
|
|
93
|
+
["TABNINE.md", "Tabnine"],
|
|
94
|
+
[".cursor", "Cursor"],
|
|
95
|
+
[".cursorrules", "Cursor"],
|
|
96
|
+
[".github/copilot-instructions.md", "Copilot"]
|
|
97
|
+
];
|
|
98
|
+
function initTip(root) {
|
|
99
|
+
const others = [
|
|
100
|
+
...new Set(OTHER_AGENTS.filter(([p]) => existsSync(join(root, p))).map(([, a]) => a))
|
|
101
|
+
];
|
|
102
|
+
const hasInstructions = ["AGENTS.md", "CLAUDE.md", "GARUDA.md"].some(
|
|
103
|
+
(f) => existsSync(join(root, f))
|
|
104
|
+
);
|
|
105
|
+
if (others.length > 0 && !existsSync(join(root, ".garuda"))) {
|
|
106
|
+
return `Found files of ${others.join(", ")}. Type /init to bring their commands, MCP servers and rules over.`;
|
|
107
|
+
}
|
|
108
|
+
if (hasInstructions) return void 0;
|
|
109
|
+
return folderKind(root) === "code" ? "Type /init to write AGENTS.md: build commands, structure and conventions for the agent." : "This folder has no code yet. Type /init to start a project here.";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
folderKind,
|
|
114
|
+
initTip
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=chunk-42DSA4EA.js.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/approver.ts
|
|
4
|
+
import { styleText } from "util";
|
|
5
|
+
var TerminalApprover = class {
|
|
6
|
+
/** Set by the CLI: it aborts the current turn. */
|
|
7
|
+
onInterrupt = () => {
|
|
8
|
+
};
|
|
9
|
+
/** One question at a time (0.14.1, review): two prompts at once mixed their keys. */
|
|
10
|
+
ask(request, signal) {
|
|
11
|
+
const next = this.asking === 0 ? this.askNow(request, signal) : this.queue.then(() => this.askNow(request, signal));
|
|
12
|
+
this.asking++;
|
|
13
|
+
this.queue = next.catch(() => {
|
|
14
|
+
}).finally(() => {
|
|
15
|
+
this.asking--;
|
|
16
|
+
});
|
|
17
|
+
return next;
|
|
18
|
+
}
|
|
19
|
+
asking = 0;
|
|
20
|
+
queue = Promise.resolve();
|
|
21
|
+
async askNow(request, signal) {
|
|
22
|
+
signal.throwIfAborted();
|
|
23
|
+
const out = process.stderr;
|
|
24
|
+
out.write(`
|
|
25
|
+
${header(request)}
|
|
26
|
+
${colorPreview(request)}
|
|
27
|
+
`);
|
|
28
|
+
if (!process.stdin.isTTY) {
|
|
29
|
+
out.write("No terminal to ask for approval, so Garuda denies this call.\n");
|
|
30
|
+
return "deny";
|
|
31
|
+
}
|
|
32
|
+
const sessionLabel = request.labels?.session ?? (request.target.kind === "command" ? "Yes, and allow this exact command for this session" : request.target.kind === "url" ? `Yes, and allow ${request.target.host} for this session` : `Yes, and allow all ${request.tool} calls for this session`);
|
|
33
|
+
const { select } = await import("@inquirer/prompts");
|
|
34
|
+
try {
|
|
35
|
+
return await select(
|
|
36
|
+
{
|
|
37
|
+
message: request.question ?? "Allow?",
|
|
38
|
+
choices: [
|
|
39
|
+
{ name: request.labels?.once ?? "Yes, once", value: "once" },
|
|
40
|
+
{ name: sessionLabel, value: "session" },
|
|
41
|
+
{ name: request.labels?.deny ?? "No, deny", value: "deny" }
|
|
42
|
+
].filter((c) => request.choices?.includes(c.value) ?? true)
|
|
43
|
+
},
|
|
44
|
+
{ signal, output: out }
|
|
45
|
+
);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.name === "ExitPromptError") this.onInterrupt();
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var SwitchApprover = class {
|
|
53
|
+
constructor(current) {
|
|
54
|
+
this.current = current;
|
|
55
|
+
}
|
|
56
|
+
current;
|
|
57
|
+
/** Called before each question: the chat's notifier (0.6). */
|
|
58
|
+
onAsk = () => {
|
|
59
|
+
};
|
|
60
|
+
ask(request, signal) {
|
|
61
|
+
this.onAsk(request);
|
|
62
|
+
return this.current.ask(request, signal);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function visible(text) {
|
|
66
|
+
return text.replace(/[\u0000-\u0008\u000b-\u001f]/g, (c) => String.fromCodePoint(9216 + c.charCodeAt(0))).replace(/\u007f/g, "\u2421").replace(
|
|
67
|
+
/[\u0080-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/gu,
|
|
68
|
+
(c) => `[U+${(c.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}]`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
function hasHidden(text) {
|
|
72
|
+
return visible(text) !== text;
|
|
73
|
+
}
|
|
74
|
+
var HIDDEN_WARNING = "! This holds hidden or control characters, shown as \u240D, \u241B or [U+\u2026]. Read it with care.";
|
|
75
|
+
function headerText(request) {
|
|
76
|
+
const { tool, target, isolation } = request;
|
|
77
|
+
if (request.title !== void 0) return visible(request.title);
|
|
78
|
+
if (target.kind === "command") {
|
|
79
|
+
const where = isolation === "none" ? "on your machine, no sandbox" : target.outsideSandbox ? "OUTSIDE the sandbox: network on, writes anywhere" : `sandbox: ${isolation}`;
|
|
80
|
+
return `${tool} wants to run a command (${where}):`;
|
|
81
|
+
}
|
|
82
|
+
if (target.kind === "input") return `${tool} wants to run with this input:`;
|
|
83
|
+
if (target.kind === "url") return `${tool} wants to fetch from ${visible(target.host)}:`;
|
|
84
|
+
return `${tool} wants to change ${visible(target.path)}:`;
|
|
85
|
+
}
|
|
86
|
+
function header(request) {
|
|
87
|
+
return styleText("bold", headerText(request));
|
|
88
|
+
}
|
|
89
|
+
function colorPreview({ target, preview }) {
|
|
90
|
+
const warning = hasHidden(preview) ? `
|
|
91
|
+
${styleText("yellow", ` ${HIDDEN_WARNING}`)}` : "";
|
|
92
|
+
if (target.kind === "command") return `${styleText("cyan", ` $ ${visible(preview)}`)}${warning}`;
|
|
93
|
+
if (target.kind === "url") return `${visible(preview)}${warning}`;
|
|
94
|
+
return `${colorDiff(visible(preview))}${warning}`;
|
|
95
|
+
}
|
|
96
|
+
function colorDiff(text) {
|
|
97
|
+
return text.split("\n").map((line) => {
|
|
98
|
+
if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("diff --git"))
|
|
99
|
+
return styleText("bold", line);
|
|
100
|
+
if (line.startsWith("+")) return styleText("green", line);
|
|
101
|
+
if (line.startsWith("-")) return styleText("red", line);
|
|
102
|
+
if (line.startsWith("@@")) return styleText("cyan", line);
|
|
103
|
+
return line;
|
|
104
|
+
}).join("\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
TerminalApprover,
|
|
109
|
+
SwitchApprover,
|
|
110
|
+
visible,
|
|
111
|
+
hasHidden,
|
|
112
|
+
HIDDEN_WARNING,
|
|
113
|
+
headerText,
|
|
114
|
+
header,
|
|
115
|
+
colorPreview,
|
|
116
|
+
colorDiff
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=chunk-4JSUUDFQ.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkAddress,
|
|
4
|
+
isIpLiteral
|
|
5
|
+
} from "./chunk-ATW7BNIG.js";
|
|
6
|
+
|
|
7
|
+
// src/net/pinnedFetch.ts
|
|
8
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
9
|
+
import { request as httpRequest } from "http";
|
|
10
|
+
import { request as httpsRequest } from "https";
|
|
11
|
+
import { Readable } from "stream";
|
|
12
|
+
var BlockedAddressError = class extends Error {
|
|
13
|
+
};
|
|
14
|
+
var defaultResolve = (host) => dnsLookup(host, { all: true, verbatim: true });
|
|
15
|
+
function pinnedFetch(options = {}) {
|
|
16
|
+
const resolve = options.resolve ?? defaultResolve;
|
|
17
|
+
return (async (input, init = {}) => {
|
|
18
|
+
const url = new URL(input instanceof Request ? input.url : input.toString());
|
|
19
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
20
|
+
throw new BlockedAddressError(`Only http and https, not ${url.protocol}`);
|
|
21
|
+
}
|
|
22
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
23
|
+
const addresses = isIpLiteral(host) ? [{ address: host, family: host.includes(":") ? 6 : 4 }] : await resolve(host);
|
|
24
|
+
if (addresses.length === 0) throw new BlockedAddressError(`${host} has no address.`);
|
|
25
|
+
for (const a of addresses) {
|
|
26
|
+
const verdict = checkAddress(a.address, false);
|
|
27
|
+
if (!verdict.ok) {
|
|
28
|
+
throw new BlockedAddressError(
|
|
29
|
+
`${host} resolves to a ${verdict.range} address (${a.address}). A project's MCP server must be public.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const pinned = addresses[0];
|
|
34
|
+
const lookup = (_host, opts, callback) => {
|
|
35
|
+
if (opts.all) callback(null, [pinned]);
|
|
36
|
+
else callback(null, pinned.address, pinned.family);
|
|
37
|
+
};
|
|
38
|
+
const headers = new Headers(
|
|
39
|
+
init.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
40
|
+
);
|
|
41
|
+
const body = await bodyBytes(
|
|
42
|
+
init.body ?? (input instanceof Request ? await input.arrayBuffer() : void 0)
|
|
43
|
+
);
|
|
44
|
+
if (init.body instanceof URLSearchParams && !headers.has("content-type")) {
|
|
45
|
+
headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
46
|
+
}
|
|
47
|
+
if (body !== void 0) headers.set("content-length", String(body.byteLength));
|
|
48
|
+
const method = init.method ?? (input instanceof Request ? input.method : "GET");
|
|
49
|
+
const signal = init.signal ?? void 0;
|
|
50
|
+
const request = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
51
|
+
return new Promise((resolvePromise, reject) => {
|
|
52
|
+
const req = request(
|
|
53
|
+
url,
|
|
54
|
+
{
|
|
55
|
+
method,
|
|
56
|
+
headers: Object.fromEntries(headers.entries()),
|
|
57
|
+
lookup,
|
|
58
|
+
...url.protocol === "https:" ? { servername: isIpLiteral(host) ? void 0 : host } : {},
|
|
59
|
+
...signal === void 0 || signal === null ? {} : { signal }
|
|
60
|
+
},
|
|
61
|
+
(res) => {
|
|
62
|
+
const status = res.statusCode ?? 0;
|
|
63
|
+
const responseHeaders = new Headers();
|
|
64
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
65
|
+
if (value === void 0) continue;
|
|
66
|
+
for (const v of Array.isArray(value) ? value : [value]) responseHeaders.append(key, v);
|
|
67
|
+
}
|
|
68
|
+
const empty = status === 204 || status === 304 || method === "HEAD";
|
|
69
|
+
if (empty) res.resume();
|
|
70
|
+
resolvePromise(
|
|
71
|
+
new Response(empty ? null : Readable.toWeb(res), {
|
|
72
|
+
status,
|
|
73
|
+
statusText: res.statusMessage ?? "",
|
|
74
|
+
headers: responseHeaders
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
req.on("error", reject);
|
|
80
|
+
if (body !== void 0) req.write(body);
|
|
81
|
+
req.end();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function bodyBytes(body) {
|
|
86
|
+
if (body === void 0 || body === null) return void 0;
|
|
87
|
+
if (typeof body === "string") return new TextEncoder().encode(body);
|
|
88
|
+
if (body instanceof Uint8Array) return body;
|
|
89
|
+
if (body instanceof ArrayBuffer) return new Uint8Array(body);
|
|
90
|
+
if (body instanceof URLSearchParams) return new TextEncoder().encode(body.toString());
|
|
91
|
+
return new Uint8Array(
|
|
92
|
+
await new Response(body).arrayBuffer()
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
defaultResolve,
|
|
98
|
+
pinnedFetch
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=chunk-4P26OKTP.js.map
|