@tablation/crew 0.72.2 → 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 +403 -344
- 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}` };
|
|
455
|
+
}
|
|
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" };
|
|
412
464
|
}
|
|
413
|
-
return
|
|
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,
|
|
@@ -2589,7 +2645,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2589
2645
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
2590
2646
|
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
2591
2647
|
const dirIsCloneBase = !c?.dir && !hasRepos && c?.repos && typeof c.repos === "object" && !Array.isArray(c.repos);
|
|
2592
|
-
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ?
|
|
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)`) ?? "";
|
|
2593
2649
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
2594
2650
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
2595
2651
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -2673,7 +2729,7 @@ function repoIdForName(r, name) {
|
|
|
2673
2729
|
function resolveApiKey(r) {
|
|
2674
2730
|
if (r.apiKey) return r.apiKey;
|
|
2675
2731
|
if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
|
|
2676
|
-
for (const line of
|
|
2732
|
+
for (const line of readFileSync7(r.apiKeyFile, "utf8").split("\n")) {
|
|
2677
2733
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
2678
2734
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
2679
2735
|
}
|
|
@@ -2688,7 +2744,7 @@ function credentialSource(r) {
|
|
|
2688
2744
|
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
2689
2745
|
if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
|
|
2690
2746
|
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
2691
|
-
if (
|
|
2747
|
+
if (readFileSync7(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
2692
2748
|
}
|
|
2693
2749
|
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
2694
2750
|
}
|
|
@@ -2744,7 +2800,7 @@ function configuredMembers(rt) {
|
|
|
2744
2800
|
return out;
|
|
2745
2801
|
}
|
|
2746
2802
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
2747
|
-
return
|
|
2803
|
+
return join8(basePath, workspace, repoName);
|
|
2748
2804
|
}
|
|
2749
2805
|
function reposOf(r) {
|
|
2750
2806
|
const named = Object.entries(r.repos);
|
|
@@ -2777,7 +2833,7 @@ function passengerRepoTargets(r) {
|
|
|
2777
2833
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
2778
2834
|
}
|
|
2779
2835
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
2780
|
-
return
|
|
2836
|
+
return join8(basePath, workspace, ".passenger-checkouts", repoName);
|
|
2781
2837
|
}
|
|
2782
2838
|
function passengerCheckoutTargets(r) {
|
|
2783
2839
|
if (!r.hostPassengers) return [];
|
|
@@ -3125,8 +3181,8 @@ function renderEnvironment(i) {
|
|
|
3125
3181
|
}
|
|
3126
3182
|
|
|
3127
3183
|
// src/agent-log.ts
|
|
3128
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
3129
|
-
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";
|
|
3130
3186
|
|
|
3131
3187
|
// src/pending-backoff.ts
|
|
3132
3188
|
var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
|
|
@@ -3193,20 +3249,20 @@ async function reportAgentRun(target, run2) {
|
|
|
3193
3249
|
}
|
|
3194
3250
|
return { agentLogId: entry.id };
|
|
3195
3251
|
}
|
|
3196
|
-
var queueDir = (stateDir) =>
|
|
3252
|
+
var queueDir = (stateDir) => join9(stateDir, "pending-agent-logs");
|
|
3197
3253
|
function queueAgentRun(stateDir, name, q) {
|
|
3198
|
-
|
|
3199
|
-
|
|
3254
|
+
mkdirSync4(queueDir(stateDir), { recursive: true });
|
|
3255
|
+
writeFileSync5(join9(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
|
|
3200
3256
|
}
|
|
3201
3257
|
async function replayQueuedAgentRuns(stateDir, route, keyFor, log, now = /* @__PURE__ */ new Date()) {
|
|
3202
3258
|
const dir = queueDir(stateDir);
|
|
3203
3259
|
if (!existsSync8(dir)) return 0;
|
|
3204
3260
|
let sent = 0;
|
|
3205
3261
|
for (const f of readdirSync2(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
3206
|
-
const path =
|
|
3262
|
+
const path = join9(dir, f);
|
|
3207
3263
|
let q;
|
|
3208
3264
|
try {
|
|
3209
|
-
q = JSON.parse(
|
|
3265
|
+
q = JSON.parse(readFileSync8(path, "utf8"));
|
|
3210
3266
|
} catch {
|
|
3211
3267
|
continue;
|
|
3212
3268
|
}
|
|
@@ -3225,7 +3281,7 @@ async function replayQueuedAgentRuns(stateDir, route, keyFor, log, now = /* @__P
|
|
|
3225
3281
|
continue;
|
|
3226
3282
|
}
|
|
3227
3283
|
const attempts = q.attempts + 1;
|
|
3228
|
-
|
|
3284
|
+
writeFileSync5(path, JSON.stringify({
|
|
3229
3285
|
...q,
|
|
3230
3286
|
attempts,
|
|
3231
3287
|
warned: true,
|
|
@@ -3406,25 +3462,25 @@ var HandoffTranscriptTracker = class {
|
|
|
3406
3462
|
};
|
|
3407
3463
|
|
|
3408
3464
|
// src/pending-handoff.ts
|
|
3409
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
3410
|
-
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";
|
|
3411
3467
|
|
|
3412
3468
|
// src/git.ts
|
|
3413
3469
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
3414
3470
|
import {
|
|
3415
3471
|
existsSync as existsSync9,
|
|
3416
|
-
mkdirSync as
|
|
3472
|
+
mkdirSync as mkdirSync5,
|
|
3417
3473
|
openSync as openSync2,
|
|
3418
3474
|
closeSync as closeSync2,
|
|
3419
3475
|
writeSync,
|
|
3420
3476
|
statSync as statSync2,
|
|
3421
3477
|
unlinkSync,
|
|
3422
|
-
readFileSync as
|
|
3423
|
-
writeFileSync as
|
|
3478
|
+
readFileSync as readFileSync9,
|
|
3479
|
+
writeFileSync as writeFileSync6,
|
|
3424
3480
|
readdirSync as readdirSync3,
|
|
3425
3481
|
rmSync as rmSync6
|
|
3426
3482
|
} from "fs";
|
|
3427
|
-
import { dirname as dirname4, basename as basename5, join as
|
|
3483
|
+
import { dirname as dirname4, basename as basename5, join as join10 } from "path";
|
|
3428
3484
|
var GitError = class extends Error {
|
|
3429
3485
|
};
|
|
3430
3486
|
function streamText(x) {
|
|
@@ -3808,11 +3864,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
|
|
|
3808
3864
|
}
|
|
3809
3865
|
for (const name of names) {
|
|
3810
3866
|
if (!name.startsWith("crew-release-")) continue;
|
|
3811
|
-
const root =
|
|
3867
|
+
const root = join10(tmp, name);
|
|
3812
3868
|
try {
|
|
3813
3869
|
const st = statSync2(root);
|
|
3814
3870
|
if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
|
|
3815
|
-
if (existsSync9(
|
|
3871
|
+
if (existsSync9(join10(root, "checkout"))) continue;
|
|
3816
3872
|
rmSync6(root, { recursive: true, force: true });
|
|
3817
3873
|
removed.push(name);
|
|
3818
3874
|
} catch {
|
|
@@ -3861,7 +3917,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
|
|
|
3861
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`
|
|
3862
3918
|
);
|
|
3863
3919
|
}
|
|
3864
|
-
|
|
3920
|
+
mkdirSync5(dirname4(dir), { recursive: true });
|
|
3865
3921
|
const url = cloneUrlFor(remote, opts);
|
|
3866
3922
|
try {
|
|
3867
3923
|
execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -3886,7 +3942,7 @@ function sleepSync(ms2) {
|
|
|
3886
3942
|
}
|
|
3887
3943
|
function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
3888
3944
|
const lockPath = checkoutLockPath(dir);
|
|
3889
|
-
|
|
3945
|
+
mkdirSync5(dirname4(lockPath), { recursive: true });
|
|
3890
3946
|
const deadline = Date.now() + timeoutMs;
|
|
3891
3947
|
for (; ; ) {
|
|
3892
3948
|
try {
|
|
@@ -3926,14 +3982,14 @@ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {
|
|
|
3926
3982
|
if (!existsSync9(`${dir}/.git`) && githubRemote) {
|
|
3927
3983
|
const url = cloneUrlFor(githubRemote, clone);
|
|
3928
3984
|
const marker = cloneFailureMarker(dir);
|
|
3929
|
-
if (existsSync9(marker) &&
|
|
3930
|
-
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 };
|
|
3931
3987
|
}
|
|
3932
3988
|
try {
|
|
3933
3989
|
ensureRepoCheckout(dir, githubRemote, clone);
|
|
3934
3990
|
} catch (e) {
|
|
3935
3991
|
if (!(e instanceof GitError)) throw e;
|
|
3936
|
-
|
|
3992
|
+
writeFileSync6(marker, `${url}
|
|
3937
3993
|
${e.message}`);
|
|
3938
3994
|
return { action: "clone-failed", detail: e.message, repeat: false };
|
|
3939
3995
|
}
|
|
@@ -4354,11 +4410,11 @@ function recoveredFailureHeadlines(o, route) {
|
|
|
4354
4410
|
}
|
|
4355
4411
|
|
|
4356
4412
|
// src/install.ts
|
|
4357
|
-
import { dirname as dirname5, join as
|
|
4413
|
+
import { dirname as dirname5, join as join11, extname } from "path";
|
|
4358
4414
|
import { homedir as homedir6 } from "os";
|
|
4359
|
-
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";
|
|
4360
4416
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4361
|
-
import { createHash } from "crypto";
|
|
4417
|
+
import { createHash as createHash2 } from "crypto";
|
|
4362
4418
|
|
|
4363
4419
|
// src/runtime-info.ts
|
|
4364
4420
|
function isCompiledBinary(moduleUrl) {
|
|
@@ -4368,7 +4424,7 @@ function isCompiledBinary(moduleUrl) {
|
|
|
4368
4424
|
// src/install.ts
|
|
4369
4425
|
function crewInvocation(crewHome) {
|
|
4370
4426
|
if (isCompiledBinary(import.meta.url)) return [process.execPath];
|
|
4371
|
-
return [process.execPath,
|
|
4427
|
+
return [process.execPath, join11(crewHome, "bin", "crew")];
|
|
4372
4428
|
}
|
|
4373
4429
|
var INTERVAL_SECONDS = 120;
|
|
4374
4430
|
function labelFor(crewHome, job) {
|
|
@@ -4379,7 +4435,7 @@ function labelFor(crewHome, job) {
|
|
|
4379
4435
|
return crewHome;
|
|
4380
4436
|
}
|
|
4381
4437
|
})();
|
|
4382
|
-
const hash =
|
|
4438
|
+
const hash = createHash2("sha1").update(real).digest("hex").slice(0, 8);
|
|
4383
4439
|
const stem = job === "release" ? "com.tablation.crew-release" : job === "passengers" ? "com.tablation.crew-passengers" : "com.tablation.crew";
|
|
4384
4440
|
return `${stem}.${hash}`;
|
|
4385
4441
|
}
|
|
@@ -4406,7 +4462,7 @@ function pathFor(ship) {
|
|
|
4406
4462
|
return parts.join(":");
|
|
4407
4463
|
}
|
|
4408
4464
|
function findOnPath(bin, searchPath) {
|
|
4409
|
-
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));
|
|
4410
4466
|
for (const candidate of candidates) {
|
|
4411
4467
|
try {
|
|
4412
4468
|
accessSync(candidate, constants.X_OK);
|
|
@@ -4416,7 +4472,7 @@ function findOnPath(bin, searchPath) {
|
|
|
4416
4472
|
}
|
|
4417
4473
|
return void 0;
|
|
4418
4474
|
}
|
|
4419
|
-
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(":");
|
|
4420
4476
|
function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
|
|
4421
4477
|
const schedulerPath = pathFor(ship);
|
|
4422
4478
|
if (findOnPath("docker", schedulerPath)) return void 0;
|
|
@@ -4428,7 +4484,7 @@ function xmlEscape(s) {
|
|
|
4428
4484
|
}
|
|
4429
4485
|
function planLaunchd(ship, crewHome, job) {
|
|
4430
4486
|
const label = labelFor(crewHome, job);
|
|
4431
|
-
const unitPath =
|
|
4487
|
+
const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4432
4488
|
const crewLog = ship.logFile;
|
|
4433
4489
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4434
4490
|
const persistent = job === "run";
|
|
@@ -4482,8 +4538,8 @@ ${scheduleKeys}
|
|
|
4482
4538
|
}
|
|
4483
4539
|
function planSystemd(ship, crewHome, job) {
|
|
4484
4540
|
const label = labelFor(crewHome, job);
|
|
4485
|
-
const unitDir =
|
|
4486
|
-
const servicePath =
|
|
4541
|
+
const unitDir = join11(homedir6(), ".config", "systemd", "user");
|
|
4542
|
+
const servicePath = join11(unitDir, `${label}.service`);
|
|
4487
4543
|
const crewLog = ship.logFile;
|
|
4488
4544
|
const schedulerLog = schedulerLogFor(crewLog, job);
|
|
4489
4545
|
const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
|
|
@@ -4523,7 +4579,7 @@ WantedBy=default.target
|
|
|
4523
4579
|
schedulerLog
|
|
4524
4580
|
};
|
|
4525
4581
|
}
|
|
4526
|
-
const timerPath =
|
|
4582
|
+
const timerPath = join11(unitDir, `${label}.timer`);
|
|
4527
4583
|
const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
|
|
4528
4584
|
[Unit]
|
|
4529
4585
|
Description=${description}
|
|
@@ -4597,7 +4653,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4597
4653
|
function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
4598
4654
|
const label = labelFor(crewHome, job);
|
|
4599
4655
|
if (hostShip === "macos") {
|
|
4600
|
-
const unitPath =
|
|
4656
|
+
const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4601
4657
|
return {
|
|
4602
4658
|
mechanism: "launchd",
|
|
4603
4659
|
unitPaths: [unitPath],
|
|
@@ -4607,18 +4663,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4607
4663
|
};
|
|
4608
4664
|
}
|
|
4609
4665
|
if (hostShip === "linux" && hasSystemd) {
|
|
4610
|
-
const unitDir =
|
|
4666
|
+
const unitDir = join11(homedir6(), ".config", "systemd", "user");
|
|
4611
4667
|
if (job === "run") {
|
|
4612
4668
|
return {
|
|
4613
4669
|
mechanism: "systemd",
|
|
4614
|
-
unitPaths: [
|
|
4670
|
+
unitPaths: [join11(unitDir, `${label}.service`)],
|
|
4615
4671
|
crontabLine: null,
|
|
4616
4672
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
|
|
4617
4673
|
};
|
|
4618
4674
|
}
|
|
4619
4675
|
return {
|
|
4620
4676
|
mechanism: "systemd",
|
|
4621
|
-
unitPaths: [
|
|
4677
|
+
unitPaths: [join11(unitDir, `${label}.service`), join11(unitDir, `${label}.timer`)],
|
|
4622
4678
|
crontabLine: null,
|
|
4623
4679
|
unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
|
|
4624
4680
|
};
|
|
@@ -4636,7 +4692,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
|
|
|
4636
4692
|
function planDaemonControl(crewHome, hostShip, hasSystemd) {
|
|
4637
4693
|
const label = labelFor(crewHome, "run");
|
|
4638
4694
|
if (hostShip === "macos") {
|
|
4639
|
-
const unitPath =
|
|
4695
|
+
const unitPath = join11(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
|
|
4640
4696
|
return {
|
|
4641
4697
|
mechanism: "launchd",
|
|
4642
4698
|
startCommand: ["launchctl", "load", unitPath],
|
|
@@ -4676,8 +4732,8 @@ async function applyInstall(plan, crewHome, dryRun, log) {
|
|
|
4676
4732
|
log.emit(`would write ${path}`);
|
|
4677
4733
|
continue;
|
|
4678
4734
|
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4735
|
+
mkdirSync6(dirname5(path), { recursive: true });
|
|
4736
|
+
writeFileSync7(path, plan.unitContent[path]);
|
|
4681
4737
|
log.emit(`wrote ${path}`);
|
|
4682
4738
|
}
|
|
4683
4739
|
if (plan.crontabLine) {
|
|
@@ -4750,7 +4806,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
|
|
|
4750
4806
|
}
|
|
4751
4807
|
var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
|
|
4752
4808
|
function launchAgentsDir() {
|
|
4753
|
-
return
|
|
4809
|
+
return join11(homedir6(), "Library", "LaunchAgents");
|
|
4754
4810
|
}
|
|
4755
4811
|
function xmlUnescape(s) {
|
|
4756
4812
|
return s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
@@ -4769,10 +4825,10 @@ function loadedCrewLabels() {
|
|
|
4769
4825
|
function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
4770
4826
|
const files = existsSync10(dir) ? readdirSync4(dir) : [];
|
|
4771
4827
|
const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
|
|
4772
|
-
const plistPath =
|
|
4828
|
+
const plistPath = join11(dir, f);
|
|
4773
4829
|
let program = null;
|
|
4774
4830
|
try {
|
|
4775
|
-
program = programFromPlist(
|
|
4831
|
+
program = programFromPlist(readFileSync10(plistPath, "utf8"));
|
|
4776
4832
|
} catch {
|
|
4777
4833
|
}
|
|
4778
4834
|
return {
|
|
@@ -4785,7 +4841,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
|
|
|
4785
4841
|
const seen = new Set(units.map((u) => u.label));
|
|
4786
4842
|
for (const label of [...loaded].sort()) {
|
|
4787
4843
|
if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
|
|
4788
|
-
units.push({ label, plistPath:
|
|
4844
|
+
units.push({ label, plistPath: join11(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
|
|
4789
4845
|
}
|
|
4790
4846
|
return units;
|
|
4791
4847
|
}
|
|
@@ -5131,17 +5187,17 @@ var SHELL_WORDS = /* @__PURE__ */ new Set([
|
|
|
5131
5187
|
]);
|
|
5132
5188
|
|
|
5133
5189
|
// src/state.ts
|
|
5134
|
-
import { readFileSync as
|
|
5135
|
-
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";
|
|
5136
5192
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
5137
5193
|
var State = class _State {
|
|
5138
5194
|
dir;
|
|
5139
5195
|
constructor(dir) {
|
|
5140
5196
|
this.dir = dir;
|
|
5141
|
-
|
|
5197
|
+
mkdirSync7(dir, { recursive: true });
|
|
5142
5198
|
}
|
|
5143
5199
|
path(...parts) {
|
|
5144
|
-
return
|
|
5200
|
+
return join12(this.dir, ...parts);
|
|
5145
5201
|
}
|
|
5146
5202
|
/**
|
|
5147
5203
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -5152,14 +5208,14 @@ var State = class _State {
|
|
|
5152
5208
|
*/
|
|
5153
5209
|
watermark() {
|
|
5154
5210
|
try {
|
|
5155
|
-
const v =
|
|
5211
|
+
const v = readFileSync11(this.path(".poll-watermark"), "utf8").trim();
|
|
5156
5212
|
return v || EPOCH;
|
|
5157
5213
|
} catch {
|
|
5158
5214
|
return EPOCH;
|
|
5159
5215
|
}
|
|
5160
5216
|
}
|
|
5161
5217
|
setWatermark(at) {
|
|
5162
|
-
|
|
5218
|
+
writeFileSync8(this.path(".poll-watermark"), `${at}
|
|
5163
5219
|
`);
|
|
5164
5220
|
}
|
|
5165
5221
|
/**
|
|
@@ -5186,7 +5242,7 @@ var State = class _State {
|
|
|
5186
5242
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
5187
5243
|
}
|
|
5188
5244
|
pause(role) {
|
|
5189
|
-
|
|
5245
|
+
writeFileSync8(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
5190
5246
|
}
|
|
5191
5247
|
resume(role) {
|
|
5192
5248
|
rmSync7(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
@@ -5200,7 +5256,7 @@ var State = class _State {
|
|
|
5200
5256
|
return existsSync12(this.path(".release-paused"));
|
|
5201
5257
|
}
|
|
5202
5258
|
setReleasePaused(paused) {
|
|
5203
|
-
if (paused)
|
|
5259
|
+
if (paused) writeFileSync8(this.path(".release-paused"), "");
|
|
5204
5260
|
else rmSync7(this.path(".release-paused"), { force: true });
|
|
5205
5261
|
}
|
|
5206
5262
|
/**
|
|
@@ -5238,7 +5294,7 @@ var State = class _State {
|
|
|
5238
5294
|
const name = _State.safe(rawName);
|
|
5239
5295
|
const file = this.path(`.${name}.lock`);
|
|
5240
5296
|
try {
|
|
5241
|
-
const existing = Number.parseInt(
|
|
5297
|
+
const existing = Number.parseInt(readFileSync11(file, "utf8").trim(), 10);
|
|
5242
5298
|
if (Number.isFinite(existing) && existing > 0) {
|
|
5243
5299
|
try {
|
|
5244
5300
|
process.kill(existing, 0);
|
|
@@ -5248,7 +5304,7 @@ var State = class _State {
|
|
|
5248
5304
|
}
|
|
5249
5305
|
} catch {
|
|
5250
5306
|
}
|
|
5251
|
-
|
|
5307
|
+
writeFileSync8(file, `${process.pid}
|
|
5252
5308
|
`);
|
|
5253
5309
|
return {
|
|
5254
5310
|
ok: true,
|
|
@@ -5332,7 +5388,7 @@ var State = class _State {
|
|
|
5332
5388
|
const heldReported = `.release-held-${suffix}`;
|
|
5333
5389
|
const read = (f) => {
|
|
5334
5390
|
try {
|
|
5335
|
-
return
|
|
5391
|
+
return readFileSync11(this.path(f), "utf8").trim() || null;
|
|
5336
5392
|
} catch {
|
|
5337
5393
|
return null;
|
|
5338
5394
|
}
|
|
@@ -5341,7 +5397,7 @@ var State = class _State {
|
|
|
5341
5397
|
blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
|
|
5342
5398
|
noteBlocked: () => {
|
|
5343
5399
|
const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
|
|
5344
|
-
|
|
5400
|
+
writeFileSync8(this.path(blocked), `${n}
|
|
5345
5401
|
`);
|
|
5346
5402
|
return n;
|
|
5347
5403
|
},
|
|
@@ -5358,7 +5414,7 @@ var State = class _State {
|
|
|
5358
5414
|
*/
|
|
5359
5415
|
deployFailedSha: () => read(failed),
|
|
5360
5416
|
noteDeployFailed: (sha) => {
|
|
5361
|
-
|
|
5417
|
+
writeFileSync8(this.path(failed), `${sha}
|
|
5362
5418
|
`);
|
|
5363
5419
|
},
|
|
5364
5420
|
clearDeployFailed: () => {
|
|
@@ -5372,7 +5428,7 @@ var State = class _State {
|
|
|
5372
5428
|
noteTestGateFailed: (sha) => {
|
|
5373
5429
|
const [prev, n] = (read(gateFailed) ?? "").split(" ");
|
|
5374
5430
|
const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
|
|
5375
|
-
|
|
5431
|
+
writeFileSync8(this.path(gateFailed), `${sha} ${next}
|
|
5376
5432
|
`);
|
|
5377
5433
|
return next;
|
|
5378
5434
|
},
|
|
@@ -5382,13 +5438,13 @@ var State = class _State {
|
|
|
5382
5438
|
/** Whether the board was already told about this head's red gate. */
|
|
5383
5439
|
heldReported: () => {
|
|
5384
5440
|
try {
|
|
5385
|
-
return
|
|
5441
|
+
return readFileSync11(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
5386
5442
|
} catch {
|
|
5387
5443
|
return null;
|
|
5388
5444
|
}
|
|
5389
5445
|
},
|
|
5390
5446
|
noteHeldReported: (signature) => {
|
|
5391
|
-
|
|
5447
|
+
writeFileSync8(this.path(heldReported), `${signature}
|
|
5392
5448
|
`);
|
|
5393
5449
|
},
|
|
5394
5450
|
clearHeldReported: () => {
|
|
@@ -5396,7 +5452,7 @@ var State = class _State {
|
|
|
5396
5452
|
},
|
|
5397
5453
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
5398
5454
|
noteTestGateReported: (sha) => {
|
|
5399
|
-
|
|
5455
|
+
writeFileSync8(this.path(gateReported), `${sha}
|
|
5400
5456
|
`);
|
|
5401
5457
|
}
|
|
5402
5458
|
};
|
|
@@ -5419,7 +5475,7 @@ var State = class _State {
|
|
|
5419
5475
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
5420
5476
|
const read = (f) => {
|
|
5421
5477
|
try {
|
|
5422
|
-
return
|
|
5478
|
+
return readFileSync11(this.path(f), "utf8").trim() || null;
|
|
5423
5479
|
} catch {
|
|
5424
5480
|
return null;
|
|
5425
5481
|
}
|
|
@@ -5452,18 +5508,18 @@ var State = class _State {
|
|
|
5452
5508
|
clear2();
|
|
5453
5509
|
return;
|
|
5454
5510
|
}
|
|
5455
|
-
|
|
5511
|
+
writeFileSync8(this.path(streakFile), `${this.readInt(streakFile) + 1}
|
|
5456
5512
|
`);
|
|
5457
5513
|
const prior = read(ticketFile);
|
|
5458
5514
|
if (topTicket && topTicket === prior) {
|
|
5459
|
-
|
|
5515
|
+
writeFileSync8(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
|
|
5460
5516
|
`);
|
|
5461
5517
|
} else if (topTicket) {
|
|
5462
|
-
|
|
5518
|
+
writeFileSync8(this.path(ticketFile), `${topTicket}
|
|
5463
5519
|
`);
|
|
5464
|
-
|
|
5520
|
+
writeFileSync8(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5465
5521
|
`);
|
|
5466
|
-
|
|
5522
|
+
writeFileSync8(this.path(ticketStreakFile), "1\n");
|
|
5467
5523
|
}
|
|
5468
5524
|
}
|
|
5469
5525
|
};
|
|
@@ -5484,14 +5540,14 @@ var State = class _State {
|
|
|
5484
5540
|
return {
|
|
5485
5541
|
previous: () => {
|
|
5486
5542
|
try {
|
|
5487
|
-
const parsed = JSON.parse(
|
|
5543
|
+
const parsed = JSON.parse(readFileSync11(this.path(file), "utf8"));
|
|
5488
5544
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
5489
5545
|
} catch {
|
|
5490
5546
|
return {};
|
|
5491
5547
|
}
|
|
5492
5548
|
},
|
|
5493
5549
|
persist: (next) => {
|
|
5494
|
-
|
|
5550
|
+
writeFileSync8(this.path(file), JSON.stringify(next));
|
|
5495
5551
|
}
|
|
5496
5552
|
};
|
|
5497
5553
|
}
|
|
@@ -5505,14 +5561,14 @@ var State = class _State {
|
|
|
5505
5561
|
return {
|
|
5506
5562
|
previous: () => {
|
|
5507
5563
|
try {
|
|
5508
|
-
const parsed = JSON.parse(
|
|
5564
|
+
const parsed = JSON.parse(readFileSync11(this.path(file), "utf8"));
|
|
5509
5565
|
return Array.isArray(parsed) ? parsed : [];
|
|
5510
5566
|
} catch {
|
|
5511
5567
|
return [];
|
|
5512
5568
|
}
|
|
5513
5569
|
},
|
|
5514
5570
|
persist: (next) => {
|
|
5515
|
-
|
|
5571
|
+
writeFileSync8(this.path(file), JSON.stringify(next));
|
|
5516
5572
|
}
|
|
5517
5573
|
};
|
|
5518
5574
|
}
|
|
@@ -5525,7 +5581,7 @@ var State = class _State {
|
|
|
5525
5581
|
const file = ".release-last-run";
|
|
5526
5582
|
return {
|
|
5527
5583
|
touch: () => {
|
|
5528
|
-
|
|
5584
|
+
writeFileSync8(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
|
|
5529
5585
|
},
|
|
5530
5586
|
at: () => {
|
|
5531
5587
|
try {
|
|
@@ -5538,7 +5594,7 @@ var State = class _State {
|
|
|
5538
5594
|
}
|
|
5539
5595
|
readInt(file) {
|
|
5540
5596
|
try {
|
|
5541
|
-
return Number.parseInt(
|
|
5597
|
+
return Number.parseInt(readFileSync11(this.path(file), "utf8").trim(), 10) || 0;
|
|
5542
5598
|
} catch {
|
|
5543
5599
|
return 0;
|
|
5544
5600
|
}
|
|
@@ -6540,12 +6596,12 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
|
|
|
6540
6596
|
|
|
6541
6597
|
// src/run-worktree.ts
|
|
6542
6598
|
import { existsSync as existsSync14 } from "fs";
|
|
6543
|
-
import { dirname as dirname6, join as
|
|
6599
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
6544
6600
|
|
|
6545
6601
|
// src/ports.ts
|
|
6546
6602
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6547
6603
|
import { existsSync as existsSync13 } from "fs";
|
|
6548
|
-
import { join as
|
|
6604
|
+
import { join as join13 } from "path";
|
|
6549
6605
|
var run = (cmd, args) => {
|
|
6550
6606
|
try {
|
|
6551
6607
|
return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -6590,7 +6646,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
|
|
|
6590
6646
|
}
|
|
6591
6647
|
function worktreeExists(parent, prefix, n) {
|
|
6592
6648
|
for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
|
|
6593
|
-
if (existsSync13(
|
|
6649
|
+
if (existsSync13(join13(parent, name, ".git"))) return true;
|
|
6594
6650
|
}
|
|
6595
6651
|
return false;
|
|
6596
6652
|
}
|
|
@@ -6682,7 +6738,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
|
6682
6738
|
if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
|
|
6683
6739
|
return { action: "recut", path: existing.path, branch, base: baseRef };
|
|
6684
6740
|
}
|
|
6685
|
-
const path =
|
|
6741
|
+
const path = join14(dirname6(dir), worktreeForTicket(dir, cfg, t, role));
|
|
6686
6742
|
if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
|
|
6687
6743
|
return { action: "cut-remote", path, branch: located.branch };
|
|
6688
6744
|
}
|
|
@@ -7026,12 +7082,12 @@ async function guardRunStrays(route, ship, tracker, role, workingId, since2, log
|
|
|
7026
7082
|
|
|
7027
7083
|
// src/pending-handoff.ts
|
|
7028
7084
|
var PENDING_MAX_AGE_MS = 48 * 36e5;
|
|
7029
|
-
var pendingDirFor = (stateDir) =>
|
|
7030
|
-
var stateDirOfStream = (streamPath) =>
|
|
7085
|
+
var pendingDirFor = (stateDir) => join15(stateDir, "pending-handoffs");
|
|
7086
|
+
var stateDirOfStream = (streamPath) => join15(streamPath, "..", "..");
|
|
7031
7087
|
function writePendingHandoff(stateDir, rec) {
|
|
7032
7088
|
const dir = pendingDirFor(stateDir);
|
|
7033
|
-
|
|
7034
|
-
|
|
7089
|
+
mkdirSync8(dir, { recursive: true });
|
|
7090
|
+
writeFileSync9(join15(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
|
|
7035
7091
|
}
|
|
7036
7092
|
function listPendingHandoffs(stateDir, route) {
|
|
7037
7093
|
const dir = pendingDirFor(stateDir);
|
|
@@ -7039,7 +7095,7 @@ function listPendingHandoffs(stateDir, route) {
|
|
|
7039
7095
|
const out = [];
|
|
7040
7096
|
for (const f of readdirSync5(dir).filter((n) => n.endsWith(".json")).sort()) {
|
|
7041
7097
|
try {
|
|
7042
|
-
const rec = JSON.parse(
|
|
7098
|
+
const rec = JSON.parse(readFileSync12(join15(dir, f), "utf8"));
|
|
7043
7099
|
if (rec.version === 1 && rec.ticketRecordId && (!route || rec.route === route)) out.push(rec);
|
|
7044
7100
|
} catch {
|
|
7045
7101
|
}
|
|
@@ -7047,7 +7103,7 @@ function listPendingHandoffs(stateDir, route) {
|
|
|
7047
7103
|
return out;
|
|
7048
7104
|
}
|
|
7049
7105
|
function removePendingHandoff(stateDir, ticketRecordId) {
|
|
7050
|
-
rmSync8(
|
|
7106
|
+
rmSync8(join15(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
|
|
7051
7107
|
}
|
|
7052
7108
|
function pendingHandoffFor(stateDir, ticketRecordId) {
|
|
7053
7109
|
return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
|
|
@@ -7157,8 +7213,8 @@ function expandSkillMarkers(text, lookup, opts = {}) {
|
|
|
7157
7213
|
}
|
|
7158
7214
|
|
|
7159
7215
|
// src/skills.ts
|
|
7160
|
-
import { readFileSync as
|
|
7161
|
-
import { join as
|
|
7216
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync6, existsSync as existsSync17 } from "fs";
|
|
7217
|
+
import { join as join17 } from "path";
|
|
7162
7218
|
import { parse as parseYaml } from "yaml";
|
|
7163
7219
|
import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
|
|
7164
7220
|
|
|
@@ -7174,8 +7230,8 @@ ${additions}`;
|
|
|
7174
7230
|
}
|
|
7175
7231
|
|
|
7176
7232
|
// src/agents.ts
|
|
7177
|
-
import { readFileSync as
|
|
7178
|
-
import { join as
|
|
7233
|
+
import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
|
|
7234
|
+
import { join as join16 } from "path";
|
|
7179
7235
|
import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
|
|
7180
7236
|
var PERSONA_NAME = {
|
|
7181
7237
|
dev: "Developer",
|
|
@@ -7189,12 +7245,12 @@ var CREW_AGENT_COLUMN = "agent_id";
|
|
|
7189
7245
|
var AgentsSyncError = class extends Error {
|
|
7190
7246
|
};
|
|
7191
7247
|
function personaDefaultPrompt(promptsDir, role) {
|
|
7192
|
-
const briefPath =
|
|
7248
|
+
const briefPath = join16(promptsDir, "personas", `lane-${role}.md`);
|
|
7193
7249
|
if (!existsSync16(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
|
|
7194
|
-
if (SELF_CONTAINED_ROLES.has(role)) return
|
|
7195
|
-
const commonPath =
|
|
7196
|
-
if (!existsSync16(commonPath)) return
|
|
7197
|
-
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")}`;
|
|
7198
7254
|
}
|
|
7199
7255
|
async function resolveAgentId(route, role, opts) {
|
|
7200
7256
|
const cached = route.resolved?.agentPersonas?.[role]?.agentId;
|
|
@@ -7641,7 +7697,7 @@ function describeCrewLink(o) {
|
|
|
7641
7697
|
var SkillSyncError = class extends Error {
|
|
7642
7698
|
};
|
|
7643
7699
|
function parseSkillFile(path) {
|
|
7644
|
-
const text =
|
|
7700
|
+
const text = readFileSync14(path, "utf8");
|
|
7645
7701
|
const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
|
|
7646
7702
|
if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
|
|
7647
7703
|
const frontmatter = match[1];
|
|
@@ -7654,9 +7710,9 @@ function parseSkillFile(path) {
|
|
|
7654
7710
|
return { name: meta.name, description: meta.description, prompt: body, file: path };
|
|
7655
7711
|
}
|
|
7656
7712
|
function listLocalSkills(promptsDir) {
|
|
7657
|
-
const dir =
|
|
7713
|
+
const dir = join17(promptsDir, "skills");
|
|
7658
7714
|
if (!existsSync17(dir)) return [];
|
|
7659
|
-
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)));
|
|
7660
7716
|
}
|
|
7661
7717
|
async function syncSkills(route, opts) {
|
|
7662
7718
|
if (!route.resolved) {
|
|
@@ -7859,23 +7915,23 @@ var AgentError = class extends Error {
|
|
|
7859
7915
|
var AgentSpawnError = class extends AgentError {
|
|
7860
7916
|
};
|
|
7861
7917
|
function planAgentRun(o) {
|
|
7862
|
-
const briefPath =
|
|
7918
|
+
const briefPath = join18(o.route.promptsDir, "personas", `lane-${o.role}.md`);
|
|
7863
7919
|
if (!existsSync18(briefPath)) {
|
|
7864
7920
|
throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
|
|
7865
7921
|
}
|
|
7866
|
-
const commonPath =
|
|
7922
|
+
const commonPath = join18(o.route.promptsDir, "personas", "common.md");
|
|
7867
7923
|
const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync18(commonPath);
|
|
7868
7924
|
const now = o.now ?? (() => Date.now());
|
|
7869
7925
|
const routeLabel = routeSlug(o.route.route);
|
|
7870
|
-
const digestPath2 =
|
|
7926
|
+
const digestPath2 = join18(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
|
|
7871
7927
|
let digest;
|
|
7872
7928
|
let ageSeconds;
|
|
7873
7929
|
if (existsSync18(digestPath2)) {
|
|
7874
7930
|
ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
|
|
7875
|
-
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest =
|
|
7931
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync15(digestPath2, "utf8");
|
|
7876
7932
|
}
|
|
7877
|
-
const common = usesCommon ?
|
|
7878
|
-
const brief =
|
|
7933
|
+
const common = usesCommon ? readFileSync15(commonPath, "utf8") : "";
|
|
7934
|
+
const brief = readFileSync15(briefPath, "utf8");
|
|
7879
7935
|
const effectiveCommon = o.divergedPrompt ? "" : common;
|
|
7880
7936
|
const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
|
|
7881
7937
|
const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
|
|
@@ -7896,8 +7952,8 @@ function planAgentRun(o) {
|
|
|
7896
7952
|
brief: expanded.text,
|
|
7897
7953
|
digest
|
|
7898
7954
|
});
|
|
7899
|
-
const promptSha =
|
|
7900
|
-
const streamsDir =
|
|
7955
|
+
const promptSha = createHash3("sha256").update(expanded.text).digest("hex");
|
|
7956
|
+
const streamsDir = join18(o.stateDir, "streams");
|
|
7901
7957
|
const base = `${routeLabel}-${o.role}-${o.cycle}`;
|
|
7902
7958
|
const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
|
|
7903
7959
|
const { toolchain, model } = seat;
|
|
@@ -7953,8 +8009,8 @@ function planAgentRun(o) {
|
|
|
7953
8009
|
digestAttached: digest !== void 0,
|
|
7954
8010
|
digestAgeSeconds: ageSeconds,
|
|
7955
8011
|
model,
|
|
7956
|
-
streamPath:
|
|
7957
|
-
eventsPath:
|
|
8012
|
+
streamPath: join18(streamsDir, `${base}.jsonl`),
|
|
8013
|
+
eventsPath: join18(streamsDir, `${base}.events.jsonl`),
|
|
7958
8014
|
ticket: o.ticket,
|
|
7959
8015
|
ticketRecordId: o.ticketRecordId ?? null,
|
|
7960
8016
|
agentLog,
|
|
@@ -7983,7 +8039,7 @@ function describePlan(p) {
|
|
|
7983
8039
|
var pidPathFor = (streamPath) => `${streamPath}.pid`;
|
|
7984
8040
|
function readRunSidecar(streamPath) {
|
|
7985
8041
|
try {
|
|
7986
|
-
const sc = JSON.parse(
|
|
8042
|
+
const sc = JSON.parse(readFileSync15(pidPathFor(streamPath), "utf8"));
|
|
7987
8043
|
return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
|
|
7988
8044
|
} catch {
|
|
7989
8045
|
return null;
|
|
@@ -8019,7 +8075,7 @@ function identityOf(plan, adapterHarness) {
|
|
|
8019
8075
|
var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
|
|
8020
8076
|
function openSink(path) {
|
|
8021
8077
|
try {
|
|
8022
|
-
|
|
8078
|
+
mkdirSync9(dirname7(path), { recursive: true });
|
|
8023
8079
|
return createWriteStream(path, { flags: "a" });
|
|
8024
8080
|
} catch {
|
|
8025
8081
|
return void 0;
|
|
@@ -8198,7 +8254,7 @@ var RunObserver = class {
|
|
|
8198
8254
|
};
|
|
8199
8255
|
function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
8200
8256
|
try {
|
|
8201
|
-
|
|
8257
|
+
writeFileSync10(`${id.streamPath}.meta.json`, JSON.stringify({
|
|
8202
8258
|
route: id.route,
|
|
8203
8259
|
role: id.role,
|
|
8204
8260
|
hint: id.ticket,
|
|
@@ -8317,7 +8373,7 @@ function spawnAgent(plan, emit) {
|
|
|
8317
8373
|
return new Promise((resolve6, reject) => {
|
|
8318
8374
|
let streamFd;
|
|
8319
8375
|
try {
|
|
8320
|
-
|
|
8376
|
+
mkdirSync9(dirname7(plan.streamPath), { recursive: true });
|
|
8321
8377
|
streamFd = openSync3(plan.streamPath, "a");
|
|
8322
8378
|
} catch (e) {
|
|
8323
8379
|
void endSink(eventsSink);
|
|
@@ -8350,7 +8406,7 @@ function spawnAgent(plan, emit) {
|
|
|
8350
8406
|
promptSha: plan.promptSha
|
|
8351
8407
|
};
|
|
8352
8408
|
try {
|
|
8353
|
-
|
|
8409
|
+
writeFileSync10(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
|
|
8354
8410
|
} catch {
|
|
8355
8411
|
}
|
|
8356
8412
|
}
|
|
@@ -8567,7 +8623,7 @@ async function runSelftest(i, deps = realDeps) {
|
|
|
8567
8623
|
...adapter.runEnv(spec),
|
|
8568
8624
|
[KEY_VAR]: i.apiKey
|
|
8569
8625
|
};
|
|
8570
|
-
const cwd = mkdtempSync(
|
|
8626
|
+
const cwd = mkdtempSync(join19(tmpdir2(), "crew-selftest-"));
|
|
8571
8627
|
try {
|
|
8572
8628
|
const run2 = await deps.run({
|
|
8573
8629
|
bin: invocation.bin,
|
|
@@ -8599,11 +8655,11 @@ function formatResult(r) {
|
|
|
8599
8655
|
const secs = `${(r.ms / 1e3).toFixed(1)}s`;
|
|
8600
8656
|
return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
|
|
8601
8657
|
}
|
|
8602
|
-
var
|
|
8658
|
+
var fileFor2 = (stateDir) => join19(stateDir, "selftest.json");
|
|
8603
8659
|
function readSelftest(stateDir) {
|
|
8604
8660
|
try {
|
|
8605
|
-
const p =
|
|
8606
|
-
return existsSync19(p) ? JSON.parse(
|
|
8661
|
+
const p = fileFor2(stateDir);
|
|
8662
|
+
return existsSync19(p) ? JSON.parse(readFileSync16(p, "utf8")) : {};
|
|
8607
8663
|
} catch {
|
|
8608
8664
|
return {};
|
|
8609
8665
|
}
|
|
@@ -8611,8 +8667,8 @@ function readSelftest(stateDir) {
|
|
|
8611
8667
|
function recordSelftest(stateDir, results) {
|
|
8612
8668
|
const all = readSelftest(stateDir);
|
|
8613
8669
|
for (const r of results) all[r.toolchain] = r;
|
|
8614
|
-
|
|
8615
|
-
|
|
8670
|
+
mkdirSync10(stateDir, { recursive: true });
|
|
8671
|
+
writeFileSync11(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
|
|
8616
8672
|
`);
|
|
8617
8673
|
}
|
|
8618
8674
|
function doctorSelftestLines(stored, toolchains) {
|
|
@@ -8624,7 +8680,7 @@ function doctorSelftestLines(stored, toolchains) {
|
|
|
8624
8680
|
}
|
|
8625
8681
|
|
|
8626
8682
|
// src/cli.ts
|
|
8627
|
-
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";
|
|
8628
8684
|
import { fileURLToPath } from "url";
|
|
8629
8685
|
import { hostname as hostname2, homedir as homedir9 } from "os";
|
|
8630
8686
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
@@ -8803,14 +8859,14 @@ function rosterMarkdown(roster, meId) {
|
|
|
8803
8859
|
}
|
|
8804
8860
|
|
|
8805
8861
|
// src/connect-resume.ts
|
|
8806
|
-
import { mkdirSync as
|
|
8807
|
-
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";
|
|
8808
8864
|
function lastSessionFile(stateDir) {
|
|
8809
|
-
return
|
|
8865
|
+
return join20(stateDir, "connect-session.json");
|
|
8810
8866
|
}
|
|
8811
8867
|
function readPointers(stateDir) {
|
|
8812
8868
|
try {
|
|
8813
|
-
const parsed = JSON.parse(
|
|
8869
|
+
const parsed = JSON.parse(readFileSync17(lastSessionFile(stateDir), "utf8"));
|
|
8814
8870
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
8815
8871
|
const out = {};
|
|
8816
8872
|
for (const [host, v] of Object.entries(parsed)) {
|
|
@@ -8824,8 +8880,8 @@ function readPointers(stateDir) {
|
|
|
8824
8880
|
}
|
|
8825
8881
|
function writePointers(stateDir, pointers) {
|
|
8826
8882
|
const file = lastSessionFile(stateDir);
|
|
8827
|
-
|
|
8828
|
-
|
|
8883
|
+
mkdirSync11(dirname8(file), { recursive: true });
|
|
8884
|
+
writeFileSync12(file, `${JSON.stringify(pointers, null, 2)}
|
|
8829
8885
|
`, { mode: 384 });
|
|
8830
8886
|
}
|
|
8831
8887
|
function rememberSession(stateDir, host, workspaceSlug) {
|
|
@@ -8881,15 +8937,15 @@ async function signOutSessions(o) {
|
|
|
8881
8937
|
}
|
|
8882
8938
|
|
|
8883
8939
|
// src/reset.ts
|
|
8884
|
-
import { existsSync as existsSync20, mkdirSync as
|
|
8885
|
-
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";
|
|
8886
8942
|
import { hostFromUrl as hostFromUrl2 } from "@tablation/client";
|
|
8887
8943
|
function clonedCheckoutsFile(stateDir) {
|
|
8888
|
-
return
|
|
8944
|
+
return join21(stateDir, "cloned-checkouts.json");
|
|
8889
8945
|
}
|
|
8890
8946
|
function readClonedCheckouts(stateDir) {
|
|
8891
8947
|
try {
|
|
8892
|
-
const parsed = JSON.parse(
|
|
8948
|
+
const parsed = JSON.parse(readFileSync18(clonedCheckoutsFile(stateDir), "utf8"));
|
|
8893
8949
|
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
|
|
8894
8950
|
} catch {
|
|
8895
8951
|
return [];
|
|
@@ -8899,8 +8955,8 @@ function recordClonedCheckout(stateDir, dir) {
|
|
|
8899
8955
|
const known = readClonedCheckouts(stateDir);
|
|
8900
8956
|
if (known.includes(dir)) return;
|
|
8901
8957
|
const file = clonedCheckoutsFile(stateDir);
|
|
8902
|
-
|
|
8903
|
-
|
|
8958
|
+
mkdirSync12(dirname9(file), { recursive: true });
|
|
8959
|
+
writeFileSync13(file, `${JSON.stringify([...known, dir], null, 2)}
|
|
8904
8960
|
`);
|
|
8905
8961
|
}
|
|
8906
8962
|
var mark = (dryRun) => dryRun ? "would-remove" : "removed";
|
|
@@ -8974,10 +9030,10 @@ function removeCheckout(dir, dryRun, fx) {
|
|
|
8974
9030
|
}
|
|
8975
9031
|
function removeState(stateDir, dryRun) {
|
|
8976
9032
|
const out = [];
|
|
8977
|
-
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")];
|
|
8978
9034
|
try {
|
|
8979
9035
|
for (const name of readdirSync7(stateDir)) {
|
|
8980
|
-
if (name.startsWith(".") && statSync6(
|
|
9036
|
+
if (name.startsWith(".") && statSync6(join21(stateDir, name)).isFile()) targets.push(join21(stateDir, name));
|
|
8981
9037
|
}
|
|
8982
9038
|
} catch {
|
|
8983
9039
|
}
|
|
@@ -9762,8 +9818,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
9762
9818
|
}
|
|
9763
9819
|
|
|
9764
9820
|
// src/poll.ts
|
|
9765
|
-
import { writeFileSync as
|
|
9766
|
-
import { join as
|
|
9821
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
9822
|
+
import { join as join22 } from "path";
|
|
9767
9823
|
|
|
9768
9824
|
// src/stalled.ts
|
|
9769
9825
|
var STALLED_AFTER_CYCLES = 30;
|
|
@@ -10054,7 +10110,7 @@ async function decideCycle(o) {
|
|
|
10054
10110
|
}
|
|
10055
10111
|
return decision;
|
|
10056
10112
|
}
|
|
10057
|
-
var digestPath = (stateDir, route, role) =>
|
|
10113
|
+
var digestPath = (stateDir, route, role) => join22(stateDir, `digest-${route}-${role}.md`);
|
|
10058
10114
|
function writeDigest(o, d, role, stateDir) {
|
|
10059
10115
|
const me = o.route.resolved?.seats[role];
|
|
10060
10116
|
if (!me) return false;
|
|
@@ -10122,7 +10178,7 @@ function writeDigest(o, d, role, stateDir) {
|
|
|
10122
10178
|
};
|
|
10123
10179
|
const text = role === "qa" ? qaDigest(input) : buildingDigest(input);
|
|
10124
10180
|
const path = digestPath(stateDir, routeSlug(o.route.route), role);
|
|
10125
|
-
|
|
10181
|
+
writeFileSync14(path, text);
|
|
10126
10182
|
o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
|
|
10127
10183
|
data: { role, bytes: Buffer.byteLength(text) }
|
|
10128
10184
|
});
|
|
@@ -10528,8 +10584,8 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
|
|
|
10528
10584
|
}
|
|
10529
10585
|
|
|
10530
10586
|
// src/connect-wizard.ts
|
|
10531
|
-
import { delimiter, dirname as dirname10, join as
|
|
10532
|
-
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";
|
|
10533
10589
|
import { stringify } from "yaml";
|
|
10534
10590
|
var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
|
|
10535
10591
|
function shouldRunWizard(opts) {
|
|
@@ -10551,7 +10607,7 @@ function defaultIsExecutable(p) {
|
|
|
10551
10607
|
function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
|
|
10552
10608
|
for (const dir of pathEnv.split(delimiter)) {
|
|
10553
10609
|
if (!dir) continue;
|
|
10554
|
-
const candidate =
|
|
10610
|
+
const candidate = join23(dir, name);
|
|
10555
10611
|
if (isExecutable(candidate)) return candidate;
|
|
10556
10612
|
}
|
|
10557
10613
|
return void 0;
|
|
@@ -10612,9 +10668,9 @@ routes:
|
|
|
10612
10668
|
${routeBlock}`;
|
|
10613
10669
|
}
|
|
10614
10670
|
function writeNewConfig(path, text) {
|
|
10615
|
-
|
|
10671
|
+
mkdirSync13(dirname10(path), { recursive: true });
|
|
10616
10672
|
try {
|
|
10617
|
-
|
|
10673
|
+
writeFileSync15(path, text, { mode: 384, flag: "wx" });
|
|
10618
10674
|
return true;
|
|
10619
10675
|
} catch (e) {
|
|
10620
10676
|
if (e.code === "EEXIST") return false;
|
|
@@ -10648,7 +10704,7 @@ Wrote ${o.configPath}.
|
|
|
10648
10704
|
// src/connect-json.ts
|
|
10649
10705
|
import { accessSync as accessSync3, constants as constants3 } from "fs";
|
|
10650
10706
|
import { homedir as homedir7 } from "os";
|
|
10651
|
-
import { dirname as dirname11, join as
|
|
10707
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
10652
10708
|
import { delimiter as delimiter2 } from "path";
|
|
10653
10709
|
var CHOICE_EXIT_CODE = 3;
|
|
10654
10710
|
function createEmitter(json, write) {
|
|
@@ -10687,12 +10743,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
|
|
|
10687
10743
|
const dirs = [
|
|
10688
10744
|
...pathEnv.split(delimiter2),
|
|
10689
10745
|
...(extraPath ?? "").split(delimiter2),
|
|
10690
|
-
|
|
10691
|
-
|
|
10746
|
+
join24(home, ".local/bin"),
|
|
10747
|
+
join24(home, ".claude/local"),
|
|
10692
10748
|
"/opt/homebrew/bin",
|
|
10693
10749
|
"/usr/local/bin",
|
|
10694
|
-
|
|
10695
|
-
|
|
10750
|
+
join24(home, ".npm-global/bin"),
|
|
10751
|
+
join24(home, ".volta/bin")
|
|
10696
10752
|
];
|
|
10697
10753
|
return [...new Set(dirs.filter(Boolean))];
|
|
10698
10754
|
}
|
|
@@ -10728,8 +10784,8 @@ function flagWizardAnswers(f, env) {
|
|
|
10728
10784
|
}
|
|
10729
10785
|
|
|
10730
10786
|
// src/release-run.ts
|
|
10731
|
-
import { readFileSync as
|
|
10732
|
-
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";
|
|
10733
10789
|
import { tmpdir as tmpdir3 } from "os";
|
|
10734
10790
|
|
|
10735
10791
|
// src/release.ts
|
|
@@ -11447,13 +11503,13 @@ async function bump(o, size) {
|
|
|
11447
11503
|
}
|
|
11448
11504
|
return printed;
|
|
11449
11505
|
}
|
|
11450
|
-
const files = o.repo.release.versionFiles.map((f) =>
|
|
11506
|
+
const files = o.repo.release.versionFiles.map((f) => join25(o.cwd, f));
|
|
11451
11507
|
const first = files[0];
|
|
11452
11508
|
if (!existsSync22(first)) {
|
|
11453
11509
|
o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
|
|
11454
11510
|
return null;
|
|
11455
11511
|
}
|
|
11456
|
-
const current = JSON.parse(
|
|
11512
|
+
const current = JSON.parse(readFileSync19(first, "utf8")).version;
|
|
11457
11513
|
if (!current) {
|
|
11458
11514
|
o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
|
|
11459
11515
|
return null;
|
|
@@ -11466,27 +11522,27 @@ async function bump(o, size) {
|
|
|
11466
11522
|
return version;
|
|
11467
11523
|
}
|
|
11468
11524
|
for (const f of files) {
|
|
11469
|
-
const src =
|
|
11525
|
+
const src = readFileSync19(f, "utf8");
|
|
11470
11526
|
const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
|
|
11471
11527
|
if (out === src) {
|
|
11472
11528
|
o.emit.error(`no version field in ${f}`);
|
|
11473
11529
|
return null;
|
|
11474
11530
|
}
|
|
11475
|
-
|
|
11531
|
+
writeFileSync16(f, out);
|
|
11476
11532
|
}
|
|
11477
11533
|
return version;
|
|
11478
11534
|
}
|
|
11479
11535
|
function writeChangelog(o, version, entries) {
|
|
11480
11536
|
const file = o.repo.release.changelog;
|
|
11481
11537
|
if (!file || entries.length === 0) return;
|
|
11482
|
-
const path =
|
|
11538
|
+
const path = join25(o.cwd, file);
|
|
11483
11539
|
const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
|
|
11484
11540
|
if (o.dryRun) {
|
|
11485
11541
|
o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
|
|
11486
11542
|
return;
|
|
11487
11543
|
}
|
|
11488
|
-
const existing = existsSync22(path) ?
|
|
11489
|
-
|
|
11544
|
+
const existing = existsSync22(path) ? readFileSync19(path, "utf8") : "# Changelog\n";
|
|
11545
|
+
writeFileSync16(path, insertChangelogSection(existing, section));
|
|
11490
11546
|
}
|
|
11491
11547
|
async function confirm(o, expected) {
|
|
11492
11548
|
if (!o.repo.hooks.released) return void 0;
|
|
@@ -11579,8 +11635,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
|
|
|
11579
11635
|
function openReleaseCheckout(o) {
|
|
11580
11636
|
const { remote, base } = o.repo.branch;
|
|
11581
11637
|
const branch = `crew/release-${Date.now()}`;
|
|
11582
|
-
const root = mkdtempSync2(
|
|
11583
|
-
const dir =
|
|
11638
|
+
const root = mkdtempSync2(join25(tmpdir3(), "crew-release-"));
|
|
11639
|
+
const dir = join25(root, "checkout");
|
|
11584
11640
|
o.emit.emit(`release checkout: ${dir}`, { step: "release" });
|
|
11585
11641
|
const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
|
|
11586
11642
|
try {
|
|
@@ -11590,10 +11646,10 @@ function openReleaseCheckout(o) {
|
|
|
11590
11646
|
throw e;
|
|
11591
11647
|
}
|
|
11592
11648
|
for (const f of o.repo.worktrees.copy) {
|
|
11593
|
-
const src =
|
|
11649
|
+
const src = join25(o.cwd, f);
|
|
11594
11650
|
if (!existsSync22(src)) continue;
|
|
11595
|
-
|
|
11596
|
-
copyFileSync(src,
|
|
11651
|
+
mkdirSync14(dirname12(join25(dir, f)), { recursive: true });
|
|
11652
|
+
copyFileSync(src, join25(dir, f));
|
|
11597
11653
|
}
|
|
11598
11654
|
return { dir, branch, close };
|
|
11599
11655
|
}
|
|
@@ -12664,19 +12720,19 @@ function nextRoles(decision, ran) {
|
|
|
12664
12720
|
}
|
|
12665
12721
|
|
|
12666
12722
|
// src/ship-name.ts
|
|
12667
|
-
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";
|
|
12668
12724
|
import { dirname as dirname14 } from "path";
|
|
12669
12725
|
import { parseDocument } from "yaml";
|
|
12670
12726
|
|
|
12671
12727
|
// src/machine-id.ts
|
|
12672
12728
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
12673
|
-
import { createHash as
|
|
12674
|
-
import { existsSync as existsSync24, mkdirSync as
|
|
12675
|
-
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";
|
|
12676
12732
|
function machineIdPathFor(stateDir) {
|
|
12677
|
-
return
|
|
12733
|
+
return join26(stateDir, "machine-id");
|
|
12678
12734
|
}
|
|
12679
|
-
var sha256 = (s) =>
|
|
12735
|
+
var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
|
|
12680
12736
|
function readRawMachineId(platform2 = process.platform) {
|
|
12681
12737
|
try {
|
|
12682
12738
|
if (platform2 === "darwin") {
|
|
@@ -12688,7 +12744,7 @@ function readRawMachineId(platform2 = process.platform) {
|
|
|
12688
12744
|
}
|
|
12689
12745
|
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
12690
12746
|
if (existsSync24(p)) {
|
|
12691
|
-
const id =
|
|
12747
|
+
const id = readFileSync20(p, "utf8").trim();
|
|
12692
12748
|
if (id) return id;
|
|
12693
12749
|
}
|
|
12694
12750
|
}
|
|
@@ -12700,14 +12756,14 @@ function ensureMachineId(stateDir, opts = {}) {
|
|
|
12700
12756
|
if (opts.override?.trim()) return sha256(opts.override.trim());
|
|
12701
12757
|
const path = machineIdPathFor(stateDir);
|
|
12702
12758
|
if (existsSync24(path)) {
|
|
12703
|
-
const cached =
|
|
12759
|
+
const cached = readFileSync20(path, "utf8").trim();
|
|
12704
12760
|
if (/^[0-9a-f]{64}$/.test(cached)) return cached;
|
|
12705
12761
|
}
|
|
12706
12762
|
const raw = (opts.read ?? readRawMachineId)();
|
|
12707
12763
|
if (!raw) return void 0;
|
|
12708
12764
|
const hashed = sha256(raw);
|
|
12709
|
-
|
|
12710
|
-
|
|
12765
|
+
mkdirSync15(dirname13(path), { recursive: true });
|
|
12766
|
+
writeFileSync17(path, `${hashed}
|
|
12711
12767
|
`, { mode: 384 });
|
|
12712
12768
|
return hashed;
|
|
12713
12769
|
}
|
|
@@ -12780,7 +12836,7 @@ async function syncRouteName(i) {
|
|
|
12780
12836
|
function readSyncedName(resolvedFile) {
|
|
12781
12837
|
if (!existsSync25(resolvedFile)) return void 0;
|
|
12782
12838
|
try {
|
|
12783
|
-
const v = JSON.parse(
|
|
12839
|
+
const v = JSON.parse(readFileSync21(resolvedFile, "utf8")).shipName;
|
|
12784
12840
|
return typeof v === "string" ? v : void 0;
|
|
12785
12841
|
} catch {
|
|
12786
12842
|
return void 0;
|
|
@@ -12790,22 +12846,22 @@ function writeSyncedName(resolvedFile, name) {
|
|
|
12790
12846
|
if (!existsSync25(resolvedFile)) return;
|
|
12791
12847
|
let raw;
|
|
12792
12848
|
try {
|
|
12793
|
-
raw = JSON.parse(
|
|
12849
|
+
raw = JSON.parse(readFileSync21(resolvedFile, "utf8"));
|
|
12794
12850
|
} catch {
|
|
12795
12851
|
return;
|
|
12796
12852
|
}
|
|
12797
12853
|
if (raw.shipName === name) return;
|
|
12798
12854
|
raw.shipName = name;
|
|
12799
|
-
|
|
12855
|
+
mkdirSync16(dirname14(resolvedFile), { recursive: true });
|
|
12800
12856
|
const tmp = `${resolvedFile}.tmp${process.pid}`;
|
|
12801
|
-
|
|
12857
|
+
writeFileSync18(tmp, `${JSON.stringify(raw, null, 2)}
|
|
12802
12858
|
`);
|
|
12803
12859
|
renameSync2(tmp, resolvedFile);
|
|
12804
12860
|
}
|
|
12805
12861
|
async function reconcileShipName(i) {
|
|
12806
12862
|
let local = i.ship.name;
|
|
12807
12863
|
try {
|
|
12808
|
-
local = shipNameOfYaml(
|
|
12864
|
+
local = shipNameOfYaml(readFileSync21(i.configFile, "utf8")) ?? local;
|
|
12809
12865
|
} catch {
|
|
12810
12866
|
}
|
|
12811
12867
|
const resolvedFile = resolvedPathFor(i.stateDir, i.route);
|
|
@@ -12829,8 +12885,8 @@ async function reconcileShipName(i) {
|
|
|
12829
12885
|
}
|
|
12830
12886
|
if (result.status !== "ok") return result;
|
|
12831
12887
|
if (result.action === "adopt" || result.action === "row-wins") {
|
|
12832
|
-
const text =
|
|
12833
|
-
|
|
12888
|
+
const text = readFileSync21(i.configFile, "utf8");
|
|
12889
|
+
writeFileSync18(i.configFile, setShipNameInYaml(text, result.name));
|
|
12834
12890
|
i.ship.name = result.name;
|
|
12835
12891
|
if (result.action === "row-wins") {
|
|
12836
12892
|
i.emit?.warn(
|
|
@@ -13738,7 +13794,7 @@ function describeCapability(i) {
|
|
|
13738
13794
|
}
|
|
13739
13795
|
|
|
13740
13796
|
// src/agents-sync-run.ts
|
|
13741
|
-
import { existsSync as existsSync26, mkdirSync as
|
|
13797
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
|
|
13742
13798
|
import { dirname as dirname15 } from "path";
|
|
13743
13799
|
async function runAgentsSync(target, o) {
|
|
13744
13800
|
const dryRun = o.dryRun ?? false;
|
|
@@ -13772,12 +13828,12 @@ async function runAgentsSync(target, o) {
|
|
|
13772
13828
|
for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
|
|
13773
13829
|
if (!dryRun) {
|
|
13774
13830
|
const resolvedPath = resolvedPathFor(o.stateDir, target.route);
|
|
13775
|
-
const raw = existsSync26(resolvedPath) ? JSON.parse(
|
|
13831
|
+
const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync22(resolvedPath, "utf8")) : {};
|
|
13776
13832
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
13777
13833
|
if (skillResult) raw.agentSkills = skillResult.agentSkills;
|
|
13778
13834
|
if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
|
|
13779
|
-
|
|
13780
|
-
|
|
13835
|
+
mkdirSync17(dirname15(resolvedPath), { recursive: true });
|
|
13836
|
+
writeFileSync19(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
13781
13837
|
`);
|
|
13782
13838
|
if (target.resolved) {
|
|
13783
13839
|
target.resolved.agentPersonas = personaResult.agentPersonas;
|
|
@@ -13792,7 +13848,7 @@ async function runAgentsSync(target, o) {
|
|
|
13792
13848
|
}
|
|
13793
13849
|
|
|
13794
13850
|
// src/logbook.ts
|
|
13795
|
-
import { createHash as
|
|
13851
|
+
import { createHash as createHash5 } from "crypto";
|
|
13796
13852
|
import { TablationClient as TablationClient4 } from "@tablation/client";
|
|
13797
13853
|
var LogbookError = class extends Error {
|
|
13798
13854
|
};
|
|
@@ -13865,7 +13921,7 @@ async function showLogEntry(route, entryId, opts = {}) {
|
|
|
13865
13921
|
};
|
|
13866
13922
|
}
|
|
13867
13923
|
const prompt = appendAdditions(match.prompt ?? "", match.prompt_additions);
|
|
13868
|
-
const actualSha =
|
|
13924
|
+
const actualSha = createHash5("sha256").update(prompt).digest("hex");
|
|
13869
13925
|
const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
|
|
13870
13926
|
return {
|
|
13871
13927
|
entry,
|
|
@@ -13884,7 +13940,7 @@ import { isMap as isMap2, parseDocument as parseDocument3, YAMLMap } from "yaml"
|
|
|
13884
13940
|
|
|
13885
13941
|
// src/repos-cmd.ts
|
|
13886
13942
|
import { homedir as homedir8 } from "os";
|
|
13887
|
-
import { basename as basename8, join as
|
|
13943
|
+
import { basename as basename8, join as join27, resolve as resolve3 } from "path";
|
|
13888
13944
|
import { isMap, isSeq, parseDocument as parseDocument2, stringify as stringify2 } from "yaml";
|
|
13889
13945
|
var ReposError = class extends Error {
|
|
13890
13946
|
};
|
|
@@ -14162,7 +14218,7 @@ function planRepoClone(i) {
|
|
|
14162
14218
|
}
|
|
14163
14219
|
const ship = doc.get("ship", true);
|
|
14164
14220
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14165
|
-
const target = i.dir ? expandDir(i.dir, i.base) :
|
|
14221
|
+
const target = i.dir ? expandDir(i.dir, i.base) : join27(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14166
14222
|
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14167
14223
|
return { ...add, remote, target };
|
|
14168
14224
|
}
|
|
@@ -14542,7 +14598,7 @@ ${run2.err}` });
|
|
|
14542
14598
|
}
|
|
14543
14599
|
|
|
14544
14600
|
// src/sample-ticket.ts
|
|
14545
|
-
import { basename as basename9, dirname as dirname17, join as
|
|
14601
|
+
import { basename as basename9, dirname as dirname17, join as join28 } from "path";
|
|
14546
14602
|
var SAMPLE_REPO = "crew-sample";
|
|
14547
14603
|
var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
|
|
14548
14604
|
var SAMPLE_MARKER = "sample: true";
|
|
@@ -14565,8 +14621,8 @@ has something safe to work on: its test and build hooks always pass, it has no
|
|
|
14565
14621
|
remote, and \`crew sample-ticket --remove\` deletes it.
|
|
14566
14622
|
`;
|
|
14567
14623
|
function sampleDir(existing, reposBase, workspace) {
|
|
14568
|
-
if (existing.length > 0) return
|
|
14569
|
-
return
|
|
14624
|
+
if (existing.length > 0) return join28(dirname17(existing[0]), SAMPLE_REPO);
|
|
14625
|
+
return join28(reposBase, workspace, SAMPLE_REPO);
|
|
14570
14626
|
}
|
|
14571
14627
|
var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
|
|
14572
14628
|
function sampleDescription() {
|
|
@@ -14647,8 +14703,8 @@ function schedulerInstalled(plan, exists, crontab) {
|
|
|
14647
14703
|
}
|
|
14648
14704
|
|
|
14649
14705
|
// src/worktree-sweep.ts
|
|
14650
|
-
import { existsSync as existsSync27, readFileSync as
|
|
14651
|
-
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";
|
|
14652
14708
|
function planWorktreeSweep(target, tickets, contract) {
|
|
14653
14709
|
const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
|
|
14654
14710
|
const verified = contract.statuses.verified;
|
|
@@ -14715,7 +14771,7 @@ function siblingWorktreeDirs(target) {
|
|
|
14715
14771
|
for (const name of names) {
|
|
14716
14772
|
if (!name.startsWith(stem)) continue;
|
|
14717
14773
|
const m = DIR_NUMBER_SUFFIXED.exec(name);
|
|
14718
|
-
const path =
|
|
14774
|
+
const path = join29(parent, name);
|
|
14719
14775
|
if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
|
|
14720
14776
|
}
|
|
14721
14777
|
return out;
|
|
@@ -14818,7 +14874,7 @@ function isHandBackMerge(path, base) {
|
|
|
14818
14874
|
if (!msgPath) return false;
|
|
14819
14875
|
let first;
|
|
14820
14876
|
try {
|
|
14821
|
-
first =
|
|
14877
|
+
first = readFileSync23(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
|
|
14822
14878
|
} catch {
|
|
14823
14879
|
return false;
|
|
14824
14880
|
}
|
|
@@ -14924,13 +14980,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
14924
14980
|
|
|
14925
14981
|
// src/stream-sweep.ts
|
|
14926
14982
|
import { existsSync as existsSync28, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8 } from "fs";
|
|
14927
|
-
import { join as
|
|
14983
|
+
import { join as join30 } from "path";
|
|
14928
14984
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
14929
14985
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
14930
|
-
const dir =
|
|
14986
|
+
const dir = join30(stateDir, "streams");
|
|
14931
14987
|
if (!existsSync28(dir)) return [];
|
|
14932
14988
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
14933
|
-
return readdirSync9(dir).map((name) =>
|
|
14989
|
+
return readdirSync9(dir).map((name) => join30(dir, name)).filter((path) => {
|
|
14934
14990
|
try {
|
|
14935
14991
|
return statSync8(path).mtimeMs < cutoff;
|
|
14936
14992
|
} catch {
|
|
@@ -14958,7 +15014,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
14958
15014
|
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
14959
15015
|
if (!existsSync28(stateDir)) return [];
|
|
14960
15016
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
14961
|
-
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) => {
|
|
14962
15018
|
try {
|
|
14963
15019
|
return statSync8(path).mtimeMs < cutoff;
|
|
14964
15020
|
} catch {
|
|
@@ -14968,24 +15024,24 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
14968
15024
|
}
|
|
14969
15025
|
|
|
14970
15026
|
// src/cli.ts
|
|
14971
|
-
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";
|
|
14972
15028
|
import { createInterface } from "readline/promises";
|
|
14973
15029
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
14974
15030
|
|
|
14975
15031
|
// src/ssh-keys.ts
|
|
14976
15032
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
14977
|
-
import { existsSync as existsSync29, mkdirSync as
|
|
14978
|
-
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";
|
|
14979
15035
|
var SshKeyError = class extends Error {
|
|
14980
15036
|
};
|
|
14981
15037
|
function shipSshKeyPathFor(stateDir) {
|
|
14982
|
-
return
|
|
15038
|
+
return join31(stateDir, "ssh", "id_ed25519");
|
|
14983
15039
|
}
|
|
14984
15040
|
function ensureShipSshKeypair(stateDir) {
|
|
14985
15041
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
14986
15042
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
14987
15043
|
if (!existsSync29(privateKeyPath)) {
|
|
14988
|
-
|
|
15044
|
+
mkdirSync18(dirname19(privateKeyPath), { recursive: true });
|
|
14989
15045
|
try {
|
|
14990
15046
|
execFileSync6("ssh-keygen", [
|
|
14991
15047
|
"-t",
|
|
@@ -15008,7 +15064,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
15008
15064
|
if (!existsSync29(publicKeyPath)) {
|
|
15009
15065
|
throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
|
|
15010
15066
|
}
|
|
15011
|
-
const publicKey =
|
|
15067
|
+
const publicKey = readFileSync24(publicKeyPath, "utf8").trim();
|
|
15012
15068
|
return { privateKeyPath, publicKeyPath, publicKey };
|
|
15013
15069
|
}
|
|
15014
15070
|
function sshKeygenAvailable() {
|
|
@@ -15022,7 +15078,7 @@ function sshKeygenAvailable() {
|
|
|
15022
15078
|
|
|
15023
15079
|
// src/passenger-containers.ts
|
|
15024
15080
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
15025
|
-
import { createHash as
|
|
15081
|
+
import { createHash as createHash6 } from "crypto";
|
|
15026
15082
|
var realExec = (cmd, args) => {
|
|
15027
15083
|
try {
|
|
15028
15084
|
return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -15059,12 +15115,12 @@ function containerNameFor(workspaceId) {
|
|
|
15059
15115
|
return `crew-passenger-${workspaceId}`;
|
|
15060
15116
|
}
|
|
15061
15117
|
function portFor(workspaceId, base = 28765, span = 1e3) {
|
|
15062
|
-
const digest =
|
|
15118
|
+
const digest = createHash6("sha256").update(workspaceId).digest();
|
|
15063
15119
|
return base + digest.readUInt32BE(0) % span;
|
|
15064
15120
|
}
|
|
15065
15121
|
function mountsDigest(mounts, image) {
|
|
15066
15122
|
const sorted2 = [...mounts].sort((a, b) => a.containerPath.localeCompare(b.containerPath));
|
|
15067
|
-
return
|
|
15123
|
+
return createHash6("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
|
|
15068
15124
|
}
|
|
15069
15125
|
function planContainers(cfg) {
|
|
15070
15126
|
const byWorkspace = /* @__PURE__ */ new Map();
|
|
@@ -15224,9 +15280,9 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
15224
15280
|
|
|
15225
15281
|
// src/tunnel.ts
|
|
15226
15282
|
import { spawn as spawn5 } from "child_process";
|
|
15227
|
-
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";
|
|
15228
15284
|
import { randomInt } from "crypto";
|
|
15229
|
-
import { join as
|
|
15285
|
+
import { join as join32 } from "path";
|
|
15230
15286
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
15231
15287
|
var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
|
|
15232
15288
|
var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -15293,21 +15349,21 @@ function startTunnel(opts) {
|
|
|
15293
15349
|
};
|
|
15294
15350
|
}
|
|
15295
15351
|
function tunnelStatePath(stateDir, workspaceId) {
|
|
15296
|
-
return
|
|
15352
|
+
return join32(stateDir, "passengers", `${workspaceId}.json`);
|
|
15297
15353
|
}
|
|
15298
15354
|
function readPersistedTunnel(stateDir, workspaceId) {
|
|
15299
15355
|
const path = tunnelStatePath(stateDir, workspaceId);
|
|
15300
15356
|
if (!existsSync30(path)) return void 0;
|
|
15301
15357
|
try {
|
|
15302
|
-
return JSON.parse(
|
|
15358
|
+
return JSON.parse(readFileSync25(path, "utf8"));
|
|
15303
15359
|
} catch {
|
|
15304
15360
|
return void 0;
|
|
15305
15361
|
}
|
|
15306
15362
|
}
|
|
15307
15363
|
function writePersistedTunnel(stateDir, state) {
|
|
15308
15364
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
15309
|
-
|
|
15310
|
-
|
|
15365
|
+
mkdirSync19(join32(stateDir, "passengers"), { recursive: true });
|
|
15366
|
+
writeFileSync20(path, `${JSON.stringify(state, null, 2)}
|
|
15311
15367
|
`);
|
|
15312
15368
|
}
|
|
15313
15369
|
function updatePersistedTunnel(stateDir, workspaceId, patch2) {
|
|
@@ -15404,13 +15460,13 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
|
|
|
15404
15460
|
}
|
|
15405
15461
|
}
|
|
15406
15462
|
function readAllPersistedWorkspaceIds(stateDir) {
|
|
15407
|
-
const dir =
|
|
15463
|
+
const dir = join32(stateDir, "passengers");
|
|
15408
15464
|
if (!existsSync30(dir)) return [];
|
|
15409
15465
|
return readdirSync10(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
|
|
15410
15466
|
}
|
|
15411
15467
|
|
|
15412
15468
|
// src/mcp-endpoint-registry.ts
|
|
15413
|
-
import { createHash as
|
|
15469
|
+
import { createHash as createHash7 } from "crypto";
|
|
15414
15470
|
function endpointsUrl(api, workspaceId, id) {
|
|
15415
15471
|
const base = `${api.baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/mcp-endpoints`;
|
|
15416
15472
|
return id ? `${base}/${id}` : base;
|
|
@@ -15478,7 +15534,7 @@ async function probeEndpointAccess(api, workspaceId) {
|
|
|
15478
15534
|
}
|
|
15479
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";
|
|
15480
15536
|
function registrationKey(url, mcpSecret) {
|
|
15481
|
-
return
|
|
15537
|
+
return createHash7("sha256").update(`${url}
|
|
15482
15538
|
${mcpSecret ?? ""}`).digest("hex").slice(0, 16);
|
|
15483
15539
|
}
|
|
15484
15540
|
async function reconcileEndpoint(workspaceId, d) {
|
|
@@ -15526,8 +15582,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
15526
15582
|
import { createServer as createHttpServer } from "http";
|
|
15527
15583
|
import { spawn as spawn6 } from "child_process";
|
|
15528
15584
|
import { randomBytes } from "crypto";
|
|
15529
|
-
import { existsSync as existsSync31, mkdirSync as
|
|
15530
|
-
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";
|
|
15531
15587
|
|
|
15532
15588
|
// src/secret-compare.ts
|
|
15533
15589
|
import { timingSafeEqual } from "crypto";
|
|
@@ -15616,21 +15672,21 @@ var defaultSpawn2 = (cmd, args, env) => {
|
|
|
15616
15672
|
return child;
|
|
15617
15673
|
};
|
|
15618
15674
|
function syncDaemonStatePath(stateDir, workspaceId) {
|
|
15619
|
-
return
|
|
15675
|
+
return join33(stateDir, "passengers", `${workspaceId}-sync.json`);
|
|
15620
15676
|
}
|
|
15621
15677
|
function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
15622
15678
|
const path = syncDaemonStatePath(stateDir, workspaceId);
|
|
15623
15679
|
if (!existsSync31(path)) return void 0;
|
|
15624
15680
|
try {
|
|
15625
|
-
return JSON.parse(
|
|
15681
|
+
return JSON.parse(readFileSync26(path, "utf8"));
|
|
15626
15682
|
} catch {
|
|
15627
15683
|
return void 0;
|
|
15628
15684
|
}
|
|
15629
15685
|
}
|
|
15630
15686
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
15631
15687
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
15632
|
-
|
|
15633
|
-
|
|
15688
|
+
mkdirSync20(join33(stateDir, "passengers"), { recursive: true });
|
|
15689
|
+
writeFileSync21(path, `${JSON.stringify(state, null, 2)}
|
|
15634
15690
|
`);
|
|
15635
15691
|
}
|
|
15636
15692
|
function removePersistedSyncDaemon(stateDir, workspaceId) {
|
|
@@ -15650,7 +15706,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
|
|
|
15650
15706
|
}
|
|
15651
15707
|
}
|
|
15652
15708
|
function readAllPersistedSyncWorkspaceIds(stateDir) {
|
|
15653
|
-
const dir =
|
|
15709
|
+
const dir = join33(stateDir, "passengers");
|
|
15654
15710
|
if (!existsSync31(dir)) return [];
|
|
15655
15711
|
const suffix = "-sync.json";
|
|
15656
15712
|
return readdirSync11(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
|
|
@@ -15739,12 +15795,12 @@ function describeCause(err) {
|
|
|
15739
15795
|
}
|
|
15740
15796
|
|
|
15741
15797
|
// src/daemon.ts
|
|
15742
|
-
import { existsSync as existsSync33, mkdirSync as
|
|
15743
|
-
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";
|
|
15744
15800
|
|
|
15745
15801
|
// src/interrupted.ts
|
|
15746
|
-
import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as
|
|
15747
|
-
import { join as
|
|
15802
|
+
import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync27 } from "fs";
|
|
15803
|
+
import { join as join34 } from "path";
|
|
15748
15804
|
var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
|
|
15749
15805
|
function interruptedComment(b) {
|
|
15750
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.";
|
|
@@ -15765,19 +15821,19 @@ function runFinished(streamPath) {
|
|
|
15765
15821
|
const path = `${streamPath}.meta.json`;
|
|
15766
15822
|
if (!existsSync32(path)) return false;
|
|
15767
15823
|
try {
|
|
15768
|
-
return JSON.parse(
|
|
15824
|
+
return JSON.parse(readFileSync27(path, "utf8")).running !== true;
|
|
15769
15825
|
} catch {
|
|
15770
15826
|
return true;
|
|
15771
15827
|
}
|
|
15772
15828
|
}
|
|
15773
15829
|
function orphanedStreams(o) {
|
|
15774
|
-
const dir =
|
|
15830
|
+
const dir = join34(o.stateDir, "streams");
|
|
15775
15831
|
if (!existsSync32(dir)) return [];
|
|
15776
15832
|
const ownerAlive = o.isOwnerAlive ?? pidAlive;
|
|
15777
15833
|
const out = [];
|
|
15778
15834
|
for (const name of readdirSync12(dir)) {
|
|
15779
15835
|
if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
|
|
15780
|
-
const streamPath =
|
|
15836
|
+
const streamPath = join34(dir, name);
|
|
15781
15837
|
if (runFinished(streamPath)) continue;
|
|
15782
15838
|
const sidecar = readRunSidecar(streamPath);
|
|
15783
15839
|
if (!sidecar || sidecar.route !== o.routeLabel) continue;
|
|
@@ -15897,7 +15953,7 @@ function latestMtime(path) {
|
|
|
15897
15953
|
if (!stat.isDirectory()) return 0;
|
|
15898
15954
|
let latest = stat.mtimeMs;
|
|
15899
15955
|
for (const entry of readdirSync13(path, { withFileTypes: true })) {
|
|
15900
|
-
latest = Math.max(latest, latestMtime(
|
|
15956
|
+
latest = Math.max(latest, latestMtime(join35(path, entry.name)));
|
|
15901
15957
|
}
|
|
15902
15958
|
return latest;
|
|
15903
15959
|
}
|
|
@@ -15909,20 +15965,20 @@ function makeStaleChecker(paths, opts = {}) {
|
|
|
15909
15965
|
return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync33(opts.reloadSentinel);
|
|
15910
15966
|
}
|
|
15911
15967
|
function reloadSentinelFor(stateDir) {
|
|
15912
|
-
return
|
|
15968
|
+
return join35(stateDir, "reload-requested");
|
|
15913
15969
|
}
|
|
15914
15970
|
function daemonStampFor(stateDir) {
|
|
15915
|
-
return
|
|
15971
|
+
return join35(stateDir, "daemon.started");
|
|
15916
15972
|
}
|
|
15917
15973
|
function noteDaemonStarted(stateDir, now = Date.now()) {
|
|
15918
|
-
|
|
15974
|
+
mkdirSync21(stateDir, { recursive: true });
|
|
15919
15975
|
rmSync14(reloadSentinelFor(stateDir), { force: true });
|
|
15920
|
-
|
|
15976
|
+
writeFileSync22(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
|
|
15921
15977
|
`);
|
|
15922
15978
|
}
|
|
15923
15979
|
function readDaemonStarted(stateDir) {
|
|
15924
15980
|
try {
|
|
15925
|
-
const v = JSON.parse(
|
|
15981
|
+
const v = JSON.parse(readFileSync28(daemonStampFor(stateDir), "utf8")).startedAt;
|
|
15926
15982
|
return typeof v === "number" ? v : null;
|
|
15927
15983
|
} catch {
|
|
15928
15984
|
return null;
|
|
@@ -16943,7 +16999,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
16943
16999
|
}
|
|
16944
17000
|
if (host === "linux" && hasSystemd) {
|
|
16945
17001
|
const label = labelFor(CREW_HOME, "run");
|
|
16946
|
-
const staleTimer =
|
|
17002
|
+
const staleTimer = join36(homedir9(), ".config", "systemd", "user", `${label}.timer`);
|
|
16947
17003
|
if (existsSync34(staleTimer)) {
|
|
16948
17004
|
process.stdout.write(
|
|
16949
17005
|
`removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
|
|
@@ -17005,7 +17061,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
17005
17061
|
for (const a of plan.actions) {
|
|
17006
17062
|
if (a.kind === "enable") {
|
|
17007
17063
|
if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
|
|
17008
|
-
|
|
17064
|
+
writeFileSync23(cfg.configFile, planEnable(readFileSync29(cfg.configFile, "utf8"), a.route));
|
|
17009
17065
|
enabledSound.add(a.route);
|
|
17010
17066
|
changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
|
|
17011
17067
|
} else {
|
|
@@ -18014,7 +18070,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18014
18070
|
process.exit(1);
|
|
18015
18071
|
}
|
|
18016
18072
|
requireArmed("run the daemon loop");
|
|
18017
|
-
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")];
|
|
18018
18074
|
const configPaths = daemonConfigPaths(cfg);
|
|
18019
18075
|
watchPaths.push(...configPaths);
|
|
18020
18076
|
noteDaemonStarted(cfg.ship.stateDir);
|
|
@@ -18233,10 +18289,10 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18233
18289
|
`);
|
|
18234
18290
|
if (!dryRun) {
|
|
18235
18291
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
18236
|
-
const raw = existsSync34(resolvedPath) ? JSON.parse(
|
|
18292
|
+
const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync29(resolvedPath, "utf8")) : {};
|
|
18237
18293
|
raw.agentSkills = result.agentSkills;
|
|
18238
|
-
|
|
18239
|
-
|
|
18294
|
+
mkdirSync22(dirOf(resolvedPath), { recursive: true });
|
|
18295
|
+
writeFileSync23(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
18240
18296
|
`);
|
|
18241
18297
|
}
|
|
18242
18298
|
if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
|
|
@@ -18259,7 +18315,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18259
18315
|
throw e;
|
|
18260
18316
|
}
|
|
18261
18317
|
const oldName = cfg.ship.name;
|
|
18262
|
-
|
|
18318
|
+
writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), newName));
|
|
18263
18319
|
cfg.ship.name = newName;
|
|
18264
18320
|
let renameMachineId;
|
|
18265
18321
|
try {
|
|
@@ -18306,7 +18362,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18306
18362
|
process.exit(2);
|
|
18307
18363
|
};
|
|
18308
18364
|
if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
|
|
18309
|
-
const configText =
|
|
18365
|
+
const configText = readFileSync29(cfg.configFile, "utf8");
|
|
18310
18366
|
if (sub === "hooks") {
|
|
18311
18367
|
try {
|
|
18312
18368
|
const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
|
|
@@ -18320,7 +18376,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18320
18376
|
}
|
|
18321
18377
|
}, read: (r) => {
|
|
18322
18378
|
try {
|
|
18323
|
-
return
|
|
18379
|
+
return readFileSync29(resolvePath(dir, r), "utf8");
|
|
18324
18380
|
} catch {
|
|
18325
18381
|
return void 0;
|
|
18326
18382
|
}
|
|
@@ -18358,13 +18414,13 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18358
18414
|
if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
|
|
18359
18415
|
if (write === "route") {
|
|
18360
18416
|
const plan = planRouteHooks(configText, a.route, a.name, picked.hooks);
|
|
18361
|
-
|
|
18417
|
+
writeFileSync23(cfg.configFile, plan.text);
|
|
18362
18418
|
written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
|
|
18363
18419
|
} else if (write === "repo") {
|
|
18364
18420
|
const have = existingRepoConfig(files);
|
|
18365
18421
|
if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
|
|
18366
18422
|
const file = resolvePath(dir, ".crew.yaml");
|
|
18367
|
-
|
|
18423
|
+
writeFileSync23(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
|
|
18368
18424
|
written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [] };
|
|
18369
18425
|
}
|
|
18370
18426
|
if (flag("json")) {
|
|
@@ -18447,7 +18503,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18447
18503
|
let resolvedRemotes;
|
|
18448
18504
|
if (existsSync34(resolvedFile)) {
|
|
18449
18505
|
try {
|
|
18450
|
-
resolvedRemotes = JSON.parse(
|
|
18506
|
+
resolvedRemotes = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
18451
18507
|
} catch {
|
|
18452
18508
|
}
|
|
18453
18509
|
}
|
|
@@ -18468,7 +18524,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18468
18524
|
isCheckout,
|
|
18469
18525
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
18470
18526
|
try {
|
|
18471
|
-
return Object.keys(parseRepoConfig(
|
|
18527
|
+
return Object.keys(parseRepoConfig(readFileSync29(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
18472
18528
|
} catch {
|
|
18473
18529
|
return false;
|
|
18474
18530
|
}
|
|
@@ -18517,7 +18573,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18517
18573
|
throw new ReposError(`${e.message}${authHint}
|
|
18518
18574
|
crew.yaml was not changed`);
|
|
18519
18575
|
}
|
|
18520
|
-
|
|
18576
|
+
writeFileSync23(cfg.configFile, plan2.text);
|
|
18521
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:
|
|
18522
18578
|
`);
|
|
18523
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}`);
|
|
@@ -18577,14 +18633,14 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18577
18633
|
process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
|
|
18578
18634
|
`);
|
|
18579
18635
|
if (existsSync34(resolvedFile)) {
|
|
18580
|
-
const raw = JSON.parse(
|
|
18636
|
+
const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
18581
18637
|
raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
|
|
18582
18638
|
raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
|
|
18583
|
-
|
|
18639
|
+
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
18584
18640
|
`);
|
|
18585
18641
|
}
|
|
18586
18642
|
}
|
|
18587
|
-
|
|
18643
|
+
writeFileSync23(cfg.configFile, plan.text);
|
|
18588
18644
|
process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
|
|
18589
18645
|
`);
|
|
18590
18646
|
} catch (e) {
|
|
@@ -18935,7 +18991,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18935
18991
|
process.exit(2);
|
|
18936
18992
|
}
|
|
18937
18993
|
if (found.shipNameAdopted && !dryRun) {
|
|
18938
|
-
|
|
18994
|
+
writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), found.shipNameAdopted));
|
|
18939
18995
|
cfg.ship.name = found.shipNameAdopted;
|
|
18940
18996
|
}
|
|
18941
18997
|
if (found.shipRow) {
|
|
@@ -18981,7 +19037,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18981
19037
|
const hadPreviousFile = existsSync34(resolvedPath);
|
|
18982
19038
|
if (hadPreviousFile) {
|
|
18983
19039
|
try {
|
|
18984
|
-
previous = JSON.parse(
|
|
19040
|
+
previous = JSON.parse(readFileSync29(resolvedPath, "utf8"));
|
|
18985
19041
|
} catch {
|
|
18986
19042
|
}
|
|
18987
19043
|
}
|
|
@@ -19101,10 +19157,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19101
19157
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
19102
19158
|
`);
|
|
19103
19159
|
} else {
|
|
19104
|
-
|
|
19160
|
+
mkdirSync22(dirname20(resolvedPath), { recursive: true });
|
|
19105
19161
|
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
19106
19162
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
19107
|
-
|
|
19163
|
+
writeFileSync23(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
19108
19164
|
`);
|
|
19109
19165
|
renameSync3(tmpPath, resolvedPath);
|
|
19110
19166
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
@@ -19146,8 +19202,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19146
19202
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
19147
19203
|
`);
|
|
19148
19204
|
} else {
|
|
19149
|
-
|
|
19150
|
-
|
|
19205
|
+
mkdirSync22(dirname20(apiKeyFile), { recursive: true });
|
|
19206
|
+
writeFileSync23(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
19151
19207
|
`, { mode: 384 });
|
|
19152
19208
|
process.stderr.write(`API key written to ${apiKeyFile}
|
|
19153
19209
|
`);
|
|
@@ -19183,8 +19239,11 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19183
19239
|
const id = `${tc.harness}\0${tc.bin}`;
|
|
19184
19240
|
if (seen.has(id)) continue;
|
|
19185
19241
|
seen.add(id);
|
|
19186
|
-
const
|
|
19187
|
-
|
|
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 } : {} });
|
|
19188
19247
|
process.stderr.write(`${line}
|
|
19189
19248
|
`);
|
|
19190
19249
|
}
|
|
@@ -19835,7 +19894,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19835
19894
|
};
|
|
19836
19895
|
const droppedRoutes = [];
|
|
19837
19896
|
try {
|
|
19838
|
-
const rawCfg = parseYaml2(
|
|
19897
|
+
const rawCfg = parseYaml2(readFileSync29(cfg.configFile, "utf8"));
|
|
19839
19898
|
for (const r of rawCfg?.routes ?? []) {
|
|
19840
19899
|
if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
|
|
19841
19900
|
try {
|
|
@@ -19877,8 +19936,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19877
19936
|
process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
|
|
19878
19937
|
break;
|
|
19879
19938
|
}
|
|
19880
|
-
|
|
19881
|
-
|
|
19939
|
+
mkdirSync22(cfg.ship.stateDir, { recursive: true });
|
|
19940
|
+
writeFileSync23(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
19882
19941
|
`);
|
|
19883
19942
|
process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
|
|
19884
19943
|
if (flag("now") && control?.stopCommand && control.startCommand) {
|
|
@@ -19929,7 +19988,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19929
19988
|
}
|
|
19930
19989
|
case "log": {
|
|
19931
19990
|
try {
|
|
19932
|
-
const text =
|
|
19991
|
+
const text = readFileSync29(cfg.ship.logFile, "utf8").split("\n");
|
|
19933
19992
|
process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
|
|
19934
19993
|
`);
|
|
19935
19994
|
} catch {
|
|
@@ -19952,7 +20011,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19952
20011
|
};
|
|
19953
20012
|
if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
|
|
19954
20013
|
const tracker = new Tracker(route, cfg.ship);
|
|
19955
|
-
const configText =
|
|
20014
|
+
const configText = readFileSync29(cfg.configFile, "utf8");
|
|
19956
20015
|
const cfgBase = dirname20(cfg.configFile);
|
|
19957
20016
|
const configured = configuredRepos(configText, route.route, cfgBase);
|
|
19958
20017
|
const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
|
|
@@ -19981,7 +20040,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19981
20040
|
}
|
|
19982
20041
|
if (configured.some((r) => r.dir === dir)) {
|
|
19983
20042
|
try {
|
|
19984
|
-
|
|
20043
|
+
writeFileSync23(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
|
|
19985
20044
|
} catch (e) {
|
|
19986
20045
|
if (e instanceof ReposError) fail(e.message);
|
|
19987
20046
|
throw e;
|
|
@@ -19990,28 +20049,28 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19990
20049
|
if (row) {
|
|
19991
20050
|
await tracker.deleteRepoRow(row.id);
|
|
19992
20051
|
if (existsSync34(resolvedFile)) {
|
|
19993
|
-
const raw = JSON.parse(
|
|
20052
|
+
const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
19994
20053
|
if (raw.repoNames) delete raw.repoNames[row.id];
|
|
19995
20054
|
if (raw.repoRemotes) delete raw.repoRemotes[row.id];
|
|
19996
|
-
|
|
20055
|
+
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
19997
20056
|
`);
|
|
19998
20057
|
}
|
|
19999
20058
|
}
|
|
20000
|
-
if (existsSync34(
|
|
20059
|
+
if (existsSync34(join36(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
|
|
20001
20060
|
say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
|
|
20002
20061
|
break;
|
|
20003
20062
|
}
|
|
20004
20063
|
const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
|
|
20005
|
-
const fresh = !existsSync34(
|
|
20064
|
+
const fresh = !existsSync34(join36(dir, ".git"));
|
|
20006
20065
|
if (existsSync34(dir) && fresh && readdirSync14(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
|
|
20007
20066
|
if (dryRun) {
|
|
20008
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`);
|
|
20009
20068
|
break;
|
|
20010
20069
|
}
|
|
20011
20070
|
if (fresh) {
|
|
20012
|
-
|
|
20013
|
-
|
|
20014
|
-
|
|
20071
|
+
mkdirSync22(dir, { recursive: true });
|
|
20072
|
+
writeFileSync23(join36(dir, "README.md"), SAMPLE_README);
|
|
20073
|
+
writeFileSync23(join36(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
|
|
20015
20074
|
const g = (...a) => {
|
|
20016
20075
|
const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
|
|
20017
20076
|
if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
|
|
@@ -20030,16 +20089,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20030
20089
|
});
|
|
20031
20090
|
repoId = (await tracker.createRepoRow(payload)).id;
|
|
20032
20091
|
if (existsSync34(resolvedFile)) {
|
|
20033
|
-
const raw = JSON.parse(
|
|
20092
|
+
const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
|
|
20034
20093
|
raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
|
|
20035
20094
|
raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
|
|
20036
|
-
|
|
20095
|
+
writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
20037
20096
|
`);
|
|
20038
20097
|
}
|
|
20039
20098
|
}
|
|
20040
20099
|
if (!configured.some((r) => r.dir === dir)) {
|
|
20041
20100
|
try {
|
|
20042
|
-
|
|
20101
|
+
writeFileSync23(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
|
|
20043
20102
|
} catch (e) {
|
|
20044
20103
|
if (e instanceof ReposError) fail(e.message);
|
|
20045
20104
|
throw e;
|
|
@@ -20393,7 +20452,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
20393
20452
|
const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
|
|
20394
20453
|
const abandons = unitAbandonsAgents(unitPlan, (p) => {
|
|
20395
20454
|
try {
|
|
20396
|
-
return
|
|
20455
|
+
return readFileSync29(p, "utf8");
|
|
20397
20456
|
} catch {
|
|
20398
20457
|
return null;
|
|
20399
20458
|
}
|