@tablation/crew 0.100.0 → 0.100.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +327 -194
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -7218,6 +7218,12 @@ var Tracker = class {
7218
7218
  if (!model) return [];
7219
7219
  return this.client.records.list(model, { limit: 200 });
7220
7220
  }
7221
+ /** Every `Repos` row with the columns the resolved file mirrors (CREW-2885). */
7222
+ async repoRowsForMap() {
7223
+ const model = this.route.resolved?.reposModelId;
7224
+ if (!model) return [];
7225
+ return this.client.records.list(model, { limit: 200 });
7226
+ }
7221
7227
  /** Delete a `Repos` row (`crew sample-ticket --remove`). */
7222
7228
  async deleteRepoRow(id) {
7223
7229
  const model = this.route.resolved?.reposModelId;
@@ -10685,7 +10691,7 @@ function doctorSelftestLines(stored, toolchains) {
10685
10691
  }
10686
10692
 
10687
10693
  // src/cli.ts
10688
- import { resolve as resolve6, dirname as dirname23, join as join46, basename as basename14 } from "path";
10694
+ import { resolve as resolve6, dirname as dirname24, join as join46, basename as basename14 } from "path";
10689
10695
  import { fileURLToPath } from "url";
10690
10696
  import { hostname as hostname3, homedir as homedir10 } from "os";
10691
10697
  import { spawnSync as spawnSync8 } from "child_process";
@@ -12366,6 +12372,115 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
12366
12372
  }
12367
12373
  }
12368
12374
 
12375
+ // src/repo-refresh.ts
12376
+ import { existsSync as existsSync24, mkdirSync as mkdirSync16, readFileSync as readFileSync23, writeFileSync as writeFileSync18 } from "fs";
12377
+ import { dirname as dirname11 } from "path";
12378
+ var REFRESH_COOLDOWN_MS = 2 * 6e4;
12379
+ var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
12380
+ var lastAsked = /* @__PURE__ */ new Map();
12381
+ function unknownRepoIds(route2, repoIds) {
12382
+ const names = route2.resolved?.repoNames;
12383
+ if (!route2.resolved?.reposModelId || !names) return [];
12384
+ return [...new Set(repoIds.filter((id) => !!id && !(id in names)))];
12385
+ }
12386
+ function applyRepoRows(resolved, rows) {
12387
+ const added = [];
12388
+ const names = resolved.repoNames ??= {};
12389
+ const remotes = resolved.repoRemotes ??= {};
12390
+ const hosts = resolved.repoForgeHosts ??= {};
12391
+ for (const r of rows) {
12392
+ if (!(r.id in names)) added.push(r.id);
12393
+ names[r.id] = r.name ?? "";
12394
+ if (r.remote) remotes[r.id] = r.remote;
12395
+ else delete remotes[r.id];
12396
+ const h = r.forge_host?.trim().toLowerCase();
12397
+ if (h) hosts[r.id] = h;
12398
+ else delete hosts[r.id];
12399
+ }
12400
+ if (Object.keys(remotes).length === 0) delete resolved.repoRemotes;
12401
+ if (Object.keys(hosts).length === 0) delete resolved.repoForgeHosts;
12402
+ return added;
12403
+ }
12404
+ function persistRepoRows(stateDir, routeName, rows) {
12405
+ const path = resolvedPathFor(stateDir, routeName);
12406
+ if (!existsSync24(path)) return;
12407
+ const raw = JSON.parse(readFileSync23(path, "utf8"));
12408
+ applyRepoRows(raw, rows);
12409
+ mkdirSync16(dirname11(path), { recursive: true });
12410
+ writeFileSync18(path, `${JSON.stringify(raw, null, 2)}
12411
+ `);
12412
+ }
12413
+ async function refreshUnknownRepos(o) {
12414
+ const unknown = unknownRepoIds(o.route, o.tickets.map((t) => t.repo_id));
12415
+ if (unknown.length === 0 || !o.route.resolved) return [];
12416
+ const now = o.now ?? Date.now();
12417
+ const last = lastAsked.get(o.route.route);
12418
+ if (!o.force && last !== void 0 && now - last < REFRESH_COOLDOWN_MS) return [];
12419
+ lastAsked.set(o.route.route, now);
12420
+ let rows;
12421
+ try {
12422
+ rows = await o.fetchRows();
12423
+ } catch {
12424
+ return [];
12425
+ }
12426
+ const added = applyRepoRows(o.route.resolved, rows);
12427
+ try {
12428
+ persistRepoRows(o.stateDir, o.route.route, rows);
12429
+ } catch {
12430
+ }
12431
+ return added;
12432
+ }
12433
+ var repoUnservableKey = (route2, issueId) => `repo_unservable:${route2}:${issueId}`;
12434
+ function repoUnservableItem(route2, issueId, repoLabel, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
12435
+ return {
12436
+ kind: "repo_unservable",
12437
+ key: repoUnservableKey(route2, issueId),
12438
+ since: nowIso,
12439
+ message: `${route2}: ${issueId} is skipped \u2014 this ship has no checkout for ${repoLabel}. Resumes automatically once the route lists that repository (\`crew repos add\`) or a Repos row for it exists`
12440
+ };
12441
+ }
12442
+ function trackUnservable(streaks, unservable) {
12443
+ const now = new Set(unservable);
12444
+ const recovered = [];
12445
+ for (const id of [...streaks.keys()]) {
12446
+ if (!now.has(id)) {
12447
+ streaks.delete(id);
12448
+ recovered.push(id);
12449
+ }
12450
+ }
12451
+ const raise = [];
12452
+ for (const id of now) {
12453
+ const n = (streaks.get(id) ?? 0) + 1;
12454
+ streaks.set(id, n);
12455
+ if (n >= UNSERVABLE_PASSES_BEFORE_ATTENTION) raise.push(id);
12456
+ }
12457
+ return { raise, recovered };
12458
+ }
12459
+ var streaksByRoute = /* @__PURE__ */ new Map();
12460
+ async function reportUnservable(o, unservable, repoLabelOf) {
12461
+ const streaks = streaksByRoute.get(o.route.route) ?? /* @__PURE__ */ new Map();
12462
+ streaksByRoute.set(o.route.route, streaks);
12463
+ const { raise, recovered } = trackUnservable(streaks, unservable);
12464
+ for (const id of recovered) {
12465
+ try {
12466
+ clearShipAttention(o.state, repoUnservableKey(o.route.route, id));
12467
+ } catch {
12468
+ }
12469
+ }
12470
+ for (const id of raise) {
12471
+ try {
12472
+ await raiseShipAttention(o, repoUnservableItem(o.route.route, id, repoLabelOf(id)));
12473
+ } catch {
12474
+ }
12475
+ }
12476
+ }
12477
+ function repoLabelFor(route2, ticket) {
12478
+ const id = ticket?.repo_id;
12479
+ if (!id) return "a repository (the ticket names none)";
12480
+ const name = route2.resolved?.repoNames?.[id];
12481
+ return name ? `repository "${name}"` : `repository ${id} (no Repos row by that id)`;
12482
+ }
12483
+
12369
12484
  // src/conflict.ts
12370
12485
  import { StaleWriteError as StaleWriteError7 } from "@tablation/client";
12371
12486
  var BOUNCE_MARKER = calloutHeader("WARNING", "Merge Conflict");
@@ -12742,13 +12857,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
12742
12857
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
12743
12858
 
12744
12859
  // src/connect-wizard.ts
12745
- import { delimiter, dirname as dirname11, join as join27 } from "path";
12746
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync24, mkdirSync as mkdirSync16, writeFileSync as writeFileSync18 } from "fs";
12860
+ import { delimiter, dirname as dirname12, join as join27 } from "path";
12861
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync25, mkdirSync as mkdirSync17, writeFileSync as writeFileSync19 } from "fs";
12747
12862
  import { stringify } from "yaml";
12748
12863
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
12749
12864
  function shouldRunWizard(opts) {
12750
12865
  if (!opts.isTTY) return false;
12751
- const exists = opts.configExists ?? existsSync24;
12866
+ const exists = opts.configExists ?? existsSync25;
12752
12867
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
12753
12868
  }
