@tablation/crew 0.115.0 → 0.117.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/dist/cli.js +924 -664
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -125,19 +125,20 @@ function excludeFile(worktree) {
|
|
|
125
125
|
return void 0;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
+
function excludeFromGit(worktree, entry) {
|
|
129
|
+
const ex = excludeFile(worktree);
|
|
130
|
+
if (!ex) return;
|
|
131
|
+
mkdirSync(dirname(ex), { recursive: true });
|
|
132
|
+
const have = existsSync2(ex) ? readFileSync(ex, "utf8") : "";
|
|
133
|
+
if (have.split("\n").some((l) => l.trim() === entry)) return;
|
|
134
|
+
appendFileSync(ex, `${have === "" || have.endsWith("\n") ? "" : "\n"}${entry}
|
|
135
|
+
`);
|
|
136
|
+
}
|
|
128
137
|
function prepareDropFile(worktree) {
|
|
129
138
|
const file = pointedAtFile(worktree);
|
|
130
139
|
mkdirSync(dirname(file), { recursive: true });
|
|
131
140
|
if (!existsSync2(file)) writeFileSync(file, "");
|
|
132
|
-
|
|
133
|
-
if (ex) {
|
|
134
|
-
mkdirSync(dirname(ex), { recursive: true });
|
|
135
|
-
const have = existsSync2(ex) ? readFileSync(ex, "utf8") : "";
|
|
136
|
-
if (!have.split("\n").some((l) => l.trim() === `${POINTED_AT_DIR}/`)) {
|
|
137
|
-
appendFileSync(ex, `${have === "" || have.endsWith("\n") ? "" : "\n"}${POINTED_AT_DIR}/
|
|
138
|
-
`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
+
excludeFromGit(worktree, `${POINTED_AT_DIR}/`);
|
|
141
142
|
return file;
|
|
142
143
|
}
|
|
143
144
|
function parseLine(raw) {
|
|
@@ -180,15 +181,160 @@ function dropFileMtime(worktree) {
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
// src/intel-register.ts
|
|
185
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
186
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
187
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
188
|
+
|
|
189
|
+
// src/intel.ts
|
|
190
|
+
var INTEL_MINIMUM_TOOLS = [
|
|
191
|
+
"find symbols by name",
|
|
192
|
+
"references to a symbol",
|
|
193
|
+
"a symbol's definition",
|
|
194
|
+
"a file's symbol outline"
|
|
195
|
+
];
|
|
196
|
+
var INTEL_SERVER_NAME = "code";
|
|
197
|
+
var lastUrl = (output) => {
|
|
198
|
+
const last = output.split("\n").map((l) => l.trim()).filter(Boolean).pop();
|
|
199
|
+
return last && /^https?:\/\/\S+$/i.test(last) ? last : void 0;
|
|
200
|
+
};
|
|
201
|
+
var oneLine = (s) => s.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
202
|
+
async function rpcResult(res) {
|
|
203
|
+
const text2 = await res.text();
|
|
204
|
+
const body = (res.headers.get("content-type") ?? "").includes("text/event-stream") ? text2.split("\n").find((l) => l.startsWith("data:"))?.slice(5).trim() ?? "" : text2;
|
|
205
|
+
const msg = JSON.parse(body);
|
|
206
|
+
if (msg.error) throw new Error(msg.error.message ?? "MCP error");
|
|
207
|
+
return msg.result;
|
|
208
|
+
}
|
|
209
|
+
async function initializeAndList(url, f, signal) {
|
|
210
|
+
const headers = { "content-type": "application/json", accept: "application/json, text/event-stream" };
|
|
211
|
+
const init = await f(url, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers,
|
|
214
|
+
signal,
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
jsonrpc: "2.0",
|
|
217
|
+
id: 1,
|
|
218
|
+
method: "initialize",
|
|
219
|
+
params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "crew", version: "0" } }
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
if (!init.ok) throw new Error(`initialize answered ${init.status}`);
|
|
223
|
+
await rpcResult(init);
|
|
224
|
+
const session = init.headers.get("mcp-session-id");
|
|
225
|
+
const sessionHeaders = session ? { ...headers, "mcp-session-id": session } : headers;
|
|
226
|
+
await f(url, { method: "POST", headers: sessionHeaders, signal, body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) }).then((r) => r.body?.cancel()).catch(() => {
|
|
227
|
+
});
|
|
228
|
+
const list = await f(url, { method: "POST", headers: sessionHeaders, signal, body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list" }) });
|
|
229
|
+
if (!list.ok) throw new Error(`tools/list answered ${list.status}`);
|
|
230
|
+
const result = await rpcResult(list);
|
|
231
|
+
return (result?.tools ?? []).map((t) => String(t.name)).filter(Boolean);
|
|
232
|
+
}
|
|
233
|
+
async function probeIntel(deps, env, startupSeconds) {
|
|
234
|
+
let ran;
|
|
235
|
+
try {
|
|
236
|
+
ran = await deps.runHook(env);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
return { state: "failed", reason: `the intel hook could not run: ${oneLine(e.message)}` };
|
|
239
|
+
}
|
|
240
|
+
if (!ran) return { state: "absent" };
|
|
241
|
+
if (ran.code !== 0) return { state: "failed", reason: `the intel hook exited ${ran.code}${ran.output.trim() ? `: ${oneLine(ran.output.trim().split("\n").pop() ?? "")}` : ""}` };
|
|
242
|
+
const url = lastUrl(ran.output);
|
|
243
|
+
if (!url) return { state: "failed", reason: "the intel hook printed no URL as its last line" };
|
|
244
|
+
const f = deps.fetch ?? fetch;
|
|
245
|
+
const sleep = deps.sleep ?? ((ms3) => new Promise((r) => setTimeout(r, ms3)));
|
|
246
|
+
const now = deps.now ?? Date.now;
|
|
247
|
+
const deadline = now() + startupSeconds * 1e3;
|
|
248
|
+
let last = "no answer";
|
|
249
|
+
for (; ; ) {
|
|
250
|
+
try {
|
|
251
|
+
const tools = await initializeAndList(url, f, AbortSignal.timeout(Math.max(1, deadline - now())));
|
|
252
|
+
return { state: "ready", url, tools };
|
|
253
|
+
} catch (e) {
|
|
254
|
+
last = oneLine(e.message);
|
|
255
|
+
}
|
|
256
|
+
if (now() >= deadline) break;
|
|
257
|
+
await sleep(500);
|
|
258
|
+
}
|
|
259
|
+
return { state: "failed", reason: `${url} did not answer an MCP initialize within ${startupSeconds}s (${last})` };
|
|
260
|
+
}
|
|
261
|
+
function codeToolsParagraph(outcome, server = INTEL_SERVER_NAME) {
|
|
262
|
+
if (outcome.state === "absent") return "No symbol service is available for this repository.";
|
|
263
|
+
if (outcome.state === "failed") return `The symbol service did not start (${outcome.reason}), so none is available for this run.`;
|
|
264
|
+
return [
|
|
265
|
+
`The \`${server}\` MCP server serves this repository's symbols from its language server. It offers at least: ${INTEL_MINIMUM_TOOLS.join("; ")}.`,
|
|
266
|
+
`Tools it advertised: ${outcome.tools.length ? outcome.tools.map((t) => `\`${t}\``).join(", ") : "(none listed)"}.`
|
|
267
|
+
].join(" ");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/intel-register.ts
|
|
271
|
+
function mergeJsonServer(existing, entry, server) {
|
|
272
|
+
let doc = {};
|
|
273
|
+
if (existing !== void 0 && existing.trim() !== "") {
|
|
274
|
+
const parsed = JSON.parse(existing);
|
|
275
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("is not a JSON object");
|
|
276
|
+
doc = parsed;
|
|
277
|
+
}
|
|
278
|
+
const servers = doc.mcpServers;
|
|
279
|
+
if (servers !== void 0 && (!servers || typeof servers !== "object" || Array.isArray(servers))) throw new Error("has an mcpServers that is not an object");
|
|
280
|
+
doc.mcpServers = { ...servers, [server]: entry };
|
|
281
|
+
return `${JSON.stringify(doc, null, 2)}
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
function mergeTomlServer(existing, url, server) {
|
|
285
|
+
const table2 = `[mcp_servers.${server}]
|
|
286
|
+
url = ${JSON.stringify(url)}
|
|
287
|
+
`;
|
|
288
|
+
const text2 = existing ?? "";
|
|
289
|
+
const lines2 = text2.split("\n");
|
|
290
|
+
const start = lines2.findIndex((l) => l.trim() === `[mcp_servers.${server}]`);
|
|
291
|
+
if (start < 0) return text2 === "" ? table2 : `${text2}${text2.endsWith("\n") ? "" : "\n"}
|
|
292
|
+
${table2}`;
|
|
293
|
+
let end = lines2.findIndex((l, i) => i > start && /^\s*\[/.test(l));
|
|
294
|
+
if (end < 0) end = lines2.length;
|
|
295
|
+
const keepTail = lines2.slice(end);
|
|
296
|
+
return [...lines2.slice(0, start), ...table2.trimEnd().split("\n"), ...keepTail.length && keepTail[0] !== "" ? [""] : [], ...keepTail].join("\n");
|
|
297
|
+
}
|
|
298
|
+
function tracked(worktree, rel) {
|
|
299
|
+
try {
|
|
300
|
+
execFileSync2("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: worktree, stdio: "ignore" });
|
|
301
|
+
return true;
|
|
302
|
+
} catch {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function registerCodeServer(worktree, url, server = INTEL_SERVER_NAME) {
|
|
307
|
+
const out = { written: [], skipped: [] };
|
|
308
|
+
const plans = [
|
|
309
|
+
[".mcp.json", (old) => mergeJsonServer(old, { type: "http", url }, server)],
|
|
310
|
+
[".codex/config.toml", (old) => mergeTomlServer(old, url, server)],
|
|
311
|
+
[".gemini/settings.json", (old) => mergeJsonServer(old, { httpUrl: url }, server)]
|
|
312
|
+
];
|
|
313
|
+
for (const [rel, merge] of plans) {
|
|
314
|
+
const file = join2(worktree, rel);
|
|
315
|
+
try {
|
|
316
|
+
const next = merge(existsSync3(file) ? readFileSync2(file, "utf8") : void 0);
|
|
317
|
+
mkdirSync2(dirname2(file), { recursive: true });
|
|
318
|
+
writeFileSync2(file, next);
|
|
319
|
+
if (tracked(worktree, rel)) execFileSync2("git", ["update-index", "--skip-worktree", "--", rel], { cwd: worktree, stdio: "ignore" });
|
|
320
|
+
else excludeFromGit(worktree, rel);
|
|
321
|
+
out.written.push(rel);
|
|
322
|
+
} catch (e) {
|
|
323
|
+
out.skipped.push(`${rel} left alone: ${e.message}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
|
|
183
329
|
// src/periscope-state.ts
|
|
184
|
-
import { existsSync as
|
|
330
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
185
331
|
import { connect } from "net";
|
|
186
|
-
import { join as
|
|
187
|
-
var dirOf = (stateDir) =>
|
|
188
|
-
var fileOf = (stateDir, ticketRecordId) =>
|
|
332
|
+
import { join as join3 } from "path";
|
|
333
|
+
var dirOf = (stateDir) => join3(stateDir, "periscopes");
|
|
334
|
+
var fileOf = (stateDir, ticketRecordId) => join3(dirOf(stateDir), `${ticketRecordId}.json`);
|
|
189
335
|
function writePeriscopeSidecar(stateDir, sc) {
|
|
190
|
-
|
|
191
|
-
|
|
336
|
+
mkdirSync3(dirOf(stateDir), { recursive: true });
|
|
337
|
+
writeFileSync3(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
|
|
192
338
|
`);
|
|
193
339
|
}
|
|
194
340
|
function removePeriscopeSidecar(stateDir, ticketRecordId) {
|
|
@@ -196,12 +342,12 @@ function removePeriscopeSidecar(stateDir, ticketRecordId) {
|
|
|
196
342
|
}
|
|
197
343
|
function readPeriscopeSidecars(stateDir) {
|
|
198
344
|
const dir = dirOf(stateDir);
|
|
199
|
-
if (!
|
|
345
|
+
if (!existsSync4(dir)) return [];
|
|
200
346
|
const out = [];
|
|
201
347
|
for (const name of readdirSync(dir)) {
|
|
202
348
|
if (!name.endsWith(".json")) continue;
|
|
203
349
|
try {
|
|
204
|
-
const sc = JSON.parse(
|
|
350
|
+
const sc = JSON.parse(readFileSync3(join3(dir, name), "utf8"));
|
|
205
351
|
if (sc && typeof sc.ticketRecordId === "string" && typeof sc.url === "string") out.push(sc);
|
|
206
352
|
} catch {
|
|
207
353
|
}
|
|
@@ -319,23 +465,23 @@ function shellInitScript(shell) {
|
|
|
319
465
|
}
|
|
320
466
|
|
|
321
467
|
// src/config.ts
|
|
322
|
-
import { readFileSync as
|
|
323
|
-
import { resolve as resolve2, dirname as
|
|
468
|
+
import { readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
|
|
469
|
+
import { resolve as resolve2, dirname as dirname6, isAbsolute as isAbsolute2, join as join14, basename as basename4 } from "path";
|
|
324
470
|
import { homedir as homedir6, tmpdir } from "os";
|
|
325
471
|
import { parse as parse2 } from "yaml";
|
|
326
472
|
|
|
327
473
|
// src/events.ts
|
|
328
474
|
import {
|
|
329
475
|
appendFileSync as appendFileSync2,
|
|
330
|
-
mkdirSync as
|
|
331
|
-
existsSync as
|
|
476
|
+
mkdirSync as mkdirSync4,
|
|
477
|
+
existsSync as existsSync5,
|
|
332
478
|
statSync as statSync2,
|
|
333
479
|
renameSync,
|
|
334
480
|
openSync,
|
|
335
481
|
closeSync,
|
|
336
482
|
rmSync as rmSync2
|
|
337
483
|
} from "fs";
|
|
338
|
-
import { join as
|
|
484
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
339
485
|
function render(e) {
|
|
340
486
|
const scope = e.role ? `${e.step}[${e.role}]` : e.step;
|
|
341
487
|
const ticket = e.ticket ? ` ${e.ticket}` : "";
|
|
@@ -347,7 +493,7 @@ var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
|
|
|
347
493
|
var ROTATE_LOCK_STALE_MS = 6e4;
|
|
348
494
|
function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
349
495
|
try {
|
|
350
|
-
if (!
|
|
496
|
+
if (!existsSync5(file) || statSync2(file).size <= maxBytes) return void 0;
|
|
351
497
|
} catch {
|
|
352
498
|
return void 0;
|
|
353
499
|
}
|
|
@@ -367,8 +513,8 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
|
367
513
|
try {
|
|
368
514
|
if (statSync2(file).size <= maxBytes) return void 0;
|
|
369
515
|
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
370
|
-
let dest =
|
|
371
|
-
for (let n = 1;
|
|
516
|
+
let dest = join4(dirname3(file), `events.${stamp}.jsonl`);
|
|
517
|
+
for (let n = 1; existsSync5(dest); n++) dest = join4(dirname3(file), `events.${stamp}-${n}.jsonl`);
|
|
372
518
|
renameSync(file, dest);
|
|
373
519
|
return dest;
|
|
374
520
|
} finally {
|
|
@@ -391,7 +537,7 @@ var Emitter = class _Emitter {
|
|
|
391
537
|
for (const f of [opts.eventFile, opts.logFile]) {
|
|
392
538
|
if (!f) continue;
|
|
393
539
|
try {
|
|
394
|
-
|
|
540
|
+
mkdirSync4(dirname3(f), { recursive: true });
|
|
395
541
|
} catch {
|
|
396
542
|
}
|
|
397
543
|
}
|
|
@@ -467,18 +613,18 @@ var Emitter = class _Emitter {
|
|
|
467
613
|
}
|
|
468
614
|
}
|
|
469
615
|
};
|
|
470
|
-
var eventFileFor = (stateDir) =>
|
|
616
|
+
var eventFileFor = (stateDir) => join4(stateDir, "events.jsonl");
|
|
471
617
|
|
|
472
618
|
// src/pair-install.ts
|
|
473
|
-
import { existsSync as
|
|
619
|
+
import { existsSync as existsSync8, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
474
620
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
475
621
|
import { homedir as homedir5 } from "os";
|
|
476
|
-
import { join as
|
|
622
|
+
import { join as join10 } from "path";
|
|
477
623
|
|
|
478
624
|
// src/adapters/claude.ts
|
|
479
|
-
import { readFileSync as
|
|
625
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
480
626
|
import { homedir as homedir3 } from "os";
|
|
481
|
-
import { join as
|
|
627
|
+
import { join as join8 } from "path";
|
|
482
628
|
|
|
483
629
|
// src/stream.ts
|
|
484
630
|
function toolTarget(name, input) {
|
|
@@ -537,19 +683,19 @@ function extractResult(line) {
|
|
|
537
683
|
|
|
538
684
|
// src/claude-mcp.ts
|
|
539
685
|
import { spawnSync } from "child_process";
|
|
540
|
-
import { readFileSync as
|
|
686
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
541
687
|
import { homedir } from "os";
|
|
542
|
-
import { join as
|
|
688
|
+
import { join as join6 } from "path";
|
|
543
689
|
|
|
544
690
|
// src/mcp-registrations.ts
|
|
545
691
|
import { createHash } from "crypto";
|
|
546
|
-
import { mkdirSync as
|
|
547
|
-
import { join as
|
|
548
|
-
var fileFor = (stateDir) =>
|
|
692
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
693
|
+
import { join as join5 } from "path";
|
|
694
|
+
var fileFor = (stateDir) => join5(stateDir, "mcp-registrations.json");
|
|
549
695
|
var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
|
|
550
696
|
function readMcpRegistrations(stateDir) {
|
|
551
697
|
try {
|
|
552
|
-
const j = JSON.parse(
|
|
698
|
+
const j = JSON.parse(readFileSync5(fileFor(stateDir), "utf8"));
|
|
553
699
|
return Array.isArray(j) ? j : [];
|
|
554
700
|
} catch {
|
|
555
701
|
return [];
|
|
@@ -557,8 +703,8 @@ function readMcpRegistrations(stateDir) {
|
|
|
557
703
|
}
|
|
558
704
|
function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
|
|
559
705
|
const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
|
|
560
|
-
|
|
561
|
-
|
|
706
|
+
mkdirSync5(stateDir, { recursive: true });
|
|
707
|
+
writeFileSync4(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
|
|
562
708
|
`, { mode: 384 });
|
|
563
709
|
}
|
|
564
710
|
function findMcpRegistration(stateDir, r) {
|
|
@@ -581,7 +727,7 @@ function runClaude(bin, args) {
|
|
|
581
727
|
}
|
|
582
728
|
function userScopeEntry(home) {
|
|
583
729
|
try {
|
|
584
|
-
const j = JSON.parse(
|
|
730
|
+
const j = JSON.parse(readFileSync6(join6(home, ".claude.json"), "utf8"));
|
|
585
731
|
return j.mcpServers?.[MCP_SERVER_NAME];
|
|
586
732
|
} catch {
|
|
587
733
|
return void 0;
|
|
@@ -665,8 +811,8 @@ function removeClaudeMcp(o) {
|
|
|
665
811
|
}
|
|
666
812
|
|
|
667
813
|
// src/launcher.ts
|
|
668
|
-
import { basename, join as
|
|
669
|
-
import { existsSync as
|
|
814
|
+
import { basename, join as join7 } from "path";
|
|
815
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
|
|
670
816
|
import { homedir as homedir2 } from "os";
|
|
671
817
|
function isOriLauncher(spec) {
|
|
672
818
|
return !!spec.launcher && basename(spec.launcher) === "ori";
|
|
@@ -675,7 +821,7 @@ function isOmlxLauncher(spec) {
|
|
|
675
821
|
return !!spec.launcher && basename(spec.launcher) === "omlx";
|
|
676
822
|
}
|
|
677
823
|
function defaultOmlxBin(home = homedir2()) {
|
|
678
|
-
return
|
|
824
|
+
return join7(home, ".omlx", "bin", "omlx");
|
|
679
825
|
}
|
|
680
826
|
function launcherExecutable(spec, home) {
|
|
681
827
|
if (!spec.launcher) return void 0;
|
|
@@ -728,8 +874,8 @@ function omlxModelPresent(model, dirs) {
|
|
|
728
874
|
});
|
|
729
875
|
}
|
|
730
876
|
function readOmlxSettings(home = homedir2()) {
|
|
731
|
-
const p =
|
|
732
|
-
return
|
|
877
|
+
const p = join7(home, ".omlx", "settings.json");
|
|
878
|
+
return existsSync6(p) ? parseOmlxSettings(readFileSync7(p, "utf8")) : void 0;
|
|
733
879
|
}
|
|
734
880
|
|
|
735
881
|
// src/capability.ts
|
|
@@ -793,7 +939,7 @@ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
|
|
|
793
939
|
if (spec.auth === "workspace") return `workspace${spec.inference ? ` (${new URL(spec.inference.baseUrl).host})` : " (endpoint resolved per route at run time)"}`;
|
|
794
940
|
let fromSettings;
|
|
795
941
|
try {
|
|
796
|
-
const parsed = JSON.parse(
|
|
942
|
+
const parsed = JSON.parse(readFileSync8(join8(home, ".claude", "settings.json"), "utf8"));
|
|
797
943
|
const e = parsed.env ?? {};
|
|
798
944
|
const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
|
|
799
945
|
if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
|
|
@@ -897,12 +1043,12 @@ var claudeAdapter = {
|
|
|
897
1043
|
// src/adapters/codex.ts
|
|
898
1044
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
899
1045
|
import { homedir as homedir4 } from "os";
|
|
900
|
-
import { join as
|
|
901
|
-
import { rmSync as rmSync4, writeFileSync as
|
|
1046
|
+
import { join as join9 } from "path";
|
|
1047
|
+
import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
902
1048
|
|
|
903
1049
|
// src/pair-files.ts
|
|
904
|
-
import { existsSync as
|
|
905
|
-
import { dirname as
|
|
1050
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync5, rmSync as rmSync3 } from "fs";
|
|
1051
|
+
import { dirname as dirname4 } from "path";
|
|
906
1052
|
var PAIR_BLOCK_START = "<!-- crew:pair start -->";
|
|
907
1053
|
var PAIR_BLOCK_END = "<!-- crew:pair end -->";
|
|
908
1054
|
var PAIR_KEY_VAR = "CREW_API_KEY";
|
|
@@ -942,11 +1088,11 @@ function removePairBlock(text2) {
|
|
|
942
1088
|
return text2.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
|
|
943
1089
|
}
|
|
944
1090
|
function readIfExists(path) {
|
|
945
|
-
return
|
|
1091
|
+
return existsSync7(path) ? readFileSync9(path, "utf8") : void 0;
|
|
946
1092
|
}
|
|
947
1093
|
function writeFileMkdir(path, text2) {
|
|
948
|
-
|
|
949
|
-
|
|
1094
|
+
mkdirSync6(dirname4(path), { recursive: true });
|
|
1095
|
+
writeFileSync5(path, text2);
|
|
950
1096
|
}
|
|
951
1097
|
function installBlockFile(path, harness, dryRun) {
|
|
952
1098
|
const before = readIfExists(path) ?? "";
|
|
@@ -962,7 +1108,7 @@ function removeBlockFile(path, dryRun) {
|
|
|
962
1108
|
if (dryRun) return true;
|
|
963
1109
|
const after = removePairBlock(before);
|
|
964
1110
|
if (after.trim() === "") rmSync3(path);
|
|
965
|
-
else
|
|
1111
|
+
else writeFileSync5(path, after);
|
|
966
1112
|
return true;
|
|
967
1113
|
}
|
|
968
1114
|
function blockVerdict(path, harness) {
|
|
@@ -1089,8 +1235,8 @@ function codexTable(url) {
|
|
|
1089
1235
|
return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
|
|
1090
1236
|
}
|
|
1091
1237
|
var codexPaths = (home) => ({
|
|
1092
|
-
instructions:
|
|
1093
|
-
config:
|
|
1238
|
+
instructions: join9(home, ".codex", "AGENTS.md"),
|
|
1239
|
+
config: join9(home, ".codex", "config.toml")
|
|
1094
1240
|
});
|
|
1095
1241
|
function codexMcpState(config, url) {
|
|
1096
1242
|
const lines2 = config.split("\n");
|
|
@@ -1141,7 +1287,7 @@ var codexPair = {
|
|
|
1141
1287
|
if (hadMcp && !o.dryRun) {
|
|
1142
1288
|
const after = removeTomlTable(config, `mcp_servers.${SERVER}`);
|
|
1143
1289
|
if (after === "") rmSync4(p.config);
|
|
1144
|
-
else
|
|
1290
|
+
else writeFileSync6(p.config, after);
|
|
1145
1291
|
}
|
|
1146
1292
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
1147
1293
|
return hadBlock || hadMcp ? `pair(codex): ${o.dryRun ? "(dry run) would remove" : "removed"} ${what}${kept}` : `pair(codex): nothing to remove${kept}`;
|
|
@@ -1237,8 +1383,8 @@ var codexAdapter = {
|
|
|
1237
1383
|
|
|
1238
1384
|
// src/pair-install.ts
|
|
1239
1385
|
var geminiPaths = (home) => ({
|
|
1240
|
-
instructions:
|
|
1241
|
-
config:
|
|
1386
|
+
instructions: join10(home, ".gemini", "GEMINI.md"),
|
|
1387
|
+
config: join10(home, ".gemini", "settings.json")
|
|
1242
1388
|
});
|
|
1243
1389
|
function readGeminiSettings(path) {
|
|
1244
1390
|
const text2 = readIfExists(path);
|
|
@@ -1326,7 +1472,7 @@ var geminiPair = {
|
|
|
1326
1472
|
if (Object.keys(rest).length > 0) next.mcpServers = rest;
|
|
1327
1473
|
else delete next.mcpServers;
|
|
1328
1474
|
if (Object.keys(next).length === 0) rmSync5(p.config);
|
|
1329
|
-
else
|
|
1475
|
+
else writeFileSync7(p.config, `${JSON.stringify(next, null, 2)}
|
|
1330
1476
|
`);
|
|
1331
1477
|
}
|
|
1332
1478
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
@@ -1337,9 +1483,9 @@ var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
|
|
|
1337
1483
|
function vscodeUserDirs(o) {
|
|
1338
1484
|
const home = o.home ?? homedir5();
|
|
1339
1485
|
const platform2 = o.platform ?? process.platform;
|
|
1340
|
-
const root = platform2 === "darwin" ?
|
|
1341
|
-
const all = VSCODE_FLAVOURS.map((f) =>
|
|
1342
|
-
const present = all.filter((d) =>
|
|
1486
|
+
const root = platform2 === "darwin" ? join10(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join10(home, "AppData", "Roaming") : join10(home, ".config");
|
|
1487
|
+
const all = VSCODE_FLAVOURS.map((f) => join10(root, f, "User"));
|
|
1488
|
+
const present = all.filter((d) => existsSync8(d));
|
|
1343
1489
|
return present.length > 0 ? present : [all[0]];
|
|
1344
1490
|
}
|
|
1345
1491
|
var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
|
|
@@ -1377,7 +1523,7 @@ function removeCopilotInstructions(path, dryRun) {
|
|
|
1377
1523
|
if (dryRun) return true;
|
|
1378
1524
|
const after = removePairBlock(before);
|
|
1379
1525
|
if (after.trim() === "" || after.trim() === COPILOT_FRONTMATTER.trim()) rmSync5(path);
|
|
1380
|
-
else
|
|
1526
|
+
else writeFileSync7(path, after);
|
|
1381
1527
|
return true;
|
|
1382
1528
|
}
|
|
1383
1529
|
var copilotPair = {
|
|
@@ -1386,8 +1532,8 @@ var copilotPair = {
|
|
|
1386
1532
|
installPair(o) {
|
|
1387
1533
|
const lines2 = [];
|
|
1388
1534
|
for (const dir of vscodeUserDirs(o)) {
|
|
1389
|
-
const mcpPath =
|
|
1390
|
-
const instrPath =
|
|
1535
|
+
const mcpPath = join10(dir, "mcp.json");
|
|
1536
|
+
const instrPath = join10(dir, "prompts", "crew-pair.instructions.md");
|
|
1391
1537
|
const settings = readVscodeMcp(mcpPath);
|
|
1392
1538
|
if (!settings) {
|
|
1393
1539
|
lines2.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
|
|
@@ -1415,8 +1561,8 @@ var copilotPair = {
|
|
|
1415
1561
|
},
|
|
1416
1562
|
verifyPair(o) {
|
|
1417
1563
|
const verdicts = vscodeUserDirs(o).map((dir) => {
|
|
1418
|
-
const instrPath =
|
|
1419
|
-
const mcpPath =
|
|
1564
|
+
const instrPath = join10(dir, "prompts", "crew-pair.instructions.md");
|
|
1565
|
+
const mcpPath = join10(dir, "mcp.json");
|
|
1420
1566
|
const b = blockVerdict(instrPath, "copilot");
|
|
1421
1567
|
if (!b.ok) return b;
|
|
1422
1568
|
const settings = readVscodeMcp(mcpPath);
|
|
@@ -1431,8 +1577,8 @@ var copilotPair = {
|
|
|
1431
1577
|
removePair(o) {
|
|
1432
1578
|
const lines2 = [];
|
|
1433
1579
|
for (const dir of vscodeUserDirs(o)) {
|
|
1434
|
-
const mcpPath =
|
|
1435
|
-
const hadBlock = removeCopilotInstructions(
|
|
1580
|
+
const mcpPath = join10(dir, "mcp.json");
|
|
1581
|
+
const hadBlock = removeCopilotInstructions(join10(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
|
|
1436
1582
|
const settings = readVscodeMcp(mcpPath);
|
|
1437
1583
|
const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
|
|
1438
1584
|
const hadMcp = st.state === "registered";
|
|
@@ -1443,7 +1589,7 @@ var copilotPair = {
|
|
|
1443
1589
|
if (Object.keys(rest).length > 0) next.servers = rest;
|
|
1444
1590
|
else delete next.servers;
|
|
1445
1591
|
if (Object.keys(next).length === 0) rmSync5(mcpPath);
|
|
1446
|
-
else
|
|
1592
|
+
else writeFileSync7(mcpPath, `${JSON.stringify(next, null, 2)}
|
|
1447
1593
|
`);
|
|
1448
1594
|
}
|
|
1449
1595
|
const what = hadBlock && hadMcp ? `the Pair instructions and ${SERVER} server` : hadBlock ? "the Pair instructions" : `the ${SERVER} server`;
|
|
@@ -1692,9 +1838,9 @@ function resolveSeat(ship, route2, role, persona = {}, repo) {
|
|
|
1692
1838
|
}
|
|
1693
1839
|
|
|
1694
1840
|
// src/forges.ts
|
|
1695
|
-
import { execFileSync as
|
|
1696
|
-
import { chmodSync, existsSync as
|
|
1697
|
-
import { dirname as
|
|
1841
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
1842
|
+
import { chmodSync, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
1843
|
+
import { dirname as dirname5, join as join11 } from "path";
|
|
1698
1844
|
var ForgeError = class extends Error {
|
|
1699
1845
|
};
|
|
1700
1846
|
var DEFAULT_FORGE_USERNAME = "x-access-token";
|
|
@@ -1722,7 +1868,7 @@ function forgeDescriptorFor(host) {
|
|
|
1722
1868
|
}
|
|
1723
1869
|
var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
|
|
1724
1870
|
function defaultTokenFilePath(stateDir, host) {
|
|
1725
|
-
return
|
|
1871
|
+
return join11(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
|
|
1726
1872
|
}
|
|
1727
1873
|
var envSource = {
|
|
1728
1874
|
read: (s, c) => c.env[s.var]?.trim() || null,
|
|
@@ -1731,19 +1877,19 @@ var envSource = {
|
|
|
1731
1877
|
var fileSource = {
|
|
1732
1878
|
read(s, c) {
|
|
1733
1879
|
const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
|
|
1734
|
-
if (!
|
|
1880
|
+
if (!existsSync9(path)) return null;
|
|
1735
1881
|
const mode = statSync3(path).mode & 511;
|
|
1736
1882
|
if (mode & 63) {
|
|
1737
1883
|
throw new ForgeError(
|
|
1738
1884
|
`${path} is mode ${mode.toString(8).padStart(3, "0")}; a forge token file must be owner-only (chmod 600 ${path})`
|
|
1739
1885
|
);
|
|
1740
1886
|
}
|
|
1741
|
-
return
|
|
1887
|
+
return readFileSync10(path, "utf8").trim() || null;
|
|
1742
1888
|
},
|
|
1743
1889
|
write(s, token, c) {
|
|
1744
1890
|
const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
|
|
1745
|
-
|
|
1746
|
-
|
|
1891
|
+
mkdirSync7(dirname5(path), { recursive: true, mode: 448 });
|
|
1892
|
+
writeFileSync8(path, `${token}
|
|
1747
1893
|
`, { mode: 384 });
|
|
1748
1894
|
chmodSync(path, 384);
|
|
1749
1895
|
},
|
|
@@ -1757,13 +1903,13 @@ var keychainSource = {
|
|
|
1757
1903
|
read(s, c) {
|
|
1758
1904
|
try {
|
|
1759
1905
|
if (process.platform === "darwin") {
|
|
1760
|
-
return
|
|
1906
|
+
return execFileSync3(
|
|
1761
1907
|
"security",
|
|
1762
1908
|
["find-generic-password", "-s", keychainService(s), "-a", c.host, "-w"],
|
|
1763
1909
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
1764
1910
|
).trim() || null;
|
|
1765
1911
|
}
|
|
1766
|
-
return
|
|
1912
|
+
return execFileSync3(
|
|
1767
1913
|
"secret-tool",
|
|
1768
1914
|
["lookup", "service", keychainService(s), "account", c.host],
|
|
1769
1915
|
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
@@ -1776,13 +1922,13 @@ var keychainSource = {
|
|
|
1776
1922
|
try {
|
|
1777
1923
|
if (process.platform === "darwin") {
|
|
1778
1924
|
const q2 = (v) => `"${v.replace(/(["\\])/g, "\\$1")}"`;
|
|
1779
|
-
|
|
1925
|
+
execFileSync3("security", ["-i"], {
|
|
1780
1926
|
input: `add-generic-password -U -s ${q2(keychainService(s))} -a ${q2(c.host)} -w ${q2(token)}
|
|
1781
1927
|
`,
|
|
1782
1928
|
stdio: ["pipe", "ignore", "pipe"]
|
|
1783
1929
|
});
|
|
1784
1930
|
} else {
|
|
1785
|
-
|
|
1931
|
+
execFileSync3(
|
|
1786
1932
|
"secret-tool",
|
|
1787
1933
|
["store", "--label", `crew ${c.host}`, "service", keychainService(s), "account", c.host],
|
|
1788
1934
|
{ input: token, stdio: ["pipe", "ignore", "pipe"] }
|
|
@@ -1795,9 +1941,9 @@ var keychainSource = {
|
|
|
1795
1941
|
erase(s, c) {
|
|
1796
1942
|
try {
|
|
1797
1943
|
if (process.platform === "darwin") {
|
|
1798
|
-
|
|
1944
|
+
execFileSync3("security", ["delete-generic-password", "-s", keychainService(s), "-a", c.host], { stdio: "ignore" });
|
|
1799
1945
|
} else {
|
|
1800
|
-
|
|
1946
|
+
execFileSync3("secret-tool", ["clear", "service", keychainService(s), "account", c.host], { stdio: "ignore" });
|
|
1801
1947
|
}
|
|
1802
1948
|
} catch {
|
|
1803
1949
|
}
|
|
@@ -1877,10 +2023,10 @@ function answerCredential(action, input, forges, stateDir, env = process.env) {
|
|
|
1877
2023
|
password=${token}
|
|
1878
2024
|
`;
|
|
1879
2025
|
}
|
|
1880
|
-
var missesPath = (stateDir) =>
|
|
2026
|
+
var missesPath = (stateDir) => join11(stateDir, "forge-misses.json");
|
|
1881
2027
|
function readForgeMisses(stateDir) {
|
|
1882
2028
|
try {
|
|
1883
|
-
const raw = JSON.parse(
|
|
2029
|
+
const raw = JSON.parse(readFileSync10(missesPath(stateDir), "utf8"));
|
|
1884
2030
|
return Object.entries(raw).filter(([, t]) => typeof t === "string").map(([host, lastMissAt]) => ({ host, lastMissAt })).sort((a, b) => a.host.localeCompare(b.host));
|
|
1885
2031
|
} catch {
|
|
1886
2032
|
return [];
|
|
@@ -1895,8 +2041,8 @@ function noteForgeMiss(stateDir, host, at) {
|
|
|
1895
2041
|
if (!(bare in cur)) return;
|
|
1896
2042
|
delete cur[bare];
|
|
1897
2043
|
} else cur[bare] = at;
|
|
1898
|
-
|
|
1899
|
-
|
|
2044
|
+
mkdirSync7(stateDir, { recursive: true });
|
|
2045
|
+
writeFileSync8(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
|
|
1900
2046
|
} catch {
|
|
1901
2047
|
}
|
|
1902
2048
|
}
|
|
@@ -1998,8 +2144,8 @@ import { hostFromUrl } from "@tablation/client";
|
|
|
1998
2144
|
|
|
1999
2145
|
// src/secret-store.ts
|
|
2000
2146
|
import { createHash as createHash2 } from "crypto";
|
|
2001
|
-
import { accessSync, constants, existsSync as
|
|
2002
|
-
import { join as
|
|
2147
|
+
import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync11, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync9, chmodSync as chmodSync2 } from "fs";
|
|
2148
|
+
import { join as join12 } from "path";
|
|
2003
2149
|
import { getSessionStore as getOsSessionStore } from "@tablation/client";
|
|
2004
2150
|
var SECRET_STORE_MODES = ["auto", "os", "file"];
|
|
2005
2151
|
function isSecretStoreMode(v) {
|
|
@@ -2010,7 +2156,7 @@ function configureSecretStore(c) {
|
|
|
2010
2156
|
configured = c;
|
|
2011
2157
|
}
|
|
2012
2158
|
function keysDir(stateDir) {
|
|
2013
|
-
return
|
|
2159
|
+
return join12(stateDir, "keys");
|
|
2014
2160
|
}
|
|
2015
2161
|
function entryName(host, slug2) {
|
|
2016
2162
|
return `${createHash2("sha256").update(`${host}\0${slug2}`).digest("hex")}.json`;
|
|
@@ -2021,34 +2167,34 @@ var FileSessionStore = class {
|
|
|
2021
2167
|
this.dir = dir;
|
|
2022
2168
|
}
|
|
2023
2169
|
ensureDir() {
|
|
2024
|
-
|
|
2170
|
+
mkdirSync8(this.dir, { recursive: true, mode: 448 });
|
|
2025
2171
|
if ((statSync4(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
|
|
2026
2172
|
}
|
|
2027
2173
|
async get(host, slug2) {
|
|
2028
|
-
const file =
|
|
2029
|
-
if (!
|
|
2174
|
+
const file = join12(this.dir, entryName(host, slug2));
|
|
2175
|
+
if (!existsSync10(this.dir)) return void 0;
|
|
2030
2176
|
accessSync(this.dir, constants.R_OK | constants.X_OK);
|
|
2031
|
-
if (!
|
|
2032
|
-
return JSON.parse(
|
|
2177
|
+
if (!existsSync10(file)) return void 0;
|
|
2178
|
+
return JSON.parse(readFileSync11(file, "utf8"));
|
|
2033
2179
|
}
|
|
2034
2180
|
async set(host, slug2, session) {
|
|
2035
2181
|
this.ensureDir();
|
|
2036
|
-
const file =
|
|
2182
|
+
const file = join12(this.dir, entryName(host, slug2));
|
|
2037
2183
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
2038
|
-
|
|
2184
|
+
writeFileSync9(tmp, JSON.stringify(session), { mode: 384 });
|
|
2039
2185
|
renameSync2(tmp, file);
|
|
2040
2186
|
}
|
|
2041
2187
|
async clear(host, slug2) {
|
|
2042
|
-
if (!
|
|
2043
|
-
rmSync7(
|
|
2188
|
+
if (!existsSync10(this.dir)) return;
|
|
2189
|
+
rmSync7(join12(this.dir, entryName(host, slug2)), { force: true });
|
|
2044
2190
|
}
|
|
2045
2191
|
/** Whether a key could be written here right now, without leaving anything behind. */
|
|
2046
2192
|
writable() {
|
|
2047
2193
|
try {
|
|
2048
2194
|
this.ensureDir();
|
|
2049
2195
|
accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
|
|
2050
|
-
const probe =
|
|
2051
|
-
|
|
2196
|
+
const probe = join12(this.dir, `.probe.${process.pid}`);
|
|
2197
|
+
writeFileSync9(probe, "", { mode: 384 });
|
|
2052
2198
|
rmSync7(probe, { force: true });
|
|
2053
2199
|
return { ok: true };
|
|
2054
2200
|
} catch (e) {
|
|
@@ -2099,11 +2245,11 @@ function osStoreReachable(env = process.env, platform2 = process.platform, hasBi
|
|
|
2099
2245
|
if (platform2 !== "linux") return true;
|
|
2100
2246
|
if (!hasBinary("secret-tool")) return false;
|
|
2101
2247
|
if (env.DBUS_SESSION_BUS_ADDRESS) return true;
|
|
2102
|
-
return !!env.XDG_RUNTIME_DIR &&
|
|
2248
|
+
return !!env.XDG_RUNTIME_DIR && existsSync10(join12(env.XDG_RUNTIME_DIR, "bus"));
|
|
2103
2249
|
}
|
|
2104
2250
|
function onPath(name) {
|
|
2105
2251
|
for (const dir of (process.env.PATH ?? "").split(":")) {
|
|
2106
|
-
if (dir &&
|
|
2252
|
+
if (dir && existsSync10(join12(dir, name))) return true;
|
|
2107
2253
|
}
|
|
2108
2254
|
return false;
|
|
2109
2255
|
}
|
|
@@ -2170,8 +2316,8 @@ function explain(ship, required) {
|
|
|
2170
2316
|
}
|
|
2171
2317
|
|
|
2172
2318
|
// src/repo-config.ts
|
|
2173
|
-
import { readFileSync as
|
|
2174
|
-
import { join as
|
|
2319
|
+
import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
|
|
2320
|
+
import { join as join13, basename as basename3 } from "path";
|
|
2175
2321
|
import { parse } from "yaml";
|
|
2176
2322
|
|
|
2177
2323
|
// src/sandbox-policy.ts
|
|
@@ -2382,6 +2528,8 @@ var HOOK_NAMES = [
|
|
|
2382
2528
|
"isolate",
|
|
2383
2529
|
"handoff",
|
|
2384
2530
|
"preview",
|
|
2531
|
+
"intel",
|
|
2532
|
+
"map",
|
|
2385
2533
|
"ci_status",
|
|
2386
2534
|
"version",
|
|
2387
2535
|
"bump",
|
|
@@ -2452,8 +2600,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
2452
2600
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
2453
2601
|
function findRepoConfig(dir) {
|
|
2454
2602
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
2455
|
-
const p =
|
|
2456
|
-
if (
|
|
2603
|
+
const p = join13(dir, name);
|
|
2604
|
+
if (existsSync11(p)) return p;
|
|
2457
2605
|
}
|
|
2458
2606
|
return null;
|
|
2459
2607
|
}
|
|
@@ -2733,7 +2881,7 @@ function parseRepoConfig(text2, file) {
|
|
|
2733
2881
|
function loadRepoConfig(dir) {
|
|
2734
2882
|
const file = findRepoConfig(dir);
|
|
2735
2883
|
if (!file) return null;
|
|
2736
|
-
return parseRepoConfig(
|
|
2884
|
+
return parseRepoConfig(readFileSync12(file, "utf8"), file);
|
|
2737
2885
|
}
|
|
2738
2886
|
function hookLabel(cfg, hook2) {
|
|
2739
2887
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -3014,6 +3162,8 @@ var ConfigError = class extends Error {
|
|
|
3014
3162
|
};
|
|
3015
3163
|
var DEFAULT_MAX_PERISCOPES = 3;
|
|
3016
3164
|
var DEFAULT_PERISCOPE_IDLE_MINUTES = 120;
|
|
3165
|
+
var DEFAULT_INTEL_STARTUP_SECONDS = 20;
|
|
3166
|
+
var DEFAULT_MAP_TOKEN_BUDGET = 1e3;
|
|
3017
3167
|
function parseCloneScheme(v) {
|
|
3018
3168
|
if (v === void 0 || v === null || v === "") return void 0;
|
|
3019
3169
|
if (v === "ssh" || v === "https") return v;
|
|
@@ -3106,6 +3256,8 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
3106
3256
|
"previewHost",
|
|
3107
3257
|
"maxPeriscopes",
|
|
3108
3258
|
"periscopeIdleMinutes",
|
|
3259
|
+
"intelStartupSeconds",
|
|
3260
|
+
"mapTokenBudget",
|
|
3109
3261
|
"secretStore",
|
|
3110
3262
|
"env",
|
|
3111
3263
|
"runner",
|
|
@@ -3258,11 +3410,11 @@ function expand(p, base) {
|
|
|
3258
3410
|
return isAbsolute2(out) ? out : resolve2(base, out);
|
|
3259
3411
|
}
|
|
3260
3412
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
3261
|
-
if (!promptSet) return
|
|
3413
|
+
if (!promptSet) return join14(crewHome, "prompts", "default");
|
|
3262
3414
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
3263
3415
|
return expand(promptSet, base);
|
|
3264
3416
|
}
|
|
3265
|
-
return
|
|
3417
|
+
return join14(crewHome, "prompts", promptSet);
|
|
3266
3418
|
}
|
|
3267
3419
|
var Missing = class {
|
|
3268
3420
|
paths = [];
|
|
@@ -3286,7 +3438,7 @@ var Missing = class {
|
|
|
3286
3438
|
};
|
|
3287
3439
|
function crewVersion(crewHome) {
|
|
3288
3440
|
try {
|
|
3289
|
-
const pkg = JSON.parse(
|
|
3441
|
+
const pkg = JSON.parse(readFileSync13(join14(crewHome, "package.json"), "utf8"));
|
|
3290
3442
|
return pkg.version ?? "0.0.0";
|
|
3291
3443
|
} catch {
|
|
3292
3444
|
return "0.0.0";
|
|
@@ -3296,26 +3448,26 @@ function configSearchPath(crewHome) {
|
|
|
3296
3448
|
if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
|
|
3297
3449
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
3298
3450
|
return [
|
|
3299
|
-
...xdg ? [
|
|
3300
|
-
|
|
3451
|
+
...xdg ? [join14(xdg, "crew", "crew.yaml")] : [],
|
|
3452
|
+
join14(homedir6(), ".config", "crew", "crew.yaml"),
|
|
3301
3453
|
resolve2(crewHome, "crew.yaml")
|
|
3302
3454
|
];
|
|
3303
3455
|
}
|
|
3304
3456
|
function findConfigFile(crewHome) {
|
|
3305
3457
|
const candidates2 = configSearchPath(crewHome);
|
|
3306
|
-
return candidates2.find((c) =>
|
|
3458
|
+
return candidates2.find((c) => existsSync12(c)) ?? candidates2[candidates2.length - 1];
|
|
3307
3459
|
}
|
|
3308
|
-
var defaultConfigPath = () =>
|
|
3460
|
+
var defaultConfigPath = () => join14(process.env.XDG_CONFIG_HOME ?? join14(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3309
3461
|
function routeSlug(route2) {
|
|
3310
3462
|
return route2.replace(/\//g, "-");
|
|
3311
3463
|
}
|
|
3312
3464
|
function resolvedPathFor(stateDir, route2) {
|
|
3313
3465
|
const [workspace, project] = splitRoute(route2);
|
|
3314
|
-
return
|
|
3466
|
+
return join14(stateDir, "resolved", workspace, `${project}.json`);
|
|
3315
3467
|
}
|
|
3316
3468
|
function apiKeyPathFor(stateDir, route2) {
|
|
3317
3469
|
const [workspace, project] = splitRoute(route2);
|
|
3318
|
-
return
|
|
3470
|
+
return join14(stateDir, "keys", workspace, `${project}.env`);
|
|
3319
3471
|
}
|
|
3320
3472
|
function splitRoute(route2) {
|
|
3321
3473
|
const parts = route2.split("/");
|
|
@@ -3326,9 +3478,9 @@ function splitRoute(route2) {
|
|
|
3326
3478
|
}
|
|
3327
3479
|
function readResolvedFile(stateDir, route2) {
|
|
3328
3480
|
const path = resolvedPathFor(stateDir, route2);
|
|
3329
|
-
if (!
|
|
3481
|
+
if (!existsSync12(path)) return void 0;
|
|
3330
3482
|
try {
|
|
3331
|
-
return JSON.parse(
|
|
3483
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
3332
3484
|
} catch (e) {
|
|
3333
3485
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
3334
3486
|
}
|
|
@@ -3389,23 +3541,23 @@ function parseResolved(raw, m, where) {
|
|
|
3389
3541
|
}
|
|
3390
3542
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3391
3543
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3392
|
-
if (opts.text === void 0 && !
|
|
3544
|
+
if (opts.text === void 0 && !existsSync12(file)) {
|
|
3393
3545
|
throw new ConfigError(
|
|
3394
3546
|
`no config found. Looked in:
|
|
3395
3547
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
3396
3548
|
|
|
3397
3549
|
Create the first of those:
|
|
3398
|
-
mkdir -p ${
|
|
3550
|
+
mkdir -p ${dirname6(defaultConfigPath())} && cp ${join14(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
3399
3551
|
);
|
|
3400
3552
|
}
|
|
3401
3553
|
let raw;
|
|
3402
3554
|
try {
|
|
3403
|
-
raw = parse2(opts.text ??
|
|
3555
|
+
raw = parse2(opts.text ?? readFileSync13(file, "utf8"));
|
|
3404
3556
|
} catch (e) {
|
|
3405
3557
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
3406
3558
|
}
|
|
3407
3559
|
if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
|
|
3408
|
-
const base =
|
|
3560
|
+
const base = dirname6(file);
|
|
3409
3561
|
const missing = new Missing();
|
|
3410
3562
|
rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
|
|
3411
3563
|
const shipRaw = raw.ship ?? {};
|
|
@@ -3454,6 +3606,14 @@ Create the first of those:
|
|
|
3454
3606
|
if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
|
|
3455
3607
|
missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
|
|
3456
3608
|
}
|
|
3609
|
+
const intelStartupSeconds = shipRaw.intelStartupSeconds === void 0 ? DEFAULT_INTEL_STARTUP_SECONDS : Number(shipRaw.intelStartupSeconds);
|
|
3610
|
+
if (!Number.isInteger(intelStartupSeconds) || intelStartupSeconds < 1) {
|
|
3611
|
+
missing.add(`ship.intelStartupSeconds must be a positive integer (got "${shipRaw.intelStartupSeconds}")`);
|
|
3612
|
+
}
|
|
3613
|
+
const mapTokenBudget = shipRaw.mapTokenBudget === void 0 ? DEFAULT_MAP_TOKEN_BUDGET : Number(shipRaw.mapTokenBudget);
|
|
3614
|
+
if (!Number.isInteger(mapTokenBudget) || mapTokenBudget < 1) {
|
|
3615
|
+
missing.add(`ship.mapTokenBudget must be a positive integer (got "${shipRaw.mapTokenBudget}")`);
|
|
3616
|
+
}
|
|
3457
3617
|
const secretStoreRaw = shipRaw.secretStore === void 0 ? "auto" : shipRaw.secretStore;
|
|
3458
3618
|
if (!isSecretStoreMode(secretStoreRaw)) {
|
|
3459
3619
|
missing.add(`ship.secretStore must be one of ${SECRET_STORE_MODES.join(" | ")} (got "${String(shipRaw.secretStore)}")`);
|
|
@@ -3519,7 +3679,7 @@ Create the first of those:
|
|
|
3519
3679
|
useNvm: shipRaw.useNvm !== false,
|
|
3520
3680
|
nvmSh: shipRaw.nvmSh,
|
|
3521
3681
|
stateDir,
|
|
3522
|
-
logFile: expand(shipRaw.logFile ??
|
|
3682
|
+
logFile: expand(shipRaw.logFile ?? join14(tmpdir(), "crew.log"), base),
|
|
3523
3683
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
3524
3684
|
maxConcurrentAgents,
|
|
3525
3685
|
maxConcurrentAgentsSet,
|
|
@@ -3537,6 +3697,8 @@ Create the first of those:
|
|
|
3537
3697
|
previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
|
|
3538
3698
|
maxPeriscopes,
|
|
3539
3699
|
periscopeIdleMinutes,
|
|
3700
|
+
intelStartupSeconds,
|
|
3701
|
+
mapTokenBudget,
|
|
3540
3702
|
secretStore,
|
|
3541
3703
|
env: parseShipEnv(shipRaw.env)
|
|
3542
3704
|
},
|
|
@@ -3699,7 +3861,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
3699
3861
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
3700
3862
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
3701
3863
|
const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
|
|
3702
|
-
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ?
|
|
3864
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join14(reposBasePath, splitRoute(route2)[0]) : "";
|
|
3703
3865
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
3704
3866
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
3705
3867
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -3762,7 +3924,7 @@ function locateDir(cfg, dir, commonDirOf) {
|
|
|
3762
3924
|
const direct = find(target);
|
|
3763
3925
|
if (direct) return direct;
|
|
3764
3926
|
const common = commonDirOf?.(target);
|
|
3765
|
-
return common ? find(
|
|
3927
|
+
return common ? find(dirname6(resolve2(target, common))) : void 0;
|
|
3766
3928
|
}
|
|
3767
3929
|
function findRoute(cfg, route2) {
|
|
3768
3930
|
if (!route2) {
|
|
@@ -3797,8 +3959,8 @@ function repoIdForName(r, name) {
|
|
|
3797
3959
|
}
|
|
3798
3960
|
function resolveApiKey(r) {
|
|
3799
3961
|
if (r.apiKey) return r.apiKey;
|
|
3800
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3801
|
-
for (const line of
|
|
3962
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync12(r.apiKeyFile)) {
|
|
3963
|
+
for (const line of readFileSync13(r.apiKeyFile, "utf8").split("\n")) {
|
|
3802
3964
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
3803
3965
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
3804
3966
|
}
|
|
@@ -3811,9 +3973,9 @@ function resolveApiKey(r) {
|
|
|
3811
3973
|
}
|
|
3812
3974
|
function credentialSource(r) {
|
|
3813
3975
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
3814
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3976
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync12(r.apiKeyFile)) {
|
|
3815
3977
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
3816
|
-
if (
|
|
3978
|
+
if (readFileSync13(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
3817
3979
|
}
|
|
3818
3980
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
3819
3981
|
}
|
|
@@ -3869,7 +4031,7 @@ function configuredMembers(rt) {
|
|
|
3869
4031
|
return out;
|
|
3870
4032
|
}
|
|
3871
4033
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
3872
|
-
return
|
|
4034
|
+
return join14(basePath, workspace, repoName);
|
|
3873
4035
|
}
|
|
3874
4036
|
function reposOf(r) {
|
|
3875
4037
|
const named = Object.entries(r.repos);
|
|
@@ -3908,10 +4070,10 @@ function passengerRepoTargets(r) {
|
|
|
3908
4070
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
3909
4071
|
}
|
|
3910
4072
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
3911
|
-
return
|
|
4073
|
+
return join14(basePath, workspace, ".passenger-checkouts", repoName);
|
|
3912
4074
|
}
|
|
3913
4075
|
function passengerMirrorArea(ship) {
|
|
3914
|
-
return ship.runner === "sandbox" ?
|
|
4076
|
+
return ship.runner === "sandbox" ? join14(ship.stateDir, "passenger-sources") : void 0;
|
|
3915
4077
|
}
|
|
3916
4078
|
function passengerCheckoutTargets(r, mirrorArea) {
|
|
3917
4079
|
if (!r.hostPassengers) return [];
|
|
@@ -3922,7 +4084,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
|
|
|
3922
4084
|
const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
|
|
3923
4085
|
return {
|
|
3924
4086
|
name: t.name,
|
|
3925
|
-
dir: mirrorArea ?
|
|
4087
|
+
dir: mirrorArea ? join14(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
|
|
3926
4088
|
remote: t.remote ?? (id ? remotes[id] : void 0),
|
|
3927
4089
|
...forgeHost ? { forgeHost } : {}
|
|
3928
4090
|
};
|
|
@@ -4039,6 +4201,7 @@ function runScript(script, opts) {
|
|
|
4039
4201
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4040
4202
|
});
|
|
4041
4203
|
let output = "";
|
|
4204
|
+
let stdout = "";
|
|
4042
4205
|
let pending = "";
|
|
4043
4206
|
const absorb = (buf) => {
|
|
4044
4207
|
const text2 = buf.toString();
|
|
@@ -4049,18 +4212,21 @@ function runScript(script, opts) {
|
|
|
4049
4212
|
pending = lines2.pop() ?? "";
|
|
4050
4213
|
for (const l of lines2) opts.onLine(l);
|
|
4051
4214
|
};
|
|
4052
|
-
child.stdout.on("data",
|
|
4215
|
+
child.stdout.on("data", (buf) => {
|
|
4216
|
+
stdout += buf.toString();
|
|
4217
|
+
absorb(buf);
|
|
4218
|
+
});
|
|
4053
4219
|
child.stderr.on("data", absorb);
|
|
4054
4220
|
const timer = opts.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs) : void 0;
|
|
4055
4221
|
child.on("error", (err3) => {
|
|
4056
4222
|
if (timer) clearTimeout(timer);
|
|
4057
4223
|
resolvePromise({ code: 127, output: `${output}
|
|
4058
|
-
crew: cannot run ${shell.bin}: ${err3.message}
|
|
4224
|
+
crew: cannot run ${shell.bin}: ${err3.message}`, stdout });
|
|
4059
4225
|
});
|
|
4060
4226
|
child.on("close", (code) => {
|
|
4061
4227
|
if (timer) clearTimeout(timer);
|
|
4062
4228
|
if (pending && opts.onLine) opts.onLine(pending);
|
|
4063
|
-
resolvePromise({ code: code ?? 1, output });
|
|
4229
|
+
resolvePromise({ code: code ?? 1, output, stdout });
|
|
4064
4230
|
});
|
|
4065
4231
|
});
|
|
4066
4232
|
}
|
|
@@ -4200,9 +4366,9 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4200
4366
|
}
|
|
4201
4367
|
|
|
4202
4368
|
// src/install.ts
|
|
4203
|
-
import { dirname as
|
|
4369
|
+
import { dirname as dirname7, join as join15, extname } from "path";
|
|
4204
4370
|
import { homedir as homedir7, userInfo } from "os";
|
|
4205
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as
|
|
4371
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync14, statSync as statSync5, writeFileSync as writeFileSync10, unlinkSync, realpathSync } from "fs";
|
|
4206
4372
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4207
4373
|
import { createHash as createHash3 } from "crypto";
|
|
4208
4374
|
|
|
@@ -4214,7 +4380,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4214
4380
|
// src/install.ts
|
|
4215
4381
|
function crewInvocation(crewHome) {
|
|
4216
4382
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4217
|
-
return [process.execPath,
|
|
4383
|
+
return [process.execPath, join15(crewHome, "bin", "crew")];
|
|
4218
4384
|
}
|
|
4219
4385
|
var INTERVAL_SECONDS = 120;
|
|
4220
4386
|
function labelFor(crewHome, job) {
|
|
@@ -4245,14 +4411,14 @@ function schedulerLogFor(crewLog, job) {
|
|
|
4245
4411
|
return `${stem}.${suffix}${ext || ".log"}`;
|
|
4246
4412
|
}
|
|
4247
4413
|
function pathFor(ship) {
|
|
4248
|
-
const nodeBinDir =
|
|
4414
|
+
const nodeBinDir = dirname7(process.execPath);
|
|
4249
4415
|
const parts = [nodeBinDir];
|
|
4250
4416
|
if (ship.extraPath) parts.push(ship.extraPath);
|
|
4251
4417
|
parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
|
|
4252
4418
|
return parts.join(":");
|
|
4253
4419
|
}
|
|
4254
4420
|
function findOnPath(bin, searchPath) {
|
|
4255
|
-
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
4421
|
+
const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join15(dir, bin));
|
|
4256
4422
|
for (const candidate of candidates2) {
|
|
4257
4423
|
try {
|
|
4258
4424
|
accessSync2(candidate, constants2.X_OK);
|
|
@@ -4262,12 +4428,12 @@ function findOnPath(bin, searchPath) {
|
|
|
4262
4428
|
}
|
|
4263
4429
|
return void 0;
|
|
4264
4430
|
}
|
|
4265
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
4431
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join15(homedir7(), ".docker", "bin")].join(":");
|
|
4266
4432
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4267
4433
|
const schedulerPath = pathFor(ship);
|
|
4268
4434
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
4269
4435
|
const inShell = findOnPath("docker", shellPath);
|
|
4270
|
-
return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${
|
|
4436
|
+
return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname7(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
|
|
4271
4437
|
}
|
|
4272
4438
|
var USER_INSTALL = { root: false, user: "" };
|
|
4273
4439
|
function installContext(env = process.env) {
|
|
@@ -4277,7 +4443,7 @@ function installContext(env = process.env) {
|
|
|
4277
4443
|
}
|
|
4278
4444
|
var SYSTEM_UNIT_DIR = "/etc/systemd/system";
|
|
4279
4445
|
function systemUnitPathFor(label, suffix = "service") {
|
|
4280
|
-
return
|
|
4446
|
+
return join15(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
|
|
4281
4447
|
}
|
|
4282
4448
|
function unitEnvironment(ship) {
|
|
4283
4449
|
return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
|
|
@@ -4298,7 +4464,7 @@ function xmlEscape(s) {
|
|
|
4298
4464
|
}
|
|
4299
4465
|
function planLaunchd(ship, crewHome, job) {
|
|
4300
4466
|
const label = labelFor(crewHome, job);
|
|
4301
|
-
const unitPath =
|
|
4467
|
+
const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4302
4468
|
const crewLog = ship.logFile;
|
|
4303
4469
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4304
4470
|
const persistent = job === "run";
|
|
@@ -4352,7 +4518,7 @@ ${scheduleKeys}
|
|
|
4352
4518
|
}
|
|
4353
4519
|
function planSystemd(ship, crewHome, job, ctx) {
|
|
4354
4520
|
const label = labelFor(crewHome, job);
|
|
4355
|
-
const unitDir = ctx.root ? SYSTEM_UNIT_DIR :
|
|
4521
|
+
const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join15(homedir7(), ".config", "systemd", "user");
|
|
4356
4522
|
const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
|
|
4357
4523
|
const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
|
|
4358
4524
|
` : "";
|
|
@@ -4361,7 +4527,7 @@ function planSystemd(ship, crewHome, job, ctx) {
|
|
|
4361
4527
|
lingerCommand: ["loginctl", "enable-linger", ctx.user],
|
|
4362
4528
|
lingerHint: `the daemon will stop when ${ctx.user} logs out; run \`sudo loginctl enable-linger ${ctx.user}\` once, or run \`crew install\` as root for a system unit`
|
|
4363
4529
|
};
|
|
4364
|
-
const servicePath =
|
|
4530
|
+
const servicePath = join15(unitDir, `${label}.service`);
|
|
4365
4531
|
const crewLog = ship.logFile;
|
|
4366
4532
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4367
4533
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4402,7 +4568,7 @@ WantedBy=${wantedBy}
|
|
|
4402
4568
|
...linger
|
|
4403
4569
|
};
|
|
4404
4570
|
}
|
|
4405
|
-
const timerPath =
|
|
4571
|
+
const timerPath = join15(unitDir, `${label}.timer`);
|
|
4406
4572
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4407
4573
|
[Unit]
|
|
4408
4574
|
Description=${description}
|
|
@@ -4477,7 +4643,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
|
|
|
4477
4643
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4478
4644
|
const label = labelFor(crewHome, job);
|
|
4479
4645
|
if (hostShip === "macos") {
|
|
4480
|
-
const unitPath =
|
|
4646
|
+
const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4481
4647
|
return {
|
|
4482
4648
|
mechanism: "launchd",
|
|
4483
4649
|
unitPaths: [unitPath],
|
|
@@ -4487,20 +4653,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4487
4653
|
};
|
|
4488
4654
|
}
|
|
4489
4655
|
if (hostShip === "linux" && hasSystemd) {
|
|
4490
|
-
const system =
|
|
4491
|
-
const unitDir = system ? SYSTEM_UNIT_DIR :
|
|
4656
|
+
const system = existsSync13(systemUnitPathFor(label));
|
|
4657
|
+
const unitDir = system ? SYSTEM_UNIT_DIR : join15(homedir7(), ".config", "systemd", "user");
|
|
4492
4658
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
4493
4659
|
if (job === "run") {
|
|
4494
4660
|
return {
|
|
4495
4661
|
mechanism: "systemd",
|
|
4496
|
-
unitPaths: [
|
|
4662
|
+
unitPaths: [join15(unitDir, `${label}.service`)],
|
|
4497
4663
|
crontabLine: null,
|
|
4498
4664
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
|
|
4499
4665
|
};
|
|
4500
4666
|
}
|
|
4501
4667
|
return {
|
|
4502
4668
|
mechanism: "systemd",
|
|
4503
|
-
unitPaths: [
|
|
4669
|
+
unitPaths: [join15(unitDir, `${label}.service`), join15(unitDir, `${label}.timer`)],
|
|
4504
4670
|
crontabLine: null,
|
|
4505
4671
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
|
|
4506
4672
|
};
|
|
@@ -4518,7 +4684,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4518
4684
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4519
4685
|
const label = labelFor(crewHome, "run");
|
|
4520
4686
|
if (hostShip === "macos") {
|
|
4521
|
-
const unitPath =
|
|
4687
|
+
const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4522
4688
|
return {
|
|
4523
4689
|
mechanism: "launchd",
|
|
4524
4690
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4528,7 +4694,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
4528
4694
|
}
|
|
4529
4695
|
if (hostShip === "linux" && hasSystemd) {
|
|
4530
4696
|
const unit = `${label}.service`;
|
|
4531
|
-
const ctl =
|
|
4697
|
+
const ctl = existsSync13(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
4532
4698
|
return {
|
|
4533
4699
|
mechanism: "systemd",
|
|
4534
4700
|
startCommand: [...ctl, "start", unit],
|
|
@@ -4560,10 +4726,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4560
4726
|
log.emit(`would write ${path}`);
|
|
4561
4727
|
continue;
|
|
4562
4728
|
}
|
|
4563
|
-
const before =
|
|
4729
|
+
const before = existsSync13(path) ? readFileSync14(path, "utf8") : null;
|
|
4564
4730
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
4565
|
-
|
|
4566
|
-
|
|
4731
|
+
mkdirSync9(dirname7(path), { recursive: true });
|
|
4732
|
+
writeFileSync10(path, plan.unitContent[path]);
|
|
4567
4733
|
log.emit(`wrote ${path}`);
|
|
4568
4734
|
}
|
|
4569
4735
|
if (plan.crontabLine) {
|
|
@@ -4627,7 +4793,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4627
4793
|
}
|
|
4628
4794
|
}
|
|
4629
4795
|
for (const path of plan.unitPaths) {
|
|
4630
|
-
if (!
|
|
4796
|
+
if (!existsSync13(path)) continue;
|
|
4631
4797
|
if (dryRun) {
|
|
4632
4798
|
log.emit(`would remove ${path}`);
|
|
4633
4799
|
continue;
|
|
@@ -4646,7 +4812,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4646
4812
|
}
|
|
4647
4813
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4648
4814
|
function launchAgentsDir() {
|
|
4649
|
-
return
|
|
4815
|
+
return join15(homedir7(), "Library", "LaunchAgents");
|
|
4650
4816
|
}
|
|
4651
4817
|
function xmlUnescape(s) {
|
|
4652
4818
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4663,25 +4829,25 @@ function loadedCrewLabels() {
|
|
|
4663
4829
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
4664
4830
|
}
|
|
4665
4831
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4666
|
-
const files =
|
|
4832
|
+
const files = existsSync13(dir) ? readdirSync3(dir) : [];
|
|
4667
4833
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4668
|
-
const plistPath =
|
|
4834
|
+
const plistPath = join15(dir, f);
|
|
4669
4835
|
let program = null;
|
|
4670
4836
|
try {
|
|
4671
|
-
program = programFromPlist(
|
|
4837
|
+
program = programFromPlist(readFileSync14(plistPath, "utf8"));
|
|
4672
4838
|
} catch {
|
|
4673
4839
|
}
|
|
4674
4840
|
return {
|
|
4675
4841
|
label: f.slice(0, -".plist".length),
|
|
4676
4842
|
plistPath,
|
|
4677
4843
|
program,
|
|
4678
|
-
programExists: program !== null &&
|
|
4844
|
+
programExists: program !== null && existsSync13(program)
|
|
4679
4845
|
};
|
|
4680
4846
|
});
|
|
4681
4847
|
const seen = new Set(units.map((u) => u.label));
|
|
4682
4848
|
for (const label of [...loaded].sort()) {
|
|
4683
4849
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4684
|
-
units.push({ label, plistPath:
|
|
4850
|
+
units.push({ label, plistPath: join15(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4685
4851
|
}
|
|
4686
4852
|
return units;
|
|
4687
4853
|
}
|
|
@@ -4694,9 +4860,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
4694
4860
|
log.emit(`would unload and remove ${unit.label}`);
|
|
4695
4861
|
return;
|
|
4696
4862
|
}
|
|
4697
|
-
if (
|
|
4863
|
+
if (existsSync13(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
4698
4864
|
spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
4699
|
-
if (
|
|
4865
|
+
if (existsSync13(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
4700
4866
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
4701
4867
|
}
|
|
4702
4868
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -4774,24 +4940,24 @@ function detectSystemd() {
|
|
|
4774
4940
|
}
|
|
4775
4941
|
|
|
4776
4942
|
// src/ship-attention.ts
|
|
4777
|
-
import { existsSync as
|
|
4943
|
+
import { existsSync as existsSync15, statfsSync } from "fs";
|
|
4778
4944
|
|
|
4779
4945
|
// src/git.ts
|
|
4780
|
-
import { execFileSync as
|
|
4946
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
4781
4947
|
import {
|
|
4782
|
-
existsSync as
|
|
4783
|
-
mkdirSync as
|
|
4948
|
+
existsSync as existsSync14,
|
|
4949
|
+
mkdirSync as mkdirSync10,
|
|
4784
4950
|
openSync as openSync2,
|
|
4785
4951
|
closeSync as closeSync2,
|
|
4786
4952
|
writeSync,
|
|
4787
4953
|
statSync as statSync6,
|
|
4788
4954
|
unlinkSync as unlinkSync2,
|
|
4789
|
-
readFileSync as
|
|
4790
|
-
writeFileSync as
|
|
4955
|
+
readFileSync as readFileSync15,
|
|
4956
|
+
writeFileSync as writeFileSync11,
|
|
4791
4957
|
readdirSync as readdirSync4,
|
|
4792
4958
|
rmSync as rmSync8
|
|
4793
4959
|
} from "fs";
|
|
4794
|
-
import { dirname as
|
|
4960
|
+
import { dirname as dirname8, basename as basename5, join as join16 } from "path";
|
|
4795
4961
|
var GitError = class extends Error {
|
|
4796
4962
|
};
|
|
4797
4963
|
function streamText(x) {
|
|
@@ -4799,7 +4965,7 @@ function streamText(x) {
|
|
|
4799
4965
|
}
|
|
4800
4966
|
function gitRaw(cwd, args) {
|
|
4801
4967
|
try {
|
|
4802
|
-
return
|
|
4968
|
+
return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
4803
4969
|
} catch (e) {
|
|
4804
4970
|
const err3 = e;
|
|
4805
4971
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4808,7 +4974,7 @@ function gitRaw(cwd, args) {
|
|
|
4808
4974
|
}
|
|
4809
4975
|
function git(cwd, args) {
|
|
4810
4976
|
try {
|
|
4811
|
-
return
|
|
4977
|
+
return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
4812
4978
|
} catch (e) {
|
|
4813
4979
|
const err3 = e;
|
|
4814
4980
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4979,9 +5145,9 @@ function keyCommitsInRange(cwd, key, range) {
|
|
|
4979
5145
|
}
|
|
4980
5146
|
function patchIdOf(cwd, from, to) {
|
|
4981
5147
|
try {
|
|
4982
|
-
const diff =
|
|
5148
|
+
const diff = execFileSync4("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
4983
5149
|
if (!diff) return null;
|
|
4984
|
-
const out =
|
|
5150
|
+
const out = execFileSync4("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
|
|
4985
5151
|
return out.split(" ")[0]?.trim() || null;
|
|
4986
5152
|
} catch {
|
|
4987
5153
|
return null;
|
|
@@ -5171,7 +5337,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
5171
5337
|
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5172
5338
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
5173
5339
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
5174
|
-
const root =
|
|
5340
|
+
const root = dirname8(path);
|
|
5175
5341
|
if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
|
|
5176
5342
|
}
|
|
5177
5343
|
}
|
|
@@ -5189,11 +5355,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5189
5355
|
}
|
|
5190
5356
|
for (const name of names) {
|
|
5191
5357
|
if (!name.startsWith("crew-release-")) continue;
|
|
5192
|
-
const root =
|
|
5358
|
+
const root = join16(tmp, name);
|
|
5193
5359
|
try {
|
|
5194
5360
|
const st = statSync6(root);
|
|
5195
5361
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5196
|
-
if (
|
|
5362
|
+
if (existsSync14(join16(root, "checkout"))) continue;
|
|
5197
5363
|
rmSync8(root, { recursive: true, force: true });
|
|
5198
5364
|
removed.push(name);
|
|
5199
5365
|
} catch {
|
|
@@ -5245,16 +5411,16 @@ function originUrlOf(dir, remoteName) {
|
|
|
5245
5411
|
}
|
|
5246
5412
|
}
|
|
5247
5413
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5248
|
-
if (
|
|
5414
|
+
if (existsSync14(`${dir}/.git`)) return false;
|
|
5249
5415
|
if (!remote) {
|
|
5250
5416
|
throw new GitError(
|
|
5251
5417
|
`${dir} does not exist and this repo has no discovered remote to clone from \u2014 re-run \`crew connect\`, or check out the repository yourself and point \`repos:\` at it`
|
|
5252
5418
|
);
|
|
5253
5419
|
}
|
|
5254
|
-
|
|
5420
|
+
mkdirSync10(dirname8(dir), { recursive: true });
|
|
5255
5421
|
const url = cloneUrlFor(remote, opts);
|
|
5256
5422
|
try {
|
|
5257
|
-
|
|
5423
|
+
execFileSync4("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
5258
5424
|
} catch (e) {
|
|
5259
5425
|
const err3 = e;
|
|
5260
5426
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5276,7 +5442,7 @@ function sleepSync(ms3) {
|
|
|
5276
5442
|
}
|
|
5277
5443
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
5278
5444
|
const lockPath = checkoutLockPath(dir);
|
|
5279
|
-
|
|
5445
|
+
mkdirSync10(dirname8(lockPath), { recursive: true });
|
|
5280
5446
|
const deadline = Date.now() + timeoutMs;
|
|
5281
5447
|
for (; ; ) {
|
|
5282
5448
|
try {
|
|
@@ -5313,17 +5479,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5313
5479
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5314
5480
|
const release = acquireCheckoutLock(dir);
|
|
5315
5481
|
try {
|
|
5316
|
-
if (!
|
|
5482
|
+
if (!existsSync14(`${dir}/.git`) && githubRemote) {
|
|
5317
5483
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5318
5484
|
const marker = cloneFailureMarker(dir);
|
|
5319
|
-
if (
|
|
5320
|
-
return { action: "clone-failed", detail:
|
|
5485
|
+
if (existsSync14(marker) && readFileSync15(marker, "utf8").split("\n")[0] === url) {
|
|
5486
|
+
return { action: "clone-failed", detail: readFileSync15(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
5321
5487
|
}
|
|
5322
5488
|
try {
|
|
5323
5489
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
5324
5490
|
} catch (e) {
|
|
5325
5491
|
if (!(e instanceof GitError)) throw e;
|
|
5326
|
-
|
|
5492
|
+
writeFileSync11(marker, `${url}
|
|
5327
5493
|
${e.message}`);
|
|
5328
5494
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
5329
5495
|
}
|
|
@@ -5549,7 +5715,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
5549
5715
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
5550
5716
|
for (const i of state.shipAttention().previous()) {
|
|
5551
5717
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
5552
|
-
const gone = !
|
|
5718
|
+
const gone = !existsSync15(i.held.path);
|
|
5553
5719
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
5554
5720
|
clearShipAttention(state, i.key);
|
|
5555
5721
|
log.emit(
|
|
@@ -5667,6 +5833,18 @@ function repoFindings(o) {
|
|
|
5667
5833
|
remedy: "add the directory holding it to ship.extraPath in crew.yaml"
|
|
5668
5834
|
});
|
|
5669
5835
|
}
|
|
5836
|
+
if (o.intelSyntax) {
|
|
5837
|
+
if (o.intelSyntax.status === "error") {
|
|
5838
|
+
problems.push({
|
|
5839
|
+
severity: "attention",
|
|
5840
|
+
kind: "intel_hook_syntax",
|
|
5841
|
+
...at,
|
|
5842
|
+
hook: "intel",
|
|
5843
|
+
message: `${o.route}/${o.repo}: the intel hook fails its syntax check, so no symbol service will start: ${(o.intelSyntax.message ?? "").split("\n")[0]}`,
|
|
5844
|
+
remedy: "fix hooks.intel in the repository's .crew.yaml (`crew repos check` prints the full error)"
|
|
5845
|
+
});
|
|
5846
|
+
} else passed++;
|
|
5847
|
+
}
|
|
5670
5848
|
return { problems, passed };
|
|
5671
5849
|
}
|
|
5672
5850
|
function attentionProblems(items, already) {
|
|
@@ -5677,13 +5855,13 @@ function attentionProblems(items, already) {
|
|
|
5677
5855
|
// src/sandbox-image.ts
|
|
5678
5856
|
import { createHash as createHash6 } from "crypto";
|
|
5679
5857
|
import { spawn as spawn4 } from "child_process";
|
|
5680
|
-
import { execFileSync as
|
|
5681
|
-
import { mkdirSync as
|
|
5682
|
-
import { dirname as
|
|
5858
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
5859
|
+
import { mkdirSync as mkdirSync16, readFileSync as readFileSync23, renameSync as renameSync3, writeFileSync as writeFileSync17, rmSync as rmSync13 } from "fs";
|
|
5860
|
+
import { dirname as dirname12, join as join28 } from "path";
|
|
5683
5861
|
import { tmpdir as tmpdir3 } from "os";
|
|
5684
5862
|
|
|
5685
5863
|
// src/passenger-containers.ts
|
|
5686
|
-
import { execFileSync as
|
|
5864
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
5687
5865
|
import { createHash as createHash5 } from "crypto";
|
|
5688
5866
|
|
|
5689
5867
|
// src/sandbox-cache.ts
|
|
@@ -5692,32 +5870,32 @@ import { promisify as promisify2 } from "util";
|
|
|
5692
5870
|
|
|
5693
5871
|
// src/deprovision-host.ts
|
|
5694
5872
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5695
|
-
import { existsSync as
|
|
5873
|
+
import { existsSync as existsSync26, readdirSync as readdirSync11 } from "fs";
|
|
5696
5874
|
|
|
5697
5875
|
// src/interrupted.ts
|
|
5698
|
-
import { existsSync as
|
|
5699
|
-
import { join as
|
|
5876
|
+
import { existsSync as existsSync24, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
|
|
5877
|
+
import { join as join26 } from "path";
|
|
5700
5878
|
|
|
5701
5879
|
// src/agent.ts
|
|
5702
5880
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5703
|
-
import { readFileSync as
|
|
5881
|
+
import { readFileSync as readFileSync21, existsSync as existsSync23, statSync as statSync8, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
5704
5882
|
import { StringDecoder } from "string_decoder";
|
|
5705
5883
|
import { finished } from "stream/promises";
|
|
5706
|
-
import { join as
|
|
5884
|
+
import { join as join25, dirname as dirname11, basename as basename9 } from "path";
|
|
5707
5885
|
import { createHash as createHash4 } from "crypto";
|
|
5708
5886
|
|
|
5709
5887
|
// src/state.ts
|
|
5710
|
-
import { readFileSync as
|
|
5711
|
-
import { join as
|
|
5888
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, existsSync as existsSync16, mkdirSync as mkdirSync11, rmSync as rmSync9, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
|
|
5889
|
+
import { join as join17 } from "path";
|
|
5712
5890
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5713
5891
|
var State = class _State {
|
|
5714
5892
|
dir;
|
|
5715
5893
|
constructor(dir) {
|
|
5716
5894
|
this.dir = dir;
|
|
5717
|
-
|
|
5895
|
+
mkdirSync11(dir, { recursive: true });
|
|
5718
5896
|
}
|
|
5719
5897
|
path(...parts) {
|
|
5720
|
-
return
|
|
5898
|
+
return join17(this.dir, ...parts);
|
|
5721
5899
|
}
|
|
5722
5900
|
/**
|
|
5723
5901
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5728,14 +5906,14 @@ var State = class _State {
|
|
|
5728
5906
|
*/
|
|
5729
5907
|
watermark() {
|
|
5730
5908
|
try {
|
|
5731
|
-
const v =
|
|
5909
|
+
const v = readFileSync16(this.path(".poll-watermark"), "utf8").trim();
|
|
5732
5910
|
return v || EPOCH;
|
|
5733
5911
|
} catch {
|
|
5734
5912
|
return EPOCH;
|
|
5735
5913
|
}
|
|
5736
5914
|
}
|
|
5737
5915
|
setWatermark(at) {
|
|
5738
|
-
|
|
5916
|
+
writeFileSync12(this.path(".poll-watermark"), `${at}
|
|
5739
5917
|
`);
|
|
5740
5918
|
}
|
|
5741
5919
|
/**
|
|
@@ -5753,16 +5931,16 @@ var State = class _State {
|
|
|
5753
5931
|
}
|
|
5754
5932
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
5755
5933
|
isPaused() {
|
|
5756
|
-
return
|
|
5934
|
+
return existsSync16(this.path(".crew-paused"));
|
|
5757
5935
|
}
|
|
5758
5936
|
isRolePaused(role) {
|
|
5759
|
-
return
|
|
5937
|
+
return existsSync16(this.path(`.role-paused-${role}`));
|
|
5760
5938
|
}
|
|
5761
5939
|
pausedRoles(roles) {
|
|
5762
5940
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5763
5941
|
}
|
|
5764
5942
|
pause(role) {
|
|
5765
|
-
|
|
5943
|
+
writeFileSync12(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5766
5944
|
}
|
|
5767
5945
|
resume(role) {
|
|
5768
5946
|
rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -5773,10 +5951,10 @@ var State = class _State {
|
|
|
5773
5951
|
* while agents keep working.
|
|
5774
5952
|
*/
|
|
5775
5953
|
isReleasePaused() {
|
|
5776
|
-
return
|
|
5954
|
+
return existsSync16(this.path(".release-paused"));
|
|
5777
5955
|
}
|
|
5778
5956
|
setReleasePaused(paused) {
|
|
5779
|
-
if (paused)
|
|
5957
|
+
if (paused) writeFileSync12(this.path(".release-paused"), "");
|
|
5780
5958
|
else rmSync9(this.path(".release-paused"), { force: true });
|
|
5781
5959
|
}
|
|
5782
5960
|
/**
|
|
@@ -5814,7 +5992,7 @@ var State = class _State {
|
|
|
5814
5992
|
const name = _State.safe(rawName);
|
|
5815
5993
|
const file = this.path(`.${name}.lock`);
|
|
5816
5994
|
try {
|
|
5817
|
-
const existing = Number.parseInt(
|
|
5995
|
+
const existing = Number.parseInt(readFileSync16(file, "utf8").trim(), 10);
|
|
5818
5996
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5819
5997
|
try {
|
|
5820
5998
|
process.kill(existing, 0);
|
|
@@ -5824,7 +6002,7 @@ var State = class _State {
|
|
|
5824
6002
|
}
|
|
5825
6003
|
} catch {
|
|
5826
6004
|
}
|
|
5827
|
-
|
|
6005
|
+
writeFileSync12(file, `${process.pid}
|
|
5828
6006
|
`);
|
|
5829
6007
|
return {
|
|
5830
6008
|
ok: true,
|
|
@@ -5852,7 +6030,7 @@ var State = class _State {
|
|
|
5852
6030
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
5853
6031
|
let pid = 0;
|
|
5854
6032
|
try {
|
|
5855
|
-
pid = Number.parseInt(
|
|
6033
|
+
pid = Number.parseInt(readFileSync16(this.path(n), "utf8").trim(), 10);
|
|
5856
6034
|
} catch {
|
|
5857
6035
|
continue;
|
|
5858
6036
|
}
|
|
@@ -5938,7 +6116,7 @@ var State = class _State {
|
|
|
5938
6116
|
ticketInFlight(recordId) {
|
|
5939
6117
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
5940
6118
|
try {
|
|
5941
|
-
const pid = Number.parseInt(
|
|
6119
|
+
const pid = Number.parseInt(readFileSync16(file, "utf8").trim(), 10);
|
|
5942
6120
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
5943
6121
|
process.kill(pid, 0);
|
|
5944
6122
|
return true;
|
|
@@ -5972,7 +6150,7 @@ var State = class _State {
|
|
|
5972
6150
|
const heldReported = `.release-held-${suffix}`;
|
|
5973
6151
|
const read = (f) => {
|
|
5974
6152
|
try {
|
|
5975
|
-
return
|
|
6153
|
+
return readFileSync16(this.path(f), "utf8").trim() || null;
|
|
5976
6154
|
} catch {
|
|
5977
6155
|
return null;
|
|
5978
6156
|
}
|
|
@@ -5981,7 +6159,7 @@ var State = class _State {
|
|
|
5981
6159
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5982
6160
|
noteBlocked: () => {
|
|
5983
6161
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5984
|
-
|
|
6162
|
+
writeFileSync12(this.path(blocked), `${n}
|
|
5985
6163
|
`);
|
|
5986
6164
|
return n;
|
|
5987
6165
|
},
|
|
@@ -5998,7 +6176,7 @@ var State = class _State {
|
|
|
5998
6176
|
*/
|
|
5999
6177
|
deployFailedSha: () => read(failed),
|
|
6000
6178
|
noteDeployFailed: (sha) => {
|
|
6001
|
-
|
|
6179
|
+
writeFileSync12(this.path(failed), `${sha}
|
|
6002
6180
|
`);
|
|
6003
6181
|
},
|
|
6004
6182
|
clearDeployFailed: () => {
|
|
@@ -6012,7 +6190,7 @@ var State = class _State {
|
|
|
6012
6190
|
noteTestGateFailed: (sha) => {
|
|
6013
6191
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
6014
6192
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
6015
|
-
|
|
6193
|
+
writeFileSync12(this.path(gateFailed), `${sha} ${next}
|
|
6016
6194
|
`);
|
|
6017
6195
|
return next;
|
|
6018
6196
|
},
|
|
@@ -6022,13 +6200,13 @@ var State = class _State {
|
|
|
6022
6200
|
/** Whether the board was already told about this head's red gate. */
|
|
6023
6201
|
heldReported: () => {
|
|
6024
6202
|
try {
|
|
6025
|
-
return
|
|
6203
|
+
return readFileSync16(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
6026
6204
|
} catch {
|
|
6027
6205
|
return null;
|
|
6028
6206
|
}
|
|
6029
6207
|
},
|
|
6030
6208
|
noteHeldReported: (signature) => {
|
|
6031
|
-
|
|
6209
|
+
writeFileSync12(this.path(heldReported), `${signature}
|
|
6032
6210
|
`);
|
|
6033
6211
|
},
|
|
6034
6212
|
clearHeldReported: () => {
|
|
@@ -6036,7 +6214,7 @@ var State = class _State {
|
|
|
6036
6214
|
},
|
|
6037
6215
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
6038
6216
|
noteTestGateReported: (sha) => {
|
|
6039
|
-
|
|
6217
|
+
writeFileSync12(this.path(gateReported), `${sha}
|
|
6040
6218
|
`);
|
|
6041
6219
|
}
|
|
6042
6220
|
};
|
|
@@ -6059,7 +6237,7 @@ var State = class _State {
|
|
|
6059
6237
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
6060
6238
|
const read = (f) => {
|
|
6061
6239
|
try {
|
|
6062
|
-
return
|
|
6240
|
+
return readFileSync16(this.path(f), "utf8").trim() || null;
|
|
6063
6241
|
} catch {
|
|
6064
6242
|
return null;
|
|
6065
6243
|
}
|
|
@@ -6092,18 +6270,18 @@ var State = class _State {
|
|
|
6092
6270
|
clear2();
|
|
6093
6271
|
return;
|
|
6094
6272
|
}
|
|
6095
|
-
|
|
6273
|
+
writeFileSync12(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
6096
6274
|
`);
|
|
6097
6275
|
const prior = read(ticketFile);
|
|
6098
6276
|
if (topTicket && topTicket === prior) {
|
|
6099
|
-
|
|
6277
|
+
writeFileSync12(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
6100
6278
|
`);
|
|
6101
6279
|
} else if (topTicket) {
|
|
6102
|
-
|
|
6280
|
+
writeFileSync12(this.path(ticketFile), `${topTicket}
|
|
6103
6281
|
`);
|
|
6104
|
-
|
|
6282
|
+
writeFileSync12(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6105
6283
|
`);
|
|
6106
|
-
|
|
6284
|
+
writeFileSync12(this.path(ticketStreakFile), "1\n");
|
|
6107
6285
|
}
|
|
6108
6286
|
}
|
|
6109
6287
|
};
|
|
@@ -6124,14 +6302,14 @@ var State = class _State {
|
|
|
6124
6302
|
return {
|
|
6125
6303
|
previous: () => {
|
|
6126
6304
|
try {
|
|
6127
|
-
const parsed = JSON.parse(
|
|
6305
|
+
const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
|
|
6128
6306
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6129
6307
|
} catch {
|
|
6130
6308
|
return {};
|
|
6131
6309
|
}
|
|
6132
6310
|
},
|
|
6133
6311
|
persist: (next) => {
|
|
6134
|
-
|
|
6312
|
+
writeFileSync12(this.path(file), JSON.stringify(next));
|
|
6135
6313
|
}
|
|
6136
6314
|
};
|
|
6137
6315
|
}
|
|
@@ -6147,14 +6325,14 @@ var State = class _State {
|
|
|
6147
6325
|
return {
|
|
6148
6326
|
previous: () => {
|
|
6149
6327
|
try {
|
|
6150
|
-
const parsed = JSON.parse(
|
|
6328
|
+
const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
|
|
6151
6329
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6152
6330
|
} catch {
|
|
6153
6331
|
return [];
|
|
6154
6332
|
}
|
|
6155
6333
|
},
|
|
6156
6334
|
persist: (next) => {
|
|
6157
|
-
|
|
6335
|
+
writeFileSync12(this.path(file), JSON.stringify(next));
|
|
6158
6336
|
}
|
|
6159
6337
|
};
|
|
6160
6338
|
}
|
|
@@ -6168,14 +6346,14 @@ var State = class _State {
|
|
|
6168
6346
|
return {
|
|
6169
6347
|
previous: () => {
|
|
6170
6348
|
try {
|
|
6171
|
-
const parsed = JSON.parse(
|
|
6349
|
+
const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
|
|
6172
6350
|
return Array.isArray(parsed) ? parsed : [];
|
|
6173
6351
|
} catch {
|
|
6174
6352
|
return [];
|
|
6175
6353
|
}
|
|
6176
6354
|
},
|
|
6177
6355
|
persist: (next) => {
|
|
6178
|
-
|
|
6356
|
+
writeFileSync12(this.path(file), JSON.stringify(next));
|
|
6179
6357
|
}
|
|
6180
6358
|
};
|
|
6181
6359
|
}
|
|
@@ -6188,7 +6366,7 @@ var State = class _State {
|
|
|
6188
6366
|
const file = ".release-last-run";
|
|
6189
6367
|
return {
|
|
6190
6368
|
touch: () => {
|
|
6191
|
-
|
|
6369
|
+
writeFileSync12(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
6192
6370
|
},
|
|
6193
6371
|
at: () => {
|
|
6194
6372
|
try {
|
|
@@ -6201,7 +6379,7 @@ var State = class _State {
|
|
|
6201
6379
|
}
|
|
6202
6380
|
readInt(file) {
|
|
6203
6381
|
try {
|
|
6204
|
-
return Number.parseInt(
|
|
6382
|
+
return Number.parseInt(readFileSync16(this.path(file), "utf8").trim(), 10) || 0;
|
|
6205
6383
|
} catch {
|
|
6206
6384
|
return 0;
|
|
6207
6385
|
}
|
|
@@ -6280,6 +6458,8 @@ var HOOK_PURPOSE = [
|
|
|
6280
6458
|
["ports", "this worktree's port assignments; prints KEY=value overrides to export"],
|
|
6281
6459
|
["handoff", "leave the operator able to open what you built; put its output in your progress comment"],
|
|
6282
6460
|
["preview", "start this worktree's dev server for a viewer, bound to CREW_PREVIEW_HOST, detached; its last stdout line is the URL"],
|
|
6461
|
+
["intel", "start this worktree's symbol service, loopback only, detached; its last stdout line is the URL of an MCP endpoint"],
|
|
6462
|
+
["map", 'prints an outline of the repository; the crew places it under "Repository map" below'],
|
|
6283
6463
|
["test", "run the suite"],
|
|
6284
6464
|
["build", "build it"],
|
|
6285
6465
|
["ci_status", "after you push and before you hand off: exit 0 means CI is green for your branch; on failure fix and push again, and say in your progress comment if it is still pending"]
|
|
@@ -6376,6 +6556,13 @@ function repoSection(r, key, heading) {
|
|
|
6376
6556
|
}
|
|
6377
6557
|
return lines2;
|
|
6378
6558
|
}
|
|
6559
|
+
function mapSection(r, heading) {
|
|
6560
|
+
if (r.map) return ["", heading, "", r.map, ""];
|
|
6561
|
+
if (r.mapSkipped) {
|
|
6562
|
+
return ["", `Note: ${r.mapSkipped}. Say so in one line of your progress comment; it does not block the work.`, ""];
|
|
6563
|
+
}
|
|
6564
|
+
return [];
|
|
6565
|
+
}
|
|
6379
6566
|
function renderEnvironment(i) {
|
|
6380
6567
|
const { route: route2, contract } = i;
|
|
6381
6568
|
const repos = i.repos;
|
|
@@ -6567,12 +6754,16 @@ function renderEnvironment(i) {
|
|
|
6567
6754
|
for (const r of repos) {
|
|
6568
6755
|
lines2.push(...i.sandbox ? ["", `### ${r.name}`] : repoSection(r, key, `### ${r.name} \u2014 \`${r.dir}\``));
|
|
6569
6756
|
lines2.push("", `Hooks for ${r.name} \u2014 the only commands you should run here:`, "", hookTable(r.config));
|
|
6757
|
+
lines2.push(...mapSection(r, `#### Repository map \u2014 ${r.name}`));
|
|
6570
6758
|
}
|
|
6571
6759
|
}
|
|
6572
6760
|
if (i.repoDir) {
|
|
6573
6761
|
lines2.push("", `This ticket's work happens in \`${i.repoDir}\`.`);
|
|
6574
6762
|
}
|
|
6575
|
-
if (only)
|
|
6763
|
+
if (only) {
|
|
6764
|
+
lines2.push("", "## Hooks \u2014 the only commands you should run", "", hookTable(only.config));
|
|
6765
|
+
lines2.push(...mapSection(only, "## Repository map"));
|
|
6766
|
+
}
|
|
6576
6767
|
return lines2.join("\n");
|
|
6577
6768
|
}
|
|
6578
6769
|
|
|
@@ -6742,8 +6933,8 @@ var PERISCOPE_COLUMNS = [
|
|
|
6742
6933
|
];
|
|
6743
6934
|
|
|
6744
6935
|
// src/agent-log.ts
|
|
6745
|
-
import { existsSync as
|
|
6746
|
-
import { join as
|
|
6936
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readdirSync as readdirSync6, readFileSync as readFileSync17, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "fs";
|
|
6937
|
+
import { join as join18 } from "path";
|
|
6747
6938
|
|
|
6748
6939
|
// src/pending-backoff.ts
|
|
6749
6940
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -6915,20 +7106,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
6915
7106
|
};
|
|
6916
7107
|
}
|
|
6917
7108
|
}
|
|
6918
|
-
var queueDir = (stateDir) =>
|
|
7109
|
+
var queueDir = (stateDir) => join18(stateDir, "pending-agent-logs");
|
|
6919
7110
|
function queueAgentRun(stateDir, name, q2) {
|
|
6920
|
-
|
|
6921
|
-
|
|
7111
|
+
mkdirSync12(queueDir(stateDir), { recursive: true });
|
|
7112
|
+
writeFileSync13(join18(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6922
7113
|
}
|
|
6923
7114
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6924
7115
|
const dir = queueDir(stateDir);
|
|
6925
|
-
if (!
|
|
7116
|
+
if (!existsSync17(dir)) return 0;
|
|
6926
7117
|
let sent = 0;
|
|
6927
7118
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6928
|
-
const path =
|
|
7119
|
+
const path = join18(dir, f);
|
|
6929
7120
|
let q2;
|
|
6930
7121
|
try {
|
|
6931
|
-
q2 = JSON.parse(
|
|
7122
|
+
q2 = JSON.parse(readFileSync17(path, "utf8"));
|
|
6932
7123
|
} catch {
|
|
6933
7124
|
continue;
|
|
6934
7125
|
}
|
|
@@ -6957,7 +7148,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6957
7148
|
continue;
|
|
6958
7149
|
}
|
|
6959
7150
|
const attempts = q2.attempts + 1;
|
|
6960
|
-
|
|
7151
|
+
writeFileSync13(path, JSON.stringify({
|
|
6961
7152
|
...q2,
|
|
6962
7153
|
attempts,
|
|
6963
7154
|
warned: true,
|
|
@@ -6968,20 +7159,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6968
7159
|
}
|
|
6969
7160
|
return sent;
|
|
6970
7161
|
}
|
|
6971
|
-
var countersDir = (stateDir) =>
|
|
7162
|
+
var countersDir = (stateDir) => join18(stateDir, "pending-ticket-counters");
|
|
6972
7163
|
function queueTicketCounters(stateDir, name, q2) {
|
|
6973
|
-
|
|
6974
|
-
|
|
7164
|
+
mkdirSync12(countersDir(stateDir), { recursive: true });
|
|
7165
|
+
writeFileSync13(join18(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6975
7166
|
}
|
|
6976
7167
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6977
7168
|
const dir = countersDir(stateDir);
|
|
6978
|
-
if (!
|
|
7169
|
+
if (!existsSync17(dir)) return 0;
|
|
6979
7170
|
let sent = 0;
|
|
6980
7171
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6981
|
-
const path =
|
|
7172
|
+
const path = join18(dir, f);
|
|
6982
7173
|
let q2;
|
|
6983
7174
|
try {
|
|
6984
|
-
q2 = JSON.parse(
|
|
7175
|
+
q2 = JSON.parse(readFileSync17(path, "utf8"));
|
|
6985
7176
|
} catch {
|
|
6986
7177
|
continue;
|
|
6987
7178
|
}
|
|
@@ -7000,7 +7191,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
7000
7191
|
continue;
|
|
7001
7192
|
}
|
|
7002
7193
|
const attempts = q2.attempts + 1;
|
|
7003
|
-
|
|
7194
|
+
writeFileSync13(path, JSON.stringify({
|
|
7004
7195
|
...q2,
|
|
7005
7196
|
attempts,
|
|
7006
7197
|
warned: true,
|
|
@@ -7203,8 +7394,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7203
7394
|
};
|
|
7204
7395
|
|
|
7205
7396
|
// src/pending-handoff.ts
|
|
7206
|
-
import { existsSync as
|
|
7207
|
-
import { join as
|
|
7397
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync7, readFileSync as readFileSync18, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
7398
|
+
import { join as join21 } from "path";
|
|
7208
7399
|
|
|
7209
7400
|
// src/ticket-branch.ts
|
|
7210
7401
|
import { basename as basename7 } from "path";
|
|
@@ -8370,16 +8561,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8370
8561
|
}
|
|
8371
8562
|
|
|
8372
8563
|
// src/run-worktree.ts
|
|
8373
|
-
import { existsSync as
|
|
8374
|
-
import { dirname as
|
|
8564
|
+
import { existsSync as existsSync19 } from "fs";
|
|
8565
|
+
import { dirname as dirname9, join as join20 } from "path";
|
|
8375
8566
|
|
|
8376
8567
|
// src/ports.ts
|
|
8377
|
-
import { execFileSync as
|
|
8378
|
-
import { existsSync as
|
|
8379
|
-
import { join as
|
|
8568
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
8569
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8570
|
+
import { join as join19 } from "path";
|
|
8380
8571
|
var run = (cmd, args) => {
|
|
8381
8572
|
try {
|
|
8382
|
-
return
|
|
8573
|
+
return execFileSync5(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
8383
8574
|
} catch {
|
|
8384
8575
|
return "";
|
|
8385
8576
|
}
|
|
@@ -8421,7 +8612,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
8421
8612
|
}
|
|
8422
8613
|
function worktreeExists(parent, prefix, n) {
|
|
8423
8614
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
8424
|
-
if (
|
|
8615
|
+
if (existsSync18(join19(parent, name, ".git"))) return true;
|
|
8425
8616
|
}
|
|
8426
8617
|
return false;
|
|
8427
8618
|
}
|
|
@@ -8504,7 +8695,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8504
8695
|
const existing = worktreeForNumber(dir, num);
|
|
8505
8696
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
8506
8697
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
8507
|
-
if (existing && !existing.prunable &&
|
|
8698
|
+
if (existing && !existing.prunable && existsSync19(existing.path)) {
|
|
8508
8699
|
const branch = existing.branch;
|
|
8509
8700
|
if (status(existing.path).length > 0) {
|
|
8510
8701
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -8520,7 +8711,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8520
8711
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8521
8712
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8522
8713
|
}
|
|
8523
|
-
const path =
|
|
8714
|
+
const path = join20(dirname9(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8524
8715
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8525
8716
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8526
8717
|
}
|
|
@@ -8574,7 +8765,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8574
8765
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
8575
8766
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
8576
8767
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8577
|
-
if (wt &&
|
|
8768
|
+
if (wt && existsSync19(wt.path)) {
|
|
8578
8769
|
if (status(wt.path).length > 0) return false;
|
|
8579
8770
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
8580
8771
|
}
|
|
@@ -8582,7 +8773,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8582
8773
|
}
|
|
8583
8774
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
8584
8775
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8585
|
-
if (!wt || !
|
|
8776
|
+
if (!wt || !existsSync19(wt.path)) return { kind: "none" };
|
|
8586
8777
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
8587
8778
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
8588
8779
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -8594,7 +8785,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
8594
8785
|
if (pids.length) await killGently(pids);
|
|
8595
8786
|
}
|
|
8596
8787
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
8597
|
-
return
|
|
8788
|
+
return existsSync19(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
8598
8789
|
}
|
|
8599
8790
|
|
|
8600
8791
|
// src/handoff-guard.ts
|
|
@@ -8977,20 +9168,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
8977
9168
|
|
|
8978
9169
|
// src/pending-handoff.ts
|
|
8979
9170
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
8980
|
-
var pendingDirFor = (stateDir) =>
|
|
8981
|
-
var stateDirOfStream = (streamPath) =>
|
|
9171
|
+
var pendingDirFor = (stateDir) => join21(stateDir, "pending-handoffs");
|
|
9172
|
+
var stateDirOfStream = (streamPath) => join21(streamPath, "..", "..");
|
|
8982
9173
|
function writePendingHandoff(stateDir, rec) {
|
|
8983
9174
|
const dir = pendingDirFor(stateDir);
|
|
8984
|
-
|
|
8985
|
-
|
|
9175
|
+
mkdirSync13(dir, { recursive: true });
|
|
9176
|
+
writeFileSync14(join21(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
8986
9177
|
}
|
|
8987
9178
|
function listPendingHandoffs(stateDir, route2) {
|
|
8988
9179
|
const dir = pendingDirFor(stateDir);
|
|
8989
|
-
if (!
|
|
9180
|
+
if (!existsSync20(dir)) return [];
|
|
8990
9181
|
const out = [];
|
|
8991
9182
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
8992
9183
|
try {
|
|
8993
|
-
const rec = JSON.parse(
|
|
9184
|
+
const rec = JSON.parse(readFileSync18(join21(dir, f), "utf8"));
|
|
8994
9185
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
8995
9186
|
} catch {
|
|
8996
9187
|
}
|
|
@@ -8998,7 +9189,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
8998
9189
|
return out;
|
|
8999
9190
|
}
|
|
9000
9191
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
9001
|
-
rmSync11(
|
|
9192
|
+
rmSync11(join21(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
9002
9193
|
}
|
|
9003
9194
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
9004
9195
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9108,8 +9299,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9108
9299
|
}
|
|
9109
9300
|
|
|
9110
9301
|
// src/skills.ts
|
|
9111
|
-
import { readFileSync as
|
|
9112
|
-
import { join as
|
|
9302
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync8, existsSync as existsSync22 } from "fs";
|
|
9303
|
+
import { join as join23 } from "path";
|
|
9113
9304
|
import { parse as parseYaml } from "yaml";
|
|
9114
9305
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9115
9306
|
|
|
@@ -9125,8 +9316,8 @@ ${additions}`;
|
|
|
9125
9316
|
}
|
|
9126
9317
|
|
|
9127
9318
|
// src/agents.ts
|
|
9128
|
-
import { readFileSync as
|
|
9129
|
-
import { join as
|
|
9319
|
+
import { readFileSync as readFileSync19, existsSync as existsSync21 } from "fs";
|
|
9320
|
+
import { join as join22 } from "path";
|
|
9130
9321
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9131
9322
|
var PERSONA_NAME = {
|
|
9132
9323
|
dev: "Developer",
|
|
@@ -9140,12 +9331,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9140
9331
|
var AgentsSyncError = class extends Error {
|
|
9141
9332
|
};
|
|
9142
9333
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9143
|
-
const briefPath =
|
|
9144
|
-
if (!
|
|
9145
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9146
|
-
const commonPath =
|
|
9147
|
-
if (!
|
|
9148
|
-
return `${
|
|
9334
|
+
const briefPath = join22(promptsDir, "personas", `lane-${role}.md`);
|
|
9335
|
+
if (!existsSync21(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
9336
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync19(briefPath, "utf8");
|
|
9337
|
+
const commonPath = join22(promptsDir, "personas", "common.md");
|
|
9338
|
+
if (!existsSync21(commonPath)) return readFileSync19(briefPath, "utf8");
|
|
9339
|
+
return `${readFileSync19(commonPath, "utf8")}${readFileSync19(briefPath, "utf8")}`;
|
|
9149
9340
|
}
|
|
9150
9341
|
async function resolveAgentId(route2, role, opts) {
|
|
9151
9342
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -9592,7 +9783,7 @@ function describeCrewLink(o) {
|
|
|
9592
9783
|
var SkillSyncError = class extends Error {
|
|
9593
9784
|
};
|
|
9594
9785
|
function parseSkillFile(path) {
|
|
9595
|
-
const text2 =
|
|
9786
|
+
const text2 = readFileSync20(path, "utf8");
|
|
9596
9787
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
9597
9788
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
9598
9789
|
const frontmatter = match[1];
|
|
@@ -9605,9 +9796,9 @@ function parseSkillFile(path) {
|
|
|
9605
9796
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
9606
9797
|
}
|
|
9607
9798
|
function listLocalSkills(promptsDir) {
|
|
9608
|
-
const dir =
|
|
9609
|
-
if (!
|
|
9610
|
-
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
9799
|
+
const dir = join23(promptsDir, "skills");
|
|
9800
|
+
if (!existsSync22(dir)) return [];
|
|
9801
|
+
return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join23(dir, f)));
|
|
9611
9802
|
}
|
|
9612
9803
|
async function syncSkills(route2, opts) {
|
|
9613
9804
|
if (!route2.resolved) {
|
|
@@ -9791,10 +9982,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9791
9982
|
}
|
|
9792
9983
|
|
|
9793
9984
|
// src/runner.ts
|
|
9794
|
-
import { spawn as spawn2, execFile, execFileSync as
|
|
9795
|
-
import { mkdtempSync, mkdirSync as
|
|
9985
|
+
import { spawn as spawn2, execFile, execFileSync as execFileSync6 } from "child_process";
|
|
9986
|
+
import { mkdtempSync, mkdirSync as mkdirSync14, writeFileSync as writeFileSync15, rmSync as rmSync12 } from "fs";
|
|
9796
9987
|
import { tmpdir as tmpdir2 } from "os";
|
|
9797
|
-
import { basename as basename8, dirname as
|
|
9988
|
+
import { basename as basename8, dirname as dirname10, join as join24 } from "path";
|
|
9798
9989
|
import { promisify } from "util";
|
|
9799
9990
|
|
|
9800
9991
|
// src/run-limit.ts
|
|
@@ -9923,8 +10114,8 @@ var SandboxRunner = class {
|
|
|
9923
10114
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
9924
10115
|
}
|
|
9925
10116
|
policyFile = policyPathFor(plan.streamPath);
|
|
9926
|
-
|
|
9927
|
-
|
|
10117
|
+
mkdirSync14(dirname10(policyFile), { recursive: true });
|
|
10118
|
+
writeFileSync15(policyFile, generated.yaml, { mode: 384 });
|
|
9928
10119
|
}
|
|
9929
10120
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
9930
10121
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -9967,10 +10158,10 @@ var SandboxRunner = class {
|
|
|
9967
10158
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
9968
10159
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
9969
10160
|
if (viaUpload) {
|
|
9970
|
-
const tmp = mkdtempSync(
|
|
10161
|
+
const tmp = mkdtempSync(join24(tmpdir2(), "crew-prompt-"));
|
|
9971
10162
|
try {
|
|
9972
|
-
const file =
|
|
9973
|
-
|
|
10163
|
+
const file = join24(tmp, "prompt.txt");
|
|
10164
|
+
writeFileSync15(file, plan.prompt, { mode: 384 });
|
|
9974
10165
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
9975
10166
|
} finally {
|
|
9976
10167
|
rmSync12(tmp, { recursive: true, force: true });
|
|
@@ -10036,7 +10227,7 @@ function describeExecError(e) {
|
|
|
10036
10227
|
}
|
|
10037
10228
|
function gitOrigin(cwd) {
|
|
10038
10229
|
try {
|
|
10039
|
-
return
|
|
10230
|
+
return execFileSync6("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
10040
10231
|
} catch {
|
|
10041
10232
|
return null;
|
|
10042
10233
|
}
|
|
@@ -10047,7 +10238,7 @@ function runnerFor(kind, sandbox) {
|
|
|
10047
10238
|
function runnerOfSidecar(sc) {
|
|
10048
10239
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
10049
10240
|
}
|
|
10050
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a) =>
|
|
10241
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync6(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
|
|
10051
10242
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
10052
10243
|
let version;
|
|
10053
10244
|
try {
|
|
@@ -10114,7 +10305,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10114
10305
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10115
10306
|
let raw;
|
|
10116
10307
|
try {
|
|
10117
|
-
raw =
|
|
10308
|
+
raw = readFileSync21(streamPath, "utf8");
|
|
10118
10309
|
} catch {
|
|
10119
10310
|
return false;
|
|
10120
10311
|
}
|
|
@@ -10137,26 +10328,26 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10137
10328
|
}
|
|
10138
10329
|
return false;
|
|
10139
10330
|
}
|
|
10140
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10331
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join25(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10141
10332
|
function planAgentRun(o) {
|
|
10142
|
-
const briefPath =
|
|
10143
|
-
if (!
|
|
10333
|
+
const briefPath = join25(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10334
|
+
if (!existsSync23(briefPath)) {
|
|
10144
10335
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10145
10336
|
}
|
|
10146
|
-
const commonPath =
|
|
10147
|
-
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) &&
|
|
10337
|
+
const commonPath = join25(o.route.promptsDir, "personas", "common.md");
|
|
10338
|
+
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync23(commonPath);
|
|
10148
10339
|
const now = o.now ?? (() => Date.now());
|
|
10149
10340
|
const routeLabel = routeSlug(o.route.route);
|
|
10150
10341
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10151
|
-
const digestPath2 = ownDigest &&
|
|
10342
|
+
const digestPath2 = ownDigest && existsSync23(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10152
10343
|
let digest;
|
|
10153
10344
|
let ageSeconds;
|
|
10154
|
-
if (
|
|
10345
|
+
if (existsSync23(digestPath2)) {
|
|
10155
10346
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10156
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10347
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync21(digestPath2, "utf8");
|
|
10157
10348
|
}
|
|
10158
|
-
const common = usesCommon ?
|
|
10159
|
-
const brief =
|
|
10349
|
+
const common = usesCommon ? readFileSync21(commonPath, "utf8") : "";
|
|
10350
|
+
const brief = readFileSync21(briefPath, "utf8");
|
|
10160
10351
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10161
10352
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10162
10353
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10179,7 +10370,7 @@ function planAgentRun(o) {
|
|
|
10179
10370
|
digest
|
|
10180
10371
|
});
|
|
10181
10372
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10182
|
-
const streamsDir =
|
|
10373
|
+
const streamsDir = join25(o.stateDir, "streams");
|
|
10183
10374
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10184
10375
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10185
10376
|
const { model } = seat;
|
|
@@ -10269,8 +10460,8 @@ function planAgentRun(o) {
|
|
|
10269
10460
|
digestAttached: digest !== void 0,
|
|
10270
10461
|
digestAgeSeconds: ageSeconds,
|
|
10271
10462
|
model,
|
|
10272
|
-
streamPath:
|
|
10273
|
-
eventsPath:
|
|
10463
|
+
streamPath: join25(streamsDir, `${base}.jsonl`),
|
|
10464
|
+
eventsPath: join25(streamsDir, `${base}.events.jsonl`),
|
|
10274
10465
|
ticket: o.ticket,
|
|
10275
10466
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10276
10467
|
repo: o.repo ?? null,
|
|
@@ -10301,7 +10492,7 @@ function describePlan(p) {
|
|
|
10301
10492
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10302
10493
|
function readRunSidecar(streamPath) {
|
|
10303
10494
|
try {
|
|
10304
|
-
const sc = JSON.parse(
|
|
10495
|
+
const sc = JSON.parse(readFileSync21(pidPathFor(streamPath), "utf8"));
|
|
10305
10496
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10306
10497
|
} catch {
|
|
10307
10498
|
return null;
|
|
@@ -10348,7 +10539,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10348
10539
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10349
10540
|
function openSink(path) {
|
|
10350
10541
|
try {
|
|
10351
|
-
|
|
10542
|
+
mkdirSync15(dirname11(path), { recursive: true });
|
|
10352
10543
|
return createWriteStream(path, { flags: "a" });
|
|
10353
10544
|
} catch {
|
|
10354
10545
|
return void 0;
|
|
@@ -10530,7 +10721,7 @@ var RunObserver = class {
|
|
|
10530
10721
|
};
|
|
10531
10722
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
10532
10723
|
try {
|
|
10533
|
-
|
|
10724
|
+
writeFileSync16(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
10534
10725
|
route: id.route,
|
|
10535
10726
|
role: id.role,
|
|
10536
10727
|
hint: id.ticket,
|
|
@@ -10690,7 +10881,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10690
10881
|
const id = identityOf(plan, adapter.harness);
|
|
10691
10882
|
let streamFd;
|
|
10692
10883
|
try {
|
|
10693
|
-
|
|
10884
|
+
mkdirSync15(dirname11(plan.streamPath), { recursive: true });
|
|
10694
10885
|
streamFd = openSync3(plan.streamPath, "a");
|
|
10695
10886
|
} catch (e) {
|
|
10696
10887
|
void endSink(eventsSink);
|
|
@@ -10748,7 +10939,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10748
10939
|
promptSha: plan.promptSha
|
|
10749
10940
|
};
|
|
10750
10941
|
try {
|
|
10751
|
-
|
|
10942
|
+
writeFileSync16(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
10752
10943
|
} catch {
|
|
10753
10944
|
}
|
|
10754
10945
|
}
|
|
@@ -10862,21 +11053,21 @@ var pidAlive = (pid) => {
|
|
|
10862
11053
|
};
|
|
10863
11054
|
function runFinished(streamPath) {
|
|
10864
11055
|
const path = `${streamPath}.meta.json`;
|
|
10865
|
-
if (!
|
|
11056
|
+
if (!existsSync24(path)) return false;
|
|
10866
11057
|
try {
|
|
10867
|
-
return JSON.parse(
|
|
11058
|
+
return JSON.parse(readFileSync22(path, "utf8")).running !== true;
|
|
10868
11059
|
} catch {
|
|
10869
11060
|
return true;
|
|
10870
11061
|
}
|
|
10871
11062
|
}
|
|
10872
11063
|
function orphanedStreams(o) {
|
|
10873
|
-
const dir =
|
|
10874
|
-
if (!
|
|
11064
|
+
const dir = join26(o.stateDir, "streams");
|
|
11065
|
+
if (!existsSync24(dir)) return [];
|
|
10875
11066
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
10876
11067
|
const out = [];
|
|
10877
11068
|
for (const name of readdirSync9(dir)) {
|
|
10878
11069
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10879
|
-
const streamPath =
|
|
11070
|
+
const streamPath = join26(dir, name);
|
|
10880
11071
|
if (runFinished(streamPath)) continue;
|
|
10881
11072
|
const sidecar = readRunSidecar(streamPath);
|
|
10882
11073
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -10929,15 +11120,15 @@ async function reconcileRuns(o) {
|
|
|
10929
11120
|
}
|
|
10930
11121
|
|
|
10931
11122
|
// src/live-runs.ts
|
|
10932
|
-
import { existsSync as
|
|
10933
|
-
import { join as
|
|
11123
|
+
import { existsSync as existsSync25, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
11124
|
+
import { join as join27 } from "path";
|
|
10934
11125
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
10935
|
-
const dir =
|
|
10936
|
-
if (!
|
|
11126
|
+
const dir = join27(stateDir, "streams");
|
|
11127
|
+
if (!existsSync25(dir)) return [];
|
|
10937
11128
|
const out = [];
|
|
10938
11129
|
for (const name of readdirSync10(dir)) {
|
|
10939
11130
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10940
|
-
const streamPath =
|
|
11131
|
+
const streamPath = join27(dir, name);
|
|
10941
11132
|
if (runFinished(streamPath)) continue;
|
|
10942
11133
|
const sidecar = readRunSidecar(streamPath);
|
|
10943
11134
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -10947,12 +11138,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
10947
11138
|
}
|
|
10948
11139
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
10949
11140
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
10950
|
-
const dir =
|
|
11141
|
+
const dir = join27(stateDir, "streams");
|
|
10951
11142
|
let newest = 0;
|
|
10952
|
-
if (
|
|
11143
|
+
if (existsSync25(dir)) {
|
|
10953
11144
|
for (const name of readdirSync10(dir)) {
|
|
10954
11145
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10955
|
-
const streamPath =
|
|
11146
|
+
const streamPath = join27(dir, name);
|
|
10956
11147
|
if (!runFinished(streamPath)) continue;
|
|
10957
11148
|
try {
|
|
10958
11149
|
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
@@ -11323,7 +11514,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11323
11514
|
if (host === "windows") return [];
|
|
11324
11515
|
const ctx = installContext();
|
|
11325
11516
|
const systemd = detectSystemd();
|
|
11326
|
-
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) =>
|
|
11517
|
+
return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync26(p));
|
|
11327
11518
|
};
|
|
11328
11519
|
return {
|
|
11329
11520
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11384,7 +11575,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11384
11575
|
},
|
|
11385
11576
|
remaining() {
|
|
11386
11577
|
const left = [];
|
|
11387
|
-
if (
|
|
11578
|
+
if (existsSync26(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11388
11579
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11389
11580
|
try {
|
|
11390
11581
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -11659,7 +11850,7 @@ function makeCachePass(o) {
|
|
|
11659
11850
|
// src/passenger-containers.ts
|
|
11660
11851
|
var realExec2 = (cmd, args, opts) => {
|
|
11661
11852
|
try {
|
|
11662
|
-
return
|
|
11853
|
+
return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
11663
11854
|
} catch (e) {
|
|
11664
11855
|
const err3 = e;
|
|
11665
11856
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -11939,7 +12130,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
11939
12130
|
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
11940
12131
|
function readWarmFiles(dir, ref, patterns) {
|
|
11941
12132
|
if (patterns.length === 0) return {};
|
|
11942
|
-
const git2 = (args) =>
|
|
12133
|
+
const git2 = (args) => execFileSync8("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
11943
12134
|
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
11944
12135
|
const res = patterns.map(globToRegExp);
|
|
11945
12136
|
const out = {};
|
|
@@ -11992,19 +12183,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
11992
12183
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
11993
12184
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
11994
12185
|
}
|
|
11995
|
-
var sandboxStatePath = (stateDir) =>
|
|
12186
|
+
var sandboxStatePath = (stateDir) => join28(stateDir, "sandbox-images.json");
|
|
11996
12187
|
function readSandboxState(stateDir) {
|
|
11997
12188
|
try {
|
|
11998
|
-
const v = JSON.parse(
|
|
12189
|
+
const v = JSON.parse(readFileSync23(sandboxStatePath(stateDir), "utf8"));
|
|
11999
12190
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
12000
12191
|
} catch {
|
|
12001
12192
|
return {};
|
|
12002
12193
|
}
|
|
12003
12194
|
}
|
|
12004
12195
|
function writeSandboxState(stateDir, state) {
|
|
12005
|
-
|
|
12196
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
12006
12197
|
const file = sandboxStatePath(stateDir);
|
|
12007
|
-
|
|
12198
|
+
writeFileSync17(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
12008
12199
|
`);
|
|
12009
12200
|
renameSync3(`${file}.tmp`, file);
|
|
12010
12201
|
}
|
|
@@ -12024,7 +12215,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
|
|
|
12024
12215
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
12025
12216
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
12026
12217
|
const done = (code, extra = "") => {
|
|
12027
|
-
|
|
12218
|
+
writeFileSync17(logPath, `$ ${engine} ${args.join(" ")}
|
|
12028
12219
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
12029
12220
|
resolve8(code);
|
|
12030
12221
|
};
|
|
@@ -12046,7 +12237,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
12046
12237
|
} catch {
|
|
12047
12238
|
}
|
|
12048
12239
|
};
|
|
12049
|
-
var sandboxLogDir = (stateDir) =>
|
|
12240
|
+
var sandboxLogDir = (stateDir) => join28(stateDir, "sandbox-logs");
|
|
12050
12241
|
async function buildSandboxImage(plan, deps) {
|
|
12051
12242
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
12052
12243
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -12055,20 +12246,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12055
12246
|
return { kind: "reused", tag: plan.tag };
|
|
12056
12247
|
}
|
|
12057
12248
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
12058
|
-
|
|
12059
|
-
const logPath =
|
|
12249
|
+
mkdirSync16(logDir, { recursive: true });
|
|
12250
|
+
const logPath = join28(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
12060
12251
|
const builtLayers = [];
|
|
12061
12252
|
for (const layer of plan.layers) {
|
|
12062
12253
|
if (deps.hasImage(layer.tag)) continue;
|
|
12063
|
-
const ctx =
|
|
12064
|
-
|
|
12254
|
+
const ctx = join28(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12255
|
+
mkdirSync16(ctx, { recursive: true });
|
|
12065
12256
|
try {
|
|
12066
|
-
|
|
12257
|
+
writeFileSync17(join28(ctx, "Dockerfile"), layer.dockerfile);
|
|
12067
12258
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
12068
|
-
|
|
12069
|
-
|
|
12259
|
+
mkdirSync16(dirname12(join28(ctx, name)), { recursive: true });
|
|
12260
|
+
writeFileSync17(join28(ctx, name), content);
|
|
12070
12261
|
}
|
|
12071
|
-
const code = await deps.run(["build", "--tag", layer.tag, "--file",
|
|
12262
|
+
const code = await deps.run(["build", "--tag", layer.tag, "--file", join28(ctx, "Dockerfile"), ctx], logPath);
|
|
12072
12263
|
if (code !== 0) {
|
|
12073
12264
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
12074
12265
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12121,10 +12312,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
12121
12312
|
}
|
|
12122
12313
|
|
|
12123
12314
|
// src/doctor-checks.ts
|
|
12124
|
-
import { execFileSync as
|
|
12315
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
12125
12316
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
12126
12317
|
var realGit = (cwd, args, env) => {
|
|
12127
|
-
|
|
12318
|
+
execFileSync9("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
12128
12319
|
};
|
|
12129
12320
|
function isSshRemote(url) {
|
|
12130
12321
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -12257,9 +12448,9 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12257
12448
|
|
|
12258
12449
|
// src/selftest.ts
|
|
12259
12450
|
import { spawn as spawn5 } from "child_process";
|
|
12260
|
-
import { existsSync as
|
|
12451
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync17, mkdtempSync as mkdtempSync2, readFileSync as readFileSync24, rmSync as rmSync14, writeFileSync as writeFileSync18 } from "fs";
|
|
12261
12452
|
import { tmpdir as tmpdir4 } from "os";
|
|
12262
|
-
import { join as
|
|
12453
|
+
import { join as join29 } from "path";
|
|
12263
12454
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12264
12455
|
var KEY_VAR = "CREW_API_KEY";
|
|
12265
12456
|
var SELFTEST_PROMPT = "This is a read-only connectivity check. Use the tracker MCP tool to read this workspace (the workspaces find-one tool, using the workspace id below) and reply with the workspace's name alone, on one line. Do not write anything, do not create files, and do not use any other tool.\n\nWorkspace id: ";
|
|
@@ -12420,7 +12611,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12420
12611
|
...adapter.runEnv(spec),
|
|
12421
12612
|
[KEY_VAR]: i.apiKey
|
|
12422
12613
|
};
|
|
12423
|
-
const cwd = mkdtempSync2(
|
|
12614
|
+
const cwd = mkdtempSync2(join29(tmpdir4(), "crew-selftest-"));
|
|
12424
12615
|
try {
|
|
12425
12616
|
const run3 = await deps.run({
|
|
12426
12617
|
bin: invocation.bin,
|
|
@@ -12453,11 +12644,11 @@ function formatResult(r) {
|
|
|
12453
12644
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
12454
12645
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
12455
12646
|
}
|
|
12456
|
-
var fileFor2 = (stateDir) =>
|
|
12647
|
+
var fileFor2 = (stateDir) => join29(stateDir, "selftest.json");
|
|
12457
12648
|
function readSelftest(stateDir) {
|
|
12458
12649
|
try {
|
|
12459
12650
|
const p = fileFor2(stateDir);
|
|
12460
|
-
return
|
|
12651
|
+
return existsSync27(p) ? JSON.parse(readFileSync24(p, "utf8")) : {};
|
|
12461
12652
|
} catch {
|
|
12462
12653
|
return {};
|
|
12463
12654
|
}
|
|
@@ -12465,8 +12656,8 @@ function readSelftest(stateDir) {
|
|
|
12465
12656
|
function recordSelftest(stateDir, results) {
|
|
12466
12657
|
const all = readSelftest(stateDir);
|
|
12467
12658
|
for (const r of results) all[r.toolchain] = r;
|
|
12468
|
-
|
|
12469
|
-
|
|
12659
|
+
mkdirSync17(stateDir, { recursive: true });
|
|
12660
|
+
writeFileSync18(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
12470
12661
|
`);
|
|
12471
12662
|
}
|
|
12472
12663
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -12478,7 +12669,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
12478
12669
|
}
|
|
12479
12670
|
|
|
12480
12671
|
// src/cli.ts
|
|
12481
|
-
import { resolve as resolve7, dirname as
|
|
12672
|
+
import { resolve as resolve7, dirname as dirname30, join as join51, basename as basename16 } from "path";
|
|
12482
12673
|
import { fileURLToPath } from "url";
|
|
12483
12674
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
12484
12675
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -12675,14 +12866,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
12675
12866
|
}
|
|
12676
12867
|
|
|
12677
12868
|
// src/connect-resume.ts
|
|
12678
|
-
import { mkdirSync as
|
|
12679
|
-
import { dirname as
|
|
12869
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, rmSync as rmSync15, writeFileSync as writeFileSync19 } from "fs";
|
|
12870
|
+
import { dirname as dirname13, join as join30 } from "path";
|
|
12680
12871
|
function lastSessionFile(stateDir) {
|
|
12681
|
-
return
|
|
12872
|
+
return join30(stateDir, "connect-session.json");
|
|
12682
12873
|
}
|
|
12683
12874
|
function readPointers(stateDir) {
|
|
12684
12875
|
try {
|
|
12685
|
-
const parsed = JSON.parse(
|
|
12876
|
+
const parsed = JSON.parse(readFileSync25(lastSessionFile(stateDir), "utf8"));
|
|
12686
12877
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
12687
12878
|
const out = {};
|
|
12688
12879
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -12696,8 +12887,8 @@ function readPointers(stateDir) {
|
|
|
12696
12887
|
}
|
|
12697
12888
|
function writePointers(stateDir, pointers) {
|
|
12698
12889
|
const file = lastSessionFile(stateDir);
|
|
12699
|
-
|
|
12700
|
-
|
|
12890
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
12891
|
+
writeFileSync19(file, `${JSON.stringify(pointers, null, 2)}
|
|
12701
12892
|
`, { mode: 384 });
|
|
12702
12893
|
}
|
|
12703
12894
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -12782,15 +12973,15 @@ async function signOutSessions(o) {
|
|
|
12782
12973
|
}
|
|
12783
12974
|
|
|
12784
12975
|
// src/reset.ts
|
|
12785
|
-
import { existsSync as
|
|
12786
|
-
import { dirname as
|
|
12976
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync26, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync10, writeFileSync as writeFileSync20 } from "fs";
|
|
12977
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
12787
12978
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
12788
12979
|
function clonedCheckoutsFile(stateDir) {
|
|
12789
|
-
return
|
|
12980
|
+
return join31(stateDir, "cloned-checkouts.json");
|
|
12790
12981
|
}
|
|
12791
12982
|
function readClonedCheckouts(stateDir) {
|
|
12792
12983
|
try {
|
|
12793
|
-
const parsed = JSON.parse(
|
|
12984
|
+
const parsed = JSON.parse(readFileSync26(clonedCheckoutsFile(stateDir), "utf8"));
|
|
12794
12985
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
12795
12986
|
} catch {
|
|
12796
12987
|
return [];
|
|
@@ -12800,8 +12991,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
12800
12991
|
const known = readClonedCheckouts(stateDir);
|
|
12801
12992
|
if (known.includes(dir)) return;
|
|
12802
12993
|
const file = clonedCheckoutsFile(stateDir);
|
|
12803
|
-
|
|
12804
|
-
|
|
12994
|
+
mkdirSync19(dirname14(file), { recursive: true });
|
|
12995
|
+
writeFileSync20(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
12805
12996
|
`);
|
|
12806
12997
|
}
|
|
12807
12998
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -12855,7 +13046,7 @@ async function runReset(o, fx) {
|
|
|
12855
13046
|
}
|
|
12856
13047
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
12857
13048
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
12858
|
-
if (
|
|
13049
|
+
if (existsSync28(o.configFile)) {
|
|
12859
13050
|
if (!o.dryRun) rmSync16(o.configFile, { force: true });
|
|
12860
13051
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
12861
13052
|
} else {
|
|
@@ -12864,7 +13055,7 @@ async function runReset(o, fx) {
|
|
|
12864
13055
|
return steps;
|
|
12865
13056
|
}
|
|
12866
13057
|
function removeCheckout(dir, dryRun, fx) {
|
|
12867
|
-
if (!
|
|
13058
|
+
if (!existsSync28(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
12868
13059
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
12869
13060
|
if (dirty.length) {
|
|
12870
13061
|
return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
|
|
@@ -12877,15 +13068,15 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
12877
13068
|
}
|
|
12878
13069
|
function removeState(stateDir, dryRun) {
|
|
12879
13070
|
const out = [];
|
|
12880
|
-
const targets = [
|
|
13071
|
+
const targets = [join31(stateDir, "resolved"), join31(stateDir, "keys"), clonedCheckoutsFile(stateDir), join31(stateDir, "connect-session.json"), join31(stateDir, "reload-requested"), join31(stateDir, "daemon.started")];
|
|
12881
13072
|
try {
|
|
12882
13073
|
for (const name of readdirSync12(stateDir)) {
|
|
12883
|
-
if (name.startsWith(".") && statSync10(
|
|
13074
|
+
if (name.startsWith(".") && statSync10(join31(stateDir, name)).isFile()) targets.push(join31(stateDir, name));
|
|
12884
13075
|
}
|
|
12885
13076
|
} catch {
|
|
12886
13077
|
}
|
|
12887
13078
|
for (const t of targets) {
|
|
12888
|
-
if (!
|
|
13079
|
+
if (!existsSync28(t)) continue;
|
|
12889
13080
|
if (!dryRun) rmSync16(t, { recursive: true, force: true });
|
|
12890
13081
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
12891
13082
|
}
|
|
@@ -12900,7 +13091,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
12900
13091
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
12901
13092
|
}
|
|
12902
13093
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
12903
|
-
if (!dryRun) for (const n of names) rmSync16(
|
|
13094
|
+
if (!dryRun) for (const n of names) rmSync16(join31(stateDir, n), { recursive: true, force: true });
|
|
12904
13095
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
12905
13096
|
}
|
|
12906
13097
|
function describeResetStep(s) {
|
|
@@ -12981,9 +13172,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
12981
13172
|
}
|
|
12982
13173
|
|
|
12983
13174
|
// src/pool-member.ts
|
|
12984
|
-
import { mkdirSync as
|
|
13175
|
+
import { mkdirSync as mkdirSync20, readFileSync as readFileSync27, renameSync as renameSync4, writeFileSync as writeFileSync21 } from "fs";
|
|
12985
13176
|
import { homedir as homedir8 } from "os";
|
|
12986
|
-
import { dirname as
|
|
13177
|
+
import { dirname as dirname15, join as join32 } from "path";
|
|
12987
13178
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
12988
13179
|
function backoffMs(attempt) {
|
|
12989
13180
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13214,22 +13405,22 @@ async function runPoolMember(deps) {
|
|
|
13214
13405
|
}
|
|
13215
13406
|
}
|
|
13216
13407
|
function defaultMemberDir(env = process.env) {
|
|
13217
|
-
return env.CREW_POOL_DIR ??
|
|
13408
|
+
return env.CREW_POOL_DIR ?? join32(env.XDG_STATE_HOME ?? join32(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13218
13409
|
}
|
|
13219
13410
|
function fileMemberStore(dir) {
|
|
13220
|
-
const file =
|
|
13411
|
+
const file = join32(dir, "member.json");
|
|
13221
13412
|
return {
|
|
13222
13413
|
read() {
|
|
13223
13414
|
try {
|
|
13224
|
-
return JSON.parse(
|
|
13415
|
+
return JSON.parse(readFileSync27(file, "utf8"));
|
|
13225
13416
|
} catch {
|
|
13226
13417
|
return {};
|
|
13227
13418
|
}
|
|
13228
13419
|
},
|
|
13229
13420
|
write(f) {
|
|
13230
|
-
|
|
13421
|
+
mkdirSync20(dirname15(file), { recursive: true, mode: 448 });
|
|
13231
13422
|
const tmp = `${file}.tmp`;
|
|
13232
|
-
|
|
13423
|
+
writeFileSync21(tmp, `${JSON.stringify(f)}
|
|
13233
13424
|
`, { mode: 384 });
|
|
13234
13425
|
renameSync4(tmp, file);
|
|
13235
13426
|
}
|
|
@@ -13281,30 +13472,30 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13281
13472
|
|
|
13282
13473
|
// src/pool-member-host.ts
|
|
13283
13474
|
import "child_process";
|
|
13284
|
-
import { existsSync as
|
|
13475
|
+
import { existsSync as existsSync32 } from "fs";
|
|
13285
13476
|
import { hostname as hostname3 } from "os";
|
|
13286
13477
|
|
|
13287
13478
|
// src/machine-id.ts
|
|
13288
|
-
import { execFileSync as
|
|
13479
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
13289
13480
|
import { createHash as createHash7 } from "crypto";
|
|
13290
|
-
import { existsSync as
|
|
13291
|
-
import { dirname as
|
|
13481
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync21, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
|
|
13482
|
+
import { dirname as dirname16, join as join33 } from "path";
|
|
13292
13483
|
function machineIdPathFor(stateDir) {
|
|
13293
|
-
return
|
|
13484
|
+
return join33(stateDir, "machine-id");
|
|
13294
13485
|
}
|
|
13295
13486
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13296
13487
|
function readRawMachineId(platform2 = process.platform) {
|
|
13297
13488
|
try {
|
|
13298
13489
|
if (platform2 === "darwin") {
|
|
13299
|
-
const out =
|
|
13490
|
+
const out = execFileSync10("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
13300
13491
|
encoding: "utf8",
|
|
13301
13492
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13302
13493
|
});
|
|
13303
13494
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13304
13495
|
}
|
|
13305
13496
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13306
|
-
if (
|
|
13307
|
-
const id =
|
|
13497
|
+
if (existsSync29(p)) {
|
|
13498
|
+
const id = readFileSync28(p, "utf8").trim();
|
|
13308
13499
|
if (id) return id;
|
|
13309
13500
|
}
|
|
13310
13501
|
}
|
|
@@ -13315,15 +13506,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13315
13506
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13316
13507
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13317
13508
|
const path = machineIdPathFor(stateDir);
|
|
13318
|
-
if (
|
|
13319
|
-
const cached =
|
|
13509
|
+
if (existsSync29(path)) {
|
|
13510
|
+
const cached = readFileSync28(path, "utf8").trim();
|
|
13320
13511
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13321
13512
|
}
|
|
13322
13513
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13323
13514
|
if (!raw) return void 0;
|
|
13324
13515
|
const hashed = sha256(raw);
|
|
13325
|
-
|
|
13326
|
-
|
|
13516
|
+
mkdirSync21(dirname16(path), { recursive: true });
|
|
13517
|
+
writeFileSync22(path, `${hashed}
|
|
13327
13518
|
`, { mode: 384 });
|
|
13328
13519
|
return hashed;
|
|
13329
13520
|
}
|
|
@@ -13336,7 +13527,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13336
13527
|
|
|
13337
13528
|
// src/provision.ts
|
|
13338
13529
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
13339
|
-
import { existsSync as
|
|
13530
|
+
import { existsSync as existsSync30, readFileSync as readFileSync29, writeFileSync as writeFileSync23 } from "fs";
|
|
13340
13531
|
import { hostname as hostname2 } from "os";
|
|
13341
13532
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13342
13533
|
|
|
@@ -13345,7 +13536,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
13345
13536
|
|
|
13346
13537
|
// src/repos-cmd.ts
|
|
13347
13538
|
import { homedir as homedir9 } from "os";
|
|
13348
|
-
import { basename as basename10, join as
|
|
13539
|
+
import { basename as basename10, join as join34, resolve as resolve4 } from "path";
|
|
13349
13540
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13350
13541
|
var ReposError = class extends Error {
|
|
13351
13542
|
};
|
|
@@ -13623,7 +13814,7 @@ function planRepoClone(i) {
|
|
|
13623
13814
|
}
|
|
13624
13815
|
const ship = doc.get("ship", true);
|
|
13625
13816
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13626
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
13817
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join34(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
13627
13818
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
13628
13819
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
13629
13820
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -13930,7 +14121,7 @@ function realProvisionDeps(crewHome) {
|
|
|
13930
14121
|
};
|
|
13931
14122
|
const configFile = () => {
|
|
13932
14123
|
const f = findConfigFile(crewHome);
|
|
13933
|
-
return
|
|
14124
|
+
return existsSync30(f) ? f : null;
|
|
13934
14125
|
};
|
|
13935
14126
|
return {
|
|
13936
14127
|
crew: spawnCrew,
|
|
@@ -13938,13 +14129,13 @@ function realProvisionDeps(crewHome) {
|
|
|
13938
14129
|
enableRoute(route2) {
|
|
13939
14130
|
const file = configFile();
|
|
13940
14131
|
if (!file) return false;
|
|
13941
|
-
const text2 =
|
|
14132
|
+
const text2 = readFileSync29(file, "utf8");
|
|
13942
14133
|
const doc = parseDocument3(text2);
|
|
13943
14134
|
const routes = doc.get("routes");
|
|
13944
14135
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
13945
14136
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
13946
14137
|
if (entry.get("enabled") === true) return false;
|
|
13947
|
-
|
|
14138
|
+
writeFileSync23(file, planEnable(text2, route2));
|
|
13948
14139
|
return true;
|
|
13949
14140
|
},
|
|
13950
14141
|
installState() {
|
|
@@ -13964,14 +14155,14 @@ function realProvisionDeps(crewHome) {
|
|
|
13964
14155
|
continue;
|
|
13965
14156
|
}
|
|
13966
14157
|
for (const p of plan.unitPaths) {
|
|
13967
|
-
if (!
|
|
14158
|
+
if (!existsSync30(p) || readFileSync29(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
13968
14159
|
}
|
|
13969
14160
|
}
|
|
13970
14161
|
const label = labelFor(crewHome, "run");
|
|
13971
14162
|
let running = false;
|
|
13972
14163
|
let lingering = true;
|
|
13973
14164
|
if (mechanism === "systemd") {
|
|
13974
|
-
const system = ctx.root ||
|
|
14165
|
+
const system = ctx.root || existsSync30(systemUnitPathFor(label));
|
|
13975
14166
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
13976
14167
|
running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
13977
14168
|
if (!system) {
|
|
@@ -13988,18 +14179,18 @@ function realProvisionDeps(crewHome) {
|
|
|
13988
14179
|
setShipSetting(key, value2) {
|
|
13989
14180
|
const file = configFile();
|
|
13990
14181
|
if (!file) return false;
|
|
13991
|
-
const doc = parseDocument3(
|
|
14182
|
+
const doc = parseDocument3(readFileSync29(file, "utf8"));
|
|
13992
14183
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
13993
14184
|
doc.setIn(["ship", key], value2);
|
|
13994
|
-
|
|
14185
|
+
writeFileSync23(file, doc.toString());
|
|
13995
14186
|
return true;
|
|
13996
14187
|
},
|
|
13997
14188
|
applyManagedDefaults() {
|
|
13998
14189
|
const file = configFile();
|
|
13999
14190
|
if (!file) return [];
|
|
14000
|
-
const doc = parseDocument3(
|
|
14191
|
+
const doc = parseDocument3(readFileSync29(file, "utf8"));
|
|
14001
14192
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
14002
|
-
if (wrote.length)
|
|
14193
|
+
if (wrote.length) writeFileSync23(file, doc.toString());
|
|
14003
14194
|
return wrote;
|
|
14004
14195
|
},
|
|
14005
14196
|
storedToken(host) {
|
|
@@ -14016,9 +14207,9 @@ host=${host}
|
|
|
14016
14207
|
|
|
14017
14208
|
// src/tunnel.ts
|
|
14018
14209
|
import { spawn as spawn6 } from "child_process";
|
|
14019
|
-
import { existsSync as
|
|
14210
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync30, writeFileSync as writeFileSync24, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
|
|
14020
14211
|
import { randomInt } from "crypto";
|
|
14021
|
-
import { join as
|
|
14212
|
+
import { join as join35 } from "path";
|
|
14022
14213
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
14023
14214
|
var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
14024
14215
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14090,21 +14281,21 @@ function startTunnel(opts) {
|
|
|
14090
14281
|
};
|
|
14091
14282
|
}
|
|
14092
14283
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14093
|
-
return
|
|
14284
|
+
return join35(stateDir, "passengers", `${workspaceId}.json`);
|
|
14094
14285
|
}
|
|
14095
14286
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14096
14287
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14097
|
-
if (!
|
|
14288
|
+
if (!existsSync31(path)) return void 0;
|
|
14098
14289
|
try {
|
|
14099
|
-
return JSON.parse(
|
|
14290
|
+
return JSON.parse(readFileSync30(path, "utf8"));
|
|
14100
14291
|
} catch {
|
|
14101
14292
|
return void 0;
|
|
14102
14293
|
}
|
|
14103
14294
|
}
|
|
14104
14295
|
function writePersistedTunnel(stateDir, state) {
|
|
14105
14296
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14106
|
-
|
|
14107
|
-
|
|
14297
|
+
mkdirSync22(join35(stateDir, "passengers"), { recursive: true });
|
|
14298
|
+
writeFileSync24(path, `${JSON.stringify(state, null, 2)}
|
|
14108
14299
|
`);
|
|
14109
14300
|
}
|
|
14110
14301
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14206,8 +14397,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14206
14397
|
}
|
|
14207
14398
|
}
|
|
14208
14399
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14209
|
-
const dir =
|
|
14210
|
-
if (!
|
|
14400
|
+
const dir = join35(stateDir, "passengers");
|
|
14401
|
+
if (!existsSync31(dir)) return [];
|
|
14211
14402
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14212
14403
|
}
|
|
14213
14404
|
|
|
@@ -14232,7 +14423,7 @@ function realPoolMemberDeps(o) {
|
|
|
14232
14423
|
crewVersion: crewVersion(o.crewHome),
|
|
14233
14424
|
hostname: hostname3()
|
|
14234
14425
|
},
|
|
14235
|
-
hasShipConfig: () =>
|
|
14426
|
+
hasShipConfig: () => existsSync32(findConfigFile(o.crewHome)),
|
|
14236
14427
|
async provision(lease, instanceClass, shipKind) {
|
|
14237
14428
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14238
14429
|
const lines2 = [];
|
|
@@ -15108,8 +15299,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15108
15299
|
}
|
|
15109
15300
|
|
|
15110
15301
|
// src/poll.ts
|
|
15111
|
-
import { writeFileSync as
|
|
15112
|
-
import { join as
|
|
15302
|
+
import { writeFileSync as writeFileSync25 } from "fs";
|
|
15303
|
+
import { join as join36 } from "path";
|
|
15113
15304
|
|
|
15114
15305
|
// src/stalled.ts
|
|
15115
15306
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15416,7 +15607,7 @@ async function decideCycle(o) {
|
|
|
15416
15607
|
}
|
|
15417
15608
|
return decision;
|
|
15418
15609
|
}
|
|
15419
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
15610
|
+
var digestPath = (stateDir, route2, role, runId) => join36(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15420
15611
|
function otherRunsRemoved(tickets, own) {
|
|
15421
15612
|
if (!own) return tickets;
|
|
15422
15613
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -15494,7 +15685,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
15494
15685
|
|
|
15495
15686
|
${body}` : body;
|
|
15496
15687
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
15497
|
-
|
|
15688
|
+
writeFileSync25(path, text2);
|
|
15498
15689
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
15499
15690
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
15500
15691
|
});
|
|
@@ -15602,8 +15793,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
15602
15793
|
}
|
|
15603
15794
|
|
|
15604
15795
|
// src/repo-refresh.ts
|
|
15605
|
-
import { existsSync as
|
|
15606
|
-
import { dirname as
|
|
15796
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync23, readFileSync as readFileSync31, writeFileSync as writeFileSync26 } from "fs";
|
|
15797
|
+
import { dirname as dirname18 } from "path";
|
|
15607
15798
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
15608
15799
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
15609
15800
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -15632,11 +15823,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
15632
15823
|
}
|
|
15633
15824
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
15634
15825
|
const path = resolvedPathFor(stateDir, routeName);
|
|
15635
|
-
if (!
|
|
15636
|
-
const raw = JSON.parse(
|
|
15826
|
+
if (!existsSync33(path)) return;
|
|
15827
|
+
const raw = JSON.parse(readFileSync31(path, "utf8"));
|
|
15637
15828
|
applyRepoRows(raw, rows);
|
|
15638
|
-
|
|
15639
|
-
|
|
15829
|
+
mkdirSync23(dirname18(path), { recursive: true });
|
|
15830
|
+
writeFileSync26(path, `${JSON.stringify(raw, null, 2)}
|
|
15640
15831
|
`);
|
|
15641
15832
|
}
|
|
15642
15833
|
async function refreshUnknownRepos(o) {
|
|
@@ -16086,13 +16277,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16086
16277
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16087
16278
|
|
|
16088
16279
|
// src/connect-wizard.ts
|
|
16089
|
-
import { delimiter, dirname as
|
|
16090
|
-
import { accessSync as accessSync3, constants as constants3, existsSync as
|
|
16280
|
+
import { delimiter, dirname as dirname19, join as join37 } from "path";
|
|
16281
|
+
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync34, mkdirSync as mkdirSync24, writeFileSync as writeFileSync27 } from "fs";
|
|
16091
16282
|
import { stringify as stringify3 } from "yaml";
|
|
16092
16283
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16093
16284
|
function shouldRunWizard(opts) {
|
|
16094
16285
|
if (!opts.isTTY) return false;
|
|
16095
|
-
const exists = opts.configExists ??
|
|
16286
|
+
const exists = opts.configExists ?? existsSync34;
|
|
16096
16287
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16097
16288
|
}
|
|
16098
16289
|
function firstRunConfigPath() {
|
|
@@ -16109,7 +16300,7 @@ function defaultIsExecutable(p) {
|
|
|
16109
16300
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16110
16301
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16111
16302
|
if (!dir) continue;
|
|
16112
|
-
const candidate =
|
|
16303
|
+
const candidate = join37(dir, name);
|
|
16113
16304
|
if (isExecutable(candidate)) return candidate;
|
|
16114
16305
|
}
|
|
16115
16306
|
return void 0;
|
|
@@ -16135,9 +16326,9 @@ async function runWizard(p, env) {
|
|
|
16135
16326
|
if (entered && isExec(entered)) agentBin = entered;
|
|
16136
16327
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
16137
16328
|
}
|
|
16138
|
-
const nodeDir =
|
|
16329
|
+
const nodeDir = dirname19(env.nodePath);
|
|
16139
16330
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16140
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
16331
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname19(pnpmPath) : void 0];
|
|
16141
16332
|
p.say(
|
|
16142
16333
|
`
|
|
16143
16334
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -16148,7 +16339,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
16148
16339
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
16149
16340
|
}
|
|
16150
16341
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16151
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
16342
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname19(dockerPath) : void 0]);
|
|
16152
16343
|
if (extraPath) {
|
|
16153
16344
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
16154
16345
|
}
|
|
@@ -16170,9 +16361,9 @@ routes:
|
|
|
16170
16361
|
${routeBlock}`;
|
|
16171
16362
|
}
|
|
16172
16363
|
function writeNewConfig(path, text2) {
|
|
16173
|
-
|
|
16364
|
+
mkdirSync24(dirname19(path), { recursive: true });
|
|
16174
16365
|
try {
|
|
16175
|
-
|
|
16366
|
+
writeFileSync27(path, text2, { mode: 384, flag: "wx" });
|
|
16176
16367
|
return true;
|
|
16177
16368
|
} catch (e) {
|
|
16178
16369
|
if (e.code === "EEXIST") return false;
|
|
@@ -16206,7 +16397,7 @@ Wrote ${o.configPath}.
|
|
|
16206
16397
|
// src/connect-json.ts
|
|
16207
16398
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16208
16399
|
import { homedir as homedir10 } from "os";
|
|
16209
|
-
import { dirname as
|
|
16400
|
+
import { dirname as dirname20, join as join38 } from "path";
|
|
16210
16401
|
import { delimiter as delimiter2 } from "path";
|
|
16211
16402
|
var CHOICE_EXIT_CODE = 3;
|
|
16212
16403
|
function createEmitter(json2, write) {
|
|
@@ -16245,12 +16436,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16245
16436
|
const dirs = [
|
|
16246
16437
|
...pathEnv.split(delimiter2),
|
|
16247
16438
|
...(extraPath ?? "").split(delimiter2),
|
|
16248
|
-
|
|
16249
|
-
|
|
16439
|
+
join38(home, ".local/bin"),
|
|
16440
|
+
join38(home, ".claude/local"),
|
|
16250
16441
|
"/opt/homebrew/bin",
|
|
16251
16442
|
"/usr/local/bin",
|
|
16252
|
-
|
|
16253
|
-
|
|
16443
|
+
join38(home, ".npm-global/bin"),
|
|
16444
|
+
join38(home, ".volta/bin")
|
|
16254
16445
|
];
|
|
16255
16446
|
return [...new Set(dirs.filter(Boolean))];
|
|
16256
16447
|
}
|
|
@@ -16272,7 +16463,7 @@ function flagWizardAnswers(f, env) {
|
|
|
16272
16463
|
const agentBin = f.agentBin ?? found?.bin;
|
|
16273
16464
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16274
16465
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16275
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
16466
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname20(env.nodePath), pnpmPath ? dirname20(pnpmPath) : void 0, dockerPath ? dirname20(dockerPath) : void 0]);
|
|
16276
16467
|
return {
|
|
16277
16468
|
answers: {
|
|
16278
16469
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -16286,8 +16477,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16286
16477
|
}
|
|
16287
16478
|
|
|
16288
16479
|
// src/release-run.ts
|
|
16289
|
-
import { readFileSync as
|
|
16290
|
-
import { dirname as
|
|
16480
|
+
import { readFileSync as readFileSync32, writeFileSync as writeFileSync28, existsSync as existsSync35, copyFileSync, mkdirSync as mkdirSync25, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
|
|
16481
|
+
import { dirname as dirname21, join as join39 } from "path";
|
|
16291
16482
|
import { tmpdir as tmpdir5 } from "os";
|
|
16292
16483
|
|
|
16293
16484
|
// src/release.ts
|
|
@@ -17028,13 +17219,13 @@ async function bump(o, size) {
|
|
|
17028
17219
|
}
|
|
17029
17220
|
return printed;
|
|
17030
17221
|
}
|
|
17031
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17222
|
+
const files = o.repo.release.versionFiles.map((f) => join39(o.cwd, f));
|
|
17032
17223
|
const first = files[0];
|
|
17033
|
-
if (!
|
|
17224
|
+
if (!existsSync35(first)) {
|
|
17034
17225
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
17035
17226
|
return null;
|
|
17036
17227
|
}
|
|
17037
|
-
const current = JSON.parse(
|
|
17228
|
+
const current = JSON.parse(readFileSync32(first, "utf8")).version;
|
|
17038
17229
|
if (!current) {
|
|
17039
17230
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
17040
17231
|
return null;
|
|
@@ -17047,27 +17238,27 @@ async function bump(o, size) {
|
|
|
17047
17238
|
return version;
|
|
17048
17239
|
}
|
|
17049
17240
|
for (const f of files) {
|
|
17050
|
-
const src =
|
|
17241
|
+
const src = readFileSync32(f, "utf8");
|
|
17051
17242
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
17052
17243
|
if (out === src) {
|
|
17053
17244
|
o.emit.error(`no version field in ${f}`);
|
|
17054
17245
|
return null;
|
|
17055
17246
|
}
|
|
17056
|
-
|
|
17247
|
+
writeFileSync28(f, out);
|
|
17057
17248
|
}
|
|
17058
17249
|
return version;
|
|
17059
17250
|
}
|
|
17060
17251
|
function writeChangelog(o, version, entries) {
|
|
17061
17252
|
const file = o.repo.release.changelog;
|
|
17062
17253
|
if (!file || entries.length === 0) return;
|
|
17063
|
-
const path =
|
|
17254
|
+
const path = join39(o.cwd, file);
|
|
17064
17255
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
17065
17256
|
if (o.dryRun) {
|
|
17066
17257
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
17067
17258
|
return;
|
|
17068
17259
|
}
|
|
17069
|
-
const existing =
|
|
17070
|
-
|
|
17260
|
+
const existing = existsSync35(path) ? readFileSync32(path, "utf8") : "# Changelog\n";
|
|
17261
|
+
writeFileSync28(path, insertChangelogSection(existing, section));
|
|
17071
17262
|
}
|
|
17072
17263
|
async function confirm(o, expected) {
|
|
17073
17264
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17160,8 +17351,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17160
17351
|
function openReleaseCheckout(o) {
|
|
17161
17352
|
const { remote, base } = o.repo.branch;
|
|
17162
17353
|
const branch = `crew/release-${Date.now()}`;
|
|
17163
|
-
const root = mkdtempSync3(
|
|
17164
|
-
const dir =
|
|
17354
|
+
const root = mkdtempSync3(join39(tmpdir5(), "crew-release-"));
|
|
17355
|
+
const dir = join39(root, "checkout");
|
|
17165
17356
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17166
17357
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17167
17358
|
try {
|
|
@@ -17171,10 +17362,10 @@ function openReleaseCheckout(o) {
|
|
|
17171
17362
|
throw e;
|
|
17172
17363
|
}
|
|
17173
17364
|
for (const f of o.repo.worktrees.copy) {
|
|
17174
|
-
const src =
|
|
17175
|
-
if (!
|
|
17176
|
-
|
|
17177
|
-
copyFileSync(src,
|
|
17365
|
+
const src = join39(o.cwd, f);
|
|
17366
|
+
if (!existsSync35(src)) continue;
|
|
17367
|
+
mkdirSync25(dirname21(join39(dir, f)), { recursive: true });
|
|
17368
|
+
copyFileSync(src, join39(dir, f));
|
|
17178
17369
|
}
|
|
17179
17370
|
return { dir, branch, close };
|
|
17180
17371
|
}
|
|
@@ -17764,6 +17955,49 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
17764
17955
|
return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
|
|
17765
17956
|
}
|
|
17766
17957
|
|
|
17958
|
+
// src/repo-map.ts
|
|
17959
|
+
var CHARS_PER_TOKEN = 4;
|
|
17960
|
+
var MAP_TIMEOUT_MS = 6e4;
|
|
17961
|
+
function budgetMap(text2, tokenBudget) {
|
|
17962
|
+
const body = text2.replace(/\s+$/, "");
|
|
17963
|
+
const limit = tokenBudget * CHARS_PER_TOKEN;
|
|
17964
|
+
if (body.length <= limit) return body;
|
|
17965
|
+
const lines2 = body.split("\n");
|
|
17966
|
+
const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
|
|
17967
|
+
let kept = lines2.length;
|
|
17968
|
+
let size = body.length;
|
|
17969
|
+
while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
|
|
17970
|
+
kept--;
|
|
17971
|
+
size -= lines2[kept].length + 1;
|
|
17972
|
+
}
|
|
17973
|
+
return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
|
|
17974
|
+
}
|
|
17975
|
+
function loadRepoMap(repo, tokenBudget, shellFallback) {
|
|
17976
|
+
const script = repo.config.hooks.map;
|
|
17977
|
+
if (!script) return Promise.resolve({ skipped: "" });
|
|
17978
|
+
return runScript(script, {
|
|
17979
|
+
cwd: repo.dir,
|
|
17980
|
+
shell: resolveShell(repo.config.shell ?? shellFallback),
|
|
17981
|
+
env: { CREW_REPO: repo.name },
|
|
17982
|
+
timeoutMs: MAP_TIMEOUT_MS
|
|
17983
|
+
}).then(
|
|
17984
|
+
(r) => {
|
|
17985
|
+
if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
|
|
17986
|
+
if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
|
|
17987
|
+
return { text: budgetMap(r.stdout, tokenBudget) };
|
|
17988
|
+
},
|
|
17989
|
+
(e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
|
|
17990
|
+
);
|
|
17991
|
+
}
|
|
17992
|
+
async function withRepoMaps(repos, tokenBudget, shellFallback) {
|
|
17993
|
+
return Promise.all(repos.map(async (r) => {
|
|
17994
|
+
if (!r.config.hooks.map) return r;
|
|
17995
|
+
const out = await loadRepoMap(r, tokenBudget, shellFallback);
|
|
17996
|
+
if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
|
|
17997
|
+
return { ...r, map: out.text };
|
|
17998
|
+
}));
|
|
17999
|
+
}
|
|
18000
|
+
|
|
17767
18001
|
// src/failure-alert.ts
|
|
17768
18002
|
var FAILURE_ALERT_MARKER = calloutHeader("CAUTION", "Failure Alert");
|
|
17769
18003
|
function findOpenFailureAlert(tickets, headline) {
|
|
@@ -18153,7 +18387,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18153
18387
|
}
|
|
18154
18388
|
|
|
18155
18389
|
// src/watch.ts
|
|
18156
|
-
import { watch as watchFile, existsSync as
|
|
18390
|
+
import { watch as watchFile, existsSync as existsSync36, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
18157
18391
|
var E = "\x1B[";
|
|
18158
18392
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18159
18393
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18254,7 +18488,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18254
18488
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18255
18489
|
}
|
|
18256
18490
|
function readFrom(file, from) {
|
|
18257
|
-
if (!
|
|
18491
|
+
if (!existsSync36(file)) return { events: [], offset: from };
|
|
18258
18492
|
const size = statSync11(file).size;
|
|
18259
18493
|
if (size < from) return readFrom(file, 0);
|
|
18260
18494
|
if (size === from) return { events: [], offset: from };
|
|
@@ -18497,8 +18731,8 @@ function nextRoles(decision, ran) {
|
|
|
18497
18731
|
}
|
|
18498
18732
|
|
|
18499
18733
|
// src/ship-name.ts
|
|
18500
|
-
import { existsSync as
|
|
18501
|
-
import { dirname as
|
|
18734
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, renameSync as renameSync5, writeFileSync as writeFileSync29 } from "fs";
|
|
18735
|
+
import { dirname as dirname22 } from "path";
|
|
18502
18736
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
18503
18737
|
var ShipNameError = class extends Error {
|
|
18504
18738
|
};
|
|
@@ -18559,34 +18793,34 @@ async function syncRouteName(i) {
|
|
|
18559
18793
|
return { status: "ok", ...base, action, name: i.local };
|
|
18560
18794
|
}
|
|
18561
18795
|
function readSyncedName(resolvedFile) {
|
|
18562
|
-
if (!
|
|
18796
|
+
if (!existsSync37(resolvedFile)) return void 0;
|
|
18563
18797
|
try {
|
|
18564
|
-
const v = JSON.parse(
|
|
18798
|
+
const v = JSON.parse(readFileSync33(resolvedFile, "utf8")).shipName;
|
|
18565
18799
|
return typeof v === "string" ? v : void 0;
|
|
18566
18800
|
} catch {
|
|
18567
18801
|
return void 0;
|
|
18568
18802
|
}
|
|
18569
18803
|
}
|
|
18570
18804
|
function writeSyncedName(resolvedFile, name) {
|
|
18571
|
-
if (!
|
|
18805
|
+
if (!existsSync37(resolvedFile)) return;
|
|
18572
18806
|
let raw;
|
|
18573
18807
|
try {
|
|
18574
|
-
raw = JSON.parse(
|
|
18808
|
+
raw = JSON.parse(readFileSync33(resolvedFile, "utf8"));
|
|
18575
18809
|
} catch {
|
|
18576
18810
|
return;
|
|
18577
18811
|
}
|
|
18578
18812
|
if (raw.shipName === name) return;
|
|
18579
18813
|
raw.shipName = name;
|
|
18580
|
-
|
|
18814
|
+
mkdirSync26(dirname22(resolvedFile), { recursive: true });
|
|
18581
18815
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
18582
|
-
|
|
18816
|
+
writeFileSync29(tmp, `${JSON.stringify(raw, null, 2)}
|
|
18583
18817
|
`);
|
|
18584
18818
|
renameSync5(tmp, resolvedFile);
|
|
18585
18819
|
}
|
|
18586
18820
|
async function reconcileShipName(i) {
|
|
18587
18821
|
let local = i.ship.name;
|
|
18588
18822
|
try {
|
|
18589
|
-
local = shipNameOfYaml(
|
|
18823
|
+
local = shipNameOfYaml(readFileSync33(i.configFile, "utf8")) ?? local;
|
|
18590
18824
|
} catch {
|
|
18591
18825
|
}
|
|
18592
18826
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -18610,8 +18844,8 @@ async function reconcileShipName(i) {
|
|
|
18610
18844
|
}
|
|
18611
18845
|
if (result.status !== "ok") return result;
|
|
18612
18846
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
18613
|
-
const text2 =
|
|
18614
|
-
|
|
18847
|
+
const text2 = readFileSync33(i.configFile, "utf8");
|
|
18848
|
+
writeFileSync29(i.configFile, setShipNameInYaml(text2, result.name));
|
|
18615
18849
|
i.ship.name = result.name;
|
|
18616
18850
|
if (result.action === "row-wins") {
|
|
18617
18851
|
i.emit?.warn(
|
|
@@ -19270,8 +19504,8 @@ function describeCapability(i) {
|
|
|
19270
19504
|
}
|
|
19271
19505
|
|
|
19272
19506
|
// src/agents-sync-run.ts
|
|
19273
|
-
import { existsSync as
|
|
19274
|
-
import { dirname as
|
|
19507
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync27, readFileSync as readFileSync34, writeFileSync as writeFileSync30 } from "fs";
|
|
19508
|
+
import { dirname as dirname23 } from "path";
|
|
19275
19509
|
async function runAgentsSync(target, o) {
|
|
19276
19510
|
const dryRun = o.dryRun ?? false;
|
|
19277
19511
|
const force = o.force ?? false;
|
|
@@ -19304,12 +19538,12 @@ async function runAgentsSync(target, o) {
|
|
|
19304
19538
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19305
19539
|
if (!dryRun) {
|
|
19306
19540
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19307
|
-
const raw =
|
|
19541
|
+
const raw = existsSync38(resolvedPath) ? JSON.parse(readFileSync34(resolvedPath, "utf8")) : {};
|
|
19308
19542
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19309
19543
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19310
19544
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19311
|
-
|
|
19312
|
-
|
|
19545
|
+
mkdirSync27(dirname23(resolvedPath), { recursive: true });
|
|
19546
|
+
writeFileSync30(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19313
19547
|
`);
|
|
19314
19548
|
if (target.resolved) {
|
|
19315
19549
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -19413,7 +19647,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
19413
19647
|
|
|
19414
19648
|
// src/repos-check.ts
|
|
19415
19649
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
19416
|
-
import { readFileSync as
|
|
19650
|
+
import { readFileSync as readFileSync35 } from "fs";
|
|
19417
19651
|
import { basename as basename11 } from "path";
|
|
19418
19652
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
19419
19653
|
function checkHookSyntax(script, shell) {
|
|
@@ -19454,7 +19688,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
19454
19688
|
}
|
|
19455
19689
|
let parsed;
|
|
19456
19690
|
try {
|
|
19457
|
-
parsed = parseRepoConfig(
|
|
19691
|
+
parsed = parseRepoConfig(readFileSync35(file, "utf8"), file);
|
|
19458
19692
|
} catch (e) {
|
|
19459
19693
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
19460
19694
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -19733,7 +19967,7 @@ function existingRepoConfig(f) {
|
|
|
19733
19967
|
}
|
|
19734
19968
|
|
|
19735
19969
|
// src/repos-hooks-agent.ts
|
|
19736
|
-
import { spawn as spawn7, execFileSync as
|
|
19970
|
+
import { spawn as spawn7, execFileSync as execFileSync11 } from "child_process";
|
|
19737
19971
|
import { realpathSync as realpathSync2 } from "fs";
|
|
19738
19972
|
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
19739
19973
|
var AgentSuggestError = class extends Error {
|
|
@@ -19836,7 +20070,7 @@ function classifyFailure(o) {
|
|
|
19836
20070
|
}
|
|
19837
20071
|
function porcelain(dir) {
|
|
19838
20072
|
try {
|
|
19839
|
-
return
|
|
20073
|
+
return execFileSync11("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
19840
20074
|
} catch {
|
|
19841
20075
|
return void 0;
|
|
19842
20076
|
}
|
|
@@ -19905,7 +20139,7 @@ ${run3.err}` });
|
|
|
19905
20139
|
}
|
|
19906
20140
|
|
|
19907
20141
|
// src/sample-ticket.ts
|
|
19908
|
-
import { basename as basename12, dirname as
|
|
20142
|
+
import { basename as basename12, dirname as dirname24, join as join40 } from "path";
|
|
19909
20143
|
var SAMPLE_REPO = "crew-sample";
|
|
19910
20144
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
19911
20145
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -19928,8 +20162,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
19928
20162
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
19929
20163
|
`;
|
|
19930
20164
|
function sampleDir(existing, reposBase, workspace) {
|
|
19931
|
-
if (existing.length > 0) return
|
|
19932
|
-
return
|
|
20165
|
+
if (existing.length > 0) return join40(dirname24(existing[0]), SAMPLE_REPO);
|
|
20166
|
+
return join40(reposBase, workspace, SAMPLE_REPO);
|
|
19933
20167
|
}
|
|
19934
20168
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
19935
20169
|
function sampleDescription() {
|
|
@@ -19973,8 +20207,8 @@ function planSampleTicket(i) {
|
|
|
19973
20207
|
}
|
|
19974
20208
|
|
|
19975
20209
|
// src/worktree-sweep.ts
|
|
19976
|
-
import { existsSync as
|
|
19977
|
-
import { basename as basename13, dirname as
|
|
20210
|
+
import { existsSync as existsSync39, readFileSync as readFileSync36, readdirSync as readdirSync14 } from "fs";
|
|
20211
|
+
import { basename as basename13, dirname as dirname25, join as join41, resolve as resolve5 } from "path";
|
|
19978
20212
|
function planWorktreeSweep(target, tickets, contract) {
|
|
19979
20213
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
19980
20214
|
const verified = contract.statuses.verified;
|
|
@@ -20038,7 +20272,7 @@ function worktreeTicketNumbers(target) {
|
|
|
20038
20272
|
return [...nums];
|
|
20039
20273
|
}
|
|
20040
20274
|
function siblingWorktreeDirs(target) {
|
|
20041
|
-
const parent =
|
|
20275
|
+
const parent = dirname25(target.dir.replace(/[/\\]+$/, ""));
|
|
20042
20276
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
20043
20277
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
20044
20278
|
let names;
|
|
@@ -20051,7 +20285,7 @@ function siblingWorktreeDirs(target) {
|
|
|
20051
20285
|
for (const name of names) {
|
|
20052
20286
|
if (!name.startsWith(stem)) continue;
|
|
20053
20287
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
20054
|
-
const path =
|
|
20288
|
+
const path = join41(parent, name);
|
|
20055
20289
|
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
20056
20290
|
}
|
|
20057
20291
|
return out;
|
|
@@ -20086,7 +20320,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20086
20320
|
let removed = 0;
|
|
20087
20321
|
let keptBranches = 0;
|
|
20088
20322
|
for (const a of actions) {
|
|
20089
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20323
|
+
if (!dryRun && base && !a.keepBranch && existsSync39(a.path) && isHandBackMerge(a.path, base)) {
|
|
20090
20324
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20091
20325
|
log.emit(
|
|
20092
20326
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20094,7 +20328,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20094
20328
|
);
|
|
20095
20329
|
}
|
|
20096
20330
|
}
|
|
20097
|
-
if (!dryRun &&
|
|
20331
|
+
if (!dryRun && existsSync39(a.path)) {
|
|
20098
20332
|
let dirty = false;
|
|
20099
20333
|
try {
|
|
20100
20334
|
dirty = status(a.path).length > 0;
|
|
@@ -20105,7 +20339,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20105
20339
|
continue;
|
|
20106
20340
|
}
|
|
20107
20341
|
}
|
|
20108
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20342
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync39(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20109
20343
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20110
20344
|
continue;
|
|
20111
20345
|
}
|
|
@@ -20154,7 +20388,7 @@ function isHandBackMerge(path, base) {
|
|
|
20154
20388
|
if (!msgPath) return false;
|
|
20155
20389
|
let first;
|
|
20156
20390
|
try {
|
|
20157
|
-
first =
|
|
20391
|
+
first = readFileSync36(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20158
20392
|
} catch {
|
|
20159
20393
|
return false;
|
|
20160
20394
|
}
|
|
@@ -20259,14 +20493,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20259
20493
|
}
|
|
20260
20494
|
|
|
20261
20495
|
// src/stream-sweep.ts
|
|
20262
|
-
import { existsSync as
|
|
20263
|
-
import { join as
|
|
20496
|
+
import { existsSync as existsSync40, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync12 } from "fs";
|
|
20497
|
+
import { join as join42 } from "path";
|
|
20264
20498
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20265
20499
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20266
|
-
const dir =
|
|
20267
|
-
if (!
|
|
20500
|
+
const dir = join42(stateDir, "streams");
|
|
20501
|
+
if (!existsSync40(dir)) return [];
|
|
20268
20502
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20269
|
-
return readdirSync15(dir).map((name) =>
|
|
20503
|
+
return readdirSync15(dir).map((name) => join42(dir, name)).filter((path) => {
|
|
20270
20504
|
try {
|
|
20271
20505
|
return statSync12(path).mtimeMs < cutoff;
|
|
20272
20506
|
} catch {
|
|
@@ -20292,9 +20526,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20292
20526
|
return removed;
|
|
20293
20527
|
}
|
|
20294
20528
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20295
|
-
if (!
|
|
20529
|
+
if (!existsSync40(stateDir)) return [];
|
|
20296
20530
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20297
|
-
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
20531
|
+
return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join42(stateDir, name)).filter((path) => {
|
|
20298
20532
|
try {
|
|
20299
20533
|
return statSync12(path).mtimeMs < cutoff;
|
|
20300
20534
|
} catch {
|
|
@@ -20304,12 +20538,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20304
20538
|
}
|
|
20305
20539
|
|
|
20306
20540
|
// src/cli.ts
|
|
20307
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
20541
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync42, existsSync as existsSync47, mkdirSync as mkdirSync34, writeFileSync as writeFileSync35, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
|
|
20308
20542
|
import { createInterface } from "readline/promises";
|
|
20309
20543
|
|
|
20310
20544
|
// src/periscope-cmd.ts
|
|
20311
|
-
import { existsSync as
|
|
20312
|
-
import { join as
|
|
20545
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync28, readFileSync as readFileSync37, rmSync as rmSync19, writeFileSync as writeFileSync31 } from "fs";
|
|
20546
|
+
import { join as join43 } from "path";
|
|
20313
20547
|
var PeriscopeError = class extends Error {
|
|
20314
20548
|
};
|
|
20315
20549
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20319,28 +20553,29 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20319
20553
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20320
20554
|
}
|
|
20321
20555
|
};
|
|
20322
|
-
var priorDir = (stateDir) =>
|
|
20323
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
20556
|
+
var priorDir = (stateDir) => join43(stateDir, "periscope-priors");
|
|
20557
|
+
var priorPath = (stateDir, ticketRecordId) => join43(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20324
20558
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20325
20559
|
rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20326
20560
|
}
|
|
20327
20561
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20328
20562
|
try {
|
|
20329
|
-
|
|
20330
|
-
|
|
20563
|
+
mkdirSync28(priorDir(stateDir), { recursive: true });
|
|
20564
|
+
writeFileSync31(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20331
20565
|
} catch {
|
|
20332
20566
|
}
|
|
20333
20567
|
}
|
|
20334
20568
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20335
20569
|
try {
|
|
20336
20570
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20337
|
-
return
|
|
20571
|
+
return existsSync41(p) ? JSON.parse(readFileSync37(p, "utf8")) : void 0;
|
|
20338
20572
|
} catch {
|
|
20339
20573
|
return void 0;
|
|
20340
20574
|
}
|
|
20341
20575
|
}
|
|
20342
20576
|
var DEFAULT_MAX_PERISCOPES2 = 3;
|
|
20343
20577
|
var DEFAULT_PERISCOPE_IDLE_MINUTES2 = 120;
|
|
20578
|
+
var DEFAULT_INTEL_STARTUP_SECONDS2 = 20;
|
|
20344
20579
|
function parsePeriscopeUpArgs(positional2, flags) {
|
|
20345
20580
|
const usage2 = "usage: crew periscope up [[route] REPO] [--topic TEXT | --issue NNN] [--isolate] [--host HOST] [--assignee CREW_ROW_ID] [--requested-by ID] [--no-open] [--json]";
|
|
20346
20581
|
const rest = positional2.slice(2);
|
|
@@ -20408,12 +20643,15 @@ function openerFor(platform2) {
|
|
|
20408
20643
|
return platform2 === "darwin" ? "open" : platform2 === "linux" ? "xdg-open" : void 0;
|
|
20409
20644
|
}
|
|
20410
20645
|
function upOutput(res, terminal) {
|
|
20411
|
-
|
|
20646
|
+
const block = terminal ? `${res.ticket}
|
|
20412
20647
|
${res.url}
|
|
20413
20648
|
${res.worktree}
|
|
20414
20649
|
` : `${res.ticket}
|
|
20415
20650
|
${res.worktree}
|
|
20416
20651
|
${res.url}
|
|
20652
|
+
`;
|
|
20653
|
+
return `${block}
|
|
20654
|
+
${res.codeTools}
|
|
20417
20655
|
`;
|
|
20418
20656
|
}
|
|
20419
20657
|
function shouldOpen(f) {
|
|
@@ -20510,6 +20748,23 @@ ${tail(preview?.output ?? "")}`);
|
|
|
20510
20748
|
if (!url) return abandon(`preview printed no URL as its last line
|
|
20511
20749
|
${tail(preview.output)}`);
|
|
20512
20750
|
d.say("preview", `preview at ${url}`, { url });
|
|
20751
|
+
const intel = await probeIntel(
|
|
20752
|
+
{ runHook: (e) => d.runHook("intel", wt.path, e) },
|
|
20753
|
+
env,
|
|
20754
|
+
d.intelStartupSeconds ?? DEFAULT_INTEL_STARTUP_SECONDS2
|
|
20755
|
+
);
|
|
20756
|
+
if (intel.state === "ready") d.say("intel", `symbol service at ${intel.url} (${intel.tools.length} tools)`, { intelUrl: intel.url, intelTools: intel.tools });
|
|
20757
|
+
else if (intel.state === "failed") d.say("warning", `symbol service not started: ${intel.reason}`);
|
|
20758
|
+
let codeOutcome = intel;
|
|
20759
|
+
if (intel.state === "ready" && d.registerCode) {
|
|
20760
|
+
try {
|
|
20761
|
+
for (const why of d.registerCode(wt.path, intel.url).skipped) d.say("warning", why);
|
|
20762
|
+
} catch (e) {
|
|
20763
|
+
codeOutcome = { state: "failed", reason: `registering it failed: ${e.message}` };
|
|
20764
|
+
d.say("warning", `symbol service not registered: ${e.message}`);
|
|
20765
|
+
}
|
|
20766
|
+
}
|
|
20767
|
+
const codeTools = codeToolsParagraph(codeOutcome);
|
|
20513
20768
|
const now = d.now();
|
|
20514
20769
|
let row;
|
|
20515
20770
|
try {
|
|
@@ -20541,6 +20796,7 @@ ${tail(preview.output)}`);
|
|
|
20541
20796
|
host: d.host,
|
|
20542
20797
|
state: "live",
|
|
20543
20798
|
isolated: args.isolate,
|
|
20799
|
+
...intel.state === "ready" ? { intelUrl: intel.url, intelTools: intel.tools } : {},
|
|
20544
20800
|
startedAt: now.toISOString(),
|
|
20545
20801
|
lastActivityAt: now.toISOString(),
|
|
20546
20802
|
expiresAt: new Date(now.getTime() + d.caps.idleMinutes * 6e4).toISOString()
|
|
@@ -20558,6 +20814,7 @@ ${tail(preview.output)}`);
|
|
|
20558
20814
|
branch: wt.branch,
|
|
20559
20815
|
url,
|
|
20560
20816
|
periscopeRecordId: row.id,
|
|
20817
|
+
codeTools,
|
|
20561
20818
|
...prior ? { existing: true, priorStatus: prior.status } : {}
|
|
20562
20819
|
};
|
|
20563
20820
|
}
|
|
@@ -20737,9 +20994,9 @@ ${tail2(preview.output)}`);
|
|
|
20737
20994
|
}
|
|
20738
20995
|
|
|
20739
20996
|
// src/periscope-idle.ts
|
|
20740
|
-
import { execFileSync as
|
|
20997
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
20741
20998
|
import { statSync as statSync13 } from "fs";
|
|
20742
|
-
import { join as
|
|
20999
|
+
import { join as join44 } from "path";
|
|
20743
21000
|
var MAX_SCANNED_FILES = 2e4;
|
|
20744
21001
|
function newestWorktreeMtime(root) {
|
|
20745
21002
|
const a = newestTrackedMtime(root);
|
|
@@ -20749,7 +21006,7 @@ function newestWorktreeMtime(root) {
|
|
|
20749
21006
|
function newestTrackedMtime(root) {
|
|
20750
21007
|
let listing;
|
|
20751
21008
|
try {
|
|
20752
|
-
listing =
|
|
21009
|
+
listing = execFileSync12("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
20753
21010
|
cwd: root,
|
|
20754
21011
|
encoding: "utf8",
|
|
20755
21012
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -20764,7 +21021,7 @@ function newestTrackedMtime(root) {
|
|
|
20764
21021
|
if (!rel) continue;
|
|
20765
21022
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
20766
21023
|
try {
|
|
20767
|
-
const m = statSync13(
|
|
21024
|
+
const m = statSync13(join44(root, rel)).mtimeMs;
|
|
20768
21025
|
if (newest === null || m > newest) newest = m;
|
|
20769
21026
|
} catch {
|
|
20770
21027
|
}
|
|
@@ -20839,21 +21096,21 @@ async function runPeriscopeIdlePass(d) {
|
|
|
20839
21096
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
20840
21097
|
|
|
20841
21098
|
// src/ssh-keys.ts
|
|
20842
|
-
import { execFileSync as
|
|
20843
|
-
import { existsSync as
|
|
20844
|
-
import { dirname as
|
|
21099
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
21100
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync29, readFileSync as readFileSync38, chmodSync as chmodSync3 } from "fs";
|
|
21101
|
+
import { dirname as dirname26, join as join45 } from "path";
|
|
20845
21102
|
var SshKeyError = class extends Error {
|
|
20846
21103
|
};
|
|
20847
21104
|
function shipSshKeyPathFor(stateDir) {
|
|
20848
|
-
return
|
|
21105
|
+
return join45(stateDir, "ssh", "id_ed25519");
|
|
20849
21106
|
}
|
|
20850
21107
|
function ensureShipSshKeypair(stateDir) {
|
|
20851
21108
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
20852
21109
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
20853
|
-
if (!
|
|
20854
|
-
|
|
21110
|
+
if (!existsSync42(privateKeyPath)) {
|
|
21111
|
+
mkdirSync29(dirname26(privateKeyPath), { recursive: true });
|
|
20855
21112
|
try {
|
|
20856
|
-
|
|
21113
|
+
execFileSync13("ssh-keygen", [
|
|
20857
21114
|
"-t",
|
|
20858
21115
|
"ed25519",
|
|
20859
21116
|
"-N",
|
|
@@ -20871,15 +21128,15 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20871
21128
|
}
|
|
20872
21129
|
chmodSync3(privateKeyPath, 384);
|
|
20873
21130
|
}
|
|
20874
|
-
if (!
|
|
21131
|
+
if (!existsSync42(publicKeyPath)) {
|
|
20875
21132
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
20876
21133
|
}
|
|
20877
|
-
const publicKey =
|
|
21134
|
+
const publicKey = readFileSync38(publicKeyPath, "utf8").trim();
|
|
20878
21135
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
20879
21136
|
}
|
|
20880
21137
|
function sshKeygenAvailable() {
|
|
20881
21138
|
try {
|
|
20882
|
-
|
|
21139
|
+
execFileSync13("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
20883
21140
|
return true;
|
|
20884
21141
|
} catch (e) {
|
|
20885
21142
|
return e.code !== "ENOENT";
|
|
@@ -21038,12 +21295,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
21038
21295
|
import { createServer as createHttpServer } from "http";
|
|
21039
21296
|
import { spawn as spawn8 } from "child_process";
|
|
21040
21297
|
import { randomBytes } from "crypto";
|
|
21041
|
-
import { existsSync as
|
|
21042
|
-
import { join as
|
|
21298
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync32, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
|
|
21299
|
+
import { join as join46 } from "path";
|
|
21043
21300
|
|
|
21044
21301
|
// src/passenger-mirror.ts
|
|
21045
|
-
import { mkdirSync as
|
|
21046
|
-
import { dirname as
|
|
21302
|
+
import { mkdirSync as mkdirSync30 } from "fs";
|
|
21303
|
+
import { dirname as dirname27 } from "path";
|
|
21047
21304
|
function mirrorRepoOf(spec) {
|
|
21048
21305
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
21049
21306
|
if (fromOrigin) return fromOrigin;
|
|
@@ -21061,9 +21318,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
21061
21318
|
continue;
|
|
21062
21319
|
}
|
|
21063
21320
|
try {
|
|
21064
|
-
const parent =
|
|
21065
|
-
|
|
21066
|
-
|
|
21321
|
+
const parent = dirname27(spec.dir);
|
|
21322
|
+
mkdirSync30(parent, { recursive: true });
|
|
21323
|
+
mkdirSync30(spec.dir, { recursive: true });
|
|
21067
21324
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
21068
21325
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
21069
21326
|
} catch (e) {
|
|
@@ -21169,21 +21426,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21169
21426
|
return child;
|
|
21170
21427
|
};
|
|
21171
21428
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21172
|
-
return
|
|
21429
|
+
return join46(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21173
21430
|
}
|
|
21174
21431
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21175
21432
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21176
|
-
if (!
|
|
21433
|
+
if (!existsSync43(path)) return void 0;
|
|
21177
21434
|
try {
|
|
21178
|
-
return JSON.parse(
|
|
21435
|
+
return JSON.parse(readFileSync39(path, "utf8"));
|
|
21179
21436
|
} catch {
|
|
21180
21437
|
return void 0;
|
|
21181
21438
|
}
|
|
21182
21439
|
}
|
|
21183
21440
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21184
21441
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21185
|
-
|
|
21186
|
-
|
|
21442
|
+
mkdirSync31(join46(stateDir, "passengers"), { recursive: true });
|
|
21443
|
+
writeFileSync32(path, `${JSON.stringify(state, null, 2)}
|
|
21187
21444
|
`);
|
|
21188
21445
|
}
|
|
21189
21446
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21203,8 +21460,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21203
21460
|
}
|
|
21204
21461
|
}
|
|
21205
21462
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21206
|
-
const dir =
|
|
21207
|
-
if (!
|
|
21463
|
+
const dir = join46(stateDir, "passengers");
|
|
21464
|
+
if (!existsSync43(dir)) return [];
|
|
21208
21465
|
const suffix = "-sync.json";
|
|
21209
21466
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21210
21467
|
}
|
|
@@ -21292,8 +21549,8 @@ function describeCause(err3) {
|
|
|
21292
21549
|
}
|
|
21293
21550
|
|
|
21294
21551
|
// src/daemon.ts
|
|
21295
|
-
import { existsSync as
|
|
21296
|
-
import { join as
|
|
21552
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync32, readFileSync as readFileSync40, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync14, writeFileSync as writeFileSync33 } from "fs";
|
|
21553
|
+
import { join as join47 } from "path";
|
|
21297
21554
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
21298
21555
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
21299
21556
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -21381,7 +21638,7 @@ function latestMtime(path) {
|
|
|
21381
21638
|
if (!stat2.isDirectory()) return 0;
|
|
21382
21639
|
let latest = stat2.mtimeMs;
|
|
21383
21640
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
21384
|
-
latest = Math.max(latest, latestMtime(
|
|
21641
|
+
latest = Math.max(latest, latestMtime(join47(path, entry.name)));
|
|
21385
21642
|
}
|
|
21386
21643
|
return latest;
|
|
21387
21644
|
}
|
|
@@ -21390,10 +21647,10 @@ function computeTreeSignature(paths) {
|
|
|
21390
21647
|
}
|
|
21391
21648
|
function makeStaleChecker(paths, opts = {}) {
|
|
21392
21649
|
const initial = computeTreeSignature(paths);
|
|
21393
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
21650
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync44(opts.reloadSentinel);
|
|
21394
21651
|
}
|
|
21395
21652
|
function reloadSentinelFor(stateDir) {
|
|
21396
|
-
return
|
|
21653
|
+
return join47(stateDir, "reload-requested");
|
|
21397
21654
|
}
|
|
21398
21655
|
function readReloadRequestedAt(stateDir) {
|
|
21399
21656
|
try {
|
|
@@ -21403,17 +21660,17 @@ function readReloadRequestedAt(stateDir) {
|
|
|
21403
21660
|
}
|
|
21404
21661
|
}
|
|
21405
21662
|
function daemonStampFor(stateDir) {
|
|
21406
|
-
return
|
|
21663
|
+
return join47(stateDir, "daemon.started");
|
|
21407
21664
|
}
|
|
21408
21665
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
21409
|
-
|
|
21666
|
+
mkdirSync32(stateDir, { recursive: true });
|
|
21410
21667
|
rmSync20(reloadSentinelFor(stateDir), { force: true });
|
|
21411
|
-
|
|
21668
|
+
writeFileSync33(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
21412
21669
|
`);
|
|
21413
21670
|
}
|
|
21414
21671
|
function readDaemonStarted(stateDir) {
|
|
21415
21672
|
try {
|
|
21416
|
-
const v = JSON.parse(
|
|
21673
|
+
const v = JSON.parse(readFileSync40(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
21417
21674
|
return typeof v === "number" ? v : null;
|
|
21418
21675
|
} catch {
|
|
21419
21676
|
return null;
|
|
@@ -21971,7 +22228,7 @@ async function buildEnvironment(route2, ship, ticket) {
|
|
|
21971
22228
|
});
|
|
21972
22229
|
const authorship = await tracker.authorshipProbe().catch(() => null);
|
|
21973
22230
|
const attachments = await tracker.attachmentsProbe().catch(() => null);
|
|
21974
|
-
return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
|
|
22231
|
+
return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos: await withRepoMaps(repos, ship.mapTokenBudget, ship.shell), contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
|
|
21975
22232
|
}
|
|
21976
22233
|
|
|
21977
22234
|
// src/control-api.ts
|
|
@@ -21979,7 +22236,7 @@ import { createHash as createHash10 } from "crypto";
|
|
|
21979
22236
|
import {
|
|
21980
22237
|
chmodSync as chmodSync4,
|
|
21981
22238
|
closeSync as closeSync5,
|
|
21982
|
-
existsSync as
|
|
22239
|
+
existsSync as existsSync45,
|
|
21983
22240
|
fstatSync,
|
|
21984
22241
|
openSync as openSync5,
|
|
21985
22242
|
readSync as readSync3,
|
|
@@ -21990,7 +22247,7 @@ import {
|
|
|
21990
22247
|
} from "fs";
|
|
21991
22248
|
import { createConnection } from "net";
|
|
21992
22249
|
import { createServer } from "http";
|
|
21993
|
-
import { basename as basename14, dirname as
|
|
22250
|
+
import { basename as basename14, dirname as dirname28, join as join48 } from "path";
|
|
21994
22251
|
import { execFile as execFile3, spawn as spawn10 } from "child_process";
|
|
21995
22252
|
|
|
21996
22253
|
// src/periscope-control.ts
|
|
@@ -22219,7 +22476,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22219
22476
|
// src/control-api.ts
|
|
22220
22477
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
22221
22478
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
22222
|
-
var controlSocketFor = (stateDir) =>
|
|
22479
|
+
var controlSocketFor = (stateDir) => join48(stateDir, CONTROL_SOCKET_NAME);
|
|
22223
22480
|
var MAX_SOCKET_PATH = 100;
|
|
22224
22481
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
22225
22482
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -22306,7 +22563,7 @@ function candidates(live, rotated) {
|
|
|
22306
22563
|
const out = [live];
|
|
22307
22564
|
if (!rotated) return out;
|
|
22308
22565
|
try {
|
|
22309
|
-
for (const n of readdirSync18(
|
|
22566
|
+
for (const n of readdirSync18(dirname28(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join48(dirname28(live), n));
|
|
22310
22567
|
} catch {
|
|
22311
22568
|
}
|
|
22312
22569
|
return out;
|
|
@@ -22350,7 +22607,7 @@ function lastLines2(path, n) {
|
|
|
22350
22607
|
}
|
|
22351
22608
|
function readTail(live, opts) {
|
|
22352
22609
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
22353
|
-
if (!
|
|
22610
|
+
if (!existsSync45(live)) {
|
|
22354
22611
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
22355
22612
|
}
|
|
22356
22613
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -22466,7 +22723,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
22466
22723
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
22467
22724
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
22468
22725
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
22469
|
-
await waitForChange(
|
|
22726
|
+
await waitForChange(dirname28(path), deadline - Date.now(), abort);
|
|
22470
22727
|
r = readTail(path, { after: r.cursor, rotated });
|
|
22471
22728
|
}
|
|
22472
22729
|
return reply2(200, r);
|
|
@@ -22506,8 +22763,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
22506
22763
|
if (p.startsWith("/streams/") && get2) {
|
|
22507
22764
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
22508
22765
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
22509
|
-
const file =
|
|
22510
|
-
if (!
|
|
22766
|
+
const file = join48(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
22767
|
+
if (!existsSync45(file)) return err2(404, "no such run");
|
|
22511
22768
|
return tailReply(file, url.searchParams, false, abort);
|
|
22512
22769
|
}
|
|
22513
22770
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -22551,7 +22808,7 @@ function send(res, r) {
|
|
|
22551
22808
|
res.end(body);
|
|
22552
22809
|
}
|
|
22553
22810
|
async function socketIsStale(path) {
|
|
22554
|
-
if (!
|
|
22811
|
+
if (!existsSync45(path)) return false;
|
|
22555
22812
|
return new Promise((resolve8) => {
|
|
22556
22813
|
const c = createConnection(path);
|
|
22557
22814
|
c.once("connect", () => {
|
|
@@ -22570,7 +22827,7 @@ async function startControlServer(deps) {
|
|
|
22570
22827
|
return null;
|
|
22571
22828
|
}
|
|
22572
22829
|
if (await socketIsStale(path)) rmSync21(path, { force: true });
|
|
22573
|
-
else if (
|
|
22830
|
+
else if (existsSync45(path)) {
|
|
22574
22831
|
log(`control API not started: ${path} is in use by another process`);
|
|
22575
22832
|
return null;
|
|
22576
22833
|
}
|
|
@@ -22656,31 +22913,31 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
22656
22913
|
|
|
22657
22914
|
// src/periscope-mcp.ts
|
|
22658
22915
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
22659
|
-
import { basename as basename15, dirname as
|
|
22916
|
+
import { basename as basename15, dirname as dirname29, isAbsolute as isAbsolute4, join as join50, relative as relative2, resolve as resolve6, sep } from "path";
|
|
22660
22917
|
import { z } from "zod";
|
|
22661
22918
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22662
22919
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
22663
22920
|
|
|
22664
22921
|
// src/periscope-endpoints.ts
|
|
22665
22922
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
22666
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
22667
|
-
import { join as
|
|
22668
|
-
var secretDir = (stateDir) =>
|
|
22669
|
-
var secretFile = (stateDir, id) =>
|
|
22670
|
-
var endpointDir = (stateDir) =>
|
|
22671
|
-
var endpointFile = (stateDir, id) =>
|
|
22923
|
+
import { chmodSync as chmodSync5, existsSync as existsSync46, mkdirSync as mkdirSync33, readdirSync as readdirSync19, readFileSync as readFileSync41, rmSync as rmSync22, writeFileSync as writeFileSync34 } from "fs";
|
|
22924
|
+
import { join as join49 } from "path";
|
|
22925
|
+
var secretDir = (stateDir) => join49(stateDir, "periscope-mcp");
|
|
22926
|
+
var secretFile = (stateDir, id) => join49(secretDir(stateDir), `${id}.json`);
|
|
22927
|
+
var endpointDir = (stateDir) => join49(stateDir, "periscope-endpoints");
|
|
22928
|
+
var endpointFile = (stateDir, id) => join49(endpointDir(stateDir), `${id}.json`);
|
|
22672
22929
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22673
|
-
|
|
22930
|
+
mkdirSync33(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
22674
22931
|
const secret = randomBytes3(32).toString("base64url");
|
|
22675
22932
|
const file = secretFile(stateDir, ticketRecordId);
|
|
22676
|
-
|
|
22933
|
+
writeFileSync34(file, `${JSON.stringify({ secret })}
|
|
22677
22934
|
`, { mode: 384 });
|
|
22678
22935
|
chmodSync5(file, 384);
|
|
22679
22936
|
return secret;
|
|
22680
22937
|
}
|
|
22681
22938
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22682
22939
|
try {
|
|
22683
|
-
const v = JSON.parse(
|
|
22940
|
+
const v = JSON.parse(readFileSync41(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
22684
22941
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
22685
22942
|
} catch {
|
|
22686
22943
|
return void 0;
|
|
@@ -22691,14 +22948,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
22691
22948
|
}
|
|
22692
22949
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
22693
22950
|
try {
|
|
22694
|
-
return JSON.parse(
|
|
22951
|
+
return JSON.parse(readFileSync41(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
22695
22952
|
} catch {
|
|
22696
22953
|
return void 0;
|
|
22697
22954
|
}
|
|
22698
22955
|
}
|
|
22699
22956
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
22700
|
-
|
|
22701
|
-
|
|
22957
|
+
mkdirSync33(endpointDir(stateDir), { recursive: true });
|
|
22958
|
+
writeFileSync34(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
22702
22959
|
`);
|
|
22703
22960
|
}
|
|
22704
22961
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -22706,7 +22963,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
22706
22963
|
}
|
|
22707
22964
|
function listPeriscopeEndpoints(stateDir) {
|
|
22708
22965
|
const dir = endpointDir(stateDir);
|
|
22709
|
-
if (!
|
|
22966
|
+
if (!existsSync46(dir)) return [];
|
|
22710
22967
|
const out = [];
|
|
22711
22968
|
for (const name of readdirSync19(dir)) {
|
|
22712
22969
|
if (!name.endsWith(".json")) continue;
|
|
@@ -22794,10 +23051,10 @@ async function realAncestor(p) {
|
|
|
22794
23051
|
}
|
|
22795
23052
|
if (isLink) {
|
|
22796
23053
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
22797
|
-
cur = resolve6(
|
|
23054
|
+
cur = resolve6(dirname29(cur), await readlink(cur));
|
|
22798
23055
|
continue;
|
|
22799
23056
|
}
|
|
22800
|
-
const parent =
|
|
23057
|
+
const parent = dirname29(cur);
|
|
22801
23058
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
22802
23059
|
tail3.push(basename15(cur));
|
|
22803
23060
|
cur = parent;
|
|
@@ -22809,13 +23066,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
22809
23066
|
let abs;
|
|
22810
23067
|
if (isAbsolute4(requested)) {
|
|
22811
23068
|
abs = resolve6(requested);
|
|
22812
|
-
if (isInside(root, abs)) abs =
|
|
23069
|
+
if (isInside(root, abs)) abs = join50(realRoot, relative2(root, abs));
|
|
22813
23070
|
} else {
|
|
22814
23071
|
abs = resolve6(realRoot, requested);
|
|
22815
23072
|
}
|
|
22816
23073
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22817
23074
|
const { real, rest } = await realAncestor(abs);
|
|
22818
|
-
const final = rest ?
|
|
23075
|
+
const final = rest ? join50(real, rest) : real;
|
|
22819
23076
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22820
23077
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
22821
23078
|
return final;
|
|
@@ -22883,7 +23140,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22883
23140
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
22884
23141
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
22885
23142
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
22886
|
-
const size = (await lstat(
|
|
23143
|
+
const size = (await lstat(join50(abs, e.name))).size;
|
|
22887
23144
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
22888
23145
|
}));
|
|
22889
23146
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -22908,7 +23165,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22908
23165
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
22909
23166
|
const abs = await inTree(path);
|
|
22910
23167
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
22911
|
-
await mkdir(
|
|
23168
|
+
await mkdir(dirname29(abs), { recursive: true });
|
|
22912
23169
|
await inTree(path);
|
|
22913
23170
|
await writeFile(abs, content);
|
|
22914
23171
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -22948,8 +23205,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22948
23205
|
inputSchema: { path: z.string() },
|
|
22949
23206
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
22950
23207
|
}, async ({ path }) => {
|
|
22951
|
-
const parent = await inTree(
|
|
22952
|
-
const abs =
|
|
23208
|
+
const parent = await inTree(dirname29(path) === "" ? "." : dirname29(path));
|
|
23209
|
+
const abs = join50(parent, basename15(path));
|
|
22953
23210
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
22954
23211
|
const st = await lstat(abs).catch(() => void 0);
|
|
22955
23212
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -23070,7 +23327,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
23070
23327
|
}
|
|
23071
23328
|
|
|
23072
23329
|
// src/cli.ts
|
|
23073
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
23330
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname30(process.execPath) : resolve7(dirname30(fileURLToPath(import.meta.url)), "..");
|
|
23074
23331
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
23075
23332
|
var CYCLE_SECONDS2 = 120;
|
|
23076
23333
|
var STALE_CYCLES = 3;
|
|
@@ -23438,7 +23695,7 @@ if (command === "provision") {
|
|
|
23438
23695
|
process.exit(outcome.code);
|
|
23439
23696
|
}
|
|
23440
23697
|
if (command === "pool-member") {
|
|
23441
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
23698
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync42(0, "utf8"));
|
|
23442
23699
|
if (typeof pool === "string") {
|
|
23443
23700
|
process.stderr.write(`crew pool-member: ${pool}
|
|
23444
23701
|
`);
|
|
@@ -23666,7 +23923,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23666
23923
|
} else {
|
|
23667
23924
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
23668
23925
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
23669
|
-
if (configless && !
|
|
23926
|
+
if (configless && !existsSync47(findConfigFile(CREW_HOME))) {
|
|
23670
23927
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
23671
23928
|
} else {
|
|
23672
23929
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -23681,7 +23938,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23681
23938
|
throw e;
|
|
23682
23939
|
}
|
|
23683
23940
|
if (command === "credential") {
|
|
23684
|
-
const r = runCredential(positional[1],
|
|
23941
|
+
const r = runCredential(positional[1], readFileSync42(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
23685
23942
|
process.stdout.write(r.out);
|
|
23686
23943
|
process.stderr.write(r.err);
|
|
23687
23944
|
process.exit(r.code);
|
|
@@ -23693,9 +23950,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
23693
23950
|
forges: cfg.ship.forges,
|
|
23694
23951
|
stateDir: cfg.ship.stateDir,
|
|
23695
23952
|
configFile: cfg.configFile,
|
|
23696
|
-
readFile: (p) =>
|
|
23697
|
-
writeFile: (p, t) =>
|
|
23698
|
-
stdin: () =>
|
|
23953
|
+
readFile: (p) => readFileSync42(p, "utf8"),
|
|
23954
|
+
writeFile: (p, t) => writeFileSync35(p, t),
|
|
23955
|
+
stdin: () => readFileSync42(0, "utf8"),
|
|
23699
23956
|
env: process.env
|
|
23700
23957
|
}
|
|
23701
23958
|
);
|
|
@@ -23988,8 +24245,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
23988
24245
|
}
|
|
23989
24246
|
if (host === "linux" && hasSystemd) {
|
|
23990
24247
|
const label = labelFor(CREW_HOME, "run");
|
|
23991
|
-
const staleTimer =
|
|
23992
|
-
if (
|
|
24248
|
+
const staleTimer = join51(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
24249
|
+
if (existsSync47(staleTimer)) {
|
|
23993
24250
|
process.stdout.write(
|
|
23994
24251
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
23995
24252
|
`
|
|
@@ -24016,7 +24273,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24016
24273
|
const tracker = new Tracker(c, cfg.ship);
|
|
24017
24274
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
24018
24275
|
for (const r of await resolvedRepos(c)) {
|
|
24019
|
-
if (!
|
|
24276
|
+
if (!existsSync47(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
24020
24277
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
24021
24278
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
24022
24279
|
}
|
|
@@ -24043,7 +24300,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24043
24300
|
}
|
|
24044
24301
|
async function doctorFix(o) {
|
|
24045
24302
|
const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
|
|
24046
|
-
const unitExists = (p) =>
|
|
24303
|
+
const unitExists = (p) => existsSync47(p);
|
|
24047
24304
|
const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
24048
24305
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
24049
24306
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -24060,7 +24317,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24060
24317
|
for (const a of plan.actions) {
|
|
24061
24318
|
if (a.kind === "enable") {
|
|
24062
24319
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
24063
|
-
|
|
24320
|
+
writeFileSync35(cfg.configFile, planEnable(readFileSync42(cfg.configFile, "utf8"), a.route));
|
|
24064
24321
|
enabledSound.add(a.route);
|
|
24065
24322
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
24066
24323
|
} else {
|
|
@@ -24137,7 +24394,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
24137
24394
|
return renderEnvironment({
|
|
24138
24395
|
route: c,
|
|
24139
24396
|
userAgent: cfg.ship.userAgent,
|
|
24140
|
-
repos: await resolvedRepos(c),
|
|
24397
|
+
repos: await withRepoMaps(await resolvedRepos(c), cfg.ship.mapTokenBudget, cfg.ship.shell),
|
|
24141
24398
|
contract,
|
|
24142
24399
|
sourceTicket: ticket,
|
|
24143
24400
|
authorship: await tracker.authorshipProbe().catch(() => null),
|
|
@@ -25199,7 +25456,7 @@ ${cu.tail.trimEnd()}
|
|
|
25199
25456
|
process.exit(1);
|
|
25200
25457
|
}
|
|
25201
25458
|
requireArmed("run the daemon loop");
|
|
25202
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
25459
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join51(CREW_HOME, "src"), join51(CREW_HOME, "dist"), join51(CREW_HOME, "bin")];
|
|
25203
25460
|
const configPaths = daemonConfigPaths(cfg);
|
|
25204
25461
|
watchPaths.push(...configPaths);
|
|
25205
25462
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -25504,10 +25761,10 @@ ${cu.tail.trimEnd()}
|
|
|
25504
25761
|
`);
|
|
25505
25762
|
if (!dryRun) {
|
|
25506
25763
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
25507
|
-
const raw =
|
|
25764
|
+
const raw = existsSync47(resolvedPath) ? JSON.parse(readFileSync42(resolvedPath, "utf8")) : {};
|
|
25508
25765
|
raw.agentSkills = result.agentSkills;
|
|
25509
|
-
|
|
25510
|
-
|
|
25766
|
+
mkdirSync34(dirOf2(resolvedPath), { recursive: true });
|
|
25767
|
+
writeFileSync35(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
25511
25768
|
`);
|
|
25512
25769
|
}
|
|
25513
25770
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -25530,7 +25787,7 @@ ${cu.tail.trimEnd()}
|
|
|
25530
25787
|
throw e;
|
|
25531
25788
|
}
|
|
25532
25789
|
const oldName = cfg.ship.name;
|
|
25533
|
-
|
|
25790
|
+
writeFileSync35(cfg.configFile, setShipNameInYaml(readFileSync42(cfg.configFile, "utf8"), newName));
|
|
25534
25791
|
cfg.ship.name = newName;
|
|
25535
25792
|
let renameMachineId;
|
|
25536
25793
|
try {
|
|
@@ -25577,14 +25834,14 @@ ${cu.tail.trimEnd()}
|
|
|
25577
25834
|
process.exit(2);
|
|
25578
25835
|
};
|
|
25579
25836
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
25580
|
-
const configText =
|
|
25837
|
+
const configText = readFileSync42(cfg.configFile, "utf8");
|
|
25581
25838
|
if (sub === "check") {
|
|
25582
25839
|
try {
|
|
25583
25840
|
const routeArg = value("route") ?? positional[2];
|
|
25584
25841
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
25585
25842
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
25586
25843
|
const named2 = value("route") ? void 0 : positional[3];
|
|
25587
|
-
const dir = named2 ? repoDirOf(configText,
|
|
25844
|
+
const dir = named2 ? repoDirOf(configText, dirname30(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
25588
25845
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
25589
25846
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
25590
25847
|
runReposCheck(dir, {
|
|
@@ -25607,9 +25864,9 @@ ${cu.tail.trimEnd()}
|
|
|
25607
25864
|
if (sub === "hooks") {
|
|
25608
25865
|
try {
|
|
25609
25866
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
25610
|
-
const dir = repoDirOf(configText,
|
|
25611
|
-
if (!
|
|
25612
|
-
const files = { exists: (r) =>
|
|
25867
|
+
const dir = repoDirOf(configText, dirname30(cfg.configFile), a.route, a.name);
|
|
25868
|
+
if (!existsSync47(dir)) throw new ReposError(`${dir} does not exist on disk`);
|
|
25869
|
+
const files = { exists: (r) => existsSync47(resolvePath(dir, r)), list: (r) => {
|
|
25613
25870
|
try {
|
|
25614
25871
|
return readdirSync20(resolvePath(dir, r));
|
|
25615
25872
|
} catch {
|
|
@@ -25617,7 +25874,7 @@ ${cu.tail.trimEnd()}
|
|
|
25617
25874
|
}
|
|
25618
25875
|
}, read: (r) => {
|
|
25619
25876
|
try {
|
|
25620
|
-
return
|
|
25877
|
+
return readFileSync42(resolvePath(dir, r), "utf8");
|
|
25621
25878
|
} catch {
|
|
25622
25879
|
return void 0;
|
|
25623
25880
|
}
|
|
@@ -25659,19 +25916,19 @@ ${cu.tail.trimEnd()}
|
|
|
25659
25916
|
let cleared;
|
|
25660
25917
|
if (clear2) {
|
|
25661
25918
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
25662
|
-
if (plan.removed.length > 0)
|
|
25919
|
+
if (plan.removed.length > 0) writeFileSync35(cfg.configFile, plan.text);
|
|
25663
25920
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
25664
25921
|
}
|
|
25665
25922
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
25666
25923
|
if (write === "route") {
|
|
25667
25924
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
25668
|
-
|
|
25925
|
+
writeFileSync35(cfg.configFile, plan.text);
|
|
25669
25926
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
25670
25927
|
} else if (write === "repo") {
|
|
25671
25928
|
const have = existingRepoConfig(files);
|
|
25672
25929
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
25673
25930
|
const file = resolvePath(dir, ".crew.yaml");
|
|
25674
|
-
|
|
25931
|
+
writeFileSync35(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
25675
25932
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
25676
25933
|
}
|
|
25677
25934
|
if (flag("json")) {
|
|
@@ -25727,7 +25984,7 @@ ${cu.tail.trimEnd()}
|
|
|
25727
25984
|
if (sub === "list" && !routeName) usage2();
|
|
25728
25985
|
if (sub === "add") {
|
|
25729
25986
|
try {
|
|
25730
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
25987
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync47(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
25731
25988
|
routeName = a.route;
|
|
25732
25989
|
addPath = a.path;
|
|
25733
25990
|
addName = a.name;
|
|
@@ -25757,7 +26014,7 @@ ${cu.tail.trimEnd()}
|
|
|
25757
26014
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
25758
26015
|
if (sub === "list" || sub === "add") {
|
|
25759
26016
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
25760
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
26017
|
+
if (liveRoute?.resolved?.reposModelId && existsSync47(resolvedFile) && !dryRun) {
|
|
25761
26018
|
try {
|
|
25762
26019
|
await hydrateApiKeys([liveRoute]);
|
|
25763
26020
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -25766,16 +26023,16 @@ ${cu.tail.trimEnd()}
|
|
|
25766
26023
|
}
|
|
25767
26024
|
}
|
|
25768
26025
|
let resolvedRemotes;
|
|
25769
|
-
if (
|
|
26026
|
+
if (existsSync47(resolvedFile)) {
|
|
25770
26027
|
try {
|
|
25771
|
-
resolvedRemotes = JSON.parse(
|
|
26028
|
+
resolvedRemotes = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
25772
26029
|
} catch {
|
|
25773
26030
|
}
|
|
25774
26031
|
}
|
|
25775
26032
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
25776
26033
|
const text2 = configText;
|
|
25777
|
-
const base =
|
|
25778
|
-
const isCheckout = (d) =>
|
|
26034
|
+
const base = dirname30(cfg.configFile);
|
|
26035
|
+
const isCheckout = (d) => existsSync47(`${d}/.git`);
|
|
25779
26036
|
try {
|
|
25780
26037
|
if (sub === "list") {
|
|
25781
26038
|
if (flag("json")) {
|
|
@@ -25785,11 +26042,11 @@ ${cu.tail.trimEnd()}
|
|
|
25785
26042
|
base,
|
|
25786
26043
|
resolved: resolvedRemotes,
|
|
25787
26044
|
originOf,
|
|
25788
|
-
exists: (d) =>
|
|
26045
|
+
exists: (d) => existsSync47(d),
|
|
25789
26046
|
isCheckout,
|
|
25790
26047
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
25791
26048
|
try {
|
|
25792
|
-
return Object.keys(parseRepoConfig(
|
|
26049
|
+
return Object.keys(parseRepoConfig(readFileSync42(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
25793
26050
|
} catch {
|
|
25794
26051
|
return false;
|
|
25795
26052
|
}
|
|
@@ -25806,7 +26063,7 @@ ${cu.tail.trimEnd()}
|
|
|
25806
26063
|
for (const r of repos) {
|
|
25807
26064
|
const origin2 = originOf(r.dir);
|
|
25808
26065
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
25809
|
-
const note = !
|
|
26066
|
+
const note = !existsSync47(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
|
|
25810
26067
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
25811
26068
|
`);
|
|
25812
26069
|
}
|
|
@@ -25818,9 +26075,9 @@ ${cu.tail.trimEnd()}
|
|
|
25818
26075
|
` : `${human}
|
|
25819
26076
|
`);
|
|
25820
26077
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
25821
|
-
if (
|
|
26078
|
+
if (existsSync47(plan2.target) && (existsSync47(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
25822
26079
|
throw new ReposError(
|
|
25823
|
-
|
|
26080
|
+
existsSync47(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
25824
26081
|
);
|
|
25825
26082
|
}
|
|
25826
26083
|
if (dryRun) {
|
|
@@ -25838,14 +26095,14 @@ ${cu.tail.trimEnd()}
|
|
|
25838
26095
|
throw new ReposError(`${e.message}${authHint}
|
|
25839
26096
|
crew.yaml was not changed`);
|
|
25840
26097
|
}
|
|
25841
|
-
|
|
26098
|
+
writeFileSync35(cfg.configFile, plan2.text);
|
|
25842
26099
|
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
25843
26100
|
`);
|
|
25844
26101
|
say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
|
|
25845
26102
|
break;
|
|
25846
26103
|
}
|
|
25847
26104
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
25848
|
-
const top =
|
|
26105
|
+
const top = existsSync47(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
25849
26106
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
25850
26107
|
const origin = originOf(top);
|
|
25851
26108
|
const plan = planRepoAdd({
|
|
@@ -25897,15 +26154,15 @@ ${cu.tail.trimEnd()}
|
|
|
25897
26154
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
25898
26155
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
25899
26156
|
`);
|
|
25900
|
-
if (
|
|
25901
|
-
const raw = JSON.parse(
|
|
26157
|
+
if (existsSync47(resolvedFile)) {
|
|
26158
|
+
const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
25902
26159
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
25903
26160
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
25904
|
-
|
|
26161
|
+
writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
25905
26162
|
`);
|
|
25906
26163
|
}
|
|
25907
26164
|
}
|
|
25908
|
-
|
|
26165
|
+
writeFileSync35(cfg.configFile, plan.text);
|
|
25909
26166
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
25910
26167
|
`);
|
|
25911
26168
|
} catch (e) {
|
|
@@ -26274,7 +26531,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26274
26531
|
process.exit(2);
|
|
26275
26532
|
}
|
|
26276
26533
|
if (found.shipNameAdopted && !dryRun) {
|
|
26277
|
-
|
|
26534
|
+
writeFileSync35(cfg.configFile, setShipNameInYaml(readFileSync42(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
26278
26535
|
cfg.ship.name = found.shipNameAdopted;
|
|
26279
26536
|
}
|
|
26280
26537
|
if (found.shipRow) {
|
|
@@ -26317,10 +26574,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26317
26574
|
}
|
|
26318
26575
|
}
|
|
26319
26576
|
let previous;
|
|
26320
|
-
const hadPreviousFile =
|
|
26577
|
+
const hadPreviousFile = existsSync47(resolvedPath);
|
|
26321
26578
|
if (hadPreviousFile) {
|
|
26322
26579
|
try {
|
|
26323
|
-
previous = JSON.parse(
|
|
26580
|
+
previous = JSON.parse(readFileSync42(resolvedPath, "utf8"));
|
|
26324
26581
|
} catch {
|
|
26325
26582
|
}
|
|
26326
26583
|
}
|
|
@@ -26450,10 +26707,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26450
26707
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
26451
26708
|
`);
|
|
26452
26709
|
} else {
|
|
26453
|
-
|
|
26710
|
+
mkdirSync34(dirname30(resolvedPath), { recursive: true });
|
|
26454
26711
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
26455
26712
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
26456
|
-
|
|
26713
|
+
writeFileSync35(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
26457
26714
|
`);
|
|
26458
26715
|
renameSync6(tmpPath, resolvedPath);
|
|
26459
26716
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -26499,8 +26756,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26499
26756
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
26500
26757
|
`);
|
|
26501
26758
|
} else {
|
|
26502
|
-
|
|
26503
|
-
|
|
26759
|
+
mkdirSync34(dirname30(apiKeyFile), { recursive: true });
|
|
26760
|
+
writeFileSync35(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
26504
26761
|
`, { mode: 384 });
|
|
26505
26762
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
26506
26763
|
`);
|
|
@@ -27375,7 +27632,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27375
27632
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
27376
27633
|
const resumed = await runPeriscopeResume({
|
|
27377
27634
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
27378
|
-
worktreeExists: (p) =>
|
|
27635
|
+
worktreeExists: (p) => existsSync47(p),
|
|
27379
27636
|
prepareDropFile,
|
|
27380
27637
|
caps: periscopeCaps(cfg.ship),
|
|
27381
27638
|
runHook: async (name, cwd, env) => {
|
|
@@ -27470,10 +27727,10 @@ ${where.join("\n")}`);
|
|
|
27470
27727
|
},
|
|
27471
27728
|
prepare: async (path) => {
|
|
27472
27729
|
for (const f of repoP.config.worktrees.copy) {
|
|
27473
|
-
const src =
|
|
27474
|
-
if (!
|
|
27475
|
-
|
|
27476
|
-
copyFileSync2(src,
|
|
27730
|
+
const src = join51(repoP.dir, f);
|
|
27731
|
+
if (!existsSync47(src)) continue;
|
|
27732
|
+
mkdirSync34(dirOf2(join51(path, f)), { recursive: true });
|
|
27733
|
+
copyFileSync2(src, join51(path, f));
|
|
27477
27734
|
}
|
|
27478
27735
|
const setup = repoP.config.hooks.setup;
|
|
27479
27736
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -27482,10 +27739,11 @@ ${where.join("\n")}`);
|
|
|
27482
27739
|
},
|
|
27483
27740
|
runHook: async (name, cwd, env) => {
|
|
27484
27741
|
const script = repoP.config.hooks[name];
|
|
27485
|
-
return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : void 0 }) : null;
|
|
27742
|
+
return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : name === "intel" ? 6e4 : void 0 }) : null;
|
|
27486
27743
|
},
|
|
27487
27744
|
createRow: (fields) => trackerP.createPeriscope(fields),
|
|
27488
27745
|
prepareDropFile,
|
|
27746
|
+
registerCode: registerCodeServer,
|
|
27489
27747
|
recordLocal: (sc) => {
|
|
27490
27748
|
writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
|
|
27491
27749
|
if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
|
|
@@ -27545,6 +27803,7 @@ ${where.join("\n")}`);
|
|
|
27545
27803
|
},
|
|
27546
27804
|
now: () => /* @__PURE__ */ new Date(),
|
|
27547
27805
|
host: a.host ?? "localhost",
|
|
27806
|
+
intelStartupSeconds: cfg.ship.intelStartupSeconds,
|
|
27548
27807
|
say
|
|
27549
27808
|
};
|
|
27550
27809
|
if (dryRun) {
|
|
@@ -27628,7 +27887,7 @@ ${where.join("\n")}`);
|
|
|
27628
27887
|
}
|
|
27629
27888
|
case "sync": {
|
|
27630
27889
|
emit.enter("worktree");
|
|
27631
|
-
let
|
|
27890
|
+
let tracked2 = 0;
|
|
27632
27891
|
let acted = 0;
|
|
27633
27892
|
let baseChecked = 0;
|
|
27634
27893
|
let baseActed = 0;
|
|
@@ -27666,7 +27925,7 @@ ${where.join("\n")}`);
|
|
|
27666
27925
|
}
|
|
27667
27926
|
if (!s2.upstream) continue;
|
|
27668
27927
|
if (w.branch !== r.config.branch.base && s2.upstream === `${r.config.branch.remote}/${r.config.branch.base}`) continue;
|
|
27669
|
-
|
|
27928
|
+
tracked2++;
|
|
27670
27929
|
if (s2.canFastForward) {
|
|
27671
27930
|
acted++;
|
|
27672
27931
|
if (dryRun) emit.emit(`would fast-forward ${w.branch}: ${s2.detail}`);
|
|
@@ -27679,8 +27938,8 @@ ${where.join("\n")}`);
|
|
|
27679
27938
|
}
|
|
27680
27939
|
if (baseUnsafe > 0) process.exitCode = 1;
|
|
27681
27940
|
if (baseChecked > 0 && baseActed === 0) emit.emit(`${baseChecked} base branch(es) checked, all level with the remote`);
|
|
27682
|
-
if (
|
|
27683
|
-
else if (acted === 0) emit.emit(`${
|
|
27941
|
+
if (tracked2 === 0) emit.emit("no worktree tracks a remote branch \u2014 nothing to sync");
|
|
27942
|
+
else if (acted === 0) emit.emit(`${tracked2} worktree(s) tracking a remote, all level with it`);
|
|
27684
27943
|
break;
|
|
27685
27944
|
}
|
|
27686
27945
|
case "pause":
|
|
@@ -27769,7 +28028,7 @@ ${where.join("\n")}`);
|
|
|
27769
28028
|
};
|
|
27770
28029
|
const droppedRoutes = [];
|
|
27771
28030
|
try {
|
|
27772
|
-
const rawCfg = parseYaml2(
|
|
28031
|
+
const rawCfg = parseYaml2(readFileSync42(cfg.configFile, "utf8"));
|
|
27773
28032
|
for (const r of rawCfg?.routes ?? []) {
|
|
27774
28033
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
27775
28034
|
try {
|
|
@@ -27822,8 +28081,8 @@ ${where.join("\n")}`);
|
|
|
27822
28081
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
27823
28082
|
break;
|
|
27824
28083
|
}
|
|
27825
|
-
|
|
27826
|
-
|
|
28084
|
+
mkdirSync34(cfg.ship.stateDir, { recursive: true });
|
|
28085
|
+
writeFileSync35(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
27827
28086
|
`);
|
|
27828
28087
|
process.stdout.write(
|
|
27829
28088
|
"crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
|
|
@@ -27888,7 +28147,7 @@ ${where.join("\n")}`);
|
|
|
27888
28147
|
}
|
|
27889
28148
|
case "log": {
|
|
27890
28149
|
try {
|
|
27891
|
-
const text2 =
|
|
28150
|
+
const text2 = readFileSync42(cfg.ship.logFile, "utf8").split("\n");
|
|
27892
28151
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
27893
28152
|
`);
|
|
27894
28153
|
} catch {
|
|
@@ -27911,8 +28170,8 @@ ${where.join("\n")}`);
|
|
|
27911
28170
|
};
|
|
27912
28171
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
27913
28172
|
const tracker = new Tracker(route2, cfg.ship);
|
|
27914
|
-
const configText =
|
|
27915
|
-
const cfgBase =
|
|
28173
|
+
const configText = readFileSync42(cfg.configFile, "utf8");
|
|
28174
|
+
const cfgBase = dirname30(cfg.configFile);
|
|
27916
28175
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
27917
28176
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
27918
28177
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -27940,7 +28199,7 @@ ${where.join("\n")}`);
|
|
|
27940
28199
|
}
|
|
27941
28200
|
if (configured2.some((r) => r.dir === dir)) {
|
|
27942
28201
|
try {
|
|
27943
|
-
|
|
28202
|
+
writeFileSync35(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
27944
28203
|
} catch (e) {
|
|
27945
28204
|
if (e instanceof ReposError) fail(e.message);
|
|
27946
28205
|
throw e;
|
|
@@ -27948,29 +28207,29 @@ ${where.join("\n")}`);
|
|
|
27948
28207
|
}
|
|
27949
28208
|
if (row) {
|
|
27950
28209
|
await tracker.deleteRepoRow(row.id);
|
|
27951
|
-
if (
|
|
27952
|
-
const raw = JSON.parse(
|
|
28210
|
+
if (existsSync47(resolvedFile)) {
|
|
28211
|
+
const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
27953
28212
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
27954
28213
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
27955
|
-
|
|
28214
|
+
writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27956
28215
|
`);
|
|
27957
28216
|
}
|
|
27958
28217
|
}
|
|
27959
|
-
if (
|
|
28218
|
+
if (existsSync47(join51(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
|
|
27960
28219
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
27961
28220
|
break;
|
|
27962
28221
|
}
|
|
27963
28222
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
27964
|
-
const fresh = !
|
|
27965
|
-
if (
|
|
28223
|
+
const fresh = !existsSync47(join51(dir, ".git"));
|
|
28224
|
+
if (existsSync47(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
27966
28225
|
if (dryRun) {
|
|
27967
28226
|
say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
|
|
27968
28227
|
break;
|
|
27969
28228
|
}
|
|
27970
28229
|
if (fresh) {
|
|
27971
|
-
|
|
27972
|
-
|
|
27973
|
-
|
|
28230
|
+
mkdirSync34(dir, { recursive: true });
|
|
28231
|
+
writeFileSync35(join51(dir, "README.md"), SAMPLE_README);
|
|
28232
|
+
writeFileSync35(join51(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
27974
28233
|
const g = (...a) => {
|
|
27975
28234
|
const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
|
|
27976
28235
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -27988,17 +28247,17 @@ ${where.join("\n")}`);
|
|
|
27988
28247
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
27989
28248
|
});
|
|
27990
28249
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
27991
|
-
if (
|
|
27992
|
-
const raw = JSON.parse(
|
|
28250
|
+
if (existsSync47(resolvedFile)) {
|
|
28251
|
+
const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
27993
28252
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
27994
28253
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
27995
|
-
|
|
28254
|
+
writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
27996
28255
|
`);
|
|
27997
28256
|
}
|
|
27998
28257
|
}
|
|
27999
28258
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
28000
28259
|
try {
|
|
28001
|
-
|
|
28260
|
+
writeFileSync35(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
28002
28261
|
} catch (e) {
|
|
28003
28262
|
if (e instanceof ReposError) fail(e.message);
|
|
28004
28263
|
throw e;
|
|
@@ -28344,7 +28603,7 @@ watermark: ${state.watermark()}
|
|
|
28344
28603
|
}
|
|
28345
28604
|
for (const r of repos) {
|
|
28346
28605
|
const remote = r.config.branch.remote;
|
|
28347
|
-
if (
|
|
28606
|
+
if (existsSync47(r.dir)) {
|
|
28348
28607
|
pushAuth.push({
|
|
28349
28608
|
route: c.route,
|
|
28350
28609
|
repo: r.name,
|
|
@@ -28382,7 +28641,8 @@ watermark: ${state.watermark()}
|
|
|
28382
28641
|
notes: validateEffective(r.config),
|
|
28383
28642
|
base: doctorBaseReport(r, r.config.branch.base, baseHealth),
|
|
28384
28643
|
hooks: r.config.hooks,
|
|
28385
|
-
onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath)
|
|
28644
|
+
onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath),
|
|
28645
|
+
intelSyntax: r.config.hooks.intel ? checkHookSyntax(r.config.hooks.intel, r.config.shell) : void 0
|
|
28386
28646
|
});
|
|
28387
28647
|
problems.push(...f.problems);
|
|
28388
28648
|
passed += f.passed;
|
|
@@ -28433,7 +28693,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28433
28693
|
}
|
|
28434
28694
|
try {
|
|
28435
28695
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
28436
|
-
const evSize =
|
|
28696
|
+
const evSize = existsSync47(evFile) ? statSync16(evFile).size : 0;
|
|
28437
28697
|
process.stdout.write(
|
|
28438
28698
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
28439
28699
|
` : "events log: ok\n"
|
|
@@ -28444,7 +28704,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28444
28704
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
28445
28705
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
28446
28706
|
try {
|
|
28447
|
-
return
|
|
28707
|
+
return readFileSync42(p, "utf8");
|
|
28448
28708
|
} catch {
|
|
28449
28709
|
return null;
|
|
28450
28710
|
}
|