@tablation/crew 0.72.2 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +445 -352
  3. 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 mkdirSync9, mkdtempSync, readFileSync as readFileSync15, rmSync as rmSync9, writeFileSync as writeFileSync10 } from "fs";
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 join18 } from "path";
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 readFileSync14, existsSync as existsSync18, statSync as statSync5, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
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 join17, dirname as dirname7, basename as basename7 } from "path";
143
- import { createHash as createHash2 } from "crypto";
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 readFileSync6, existsSync as existsSync7 } from "fs";
147
- import { resolve, dirname as dirname3, isAbsolute, join as join7, basename as basename3 } from "path";
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 writeFileSync3 } from "fs";
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 join5 } from "path";
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 readFileSync2 } from "fs";
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(readFileSync2(join2(home, ".claude.json"), "utf8"));
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}`) return { state: "different", detail: `it is registered with a different key than this route's` };
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
- if (st.state === "different" && !o.replace) {
392
- return `mcp: left Claude Code's "${MCP_SERVER_NAME}" server alone \u2014 ${st.detail}. Re-run with --mcp replace to overwrite it.`;
393
- }
394
- if (o.dryRun) return `(dry run) would register the ${MCP_SERVER_NAME} MCP server with Claude Code \u2192 ${o.url}`;
430
+ if (st.state === "no-claude") return { line: `mcp: \`${o.bin}\` not found, so Claude Code was not configured. To do it by hand: ${manual}` };
431
+ if (st.state === "registered") return { line: `mcp: ${MCP_SERVER_NAME} already registered with Claude Code \u2192 ${st.url}`, action: "registered" };
432
+ const crewWrote = st.state === "different" ? st.crewWrote : void 0;
433
+ const ours = crewWrote !== void 0;
434
+ if (st.state === "different" && !o.replace && !ours) {
435
+ return { line: `mcp: left Claude Code's "${MCP_SERVER_NAME}" server alone \u2014 ${st.detail}. Re-run with --mcp replace to overwrite it.`, action: "kept" };
436
+ }
437
+ if (o.dryRun) return { line: `(dry run) would register the ${MCP_SERVER_NAME} MCP server with Claude Code \u2192 ${o.url}` };
395
438
  if (st.state === "different") {
396
439
  runClaude(o.bin, ["mcp", "remove", MCP_SERVER_NAME, "--scope", "user"]);
397
440
  }
@@ -408,9 +451,21 @@ function ensureClaudeMcp(o) {
408
451
  "user"
409
452
  ]);
410
453
  if (add.status !== 0) {
411
- return `mcp: \`claude mcp add\` failed (${redact(add.stderr.trim(), o.key) || `exit ${add.status}`}). To do it by hand: ${manual}`;
454
+ return { line: `mcp: \`claude mcp add\` failed (${redact(add.stderr.trim(), o.key) || `exit ${add.status}`}). To do it by hand: ${manual}` };
412
455
  }
413
- return `mcp: registered the ${MCP_SERVER_NAME} MCP server with Claude Code \u2192 ${o.url} (user scope, ~/.claude.json)`;
456
+ if (o.stateDir) {
457
+ try {
458
+ recordMcpRegistration(o.stateDir, { harness: "claude", scope: "user", name: MCP_SERVER_NAME, url: o.url, keyFingerprint: keyFingerprint(o.key) });
459
+ } catch {
460
+ }
461
+ }
462
+ if (crewWrote !== void 0) {
463
+ return { line: `mcp: updated the key crew registered on ${crewWrote.slice(0, 10)} \u2192 ${o.url}`, action: "replaced" };
464
+ }
465
+ return {
466
+ line: `mcp: ${st.state === "different" ? "replaced" : "registered"} the ${MCP_SERVER_NAME} MCP server with Claude Code \u2192 ${o.url} (user scope, ~/.claude.json)`,
467
+ action: st.state === "different" ? "replaced" : "registered"
468
+ };
414
469
  }
415
470
  function removeClaudeMcp(o) {
416
471
  const got = runClaude(o.bin, ["mcp", "get", MCP_SERVER_NAME]);
@@ -422,8 +477,8 @@ function removeClaudeMcp(o) {
422
477
  }
423
478
 
424
479
  // src/launcher.ts
425
- import { basename, join as join3 } from "path";
426
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
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 join3(home, ".omlx", "bin", "omlx");
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 = join3(home, ".omlx", "settings.json");
489
- return existsSync3(p) ? parseOmlxSettings(readFileSync3(p, "utf8")) : void 0;
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 join4 } from "path";
623
- import { rmSync as rmSync3, writeFileSync as writeFileSync2 } from "fs";
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 mkdirSync2, readFileSync as readFileSync4, writeFileSync, rmSync as rmSync2 } from "fs";
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) ? readFileSync4(path, "utf8") : void 0;
723
+ return existsSync4(path) ? readFileSync5(path, "utf8") : void 0;
668
724
  }