12754
12869
  function firstRunConfigPath() {
@@ -12791,9 +12906,9 @@ async function runWizard(p, env) {
12791
12906
  if (entered && isExec(entered)) agentBin = entered;
12792
12907
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
12793
12908
  }
12794
- const nodeDir = dirname11(env.nodePath);
12909
+ const nodeDir = dirname12(env.nodePath);
12795
12910
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
12796
- const baseDirs = [nodeDir, pnpmPath ? dirname11(pnpmPath) : void 0];
12911
+ const baseDirs = [nodeDir, pnpmPath ? dirname12(pnpmPath) : void 0];
12797
12912
  p.say(
12798
12913
  `
12799
12914
  Host Passengers lets this machine run the agent sessions that other people's
@@ -12804,7 +12919,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
12804
12919
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
12805
12920
  }
12806
12921
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
12807
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname11(dockerPath) : void 0]);
12922
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname12(dockerPath) : void 0]);
12808
12923
  if (extraPath) {
12809
12924
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
12810
12925
  }
@@ -12826,9 +12941,9 @@ routes:
12826
12941
  ${routeBlock}`;
12827
12942
  }
12828
12943
  function writeNewConfig(path, text2) {
12829
- mkdirSync16(dirname11(path), { recursive: true });
12944
+ mkdirSync17(dirname12(path), { recursive: true });
12830
12945
  try {
12831
- writeFileSync18(path, text2, { mode: 384, flag: "wx" });
12946
+ writeFileSync19(path, text2, { mode: 384, flag: "wx" });
12832
12947
  return true;
12833
12948
  } catch (e) {
12834
12949
  if (e.code === "EEXIST") return false;
@@ -12862,7 +12977,7 @@ Wrote ${o.configPath}.
12862
12977
  // src/connect-json.ts
12863
12978
  import { accessSync as accessSync4, constants as constants4 } from "fs";
12864
12979
  import { homedir as homedir8 } from "os";
12865
- import { dirname as dirname12, join as join28 } from "path";
12980
+ import { dirname as dirname13, join as join28 } from "path";
12866
12981
  import { delimiter as delimiter2 } from "path";
12867
12982
  var CHOICE_EXIT_CODE = 3;
12868
12983
  function createEmitter(json2, write) {
@@ -12928,7 +13043,7 @@ function flagWizardAnswers(f, env) {
12928
13043
  const agentBin = f.agentBin ?? found?.bin;
12929
13044
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
12930
13045
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
12931
- const extraPath = f.extraPath ?? deriveExtraPath([dirname12(env.nodePath), pnpmPath ? dirname12(pnpmPath) : void 0, dockerPath ? dirname12(dockerPath) : void 0]);
13046
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname13(env.nodePath), pnpmPath ? dirname13(pnpmPath) : void 0, dockerPath ? dirname13(dockerPath) : void 0]);
12932
13047
  return {
12933
13048
  answers: {
12934
13049
  name: f.shipName?.trim() || env.hostname,
@@ -12943,7 +13058,7 @@ function flagWizardAnswers(f, env) {
12943
13058
 
12944
13059
  // src/provision.ts
12945
13060
  import { spawnSync as spawnSync6 } from "child_process";
12946
- import { existsSync as existsSync25, readFileSync as readFileSync23, writeFileSync as writeFileSync19 } from "fs";
13061
+ import { existsSync as existsSync26, readFileSync as readFileSync24, writeFileSync as writeFileSync20 } from "fs";
12947
13062
  import { hostname as hostname2 } from "os";
12948
13063
  import { parseDocument as parseDocument3 } from "yaml";
12949
13064
 
@@ -13460,7 +13575,7 @@ function realProvisionDeps(crewHome) {
13460
13575
  };
13461
13576
  const configFile = () => {
13462
13577
  const f = findConfigFile(crewHome);
13463
- return existsSync25(f) ? f : null;
13578
+ return existsSync26(f) ? f : null;
13464
13579
  };
13465
13580
  return {
13466
13581
  crew: spawnCrew,
@@ -13468,13 +13583,13 @@ function realProvisionDeps(crewHome) {
13468
13583
  enableRoute(route2) {
13469
13584
  const file = configFile();
13470
13585
  if (!file) return false;
13471
- const text2 = readFileSync23(file, "utf8");
13586
+ const text2 = readFileSync24(file, "utf8");
13472
13587
  const doc = parseDocument3(text2);
13473
13588
  const routes = doc.get("routes");
13474
13589
  const entry = routes?.items?.find((r) => r.get("route") === route2);
13475
13590
  if (!entry) throw new Error(`route ${route2} is not in ${file}`);
13476
13591
  if (entry.get("enabled") === true) return false;
13477
- writeFileSync19(file, planEnable(text2, route2));
13592
+ writeFileSync20(file, planEnable(text2, route2));
13478
13593
  return true;
13479
13594
  },
13480
13595
  installState() {
@@ -13494,14 +13609,14 @@ function realProvisionDeps(crewHome) {
13494
13609
  continue;
13495
13610
  }
13496
13611
  for (const p of plan.unitPaths) {
13497
- if (!existsSync25(p) || readFileSync23(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
13612
+ if (!existsSync26(p) || readFileSync24(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
13498
13613
  }
13499
13614
  }
13500
13615
  const label = labelFor(crewHome, "run");
13501
13616
  let running = false;
13502
13617
  let lingering = true;
13503
13618
  if (mechanism === "systemd") {
13504
- const system = ctx.root || existsSync25(systemUnitPathFor(label));
13619
+ const system = ctx.root || existsSync26(systemUnitPathFor(label));
13505
13620
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
13506
13621
  running = spawnSync6(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
13507
13622
  if (!system) {
@@ -13528,8 +13643,8 @@ host=${host}
13528
13643
  }
13529
13644
 
13530
13645
  // src/release-run.ts
13531
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync20, existsSync as existsSync26, copyFileSync, mkdirSync as mkdirSync17, mkdtempSync as mkdtempSync2, rmSync as rmSync14 } from "fs";
13532
- import { dirname as dirname14, join as join30 } from "path";
13646
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync21, existsSync as existsSync27, copyFileSync, mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync2, rmSync as rmSync14 } from "fs";
13647
+ import { dirname as dirname15, join as join30 } from "path";
13533
13648
  import { tmpdir as tmpdir3 } from "os";
13534
13649
 
13535
13650
  // src/release.ts
@@ -14272,11 +14387,11 @@ async function bump(o, size) {
14272
14387
  }
14273
14388
  const files = o.repo.release.versionFiles.map((f) => join30(o.cwd, f));
14274
14389
  const first = files[0];
14275
- if (!existsSync26(first)) {
14390
+ if (!existsSync27(first)) {
14276
14391
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
14277
14392
  return null;
14278
14393
  }
14279
- const current = JSON.parse(readFileSync24(first, "utf8")).version;
14394
+ const current = JSON.parse(readFileSync25(first, "utf8")).version;
14280
14395
  if (!current) {
14281
14396
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
14282
14397
  return null;
@@ -14289,13 +14404,13 @@ async function bump(o, size) {
14289
14404
  return version;
14290
14405
  }
14291
14406
  for (const f of files) {
14292
- const src = readFileSync24(f, "utf8");
14407
+ const src = readFileSync25(f, "utf8");
14293
14408
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
14294
14409
  if (out === src) {
14295
14410
  o.emit.error(`no version field in ${f}`);
14296
14411
  return null;
14297
14412
  }
14298
- writeFileSync20(f, out);
14413
+ writeFileSync21(f, out);
14299
14414
  }
14300
14415
  return version;
14301
14416
  }
@@ -14308,8 +14423,8 @@ function writeChangelog(o, version, entries) {
14308
14423
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
14309
14424
  return;
14310
14425
  }
14311
- const existing = existsSync26(path) ? readFileSync24(path, "utf8") : "# Changelog\n";
14312
- writeFileSync20(path, insertChangelogSection(existing, section));
14426
+ const existing = existsSync27(path) ? readFileSync25(path, "utf8") : "# Changelog\n";
14427
+ writeFileSync21(path, insertChangelogSection(existing, section));
14313
14428
  }
14314
14429
  async function confirm(o, expected) {
14315
14430
  if (!o.repo.hooks.released) return void 0;
@@ -14414,8 +14529,8 @@ function openReleaseCheckout(o) {
14414
14529
  }
14415
14530
  for (const f of o.repo.worktrees.copy) {
14416
14531
  const src = join30(o.cwd, f);
14417
- if (!existsSync26(src)) continue;
14418
- mkdirSync17(dirname14(join30(dir, f)), { recursive: true });
14532
+ if (!existsSync27(src)) continue;
14533
+ mkdirSync18(dirname15(join30(dir, f)), { recursive: true });
14419
14534
  copyFileSync(src, join30(dir, f));
14420
14535
  }
14421
14536
  return { dir, branch, close };
@@ -15117,11 +15232,11 @@ Passed: ${passed}.${willClose ? " Closing this alert." : ""}`,
15117
15232
  }
15118
15233
 
15119
15234
  // src/live-runs.ts
15120
- import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
15235
+ import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
15121
15236
  import { join as join32 } from "path";
15122
15237
 
15123
15238
  // src/interrupted.ts
15124
- import { existsSync as existsSync27, readdirSync as readdirSync10, readFileSync as readFileSync25 } from "fs";
15239
+ import { existsSync as existsSync28, readdirSync as readdirSync10, readFileSync as readFileSync26 } from "fs";
15125
15240
  import { join as join31 } from "path";
15126
15241
  var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
15127
15242
  function interruptedComment(b) {
@@ -15141,16 +15256,16 @@ var pidAlive = (pid) => {
15141
15256
  };
15142
15257
  function runFinished(streamPath) {
15143
15258
  const path = `${streamPath}.meta.json`;
15144
- if (!existsSync27(path)) return false;
15259
+ if (!existsSync28(path)) return false;
15145
15260
  try {
15146
- return JSON.parse(readFileSync25(path, "utf8")).running !== true;
15261
+ return JSON.parse(readFileSync26(path, "utf8")).running !== true;
15147
15262
  } catch {
15148
15263
  return true;
15149
15264
  }
15150
15265
  }
15151
15266
  function orphanedStreams(o) {
15152
15267
  const dir = join31(o.stateDir, "streams");
15153
- if (!existsSync27(dir)) return [];
15268
+ if (!existsSync28(dir)) return [];
15154
15269
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
15155
15270
  const out = [];
15156
15271
  for (const name of readdirSync10(dir)) {
@@ -15210,7 +15325,7 @@ async function reconcileRuns(o) {
15210
15325
  // src/live-runs.ts
15211
15326
  function liveRuns(stateDir, isAlive = runIsAlive) {
15212
15327
  const dir = join32(stateDir, "streams");
15213
- if (!existsSync28(dir)) return [];
15328
+ if (!existsSync29(dir)) return [];
15214
15329
  const out = [];
15215
15330
  for (const name of readdirSync11(dir)) {
15216
15331
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
@@ -15549,7 +15664,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
15549
15664
  }
15550
15665
 
15551
15666
  // src/watch.ts
15552
- import { watch as watchFile, existsSync as existsSync29, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
15667
+ import { watch as watchFile, existsSync as existsSync30, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
15553
15668
  var E = "\x1B[";
15554
15669
  var dim = (s) => `${E}2m${s}${E}0m`;
15555
15670
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -15650,7 +15765,7 @@ function renderHeaders(states, now = Date.now()) {
15650
15765
  return states.map((s) => renderHeader(s, now)).join("");
15651
15766
  }
15652
15767
  function readFrom(file, from) {
15653
- if (!existsSync29(file)) return { events: [], offset: from };
15768
+ if (!existsSync30(file)) return { events: [], offset: from };
15654
15769
  const size = statSync9(file).size;
15655
15770
  if (size < from) return readFrom(file, 0);
15656
15771
  if (size === from) return { events: [], offset: from };
@@ -15893,15 +16008,15 @@ function nextRoles(decision, ran) {
15893
16008
  }
15894
16009
 
15895
16010
  // src/ship-name.ts
15896
- import { existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync3, writeFileSync as writeFileSync22 } from "fs";
15897
- import { dirname as dirname16 } from "path";
16011
+ import { existsSync as existsSync32, mkdirSync as mkdirSync20, readFileSync as readFileSync28, renameSync as renameSync3, writeFileSync as writeFileSync23 } from "fs";
16012
+ import { dirname as dirname17 } from "path";
15898
16013
  import { parseDocument as parseDocument5 } from "yaml";
15899
16014
 
15900
16015
  // src/machine-id.ts
15901
16016
  import { execFileSync as execFileSync5 } from "child_process";
15902
16017
  import { createHash as createHash5 } from "crypto";
15903
- import { existsSync as existsSync30, mkdirSync as mkdirSync18, readFileSync as readFileSync26, writeFileSync as writeFileSync21 } from "fs";
15904
- import { dirname as dirname15, join as join33 } from "path";
16018
+ import { existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync27, writeFileSync as writeFileSync22 } from "fs";
16019
+ import { dirname as dirname16, join as join33 } from "path";
15905
16020
  function machineIdPathFor(stateDir) {
15906
16021
  return join33(stateDir, "machine-id");
15907
16022
  }
@@ -15916,8 +16031,8 @@ function readRawMachineId(platform2 = process.platform) {
15916
16031
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
15917
16032
  }
15918
16033
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
15919
- if (existsSync30(p)) {
15920
- const id = readFileSync26(p, "utf8").trim();
16034
+ if (existsSync31(p)) {
16035
+ const id = readFileSync27(p, "utf8").trim();
15921
16036
  if (id) return id;
15922
16037
  }
15923
16038
  }
@@ -15928,15 +16043,15 @@ function readRawMachineId(platform2 = process.platform) {
15928
16043
  function ensureMachineId(stateDir, opts = {}) {
15929
16044
  if (opts.override?.trim()) return sha256(opts.override.trim());
15930
16045
  const path = machineIdPathFor(stateDir);
15931
- if (existsSync30(path)) {
15932
- const cached = readFileSync26(path, "utf8").trim();
16046
+ if (existsSync31(path)) {
16047
+ const cached = readFileSync27(path, "utf8").trim();
15933
16048
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
15934
16049
  }
15935
16050
  const raw = (opts.read ?? readRawMachineId)();
15936
16051
  if (!raw) return void 0;
15937
16052
  const hashed = sha256(raw);
15938
- mkdirSync18(dirname15(path), { recursive: true });
15939
- writeFileSync21(path, `${hashed}
16053
+ mkdirSync19(dirname16(path), { recursive: true });
16054
+ writeFileSync22(path, `${hashed}
15940
16055
  `, { mode: 384 });
15941
16056
  return hashed;
15942
16057
  }
@@ -16007,34 +16122,34 @@ async function syncRouteName(i) {
16007
16122
  return { status: "ok", ...base, action, name: i.local };
16008
16123
  }
16009
16124
  function readSyncedName(resolvedFile) {
16010
- if (!existsSync31(resolvedFile)) return void 0;
16125
+ if (!existsSync32(resolvedFile)) return void 0;
16011
16126
  try {
16012
- const v = JSON.parse(readFileSync27(resolvedFile, "utf8")).shipName;
16127
+ const v = JSON.parse(readFileSync28(resolvedFile, "utf8")).shipName;
16013
16128
  return typeof v === "string" ? v : void 0;
16014
16129
  } catch {
16015
16130
  return void 0;
16016
16131
  }
16017
16132
  }
16018
16133
  function writeSyncedName(resolvedFile, name) {
16019
- if (!existsSync31(resolvedFile)) return;
16134
+ if (!existsSync32(resolvedFile)) return;
16020
16135
  let raw;
16021
16136
  try {
16022
- raw = JSON.parse(readFileSync27(resolvedFile, "utf8"));
16137
+ raw = JSON.parse(readFileSync28(resolvedFile, "utf8"));
16023
16138
  } catch {
16024
16139
  return;
16025
16140
  }
16026
16141
  if (raw.shipName === name) return;
16027
16142
  raw.shipName = name;
16028
- mkdirSync19(dirname16(resolvedFile), { recursive: true });
16143
+ mkdirSync20(dirname17(resolvedFile), { recursive: true });
16029
16144
  const tmp = `${resolvedFile}.tmp${process.pid}`;
16030
- writeFileSync22(tmp, `${JSON.stringify(raw, null, 2)}
16145
+ writeFileSync23(tmp, `${JSON.stringify(raw, null, 2)}
16031
16146
  `);
16032
16147
  renameSync3(tmp, resolvedFile);
16033
16148
  }
16034
16149
  async function reconcileShipName(i) {
16035
16150
  let local = i.ship.name;
16036
16151
  try {
16037
- local = shipNameOfYaml(readFileSync27(i.configFile, "utf8")) ?? local;
16152
+ local = shipNameOfYaml(readFileSync28(i.configFile, "utf8")) ?? local;
16038
16153
  } catch {
16039
16154
  }
16040
16155
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -16058,8 +16173,8 @@ async function reconcileShipName(i) {
16058
16173
  }
16059
16174
  if (result.status !== "ok") return result;
16060
16175
  if (result.action === "adopt" || result.action === "row-wins") {
16061
- const text2 = readFileSync27(i.configFile, "utf8");
16062
- writeFileSync22(i.configFile, setShipNameInYaml(text2, result.name));
16176
+ const text2 = readFileSync28(i.configFile, "utf8");
16177
+ writeFileSync23(i.configFile, setShipNameInYaml(text2, result.name));
16063
16178
  i.ship.name = result.name;
16064
16179
  if (result.action === "row-wins") {
16065
16180
  i.emit?.warn(
@@ -16715,8 +16830,8 @@ function describeCapability(i) {
16715
16830
  }
16716
16831
 
16717
16832
  // src/agents-sync-run.ts
16718
- import { existsSync as existsSync32, mkdirSync as mkdirSync20, readFileSync as readFileSync28, writeFileSync as writeFileSync23 } from "fs";
16719
- import { dirname as dirname17 } from "path";
16833
+ import { existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "fs";
16834
+ import { dirname as dirname18 } from "path";
16720
16835
  async function runAgentsSync(target, o) {
16721
16836
  const dryRun = o.dryRun ?? false;
16722
16837
  const force = o.force ?? false;
@@ -16749,12 +16864,12 @@ async function runAgentsSync(target, o) {
16749
16864
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
16750
16865
  if (!dryRun) {
16751
16866
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
16752
- const raw = existsSync32(resolvedPath) ? JSON.parse(readFileSync28(resolvedPath, "utf8")) : {};
16867
+ const raw = existsSync33(resolvedPath) ? JSON.parse(readFileSync29(resolvedPath, "utf8")) : {};
16753
16868
  raw.agentPersonas = personaResult.agentPersonas;
16754
16869
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
16755
16870
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
16756
- mkdirSync20(dirname17(resolvedPath), { recursive: true });
16757
- writeFileSync23(resolvedPath, `${JSON.stringify(raw, null, 2)}
16871
+ mkdirSync21(dirname18(resolvedPath), { recursive: true });
16872
+ writeFileSync24(resolvedPath, `${JSON.stringify(raw, null, 2)}
16758
16873
  `);
16759
16874
  if (target.resolved) {
16760
16875
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -16858,7 +16973,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
16858
16973
 
16859
16974
  // src/repos-check.ts
16860
16975
  import { spawnSync as spawnSync7 } from "child_process";
16861
- import { readFileSync as readFileSync29 } from "fs";
16976
+ import { readFileSync as readFileSync30 } from "fs";
16862
16977
  import { basename as basename9 } from "path";
16863
16978
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
16864
16979
  function checkHookSyntax(script, shell) {
@@ -16899,7 +17014,7 @@ function checkRepoSpec(dir, opts = {}) {
16899
17014
  }
16900
17015
  let parsed;
16901
17016
  try {
16902
- parsed = parseRepoConfig(readFileSync29(file, "utf8"), file);
17017
+ parsed = parseRepoConfig(readFileSync30(file, "utf8"), file);
16903
17018
  } catch (e) {
16904
17019
  if (!(e instanceof RepoConfigError)) throw e;
16905
17020
  return { ...empty, file, error: e.message, exitCode: 1 };
@@ -17346,7 +17461,7 @@ ${run2.err}` });
17346
17461
  }
17347
17462
 
17348
17463
  // src/sample-ticket.ts
17349
- import { basename as basename10, dirname as dirname18, join as join34 } from "path";
17464
+ import { basename as basename10, dirname as dirname19, join as join34 } from "path";
17350
17465
  var SAMPLE_REPO = "crew-sample";
17351
17466
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
17352
17467
  var SAMPLE_MARKER = "sample: true";
@@ -17369,7 +17484,7 @@ has something safe to work on: its test and build hooks always pass, it has no
17369
17484
  remote, and \`crew sample-ticket --remove\` deletes it.
17370
17485
  `;
17371
17486
  function sampleDir(existing, reposBase, workspace) {
17372
- if (existing.length > 0) return join34(dirname18(existing[0]), SAMPLE_REPO);
17487
+ if (existing.length > 0) return join34(dirname19(existing[0]), SAMPLE_REPO);
17373
17488
  return join34(reposBase, workspace, SAMPLE_REPO);
17374
17489
  }
17375
17490
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
@@ -17414,8 +17529,8 @@ function planSampleTicket(i) {
17414
17529
  }
17415
17530
 
17416
17531
  // src/worktree-sweep.ts
17417
- import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync12 } from "fs";
17418
- import { basename as basename11, dirname as dirname19, join as join35, resolve as resolve4 } from "path";
17532
+ import { existsSync as existsSync34, readFileSync as readFileSync31, readdirSync as readdirSync12 } from "fs";
17533
+ import { basename as basename11, dirname as dirname20, join as join35, resolve as resolve4 } from "path";
17419
17534
  function planWorktreeSweep(target, tickets, contract) {
17420
17535
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
17421
17536
  const verified = contract.statuses.verified;
@@ -17479,7 +17594,7 @@ function worktreeTicketNumbers(target) {
17479
17594
  return [...nums];
17480
17595
  }
17481
17596
  function siblingWorktreeDirs(target) {
17482
- const parent = dirname19(target.dir.replace(/[/\\]+$/, ""));
17597
+ const parent = dirname20(target.dir.replace(/[/\\]+$/, ""));
17483
17598
  const stem = `${basename11(target.dir.replace(/[/\\]+$/, ""))}-`;
17484
17599
  const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
17485
17600
  let names;
@@ -17527,7 +17642,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
17527
17642
  let removed = 0;
17528
17643
  let keptBranches = 0;
17529
17644
  for (const a of actions) {
17530
- if (!dryRun && base && !a.keepBranch && existsSync33(a.path) && isHandBackMerge(a.path, base)) {
17645
+ if (!dryRun && base && !a.keepBranch && existsSync34(a.path) && isHandBackMerge(a.path, base)) {
17531
17646
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
17532
17647
  log.emit(
17533
17648
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -17535,7 +17650,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
17535
17650
  );
17536
17651
  }
17537
17652
  }
17538
- if (!dryRun && existsSync33(a.path)) {
17653
+ if (!dryRun && existsSync34(a.path)) {
17539
17654
  let dirty = false;
17540
17655
  try {
17541
17656
  dirty = status(a.path).length > 0;
@@ -17546,7 +17661,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
17546
17661
  continue;
17547
17662
  }
17548
17663
  }
17549
- if (!dryRun && a.requirePushed && a.branch && existsSync33(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
17664
+ if (!dryRun && a.requirePushed && a.branch && existsSync34(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
17550
17665
  refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
17551
17666
  continue;
17552
17667
  }
@@ -17595,7 +17710,7 @@ function isHandBackMerge(path, base) {
17595
17710
  if (!msgPath) return false;
17596
17711
  let first;
17597
17712
  try {
17598
- first = readFileSync30(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
17713
+ first = readFileSync31(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
17599
17714
  } catch {
17600
17715
  return false;
17601
17716
  }
@@ -17700,12 +17815,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
17700
17815
  }
17701
17816
 
17702
17817
  // src/stream-sweep.ts
17703
- import { existsSync as existsSync34, readdirSync as readdirSync13, rmSync as rmSync15, statSync as statSync10 } from "fs";
17818
+ import { existsSync as existsSync35, readdirSync as readdirSync13, rmSync as rmSync15, statSync as statSync10 } from "fs";
17704
17819
  import { join as join36 } from "path";
17705
17820
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
17706
17821
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
17707
17822
  const dir = join36(stateDir, "streams");
17708
- if (!existsSync34(dir)) return [];
17823
+ if (!existsSync35(dir)) return [];
17709
17824
  const cutoff = now - retentionDays * DAY_MS2;
17710
17825
  return readdirSync13(dir).map((name) => join36(dir, name)).filter((path) => {
17711
17826
  try {
@@ -17733,7 +17848,7 @@ function applyStreamSweep(paths, dryRun, log) {
17733
17848
  return removed;
17734
17849
  }
17735
17850
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
17736
- if (!existsSync34(stateDir)) return [];
17851
+ if (!existsSync35(stateDir)) return [];
17737
17852
  const cutoff = now - retentionDays * DAY_MS2;
17738
17853
  return readdirSync13(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join36(stateDir, name)).filter((path) => {
17739
17854
  try {
@@ -17745,11 +17860,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
17745
17860
  }
17746
17861
 
17747
17862
  // src/cli.ts
17748
- import { realpathSync as realpathSync3, readFileSync as readFileSync37, existsSync as existsSync42, mkdirSync as mkdirSync27, writeFileSync as writeFileSync29, rmSync as rmSync20, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync4, statSync as statSync14, readdirSync as readdirSync19 } from "fs";
17863
+ import { realpathSync as realpathSync3, readFileSync as readFileSync38, existsSync as existsSync43, mkdirSync as mkdirSync28, writeFileSync as writeFileSync30, rmSync as rmSync20, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync4, statSync as statSync14, readdirSync as readdirSync19 } from "fs";
17749
17864
  import { createInterface } from "readline/promises";
17750
17865
 
17751
17866
  // src/periscope-cmd.ts
17752
- import { existsSync as existsSync35, mkdirSync as mkdirSync21, readFileSync as readFileSync31, rmSync as rmSync16, writeFileSync as writeFileSync24 } from "fs";
17867
+ import { existsSync as existsSync36, mkdirSync as mkdirSync22, readFileSync as readFileSync32, rmSync as rmSync16, writeFileSync as writeFileSync25 } from "fs";
17753
17868
  import { join as join37 } from "path";
17754
17869
  var PeriscopeError = class extends Error {
17755
17870
  };
@@ -17767,15 +17882,15 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
17767
17882
  }
17768
17883
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
17769
17884
  try {
17770
- mkdirSync21(priorDir(stateDir), { recursive: true });
17771
- writeFileSync24(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
17885
+ mkdirSync22(priorDir(stateDir), { recursive: true });
17886
+ writeFileSync25(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
17772
17887
  } catch {
17773
17888
  }
17774
17889
  }
17775
17890
  function readPeriscopePrior(stateDir, ticketRecordId) {
17776
17891
  try {
17777
17892
  const p = priorPath(stateDir, ticketRecordId);
17778
- return existsSync35(p) ? JSON.parse(readFileSync31(p, "utf8")) : void 0;
17893
+ return existsSync36(p) ? JSON.parse(readFileSync32(p, "utf8")) : void 0;
17779
17894
  } catch {
17780
17895
  return void 0;
17781
17896
  }
@@ -18270,8 +18385,8 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
18270
18385
 
18271
18386
  // src/ssh-keys.ts
18272
18387
  import { execFileSync as execFileSync8 } from "child_process";
18273
- import { existsSync as existsSync36, mkdirSync as mkdirSync22, readFileSync as readFileSync32, chmodSync as chmodSync3 } from "fs";
18274
- import { dirname as dirname20, join as join39 } from "path";
18388
+ import { existsSync as existsSync37, mkdirSync as mkdirSync23, readFileSync as readFileSync33, chmodSync as chmodSync3 } from "fs";
18389
+ import { dirname as dirname21, join as join39 } from "path";
18275
18390
  var SshKeyError = class extends Error {
18276
18391
  };
18277
18392
  function shipSshKeyPathFor(stateDir) {
@@ -18280,8 +18395,8 @@ function shipSshKeyPathFor(stateDir) {
18280
18395
  function ensureShipSshKeypair(stateDir) {
18281
18396
  const privateKeyPath = shipSshKeyPathFor(stateDir);
18282
18397
  const publicKeyPath = `${privateKeyPath}.pub`;
18283
- if (!existsSync36(privateKeyPath)) {
18284
- mkdirSync22(dirname20(privateKeyPath), { recursive: true });
18398
+ if (!existsSync37(privateKeyPath)) {
18399
+ mkdirSync23(dirname21(privateKeyPath), { recursive: true });
18285
18400
  try {
18286
18401
  execFileSync8("ssh-keygen", [
18287
18402
  "-t",
@@ -18301,10 +18416,10 @@ function ensureShipSshKeypair(stateDir) {
18301
18416
  }
18302
18417
  chmodSync3(privateKeyPath, 384);
18303
18418
  }
18304
- if (!existsSync36(publicKeyPath)) {
18419
+ if (!existsSync37(publicKeyPath)) {
18305
18420
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
18306
18421
  }
18307
- const publicKey = readFileSync32(publicKeyPath, "utf8").trim();
18422
+ const publicKey = readFileSync33(publicKeyPath, "utf8").trim();
18308
18423
  return { privateKeyPath, publicKeyPath, publicKey };
18309
18424
  }
18310
18425
  function sshKeygenAvailable() {
@@ -18542,7 +18657,7 @@ function syncAllPassengerCheckouts(cfg) {
18542
18657
 
18543
18658
  // src/tunnel.ts
18544
18659
  import { spawn as spawn5 } from "child_process";
18545
- import { existsSync as existsSync37, mkdirSync as mkdirSync23, readFileSync as readFileSync33, writeFileSync as writeFileSync25, unlinkSync as unlinkSync3, readdirSync as readdirSync14 } from "fs";
18660
+ import { existsSync as existsSync38, mkdirSync as mkdirSync24, readFileSync as readFileSync34, writeFileSync as writeFileSync26, unlinkSync as unlinkSync3, readdirSync as readdirSync14 } from "fs";
18546
18661
  import { randomInt } from "crypto";
18547
18662
  import { join as join40 } from "path";
18548
18663
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
@@ -18620,17 +18735,17 @@ function tunnelStatePath(stateDir, workspaceId) {
18620
18735
  }
18621
18736
  function readPersistedTunnel(stateDir, workspaceId) {
18622
18737
  const path = tunnelStatePath(stateDir, workspaceId);
18623
- if (!existsSync37(path)) return void 0;
18738
+ if (!existsSync38(path)) return void 0;
18624
18739
  try {
18625
- return JSON.parse(readFileSync33(path, "utf8"));
18740
+ return JSON.parse(readFileSync34(path, "utf8"));
18626
18741
  } catch {
18627
18742
  return void 0;
18628
18743
  }
18629
18744
  }
18630
18745
  function writePersistedTunnel(stateDir, state) {
18631
18746
  const path = tunnelStatePath(stateDir, state.workspaceId);
18632
- mkdirSync23(join40(stateDir, "passengers"), { recursive: true });
18633
- writeFileSync25(path, `${JSON.stringify(state, null, 2)}
18747
+ mkdirSync24(join40(stateDir, "passengers"), { recursive: true });
18748
+ writeFileSync26(path, `${JSON.stringify(state, null, 2)}
18634
18749
  `);
18635
18750
  }
18636
18751
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -18733,7 +18848,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
18733
18848
  }
18734
18849
  function readAllPersistedWorkspaceIds(stateDir) {
18735
18850
  const dir = join40(stateDir, "passengers");
18736
- if (!existsSync37(dir)) return [];
18851
+ if (!existsSync38(dir)) return [];
18737
18852
  return readdirSync14(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
18738
18853
  }
18739
18854
 
@@ -18854,7 +18969,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
18854
18969
  import { createServer as createHttpServer } from "http";
18855
18970
  import { spawn as spawn6 } from "child_process";
18856
18971
  import { randomBytes } from "crypto";
18857
- import { existsSync as existsSync38, mkdirSync as mkdirSync24, readFileSync as readFileSync34, writeFileSync as writeFileSync26, unlinkSync as unlinkSync4, readdirSync as readdirSync15 } from "fs";
18972
+ import { existsSync as existsSync39, mkdirSync as mkdirSync25, readFileSync as readFileSync35, writeFileSync as writeFileSync27, unlinkSync as unlinkSync4, readdirSync as readdirSync15 } from "fs";
18858
18973
  import { join as join41 } from "path";
18859
18974
 
18860
18975
  // src/secret-compare.ts
@@ -18948,17 +19063,17 @@ function syncDaemonStatePath(stateDir, workspaceId) {
18948
19063
  }
18949
19064
  function readPersistedSyncDaemon(stateDir, workspaceId) {
18950
19065
  const path = syncDaemonStatePath(stateDir, workspaceId);
18951
- if (!existsSync38(path)) return void 0;
19066
+ if (!existsSync39(path)) return void 0;
18952
19067
  try {
18953
- return JSON.parse(readFileSync34(path, "utf8"));
19068
+ return JSON.parse(readFileSync35(path, "utf8"));
18954
19069
  } catch {
18955
19070
  return void 0;
18956
19071
  }
18957
19072
  }
18958
19073
  function writePersistedSyncDaemon(stateDir, state) {
18959
19074
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
18960
- mkdirSync24(join41(stateDir, "passengers"), { recursive: true });
18961
- writeFileSync26(path, `${JSON.stringify(state, null, 2)}
19075
+ mkdirSync25(join41(stateDir, "passengers"), { recursive: true });
19076
+ writeFileSync27(path, `${JSON.stringify(state, null, 2)}
18962
19077
  `);
18963
19078
  }
18964
19079
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -18979,7 +19094,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
18979
19094
  }
18980
19095
  function readAllPersistedSyncWorkspaceIds(stateDir) {
18981
19096
  const dir = join41(stateDir, "passengers");
18982
- if (!existsSync38(dir)) return [];
19097
+ if (!existsSync39(dir)) return [];
18983
19098
  const suffix = "-sync.json";
18984
19099
  return readdirSync15(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
18985
19100
  }
@@ -19067,7 +19182,7 @@ function describeCause(err3) {
19067
19182
  }
19068
19183
 
19069
19184
  // src/daemon.ts
19070
- import { existsSync as existsSync39, mkdirSync as mkdirSync25, readFileSync as readFileSync35, readdirSync as readdirSync16, rmSync as rmSync17, statSync as statSync12, writeFileSync as writeFileSync27 } from "fs";
19185
+ import { existsSync as existsSync40, mkdirSync as mkdirSync26, readFileSync as readFileSync36, readdirSync as readdirSync16, rmSync as rmSync17, statSync as statSync12, writeFileSync as writeFileSync28 } from "fs";
19071
19186
  import { join as join42 } from "path";
19072
19187
  import { randomBytes as randomBytes2 } from "crypto";
19073
19188
  var DEFAULT_FLOOR_MS = 5e3;
@@ -19149,7 +19264,7 @@ function computeTreeSignature(paths) {
19149
19264
  }
19150
19265
  function makeStaleChecker(paths, opts = {}) {
19151
19266
  const initial = computeTreeSignature(paths);
19152
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync39(opts.reloadSentinel);
19267
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync40(opts.reloadSentinel);
19153
19268
  }
19154
19269
  function reloadSentinelFor(stateDir) {
19155
19270
  return join42(stateDir, "reload-requested");
@@ -19158,14 +19273,14 @@ function daemonStampFor(stateDir) {
19158
19273
  return join42(stateDir, "daemon.started");
19159
19274
  }
19160
19275
  function noteDaemonStarted(stateDir, now = Date.now()) {
19161
- mkdirSync25(stateDir, { recursive: true });
19276
+ mkdirSync26(stateDir, { recursive: true });
19162
19277
  rmSync17(reloadSentinelFor(stateDir), { force: true });
19163
- writeFileSync27(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
19278
+ writeFileSync28(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
19164
19279
  `);
19165
19280
  }
19166
19281
  function readDaemonStarted(stateDir) {
19167
19282
  try {
19168
- const v = JSON.parse(readFileSync35(daemonStampFor(stateDir), "utf8")).startedAt;
19283
+ const v = JSON.parse(readFileSync36(daemonStampFor(stateDir), "utf8")).startedAt;
19169
19284
  return typeof v === "number" ? v : null;
19170
19285
  } catch {
19171
19286
  return null;
@@ -19545,6 +19660,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
19545
19660
  if (seat) {
19546
19661
  const candidates2 = rankedCandidates(role, decision.selectionInput);
19547
19662
  const contract = decision.selectionInput.contract ?? DEFAULT_CONTRACT;
19663
+ await refreshUnknownRepos({ route: route2, stateDir: ship.stateDir, fetchRows: () => tracker.repoRowsForMap(), tickets: candidates2 });
19548
19664
  const result = await resolveTopCandidate(
19549
19665
  tracker,
19550
19666
  candidates2,
@@ -19571,6 +19687,11 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
19571
19687
  role
19572
19688
  });
19573
19689
  }
19690
+ await reportUnservable(
19691
+ { state: ctx.state, emit, route: route2, ship },
19692
+ result.unservable,
19693
+ (id) => repoLabelFor(route2, candidates2.find((c) => c.issue_id === id))
19694
+ );
19574
19695
  if (result.held.length) {
19575
19696
  emit.emit(`skipped ${result.held.join(", ")} \u2014 held by another live ship`, { step: "select", role });
19576
19697
  }
@@ -19714,7 +19835,7 @@ import { createHash as createHash9 } from "crypto";
19714
19835
  import {
19715
19836
  chmodSync as chmodSync4,
19716
19837
  closeSync as closeSync5,
19717
- existsSync as existsSync40,
19838
+ existsSync as existsSync41,
19718
19839
  fstatSync,
19719
19840
  openSync as openSync5,
19720
19841
  readSync as readSync3,
@@ -19725,7 +19846,7 @@ import {
19725
19846
  } from "fs";
19726
19847
  import { createConnection } from "net";
19727
19848
  import { createServer } from "http";
19728
- import { basename as basename12, dirname as dirname21, join as join43 } from "path";
19849
+ import { basename as basename12, dirname as dirname22, join as join43 } from "path";
19729
19850
  import { execFile, spawn as spawn8 } from "child_process";
19730
19851
 
19731
19852
  // src/periscope-control.ts
@@ -20029,7 +20150,7 @@ function candidates(live, rotated) {
20029
20150
  const out = [live];
20030
20151
  if (!rotated) return out;
20031
20152
  try {
20032
- for (const n of readdirSync17(dirname21(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join43(dirname21(live), n));
20153
+ for (const n of readdirSync17(dirname22(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join43(dirname22(live), n));
20033
20154
  } catch {
20034
20155
  }
20035
20156
  return out;
@@ -20073,7 +20194,7 @@ function lastLines2(path, n) {
20073
20194
  }
20074
20195
  function readTail(live, opts) {
20075
20196
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
20076
- if (!existsSync40(live)) {
20197
+ if (!existsSync41(live)) {
20077
20198
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
20078
20199
  }
20079
20200
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -20189,7 +20310,7 @@ async function tailReply(path, q, rotated, abort) {
20189
20310
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
20190
20311
  let r = readTail(path, { after, tail: tail3, rotated });
20191
20312
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
20192
- await waitForChange(dirname21(path), deadline - Date.now(), abort);
20313
+ await waitForChange(dirname22(path), deadline - Date.now(), abort);
20193
20314
  r = readTail(path, { after: r.cursor, rotated });
20194
20315
  }
20195
20316
  return reply2(200, r);
@@ -20230,7 +20351,7 @@ async function route(deps, method, url, req, abort) {
20230
20351
  const run2 = decodeURIComponent(p.slice("/streams/".length));
20231
20352
  if (!RUN_NAME_RE.test(run2) || run2.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run2)) return err2(400, "not a run name");
20232
20353
  const file = join43(deps.stateDir, "streams", run2.endsWith(".jsonl") ? run2 : `${run2}.jsonl`);
20233
- if (!existsSync40(file)) return err2(404, "no such run");
20354
+ if (!existsSync41(file)) return err2(404, "no such run");
20234
20355
  return tailReply(file, url.searchParams, false, abort);
20235
20356
  }
20236
20357
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -20274,7 +20395,7 @@ function send(res, r) {
20274
20395
  res.end(body);
20275
20396
  }
20276
20397
  async function socketIsStale(path) {
20277
- if (!existsSync40(path)) return false;
20398
+ if (!existsSync41(path)) return false;
20278
20399
  return new Promise((resolve7) => {
20279
20400
  const c = createConnection(path);
20280
20401
  c.once("connect", () => {
@@ -20293,7 +20414,7 @@ async function startControlServer(deps) {
20293
20414
  return null;
20294
20415
  }
20295
20416
  if (await socketIsStale(path)) rmSync18(path, { force: true });
20296
- else if (existsSync40(path)) {
20417
+ else if (existsSync41(path)) {
20297
20418
  log(`control API not started: ${path} is in use by another process`);
20298
20419
  return null;
20299
20420
  }
@@ -20376,31 +20497,31 @@ function crewJson(args, timeoutMs = 6e4) {
20376
20497
 
20377
20498
  // src/periscope-mcp.ts
20378
20499
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
20379
- import { basename as basename13, dirname as dirname22, isAbsolute as isAbsolute3, join as join45, relative as relative2, resolve as resolve5, sep } from "path";
20500
+ import { basename as basename13, dirname as dirname23, isAbsolute as isAbsolute3, join as join45, relative as relative2, resolve as resolve5, sep } from "path";
20380
20501
  import { z } from "zod";
20381
20502
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
20382
20503
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
20383
20504
 
20384
20505
  // src/periscope-endpoints.ts
20385
20506
  import { randomBytes as randomBytes3 } from "crypto";
20386
- import { chmodSync as chmodSync5, existsSync as existsSync41, mkdirSync as mkdirSync26, readdirSync as readdirSync18, readFileSync as readFileSync36, rmSync as rmSync19, writeFileSync as writeFileSync28 } from "fs";
20507
+ import { chmodSync as chmodSync5, existsSync as existsSync42, mkdirSync as mkdirSync27, readdirSync as readdirSync18, readFileSync as readFileSync37, rmSync as rmSync19, writeFileSync as writeFileSync29 } from "fs";
20387
20508
  import { join as join44 } from "path";
20388
20509
  var secretDir = (stateDir) => join44(stateDir, "periscope-mcp");
20389
20510
  var secretFile = (stateDir, id) => join44(secretDir(stateDir), `${id}.json`);
20390
20511
  var endpointDir = (stateDir) => join44(stateDir, "periscope-endpoints");
20391
20512
  var endpointFile = (stateDir, id) => join44(endpointDir(stateDir), `${id}.json`);
20392
20513
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
20393
- mkdirSync26(secretDir(stateDir), { recursive: true, mode: 448 });
20514
+ mkdirSync27(secretDir(stateDir), { recursive: true, mode: 448 });
20394
20515
  const secret = randomBytes3(32).toString("base64url");
20395
20516
  const file = secretFile(stateDir, ticketRecordId);
20396
- writeFileSync28(file, `${JSON.stringify({ secret })}
20517
+ writeFileSync29(file, `${JSON.stringify({ secret })}
20397
20518
  `, { mode: 384 });
20398
20519
  chmodSync5(file, 384);
20399
20520
  return secret;
20400
20521
  }
20401
20522
  function readPeriscopeSecret(stateDir, ticketRecordId) {
20402
20523
  try {
20403
- const v = JSON.parse(readFileSync36(secretFile(stateDir, ticketRecordId), "utf8"));
20524
+ const v = JSON.parse(readFileSync37(secretFile(stateDir, ticketRecordId), "utf8"));
20404
20525
  return typeof v.secret === "string" && v.secret ? v.secret : void 0;
20405
20526
  } catch {
20406
20527
  return void 0;
@@ -20411,14 +20532,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
20411
20532
  }
20412
20533
  function readPeriscopeEndpoint(stateDir, ticketRecordId) {
20413
20534
  try {
20414
- return JSON.parse(readFileSync36(endpointFile(stateDir, ticketRecordId), "utf8"));
20535
+ return JSON.parse(readFileSync37(endpointFile(stateDir, ticketRecordId), "utf8"));
20415
20536
  } catch {
20416
20537
  return void 0;
20417
20538
  }
20418
20539
  }
20419
20540
  function writePeriscopeEndpoint(stateDir, rec) {
20420
- mkdirSync26(endpointDir(stateDir), { recursive: true });
20421
- writeFileSync28(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
20541
+ mkdirSync27(endpointDir(stateDir), { recursive: true });
20542
+ writeFileSync29(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
20422
20543
  `);
20423
20544
  }
20424
20545
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
@@ -20426,7 +20547,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
20426
20547
  }
20427
20548
  function listPeriscopeEndpoints(stateDir) {
20428
20549
  const dir = endpointDir(stateDir);
20429
- if (!existsSync41(dir)) return [];
20550
+ if (!existsSync42(dir)) return [];
20430
20551
  const out = [];
20431
20552
  for (const name of readdirSync18(dir)) {
20432
20553
  if (!name.endsWith(".json")) continue;
@@ -20513,10 +20634,10 @@ async function realAncestor(p) {
20513
20634
  }
20514
20635
  if (isLink) {
20515
20636
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
20516
- cur = resolve5(dirname22(cur), await readlink(cur));
20637
+ cur = resolve5(dirname23(cur), await readlink(cur));
20517
20638
  continue;
20518
20639
  }
20519
- const parent = dirname22(cur);
20640
+ const parent = dirname23(cur);
20520
20641
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
20521
20642
  tail3.push(basename13(cur));
20522
20643
  cur = parent;
@@ -20627,7 +20748,7 @@ function createPeriscopeMcpServer(d, sc) {
20627
20748
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
20628
20749
  const abs = await inTree(path);
20629
20750
  if (abs === await realpath(root)) return text("path must name a file", true);
20630
- await mkdir(dirname22(abs), { recursive: true });
20751
+ await mkdir(dirname23(abs), { recursive: true });
20631
20752
  await inTree(path);
20632
20753
  await writeFile(abs, content);
20633
20754
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -20667,7 +20788,7 @@ function createPeriscopeMcpServer(d, sc) {
20667
20788
  inputSchema: { path: z.string() },
20668
20789
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
20669
20790
  }, async ({ path }) => {
20670
- const parent = await inTree(dirname22(path) === "" ? "." : dirname22(path));
20791
+ const parent = await inTree(dirname23(path) === "" ? "." : dirname23(path));
20671
20792
  const abs = join45(parent, basename13(path));
20672
20793
  if (basename13(path) === ".git" || abs === parent) return text("path must name a file", true);
20673
20794
  const st = await lstat(abs).catch(() => void 0);
@@ -20789,7 +20910,7 @@ function findPeriscope(sidecars, segment) {
20789
20910
  }
20790
20911
 
20791
20912
  // src/cli.ts
20792
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname23(process.execPath) : resolve6(dirname23(fileURLToPath(import.meta.url)), "..");
20913
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname24(process.execPath) : resolve6(dirname24(fileURLToPath(import.meta.url)), "..");
20793
20914
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
20794
20915
  var CYCLE_SECONDS2 = 120;
20795
20916
  var STALE_CYCLES = 3;
@@ -21306,7 +21427,7 @@ if (command === "passenger-sync-daemon") {
21306
21427
  } else {
21307
21428
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "connect" && flag("sign-out");
21308
21429
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset");
21309
- if (configless && !existsSync42(findConfigFile(CREW_HOME))) {
21430
+ if (configless && !existsSync43(findConfigFile(CREW_HOME))) {
21310
21431
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
21311
21432
  } else {
21312
21433
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -21321,7 +21442,7 @@ if (command === "passenger-sync-daemon") {
21321
21442
  throw e;
21322
21443
  }
21323
21444
  if (command === "credential") {
21324
- const r = runCredential(positional[1], readFileSync37(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
21445
+ const r = runCredential(positional[1], readFileSync38(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
21325
21446
  process.stdout.write(r.out);
21326
21447
  process.stderr.write(r.err);
21327
21448
  process.exit(r.code);
@@ -21333,9 +21454,9 @@ if (command === "passenger-sync-daemon") {
21333
21454
  forges: cfg.ship.forges,
21334
21455
  stateDir: cfg.ship.stateDir,
21335
21456
  configFile: cfg.configFile,
21336
- readFile: (p) => readFileSync37(p, "utf8"),
21337
- writeFile: (p, t) => writeFileSync29(p, t),
21338
- stdin: () => readFileSync37(0, "utf8"),
21457
+ readFile: (p) => readFileSync38(p, "utf8"),
21458
+ writeFile: (p, t) => writeFileSync30(p, t),
21459
+ stdin: () => readFileSync38(0, "utf8"),
21339
21460
  env: process.env
21340
21461
  }
21341
21462
  );
@@ -21619,7 +21740,7 @@ if (command === "passenger-sync-daemon") {
21619
21740
  if (host === "linux" && hasSystemd) {
21620
21741
  const label = labelFor(CREW_HOME, "run");
21621
21742
  const staleTimer = join46(homedir10(), ".config", "systemd", "user", `${label}.timer`);
21622
- if (existsSync42(staleTimer)) {
21743
+ if (existsSync43(staleTimer)) {
21623
21744
  process.stdout.write(
21624
21745
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
21625
21746
  `
@@ -21646,7 +21767,7 @@ if (command === "passenger-sync-daemon") {
21646
21767
  const tracker = new Tracker(c, cfg.ship);
21647
21768
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
21648
21769
  for (const r of await resolvedRepos(c)) {
21649
- if (!existsSync42(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
21770
+ if (!existsSync43(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
21650
21771
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
21651
21772
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
21652
21773
  }
@@ -21664,7 +21785,7 @@ if (command === "passenger-sync-daemon") {
21664
21785
  }
21665
21786
  async function doctorFix(o) {
21666
21787
  const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
21667
- const unitExists = (p) => existsSync42(p);
21788
+ const unitExists = (p) => existsSync43(p);
21668
21789
  const crontab = o.host === "linux" ? spawnSync8("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
21669
21790
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
21670
21791
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -21681,7 +21802,7 @@ if (command === "passenger-sync-daemon") {
21681
21802
  for (const a of plan.actions) {
21682
21803
  if (a.kind === "enable") {
21683
21804
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
21684
- writeFileSync29(cfg.configFile, planEnable(readFileSync37(cfg.configFile, "utf8"), a.route));
21805
+ writeFileSync30(cfg.configFile, planEnable(readFileSync38(cfg.configFile, "utf8"), a.route));
21685
21806
  enabledSound.add(a.route);
21686
21807
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
21687
21808
  } else {
@@ -22360,6 +22481,7 @@ ${cu.tail.trimEnd()}
22360
22481
  if (seat) {
22361
22482
  const candidates2 = rankedCandidates(w.role, w.decision.selectionInput);
22362
22483
  const contract = w.decision.selectionInput.contract ?? DEFAULT_CONTRACT;
22484
+ await refreshUnknownRepos({ route: w.route, stateDir: cfg.ship.stateDir, fetchRows: () => fleetTracker.repoRowsForMap(), tickets: candidates2 });
22363
22485
  const result = await resolveTopCandidate(
22364
22486
  fleetTracker,
22365
22487
  candidates2,
@@ -22571,6 +22693,7 @@ ${cu.tail.trimEnd()}
22571
22693
  if (seat) {
22572
22694
  const candidates2 = rankedCandidates(current, decision.selectionInput);
22573
22695
  const contract = decision.selectionInput.contract ?? DEFAULT_CONTRACT;
22696
+ await refreshUnknownRepos({ route: route2, stateDir: cfg.ship.stateDir, fetchRows: () => tracker2.repoRowsForMap(), tickets: candidates2 });
22574
22697
  const result = await resolveTopCandidate(
22575
22698
  tracker2,
22576
22699
  candidates2,
@@ -23077,10 +23200,10 @@ ${cu.tail.trimEnd()}
23077
23200
  `);
23078
23201
  if (!dryRun) {
23079
23202
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
23080
- const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync37(resolvedPath, "utf8")) : {};
23203
+ const raw = existsSync43(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
23081
23204
  raw.agentSkills = result.agentSkills;
23082
- mkdirSync27(dirOf2(resolvedPath), { recursive: true });
23083
- writeFileSync29(resolvedPath, `${JSON.stringify(raw, null, 2)}
23205
+ mkdirSync28(dirOf2(resolvedPath), { recursive: true });
23206
+ writeFileSync30(resolvedPath, `${JSON.stringify(raw, null, 2)}
23084
23207
  `);
23085
23208
  }
23086
23209
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -23103,7 +23226,7 @@ ${cu.tail.trimEnd()}
23103
23226
  throw e;
23104
23227
  }
23105
23228
  const oldName = cfg.ship.name;
23106
- writeFileSync29(cfg.configFile, setShipNameInYaml(readFileSync37(cfg.configFile, "utf8"), newName));
23229
+ writeFileSync30(cfg.configFile, setShipNameInYaml(readFileSync38(cfg.configFile, "utf8"), newName));
23107
23230
  cfg.ship.name = newName;
23108
23231
  let renameMachineId;
23109
23232
  try {
@@ -23150,14 +23273,14 @@ ${cu.tail.trimEnd()}
23150
23273
  process.exit(2);
23151
23274
  };
23152
23275
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
23153
- const configText = readFileSync37(cfg.configFile, "utf8");
23276
+ const configText = readFileSync38(cfg.configFile, "utf8");
23154
23277
  if (sub === "check") {
23155
23278
  try {
23156
23279
  const routeArg = value("route") ?? positional[2];
23157
23280
  const found = cfg.routes.find((r) => r.route === routeArg);
23158
23281
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
23159
23282
  const named2 = value("route") ? void 0 : positional[3];
23160
- const dir = named2 ? repoDirOf(configText, dirname23(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
23283
+ const dir = named2 ? repoDirOf(configText, dirname24(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
23161
23284
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
23162
23285
  const o = target ? found.repoOverrides[target.name] : void 0;
23163
23286
  runReposCheck(dir, {
@@ -23180,9 +23303,9 @@ ${cu.tail.trimEnd()}
23180
23303
  if (sub === "hooks") {
23181
23304
  try {
23182
23305
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
23183
- const dir = repoDirOf(configText, dirname23(cfg.configFile), a.route, a.name);
23184
- if (!existsSync42(dir)) throw new ReposError(`${dir} does not exist on disk`);
23185
- const files = { exists: (r) => existsSync42(resolvePath(dir, r)), list: (r) => {
23306
+ const dir = repoDirOf(configText, dirname24(cfg.configFile), a.route, a.name);
23307
+ if (!existsSync43(dir)) throw new ReposError(`${dir} does not exist on disk`);
23308
+ const files = { exists: (r) => existsSync43(resolvePath(dir, r)), list: (r) => {
23186
23309
  try {
23187
23310
  return readdirSync19(resolvePath(dir, r));
23188
23311
  } catch {
@@ -23190,7 +23313,7 @@ ${cu.tail.trimEnd()}
23190
23313
  }
23191
23314
  }, read: (r) => {
23192
23315
  try {
23193
- return readFileSync37(resolvePath(dir, r), "utf8");
23316
+ return readFileSync38(resolvePath(dir, r), "utf8");
23194
23317
  } catch {
23195
23318
  return void 0;
23196
23319
  }
@@ -23232,19 +23355,19 @@ ${cu.tail.trimEnd()}
23232
23355
  let cleared;
23233
23356
  if (clear2) {
23234
23357
  const plan = clearRouteHooks(configText, a.route, a.name);
23235
- if (plan.removed.length > 0) writeFileSync29(cfg.configFile, plan.text);
23358
+ if (plan.removed.length > 0) writeFileSync30(cfg.configFile, plan.text);
23236
23359
  cleared = { file: cfg.configFile, removed: plan.removed };
23237
23360
  }
23238
23361
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
23239
23362
  if (write === "route") {
23240
23363
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
23241
- writeFileSync29(cfg.configFile, plan.text);
23364
+ writeFileSync30(cfg.configFile, plan.text);
23242
23365
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
23243
23366
  } else if (write === "repo") {
23244
23367
  const have = existingRepoConfig(files);
23245
23368
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
23246
23369
  const file = resolvePath(dir, ".crew.yaml");
23247
- writeFileSync29(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
23370
+ writeFileSync30(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
23248
23371
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
23249
23372
  }
23250
23373
  if (flag("json")) {
@@ -23300,7 +23423,7 @@ ${cu.tail.trimEnd()}
23300
23423
  if (sub === "list" && !routeName) usage2();
23301
23424
  if (sub === "add") {
23302
23425
  try {
23303
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync42(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
23426
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync43(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
23304
23427
  routeName = a.route;
23305
23428
  addPath = a.path;
23306
23429
  addName = a.name;
@@ -23328,17 +23451,27 @@ ${cu.tail.trimEnd()}
23328
23451
  }
23329
23452
  }
23330
23453
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
23454
+ if (sub === "list" || sub === "add") {
23455
+ const liveRoute = cfg.routes.find((r) => r.route === routeName);
23456
+ if (liveRoute?.resolved?.reposModelId && existsSync43(resolvedFile) && !dryRun) {
23457
+ try {
23458
+ await hydrateApiKeys([liveRoute]);
23459
+ persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
23460
+ } catch {
23461
+ }
23462
+ }
23463
+ }
23331
23464
  let resolvedRemotes;
23332
- if (existsSync42(resolvedFile)) {
23465
+ if (existsSync43(resolvedFile)) {
23333
23466
  try {
23334
- resolvedRemotes = JSON.parse(readFileSync37(resolvedFile, "utf8"));
23467
+ resolvedRemotes = JSON.parse(readFileSync38(resolvedFile, "utf8"));
23335
23468
  } catch {
23336
23469
  }
23337
23470
  }
23338
23471
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
23339
23472
  const text2 = configText;
23340
- const base = dirname23(cfg.configFile);
23341
- const isCheckout = (d) => existsSync42(`${d}/.git`);
23473
+ const base = dirname24(cfg.configFile);
23474
+ const isCheckout = (d) => existsSync43(`${d}/.git`);
23342
23475
  try {
23343
23476
  if (sub === "list") {
23344
23477
  if (flag("json")) {
@@ -23348,11 +23481,11 @@ ${cu.tail.trimEnd()}
23348
23481
  base,
23349
23482
  resolved: resolvedRemotes,
23350
23483
  originOf,
23351
- exists: (d) => existsSync42(d),
23484
+ exists: (d) => existsSync43(d),
23352
23485
  isCheckout,
23353
23486
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
23354
23487
  try {
23355
- return Object.keys(parseRepoConfig(readFileSync37(resolvePath(d, f), "utf8"), f).hooks).length > 0;
23488
+ return Object.keys(parseRepoConfig(readFileSync38(resolvePath(d, f), "utf8"), f).hooks).length > 0;
23356
23489
  } catch {
23357
23490
  return false;
23358
23491
  }
@@ -23369,7 +23502,7 @@ ${cu.tail.trimEnd()}
23369
23502
  for (const r of repos) {
23370
23503
  const origin2 = originOf(r.dir);
23371
23504
  const row = matchRepoName(origin2, resolvedRemotes);
23372
- const note = !existsSync42(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
23505
+ const note = !existsSync43(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
23373
23506
  process.stdout.write(`${r.name} ${r.dir} ${note}
23374
23507
  `);
23375
23508
  }
@@ -23381,9 +23514,9 @@ ${cu.tail.trimEnd()}
23381
23514
  ` : `${human}
23382
23515
  `);
23383
23516
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
23384
- if (existsSync42(plan2.target) && (existsSync42(`${plan2.target}/.git`) || readdirSync19(plan2.target).length > 0)) {
23517
+ if (existsSync43(plan2.target) && (existsSync43(`${plan2.target}/.git`) || readdirSync19(plan2.target).length > 0)) {
23385
23518
  throw new ReposError(
23386
- existsSync42(`${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`
23519
+ existsSync43(`${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`
23387
23520
  );
23388
23521
  }
23389
23522
  if (dryRun) {
@@ -23401,14 +23534,14 @@ ${cu.tail.trimEnd()}
23401
23534
  throw new ReposError(`${e.message}${authHint}
23402
23535
  crew.yaml was not changed`);
23403
23536
  }
23404
- writeFileSync29(cfg.configFile, plan2.text);
23537
+ writeFileSync30(cfg.configFile, plan2.text);
23405
23538
  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:
23406
23539
  `);
23407
23540
  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}`);
23408
23541
  break;
23409
23542
  }
23410
23543
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir10()));
23411
- const top = existsSync42(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
23544
+ const top = existsSync43(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
23412
23545
  if (!top) throw new ReposError(`${target} is not a git checkout`);
23413
23546
  const origin = originOf(top);
23414
23547
  const plan = planRepoAdd({
@@ -23460,15 +23593,15 @@ ${cu.tail.trimEnd()}
23460
23593
  const created = await rowTracker.createRepoRow(rowPayload);
23461
23594
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
23462
23595
  `);
23463
- if (existsSync42(resolvedFile)) {
23464
- const raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
23596
+ if (existsSync43(resolvedFile)) {
23597
+ const raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
23465
23598
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
23466
23599
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
23467
- writeFileSync29(resolvedFile, `${JSON.stringify(raw, null, 2)}
23600
+ writeFileSync30(resolvedFile, `${JSON.stringify(raw, null, 2)}
23468
23601
  `);
23469
23602
  }
23470
23603
  }
23471
- writeFileSync29(cfg.configFile, plan.text);
23604
+ writeFileSync30(cfg.configFile, plan.text);
23472
23605
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
23473
23606
  `);
23474
23607
  } catch (e) {
@@ -23835,7 +23968,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
23835
23968
  process.exit(2);
23836
23969
  }
23837
23970
  if (found.shipNameAdopted && !dryRun) {
23838
- writeFileSync29(cfg.configFile, setShipNameInYaml(readFileSync37(cfg.configFile, "utf8"), found.shipNameAdopted));
23971
+ writeFileSync30(cfg.configFile, setShipNameInYaml(readFileSync38(cfg.configFile, "utf8"), found.shipNameAdopted));
23839
23972
  cfg.ship.name = found.shipNameAdopted;
23840
23973
  }
23841
23974
  if (found.shipRow) {
@@ -23878,10 +24011,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
23878
24011
  }
23879
24012
  }
23880
24013
  let previous;
23881
- const hadPreviousFile = existsSync42(resolvedPath);
24014
+ const hadPreviousFile = existsSync43(resolvedPath);
23882
24015
  if (hadPreviousFile) {
23883
24016
  try {
23884
- previous = JSON.parse(readFileSync37(resolvedPath, "utf8"));
24017
+ previous = JSON.parse(readFileSync38(resolvedPath, "utf8"));
23885
24018
  } catch {
23886
24019
  }
23887
24020
  }
@@ -24011,10 +24144,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
24011
24144
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
24012
24145
  `);
24013
24146
  } else {
24014
- mkdirSync27(dirname23(resolvedPath), { recursive: true });
24147
+ mkdirSync28(dirname24(resolvedPath), { recursive: true });
24015
24148
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
24016
24149
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
24017
- writeFileSync29(tmpPath, `${JSON.stringify(resolved, null, 2)}
24150
+ writeFileSync30(tmpPath, `${JSON.stringify(resolved, null, 2)}
24018
24151
  `);
24019
24152
  renameSync4(tmpPath, resolvedPath);
24020
24153
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -24060,8 +24193,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
24060
24193
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
24061
24194
  `);
24062
24195
  } else {
24063
- mkdirSync27(dirname23(apiKeyFile), { recursive: true });
24064
- writeFileSync29(apiKeyFile, `${apiKeyVar}=${apiKey}
24196
+ mkdirSync28(dirname24(apiKeyFile), { recursive: true });
24197
+ writeFileSync30(apiKeyFile, `${apiKeyVar}=${apiKey}
24065
24198
  `, { mode: 384 });
24066
24199
  process.stderr.write(`API key written to ${apiKeyFile}
24067
24200
  `);
@@ -24797,7 +24930,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
24797
24930
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
24798
24931
  const resumed = await runPeriscopeResume({
24799
24932
  find: async () => hit && { row: hit.row, tag: hit.tag },
24800
- worktreeExists: (p) => existsSync42(p),
24933
+ worktreeExists: (p) => existsSync43(p),
24801
24934
  caps: periscopeCaps(cfg.ship),
24802
24935
  runHook: async (name, cwd, env) => {
24803
24936
  const script = hit?.repo.config.hooks[name];
@@ -24892,8 +25025,8 @@ ${where.join("\n")}`);
24892
25025
  prepare: async (path) => {
24893
25026
  for (const f of repoP.config.worktrees.copy) {
24894
25027
  const src = join46(repoP.dir, f);
24895
- if (!existsSync42(src)) continue;
24896
- mkdirSync27(dirOf2(join46(path, f)), { recursive: true });
25028
+ if (!existsSync43(src)) continue;
25029
+ mkdirSync28(dirOf2(join46(path, f)), { recursive: true });
24897
25030
  copyFileSync2(src, join46(path, f));
24898
25031
  }
24899
25032
  const setup = repoP.config.hooks.setup;
@@ -25187,7 +25320,7 @@ ${where.join("\n")}`);
25187
25320
  };
25188
25321
  const droppedRoutes = [];
25189
25322
  try {
25190
- const rawCfg = parseYaml2(readFileSync37(cfg.configFile, "utf8"));
25323
+ const rawCfg = parseYaml2(readFileSync38(cfg.configFile, "utf8"));
25191
25324
  for (const r of rawCfg?.routes ?? []) {
25192
25325
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
25193
25326
  try {
@@ -25229,8 +25362,8 @@ ${where.join("\n")}`);
25229
25362
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
25230
25363
  break;
25231
25364
  }
25232
- mkdirSync27(cfg.ship.stateDir, { recursive: true });
25233
- writeFileSync29(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
25365
+ mkdirSync28(cfg.ship.stateDir, { recursive: true });
25366
+ writeFileSync30(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
25234
25367
  `);
25235
25368
  process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
25236
25369
  if (flag("now") && control?.stopCommand && control.startCommand) {
@@ -25281,7 +25414,7 @@ ${where.join("\n")}`);
25281
25414
  }
25282
25415
  case "log": {
25283
25416
  try {
25284
- const text2 = readFileSync37(cfg.ship.logFile, "utf8").split("\n");
25417
+ const text2 = readFileSync38(cfg.ship.logFile, "utf8").split("\n");
25285
25418
  process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
25286
25419
  `);
25287
25420
  } catch {
@@ -25304,8 +25437,8 @@ ${where.join("\n")}`);
25304
25437
  };
25305
25438
  if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
25306
25439
  const tracker = new Tracker(route2, cfg.ship);
25307
- const configText = readFileSync37(cfg.configFile, "utf8");
25308
- const cfgBase = dirname23(cfg.configFile);
25440
+ const configText = readFileSync38(cfg.configFile, "utf8");
25441
+ const cfgBase = dirname24(cfg.configFile);
25309
25442
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
25310
25443
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
25311
25444
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -25333,7 +25466,7 @@ ${where.join("\n")}`);
25333
25466
  }
25334
25467
  if (configured2.some((r) => r.dir === dir)) {
25335
25468
  try {
25336
- writeFileSync29(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
25469
+ writeFileSync30(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
25337
25470
  } catch (e) {
25338
25471
  if (e instanceof ReposError) fail(e.message);
25339
25472
  throw e;
@@ -25341,29 +25474,29 @@ ${where.join("\n")}`);
25341
25474
  }
25342
25475
  if (row) {
25343
25476
  await tracker.deleteRepoRow(row.id);
25344
- if (existsSync42(resolvedFile)) {
25345
- const raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
25477
+ if (existsSync43(resolvedFile)) {
25478
+ const raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
25346
25479
  if (raw.repoNames) delete raw.repoNames[row.id];
25347
25480
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
25348
- writeFileSync29(resolvedFile, `${JSON.stringify(raw, null, 2)}
25481
+ writeFileSync30(resolvedFile, `${JSON.stringify(raw, null, 2)}
25349
25482
  `);
25350
25483
  }
25351
25484
  }
25352
- if (existsSync42(join46(dir, ".git")) && basename14(dir) === SAMPLE_REPO) rmSync20(dir, { recursive: true, force: true });
25485
+ if (existsSync43(join46(dir, ".git")) && basename14(dir) === SAMPLE_REPO) rmSync20(dir, { recursive: true, force: true });
25353
25486
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
25354
25487
  break;
25355
25488
  }
25356
25489
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
25357
- const fresh = !existsSync42(join46(dir, ".git"));
25358
- if (existsSync42(dir) && fresh && readdirSync19(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
25490
+ const fresh = !existsSync43(join46(dir, ".git"));
25491
+ if (existsSync43(dir) && fresh && readdirSync19(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
25359
25492
  if (dryRun) {
25360
25493
  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`);
25361
25494
  break;
25362
25495
  }
25363
25496
  if (fresh) {
25364
- mkdirSync27(dir, { recursive: true });
25365
- writeFileSync29(join46(dir, "README.md"), SAMPLE_README);
25366
- writeFileSync29(join46(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
25497
+ mkdirSync28(dir, { recursive: true });
25498
+ writeFileSync30(join46(dir, "README.md"), SAMPLE_README);
25499
+ writeFileSync30(join46(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
25367
25500
  const g = (...a) => {
25368
25501
  const r = spawnSync8("git", a, { cwd: dir, encoding: "utf8" });
25369
25502
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -25381,17 +25514,17 @@ ${where.join("\n")}`);
25381
25514
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
25382
25515
  });
25383
25516
  repoId = (await tracker.createRepoRow(payload)).id;
25384
- if (existsSync42(resolvedFile)) {
25385
- const raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
25517
+ if (existsSync43(resolvedFile)) {
25518
+ const raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
25386
25519
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
25387
25520
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
25388
- writeFileSync29(resolvedFile, `${JSON.stringify(raw, null, 2)}
25521
+ writeFileSync30(resolvedFile, `${JSON.stringify(raw, null, 2)}
25389
25522
  `);
25390
25523
  }
25391
25524
  }
25392
25525
  if (!configured2.some((r) => r.dir === dir)) {
25393
25526
  try {
25394
- writeFileSync29(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
25527
+ writeFileSync30(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
25395
25528
  } catch (e) {
25396
25529
  if (e instanceof ReposError) fail(e.message);
25397
25530
  throw e;
@@ -25729,7 +25862,7 @@ watermark: ${state.watermark()}
25729
25862
  }
25730
25863
  for (const r of repos) {
25731
25864
  const remote = r.config.branch.remote;
25732
- if (existsSync42(r.dir)) {
25865
+ if (existsSync43(r.dir)) {
25733
25866
  pushAuth.push({
25734
25867
  route: c.route,
25735
25868
  repo: r.name,
@@ -25815,7 +25948,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
25815
25948
  }
25816
25949
  try {
25817
25950
  const evFile = eventFileFor(cfg.ship.stateDir);
25818
- const evSize = existsSync42(evFile) ? statSync14(evFile).size : 0;
25951
+ const evSize = existsSync43(evFile) ? statSync14(evFile).size : 0;
25819
25952
  process.stdout.write(
25820
25953
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
25821
25954
  ` : "events log: ok\n"
@@ -25826,7 +25959,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
25826
25959
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
25827
25960
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
25828
25961
  try {
25829
- return readFileSync37(p, "utf8");
25962
+ return readFileSync38(p, "utf8");
25830
25963
  } catch {
25831
25964
  return null;
25832
25965
  }