@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,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
cleanLine
|
|
4
|
+
} from "./chunk-PZS3V6P3.js";
|
|
5
|
+
import {
|
|
6
|
+
EFFORTS
|
|
7
|
+
} from "./chunk-SI2QJYCR.js";
|
|
8
|
+
import {
|
|
9
|
+
formatRule
|
|
10
|
+
} from "./chunk-3NJNVZTK.js";
|
|
11
|
+
|
|
12
|
+
// src/model/pricing.ts
|
|
13
|
+
var DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
14
|
+
var M = 1e6;
|
|
15
|
+
var ALWAYS = { mode: "always", efforts: EFFORTS };
|
|
16
|
+
var OPTIONAL = { mode: "optional", efforts: EFFORTS };
|
|
17
|
+
var OPTIONAL_46 = {
|
|
18
|
+
mode: "optional",
|
|
19
|
+
efforts: EFFORTS.filter((e) => e !== "xhigh")
|
|
20
|
+
};
|
|
21
|
+
var KNOWN = [
|
|
22
|
+
["claude-fable-5-1", { price: p(10, 50, 0.25, 12.5), contextWindow: M, thinking: ALWAYS }],
|
|
23
|
+
["claude-fable-5", { price: p(10, 50, 1, 12.5), contextWindow: M, thinking: ALWAYS }],
|
|
24
|
+
["claude-opus-5-5", { price: p(4, 20, 0.2, 5), contextWindow: M, thinking: ALWAYS }],
|
|
25
|
+
["claude-opus-5", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: ALWAYS }],
|
|
26
|
+
["claude-opus-4-8", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: OPTIONAL }],
|
|
27
|
+
["claude-opus-4-7", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: OPTIONAL }],
|
|
28
|
+
["claude-opus-4-6", { price: p(5, 25, 0.5, 6.25), contextWindow: M, thinking: OPTIONAL_46 }],
|
|
29
|
+
["claude-opus-4-5", { price: p(5, 25, 0.5, 6.25), contextWindow: 2e5 }],
|
|
30
|
+
["claude-sonnet-5", { price: p(2, 10, 0.2, 2.5), contextWindow: M, thinking: ALWAYS }],
|
|
31
|
+
["claude-sonnet-4-6", { price: p(3, 15, 0.3, 3.75), contextWindow: M, thinking: OPTIONAL_46 }],
|
|
32
|
+
["claude-sonnet-4-5", { price: p(3, 15, 0.3, 3.75), contextWindow: 2e5 }],
|
|
33
|
+
["claude-haiku-4-5", { price: p(1, 5, 0.1, 1.25), contextWindow: 2e5 }]
|
|
34
|
+
];
|
|
35
|
+
function p(input, output, cacheRead, cacheWrite) {
|
|
36
|
+
return { input, output, cacheRead, cacheWrite };
|
|
37
|
+
}
|
|
38
|
+
function aliasModel(alias) {
|
|
39
|
+
if (!/^(haiku|sonnet|opus|fable)$/.test(alias)) return void 0;
|
|
40
|
+
return KNOWN.find(([prefix]) => prefix.startsWith(`claude-${alias}-`))?.[0];
|
|
41
|
+
}
|
|
42
|
+
function knownModels() {
|
|
43
|
+
return KNOWN.map(([id, info]) => ({ id, info }));
|
|
44
|
+
}
|
|
45
|
+
function lookupModel(modelId) {
|
|
46
|
+
let best;
|
|
47
|
+
for (const [prefix, info] of KNOWN) {
|
|
48
|
+
if (modelId.startsWith(prefix) && prefix.length > (best?.prefix.length ?? 0)) {
|
|
49
|
+
best = { prefix, info };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return best?.info ?? { contextWindow: DEFAULT_CONTEXT_WINDOW };
|
|
53
|
+
}
|
|
54
|
+
var WEB_SEARCH_USD = 0.01;
|
|
55
|
+
function costOf(usage, price, factor = 1) {
|
|
56
|
+
return (usage.inputTokens * price.input + usage.outputTokens * price.output + usage.cacheReadTokens * price.cacheRead + usage.cacheWriteTokens * price.cacheWrite) / M * factor + (usage.webSearches ?? 0) * WEB_SEARCH_USD;
|
|
57
|
+
}
|
|
58
|
+
function responseCost(response, price) {
|
|
59
|
+
return costOf(response.usage, price, response.priceFactor ?? 1);
|
|
60
|
+
}
|
|
61
|
+
function totalTokens(usage) {
|
|
62
|
+
return usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
63
|
+
}
|
|
64
|
+
function contextSize(usage) {
|
|
65
|
+
return totalTokens(usage);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/permissions/projectSettings.ts
|
|
69
|
+
import { createHash } from "crypto";
|
|
70
|
+
function settingsRisk(settings) {
|
|
71
|
+
const risk = {
|
|
72
|
+
executorHost: settings.executor === "host",
|
|
73
|
+
allow: settings.allow.map(formatRule),
|
|
74
|
+
writePaths: [...settings.sandbox?.writePaths ?? []],
|
|
75
|
+
envAllow: [...settings.envAllow],
|
|
76
|
+
allowLocalhost: settings.web?.allowLocalhost === true
|
|
77
|
+
};
|
|
78
|
+
const formatterCommands = Object.entries(settings.formatters?.commands ?? {}).flatMap(
|
|
79
|
+
([name, f]) => f === false ? [] : [`${name}: ${f.command.join(" ")}`]
|
|
80
|
+
);
|
|
81
|
+
if (formatterCommands.length > 0) risk.formatterCommands = formatterCommands;
|
|
82
|
+
const none = !risk.executorHost && risk.allow.length === 0 && risk.writePaths.length === 0 && risk.envAllow.length === 0 && !risk.allowLocalhost && risk.formatterCommands === void 0;
|
|
83
|
+
return none ? void 0 : risk;
|
|
84
|
+
}
|
|
85
|
+
function settingsRiskHash(risk) {
|
|
86
|
+
return createHash("sha256").update(JSON.stringify(risk)).digest("hex");
|
|
87
|
+
}
|
|
88
|
+
function settingsRiskLines(risk) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
if (risk.executorHost) lines.push('executor: "host" (commands run with no OS sandbox)');
|
|
91
|
+
if (risk.allow.length > 0) {
|
|
92
|
+
lines.push(`permissions.allow (no question for these): ${risk.allow.join(", ")}`);
|
|
93
|
+
}
|
|
94
|
+
if (risk.writePaths.length > 0) {
|
|
95
|
+
lines.push(`sandbox.writePaths (commands may write there): ${risk.writePaths.join(", ")}`);
|
|
96
|
+
}
|
|
97
|
+
if (risk.envAllow.length > 0) {
|
|
98
|
+
lines.push(`env.allow (commands see these variables): ${risk.envAllow.join(", ")}`);
|
|
99
|
+
}
|
|
100
|
+
if (risk.allowLocalhost) lines.push("web.allowLocalhost (web_fetch may reach this machine)");
|
|
101
|
+
if (risk.formatterCommands !== void 0) {
|
|
102
|
+
lines.push(
|
|
103
|
+
`formatters.commands (run after each edit): ${risk.formatterCommands.map(cleanLine).join("; ")}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return lines;
|
|
107
|
+
}
|
|
108
|
+
function withoutRisk(settings) {
|
|
109
|
+
const { sandbox, web, formatters, ...rest } = settings;
|
|
110
|
+
const { writePaths: _writePaths, ...sandboxRest } = sandbox ?? {};
|
|
111
|
+
const keptCommands = Object.fromEntries(
|
|
112
|
+
Object.entries(formatters?.commands ?? {}).filter(([, f]) => f === false)
|
|
113
|
+
);
|
|
114
|
+
return {
|
|
115
|
+
...rest,
|
|
116
|
+
...formatters === void 0 ? {} : { formatters: { ...formatters, commands: keptCommands } },
|
|
117
|
+
executor: settings.executor === "host" ? "auto" : settings.executor,
|
|
118
|
+
allow: [],
|
|
119
|
+
envAllow: [],
|
|
120
|
+
...sandbox === void 0 ? {} : { sandbox: sandboxRest },
|
|
121
|
+
...web === void 0 ? {} : { web: { ...web, allowLocalhost: false } }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async function gateProjectSettings(options) {
|
|
125
|
+
const risk = settingsRisk(options.settings);
|
|
126
|
+
if (risk === void 0) return { settings: options.settings };
|
|
127
|
+
const hash = settingsRiskHash(risk);
|
|
128
|
+
const known = options.trust?.settingsHash(options.root);
|
|
129
|
+
if (known === hash) return { settings: options.settings };
|
|
130
|
+
const lines = settingsRiskLines(risk);
|
|
131
|
+
if (options.ask !== void 0) {
|
|
132
|
+
const choice = await options.ask({
|
|
133
|
+
tool: "settings",
|
|
134
|
+
target: { kind: "input", json: "" },
|
|
135
|
+
title: "Project settings that loosen safety",
|
|
136
|
+
preview: [
|
|
137
|
+
`This project's .garuda/settings.json ${known === void 0 ? "asks for" : "changed and now asks for"}:`,
|
|
138
|
+
...lines.map((line) => ` - ${line}`),
|
|
139
|
+
"A cloned repository can ship this file. Without your yes, Garuda uses safe defaults for these",
|
|
140
|
+
"parts; the rest of the file applies."
|
|
141
|
+
].join("\n"),
|
|
142
|
+
isolation: options.isolation ?? "os",
|
|
143
|
+
question: "Use these project settings?",
|
|
144
|
+
choices: ["once", "session", "deny"],
|
|
145
|
+
labels: {
|
|
146
|
+
once: "Yes, for this run",
|
|
147
|
+
session: "Yes, and remember (asks again when they change)",
|
|
148
|
+
deny: "No, use safe defaults"
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
if (choice === "session") await options.trust?.setSettingsHash(options.root, hash);
|
|
152
|
+
if (choice !== "deny") return { settings: options.settings };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
settings: withoutRisk(options.settings),
|
|
156
|
+
notice: [
|
|
157
|
+
`Project settings not applied (not approved): ${lines.join("; ")}.`,
|
|
158
|
+
"Start the chat in a terminal and answer yes to use them."
|
|
159
|
+
].join(" ")
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
aliasModel,
|
|
165
|
+
knownModels,
|
|
166
|
+
lookupModel,
|
|
167
|
+
WEB_SEARCH_USD,
|
|
168
|
+
responseCost,
|
|
169
|
+
totalTokens,
|
|
170
|
+
contextSize,
|
|
171
|
+
gateProjectSettings
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=chunk-HRGX53VC.js.map
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
commandMatches,
|
|
4
|
+
commandParts,
|
|
5
|
+
hostMatches,
|
|
6
|
+
pathMatches
|
|
7
|
+
} from "./chunk-3NJNVZTK.js";
|
|
8
|
+
|
|
9
|
+
// src/permissions/policy.ts
|
|
10
|
+
import { existsSync } from "fs";
|
|
11
|
+
import { readFile } from "fs/promises";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var POLICY_FILE = join(".garuda", "policy.json");
|
|
16
|
+
function managedPolicyPath(platform = process.platform) {
|
|
17
|
+
if (platform === "darwin") return "/Library/Application Support/Garuda/policy.json";
|
|
18
|
+
if (platform === "linux") return "/etc/garuda/policy.json";
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
function ignoredProjectPolicy(root) {
|
|
22
|
+
const file = join(root, POLICY_FILE);
|
|
23
|
+
return existsSync(file) ? file : void 0;
|
|
24
|
+
}
|
|
25
|
+
var policySchema = z.strictObject({
|
|
26
|
+
disallowedCommands: z.array(z.string()).optional(),
|
|
27
|
+
requireSandbox: z.boolean().optional(),
|
|
28
|
+
denyPaths: z.array(z.string()).optional(),
|
|
29
|
+
allowedModels: z.array(z.string()).optional(),
|
|
30
|
+
network: z.strictObject({
|
|
31
|
+
blockedHosts: z.array(z.string()).optional(),
|
|
32
|
+
strictAllowlist: z.boolean().optional()
|
|
33
|
+
}).optional(),
|
|
34
|
+
limits: z.strictObject({
|
|
35
|
+
maxSteps: z.number().int().positive().optional(),
|
|
36
|
+
tokenBudget: z.number().int().positive().optional()
|
|
37
|
+
}).optional(),
|
|
38
|
+
audit: z.strictObject({
|
|
39
|
+
enabled: z.boolean().optional(),
|
|
40
|
+
level: z.enum(["all", "mutations", "denials"]).optional()
|
|
41
|
+
}).optional()
|
|
42
|
+
});
|
|
43
|
+
function parsePolicy(json) {
|
|
44
|
+
const result = policySchema.safeParse(json);
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
throw new Error(`Invalid policy schema: ${z.prettifyError(result.error)}`);
|
|
47
|
+
}
|
|
48
|
+
const data = result.data;
|
|
49
|
+
return {
|
|
50
|
+
...data.disallowedCommands === void 0 ? {} : { disallowedCommands: data.disallowedCommands },
|
|
51
|
+
...data.requireSandbox === void 0 ? {} : { requireSandbox: data.requireSandbox },
|
|
52
|
+
...data.denyPaths === void 0 ? {} : { denyPaths: data.denyPaths },
|
|
53
|
+
...data.allowedModels === void 0 ? {} : { allowedModels: data.allowedModels },
|
|
54
|
+
...data.network === void 0 ? {} : {
|
|
55
|
+
network: {
|
|
56
|
+
...data.network.blockedHosts === void 0 ? {} : { blockedHosts: data.network.blockedHosts },
|
|
57
|
+
...data.network.strictAllowlist === void 0 ? {} : { strictAllowlist: data.network.strictAllowlist }
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
...data.limits === void 0 ? {} : {
|
|
61
|
+
limits: {
|
|
62
|
+
...data.limits.maxSteps === void 0 ? {} : { maxSteps: data.limits.maxSteps },
|
|
63
|
+
...data.limits.tokenBudget === void 0 ? {} : { tokenBudget: data.limits.tokenBudget }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
...data.audit === void 0 ? {} : {
|
|
67
|
+
audit: {
|
|
68
|
+
...data.audit.enabled === void 0 ? {} : { enabled: data.audit.enabled },
|
|
69
|
+
...data.audit.level === void 0 ? {} : { level: data.audit.level }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function readPolicyFile(file) {
|
|
75
|
+
let text;
|
|
76
|
+
try {
|
|
77
|
+
text = await readFile(file, "utf8");
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error.code === "ENOENT") return void 0;
|
|
80
|
+
throw new Error(`${file}: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
let json;
|
|
83
|
+
try {
|
|
84
|
+
json = JSON.parse(text);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`${file}: invalid JSON: ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return parsePolicy(json);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new Error(`${file}: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function mergePolicies(user, managed) {
|
|
95
|
+
if (user === void 0) return managed;
|
|
96
|
+
if (managed === void 0) return user;
|
|
97
|
+
const union = (a, b) => a === void 0 && b === void 0 ? void 0 : [.../* @__PURE__ */ new Set([...a ?? [], ...b ?? []])];
|
|
98
|
+
const either = (a, b) => a === void 0 && b === void 0 ? void 0 : a === true || b === true;
|
|
99
|
+
const lower = (a, b) => a === void 0 ? b : b === void 0 ? a : Math.min(a, b);
|
|
100
|
+
const disallowedCommands = union(user.disallowedCommands, managed.disallowedCommands);
|
|
101
|
+
const denyPaths = union(user.denyPaths, managed.denyPaths);
|
|
102
|
+
const requireSandbox = either(user.requireSandbox, managed.requireSandbox);
|
|
103
|
+
const allowedModels = managed.allowedModels ?? user.allowedModels;
|
|
104
|
+
const blockedHosts = union(user.network?.blockedHosts, managed.network?.blockedHosts);
|
|
105
|
+
const strictAllowlist = either(user.network?.strictAllowlist, managed.network?.strictAllowlist);
|
|
106
|
+
const maxSteps = lower(user.limits?.maxSteps, managed.limits?.maxSteps);
|
|
107
|
+
const tokenBudget = lower(user.limits?.tokenBudget, managed.limits?.tokenBudget);
|
|
108
|
+
const audit = managed.audit ?? user.audit;
|
|
109
|
+
return {
|
|
110
|
+
...disallowedCommands === void 0 ? {} : { disallowedCommands },
|
|
111
|
+
...requireSandbox === void 0 ? {} : { requireSandbox },
|
|
112
|
+
...denyPaths === void 0 ? {} : { denyPaths },
|
|
113
|
+
...allowedModels === void 0 ? {} : { allowedModels },
|
|
114
|
+
...blockedHosts === void 0 && strictAllowlist === void 0 ? {} : {
|
|
115
|
+
network: {
|
|
116
|
+
...blockedHosts === void 0 ? {} : { blockedHosts },
|
|
117
|
+
...strictAllowlist === void 0 ? {} : { strictAllowlist }
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
...maxSteps === void 0 && tokenBudget === void 0 ? {} : {
|
|
121
|
+
limits: {
|
|
122
|
+
...maxSteps === void 0 ? {} : { maxSteps },
|
|
123
|
+
...tokenBudget === void 0 ? {} : { tokenBudget }
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
...audit === void 0 ? {} : { audit }
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function loadTeamPolicy(options = {}) {
|
|
130
|
+
const userFile = join(options.home ?? homedir(), POLICY_FILE);
|
|
131
|
+
const managedFile = "managed" in options ? options.managed : managedPolicyPath();
|
|
132
|
+
const user = await readPolicyFile(userFile);
|
|
133
|
+
const managed = managedFile === void 0 ? void 0 : await readPolicyFile(managedFile);
|
|
134
|
+
const policy = mergePolicies(user, managed);
|
|
135
|
+
if (policy === void 0) return void 0;
|
|
136
|
+
const sources = [
|
|
137
|
+
...managed === void 0 || managedFile === void 0 ? [] : [managedFile],
|
|
138
|
+
...user === void 0 ? [] : [userFile]
|
|
139
|
+
];
|
|
140
|
+
return { policy, sources };
|
|
141
|
+
}
|
|
142
|
+
function isCommandDisallowedByPolicy(policy, command) {
|
|
143
|
+
if (!policy.disallowedCommands || policy.disallowedCommands.length === 0) {
|
|
144
|
+
return { disallowed: false };
|
|
145
|
+
}
|
|
146
|
+
const parts = commandParts(command);
|
|
147
|
+
for (const pattern of policy.disallowedCommands) {
|
|
148
|
+
const hit = (part) => commandMatches(pattern, part);
|
|
149
|
+
if (parts.some(hit) || hit(command)) {
|
|
150
|
+
return {
|
|
151
|
+
disallowed: true,
|
|
152
|
+
pattern,
|
|
153
|
+
reason: `Command is disallowed by team policy: pattern "${pattern}".`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { disallowed: false };
|
|
158
|
+
}
|
|
159
|
+
function isSandboxRequiredByPolicy(policy, outsideSandbox) {
|
|
160
|
+
if (policy.requireSandbox === true && outsideSandbox) {
|
|
161
|
+
return {
|
|
162
|
+
disallowed: true,
|
|
163
|
+
reason: "The team policy requires the OS sandbox: this command would run outside it (outside_sandbox, or no sandbox on this machine)."
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return { disallowed: false };
|
|
167
|
+
}
|
|
168
|
+
function isPathDeniedByPolicy(policy, path) {
|
|
169
|
+
if (!policy.denyPaths || policy.denyPaths.length === 0) {
|
|
170
|
+
return { denied: false };
|
|
171
|
+
}
|
|
172
|
+
for (const pattern of policy.denyPaths) {
|
|
173
|
+
if (pathMatches(pattern, path)) {
|
|
174
|
+
return {
|
|
175
|
+
denied: true,
|
|
176
|
+
pattern,
|
|
177
|
+
reason: `Access to path "${path}" is disallowed by team policy: pattern "${pattern}".`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { denied: false };
|
|
182
|
+
}
|
|
183
|
+
function isModelAllowedByPolicy(policy, modelId) {
|
|
184
|
+
if (!policy.allowedModels || policy.allowedModels.length === 0) {
|
|
185
|
+
return { allowed: true };
|
|
186
|
+
}
|
|
187
|
+
const allowed = policy.allowedModels.some((pattern) => commandMatches(pattern, modelId));
|
|
188
|
+
if (!allowed) {
|
|
189
|
+
return {
|
|
190
|
+
allowed: false,
|
|
191
|
+
reason: `Model "${modelId}" is not permitted by team policy. Allowed: ${policy.allowedModels.join(", ")}.`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { allowed: true };
|
|
195
|
+
}
|
|
196
|
+
function assertModelAllowedByPolicy(policy, modelId) {
|
|
197
|
+
if (policy === void 0) return;
|
|
198
|
+
const decision = isModelAllowedByPolicy(policy, modelId);
|
|
199
|
+
if (!decision.allowed) {
|
|
200
|
+
throw new Error(decision.reason ?? `Model "${modelId}" is not permitted by team policy.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function isHostBlockedByPolicy(policy, host) {
|
|
204
|
+
if (!policy.network?.blockedHosts || policy.network.blockedHosts.length === 0) {
|
|
205
|
+
return { blocked: false };
|
|
206
|
+
}
|
|
207
|
+
const name = host.replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
208
|
+
for (const pattern of policy.network.blockedHosts) {
|
|
209
|
+
if (hostMatches(pattern, name)) {
|
|
210
|
+
return {
|
|
211
|
+
blocked: true,
|
|
212
|
+
pattern,
|
|
213
|
+
reason: `Network access to host "${host}" is blocked by team policy: pattern "${pattern}".`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { blocked: false };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
POLICY_FILE,
|
|
222
|
+
managedPolicyPath,
|
|
223
|
+
ignoredProjectPolicy,
|
|
224
|
+
parsePolicy,
|
|
225
|
+
mergePolicies,
|
|
226
|
+
loadTeamPolicy,
|
|
227
|
+
isCommandDisallowedByPolicy,
|
|
228
|
+
isSandboxRequiredByPolicy,
|
|
229
|
+
isPathDeniedByPolicy,
|
|
230
|
+
isModelAllowedByPolicy,
|
|
231
|
+
assertModelAllowedByPolicy,
|
|
232
|
+
isHostBlockedByPolicy
|
|
233
|
+
};
|
|
234
|
+
//# sourceMappingURL=chunk-HUIK3WXO.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MANAGED,
|
|
4
|
+
jdtlsInstallCommand,
|
|
5
|
+
managedDir
|
|
6
|
+
} from "./chunk-EU7PA45O.js";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_ENV_ALLOWLIST
|
|
9
|
+
} from "./chunk-SI2QJYCR.js";
|
|
10
|
+
|
|
11
|
+
// src/lsp/install.ts
|
|
12
|
+
import { mkdir } from "fs/promises";
|
|
13
|
+
var INSTALL_TIMEOUT_MS = 3e5;
|
|
14
|
+
var NPM_ENV = [
|
|
15
|
+
"HTTP_PROXY",
|
|
16
|
+
"HTTPS_PROXY",
|
|
17
|
+
"NO_PROXY",
|
|
18
|
+
"http_proxy",
|
|
19
|
+
"https_proxy",
|
|
20
|
+
"no_proxy",
|
|
21
|
+
"NODE_EXTRA_CA_CERTS",
|
|
22
|
+
"npm_config_registry",
|
|
23
|
+
"NPM_CONFIG_REGISTRY"
|
|
24
|
+
];
|
|
25
|
+
function installCommand(language, dir) {
|
|
26
|
+
const managed = MANAGED[language];
|
|
27
|
+
if (managed.kind === "eclipse") return jdtlsInstallCommand(dir, quote);
|
|
28
|
+
return [
|
|
29
|
+
"npm",
|
|
30
|
+
"install",
|
|
31
|
+
"--prefix",
|
|
32
|
+
quote(dir),
|
|
33
|
+
"--ignore-scripts",
|
|
34
|
+
"--no-audit",
|
|
35
|
+
"--no-fund",
|
|
36
|
+
"--loglevel=error",
|
|
37
|
+
...managed.packages
|
|
38
|
+
].join(" ");
|
|
39
|
+
}
|
|
40
|
+
async function installServer(language, options) {
|
|
41
|
+
const dir = managedDir(language, options.home);
|
|
42
|
+
await mkdir(dir, { recursive: true });
|
|
43
|
+
const command = installCommand(language, dir);
|
|
44
|
+
const policy = {
|
|
45
|
+
root: dir,
|
|
46
|
+
sandbox: false,
|
|
47
|
+
writePaths: [],
|
|
48
|
+
denyWritePaths: [],
|
|
49
|
+
denyReadPaths: [],
|
|
50
|
+
network: true,
|
|
51
|
+
envAllowlist: [...DEFAULT_ENV_ALLOWLIST, ...NPM_ENV],
|
|
52
|
+
timeoutMs: INSTALL_TIMEOUT_MS,
|
|
53
|
+
maxOutputBytes: 2e4
|
|
54
|
+
};
|
|
55
|
+
const result = await options.executor.run(
|
|
56
|
+
command,
|
|
57
|
+
policy,
|
|
58
|
+
options.signal === void 0 ? {} : { signal: options.signal }
|
|
59
|
+
);
|
|
60
|
+
if (result.exitCode === 0) return { ok: true, dir, command };
|
|
61
|
+
const why = result.timedOut ? `npm took longer than ${INSTALL_TIMEOUT_MS / 1e3} s.` : [result.stderr.text, result.stdout.text].join("\n").trim().split("\n").slice(-8).join("\n");
|
|
62
|
+
return { ok: false, dir, command, output: why || `npm exited with ${result.exitCode}.` };
|
|
63
|
+
}
|
|
64
|
+
function quote(text) {
|
|
65
|
+
return /^[\w./:@-]+$/.test(text) ? text : `'${text.replaceAll("'", `'\\''`)}'`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
INSTALL_TIMEOUT_MS,
|
|
70
|
+
installCommand,
|
|
71
|
+
installServer
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=chunk-INRZR772.js.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
JobGitError,
|
|
4
|
+
git
|
|
5
|
+
} from "./chunk-2TAXSFWN.js";
|
|
6
|
+
|
|
7
|
+
// src/jobs/worktree.ts
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { existsSync, lstatSync, realpathSync } from "fs";
|
|
10
|
+
import { mkdir, symlink } from "fs/promises";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { basename, join } from "path";
|
|
13
|
+
var LINK_CANDIDATES = ["node_modules", ".venv", "venv"];
|
|
14
|
+
var NEVER_COMMIT = [
|
|
15
|
+
".garuda/sessions",
|
|
16
|
+
".garuda/index",
|
|
17
|
+
".garuda/evals",
|
|
18
|
+
".garuda/jobs",
|
|
19
|
+
".garuda/audit.jsonl"
|
|
20
|
+
];
|
|
21
|
+
function worktreeDir(root, id, home = homedir()) {
|
|
22
|
+
const hash = createHash("sha256").update(root).digest("hex").slice(0, 8);
|
|
23
|
+
return join(home, ".garuda", "worktrees", `${basename(root)}-${hash}`, id);
|
|
24
|
+
}
|
|
25
|
+
async function jobBase(executor, root) {
|
|
26
|
+
const head = await git(executor, root, ["rev-parse", "--verify", "HEAD"], { check: false });
|
|
27
|
+
if (head.exitCode !== 0) {
|
|
28
|
+
throw new JobGitError("A job needs a git repository with at least one commit.");
|
|
29
|
+
}
|
|
30
|
+
const commit = head.stdout.trim();
|
|
31
|
+
const stash = (await git(executor, root, ["stash", "create"])).stdout.trim();
|
|
32
|
+
const untracked = (await git(executor, root, ["ls-files", "--others", "--exclude-standard"])).stdout.split("\n").filter((l) => l !== "" && !l.startsWith(".garuda/")).length;
|
|
33
|
+
if (stash === "") return { commit, uncommitted: false, untracked };
|
|
34
|
+
const tree = `${stash}^{tree}`;
|
|
35
|
+
const base = await git(executor, root, [
|
|
36
|
+
"commit-tree",
|
|
37
|
+
tree,
|
|
38
|
+
"-p",
|
|
39
|
+
commit,
|
|
40
|
+
"-m",
|
|
41
|
+
"Garuda job base: uncommitted changes at scheduling time"
|
|
42
|
+
]);
|
|
43
|
+
return { commit: base.stdout.trim(), uncommitted: true, untracked };
|
|
44
|
+
}
|
|
45
|
+
function cleanArgs(links = LINK_CANDIDATES) {
|
|
46
|
+
return ["clean", "-fdq", ...links.flatMap((name) => ["-e", `/${name}`])];
|
|
47
|
+
}
|
|
48
|
+
function linkCandidates(root) {
|
|
49
|
+
return LINK_CANDIDATES.filter((name) => {
|
|
50
|
+
try {
|
|
51
|
+
return lstatSync(join(root, name)).isDirectory();
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async function prepareWorktree(executor, job, signal) {
|
|
58
|
+
if (!existsSync(join(job.worktree, ".git"))) {
|
|
59
|
+
await mkdir(join(job.worktree, ".."), { recursive: true, mode: 448 });
|
|
60
|
+
const branch = await git(
|
|
61
|
+
executor,
|
|
62
|
+
job.root,
|
|
63
|
+
["rev-parse", "--verify", `refs/heads/${job.branch}`],
|
|
64
|
+
{ check: false, ...signal === void 0 ? {} : { signal } }
|
|
65
|
+
);
|
|
66
|
+
const args = branch.exitCode === 0 ? ["worktree", "add", job.worktree, job.branch] : ["worktree", "add", "-b", job.branch, job.worktree, job.base];
|
|
67
|
+
await git(executor, job.root, args, signal === void 0 ? {} : { signal });
|
|
68
|
+
}
|
|
69
|
+
const links = [];
|
|
70
|
+
for (const name of job.links) {
|
|
71
|
+
const target = join(job.root, name);
|
|
72
|
+
const link = join(job.worktree, name);
|
|
73
|
+
if (!existsSync(target)) continue;
|
|
74
|
+
if (!existsSync(link)) await symlink(target, link, "dir");
|
|
75
|
+
links.push(realpathSync(target));
|
|
76
|
+
}
|
|
77
|
+
return { links };
|
|
78
|
+
}
|
|
79
|
+
async function commitJob(executor, job, message) {
|
|
80
|
+
const exclude = [...NEVER_COMMIT, ...job.links].map((p) => `:(exclude)${p}`);
|
|
81
|
+
await git(executor, job.worktree, ["add", "-A", "--", ".", ...exclude]);
|
|
82
|
+
const staged = await git(executor, job.worktree, ["diff", "--cached", "--quiet"], {
|
|
83
|
+
check: false
|
|
84
|
+
});
|
|
85
|
+
if (staged.exitCode === 0) return void 0;
|
|
86
|
+
await git(executor, job.worktree, ["commit", "--no-verify", "-q", "-m", message]);
|
|
87
|
+
return (await git(executor, job.worktree, ["rev-parse", "HEAD"])).stdout.trim();
|
|
88
|
+
}
|
|
89
|
+
async function jobChanges(executor, job) {
|
|
90
|
+
const range = [job.base, job.branch];
|
|
91
|
+
const names = await git(executor, job.root, [
|
|
92
|
+
"diff",
|
|
93
|
+
"--no-renames",
|
|
94
|
+
"--name-status",
|
|
95
|
+
"-z",
|
|
96
|
+
...range
|
|
97
|
+
]);
|
|
98
|
+
const counts = await git(executor, job.root, [
|
|
99
|
+
"diff",
|
|
100
|
+
"--no-renames",
|
|
101
|
+
"--numstat",
|
|
102
|
+
"-z",
|
|
103
|
+
...range
|
|
104
|
+
]);
|
|
105
|
+
return parseChanges(names, counts);
|
|
106
|
+
}
|
|
107
|
+
function parseChanges(names, counts) {
|
|
108
|
+
const lines = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const entry of counts.stdout.split("\0")) {
|
|
110
|
+
const m = /^(\d+|-)\t(\d+|-)\t(.*)$/s.exec(entry);
|
|
111
|
+
if (m === null) continue;
|
|
112
|
+
lines.set(m[3], m[1] === "-" ? {} : { added: Number(m[1]), removed: Number(m[2]) });
|
|
113
|
+
}
|
|
114
|
+
const parts = names.stdout.split("\0").filter((p) => p !== "");
|
|
115
|
+
const files = [];
|
|
116
|
+
for (let i = 0; i + 1 < parts.length; i += 2) {
|
|
117
|
+
const code = parts[i];
|
|
118
|
+
const path = parts[i + 1];
|
|
119
|
+
files.push({
|
|
120
|
+
status: code === "A" ? "added" : code === "D" ? "deleted" : "modified",
|
|
121
|
+
path,
|
|
122
|
+
...lines.get(path)
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return files;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
LINK_CANDIDATES,
|
|
130
|
+
worktreeDir,
|
|
131
|
+
jobBase,
|
|
132
|
+
cleanArgs,
|
|
133
|
+
linkCandidates,
|
|
134
|
+
prepareWorktree,
|
|
135
|
+
commitJob,
|
|
136
|
+
jobChanges
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=chunk-K3SIUOAG.js.map
|