669
725
  function writeFileMkdir(path, text) {
670
- mkdirSync2(dirname2(path), { recursive: true });
671
- writeFileSync(path, text);
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 writeFileSync(path, after);
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: join4(home, ".codex", "AGENTS.md"),
815
- config: join4(home, ".codex", "config.toml")
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 writeFileSync2(p.config, after);
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: join5(home, ".gemini", "GEMINI.md"),
963
- config: join5(home, ".gemini", "settings.json")
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 writeFileSync3(p.config, `${JSON.stringify(next, null, 2)}
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" ? join5(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join5(home, "AppData", "Roaming") : join5(home, ".config");
1063
- const all = VSCODE_FLAVOURS.map((f) => join5(root, f, "User"));
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 writeFileSync3(path, after);
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 = join5(dir, "mcp.json");
1112
- const instrPath = join5(dir, "prompts", "crew-pair.instructions.md");
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 = join5(dir, "prompts", "crew-pair.instructions.md");
1141
- const mcpPath = join5(dir, "mcp.json");
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 = join5(dir, "mcp.json");
1157
- const hadBlock = removeCopilotInstructions(join5(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
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 writeFileSync3(mcpPath, `${JSON.stringify(next, null, 2)}
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 readFileSync5, existsSync as existsSync6 } from "fs";
1457
- import { join as join6, basename as basename2 } from "path";
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 = join6(dir, name);
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(readFileSync5(file, "utf8"), file);
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 join7(crewHome, "prompts", "default");
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 join7(crewHome, "prompts", promptSet);
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(readFileSync6(join7(crewHome, "package.json"), "utf8"));
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 ? [join7(xdg, "crew", "crew.yaml")] : [],
2272
- join7(homedir5(), ".config", "crew", "crew.yaml"),
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 = () => join7(process.env.XDG_CONFIG_HOME ?? join7(homedir5(), ".config"), "crew", "crew.yaml");
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 join7(stateDir, "resolved", workspace, `${project}.json`);
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 join7(stateDir, "keys", workspace, `${project}.env`);
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(readFileSync6(path, "utf8"));
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 ${join7(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
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 ?? readFileSync6(file, "utf8"));
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 ?? join7(tmpdir(), "crew.log"), base),
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 ? join7(reposBasePath, splitRoute(route)[0]) : missing.req(void 0, `${where}.dir (or ${where}.repos)`) ?? "";
2648
+ const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join8(reposBasePath, splitRoute(route)[0]) : missing.req(void 0, `${where}.dir (or ${where}.repos)`) ?? "";
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 readFileSync6(r.apiKeyFile, "utf8").split("\n")) {
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 (readFileSync6(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
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 join7(basePath, workspace, repoName);
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 join7(basePath, workspace, ".passenger-checkouts", repoName);
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 mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "fs";
3129
- import { join as join8 } from "path";
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) => join8(stateDir, "pending-agent-logs");
3252
+ var queueDir = (stateDir) => join9(stateDir, "pending-agent-logs");
3197
3253
  function queueAgentRun(stateDir, name, q) {
3198
- mkdirSync3(queueDir(stateDir), { recursive: true });
3199
- writeFileSync4(join8(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
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 = join8(dir, f);
3262
+ const path = join9(dir, f);
3207
3263
  let q;
3208
3264
  try {
3209
- q = JSON.parse(readFileSync7(path, "utf8"));
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
- writeFileSync4(path, JSON.stringify({
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 mkdirSync7, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "fs";
3410
- import { join as join14 } from "path";
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 mkdirSync4,
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 readFileSync8,
3423
- writeFileSync as writeFileSync5,
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 join9 } from "path";
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 = join9(tmp, name);
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(join9(root, "checkout"))) continue;
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
- mkdirSync4(dirname4(dir), { recursive: true });
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
- mkdirSync4(dirname4(lockPath), { recursive: true });
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) && readFileSync8(marker, "utf8").split("\n")[0] === url) {
3930
- return { action: "clone-failed", detail: readFileSync8(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
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
- writeFileSync5(marker, `${url}
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 join10, extname } from "path";
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 mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, realpathSync } from "fs";
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, join10(crewHome, "bin", "crew")];
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 = createHash("sha1").update(real).digest("hex").slice(0, 8);
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) => join10(dir, bin));
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", join10(homedir6(), ".docker", "bin")].join(":");
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 = join10(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
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 = join10(homedir6(), ".config", "systemd", "user");
4486
- const servicePath = join10(unitDir, `${label}.service`);
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 = join10(unitDir, `${label}.timer`);
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 = join10(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
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 = join10(homedir6(), ".config", "systemd", "user");
4666
+ const unitDir = join11(homedir6(), ".config", "systemd", "user");
4611
4667
  if (job === "run") {
4612
4668
  return {
4613
4669
  mechanism: "systemd",
4614
- unitPaths: [join10(unitDir, `${label}.service`)],
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: [join10(unitDir, `${label}.service`), join10(unitDir, `${label}.timer`)],
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 = join10(homedir6(), "Library", "LaunchAgents", `${label}.plist`);
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
- mkdirSync5(dirname5(path), { recursive: true });
4680
- writeFileSync6(path, plan.unitContent[path]);
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 join10(homedir6(), "Library", "LaunchAgents");
4809
+ return join11(homedir6(), "Library", "LaunchAgents");
4754
4810
  }
4755
4811
  function xmlUnescape(s) {
4756
4812
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/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 = join10(dir, f);
4828
+ const plistPath = join11(dir, f);
4773
4829
  let program = null;
4774
4830
  try {
4775
- program = programFromPlist(readFileSync9(plistPath, "utf8"));
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: join10(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
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 readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync as rmSync7, statSync as statSync4 } from "fs";
5135
- import { join as join11 } from "path";
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
- mkdirSync6(dir, { recursive: true });
5197
+ mkdirSync7(dir, { recursive: true });
5142
5198
  }
5143
5199
  path(...parts) {
5144
- return join11(this.dir, ...parts);
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 = readFileSync10(this.path(".poll-watermark"), "utf8").trim();
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
- writeFileSync7(this.path(".poll-watermark"), `${at}
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
- writeFileSync7(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
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) writeFileSync7(this.path(".release-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(readFileSync10(file, "utf8").trim(), 10);
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
- writeFileSync7(file, `${process.pid}
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 readFileSync10(this.path(f), "utf8").trim() || null;
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
- writeFileSync7(this.path(blocked), `${n}
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
- writeFileSync7(this.path(failed), `${sha}
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
- writeFileSync7(this.path(gateFailed), `${sha} ${next}
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 readFileSync10(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
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
- writeFileSync7(this.path(heldReported), `${signature}
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
- writeFileSync7(this.path(gateReported), `${sha}
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 readFileSync10(this.path(f), "utf8").trim() || null;
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
- writeFileSync7(this.path(streakFile), `${this.readInt(streakFile) + 1}
5511
+ writeFileSync8(this.path(streakFile), `${this.readInt(streakFile) + 1}
5456
5512
  `);
5457
5513
  const prior = read(ticketFile);
5458
5514
  if (topTicket && topTicket === prior) {
5459
- writeFileSync7(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
5515
+ writeFileSync8(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
5460
5516
  `);
5461
5517
  } else if (topTicket) {
5462
- writeFileSync7(this.path(ticketFile), `${topTicket}
5518
+ writeFileSync8(this.path(ticketFile), `${topTicket}
5463
5519
  `);
5464
- writeFileSync7(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
5520
+ writeFileSync8(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
5465
5521
  `);
5466
- writeFileSync7(this.path(ticketStreakFile), "1\n");
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(readFileSync10(this.path(file), "utf8"));
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
- writeFileSync7(this.path(file), JSON.stringify(next));
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(readFileSync10(this.path(file), "utf8"));
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
- writeFileSync7(this.path(file), JSON.stringify(next));
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
- writeFileSync7(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
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(readFileSync10(this.path(file), "utf8").trim(), 10) || 0;
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 join13 } from "path";
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 join12 } from "path";
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(join12(parent, name, ".git"))) return true;
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 = join13(dirname6(dir), worktreeForTicket(dir, cfg, t, role));
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) => join14(stateDir, "pending-handoffs");
7030
- var stateDirOfStream = (streamPath) => join14(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
- mkdirSync7(dir, { recursive: true });
7034
- writeFileSync8(join14(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
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(readFileSync11(join14(dir, f), "utf8"));
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(join14(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
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 readFileSync13, readdirSync as readdirSync6, existsSync as existsSync17 } from "fs";
7161
- import { join as join16 } from "path";
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 readFileSync12, existsSync as existsSync16 } from "fs";
7178
- import { join as join15 } from "path";
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 = join15(promptsDir, "personas", `lane-${role}.md`);
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 readFileSync12(briefPath, "utf8");
7195
- const commonPath = join15(promptsDir, "personas", "common.md");
7196
- if (!existsSync16(commonPath)) return readFileSync12(briefPath, "utf8");
7197
- return `${readFileSync12(commonPath, "utf8")}${readFileSync12(briefPath, "utf8")}`;
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 = readFileSync13(path, "utf8");
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 = join16(promptsDir, "skills");
7713
+ const dir = join17(promptsDir, "skills");
7658
7714
  if (!existsSync17(dir)) return [];
7659
- return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join16(dir, f)));
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 = join17(o.route.promptsDir, "personas", `lane-${o.role}.md`);
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 = join17(o.route.promptsDir, "personas", "common.md");
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 = join17(o.stateDir, `digest-${routeLabel}-${o.role}.md`);
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 = readFileSync14(digestPath2, "utf8");
7931
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync15(digestPath2, "utf8");
7876
7932
  }
7877
- const common = usesCommon ? readFileSync14(commonPath, "utf8") : "";
7878
- const brief = readFileSync14(briefPath, "utf8");
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 = createHash2("sha256").update(expanded.text).digest("hex");
7900
- const streamsDir = join17(o.stateDir, "streams");
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: join17(streamsDir, `${base}.jsonl`),
7957
- eventsPath: join17(streamsDir, `${base}.events.jsonl`),
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(readFileSync14(pidPathFor(streamPath), "utf8"));
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
- mkdirSync8(dirname7(path), { recursive: true });
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
- writeFileSync9(`${id.streamPath}.meta.json`, JSON.stringify({
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
- mkdirSync8(dirname7(plan.streamPath), { recursive: true });
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
- writeFileSync9(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
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(join18(tmpdir2(), "crew-selftest-"));
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 fileFor = (stateDir) => join18(stateDir, "selftest.json");
8658
+ var fileFor2 = (stateDir) => join19(stateDir, "selftest.json");
8603
8659
  function readSelftest(stateDir) {
8604
8660
  try {
8605
- const p = fileFor(stateDir);
8606
- return existsSync19(p) ? JSON.parse(readFileSync15(p, "utf8")) : {};
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
- mkdirSync9(stateDir, { recursive: true });
8615
- writeFileSync10(fileFor(stateDir), `${JSON.stringify(all, null, 2)}
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 join35, basename as basename11 } from "path";
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";
@@ -8636,6 +8692,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
8636
8692
  "area",
8637
8693
  "base-url",
8638
8694
  "choose",
8695
+ "clear",
8639
8696
  "dir",
8640
8697
  "extra-path",
8641
8698
  "harness",
@@ -8803,14 +8860,14 @@ function rosterMarkdown(roster, meId) {
8803
8860
  }
8804
8861
 
8805
8862
  // src/connect-resume.ts
8806
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
8807
- import { dirname as dirname8, join as join19 } from "path";
8863
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync17, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
8864
+ import { dirname as dirname8, join as join20 } from "path";
8808
8865
  function lastSessionFile(stateDir) {
8809
- return join19(stateDir, "connect-session.json");
8866
+ return join20(stateDir, "connect-session.json");
8810
8867
  }
8811
8868
  function readPointers(stateDir) {
8812
8869
  try {
8813
- const parsed = JSON.parse(readFileSync16(lastSessionFile(stateDir), "utf8"));
8870
+ const parsed = JSON.parse(readFileSync17(lastSessionFile(stateDir), "utf8"));
8814
8871
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
8815
8872
  const out = {};
8816
8873
  for (const [host, v] of Object.entries(parsed)) {
@@ -8824,8 +8881,8 @@ function readPointers(stateDir) {
8824
8881
  }
8825
8882
  function writePointers(stateDir, pointers) {
8826
8883
  const file = lastSessionFile(stateDir);
8827
- mkdirSync10(dirname8(file), { recursive: true });
8828
- writeFileSync11(file, `${JSON.stringify(pointers, null, 2)}
8884
+ mkdirSync11(dirname8(file), { recursive: true });
8885
+ writeFileSync12(file, `${JSON.stringify(pointers, null, 2)}
8829
8886
  `, { mode: 384 });
8830
8887
  }
8831
8888
  function rememberSession(stateDir, host, workspaceSlug) {
@@ -8881,15 +8938,15 @@ async function signOutSessions(o) {
8881
8938
  }
8882
8939
 
8883
8940
  // src/reset.ts
8884
- import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync17, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync12 } from "fs";
8885
- import { dirname as dirname9, join as join20 } from "path";
8941
+ import { existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as readFileSync18, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync13 } from "fs";
8942
+ import { dirname as dirname9, join as join21 } from "path";
8886
8943
  import { hostFromUrl as hostFromUrl2 } from "@tablation/client";
8887
8944
  function clonedCheckoutsFile(stateDir) {
8888
- return join20(stateDir, "cloned-checkouts.json");
8945
+ return join21(stateDir, "cloned-checkouts.json");
8889
8946
  }
8890
8947
  function readClonedCheckouts(stateDir) {
8891
8948
  try {
8892
- const parsed = JSON.parse(readFileSync17(clonedCheckoutsFile(stateDir), "utf8"));
8949
+ const parsed = JSON.parse(readFileSync18(clonedCheckoutsFile(stateDir), "utf8"));
8893
8950
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
8894
8951
  } catch {
8895
8952
  return [];
@@ -8899,8 +8956,8 @@ function recordClonedCheckout(stateDir, dir) {
8899
8956
  const known = readClonedCheckouts(stateDir);
8900
8957
  if (known.includes(dir)) return;
8901
8958
  const file = clonedCheckoutsFile(stateDir);
8902
- mkdirSync11(dirname9(file), { recursive: true });
8903
- writeFileSync12(file, `${JSON.stringify([...known, dir], null, 2)}
8959
+ mkdirSync12(dirname9(file), { recursive: true });
8960
+ writeFileSync13(file, `${JSON.stringify([...known, dir], null, 2)}
8904
8961
  `);
8905
8962
  }
8906
8963
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -8974,10 +9031,10 @@ function removeCheckout(dir, dryRun, fx) {
8974
9031
  }
8975
9032
  function removeState(stateDir, dryRun) {
8976
9033
  const out = [];
8977
- const targets = [join20(stateDir, "resolved"), join20(stateDir, "keys"), clonedCheckoutsFile(stateDir), join20(stateDir, "connect-session.json"), join20(stateDir, "reload-requested"), join20(stateDir, "daemon.started")];
9034
+ const targets = [join21(stateDir, "resolved"), join21(stateDir, "keys"), clonedCheckoutsFile(stateDir), join21(stateDir, "connect-session.json"), join21(stateDir, "reload-requested"), join21(stateDir, "daemon.started")];
8978
9035
  try {
8979
9036
  for (const name of readdirSync7(stateDir)) {
8980
- if (name.startsWith(".") && statSync6(join20(stateDir, name)).isFile()) targets.push(join20(stateDir, name));
9037
+ if (name.startsWith(".") && statSync6(join21(stateDir, name)).isFile()) targets.push(join21(stateDir, name));
8981
9038
  }
8982
9039
  } catch {
8983
9040
  }
@@ -9762,8 +9819,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
9762
9819
  }
9763
9820
 
9764
9821
  // src/poll.ts
9765
- import { writeFileSync as writeFileSync13 } from "fs";
9766
- import { join as join21 } from "path";
9822
+ import { writeFileSync as writeFileSync14 } from "fs";
9823
+ import { join as join22 } from "path";
9767
9824
 
9768
9825
  // src/stalled.ts
9769
9826
  var STALLED_AFTER_CYCLES = 30;
@@ -10054,7 +10111,7 @@ async function decideCycle(o) {
10054
10111
  }
10055
10112
  return decision;
10056
10113
  }
10057
- var digestPath = (stateDir, route, role) => join21(stateDir, `digest-${route}-${role}.md`);
10114
+ var digestPath = (stateDir, route, role) => join22(stateDir, `digest-${route}-${role}.md`);
10058
10115
  function writeDigest(o, d, role, stateDir) {
10059
10116
  const me = o.route.resolved?.seats[role];
10060
10117
  if (!me) return false;
@@ -10122,7 +10179,7 @@ function writeDigest(o, d, role, stateDir) {
10122
10179
  };
10123
10180
  const text = role === "qa" ? qaDigest(input) : buildingDigest(input);
10124
10181
  const path = digestPath(stateDir, routeSlug(o.route.route), role);
10125
- writeFileSync13(path, text);
10182
+ writeFileSync14(path, text);
10126
10183
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
10127
10184
  data: { role, bytes: Buffer.byteLength(text) }
10128
10185
  });
@@ -10528,8 +10585,8 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
10528
10585
  }
10529
10586
 
10530
10587
  // src/connect-wizard.ts
10531
- import { delimiter, dirname as dirname10, join as join22 } from "path";
10532
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as mkdirSync12, writeFileSync as writeFileSync14 } from "fs";
10588
+ import { delimiter, dirname as dirname10, join as join23 } from "path";
10589
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync21, mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
10533
10590
  import { stringify } from "yaml";
10534
10591
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
10535
10592
  function shouldRunWizard(opts) {
@@ -10551,7 +10608,7 @@ function defaultIsExecutable(p) {
10551
10608
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
10552
10609
  for (const dir of pathEnv.split(delimiter)) {
10553
10610
  if (!dir) continue;
10554
- const candidate = join22(dir, name);
10611
+ const candidate = join23(dir, name);
10555
10612
  if (isExecutable(candidate)) return candidate;
10556
10613
  }
10557
10614
  return void 0;
@@ -10612,9 +10669,9 @@ routes:
10612
10669
  ${routeBlock}`;
10613
10670
  }
10614
10671
  function writeNewConfig(path, text) {
10615
- mkdirSync12(dirname10(path), { recursive: true });
10672
+ mkdirSync13(dirname10(path), { recursive: true });
10616
10673
  try {
10617
- writeFileSync14(path, text, { mode: 384, flag: "wx" });
10674
+ writeFileSync15(path, text, { mode: 384, flag: "wx" });
10618
10675
  return true;
10619
10676
  } catch (e) {
10620
10677
  if (e.code === "EEXIST") return false;
@@ -10648,7 +10705,7 @@ Wrote ${o.configPath}.
10648
10705
  // src/connect-json.ts
10649
10706
  import { accessSync as accessSync3, constants as constants3 } from "fs";
10650
10707
  import { homedir as homedir7 } from "os";
10651
- import { dirname as dirname11, join as join23 } from "path";
10708
+ import { dirname as dirname11, join as join24 } from "path";
10652
10709
  import { delimiter as delimiter2 } from "path";
10653
10710
  var CHOICE_EXIT_CODE = 3;
10654
10711
  function createEmitter(json, write) {
@@ -10687,12 +10744,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
10687
10744
  const dirs = [
10688
10745
  ...pathEnv.split(delimiter2),
10689
10746
  ...(extraPath ?? "").split(delimiter2),
10690
- join23(home, ".local/bin"),
10691
- join23(home, ".claude/local"),
10747
+ join24(home, ".local/bin"),
10748
+ join24(home, ".claude/local"),
10692
10749
  "/opt/homebrew/bin",
10693
10750
  "/usr/local/bin",
10694
- join23(home, ".npm-global/bin"),
10695
- join23(home, ".volta/bin")
10751
+ join24(home, ".npm-global/bin"),
10752
+ join24(home, ".volta/bin")
10696
10753
  ];
10697
10754
  return [...new Set(dirs.filter(Boolean))];
10698
10755
  }
@@ -10728,8 +10785,8 @@ function flagWizardAnswers(f, env) {
10728
10785
  }
10729
10786
 
10730
10787
  // src/release-run.ts
10731
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync15, existsSync as existsSync22, copyFileSync, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync2, rmSync as rmSync12 } from "fs";
10732
- import { dirname as dirname12, join as join24 } from "path";
10788
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync16, existsSync as existsSync22, copyFileSync, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync12 } from "fs";
10789
+ import { dirname as dirname12, join as join25 } from "path";
10733
10790
  import { tmpdir as tmpdir3 } from "os";
10734
10791
 
10735
10792
  // src/release.ts
@@ -11447,13 +11504,13 @@ async function bump(o, size) {
11447
11504
  }
11448
11505
  return printed;
11449
11506
  }
11450
- const files = o.repo.release.versionFiles.map((f) => join24(o.cwd, f));
11507
+ const files = o.repo.release.versionFiles.map((f) => join25(o.cwd, f));
11451
11508
  const first = files[0];
11452
11509
  if (!existsSync22(first)) {
11453
11510
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
11454
11511
  return null;
11455
11512
  }
11456
- const current = JSON.parse(readFileSync18(first, "utf8")).version;
11513
+ const current = JSON.parse(readFileSync19(first, "utf8")).version;
11457
11514
  if (!current) {
11458
11515
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
11459
11516
  return null;
@@ -11466,27 +11523,27 @@ async function bump(o, size) {
11466
11523
  return version;
11467
11524
  }
11468
11525
  for (const f of files) {
11469
- const src = readFileSync18(f, "utf8");
11526
+ const src = readFileSync19(f, "utf8");
11470
11527
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
11471
11528
  if (out === src) {
11472
11529
  o.emit.error(`no version field in ${f}`);
11473
11530
  return null;
11474
11531
  }
11475
- writeFileSync15(f, out);
11532
+ writeFileSync16(f, out);
11476
11533
  }
11477
11534
  return version;
11478
11535
  }
11479
11536
  function writeChangelog(o, version, entries) {
11480
11537
  const file = o.repo.release.changelog;
11481
11538
  if (!file || entries.length === 0) return;
11482
- const path = join24(o.cwd, file);
11539
+ const path = join25(o.cwd, file);
11483
11540
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
11484
11541
  if (o.dryRun) {
11485
11542
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
11486
11543
  return;
11487
11544
  }
11488
- const existing = existsSync22(path) ? readFileSync18(path, "utf8") : "# Changelog\n";
11489
- writeFileSync15(path, insertChangelogSection(existing, section));
11545
+ const existing = existsSync22(path) ? readFileSync19(path, "utf8") : "# Changelog\n";
11546
+ writeFileSync16(path, insertChangelogSection(existing, section));
11490
11547
  }
11491
11548
  async function confirm(o, expected) {
11492
11549
  if (!o.repo.hooks.released) return void 0;
@@ -11579,8 +11636,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
11579
11636
  function openReleaseCheckout(o) {
11580
11637
  const { remote, base } = o.repo.branch;
11581
11638
  const branch = `crew/release-${Date.now()}`;
11582
- const root = mkdtempSync2(join24(tmpdir3(), "crew-release-"));
11583
- const dir = join24(root, "checkout");
11639
+ const root = mkdtempSync2(join25(tmpdir3(), "crew-release-"));
11640
+ const dir = join25(root, "checkout");
11584
11641
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
11585
11642
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
11586
11643
  try {
@@ -11590,10 +11647,10 @@ function openReleaseCheckout(o) {
11590
11647
  throw e;
11591
11648
  }
11592
11649
  for (const f of o.repo.worktrees.copy) {
11593
- const src = join24(o.cwd, f);
11650
+ const src = join25(o.cwd, f);
11594
11651
  if (!existsSync22(src)) continue;
11595
- mkdirSync13(dirname12(join24(dir, f)), { recursive: true });
11596
- copyFileSync(src, join24(dir, f));
11652
+ mkdirSync14(dirname12(join25(dir, f)), { recursive: true });
11653
+ copyFileSync(src, join25(dir, f));
11597
11654
  }
11598
11655
  return { dir, branch, close };
11599
11656
  }
@@ -12664,19 +12721,19 @@ function nextRoles(decision, ran) {
12664
12721
  }
12665
12722
 
12666
12723
  // src/ship-name.ts
12667
- import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync20, renameSync as renameSync2, writeFileSync as writeFileSync17 } from "fs";
12724
+ import { existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync21, renameSync as renameSync2, writeFileSync as writeFileSync18 } from "fs";
12668
12725
  import { dirname as dirname14 } from "path";
12669
12726
  import { parseDocument } from "yaml";
12670
12727
 
12671
12728
  // src/machine-id.ts
12672
12729
  import { execFileSync as execFileSync4 } from "child_process";
12673
- import { createHash as createHash3 } from "crypto";
12674
- import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
12675
- import { dirname as dirname13, join as join25 } from "path";
12730
+ import { createHash as createHash4 } from "crypto";
12731
+ import { existsSync as existsSync24, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
12732
+ import { dirname as dirname13, join as join26 } from "path";
12676
12733
  function machineIdPathFor(stateDir) {
12677
- return join25(stateDir, "machine-id");
12734
+ return join26(stateDir, "machine-id");
12678
12735
  }
12679
- var sha256 = (s) => createHash3("sha256").update(s).digest("hex");
12736
+ var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
12680
12737
  function readRawMachineId(platform2 = process.platform) {
12681
12738
  try {
12682
12739
  if (platform2 === "darwin") {
@@ -12688,7 +12745,7 @@ function readRawMachineId(platform2 = process.platform) {
12688
12745
  }
12689
12746
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
12690
12747
  if (existsSync24(p)) {
12691
- const id = readFileSync19(p, "utf8").trim();
12748
+ const id = readFileSync20(p, "utf8").trim();
12692
12749
  if (id) return id;
12693
12750
  }
12694
12751
  }
@@ -12700,14 +12757,14 @@ function ensureMachineId(stateDir, opts = {}) {
12700
12757
  if (opts.override?.trim()) return sha256(opts.override.trim());
12701
12758
  const path = machineIdPathFor(stateDir);
12702
12759
  if (existsSync24(path)) {
12703
- const cached = readFileSync19(path, "utf8").trim();
12760
+ const cached = readFileSync20(path, "utf8").trim();
12704
12761
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
12705
12762
  }
12706
12763
  const raw = (opts.read ?? readRawMachineId)();
12707
12764
  if (!raw) return void 0;
12708
12765
  const hashed = sha256(raw);
12709
- mkdirSync14(dirname13(path), { recursive: true });
12710
- writeFileSync16(path, `${hashed}
12766
+ mkdirSync15(dirname13(path), { recursive: true });
12767
+ writeFileSync17(path, `${hashed}
12711
12768
  `, { mode: 384 });
12712
12769
  return hashed;
12713
12770
  }
@@ -12780,7 +12837,7 @@ async function syncRouteName(i) {
12780
12837
  function readSyncedName(resolvedFile) {
12781
12838
  if (!existsSync25(resolvedFile)) return void 0;
12782
12839
  try {
12783
- const v = JSON.parse(readFileSync20(resolvedFile, "utf8")).shipName;
12840
+ const v = JSON.parse(readFileSync21(resolvedFile, "utf8")).shipName;
12784
12841
  return typeof v === "string" ? v : void 0;
12785
12842
  } catch {
12786
12843
  return void 0;
@@ -12790,22 +12847,22 @@ function writeSyncedName(resolvedFile, name) {
12790
12847
  if (!existsSync25(resolvedFile)) return;
12791
12848
  let raw;
12792
12849
  try {
12793
- raw = JSON.parse(readFileSync20(resolvedFile, "utf8"));
12850
+ raw = JSON.parse(readFileSync21(resolvedFile, "utf8"));
12794
12851
  } catch {
12795
12852
  return;
12796
12853
  }
12797
12854
  if (raw.shipName === name) return;
12798
12855
  raw.shipName = name;
12799
- mkdirSync15(dirname14(resolvedFile), { recursive: true });
12856
+ mkdirSync16(dirname14(resolvedFile), { recursive: true });
12800
12857
  const tmp = `${resolvedFile}.tmp${process.pid}`;
12801
- writeFileSync17(tmp, `${JSON.stringify(raw, null, 2)}
12858
+ writeFileSync18(tmp, `${JSON.stringify(raw, null, 2)}
12802
12859
  `);
12803
12860
  renameSync2(tmp, resolvedFile);
12804
12861
  }
12805
12862
  async function reconcileShipName(i) {
12806
12863
  let local = i.ship.name;
12807
12864
  try {
12808
- local = shipNameOfYaml(readFileSync20(i.configFile, "utf8")) ?? local;
12865
+ local = shipNameOfYaml(readFileSync21(i.configFile, "utf8")) ?? local;
12809
12866
  } catch {
12810
12867
  }
12811
12868
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -12829,8 +12886,8 @@ async function reconcileShipName(i) {
12829
12886
  }
12830
12887
  if (result.status !== "ok") return result;
12831
12888
  if (result.action === "adopt" || result.action === "row-wins") {
12832
- const text = readFileSync20(i.configFile, "utf8");
12833
- writeFileSync17(i.configFile, setShipNameInYaml(text, result.name));
12889
+ const text = readFileSync21(i.configFile, "utf8");
12890
+ writeFileSync18(i.configFile, setShipNameInYaml(text, result.name));
12834
12891
  i.ship.name = result.name;
12835
12892
  if (result.action === "row-wins") {
12836
12893
  i.emit?.warn(
@@ -13738,7 +13795,7 @@ function describeCapability(i) {
13738
13795
  }
13739
13796
 
13740
13797
  // src/agents-sync-run.ts
13741
- import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
13798
+ import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
13742
13799
  import { dirname as dirname15 } from "path";
13743
13800
  async function runAgentsSync(target, o) {
13744
13801
  const dryRun = o.dryRun ?? false;
@@ -13772,12 +13829,12 @@ async function runAgentsSync(target, o) {
13772
13829
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
13773
13830
  if (!dryRun) {
13774
13831
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
13775
- const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync21(resolvedPath, "utf8")) : {};
13832
+ const raw = existsSync26(resolvedPath) ? JSON.parse(readFileSync22(resolvedPath, "utf8")) : {};
13776
13833
  raw.agentPersonas = personaResult.agentPersonas;
13777
13834
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
13778
13835
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
13779
- mkdirSync16(dirname15(resolvedPath), { recursive: true });
13780
- writeFileSync18(resolvedPath, `${JSON.stringify(raw, null, 2)}
13836
+ mkdirSync17(dirname15(resolvedPath), { recursive: true });
13837
+ writeFileSync19(resolvedPath, `${JSON.stringify(raw, null, 2)}
13781
13838
  `);
13782
13839
  if (target.resolved) {
13783
13840
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -13792,7 +13849,7 @@ async function runAgentsSync(target, o) {
13792
13849
  }
13793
13850
 
13794
13851
  // src/logbook.ts
13795
- import { createHash as createHash4 } from "crypto";
13852
+ import { createHash as createHash5 } from "crypto";
13796
13853
  import { TablationClient as TablationClient4 } from "@tablation/client";
13797
13854
  var LogbookError = class extends Error {
13798
13855
  };
@@ -13865,7 +13922,7 @@ async function showLogEntry(route, entryId, opts = {}) {
13865
13922
  };
13866
13923
  }
13867
13924
  const prompt = appendAdditions(match.prompt ?? "", match.prompt_additions);
13868
- const actualSha = createHash4("sha256").update(prompt).digest("hex");
13925
+ const actualSha = createHash5("sha256").update(prompt).digest("hex");
13869
13926
  const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
13870
13927
  return {
13871
13928
  entry,
@@ -13884,7 +13941,7 @@ import { isMap as isMap2, parseDocument as parseDocument3, YAMLMap } from "yaml"
13884
13941
 
13885
13942
  // src/repos-cmd.ts
13886
13943
  import { homedir as homedir8 } from "os";
13887
- import { basename as basename8, join as join26, resolve as resolve3 } from "path";
13944
+ import { basename as basename8, join as join27, resolve as resolve3 } from "path";
13888
13945
  import { isMap, isSeq, parseDocument as parseDocument2, stringify as stringify2 } from "yaml";
13889
13946
  var ReposError = class extends Error {
13890
13947
  };
@@ -14162,7 +14219,7 @@ function planRepoClone(i) {
14162
14219
  }
14163
14220
  const ship = doc.get("ship", true);
14164
14221
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
14165
- const target = i.dir ? expandDir(i.dir, i.base) : join26(expandDir(basePath, i.base), i.route.split("/")[0], name);
14222
+ const target = i.dir ? expandDir(i.dir, i.base) : join27(expandDir(basePath, i.base), i.route.split("/")[0], name);
14166
14223
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
14167
14224
  return { ...add, remote, target };
14168
14225
  }
@@ -14307,7 +14364,7 @@ function repoDirOf(text, base, route, name) {
14307
14364
  }
14308
14365
  return hit.dir;
14309
14366
  }
14310
- function planRouteHooks(text, route, name, hooks) {
14367
+ function planRouteHooks(text, route, name, hooks, opts = {}) {
14311
14368
  const doc = parseDocument3(text);
14312
14369
  if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
14313
14370
  const m = routeMapOf(doc, route);
@@ -14332,15 +14389,34 @@ function planRouteHooks(text, route, name, hooks) {
14332
14389
  }
14333
14390
  const written = [];
14334
14391
  const kept = [];
14392
+ const replaced = [];
14335
14393
  for (const [k, v] of Object.entries(hooks)) {
14336
14394
  if (v === void 0) continue;
14337
- if (hooksMap.has(k)) kept.push(k);
14395
+ if (hooksMap.has(k) && !opts.replace) kept.push(k);
14338
14396
  else {
14397
+ if (hooksMap.has(k)) replaced.push(k);
14339
14398
  hooksMap.set(k, v);
14340
14399
  written.push(k);
14341
14400
  }
14342
14401
  }
14343
- return { text: String(doc), written, kept };
14402
+ return { text: String(doc), written, kept, replaced };
14403
+ }
14404
+ function clearRouteHooks(text, route, name) {
14405
+ const doc = parseDocument3(text);
14406
+ if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
14407
+ const m = routeMapOf(doc, route);
14408
+ const repos = m.get("repos", true);
14409
+ let target = m;
14410
+ if (isMap2(repos)) {
14411
+ const entry = repos.get(name, true);
14412
+ if (!isMap2(entry)) return { text, removed: [] };
14413
+ target = entry;
14414
+ }
14415
+ const current = target.get("hooks", true);
14416
+ if (!isMap2(current)) return { text, removed: [] };
14417
+ const removed = current.items.map((i) => String(i.key.value ?? i.key));
14418
+ target.delete("hooks");
14419
+ return { text: String(doc), removed };
14344
14420
  }
14345
14421
  function starterRepoConfig(hooks, release) {
14346
14422
  const lines = [
@@ -14542,7 +14618,7 @@ ${run2.err}` });
14542
14618
  }
14543
14619
 
14544
14620
  // src/sample-ticket.ts
14545
- import { basename as basename9, dirname as dirname17, join as join27 } from "path";
14621
+ import { basename as basename9, dirname as dirname17, join as join28 } from "path";
14546
14622
  var SAMPLE_REPO = "crew-sample";
14547
14623
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
14548
14624
  var SAMPLE_MARKER = "sample: true";
@@ -14565,8 +14641,8 @@ has something safe to work on: its test and build hooks always pass, it has no
14565
14641
  remote, and \`crew sample-ticket --remove\` deletes it.
14566
14642
  `;
14567
14643
  function sampleDir(existing, reposBase, workspace) {
14568
- if (existing.length > 0) return join27(dirname17(existing[0]), SAMPLE_REPO);
14569
- return join27(reposBase, workspace, SAMPLE_REPO);
14644
+ if (existing.length > 0) return join28(dirname17(existing[0]), SAMPLE_REPO);
14645
+ return join28(reposBase, workspace, SAMPLE_REPO);
14570
14646
  }
14571
14647
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
14572
14648
  function sampleDescription() {
@@ -14647,8 +14723,8 @@ function schedulerInstalled(plan, exists, crontab) {
14647
14723
  }
14648
14724
 
14649
14725
  // src/worktree-sweep.ts
14650
- import { existsSync as existsSync27, readFileSync as readFileSync22, readdirSync as readdirSync8 } from "fs";
14651
- import { basename as basename10, dirname as dirname18, join as join28, resolve as resolve4 } from "path";
14726
+ import { existsSync as existsSync27, readFileSync as readFileSync23, readdirSync as readdirSync8 } from "fs";
14727
+ import { basename as basename10, dirname as dirname18, join as join29, resolve as resolve4 } from "path";
14652
14728
  function planWorktreeSweep(target, tickets, contract) {
14653
14729
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
14654
14730
  const verified = contract.statuses.verified;
@@ -14715,7 +14791,7 @@ function siblingWorktreeDirs(target) {
14715
14791
  for (const name of names) {
14716
14792
  if (!name.startsWith(stem)) continue;
14717
14793
  const m = DIR_NUMBER_SUFFIXED.exec(name);
14718
- const path = join28(parent, name);
14794
+ const path = join29(parent, name);
14719
14795
  if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
14720
14796
  }
14721
14797
  return out;
@@ -14818,7 +14894,7 @@ function isHandBackMerge(path, base) {
14818
14894
  if (!msgPath) return false;
14819
14895
  let first;
14820
14896
  try {
14821
- first = readFileSync22(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
14897
+ first = readFileSync23(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
14822
14898
  } catch {
14823
14899
  return false;
14824
14900
  }
@@ -14924,13 +15000,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
14924
15000
 
14925
15001
  // src/stream-sweep.ts
14926
15002
  import { existsSync as existsSync28, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8 } from "fs";
14927
- import { join as join29 } from "path";
15003
+ import { join as join30 } from "path";
14928
15004
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
14929
15005
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
14930
- const dir = join29(stateDir, "streams");
15006
+ const dir = join30(stateDir, "streams");
14931
15007
  if (!existsSync28(dir)) return [];
14932
15008
  const cutoff = now - retentionDays * DAY_MS2;
14933
- return readdirSync9(dir).map((name) => join29(dir, name)).filter((path) => {
15009
+ return readdirSync9(dir).map((name) => join30(dir, name)).filter((path) => {
14934
15010
  try {
14935
15011
  return statSync8(path).mtimeMs < cutoff;
14936
15012
  } catch {
@@ -14958,7 +15034,7 @@ function applyStreamSweep(paths, dryRun, log) {
14958
15034
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
14959
15035
  if (!existsSync28(stateDir)) return [];
14960
15036
  const cutoff = now - retentionDays * DAY_MS2;
14961
- return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join29(stateDir, name)).filter((path) => {
15037
+ return readdirSync9(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join30(stateDir, name)).filter((path) => {
14962
15038
  try {
14963
15039
  return statSync8(path).mtimeMs < cutoff;
14964
15040
  } catch {
@@ -14968,24 +15044,24 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
14968
15044
  }
14969
15045
 
14970
15046
  // src/cli.ts
14971
- import { realpathSync as realpathSync3, readFileSync as readFileSync28, existsSync as existsSync34, mkdirSync as mkdirSync21, writeFileSync as writeFileSync22, rmSync as rmSync15, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync10, readdirSync as readdirSync14 } from "fs";
15047
+ 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
15048
  import { createInterface } from "readline/promises";
14973
15049
  import { dirname as dirOf, resolve as resolvePath } from "path";
14974
15050
 
14975
15051
  // src/ssh-keys.ts
14976
15052
  import { execFileSync as execFileSync6 } from "child_process";
14977
- import { existsSync as existsSync29, mkdirSync as mkdirSync17, readFileSync as readFileSync23, chmodSync } from "fs";
14978
- import { dirname as dirname19, join as join30 } from "path";
15053
+ import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync24, chmodSync } from "fs";
15054
+ import { dirname as dirname19, join as join31 } from "path";
14979
15055
  var SshKeyError = class extends Error {
14980
15056
  };
14981
15057
  function shipSshKeyPathFor(stateDir) {
14982
- return join30(stateDir, "ssh", "id_ed25519");
15058
+ return join31(stateDir, "ssh", "id_ed25519");
14983
15059
  }
14984
15060
  function ensureShipSshKeypair(stateDir) {
14985
15061
  const privateKeyPath = shipSshKeyPathFor(stateDir);
14986
15062
  const publicKeyPath = `${privateKeyPath}.pub`;
14987
15063
  if (!existsSync29(privateKeyPath)) {
14988
- mkdirSync17(dirname19(privateKeyPath), { recursive: true });
15064
+ mkdirSync18(dirname19(privateKeyPath), { recursive: true });
14989
15065
  try {
14990
15066
  execFileSync6("ssh-keygen", [
14991
15067
  "-t",
@@ -15008,7 +15084,7 @@ function ensureShipSshKeypair(stateDir) {
15008
15084
  if (!existsSync29(publicKeyPath)) {
15009
15085
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
15010
15086
  }
15011
- const publicKey = readFileSync23(publicKeyPath, "utf8").trim();
15087
+ const publicKey = readFileSync24(publicKeyPath, "utf8").trim();
15012
15088
  return { privateKeyPath, publicKeyPath, publicKey };
15013
15089
  }
15014
15090
  function sshKeygenAvailable() {
@@ -15022,7 +15098,7 @@ function sshKeygenAvailable() {
15022
15098
 
15023
15099
  // src/passenger-containers.ts
15024
15100
  import { execFileSync as execFileSync7 } from "child_process";
15025
- import { createHash as createHash5 } from "crypto";
15101
+ import { createHash as createHash6 } from "crypto";
15026
15102
  var realExec = (cmd, args) => {
15027
15103
  try {
15028
15104
  return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
@@ -15059,12 +15135,12 @@ function containerNameFor(workspaceId) {
15059
15135
  return `crew-passenger-${workspaceId}`;
15060
15136
  }
15061
15137
  function portFor(workspaceId, base = 28765, span = 1e3) {
15062
- const digest = createHash5("sha256").update(workspaceId).digest();
15138
+ const digest = createHash6("sha256").update(workspaceId).digest();
15063
15139
  return base + digest.readUInt32BE(0) % span;
15064
15140
  }
15065
15141
  function mountsDigest(mounts, image) {
15066
15142
  const sorted2 = [...mounts].sort((a, b) => a.containerPath.localeCompare(b.containerPath));
15067
- return createHash5("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
15143
+ return createHash6("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
15068
15144
  }
15069
15145
  function planContainers(cfg) {
15070
15146
  const byWorkspace = /* @__PURE__ */ new Map();
@@ -15224,9 +15300,9 @@ function syncAllPassengerCheckouts(cfg) {
15224
15300
 
15225
15301
  // src/tunnel.ts
15226
15302
  import { spawn as spawn5 } from "child_process";
15227
- import { existsSync as existsSync30, mkdirSync as mkdirSync18, readFileSync as readFileSync24, writeFileSync as writeFileSync19, unlinkSync as unlinkSync3, readdirSync as readdirSync10 } from "fs";
15303
+ import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync20, unlinkSync as unlinkSync3, readdirSync as readdirSync10 } from "fs";
15228
15304
  import { randomInt } from "crypto";
15229
- import { join as join31 } from "path";
15305
+ import { join as join32 } from "path";
15230
15306
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
15231
15307
  var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
15232
15308
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -15293,21 +15369,21 @@ function startTunnel(opts) {
15293
15369
  };
15294
15370
  }
15295
15371
  function tunnelStatePath(stateDir, workspaceId) {
15296
- return join31(stateDir, "passengers", `${workspaceId}.json`);
15372
+ return join32(stateDir, "passengers", `${workspaceId}.json`);
15297
15373
  }
15298
15374
  function readPersistedTunnel(stateDir, workspaceId) {
15299
15375
  const path = tunnelStatePath(stateDir, workspaceId);
15300
15376
  if (!existsSync30(path)) return void 0;
15301
15377
  try {
15302
- return JSON.parse(readFileSync24(path, "utf8"));
15378
+ return JSON.parse(readFileSync25(path, "utf8"));
15303
15379
  } catch {
15304
15380
  return void 0;
15305
15381
  }
15306
15382
  }
15307
15383
  function writePersistedTunnel(stateDir, state) {
15308
15384
  const path = tunnelStatePath(stateDir, state.workspaceId);
15309
- mkdirSync18(join31(stateDir, "passengers"), { recursive: true });
15310
- writeFileSync19(path, `${JSON.stringify(state, null, 2)}
15385
+ mkdirSync19(join32(stateDir, "passengers"), { recursive: true });
15386
+ writeFileSync20(path, `${JSON.stringify(state, null, 2)}
15311
15387
  `);
15312
15388
  }
15313
15389
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -15404,13 +15480,13 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15404
15480
  }
15405
15481
  }
15406
15482
  function readAllPersistedWorkspaceIds(stateDir) {
15407
- const dir = join31(stateDir, "passengers");
15483
+ const dir = join32(stateDir, "passengers");
15408
15484
  if (!existsSync30(dir)) return [];
15409
15485
  return readdirSync10(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
15410
15486
  }
15411
15487
 
15412
15488
  // src/mcp-endpoint-registry.ts
15413
- import { createHash as createHash6 } from "crypto";
15489
+ import { createHash as createHash7 } from "crypto";
15414
15490
  function endpointsUrl(api, workspaceId, id) {
15415
15491
  const base = `${api.baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/mcp-endpoints`;
15416
15492
  return id ? `${base}/${id}` : base;
@@ -15478,7 +15554,7 @@ async function probeEndpointAccess(api, workspaceId) {
15478
15554
  }
15479
15555
  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
15556
  function registrationKey(url, mcpSecret) {
15481
- return createHash6("sha256").update(`${url}
15557
+ return createHash7("sha256").update(`${url}
15482
15558
  ${mcpSecret ?? ""}`).digest("hex").slice(0, 16);
15483
15559
  }
15484
15560
  async function reconcileEndpoint(workspaceId, d) {
@@ -15526,8 +15602,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
15526
15602
  import { createServer as createHttpServer } from "http";
15527
15603
  import { spawn as spawn6 } from "child_process";
15528
15604
  import { randomBytes } from "crypto";
15529
- import { existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync20, unlinkSync as unlinkSync4, readdirSync as readdirSync11 } from "fs";
15530
- import { join as join32 } from "path";
15605
+ import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync26, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readdirSync as readdirSync11 } from "fs";
15606
+ import { join as join33 } from "path";
15531
15607
 
15532
15608
  // src/secret-compare.ts
15533
15609
  import { timingSafeEqual } from "crypto";
@@ -15616,21 +15692,21 @@ var defaultSpawn2 = (cmd, args, env) => {
15616
15692
  return child;
15617
15693
  };
15618
15694
  function syncDaemonStatePath(stateDir, workspaceId) {
15619
- return join32(stateDir, "passengers", `${workspaceId}-sync.json`);
15695
+ return join33(stateDir, "passengers", `${workspaceId}-sync.json`);
15620
15696
  }
15621
15697
  function readPersistedSyncDaemon(stateDir, workspaceId) {
15622
15698
  const path = syncDaemonStatePath(stateDir, workspaceId);
15623
15699
  if (!existsSync31(path)) return void 0;
15624
15700
  try {
15625
- return JSON.parse(readFileSync25(path, "utf8"));
15701
+ return JSON.parse(readFileSync26(path, "utf8"));
15626
15702
  } catch {
15627
15703
  return void 0;
15628
15704
  }
15629
15705
  }
15630
15706
  function writePersistedSyncDaemon(stateDir, state) {
15631
15707
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
15632
- mkdirSync19(join32(stateDir, "passengers"), { recursive: true });
15633
- writeFileSync20(path, `${JSON.stringify(state, null, 2)}
15708
+ mkdirSync20(join33(stateDir, "passengers"), { recursive: true });
15709
+ writeFileSync21(path, `${JSON.stringify(state, null, 2)}
15634
15710
  `);
15635
15711
  }
15636
15712
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -15650,7 +15726,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
15650
15726
  }
15651
15727
  }
15652
15728
  function readAllPersistedSyncWorkspaceIds(stateDir) {
15653
- const dir = join32(stateDir, "passengers");
15729
+ const dir = join33(stateDir, "passengers");
15654
15730
  if (!existsSync31(dir)) return [];
15655
15731
  const suffix = "-sync.json";
15656
15732
  return readdirSync11(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
@@ -15739,12 +15815,12 @@ function describeCause(err) {
15739
15815
  }
15740
15816
 
15741
15817
  // src/daemon.ts
15742
- import { existsSync as existsSync33, mkdirSync as mkdirSync20, readFileSync as readFileSync27, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync21 } from "fs";
15743
- import { join as join34 } from "path";
15818
+ import { existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync28, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9, writeFileSync as writeFileSync22 } from "fs";
15819
+ import { join as join35 } from "path";
15744
15820
 
15745
15821
  // src/interrupted.ts
15746
- import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync26 } from "fs";
15747
- import { join as join33 } from "path";
15822
+ import { existsSync as existsSync32, readdirSync as readdirSync12, readFileSync as readFileSync27 } from "fs";
15823
+ import { join as join34 } from "path";
15748
15824
  var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
15749
15825
  function interruptedComment(b) {
15750
15826
  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 +15841,19 @@ function runFinished(streamPath) {
15765
15841
  const path = `${streamPath}.meta.json`;
15766
15842
  if (!existsSync32(path)) return false;
15767
15843
  try {
15768
- return JSON.parse(readFileSync26(path, "utf8")).running !== true;
15844
+ return JSON.parse(readFileSync27(path, "utf8")).running !== true;
15769
15845
  } catch {
15770
15846
  return true;
15771
15847
  }
15772
15848
  }
15773
15849
  function orphanedStreams(o) {
15774
- const dir = join33(o.stateDir, "streams");
15850
+ const dir = join34(o.stateDir, "streams");
15775
15851
  if (!existsSync32(dir)) return [];
15776
15852
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
15777
15853
  const out = [];
15778
15854
  for (const name of readdirSync12(dir)) {
15779
15855
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
15780
- const streamPath = join33(dir, name);
15856
+ const streamPath = join34(dir, name);
15781
15857
  if (runFinished(streamPath)) continue;
15782
15858
  const sidecar = readRunSidecar(streamPath);
15783
15859
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -15897,7 +15973,7 @@ function latestMtime(path) {
15897
15973
  if (!stat.isDirectory()) return 0;
15898
15974
  let latest = stat.mtimeMs;
15899
15975
  for (const entry of readdirSync13(path, { withFileTypes: true })) {
15900
- latest = Math.max(latest, latestMtime(join34(path, entry.name)));
15976
+ latest = Math.max(latest, latestMtime(join35(path, entry.name)));
15901
15977
  }
15902
15978
  return latest;
15903
15979
  }
@@ -15909,20 +15985,20 @@ function makeStaleChecker(paths, opts = {}) {
15909
15985
  return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync33(opts.reloadSentinel);
15910
15986
  }
15911
15987
  function reloadSentinelFor(stateDir) {
15912
- return join34(stateDir, "reload-requested");
15988
+ return join35(stateDir, "reload-requested");
15913
15989
  }
15914
15990
  function daemonStampFor(stateDir) {
15915
- return join34(stateDir, "daemon.started");
15991
+ return join35(stateDir, "daemon.started");
15916
15992
  }
15917
15993
  function noteDaemonStarted(stateDir, now = Date.now()) {
15918
- mkdirSync20(stateDir, { recursive: true });
15994
+ mkdirSync21(stateDir, { recursive: true });
15919
15995
  rmSync14(reloadSentinelFor(stateDir), { force: true });
15920
- writeFileSync21(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
15996
+ writeFileSync22(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
15921
15997
  `);
15922
15998
  }
15923
15999
  function readDaemonStarted(stateDir) {
15924
16000
  try {
15925
- const v = JSON.parse(readFileSync27(daemonStampFor(stateDir), "utf8")).startedAt;
16001
+ const v = JSON.parse(readFileSync28(daemonStampFor(stateDir), "utf8")).startedAt;
15926
16002
  return typeof v === "number" ? v : null;
15927
16003
  } catch {
15928
16004
  return null;
@@ -16943,7 +17019,7 @@ if (command === "passenger-sync-daemon") {
16943
17019
  }
16944
17020
  if (host === "linux" && hasSystemd) {
16945
17021
  const label = labelFor(CREW_HOME, "run");
16946
- const staleTimer = join35(homedir9(), ".config", "systemd", "user", `${label}.timer`);
17022
+ const staleTimer = join36(homedir9(), ".config", "systemd", "user", `${label}.timer`);
16947
17023
  if (existsSync34(staleTimer)) {
16948
17024
  process.stdout.write(
16949
17025
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
@@ -17005,7 +17081,7 @@ if (command === "passenger-sync-daemon") {
17005
17081
  for (const a of plan.actions) {
17006
17082
  if (a.kind === "enable") {
17007
17083
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
17008
- writeFileSync22(cfg.configFile, planEnable(readFileSync28(cfg.configFile, "utf8"), a.route));
17084
+ writeFileSync23(cfg.configFile, planEnable(readFileSync29(cfg.configFile, "utf8"), a.route));
17009
17085
  enabledSound.add(a.route);
17010
17086
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
17011
17087
  } else {
@@ -18014,7 +18090,7 @@ ${changed2.map((c) => ` - ${c}
18014
18090
  process.exit(1);
18015
18091
  }
18016
18092
  requireArmed("run the daemon loop");
18017
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join35(CREW_HOME, "src"), join35(CREW_HOME, "dist"), join35(CREW_HOME, "bin")];
18093
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join36(CREW_HOME, "src"), join36(CREW_HOME, "dist"), join36(CREW_HOME, "bin")];
18018
18094
  const configPaths = daemonConfigPaths(cfg);
18019
18095
  watchPaths.push(...configPaths);
18020
18096
  noteDaemonStarted(cfg.ship.stateDir);
@@ -18233,10 +18309,10 @@ ${changed2.map((c) => ` - ${c}
18233
18309
  `);
18234
18310
  if (!dryRun) {
18235
18311
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
18236
- const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync28(resolvedPath, "utf8")) : {};
18312
+ const raw = existsSync34(resolvedPath) ? JSON.parse(readFileSync29(resolvedPath, "utf8")) : {};
18237
18313
  raw.agentSkills = result.agentSkills;
18238
- mkdirSync21(dirOf(resolvedPath), { recursive: true });
18239
- writeFileSync22(resolvedPath, `${JSON.stringify(raw, null, 2)}
18314
+ mkdirSync22(dirOf(resolvedPath), { recursive: true });
18315
+ writeFileSync23(resolvedPath, `${JSON.stringify(raw, null, 2)}
18240
18316
  `);
18241
18317
  }
18242
18318
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -18259,7 +18335,7 @@ ${changed2.map((c) => ` - ${c}
18259
18335
  throw e;
18260
18336
  }
18261
18337
  const oldName = cfg.ship.name;
18262
- writeFileSync22(cfg.configFile, setShipNameInYaml(readFileSync28(cfg.configFile, "utf8"), newName));
18338
+ writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), newName));
18263
18339
  cfg.ship.name = newName;
18264
18340
  let renameMachineId;
18265
18341
  try {
@@ -18302,11 +18378,11 @@ ${changed2.map((c) => ` - ${c}
18302
18378
  case "repos": {
18303
18379
  const sub = positional[1];
18304
18380
  const usage2 = () => {
18305
- process.stderr.write("crew repos add [workspace/project] [name] [path] [--name N] [--project P] [--dry-run] [--no-create]\ncrew repos clone [workspace/project] <name> [--dir PATH] [--dry-run] [--json]\ncrew repos list <workspace/project> [--json]\ncrew repos hooks [workspace/project] <name> [--suggest] [--agent] [--write route|repo] [--recipes ID,ID] [--hook KEY=CMD ...] [--json]\n");
18381
+ process.stderr.write("crew repos add [workspace/project] [name] [path] [--name N] [--project P] [--dry-run] [--no-create]\ncrew repos clone [workspace/project] <name> [--dir PATH] [--dry-run] [--json]\ncrew repos list <workspace/project> [--json]\ncrew repos hooks [workspace/project] <name> [--suggest] [--agent] [--write route|repo [--replace]] [--clear route] [--recipes ID,ID] [--hook KEY=CMD ...] [--json]\n");
18306
18382
  process.exit(2);
18307
18383
  };
18308
18384
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
18309
- const configText = readFileSync28(cfg.configFile, "utf8");
18385
+ const configText = readFileSync29(cfg.configFile, "utf8");
18310
18386
  if (sub === "hooks") {
18311
18387
  try {
18312
18388
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
@@ -18320,7 +18396,7 @@ ${changed2.map((c) => ` - ${c}
18320
18396
  }
18321
18397
  }, read: (r) => {
18322
18398
  try {
18323
- return readFileSync28(resolvePath(dir, r), "utf8");
18399
+ return readFileSync29(resolvePath(dir, r), "utf8");
18324
18400
  } catch {
18325
18401
  return void 0;
18326
18402
  }
@@ -18328,6 +18404,10 @@ ${changed2.map((c) => ` - ${c}
18328
18404
  const suggestion = suggestHooks(files);
18329
18405
  const write = value("write");
18330
18406
  if (write !== void 0 && write !== "route" && write !== "repo") throw new ReposError(`--write takes "route" or "repo" (got "${write}")`);
18407
+ const clear2 = value("clear");
18408
+ if (clear2 !== void 0 && clear2 !== "route") throw new ReposError(`--clear takes "route" (got "${clear2}")`);
18409
+ if (clear2 && (write || flag("suggest") || flag("agent"))) throw new ReposError("--clear cannot be combined with --write, --suggest or --agent");
18410
+ if (flag("replace") && write !== "route") throw new ReposError("--replace only applies with --write route");
18331
18411
  const recipeIds = (value("recipes") ?? "").split(",").map((x) => x.trim()).filter((x) => x && x !== "none");
18332
18412
  if (recipeIds.length > 0 && !write) throw new ReposError("--recipes only applies with --write route|repo");
18333
18413
  if (flag("agent") && write) throw new ReposError("--agent is read-only; drop --write (write the sniffed hooks, then re-run --agent to compare)");
@@ -18355,20 +18435,26 @@ ${changed2.map((c) => ` - ${c}
18355
18435
  const base2 = selectRecipes(suggestion, recipeIds);
18356
18436
  const picked = { ...base2, hooks: { ...base2.hooks, ...overrides } };
18357
18437
  let written;
18438
+ let cleared;
18439
+ if (clear2) {
18440
+ const plan = clearRouteHooks(configText, a.route, a.name);
18441
+ if (plan.removed.length > 0) writeFileSync23(cfg.configFile, plan.text);
18442
+ cleared = { file: cfg.configFile, removed: plan.removed };
18443
+ }
18358
18444
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
18359
18445
  if (write === "route") {
18360
- const plan = planRouteHooks(configText, a.route, a.name, picked.hooks);
18361
- writeFileSync22(cfg.configFile, plan.text);
18362
- written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
18446
+ const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
18447
+ writeFileSync23(cfg.configFile, plan.text);
18448
+ written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
18363
18449
  } else if (write === "repo") {
18364
18450
  const have = existingRepoConfig(files);
18365
18451
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
18366
18452
  const file = resolvePath(dir, ".crew.yaml");
18367
- writeFileSync22(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
18368
- written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [] };
18453
+ writeFileSync23(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
18454
+ written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
18369
18455
  }
18370
18456
  if (flag("json")) {
18371
- process.stdout.write(`${JSON.stringify({ route: a.route, repo: a.name, dir, ...suggestion, agent, agentError, written: written ?? null })}
18457
+ process.stdout.write(`${JSON.stringify({ route: a.route, repo: a.name, dir, ...suggestion, agent, agentError, written: written ?? null, cleared: cleared ?? null })}
18372
18458
  `);
18373
18459
  } else {
18374
18460
  if (suggestion.source) process.stdout.write(`${a.name}: proposed from ${suggestion.source}
@@ -18389,8 +18475,12 @@ ${changed2.map((c) => ` - ${c}
18389
18475
  }
18390
18476
  if (agentError) process.stderr.write(`crew repos: --agent failed (${agentError.reason}): ${agentError.message}
18391
18477
  `);
18478
+ if (cleared) process.stdout.write(cleared.removed.length ? `removed route hooks ${cleared.removed.join(", ")} from ${cleared.file}
18479
+ ` : "no route hooks to remove\n");
18392
18480
  if (written) {
18393
18481
  process.stdout.write(`wrote ${written.hooks.join(", ") || "nothing new"} to ${written.file}
18482
+ `);
18483
+ if (written.replaced.length) process.stdout.write(`replaced route hooks: ${written.replaced.join(", ")}
18394
18484
  `);
18395
18485
  if (written.kept.length) process.stdout.write(`kept existing route hooks: ${written.kept.join(", ")}
18396
18486
  `);
@@ -18447,7 +18537,7 @@ ${changed2.map((c) => ` - ${c}
18447
18537
  let resolvedRemotes;
18448
18538
  if (existsSync34(resolvedFile)) {
18449
18539
  try {
18450
- resolvedRemotes = JSON.parse(readFileSync28(resolvedFile, "utf8"));
18540
+ resolvedRemotes = JSON.parse(readFileSync29(resolvedFile, "utf8"));
18451
18541
  } catch {
18452
18542
  }
18453
18543
  }
@@ -18468,7 +18558,7 @@ ${changed2.map((c) => ` - ${c}
18468
18558
  isCheckout,
18469
18559
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
18470
18560
  try {
18471
- return Object.keys(parseRepoConfig(readFileSync28(resolvePath(d, f), "utf8"), f).hooks).length > 0;
18561
+ return Object.keys(parseRepoConfig(readFileSync29(resolvePath(d, f), "utf8"), f).hooks).length > 0;
18472
18562
  } catch {
18473
18563
  return false;
18474
18564
  }
@@ -18517,7 +18607,7 @@ ${changed2.map((c) => ` - ${c}
18517
18607
  throw new ReposError(`${e.message}${authHint}
18518
18608
  crew.yaml was not changed`);
18519
18609
  }
18520
- writeFileSync22(cfg.configFile, plan2.text);
18610
+ writeFileSync23(cfg.configFile, plan2.text);
18521
18611
  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
18612
  `);
18523
18613
  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 +18667,14 @@ ${changed2.map((c) => ` - ${c}
18577
18667
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
18578
18668
  `);
18579
18669
  if (existsSync34(resolvedFile)) {
18580
- const raw = JSON.parse(readFileSync28(resolvedFile, "utf8"));
18670
+ const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
18581
18671
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
18582
18672
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
18583
- writeFileSync22(resolvedFile, `${JSON.stringify(raw, null, 2)}
18673
+ writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
18584
18674
  `);
18585
18675
  }
18586
18676
  }
18587
- writeFileSync22(cfg.configFile, plan.text);
18677
+ writeFileSync23(cfg.configFile, plan.text);
18588
18678
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
18589
18679
  `);
18590
18680
  } catch (e) {
@@ -18935,7 +19025,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18935
19025
  process.exit(2);
18936
19026
  }
18937
19027
  if (found.shipNameAdopted && !dryRun) {
18938
- writeFileSync22(cfg.configFile, setShipNameInYaml(readFileSync28(cfg.configFile, "utf8"), found.shipNameAdopted));
19028
+ writeFileSync23(cfg.configFile, setShipNameInYaml(readFileSync29(cfg.configFile, "utf8"), found.shipNameAdopted));
18939
19029
  cfg.ship.name = found.shipNameAdopted;
18940
19030
  }
18941
19031
  if (found.shipRow) {
@@ -18981,7 +19071,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18981
19071
  const hadPreviousFile = existsSync34(resolvedPath);
18982
19072
  if (hadPreviousFile) {
18983
19073
  try {
18984
- previous = JSON.parse(readFileSync28(resolvedPath, "utf8"));
19074
+ previous = JSON.parse(readFileSync29(resolvedPath, "utf8"));
18985
19075
  } catch {
18986
19076
  }
18987
19077
  }
@@ -19101,10 +19191,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19101
19191
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
19102
19192
  `);
19103
19193
  } else {
19104
- mkdirSync21(dirname20(resolvedPath), { recursive: true });
19194
+ mkdirSync22(dirname20(resolvedPath), { recursive: true });
19105
19195
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
19106
19196
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
19107
- writeFileSync22(tmpPath, `${JSON.stringify(resolved, null, 2)}
19197
+ writeFileSync23(tmpPath, `${JSON.stringify(resolved, null, 2)}
19108
19198
  `);
19109
19199
  renameSync3(tmpPath, resolvedPath);
19110
19200
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -19146,8 +19236,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19146
19236
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
19147
19237
  `);
19148
19238
  } else {
19149
- mkdirSync21(dirname20(apiKeyFile), { recursive: true });
19150
- writeFileSync22(apiKeyFile, `${apiKeyVar}=${apiKey}
19239
+ mkdirSync22(dirname20(apiKeyFile), { recursive: true });
19240
+ writeFileSync23(apiKeyFile, `${apiKeyVar}=${apiKey}
19151
19241
  `, { mode: 384 });
19152
19242
  process.stderr.write(`API key written to ${apiKeyFile}
19153
19243
  `);
@@ -19183,8 +19273,11 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19183
19273
  const id = `${tc.harness}\0${tc.bin}`;
19184
19274
  if (seen.has(id)) continue;
19185
19275
  seen.add(id);
19186
- const line = adapterFor(tc.harness).ensureMcp({ bin: tc.bin, url: mcpUrlFor(baseUrl), key: apiKey, replace, dryRun });
19187
- connectEvents.emit("mcp_registered", line, { harness: tc.harness, bin: tc.bin });
19276
+ const adapter = adapterFor(tc.harness);
19277
+ const mcpUrl = mcpUrlFor(baseUrl);
19278
+ const mcpOpts = { bin: tc.bin, url: mcpUrl, key: apiKey, replace, dryRun, stateDir: cfg.ship.stateDir };
19279
+ const { line, action } = adapter.ensureMcpResult ? adapter.ensureMcpResult(mcpOpts) : { line: adapter.ensureMcp(mcpOpts), action: void 0 };
19280
+ connectEvents.emit("mcp_registered", line, { harness: tc.harness, bin: tc.bin, url: mcpUrl, ...action ? { action } : {} });
19188
19281
  process.stderr.write(`${line}
19189
19282
  `);
19190
19283
  }
@@ -19835,7 +19928,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19835
19928
  };
19836
19929
  const droppedRoutes = [];
19837
19930
  try {
19838
- const rawCfg = parseYaml2(readFileSync28(cfg.configFile, "utf8"));
19931
+ const rawCfg = parseYaml2(readFileSync29(cfg.configFile, "utf8"));
19839
19932
  for (const r of rawCfg?.routes ?? []) {
19840
19933
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
19841
19934
  try {
@@ -19877,8 +19970,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19877
19970
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
19878
19971
  break;
19879
19972
  }
19880
- mkdirSync21(cfg.ship.stateDir, { recursive: true });
19881
- writeFileSync22(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
19973
+ mkdirSync22(cfg.ship.stateDir, { recursive: true });
19974
+ writeFileSync23(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
19882
19975
  `);
19883
19976
  process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
19884
19977
  if (flag("now") && control?.stopCommand && control.startCommand) {
@@ -19929,7 +20022,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19929
20022
  }
19930
20023
  case "log": {
19931
20024
  try {
19932
- const text = readFileSync28(cfg.ship.logFile, "utf8").split("\n");
20025
+ const text = readFileSync29(cfg.ship.logFile, "utf8").split("\n");
19933
20026
  process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
19934
20027
  `);
19935
20028
  } catch {
@@ -19952,7 +20045,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19952
20045
  };
19953
20046
  if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
19954
20047
  const tracker = new Tracker(route, cfg.ship);
19955
- const configText = readFileSync28(cfg.configFile, "utf8");
20048
+ const configText = readFileSync29(cfg.configFile, "utf8");
19956
20049
  const cfgBase = dirname20(cfg.configFile);
19957
20050
  const configured = configuredRepos(configText, route.route, cfgBase);
19958
20051
  const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
@@ -19981,7 +20074,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19981
20074
  }
19982
20075
  if (configured.some((r) => r.dir === dir)) {
19983
20076
  try {
19984
- writeFileSync22(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
20077
+ writeFileSync23(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
19985
20078
  } catch (e) {
19986
20079
  if (e instanceof ReposError) fail(e.message);
19987
20080
  throw e;
@@ -19990,28 +20083,28 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
19990
20083
  if (row) {
19991
20084
  await tracker.deleteRepoRow(row.id);
19992
20085
  if (existsSync34(resolvedFile)) {
19993
- const raw = JSON.parse(readFileSync28(resolvedFile, "utf8"));
20086
+ const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
19994
20087
  if (raw.repoNames) delete raw.repoNames[row.id];
19995
20088
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
19996
- writeFileSync22(resolvedFile, `${JSON.stringify(raw, null, 2)}
20089
+ writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
19997
20090
  `);
19998
20091
  }
19999
20092
  }
20000
- if (existsSync34(join35(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
20093
+ if (existsSync34(join36(dir, ".git")) && basename11(dir) === SAMPLE_REPO) rmSync15(dir, { recursive: true, force: true });
20001
20094
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
20002
20095
  break;
20003
20096
  }
20004
20097
  const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
20005
- const fresh = !existsSync34(join35(dir, ".git"));
20098
+ const fresh = !existsSync34(join36(dir, ".git"));
20006
20099
  if (existsSync34(dir) && fresh && readdirSync14(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
20007
20100
  if (dryRun) {
20008
20101
  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
20102
  break;
20010
20103
  }
20011
20104
  if (fresh) {
20012
- mkdirSync21(dir, { recursive: true });
20013
- writeFileSync22(join35(dir, "README.md"), SAMPLE_README);
20014
- writeFileSync22(join35(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
20105
+ mkdirSync22(dir, { recursive: true });
20106
+ writeFileSync23(join36(dir, "README.md"), SAMPLE_README);
20107
+ writeFileSync23(join36(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
20015
20108
  const g = (...a) => {
20016
20109
  const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
20017
20110
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -20030,16 +20123,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
20030
20123
  });
20031
20124
  repoId = (await tracker.createRepoRow(payload)).id;
20032
20125
  if (existsSync34(resolvedFile)) {
20033
- const raw = JSON.parse(readFileSync28(resolvedFile, "utf8"));
20126
+ const raw = JSON.parse(readFileSync29(resolvedFile, "utf8"));
20034
20127
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
20035
20128
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
20036
- writeFileSync22(resolvedFile, `${JSON.stringify(raw, null, 2)}
20129
+ writeFileSync23(resolvedFile, `${JSON.stringify(raw, null, 2)}
20037
20130
  `);
20038
20131
  }
20039
20132
  }
20040
20133
  if (!configured.some((r) => r.dir === dir)) {
20041
20134
  try {
20042
- writeFileSync22(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
20135
+ writeFileSync23(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
20043
20136
  } catch (e) {
20044
20137
  if (e instanceof ReposError) fail(e.message);
20045
20138
  throw e;
@@ -20393,7 +20486,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
20393
20486
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
20394
20487
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
20395
20488
  try {
20396
- return readFileSync28(p, "utf8");
20489
+ return readFileSync29(p, "utf8");
20397
20490
  } catch {
20398
20491
  return null;
20399
20492
  }