@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
package/SECURITY.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
Garuda runs commands and edits files for a model, so we take security reports seriously.
|
|
4
|
+
|
|
5
|
+
## Supported versions
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| 0.14.x | Yes |
|
|
10
|
+
| Older | No |
|
|
11
|
+
|
|
12
|
+
## Report a vulnerability
|
|
13
|
+
|
|
14
|
+
Do not open a public issue. Use GitHub's private vulnerability reporting: in this repository, open
|
|
15
|
+
**Security**, then **Report a vulnerability**. Give:
|
|
16
|
+
|
|
17
|
+
- the version (`garuda --version`) and the platform (macOS or Linux, the sandbox in use);
|
|
18
|
+
- the steps to reproduce, and what you expected;
|
|
19
|
+
- the effect, for example a sandbox escape, a permission or team-policy bypass, a secret in a
|
|
20
|
+
session file or the audit log, or a write that the user did not approve.
|
|
21
|
+
|
|
22
|
+
You get an answer within 7 days. We agree a fix and a disclosure date with you, and we credit you in
|
|
23
|
+
the release notes unless you ask us not to.
|
|
24
|
+
|
|
25
|
+
## Scope
|
|
26
|
+
|
|
27
|
+
In scope: the permission engine and the team policy, the OS sandbox (Seatbelt, bubblewrap), the
|
|
28
|
+
network allowlist and its proxy, secret redaction, the audit log, and the consent for project
|
|
29
|
+
content (hooks, MCP servers, settings, commands, skills, agents).
|
|
30
|
+
|
|
31
|
+
Known limits are listed in the README under the status table. A report about one of them is still
|
|
32
|
+
welcome when it shows a new way to reach the same effect.
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/model/anthropic.ts
|
|
4
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
5
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
6
|
+
var WEB_SEARCH_TOOL = "web_search_20250305";
|
|
7
|
+
var AnthropicClient = class {
|
|
8
|
+
/** Claude's web search runs on Anthropic's servers (0.6). */
|
|
9
|
+
serverTools = ["web_search"];
|
|
10
|
+
client;
|
|
11
|
+
model;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.model = options.model;
|
|
14
|
+
this.client = options.client ?? new Anthropic(clientOptions(options.apiKey));
|
|
15
|
+
}
|
|
16
|
+
async *stream(request, options) {
|
|
17
|
+
const stream = this.client.messages.stream(
|
|
18
|
+
toWireParams(this.model, request),
|
|
19
|
+
options?.signal === void 0 ? {} : { signal: options.signal }
|
|
20
|
+
);
|
|
21
|
+
for await (const event of stream) {
|
|
22
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
23
|
+
yield { type: "text_delta", text: event.delta.text };
|
|
24
|
+
} else if (event.type === "content_block_delta" && event.delta.type === "thinking_delta" && event.delta.thinking !== "" && request.thinking?.hide !== true) {
|
|
25
|
+
yield { type: "thinking_delta", text: event.delta.thinking };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const final = await stream.finalMessage();
|
|
29
|
+
const response = fromWireMessage(final);
|
|
30
|
+
yield {
|
|
31
|
+
type: "response",
|
|
32
|
+
response: request.thinking?.hide === true ? withHiddenThinking(response) : response
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function withHiddenThinking(response) {
|
|
37
|
+
return {
|
|
38
|
+
...response,
|
|
39
|
+
content: response.content.map((b) => b.type === "thinking" ? { ...b, text: "" } : b)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
var STREAM_IDLE_MS = 20 * 6e4;
|
|
43
|
+
var dispatchers = /* @__PURE__ */ new Map();
|
|
44
|
+
function streamFetch(idleMs = STREAM_IDLE_MS) {
|
|
45
|
+
let agent = dispatchers.get(idleMs);
|
|
46
|
+
if (agent === void 0) {
|
|
47
|
+
agent = new Agent({ bodyTimeout: idleMs });
|
|
48
|
+
dispatchers.set(idleMs, agent);
|
|
49
|
+
}
|
|
50
|
+
const dispatcher = agent;
|
|
51
|
+
return ((input, init) => undiciFetch(input, { ...init, dispatcher }));
|
|
52
|
+
}
|
|
53
|
+
function clientOptions(apiKey) {
|
|
54
|
+
return { ...apiKey === void 0 ? {} : { apiKey }, fetch: streamFetch() };
|
|
55
|
+
}
|
|
56
|
+
function toWireParams(model, request) {
|
|
57
|
+
const params = {
|
|
58
|
+
model,
|
|
59
|
+
max_tokens: request.maxTokens,
|
|
60
|
+
system: [{ type: "text", text: request.system, cache_control: { type: "ephemeral" } }],
|
|
61
|
+
messages: request.messages.map(toWireMessage)
|
|
62
|
+
};
|
|
63
|
+
const last = params.messages.at(-1);
|
|
64
|
+
if (last !== void 0 && Array.isArray(last.content) && last.content.length > 0) {
|
|
65
|
+
const blocks = last.content;
|
|
66
|
+
const block = blocks.findLast((b) => b.type !== "thinking" && b.type !== "redacted_thinking");
|
|
67
|
+
if (block !== void 0) block.cache_control = { type: "ephemeral" };
|
|
68
|
+
}
|
|
69
|
+
const tools = toWireTools(request.tools, request.serverTools ?? []);
|
|
70
|
+
if (tools.length > 0) params.tools = tools;
|
|
71
|
+
const thinking = request.thinking;
|
|
72
|
+
if (thinking?.adaptive === true || thinking?.display !== void 0) {
|
|
73
|
+
params.thinking = {
|
|
74
|
+
type: "adaptive",
|
|
75
|
+
...thinking.display === void 0 ? {} : { display: thinking.display }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (thinking?.effort !== void 0) params.output_config = { effort: thinking.effort };
|
|
79
|
+
return params;
|
|
80
|
+
}
|
|
81
|
+
function toWireTools(tools, serverTools = []) {
|
|
82
|
+
const out = [
|
|
83
|
+
...tools.map(
|
|
84
|
+
(tool) => ({
|
|
85
|
+
name: tool.name,
|
|
86
|
+
description: tool.description,
|
|
87
|
+
input_schema: { ...tool.inputSchema, type: "object" }
|
|
88
|
+
})
|
|
89
|
+
),
|
|
90
|
+
...serverTools.map(
|
|
91
|
+
(spec) => ({
|
|
92
|
+
type: WEB_SEARCH_TOOL,
|
|
93
|
+
name: "web_search",
|
|
94
|
+
max_uses: spec.maxUses,
|
|
95
|
+
...spec.allowedDomains === void 0 ? {} : { allowed_domains: [...spec.allowedDomains] },
|
|
96
|
+
...spec.blockedDomains === void 0 ? {} : { blocked_domains: [...spec.blockedDomains] }
|
|
97
|
+
})
|
|
98
|
+
)
|
|
99
|
+
];
|
|
100
|
+
const last = out.at(-1);
|
|
101
|
+
if (last !== void 0) last.cache_control = { type: "ephemeral" };
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function toWireMessage(message) {
|
|
105
|
+
return { role: message.role, content: message.content.map(toWireBlock) };
|
|
106
|
+
}
|
|
107
|
+
function toWireBlock(block) {
|
|
108
|
+
switch (block.type) {
|
|
109
|
+
case "text":
|
|
110
|
+
return block.citations === void 0 ? { type: "text", text: block.text } : {
|
|
111
|
+
type: "text",
|
|
112
|
+
text: block.text,
|
|
113
|
+
citations: structuredClone(block.citations)
|
|
114
|
+
};
|
|
115
|
+
// Server tool blocks go back exactly as they came (the results hold encrypted content), and so
|
|
116
|
+
// do thinking blocks (0.9: the signature checks them).
|
|
117
|
+
case "server_tool_use":
|
|
118
|
+
case "server_tool_result":
|
|
119
|
+
case "thinking":
|
|
120
|
+
return structuredClone(block.wire);
|
|
121
|
+
case "tool_use":
|
|
122
|
+
return { type: "tool_use", id: block.id, name: block.name, input: block.input };
|
|
123
|
+
case "tool_result":
|
|
124
|
+
return {
|
|
125
|
+
type: "tool_result",
|
|
126
|
+
tool_use_id: block.toolUseId,
|
|
127
|
+
content: block.content,
|
|
128
|
+
is_error: block.isError
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function fromWireMessage(message) {
|
|
133
|
+
const content = [];
|
|
134
|
+
for (const block of message.content) {
|
|
135
|
+
if (block.type === "text") {
|
|
136
|
+
const citations = block.citations ?? [];
|
|
137
|
+
content.push(
|
|
138
|
+
citations.length === 0 ? { type: "text", text: block.text } : { type: "text", text: block.text, citations: structuredClone(citations) }
|
|
139
|
+
);
|
|
140
|
+
} else if (block.type === "tool_use") {
|
|
141
|
+
content.push({ type: "tool_use", id: block.id, name: block.name, input: block.input });
|
|
142
|
+
} else if (block.type === "server_tool_use") {
|
|
143
|
+
content.push({
|
|
144
|
+
type: "server_tool_use",
|
|
145
|
+
id: block.id,
|
|
146
|
+
name: block.name,
|
|
147
|
+
input: block.input,
|
|
148
|
+
wire: structuredClone(block)
|
|
149
|
+
});
|
|
150
|
+
} else if (block.type === "web_search_tool_result") {
|
|
151
|
+
content.push(webSearchResult(block));
|
|
152
|
+
} else if (block.type === "thinking") {
|
|
153
|
+
content.push({ type: "thinking", text: block.thinking, wire: structuredClone(block) });
|
|
154
|
+
} else if (block.type === "redacted_thinking") {
|
|
155
|
+
content.push({ type: "thinking", text: "", wire: structuredClone(block) });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
content,
|
|
160
|
+
stopReason: fromWireStopReason(message.stop_reason),
|
|
161
|
+
usage: fromWireUsage(message.usage)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function webSearchResult(block) {
|
|
165
|
+
const wire = structuredClone(block);
|
|
166
|
+
if (!Array.isArray(block.content)) {
|
|
167
|
+
return {
|
|
168
|
+
type: "server_tool_result",
|
|
169
|
+
toolUseId: block.tool_use_id,
|
|
170
|
+
name: "web_search",
|
|
171
|
+
results: [],
|
|
172
|
+
error: block.content.error_code,
|
|
173
|
+
wire
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
type: "server_tool_result",
|
|
178
|
+
toolUseId: block.tool_use_id,
|
|
179
|
+
name: "web_search",
|
|
180
|
+
results: block.content.map((r) => ({
|
|
181
|
+
title: r.title,
|
|
182
|
+
url: r.url,
|
|
183
|
+
...r.page_age === null || r.page_age === void 0 ? {} : { age: r.page_age }
|
|
184
|
+
})),
|
|
185
|
+
wire
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function fromWireStopReason(reason) {
|
|
189
|
+
switch (reason) {
|
|
190
|
+
case "end_turn":
|
|
191
|
+
case "stop_sequence":
|
|
192
|
+
return "end_turn";
|
|
193
|
+
case "tool_use":
|
|
194
|
+
return "tool_use";
|
|
195
|
+
case "max_tokens":
|
|
196
|
+
case "model_context_window_exceeded":
|
|
197
|
+
return "max_tokens";
|
|
198
|
+
case "refusal":
|
|
199
|
+
return "refusal";
|
|
200
|
+
case "pause_turn":
|
|
201
|
+
return "pause_turn";
|
|
202
|
+
default:
|
|
203
|
+
return "other";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function fromWireUsage(usage) {
|
|
207
|
+
const searches = usage.server_tool_use?.web_search_requests ?? 0;
|
|
208
|
+
return {
|
|
209
|
+
inputTokens: usage.input_tokens,
|
|
210
|
+
outputTokens: usage.output_tokens,
|
|
211
|
+
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
|
|
212
|
+
cacheWriteTokens: usage.cache_creation_input_tokens ?? 0,
|
|
213
|
+
...searches > 0 ? { webSearches: searches } : {}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
var BATCH_CHECK_TIMEOUT_MS = 6e4;
|
|
217
|
+
var MAX_FAILED_CHECKS = 10;
|
|
218
|
+
var BATCH_POLL_MS = [5e3, 1e4, 2e4, 3e4];
|
|
219
|
+
var AnthropicBatchClient = class {
|
|
220
|
+
serverTools = ["web_search"];
|
|
221
|
+
client;
|
|
222
|
+
model;
|
|
223
|
+
pollMs;
|
|
224
|
+
onWait;
|
|
225
|
+
noticeMs;
|
|
226
|
+
constructor(options) {
|
|
227
|
+
this.model = options.model;
|
|
228
|
+
this.pollMs = options.pollMs ?? BATCH_POLL_MS;
|
|
229
|
+
this.onWait = options.onWait;
|
|
230
|
+
this.noticeMs = options.noticeMs ?? 6e4;
|
|
231
|
+
this.client = options.client ?? new Anthropic(clientOptions(options.apiKey));
|
|
232
|
+
}
|
|
233
|
+
async *stream(request, options) {
|
|
234
|
+
const signal = options?.signal;
|
|
235
|
+
const params = toWireParams(
|
|
236
|
+
this.model,
|
|
237
|
+
request
|
|
238
|
+
);
|
|
239
|
+
const batch = await this.client.messages.batches.create(
|
|
240
|
+
{ requests: [{ custom_id: "garuda", params }] },
|
|
241
|
+
signal === void 0 ? {} : { signal }
|
|
242
|
+
);
|
|
243
|
+
const started = Date.now();
|
|
244
|
+
this.onWait?.({ batchId: batch.id, waitedMs: 0, status: "created" });
|
|
245
|
+
let noticed = 0;
|
|
246
|
+
let failed = 0;
|
|
247
|
+
try {
|
|
248
|
+
for (let i = 0; ; i++) {
|
|
249
|
+
await wait(this.pollMs[Math.min(i, this.pollMs.length - 1)] ?? 3e4, signal);
|
|
250
|
+
let status;
|
|
251
|
+
try {
|
|
252
|
+
const state = await this.client.messages.batches.retrieve(batch.id, void 0, {
|
|
253
|
+
timeout: BATCH_CHECK_TIMEOUT_MS,
|
|
254
|
+
...signal === void 0 ? {} : { signal }
|
|
255
|
+
});
|
|
256
|
+
status = state.processing_status;
|
|
257
|
+
failed = 0;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (signal?.aborted === true || ++failed >= MAX_FAILED_CHECKS) throw error;
|
|
260
|
+
status = "check failed, trying again";
|
|
261
|
+
}
|
|
262
|
+
if (status === "ended") break;
|
|
263
|
+
const waited = Date.now() - started;
|
|
264
|
+
if (waited - noticed >= this.noticeMs) {
|
|
265
|
+
noticed = waited;
|
|
266
|
+
this.onWait?.({ batchId: batch.id, waitedMs: waited, status });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
await this.client.messages.batches.cancel(batch.id).catch(() => void 0);
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
const results = await this.client.messages.batches.results(batch.id);
|
|
274
|
+
for await (const item of results) {
|
|
275
|
+
if (item.custom_id !== "garuda") continue;
|
|
276
|
+
const result = item.result;
|
|
277
|
+
if (result.type === "succeeded") {
|
|
278
|
+
const response = { ...fromWireMessage(result.message), priceFactor: 0.5 };
|
|
279
|
+
const text = response.content.map((b) => b.type === "text" ? b.text : "").join("");
|
|
280
|
+
if (text !== "") yield { type: "text_delta", text };
|
|
281
|
+
yield { type: "response", response };
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (result.type === "errored") {
|
|
285
|
+
const detail = result.error.error;
|
|
286
|
+
throw Object.assign(new Error(`Batch request failed: ${detail.message}`), {
|
|
287
|
+
error: result.error
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`The batch request was ${result.type}.`);
|
|
291
|
+
}
|
|
292
|
+
throw new Error("The batch ended without a result.");
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
function wait(ms, signal) {
|
|
296
|
+
return new Promise((resolve, reject) => {
|
|
297
|
+
if (signal?.aborted) {
|
|
298
|
+
reject(signal.reason);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const timer = setTimeout(() => {
|
|
302
|
+
signal?.removeEventListener("abort", onAbort);
|
|
303
|
+
resolve();
|
|
304
|
+
}, ms);
|
|
305
|
+
const onAbort = () => {
|
|
306
|
+
clearTimeout(timer);
|
|
307
|
+
reject(signal?.reason);
|
|
308
|
+
};
|
|
309
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
export {
|
|
313
|
+
AnthropicBatchClient,
|
|
314
|
+
AnthropicClient,
|
|
315
|
+
BATCH_CHECK_TIMEOUT_MS,
|
|
316
|
+
BATCH_POLL_MS,
|
|
317
|
+
STREAM_IDLE_MS,
|
|
318
|
+
WEB_SEARCH_TOOL,
|
|
319
|
+
clientOptions,
|
|
320
|
+
fromWireMessage,
|
|
321
|
+
streamFetch,
|
|
322
|
+
toWireMessage,
|
|
323
|
+
toWireParams,
|
|
324
|
+
toWireTools,
|
|
325
|
+
withHiddenThinking
|
|
326
|
+
};
|
|
327
|
+
//# sourceMappingURL=anthropic-NQDBCHJU.js.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
hostCommand,
|
|
4
|
+
shellWord
|
|
5
|
+
} from "./chunk-ZGZ2ZADL.js";
|
|
6
|
+
import {
|
|
7
|
+
JOBS_DIR
|
|
8
|
+
} from "./chunk-KKB5W47S.js";
|
|
9
|
+
|
|
10
|
+
// src/jobs/launchd.ts
|
|
11
|
+
import { createHash } from "crypto";
|
|
12
|
+
import { existsSync, readFileSync } from "fs";
|
|
13
|
+
import { mkdir, rm, writeFile } from "fs/promises";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { basename, join } from "path";
|
|
16
|
+
function defaultAgentEnv() {
|
|
17
|
+
const shell = process.env.SHELL ?? "";
|
|
18
|
+
return {
|
|
19
|
+
home: homedir(),
|
|
20
|
+
uid: process.getuid?.() ?? 501,
|
|
21
|
+
shell: /\/(zsh|bash)$/.test(shell) ? shell : "/bin/zsh",
|
|
22
|
+
node: process.execPath,
|
|
23
|
+
script: process.argv[1] ?? ""
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function agentLabel(id) {
|
|
27
|
+
return `dev.garuda.job.${id}`;
|
|
28
|
+
}
|
|
29
|
+
function agentPath(home, id) {
|
|
30
|
+
return join(home, "Library", "LaunchAgents", `${agentLabel(id)}.plist`);
|
|
31
|
+
}
|
|
32
|
+
function nextTime(at, now = /* @__PURE__ */ new Date()) {
|
|
33
|
+
const [hour, minute] = at.split(":").map(Number);
|
|
34
|
+
const next = new Date(now);
|
|
35
|
+
next.setHours(hour, minute, 0, 0);
|
|
36
|
+
if (next.getTime() <= now.getTime()) next.setDate(next.getDate() + 1);
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
var xml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
40
|
+
function jobSpec(job) {
|
|
41
|
+
return {
|
|
42
|
+
label: agentLabel(job.id),
|
|
43
|
+
root: job.root,
|
|
44
|
+
args: ["run", job.id, "--from-launchd"],
|
|
45
|
+
log: join(job.root, JOBS_DIR, `${job.id}.log`)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function nightSpec(root) {
|
|
49
|
+
const hash = createHash("sha256").update(root).digest("hex").slice(0, 8);
|
|
50
|
+
const name = basename(root).replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 40) || "project";
|
|
51
|
+
return {
|
|
52
|
+
label: `dev.garuda.night.${name}-${hash}`,
|
|
53
|
+
root,
|
|
54
|
+
args: ["night", "--from-launchd"],
|
|
55
|
+
log: join(root, JOBS_DIR, "night.log")
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function specPath(home, spec) {
|
|
59
|
+
return join(home, "Library", "LaunchAgents", `${spec.label}.plist`);
|
|
60
|
+
}
|
|
61
|
+
function agentPlist(job, env, when) {
|
|
62
|
+
return specPlist(jobSpec(job), env, when);
|
|
63
|
+
}
|
|
64
|
+
function specPlist(spec, env, when) {
|
|
65
|
+
const program = env.script === "" || env.script === env.node ? shellWord(env.node) : `${shellWord(env.node)} ${shellWord(env.script)}`;
|
|
66
|
+
const command = `cd ${shellWord(spec.root)} && exec ${program} ${spec.args.map(shellWord).join(" ")}`;
|
|
67
|
+
const args = ["/usr/bin/caffeinate", "-i", env.shell, "-lic", command];
|
|
68
|
+
const int = (key, value) => ` <key>${key}</key><integer>${value}</integer>`;
|
|
69
|
+
return [
|
|
70
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
71
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
72
|
+
'<plist version="1.0">',
|
|
73
|
+
" <dict>",
|
|
74
|
+
` <key>Label</key><string>${xml(spec.label)}</string>`,
|
|
75
|
+
" <key>ProgramArguments</key>",
|
|
76
|
+
" <array>",
|
|
77
|
+
...args.map((a) => ` <string>${xml(a)}</string>`),
|
|
78
|
+
" </array>",
|
|
79
|
+
` <key>WorkingDirectory</key><string>${xml(spec.root)}</string>`,
|
|
80
|
+
" <key>StartCalendarInterval</key>",
|
|
81
|
+
" <dict>",
|
|
82
|
+
int("Month", when.getMonth() + 1),
|
|
83
|
+
int("Day", when.getDate()),
|
|
84
|
+
int("Hour", when.getHours()),
|
|
85
|
+
int("Minute", when.getMinutes()),
|
|
86
|
+
" </dict>",
|
|
87
|
+
" <key>RunAtLoad</key><false/>",
|
|
88
|
+
` <key>StandardOutPath</key><string>${xml(spec.log)}</string>`,
|
|
89
|
+
` <key>StandardErrorPath</key><string>${xml(spec.log)}</string>`,
|
|
90
|
+
" </dict>",
|
|
91
|
+
"</plist>",
|
|
92
|
+
""
|
|
93
|
+
].join("\n");
|
|
94
|
+
}
|
|
95
|
+
async function installAgent(executor, job, env, now = /* @__PURE__ */ new Date()) {
|
|
96
|
+
if (job.at === void 0) throw new Error("The job has no time.");
|
|
97
|
+
return installSpec(executor, jobSpec(job), env, nextTime(job.at, now));
|
|
98
|
+
}
|
|
99
|
+
async function installSpec(executor, spec, env, when) {
|
|
100
|
+
const plist = specPath(env.home, spec);
|
|
101
|
+
await mkdir(join(env.home, "Library", "LaunchAgents"), { recursive: true });
|
|
102
|
+
await writeFile(plist, specPlist(spec, env, when), { mode: 420 });
|
|
103
|
+
await hostCommand(executor, spec.root, ["launchctl", "bootout", `gui/${env.uid}/${spec.label}`], {
|
|
104
|
+
check: false
|
|
105
|
+
});
|
|
106
|
+
await hostCommand(executor, spec.root, ["launchctl", "bootstrap", `gui/${env.uid}`, plist]);
|
|
107
|
+
return { plist, when };
|
|
108
|
+
}
|
|
109
|
+
async function removeSpec(executor, spec, env) {
|
|
110
|
+
const plist = specPath(env.home, spec);
|
|
111
|
+
const had = existsSync(plist);
|
|
112
|
+
await rm(plist, { force: true });
|
|
113
|
+
const out = await hostCommand(
|
|
114
|
+
executor,
|
|
115
|
+
spec.root,
|
|
116
|
+
["launchctl", "bootout", `gui/${env.uid}/${spec.label}`],
|
|
117
|
+
{ check: false }
|
|
118
|
+
);
|
|
119
|
+
return had || out.exitCode === 0;
|
|
120
|
+
}
|
|
121
|
+
function nightAgentTime(root, env) {
|
|
122
|
+
let text;
|
|
123
|
+
try {
|
|
124
|
+
text = readFileSync(specPath(env.home, nightSpec(root)), "utf8");
|
|
125
|
+
} catch {
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
const n = (key) => Number(new RegExp(`<key>${key}</key><integer>(\\d+)</integer>`).exec(text)?.[1]);
|
|
129
|
+
const month = n("Month");
|
|
130
|
+
const day = n("Day");
|
|
131
|
+
if (Number.isNaN(month) || Number.isNaN(day)) return void 0;
|
|
132
|
+
const now = /* @__PURE__ */ new Date();
|
|
133
|
+
const when = new Date(now.getFullYear(), month - 1, day, n("Hour"), n("Minute"));
|
|
134
|
+
if (when.getTime() < now.getTime() - 180 * 864e5) when.setFullYear(now.getFullYear() + 1);
|
|
135
|
+
return when;
|
|
136
|
+
}
|
|
137
|
+
async function removeAgent(executor, job, env) {
|
|
138
|
+
return removeSpec(executor, jobSpec(job), env);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
defaultAgentEnv,
|
|
143
|
+
agentLabel,
|
|
144
|
+
agentPath,
|
|
145
|
+
nextTime,
|
|
146
|
+
jobSpec,
|
|
147
|
+
nightSpec,
|
|
148
|
+
specPath,
|
|
149
|
+
agentPlist,
|
|
150
|
+
specPlist,
|
|
151
|
+
installAgent,
|
|
152
|
+
installSpec,
|
|
153
|
+
removeSpec,
|
|
154
|
+
nightAgentTime,
|
|
155
|
+
removeAgent
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=chunk-35GMLLJW.js.map
|