@tablation/crew 0.72.1 → 0.72.3

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