@tablation/crew 0.116.0 → 0.117.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,7 @@ var HOOK_NAMES = [
|
|
|
2382
2528
|
"isolate",
|
|
2383
2529
|
"handoff",
|
|
2384
2530
|
"preview",
|
|
2531
|
+
"intel",
|
|
2385
2532
|
"map",
|
|
2386
2533
|
"ci_status",
|
|
2387
2534
|
"version",
|
|
@@ -2453,8 +2600,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
2453
2600
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
2454
2601
|
function findRepoConfig(dir) {
|
|
2455
2602
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
2456
|
-
const p =
|
|
2457
|
-
if (
|
|
2603
|
+
const p = join13(dir, name);
|
|
2604
|
+
if (existsSync11(p)) return p;
|
|
2458
2605
|
}
|
|
2459
2606
|
return null;
|
|
2460
2607
|
}
|
|
@@ -2734,7 +2881,7 @@ function parseRepoConfig(text2, file) {
|
|
|
2734
2881
|
function loadRepoConfig(dir) {
|
|
2735
2882
|
const file = findRepoConfig(dir);
|
|
2736
2883
|
if (!file) return null;
|
|
2737
|
-
return parseRepoConfig(
|
|
2884
|
+
return parseRepoConfig(readFileSync12(file, "utf8"), file);
|
|
2738
2885
|
}
|
|
2739
2886
|
function hookLabel(cfg, hook2) {
|
|
2740
2887
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -3015,6 +3162,7 @@ var ConfigError = class extends Error {
|
|
|
3015
3162
|
};
|
|
3016
3163
|
var DEFAULT_MAX_PERISCOPES = 3;
|
|
3017
3164
|
var DEFAULT_PERISCOPE_IDLE_MINUTES = 120;
|
|
3165
|
+
var DEFAULT_INTEL_STARTUP_SECONDS = 20;
|
|
3018
3166
|
var DEFAULT_MAP_TOKEN_BUDGET = 1e3;
|
|
3019
3167
|
function parseCloneScheme(v) {
|
|
3020
3168
|
if (v === void 0 || v === null || v === "") return void 0;
|
|
@@ -3108,6 +3256,7 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
3108
3256
|
"previewHost",
|
|
3109
3257
|
"maxPeriscopes",
|
|
3110
3258
|
"periscopeIdleMinutes",
|
|
3259
|
+
"intelStartupSeconds",
|
|
3111
3260
|
"mapTokenBudget",
|
|
3112
3261
|
"secretStore",
|
|
3113
3262
|
"env",
|
|
@@ -3261,11 +3410,11 @@ function expand(p, base) {
|
|
|
3261
3410
|
return isAbsolute2(out) ? out : resolve2(base, out);
|
|
3262
3411
|
}
|
|
3263
3412
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
3264
|
-
if (!promptSet) return
|
|
3413
|
+
if (!promptSet) return join14(crewHome, "prompts", "default");
|
|
3265
3414
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
3266
3415
|
return expand(promptSet, base);
|
|
3267
3416
|
}
|
|
3268
|
-
return
|
|
3417
|
+
return join14(crewHome, "prompts", promptSet);
|
|
3269
3418
|
}
|
|
3270
3419
|
var Missing = class {
|
|
3271
3420
|
paths = [];
|
|
@@ -3289,7 +3438,7 @@ var Missing = class {
|
|
|
3289
3438
|
};
|
|
3290
3439
|
function crewVersion(crewHome) {
|
|
3291
3440
|
try {
|
|
3292
|
-
const pkg = JSON.parse(
|
|
3441
|
+
const pkg = JSON.parse(readFileSync13(join14(crewHome, "package.json"), "utf8"));
|
|
3293
3442
|
return pkg.version ?? "0.0.0";
|
|
3294
3443
|
} catch {
|
|
3295
3444
|
return "0.0.0";
|
|
@@ -3299,26 +3448,26 @@ function configSearchPath(crewHome) {
|
|
|
3299
3448
|
if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
|
|
3300
3449
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
3301
3450
|
return [
|
|
3302
|
-
...xdg ? [
|
|
3303
|
-
|
|
3451
|
+
...xdg ? [join14(xdg, "crew", "crew.yaml")] : [],
|
|
3452
|
+
join14(homedir6(), ".config", "crew", "crew.yaml"),
|
|
3304
3453
|
resolve2(crewHome, "crew.yaml")
|
|
3305
3454
|
];
|
|
3306
3455
|
}
|
|
3307
3456
|
function findConfigFile(crewHome) {
|
|
3308
3457
|
const candidates2 = configSearchPath(crewHome);
|
|
3309
|
-
return candidates2.find((c) =>
|
|
3458
|
+
return candidates2.find((c) => existsSync12(c)) ?? candidates2[candidates2.length - 1];
|
|
3310
3459
|
}
|
|
3311
|
-
var defaultConfigPath = () =>
|
|
3460
|
+
var defaultConfigPath = () => join14(process.env.XDG_CONFIG_HOME ?? join14(homedir6(), ".config"), "crew", "crew.yaml");
|
|
3312
3461
|
function routeSlug(route2) {
|
|
3313
3462
|
return route2.replace(/\//g, "-");
|
|
3314
3463
|
}
|
|
3315
3464
|
function resolvedPathFor(stateDir, route2) {
|
|
3316
3465
|
const [workspace, project] = splitRoute(route2);
|
|
3317
|
-
return
|
|
3466
|
+
return join14(stateDir, "resolved", workspace, `${project}.json`);
|
|
3318
3467
|
}
|
|
3319
3468
|
function apiKeyPathFor(stateDir, route2) {
|
|
3320
3469
|
const [workspace, project] = splitRoute(route2);
|
|
3321
|
-
return
|
|
3470
|
+
return join14(stateDir, "keys", workspace, `${project}.env`);
|
|
3322
3471
|
}
|
|
3323
3472
|
function splitRoute(route2) {
|
|
3324
3473
|
const parts = route2.split("/");
|
|
@@ -3329,9 +3478,9 @@ function splitRoute(route2) {
|
|
|
3329
3478
|
}
|
|
3330
3479
|
function readResolvedFile(stateDir, route2) {
|
|
3331
3480
|
const path = resolvedPathFor(stateDir, route2);
|
|
3332
|
-
if (!
|
|
3481
|
+
if (!existsSync12(path)) return void 0;
|
|
3333
3482
|
try {
|
|
3334
|
-
return JSON.parse(
|
|
3483
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
3335
3484
|
} catch (e) {
|
|
3336
3485
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
3337
3486
|
}
|
|
@@ -3392,23 +3541,23 @@ function parseResolved(raw, m, where) {
|
|
|
3392
3541
|
}
|
|
3393
3542
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
3394
3543
|
const file = configFile ?? findConfigFile(crewHome);
|
|
3395
|
-
if (opts.text === void 0 && !
|
|
3544
|
+
if (opts.text === void 0 && !existsSync12(file)) {
|
|
3396
3545
|
throw new ConfigError(
|
|
3397
3546
|
`no config found. Looked in:
|
|
3398
3547
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
3399
3548
|
|
|
3400
3549
|
Create the first of those:
|
|
3401
|
-
mkdir -p ${
|
|
3550
|
+
mkdir -p ${dirname6(defaultConfigPath())} && cp ${join14(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
3402
3551
|
);
|
|
3403
3552
|
}
|
|
3404
3553
|
let raw;
|
|
3405
3554
|
try {
|
|
3406
|
-
raw = parse2(opts.text ??
|
|
3555
|
+
raw = parse2(opts.text ?? readFileSync13(file, "utf8"));
|
|
3407
3556
|
} catch (e) {
|
|
3408
3557
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
3409
3558
|
}
|
|
3410
3559
|
if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
|
|
3411
|
-
const base =
|
|
3560
|
+
const base = dirname6(file);
|
|
3412
3561
|
const missing = new Missing();
|
|
3413
3562
|
rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
|
|
3414
3563
|
const shipRaw = raw.ship ?? {};
|
|
@@ -3457,6 +3606,10 @@ Create the first of those:
|
|
|
3457
3606
|
if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
|
|
3458
3607
|
missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
|
|
3459
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
|
+
}
|
|
3460
3613
|
const mapTokenBudget = shipRaw.mapTokenBudget === void 0 ? DEFAULT_MAP_TOKEN_BUDGET : Number(shipRaw.mapTokenBudget);
|
|
3461
3614
|
if (!Number.isInteger(mapTokenBudget) || mapTokenBudget < 1) {
|
|
3462
3615
|
missing.add(`ship.mapTokenBudget must be a positive integer (got "${shipRaw.mapTokenBudget}")`);
|
|
@@ -3526,7 +3679,7 @@ Create the first of those:
|
|
|
3526
3679
|
useNvm: shipRaw.useNvm !== false,
|
|
3527
3680
|
nvmSh: shipRaw.nvmSh,
|
|
3528
3681
|
stateDir,
|
|
3529
|
-
logFile: expand(shipRaw.logFile ??
|
|
3682
|
+
logFile: expand(shipRaw.logFile ?? join14(tmpdir(), "crew.log"), base),
|
|
3530
3683
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
3531
3684
|
maxConcurrentAgents,
|
|
3532
3685
|
maxConcurrentAgentsSet,
|
|
@@ -3544,6 +3697,7 @@ Create the first of those:
|
|
|
3544
3697
|
previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
|
|
3545
3698
|
maxPeriscopes,
|
|
3546
3699
|
periscopeIdleMinutes,
|
|
3700
|
+
intelStartupSeconds,
|
|
3547
3701
|
mapTokenBudget,
|
|
3548
3702
|
secretStore,
|
|
3549
3703
|
env: parseShipEnv(shipRaw.env)
|
|
@@ -3707,7 +3861,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
3707
3861
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
3708
3862
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
3709
3863
|
const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
|
|
3710
|
-
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]) : "";
|
|
3711
3865
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
3712
3866
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
3713
3867
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -3770,7 +3924,7 @@ function locateDir(cfg, dir, commonDirOf) {
|
|
|
3770
3924
|
const direct = find(target);
|
|
3771
3925
|
if (direct) return direct;
|
|
3772
3926
|
const common = commonDirOf?.(target);
|
|
3773
|
-
return common ? find(
|
|
3927
|
+
return common ? find(dirname6(resolve2(target, common))) : void 0;
|
|
3774
3928
|
}
|
|
3775
3929
|
function findRoute(cfg, route2) {
|
|
3776
3930
|
if (!route2) {
|
|
@@ -3805,8 +3959,8 @@ function repoIdForName(r, name) {
|
|
|
3805
3959
|
}
|
|
3806
3960
|
function resolveApiKey(r) {
|
|
3807
3961
|
if (r.apiKey) return r.apiKey;
|
|
3808
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3809
|
-
for (const line of
|
|
3962
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync12(r.apiKeyFile)) {
|
|
3963
|
+
for (const line of readFileSync13(r.apiKeyFile, "utf8").split("\n")) {
|
|
3810
3964
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
3811
3965
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
3812
3966
|
}
|
|
@@ -3819,9 +3973,9 @@ function resolveApiKey(r) {
|
|
|
3819
3973
|
}
|
|
3820
3974
|
function credentialSource(r) {
|
|
3821
3975
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
3822
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
3976
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync12(r.apiKeyFile)) {
|
|
3823
3977
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
3824
|
-
if (
|
|
3978
|
+
if (readFileSync13(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
3825
3979
|
}
|
|
3826
3980
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
3827
3981
|
}
|
|
@@ -3877,7 +4031,7 @@ function configuredMembers(rt) {
|
|
|
3877
4031
|
return out;
|
|
3878
4032
|
}
|
|
3879
4033
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
3880
|
-
return
|
|
4034
|
+
return join14(basePath, workspace, repoName);
|
|
3881
4035
|
}
|
|
3882
4036
|
function reposOf(r) {
|
|
3883
4037
|
const named = Object.entries(r.repos);
|
|
@@ -3916,10 +4070,10 @@ function passengerRepoTargets(r) {
|
|
|
3916
4070
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
3917
4071
|
}
|
|
3918
4072
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
3919
|
-
return
|
|
4073
|
+
return join14(basePath, workspace, ".passenger-checkouts", repoName);
|
|
3920
4074
|
}
|
|
3921
4075
|
function passengerMirrorArea(ship) {
|
|
3922
|
-
return ship.runner === "sandbox" ?
|
|
4076
|
+
return ship.runner === "sandbox" ? join14(ship.stateDir, "passenger-sources") : void 0;
|
|
3923
4077
|
}
|
|
3924
4078
|
function passengerCheckoutTargets(r, mirrorArea) {
|
|
3925
4079
|
if (!r.hostPassengers) return [];
|
|
@@ -3930,7 +4084,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
|
|
|
3930
4084
|
const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
|
|
3931
4085
|
return {
|
|
3932
4086
|
name: t.name,
|
|
3933
|
-
dir: mirrorArea ?
|
|
4087
|
+
dir: mirrorArea ? join14(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
|
|
3934
4088
|
remote: t.remote ?? (id ? remotes[id] : void 0),
|
|
3935
4089
|
...forgeHost ? { forgeHost } : {}
|
|
3936
4090
|
};
|
|
@@ -4212,9 +4366,9 @@ function recoveredFailureHeadlines(o, route2) {
|
|
|
4212
4366
|
}
|
|
4213
4367
|
|
|
4214
4368
|
// src/install.ts
|
|
4215
|
-
import { dirname as
|
|
4369
|
+
import { dirname as dirname7, join as join15, extname } from "path";
|
|
4216
4370
|
import { homedir as homedir7, userInfo } from "os";
|
|
4217
|
-
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";
|
|
4218
4372
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4219
4373
|
import { createHash as createHash3 } from "crypto";
|
|
4220
4374
|
|
|
@@ -4226,7 +4380,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4226
4380
|
// src/install.ts
|
|
4227
4381
|
function crewInvocation(crewHome) {
|
|
4228
4382
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4229
|
-
return [process.execPath,
|
|
4383
|
+
return [process.execPath, join15(crewHome, "bin", "crew")];
|
|
4230
4384
|
}
|
|
4231
4385
|
var INTERVAL_SECONDS = 120;
|
|
4232
4386
|
function labelFor(crewHome, job) {
|
|
@@ -4257,14 +4411,14 @@ function schedulerLogFor(crewLog, job) {
|
|
|
4257
4411
|
return `${stem}.${suffix}${ext || ".log"}`;
|
|
4258
4412
|
}
|
|
4259
4413
|
function pathFor(ship) {
|
|
4260
|
-
const nodeBinDir =
|
|
4414
|
+
const nodeBinDir = dirname7(process.execPath);
|
|
4261
4415
|
const parts = [nodeBinDir];
|
|
4262
4416
|
if (ship.extraPath) parts.push(ship.extraPath);
|
|
4263
4417
|
parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
|
|
4264
4418
|
return parts.join(":");
|
|
4265
4419
|
}
|
|
4266
4420
|
function findOnPath(bin, searchPath) {
|
|
4267
|
-
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));
|
|
4268
4422
|
for (const candidate of candidates2) {
|
|
4269
4423
|
try {
|
|
4270
4424
|
accessSync2(candidate, constants2.X_OK);
|
|
@@ -4274,12 +4428,12 @@ function findOnPath(bin, searchPath) {
|
|
|
4274
4428
|
}
|
|
4275
4429
|
return void 0;
|
|
4276
4430
|
}
|
|
4277
|
-
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(":");
|
|
4278
4432
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4279
4433
|
const schedulerPath = pathFor(ship);
|
|
4280
4434
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
4281
4435
|
const inShell = findOnPath("docker", shellPath);
|
|
4282
|
-
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\``;
|
|
4283
4437
|
}
|
|
4284
4438
|
var USER_INSTALL = { root: false, user: "" };
|
|
4285
4439
|
function installContext(env = process.env) {
|
|
@@ -4289,7 +4443,7 @@ function installContext(env = process.env) {
|
|
|
4289
4443
|
}
|
|
4290
4444
|
var SYSTEM_UNIT_DIR = "/etc/systemd/system";
|
|
4291
4445
|
function systemUnitPathFor(label, suffix = "service") {
|
|
4292
|
-
return
|
|
4446
|
+
return join15(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
|
|
4293
4447
|
}
|
|
4294
4448
|
function unitEnvironment(ship) {
|
|
4295
4449
|
return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
|
|
@@ -4310,7 +4464,7 @@ function xmlEscape(s) {
|
|
|
4310
4464
|
}
|
|
4311
4465
|
function planLaunchd(ship, crewHome, job) {
|
|
4312
4466
|
const label = labelFor(crewHome, job);
|
|
4313
|
-
const unitPath =
|
|
4467
|
+
const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4314
4468
|
const crewLog = ship.logFile;
|
|
4315
4469
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4316
4470
|
const persistent = job === "run";
|
|
@@ -4364,7 +4518,7 @@ ${scheduleKeys}
|
|
|
4364
4518
|
}
|
|
4365
4519
|
function planSystemd(ship, crewHome, job, ctx) {
|
|
4366
4520
|
const label = labelFor(crewHome, job);
|
|
4367
|
-
const unitDir = ctx.root ? SYSTEM_UNIT_DIR :
|
|
4521
|
+
const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join15(homedir7(), ".config", "systemd", "user");
|
|
4368
4522
|
const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
|
|
4369
4523
|
const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
|
|
4370
4524
|
` : "";
|
|
@@ -4373,7 +4527,7 @@ function planSystemd(ship, crewHome, job, ctx) {
|
|
|
4373
4527
|
lingerCommand: ["loginctl", "enable-linger", ctx.user],
|
|
4374
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`
|
|
4375
4529
|
};
|
|
4376
|
-
const servicePath =
|
|
4530
|
+
const servicePath = join15(unitDir, `${label}.service`);
|
|
4377
4531
|
const crewLog = ship.logFile;
|
|
4378
4532
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4379
4533
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4414,7 +4568,7 @@ WantedBy=${wantedBy}
|
|
|
4414
4568
|
...linger
|
|
4415
4569
|
};
|
|
4416
4570
|
}
|
|
4417
|
-
const timerPath =
|
|
4571
|
+
const timerPath = join15(unitDir, `${label}.timer`);
|
|
4418
4572
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4419
4573
|
[Unit]
|
|
4420
4574
|
Description=${description}
|
|
@@ -4489,7 +4643,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
|
|
|
4489
4643
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4490
4644
|
const label = labelFor(crewHome, job);
|
|
4491
4645
|
if (hostShip === "macos") {
|
|
4492
|
-
const unitPath =
|
|
4646
|
+
const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4493
4647
|
return {
|
|
4494
4648
|
mechanism: "launchd",
|
|
4495
4649
|
unitPaths: [unitPath],
|
|
@@ -4499,20 +4653,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4499
4653
|
};
|
|
4500
4654
|
}
|
|
4501
4655
|
if (hostShip === "linux" && hasSystemd) {
|
|
4502
|
-
const system =
|
|
4503
|
-
const unitDir = system ? SYSTEM_UNIT_DIR :
|
|
4656
|
+
const system = existsSync13(systemUnitPathFor(label));
|
|
4657
|
+
const unitDir = system ? SYSTEM_UNIT_DIR : join15(homedir7(), ".config", "systemd", "user");
|
|
4504
4658
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
4505
4659
|
if (job === "run") {
|
|
4506
4660
|
return {
|
|
4507
4661
|
mechanism: "systemd",
|
|
4508
|
-
unitPaths: [
|
|
4662
|
+
unitPaths: [join15(unitDir, `${label}.service`)],
|
|
4509
4663
|
crontabLine: null,
|
|
4510
4664
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
|
|
4511
4665
|
};
|
|
4512
4666
|
}
|
|
4513
4667
|
return {
|
|
4514
4668
|
mechanism: "systemd",
|
|
4515
|
-
unitPaths: [
|
|
4669
|
+
unitPaths: [join15(unitDir, `${label}.service`), join15(unitDir, `${label}.timer`)],
|
|
4516
4670
|
crontabLine: null,
|
|
4517
4671
|
unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
|
|
4518
4672
|
};
|
|
@@ -4530,7 +4684,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4530
4684
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4531
4685
|
const label = labelFor(crewHome, "run");
|
|
4532
4686
|
if (hostShip === "macos") {
|
|
4533
|
-
const unitPath =
|
|
4687
|
+
const unitPath = join15(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4534
4688
|
return {
|
|
4535
4689
|
mechanism: "launchd",
|
|
4536
4690
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4540,7 +4694,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
|
4540
4694
|
}
|
|
4541
4695
|
if (hostShip === "linux" && hasSystemd) {
|
|
4542
4696
|
const unit = `${label}.service`;
|
|
4543
|
-
const ctl =
|
|
4697
|
+
const ctl = existsSync13(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
|
|
4544
4698
|
return {
|
|
4545
4699
|
mechanism: "systemd",
|
|
4546
4700
|
startCommand: [...ctl, "start", unit],
|
|
@@ -4572,10 +4726,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4572
4726
|
log.emit(`would write ${path}`);
|
|
4573
4727
|
continue;
|
|
4574
4728
|
}
|
|
4575
|
-
const before =
|
|
4729
|
+
const before = existsSync13(path) ? readFileSync14(path, "utf8") : null;
|
|
4576
4730
|
if (before !== plan.unitContent[path]) changed2 = true;
|
|
4577
|
-
|
|
4578
|
-
|
|
4731
|
+
mkdirSync9(dirname7(path), { recursive: true });
|
|
4732
|
+
writeFileSync10(path, plan.unitContent[path]);
|
|
4579
4733
|
log.emit(`wrote ${path}`);
|
|
4580
4734
|
}
|
|
4581
4735
|
if (plan.crontabLine) {
|
|
@@ -4639,7 +4793,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4639
4793
|
}
|
|
4640
4794
|
}
|
|
4641
4795
|
for (const path of plan.unitPaths) {
|
|
4642
|
-
if (!
|
|
4796
|
+
if (!existsSync13(path)) continue;
|
|
4643
4797
|
if (dryRun) {
|
|
4644
4798
|
log.emit(`would remove ${path}`);
|
|
4645
4799
|
continue;
|
|
@@ -4658,7 +4812,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4658
4812
|
}
|
|
4659
4813
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4660
4814
|
function launchAgentsDir() {
|
|
4661
|
-
return
|
|
4815
|
+
return join15(homedir7(), "Library", "LaunchAgents");
|
|
4662
4816
|
}
|
|
4663
4817
|
function xmlUnescape(s) {
|
|
4664
4818
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4675,25 +4829,25 @@ function loadedCrewLabels() {
|
|
|
4675
4829
|
return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
|
|
4676
4830
|
}
|
|
4677
4831
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4678
|
-
const files =
|
|
4832
|
+
const files = existsSync13(dir) ? readdirSync3(dir) : [];
|
|
4679
4833
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4680
|
-
const plistPath =
|
|
4834
|
+
const plistPath = join15(dir, f);
|
|
4681
4835
|
let program = null;
|
|
4682
4836
|
try {
|
|
4683
|
-
program = programFromPlist(
|
|
4837
|
+
program = programFromPlist(readFileSync14(plistPath, "utf8"));
|
|
4684
4838
|
} catch {
|
|
4685
4839
|
}
|
|
4686
4840
|
return {
|
|
4687
4841
|
label: f.slice(0, -".plist".length),
|
|
4688
4842
|
plistPath,
|
|
4689
4843
|
program,
|
|
4690
|
-
programExists: program !== null &&
|
|
4844
|
+
programExists: program !== null && existsSync13(program)
|
|
4691
4845
|
};
|
|
4692
4846
|
});
|
|
4693
4847
|
const seen = new Set(units.map((u) => u.label));
|
|
4694
4848
|
for (const label of [...loaded].sort()) {
|
|
4695
4849
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4696
|
-
units.push({ label, plistPath:
|
|
4850
|
+
units.push({ label, plistPath: join15(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4697
4851
|
}
|
|
4698
4852
|
return units;
|
|
4699
4853
|
}
|
|
@@ -4706,9 +4860,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
|
|
|
4706
4860
|
log.emit(`would unload and remove ${unit.label}`);
|
|
4707
4861
|
return;
|
|
4708
4862
|
}
|
|
4709
|
-
if (
|
|
4863
|
+
if (existsSync13(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
|
|
4710
4864
|
spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
|
|
4711
|
-
if (
|
|
4865
|
+
if (existsSync13(unit.plistPath)) unlinkSync(unit.plistPath);
|
|
4712
4866
|
log.emit(`removed ${unit.label} (${unit.plistPath})`);
|
|
4713
4867
|
}
|
|
4714
4868
|
function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
|
|
@@ -4786,24 +4940,24 @@ function detectSystemd() {
|
|
|
4786
4940
|
}
|
|
4787
4941
|
|
|
4788
4942
|
// src/ship-attention.ts
|
|
4789
|
-
import { existsSync as
|
|
4943
|
+
import { existsSync as existsSync15, statfsSync } from "fs";
|
|
4790
4944
|
|
|
4791
4945
|
// src/git.ts
|
|
4792
|
-
import { execFileSync as
|
|
4946
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
4793
4947
|
import {
|
|
4794
|
-
existsSync as
|
|
4795
|
-
mkdirSync as
|
|
4948
|
+
existsSync as existsSync14,
|
|
4949
|
+
mkdirSync as mkdirSync10,
|
|
4796
4950
|
openSync as openSync2,
|
|
4797
4951
|
closeSync as closeSync2,
|
|
4798
4952
|
writeSync,
|
|
4799
4953
|
statSync as statSync6,
|
|
4800
4954
|
unlinkSync as unlinkSync2,
|
|
4801
|
-
readFileSync as
|
|
4802
|
-
writeFileSync as
|
|
4955
|
+
readFileSync as readFileSync15,
|
|
4956
|
+
writeFileSync as writeFileSync11,
|
|
4803
4957
|
readdirSync as readdirSync4,
|
|
4804
4958
|
rmSync as rmSync8
|
|
4805
4959
|
} from "fs";
|
|
4806
|
-
import { dirname as
|
|
4960
|
+
import { dirname as dirname8, basename as basename5, join as join16 } from "path";
|
|
4807
4961
|
var GitError = class extends Error {
|
|
4808
4962
|
};
|
|
4809
4963
|
function streamText(x) {
|
|
@@ -4811,7 +4965,7 @@ function streamText(x) {
|
|
|
4811
4965
|
}
|
|
4812
4966
|
function gitRaw(cwd, args) {
|
|
4813
4967
|
try {
|
|
4814
|
-
return
|
|
4968
|
+
return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
4815
4969
|
} catch (e) {
|
|
4816
4970
|
const err3 = e;
|
|
4817
4971
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4820,7 +4974,7 @@ function gitRaw(cwd, args) {
|
|
|
4820
4974
|
}
|
|
4821
4975
|
function git(cwd, args) {
|
|
4822
4976
|
try {
|
|
4823
|
-
return
|
|
4977
|
+
return execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
4824
4978
|
} catch (e) {
|
|
4825
4979
|
const err3 = e;
|
|
4826
4980
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -4991,9 +5145,9 @@ function keyCommitsInRange(cwd, key, range) {
|
|
|
4991
5145
|
}
|
|
4992
5146
|
function patchIdOf(cwd, from, to) {
|
|
4993
5147
|
try {
|
|
4994
|
-
const diff =
|
|
5148
|
+
const diff = execFileSync4("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
4995
5149
|
if (!diff) return null;
|
|
4996
|
-
const out =
|
|
5150
|
+
const out = execFileSync4("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
|
|
4997
5151
|
return out.split(" ")[0]?.trim() || null;
|
|
4998
5152
|
} catch {
|
|
4999
5153
|
return null;
|
|
@@ -5183,7 +5337,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
|
|
|
5183
5337
|
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
5184
5338
|
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
5185
5339
|
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
5186
|
-
const root =
|
|
5340
|
+
const root = dirname8(path);
|
|
5187
5341
|
if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
|
|
5188
5342
|
}
|
|
5189
5343
|
}
|
|
@@ -5201,11 +5355,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
5201
5355
|
}
|
|
5202
5356
|
for (const name of names) {
|
|
5203
5357
|
if (!name.startsWith("crew-release-")) continue;
|
|
5204
|
-
const root =
|
|
5358
|
+
const root = join16(tmp, name);
|
|
5205
5359
|
try {
|
|
5206
5360
|
const st = statSync6(root);
|
|
5207
5361
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
5208
|
-
if (
|
|
5362
|
+
if (existsSync14(join16(root, "checkout"))) continue;
|
|
5209
5363
|
rmSync8(root, { recursive: true, force: true });
|
|
5210
5364
|
removed.push(name);
|
|
5211
5365
|
} catch {
|
|
@@ -5257,16 +5411,16 @@ function originUrlOf(dir, remoteName) {
|
|
|
5257
5411
|
}
|
|
5258
5412
|
}
|
|
5259
5413
|
function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
5260
|
-
if (
|
|
5414
|
+
if (existsSync14(`${dir}/.git`)) return false;
|
|
5261
5415
|
if (!remote) {
|
|
5262
5416
|
throw new GitError(
|
|
5263
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`
|
|
5264
5418
|
);
|
|
5265
5419
|
}
|
|
5266
|
-
|
|
5420
|
+
mkdirSync10(dirname8(dir), { recursive: true });
|
|
5267
5421
|
const url = cloneUrlFor(remote, opts);
|
|
5268
5422
|
try {
|
|
5269
|
-
|
|
5423
|
+
execFileSync4("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
5270
5424
|
} catch (e) {
|
|
5271
5425
|
const err3 = e;
|
|
5272
5426
|
const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
|
|
@@ -5288,7 +5442,7 @@ function sleepSync(ms3) {
|
|
|
5288
5442
|
}
|
|
5289
5443
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
5290
5444
|
const lockPath = checkoutLockPath(dir);
|
|
5291
|
-
|
|
5445
|
+
mkdirSync10(dirname8(lockPath), { recursive: true });
|
|
5292
5446
|
const deadline = Date.now() + timeoutMs;
|
|
5293
5447
|
for (; ; ) {
|
|
5294
5448
|
try {
|
|
@@ -5325,17 +5479,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
5325
5479
|
function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
|
|
5326
5480
|
const release = acquireCheckoutLock(dir);
|
|
5327
5481
|
try {
|
|
5328
|
-
if (!
|
|
5482
|
+
if (!existsSync14(`${dir}/.git`) && githubRemote) {
|
|
5329
5483
|
const url = cloneUrlFor(githubRemote, clone);
|
|
5330
5484
|
const marker = cloneFailureMarker(dir);
|
|
5331
|
-
if (
|
|
5332
|
-
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 };
|
|
5333
5487
|
}
|
|
5334
5488
|
try {
|
|
5335
5489
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
5336
5490
|
} catch (e) {
|
|
5337
5491
|
if (!(e instanceof GitError)) throw e;
|
|
5338
|
-
|
|
5492
|
+
writeFileSync11(marker, `${url}
|
|
5339
5493
|
${e.message}`);
|
|
5340
5494
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
5341
5495
|
}
|
|
@@ -5561,7 +5715,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
|
|
|
5561
5715
|
function reconcileHeldWorktrees(state, repo, log, stillRefused) {
|
|
5562
5716
|
for (const i of state.shipAttention().previous()) {
|
|
5563
5717
|
if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
|
|
5564
|
-
const gone = !
|
|
5718
|
+
const gone = !existsSync15(i.held.path);
|
|
5565
5719
|
if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
|
|
5566
5720
|
clearShipAttention(state, i.key);
|
|
5567
5721
|
log.emit(
|
|
@@ -5679,6 +5833,18 @@ function repoFindings(o) {
|
|
|
5679
5833
|
remedy: "add the directory holding it to ship.extraPath in crew.yaml"
|
|
5680
5834
|
});
|
|
5681
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
|
+
}
|
|
5682
5848
|
return { problems, passed };
|
|
5683
5849
|
}
|
|
5684
5850
|
function attentionProblems(items, already) {
|
|
@@ -5689,13 +5855,13 @@ function attentionProblems(items, already) {
|
|
|
5689
5855
|
// src/sandbox-image.ts
|
|
5690
5856
|
import { createHash as createHash6 } from "crypto";
|
|
5691
5857
|
import { spawn as spawn4 } from "child_process";
|
|
5692
|
-
import { execFileSync as
|
|
5693
|
-
import { mkdirSync as
|
|
5694
|
-
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";
|
|
5695
5861
|
import { tmpdir as tmpdir3 } from "os";
|
|
5696
5862
|
|
|
5697
5863
|
// src/passenger-containers.ts
|
|
5698
|
-
import { execFileSync as
|
|
5864
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
5699
5865
|
import { createHash as createHash5 } from "crypto";
|
|
5700
5866
|
|
|
5701
5867
|
// src/sandbox-cache.ts
|
|
@@ -5704,32 +5870,32 @@ import { promisify as promisify2 } from "util";
|
|
|
5704
5870
|
|
|
5705
5871
|
// src/deprovision-host.ts
|
|
5706
5872
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5707
|
-
import { existsSync as
|
|
5873
|
+
import { existsSync as existsSync26, readdirSync as readdirSync11 } from "fs";
|
|
5708
5874
|
|
|
5709
5875
|
// src/interrupted.ts
|
|
5710
|
-
import { existsSync as
|
|
5711
|
-
import { join as
|
|
5876
|
+
import { existsSync as existsSync24, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
|
|
5877
|
+
import { join as join26 } from "path";
|
|
5712
5878
|
|
|
5713
5879
|
// src/agent.ts
|
|
5714
5880
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5715
|
-
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";
|
|
5716
5882
|
import { StringDecoder } from "string_decoder";
|
|
5717
5883
|
import { finished } from "stream/promises";
|
|
5718
|
-
import { join as
|
|
5884
|
+
import { join as join25, dirname as dirname11, basename as basename9 } from "path";
|
|
5719
5885
|
import { createHash as createHash4 } from "crypto";
|
|
5720
5886
|
|
|
5721
5887
|
// src/state.ts
|
|
5722
|
-
import { readFileSync as
|
|
5723
|
-
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";
|
|
5724
5890
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5725
5891
|
var State = class _State {
|
|
5726
5892
|
dir;
|
|
5727
5893
|
constructor(dir) {
|
|
5728
5894
|
this.dir = dir;
|
|
5729
|
-
|
|
5895
|
+
mkdirSync11(dir, { recursive: true });
|
|
5730
5896
|
}
|
|
5731
5897
|
path(...parts) {
|
|
5732
|
-
return
|
|
5898
|
+
return join17(this.dir, ...parts);
|
|
5733
5899
|
}
|
|
5734
5900
|
/**
|
|
5735
5901
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5740,14 +5906,14 @@ var State = class _State {
|
|
|
5740
5906
|
*/
|
|
5741
5907
|
watermark() {
|
|
5742
5908
|
try {
|
|
5743
|
-
const v =
|
|
5909
|
+
const v = readFileSync16(this.path(".poll-watermark"), "utf8").trim();
|
|
5744
5910
|
return v || EPOCH;
|
|
5745
5911
|
} catch {
|
|
5746
5912
|
return EPOCH;
|
|
5747
5913
|
}
|
|
5748
5914
|
}
|
|
5749
5915
|
setWatermark(at) {
|
|
5750
|
-
|
|
5916
|
+
writeFileSync12(this.path(".poll-watermark"), `${at}
|
|
5751
5917
|
`);
|
|
5752
5918
|
}
|
|
5753
5919
|
/**
|
|
@@ -5765,16 +5931,16 @@ var State = class _State {
|
|
|
5765
5931
|
}
|
|
5766
5932
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
5767
5933
|
isPaused() {
|
|
5768
|
-
return
|
|
5934
|
+
return existsSync16(this.path(".crew-paused"));
|
|
5769
5935
|
}
|
|
5770
5936
|
isRolePaused(role) {
|
|
5771
|
-
return
|
|
5937
|
+
return existsSync16(this.path(`.role-paused-${role}`));
|
|
5772
5938
|
}
|
|
5773
5939
|
pausedRoles(roles) {
|
|
5774
5940
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5775
5941
|
}
|
|
5776
5942
|
pause(role) {
|
|
5777
|
-
|
|
5943
|
+
writeFileSync12(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5778
5944
|
}
|
|
5779
5945
|
resume(role) {
|
|
5780
5946
|
rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -5785,10 +5951,10 @@ var State = class _State {
|
|
|
5785
5951
|
* while agents keep working.
|
|
5786
5952
|
*/
|
|
5787
5953
|
isReleasePaused() {
|
|
5788
|
-
return
|
|
5954
|
+
return existsSync16(this.path(".release-paused"));
|
|
5789
5955
|
}
|
|
5790
5956
|
setReleasePaused(paused) {
|
|
5791
|
-
if (paused)
|
|
5957
|
+
if (paused) writeFileSync12(this.path(".release-paused"), "");
|
|
5792
5958
|
else rmSync9(this.path(".release-paused"), { force: true });
|
|
5793
5959
|
}
|
|
5794
5960
|
/**
|
|
@@ -5826,7 +5992,7 @@ var State = class _State {
|
|
|
5826
5992
|
const name = _State.safe(rawName);
|
|
5827
5993
|
const file = this.path(`.${name}.lock`);
|
|
5828
5994
|
try {
|
|
5829
|
-
const existing = Number.parseInt(
|
|
5995
|
+
const existing = Number.parseInt(readFileSync16(file, "utf8").trim(), 10);
|
|
5830
5996
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5831
5997
|
try {
|
|
5832
5998
|
process.kill(existing, 0);
|
|
@@ -5836,7 +6002,7 @@ var State = class _State {
|
|
|
5836
6002
|
}
|
|
5837
6003
|
} catch {
|
|
5838
6004
|
}
|
|
5839
|
-
|
|
6005
|
+
writeFileSync12(file, `${process.pid}
|
|
5840
6006
|
`);
|
|
5841
6007
|
return {
|
|
5842
6008
|
ok: true,
|
|
@@ -5864,7 +6030,7 @@ var State = class _State {
|
|
|
5864
6030
|
if (!/^\.release-.+\.lock$/.test(n)) continue;
|
|
5865
6031
|
let pid = 0;
|
|
5866
6032
|
try {
|
|
5867
|
-
pid = Number.parseInt(
|
|
6033
|
+
pid = Number.parseInt(readFileSync16(this.path(n), "utf8").trim(), 10);
|
|
5868
6034
|
} catch {
|
|
5869
6035
|
continue;
|
|
5870
6036
|
}
|
|
@@ -5950,7 +6116,7 @@ var State = class _State {
|
|
|
5950
6116
|
ticketInFlight(recordId) {
|
|
5951
6117
|
const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
|
|
5952
6118
|
try {
|
|
5953
|
-
const pid = Number.parseInt(
|
|
6119
|
+
const pid = Number.parseInt(readFileSync16(file, "utf8").trim(), 10);
|
|
5954
6120
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
5955
6121
|
process.kill(pid, 0);
|
|
5956
6122
|
return true;
|
|
@@ -5984,7 +6150,7 @@ var State = class _State {
|
|
|
5984
6150
|
const heldReported = `.release-held-${suffix}`;
|
|
5985
6151
|
const read = (f) => {
|
|
5986
6152
|
try {
|
|
5987
|
-
return
|
|
6153
|
+
return readFileSync16(this.path(f), "utf8").trim() || null;
|
|
5988
6154
|
} catch {
|
|
5989
6155
|
return null;
|
|
5990
6156
|
}
|
|
@@ -5993,7 +6159,7 @@ var State = class _State {
|
|
|
5993
6159
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5994
6160
|
noteBlocked: () => {
|
|
5995
6161
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5996
|
-
|
|
6162
|
+
writeFileSync12(this.path(blocked), `${n}
|
|
5997
6163
|
`);
|
|
5998
6164
|
return n;
|
|
5999
6165
|
},
|
|
@@ -6010,7 +6176,7 @@ var State = class _State {
|
|
|
6010
6176
|
*/
|
|
6011
6177
|
deployFailedSha: () => read(failed),
|
|
6012
6178
|
noteDeployFailed: (sha) => {
|
|
6013
|
-
|
|
6179
|
+
writeFileSync12(this.path(failed), `${sha}
|
|
6014
6180
|
`);
|
|
6015
6181
|
},
|
|
6016
6182
|
clearDeployFailed: () => {
|
|
@@ -6024,7 +6190,7 @@ var State = class _State {
|
|
|
6024
6190
|
noteTestGateFailed: (sha) => {
|
|
6025
6191
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
6026
6192
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
6027
|
-
|
|
6193
|
+
writeFileSync12(this.path(gateFailed), `${sha} ${next}
|
|
6028
6194
|
`);
|
|
6029
6195
|
return next;
|
|
6030
6196
|
},
|
|
@@ -6034,13 +6200,13 @@ var State = class _State {
|
|
|
6034
6200
|
/** Whether the board was already told about this head's red gate. */
|
|
6035
6201
|
heldReported: () => {
|
|
6036
6202
|
try {
|
|
6037
|
-
return
|
|
6203
|
+
return readFileSync16(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
6038
6204
|
} catch {
|
|
6039
6205
|
return null;
|
|
6040
6206
|
}
|
|
6041
6207
|
},
|
|
6042
6208
|
noteHeldReported: (signature) => {
|
|
6043
|
-
|
|
6209
|
+
writeFileSync12(this.path(heldReported), `${signature}
|
|
6044
6210
|
`);
|
|
6045
6211
|
},
|
|
6046
6212
|
clearHeldReported: () => {
|
|
@@ -6048,7 +6214,7 @@ var State = class _State {
|
|
|
6048
6214
|
},
|
|
6049
6215
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
6050
6216
|
noteTestGateReported: (sha) => {
|
|
6051
|
-
|
|
6217
|
+
writeFileSync12(this.path(gateReported), `${sha}
|
|
6052
6218
|
`);
|
|
6053
6219
|
}
|
|
6054
6220
|
};
|
|
@@ -6071,7 +6237,7 @@ var State = class _State {
|
|
|
6071
6237
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
6072
6238
|
const read = (f) => {
|
|
6073
6239
|
try {
|
|
6074
|
-
return
|
|
6240
|
+
return readFileSync16(this.path(f), "utf8").trim() || null;
|
|
6075
6241
|
} catch {
|
|
6076
6242
|
return null;
|
|
6077
6243
|
}
|
|
@@ -6104,18 +6270,18 @@ var State = class _State {
|
|
|
6104
6270
|
clear2();
|
|
6105
6271
|
return;
|
|
6106
6272
|
}
|
|
6107
|
-
|
|
6273
|
+
writeFileSync12(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
6108
6274
|
`);
|
|
6109
6275
|
const prior = read(ticketFile);
|
|
6110
6276
|
if (topTicket && topTicket === prior) {
|
|
6111
|
-
|
|
6277
|
+
writeFileSync12(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
6112
6278
|
`);
|
|
6113
6279
|
} else if (topTicket) {
|
|
6114
|
-
|
|
6280
|
+
writeFileSync12(this.path(ticketFile), `${topTicket}
|
|
6115
6281
|
`);
|
|
6116
|
-
|
|
6282
|
+
writeFileSync12(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6117
6283
|
`);
|
|
6118
|
-
|
|
6284
|
+
writeFileSync12(this.path(ticketStreakFile), "1\n");
|
|
6119
6285
|
}
|
|
6120
6286
|
}
|
|
6121
6287
|
};
|
|
@@ -6136,14 +6302,14 @@ var State = class _State {
|
|
|
6136
6302
|
return {
|
|
6137
6303
|
previous: () => {
|
|
6138
6304
|
try {
|
|
6139
|
-
const parsed = JSON.parse(
|
|
6305
|
+
const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
|
|
6140
6306
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6141
6307
|
} catch {
|
|
6142
6308
|
return {};
|
|
6143
6309
|
}
|
|
6144
6310
|
},
|
|
6145
6311
|
persist: (next) => {
|
|
6146
|
-
|
|
6312
|
+
writeFileSync12(this.path(file), JSON.stringify(next));
|
|
6147
6313
|
}
|
|
6148
6314
|
};
|
|
6149
6315
|
}
|
|
@@ -6159,14 +6325,14 @@ var State = class _State {
|
|
|
6159
6325
|
return {
|
|
6160
6326
|
previous: () => {
|
|
6161
6327
|
try {
|
|
6162
|
-
const parsed = JSON.parse(
|
|
6328
|
+
const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
|
|
6163
6329
|
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
|
|
6164
6330
|
} catch {
|
|
6165
6331
|
return [];
|
|
6166
6332
|
}
|
|
6167
6333
|
},
|
|
6168
6334
|
persist: (next) => {
|
|
6169
|
-
|
|
6335
|
+
writeFileSync12(this.path(file), JSON.stringify(next));
|
|
6170
6336
|
}
|
|
6171
6337
|
};
|
|
6172
6338
|
}
|
|
@@ -6180,14 +6346,14 @@ var State = class _State {
|
|
|
6180
6346
|
return {
|
|
6181
6347
|
previous: () => {
|
|
6182
6348
|
try {
|
|
6183
|
-
const parsed = JSON.parse(
|
|
6349
|
+
const parsed = JSON.parse(readFileSync16(this.path(file), "utf8"));
|
|
6184
6350
|
return Array.isArray(parsed) ? parsed : [];
|
|
6185
6351
|
} catch {
|
|
6186
6352
|
return [];
|
|
6187
6353
|
}
|
|
6188
6354
|
},
|
|
6189
6355
|
persist: (next) => {
|
|
6190
|
-
|
|
6356
|
+
writeFileSync12(this.path(file), JSON.stringify(next));
|
|
6191
6357
|
}
|
|
6192
6358
|
};
|
|
6193
6359
|
}
|
|
@@ -6200,7 +6366,7 @@ var State = class _State {
|
|
|
6200
6366
|
const file = ".release-last-run";
|
|
6201
6367
|
return {
|
|
6202
6368
|
touch: () => {
|
|
6203
|
-
|
|
6369
|
+
writeFileSync12(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
6204
6370
|
},
|
|
6205
6371
|
at: () => {
|
|
6206
6372
|
try {
|
|
@@ -6213,7 +6379,7 @@ var State = class _State {
|
|
|
6213
6379
|
}
|
|
6214
6380
|
readInt(file) {
|
|
6215
6381
|
try {
|
|
6216
|
-
return Number.parseInt(
|
|
6382
|
+
return Number.parseInt(readFileSync16(this.path(file), "utf8").trim(), 10) || 0;
|
|
6217
6383
|
} catch {
|
|
6218
6384
|
return 0;
|
|
6219
6385
|
}
|
|
@@ -6292,6 +6458,7 @@ var HOOK_PURPOSE = [
|
|
|
6292
6458
|
["ports", "this worktree's port assignments; prints KEY=value overrides to export"],
|
|
6293
6459
|
["handoff", "leave the operator able to open what you built; put its output in your progress comment"],
|
|
6294
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"],
|
|
6295
6462
|
["map", 'prints an outline of the repository; the crew places it under "Repository map" below'],
|
|
6296
6463
|
["test", "run the suite"],
|
|
6297
6464
|
["build", "build it"],
|
|
@@ -6766,8 +6933,8 @@ var PERISCOPE_COLUMNS = [
|
|
|
6766
6933
|
];
|
|
6767
6934
|
|
|
6768
6935
|
// src/agent-log.ts
|
|
6769
|
-
import { existsSync as
|
|
6770
|
-
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";
|
|
6771
6938
|
|
|
6772
6939
|
// src/pending-backoff.ts
|
|
6773
6940
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -6939,20 +7106,20 @@ async function addRunToTicket(target, t, recordId, run3) {
|
|
|
6939
7106
|
};
|
|
6940
7107
|
}
|
|
6941
7108
|
}
|
|
6942
|
-
var queueDir = (stateDir) =>
|
|
7109
|
+
var queueDir = (stateDir) => join18(stateDir, "pending-agent-logs");
|
|
6943
7110
|
function queueAgentRun(stateDir, name, q2) {
|
|
6944
|
-
|
|
6945
|
-
|
|
7111
|
+
mkdirSync12(queueDir(stateDir), { recursive: true });
|
|
7112
|
+
writeFileSync13(join18(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6946
7113
|
}
|
|
6947
7114
|
async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
6948
7115
|
const dir = queueDir(stateDir);
|
|
6949
|
-
if (!
|
|
7116
|
+
if (!existsSync17(dir)) return 0;
|
|
6950
7117
|
let sent = 0;
|
|
6951
7118
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
6952
|
-
const path =
|
|
7119
|
+
const path = join18(dir, f);
|
|
6953
7120
|
let q2;
|
|
6954
7121
|
try {
|
|
6955
|
-
q2 = JSON.parse(
|
|
7122
|
+
q2 = JSON.parse(readFileSync17(path, "utf8"));
|
|
6956
7123
|
} catch {
|
|
6957
7124
|
continue;
|
|
6958
7125
|
}
|
|
@@ -6981,7 +7148,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6981
7148
|
continue;
|
|
6982
7149
|
}
|
|
6983
7150
|
const attempts = q2.attempts + 1;
|
|
6984
|
-
|
|
7151
|
+
writeFileSync13(path, JSON.stringify({
|
|
6985
7152
|
...q2,
|
|
6986
7153
|
attempts,
|
|
6987
7154
|
warned: true,
|
|
@@ -6992,20 +7159,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
|
|
|
6992
7159
|
}
|
|
6993
7160
|
return sent;
|
|
6994
7161
|
}
|
|
6995
|
-
var countersDir = (stateDir) =>
|
|
7162
|
+
var countersDir = (stateDir) => join18(stateDir, "pending-ticket-counters");
|
|
6996
7163
|
function queueTicketCounters(stateDir, name, q2) {
|
|
6997
|
-
|
|
6998
|
-
|
|
7164
|
+
mkdirSync12(countersDir(stateDir), { recursive: true });
|
|
7165
|
+
writeFileSync13(join18(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
|
|
6999
7166
|
}
|
|
7000
7167
|
async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
7001
7168
|
const dir = countersDir(stateDir);
|
|
7002
|
-
if (!
|
|
7169
|
+
if (!existsSync17(dir)) return 0;
|
|
7003
7170
|
let sent = 0;
|
|
7004
7171
|
for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7005
|
-
const path =
|
|
7172
|
+
const path = join18(dir, f);
|
|
7006
7173
|
let q2;
|
|
7007
7174
|
try {
|
|
7008
|
-
q2 = JSON.parse(
|
|
7175
|
+
q2 = JSON.parse(readFileSync17(path, "utf8"));
|
|
7009
7176
|
} catch {
|
|
7010
7177
|
continue;
|
|
7011
7178
|
}
|
|
@@ -7024,7 +7191,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
|
|
|
7024
7191
|
continue;
|
|
7025
7192
|
}
|
|
7026
7193
|
const attempts = q2.attempts + 1;
|
|
7027
|
-
|
|
7194
|
+
writeFileSync13(path, JSON.stringify({
|
|
7028
7195
|
...q2,
|
|
7029
7196
|
attempts,
|
|
7030
7197
|
warned: true,
|
|
@@ -7227,8 +7394,8 @@ var HandoffTranscriptTracker = class {
|
|
|
7227
7394
|
};
|
|
7228
7395
|
|
|
7229
7396
|
// src/pending-handoff.ts
|
|
7230
|
-
import { existsSync as
|
|
7231
|
-
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";
|
|
7232
7399
|
|
|
7233
7400
|
// src/ticket-branch.ts
|
|
7234
7401
|
import { basename as basename7 } from "path";
|
|
@@ -8394,16 +8561,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
8394
8561
|
}
|
|
8395
8562
|
|
|
8396
8563
|
// src/run-worktree.ts
|
|
8397
|
-
import { existsSync as
|
|
8398
|
-
import { dirname as
|
|
8564
|
+
import { existsSync as existsSync19 } from "fs";
|
|
8565
|
+
import { dirname as dirname9, join as join20 } from "path";
|
|
8399
8566
|
|
|
8400
8567
|
// src/ports.ts
|
|
8401
|
-
import { execFileSync as
|
|
8402
|
-
import { existsSync as
|
|
8403
|
-
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";
|
|
8404
8571
|
var run = (cmd, args) => {
|
|
8405
8572
|
try {
|
|
8406
|
-
return
|
|
8573
|
+
return execFileSync5(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
8407
8574
|
} catch {
|
|
8408
8575
|
return "";
|
|
8409
8576
|
}
|
|
@@ -8445,7 +8612,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
8445
8612
|
}
|
|
8446
8613
|
function worktreeExists(parent, prefix, n) {
|
|
8447
8614
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
8448
|
-
if (
|
|
8615
|
+
if (existsSync18(join19(parent, name, ".git"))) return true;
|
|
8449
8616
|
}
|
|
8450
8617
|
return false;
|
|
8451
8618
|
}
|
|
@@ -8528,7 +8695,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8528
8695
|
const existing = worktreeForNumber(dir, num);
|
|
8529
8696
|
const located = locateBranchForTicket(dir, cfg, t, role);
|
|
8530
8697
|
const baseRef = hasRemote ? `${remote}/${base}` : base;
|
|
8531
|
-
if (existing && !existing.prunable &&
|
|
8698
|
+
if (existing && !existing.prunable && existsSync19(existing.path)) {
|
|
8532
8699
|
const branch = existing.branch;
|
|
8533
8700
|
if (status(existing.path).length > 0) {
|
|
8534
8701
|
return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
|
|
@@ -8544,7 +8711,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
8544
8711
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
8545
8712
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
8546
8713
|
}
|
|
8547
|
-
const path =
|
|
8714
|
+
const path = join20(dirname9(dir), worktreeForTicket(dir, cfg, t, role));
|
|
8548
8715
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
8549
8716
|
return { action: "cut-remote", path, branch: located.branch };
|
|
8550
8717
|
}
|
|
@@ -8598,7 +8765,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8598
8765
|
if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
|
|
8599
8766
|
if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
|
|
8600
8767
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8601
|
-
if (wt &&
|
|
8768
|
+
if (wt && existsSync19(wt.path)) {
|
|
8602
8769
|
if (status(wt.path).length > 0) return false;
|
|
8603
8770
|
if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
|
|
8604
8771
|
}
|
|
@@ -8606,7 +8773,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
|
|
|
8606
8773
|
}
|
|
8607
8774
|
async function finishRunWorktree(dir, cfg, branch) {
|
|
8608
8775
|
const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
|
|
8609
|
-
if (!wt || !
|
|
8776
|
+
if (!wt || !existsSync19(wt.path)) return { kind: "none" };
|
|
8610
8777
|
if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
|
|
8611
8778
|
if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
|
|
8612
8779
|
if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
|
|
@@ -8618,7 +8785,7 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
8618
8785
|
if (pids.length) await killGently(pids);
|
|
8619
8786
|
}
|
|
8620
8787
|
gitOk(dir, ["worktree", "remove", wt.path]);
|
|
8621
|
-
return
|
|
8788
|
+
return existsSync19(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
|
|
8622
8789
|
}
|
|
8623
8790
|
|
|
8624
8791
|
// src/handoff-guard.ts
|
|
@@ -9001,20 +9168,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
|
|
|
9001
9168
|
|
|
9002
9169
|
// src/pending-handoff.ts
|
|
9003
9170
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
9004
|
-
var pendingDirFor = (stateDir) =>
|
|
9005
|
-
var stateDirOfStream = (streamPath) =>
|
|
9171
|
+
var pendingDirFor = (stateDir) => join21(stateDir, "pending-handoffs");
|
|
9172
|
+
var stateDirOfStream = (streamPath) => join21(streamPath, "..", "..");
|
|
9006
9173
|
function writePendingHandoff(stateDir, rec) {
|
|
9007
9174
|
const dir = pendingDirFor(stateDir);
|
|
9008
|
-
|
|
9009
|
-
|
|
9175
|
+
mkdirSync13(dir, { recursive: true });
|
|
9176
|
+
writeFileSync14(join21(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
9010
9177
|
}
|
|
9011
9178
|
function listPendingHandoffs(stateDir, route2) {
|
|
9012
9179
|
const dir = pendingDirFor(stateDir);
|
|
9013
|
-
if (!
|
|
9180
|
+
if (!existsSync20(dir)) return [];
|
|
9014
9181
|
const out = [];
|
|
9015
9182
|
for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
9016
9183
|
try {
|
|
9017
|
-
const rec = JSON.parse(
|
|
9184
|
+
const rec = JSON.parse(readFileSync18(join21(dir, f), "utf8"));
|
|
9018
9185
|
if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
|
|
9019
9186
|
} catch {
|
|
9020
9187
|
}
|
|
@@ -9022,7 +9189,7 @@ function listPendingHandoffs(stateDir, route2) {
|
|
|
9022
9189
|
return out;
|
|
9023
9190
|
}
|
|
9024
9191
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
9025
|
-
rmSync11(
|
|
9192
|
+
rmSync11(join21(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
9026
9193
|
}
|
|
9027
9194
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
9028
9195
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -9132,8 +9299,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
|
|
|
9132
9299
|
}
|
|
9133
9300
|
|
|
9134
9301
|
// src/skills.ts
|
|
9135
|
-
import { readFileSync as
|
|
9136
|
-
import { join as
|
|
9302
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync8, existsSync as existsSync22 } from "fs";
|
|
9303
|
+
import { join as join23 } from "path";
|
|
9137
9304
|
import { parse as parseYaml } from "yaml";
|
|
9138
9305
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
9139
9306
|
|
|
@@ -9149,8 +9316,8 @@ ${additions}`;
|
|
|
9149
9316
|
}
|
|
9150
9317
|
|
|
9151
9318
|
// src/agents.ts
|
|
9152
|
-
import { readFileSync as
|
|
9153
|
-
import { join as
|
|
9319
|
+
import { readFileSync as readFileSync19, existsSync as existsSync21 } from "fs";
|
|
9320
|
+
import { join as join22 } from "path";
|
|
9154
9321
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
9155
9322
|
var PERSONA_NAME = {
|
|
9156
9323
|
dev: "Developer",
|
|
@@ -9164,12 +9331,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
9164
9331
|
var AgentsSyncError = class extends Error {
|
|
9165
9332
|
};
|
|
9166
9333
|
function personaDefaultPrompt(promptsDir, role) {
|
|
9167
|
-
const briefPath =
|
|
9168
|
-
if (!
|
|
9169
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
9170
|
-
const commonPath =
|
|
9171
|
-
if (!
|
|
9172
|
-
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")}`;
|
|
9173
9340
|
}
|
|
9174
9341
|
async function resolveAgentId(route2, role, opts) {
|
|
9175
9342
|
const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -9616,7 +9783,7 @@ function describeCrewLink(o) {
|
|
|
9616
9783
|
var SkillSyncError = class extends Error {
|
|
9617
9784
|
};
|
|
9618
9785
|
function parseSkillFile(path) {
|
|
9619
|
-
const text2 =
|
|
9786
|
+
const text2 = readFileSync20(path, "utf8");
|
|
9620
9787
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
|
|
9621
9788
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
9622
9789
|
const frontmatter = match[1];
|
|
@@ -9629,9 +9796,9 @@ function parseSkillFile(path) {
|
|
|
9629
9796
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
9630
9797
|
}
|
|
9631
9798
|
function listLocalSkills(promptsDir) {
|
|
9632
|
-
const dir =
|
|
9633
|
-
if (!
|
|
9634
|
-
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)));
|
|
9635
9802
|
}
|
|
9636
9803
|
async function syncSkills(route2, opts) {
|
|
9637
9804
|
if (!route2.resolved) {
|
|
@@ -9815,10 +9982,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
|
|
|
9815
9982
|
}
|
|
9816
9983
|
|
|
9817
9984
|
// src/runner.ts
|
|
9818
|
-
import { spawn as spawn2, execFile, execFileSync as
|
|
9819
|
-
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";
|
|
9820
9987
|
import { tmpdir as tmpdir2 } from "os";
|
|
9821
|
-
import { basename as basename8, dirname as
|
|
9988
|
+
import { basename as basename8, dirname as dirname10, join as join24 } from "path";
|
|
9822
9989
|
import { promisify } from "util";
|
|
9823
9990
|
|
|
9824
9991
|
// src/run-limit.ts
|
|
@@ -9947,8 +10114,8 @@ var SandboxRunner = class {
|
|
|
9947
10114
|
throw new Error(`cannot generate the sandbox policy: ${e.message}`);
|
|
9948
10115
|
}
|
|
9949
10116
|
policyFile = policyPathFor(plan.streamPath);
|
|
9950
|
-
|
|
9951
|
-
|
|
10117
|
+
mkdirSync14(dirname10(policyFile), { recursive: true });
|
|
10118
|
+
writeFileSync15(policyFile, generated.yaml, { mode: 384 });
|
|
9952
10119
|
}
|
|
9953
10120
|
const create2 = ["sandbox", "create", "--name", name, "--detach"];
|
|
9954
10121
|
if (policyFile) create2.push("--policy", policyFile);
|
|
@@ -9991,10 +10158,10 @@ var SandboxRunner = class {
|
|
|
9991
10158
|
const harness = cfg.agentBin ?? basename8(plan.bin);
|
|
9992
10159
|
const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
|
|
9993
10160
|
if (viaUpload) {
|
|
9994
|
-
const tmp = mkdtempSync(
|
|
10161
|
+
const tmp = mkdtempSync(join24(tmpdir2(), "crew-prompt-"));
|
|
9995
10162
|
try {
|
|
9996
|
-
const file =
|
|
9997
|
-
|
|
10163
|
+
const file = join24(tmp, "prompt.txt");
|
|
10164
|
+
writeFileSync15(file, plan.prompt, { mode: 384 });
|
|
9998
10165
|
await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
|
|
9999
10166
|
} finally {
|
|
10000
10167
|
rmSync12(tmp, { recursive: true, force: true });
|
|
@@ -10060,7 +10227,7 @@ function describeExecError(e) {
|
|
|
10060
10227
|
}
|
|
10061
10228
|
function gitOrigin(cwd) {
|
|
10062
10229
|
try {
|
|
10063
|
-
return
|
|
10230
|
+
return execFileSync6("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
10064
10231
|
} catch {
|
|
10065
10232
|
return null;
|
|
10066
10233
|
}
|
|
@@ -10071,7 +10238,7 @@ function runnerFor(kind, sandbox) {
|
|
|
10071
10238
|
function runnerOfSidecar(sc) {
|
|
10072
10239
|
return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
|
|
10073
10240
|
}
|
|
10074
|
-
function checkSandboxRunner(cfg = {}, run3 = (b, a) =>
|
|
10241
|
+
function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync6(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
|
|
10075
10242
|
const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
|
|
10076
10243
|
let version;
|
|
10077
10244
|
try {
|
|
@@ -10138,7 +10305,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
|
|
|
10138
10305
|
function streamShowsAllowanceRefusal(streamPath) {
|
|
10139
10306
|
let raw;
|
|
10140
10307
|
try {
|
|
10141
|
-
raw =
|
|
10308
|
+
raw = readFileSync21(streamPath, "utf8");
|
|
10142
10309
|
} catch {
|
|
10143
10310
|
return false;
|
|
10144
10311
|
}
|
|
@@ -10161,26 +10328,26 @@ function streamShowsAllowanceRefusal(streamPath) {
|
|
|
10161
10328
|
}
|
|
10162
10329
|
return false;
|
|
10163
10330
|
}
|
|
10164
|
-
var digestPathFor = (stateDir, routeLabel, role, runId) =>
|
|
10331
|
+
var digestPathFor = (stateDir, routeLabel, role, runId) => join25(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
10165
10332
|
function planAgentRun(o) {
|
|
10166
|
-
const briefPath =
|
|
10167
|
-
if (!
|
|
10333
|
+
const briefPath = join25(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
10334
|
+
if (!existsSync23(briefPath)) {
|
|
10168
10335
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
10169
10336
|
}
|
|
10170
|
-
const commonPath =
|
|
10171
|
-
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);
|
|
10172
10339
|
const now = o.now ?? (() => Date.now());
|
|
10173
10340
|
const routeLabel = routeSlug(o.route.route);
|
|
10174
10341
|
const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
|
|
10175
|
-
const digestPath2 = ownDigest &&
|
|
10342
|
+
const digestPath2 = ownDigest && existsSync23(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
|
|
10176
10343
|
let digest;
|
|
10177
10344
|
let ageSeconds;
|
|
10178
|
-
if (
|
|
10345
|
+
if (existsSync23(digestPath2)) {
|
|
10179
10346
|
ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
|
|
10180
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
10347
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync21(digestPath2, "utf8");
|
|
10181
10348
|
}
|
|
10182
|
-
const common = usesCommon ?
|
|
10183
|
-
const brief =
|
|
10349
|
+
const common = usesCommon ? readFileSync21(commonPath, "utf8") : "";
|
|
10350
|
+
const brief = readFileSync21(briefPath, "utf8");
|
|
10184
10351
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
10185
10352
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
10186
10353
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -10203,7 +10370,7 @@ function planAgentRun(o) {
|
|
|
10203
10370
|
digest
|
|
10204
10371
|
});
|
|
10205
10372
|
const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
|
|
10206
|
-
const streamsDir =
|
|
10373
|
+
const streamsDir = join25(o.stateDir, "streams");
|
|
10207
10374
|
const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
|
|
10208
10375
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
10209
10376
|
const { model } = seat;
|
|
@@ -10293,8 +10460,8 @@ function planAgentRun(o) {
|
|
|
10293
10460
|
digestAttached: digest !== void 0,
|
|
10294
10461
|
digestAgeSeconds: ageSeconds,
|
|
10295
10462
|
model,
|
|
10296
|
-
streamPath:
|
|
10297
|
-
eventsPath:
|
|
10463
|
+
streamPath: join25(streamsDir, `${base}.jsonl`),
|
|
10464
|
+
eventsPath: join25(streamsDir, `${base}.events.jsonl`),
|
|
10298
10465
|
ticket: o.ticket,
|
|
10299
10466
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
10300
10467
|
repo: o.repo ?? null,
|
|
@@ -10325,7 +10492,7 @@ function describePlan(p) {
|
|
|
10325
10492
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
10326
10493
|
function readRunSidecar(streamPath) {
|
|
10327
10494
|
try {
|
|
10328
|
-
const sc = JSON.parse(
|
|
10495
|
+
const sc = JSON.parse(readFileSync21(pidPathFor(streamPath), "utf8"));
|
|
10329
10496
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
10330
10497
|
} catch {
|
|
10331
10498
|
return null;
|
|
@@ -10372,7 +10539,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
|
|
|
10372
10539
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
10373
10540
|
function openSink(path) {
|
|
10374
10541
|
try {
|
|
10375
|
-
|
|
10542
|
+
mkdirSync15(dirname11(path), { recursive: true });
|
|
10376
10543
|
return createWriteStream(path, { flags: "a" });
|
|
10377
10544
|
} catch {
|
|
10378
10545
|
return void 0;
|
|
@@ -10554,7 +10721,7 @@ var RunObserver = class {
|
|
|
10554
10721
|
};
|
|
10555
10722
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
10556
10723
|
try {
|
|
10557
|
-
|
|
10724
|
+
writeFileSync16(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
10558
10725
|
route: id.route,
|
|
10559
10726
|
role: id.role,
|
|
10560
10727
|
hint: id.ticket,
|
|
@@ -10714,7 +10881,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10714
10881
|
const id = identityOf(plan, adapter.harness);
|
|
10715
10882
|
let streamFd;
|
|
10716
10883
|
try {
|
|
10717
|
-
|
|
10884
|
+
mkdirSync15(dirname11(plan.streamPath), { recursive: true });
|
|
10718
10885
|
streamFd = openSync3(plan.streamPath, "a");
|
|
10719
10886
|
} catch (e) {
|
|
10720
10887
|
void endSink(eventsSink);
|
|
@@ -10772,7 +10939,7 @@ function spawnAgent(plan, emit, hintMoved) {
|
|
|
10772
10939
|
promptSha: plan.promptSha
|
|
10773
10940
|
};
|
|
10774
10941
|
try {
|
|
10775
|
-
|
|
10942
|
+
writeFileSync16(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
10776
10943
|
} catch {
|
|
10777
10944
|
}
|
|
10778
10945
|
}
|
|
@@ -10886,21 +11053,21 @@ var pidAlive = (pid) => {
|
|
|
10886
11053
|
};
|
|
10887
11054
|
function runFinished(streamPath) {
|
|
10888
11055
|
const path = `${streamPath}.meta.json`;
|
|
10889
|
-
if (!
|
|
11056
|
+
if (!existsSync24(path)) return false;
|
|
10890
11057
|
try {
|
|
10891
|
-
return JSON.parse(
|
|
11058
|
+
return JSON.parse(readFileSync22(path, "utf8")).running !== true;
|
|
10892
11059
|
} catch {
|
|
10893
11060
|
return true;
|
|
10894
11061
|
}
|
|
10895
11062
|
}
|
|
10896
11063
|
function orphanedStreams(o) {
|
|
10897
|
-
const dir =
|
|
10898
|
-
if (!
|
|
11064
|
+
const dir = join26(o.stateDir, "streams");
|
|
11065
|
+
if (!existsSync24(dir)) return [];
|
|
10899
11066
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
10900
11067
|
const out = [];
|
|
10901
11068
|
for (const name of readdirSync9(dir)) {
|
|
10902
11069
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10903
|
-
const streamPath =
|
|
11070
|
+
const streamPath = join26(dir, name);
|
|
10904
11071
|
if (runFinished(streamPath)) continue;
|
|
10905
11072
|
const sidecar = readRunSidecar(streamPath);
|
|
10906
11073
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -10953,15 +11120,15 @@ async function reconcileRuns(o) {
|
|
|
10953
11120
|
}
|
|
10954
11121
|
|
|
10955
11122
|
// src/live-runs.ts
|
|
10956
|
-
import { existsSync as
|
|
10957
|
-
import { join as
|
|
11123
|
+
import { existsSync as existsSync25, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
|
|
11124
|
+
import { join as join27 } from "path";
|
|
10958
11125
|
function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
10959
|
-
const dir =
|
|
10960
|
-
if (!
|
|
11126
|
+
const dir = join27(stateDir, "streams");
|
|
11127
|
+
if (!existsSync25(dir)) return [];
|
|
10961
11128
|
const out = [];
|
|
10962
11129
|
for (const name of readdirSync10(dir)) {
|
|
10963
11130
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10964
|
-
const streamPath =
|
|
11131
|
+
const streamPath = join27(dir, name);
|
|
10965
11132
|
if (runFinished(streamPath)) continue;
|
|
10966
11133
|
const sidecar = readRunSidecar(streamPath);
|
|
10967
11134
|
if (!sidecar || !isAlive(sidecar)) continue;
|
|
@@ -10971,12 +11138,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
|
|
|
10971
11138
|
}
|
|
10972
11139
|
function runActivity(stateDir, isAlive = runIsAlive) {
|
|
10973
11140
|
const activeRuns = liveRuns(stateDir, isAlive).length;
|
|
10974
|
-
const dir =
|
|
11141
|
+
const dir = join27(stateDir, "streams");
|
|
10975
11142
|
let newest = 0;
|
|
10976
|
-
if (
|
|
11143
|
+
if (existsSync25(dir)) {
|
|
10977
11144
|
for (const name of readdirSync10(dir)) {
|
|
10978
11145
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
10979
|
-
const streamPath =
|
|
11146
|
+
const streamPath = join27(dir, name);
|
|
10980
11147
|
if (!runFinished(streamPath)) continue;
|
|
10981
11148
|
try {
|
|
10982
11149
|
newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
|
|
@@ -11347,7 +11514,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11347
11514
|
if (host === "windows") return [];
|
|
11348
11515
|
const ctx = installContext();
|
|
11349
11516
|
const systemd = detectSystemd();
|
|
11350
|
-
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));
|
|
11351
11518
|
};
|
|
11352
11519
|
return {
|
|
11353
11520
|
liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
|
|
@@ -11408,7 +11575,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
|
|
|
11408
11575
|
},
|
|
11409
11576
|
remaining() {
|
|
11410
11577
|
const left = [];
|
|
11411
|
-
if (
|
|
11578
|
+
if (existsSync26(cfg.configFile)) left.push(`config ${cfg.configFile}`);
|
|
11412
11579
|
for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
|
|
11413
11580
|
try {
|
|
11414
11581
|
for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
|
|
@@ -11683,7 +11850,7 @@ function makeCachePass(o) {
|
|
|
11683
11850
|
// src/passenger-containers.ts
|
|
11684
11851
|
var realExec2 = (cmd, args, opts) => {
|
|
11685
11852
|
try {
|
|
11686
|
-
return
|
|
11853
|
+
return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
|
|
11687
11854
|
} catch (e) {
|
|
11688
11855
|
const err3 = e;
|
|
11689
11856
|
const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
|
|
@@ -11963,7 +12130,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
11963
12130
|
var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
11964
12131
|
function readWarmFiles(dir, ref, patterns) {
|
|
11965
12132
|
if (patterns.length === 0) return {};
|
|
11966
|
-
const git2 = (args) =>
|
|
12133
|
+
const git2 = (args) => execFileSync8("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
|
|
11967
12134
|
const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
|
|
11968
12135
|
const res = patterns.map(globToRegExp);
|
|
11969
12136
|
const out = {};
|
|
@@ -12016,19 +12183,19 @@ function planSandbox(repo, decl, warm = {}) {
|
|
|
12016
12183
|
const layer = repoLayer(repo, decl, crew, warm);
|
|
12017
12184
|
return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
|
|
12018
12185
|
}
|
|
12019
|
-
var sandboxStatePath = (stateDir) =>
|
|
12186
|
+
var sandboxStatePath = (stateDir) => join28(stateDir, "sandbox-images.json");
|
|
12020
12187
|
function readSandboxState(stateDir) {
|
|
12021
12188
|
try {
|
|
12022
|
-
const v = JSON.parse(
|
|
12189
|
+
const v = JSON.parse(readFileSync23(sandboxStatePath(stateDir), "utf8"));
|
|
12023
12190
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
12024
12191
|
} catch {
|
|
12025
12192
|
return {};
|
|
12026
12193
|
}
|
|
12027
12194
|
}
|
|
12028
12195
|
function writeSandboxState(stateDir, state) {
|
|
12029
|
-
|
|
12196
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
12030
12197
|
const file = sandboxStatePath(stateDir);
|
|
12031
|
-
|
|
12198
|
+
writeFileSync17(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
|
|
12032
12199
|
`);
|
|
12033
12200
|
renameSync3(`${file}.tmp`, file);
|
|
12034
12201
|
}
|
|
@@ -12048,7 +12215,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
|
|
|
12048
12215
|
child.stdout.on("data", (d) => chunks.push(d));
|
|
12049
12216
|
child.stderr.on("data", (d) => chunks.push(d));
|
|
12050
12217
|
const done = (code, extra = "") => {
|
|
12051
|
-
|
|
12218
|
+
writeFileSync17(logPath, `$ ${engine} ${args.join(" ")}
|
|
12052
12219
|
${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
|
|
12053
12220
|
resolve8(code);
|
|
12054
12221
|
};
|
|
@@ -12070,7 +12237,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
|
|
|
12070
12237
|
} catch {
|
|
12071
12238
|
}
|
|
12072
12239
|
};
|
|
12073
|
-
var sandboxLogDir = (stateDir) =>
|
|
12240
|
+
var sandboxLogDir = (stateDir) => join28(stateDir, "sandbox-logs");
|
|
12074
12241
|
async function buildSandboxImage(plan, deps) {
|
|
12075
12242
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
12076
12243
|
const state = readSandboxState(deps.stateDir);
|
|
@@ -12079,20 +12246,20 @@ async function buildSandboxImage(plan, deps) {
|
|
|
12079
12246
|
return { kind: "reused", tag: plan.tag };
|
|
12080
12247
|
}
|
|
12081
12248
|
const logDir = sandboxLogDir(deps.stateDir);
|
|
12082
|
-
|
|
12083
|
-
const logPath =
|
|
12249
|
+
mkdirSync16(logDir, { recursive: true });
|
|
12250
|
+
const logPath = join28(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
12084
12251
|
const builtLayers = [];
|
|
12085
12252
|
for (const layer of plan.layers) {
|
|
12086
12253
|
if (deps.hasImage(layer.tag)) continue;
|
|
12087
|
-
const ctx =
|
|
12088
|
-
|
|
12254
|
+
const ctx = join28(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
|
|
12255
|
+
mkdirSync16(ctx, { recursive: true });
|
|
12089
12256
|
try {
|
|
12090
|
-
|
|
12257
|
+
writeFileSync17(join28(ctx, "Dockerfile"), layer.dockerfile);
|
|
12091
12258
|
for (const [name, content] of Object.entries(layer.files)) {
|
|
12092
|
-
|
|
12093
|
-
|
|
12259
|
+
mkdirSync16(dirname12(join28(ctx, name)), { recursive: true });
|
|
12260
|
+
writeFileSync17(join28(ctx, name), content);
|
|
12094
12261
|
}
|
|
12095
|
-
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);
|
|
12096
12263
|
if (code !== 0) {
|
|
12097
12264
|
state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
|
|
12098
12265
|
writeSandboxState(deps.stateDir, state);
|
|
@@ -12145,10 +12312,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
|
|
|
12145
12312
|
}
|
|
12146
12313
|
|
|
12147
12314
|
// src/doctor-checks.ts
|
|
12148
|
-
import { execFileSync as
|
|
12315
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
12149
12316
|
var PROBE_BRANCH = "crew-probe-branch";
|
|
12150
12317
|
var realGit = (cwd, args, env) => {
|
|
12151
|
-
|
|
12318
|
+
execFileSync9("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
12152
12319
|
};
|
|
12153
12320
|
function isSshRemote(url) {
|
|
12154
12321
|
return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
|
|
@@ -12281,9 +12448,9 @@ function managedRuntimeWarnings(kind, ship) {
|
|
|
12281
12448
|
|
|
12282
12449
|
// src/selftest.ts
|
|
12283
12450
|
import { spawn as spawn5 } from "child_process";
|
|
12284
|
-
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";
|
|
12285
12452
|
import { tmpdir as tmpdir4 } from "os";
|
|
12286
|
-
import { join as
|
|
12453
|
+
import { join as join29 } from "path";
|
|
12287
12454
|
var SELFTEST_TIMEOUT_MS = 12e4;
|
|
12288
12455
|
var KEY_VAR = "CREW_API_KEY";
|
|
12289
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: ";
|
|
@@ -12444,7 +12611,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
12444
12611
|
...adapter.runEnv(spec),
|
|
12445
12612
|
[KEY_VAR]: i.apiKey
|
|
12446
12613
|
};
|
|
12447
|
-
const cwd = mkdtempSync2(
|
|
12614
|
+
const cwd = mkdtempSync2(join29(tmpdir4(), "crew-selftest-"));
|
|
12448
12615
|
try {
|
|
12449
12616
|
const run3 = await deps.run({
|
|
12450
12617
|
bin: invocation.bin,
|
|
@@ -12477,11 +12644,11 @@ function formatResult(r) {
|
|
|
12477
12644
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
12478
12645
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
12479
12646
|
}
|
|
12480
|
-
var fileFor2 = (stateDir) =>
|
|
12647
|
+
var fileFor2 = (stateDir) => join29(stateDir, "selftest.json");
|
|
12481
12648
|
function readSelftest(stateDir) {
|
|
12482
12649
|
try {
|
|
12483
12650
|
const p = fileFor2(stateDir);
|
|
12484
|
-
return
|
|
12651
|
+
return existsSync27(p) ? JSON.parse(readFileSync24(p, "utf8")) : {};
|
|
12485
12652
|
} catch {
|
|
12486
12653
|
return {};
|
|
12487
12654
|
}
|
|
@@ -12489,8 +12656,8 @@ function readSelftest(stateDir) {
|
|
|
12489
12656
|
function recordSelftest(stateDir, results) {
|
|
12490
12657
|
const all = readSelftest(stateDir);
|
|
12491
12658
|
for (const r of results) all[r.toolchain] = r;
|
|
12492
|
-
|
|
12493
|
-
|
|
12659
|
+
mkdirSync17(stateDir, { recursive: true });
|
|
12660
|
+
writeFileSync18(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
12494
12661
|
`);
|
|
12495
12662
|
}
|
|
12496
12663
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -12502,7 +12669,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
12502
12669
|
}
|
|
12503
12670
|
|
|
12504
12671
|
// src/cli.ts
|
|
12505
|
-
import { resolve as resolve7, dirname as
|
|
12672
|
+
import { resolve as resolve7, dirname as dirname30, join as join51, basename as basename16 } from "path";
|
|
12506
12673
|
import { fileURLToPath } from "url";
|
|
12507
12674
|
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
12508
12675
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
@@ -12699,14 +12866,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
12699
12866
|
}
|
|
12700
12867
|
|
|
12701
12868
|
// src/connect-resume.ts
|
|
12702
|
-
import { mkdirSync as
|
|
12703
|
-
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";
|
|
12704
12871
|
function lastSessionFile(stateDir) {
|
|
12705
|
-
return
|
|
12872
|
+
return join30(stateDir, "connect-session.json");
|
|
12706
12873
|
}
|
|
12707
12874
|
function readPointers(stateDir) {
|
|
12708
12875
|
try {
|
|
12709
|
-
const parsed = JSON.parse(
|
|
12876
|
+
const parsed = JSON.parse(readFileSync25(lastSessionFile(stateDir), "utf8"));
|
|
12710
12877
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
12711
12878
|
const out = {};
|
|
12712
12879
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -12720,8 +12887,8 @@ function readPointers(stateDir) {
|
|
|
12720
12887
|
}
|
|
12721
12888
|
function writePointers(stateDir, pointers) {
|
|
12722
12889
|
const file = lastSessionFile(stateDir);
|
|
12723
|
-
|
|
12724
|
-
|
|
12890
|
+
mkdirSync18(dirname13(file), { recursive: true });
|
|
12891
|
+
writeFileSync19(file, `${JSON.stringify(pointers, null, 2)}
|
|
12725
12892
|
`, { mode: 384 });
|
|
12726
12893
|
}
|
|
12727
12894
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -12806,15 +12973,15 @@ async function signOutSessions(o) {
|
|
|
12806
12973
|
}
|
|
12807
12974
|
|
|
12808
12975
|
// src/reset.ts
|
|
12809
|
-
import { existsSync as
|
|
12810
|
-
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";
|
|
12811
12978
|
import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
|
|
12812
12979
|
function clonedCheckoutsFile(stateDir) {
|
|
12813
|
-
return
|
|
12980
|
+
return join31(stateDir, "cloned-checkouts.json");
|
|
12814
12981
|
}
|
|
12815
12982
|
function readClonedCheckouts(stateDir) {
|
|
12816
12983
|
try {
|
|
12817
|
-
const parsed = JSON.parse(
|
|
12984
|
+
const parsed = JSON.parse(readFileSync26(clonedCheckoutsFile(stateDir), "utf8"));
|
|
12818
12985
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
12819
12986
|
} catch {
|
|
12820
12987
|
return [];
|
|
@@ -12824,8 +12991,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
12824
12991
|
const known = readClonedCheckouts(stateDir);
|
|
12825
12992
|
if (known.includes(dir)) return;
|
|
12826
12993
|
const file = clonedCheckoutsFile(stateDir);
|
|
12827
|
-
|
|
12828
|
-
|
|
12994
|
+
mkdirSync19(dirname14(file), { recursive: true });
|
|
12995
|
+
writeFileSync20(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
12829
12996
|
`);
|
|
12830
12997
|
}
|
|
12831
12998
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -12879,7 +13046,7 @@ async function runReset(o, fx) {
|
|
|
12879
13046
|
}
|
|
12880
13047
|
if (o.beforeState) steps.push(...await o.beforeState());
|
|
12881
13048
|
steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
|
|
12882
|
-
if (
|
|
13049
|
+
if (existsSync28(o.configFile)) {
|
|
12883
13050
|
if (!o.dryRun) rmSync16(o.configFile, { force: true });
|
|
12884
13051
|
steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
|
|
12885
13052
|
} else {
|
|
@@ -12888,7 +13055,7 @@ async function runReset(o, fx) {
|
|
|
12888
13055
|
return steps;
|
|
12889
13056
|
}
|
|
12890
13057
|
function removeCheckout(dir, dryRun, fx) {
|
|
12891
|
-
if (!
|
|
13058
|
+
if (!existsSync28(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
|
|
12892
13059
|
const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
|
|
12893
13060
|
if (dirty.length) {
|
|
12894
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" : ""})` };
|
|
@@ -12901,15 +13068,15 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
12901
13068
|
}
|
|
12902
13069
|
function removeState(stateDir, dryRun) {
|
|
12903
13070
|
const out = [];
|
|
12904
|
-
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")];
|
|
12905
13072
|
try {
|
|
12906
13073
|
for (const name of readdirSync12(stateDir)) {
|
|
12907
|
-
if (name.startsWith(".") && statSync10(
|
|
13074
|
+
if (name.startsWith(".") && statSync10(join31(stateDir, name)).isFile()) targets.push(join31(stateDir, name));
|
|
12908
13075
|
}
|
|
12909
13076
|
} catch {
|
|
12910
13077
|
}
|
|
12911
13078
|
for (const t of targets) {
|
|
12912
|
-
if (!
|
|
13079
|
+
if (!existsSync28(t)) continue;
|
|
12913
13080
|
if (!dryRun) rmSync16(t, { recursive: true, force: true });
|
|
12914
13081
|
out.push({ step: "state", target: t, action: mark(dryRun) });
|
|
12915
13082
|
}
|
|
@@ -12924,7 +13091,7 @@ function wipeStateDir(stateDir, dryRun) {
|
|
|
12924
13091
|
return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
|
|
12925
13092
|
}
|
|
12926
13093
|
if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
|
|
12927
|
-
if (!dryRun) for (const n of names) rmSync16(
|
|
13094
|
+
if (!dryRun) for (const n of names) rmSync16(join31(stateDir, n), { recursive: true, force: true });
|
|
12928
13095
|
return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
|
|
12929
13096
|
}
|
|
12930
13097
|
function describeResetStep(s) {
|
|
@@ -13005,9 +13172,9 @@ async function runDeprovision(o, fx, deps) {
|
|
|
13005
13172
|
}
|
|
13006
13173
|
|
|
13007
13174
|
// src/pool-member.ts
|
|
13008
|
-
import { mkdirSync as
|
|
13175
|
+
import { mkdirSync as mkdirSync20, readFileSync as readFileSync27, renameSync as renameSync4, writeFileSync as writeFileSync21 } from "fs";
|
|
13009
13176
|
import { homedir as homedir8 } from "os";
|
|
13010
|
-
import { dirname as
|
|
13177
|
+
import { dirname as dirname15, join as join32 } from "path";
|
|
13011
13178
|
var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
|
|
13012
13179
|
function backoffMs(attempt) {
|
|
13013
13180
|
return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
|
|
@@ -13238,22 +13405,22 @@ async function runPoolMember(deps) {
|
|
|
13238
13405
|
}
|
|
13239
13406
|
}
|
|
13240
13407
|
function defaultMemberDir(env = process.env) {
|
|
13241
|
-
return env.CREW_POOL_DIR ??
|
|
13408
|
+
return env.CREW_POOL_DIR ?? join32(env.XDG_STATE_HOME ?? join32(homedir8(), ".local", "state"), "crew-pool-member");
|
|
13242
13409
|
}
|
|
13243
13410
|
function fileMemberStore(dir) {
|
|
13244
|
-
const file =
|
|
13411
|
+
const file = join32(dir, "member.json");
|
|
13245
13412
|
return {
|
|
13246
13413
|
read() {
|
|
13247
13414
|
try {
|
|
13248
|
-
return JSON.parse(
|
|
13415
|
+
return JSON.parse(readFileSync27(file, "utf8"));
|
|
13249
13416
|
} catch {
|
|
13250
13417
|
return {};
|
|
13251
13418
|
}
|
|
13252
13419
|
},
|
|
13253
13420
|
write(f) {
|
|
13254
|
-
|
|
13421
|
+
mkdirSync20(dirname15(file), { recursive: true, mode: 448 });
|
|
13255
13422
|
const tmp = `${file}.tmp`;
|
|
13256
|
-
|
|
13423
|
+
writeFileSync21(tmp, `${JSON.stringify(f)}
|
|
13257
13424
|
`, { mode: 384 });
|
|
13258
13425
|
renameSync4(tmp, file);
|
|
13259
13426
|
}
|
|
@@ -13305,30 +13472,30 @@ function hostArchitecture(arch = process.arch) {
|
|
|
13305
13472
|
|
|
13306
13473
|
// src/pool-member-host.ts
|
|
13307
13474
|
import "child_process";
|
|
13308
|
-
import { existsSync as
|
|
13475
|
+
import { existsSync as existsSync32 } from "fs";
|
|
13309
13476
|
import { hostname as hostname3 } from "os";
|
|
13310
13477
|
|
|
13311
13478
|
// src/machine-id.ts
|
|
13312
|
-
import { execFileSync as
|
|
13479
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
13313
13480
|
import { createHash as createHash7 } from "crypto";
|
|
13314
|
-
import { existsSync as
|
|
13315
|
-
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";
|
|
13316
13483
|
function machineIdPathFor(stateDir) {
|
|
13317
|
-
return
|
|
13484
|
+
return join33(stateDir, "machine-id");
|
|
13318
13485
|
}
|
|
13319
13486
|
var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
|
|
13320
13487
|
function readRawMachineId(platform2 = process.platform) {
|
|
13321
13488
|
try {
|
|
13322
13489
|
if (platform2 === "darwin") {
|
|
13323
|
-
const out =
|
|
13490
|
+
const out = execFileSync10("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
13324
13491
|
encoding: "utf8",
|
|
13325
13492
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13326
13493
|
});
|
|
13327
13494
|
return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
13328
13495
|
}
|
|
13329
13496
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
13330
|
-
if (
|
|
13331
|
-
const id =
|
|
13497
|
+
if (existsSync29(p)) {
|
|
13498
|
+
const id = readFileSync28(p, "utf8").trim();
|
|
13332
13499
|
if (id) return id;
|
|
13333
13500
|
}
|
|
13334
13501
|
}
|
|
@@ -13339,15 +13506,15 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
13339
13506
|
function ensureMachineId(stateDir, opts = {}) {
|
|
13340
13507
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
13341
13508
|
const path = machineIdPathFor(stateDir);
|
|
13342
|
-
if (
|
|
13343
|
-
const cached =
|
|
13509
|
+
if (existsSync29(path)) {
|
|
13510
|
+
const cached = readFileSync28(path, "utf8").trim();
|
|
13344
13511
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
13345
13512
|
}
|
|
13346
13513
|
const raw = (opts.read ?? readRawMachineId)();
|
|
13347
13514
|
if (!raw) return void 0;
|
|
13348
13515
|
const hashed = sha256(raw);
|
|
13349
|
-
|
|
13350
|
-
|
|
13516
|
+
mkdirSync21(dirname16(path), { recursive: true });
|
|
13517
|
+
writeFileSync22(path, `${hashed}
|
|
13351
13518
|
`, { mode: 384 });
|
|
13352
13519
|
return hashed;
|
|
13353
13520
|
}
|
|
@@ -13360,7 +13527,7 @@ function matchShipRows(ships, shipName2, machineId) {
|
|
|
13360
13527
|
|
|
13361
13528
|
// src/provision.ts
|
|
13362
13529
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
13363
|
-
import { existsSync as
|
|
13530
|
+
import { existsSync as existsSync30, readFileSync as readFileSync29, writeFileSync as writeFileSync23 } from "fs";
|
|
13364
13531
|
import { hostname as hostname2 } from "os";
|
|
13365
13532
|
import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
|
|
13366
13533
|
|
|
@@ -13369,7 +13536,7 @@ import { parseDocument as parseDocument2 } from "yaml";
|
|
|
13369
13536
|
|
|
13370
13537
|
// src/repos-cmd.ts
|
|
13371
13538
|
import { homedir as homedir9 } from "os";
|
|
13372
|
-
import { basename as basename10, join as
|
|
13539
|
+
import { basename as basename10, join as join34, resolve as resolve4 } from "path";
|
|
13373
13540
|
import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
|
|
13374
13541
|
var ReposError = class extends Error {
|
|
13375
13542
|
};
|
|
@@ -13647,7 +13814,7 @@ function planRepoClone(i) {
|
|
|
13647
13814
|
}
|
|
13648
13815
|
const ship = doc.get("ship", true);
|
|
13649
13816
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
13650
|
-
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);
|
|
13651
13818
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
13652
13819
|
const forgeHost = i.resolved?.repoForgeHosts?.[id];
|
|
13653
13820
|
return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
|
|
@@ -13954,7 +14121,7 @@ function realProvisionDeps(crewHome) {
|
|
|
13954
14121
|
};
|
|
13955
14122
|
const configFile = () => {
|
|
13956
14123
|
const f = findConfigFile(crewHome);
|
|
13957
|
-
return
|
|
14124
|
+
return existsSync30(f) ? f : null;
|
|
13958
14125
|
};
|
|
13959
14126
|
return {
|
|
13960
14127
|
crew: spawnCrew,
|
|
@@ -13962,13 +14129,13 @@ function realProvisionDeps(crewHome) {
|
|
|
13962
14129
|
enableRoute(route2) {
|
|
13963
14130
|
const file = configFile();
|
|
13964
14131
|
if (!file) return false;
|
|
13965
|
-
const text2 =
|
|
14132
|
+
const text2 = readFileSync29(file, "utf8");
|
|
13966
14133
|
const doc = parseDocument3(text2);
|
|
13967
14134
|
const routes = doc.get("routes");
|
|
13968
14135
|
const entry = routes?.items?.find((r) => r.get("route") === route2);
|
|
13969
14136
|
if (!entry) throw new Error(`route ${route2} is not in ${file}`);
|
|
13970
14137
|
if (entry.get("enabled") === true) return false;
|
|
13971
|
-
|
|
14138
|
+
writeFileSync23(file, planEnable(text2, route2));
|
|
13972
14139
|
return true;
|
|
13973
14140
|
},
|
|
13974
14141
|
installState() {
|
|
@@ -13988,14 +14155,14 @@ function realProvisionDeps(crewHome) {
|
|
|
13988
14155
|
continue;
|
|
13989
14156
|
}
|
|
13990
14157
|
for (const p of plan.unitPaths) {
|
|
13991
|
-
if (!
|
|
14158
|
+
if (!existsSync30(p) || readFileSync29(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
|
|
13992
14159
|
}
|
|
13993
14160
|
}
|
|
13994
14161
|
const label = labelFor(crewHome, "run");
|
|
13995
14162
|
let running = false;
|
|
13996
14163
|
let lingering = true;
|
|
13997
14164
|
if (mechanism === "systemd") {
|
|
13998
|
-
const system = ctx.root ||
|
|
14165
|
+
const system = ctx.root || existsSync30(systemUnitPathFor(label));
|
|
13999
14166
|
const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
|
|
14000
14167
|
running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
|
|
14001
14168
|
if (!system) {
|
|
@@ -14012,18 +14179,18 @@ function realProvisionDeps(crewHome) {
|
|
|
14012
14179
|
setShipSetting(key, value2) {
|
|
14013
14180
|
const file = configFile();
|
|
14014
14181
|
if (!file) return false;
|
|
14015
|
-
const doc = parseDocument3(
|
|
14182
|
+
const doc = parseDocument3(readFileSync29(file, "utf8"));
|
|
14016
14183
|
if (doc.getIn(["ship", key]) === value2) return false;
|
|
14017
14184
|
doc.setIn(["ship", key], value2);
|
|
14018
|
-
|
|
14185
|
+
writeFileSync23(file, doc.toString());
|
|
14019
14186
|
return true;
|
|
14020
14187
|
},
|
|
14021
14188
|
applyManagedDefaults() {
|
|
14022
14189
|
const file = configFile();
|
|
14023
14190
|
if (!file) return [];
|
|
14024
|
-
const doc = parseDocument3(
|
|
14191
|
+
const doc = parseDocument3(readFileSync29(file, "utf8"));
|
|
14025
14192
|
const wrote = applyManagedDefaultsToDoc(doc);
|
|
14026
|
-
if (wrote.length)
|
|
14193
|
+
if (wrote.length) writeFileSync23(file, doc.toString());
|
|
14027
14194
|
return wrote;
|
|
14028
14195
|
},
|
|
14029
14196
|
storedToken(host) {
|
|
@@ -14040,9 +14207,9 @@ host=${host}
|
|
|
14040
14207
|
|
|
14041
14208
|
// src/tunnel.ts
|
|
14042
14209
|
import { spawn as spawn6 } from "child_process";
|
|
14043
|
-
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";
|
|
14044
14211
|
import { randomInt } from "crypto";
|
|
14045
|
-
import { join as
|
|
14212
|
+
import { join as join35 } from "path";
|
|
14046
14213
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
14047
14214
|
var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
14048
14215
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -14114,21 +14281,21 @@ function startTunnel(opts) {
|
|
|
14114
14281
|
};
|
|
14115
14282
|
}
|
|
14116
14283
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
14117
|
-
return
|
|
14284
|
+
return join35(stateDir, "passengers", `${workspaceId}.json`);
|
|
14118
14285
|
}
|
|
14119
14286
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
14120
14287
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
14121
|
-
if (!
|
|
14288
|
+
if (!existsSync31(path)) return void 0;
|
|
14122
14289
|
try {
|
|
14123
|
-
return JSON.parse(
|
|
14290
|
+
return JSON.parse(readFileSync30(path, "utf8"));
|
|
14124
14291
|
} catch {
|
|
14125
14292
|
return void 0;
|
|
14126
14293
|
}
|
|
14127
14294
|
}
|
|
14128
14295
|
function writePersistedTunnel(stateDir, state) {
|
|
14129
14296
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
14130
|
-
|
|
14131
|
-
|
|
14297
|
+
mkdirSync22(join35(stateDir, "passengers"), { recursive: true });
|
|
14298
|
+
writeFileSync24(path, `${JSON.stringify(state, null, 2)}
|
|
14132
14299
|
`);
|
|
14133
14300
|
}
|
|
14134
14301
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -14230,8 +14397,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
14230
14397
|
}
|
|
14231
14398
|
}
|
|
14232
14399
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
14233
|
-
const dir =
|
|
14234
|
-
if (!
|
|
14400
|
+
const dir = join35(stateDir, "passengers");
|
|
14401
|
+
if (!existsSync31(dir)) return [];
|
|
14235
14402
|
return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
14236
14403
|
}
|
|
14237
14404
|
|
|
@@ -14256,7 +14423,7 @@ function realPoolMemberDeps(o) {
|
|
|
14256
14423
|
crewVersion: crewVersion(o.crewHome),
|
|
14257
14424
|
hostname: hostname3()
|
|
14258
14425
|
},
|
|
14259
|
-
hasShipConfig: () =>
|
|
14426
|
+
hasShipConfig: () => existsSync32(findConfigFile(o.crewHome)),
|
|
14260
14427
|
async provision(lease, instanceClass, shipKind) {
|
|
14261
14428
|
const hosts = lease.forges.map((f) => f.host.toLowerCase());
|
|
14262
14429
|
const lines2 = [];
|
|
@@ -15132,8 +15299,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
15132
15299
|
}
|
|
15133
15300
|
|
|
15134
15301
|
// src/poll.ts
|
|
15135
|
-
import { writeFileSync as
|
|
15136
|
-
import { join as
|
|
15302
|
+
import { writeFileSync as writeFileSync25 } from "fs";
|
|
15303
|
+
import { join as join36 } from "path";
|
|
15137
15304
|
|
|
15138
15305
|
// src/stalled.ts
|
|
15139
15306
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -15440,7 +15607,7 @@ async function decideCycle(o) {
|
|
|
15440
15607
|
}
|
|
15441
15608
|
return decision;
|
|
15442
15609
|
}
|
|
15443
|
-
var digestPath = (stateDir, route2, role, runId) =>
|
|
15610
|
+
var digestPath = (stateDir, route2, role, runId) => join36(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
|
|
15444
15611
|
function otherRunsRemoved(tickets, own) {
|
|
15445
15612
|
if (!own) return tickets;
|
|
15446
15613
|
return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
|
|
@@ -15518,7 +15685,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
|
|
|
15518
15685
|
|
|
15519
15686
|
${body}` : body;
|
|
15520
15687
|
const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
|
|
15521
|
-
|
|
15688
|
+
writeFileSync25(path, text2);
|
|
15522
15689
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
|
|
15523
15690
|
data: { role, bytes: Buffer.byteLength(text2) }
|
|
15524
15691
|
});
|
|
@@ -15626,8 +15793,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
|
|
|
15626
15793
|
}
|
|
15627
15794
|
|
|
15628
15795
|
// src/repo-refresh.ts
|
|
15629
|
-
import { existsSync as
|
|
15630
|
-
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";
|
|
15631
15798
|
var REFRESH_COOLDOWN_MS = 2 * 6e4;
|
|
15632
15799
|
var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
|
|
15633
15800
|
var lastAsked = /* @__PURE__ */ new Map();
|
|
@@ -15656,11 +15823,11 @@ function applyRepoRows(resolved, rows) {
|
|
|
15656
15823
|
}
|
|
15657
15824
|
function persistRepoRows(stateDir, routeName, rows) {
|
|
15658
15825
|
const path = resolvedPathFor(stateDir, routeName);
|
|
15659
|
-
if (!
|
|
15660
|
-
const raw = JSON.parse(
|
|
15826
|
+
if (!existsSync33(path)) return;
|
|
15827
|
+
const raw = JSON.parse(readFileSync31(path, "utf8"));
|
|
15661
15828
|
applyRepoRows(raw, rows);
|
|
15662
|
-
|
|
15663
|
-
|
|
15829
|
+
mkdirSync23(dirname18(path), { recursive: true });
|
|
15830
|
+
writeFileSync26(path, `${JSON.stringify(raw, null, 2)}
|
|
15664
15831
|
`);
|
|
15665
15832
|
}
|
|
15666
15833
|
async function refreshUnknownRepos(o) {
|
|
@@ -16110,13 +16277,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
|
|
|
16110
16277
|
var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
|
|
16111
16278
|
|
|
16112
16279
|
// src/connect-wizard.ts
|
|
16113
|
-
import { delimiter, dirname as
|
|
16114
|
-
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";
|
|
16115
16282
|
import { stringify as stringify3 } from "yaml";
|
|
16116
16283
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
16117
16284
|
function shouldRunWizard(opts) {
|
|
16118
16285
|
if (!opts.isTTY) return false;
|
|
16119
|
-
const exists = opts.configExists ??
|
|
16286
|
+
const exists = opts.configExists ?? existsSync34;
|
|
16120
16287
|
return !configSearchPath(opts.crewHome).some((p) => exists(p));
|
|
16121
16288
|
}
|
|
16122
16289
|
function firstRunConfigPath() {
|
|
@@ -16133,7 +16300,7 @@ function defaultIsExecutable(p) {
|
|
|
16133
16300
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
16134
16301
|
for (const dir of pathEnv.split(delimiter)) {
|
|
16135
16302
|
if (!dir) continue;
|
|
16136
|
-
const candidate =
|
|
16303
|
+
const candidate = join37(dir, name);
|
|
16137
16304
|
if (isExecutable(candidate)) return candidate;
|
|
16138
16305
|
}
|
|
16139
16306
|
return void 0;
|
|
@@ -16159,9 +16326,9 @@ async function runWizard(p, env) {
|
|
|
16159
16326
|
if (entered && isExec(entered)) agentBin = entered;
|
|
16160
16327
|
else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
|
|
16161
16328
|
}
|
|
16162
|
-
const nodeDir =
|
|
16329
|
+
const nodeDir = dirname19(env.nodePath);
|
|
16163
16330
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16164
|
-
const baseDirs = [nodeDir, pnpmPath ?
|
|
16331
|
+
const baseDirs = [nodeDir, pnpmPath ? dirname19(pnpmPath) : void 0];
|
|
16165
16332
|
p.say(
|
|
16166
16333
|
`
|
|
16167
16334
|
Host Passengers lets this machine run the agent sessions that other people's
|
|
@@ -16172,7 +16339,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
|
|
|
16172
16339
|
p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
|
|
16173
16340
|
}
|
|
16174
16341
|
const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16175
|
-
const extraPath = deriveExtraPath([...baseDirs, dockerPath ?
|
|
16342
|
+
const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname19(dockerPath) : void 0]);
|
|
16176
16343
|
if (extraPath) {
|
|
16177
16344
|
p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
|
|
16178
16345
|
}
|
|
@@ -16194,9 +16361,9 @@ routes:
|
|
|
16194
16361
|
${routeBlock}`;
|
|
16195
16362
|
}
|
|
16196
16363
|
function writeNewConfig(path, text2) {
|
|
16197
|
-
|
|
16364
|
+
mkdirSync24(dirname19(path), { recursive: true });
|
|
16198
16365
|
try {
|
|
16199
|
-
|
|
16366
|
+
writeFileSync27(path, text2, { mode: 384, flag: "wx" });
|
|
16200
16367
|
return true;
|
|
16201
16368
|
} catch (e) {
|
|
16202
16369
|
if (e.code === "EEXIST") return false;
|
|
@@ -16230,7 +16397,7 @@ Wrote ${o.configPath}.
|
|
|
16230
16397
|
// src/connect-json.ts
|
|
16231
16398
|
import { accessSync as accessSync4, constants as constants4 } from "fs";
|
|
16232
16399
|
import { homedir as homedir10 } from "os";
|
|
16233
|
-
import { dirname as
|
|
16400
|
+
import { dirname as dirname20, join as join38 } from "path";
|
|
16234
16401
|
import { delimiter as delimiter2 } from "path";
|
|
16235
16402
|
var CHOICE_EXIT_CODE = 3;
|
|
16236
16403
|
function createEmitter(json2, write) {
|
|
@@ -16269,12 +16436,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
16269
16436
|
const dirs = [
|
|
16270
16437
|
...pathEnv.split(delimiter2),
|
|
16271
16438
|
...(extraPath ?? "").split(delimiter2),
|
|
16272
|
-
|
|
16273
|
-
|
|
16439
|
+
join38(home, ".local/bin"),
|
|
16440
|
+
join38(home, ".claude/local"),
|
|
16274
16441
|
"/opt/homebrew/bin",
|
|
16275
16442
|
"/usr/local/bin",
|
|
16276
|
-
|
|
16277
|
-
|
|
16443
|
+
join38(home, ".npm-global/bin"),
|
|
16444
|
+
join38(home, ".volta/bin")
|
|
16278
16445
|
];
|
|
16279
16446
|
return [...new Set(dirs.filter(Boolean))];
|
|
16280
16447
|
}
|
|
@@ -16296,7 +16463,7 @@ function flagWizardAnswers(f, env) {
|
|
|
16296
16463
|
const agentBin = f.agentBin ?? found?.bin;
|
|
16297
16464
|
const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
|
|
16298
16465
|
const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
|
|
16299
|
-
const extraPath = f.extraPath ?? deriveExtraPath([
|
|
16466
|
+
const extraPath = f.extraPath ?? deriveExtraPath([dirname20(env.nodePath), pnpmPath ? dirname20(pnpmPath) : void 0, dockerPath ? dirname20(dockerPath) : void 0]);
|
|
16300
16467
|
return {
|
|
16301
16468
|
answers: {
|
|
16302
16469
|
name: f.shipName?.trim() || env.hostname,
|
|
@@ -16310,8 +16477,8 @@ function flagWizardAnswers(f, env) {
|
|
|
16310
16477
|
}
|
|
16311
16478
|
|
|
16312
16479
|
// src/release-run.ts
|
|
16313
|
-
import { readFileSync as
|
|
16314
|
-
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";
|
|
16315
16482
|
import { tmpdir as tmpdir5 } from "os";
|
|
16316
16483
|
|
|
16317
16484
|
// src/release.ts
|
|
@@ -17052,13 +17219,13 @@ async function bump(o, size) {
|
|
|
17052
17219
|
}
|
|
17053
17220
|
return printed;
|
|
17054
17221
|
}
|
|
17055
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
17222
|
+
const files = o.repo.release.versionFiles.map((f) => join39(o.cwd, f));
|
|
17056
17223
|
const first = files[0];
|
|
17057
|
-
if (!
|
|
17224
|
+
if (!existsSync35(first)) {
|
|
17058
17225
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
17059
17226
|
return null;
|
|
17060
17227
|
}
|
|
17061
|
-
const current = JSON.parse(
|
|
17228
|
+
const current = JSON.parse(readFileSync32(first, "utf8")).version;
|
|
17062
17229
|
if (!current) {
|
|
17063
17230
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
17064
17231
|
return null;
|
|
@@ -17071,27 +17238,27 @@ async function bump(o, size) {
|
|
|
17071
17238
|
return version;
|
|
17072
17239
|
}
|
|
17073
17240
|
for (const f of files) {
|
|
17074
|
-
const src =
|
|
17241
|
+
const src = readFileSync32(f, "utf8");
|
|
17075
17242
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
17076
17243
|
if (out === src) {
|
|
17077
17244
|
o.emit.error(`no version field in ${f}`);
|
|
17078
17245
|
return null;
|
|
17079
17246
|
}
|
|
17080
|
-
|
|
17247
|
+
writeFileSync28(f, out);
|
|
17081
17248
|
}
|
|
17082
17249
|
return version;
|
|
17083
17250
|
}
|
|
17084
17251
|
function writeChangelog(o, version, entries) {
|
|
17085
17252
|
const file = o.repo.release.changelog;
|
|
17086
17253
|
if (!file || entries.length === 0) return;
|
|
17087
|
-
const path =
|
|
17254
|
+
const path = join39(o.cwd, file);
|
|
17088
17255
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
17089
17256
|
if (o.dryRun) {
|
|
17090
17257
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
17091
17258
|
return;
|
|
17092
17259
|
}
|
|
17093
|
-
const existing =
|
|
17094
|
-
|
|
17260
|
+
const existing = existsSync35(path) ? readFileSync32(path, "utf8") : "# Changelog\n";
|
|
17261
|
+
writeFileSync28(path, insertChangelogSection(existing, section));
|
|
17095
17262
|
}
|
|
17096
17263
|
async function confirm(o, expected) {
|
|
17097
17264
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -17184,8 +17351,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
|
|
|
17184
17351
|
function openReleaseCheckout(o) {
|
|
17185
17352
|
const { remote, base } = o.repo.branch;
|
|
17186
17353
|
const branch = `crew/release-${Date.now()}`;
|
|
17187
|
-
const root = mkdtempSync3(
|
|
17188
|
-
const dir =
|
|
17354
|
+
const root = mkdtempSync3(join39(tmpdir5(), "crew-release-"));
|
|
17355
|
+
const dir = join39(root, "checkout");
|
|
17189
17356
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
17190
17357
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
17191
17358
|
try {
|
|
@@ -17195,10 +17362,10 @@ function openReleaseCheckout(o) {
|
|
|
17195
17362
|
throw e;
|
|
17196
17363
|
}
|
|
17197
17364
|
for (const f of o.repo.worktrees.copy) {
|
|
17198
|
-
const src =
|
|
17199
|
-
if (!
|
|
17200
|
-
|
|
17201
|
-
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));
|
|
17202
17369
|
}
|
|
17203
17370
|
return { dir, branch, close };
|
|
17204
17371
|
}
|
|
@@ -18220,7 +18387,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
18220
18387
|
}
|
|
18221
18388
|
|
|
18222
18389
|
// src/watch.ts
|
|
18223
|
-
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";
|
|
18224
18391
|
var E = "\x1B[";
|
|
18225
18392
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
18226
18393
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -18321,7 +18488,7 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
18321
18488
|
return states.map((s) => renderHeader(s, now)).join("");
|
|
18322
18489
|
}
|
|
18323
18490
|
function readFrom(file, from) {
|
|
18324
|
-
if (!
|
|
18491
|
+
if (!existsSync36(file)) return { events: [], offset: from };
|
|
18325
18492
|
const size = statSync11(file).size;
|
|
18326
18493
|
if (size < from) return readFrom(file, 0);
|
|
18327
18494
|
if (size === from) return { events: [], offset: from };
|
|
@@ -18564,8 +18731,8 @@ function nextRoles(decision, ran) {
|
|
|
18564
18731
|
}
|
|
18565
18732
|
|
|
18566
18733
|
// src/ship-name.ts
|
|
18567
|
-
import { existsSync as
|
|
18568
|
-
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";
|
|
18569
18736
|
import { parseDocument as parseDocument5 } from "yaml";
|
|
18570
18737
|
var ShipNameError = class extends Error {
|
|
18571
18738
|
};
|
|
@@ -18626,34 +18793,34 @@ async function syncRouteName(i) {
|
|
|
18626
18793
|
return { status: "ok", ...base, action, name: i.local };
|
|
18627
18794
|
}
|
|
18628
18795
|
function readSyncedName(resolvedFile) {
|
|
18629
|
-
if (!
|
|
18796
|
+
if (!existsSync37(resolvedFile)) return void 0;
|
|
18630
18797
|
try {
|
|
18631
|
-
const v = JSON.parse(
|
|
18798
|
+
const v = JSON.parse(readFileSync33(resolvedFile, "utf8")).shipName;
|
|
18632
18799
|
return typeof v === "string" ? v : void 0;
|
|
18633
18800
|
} catch {
|
|
18634
18801
|
return void 0;
|
|
18635
18802
|
}
|
|
18636
18803
|
}
|
|
18637
18804
|
function writeSyncedName(resolvedFile, name) {
|
|
18638
|
-
if (!
|
|
18805
|
+
if (!existsSync37(resolvedFile)) return;
|
|
18639
18806
|
let raw;
|
|
18640
18807
|
try {
|
|
18641
|
-
raw = JSON.parse(
|
|
18808
|
+
raw = JSON.parse(readFileSync33(resolvedFile, "utf8"));
|
|
18642
18809
|
} catch {
|
|
18643
18810
|
return;
|
|
18644
18811
|
}
|
|
18645
18812
|
if (raw.shipName === name) return;
|
|
18646
18813
|
raw.shipName = name;
|
|
18647
|
-
|
|
18814
|
+
mkdirSync26(dirname22(resolvedFile), { recursive: true });
|
|
18648
18815
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
18649
|
-
|
|
18816
|
+
writeFileSync29(tmp, `${JSON.stringify(raw, null, 2)}
|
|
18650
18817
|
`);
|
|
18651
18818
|
renameSync5(tmp, resolvedFile);
|
|
18652
18819
|
}
|
|
18653
18820
|
async function reconcileShipName(i) {
|
|
18654
18821
|
let local = i.ship.name;
|
|
18655
18822
|
try {
|
|
18656
|
-
local = shipNameOfYaml(
|
|
18823
|
+
local = shipNameOfYaml(readFileSync33(i.configFile, "utf8")) ?? local;
|
|
18657
18824
|
} catch {
|
|
18658
18825
|
}
|
|
18659
18826
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -18677,8 +18844,8 @@ async function reconcileShipName(i) {
|
|
|
18677
18844
|
}
|
|
18678
18845
|
if (result.status !== "ok") return result;
|
|
18679
18846
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
18680
|
-
const text2 =
|
|
18681
|
-
|
|
18847
|
+
const text2 = readFileSync33(i.configFile, "utf8");
|
|
18848
|
+
writeFileSync29(i.configFile, setShipNameInYaml(text2, result.name));
|
|
18682
18849
|
i.ship.name = result.name;
|
|
18683
18850
|
if (result.action === "row-wins") {
|
|
18684
18851
|
i.emit?.warn(
|
|
@@ -19337,8 +19504,8 @@ function describeCapability(i) {
|
|
|
19337
19504
|
}
|
|
19338
19505
|
|
|
19339
19506
|
// src/agents-sync-run.ts
|
|
19340
|
-
import { existsSync as
|
|
19341
|
-
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";
|
|
19342
19509
|
async function runAgentsSync(target, o) {
|
|
19343
19510
|
const dryRun = o.dryRun ?? false;
|
|
19344
19511
|
const force = o.force ?? false;
|
|
@@ -19371,12 +19538,12 @@ async function runAgentsSync(target, o) {
|
|
|
19371
19538
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
19372
19539
|
if (!dryRun) {
|
|
19373
19540
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
19374
|
-
const raw =
|
|
19541
|
+
const raw = existsSync38(resolvedPath) ? JSON.parse(readFileSync34(resolvedPath, "utf8")) : {};
|
|
19375
19542
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
19376
19543
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
19377
19544
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
19378
|
-
|
|
19379
|
-
|
|
19545
|
+
mkdirSync27(dirname23(resolvedPath), { recursive: true });
|
|
19546
|
+
writeFileSync30(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
19380
19547
|
`);
|
|
19381
19548
|
if (target.resolved) {
|
|
19382
19549
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -19480,7 +19647,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
|
|
|
19480
19647
|
|
|
19481
19648
|
// src/repos-check.ts
|
|
19482
19649
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
19483
|
-
import { readFileSync as
|
|
19650
|
+
import { readFileSync as readFileSync35 } from "fs";
|
|
19484
19651
|
import { basename as basename11 } from "path";
|
|
19485
19652
|
var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
|
|
19486
19653
|
function checkHookSyntax(script, shell) {
|
|
@@ -19521,7 +19688,7 @@ function checkRepoSpec(dir, opts = {}) {
|
|
|
19521
19688
|
}
|
|
19522
19689
|
let parsed;
|
|
19523
19690
|
try {
|
|
19524
|
-
parsed = parseRepoConfig(
|
|
19691
|
+
parsed = parseRepoConfig(readFileSync35(file, "utf8"), file);
|
|
19525
19692
|
} catch (e) {
|
|
19526
19693
|
if (!(e instanceof RepoConfigError)) throw e;
|
|
19527
19694
|
return { ...empty, file, error: e.message, exitCode: 1 };
|
|
@@ -19800,7 +19967,7 @@ function existingRepoConfig(f) {
|
|
|
19800
19967
|
}
|
|
19801
19968
|
|
|
19802
19969
|
// src/repos-hooks-agent.ts
|
|
19803
|
-
import { spawn as spawn7, execFileSync as
|
|
19970
|
+
import { spawn as spawn7, execFileSync as execFileSync11 } from "child_process";
|
|
19804
19971
|
import { realpathSync as realpathSync2 } from "fs";
|
|
19805
19972
|
import { isAbsolute as isAbsolute3, relative } from "path";
|
|
19806
19973
|
var AgentSuggestError = class extends Error {
|
|
@@ -19903,7 +20070,7 @@ function classifyFailure(o) {
|
|
|
19903
20070
|
}
|
|
19904
20071
|
function porcelain(dir) {
|
|
19905
20072
|
try {
|
|
19906
|
-
return
|
|
20073
|
+
return execFileSync11("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
|
|
19907
20074
|
} catch {
|
|
19908
20075
|
return void 0;
|
|
19909
20076
|
}
|
|
@@ -19972,7 +20139,7 @@ ${run3.err}` });
|
|
|
19972
20139
|
}
|
|
19973
20140
|
|
|
19974
20141
|
// src/sample-ticket.ts
|
|
19975
|
-
import { basename as basename12, dirname as
|
|
20142
|
+
import { basename as basename12, dirname as dirname24, join as join40 } from "path";
|
|
19976
20143
|
var SAMPLE_REPO = "crew-sample";
|
|
19977
20144
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
19978
20145
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -19995,8 +20162,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
19995
20162
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
19996
20163
|
`;
|
|
19997
20164
|
function sampleDir(existing, reposBase, workspace) {
|
|
19998
|
-
if (existing.length > 0) return
|
|
19999
|
-
return
|
|
20165
|
+
if (existing.length > 0) return join40(dirname24(existing[0]), SAMPLE_REPO);
|
|
20166
|
+
return join40(reposBase, workspace, SAMPLE_REPO);
|
|
20000
20167
|
}
|
|
20001
20168
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
20002
20169
|
function sampleDescription() {
|
|
@@ -20040,8 +20207,8 @@ function planSampleTicket(i) {
|
|
|
20040
20207
|
}
|
|
20041
20208
|
|
|
20042
20209
|
// src/worktree-sweep.ts
|
|
20043
|
-
import { existsSync as
|
|
20044
|
-
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";
|
|
20045
20212
|
function planWorktreeSweep(target, tickets, contract) {
|
|
20046
20213
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
20047
20214
|
const verified = contract.statuses.verified;
|
|
@@ -20105,7 +20272,7 @@ function worktreeTicketNumbers(target) {
|
|
|
20105
20272
|
return [...nums];
|
|
20106
20273
|
}
|
|
20107
20274
|
function siblingWorktreeDirs(target) {
|
|
20108
|
-
const parent =
|
|
20275
|
+
const parent = dirname25(target.dir.replace(/[/\\]+$/, ""));
|
|
20109
20276
|
const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
|
|
20110
20277
|
const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
|
|
20111
20278
|
let names;
|
|
@@ -20118,7 +20285,7 @@ function siblingWorktreeDirs(target) {
|
|
|
20118
20285
|
for (const name of names) {
|
|
20119
20286
|
if (!name.startsWith(stem)) continue;
|
|
20120
20287
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
20121
|
-
const path =
|
|
20288
|
+
const path = join41(parent, name);
|
|
20122
20289
|
if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
|
|
20123
20290
|
}
|
|
20124
20291
|
return out;
|
|
@@ -20153,7 +20320,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20153
20320
|
let removed = 0;
|
|
20154
20321
|
let keptBranches = 0;
|
|
20155
20322
|
for (const a of actions) {
|
|
20156
|
-
if (!dryRun && base && !a.keepBranch &&
|
|
20323
|
+
if (!dryRun && base && !a.keepBranch && existsSync39(a.path) && isHandBackMerge(a.path, base)) {
|
|
20157
20324
|
if (gitOk(a.path, ["merge", "--abort"]) !== null) {
|
|
20158
20325
|
log.emit(
|
|
20159
20326
|
`abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
|
|
@@ -20161,7 +20328,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20161
20328
|
);
|
|
20162
20329
|
}
|
|
20163
20330
|
}
|
|
20164
|
-
if (!dryRun &&
|
|
20331
|
+
if (!dryRun && existsSync39(a.path)) {
|
|
20165
20332
|
let dirty = false;
|
|
20166
20333
|
try {
|
|
20167
20334
|
dirty = status(a.path).length > 0;
|
|
@@ -20172,7 +20339,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
|
|
|
20172
20339
|
continue;
|
|
20173
20340
|
}
|
|
20174
20341
|
}
|
|
20175
|
-
if (!dryRun && a.requirePushed && a.branch &&
|
|
20342
|
+
if (!dryRun && a.requirePushed && a.branch && existsSync39(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
|
|
20176
20343
|
refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
|
|
20177
20344
|
continue;
|
|
20178
20345
|
}
|
|
@@ -20221,7 +20388,7 @@ function isHandBackMerge(path, base) {
|
|
|
20221
20388
|
if (!msgPath) return false;
|
|
20222
20389
|
let first;
|
|
20223
20390
|
try {
|
|
20224
|
-
first =
|
|
20391
|
+
first = readFileSync36(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
20225
20392
|
} catch {
|
|
20226
20393
|
return false;
|
|
20227
20394
|
}
|
|
@@ -20326,14 +20493,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
20326
20493
|
}
|
|
20327
20494
|
|
|
20328
20495
|
// src/stream-sweep.ts
|
|
20329
|
-
import { existsSync as
|
|
20330
|
-
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";
|
|
20331
20498
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
20332
20499
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20333
|
-
const dir =
|
|
20334
|
-
if (!
|
|
20500
|
+
const dir = join42(stateDir, "streams");
|
|
20501
|
+
if (!existsSync40(dir)) return [];
|
|
20335
20502
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20336
|
-
return readdirSync15(dir).map((name) =>
|
|
20503
|
+
return readdirSync15(dir).map((name) => join42(dir, name)).filter((path) => {
|
|
20337
20504
|
try {
|
|
20338
20505
|
return statSync12(path).mtimeMs < cutoff;
|
|
20339
20506
|
} catch {
|
|
@@ -20359,9 +20526,9 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
20359
20526
|
return removed;
|
|
20360
20527
|
}
|
|
20361
20528
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
20362
|
-
if (!
|
|
20529
|
+
if (!existsSync40(stateDir)) return [];
|
|
20363
20530
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
20364
|
-
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) => {
|
|
20365
20532
|
try {
|
|
20366
20533
|
return statSync12(path).mtimeMs < cutoff;
|
|
20367
20534
|
} catch {
|
|
@@ -20371,12 +20538,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
20371
20538
|
}
|
|
20372
20539
|
|
|
20373
20540
|
// src/cli.ts
|
|
20374
|
-
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";
|
|
20375
20542
|
import { createInterface } from "readline/promises";
|
|
20376
20543
|
|
|
20377
20544
|
// src/periscope-cmd.ts
|
|
20378
|
-
import { existsSync as
|
|
20379
|
-
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";
|
|
20380
20547
|
var PeriscopeError = class extends Error {
|
|
20381
20548
|
};
|
|
20382
20549
|
var LivePeriscopeError = class extends PeriscopeError {
|
|
@@ -20386,28 +20553,29 @@ var LivePeriscopeError = class extends PeriscopeError {
|
|
|
20386
20553
|
this.data = { refused: "live_periscope_exists", ...live };
|
|
20387
20554
|
}
|
|
20388
20555
|
};
|
|
20389
|
-
var priorDir = (stateDir) =>
|
|
20390
|
-
var priorPath = (stateDir, ticketRecordId) =>
|
|
20556
|
+
var priorDir = (stateDir) => join43(stateDir, "periscope-priors");
|
|
20557
|
+
var priorPath = (stateDir, ticketRecordId) => join43(priorDir(stateDir), `${ticketRecordId}.json`);
|
|
20391
20558
|
function removePeriscopePrior(stateDir, ticketRecordId) {
|
|
20392
20559
|
rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
|
|
20393
20560
|
}
|
|
20394
20561
|
function writePeriscopePrior(stateDir, ticketRecordId, prior) {
|
|
20395
20562
|
try {
|
|
20396
|
-
|
|
20397
|
-
|
|
20563
|
+
mkdirSync28(priorDir(stateDir), { recursive: true });
|
|
20564
|
+
writeFileSync31(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
|
|
20398
20565
|
} catch {
|
|
20399
20566
|
}
|
|
20400
20567
|
}
|
|
20401
20568
|
function readPeriscopePrior(stateDir, ticketRecordId) {
|
|
20402
20569
|
try {
|
|
20403
20570
|
const p = priorPath(stateDir, ticketRecordId);
|
|
20404
|
-
return
|
|
20571
|
+
return existsSync41(p) ? JSON.parse(readFileSync37(p, "utf8")) : void 0;
|
|
20405
20572
|
} catch {
|
|
20406
20573
|
return void 0;
|
|
20407
20574
|
}
|
|
20408
20575
|
}
|
|
20409
20576
|
var DEFAULT_MAX_PERISCOPES2 = 3;
|
|
20410
20577
|
var DEFAULT_PERISCOPE_IDLE_MINUTES2 = 120;
|
|
20578
|
+
var DEFAULT_INTEL_STARTUP_SECONDS2 = 20;
|
|
20411
20579
|
function parsePeriscopeUpArgs(positional2, flags) {
|
|
20412
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]";
|
|
20413
20581
|
const rest = positional2.slice(2);
|
|
@@ -20475,12 +20643,15 @@ function openerFor(platform2) {
|
|
|
20475
20643
|
return platform2 === "darwin" ? "open" : platform2 === "linux" ? "xdg-open" : void 0;
|
|
20476
20644
|
}
|
|
20477
20645
|
function upOutput(res, terminal) {
|
|
20478
|
-
|
|
20646
|
+
const block = terminal ? `${res.ticket}
|
|
20479
20647
|
${res.url}
|
|
20480
20648
|
${res.worktree}
|
|
20481
20649
|
` : `${res.ticket}
|
|
20482
20650
|
${res.worktree}
|
|
20483
20651
|
${res.url}
|
|
20652
|
+
`;
|
|
20653
|
+
return `${block}
|
|
20654
|
+
${res.codeTools}
|
|
20484
20655
|
`;
|
|
20485
20656
|
}
|
|
20486
20657
|
function shouldOpen(f) {
|
|
@@ -20577,6 +20748,23 @@ ${tail(preview?.output ?? "")}`);
|
|
|
20577
20748
|
if (!url) return abandon(`preview printed no URL as its last line
|
|
20578
20749
|
${tail(preview.output)}`);
|
|
20579
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);
|
|
20580
20768
|
const now = d.now();
|
|
20581
20769
|
let row;
|
|
20582
20770
|
try {
|
|
@@ -20608,6 +20796,7 @@ ${tail(preview.output)}`);
|
|
|
20608
20796
|
host: d.host,
|
|
20609
20797
|
state: "live",
|
|
20610
20798
|
isolated: args.isolate,
|
|
20799
|
+
...intel.state === "ready" ? { intelUrl: intel.url, intelTools: intel.tools } : {},
|
|
20611
20800
|
startedAt: now.toISOString(),
|
|
20612
20801
|
lastActivityAt: now.toISOString(),
|
|
20613
20802
|
expiresAt: new Date(now.getTime() + d.caps.idleMinutes * 6e4).toISOString()
|
|
@@ -20625,6 +20814,7 @@ ${tail(preview.output)}`);
|
|
|
20625
20814
|
branch: wt.branch,
|
|
20626
20815
|
url,
|
|
20627
20816
|
periscopeRecordId: row.id,
|
|
20817
|
+
codeTools,
|
|
20628
20818
|
...prior ? { existing: true, priorStatus: prior.status } : {}
|
|
20629
20819
|
};
|
|
20630
20820
|
}
|
|
@@ -20804,9 +20994,9 @@ ${tail2(preview.output)}`);
|
|
|
20804
20994
|
}
|
|
20805
20995
|
|
|
20806
20996
|
// src/periscope-idle.ts
|
|
20807
|
-
import { execFileSync as
|
|
20997
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
20808
20998
|
import { statSync as statSync13 } from "fs";
|
|
20809
|
-
import { join as
|
|
20999
|
+
import { join as join44 } from "path";
|
|
20810
21000
|
var MAX_SCANNED_FILES = 2e4;
|
|
20811
21001
|
function newestWorktreeMtime(root) {
|
|
20812
21002
|
const a = newestTrackedMtime(root);
|
|
@@ -20816,7 +21006,7 @@ function newestWorktreeMtime(root) {
|
|
|
20816
21006
|
function newestTrackedMtime(root) {
|
|
20817
21007
|
let listing;
|
|
20818
21008
|
try {
|
|
20819
|
-
listing =
|
|
21009
|
+
listing = execFileSync12("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
20820
21010
|
cwd: root,
|
|
20821
21011
|
encoding: "utf8",
|
|
20822
21012
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -20831,7 +21021,7 @@ function newestTrackedMtime(root) {
|
|
|
20831
21021
|
if (!rel) continue;
|
|
20832
21022
|
if (++scanned > MAX_SCANNED_FILES) break;
|
|
20833
21023
|
try {
|
|
20834
|
-
const m = statSync13(
|
|
21024
|
+
const m = statSync13(join44(root, rel)).mtimeMs;
|
|
20835
21025
|
if (newest === null || m > newest) newest = m;
|
|
20836
21026
|
} catch {
|
|
20837
21027
|
}
|
|
@@ -20906,21 +21096,21 @@ async function runPeriscopeIdlePass(d) {
|
|
|
20906
21096
|
import { dirname as dirOf2, resolve as resolvePath } from "path";
|
|
20907
21097
|
|
|
20908
21098
|
// src/ssh-keys.ts
|
|
20909
|
-
import { execFileSync as
|
|
20910
|
-
import { existsSync as
|
|
20911
|
-
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";
|
|
20912
21102
|
var SshKeyError = class extends Error {
|
|
20913
21103
|
};
|
|
20914
21104
|
function shipSshKeyPathFor(stateDir) {
|
|
20915
|
-
return
|
|
21105
|
+
return join45(stateDir, "ssh", "id_ed25519");
|
|
20916
21106
|
}
|
|
20917
21107
|
function ensureShipSshKeypair(stateDir) {
|
|
20918
21108
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
20919
21109
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
20920
|
-
if (!
|
|
20921
|
-
|
|
21110
|
+
if (!existsSync42(privateKeyPath)) {
|
|
21111
|
+
mkdirSync29(dirname26(privateKeyPath), { recursive: true });
|
|
20922
21112
|
try {
|
|
20923
|
-
|
|
21113
|
+
execFileSync13("ssh-keygen", [
|
|
20924
21114
|
"-t",
|
|
20925
21115
|
"ed25519",
|
|
20926
21116
|
"-N",
|
|
@@ -20938,15 +21128,15 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
20938
21128
|
}
|
|
20939
21129
|
chmodSync3(privateKeyPath, 384);
|
|
20940
21130
|
}
|
|
20941
|
-
if (!
|
|
21131
|
+
if (!existsSync42(publicKeyPath)) {
|
|
20942
21132
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
20943
21133
|
}
|
|
20944
|
-
const publicKey =
|
|
21134
|
+
const publicKey = readFileSync38(publicKeyPath, "utf8").trim();
|
|
20945
21135
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
20946
21136
|
}
|
|
20947
21137
|
function sshKeygenAvailable() {
|
|
20948
21138
|
try {
|
|
20949
|
-
|
|
21139
|
+
execFileSync13("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
|
|
20950
21140
|
return true;
|
|
20951
21141
|
} catch (e) {
|
|
20952
21142
|
return e.code !== "ENOENT";
|
|
@@ -21105,12 +21295,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
21105
21295
|
import { createServer as createHttpServer } from "http";
|
|
21106
21296
|
import { spawn as spawn8 } from "child_process";
|
|
21107
21297
|
import { randomBytes } from "crypto";
|
|
21108
|
-
import { existsSync as
|
|
21109
|
-
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";
|
|
21110
21300
|
|
|
21111
21301
|
// src/passenger-mirror.ts
|
|
21112
|
-
import { mkdirSync as
|
|
21113
|
-
import { dirname as
|
|
21302
|
+
import { mkdirSync as mkdirSync30 } from "fs";
|
|
21303
|
+
import { dirname as dirname27 } from "path";
|
|
21114
21304
|
function mirrorRepoOf(spec) {
|
|
21115
21305
|
const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
|
|
21116
21306
|
if (fromOrigin) return fromOrigin;
|
|
@@ -21128,9 +21318,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
|
|
|
21128
21318
|
continue;
|
|
21129
21319
|
}
|
|
21130
21320
|
try {
|
|
21131
|
-
const parent =
|
|
21132
|
-
|
|
21133
|
-
|
|
21321
|
+
const parent = dirname27(spec.dir);
|
|
21322
|
+
mkdirSync30(parent, { recursive: true });
|
|
21323
|
+
mkdirSync30(spec.dir, { recursive: true });
|
|
21134
21324
|
const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
|
|
21135
21325
|
out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
|
|
21136
21326
|
} catch (e) {
|
|
@@ -21236,21 +21426,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
21236
21426
|
return child;
|
|
21237
21427
|
};
|
|
21238
21428
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
21239
|
-
return
|
|
21429
|
+
return join46(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
21240
21430
|
}
|
|
21241
21431
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
21242
21432
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
21243
|
-
if (!
|
|
21433
|
+
if (!existsSync43(path)) return void 0;
|
|
21244
21434
|
try {
|
|
21245
|
-
return JSON.parse(
|
|
21435
|
+
return JSON.parse(readFileSync39(path, "utf8"));
|
|
21246
21436
|
} catch {
|
|
21247
21437
|
return void 0;
|
|
21248
21438
|
}
|
|
21249
21439
|
}
|
|
21250
21440
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
21251
21441
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
21252
|
-
|
|
21253
|
-
|
|
21442
|
+
mkdirSync31(join46(stateDir, "passengers"), { recursive: true });
|
|
21443
|
+
writeFileSync32(path, `${JSON.stringify(state, null, 2)}
|
|
21254
21444
|
`);
|
|
21255
21445
|
}
|
|
21256
21446
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -21270,8 +21460,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
21270
21460
|
}
|
|
21271
21461
|
}
|
|
21272
21462
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
21273
|
-
const dir =
|
|
21274
|
-
if (!
|
|
21463
|
+
const dir = join46(stateDir, "passengers");
|
|
21464
|
+
if (!existsSync43(dir)) return [];
|
|
21275
21465
|
const suffix = "-sync.json";
|
|
21276
21466
|
return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
21277
21467
|
}
|
|
@@ -21359,8 +21549,8 @@ function describeCause(err3) {
|
|
|
21359
21549
|
}
|
|
21360
21550
|
|
|
21361
21551
|
// src/daemon.ts
|
|
21362
|
-
import { existsSync as
|
|
21363
|
-
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";
|
|
21364
21554
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
21365
21555
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
21366
21556
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -21448,7 +21638,7 @@ function latestMtime(path) {
|
|
|
21448
21638
|
if (!stat2.isDirectory()) return 0;
|
|
21449
21639
|
let latest = stat2.mtimeMs;
|
|
21450
21640
|
for (const entry of readdirSync17(path, { withFileTypes: true })) {
|
|
21451
|
-
latest = Math.max(latest, latestMtime(
|
|
21641
|
+
latest = Math.max(latest, latestMtime(join47(path, entry.name)));
|
|
21452
21642
|
}
|
|
21453
21643
|
return latest;
|
|
21454
21644
|
}
|
|
@@ -21457,10 +21647,10 @@ function computeTreeSignature(paths) {
|
|
|
21457
21647
|
}
|
|
21458
21648
|
function makeStaleChecker(paths, opts = {}) {
|
|
21459
21649
|
const initial = computeTreeSignature(paths);
|
|
21460
|
-
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 &&
|
|
21650
|
+
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync44(opts.reloadSentinel);
|
|
21461
21651
|
}
|
|
21462
21652
|
function reloadSentinelFor(stateDir) {
|
|
21463
|
-
return
|
|
21653
|
+
return join47(stateDir, "reload-requested");
|
|
21464
21654
|
}
|
|
21465
21655
|
function readReloadRequestedAt(stateDir) {
|
|
21466
21656
|
try {
|
|
@@ -21470,17 +21660,17 @@ function readReloadRequestedAt(stateDir) {
|
|
|
21470
21660
|
}
|
|
21471
21661
|
}
|
|
21472
21662
|
function daemonStampFor(stateDir) {
|
|
21473
|
-
return
|
|
21663
|
+
return join47(stateDir, "daemon.started");
|
|
21474
21664
|
}
|
|
21475
21665
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
21476
|
-
|
|
21666
|
+
mkdirSync32(stateDir, { recursive: true });
|
|
21477
21667
|
rmSync20(reloadSentinelFor(stateDir), { force: true });
|
|
21478
|
-
|
|
21668
|
+
writeFileSync33(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
21479
21669
|
`);
|
|
21480
21670
|
}
|
|
21481
21671
|
function readDaemonStarted(stateDir) {
|
|
21482
21672
|
try {
|
|
21483
|
-
const v = JSON.parse(
|
|
21673
|
+
const v = JSON.parse(readFileSync40(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
21484
21674
|
return typeof v === "number" ? v : null;
|
|
21485
21675
|
} catch {
|
|
21486
21676
|
return null;
|
|
@@ -22046,7 +22236,7 @@ import { createHash as createHash10 } from "crypto";
|
|
|
22046
22236
|
import {
|
|
22047
22237
|
chmodSync as chmodSync4,
|
|
22048
22238
|
closeSync as closeSync5,
|
|
22049
|
-
existsSync as
|
|
22239
|
+
existsSync as existsSync45,
|
|
22050
22240
|
fstatSync,
|
|
22051
22241
|
openSync as openSync5,
|
|
22052
22242
|
readSync as readSync3,
|
|
@@ -22057,7 +22247,7 @@ import {
|
|
|
22057
22247
|
} from "fs";
|
|
22058
22248
|
import { createConnection } from "net";
|
|
22059
22249
|
import { createServer } from "http";
|
|
22060
|
-
import { basename as basename14, dirname as
|
|
22250
|
+
import { basename as basename14, dirname as dirname28, join as join48 } from "path";
|
|
22061
22251
|
import { execFile as execFile3, spawn as spawn10 } from "child_process";
|
|
22062
22252
|
|
|
22063
22253
|
// src/periscope-control.ts
|
|
@@ -22286,7 +22476,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
|
|
|
22286
22476
|
// src/control-api.ts
|
|
22287
22477
|
var PAUSABLE = ["dev", "design", "qa"];
|
|
22288
22478
|
var CONTROL_SOCKET_NAME = "control.sock";
|
|
22289
|
-
var controlSocketFor = (stateDir) =>
|
|
22479
|
+
var controlSocketFor = (stateDir) => join48(stateDir, CONTROL_SOCKET_NAME);
|
|
22290
22480
|
var MAX_SOCKET_PATH = 100;
|
|
22291
22481
|
var MAX_TAIL_BYTES = 1024 * 1024;
|
|
22292
22482
|
var MAX_WAIT_SECONDS = 30;
|
|
@@ -22373,7 +22563,7 @@ function candidates(live, rotated) {
|
|
|
22373
22563
|
const out = [live];
|
|
22374
22564
|
if (!rotated) return out;
|
|
22375
22565
|
try {
|
|
22376
|
-
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));
|
|
22377
22567
|
} catch {
|
|
22378
22568
|
}
|
|
22379
22569
|
return out;
|
|
@@ -22417,7 +22607,7 @@ function lastLines2(path, n) {
|
|
|
22417
22607
|
}
|
|
22418
22608
|
function readTail(live, opts) {
|
|
22419
22609
|
const max = opts.maxBytes ?? MAX_TAIL_BYTES;
|
|
22420
|
-
if (!
|
|
22610
|
+
if (!existsSync45(live)) {
|
|
22421
22611
|
return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
|
|
22422
22612
|
}
|
|
22423
22613
|
const cur = opts.after ? decodeCursor(opts.after) : null;
|
|
@@ -22533,7 +22723,7 @@ async function tailReply(path, q2, rotated, abort) {
|
|
|
22533
22723
|
const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
|
|
22534
22724
|
let r = readTail(path, { after, tail: tail3, rotated });
|
|
22535
22725
|
while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
|
|
22536
|
-
await waitForChange(
|
|
22726
|
+
await waitForChange(dirname28(path), deadline - Date.now(), abort);
|
|
22537
22727
|
r = readTail(path, { after: r.cursor, rotated });
|
|
22538
22728
|
}
|
|
22539
22729
|
return reply2(200, r);
|
|
@@ -22573,8 +22763,8 @@ async function route(deps, method, url, req, abort) {
|
|
|
22573
22763
|
if (p.startsWith("/streams/") && get2) {
|
|
22574
22764
|
const run3 = decodeURIComponent(p.slice("/streams/".length));
|
|
22575
22765
|
if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
|
|
22576
|
-
const file =
|
|
22577
|
-
if (!
|
|
22766
|
+
const file = join48(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
|
|
22767
|
+
if (!existsSync45(file)) return err2(404, "no such run");
|
|
22578
22768
|
return tailReply(file, url.searchParams, false, abort);
|
|
22579
22769
|
}
|
|
22580
22770
|
if ((p === "/pause" || p === "/resume") && post3) {
|
|
@@ -22618,7 +22808,7 @@ function send(res, r) {
|
|
|
22618
22808
|
res.end(body);
|
|
22619
22809
|
}
|
|
22620
22810
|
async function socketIsStale(path) {
|
|
22621
|
-
if (!
|
|
22811
|
+
if (!existsSync45(path)) return false;
|
|
22622
22812
|
return new Promise((resolve8) => {
|
|
22623
22813
|
const c = createConnection(path);
|
|
22624
22814
|
c.once("connect", () => {
|
|
@@ -22637,7 +22827,7 @@ async function startControlServer(deps) {
|
|
|
22637
22827
|
return null;
|
|
22638
22828
|
}
|
|
22639
22829
|
if (await socketIsStale(path)) rmSync21(path, { force: true });
|
|
22640
|
-
else if (
|
|
22830
|
+
else if (existsSync45(path)) {
|
|
22641
22831
|
log(`control API not started: ${path} is in use by another process`);
|
|
22642
22832
|
return null;
|
|
22643
22833
|
}
|
|
@@ -22723,31 +22913,31 @@ function crewJson(args, timeoutMs = 6e4) {
|
|
|
22723
22913
|
|
|
22724
22914
|
// src/periscope-mcp.ts
|
|
22725
22915
|
import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
|
|
22726
|
-
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";
|
|
22727
22917
|
import { z } from "zod";
|
|
22728
22918
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
22729
22919
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
22730
22920
|
|
|
22731
22921
|
// src/periscope-endpoints.ts
|
|
22732
22922
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
22733
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
22734
|
-
import { join as
|
|
22735
|
-
var secretDir = (stateDir) =>
|
|
22736
|
-
var secretFile = (stateDir, id) =>
|
|
22737
|
-
var endpointDir = (stateDir) =>
|
|
22738
|
-
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`);
|
|
22739
22929
|
function mintPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22740
|
-
|
|
22930
|
+
mkdirSync33(secretDir(stateDir), { recursive: true, mode: 448 });
|
|
22741
22931
|
const secret = randomBytes3(32).toString("base64url");
|
|
22742
22932
|
const file = secretFile(stateDir, ticketRecordId);
|
|
22743
|
-
|
|
22933
|
+
writeFileSync34(file, `${JSON.stringify({ secret })}
|
|
22744
22934
|
`, { mode: 384 });
|
|
22745
22935
|
chmodSync5(file, 384);
|
|
22746
22936
|
return secret;
|
|
22747
22937
|
}
|
|
22748
22938
|
function readPeriscopeSecret(stateDir, ticketRecordId) {
|
|
22749
22939
|
try {
|
|
22750
|
-
const v = JSON.parse(
|
|
22940
|
+
const v = JSON.parse(readFileSync41(secretFile(stateDir, ticketRecordId), "utf8"));
|
|
22751
22941
|
return typeof v.secret === "string" && v.secret ? v.secret : void 0;
|
|
22752
22942
|
} catch {
|
|
22753
22943
|
return void 0;
|
|
@@ -22758,14 +22948,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
|
|
|
22758
22948
|
}
|
|
22759
22949
|
function readPeriscopeEndpoint(stateDir, ticketRecordId) {
|
|
22760
22950
|
try {
|
|
22761
|
-
return JSON.parse(
|
|
22951
|
+
return JSON.parse(readFileSync41(endpointFile(stateDir, ticketRecordId), "utf8"));
|
|
22762
22952
|
} catch {
|
|
22763
22953
|
return void 0;
|
|
22764
22954
|
}
|
|
22765
22955
|
}
|
|
22766
22956
|
function writePeriscopeEndpoint(stateDir, rec) {
|
|
22767
|
-
|
|
22768
|
-
|
|
22957
|
+
mkdirSync33(endpointDir(stateDir), { recursive: true });
|
|
22958
|
+
writeFileSync34(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
|
|
22769
22959
|
`);
|
|
22770
22960
|
}
|
|
22771
22961
|
function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
@@ -22773,7 +22963,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
|
|
|
22773
22963
|
}
|
|
22774
22964
|
function listPeriscopeEndpoints(stateDir) {
|
|
22775
22965
|
const dir = endpointDir(stateDir);
|
|
22776
|
-
if (!
|
|
22966
|
+
if (!existsSync46(dir)) return [];
|
|
22777
22967
|
const out = [];
|
|
22778
22968
|
for (const name of readdirSync19(dir)) {
|
|
22779
22969
|
if (!name.endsWith(".json")) continue;
|
|
@@ -22861,10 +23051,10 @@ async function realAncestor(p) {
|
|
|
22861
23051
|
}
|
|
22862
23052
|
if (isLink) {
|
|
22863
23053
|
if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
|
|
22864
|
-
cur = resolve6(
|
|
23054
|
+
cur = resolve6(dirname29(cur), await readlink(cur));
|
|
22865
23055
|
continue;
|
|
22866
23056
|
}
|
|
22867
|
-
const parent =
|
|
23057
|
+
const parent = dirname29(cur);
|
|
22868
23058
|
if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
|
|
22869
23059
|
tail3.push(basename15(cur));
|
|
22870
23060
|
cur = parent;
|
|
@@ -22876,13 +23066,13 @@ async function resolveInWorktree(root, requested) {
|
|
|
22876
23066
|
let abs;
|
|
22877
23067
|
if (isAbsolute4(requested)) {
|
|
22878
23068
|
abs = resolve6(requested);
|
|
22879
|
-
if (isInside(root, abs)) abs =
|
|
23069
|
+
if (isInside(root, abs)) abs = join50(realRoot, relative2(root, abs));
|
|
22880
23070
|
} else {
|
|
22881
23071
|
abs = resolve6(realRoot, requested);
|
|
22882
23072
|
}
|
|
22883
23073
|
if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22884
23074
|
const { real, rest } = await realAncestor(abs);
|
|
22885
|
-
const final = rest ?
|
|
23075
|
+
const final = rest ? join50(real, rest) : real;
|
|
22886
23076
|
if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
|
|
22887
23077
|
if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
|
|
22888
23078
|
return final;
|
|
@@ -22950,7 +23140,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22950
23140
|
const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
|
|
22951
23141
|
const lines2 = await Promise.all(entries.map(async (e) => {
|
|
22952
23142
|
if (e.isDirectory()) return `[DIR] ${e.name}`;
|
|
22953
|
-
const size = (await lstat(
|
|
23143
|
+
const size = (await lstat(join50(abs, e.name))).size;
|
|
22954
23144
|
return `[FILE] ${e.name} (${size} bytes)`;
|
|
22955
23145
|
}));
|
|
22956
23146
|
return text(lines2.join("\n") || "(empty)");
|
|
@@ -22975,7 +23165,7 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
22975
23165
|
if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
|
|
22976
23166
|
const abs = await inTree(path);
|
|
22977
23167
|
if (abs === await realpath(root)) return text("path must name a file", true);
|
|
22978
|
-
await mkdir(
|
|
23168
|
+
await mkdir(dirname29(abs), { recursive: true });
|
|
22979
23169
|
await inTree(path);
|
|
22980
23170
|
await writeFile(abs, content);
|
|
22981
23171
|
return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
|
|
@@ -23015,8 +23205,8 @@ function createPeriscopeMcpServer(d, sc) {
|
|
|
23015
23205
|
inputSchema: { path: z.string() },
|
|
23016
23206
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
23017
23207
|
}, async ({ path }) => {
|
|
23018
|
-
const parent = await inTree(
|
|
23019
|
-
const abs =
|
|
23208
|
+
const parent = await inTree(dirname29(path) === "" ? "." : dirname29(path));
|
|
23209
|
+
const abs = join50(parent, basename15(path));
|
|
23020
23210
|
if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
|
|
23021
23211
|
const st = await lstat(abs).catch(() => void 0);
|
|
23022
23212
|
if (!st) return text(`${path} does not exist`, true);
|
|
@@ -23137,7 +23327,7 @@ function findPeriscope(sidecars, segment) {
|
|
|
23137
23327
|
}
|
|
23138
23328
|
|
|
23139
23329
|
// src/cli.ts
|
|
23140
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
23330
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname30(process.execPath) : resolve7(dirname30(fileURLToPath(import.meta.url)), "..");
|
|
23141
23331
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
23142
23332
|
var CYCLE_SECONDS2 = 120;
|
|
23143
23333
|
var STALE_CYCLES = 3;
|
|
@@ -23505,7 +23695,7 @@ if (command === "provision") {
|
|
|
23505
23695
|
process.exit(outcome.code);
|
|
23506
23696
|
}
|
|
23507
23697
|
if (command === "pool-member") {
|
|
23508
|
-
const pool = poolConfigFrom(argv, process.env, () =>
|
|
23698
|
+
const pool = poolConfigFrom(argv, process.env, () => readFileSync42(0, "utf8"));
|
|
23509
23699
|
if (typeof pool === "string") {
|
|
23510
23700
|
process.stderr.write(`crew pool-member: ${pool}
|
|
23511
23701
|
`);
|
|
@@ -23733,7 +23923,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23733
23923
|
} else {
|
|
23734
23924
|
const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
|
|
23735
23925
|
const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
|
|
23736
|
-
if (configless && !
|
|
23926
|
+
if (configless && !existsSync47(findConfigFile(CREW_HOME))) {
|
|
23737
23927
|
cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
|
|
23738
23928
|
} else {
|
|
23739
23929
|
cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
|
|
@@ -23748,7 +23938,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
23748
23938
|
throw e;
|
|
23749
23939
|
}
|
|
23750
23940
|
if (command === "credential") {
|
|
23751
|
-
const r = runCredential(positional[1],
|
|
23941
|
+
const r = runCredential(positional[1], readFileSync42(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
|
|
23752
23942
|
process.stdout.write(r.out);
|
|
23753
23943
|
process.stderr.write(r.err);
|
|
23754
23944
|
process.exit(r.code);
|
|
@@ -23760,9 +23950,9 @@ if (command === "passenger-sync-daemon") {
|
|
|
23760
23950
|
forges: cfg.ship.forges,
|
|
23761
23951
|
stateDir: cfg.ship.stateDir,
|
|
23762
23952
|
configFile: cfg.configFile,
|
|
23763
|
-
readFile: (p) =>
|
|
23764
|
-
writeFile: (p, t) =>
|
|
23765
|
-
stdin: () =>
|
|
23953
|
+
readFile: (p) => readFileSync42(p, "utf8"),
|
|
23954
|
+
writeFile: (p, t) => writeFileSync35(p, t),
|
|
23955
|
+
stdin: () => readFileSync42(0, "utf8"),
|
|
23766
23956
|
env: process.env
|
|
23767
23957
|
}
|
|
23768
23958
|
);
|
|
@@ -24055,8 +24245,8 @@ if (command === "passenger-sync-daemon") {
|
|
|
24055
24245
|
}
|
|
24056
24246
|
if (host === "linux" && hasSystemd) {
|
|
24057
24247
|
const label = labelFor(CREW_HOME, "run");
|
|
24058
|
-
const staleTimer =
|
|
24059
|
-
if (
|
|
24248
|
+
const staleTimer = join51(homedir11(), ".config", "systemd", "user", `${label}.timer`);
|
|
24249
|
+
if (existsSync47(staleTimer)) {
|
|
24060
24250
|
process.stdout.write(
|
|
24061
24251
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
24062
24252
|
`
|
|
@@ -24083,7 +24273,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24083
24273
|
const tracker = new Tracker(c, cfg.ship);
|
|
24084
24274
|
problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
|
|
24085
24275
|
for (const r of await resolvedRepos(c)) {
|
|
24086
|
-
if (!
|
|
24276
|
+
if (!existsSync47(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
|
|
24087
24277
|
if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
|
|
24088
24278
|
readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
|
|
24089
24279
|
}
|
|
@@ -24110,7 +24300,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24110
24300
|
}
|
|
24111
24301
|
async function doctorFix(o) {
|
|
24112
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))));
|
|
24113
|
-
const unitExists = (p) =>
|
|
24303
|
+
const unitExists = (p) => existsSync47(p);
|
|
24114
24304
|
const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
|
|
24115
24305
|
const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
|
|
24116
24306
|
const plan = planFix({ routes, schedulerAbsent, host: o.host });
|
|
@@ -24127,7 +24317,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
24127
24317
|
for (const a of plan.actions) {
|
|
24128
24318
|
if (a.kind === "enable") {
|
|
24129
24319
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
24130
|
-
|
|
24320
|
+
writeFileSync35(cfg.configFile, planEnable(readFileSync42(cfg.configFile, "utf8"), a.route));
|
|
24131
24321
|
enabledSound.add(a.route);
|
|
24132
24322
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
24133
24323
|
} else {
|
|
@@ -25266,7 +25456,7 @@ ${cu.tail.trimEnd()}
|
|
|
25266
25456
|
process.exit(1);
|
|
25267
25457
|
}
|
|
25268
25458
|
requireArmed("run the daemon loop");
|
|
25269
|
-
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")];
|
|
25270
25460
|
const configPaths = daemonConfigPaths(cfg);
|
|
25271
25461
|
watchPaths.push(...configPaths);
|
|
25272
25462
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -25571,10 +25761,10 @@ ${cu.tail.trimEnd()}
|
|
|
25571
25761
|
`);
|
|
25572
25762
|
if (!dryRun) {
|
|
25573
25763
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
25574
|
-
const raw =
|
|
25764
|
+
const raw = existsSync47(resolvedPath) ? JSON.parse(readFileSync42(resolvedPath, "utf8")) : {};
|
|
25575
25765
|
raw.agentSkills = result.agentSkills;
|
|
25576
|
-
|
|
25577
|
-
|
|
25766
|
+
mkdirSync34(dirOf2(resolvedPath), { recursive: true });
|
|
25767
|
+
writeFileSync35(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
25578
25768
|
`);
|
|
25579
25769
|
}
|
|
25580
25770
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -25597,7 +25787,7 @@ ${cu.tail.trimEnd()}
|
|
|
25597
25787
|
throw e;
|
|
25598
25788
|
}
|
|
25599
25789
|
const oldName = cfg.ship.name;
|
|
25600
|
-
|
|
25790
|
+
writeFileSync35(cfg.configFile, setShipNameInYaml(readFileSync42(cfg.configFile, "utf8"), newName));
|
|
25601
25791
|
cfg.ship.name = newName;
|
|
25602
25792
|
let renameMachineId;
|
|
25603
25793
|
try {
|
|
@@ -25644,14 +25834,14 @@ ${cu.tail.trimEnd()}
|
|
|
25644
25834
|
process.exit(2);
|
|
25645
25835
|
};
|
|
25646
25836
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
|
|
25647
|
-
const configText =
|
|
25837
|
+
const configText = readFileSync42(cfg.configFile, "utf8");
|
|
25648
25838
|
if (sub === "check") {
|
|
25649
25839
|
try {
|
|
25650
25840
|
const routeArg = value("route") ?? positional[2];
|
|
25651
25841
|
const found = cfg.routes.find((r) => r.route === routeArg);
|
|
25652
25842
|
if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
|
|
25653
25843
|
const named2 = value("route") ? void 0 : positional[3];
|
|
25654
|
-
const dir = named2 ? repoDirOf(configText,
|
|
25844
|
+
const dir = named2 ? repoDirOf(configText, dirname30(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
|
|
25655
25845
|
const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
|
|
25656
25846
|
const o = target ? found.repoOverrides[target.name] : void 0;
|
|
25657
25847
|
runReposCheck(dir, {
|
|
@@ -25674,9 +25864,9 @@ ${cu.tail.trimEnd()}
|
|
|
25674
25864
|
if (sub === "hooks") {
|
|
25675
25865
|
try {
|
|
25676
25866
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
25677
|
-
const dir = repoDirOf(configText,
|
|
25678
|
-
if (!
|
|
25679
|
-
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) => {
|
|
25680
25870
|
try {
|
|
25681
25871
|
return readdirSync20(resolvePath(dir, r));
|
|
25682
25872
|
} catch {
|
|
@@ -25684,7 +25874,7 @@ ${cu.tail.trimEnd()}
|
|
|
25684
25874
|
}
|
|
25685
25875
|
}, read: (r) => {
|
|
25686
25876
|
try {
|
|
25687
|
-
return
|
|
25877
|
+
return readFileSync42(resolvePath(dir, r), "utf8");
|
|
25688
25878
|
} catch {
|
|
25689
25879
|
return void 0;
|
|
25690
25880
|
}
|
|
@@ -25726,19 +25916,19 @@ ${cu.tail.trimEnd()}
|
|
|
25726
25916
|
let cleared;
|
|
25727
25917
|
if (clear2) {
|
|
25728
25918
|
const plan = clearRouteHooks(configText, a.route, a.name);
|
|
25729
|
-
if (plan.removed.length > 0)
|
|
25919
|
+
if (plan.removed.length > 0) writeFileSync35(cfg.configFile, plan.text);
|
|
25730
25920
|
cleared = { file: cfg.configFile, removed: plan.removed };
|
|
25731
25921
|
}
|
|
25732
25922
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
25733
25923
|
if (write === "route") {
|
|
25734
25924
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
|
|
25735
|
-
|
|
25925
|
+
writeFileSync35(cfg.configFile, plan.text);
|
|
25736
25926
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
|
|
25737
25927
|
} else if (write === "repo") {
|
|
25738
25928
|
const have = existingRepoConfig(files);
|
|
25739
25929
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
25740
25930
|
const file = resolvePath(dir, ".crew.yaml");
|
|
25741
|
-
|
|
25931
|
+
writeFileSync35(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
25742
25932
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
|
|
25743
25933
|
}
|
|
25744
25934
|
if (flag("json")) {
|
|
@@ -25794,7 +25984,7 @@ ${cu.tail.trimEnd()}
|
|
|
25794
25984
|
if (sub === "list" && !routeName) usage2();
|
|
25795
25985
|
if (sub === "add") {
|
|
25796
25986
|
try {
|
|
25797
|
-
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) =>
|
|
25987
|
+
const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync47(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
|
|
25798
25988
|
routeName = a.route;
|
|
25799
25989
|
addPath = a.path;
|
|
25800
25990
|
addName = a.name;
|
|
@@ -25824,7 +26014,7 @@ ${cu.tail.trimEnd()}
|
|
|
25824
26014
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
|
|
25825
26015
|
if (sub === "list" || sub === "add") {
|
|
25826
26016
|
const liveRoute = cfg.routes.find((r) => r.route === routeName);
|
|
25827
|
-
if (liveRoute?.resolved?.reposModelId &&
|
|
26017
|
+
if (liveRoute?.resolved?.reposModelId && existsSync47(resolvedFile) && !dryRun) {
|
|
25828
26018
|
try {
|
|
25829
26019
|
await hydrateApiKeys([liveRoute]);
|
|
25830
26020
|
persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
|
|
@@ -25833,16 +26023,16 @@ ${cu.tail.trimEnd()}
|
|
|
25833
26023
|
}
|
|
25834
26024
|
}
|
|
25835
26025
|
let resolvedRemotes;
|
|
25836
|
-
if (
|
|
26026
|
+
if (existsSync47(resolvedFile)) {
|
|
25837
26027
|
try {
|
|
25838
|
-
resolvedRemotes = JSON.parse(
|
|
26028
|
+
resolvedRemotes = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
25839
26029
|
} catch {
|
|
25840
26030
|
}
|
|
25841
26031
|
}
|
|
25842
26032
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
25843
26033
|
const text2 = configText;
|
|
25844
|
-
const base =
|
|
25845
|
-
const isCheckout = (d) =>
|
|
26034
|
+
const base = dirname30(cfg.configFile);
|
|
26035
|
+
const isCheckout = (d) => existsSync47(`${d}/.git`);
|
|
25846
26036
|
try {
|
|
25847
26037
|
if (sub === "list") {
|
|
25848
26038
|
if (flag("json")) {
|
|
@@ -25852,11 +26042,11 @@ ${cu.tail.trimEnd()}
|
|
|
25852
26042
|
base,
|
|
25853
26043
|
resolved: resolvedRemotes,
|
|
25854
26044
|
originOf,
|
|
25855
|
-
exists: (d) =>
|
|
26045
|
+
exists: (d) => existsSync47(d),
|
|
25856
26046
|
isCheckout,
|
|
25857
26047
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
25858
26048
|
try {
|
|
25859
|
-
return Object.keys(parseRepoConfig(
|
|
26049
|
+
return Object.keys(parseRepoConfig(readFileSync42(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
25860
26050
|
} catch {
|
|
25861
26051
|
return false;
|
|
25862
26052
|
}
|
|
@@ -25873,7 +26063,7 @@ ${cu.tail.trimEnd()}
|
|
|
25873
26063
|
for (const r of repos) {
|
|
25874
26064
|
const origin2 = originOf(r.dir);
|
|
25875
26065
|
const row = matchRepoName(origin2, resolvedRemotes);
|
|
25876
|
-
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)"}`;
|
|
25877
26067
|
process.stdout.write(`${r.name} ${r.dir} ${note}
|
|
25878
26068
|
`);
|
|
25879
26069
|
}
|
|
@@ -25885,9 +26075,9 @@ ${cu.tail.trimEnd()}
|
|
|
25885
26075
|
` : `${human}
|
|
25886
26076
|
`);
|
|
25887
26077
|
const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
25888
|
-
if (
|
|
26078
|
+
if (existsSync47(plan2.target) && (existsSync47(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
|
|
25889
26079
|
throw new ReposError(
|
|
25890
|
-
|
|
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`
|
|
25891
26081
|
);
|
|
25892
26082
|
}
|
|
25893
26083
|
if (dryRun) {
|
|
@@ -25905,14 +26095,14 @@ ${cu.tail.trimEnd()}
|
|
|
25905
26095
|
throw new ReposError(`${e.message}${authHint}
|
|
25906
26096
|
crew.yaml was not changed`);
|
|
25907
26097
|
}
|
|
25908
|
-
|
|
26098
|
+
writeFileSync35(cfg.configFile, plan2.text);
|
|
25909
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:
|
|
25910
26100
|
`);
|
|
25911
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}`);
|
|
25912
26102
|
break;
|
|
25913
26103
|
}
|
|
25914
26104
|
const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
|
|
25915
|
-
const top =
|
|
26105
|
+
const top = existsSync47(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
|
|
25916
26106
|
if (!top) throw new ReposError(`${target} is not a git checkout`);
|
|
25917
26107
|
const origin = originOf(top);
|
|
25918
26108
|
const plan = planRepoAdd({
|
|
@@ -25964,15 +26154,15 @@ ${cu.tail.trimEnd()}
|
|
|
25964
26154
|
const created = await rowTracker.createRepoRow(rowPayload);
|
|
25965
26155
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
25966
26156
|
`);
|
|
25967
|
-
if (
|
|
25968
|
-
const raw = JSON.parse(
|
|
26157
|
+
if (existsSync47(resolvedFile)) {
|
|
26158
|
+
const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
25969
26159
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
25970
26160
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
25971
|
-
|
|
26161
|
+
writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
25972
26162
|
`);
|
|
25973
26163
|
}
|
|
25974
26164
|
}
|
|
25975
|
-
|
|
26165
|
+
writeFileSync35(cfg.configFile, plan.text);
|
|
25976
26166
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
25977
26167
|
`);
|
|
25978
26168
|
} catch (e) {
|
|
@@ -26341,7 +26531,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26341
26531
|
process.exit(2);
|
|
26342
26532
|
}
|
|
26343
26533
|
if (found.shipNameAdopted && !dryRun) {
|
|
26344
|
-
|
|
26534
|
+
writeFileSync35(cfg.configFile, setShipNameInYaml(readFileSync42(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
26345
26535
|
cfg.ship.name = found.shipNameAdopted;
|
|
26346
26536
|
}
|
|
26347
26537
|
if (found.shipRow) {
|
|
@@ -26384,10 +26574,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26384
26574
|
}
|
|
26385
26575
|
}
|
|
26386
26576
|
let previous;
|
|
26387
|
-
const hadPreviousFile =
|
|
26577
|
+
const hadPreviousFile = existsSync47(resolvedPath);
|
|
26388
26578
|
if (hadPreviousFile) {
|
|
26389
26579
|
try {
|
|
26390
|
-
previous = JSON.parse(
|
|
26580
|
+
previous = JSON.parse(readFileSync42(resolvedPath, "utf8"));
|
|
26391
26581
|
} catch {
|
|
26392
26582
|
}
|
|
26393
26583
|
}
|
|
@@ -26517,10 +26707,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26517
26707
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
26518
26708
|
`);
|
|
26519
26709
|
} else {
|
|
26520
|
-
|
|
26710
|
+
mkdirSync34(dirname30(resolvedPath), { recursive: true });
|
|
26521
26711
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
26522
26712
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
26523
|
-
|
|
26713
|
+
writeFileSync35(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
26524
26714
|
`);
|
|
26525
26715
|
renameSync6(tmpPath, resolvedPath);
|
|
26526
26716
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -26566,8 +26756,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
26566
26756
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
26567
26757
|
`);
|
|
26568
26758
|
} else {
|
|
26569
|
-
|
|
26570
|
-
|
|
26759
|
+
mkdirSync34(dirname30(apiKeyFile), { recursive: true });
|
|
26760
|
+
writeFileSync35(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
26571
26761
|
`, { mode: 384 });
|
|
26572
26762
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
26573
26763
|
`);
|
|
@@ -27442,7 +27632,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
27442
27632
|
const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
|
|
27443
27633
|
const resumed = await runPeriscopeResume({
|
|
27444
27634
|
find: async () => hit && { row: hit.row, tag: hit.tag },
|
|
27445
|
-
worktreeExists: (p) =>
|
|
27635
|
+
worktreeExists: (p) => existsSync47(p),
|
|
27446
27636
|
prepareDropFile,
|
|
27447
27637
|
caps: periscopeCaps(cfg.ship),
|
|
27448
27638
|
runHook: async (name, cwd, env) => {
|
|
@@ -27537,10 +27727,10 @@ ${where.join("\n")}`);
|
|
|
27537
27727
|
},
|
|
27538
27728
|
prepare: async (path) => {
|
|
27539
27729
|
for (const f of repoP.config.worktrees.copy) {
|
|
27540
|
-
const src =
|
|
27541
|
-
if (!
|
|
27542
|
-
|
|
27543
|
-
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));
|
|
27544
27734
|
}
|
|
27545
27735
|
const setup = repoP.config.hooks.setup;
|
|
27546
27736
|
return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
|
|
@@ -27549,10 +27739,11 @@ ${where.join("\n")}`);
|
|
|
27549
27739
|
},
|
|
27550
27740
|
runHook: async (name, cwd, env) => {
|
|
27551
27741
|
const script = repoP.config.hooks[name];
|
|
27552
|
-
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;
|
|
27553
27743
|
},
|
|
27554
27744
|
createRow: (fields) => trackerP.createPeriscope(fields),
|
|
27555
27745
|
prepareDropFile,
|
|
27746
|
+
registerCode: registerCodeServer,
|
|
27556
27747
|
recordLocal: (sc) => {
|
|
27557
27748
|
writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
|
|
27558
27749
|
if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
|
|
@@ -27612,6 +27803,7 @@ ${where.join("\n")}`);
|
|
|
27612
27803
|
},
|
|
27613
27804
|
now: () => /* @__PURE__ */ new Date(),
|
|
27614
27805
|
host: a.host ?? "localhost",
|
|
27806
|
+
intelStartupSeconds: cfg.ship.intelStartupSeconds,
|
|
27615
27807
|
say
|
|
27616
27808
|
};
|
|
27617
27809
|
if (dryRun) {
|
|
@@ -27695,7 +27887,7 @@ ${where.join("\n")}`);
|
|
|
27695
27887
|
}
|
|
27696
27888
|
case "sync": {
|
|
27697
27889
|
emit.enter("worktree");
|
|
27698
|
-
let
|
|
27890
|
+
let tracked2 = 0;
|
|
27699
27891
|
let acted = 0;
|
|
27700
27892
|
let baseChecked = 0;
|
|
27701
27893
|
let baseActed = 0;
|
|
@@ -27733,7 +27925,7 @@ ${where.join("\n")}`);
|
|
|
27733
27925
|
}
|
|
27734
27926
|
if (!s2.upstream) continue;
|
|
27735
27927
|
if (w.branch !== r.config.branch.base && s2.upstream === `${r.config.branch.remote}/${r.config.branch.base}`) continue;
|
|
27736
|
-
|
|
27928
|
+
tracked2++;
|
|
27737
27929
|
if (s2.canFastForward) {
|
|
27738
27930
|
acted++;
|
|
27739
27931
|
if (dryRun) emit.emit(`would fast-forward ${w.branch}: ${s2.detail}`);
|
|
@@ -27746,8 +27938,8 @@ ${where.join("\n")}`);
|
|
|
27746
27938
|
}
|
|
27747
27939
|
if (baseUnsafe > 0) process.exitCode = 1;
|
|
27748
27940
|
if (baseChecked > 0 && baseActed === 0) emit.emit(`${baseChecked} base branch(es) checked, all level with the remote`);
|
|
27749
|
-
if (
|
|
27750
|
-
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`);
|
|
27751
27943
|
break;
|
|
27752
27944
|
}
|
|
27753
27945
|
case "pause":
|
|
@@ -27836,7 +28028,7 @@ ${where.join("\n")}`);
|
|
|
27836
28028
|
};
|
|
27837
28029
|
const droppedRoutes = [];
|
|
27838
28030
|
try {
|
|
27839
|
-
const rawCfg = parseYaml2(
|
|
28031
|
+
const rawCfg = parseYaml2(readFileSync42(cfg.configFile, "utf8"));
|
|
27840
28032
|
for (const r of rawCfg?.routes ?? []) {
|
|
27841
28033
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
27842
28034
|
try {
|
|
@@ -27889,8 +28081,8 @@ ${where.join("\n")}`);
|
|
|
27889
28081
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
27890
28082
|
break;
|
|
27891
28083
|
}
|
|
27892
|
-
|
|
27893
|
-
|
|
28084
|
+
mkdirSync34(cfg.ship.stateDir, { recursive: true });
|
|
28085
|
+
writeFileSync35(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
27894
28086
|
`);
|
|
27895
28087
|
process.stdout.write(
|
|
27896
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"
|
|
@@ -27955,7 +28147,7 @@ ${where.join("\n")}`);
|
|
|
27955
28147
|
}
|
|
27956
28148
|
case "log": {
|
|
27957
28149
|
try {
|
|
27958
|
-
const text2 =
|
|
28150
|
+
const text2 = readFileSync42(cfg.ship.logFile, "utf8").split("\n");
|
|
27959
28151
|
process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
27960
28152
|
`);
|
|
27961
28153
|
} catch {
|
|
@@ -27978,8 +28170,8 @@ ${where.join("\n")}`);
|
|
|
27978
28170
|
};
|
|
27979
28171
|
if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
|
|
27980
28172
|
const tracker = new Tracker(route2, cfg.ship);
|
|
27981
|
-
const configText =
|
|
27982
|
-
const cfgBase =
|
|
28173
|
+
const configText = readFileSync42(cfg.configFile, "utf8");
|
|
28174
|
+
const cfgBase = dirname30(cfg.configFile);
|
|
27983
28175
|
const configured2 = configuredRepos(configText, route2.route, cfgBase);
|
|
27984
28176
|
const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
|
|
27985
28177
|
const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
|
|
@@ -28007,7 +28199,7 @@ ${where.join("\n")}`);
|
|
|
28007
28199
|
}
|
|
28008
28200
|
if (configured2.some((r) => r.dir === dir)) {
|
|
28009
28201
|
try {
|
|
28010
|
-
|
|
28202
|
+
writeFileSync35(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
|
|
28011
28203
|
} catch (e) {
|
|
28012
28204
|
if (e instanceof ReposError) fail(e.message);
|
|
28013
28205
|
throw e;
|
|
@@ -28015,29 +28207,29 @@ ${where.join("\n")}`);
|
|
|
28015
28207
|
}
|
|
28016
28208
|
if (row) {
|
|
28017
28209
|
await tracker.deleteRepoRow(row.id);
|
|
28018
|
-
if (
|
|
28019
|
-
const raw = JSON.parse(
|
|
28210
|
+
if (existsSync47(resolvedFile)) {
|
|
28211
|
+
const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
28020
28212
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
28021
28213
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
28022
|
-
|
|
28214
|
+
writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
28023
28215
|
`);
|
|
28024
28216
|
}
|
|
28025
28217
|
}
|
|
28026
|
-
if (
|
|
28218
|
+
if (existsSync47(join51(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
|
|
28027
28219
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
28028
28220
|
break;
|
|
28029
28221
|
}
|
|
28030
28222
|
const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
28031
|
-
const fresh = !
|
|
28032
|
-
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`);
|
|
28033
28225
|
if (dryRun) {
|
|
28034
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`);
|
|
28035
28227
|
break;
|
|
28036
28228
|
}
|
|
28037
28229
|
if (fresh) {
|
|
28038
|
-
|
|
28039
|
-
|
|
28040
|
-
|
|
28230
|
+
mkdirSync34(dir, { recursive: true });
|
|
28231
|
+
writeFileSync35(join51(dir, "README.md"), SAMPLE_README);
|
|
28232
|
+
writeFileSync35(join51(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
28041
28233
|
const g = (...a) => {
|
|
28042
28234
|
const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
|
|
28043
28235
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -28055,17 +28247,17 @@ ${where.join("\n")}`);
|
|
|
28055
28247
|
spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
|
|
28056
28248
|
});
|
|
28057
28249
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
28058
|
-
if (
|
|
28059
|
-
const raw = JSON.parse(
|
|
28250
|
+
if (existsSync47(resolvedFile)) {
|
|
28251
|
+
const raw = JSON.parse(readFileSync42(resolvedFile, "utf8"));
|
|
28060
28252
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
28061
28253
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
28062
|
-
|
|
28254
|
+
writeFileSync35(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
28063
28255
|
`);
|
|
28064
28256
|
}
|
|
28065
28257
|
}
|
|
28066
28258
|
if (!configured2.some((r) => r.dir === dir)) {
|
|
28067
28259
|
try {
|
|
28068
|
-
|
|
28260
|
+
writeFileSync35(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
|
|
28069
28261
|
} catch (e) {
|
|
28070
28262
|
if (e instanceof ReposError) fail(e.message);
|
|
28071
28263
|
throw e;
|
|
@@ -28411,7 +28603,7 @@ watermark: ${state.watermark()}
|
|
|
28411
28603
|
}
|
|
28412
28604
|
for (const r of repos) {
|
|
28413
28605
|
const remote = r.config.branch.remote;
|
|
28414
|
-
if (
|
|
28606
|
+
if (existsSync47(r.dir)) {
|
|
28415
28607
|
pushAuth.push({
|
|
28416
28608
|
route: c.route,
|
|
28417
28609
|
repo: r.name,
|
|
@@ -28449,7 +28641,8 @@ watermark: ${state.watermark()}
|
|
|
28449
28641
|
notes: validateEffective(r.config),
|
|
28450
28642
|
base: doctorBaseReport(r, r.config.branch.base, baseHealth),
|
|
28451
28643
|
hooks: r.config.hooks,
|
|
28452
|
-
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
|
|
28453
28646
|
});
|
|
28454
28647
|
problems.push(...f.problems);
|
|
28455
28648
|
passed += f.passed;
|
|
@@ -28500,7 +28693,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28500
28693
|
}
|
|
28501
28694
|
try {
|
|
28502
28695
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
28503
|
-
const evSize =
|
|
28696
|
+
const evSize = existsSync47(evFile) ? statSync16(evFile).size : 0;
|
|
28504
28697
|
process.stdout.write(
|
|
28505
28698
|
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
28506
28699
|
` : "events log: ok\n"
|
|
@@ -28511,7 +28704,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
28511
28704
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
|
|
28512
28705
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
28513
28706
|
try {
|
|
28514
|
-
return
|
|
28707
|
+
return readFileSync42(p, "utf8");
|
|
28515
28708
|
} catch {
|
|
28516
28709
|
return null;
|
|
28517
28710
|
}
|