@tablation/crew 0.72.1 → 0.72.3
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 +462 -357
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -130,21 +130,21 @@ function formatStaleOwnLock(scope, holder) {
|
|
|
130
130
|
|
|
131
131
|
// src/selftest.ts
|
|
132
132
|
import { spawn as spawn3 } from "child_process";
|
|
133
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
133
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync16, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
134
134
|
import { tmpdir as tmpdir2 } from "os";
|
|
135
|
-
import { join as
|
|
135
|
+
import { join as join19 } from "path";
|
|
136
136
|
|
|
137
137
|
// src/agent.ts
|
|
138
138
|
import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
|
|
139
|
-
import { readFileSync as
|
|
139
|
+
import { readFileSync as readFileSync15, existsSync as existsSync18, statSync as statSync5, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
140
140
|
import { StringDecoder } from "string_decoder";
|
|
141
141
|
import { finished } from "stream/promises";
|
|
142
|
-
import { join as
|
|
143
|
-
import { createHash as
|
|
142
|
+
import { join as join18, dirname as dirname7, basename as basename7 } from "path";
|
|
143
|
+
import { createHash as createHash3 } from "crypto";
|
|
144
144
|
|
|
145
145
|
// src/config.ts
|
|
146
|
-
import { readFileSync as
|
|
147
|
-
import { resolve, dirname as dirname3, isAbsolute, join as
|
|
146
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
147
|
+
import { resolve, dirname as dirname3, isAbsolute, join as join8, basename as basename3 } from "path";
|
|
148
148
|
import { homedir as homedir5, tmpdir } from "os";
|
|
149
149
|
import { parse as parse2 } from "yaml";
|
|
150
150
|
|
|
@@ -294,10 +294,10 @@ var Emitter = class _Emitter {
|
|
|
294
294
|
var eventFileFor = (stateDir) => join(stateDir, "events.jsonl");
|
|
295
295
|
|
|
296
296
|
// src/pair-install.ts
|
|
297
|
-
import { existsSync as existsSync5, rmSync as rmSync4, writeFileSync as
|
|
297
|
+
import { existsSync as existsSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
298
298
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
299
299
|
import { homedir as homedir4 } from "os";
|
|
300
|
-
import { join as
|
|
300
|
+
import { join as join6 } from "path";
|
|
301
301
|
|
|
302
302
|
// src/stream.ts
|
|
303
303
|
function toolTarget(name, input) {
|
|
@@ -349,9 +349,38 @@ function extractResult(line) {
|
|
|
349
349
|
|
|
350
350
|
// src/claude-mcp.ts
|
|
351
351
|
import { spawnSync } from "child_process";
|
|
352
|
-
import { readFileSync as
|
|
352
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
353
353
|
import { homedir } from "os";
|
|
354
|
+
import { join as join3 } from "path";
|
|
355
|
+
|
|
356
|
+
// src/mcp-registrations.ts
|
|
357
|
+
import { createHash } from "crypto";
|
|
358
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
354
359
|
import { join as join2 } from "path";
|
|
360
|
+
var fileFor = (stateDir) => join2(stateDir, "mcp-registrations.json");
|
|
361
|
+
var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
|
|
362
|
+
function readMcpRegistrations(stateDir) {
|
|
363
|
+
try {
|
|
364
|
+
const j = JSON.parse(readFileSync2(fileFor(stateDir), "utf8"));
|
|
365
|
+
return Array.isArray(j) ? j : [];
|
|
366
|
+
} catch {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
|
|
371
|
+
const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
|
|
372
|
+
mkdirSync2(stateDir, { recursive: true });
|
|
373
|
+
writeFileSync(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
|
|
374
|
+
`, { mode: 384 });
|
|
375
|
+
}
|
|
376
|
+
function findMcpRegistration(stateDir, r) {
|
|
377
|
+
const fp = keyFingerprint(r.key);
|
|
378
|
+
return readMcpRegistrations(stateDir).find(
|
|
379
|
+
(x) => x.harness === r.harness && x.scope === r.scope && x.name === r.name && x.url === r.url && x.keyFingerprint === fp
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/claude-mcp.ts
|
|
355
384
|
var MCP_SERVER_NAME = "tablation";
|
|
356
385
|
var redact = (s, key) => key ? s.split(key).join("sk_***") : s;
|
|
357
386
|
function mcpUrlFor(baseUrl) {
|
|
@@ -364,7 +393,7 @@ function runClaude(bin, args) {
|
|
|
364
393
|
}
|
|
365
394
|
function userScopeEntry(home) {
|
|
366
395
|
try {
|
|
367
|
-
const j = JSON.parse(
|
|
396
|
+
const j = JSON.parse(readFileSync3(join3(home, ".claude.json"), "utf8"));
|
|
368
397
|
return j.mcpServers?.[MCP_SERVER_NAME];
|
|
369
398
|
} catch {
|
|
370
399
|
return void 0;
|
|
@@ -380,18 +409,32 @@ function inspectClaudeMcp(o) {
|
|
|
380
409
|
}
|
|
381
410
|
if (entry.url !== o.url) return { state: "different", detail: `it points at ${entry.url ?? "(no url)"}, not ${o.url}` };
|
|
382
411
|
const auth = Object.entries(entry.headers ?? {}).find(([k]) => k.toLowerCase() === "authorization")?.[1];
|
|
383
|
-
if (auth !== `Bearer ${o.key}`)
|
|
412
|
+
if (auth !== `Bearer ${o.key}`) {
|
|
413
|
+
const bearer = auth?.startsWith("Bearer ") ? auth.slice("Bearer ".length) : void 0;
|
|
414
|
+
const mine = o.stateDir && bearer ? findMcpRegistration(o.stateDir, { harness: "claude", scope: "user", name: MCP_SERVER_NAME, url: o.url, key: bearer }) : void 0;
|
|
415
|
+
if (mine) return { state: "different", detail: `crew registered an earlier key here`, crewWrote: mine.at };
|
|
416
|
+
return { state: "different", detail: `it points at ${o.url} with a key that is not one crew issued` };
|
|
417
|
+
}
|
|
384
418
|
return { state: "registered", url: o.url };
|
|
385
419
|
}
|
|
386
420
|
function ensureClaudeMcp(o) {
|
|
421
|
+
return ensureClaudeMcpResult(o).line;
|
|
422
|
+
}
|
|
423
|
+
function ensureClaudeMcpResult(o) {
|
|
424
|
+
const r = ensureInner(o);
|
|
425
|
+
return { ...r, url: o.url };
|
|
426
|
+
}
|
|
427
|
+
function ensureInner(o) {
|
|
387
428
|
const manual = `claude mcp add --transport http ${MCP_SERVER_NAME} ${o.url} --header "Authorization: Bearer <key>" --scope user`;
|
|
388
429
|
const st = inspectClaudeMcp(o);
|
|
389
|
-
if (st.state === "no-claude") return `mcp: \`${o.bin}\` not found, so Claude Code was not configured. To do it by hand: ${manual}
|
|
390
|
-
if (st.state === "registered") return `mcp: ${MCP_SERVER_NAME} already registered with Claude Code \u2192 ${st.url}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
430
|
+
if (st.state === "no-claude") return { line: `mcp: \`${o.bin}\` not found, so Claude Code was not configured. To do it by hand: ${manual}` };
|
|
431
|
+
if (st.state === "registered") return { line: `mcp: ${MCP_SERVER_NAME} already registered with Claude Code \u2192 ${st.url}`, action: "registered" };
|
|
432
|
+
const crewWrote = st.state === "different" ? st.crewWrote : void 0;
|
|
433
|
+
const ours = crewWrote !== void 0;
|
|
434
|
+
if (st.state === "different" && !o.replace && !ours) {
|
|
435
|
+
return { line: `mcp: left Claude Code's "${MCP_SERVER_NAME}" server alone \u2014 ${st.detail}. Re-run with --mcp replace to overwrite it.`, action: "kept" };
|
|
436
|
+
}
|
|
437
|
+
if (o.dryRun) return { line: `(dry run) would register the ${MCP_SERVER_NAME} MCP server with Claude Code \u2192 ${o.url}` };
|
|
395
438
|
if (st.state === "different") {
|
|
396
439
|
runClaude(o.bin, ["mcp", "remove", MCP_SERVER_NAME, "--scope", "user"]);
|
|
397
440
|
}
|
|
@@ -408,9 +451,21 @@ function ensureClaudeMcp(o) {
|
|
|
408
451
|
"user"
|
|
409
452
|
]);
|
|
410
453
|
if (add.status !== 0) {
|
|
411
|
-
return `mcp: \`claude mcp add\` failed (${redact(add.stderr.trim(), o.key) || `exit ${add.status}`}). To do it by hand: ${manual}
|
|
454
|
+
return { line: `mcp: \`claude mcp add\` failed (${redact(add.stderr.trim(), o.key) || `exit ${add.status}`}). To do it by hand: ${manual}` };
|
|
412
455
|
}
|
|
413
|
-
|
|
456
|
+
if (o.stateDir) {
|
|
457
|
+
try {
|
|
458
|
+
recordMcpRegistration(o.stateDir, { harness: "claude", scope: "user", name: MCP_SERVER_NAME, url: o.url, keyFingerprint: keyFingerprint(o.key) });
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (crewWrote !== void 0) {
|
|
463
|
+
return { line: `mcp: updated the key crew registered on ${crewWrote.slice(0, 10)} \u2192 ${o.url}`, action: "replaced" };
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
line: `mcp: ${st.state === "different" ? "replaced" : "registered"} the ${MCP_SERVER_NAME} MCP server with Claude Code \u2192 ${o.url} (user scope, ~/.claude.json)`,
|
|
467
|
+
action: st.state === "different" ? "replaced" : "registered"
|
|
468
|
+
};
|
|
414
469
|
}
|
|
415
470
|
function removeClaudeMcp(o) {
|
|
416
471
|
const got = runClaude(o.bin, ["mcp", "get", MCP_SERVER_NAME]);
|
|
@@ -422,8 +477,8 @@ function removeClaudeMcp(o) {
|
|
|
422
477
|
}
|
|
423
478
|
|
|
424
479
|
// src/launcher.ts
|
|
425
|
-
import { basename, join as
|
|
426
|
-
import { existsSync as existsSync3, readFileSync as
|
|
480
|
+
import { basename, join as join4 } from "path";
|
|
481
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
427
482
|
import { homedir as homedir2 } from "os";
|
|
428
483
|
function isOriLauncher(spec) {
|
|
429
484
|
return !!spec.launcher && basename(spec.launcher) === "ori";
|
|
@@ -432,7 +487,7 @@ function isOmlxLauncher(spec) {
|
|
|
432
487
|
return !!spec.launcher && basename(spec.launcher) === "omlx";
|
|
433
488
|
}
|
|
434
489
|
function defaultOmlxBin(home = homedir2()) {
|
|
435
|
-
return
|
|
490
|
+
return join4(home, ".omlx", "bin", "omlx");
|
|
436
491
|
}
|
|
437
492
|
function launcherExecutable(spec, home) {
|
|
438
493
|
if (!spec.launcher) return void 0;
|
|
@@ -485,8 +540,8 @@ function omlxModelPresent(model, dirs) {
|
|
|
485
540
|
});
|
|
486
541
|
}
|
|
487
542
|
function readOmlxSettings(home = homedir2()) {
|
|
488
|
-
const p =
|
|
489
|
-
return existsSync3(p) ? parseOmlxSettings(
|
|
543
|
+
const p = join4(home, ".omlx", "settings.json");
|
|
544
|
+
return existsSync3(p) ? parseOmlxSettings(readFileSync4(p, "utf8")) : void 0;
|
|
490
545
|
}
|
|
491
546
|
|
|
492
547
|
// src/capability.ts
|
|
@@ -613,17 +668,18 @@ var claudeAdapter = {
|
|
|
613
668
|
const s = inspectClaudeMcp(o);
|
|
614
669
|
return s.state === "no-claude" ? { state: "no-binary" } : s;
|
|
615
670
|
},
|
|
616
|
-
ensureMcp: ensureClaudeMcp
|
|
671
|
+
ensureMcp: ensureClaudeMcp,
|
|
672
|
+
ensureMcpResult: ensureClaudeMcpResult
|
|
617
673
|
};
|
|
618
674
|
|
|
619
675
|
// src/adapters/codex.ts
|
|
620
676
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
621
677
|
import { homedir as homedir3 } from "os";
|
|
622
|
-
import { join as
|
|
623
|
-
import { rmSync as rmSync3, writeFileSync as
|
|
678
|
+
import { join as join5 } from "path";
|
|
679
|
+
import { rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
624
680
|
|
|
625
681
|
// src/pair-files.ts
|
|
626
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
682
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
|
|
627
683
|
import { dirname as dirname2 } from "path";
|
|
628
684
|
var PAIR_BLOCK_START = "<!-- crew:pair start -->";
|
|
629
685
|
var PAIR_BLOCK_END = "<!-- crew:pair end -->";
|
|
@@ -664,11 +720,11 @@ function removePairBlock(text) {
|
|
|
664
720
|
return text.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
|
|
665
721
|
}
|
|
666
722
|
function readIfExists(path) {
|
|
667
|
-
return existsSync4(path) ?
|
|
723
|
+
return existsSync4(path) ? readFileSync5(path, "utf8") : void 0;
|
|
668
724
|
}
|
|
669
725
|
function writeFileMkdir(path, text) {
|
|
670
|
-
|
|
671
|
-
|
|
726
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
727
|
+
writeFileSync2(path, text);
|
|
672
728
|
}
|
|
673
729
|
function installBlockFile(path, harness, dryRun) {
|
|
674
730
|
const before = readIfExists(path) ?? "";
|
|
@@ -684,7 +740,7 @@ function removeBlockFile(path, dryRun) {
|
|
|
684
740
|
if (dryRun) return true;
|
|
685
741
|
const after = removePairBlock(before);
|
|
686
742
|
if (after.trim() === "") rmSync2(path);
|
|
687
|
-
else
|
|
743
|
+
else writeFileSync2(path, after);
|
|
688
744
|
return true;
|
|
689
745
|
}
|
|
690
746
|
function blockVerdict(path, harness) {
|
|
@@ -811,8 +867,8 @@ function codexTable(url) {
|
|
|
811
867
|
return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
|
|
812
868
|
}
|
|
813
869
|
var codexPaths = (home) => ({
|
|
814
|
-
instructions:
|
|
815
|
-
config:
|
|
870
|
+
instructions: join5(home, ".codex", "AGENTS.md"),
|
|
871
|
+
config: join5(home, ".codex", "config.toml")
|
|
816
872
|
});
|
|
817
873
|
function codexMcpState(config, url) {
|
|
818
874
|
const lines = config.split("\n");
|
|
@@ -863,7 +919,7 @@ var codexPair = {
|
|
|
863
919
|
if (hadMcp && !o.dryRun) {
|
|
864
920
|
const after = removeTomlTable(config, `mcp_servers.${SERVER}`);
|
|
865
921
|
if (after === "") rmSync3(p.config);
|
|
866
|
-
else
|
|
922
|
+
else writeFileSync3(p.config, after);
|
|
867
923
|
}
|
|
868
924
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
869
925
|
return hadBlock || hadMcp ? `pair(codex): ${o.dryRun ? "(dry run) would remove" : "removed"} ${what}${kept}` : `pair(codex): nothing to remove${kept}`;
|
|
@@ -959,8 +1015,8 @@ var codexAdapter = {
|
|
|
959
1015
|
|
|
960
1016
|
// src/pair-install.ts
|
|
961
1017
|
var geminiPaths = (home) => ({
|
|
962
|
-
instructions:
|
|
963
|
-
config:
|
|
1018
|
+
instructions: join6(home, ".gemini", "GEMINI.md"),
|
|
1019
|
+
config: join6(home, ".gemini", "settings.json")
|
|
964
1020
|
});
|
|
965
1021
|
function readGeminiSettings(path) {
|
|
966
1022
|
const text = readIfExists(path);
|
|
@@ -1048,7 +1104,7 @@ var geminiPair = {
|
|
|
1048
1104
|
if (Object.keys(rest).length > 0) next.mcpServers = rest;
|
|
1049
1105
|
else delete next.mcpServers;
|
|
1050
1106
|
if (Object.keys(next).length === 0) rmSync4(p.config);
|
|
1051
|
-
else
|
|
1107
|
+
else writeFileSync4(p.config, `${JSON.stringify(next, null, 2)}
|
|
1052
1108
|
`);
|
|
1053
1109
|
}
|
|
1054
1110
|
const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
|
|
@@ -1059,8 +1115,8 @@ var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
|
|
|
1059
1115
|
function vscodeUserDirs(o) {
|
|
1060
1116
|
const home = o.home ?? homedir4();
|
|
1061
1117
|
const platform2 = o.platform ?? process.platform;
|
|
1062
|
-
const root = platform2 === "darwin" ?
|
|
1063
|
-
const all = VSCODE_FLAVOURS.map((f) =>
|
|
1118
|
+
const root = platform2 === "darwin" ? join6(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join6(home, "AppData", "Roaming") : join6(home, ".config");
|
|
1119
|
+
const all = VSCODE_FLAVOURS.map((f) => join6(root, f, "User"));
|
|
1064
1120
|
const present = all.filter((d) => existsSync5(d));
|
|
1065
1121
|
return present.length > 0 ? present : [all[0]];
|
|
1066
1122
|
}
|
|
@@ -1099,7 +1155,7 @@ function removeCopilotInstructions(path, dryRun) {
|
|
|
1099
1155
|
if (dryRun) return true;
|
|
1100
1156
|
const after = removePairBlock(before);
|
|
1101
1157
|
if (after.trim() === "" || after.trim() === COPILOT_FRONTMATTER.trim()) rmSync4(path);
|
|
1102
|
-
else
|
|
1158
|
+
else writeFileSync4(path, after);
|
|
1103
1159
|
return true;
|
|
1104
1160
|
}
|
|
1105
1161
|
var copilotPair = {
|
|
@@ -1108,8 +1164,8 @@ var copilotPair = {
|
|
|
1108
1164
|
installPair(o) {
|
|
1109
1165
|
const lines = [];
|
|
1110
1166
|
for (const dir of vscodeUserDirs(o)) {
|
|
1111
|
-
const mcpPath =
|
|
1112
|
-
const instrPath =
|
|
1167
|
+
const mcpPath = join6(dir, "mcp.json");
|
|
1168
|
+
const instrPath = join6(dir, "prompts", "crew-pair.instructions.md");
|
|
1113
1169
|
const settings = readVscodeMcp(mcpPath);
|
|
1114
1170
|
if (!settings) {
|
|
1115
1171
|
lines.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
|
|
@@ -1137,8 +1193,8 @@ var copilotPair = {
|
|
|
1137
1193
|
},
|
|
1138
1194
|
verifyPair(o) {
|
|
1139
1195
|
const verdicts = vscodeUserDirs(o).map((dir) => {
|
|
1140
|
-
const instrPath =
|
|
1141
|
-
const mcpPath =
|
|
1196
|
+
const instrPath = join6(dir, "prompts", "crew-pair.instructions.md");
|
|
1197
|
+
const mcpPath = join6(dir, "mcp.json");
|
|
1142
1198
|
const b = blockVerdict(instrPath, "copilot");
|
|
1143
1199
|
if (!b.ok) return b;
|
|
1144
1200
|
const settings = readVscodeMcp(mcpPath);
|
|
@@ -1153,8 +1209,8 @@ var copilotPair = {
|
|
|
1153
1209
|
removePair(o) {
|
|
1154
1210
|
const lines = [];
|
|
1155
1211
|
for (const dir of vscodeUserDirs(o)) {
|
|
1156
|
-
const mcpPath =
|
|
1157
|
-
const hadBlock = removeCopilotInstructions(
|
|
1212
|
+
const mcpPath = join6(dir, "mcp.json");
|
|
1213
|
+
const hadBlock = removeCopilotInstructions(join6(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
|
|
1158
1214
|
const settings = readVscodeMcp(mcpPath);
|
|
1159
1215
|
const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
|
|
1160
1216
|
const hadMcp = st.state === "registered";
|
|
@@ -1165,7 +1221,7 @@ var copilotPair = {
|
|
|
1165
1221
|
if (Object.keys(rest).length > 0) next.servers = rest;
|
|
1166
1222
|
else delete next.servers;
|
|
1167
1223
|
if (Object.keys(next).length === 0) rmSync4(mcpPath);
|
|
1168
|
-
else
|
|
1224
|
+
else writeFileSync4(mcpPath, `${JSON.stringify(next, null, 2)}
|
|
1169
1225
|
`);
|
|
1170
1226
|
}
|
|
1171
1227
|
const what = hadBlock && hadMcp ? `the Pair instructions and ${SERVER} server` : hadBlock ? "the Pair instructions" : `the ${SERVER} server`;
|
|
@@ -1453,8 +1509,8 @@ function explain(ship, required) {
|
|
|
1453
1509
|
}
|
|
1454
1510
|
|
|
1455
1511
|
// src/repo-config.ts
|
|
1456
|
-
import { readFileSync as
|
|
1457
|
-
import { join as
|
|
1512
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
1513
|
+
import { join as join7, basename as basename2 } from "path";
|
|
1458
1514
|
import { parse } from "yaml";
|
|
1459
1515
|
var SPEC_VERSION = 1;
|
|
1460
1516
|
var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
|
|
@@ -1537,7 +1593,7 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
1537
1593
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
1538
1594
|
function findRepoConfig(dir) {
|
|
1539
1595
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
1540
|
-
const p =
|
|
1596
|
+
const p = join7(dir, name);
|
|
1541
1597
|
if (existsSync6(p)) return p;
|
|
1542
1598
|
}
|
|
1543
1599
|
return null;
|
|
@@ -1776,7 +1832,7 @@ function parseRepoConfig(text, file) {
|
|
|
1776
1832
|
function loadRepoConfig(dir) {
|
|
1777
1833
|
const file = findRepoConfig(dir);
|
|
1778
1834
|
if (!file) return null;
|
|
1779
|
-
return parseRepoConfig(
|
|
1835
|
+
return parseRepoConfig(readFileSync6(file, "utf8"), file);
|
|
1780
1836
|
}
|
|
1781
1837
|
function hookLabel(cfg, hook2) {
|
|
1782
1838
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -2230,11 +2286,11 @@ function expand(p, base) {
|
|
|
2230
2286
|
return isAbsolute(out) ? out : resolve(base, out);
|
|
2231
2287
|
}
|
|
2232
2288
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
2233
|
-
if (!promptSet) return
|
|
2289
|
+
if (!promptSet) return join8(crewHome, "prompts", "default");
|
|
2234
2290
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
2235
2291
|
return expand(promptSet, base);
|
|
2236
2292
|
}
|
|
2237
|
-
return
|
|
2293
|
+
return join8(crewHome, "prompts", promptSet);
|
|
2238
2294
|
}
|
|
2239
2295
|
var Missing = class {
|
|
2240
2296
|
paths = [];
|
|
@@ -2258,7 +2314,7 @@ var Missing = class {
|
|
|
2258
2314
|
};
|
|
2259
2315
|
function crewVersion(crewHome) {
|
|
2260
2316
|
try {
|
|
2261
|
-
const pkg = JSON.parse(
|
|
2317
|
+
const pkg = JSON.parse(readFileSync7(join8(crewHome, "package.json"), "utf8"));
|
|
2262
2318
|
return pkg.version ?? "0.0.0";
|
|
2263
2319
|
} catch {
|
|
2264
2320
|
return "0.0.0";
|
|
@@ -2268,8 +2324,8 @@ function configSearchPath(crewHome) {
|
|
|
2268
2324
|
if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
|
|
2269
2325
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2270
2326
|
return [
|
|
2271
|
-
...xdg ? [
|
|
2272
|
-
|
|
2327
|
+
...xdg ? [join8(xdg, "crew", "crew.yaml")] : [],
|
|
2328
|
+
join8(homedir5(), ".config", "crew", "crew.yaml"),
|
|
2273
2329
|
resolve(crewHome, "crew.yaml")
|
|
2274
2330
|
];
|
|
2275
2331
|
}
|
|
@@ -2277,17 +2333,17 @@ function findConfigFile(crewHome) {
|
|
|
2277
2333
|
const candidates = configSearchPath(crewHome);
|
|
2278
2334
|
return candidates.find((c) => existsSync7(c)) ?? candidates[candidates.length - 1];
|
|
2279
2335
|
}
|
|
2280
|
-
var defaultConfigPath = () =>
|
|
2336
|
+
var defaultConfigPath = () => join8(process.env.XDG_CONFIG_HOME ?? join8(homedir5(), ".config"), "crew", "crew.yaml");
|
|
2281
2337
|
function routeSlug(route) {
|
|
2282
2338
|
return route.replace(/\//g, "-");
|
|
2283
2339
|
}
|
|
2284
2340
|
function resolvedPathFor(stateDir, route) {
|
|
2285
2341
|
const [workspace, project] = splitRoute(route);
|
|
2286
|
-
return
|
|
2342
|
+
return join8(stateDir, "resolved", workspace, `${project}.json`);
|
|
2287
2343
|
}
|
|
2288
2344
|
function apiKeyPathFor(stateDir, route) {
|
|
2289
2345
|
const [workspace, project] = splitRoute(route);
|
|
2290
|
-
return
|
|
2346
|
+
return join8(stateDir, "keys", workspace, `${project}.env`);
|
|
2291
2347
|
}
|
|
2292
2348
|
function splitRoute(route) {
|
|
2293
2349
|
const parts = route.split("/");
|
|
@@ -2300,7 +2356,7 @@ function readResolvedFile(stateDir, route) {
|
|
|
2300
2356
|
const path = resolvedPathFor(stateDir, route);
|
|
2301
2357
|
if (!existsSync7(path)) return void 0;
|
|
2302
2358
|
try {
|
|
2303
|
-
return JSON.parse(
|
|
2359
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
2304
2360
|
} catch (e) {
|
|
2305
2361
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
2306
2362
|
}
|
|
@@ -2365,12 +2421,12 @@ function loadConfig(crewHome, configFile, opts = {}) {
|
|
|
2365
2421
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
2366
2422
|
|
|
2367
2423
|
Create the first of those:
|
|
2368
|
-
mkdir -p ${dirname3(defaultConfigPath())} && cp ${
|
|
2424
|
+
mkdir -p ${dirname3(defaultConfigPath())} && cp ${join8(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
2369
2425
|
);
|
|
2370
2426
|
}
|
|
2371
2427
|
let raw;
|
|
2372
2428
|
try {
|
|
2373
|
-
raw = parse2(opts.text ??
|
|
2429
|
+
raw = parse2(opts.text ?? readFileSync7(file, "utf8"));
|
|
2374
2430
|
} catch (e) {
|
|
2375
2431
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
2376
2432
|
}
|
|
@@ -2448,7 +2504,7 @@ Create the first of those:
|
|
|
2448
2504
|
useNvm: shipRaw.useNvm !== false,
|
|
2449
2505
|
nvmSh: shipRaw.nvmSh,
|
|
2450
2506
|
stateDir,
|
|
2451
|
-
logFile: expand(shipRaw.logFile ??
|
|
2507
|
+
logFile: expand(shipRaw.logFile ?? join8(tmpdir(), "crew.log"), base),
|
|
2452
2508
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
2453
2509
|
maxConcurrentAgents,
|
|
2454
2510
|
streamRetentionDays,
|
|
@@ -2587,7 +2643,9 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2587
2643
|
}
|
|
2588
2644
|
}
|
|
2589
2645
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
2590
|
-
const
|
|
2646
|
+
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
2647
|
+
const dirIsCloneBase = !c?.dir && !hasRepos && c?.repos && typeof c.repos === "object" && !Array.isArray(c.repos);
|
|
2648
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join8(reposBasePath, splitRoute(route)[0]) : missing.req(void 0, `${where}.dir (or ${where}.repos)`) ?? "";
|
|
2591
2649
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
2592
2650
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
2593
2651
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -2597,8 +2655,9 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2597
2655
|
enabled: c?.enabled === true,
|
|
2598
2656
|
area: c?.area,
|
|
2599
2657
|
dir,
|
|
2658
|
+
...dirIsCloneBase ? { dirIsCloneBase: true } : {},
|
|
2600
2659
|
repos: repoDirs,
|
|
2601
|
-
reposBasePath
|
|
2660
|
+
reposBasePath,
|
|
2602
2661
|
repoOverrides,
|
|
2603
2662
|
worktreePrefix: c?.worktreePrefix ? String(c.worktreePrefix) : void 0,
|
|
2604
2663
|
...toolchain !== void 0 ? { toolchain } : {},
|
|
@@ -2656,7 +2715,7 @@ function dirForRepo(r, repoId) {
|
|
|
2656
2715
|
}
|
|
2657
2716
|
function repoTargetFor(r, repoId) {
|
|
2658
2717
|
const names = r.resolved?.repoNames;
|
|
2659
|
-
if (!repoId || !names) return Object.keys(r.repos).length ? null : { name: basename3(r.dir), dir: r.dir };
|
|
2718
|
+
if (!repoId || !names) return Object.keys(r.repos).length || r.dirIsCloneBase ? null : { name: basename3(r.dir), dir: r.dir };
|
|
2660
2719
|
const name = names[repoId];
|
|
2661
2720
|
if (!name) return null;
|
|
2662
2721
|
return reposOf(r).find((t) => t.name === name) ?? null;
|
|
@@ -2670,7 +2729,7 @@ function repoIdForName(r, name) {
|
|
|
2670
2729
|
function resolveApiKey(r) {
|
|
2671
2730
|
if (r.apiKey) return r.apiKey;
|
|
2672
2731
|
if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
|
|
2673
|
-
for (const line of
|
|
2732
|
+
for (const line of readFileSync7(r.apiKeyFile, "utf8").split("\n")) {
|
|
2674
2733
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
2675
2734
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
2676
2735
|
}
|
|
@@ -2685,7 +2744,7 @@ function credentialSource(r) {
|
|
|
2685
2744
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
2686
2745
|
if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
|
|
2687
2746
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
2688
|
-
if (
|
|
2747
|
+
if (readFileSync7(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
2689
2748
|
}
|
|
2690
2749
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
2691
2750
|
}
|
|
@@ -2741,12 +2800,21 @@ function configuredMembers(rt) {
|
|
|
2741
2800
|
return out;
|
|
2742
2801
|
}
|
|
2743
2802
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
2744
|
-
return
|
|
2803
|
+
return join8(basePath, workspace, repoName);
|
|
2745
2804
|
}
|
|
2746
2805
|
function reposOf(r) {
|
|
2747
2806
|
const named = Object.entries(r.repos);
|
|
2748
2807
|
if (named.length > 0) return named.map(([name, dir]) => ({ name, dir }));
|
|
2749
2808
|
const trackerNames = r.resolved?.repoNames ? Object.values(r.resolved.repoNames) : [];
|
|
2809
|
+
if (r.dirIsCloneBase) {
|
|
2810
|
+
const [workspace2] = splitRoute(r.route);
|
|
2811
|
+
const remotes2 = r.resolved?.repoRemotes ?? {};
|
|
2812
|
+
return Object.entries(r.resolved?.repoNames ?? {}).map(([id, name]) => ({
|
|
2813
|
+
name,
|
|
2814
|
+
dir: defaultRepoDir(r.reposBasePath, workspace2, name),
|
|
2815
|
+
remote: remotes2[id]
|
|
2816
|
+
}));
|
|
2817
|
+
}
|
|
2750
2818
|
if (trackerNames.length <= 1) return [{ name: basename3(r.dir), dir: r.dir }];
|
|
2751
2819
|
const remotes = r.resolved?.repoRemotes ?? {};
|
|
2752
2820
|
const remoteByName = {};
|
|
@@ -2765,7 +2833,7 @@ function passengerRepoTargets(r) {
|
|
|
2765
2833
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
2766
2834
|
}
|
|
2767
2835
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
2768
|
-
return
|
|
2836
|
+
return join8(basePath, workspace, ".passenger-checkouts", repoName);
|
|
2769
2837
|
}
|
|
2770
2838
|
function passengerCheckoutTargets(r) {
|
|
2771
2839
|
if (!r.hostPassengers) return [];
|
|
@@ -3113,8 +3181,8 @@ function renderEnvironment(i) {
|
|
|
3113
3181
|
}
|
|
3114
3182
|
|
|
3115
3183
|
// src/agent-log.ts
|
|
3116
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
3117
|
-
import { join as
|
|
3184
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
3185
|
+
import { join as join9 } from "path";
|
|
3118
3186
|
|
|
3119
3187
|
// src/pending-backoff.ts
|
|
3120
3188
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -3181,20 +3249,20 @@ async function reportAgentRun(target, run2) {
|
|
|
3181
3249
|
}
|
|
3182
3250
|
return { agentLogId: entry.id };
|
|
3183
3251
|
}
|
|
3184
|
-
var queueDir = (stateDir) =>
|
|
3252
|
+
var queueDir = (stateDir) => join9(stateDir, "pending-agent-logs");
|
|
3185
3253
|
function queueAgentRun(stateDir, name, q) {
|
|
3186
|
-
|
|
3187
|
-
|
|
3254
|
+
mkdirSync4(queueDir(stateDir), { recursive: true });
|
|
3255
|
+
writeFileSync5(join9(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
|
|
3188
3256
|
}
|
|
3189
3257
|
async function replayQueuedAgentRuns(stateDir, route, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
3190
3258
|
const dir = queueDir(stateDir);
|
|
3191
3259
|
if (!existsSync8(dir)) return 0;
|
|
3192
3260
|
let sent = 0;
|
|
3193
3261
|
for (const f of readdirSync2(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
3194
|
-
const path =
|
|
3262
|
+
const path = join9(dir, f);
|
|
3195
3263
|
let q;
|
|
3196
3264
|
try {
|
|
3197
|
-
q = JSON.parse(
|
|
3265
|
+
q = JSON.parse(readFileSync8(path, "utf8"));
|
|
3198
3266
|
} catch {
|
|
3199
3267
|
continue;
|
|
3200
3268
|
}
|
|
@@ -3213,7 +3281,7 @@ async function replayQueuedAgentRuns(stateDir, route, keyFor, log, now = /* @__P
|
|
|
3213
3281
|
continue;
|
|
3214
3282
|
}
|
|
3215
3283
|
const attempts = q.attempts + 1;
|
|
3216
|
-
|
|
3284
|
+
writeFileSync5(path, JSON.stringify({
|
|
3217
3285
|
...q,
|
|
3218
3286
|
attempts,
|
|
3219
3287
|
warned: true,
|
|
@@ -3394,25 +3462,25 @@ var HandoffTranscriptTracker = class {
|
|
|
3394
3462
|
};
|
|
3395
3463
|
|
|
3396
3464
|
// src/pending-handoff.ts
|
|
3397
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
3398
|
-
import { join as
|
|
3465
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
3466
|
+
import { join as join15 } from "path";
|
|
3399
3467
|
|
|
3400
3468
|
// src/git.ts
|
|
3401
3469
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3402
3470
|
import {
|
|
3403
3471
|
existsSync as existsSync9,
|
|
3404
|
-
mkdirSync as
|
|
3472
|
+
mkdirSync as mkdirSync5,
|
|
3405
3473
|
openSync as openSync2,
|
|
3406
3474
|
closeSync as closeSync2,
|
|
3407
3475
|
writeSync,
|
|
3408
3476
|
statSync as statSync2,
|
|
3409
3477
|
unlinkSync,
|
|
3410
|
-
readFileSync as
|
|
3411
|
-
writeFileSync as
|
|
3478
|
+
readFileSync as readFileSync9,
|
|
3479
|
+
writeFileSync as writeFileSync6,
|
|
3412
3480
|
readdirSync as readdirSync3,
|
|
3413
3481
|
rmSync as rmSync6
|
|
3414
3482
|
} from "fs";
|
|
3415
|
-
import { dirname as dirname4, basename as basename5, join as
|
|
3483
|
+
import { dirname as dirname4, basename as basename5, join as join10 } from "path";
|
|
3416
3484
|
var GitError = class extends Error {
|
|
3417
3485
|
};
|
|
3418
3486
|
function streamText(x) {
|
|
@@ -3796,11 +3864,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
3796
3864
|
}
|
|
3797
3865
|
for (const name of names) {
|
|
3798
3866
|
if (!name.startsWith("crew-release-")) continue;
|
|
3799
|
-
const root =
|
|
3867
|
+
const root = join10(tmp, name);
|
|
3800
3868
|
try {
|
|
3801
3869
|
const st = statSync2(root);
|
|
3802
3870
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
3803
|
-
if (existsSync9(
|
|
3871
|
+
if (existsSync9(join10(root, "checkout"))) continue;
|
|
3804
3872
|
rmSync6(root, { recursive: true, force: true });
|
|
3805
3873
|
removed.push(name);
|
|
3806
3874
|
} catch {
|
|
@@ -3849,7 +3917,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
|
3849
3917
|
`${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`
|
|
3850
3918
|
);
|
|
3851
3919
|
}
|
|
3852
|
-
|
|
3920
|
+
mkdirSync5(dirname4(dir), { recursive: true });
|
|
3853
3921
|
const url = cloneUrlFor(remote, opts);
|
|
3854
3922
|
try {
|
|
3855
3923
|
execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -3874,7 +3942,7 @@ function sleepSync(ms2) {
|
|
|
3874
3942
|
}
|
|
3875
3943
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
3876
3944
|
const lockPath = checkoutLockPath(dir);
|
|
3877
|
-
|
|
3945
|
+
mkdirSync5(dirname4(lockPath), { recursive: true });
|
|
3878
3946
|
const deadline = Date.now() + timeoutMs;
|
|
3879
3947
|
for (; ; ) {
|
|
3880
3948
|
try {
|
|
@@ -3914,14 +3982,14 @@ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {
|
|
|
3914
3982
|
if (!existsSync9(`${dir}/.git`) && githubRemote) {
|
|
3915
3983
|
const url = cloneUrlFor(githubRemote, clone);
|
|
3916
3984
|
const marker = cloneFailureMarker(dir);
|
|
3917
|
-
if (existsSync9(marker) &&
|
|
3918
|
-
return { action: "clone-failed", detail:
|
|
3985
|
+
if (existsSync9(marker) && readFileSync9(marker, "utf8").split("\n")[0] === url) {
|
|
3986
|
+
return { action: "clone-failed", detail: readFileSync9(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
|
|
3919
3987
|
}
|
|
3920
3988
|
try {
|
|
3921
3989
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
3922
3990
|
} catch (e) {
|
|
3923
3991
|
if (!(e instanceof GitError)) throw e;
|
|
3924
|
-
|
|
3992
|
+
writeFileSync6(marker, `${url}
|
|
3925
3993
|
${e.message}`);
|
|
3926
3994
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
3927
3995
|
}
|
|
@@ -4342,11 +4410,11 @@ function recoveredFailureHeadlines(o, route) {
|
|
|
4342
4410
|
}
|
|
4343
4411
|
|
|
4344
4412
|
// src/install.ts
|
|
4345
|
-
import { dirname as dirname5, join as
|
|
4413
|
+
import { dirname as dirname5, join as join11, extname } from "path";
|
|
4346
4414
|
import { homedir as homedir6 } from "os";
|
|
4347
|
-
import { accessSync, constants, existsSync as existsSync10, mkdirSync as
|
|
4415
|
+
import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync10, statSync as statSync3, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, realpathSync } from "fs";
|
|
4348
4416
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4349
|
-
import { createHash } from "crypto";
|
|
4417
|
+
import { createHash as createHash2 } from "crypto";
|
|
4350
4418
|
|
|
4351
4419
|
// src/runtime-info.ts
|
|
4352
4420
|
function isCompiledBinary(moduleUrl) {
|
|
@@ -4356,7 +4424,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4356
4424
|
// src/install.ts
|
|
4357
4425
|
function crewInvocation(crewHome) {
|
|
4358
4426
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4359
|
-
return [process.execPath,
|
|
4427
|
+
return [process.execPath, join11(crewHome, "bin", "crew")];
|
|
4360
4428
|
}
|
|
4361
4429
|
var INTERVAL_SECONDS = 120;
|
|
4362
4430
|
function labelFor(crewHome, job) {
|
|
@@ -4367,7 +4435,7 @@ function labelFor(crewHome, job) {
|
|
|
4367
4435
|
return crewHome;
|
|
4368
4436
|
}
|
|
4369
4437
|
})();
|
|
4370
|
-
const hash =
|
|
4438
|
+
const hash = createHash2("sha1").update(real).digest("hex").slice(0, 8);
|
|
4371
4439
|
const stem = job === "release" ? "com.tablation.crew-release" : job === "passengers" ? "com.tablation.crew-passengers" : "com.tablation.crew";
|
|
4372
4440
|
return `${stem}.${hash}`;
|
|
4373
4441
|
}
|
|
@@ -4394,7 +4462,7 @@ function pathFor(ship) {
|
|
|
4394
4462
|
return parts.join(":");
|
|
4395
4463
|
}
|
|
4396
4464
|
function findOnPath(bin, searchPath) {
|
|
4397
|
-
const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) =>
|
|
4465
|
+
const candidates = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join11(dir, bin));
|
|
4398
4466
|
for (const candidate of candidates) {
|
|
4399
4467
|
try {
|
|
4400
4468
|
accessSync(candidate, constants.X_OK);
|
|
@@ -4404,7 +4472,7 @@ function findOnPath(bin, searchPath) {
|
|
|
4404
4472
|
}
|
|
4405
4473
|
return void 0;
|
|
4406
4474
|
}
|
|
4407
|
-
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin",
|
|
4475
|
+
var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join11(homedir6(), ".docker", "bin")].join(":");
|
|
4408
4476
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4409
4477
|
const schedulerPath = pathFor(ship);
|
|
4410
4478
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
@@ -4416,7 +4484,7 @@ function xmlEscape(s) {
|
|
|
4416
4484
|
}
|
|
4417
4485
|
function planLaunchd(ship, crewHome, job) {
|
|
4418
4486
|
const label = labelFor(crewHome, job);
|
|
4419
|
-
const unitPath =
|
|
4487
|
+
const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4420
4488
|
const crewLog = ship.logFile;
|
|
4421
4489
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4422
4490
|
const persistent = job === "run";
|
|
@@ -4470,8 +4538,8 @@ ${scheduleKeys}
|
|
|
4470
4538
|
}
|
|
4471
4539
|
function planSystemd(ship, crewHome, job) {
|
|
4472
4540
|
const label = labelFor(crewHome, job);
|
|
4473
|
-
const unitDir =
|
|
4474
|
-
const servicePath =
|
|
4541
|
+
const unitDir = join11(homedir6(), ".config", "systemd", "user");
|
|
4542
|
+
const servicePath = join11(unitDir, `${label}.service`);
|
|
4475
4543
|
const crewLog = ship.logFile;
|
|
4476
4544
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4477
4545
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4511,7 +4579,7 @@ WantedBy=default.target
|
|
|
4511
4579
|
schedulerLog
|
|
4512
4580
|
};
|
|
4513
4581
|
}
|
|
4514
|
-
const timerPath =
|
|
4582
|
+
const timerPath = join11(unitDir, `${label}.timer`);
|
|
4515
4583
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4516
4584
|
[Unit]
|
|
4517
4585
|
Description=${description}
|
|
@@ -4585,7 +4653,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4585
4653
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4586
4654
|
const label = labelFor(crewHome, job);
|
|
4587
4655
|
if (hostShip === "macos") {
|
|
4588
|
-
const unitPath =
|
|
4656
|
+
const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4589
4657
|
return {
|
|
4590
4658
|
mechanism: "launchd",
|
|
4591
4659
|
unitPaths: [unitPath],
|
|
@@ -4595,18 +4663,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4595
4663
|
};
|
|
4596
4664
|
}
|
|
4597
4665
|
if (hostShip === "linux" && hasSystemd) {
|
|
4598
|
-
const unitDir =
|
|
4666
|
+
const unitDir = join11(homedir6(), ".config", "systemd", "user");
|
|
4599
4667
|
if (job === "run") {
|
|
4600
4668
|
return {
|
|
4601
4669
|
mechanism: "systemd",
|
|
4602
|
-
unitPaths: [
|
|
4670
|
+
unitPaths: [join11(unitDir, `${label}.service`)],
|
|
4603
4671
|
crontabLine: null,
|
|
4604
4672
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
|
|
4605
4673
|
};
|
|
4606
4674
|
}
|
|
4607
4675
|
return {
|
|
4608
4676
|
mechanism: "systemd",
|
|
4609
|
-
unitPaths: [
|
|
4677
|
+
unitPaths: [join11(unitDir, `${label}.service`), join11(unitDir, `${label}.timer`)],
|
|
4610
4678
|
crontabLine: null,
|
|
4611
4679
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
|
|
4612
4680
|
};
|
|
@@ -4624,7 +4692,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4624
4692
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4625
4693
|
const label = labelFor(crewHome, "run");
|
|
4626
4694
|
if (hostShip === "macos") {
|
|
4627
|
-
const unitPath =
|
|
4695
|
+
const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4628
4696
|
return {
|
|
4629
4697
|
mechanism: "launchd",
|
|
4630
4698
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4664,8 +4732,8 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4664
4732
|
log.emit(`would write ${path}`);
|
|
4665
4733
|
continue;
|
|
4666
4734
|
}
|
|
4667
|
-
|
|
4668
|
-
|
|
4735
|
+
mkdirSync6(dirname5(path), { recursive: true });
|
|
4736
|
+
writeFileSync7(path, plan.unitContent[path]);
|
|
4669
4737
|
log.emit(`wrote ${path}`);
|
|
4670
4738
|
}
|
|
4671
4739
|
if (plan.crontabLine) {
|
|
@@ -4738,7 +4806,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4738
4806
|
}
|
|
4739
4807
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4740
4808
|
function launchAgentsDir() {
|
|
4741
|
-
return
|
|
4809
|
+
return join11(homedir6(), "Library", "LaunchAgents");
|
|
4742
4810
|
}
|
|
4743
4811
|
function xmlUnescape(s) {
|
|
4744
4812
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4757,10 +4825,10 @@ function loadedCrewLabels() {
|
|
|
4757
4825
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4758
4826
|
const files = existsSync10(dir) ? readdirSync4(dir) : [];
|
|
4759
4827
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4760
|
-
const plistPath =
|
|
4828
|
+
const plistPath = join11(dir, f);
|
|
4761
4829
|
let program = null;
|
|
4762
4830
|
try {
|
|
4763
|
-
program = programFromPlist(
|
|
4831
|
+
program = programFromPlist(readFileSync10(plistPath, "utf8"));
|
|
4764
4832
|
} catch {
|
|
4765
4833
|
}
|
|
4766
4834
|
return {
|
|
@@ -4773,7 +4841,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
|
4773
4841
|
const seen = new Set(units.map((u) => u.label));
|
|
4774
4842
|
for (const label of [...loaded].sort()) {
|
|
4775
4843
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4776
|
-
units.push({ label, plistPath:
|
|
4844
|
+
units.push({ label, plistPath: join11(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4777
4845
|
}
|
|
4778
4846
|
return units;
|
|
4779
4847
|
}
|
|
@@ -5119,17 +5187,17 @@ var SHELL_WORDS = /* @__PURE__ */ new Set([
|
|
|
5119
5187
|
]);
|
|
5120
5188
|
|
|
5121
5189
|
// src/state.ts
|
|
5122
|
-
import { readFileSync as
|
|
5123
|
-
import { join as
|
|
5190
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync7, rmSync as rmSync7, statSync as statSync4 } from "fs";
|
|
5191
|
+
import { join as join12 } from "path";
|
|
5124
5192
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5125
5193
|
var State = class _State {
|
|
5126
5194
|
dir;
|
|
5127
5195
|
constructor(dir) {
|
|
5128
5196
|
this.dir = dir;
|
|
5129
|
-
|
|
5197
|
+
mkdirSync7(dir, { recursive: true });
|
|
5130
5198
|
}
|
|
5131
5199
|
path(...parts) {
|
|
5132
|
-
return
|
|
5200
|
+
return join12(this.dir, ...parts);
|
|
5133
5201
|
}
|
|
5134
5202
|
/**
|
|
5135
5203
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5140,14 +5208,14 @@ var State = class _State {
|
|
|
5140
5208
|
*/
|
|
5141
5209
|
watermark() {
|
|
5142
5210
|
try {
|
|
5143
|
-
const v =
|
|
5211
|
+
const v = readFileSync11(this.path(".poll-watermark"), "utf8").trim();
|
|
5144
5212
|
return v || EPOCH;
|
|
5145
5213
|
} catch {
|
|
5146
5214
|
return EPOCH;
|
|
5147
5215
|
}
|
|
5148
5216
|
}
|
|
5149
5217
|
setWatermark(at) {
|
|
5150
|
-
|
|
5218
|
+
writeFileSync8(this.path(".poll-watermark"), `${at}
|
|
5151
5219
|
`);
|
|
5152
5220
|
}
|
|
5153
5221
|
/**
|
|
@@ -5174,7 +5242,7 @@ var State = class _State {
|
|
|
5174
5242
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5175
5243
|
}
|
|
5176
5244
|
pause(role) {
|
|
5177
|
-
|
|
5245
|
+
writeFileSync8(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5178
5246
|
}
|
|
5179
5247
|
resume(role) {
|
|
5180
5248
|
rmSync7(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -5188,7 +5256,7 @@ var State = class _State {
|
|
|
5188
5256
|
return existsSync12(this.path(".release-paused"));
|
|
5189
5257
|
}
|
|
5190
5258
|
setReleasePaused(paused) {
|
|
5191
|
-
if (paused)
|
|
5259
|
+
if (paused) writeFileSync8(this.path(".release-paused"), "");
|
|
5192
5260
|
else rmSync7(this.path(".release-paused"), { force: true });
|
|
5193
5261
|
}
|
|
5194
5262
|
/**
|
|
@@ -5226,7 +5294,7 @@ var State = class _State {
|
|
|
5226
5294
|
const name = _State.safe(rawName);
|
|
5227
5295
|
const file = this.path(`.${name}.lock`);
|
|
5228
5296
|
try {
|
|
5229
|
-
const existing = Number.parseInt(
|
|
5297
|
+
const existing = Number.parseInt(readFileSync11(file, "utf8").trim(), 10);
|
|
5230
5298
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5231
5299
|
try {
|
|
5232
5300
|
process.kill(existing, 0);
|
|
@@ -5236,7 +5304,7 @@ var State = class _State {
|
|
|
5236
5304
|
}
|
|
5237
5305
|
} catch {
|
|
5238
5306
|
}
|
|
5239
|
-
|
|
5307
|
+
writeFileSync8(file, `${process.pid}
|
|
5240
5308
|
`);
|
|
5241
5309
|
return {
|
|
5242
5310
|
ok: true,
|
|
@@ -5320,7 +5388,7 @@ var State = class _State {
|
|
|
5320
5388
|
const heldReported = `.release-held-${suffix}`;
|
|
5321
5389
|
const read = (f) => {
|
|
5322
5390
|
try {
|
|
5323
|
-
return
|
|
5391
|
+
return readFileSync11(this.path(f), "utf8").trim() || null;
|
|
5324
5392
|
} catch {
|
|
5325
5393
|
return null;
|
|
5326
5394
|
}
|
|
@@ -5329,7 +5397,7 @@ var State = class _State {
|
|
|
5329
5397
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5330
5398
|
noteBlocked: () => {
|
|
5331
5399
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5332
|
-
|
|
5400
|
+
writeFileSync8(this.path(blocked), `${n}
|
|
5333
5401
|
`);
|
|
5334
5402
|
return n;
|
|
5335
5403
|
},
|
|
@@ -5346,7 +5414,7 @@ var State = class _State {
|
|
|
5346
5414
|
*/
|
|
5347
5415
|
deployFailedSha: () => read(failed),
|
|
5348
5416
|
noteDeployFailed: (sha) => {
|
|
5349
|
-
|
|
5417
|
+
writeFileSync8(this.path(failed), `${sha}
|
|
5350
5418
|
`);
|
|
5351
5419
|
},
|
|
5352
5420
|
clearDeployFailed: () => {
|
|
@@ -5360,7 +5428,7 @@ var State = class _State {
|
|
|
5360
5428
|
noteTestGateFailed: (sha) => {
|
|
5361
5429
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
5362
5430
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
5363
|
-
|
|
5431
|
+
writeFileSync8(this.path(gateFailed), `${sha} ${next}
|
|
5364
5432
|
`);
|
|
5365
5433
|
return next;
|
|
5366
5434
|
},
|
|
@@ -5370,13 +5438,13 @@ var State = class _State {
|
|
|
5370
5438
|
/** Whether the board was already told about this head's red gate. */
|
|
5371
5439
|
heldReported: () => {
|
|
5372
5440
|
try {
|
|
5373
|
-
return
|
|
5441
|
+
return readFileSync11(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
5374
5442
|
} catch {
|
|
5375
5443
|
return null;
|
|
5376
5444
|
}
|
|
5377
5445
|
},
|
|
5378
5446
|
noteHeldReported: (signature) => {
|
|
5379
|
-
|
|
5447
|
+
writeFileSync8(this.path(heldReported), `${signature}
|
|
5380
5448
|
`);
|
|
5381
5449
|
},
|
|
5382
5450
|
clearHeldReported: () => {
|
|
@@ -5384,7 +5452,7 @@ var State = class _State {
|
|
|
5384
5452
|
},
|
|
5385
5453
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
5386
5454
|
noteTestGateReported: (sha) => {
|
|
5387
|
-
|
|
5455
|
+
writeFileSync8(this.path(gateReported), `${sha}
|
|
5388
5456
|
`);
|
|
5389
5457
|
}
|
|
5390
5458
|
};
|
|
@@ -5407,7 +5475,7 @@ var State = class _State {
|
|
|
5407
5475
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
5408
5476
|
const read = (f) => {
|
|
5409
5477
|
try {
|
|
5410
|
-
return
|
|
5478
|
+
return readFileSync11(this.path(f), "utf8").trim() || null;
|
|
5411
5479
|
} catch {
|
|
5412
5480
|
return null;
|
|
5413
5481
|
}
|
|
@@ -5440,18 +5508,18 @@ var State = class _State {
|
|
|
5440
5508
|
clear2();
|
|
5441
5509
|
return;
|
|
5442
5510
|
}
|
|
5443
|
-
|
|
5511
|
+
writeFileSync8(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
5444
5512
|
`);
|
|
5445
5513
|
const prior = read(ticketFile);
|
|
5446
5514
|
if (topTicket && topTicket === prior) {
|
|
5447
|
-
|
|
5515
|
+
writeFileSync8(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
5448
5516
|
`);
|
|
5449
5517
|
} else if (topTicket) {
|
|
5450
|
-
|
|
5518
|
+
writeFileSync8(this.path(ticketFile), `${topTicket}
|
|
5451
5519
|
`);
|
|
5452
|
-
|
|
5520
|
+
writeFileSync8(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5453
5521
|
`);
|
|
5454
|
-
|
|
5522
|
+
writeFileSync8(this.path(ticketStreakFile), "1\n");
|
|
5455
5523
|
}
|
|
5456
5524
|
}
|
|
5457
5525
|
};
|
|
@@ -5472,14 +5540,14 @@ var State = class _State {
|
|
|
5472
5540
|
return {
|
|
5473
5541
|
previous: () => {
|
|
5474
5542
|
try {
|
|
5475
|
-
const parsed = JSON.parse(
|
|
5543
|
+
const parsed = JSON.parse(readFileSync11(this.path(file), "utf8"));
|
|
5476
5544
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
5477
5545
|
} catch {
|
|
5478
5546
|
return {};
|
|
5479
5547
|
}
|
|
5480
5548
|
},
|
|
5481
5549
|
persist: (next) => {
|
|
5482
|
-
|
|
5550
|
+
writeFileSync8(this.path(file), JSON.stringify(next));
|
|
5483
5551
|
}
|
|
5484
5552
|
};
|
|
5485
5553
|
}
|
|
@@ -5493,14 +5561,14 @@ var State = class _State {
|
|
|
5493
5561
|
return {
|
|
5494
5562
|
previous: () => {
|
|
5495
5563
|
try {
|
|
5496
|
-
const parsed = JSON.parse(
|
|
5564
|
+
const parsed = JSON.parse(readFileSync11(this.path(file), "utf8"));
|
|
5497
5565
|
return Array.isArray(parsed) ? parsed : [];
|
|
5498
5566
|
} catch {
|
|
5499
5567
|
return [];
|
|
5500
5568
|
}
|
|
5501
5569
|
},
|
|
5502
5570
|
persist: (next) => {
|
|
5503
|
-
|
|
5571
|
+
writeFileSync8(this.path(file), JSON.stringify(next));
|
|
5504
5572
|
}
|
|
5505
5573
|
};
|
|
5506
5574
|
}
|
|
@@ -5513,7 +5581,7 @@ var State = class _State {
|
|
|
5513
5581
|
const file = ".release-last-run";
|
|
5514
5582
|
return {
|
|
5515
5583
|
touch: () => {
|
|
5516
|
-
|
|
5584
|
+
writeFileSync8(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
5517
5585
|
},
|
|
5518
5586
|
at: () => {
|
|
5519
5587
|
try {
|
|
@@ -5526,7 +5594,7 @@ var State = class _State {
|
|
|
5526
5594
|
}
|
|
5527
5595
|
readInt(file) {
|
|
5528
5596
|
try {
|
|
5529
|
-
return Number.parseInt(
|
|
5597
|
+
return Number.parseInt(readFileSync11(this.path(file), "utf8").trim(), 10) || 0;
|
|
5530
5598
|
} catch {
|
|
5531
5599
|
return 0;
|
|
5532
5600
|
}
|
|
@@ -6528,12 +6596,12 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
6528
6596
|
|
|
6529
6597
|
// src/run-worktree.ts
|
|
6530
6598
|
import { existsSync as existsSync14 } from "fs";
|
|
6531
|
-
import { dirname as dirname6, join as
|
|
6599
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
6532
6600
|
|
|
6533
6601
|
// src/ports.ts
|
|
6534
6602
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6535
6603
|
import { existsSync as existsSync13 } from "fs";
|
|
6536
|
-
import { join as
|
|
6604
|
+
import { join as join13 } from "path";
|
|
6537
6605
|
var run = (cmd, args) => {
|
|
6538
6606
|
try {
|
|
6539
6607
|
return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -6578,7 +6646,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
6578
6646
|
}
|
|
6579
6647
|
function worktreeExists(parent, prefix, n) {
|
|
6580
6648
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
6581
|
-
if (existsSync13(
|
|
6649
|
+
if (existsSync13(join13(parent, name, ".git"))) return true;
|
|
6582
6650
|
}
|
|
6583
6651
|
return false;
|
|
6584
6652
|
}
|
|
@@ -6670,7 +6738,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
6670
6738
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
6671
6739
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
6672
6740
|
}
|
|
6673
|
-
const path =
|
|
6741
|
+
const path = join14(dirname6(dir), worktreeForTicket(dir, cfg, t, role));
|
|
6674
6742
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
6675
6743
|
return { action: "cut-remote", path, branch: located.branch };
|
|
6676
6744
|
}
|
|
@@ -7014,12 +7082,12 @@ async function guardRunStrays(route, ship, tracker, role, workingId, since2, log
|
|
|
7014
7082
|
|
|
7015
7083
|
// src/pending-handoff.ts
|
|
7016
7084
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
7017
|
-
var pendingDirFor = (stateDir) =>
|
|
7018
|
-
var stateDirOfStream = (streamPath) =>
|
|
7085
|
+
var pendingDirFor = (stateDir) => join15(stateDir, "pending-handoffs");
|
|
7086
|
+
var stateDirOfStream = (streamPath) => join15(streamPath, "..", "..");
|
|
7019
7087
|
function writePendingHandoff(stateDir, rec) {
|
|
7020
7088
|
const dir = pendingDirFor(stateDir);
|
|
7021
|
-
|
|
7022
|
-
|
|
7089
|
+
mkdirSync8(dir, { recursive: true });
|
|
7090
|
+
writeFileSync9(join15(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
7023
7091
|
}
|
|
7024
7092
|
function listPendingHandoffs(stateDir, route) {
|
|
7025
7093
|
const dir = pendingDirFor(stateDir);
|
|
@@ -7027,7 +7095,7 @@ function listPendingHandoffs(stateDir, route) {
|
|
|
7027
7095
|
const out = [];
|
|
7028
7096
|
for (const f of readdirSync5(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7029
7097
|
try {
|
|
7030
|
-
const rec = JSON.parse(
|
|
7098
|
+
const rec = JSON.parse(readFileSync12(join15(dir, f), "utf8"));
|
|
7031
7099
|
if (rec.version === 1 && rec.ticketRecordId && (!route || rec.route === route)) out.push(rec);
|
|
7032
7100
|
} catch {
|
|
7033
7101
|
}
|
|
@@ -7035,7 +7103,7 @@ function listPendingHandoffs(stateDir, route) {
|
|
|
7035
7103
|
return out;
|
|
7036
7104
|
}
|
|
7037
7105
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
7038
|
-
rmSync8(
|
|
7106
|
+
rmSync8(join15(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
7039
7107
|
}
|
|
7040
7108
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
7041
7109
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -7145,8 +7213,8 @@ function expandSkillMarkers(text, lookup, opts = {}) {
|
|
|
7145
7213
|
}
|
|
7146
7214
|
|
|
7147
7215
|
// src/skills.ts
|
|
7148
|
-
import { readFileSync as
|
|
7149
|
-
import { join as
|
|
7216
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync6, existsSync as existsSync17 } from "fs";
|
|
7217
|
+
import { join as join17 } from "path";
|
|
7150
7218
|
import { parse as parseYaml } from "yaml";
|
|
7151
7219
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7152
7220
|
|
|
@@ -7162,8 +7230,8 @@ ${additions}`;
|
|
|
7162
7230
|
}
|
|
7163
7231
|
|
|
7164
7232
|
// src/agents.ts
|
|
7165
|
-
import { readFileSync as
|
|
7166
|
-
import { join as
|
|
7233
|
+
import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
|
|
7234
|
+
import { join as join16 } from "path";
|
|
7167
7235
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
7168
7236
|
var PERSONA_NAME = {
|
|
7169
7237
|
dev: "Developer",
|
|
@@ -7177,12 +7245,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
7177
7245
|
var AgentsSyncError = class extends Error {
|
|
7178
7246
|
};
|
|
7179
7247
|
function personaDefaultPrompt(promptsDir, role) {
|
|
7180
|
-
const briefPath =
|
|
7248
|
+
const briefPath = join16(promptsDir, "personas", `lane-${role}.md`);
|
|
7181
7249
|
if (!existsSync16(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
7182
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
7183
|
-
const commonPath =
|
|
7184
|
-
if (!existsSync16(commonPath)) return
|
|
7185
|
-
return `${
|
|
7250
|
+
if (SELF_CONTAINED_ROLES.has(role)) return readFileSync13(briefPath, "utf8");
|
|
7251
|
+
const commonPath = join16(promptsDir, "personas", "common.md");
|
|
7252
|
+
if (!existsSync16(commonPath)) return readFileSync13(briefPath, "utf8");
|
|
7253
|
+
return `${readFileSync13(commonPath, "utf8")}${readFileSync13(briefPath, "utf8")}`;
|
|
7186
7254
|
}
|
|
7187
7255
|
async function resolveAgentId(route, role, opts) {
|
|
7188
7256
|
const cached = route.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -7629,7 +7697,7 @@ function describeCrewLink(o) {
|
|
|
7629
7697
|
var SkillSyncError = class extends Error {
|
|
7630
7698
|
};
|
|
7631
7699
|
function parseSkillFile(path) {
|
|
7632
|
-
const text =
|
|
7700
|
+
const text = readFileSync14(path, "utf8");
|
|
7633
7701
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
|
|
7634
7702
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
7635
7703
|
const frontmatter = match[1];
|
|
@@ -7642,9 +7710,9 @@ function parseSkillFile(path) {
|
|
|
7642
7710
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
7643
7711
|
}
|
|
7644
7712
|
function listLocalSkills(promptsDir) {
|
|
7645
|
-
const dir =
|
|
7713
|
+
const dir = join17(promptsDir, "skills");
|
|
7646
7714
|
if (!existsSync17(dir)) return [];
|
|
7647
|
-
return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(
|
|
7715
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join17(dir, f)));
|
|
7648
7716
|
}
|
|
7649
7717
|
async function syncSkills(route, opts) {
|
|
7650
7718
|
if (!route.resolved) {
|
|
@@ -7847,23 +7915,23 @@ var AgentError = class extends Error {
|
|
|
7847
7915
|
var AgentSpawnError = class extends AgentError {
|
|
7848
7916
|
};
|
|
7849
7917
|
function planAgentRun(o) {
|
|
7850
|
-
const briefPath =
|
|
7918
|
+
const briefPath = join18(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
7851
7919
|
if (!existsSync18(briefPath)) {
|
|
7852
7920
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
7853
7921
|
}
|
|
7854
|
-
const commonPath =
|
|
7922
|
+
const commonPath = join18(o.route.promptsDir, "personas", "common.md");
|
|
7855
7923
|
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync18(commonPath);
|
|
7856
7924
|
const now = o.now ?? (() => Date.now());
|
|
7857
7925
|
const routeLabel = routeSlug(o.route.route);
|
|
7858
|
-
const digestPath2 =
|
|
7926
|
+
const digestPath2 = join18(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
|
|
7859
7927
|
let digest;
|
|
7860
7928
|
let ageSeconds;
|
|
7861
7929
|
if (existsSync18(digestPath2)) {
|
|
7862
7930
|
ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
|
|
7863
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
7931
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync15(digestPath2, "utf8");
|
|
7864
7932
|
}
|
|
7865
|
-
const common = usesCommon ?
|
|
7866
|
-
const brief =
|
|
7933
|
+
const common = usesCommon ? readFileSync15(commonPath, "utf8") : "";
|
|
7934
|
+
const brief = readFileSync15(briefPath, "utf8");
|
|
7867
7935
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
7868
7936
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
7869
7937
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -7884,8 +7952,8 @@ function planAgentRun(o) {
|
|
|
7884
7952
|
brief: expanded.text,
|
|
7885
7953
|
digest
|
|
7886
7954
|
});
|
|
7887
|
-
const promptSha =
|
|
7888
|
-
const streamsDir =
|
|
7955
|
+
const promptSha = createHash3("sha256").update(expanded.text).digest("hex");
|
|
7956
|
+
const streamsDir = join18(o.stateDir, "streams");
|
|
7889
7957
|
const base = `${routeLabel}-${o.role}-${o.cycle}`;
|
|
7890
7958
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
7891
7959
|
const { toolchain, model } = seat;
|
|
@@ -7941,8 +8009,8 @@ function planAgentRun(o) {
|
|
|
7941
8009
|
digestAttached: digest !== void 0,
|
|
7942
8010
|
digestAgeSeconds: ageSeconds,
|
|
7943
8011
|
model,
|
|
7944
|
-
streamPath:
|
|
7945
|
-
eventsPath:
|
|
8012
|
+
streamPath: join18(streamsDir, `${base}.jsonl`),
|
|
8013
|
+
eventsPath: join18(streamsDir, `${base}.events.jsonl`),
|
|
7946
8014
|
ticket: o.ticket,
|
|
7947
8015
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
7948
8016
|
agentLog,
|
|
@@ -7971,7 +8039,7 @@ function describePlan(p) {
|
|
|
7971
8039
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
7972
8040
|
function readRunSidecar(streamPath) {
|
|
7973
8041
|
try {
|
|
7974
|
-
const sc = JSON.parse(
|
|
8042
|
+
const sc = JSON.parse(readFileSync15(pidPathFor(streamPath), "utf8"));
|
|
7975
8043
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
7976
8044
|
} catch {
|
|
7977
8045
|
return null;
|
|
@@ -8007,7 +8075,7 @@ function identityOf(plan, adapterHarness) {
|
|
|
8007
8075
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
8008
8076
|
function openSink(path) {
|
|
8009
8077
|
try {
|
|
8010
|
-
|
|
8078
|
+
mkdirSync9(dirname7(path), { recursive: true });
|
|
8011
8079
|
return createWriteStream(path, { flags: "a" });
|
|
8012
8080
|
} catch {
|
|
8013
8081
|
return void 0;
|
|
@@ -8186,7 +8254,7 @@ var RunObserver = class {
|
|
|
8186
8254
|
};
|
|
8187
8255
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
8188
8256
|
try {
|
|
8189
|
-
|
|
8257
|
+
writeFileSync10(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
8190
8258
|
route: id.route,
|
|
8191
8259
|
role: id.role,
|
|
8192
8260
|
hint: id.ticket,
|
|
@@ -8305,7 +8373,7 @@ function spawnAgent(plan, emit) {
|
|
|
8305
8373
|
return new Promise((resolve6, reject) => {
|
|
8306
8374
|
let streamFd;
|
|
8307
8375
|
try {
|
|
8308
|
-
|
|
8376
|
+
mkdirSync9(dirname7(plan.streamPath), { recursive: true });
|
|
8309
8377
|
streamFd = openSync3(plan.streamPath, "a");
|
|
8310
8378
|
} catch (e) {
|
|
8311
8379
|
void endSink(eventsSink);
|
|
@@ -8338,7 +8406,7 @@ function spawnAgent(plan, emit) {
|
|
|
8338
8406
|
promptSha: plan.promptSha
|
|
8339
8407
|
};
|
|
8340
8408
|
try {
|
|
8341
|
-
|
|
8409
|
+
writeFileSync10(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
8342
8410
|
} catch {
|
|
8343
8411
|
}
|
|
8344
8412
|
}
|
|
@@ -8555,7 +8623,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
8555
8623
|
...adapter.runEnv(spec),
|
|
8556
8624
|
[KEY_VAR]: i.apiKey
|
|
8557
8625
|
};
|
|
8558
|
-
const cwd = mkdtempSync(
|
|
8626
|
+
const cwd = mkdtempSync(join19(tmpdir2(), "crew-selftest-"));
|
|
8559
8627
|
try {
|
|
8560
8628
|
const run2 = await deps.run({
|
|
8561
8629
|
bin: invocation.bin,
|
|
@@ -8587,11 +8655,11 @@ function formatResult(r) {
|
|
|
8587
8655
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
8588
8656
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
8589
8657
|
}
|
|
8590
|
-
var
|
|
8658
|
+
var fileFor2 = (stateDir) => join19(stateDir, "selftest.json");
|
|
8591
8659
|
function readSelftest(stateDir) {
|
|
8592
8660
|
try {
|
|
8593
|
-
const p =
|
|
8594
|
-
return existsSync19(p) ? JSON.parse(
|
|
8661
|
+
const p = fileFor2(stateDir);
|
|
8662
|
+
return existsSync19(p) ? JSON.parse(readFileSync16(p, "utf8")) : {};
|
|
8595
8663
|
} catch {
|
|
8596
8664
|
return {};
|
|
8597
8665
|
}
|
|
@@ -8599,8 +8667,8 @@ function readSelftest(stateDir) {
|
|
|
8599
8667
|
function recordSelftest(stateDir, results) {
|
|
8600
8668
|
const all = readSelftest(stateDir);
|
|
8601
8669
|
for (const r of results) all[r.toolchain] = r;
|
|
8602
|
-
|
|
8603
|
-
|
|
8670
|
+
mkdirSync10(stateDir, { recursive: true });
|
|
8671
|
+
writeFileSync11(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
8604
8672
|
`);
|
|
8605
8673
|
}
|
|
8606
8674
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -8612,7 +8680,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
8612
8680
|
}
|
|
8613
8681
|
|
|
8614
8682
|
// src/cli.ts
|
|
8615
|
-
import { resolve as resolve5, dirname as dirname20, join as
|
|
8683
|
+
import { resolve as resolve5, dirname as dirname20, join as join36, basename as basename11 } from "path";
|
|
8616
8684
|
import { fileURLToPath } from "url";
|
|
8617
8685
|
import { hostname as hostname2, homedir as homedir9 } from "os";
|
|
8618
8686
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
@@ -8632,6 +8700,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
8632
8700
|
"key",
|
|
8633
8701
|
"level",
|
|
8634
8702
|
"limit",
|
|
8703
|
+
"machine-id",
|
|
8635
8704
|
"max-roles",
|
|
8636
8705
|
"mcp",
|
|
8637
8706
|
"member",
|
|
@@ -8644,6 +8713,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
8644
8713
|
"ship-name",
|
|
8645
8714
|
"tail",
|
|
8646
8715
|
"ticket",
|
|
8716
|
+
"toolchain",
|
|
8647
8717
|
"workspace",
|
|
8648
8718
|
"workspace-id",
|
|
8649
8719
|
"write"
|
|
@@ -8789,14 +8859,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
8789
8859
|
}
|
|
8790
8860
|
|
|
8791
8861
|
// src/connect-resume.ts
|
|
8792
|
-
import { mkdirSync as
|
|
8793
|
-
import { dirname as dirname8, join as
|
|
8862
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync17, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
8863
|
+
import { dirname as dirname8, join as join20 } from "path";
|
|
8794
8864
|
function lastSessionFile(stateDir) {
|
|
8795
|
-
return
|
|
8865
|
+
return join20(stateDir, "connect-session.json");
|
|
8796
8866
|
}
|
|
8797
8867
|
function readPointers(stateDir) {
|
|
8798
8868
|
try {
|
|
8799
|
-
const parsed = JSON.parse(
|
|
8869
|
+
const parsed = JSON.parse(readFileSync17(lastSessionFile(stateDir), "utf8"));
|
|
8800
8870
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
8801
8871
|
const out = {};
|
|
8802
8872
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -8810,8 +8880,8 @@ function readPointers(stateDir) {
|
|
|
8810
8880
|
}
|
|
8811
8881
|
function writePointers(stateDir, pointers) {
|
|
8812
8882
|
const file = lastSessionFile(stateDir);
|
|
8813
|
-
|
|
8814
|
-
|
|
8883
|
+
mkdirSync11(dirname8(file), { recursive: true });
|
|
8884
|
+
writeFileSync12(file, `${JSON.stringify(pointers, null, 2)}
|
|
8815
8885
|
`, { mode: 384 });
|
|
8816
8886
|
}
|
|
8817
8887
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -8867,15 +8937,15 @@ async function signOutSessions(o) {
|
|
|
8867
8937
|
}
|
|
8868
8938
|
|
|
8869
8939
|
// src/reset.ts
|
|
8870
|
-
import { existsSync as existsSync20, mkdirSync as
|
|
8871
|
-
import { dirname as dirname9, join as
|
|
8940
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as readFileSync18, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync13 } from "fs";
|
|
8941
|
+
import { dirname as dirname9, join as join21 } from "path";
|
|
8872
8942
|
import { hostFromUrl as hostFromUrl2 } from "@tablation/client";
|
|
8873
8943
|
function clonedCheckoutsFile(stateDir) {
|
|
8874
|
-
return
|
|
8944
|
+
return join21(stateDir, "cloned-checkouts.json");
|
|
8875
8945
|
}
|
|
8876
8946
|
function readClonedCheckouts(stateDir) {
|
|
8877
8947
|
try {
|
|
8878
|
-
const parsed = JSON.parse(
|
|
8948
|
+
const parsed = JSON.parse(readFileSync18(clonedCheckoutsFile(stateDir), "utf8"));
|
|
8879
8949
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
8880
8950
|
} catch {
|
|
8881
8951
|
return [];
|
|
@@ -8885,8 +8955,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
8885
8955
|
const known = readClonedCheckouts(stateDir);
|
|
8886
8956
|
if (known.includes(dir)) return;
|
|
8887
8957
|
const file = clonedCheckoutsFile(stateDir);
|
|
8888
|
-
|
|
8889
|
-
|
|
8958
|
+
mkdirSync12(dirname9(file), { recursive: true });
|
|
8959
|
+
writeFileSync13(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
8890
8960
|
`);
|
|
8891
8961
|
}
|
|
8892
8962
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -8960,10 +9030,10 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
8960
9030
|
}
|
|
8961
9031
|
function removeState(stateDir, dryRun) {
|
|
8962
9032
|
const out = [];
|
|
8963
|
-
const targets = [
|
|
9033
|
+
const targets = [join21(stateDir, "resolved"), join21(stateDir, "keys"), clonedCheckoutsFile(stateDir), join21(stateDir, "connect-session.json"), join21(stateDir, "reload-requested"), join21(stateDir, "daemon.started")];
|
|
8964
9034
|
try {
|
|
8965
9035
|
for (const name of readdirSync7(stateDir)) {
|
|
8966
|
-
if (name.startsWith(".") && statSync6(
|
|
9036
|
+
if (name.startsWith(".") && statSync6(join21(stateDir, name)).isFile()) targets.push(join21(stateDir, name));
|
|
8967
9037
|
}
|
|
8968
9038
|
} catch {
|
|
8969
9039
|
}
|
|
@@ -9748,8 +9818,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
9748
9818
|
}
|
|
9749
9819
|
|
|
9750
9820
|
// src/poll.ts
|
|
9751
|
-
import { writeFileSync as
|
|
9752
|
-
import { join as
|
|
9821
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
9822
|
+
import { join as join22 } from "path";
|
|
9753
9823
|
|
|
9754
9824
|
// src/stalled.ts
|
|
9755
9825
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -10040,7 +10110,7 @@ async function decideCycle(o) {
|
|
|
10040
10110
|
}
|
|
10041
10111
|
return decision;
|
|
10042
10112
|
}
|
|
10043
|
-
var digestPath = (stateDir, route, role) =>
|
|
10113
|
+
var digestPath = (stateDir, route, role) => join22(stateDir, `digest-${route}-${role}.md`);
|
|
10044
10114
|
function writeDigest(o, d, role, stateDir) {
|
|
10045
10115
|
const me = o.route.resolved?.seats[role];
|
|
10046
10116
|
if (!me) return false;
|
|
@@ -10108,7 +10178,7 @@ function writeDigest(o, d, role, stateDir) {
|
|
|
10108
10178
|
};
|
|
10109
10179
|
const text = role === "qa" ? qaDigest(input) : buildingDigest(input);
|
|
10110
10180
|
const path = digestPath(stateDir, routeSlug(o.route.route), role);
|
|
10111
|
-
|
|
10181
|
+
writeFileSync14(path, text);
|
|
10112
10182
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
|
|
10113
10183
|
data: { role, bytes: Buffer.byteLength(text) }
|
|
10114
10184
|
});
|
|
@@ -10514,8 +10584,8 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
|
|
|
10514
10584
|
}
|
|
10515
10585
|
|
|
10516
10586
|
// src/connect-wizard.ts
|
|
10517
|
-
import { delimiter, dirname as dirname10, join as
|
|
10518
|
-
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as
|
|
10587
|
+
import { delimiter, dirname as dirname10, join as join23 } from "path";
|
|
10588
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
|
|
10519
10589
|
import { stringify } from "yaml";
|
|
10520
10590
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
10521
10591
|
function shouldRunWizard(opts) {
|
|
@@ -10537,7 +10607,7 @@ function defaultIsExecutable(p) {
|
|
|
10537
10607
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
10538
10608
|
for (const dir of pathEnv.split(delimiter)) {
|
|
10539
10609
|
if (!dir) continue;
|
|
10540
|
-
const candidate =
|
|
10610
|
+
const candidate = join23(dir, name);
|
|
10541
10611
|
if (isExecutable(candidate)) return candidate;
|
|
10542
10612
|
}
|
|
10543
10613
|
return void 0;
|
|
@@ -10598,9 +10668,9 @@ routes:
|
|
|
10598
10668
|
${routeBlock}`;
|
|
10599
10669
|
}
|
|
10600
10670
|
function writeNewConfig(path, text) {
|
|
10601
|
-
|
|
10671
|
+
mkdirSync13(dirname10(path), { recursive: true });
|
|
10602
10672
|
try {
|
|
10603
|
-
|
|
10673
|
+
writeFileSync15(path, text, { mode: 384, flag: "wx" });
|
|
10604
10674
|
return true;
|
|
10605
10675
|
} catch (e) {
|
|
10606
10676
|
if (e.code === "EEXIST") return false;
|
|
@@ -10634,7 +10704,7 @@ Wrote ${o.configPath}.
|
|
|
10634
10704
|
// src/connect-json.ts
|
|
10635
10705
|
import { accessSync as accessSync3, constants as constants3 } from "fs";
|
|
10636
10706
|
import { homedir as homedir7 } from "os";
|
|
10637
|
-
import { dirname as dirname11, join as
|
|
10707
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
10638
10708
|
import { delimiter as delimiter2 } from "path";
|
|
10639
10709
|
var CHOICE_EXIT_CODE = 3;
|
|
10640
10710
|
function createEmitter(json, write) {
|
|
@@ -10673,12 +10743,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
10673
10743
|
const dirs = [
|
|
10674
10744
|
...pathEnv.split(delimiter2),
|
|
10675
10745
|
...(extraPath ?? "").split(delimiter2),
|
|
10676
|
-
|
|
10677
|
-
|
|
10746
|
+
join24(home, ".local/bin"),
|
|
10747
|
+
join24(home, ".claude/local"),
|
|
10678
10748
|
"/opt/homebrew/bin",
|
|
10679
10749
|
"/usr/local/bin",
|
|
10680
|
-
|
|
10681
|
-
|
|
10750
|
+
join24(home, ".npm-global/bin"),
|
|
10751
|
+
join24(home, ".volta/bin")
|
|
10682
10752
|
];
|
|
10683
10753
|
return [...new Set(dirs.filter(Boolean))];
|
|
10684
10754
|
}
|
|
@@ -10714,8 +10784,8 @@ function flagWizardAnswers(f, env) {
|
|
|
10714
10784
|
}
|
|
10715
10785
|
|
|
10716
10786
|
// src/release-run.ts
|
|
10717
|
-
import { readFileSync as
|
|
10718
|
-
import { dirname as dirname12, join as
|
|
10787
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync16, existsSync as existsSync22, copyFileSync, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync12 } from "fs";
|
|
10788
|
+
import { dirname as dirname12, join as join25 } from "path";
|
|
10719
10789
|
import { tmpdir as tmpdir3 } from "os";
|
|
10720
10790
|
|
|
10721
10791
|
// src/release.ts
|
|
@@ -11433,13 +11503,13 @@ async function bump(o, size) {
|
|
|
11433
11503
|
}
|
|
11434
11504
|
return printed;
|
|
11435
11505
|
}
|
|
11436
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
11506
|
+
const files = o.repo.release.versionFiles.map((f) => join25(o.cwd, f));
|
|
11437
11507
|
const first = files[0];
|
|
11438
11508
|
if (!existsSync22(first)) {
|
|
11439
11509
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
11440
11510
|
return null;
|
|
11441
11511
|
}
|
|
11442
|
-
const current = JSON.parse(
|
|
11512
|
+
const current = JSON.parse(readFileSync19(first, "utf8")).version;
|
|
11443
11513
|
if (!current) {
|
|
11444
11514
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
11445
11515
|
return null;
|
|
@@ -11452,27 +11522,27 @@ async function bump(o, size) {
|
|
|
11452
11522
|
return version;
|
|
11453
11523
|
}
|
|
11454
11524
|
for (const f of files) {
|
|
11455
|
-
const src =
|
|
11525
|
+
const src = readFileSync19(f, "utf8");
|
|
11456
11526
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
11457
11527
|
if (out === src) {
|
|
11458
11528
|
o.emit.error(`no version field in ${f}`);
|
|
11459
11529
|
return null;
|
|
11460
11530
|
}
|
|
11461
|
-
|
|
11531
|
+
writeFileSync16(f, out);
|
|
11462
11532
|
}
|
|
11463
11533
|
return version;
|
|
11464
11534
|
}
|
|
11465
11535
|
function writeChangelog(o, version, entries) {
|
|
11466
11536
|
const file = o.repo.release.changelog;
|
|
11467
11537
|
if (!file || entries.length === 0) return;
|
|
11468
|
-
const path =
|
|
11538
|
+
const path = join25(o.cwd, file);
|
|
11469
11539
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
11470
11540
|
if (o.dryRun) {
|
|
11471
11541
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
11472
11542
|
return;
|
|
11473
11543
|
}
|
|
11474
|
-
const existing = existsSync22(path) ?
|
|
11475
|
-
|
|
11544
|
+
const existing = existsSync22(path) ? readFileSync19(path, "utf8") : "# Changelog\n";
|
|
11545
|
+
writeFileSync16(path, insertChangelogSection(existing, section));
|
|
11476
11546
|
}
|
|
11477
11547
|
async function confirm(o, expected) {
|
|
11478
11548
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -11565,8 +11635,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
11565
11635
|
function openReleaseCheckout(o) {
|
|
11566
11636
|
const { remote, base } = o.repo.branch;
|
|
11567
11637
|
const branch = `crew/release-${Date.now()}`;
|
|
11568
|
-
const root = mkdtempSync2(
|
|
11569
|
-
const dir =
|
|
11638
|
+
const root = mkdtempSync2(join25(tmpdir3(), "crew-release-"));
|
|
11639
|
+
const dir = join25(root, "checkout");
|
|
11570
11640
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
11571
11641
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
11572
11642
|
try {
|
|
@@ -11576,10 +11646,10 @@ function openReleaseCheckout(o) {
|
|
|
11576
11646
|
throw e;
|
|
11577
11647
|
}
|
|
11578
11648
|
for (const f of o.repo.worktrees.copy) {
|
|
11579
|
-
const src =
|
|
11649
|
+
const src = join25(o.cwd, f);
|
|
11580
11650
|
if (!existsSync22(src)) continue;
|
|
11581
|
-
|
|
11582
|
-
copyFileSync(src,
|
|
11651
|
+
mkdirSync14(dirname12(join25(dir, f)), { recursive: true });
|
|
11652
|
+
copyFileSync(src, join25(dir, f));
|
|
11583
11653
|
}
|
|
11584
11654
|
return { dir, branch, close };
|
|
11585
11655
|
}
|
|
@@ -12650,19 +12720,19 @@ function nextRoles(decision, ran) {
|
|
|
12650
12720
|
}
|
|
12651
12721
|
|
|
12652
12722
|
// src/ship-name.ts
|
|
12653
|
-
import { existsSync as existsSync25, mkdirSync as
|
|
12723
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync21, renameSync as renameSync2, writeFileSync as writeFileSync18 } from "fs";
|
|
12654
12724
|
import { dirname as dirname14 } from "path";
|
|
12655
12725
|
import { parseDocument } from "yaml";
|
|
12656
12726
|
|
|
12657
12727
|
// src/machine-id.ts
|
|
12658
12728
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
12659
|
-
import { createHash as
|
|
12660
|
-
import { existsSync as existsSync24, mkdirSync as
|
|
12661
|
-
import { dirname as dirname13, join as
|
|
12729
|
+
import { createHash as createHash4 } from "crypto";
|
|
12730
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
|
|
12731
|
+
import { dirname as dirname13, join as join26 } from "path";
|
|
12662
12732
|
function machineIdPathFor(stateDir) {
|
|
12663
|
-
return
|
|
12733
|
+
return join26(stateDir, "machine-id");
|
|
12664
12734
|
}
|
|
12665
|
-
var sha256 = (s) =>
|
|
12735
|
+
var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
12666
12736
|
function readRawMachineId(platform2 = process.platform) {
|
|
12667
12737
|
try {
|
|
12668
12738
|
if (platform2 === "darwin") {
|
|
@@ -12674,7 +12744,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
12674
12744
|
}
|
|
12675
12745
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
12676
12746
|
if (existsSync24(p)) {
|
|
12677
|
-
const id =
|
|
12747
|
+
const id = readFileSync20(p, "utf8").trim();
|
|
12678
12748
|
if (id) return id;
|
|
12679
12749
|
}
|
|
12680
12750
|
}
|
|
@@ -12686,14 +12756,14 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
12686
12756
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
12687
12757
|
const path = machineIdPathFor(stateDir);
|
|
12688
12758
|
if (existsSync24(path)) {
|
|
12689
|
-
const cached =
|
|
12759
|
+
const cached = readFileSync20(path, "utf8").trim();
|
|
12690
12760
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
12691
12761
|
}
|
|
12692
12762
|
const raw = (opts.read ?? readRawMachineId)();
|
|
12693
12763
|
if (!raw) return void 0;
|
|
12694
12764
|
const hashed = sha256(raw);
|
|
12695
|
-
|
|
12696
|
-
|
|
12765
|
+
mkdirSync15(dirname13(path), { recursive: true });
|
|
12766
|
+
writeFileSync17(path, `${hashed}
|
|
12697
12767
|
`, { mode: 384 });
|
|
12698
12768
|
return hashed;
|
|
12699
12769
|
}
|
|
@@ -12766,7 +12836,7 @@ async function syncRouteName(i) {
|
|
|
12766
12836
|
function readSyncedName(resolvedFile) {
|
|
12767
12837
|
if (!existsSync25(resolvedFile)) return void 0;
|
|
12768
12838
|
try {
|
|
12769
|
-
const v = JSON.parse(
|
|
12839
|
+
const v = JSON.parse(readFileSync21(resolvedFile, "utf8")).shipName;
|
|
12770
12840
|
return typeof v === "string" ? v : void 0;
|
|
12771
12841
|
} catch {
|
|
12772
12842
|
return void 0;
|
|
@@ -12776,22 +12846,22 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
12776
12846
|
if (!existsSync25(resolvedFile)) return;
|
|
12777
12847
|
let raw;
|
|
12778
12848
|
try {
|
|
12779
|
-
raw = JSON.parse(
|
|
12849
|
+
raw = JSON.parse(readFileSync21(resolvedFile, "utf8"));
|
|
12780
12850
|
} catch {
|
|
12781
12851
|
return;
|
|
12782
12852
|
}
|
|
12783
12853
|
if (raw.shipName === name) return;
|
|
12784
12854
|
raw.shipName = name;
|
|
12785
|
-
|
|
12855
|
+
mkdirSync16(dirname14(resolvedFile), { recursive: true });
|
|
12786
12856
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
12787
|
-
|
|
12857
|
+
writeFileSync18(tmp, `${JSON.stringify(raw, null, 2)}
|
|
12788
12858
|
`);
|
|
12789
12859
|
renameSync2(tmp, resolvedFile);
|
|
12790
12860
|
}
|
|
12791
12861
|
async function reconcileShipName(i) {
|
|
12792
12862
|
let local = i.ship.name;
|
|
12793
12863
|
try {
|
|
12794
|
-
local = shipNameOfYaml(
|
|
12864
|
+
local = shipNameOfYaml(readFileSync21(i.configFile, "utf8")) ?? local;
|
|
12795
12865
|
} catch {
|
|
12796
12866
|
}
|
|
12797
12867
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -12815,8 +12885,8 @@ async function reconcileShipName(i) {
|
|
|
12815
12885
|
}
|
|
12816
12886
|
if (result.status !== "ok") return result;
|
|
12817
12887
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
12818
|
-
const text =
|
|
12819
|
-
|
|
12888
|
+
const text = readFileSync21(i.configFile, "utf8");
|
|
12889
|
+
writeFileSync18(i.configFile, setShipNameInYaml(text, result.name));
|
|
12820
12890
|
i.ship.name = result.name;
|
|
12821
12891
|
if (result.action === "row-wins") {
|
|
12822
12892
|
i.emit?.warn(
|
|
@@ -13399,7 +13469,7 @@ function renderConnection(d, route, dir, opts = {}) {
|
|
|
13399
13469
|
return ` - route: ${route}
|
|
13400
13470
|
enabled: false # arm it deliberately, once doctor is green
|
|
13401
13471
|
${opts.area ? ` area: "${opts.area}"
|
|
13402
|
-
` : ""} dir: "${dir}"
|
|
13472
|
+
` : ""} ${dir === null ? "repos: {} # `crew repos clone` / `crew repos add` fill this in" : `dir: "${dir}"`}
|
|
13403
13473
|
# worktreePrefix: omitted \u2014 each repo derives its own from its checkout's
|
|
13404
13474
|
# directory name (ISSUE-400). Only set this if every repo this route
|
|
13405
13475
|
# serves genuinely wants the SAME prefix, which is rare once an area has
|
|
@@ -13724,7 +13794,7 @@ function describeCapability(i) {
|
|
|
13724
13794
|
}
|
|
13725
13795
|
|
|
13726
13796
|
// src/agents-sync-run.ts
|
|
13727
|
-
import { existsSync as existsSync26, mkdirSync as
|
|
13797
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
|
|
13728
13798
|
import { dirname as dirname15 } from "path";
|
|
13729
13799
|
async function runAgentsSync(target, o) {
|
|
13730
13800
|
const dryRun = o.dryRun ?? false;
|
|
@@ -13758,12 +13828,12 @@ async function runAgentsSync(target, o) {
|
|
|
13758
13828
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
13759
13829
|
if (!dryRun) {
|
|
13760
13830
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
13761
|
-
const raw = existsSync26(resolvedPath) ? JSON.parse(
|
|
13831
|
+
const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync22(resolvedPath, "utf8")) : {};
|
|
13762
13832
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
13763
13833
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
13764
13834
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
13765
|
-
|
|
13766
|
-
|
|
13835
|
+
mkdirSync17(dirname15(resolvedPath), { recursive: true });
|
|
13836
|
+
writeFileSync19(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
13767
13837
|
`);
|
|
13768
13838
|
if (target.resolved) {
|
|
13769
13839
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -13778,7 +13848,7 @@ async function runAgentsSync(target, o) {
|
|
|
13778
13848
|
}
|
|
13779
13849
|
|
|
13780
13850
|
// src/logbook.ts
|
|
13781
|
-
import { createHash as
|
|
13851
|
+
import { createHash as createHash5 } from "crypto";
|
|
13782
13852
|
import { TablationClient as TablationClient4 } from "@tablation/client";
|
|
13783
13853
|
var LogbookError = class extends Error {
|
|
13784
13854
|
};
|
|
@@ -13851,7 +13921,7 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
13851
13921
|
};
|
|
13852
13922
|
}
|
|
13853
13923
|
const prompt = appendAdditions(match.prompt ?? "", match.prompt_additions);
|
|
13854
|
-
const actualSha =
|
|
13924
|
+
const actualSha = createHash5("sha256").update(prompt).digest("hex");
|
|
13855
13925
|
const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
|
|
13856
13926
|
return {
|
|
13857
13927
|
entry,
|
|
@@ -13870,7 +13940,7 @@ import { isMap as isMap2, parseDocument as parseDocument3, YAMLMap } from "yaml"
|
|
|
13870
13940
|
|
|
13871
13941
|
// src/repos-cmd.ts
|
|
13872
13942
|
import { homedir as homedir8 } from "os";
|
|
13873
|
-
import { basename as basename8, join as
|
|
13943
|
+
import { basename as basename8, join as join27, resolve as resolve3 } from "path";
|
|
13874
13944
|
import { isMap, isSeq, parseDocument as parseDocument2, stringify as stringify2 } from "yaml";
|
|
13875
13945
|
var ReposError = class extends Error {
|
|
13876
13946
|
};
|
|
@@ -13944,6 +14014,15 @@ function configuredRepos(text, route, base) {
|
|
|
13944
14014
|
const dir = m.get("dir");
|
|
13945
14015
|
return dir ? [{ name: basename8(String(dir)), dir: expandDir(String(dir), base) }] : [];
|
|
13946
14016
|
}
|
|
14017
|
+
function cloneBaseOf(text, route, base, isCheckout) {
|
|
14018
|
+
const m = routeMapOf(parseDocument2(text), route);
|
|
14019
|
+
const repos = m.get("repos", true);
|
|
14020
|
+
if (isMap(repos) && repos.items.length > 0) return null;
|
|
14021
|
+
const dir = m.get("dir");
|
|
14022
|
+
if (!dir) return null;
|
|
14023
|
+
const abs = expandDir(String(dir), base);
|
|
14024
|
+
return isCheckout(abs) ? null : abs;
|
|
14025
|
+
}
|
|
13947
14026
|
function routeSetsHooks(text, route, name) {
|
|
13948
14027
|
const m = routeMapOf(parseDocument2(text), route);
|
|
13949
14028
|
const repos = m.get("repos", true);
|
|
@@ -13954,7 +14033,8 @@ function routeSetsHooks(text, route, name) {
|
|
|
13954
14033
|
return isMap(hooks) && hooks.items.length > 0;
|
|
13955
14034
|
}
|
|
13956
14035
|
function listRepoRows(i) {
|
|
13957
|
-
const
|
|
14036
|
+
const cloneBase = i.isCheckout ? cloneBaseOf(i.text, i.route, i.base, i.isCheckout) : null;
|
|
14037
|
+
const checkouts = configuredRepos(i.text, i.route, i.base).filter((c) => c.dir !== cloneBase);
|
|
13958
14038
|
const claimed = /* @__PURE__ */ new Set();
|
|
13959
14039
|
const hooksSourceOf = (c) => i.exists(c.dir) && i.repoHasHooks(c.dir) ? "repo" : routeSetsHooks(i.text, i.route, c.name) ? "route" : "none";
|
|
13960
14040
|
const originFor = (c, remote) => {
|
|
@@ -14004,8 +14084,19 @@ function planRepoAdd(i) {
|
|
|
14004
14084
|
const doc = parseDocument2(i.text);
|
|
14005
14085
|
if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
|
|
14006
14086
|
const m = routeMapOf(doc, i.route);
|
|
14007
|
-
|
|
14087
|
+
let existing = configuredRepos(i.text, i.route, i.base);
|
|
14008
14088
|
const target = expandDir(i.path, i.base);
|
|
14089
|
+
let droppedDir;
|
|
14090
|
+
const placeholder = m.get("dir");
|
|
14091
|
+
const reposNow = m.get("repos", true);
|
|
14092
|
+
if (i.isCheckout && placeholder && !(isMap(reposNow) && reposNow.items.length > 0)) {
|
|
14093
|
+
const placeholderDir = expandDir(String(placeholder), i.base);
|
|
14094
|
+
if (placeholderDir !== target && !i.isCheckout(placeholderDir)) {
|
|
14095
|
+
droppedDir = placeholderDir;
|
|
14096
|
+
m.delete("dir");
|
|
14097
|
+
existing = [];
|
|
14098
|
+
}
|
|
14099
|
+
}
|
|
14009
14100
|
if (existing.some((e) => e.dir === target)) {
|
|
14010
14101
|
throw new ReposError(`${target} is already configured on route ${i.route}`);
|
|
14011
14102
|
}
|
|
@@ -14031,11 +14122,14 @@ function planRepoAdd(i) {
|
|
|
14031
14122
|
pair.key = doc.createNode("repos");
|
|
14032
14123
|
pair.value = doc.createNode({ [oldName]: oldDir, [name]: target });
|
|
14033
14124
|
mode = "repos";
|
|
14125
|
+
} else if (droppedDir) {
|
|
14126
|
+
m.set("repos", doc.createNode({ [name]: target }));
|
|
14127
|
+
mode = "repos";
|
|
14034
14128
|
} else {
|
|
14035
14129
|
m.set("dir", target);
|
|
14036
14130
|
mode = "dir";
|
|
14037
14131
|
}
|
|
14038
|
-
return { text: String(doc), mode, name, block: renderBlock(m), warning, unmatched: !matched };
|
|
14132
|
+
return { text: String(doc), mode, name, block: renderBlock(m), warning, unmatched: !matched, droppedDir };
|
|
14039
14133
|
}
|
|
14040
14134
|
function planRepoRemove(text, base, route, path) {
|
|
14041
14135
|
const doc = parseDocument2(text);
|
|
@@ -14124,8 +14218,8 @@ function planRepoClone(i) {
|
|
|
14124
14218
|
}
|
|
14125
14219
|
const ship = doc.get("ship", true);
|
|
14126
14220
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14127
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14128
|
-
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name });
|
|
14221
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join27(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14222
|
+
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14129
14223
|
return { ...add, remote, target };
|
|
14130
14224
|
}
|
|
14131
14225
|
|
|
@@ -14504,7 +14598,7 @@ ${run2.err}` });
|
|
|
14504
14598
|
}
|
|
14505
14599
|
|
|
14506
14600
|
// src/sample-ticket.ts
|
|
14507
|
-
import { basename as basename9, dirname as dirname17, join as
|
|
14601
|
+
import { basename as basename9, dirname as dirname17, join as join28 } from "path";
|
|
14508
14602
|
var SAMPLE_REPO = "crew-sample";
|
|
14509
14603
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
14510
14604
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -14527,8 +14621,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
14527
14621
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
14528
14622
|
`;
|
|
14529
14623
|
function sampleDir(existing, reposBase, workspace) {
|
|
14530
|
-
if (existing.length > 0) return
|
|
14531
|
-
return
|
|
14624
|
+
if (existing.length > 0) return join28(dirname17(existing[0]), SAMPLE_REPO);
|
|
14625
|
+
return join28(reposBase, workspace, SAMPLE_REPO);
|
|
14532
14626
|
}
|
|
14533
14627
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
14534
14628
|
function sampleDescription() {
|
|
@@ -14609,8 +14703,8 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
14609
14703
|
}
|
|
14610
14704
|
|
|
14611
14705
|
// src/worktree-sweep.ts
|
|
14612
|
-
import { existsSync as existsSync27, readFileSync as
|
|
14613
|
-
import { basename as basename10, dirname as dirname18, join as
|
|
14706
|
+
import { existsSync as existsSync27, readFileSync as readFileSync23, readdirSync as readdirSync8 } from "fs";
|
|
14707
|
+
import { basename as basename10, dirname as dirname18, join as join29, resolve as resolve4 } from "path";
|
|
14614
14708
|
function planWorktreeSweep(target, tickets, contract) {
|
|
14615
14709
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
14616
14710
|
const verified = contract.statuses.verified;
|
|
@@ -14677,7 +14771,7 @@ function siblingWorktreeDirs(target) {
|
|
|
14677
14771
|
for (const name of names) {
|
|
14678
14772
|
if (!name.startsWith(stem)) continue;
|
|
14679
14773
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
14680
|
-
const path =
|
|
14774
|
+
const path = join29(parent, name);
|
|
14681
14775
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
14682
14776
|
}
|
|
14683
14777
|
return out;
|
|
@@ -14780,7 +14874,7 @@ function isHandBackMerge(path, base) {
|
|
|
14780
14874
|
if (!msgPath) return false;
|
|
14781
14875
|
let first;
|
|
14782
14876
|
try {
|
|
14783
|
-
first =
|
|
14877
|
+
first = readFileSync23(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
14784
14878
|
} catch {
|
|
14785
14879
|
return false;
|
|
14786
14880
|
}
|
|
@@ -14886,13 +14980,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
14886
14980
|
|
|
14887
14981
|
// src/stream-sweep.ts
|
|
14888
14982
|
import { existsSync as existsSync28, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8 } from "fs";
|
|
14889
|
-
import { join as
|
|
14983
|
+
import { join as join30 } from "path";
|
|
14890
14984
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
14891
14985
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
14892
|
-
const dir =
|
|
14986
|
+
const dir = join30(stateDir, "streams");
|
|
14893
14987
|
if (!existsSync28(dir)) return [];
|
|
14894
14988
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
14895
|
-
return readdirSync9(dir).map((name) =>
|
|
14989
|
+
return readdirSync9(dir).map((name) => join30(dir, name)).filter((path) => {
|
|
14896
14990
|
try {
|
|
14897
14991
|
return statSync8(path).mtimeMs < cutoff;
|
|
14898
14992
|
} catch {
|
|
@@ -14920,7 +15014,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
14920
15014
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
14921
15015
|
if (!existsSync28(stateDir)) return [];
|
|
14922
15016
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
14923
|
-
return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) =>
|
|
15017
|
+
return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join30(stateDir, name)).filter((path) => {
|
|
14924
15018
|
try {
|
|
14925
15019
|
return statSync8(path).mtimeMs < cutoff;
|
|
14926
15020
|
} catch {
|
|
@@ -14930,24 +15024,24 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
14930
15024
|
}
|
|
14931
15025
|
|
|
14932
15026
|
// src/cli.ts
|
|
14933
|
-
import { realpathSync as realpathSync3, readFileSync as
|
|
15027
|
+
import { realpathSync as realpathSync3, readFileSync as readFileSync29, existsSync as existsSync34, mkdirSync as mkdirSync22, writeFileSync as writeFileSync23, rmSync as rmSync15, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync10, readdirSync as readdirSync14 } from "fs";
|
|
14934
15028
|
import { createInterface } from "readline/promises";
|
|
14935
15029
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
14936
15030
|
|
|
14937
15031
|
// src/ssh-keys.ts
|
|
14938
15032
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
14939
|
-
import { existsSync as existsSync29, mkdirSync as
|
|
14940
|
-
import { dirname as dirname19, join as
|
|
15033
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync24, chmodSync } from "fs";
|
|
15034
|
+
import { dirname as dirname19, join as join31 } from "path";
|
|
14941
15035
|
var SshKeyError = class extends Error {
|
|
14942
15036
|
};
|
|
14943
15037
|
function shipSshKeyPathFor(stateDir) {
|
|
14944
|
-
return
|
|
15038
|
+
return join31(stateDir, "ssh", "id_ed25519");
|
|
14945
15039
|
}
|
|
14946
15040
|
function ensureShipSshKeypair(stateDir) {
|
|
14947
15041
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
14948
15042
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
14949
15043
|
if (!existsSync29(privateKeyPath)) {
|
|
14950
|
-
|
|
15044
|
+
mkdirSync18(dirname19(privateKeyPath), { recursive: true });
|
|
14951
15045
|
try {
|
|
14952
15046
|
execFileSync6("ssh-keygen", [
|
|
14953
15047
|
"-t",
|
|
@@ -14970,7 +15064,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
14970
15064
|
if (!existsSync29(publicKeyPath)) {
|
|
14971
15065
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
14972
15066
|
}
|
|
14973
|
-
const publicKey =
|
|
15067
|
+
const publicKey = readFileSync24(publicKeyPath, "utf8").trim();
|
|
14974
15068
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
14975
15069
|
}
|
|
14976
15070
|
function sshKeygenAvailable() {
|
|
@@ -14984,7 +15078,7 @@ function sshKeygenAvailable() {
|
|
|
14984
15078
|
|
|
14985
15079
|
// src/passenger-containers.ts
|
|
14986
15080
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
14987
|
-
import { createHash as
|
|
15081
|
+
import { createHash as createHash6 } from "crypto";
|
|
14988
15082
|
var realExec = (cmd, args) => {
|
|
14989
15083
|
try {
|
|
14990
15084
|
return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -15021,12 +15115,12 @@ function containerNameFor(workspaceId) {
|
|
|
15021
15115
|
return `crew-passenger-${workspaceId}`;
|
|
15022
15116
|
}
|
|
15023
15117
|
function portFor(workspaceId, base = 28765, span = 1e3) {
|
|
15024
|
-
const digest =
|
|
15118
|
+
const digest = createHash6("sha256").update(workspaceId).digest();
|
|
15025
15119
|
return base + digest.readUInt32BE(0) % span;
|
|
15026
15120
|
}
|
|
15027
15121
|
function mountsDigest(mounts, image) {
|
|
15028
15122
|
const sorted2 = [...mounts].sort((a, b) => a.containerPath.localeCompare(b.containerPath));
|
|
15029
|
-
return
|
|
15123
|
+
return createHash6("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
|
|
15030
15124
|
}
|
|
15031
15125
|
function planContainers(cfg) {
|
|
15032
15126
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
@@ -15186,9 +15280,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
15186
15280
|
|
|
15187
15281
|
// src/tunnel.ts
|
|
15188
15282
|
import { spawn as spawn5 } from "child_process";
|
|
15189
|
-
import { existsSync as existsSync30, mkdirSync as
|
|
15283
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync20, unlinkSync as unlinkSync3, readdirSync as readdirSync10 } from "fs";
|
|
15190
15284
|
import { randomInt } from "crypto";
|
|
15191
|
-
import { join as
|
|
15285
|
+
import { join as join32 } from "path";
|
|
15192
15286
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
15193
15287
|
var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
15194
15288
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -15255,21 +15349,21 @@ function startTunnel(opts) {
|
|
|
15255
15349
|
};
|
|
15256
15350
|
}
|
|
15257
15351
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
15258
|
-
return
|
|
15352
|
+
return join32(stateDir, "passengers", `${workspaceId}.json`);
|
|
15259
15353
|
}
|
|
15260
15354
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
15261
15355
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
15262
15356
|
if (!existsSync30(path)) return void 0;
|
|
15263
15357
|
try {
|
|
15264
|
-
return JSON.parse(
|
|
15358
|
+
return JSON.parse(readFileSync25(path, "utf8"));
|
|
15265
15359
|
} catch {
|
|
15266
15360
|
return void 0;
|
|
15267
15361
|
}
|
|
15268
15362
|
}
|
|
15269
15363
|
function writePersistedTunnel(stateDir, state) {
|
|
15270
15364
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
15271
|
-
|
|
15272
|
-
|
|
15365
|
+
mkdirSync19(join32(stateDir, "passengers"), { recursive: true });
|
|
15366
|
+
writeFileSync20(path, `${JSON.stringify(state, null, 2)}
|
|
15273
15367
|
`);
|
|
15274
15368
|
}
|
|
15275
15369
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -15366,13 +15460,13 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15366
15460
|
}
|
|
15367
15461
|
}
|
|
15368
15462
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15369
|
-
const dir =
|
|
15463
|
+
const dir = join32(stateDir, "passengers");
|
|
15370
15464
|
if (!existsSync30(dir)) return [];
|
|
15371
15465
|
return readdirSync10(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15372
15466
|
}
|
|
15373
15467
|
|
|
15374
15468
|
// src/mcp-endpoint-registry.ts
|
|
15375
|
-
import { createHash as
|
|
15469
|
+
import { createHash as createHash7 } from "crypto";
|
|
15376
15470
|
function endpointsUrl(api, workspaceId, id) {
|
|
15377
15471
|
const base = `${api.baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/mcp-endpoints`;
|
|
15378
15472
|
return id ? `${base}/${id}` : base;
|
|
@@ -15440,7 +15534,7 @@ async function probeEndpointAccess(api, workspaceId) {
|
|
|
15440
15534
|
}
|
|
15441
15535
|
var REGISTER_MCP_ENDPOINTS_HINT = "give this key's role the REGISTER_MCP_ENDPOINTS capability, e.g. via the group that holds the Crew project role";
|
|
15442
15536
|
function registrationKey(url, mcpSecret) {
|
|
15443
|
-
return
|
|
15537
|
+
return createHash7("sha256").update(`${url}
|
|
15444
15538
|
${mcpSecret ?? ""}`).digest("hex").slice(0, 16);
|
|
15445
15539
|
}
|
|
15446
15540
|
async function reconcileEndpoint(workspaceId, d) {
|
|
@@ -15488,8 +15582,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
15488
15582
|
import { createServer as createHttpServer } from "http";
|
|
15489
15583
|
import { spawn as spawn6 } from "child_process";
|
|
15490
15584
|
import { randomBytes } from "crypto";
|
|
15491
|
-
import { existsSync as existsSync31, mkdirSync as
|
|
15492
|
-
import { join as
|
|
15585
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync26, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readdirSync as readdirSync11 } from "fs";
|
|
15586
|
+
import { join as join33 } from "path";
|
|
15493
15587
|
|
|
15494
15588
|
// src/secret-compare.ts
|
|
15495
15589
|
import { timingSafeEqual } from "crypto";
|
|
@@ -15578,21 +15672,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
15578
15672
|
return child;
|
|
15579
15673
|
};
|
|
15580
15674
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
15581
|
-
return
|
|
15675
|
+
return join33(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
15582
15676
|
}
|
|
15583
15677
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
15584
15678
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
15585
15679
|
if (!existsSync31(path)) return void 0;
|
|
15586
15680
|
try {
|
|
15587
|
-
return JSON.parse(
|
|
15681
|
+
return JSON.parse(readFileSync26(path, "utf8"));
|
|
15588
15682
|
} catch {
|
|
15589
15683
|
return void 0;
|
|
15590
15684
|
}
|
|
15591
15685
|
}
|
|
15592
15686
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
15593
15687
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
15594
|
-
|
|
15595
|
-
|
|
15688
|
+
mkdirSync20(join33(stateDir, "passengers"), { recursive: true });
|
|
15689
|
+
writeFileSync21(path, `${JSON.stringify(state, null, 2)}
|
|
15596
15690
|
`);
|
|
15597
15691
|
}
|
|
15598
15692
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -15612,7 +15706,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
15612
15706
|
}
|
|
15613
15707
|
}
|
|
15614
15708
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
15615
|
-
const dir =
|
|
15709
|
+
const dir = join33(stateDir, "passengers");
|
|
15616
15710
|
if (!existsSync31(dir)) return [];
|
|
15617
15711
|
const suffix = "-sync.json";
|
|
15618
15712
|
return readdirSync11(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
@@ -15701,12 +15795,12 @@ function describeCause(err) {
|
|
|
15701
15795
|
}
|
|
15702
15796
|
|
|
15703
15797
|
// src/daemon.ts
|
|
15704
|
-
import { existsSync as existsSync33, mkdirSync as
|
|
15705
|
-
import { join as
|
|
15798
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync28, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync22 } from "fs";
|
|
15799
|
+
import { join as join35 } from "path";
|
|
15706
15800
|
|
|
15707
15801
|
// src/interrupted.ts
|
|
15708
|
-
import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as
|
|
15709
|
-
import { join as
|
|
15802
|
+
import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync27 } from "fs";
|
|
15803
|
+
import { join as join34 } from "path";
|
|
15710
15804
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
15711
15805
|
function interruptedComment(b) {
|
|
15712
15806
|
const tree = b.dirty === null ? "The worktree could not be checked." : b.dirty ? "The worktree has uncommitted changes \u2014 they are this seat's own, from that run." : "The worktree is clean.";
|
|
@@ -15727,19 +15821,19 @@ function runFinished(streamPath) {
|
|
|
15727
15821
|
const path = `${streamPath}.meta.json`;
|
|
15728
15822
|
if (!existsSync32(path)) return false;
|
|
15729
15823
|
try {
|
|
15730
|
-
return JSON.parse(
|
|
15824
|
+
return JSON.parse(readFileSync27(path, "utf8")).running !== true;
|
|
15731
15825
|
} catch {
|
|
15732
15826
|
return true;
|
|
15733
15827
|
}
|
|
15734
15828
|
}
|
|
15735
15829
|
function orphanedStreams(o) {
|
|
15736
|
-
const dir =
|
|
15830
|
+
const dir = join34(o.stateDir, "streams");
|
|
15737
15831
|
if (!existsSync32(dir)) return [];
|
|
15738
15832
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
15739
15833
|
const out = [];
|
|
15740
15834
|
for (const name of readdirSync12(dir)) {
|
|
15741
15835
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
15742
|
-
const streamPath =
|
|
15836
|
+
const streamPath = join34(dir, name);
|
|
15743
15837
|
if (runFinished(streamPath)) continue;
|
|
15744
15838
|
const sidecar = readRunSidecar(streamPath);
|
|
15745
15839
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -15859,7 +15953,7 @@ function latestMtime(path) {
|
|
|
15859
15953
|
if (!stat.isDirectory()) return 0;
|
|
15860
15954
|
let latest = stat.mtimeMs;
|
|
15861
15955
|
for (const entry of readdirSync13(path, { withFileTypes: true })) {
|
|
15862
|
-
latest = Math.max(latest, latestMtime(
|
|
15956
|
+
latest = Math.max(latest, latestMtime(join35(path, entry.name)));
|
|
15863
15957
|
}
|
|
15864
15958
|
return latest;
|
|
15865
15959
|
}
|
|
@@ -15871,20 +15965,20 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
15871
15965
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync33(opts.reloadSentinel);
|
|
15872
15966
|
}
|
|
15873
15967
|
function reloadSentinelFor(stateDir) {
|
|
15874
|
-
return
|
|
15968
|
+
return join35(stateDir, "reload-requested");
|
|
15875
15969
|
}
|
|
15876
15970
|
function daemonStampFor(stateDir) {
|
|
15877
|
-
return
|
|
15971
|
+
return join35(stateDir, "daemon.started");
|
|
15878
15972
|
}
|
|
15879
15973
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
15880
|
-
|
|
15974
|
+
mkdirSync21(stateDir, { recursive: true });
|
|
15881
15975
|
rmSync14(reloadSentinelFor(stateDir), { force: true });
|
|
15882
|
-
|
|
15976
|
+
writeFileSync22(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
15883
15977
|
`);
|
|
15884
15978
|
}
|
|
15885
15979
|
function readDaemonStarted(stateDir) {
|
|
15886
15980
|
try {
|
|
15887
|
-
const v = JSON.parse(
|
|
15981
|
+
const v = JSON.parse(readFileSync28(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
15888
15982
|
return typeof v === "number" ? v : null;
|
|
15889
15983
|
} catch {
|
|
15890
15984
|
return null;
|
|
@@ -16905,7 +16999,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16905
16999
|
}
|
|
16906
17000
|
if (host === "linux" && hasSystemd) {
|
|
16907
17001
|
const label = labelFor(CREW_HOME, "run");
|
|
16908
|
-
const staleTimer =
|
|
17002
|
+
const staleTimer = join36(homedir9(), ".config", "systemd", "user", `${label}.timer`);
|
|
16909
17003
|
if (existsSync34(staleTimer)) {
|
|
16910
17004
|
process.stdout.write(
|
|
16911
17005
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -16967,7 +17061,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16967
17061
|
for (const a of plan.actions) {
|
|
16968
17062
|
if (a.kind === "enable") {
|
|
16969
17063
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
16970
|
-
|
|
17064
|
+
writeFileSync23(cfg.configFile, planEnable(readFileSync29(cfg.configFile, "utf8"), a.route));
|
|
16971
17065
|
enabledSound.add(a.route);
|
|
16972
17066
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
16973
17067
|
} else {
|
|
@@ -17976,7 +18070,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
17976
18070
|
process.exit(1);
|
|
17977
18071
|
}
|
|
17978
18072
|
requireArmed("run the daemon loop");
|
|
17979
|
-
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [
|
|
18073
|
+
const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join36(CREW_HOME, "src"), join36(CREW_HOME, "dist"), join36(CREW_HOME, "bin")];
|
|
17980
18074
|
const configPaths = daemonConfigPaths(cfg);
|
|
17981
18075
|
watchPaths.push(...configPaths);
|
|
17982
18076
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -18195,10 +18289,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18195
18289
|
`);
|
|
18196
18290
|
if (!dryRun) {
|
|
18197
18291
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
18198
|
-
const raw = existsSync34(resolvedPath) ? JSON.parse(
|
|
18292
|
+
const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync29(resolvedPath, "utf8")) : {};
|
|
18199
18293
|
raw.agentSkills = result.agentSkills;
|
|
18200
|
-
|
|
18201
|
-
|
|
18294
|
+
mkdirSync22(dirOf(resolvedPath), { recursive: true });
|
|
18295
|
+
writeFileSync23(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
18202
18296
|
`);
|
|
18203
18297
|
}
|
|
18204
18298
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -18221,7 +18315,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18221
18315
|
throw e;
|
|
18222
18316
|
}
|
|
18223
18317
|
const oldName = cfg.ship.name;
|
|
18224
|
-
|
|
18318
|
+
writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), newName));
|
|
18225
18319
|
cfg.ship.name = newName;
|
|
18226
18320
|
let renameMachineId;
|
|
18227
18321
|
try {
|
|
@@ -18268,7 +18362,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18268
18362
|
process.exit(2);
|
|
18269
18363
|
};
|
|
18270
18364
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
|
|
18271
|
-
const configText =
|
|
18365
|
+
const configText = readFileSync29(cfg.configFile, "utf8");
|
|
18272
18366
|
if (sub === "hooks") {
|
|
18273
18367
|
try {
|
|
18274
18368
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
@@ -18282,7 +18376,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18282
18376
|
}
|
|
18283
18377
|
}, read: (r) => {
|
|
18284
18378
|
try {
|
|
18285
|
-
return
|
|
18379
|
+
return readFileSync29(resolvePath(dir, r), "utf8");
|
|
18286
18380
|
} catch {
|
|
18287
18381
|
return void 0;
|
|
18288
18382
|
}
|
|
@@ -18320,13 +18414,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18320
18414
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
18321
18415
|
if (write === "route") {
|
|
18322
18416
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks);
|
|
18323
|
-
|
|
18417
|
+
writeFileSync23(cfg.configFile, plan.text);
|
|
18324
18418
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
|
|
18325
18419
|
} else if (write === "repo") {
|
|
18326
18420
|
const have = existingRepoConfig(files);
|
|
18327
18421
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
18328
18422
|
const file = resolvePath(dir, ".crew.yaml");
|
|
18329
|
-
|
|
18423
|
+
writeFileSync23(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
18330
18424
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [] };
|
|
18331
18425
|
}
|
|
18332
18426
|
if (flag("json")) {
|
|
@@ -18409,13 +18503,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18409
18503
|
let resolvedRemotes;
|
|
18410
18504
|
if (existsSync34(resolvedFile)) {
|
|
18411
18505
|
try {
|
|
18412
|
-
resolvedRemotes = JSON.parse(
|
|
18506
|
+
resolvedRemotes = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
18413
18507
|
} catch {
|
|
18414
18508
|
}
|
|
18415
18509
|
}
|
|
18416
18510
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
18417
18511
|
const text = configText;
|
|
18418
18512
|
const base = dirname20(cfg.configFile);
|
|
18513
|
+
const isCheckout = (d) => existsSync34(`${d}/.git`);
|
|
18419
18514
|
try {
|
|
18420
18515
|
if (sub === "list") {
|
|
18421
18516
|
if (flag("json")) {
|
|
@@ -18426,19 +18521,21 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18426
18521
|
resolved: resolvedRemotes,
|
|
18427
18522
|
originOf,
|
|
18428
18523
|
exists: (d) => existsSync34(d),
|
|
18524
|
+
isCheckout,
|
|
18429
18525
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
18430
18526
|
try {
|
|
18431
|
-
return Object.keys(parseRepoConfig(
|
|
18527
|
+
return Object.keys(parseRepoConfig(readFileSync29(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
18432
18528
|
} catch {
|
|
18433
18529
|
return false;
|
|
18434
18530
|
}
|
|
18435
18531
|
})
|
|
18436
18532
|
});
|
|
18437
|
-
process.stdout.write(`${JSON.stringify({ route: routeName, repos: rows })}
|
|
18533
|
+
process.stdout.write(`${JSON.stringify({ route: routeName, repos: rows, cloneBase: cloneBaseOf(text, routeName, base, isCheckout) })}
|
|
18438
18534
|
`);
|
|
18439
18535
|
break;
|
|
18440
18536
|
}
|
|
18441
|
-
const
|
|
18537
|
+
const cloneBase = cloneBaseOf(text, routeName, base, isCheckout);
|
|
18538
|
+
const repos = configuredRepos(text, routeName, base).filter((r) => r.dir !== cloneBase);
|
|
18442
18539
|
if (repos.length === 0) process.stdout.write(`route ${routeName} has no checkout configured \u2014 \`crew repos add ${routeName} <path>\`
|
|
18443
18540
|
`);
|
|
18444
18541
|
for (const r of repos) {
|
|
@@ -18455,7 +18552,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18455
18552
|
const say = (event, extra, human) => process.stdout.write(json ? `${JSON.stringify({ event, ...extra })}
|
|
18456
18553
|
` : `${human}
|
|
18457
18554
|
`);
|
|
18458
|
-
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir") });
|
|
18555
|
+
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
18459
18556
|
if (existsSync34(plan2.target) && (existsSync34(`${plan2.target}/.git`) || readdirSync14(plan2.target).length > 0)) {
|
|
18460
18557
|
throw new ReposError(
|
|
18461
18558
|
existsSync34(`${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`
|
|
@@ -18476,7 +18573,9 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18476
18573
|
throw new ReposError(`${e.message}${authHint}
|
|
18477
18574
|
crew.yaml was not changed`);
|
|
18478
18575
|
}
|
|
18479
|
-
|
|
18576
|
+
writeFileSync23(cfg.configFile, plan2.text);
|
|
18577
|
+
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:
|
|
18578
|
+
`);
|
|
18480
18579
|
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}`);
|
|
18481
18580
|
break;
|
|
18482
18581
|
}
|
|
@@ -18492,8 +18591,11 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18492
18591
|
origin,
|
|
18493
18592
|
resolved: resolvedRemotes,
|
|
18494
18593
|
name: value("name") ?? addName,
|
|
18495
|
-
nameForExisting: (d) => matchRepoName(originOf(d), resolvedRemotes)
|
|
18594
|
+
nameForExisting: (d) => matchRepoName(originOf(d), resolvedRemotes),
|
|
18595
|
+
isCheckout
|
|
18496
18596
|
});
|
|
18597
|
+
if (plan.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
18598
|
+
`);
|
|
18497
18599
|
let rowTracker;
|
|
18498
18600
|
let rowPayload;
|
|
18499
18601
|
if (plan.unmatched && origin && !flag("no-create")) {
|
|
@@ -18531,14 +18633,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18531
18633
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
18532
18634
|
`);
|
|
18533
18635
|
if (existsSync34(resolvedFile)) {
|
|
18534
|
-
const raw = JSON.parse(
|
|
18636
|
+
const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
18535
18637
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
18536
18638
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
18537
|
-
|
|
18639
|
+
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
18538
18640
|
`);
|
|
18539
18641
|
}
|
|
18540
18642
|
}
|
|
18541
|
-
|
|
18643
|
+
writeFileSync23(cfg.configFile, plan.text);
|
|
18542
18644
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
18543
18645
|
`);
|
|
18544
18646
|
} catch (e) {
|
|
@@ -18889,7 +18991,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18889
18991
|
process.exit(2);
|
|
18890
18992
|
}
|
|
18891
18993
|
if (found.shipNameAdopted && !dryRun) {
|
|
18892
|
-
|
|
18994
|
+
writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
18893
18995
|
cfg.ship.name = found.shipNameAdopted;
|
|
18894
18996
|
}
|
|
18895
18997
|
if (found.shipRow) {
|
|
@@ -18935,7 +19037,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18935
19037
|
const hadPreviousFile = existsSync34(resolvedPath);
|
|
18936
19038
|
if (hadPreviousFile) {
|
|
18937
19039
|
try {
|
|
18938
|
-
previous = JSON.parse(
|
|
19040
|
+
previous = JSON.parse(readFileSync29(resolvedPath, "utf8"));
|
|
18939
19041
|
} catch {
|
|
18940
19042
|
}
|
|
18941
19043
|
}
|
|
@@ -19055,10 +19157,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19055
19157
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
19056
19158
|
`);
|
|
19057
19159
|
} else {
|
|
19058
|
-
|
|
19160
|
+
mkdirSync22(dirname20(resolvedPath), { recursive: true });
|
|
19059
19161
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
19060
19162
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
19061
|
-
|
|
19163
|
+
writeFileSync23(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
19062
19164
|
`);
|
|
19063
19165
|
renameSync3(tmpPath, resolvedPath);
|
|
19064
19166
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -19100,8 +19202,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19100
19202
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
19101
19203
|
`);
|
|
19102
19204
|
} else {
|
|
19103
|
-
|
|
19104
|
-
|
|
19205
|
+
mkdirSync22(dirname20(apiKeyFile), { recursive: true });
|
|
19206
|
+
writeFileSync23(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
19105
19207
|
`, { mode: 384 });
|
|
19106
19208
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
19107
19209
|
`);
|
|
@@ -19110,11 +19212,11 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19110
19212
|
const keyPersisted = keychainBacked || apiKeyFile !== void 0;
|
|
19111
19213
|
const wizard = firstRun;
|
|
19112
19214
|
const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
|
|
19113
|
-
const routeDir = value("dir")
|
|
19215
|
+
const routeDir = value("dir");
|
|
19114
19216
|
const routeBlock = renderConnection(
|
|
19115
19217
|
found,
|
|
19116
19218
|
route2,
|
|
19117
|
-
routeDir ?? "REPLACE \u2014 the local checkout this route works",
|
|
19219
|
+
routeDir ?? (writeFirstRun ? null : "REPLACE \u2014 the local checkout this route works"),
|
|
19118
19220
|
{
|
|
19119
19221
|
area: value("area"),
|
|
19120
19222
|
apiKeyFile,
|
|
@@ -19137,8 +19239,11 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19137
19239
|
const id = `${tc.harness}\0${tc.bin}`;
|
|
19138
19240
|
if (seen.has(id)) continue;
|
|
19139
19241
|
seen.add(id);
|
|
19140
|
-
const
|
|
19141
|
-
|
|
19242
|
+
const adapter = adapterFor(tc.harness);
|
|
19243
|
+
const mcpUrl = mcpUrlFor(baseUrl);
|
|
19244
|
+
const mcpOpts = { bin: tc.bin, url: mcpUrl, key: apiKey, replace, dryRun, stateDir: cfg.ship.stateDir };
|
|
19245
|
+
const { line, action } = adapter.ensureMcpResult ? adapter.ensureMcpResult(mcpOpts) : { line: adapter.ensureMcp(mcpOpts), action: void 0 };
|
|
19246
|
+
connectEvents.emit("mcp_registered", line, { harness: tc.harness, bin: tc.bin, url: mcpUrl, ...action ? { action } : {} });
|
|
19142
19247
|
process.stderr.write(`${line}
|
|
19143
19248
|
`);
|
|
19144
19249
|
}
|
|
@@ -19789,7 +19894,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19789
19894
|
};
|
|
19790
19895
|
const droppedRoutes = [];
|
|
19791
19896
|
try {
|
|
19792
|
-
const rawCfg = parseYaml2(
|
|
19897
|
+
const rawCfg = parseYaml2(readFileSync29(cfg.configFile, "utf8"));
|
|
19793
19898
|
for (const r of rawCfg?.routes ?? []) {
|
|
19794
19899
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
19795
19900
|
try {
|
|
@@ -19831,8 +19936,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19831
19936
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
19832
19937
|
break;
|
|
19833
19938
|
}
|
|
19834
|
-
|
|
19835
|
-
|
|
19939
|
+
mkdirSync22(cfg.ship.stateDir, { recursive: true });
|
|
19940
|
+
writeFileSync23(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
19836
19941
|
`);
|
|
19837
19942
|
process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
|
|
19838
19943
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
@@ -19883,7 +19988,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19883
19988
|
}
|
|
19884
19989
|
case "log": {
|
|
19885
19990
|
try {
|
|
19886
|
-
const text =
|
|
19991
|
+
const text = readFileSync29(cfg.ship.logFile, "utf8").split("\n");
|
|
19887
19992
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
19888
19993
|
`);
|
|
19889
19994
|
} catch {
|
|
@@ -19906,7 +20011,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19906
20011
|
};
|
|
19907
20012
|
if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
|
|
19908
20013
|
const tracker = new Tracker(route, cfg.ship);
|
|
19909
|
-
const configText =
|
|
20014
|
+
const configText = readFileSync29(cfg.configFile, "utf8");
|
|
19910
20015
|
const cfgBase = dirname20(cfg.configFile);
|
|
19911
20016
|
const configured = configuredRepos(configText, route.route, cfgBase);
|
|
19912
20017
|
const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
|
|
@@ -19935,7 +20040,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19935
20040
|
}
|
|
19936
20041
|
if (configured.some((r) => r.dir === dir)) {
|
|
19937
20042
|
try {
|
|
19938
|
-
|
|
20043
|
+
writeFileSync23(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
|
|
19939
20044
|
} catch (e) {
|
|
19940
20045
|
if (e instanceof ReposError) fail(e.message);
|
|
19941
20046
|
throw e;
|
|
@@ -19944,28 +20049,28 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19944
20049
|
if (row) {
|
|
19945
20050
|
await tracker.deleteRepoRow(row.id);
|
|
19946
20051
|
if (existsSync34(resolvedFile)) {
|
|
19947
|
-
const raw = JSON.parse(
|
|
20052
|
+
const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
19948
20053
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
19949
20054
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
19950
|
-
|
|
20055
|
+
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
19951
20056
|
`);
|
|
19952
20057
|
}
|
|
19953
20058
|
}
|
|
19954
|
-
if (existsSync34(
|
|
20059
|
+
if (existsSync34(join36(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
|
|
19955
20060
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
19956
20061
|
break;
|
|
19957
20062
|
}
|
|
19958
20063
|
const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
19959
|
-
const fresh = !existsSync34(
|
|
20064
|
+
const fresh = !existsSync34(join36(dir, ".git"));
|
|
19960
20065
|
if (existsSync34(dir) && fresh && readdirSync14(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
19961
20066
|
if (dryRun) {
|
|
19962
20067
|
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`);
|
|
19963
20068
|
break;
|
|
19964
20069
|
}
|
|
19965
20070
|
if (fresh) {
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
20071
|
+
mkdirSync22(dir, { recursive: true });
|
|
20072
|
+
writeFileSync23(join36(dir, "README.md"), SAMPLE_README);
|
|
20073
|
+
writeFileSync23(join36(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
19969
20074
|
const g = (...a) => {
|
|
19970
20075
|
const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
|
|
19971
20076
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -19984,16 +20089,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19984
20089
|
});
|
|
19985
20090
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
19986
20091
|
if (existsSync34(resolvedFile)) {
|
|
19987
|
-
const raw = JSON.parse(
|
|
20092
|
+
const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
19988
20093
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
19989
20094
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
19990
|
-
|
|
20095
|
+
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
19991
20096
|
`);
|
|
19992
20097
|
}
|
|
19993
20098
|
}
|
|
19994
20099
|
if (!configured.some((r) => r.dir === dir)) {
|
|
19995
20100
|
try {
|
|
19996
|
-
|
|
20101
|
+
writeFileSync23(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
|
|
19997
20102
|
} catch (e) {
|
|
19998
20103
|
if (e instanceof ReposError) fail(e.message);
|
|
19999
20104
|
throw e;
|
|
@@ -20347,7 +20452,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
20347
20452
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
20348
20453
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
20349
20454
|
try {
|
|
20350
|
-
return
|
|
20455
|
+
return readFileSync29(p, "utf8");
|
|
20351
20456
|
} catch {
|
|
20352
20457
|
return null;
|
|
20353
20458
|
}
|