@tablation/crew 0.114.0 → 0.116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +898 -741
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -45,12 +45,12 @@ function periscopeSummary(cfg) {
45
45
  if (routes.length === 0) return `off (no enabled route sets hostPeriscopes: true; ${limits})`;
46
46
  return `ON for ${routes.join(", ")} (host ${cfg.ship.previewHost ?? "NOT SET (ship.previewHost)"}, ${limits})`;
47
47
  }
48
- async function previewHostProblem(cfg, resolve7 = (h) => lookup(h)) {
48
+ async function previewHostProblem(cfg, resolve8 = (h) => lookup(h)) {
49
49
  if (periscopeRoutes(cfg).length === 0) return void 0;
50
50
  const host = cfg.ship.previewHost;
51
51
  if (!host) return "ship.previewHost is not set \u2014 a route hosts periscopes, so Passengers have no host to be sent to";
52
52
  try {
53
- await resolve7(host);
53
+ await resolve8(host);
54
54
  } catch {
55
55
  return `ship.previewHost "${host}" does not resolve from this machine`;
56
56
  }
@@ -103,15 +103,92 @@ function duplicateLivePeriscopes(rows, tagOf, shipName2, route2) {
103
103
  return out;
104
104
  }
105
105
 
106
+ // src/periscope-pointed.ts
107
+ import { execFileSync } from "child_process";
108
+ import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
109
+ import { dirname, isAbsolute, join, resolve } from "path";
110
+ var POINTED_AT_DIR = ".crew";
111
+ var POINTED_AT_REL = `${POINTED_AT_DIR}/pointed-at.jsonl`;
112
+ var pointedAtFile = (worktree) => join(worktree, POINTED_AT_REL);
113
+ function pointedAtEnv(worktree) {
114
+ return { CREW_PERISCOPE: "1", CREW_POINTED_AT_FILE: pointedAtFile(worktree) };
115
+ }
116
+ function excludeFile(worktree) {
117
+ try {
118
+ const p = execFileSync("git", ["rev-parse", "--git-path", "info/exclude"], {
119
+ cwd: worktree,
120
+ encoding: "utf8",
121
+ stdio: ["ignore", "pipe", "ignore"]
122
+ }).trim();
123
+ return p ? isAbsolute(p) ? p : resolve(worktree, p) : void 0;
124
+ } catch {
125
+ return void 0;
126
+ }
127
+ }
128
+ function prepareDropFile(worktree) {
129
+ const file = pointedAtFile(worktree);
130
+ mkdirSync(dirname(file), { recursive: true });
131
+ if (!existsSync2(file)) writeFileSync(file, "");
132
+ const ex = excludeFile(worktree);
133
+ if (ex) {
134
+ mkdirSync(dirname(ex), { recursive: true });
135
+ const have = existsSync2(ex) ? readFileSync(ex, "utf8") : "";
136
+ if (!have.split("\n").some((l) => l.trim() === `${POINTED_AT_DIR}/`)) {
137
+ appendFileSync(ex, `${have === "" || have.endsWith("\n") ? "" : "\n"}${POINTED_AT_DIR}/
138
+ `);
139
+ }
140
+ }
141
+ return file;
142
+ }
143
+ function parseLine(raw) {
144
+ let v;
145
+ try {
146
+ v = JSON.parse(raw);
147
+ } catch {
148
+ return void 0;
149
+ }
150
+ if (!v || typeof v !== "object") return void 0;
151
+ const o = v;
152
+ if (typeof o.path !== "string" || o.path === "" || typeof o.line !== "number" || !Number.isInteger(o.line) || o.line < 1) return void 0;
153
+ if (typeof o.at !== "string") return void 0;
154
+ const out = { path: o.path, line: o.line, at: o.at };
155
+ if (typeof o.column === "number") out.column = o.column;
156
+ if (typeof o.component === "string") out.component = o.component;
157
+ if (typeof o.url === "string") out.url = o.url;
158
+ return out;
159
+ }
160
+ function readPointedAt(worktree) {
161
+ let text2;
162
+ try {
163
+ text2 = readFileSync(pointedAtFile(worktree), "utf8");
164
+ } catch {
165
+ return null;
166
+ }
167
+ const lines2 = text2.split("\n");
168
+ for (let i = lines2.length - 1; i >= 0; i--) {
169
+ if (lines2[i].trim() === "") continue;
170
+ const p = parseLine(lines2[i]);
171
+ if (p) return p;
172
+ }
173
+ return null;
174
+ }
175
+ function dropFileMtime(worktree) {
176
+ try {
177
+ return statSync(pointedAtFile(worktree)).mtimeMs;
178
+ } catch {
179
+ return null;
180
+ }
181
+ }
182
+
106
183
  // src/periscope-state.ts
107
- import { existsSync as existsSync2, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
184
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
108
185
  import { connect } from "net";
109
- import { join } from "path";
110
- var dirOf = (stateDir) => join(stateDir, "periscopes");
111
- var fileOf = (stateDir, ticketRecordId) => join(dirOf(stateDir), `${ticketRecordId}.json`);
186
+ import { join as join2 } from "path";
187
+ var dirOf = (stateDir) => join2(stateDir, "periscopes");
188
+ var fileOf = (stateDir, ticketRecordId) => join2(dirOf(stateDir), `${ticketRecordId}.json`);
112
189
  function writePeriscopeSidecar(stateDir, sc) {
113
- mkdirSync(dirOf(stateDir), { recursive: true });
114
- writeFileSync(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
190
+ mkdirSync2(dirOf(stateDir), { recursive: true });
191
+ writeFileSync2(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
115
192
  `);
116
193
  }
117
194
  function removePeriscopeSidecar(stateDir, ticketRecordId) {
@@ -119,12 +196,12 @@ function removePeriscopeSidecar(stateDir, ticketRecordId) {
119
196
  }
120
197
  function readPeriscopeSidecars(stateDir) {
121
198
  const dir = dirOf(stateDir);
122
- if (!existsSync2(dir)) return [];
199
+ if (!existsSync3(dir)) return [];
123
200
  const out = [];
124
201
  for (const name of readdirSync(dir)) {
125
202
  if (!name.endsWith(".json")) continue;
126
203
  try {
127
- const sc = JSON.parse(readFileSync(join(dir, name), "utf8"));
204
+ const sc = JSON.parse(readFileSync2(join2(dir, name), "utf8"));
128
205
  if (sc && typeof sc.ticketRecordId === "string" && typeof sc.url === "string") out.push(sc);
129
206
  } catch {
130
207
  }
@@ -140,11 +217,11 @@ function urlPort(url) {
140
217
  }
141
218
  }
142
219
  function portListening(port, timeoutMs = 500) {
143
- return new Promise((resolve7) => {
220
+ return new Promise((resolve8) => {
144
221
  const s = connect({ port, host: "127.0.0.1" });
145
222
  const done = (ok) => {
146
223
  s.destroy();
147
- resolve7(ok);
224
+ resolve8(ok);
148
225
  };
149
226
  s.setTimeout(timeoutMs, () => done(false));
150
227
  s.once("connect", () => done(true));
@@ -154,7 +231,7 @@ function portListening(port, timeoutMs = 500) {
154
231
  async function periscopesForStatus(stateDir, probe = portListening) {
155
232
  return Promise.all(readPeriscopeSidecars(stateDir).map(async (sc) => {
156
233
  const port = urlPort(sc.url);
157
- return { ...sc, serving: port === void 0 ? false : await probe(port) };
234
+ return { ...sc, serving: port === void 0 ? false : await probe(port), pointedAt: readPointedAt(sc.worktree) };
158
235
  }));
159
236
  }
160
237
 
@@ -242,23 +319,23 @@ function shellInitScript(shell) {
242
319
  }
243
320
 
244
321
  // src/config.ts
245
- import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
246
- import { resolve, dirname as dirname4, isAbsolute, join as join12, basename as basename4 } from "path";
322
+ import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
323
+ import { resolve as resolve2, dirname as dirname5, isAbsolute as isAbsolute2, join as join13, basename as basename4 } from "path";
247
324
  import { homedir as homedir6, tmpdir } from "os";
248
325
  import { parse as parse2 } from "yaml";
249
326
 
250
327
  // src/events.ts
251
328
  import {
252
- appendFileSync,
253
- mkdirSync as mkdirSync2,
254
- existsSync as existsSync3,
255
- statSync,
329
+ appendFileSync as appendFileSync2,
330
+ mkdirSync as mkdirSync3,
331
+ existsSync as existsSync4,
332
+ statSync as statSync2,
256
333
  renameSync,
257
334
  openSync,
258
335
  closeSync,
259
336
  rmSync as rmSync2
260
337
  } from "fs";
261
- import { join as join2, dirname } from "path";
338
+ import { join as join3, dirname as dirname2 } from "path";
262
339
  function render(e) {
263
340
  const scope = e.role ? `${e.step}[${e.role}]` : e.step;
264
341
  const ticket = e.ticket ? ` ${e.ticket}` : "";
@@ -270,7 +347,7 @@ var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
270
347
  var ROTATE_LOCK_STALE_MS = 6e4;
271
348
  function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
272
349
  try {
273
- if (!existsSync3(file) || statSync(file).size <= maxBytes) return void 0;
350
+ if (!existsSync4(file) || statSync2(file).size <= maxBytes) return void 0;
274
351
  } catch {
275
352
  return void 0;
276
353
  }
@@ -280,7 +357,7 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
280
357
  closeSync(openSync(lock, "wx"));
281
358
  } catch {
282
359
  try {
283
- if (Date.now() - statSync(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
360
+ if (Date.now() - statSync2(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
284
361
  rmSync2(lock, { force: true });
285
362
  closeSync(openSync(lock, "wx"));
286
363
  } catch {
@@ -288,10 +365,10 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
288
365
  }
289
366
  }
290
367
  try {
291
- if (statSync(file).size <= maxBytes) return void 0;
368
+ if (statSync2(file).size <= maxBytes) return void 0;
292
369
  const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
293
- let dest = join2(dirname(file), `events.${stamp}.jsonl`);
294
- for (let n = 1; existsSync3(dest); n++) dest = join2(dirname(file), `events.${stamp}-${n}.jsonl`);
370
+ let dest = join3(dirname2(file), `events.${stamp}.jsonl`);
371
+ for (let n = 1; existsSync4(dest); n++) dest = join3(dirname2(file), `events.${stamp}-${n}.jsonl`);
295
372
  renameSync(file, dest);
296
373
  return dest;
297
374
  } finally {
@@ -314,7 +391,7 @@ var Emitter = class _Emitter {
314
391
  for (const f of [opts.eventFile, opts.logFile]) {
315
392
  if (!f) continue;
316
393
  try {
317
- mkdirSync2(dirname(f), { recursive: true });
394
+ mkdirSync3(dirname2(f), { recursive: true });
318
395
  } catch {
319
396
  }
320
397
  }
@@ -342,12 +419,12 @@ var Emitter = class _Emitter {
342
419
  } catch {
343
420
  }
344
421
  try {
345
- if (this.opts.eventFile) appendFileSync(this.opts.eventFile, `${JSON.stringify(e)}
422
+ if (this.opts.eventFile) appendFileSync2(this.opts.eventFile, `${JSON.stringify(e)}
346
423
  `);
347
424
  } catch {
348
425
  }
349
426
  try {
350
- if (this.opts.logFile) appendFileSync(this.opts.logFile, `${line}
427
+ if (this.opts.logFile) appendFileSync2(this.opts.logFile, `${line}
351
428
  `);
352
429
  } catch {
353
430
  }
@@ -390,18 +467,18 @@ var Emitter = class _Emitter {
390
467
  }
391
468
  }
392
469
  };
393
- var eventFileFor = (stateDir) => join2(stateDir, "events.jsonl");
470
+ var eventFileFor = (stateDir) => join3(stateDir, "events.jsonl");
394
471
 
395
472
  // src/pair-install.ts
396
- import { existsSync as existsSync6, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
473
+ import { existsSync as existsSync7, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
397
474
  import { spawnSync as spawnSync3 } from "child_process";
398
475
  import { homedir as homedir5 } from "os";
399
- import { join as join8 } from "path";
476
+ import { join as join9 } from "path";
400
477
 
401
478
  // src/adapters/claude.ts
402
- import { readFileSync as readFileSync6 } from "fs";
479
+ import { readFileSync as readFileSync7 } from "fs";
403
480
  import { homedir as homedir3 } from "os";
404
- import { join as join6 } from "path";
481
+ import { join as join7 } from "path";
405
482
 
406
483
  // src/stream.ts
407
484
  function toolTarget(name, input) {
@@ -460,19 +537,19 @@ function extractResult(line) {
460
537
 
461
538
  // src/claude-mcp.ts
462
539
  import { spawnSync } from "child_process";
463
- import { readFileSync as readFileSync4 } from "fs";
540
+ import { readFileSync as readFileSync5 } from "fs";
464
541
  import { homedir } from "os";
465
- import { join as join4 } from "path";
542
+ import { join as join5 } from "path";
466
543
 
467
544
  // src/mcp-registrations.ts
468
545
  import { createHash } from "crypto";
469
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
470
- import { join as join3 } from "path";
471
- var fileFor = (stateDir) => join3(stateDir, "mcp-registrations.json");
546
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
547
+ import { join as join4 } from "path";
548
+ var fileFor = (stateDir) => join4(stateDir, "mcp-registrations.json");
472
549
  var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
473
550
  function readMcpRegistrations(stateDir) {
474
551
  try {
475
- const j = JSON.parse(readFileSync3(fileFor(stateDir), "utf8"));
552
+ const j = JSON.parse(readFileSync4(fileFor(stateDir), "utf8"));
476
553
  return Array.isArray(j) ? j : [];
477
554
  } catch {
478
555
  return [];
@@ -480,8 +557,8 @@ function readMcpRegistrations(stateDir) {
480
557
  }
481
558
  function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
482
559
  const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
483
- mkdirSync3(stateDir, { recursive: true });
484
- writeFileSync2(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
560
+ mkdirSync4(stateDir, { recursive: true });
561
+ writeFileSync3(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
485
562
  `, { mode: 384 });
486
563
  }
487
564
  function findMcpRegistration(stateDir, r) {
@@ -504,7 +581,7 @@ function runClaude(bin, args) {
504
581
  }
505
582
  function userScopeEntry(home) {
506
583
  try {
507
- const j = JSON.parse(readFileSync4(join4(home, ".claude.json"), "utf8"));
584
+ const j = JSON.parse(readFileSync5(join5(home, ".claude.json"), "utf8"));
508
585
  return j.mcpServers?.[MCP_SERVER_NAME];
509
586
  } catch {
510
587
  return void 0;
@@ -588,8 +665,8 @@ function removeClaudeMcp(o) {
588
665
  }
589
666
 
590
667
  // src/launcher.ts
591
- import { basename, join as join5 } from "path";
592
- import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
668
+ import { basename, join as join6 } from "path";
669
+ import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
593
670
  import { homedir as homedir2 } from "os";
594
671
  function isOriLauncher(spec) {
595
672
  return !!spec.launcher && basename(spec.launcher) === "ori";
@@ -598,7 +675,7 @@ function isOmlxLauncher(spec) {
598
675
  return !!spec.launcher && basename(spec.launcher) === "omlx";
599
676
  }
600
677
  function defaultOmlxBin(home = homedir2()) {
601
- return join5(home, ".omlx", "bin", "omlx");
678
+ return join6(home, ".omlx", "bin", "omlx");
602
679
  }
603
680
  function launcherExecutable(spec, home) {
604
681
  if (!spec.launcher) return void 0;
@@ -651,8 +728,8 @@ function omlxModelPresent(model, dirs) {
651
728
  });
652
729
  }
653
730
  function readOmlxSettings(home = homedir2()) {
654
- const p = join5(home, ".omlx", "settings.json");
655
- return existsSync4(p) ? parseOmlxSettings(readFileSync5(p, "utf8")) : void 0;
731
+ const p = join6(home, ".omlx", "settings.json");
732
+ return existsSync5(p) ? parseOmlxSettings(readFileSync6(p, "utf8")) : void 0;
656
733
  }
657
734
 
658
735
  // src/capability.ts
@@ -716,7 +793,7 @@ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
716
793
  if (spec.auth === "workspace") return `workspace${spec.inference ? ` (${new URL(spec.inference.baseUrl).host})` : " (endpoint resolved per route at run time)"}`;
717
794
  let fromSettings;
718
795
  try {
719
- const parsed = JSON.parse(readFileSync6(join6(home, ".claude", "settings.json"), "utf8"));
796
+ const parsed = JSON.parse(readFileSync7(join7(home, ".claude", "settings.json"), "utf8"));
720
797
  const e = parsed.env ?? {};
721
798
  const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
722
799
  if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
@@ -820,12 +897,12 @@ var claudeAdapter = {
820
897
  // src/adapters/codex.ts
821
898
  import { spawnSync as spawnSync2 } from "child_process";
822
899
  import { homedir as homedir4 } from "os";
823
- import { join as join7 } from "path";
824
- import { rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
900
+ import { join as join8 } from "path";
901
+ import { rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
825
902
 
826
903
  // src/pair-files.ts
827
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, rmSync as rmSync3 } from "fs";
828
- import { dirname as dirname2 } from "path";
904
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4, rmSync as rmSync3 } from "fs";
905
+ import { dirname as dirname3 } from "path";
829
906
  var PAIR_BLOCK_START = "<!-- crew:pair start -->";
830
907
  var PAIR_BLOCK_END = "<!-- crew:pair end -->";
831
908
  var PAIR_KEY_VAR = "CREW_API_KEY";
@@ -865,11 +942,11 @@ function removePairBlock(text2) {
865
942
  return text2.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
866
943
  }
867
944
  function readIfExists(path) {
868
- return existsSync5(path) ? readFileSync7(path, "utf8") : void 0;
945
+ return existsSync6(path) ? readFileSync8(path, "utf8") : void 0;
869
946
  }
870
947
  function writeFileMkdir(path, text2) {
871
- mkdirSync4(dirname2(path), { recursive: true });
872
- writeFileSync3(path, text2);
948
+ mkdirSync5(dirname3(path), { recursive: true });
949
+ writeFileSync4(path, text2);
873
950
  }
874
951
  function installBlockFile(path, harness, dryRun) {
875
952
  const before = readIfExists(path) ?? "";
@@ -885,7 +962,7 @@ function removeBlockFile(path, dryRun) {
885
962
  if (dryRun) return true;
886
963
  const after = removePairBlock(before);
887
964
  if (after.trim() === "") rmSync3(path);
888
- else writeFileSync3(path, after);
965
+ else writeFileSync4(path, after);
889
966
  return true;
890
967
  }
891
968
  function blockVerdict(path, harness) {
@@ -1012,8 +1089,8 @@ function codexTable(url) {
1012
1089
  return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
1013
1090
  }
1014
1091
  var codexPaths = (home) => ({
1015
- instructions: join7(home, ".codex", "AGENTS.md"),
1016
- config: join7(home, ".codex", "config.toml")
1092
+ instructions: join8(home, ".codex", "AGENTS.md"),
1093
+ config: join8(home, ".codex", "config.toml")
1017
1094
  });
1018
1095
  function codexMcpState(config, url) {
1019
1096
  const lines2 = config.split("\n");
@@ -1064,7 +1141,7 @@ var codexPair = {
1064
1141
  if (hadMcp && !o.dryRun) {
1065
1142
  const after = removeTomlTable(config, `mcp_servers.${SERVER}`);
1066
1143
  if (after === "") rmSync4(p.config);
1067
- else writeFileSync4(p.config, after);
1144
+ else writeFileSync5(p.config, after);
1068
1145
  }
1069
1146
  const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
1070
1147
  return hadBlock || hadMcp ? `pair(codex): ${o.dryRun ? "(dry run) would remove" : "removed"} ${what}${kept}` : `pair(codex): nothing to remove${kept}`;
@@ -1160,8 +1237,8 @@ var codexAdapter = {
1160
1237
 
1161
1238
  // src/pair-install.ts
1162
1239
  var geminiPaths = (home) => ({
1163
- instructions: join8(home, ".gemini", "GEMINI.md"),
1164
- config: join8(home, ".gemini", "settings.json")
1240
+ instructions: join9(home, ".gemini", "GEMINI.md"),
1241
+ config: join9(home, ".gemini", "settings.json")
1165
1242
  });
1166
1243
  function readGeminiSettings(path) {
1167
1244
  const text2 = readIfExists(path);
@@ -1249,7 +1326,7 @@ var geminiPair = {
1249
1326
  if (Object.keys(rest).length > 0) next.mcpServers = rest;
1250
1327
  else delete next.mcpServers;
1251
1328
  if (Object.keys(next).length === 0) rmSync5(p.config);
1252
- else writeFileSync5(p.config, `${JSON.stringify(next, null, 2)}
1329
+ else writeFileSync6(p.config, `${JSON.stringify(next, null, 2)}
1253
1330
  `);
1254
1331
  }
1255
1332
  const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
@@ -1260,9 +1337,9 @@ var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
1260
1337
  function vscodeUserDirs(o) {
1261
1338
  const home = o.home ?? homedir5();
1262
1339
  const platform2 = o.platform ?? process.platform;
1263
- const root = platform2 === "darwin" ? join8(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join8(home, "AppData", "Roaming") : join8(home, ".config");
1264
- const all = VSCODE_FLAVOURS.map((f) => join8(root, f, "User"));
1265
- const present = all.filter((d) => existsSync6(d));
1340
+ const root = platform2 === "darwin" ? join9(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join9(home, "AppData", "Roaming") : join9(home, ".config");
1341
+ const all = VSCODE_FLAVOURS.map((f) => join9(root, f, "User"));
1342
+ const present = all.filter((d) => existsSync7(d));
1266
1343
  return present.length > 0 ? present : [all[0]];
1267
1344
  }
1268
1345
  var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
@@ -1300,7 +1377,7 @@ function removeCopilotInstructions(path, dryRun) {
1300
1377
  if (dryRun) return true;
1301
1378
  const after = removePairBlock(before);
1302
1379
  if (after.trim() === "" || after.trim() === COPILOT_FRONTMATTER.trim()) rmSync5(path);
1303
- else writeFileSync5(path, after);
1380
+ else writeFileSync6(path, after);
1304
1381
  return true;
1305
1382
  }
1306
1383
  var copilotPair = {
@@ -1309,8 +1386,8 @@ var copilotPair = {
1309
1386
  installPair(o) {
1310
1387
  const lines2 = [];
1311
1388
  for (const dir of vscodeUserDirs(o)) {
1312
- const mcpPath = join8(dir, "mcp.json");
1313
- const instrPath = join8(dir, "prompts", "crew-pair.instructions.md");
1389
+ const mcpPath = join9(dir, "mcp.json");
1390
+ const instrPath = join9(dir, "prompts", "crew-pair.instructions.md");
1314
1391
  const settings = readVscodeMcp(mcpPath);
1315
1392
  if (!settings) {
1316
1393
  lines2.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
@@ -1338,8 +1415,8 @@ var copilotPair = {
1338
1415
  },
1339
1416
  verifyPair(o) {
1340
1417
  const verdicts = vscodeUserDirs(o).map((dir) => {
1341
- const instrPath = join8(dir, "prompts", "crew-pair.instructions.md");
1342
- const mcpPath = join8(dir, "mcp.json");
1418
+ const instrPath = join9(dir, "prompts", "crew-pair.instructions.md");
1419
+ const mcpPath = join9(dir, "mcp.json");
1343
1420
  const b = blockVerdict(instrPath, "copilot");
1344
1421
  if (!b.ok) return b;
1345
1422
  const settings = readVscodeMcp(mcpPath);
@@ -1354,8 +1431,8 @@ var copilotPair = {
1354
1431
  removePair(o) {
1355
1432
  const lines2 = [];
1356
1433
  for (const dir of vscodeUserDirs(o)) {
1357
- const mcpPath = join8(dir, "mcp.json");
1358
- const hadBlock = removeCopilotInstructions(join8(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1434
+ const mcpPath = join9(dir, "mcp.json");
1435
+ const hadBlock = removeCopilotInstructions(join9(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1359
1436
  const settings = readVscodeMcp(mcpPath);
1360
1437
  const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
1361
1438
  const hadMcp = st.state === "registered";
@@ -1366,7 +1443,7 @@ var copilotPair = {
1366
1443
  if (Object.keys(rest).length > 0) next.servers = rest;
1367
1444
  else delete next.servers;
1368
1445
  if (Object.keys(next).length === 0) rmSync5(mcpPath);
1369
- else writeFileSync5(mcpPath, `${JSON.stringify(next, null, 2)}
1446
+ else writeFileSync6(mcpPath, `${JSON.stringify(next, null, 2)}
1370
1447
  `);
1371
1448
  }
1372
1449
  const what = hadBlock && hadMcp ? `the Pair instructions and ${SERVER} server` : hadBlock ? "the Pair instructions" : `the ${SERVER} server`;
@@ -1615,9 +1692,9 @@ function resolveSeat(ship, route2, role, persona = {}, repo) {
1615
1692
  }
1616
1693
 
1617
1694
  // src/forges.ts
1618
- import { execFileSync } from "child_process";
1619
- import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync6, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
1620
- import { dirname as dirname3, join as join9 } from "path";
1695
+ import { execFileSync as execFileSync2 } from "child_process";
1696
+ import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
1697
+ import { dirname as dirname4, join as join10 } from "path";
1621
1698
  var ForgeError = class extends Error {
1622
1699
  };
1623
1700
  var DEFAULT_FORGE_USERNAME = "x-access-token";
@@ -1645,7 +1722,7 @@ function forgeDescriptorFor(host) {
1645
1722
  }
1646
1723
  var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
1647
1724
  function defaultTokenFilePath(stateDir, host) {
1648
- return join9(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
1725
+ return join10(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
1649
1726
  }
1650
1727
  var envSource = {
1651
1728
  read: (s, c) => c.env[s.var]?.trim() || null,
@@ -1654,19 +1731,19 @@ var envSource = {
1654
1731
  var fileSource = {
1655
1732
  read(s, c) {
1656
1733
  const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
1657
- if (!existsSync7(path)) return null;
1658
- const mode = statSync2(path).mode & 511;
1734
+ if (!existsSync8(path)) return null;
1735
+ const mode = statSync3(path).mode & 511;
1659
1736
  if (mode & 63) {
1660
1737
  throw new ForgeError(
1661
1738
  `${path} is mode ${mode.toString(8).padStart(3, "0")}; a forge token file must be owner-only (chmod 600 ${path})`
1662
1739
  );
1663
1740
  }
1664
- return readFileSync8(path, "utf8").trim() || null;
1741
+ return readFileSync9(path, "utf8").trim() || null;
1665
1742
  },
1666
1743
  write(s, token, c) {
1667
1744
  const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
1668
- mkdirSync5(dirname3(path), { recursive: true, mode: 448 });
1669
- writeFileSync6(path, `${token}
1745
+ mkdirSync6(dirname4(path), { recursive: true, mode: 448 });
1746
+ writeFileSync7(path, `${token}
1670
1747
  `, { mode: 384 });
1671
1748
  chmodSync(path, 384);
1672
1749
  },
@@ -1680,13 +1757,13 @@ var keychainSource = {
1680
1757
  read(s, c) {
1681
1758
  try {
1682
1759
  if (process.platform === "darwin") {
1683
- return execFileSync(
1760
+ return execFileSync2(
1684
1761
  "security",
1685
1762
  ["find-generic-password", "-s", keychainService(s), "-a", c.host, "-w"],
1686
1763
  { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
1687
1764
  ).trim() || null;
1688
1765
  }
1689
- return execFileSync(
1766
+ return execFileSync2(
1690
1767
  "secret-tool",
1691
1768
  ["lookup", "service", keychainService(s), "account", c.host],
1692
1769
  { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
@@ -1699,13 +1776,13 @@ var keychainSource = {
1699
1776
  try {
1700
1777
  if (process.platform === "darwin") {
1701
1778
  const q2 = (v) => `"${v.replace(/(["\\])/g, "\\$1")}"`;
1702
- execFileSync("security", ["-i"], {
1779
+ execFileSync2("security", ["-i"], {
1703
1780
  input: `add-generic-password -U -s ${q2(keychainService(s))} -a ${q2(c.host)} -w ${q2(token)}
1704
1781
  `,
1705
1782
  stdio: ["pipe", "ignore", "pipe"]
1706
1783
  });
1707
1784
  } else {
1708
- execFileSync(
1785
+ execFileSync2(
1709
1786
  "secret-tool",
1710
1787
  ["store", "--label", `crew ${c.host}`, "service", keychainService(s), "account", c.host],
1711
1788
  { input: token, stdio: ["pipe", "ignore", "pipe"] }
@@ -1718,9 +1795,9 @@ var keychainSource = {
1718
1795
  erase(s, c) {
1719
1796
  try {
1720
1797
  if (process.platform === "darwin") {
1721
- execFileSync("security", ["delete-generic-password", "-s", keychainService(s), "-a", c.host], { stdio: "ignore" });
1798
+ execFileSync2("security", ["delete-generic-password", "-s", keychainService(s), "-a", c.host], { stdio: "ignore" });
1722
1799
  } else {
1723
- execFileSync("secret-tool", ["clear", "service", keychainService(s), "account", c.host], { stdio: "ignore" });
1800
+ execFileSync2("secret-tool", ["clear", "service", keychainService(s), "account", c.host], { stdio: "ignore" });
1724
1801
  }
1725
1802
  } catch {
1726
1803
  }
@@ -1800,10 +1877,10 @@ function answerCredential(action, input, forges, stateDir, env = process.env) {
1800
1877
  password=${token}
1801
1878
  `;
1802
1879
  }
1803
- var missesPath = (stateDir) => join9(stateDir, "forge-misses.json");
1880
+ var missesPath = (stateDir) => join10(stateDir, "forge-misses.json");
1804
1881
  function readForgeMisses(stateDir) {
1805
1882
  try {
1806
- const raw = JSON.parse(readFileSync8(missesPath(stateDir), "utf8"));
1883
+ const raw = JSON.parse(readFileSync9(missesPath(stateDir), "utf8"));
1807
1884
  return Object.entries(raw).filter(([, t]) => typeof t === "string").map(([host, lastMissAt]) => ({ host, lastMissAt })).sort((a, b) => a.host.localeCompare(b.host));
1808
1885
  } catch {
1809
1886
  return [];
@@ -1818,8 +1895,8 @@ function noteForgeMiss(stateDir, host, at) {
1818
1895
  if (!(bare in cur)) return;
1819
1896
  delete cur[bare];
1820
1897
  } else cur[bare] = at;
1821
- mkdirSync5(stateDir, { recursive: true });
1822
- writeFileSync6(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
1898
+ mkdirSync6(stateDir, { recursive: true });
1899
+ writeFileSync7(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
1823
1900
  } catch {
1824
1901
  }
1825
1902
  }
@@ -1921,8 +1998,8 @@ import { hostFromUrl } from "@tablation/client";
1921
1998
 
1922
1999
  // src/secret-store.ts
1923
2000
  import { createHash as createHash2 } from "crypto";
1924
- import { accessSync, constants, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync3, writeFileSync as writeFileSync7, chmodSync as chmodSync2 } from "fs";
1925
- import { join as join10 } from "path";
2001
+ import { accessSync, constants, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync2, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync8, chmodSync as chmodSync2 } from "fs";
2002
+ import { join as join11 } from "path";
1926
2003
  import { getSessionStore as getOsSessionStore } from "@tablation/client";
1927
2004
  var SECRET_STORE_MODES = ["auto", "os", "file"];
1928
2005
  function isSecretStoreMode(v) {
@@ -1933,7 +2010,7 @@ function configureSecretStore(c) {
1933
2010
  configured = c;
1934
2011
  }
1935
2012
  function keysDir(stateDir) {
1936
- return join10(stateDir, "keys");
2013
+ return join11(stateDir, "keys");
1937
2014
  }
1938
2015
  function entryName(host, slug2) {
1939
2016
  return `${createHash2("sha256").update(`${host}\0${slug2}`).digest("hex")}.json`;
@@ -1944,34 +2021,34 @@ var FileSessionStore = class {
1944
2021
  this.dir = dir;
1945
2022
  }
1946
2023
  ensureDir() {
1947
- mkdirSync6(this.dir, { recursive: true, mode: 448 });
1948
- if ((statSync3(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
2024
+ mkdirSync7(this.dir, { recursive: true, mode: 448 });
2025
+ if ((statSync4(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
1949
2026
  }
1950
2027
  async get(host, slug2) {
1951
- const file = join10(this.dir, entryName(host, slug2));
1952
- if (!existsSync8(this.dir)) return void 0;
2028
+ const file = join11(this.dir, entryName(host, slug2));
2029
+ if (!existsSync9(this.dir)) return void 0;
1953
2030
  accessSync(this.dir, constants.R_OK | constants.X_OK);
1954
- if (!existsSync8(file)) return void 0;
1955
- return JSON.parse(readFileSync9(file, "utf8"));
2031
+ if (!existsSync9(file)) return void 0;
2032
+ return JSON.parse(readFileSync10(file, "utf8"));
1956
2033
  }
1957
2034
  async set(host, slug2, session) {
1958
2035
  this.ensureDir();
1959
- const file = join10(this.dir, entryName(host, slug2));
2036
+ const file = join11(this.dir, entryName(host, slug2));
1960
2037
  const tmp = `${file}.${process.pid}.tmp`;
1961
- writeFileSync7(tmp, JSON.stringify(session), { mode: 384 });
2038
+ writeFileSync8(tmp, JSON.stringify(session), { mode: 384 });
1962
2039
  renameSync2(tmp, file);
1963
2040
  }
1964
2041
  async clear(host, slug2) {
1965
- if (!existsSync8(this.dir)) return;
1966
- rmSync7(join10(this.dir, entryName(host, slug2)), { force: true });
2042
+ if (!existsSync9(this.dir)) return;
2043
+ rmSync7(join11(this.dir, entryName(host, slug2)), { force: true });
1967
2044
  }
1968
2045
  /** Whether a key could be written here right now, without leaving anything behind. */
1969
2046
  writable() {
1970
2047
  try {
1971
2048
  this.ensureDir();
1972
2049
  accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
1973
- const probe = join10(this.dir, `.probe.${process.pid}`);
1974
- writeFileSync7(probe, "", { mode: 384 });
2050
+ const probe = join11(this.dir, `.probe.${process.pid}`);
2051
+ writeFileSync8(probe, "", { mode: 384 });
1975
2052
  rmSync7(probe, { force: true });
1976
2053
  return { ok: true };
1977
2054
  } catch (e) {
@@ -2022,11 +2099,11 @@ function osStoreReachable(env = process.env, platform2 = process.platform, hasBi
2022
2099
  if (platform2 !== "linux") return true;
2023
2100
  if (!hasBinary("secret-tool")) return false;
2024
2101
  if (env.DBUS_SESSION_BUS_ADDRESS) return true;
2025
- return !!env.XDG_RUNTIME_DIR && existsSync8(join10(env.XDG_RUNTIME_DIR, "bus"));
2102
+ return !!env.XDG_RUNTIME_DIR && existsSync9(join11(env.XDG_RUNTIME_DIR, "bus"));
2026
2103
  }
2027
2104
  function onPath(name) {
2028
2105
  for (const dir of (process.env.PATH ?? "").split(":")) {
2029
- if (dir && existsSync8(join10(dir, name))) return true;
2106
+ if (dir && existsSync9(join11(dir, name))) return true;
2030
2107
  }
2031
2108
  return false;
2032
2109
  }
@@ -2093,8 +2170,8 @@ function explain(ship, required) {
2093
2170
  }
2094
2171
 
2095
2172
  // src/repo-config.ts
2096
- import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
2097
- import { join as join11, basename as basename3 } from "path";
2173
+ import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
2174
+ import { join as join12, basename as basename3 } from "path";
2098
2175
  import { parse } from "yaml";
2099
2176
 
2100
2177
  // src/sandbox-policy.ts
@@ -2305,6 +2382,7 @@ var HOOK_NAMES = [
2305
2382
  "isolate",
2306
2383
  "handoff",
2307
2384
  "preview",
2385
+ "map",
2308
2386
  "ci_status",
2309
2387
  "version",
2310
2388
  "bump",
@@ -2375,8 +2453,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
2375
2453
  var PROVIDERS = ["github", "buildkite", "other", "none"];
2376
2454
  function findRepoConfig(dir) {
2377
2455
  for (const name of REPO_CONFIG_FILENAMES) {
2378
- const p = join11(dir, name);
2379
- if (existsSync9(p)) return p;
2456
+ const p = join12(dir, name);
2457
+ if (existsSync10(p)) return p;
2380
2458
  }
2381
2459
  return null;
2382
2460
  }
@@ -2656,7 +2734,7 @@ function parseRepoConfig(text2, file) {
2656
2734
  function loadRepoConfig(dir) {
2657
2735
  const file = findRepoConfig(dir);
2658
2736
  if (!file) return null;
2659
- return parseRepoConfig(readFileSync10(file, "utf8"), file);
2737
+ return parseRepoConfig(readFileSync11(file, "utf8"), file);
2660
2738
  }
2661
2739
  function hookLabel(cfg, hook2) {
2662
2740
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -2937,6 +3015,7 @@ var ConfigError = class extends Error {
2937
3015
  };
2938
3016
  var DEFAULT_MAX_PERISCOPES = 3;
2939
3017
  var DEFAULT_PERISCOPE_IDLE_MINUTES = 120;
3018
+ var DEFAULT_MAP_TOKEN_BUDGET = 1e3;
2940
3019
  function parseCloneScheme(v) {
2941
3020
  if (v === void 0 || v === null || v === "") return void 0;
2942
3021
  if (v === "ssh" || v === "https") return v;
@@ -3029,6 +3108,7 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
3029
3108
  "previewHost",
3030
3109
  "maxPeriscopes",
3031
3110
  "periscopeIdleMinutes",
3111
+ "mapTokenBudget",
3032
3112
  "secretStore",
3033
3113
  "env",
3034
3114
  "runner",
@@ -3178,14 +3258,14 @@ function mergeRouteRelease(route2, override) {
3178
3258
  function expand(p, base) {
3179
3259
  let out = p.startsWith("~") ? p.replace(/^~/, homedir6()) : p;
3180
3260
  out = out.replace(/\$\{?HOME\}?/g, homedir6());
3181
- return isAbsolute(out) ? out : resolve(base, out);
3261
+ return isAbsolute2(out) ? out : resolve2(base, out);
3182
3262
  }
3183
3263
  function resolvePromptsDir(promptSet, crewHome, base) {
3184
- if (!promptSet) return join12(crewHome, "prompts", "default");
3264
+ if (!promptSet) return join13(crewHome, "prompts", "default");
3185
3265
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
3186
3266
  return expand(promptSet, base);
3187
3267
  }
3188
- return join12(crewHome, "prompts", promptSet);
3268
+ return join13(crewHome, "prompts", promptSet);
3189
3269
  }
3190
3270
  var Missing = class {
3191
3271
  paths = [];
@@ -3209,36 +3289,36 @@ var Missing = class {
3209
3289
  };
3210
3290
  function crewVersion(crewHome) {
3211
3291
  try {
3212
- const pkg = JSON.parse(readFileSync11(join12(crewHome, "package.json"), "utf8"));
3292
+ const pkg = JSON.parse(readFileSync12(join13(crewHome, "package.json"), "utf8"));
3213
3293
  return pkg.version ?? "0.0.0";
3214
3294
  } catch {
3215
3295
  return "0.0.0";
3216
3296
  }
3217
3297
  }
3218
3298
  function configSearchPath(crewHome) {
3219
- if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
3299
+ if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
3220
3300
  const xdg = process.env.XDG_CONFIG_HOME;
3221
3301
  return [
3222
- ...xdg ? [join12(xdg, "crew", "crew.yaml")] : [],
3223
- join12(homedir6(), ".config", "crew", "crew.yaml"),
3224
- resolve(crewHome, "crew.yaml")
3302
+ ...xdg ? [join13(xdg, "crew", "crew.yaml")] : [],
3303
+ join13(homedir6(), ".config", "crew", "crew.yaml"),
3304
+ resolve2(crewHome, "crew.yaml")
3225
3305
  ];
3226
3306
  }
3227
3307
  function findConfigFile(crewHome) {
3228
3308
  const candidates2 = configSearchPath(crewHome);
3229
- return candidates2.find((c) => existsSync10(c)) ?? candidates2[candidates2.length - 1];
3309
+ return candidates2.find((c) => existsSync11(c)) ?? candidates2[candidates2.length - 1];
3230
3310
  }
3231
- var defaultConfigPath = () => join12(process.env.XDG_CONFIG_HOME ?? join12(homedir6(), ".config"), "crew", "crew.yaml");
3311
+ var defaultConfigPath = () => join13(process.env.XDG_CONFIG_HOME ?? join13(homedir6(), ".config"), "crew", "crew.yaml");
3232
3312
  function routeSlug(route2) {
3233
3313
  return route2.replace(/\//g, "-");
3234
3314
  }
3235
3315
  function resolvedPathFor(stateDir, route2) {
3236
3316
  const [workspace, project] = splitRoute(route2);
3237
- return join12(stateDir, "resolved", workspace, `${project}.json`);
3317
+ return join13(stateDir, "resolved", workspace, `${project}.json`);
3238
3318
  }
3239
3319
  function apiKeyPathFor(stateDir, route2) {
3240
3320
  const [workspace, project] = splitRoute(route2);
3241
- return join12(stateDir, "keys", workspace, `${project}.env`);
3321
+ return join13(stateDir, "keys", workspace, `${project}.env`);
3242
3322
  }
3243
3323
  function splitRoute(route2) {
3244
3324
  const parts = route2.split("/");
@@ -3249,9 +3329,9 @@ function splitRoute(route2) {
3249
3329
  }
3250
3330
  function readResolvedFile(stateDir, route2) {
3251
3331
  const path = resolvedPathFor(stateDir, route2);
3252
- if (!existsSync10(path)) return void 0;
3332
+ if (!existsSync11(path)) return void 0;
3253
3333
  try {
3254
- return JSON.parse(readFileSync11(path, "utf8"));
3334
+ return JSON.parse(readFileSync12(path, "utf8"));
3255
3335
  } catch (e) {
3256
3336
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
3257
3337
  }
@@ -3312,23 +3392,23 @@ function parseResolved(raw, m, where) {
3312
3392
  }
3313
3393
  function loadConfig(crewHome, configFile, opts = {}) {
3314
3394
  const file = configFile ?? findConfigFile(crewHome);
3315
- if (opts.text === void 0 && !existsSync10(file)) {
3395
+ if (opts.text === void 0 && !existsSync11(file)) {
3316
3396
  throw new ConfigError(
3317
3397
  `no config found. Looked in:
3318
3398
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
3319
3399
 
3320
3400
  Create the first of those:
3321
- mkdir -p ${dirname4(defaultConfigPath())} && cp ${join12(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3401
+ mkdir -p ${dirname5(defaultConfigPath())} && cp ${join13(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3322
3402
  );
3323
3403
  }
3324
3404
  let raw;
3325
3405
  try {
3326
- raw = parse2(opts.text ?? readFileSync11(file, "utf8"));
3406
+ raw = parse2(opts.text ?? readFileSync12(file, "utf8"));
3327
3407
  } catch (e) {
3328
3408
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
3329
3409
  }
3330
3410
  if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
3331
- const base = dirname4(file);
3411
+ const base = dirname5(file);
3332
3412
  const missing = new Missing();
3333
3413
  rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
3334
3414
  const shipRaw = raw.ship ?? {};
@@ -3377,6 +3457,10 @@ Create the first of those:
3377
3457
  if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
3378
3458
  missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
3379
3459
  }
3460
+ const mapTokenBudget = shipRaw.mapTokenBudget === void 0 ? DEFAULT_MAP_TOKEN_BUDGET : Number(shipRaw.mapTokenBudget);
3461
+ if (!Number.isInteger(mapTokenBudget) || mapTokenBudget < 1) {
3462
+ missing.add(`ship.mapTokenBudget must be a positive integer (got "${shipRaw.mapTokenBudget}")`);
3463
+ }
3380
3464
  const secretStoreRaw = shipRaw.secretStore === void 0 ? "auto" : shipRaw.secretStore;
3381
3465
  if (!isSecretStoreMode(secretStoreRaw)) {
3382
3466
  missing.add(`ship.secretStore must be one of ${SECRET_STORE_MODES.join(" | ")} (got "${String(shipRaw.secretStore)}")`);
@@ -3442,7 +3526,7 @@ Create the first of those:
3442
3526
  useNvm: shipRaw.useNvm !== false,
3443
3527
  nvmSh: shipRaw.nvmSh,
3444
3528
  stateDir,
3445
- logFile: expand(shipRaw.logFile ?? join12(tmpdir(), "crew.log"), base),
3529
+ logFile: expand(shipRaw.logFile ?? join13(tmpdir(), "crew.log"), base),
3446
3530
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
3447
3531
  maxConcurrentAgents,
3448
3532
  maxConcurrentAgentsSet,
@@ -3460,6 +3544,7 @@ Create the first of those:
3460
3544
  previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
3461
3545
  maxPeriscopes,
3462
3546
  periscopeIdleMinutes,
3547
+ mapTokenBudget,
3463
3548
  secretStore,
3464
3549
  env: parseShipEnv(shipRaw.env)
3465
3550
  },
@@ -3622,7 +3707,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
3622
3707
  const hasRepos = Object.keys(repoDirs).length > 0;
3623
3708
  const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
3624
3709
  const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
3625
- const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join12(reposBasePath, splitRoute(route2)[0]) : "";
3710
+ const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join13(reposBasePath, splitRoute(route2)[0]) : "";
3626
3711
  const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
3627
3712
  if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
3628
3713
  missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
@@ -3671,21 +3756,21 @@ function routeForDir(cfg, dir) {
3671
3756
  }
3672
3757
  function locateDir(cfg, dir, commonDirOf) {
3673
3758
  const within = (base, target2) => {
3674
- const b = resolve(base);
3759
+ const b = resolve2(base);
3675
3760
  return target2 === b || target2.startsWith(`${b}/`);
3676
3761
  };
3677
3762
  const find = (target2) => {
3678
3763
  for (const route2 of cfg.routes) {
3679
3764
  const repo = reposOf(route2).find((x) => within(x.dir, target2));
3680
- if (repo) return { route: route2, repo, checkout: resolve(repo.dir) };
3765
+ if (repo) return { route: route2, repo, checkout: resolve2(repo.dir) };
3681
3766
  }
3682
3767
  return void 0;
3683
3768
  };
3684
- const target = resolve(dir);
3769
+ const target = resolve2(dir);
3685
3770
  const direct = find(target);
3686
3771
  if (direct) return direct;
3687
3772
  const common = commonDirOf?.(target);
3688
- return common ? find(dirname4(resolve(target, common))) : void 0;
3773
+ return common ? find(dirname5(resolve2(target, common))) : void 0;
3689
3774
  }
3690
3775
  function findRoute(cfg, route2) {
3691
3776
  if (!route2) {
@@ -3720,8 +3805,8 @@ function repoIdForName(r, name) {
3720
3805
  }
3721
3806
  function resolveApiKey(r) {
3722
3807
  if (r.apiKey) return r.apiKey;
3723
- if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
3724
- for (const line of readFileSync11(r.apiKeyFile, "utf8").split("\n")) {
3808
+ if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
3809
+ for (const line of readFileSync12(r.apiKeyFile, "utf8").split("\n")) {
3725
3810
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
3726
3811
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
3727
3812
  }
@@ -3734,9 +3819,9 @@ function resolveApiKey(r) {
3734
3819
  }
3735
3820
  function credentialSource(r) {
3736
3821
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
3737
- if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
3822
+ if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
3738
3823
  const re = new RegExp(`^${r.apiKeyVar}=`);
3739
- if (readFileSync11(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3824
+ if (readFileSync12(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3740
3825
  }
3741
3826
  return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
3742
3827
  }
@@ -3792,7 +3877,7 @@ function configuredMembers(rt) {
3792
3877
  return out;
3793
3878
  }
3794
3879
  function defaultRepoDir(basePath, workspace, repoName) {
3795
- return join12(basePath, workspace, repoName);
3880
+ return join13(basePath, workspace, repoName);
3796
3881
  }
3797
3882
  function reposOf(r) {
3798
3883
  const named = Object.entries(r.repos);
@@ -3831,10 +3916,10 @@ function passengerRepoTargets(r) {
3831
3916
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
3832
3917
  }
3833
3918
  function passengerCheckoutDir(basePath, workspace, repoName) {
3834
- return join12(basePath, workspace, ".passenger-checkouts", repoName);
3919
+ return join13(basePath, workspace, ".passenger-checkouts", repoName);
3835
3920
  }
3836
3921
  function passengerMirrorArea(ship) {
3837
- return ship.runner === "sandbox" ? join12(ship.stateDir, "passenger-sources") : void 0;
3922
+ return ship.runner === "sandbox" ? join13(ship.stateDir, "passenger-sources") : void 0;
3838
3923
  }
3839
3924
  function passengerCheckoutTargets(r, mirrorArea) {
3840
3925
  if (!r.hostPassengers) return [];
@@ -3845,7 +3930,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
3845
3930
  const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
3846
3931
  return {
3847
3932
  name: t.name,
3848
- dir: mirrorArea ? join12(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
3933
+ dir: mirrorArea ? join13(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
3849
3934
  remote: t.remote ?? (id ? remotes[id] : void 0),
3850
3935
  ...forgeHost ? { forgeHost } : {}
3851
3936
  };
@@ -3962,6 +4047,7 @@ function runScript(script, opts) {
3962
4047
  stdio: ["ignore", "pipe", "pipe"]
3963
4048
  });
3964
4049
  let output = "";
4050
+ let stdout = "";
3965
4051
  let pending = "";
3966
4052
  const absorb = (buf) => {
3967
4053
  const text2 = buf.toString();
@@ -3972,18 +4058,21 @@ function runScript(script, opts) {
3972
4058
  pending = lines2.pop() ?? "";
3973
4059
  for (const l of lines2) opts.onLine(l);
3974
4060
  };
3975
- child.stdout.on("data", absorb);
4061
+ child.stdout.on("data", (buf) => {
4062
+ stdout += buf.toString();
4063
+ absorb(buf);
4064
+ });
3976
4065
  child.stderr.on("data", absorb);
3977
4066
  const timer = opts.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs) : void 0;
3978
4067
  child.on("error", (err3) => {
3979
4068
  if (timer) clearTimeout(timer);
3980
4069
  resolvePromise({ code: 127, output: `${output}
3981
- crew: cannot run ${shell.bin}: ${err3.message}` });
4070
+ crew: cannot run ${shell.bin}: ${err3.message}`, stdout });
3982
4071
  });
3983
4072
  child.on("close", (code) => {
3984
4073
  if (timer) clearTimeout(timer);
3985
4074
  if (pending && opts.onLine) opts.onLine(pending);
3986
- resolvePromise({ code: code ?? 1, output });
4075
+ resolvePromise({ code: code ?? 1, output, stdout });
3987
4076
  });
3988
4077
  });
3989
4078
  }
@@ -4123,9 +4212,9 @@ function recoveredFailureHeadlines(o, route2) {
4123
4212
  }
4124
4213
 
4125
4214
  // src/install.ts
4126
- import { dirname as dirname5, join as join13, extname } from "path";
4215
+ import { dirname as dirname6, join as join14, extname } from "path";
4127
4216
  import { homedir as homedir7, userInfo } from "os";
4128
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync4, writeFileSync as writeFileSync8, unlinkSync, realpathSync } from "fs";
4217
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync5, writeFileSync as writeFileSync9, unlinkSync, realpathSync } from "fs";
4129
4218
  import { spawnSync as spawnSync4 } from "child_process";
4130
4219
  import { createHash as createHash3 } from "crypto";
4131
4220
 
@@ -4137,7 +4226,7 @@ function isCompiledBinary(moduleUrl) {
4137
4226
  // src/install.ts
4138
4227
  function crewInvocation(crewHome) {
4139
4228
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
4140
- return [process.execPath, join13(crewHome, "bin", "crew")];
4229
+ return [process.execPath, join14(crewHome, "bin", "crew")];
4141
4230
  }
4142
4231
  var INTERVAL_SECONDS = 120;
4143
4232
  function labelFor(crewHome, job) {
@@ -4168,29 +4257,29 @@ function schedulerLogFor(crewLog, job) {
4168
4257
  return `${stem}.${suffix}${ext || ".log"}`;
4169
4258
  }
4170
4259
  function pathFor(ship) {
4171
- const nodeBinDir = dirname5(process.execPath);
4260
+ const nodeBinDir = dirname6(process.execPath);
4172
4261
  const parts = [nodeBinDir];
4173
4262
  if (ship.extraPath) parts.push(ship.extraPath);
4174
4263
  parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
4175
4264
  return parts.join(":");
4176
4265
  }
4177
4266
  function findOnPath(bin, searchPath) {
4178
- const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join13(dir, bin));
4267
+ const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join14(dir, bin));
4179
4268
  for (const candidate of candidates2) {
4180
4269
  try {
4181
4270
  accessSync2(candidate, constants2.X_OK);
4182
- if (statSync4(candidate).isFile()) return candidate;
4271
+ if (statSync5(candidate).isFile()) return candidate;
4183
4272
  } catch {
4184
4273
  }
4185
4274
  }
4186
4275
  return void 0;
4187
4276
  }
4188
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join13(homedir7(), ".docker", "bin")].join(":");
4277
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join14(homedir7(), ".docker", "bin")].join(":");
4189
4278
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
4190
4279
  const schedulerPath = pathFor(ship);
4191
4280
  if (findOnPath("docker", schedulerPath)) return void 0;
4192
4281
  const inShell = findOnPath("docker", shellPath);
4193
- return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname5(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
4282
+ return inShell ? `\`docker\` is not on the scheduler PATH (${schedulerPath}) but is at ${inShell} in your shell; add ${dirname6(inShell)} to ship.extraPath in crew.yaml and run \`crew install\`` : `\`docker\` is not on the scheduler PATH (${schedulerPath}) or your shell PATH; install Docker, then add its directory to ship.extraPath in crew.yaml and run \`crew install\``;
4194
4283
  }
4195
4284
  var USER_INSTALL = { root: false, user: "" };
4196
4285
  function installContext(env = process.env) {
@@ -4200,7 +4289,7 @@ function installContext(env = process.env) {
4200
4289
  }
4201
4290
  var SYSTEM_UNIT_DIR = "/etc/systemd/system";
4202
4291
  function systemUnitPathFor(label, suffix = "service") {
4203
- return join13(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4292
+ return join14(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4204
4293
  }
4205
4294
  function unitEnvironment(ship) {
4206
4295
  return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
@@ -4221,7 +4310,7 @@ function xmlEscape(s) {
4221
4310
  }
4222
4311
  function planLaunchd(ship, crewHome, job) {
4223
4312
  const label = labelFor(crewHome, job);
4224
- const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4313
+ const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4225
4314
  const crewLog = ship.logFile;
4226
4315
  const schedulerLog = schedulerLogFor(crewLog, job);
4227
4316
  const persistent = job === "run";
@@ -4275,7 +4364,7 @@ ${scheduleKeys}
4275
4364
  }
4276
4365
  function planSystemd(ship, crewHome, job, ctx) {
4277
4366
  const label = labelFor(crewHome, job);
4278
- const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join13(homedir7(), ".config", "systemd", "user");
4367
+ const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
4279
4368
  const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
4280
4369
  const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
4281
4370
  ` : "";
@@ -4284,7 +4373,7 @@ function planSystemd(ship, crewHome, job, ctx) {
4284
4373
  lingerCommand: ["loginctl", "enable-linger", ctx.user],
4285
4374
  lingerHint: `the daemon will stop when ${ctx.user} logs out; run \`sudo loginctl enable-linger ${ctx.user}\` once, or run \`crew install\` as root for a system unit`
4286
4375
  };
4287
- const servicePath = join13(unitDir, `${label}.service`);
4376
+ const servicePath = join14(unitDir, `${label}.service`);
4288
4377
  const crewLog = ship.logFile;
4289
4378
  const schedulerLog = schedulerLogFor(crewLog, job);
4290
4379
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -4325,7 +4414,7 @@ WantedBy=${wantedBy}
4325
4414
  ...linger
4326
4415
  };
4327
4416
  }
4328
- const timerPath = join13(unitDir, `${label}.timer`);
4417
+ const timerPath = join14(unitDir, `${label}.timer`);
4329
4418
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
4330
4419
  [Unit]
4331
4420
  Description=${description}
@@ -4400,7 +4489,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
4400
4489
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4401
4490
  const label = labelFor(crewHome, job);
4402
4491
  if (hostShip === "macos") {
4403
- const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4492
+ const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4404
4493
  return {
4405
4494
  mechanism: "launchd",
4406
4495
  unitPaths: [unitPath],
@@ -4410,20 +4499,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4410
4499
  };
4411
4500
  }
4412
4501
  if (hostShip === "linux" && hasSystemd) {
4413
- const system = existsSync11(systemUnitPathFor(label));
4414
- const unitDir = system ? SYSTEM_UNIT_DIR : join13(homedir7(), ".config", "systemd", "user");
4502
+ const system = existsSync12(systemUnitPathFor(label));
4503
+ const unitDir = system ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
4415
4504
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
4416
4505
  if (job === "run") {
4417
4506
  return {
4418
4507
  mechanism: "systemd",
4419
- unitPaths: [join13(unitDir, `${label}.service`)],
4508
+ unitPaths: [join14(unitDir, `${label}.service`)],
4420
4509
  crontabLine: null,
4421
4510
  unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
4422
4511
  };
4423
4512
  }
4424
4513
  return {
4425
4514
  mechanism: "systemd",
4426
- unitPaths: [join13(unitDir, `${label}.service`), join13(unitDir, `${label}.timer`)],
4515
+ unitPaths: [join14(unitDir, `${label}.service`), join14(unitDir, `${label}.timer`)],
4427
4516
  crontabLine: null,
4428
4517
  unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
4429
4518
  };
@@ -4441,7 +4530,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4441
4530
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
4442
4531
  const label = labelFor(crewHome, "run");
4443
4532
  if (hostShip === "macos") {
4444
- const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4533
+ const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4445
4534
  return {
4446
4535
  mechanism: "launchd",
4447
4536
  startCommand: ["launchctl", "load", unitPath],
@@ -4451,7 +4540,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
4451
4540
  }
4452
4541
  if (hostShip === "linux" && hasSystemd) {
4453
4542
  const unit = `${label}.service`;
4454
- const ctl = existsSync11(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
4543
+ const ctl = existsSync12(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
4455
4544
  return {
4456
4545
  mechanism: "systemd",
4457
4546
  startCommand: [...ctl, "start", unit],
@@ -4483,10 +4572,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
4483
4572
  log.emit(`would write ${path}`);
4484
4573
  continue;
4485
4574
  }
4486
- const before = existsSync11(path) ? readFileSync12(path, "utf8") : null;
4575
+ const before = existsSync12(path) ? readFileSync13(path, "utf8") : null;
4487
4576
  if (before !== plan.unitContent[path]) changed2 = true;
4488
- mkdirSync7(dirname5(path), { recursive: true });
4489
- writeFileSync8(path, plan.unitContent[path]);
4577
+ mkdirSync8(dirname6(path), { recursive: true });
4578
+ writeFileSync9(path, plan.unitContent[path]);
4490
4579
  log.emit(`wrote ${path}`);
4491
4580
  }
4492
4581
  if (plan.crontabLine) {
@@ -4550,7 +4639,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
4550
4639
  }
4551
4640
  }
4552
4641
  for (const path of plan.unitPaths) {
4553
- if (!existsSync11(path)) continue;
4642
+ if (!existsSync12(path)) continue;
4554
4643
  if (dryRun) {
4555
4644
  log.emit(`would remove ${path}`);
4556
4645
  continue;
@@ -4569,7 +4658,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
4569
4658
  }
4570
4659
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
4571
4660
  function launchAgentsDir() {
4572
- return join13(homedir7(), "Library", "LaunchAgents");
4661
+ return join14(homedir7(), "Library", "LaunchAgents");
4573
4662
  }
4574
4663
  function xmlUnescape(s) {
4575
4664
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -4586,25 +4675,25 @@ function loadedCrewLabels() {
4586
4675
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
4587
4676
  }
4588
4677
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
4589
- const files = existsSync11(dir) ? readdirSync3(dir) : [];
4678
+ const files = existsSync12(dir) ? readdirSync3(dir) : [];
4590
4679
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
4591
- const plistPath = join13(dir, f);
4680
+ const plistPath = join14(dir, f);
4592
4681
  let program = null;
4593
4682
  try {
4594
- program = programFromPlist(readFileSync12(plistPath, "utf8"));
4683
+ program = programFromPlist(readFileSync13(plistPath, "utf8"));
4595
4684
  } catch {
4596
4685
  }
4597
4686
  return {
4598
4687
  label: f.slice(0, -".plist".length),
4599
4688
  plistPath,
4600
4689
  program,
4601
- programExists: program !== null && existsSync11(program)
4690
+ programExists: program !== null && existsSync12(program)
4602
4691
  };
4603
4692
  });
4604
4693
  const seen = new Set(units.map((u) => u.label));
4605
4694
  for (const label of [...loaded].sort()) {
4606
4695
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
4607
- units.push({ label, plistPath: join13(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4696
+ units.push({ label, plistPath: join14(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4608
4697
  }
4609
4698
  return units;
4610
4699
  }
@@ -4617,9 +4706,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
4617
4706
  log.emit(`would unload and remove ${unit.label}`);
4618
4707
  return;
4619
4708
  }
4620
- if (existsSync11(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4709
+ if (existsSync12(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4621
4710
  spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
4622
- if (existsSync11(unit.plistPath)) unlinkSync(unit.plistPath);
4711
+ if (existsSync12(unit.plistPath)) unlinkSync(unit.plistPath);
4623
4712
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
4624
4713
  }
4625
4714
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -4697,24 +4786,24 @@ function detectSystemd() {
4697
4786
  }
4698
4787
 
4699
4788
  // src/ship-attention.ts
4700
- import { existsSync as existsSync13, statfsSync } from "fs";
4789
+ import { existsSync as existsSync14, statfsSync } from "fs";
4701
4790
 
4702
4791
  // src/git.ts
4703
- import { execFileSync as execFileSync2 } from "child_process";
4792
+ import { execFileSync as execFileSync3 } from "child_process";
4704
4793
  import {
4705
- existsSync as existsSync12,
4706
- mkdirSync as mkdirSync8,
4794
+ existsSync as existsSync13,
4795
+ mkdirSync as mkdirSync9,
4707
4796
  openSync as openSync2,
4708
4797
  closeSync as closeSync2,
4709
4798
  writeSync,
4710
- statSync as statSync5,
4799
+ statSync as statSync6,
4711
4800
  unlinkSync as unlinkSync2,
4712
- readFileSync as readFileSync13,
4713
- writeFileSync as writeFileSync9,
4801
+ readFileSync as readFileSync14,
4802
+ writeFileSync as writeFileSync10,
4714
4803
  readdirSync as readdirSync4,
4715
4804
  rmSync as rmSync8
4716
4805
  } from "fs";
4717
- import { dirname as dirname6, basename as basename5, join as join14 } from "path";
4806
+ import { dirname as dirname7, basename as basename5, join as join15 } from "path";
4718
4807
  var GitError = class extends Error {
4719
4808
  };
4720
4809
  function streamText(x) {
@@ -4722,7 +4811,7 @@ function streamText(x) {
4722
4811
  }
4723
4812
  function gitRaw(cwd, args) {
4724
4813
  try {
4725
- return execFileSync2("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4814
+ return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4726
4815
  } catch (e) {
4727
4816
  const err3 = e;
4728
4817
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -4731,7 +4820,7 @@ function gitRaw(cwd, args) {
4731
4820
  }
4732
4821
  function git(cwd, args) {
4733
4822
  try {
4734
- return execFileSync2("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4823
+ return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4735
4824
  } catch (e) {
4736
4825
  const err3 = e;
4737
4826
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -4861,7 +4950,7 @@ function worktreeForNumber(cwd, num) {
4861
4950
  return worktrees(cwd).find((w) => re.test(basename5(w.path))) ?? null;
4862
4951
  }
4863
4952
  var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
4864
- var resolve2 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
4953
+ var resolve3 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
4865
4954
  var countCommits = (cwd, range) => Number.parseInt(gitOk(cwd, ["rev-list", "--count", range]) ?? "0", 10);
4866
4955
  function latestReleaseTag(cwd, pattern = "v*", ref = "HEAD") {
4867
4956
  return gitOk(cwd, ["describe", "--tags", "--abbrev=0", "--match", pattern, ref]);
@@ -4870,7 +4959,7 @@ function firstReleaseTagContaining(cwd, sha, pattern = "v*") {
4870
4959
  const out = gitOk(cwd, ["tag", "--contains", sha, "--list", pattern, "--sort=creatordate"]);
4871
4960
  return out?.split("\n").map((l) => l.trim()).find(Boolean) ?? null;
4872
4961
  }
4873
- var tagCommit = (cwd, tag) => resolve2(cwd, tag);
4962
+ var tagCommit = (cwd, tag) => resolve3(cwd, tag);
4874
4963
  function createReleaseTag(cwd, tag, sha, message) {
4875
4964
  git(cwd, ["tag", "-a", tag, sha, "-m", message]);
4876
4965
  }
@@ -4902,9 +4991,9 @@ function keyCommitsInRange(cwd, key, range) {
4902
4991
  }
4903
4992
  function patchIdOf(cwd, from, to) {
4904
4993
  try {
4905
- const diff = execFileSync2("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
4994
+ const diff = execFileSync3("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
4906
4995
  if (!diff) return null;
4907
- const out = execFileSync2("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
4996
+ const out = execFileSync3("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
4908
4997
  return out.split(" ")[0]?.trim() || null;
4909
4998
  } catch {
4910
4999
  return null;
@@ -5094,7 +5183,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
5094
5183
  if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
5095
5184
  else if (line.startsWith("branch refs/heads/crew/release-") && path) {
5096
5185
  gitOk(cwd, ["worktree", "remove", "--force", path]);
5097
- const root = dirname6(path);
5186
+ const root = dirname7(path);
5098
5187
  if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
5099
5188
  }
5100
5189
  }
@@ -5112,11 +5201,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
5112
5201
  }
5113
5202
  for (const name of names) {
5114
5203
  if (!name.startsWith("crew-release-")) continue;
5115
- const root = join14(tmp, name);
5204
+ const root = join15(tmp, name);
5116
5205
  try {
5117
- const st = statSync5(root);
5206
+ const st = statSync6(root);
5118
5207
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
5119
- if (existsSync12(join14(root, "checkout"))) continue;
5208
+ if (existsSync13(join15(root, "checkout"))) continue;
5120
5209
  rmSync8(root, { recursive: true, force: true });
5121
5210
  removed.push(name);
5122
5211
  } catch {
@@ -5168,16 +5257,16 @@ function originUrlOf(dir, remoteName) {
5168
5257
  }
5169
5258
  }
5170
5259
  function ensureRepoCheckout(dir, remote, opts = {}) {
5171
- if (existsSync12(`${dir}/.git`)) return false;
5260
+ if (existsSync13(`${dir}/.git`)) return false;
5172
5261
  if (!remote) {
5173
5262
  throw new GitError(
5174
5263
  `${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`
5175
5264
  );
5176
5265
  }
5177
- mkdirSync8(dirname6(dir), { recursive: true });
5266
+ mkdirSync9(dirname7(dir), { recursive: true });
5178
5267
  const url = cloneUrlFor(remote, opts);
5179
5268
  try {
5180
- execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5269
+ execFileSync3("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5181
5270
  } catch (e) {
5182
5271
  const err3 = e;
5183
5272
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5199,7 +5288,7 @@ function sleepSync(ms3) {
5199
5288
  }
5200
5289
  function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5201
5290
  const lockPath = checkoutLockPath(dir);
5202
- mkdirSync8(dirname6(lockPath), { recursive: true });
5291
+ mkdirSync9(dirname7(lockPath), { recursive: true });
5203
5292
  const deadline = Date.now() + timeoutMs;
5204
5293
  for (; ; ) {
5205
5294
  try {
@@ -5217,7 +5306,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5217
5306
  const err3 = e;
5218
5307
  if (err3.code !== "EEXIST") throw err3;
5219
5308
  try {
5220
- if (Date.now() - statSync5(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
5309
+ if (Date.now() - statSync6(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
5221
5310
  try {
5222
5311
  unlinkSync2(lockPath);
5223
5312
  } catch {
@@ -5236,17 +5325,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5236
5325
  function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
5237
5326
  const release = acquireCheckoutLock(dir);
5238
5327
  try {
5239
- if (!existsSync12(`${dir}/.git`) && githubRemote) {
5328
+ if (!existsSync13(`${dir}/.git`) && githubRemote) {
5240
5329
  const url = cloneUrlFor(githubRemote, clone);
5241
5330
  const marker = cloneFailureMarker(dir);
5242
- if (existsSync12(marker) && readFileSync13(marker, "utf8").split("\n")[0] === url) {
5243
- return { action: "clone-failed", detail: readFileSync13(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5331
+ if (existsSync13(marker) && readFileSync14(marker, "utf8").split("\n")[0] === url) {
5332
+ return { action: "clone-failed", detail: readFileSync14(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5244
5333
  }
5245
5334
  try {
5246
5335
  ensureRepoCheckout(dir, githubRemote, clone);
5247
5336
  } catch (e) {
5248
5337
  if (!(e instanceof GitError)) throw e;
5249
- writeFileSync9(marker, `${url}
5338
+ writeFileSync10(marker, `${url}
5250
5339
  ${e.message}`);
5251
5340
  return { action: "clone-failed", detail: e.message, repeat: false };
5252
5341
  }
@@ -5472,7 +5561,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
5472
5561
  function reconcileHeldWorktrees(state, repo, log, stillRefused) {
5473
5562
  for (const i of state.shipAttention().previous()) {
5474
5563
  if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
5475
- const gone = !existsSync13(i.held.path);
5564
+ const gone = !existsSync14(i.held.path);
5476
5565
  if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
5477
5566
  clearShipAttention(state, i.key);
5478
5567
  log.emit(
@@ -5600,13 +5689,13 @@ function attentionProblems(items, already) {
5600
5689
  // src/sandbox-image.ts
5601
5690
  import { createHash as createHash6 } from "crypto";
5602
5691
  import { spawn as spawn4 } from "child_process";
5603
- import { execFileSync as execFileSync6 } from "child_process";
5604
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, renameSync as renameSync3, writeFileSync as writeFileSync15, rmSync as rmSync13 } from "fs";
5605
- import { dirname as dirname10, join as join26 } from "path";
5692
+ import { execFileSync as execFileSync7 } from "child_process";
5693
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync22, renameSync as renameSync3, writeFileSync as writeFileSync16, rmSync as rmSync13 } from "fs";
5694
+ import { dirname as dirname11, join as join27 } from "path";
5606
5695
  import { tmpdir as tmpdir3 } from "os";
5607
5696
 
5608
5697
  // src/passenger-containers.ts
5609
- import { execFileSync as execFileSync5 } from "child_process";
5698
+ import { execFileSync as execFileSync6 } from "child_process";
5610
5699
  import { createHash as createHash5 } from "crypto";
5611
5700
 
5612
5701
  // src/sandbox-cache.ts
@@ -5615,32 +5704,32 @@ import { promisify as promisify2 } from "util";
5615
5704
 
5616
5705
  // src/deprovision-host.ts
5617
5706
  import { spawnSync as spawnSync6 } from "child_process";
5618
- import { existsSync as existsSync24, readdirSync as readdirSync11 } from "fs";
5707
+ import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
5619
5708
 
5620
5709
  // src/interrupted.ts
5621
- import { existsSync as existsSync22, readdirSync as readdirSync9, readFileSync as readFileSync20 } from "fs";
5622
- import { join as join24 } from "path";
5710
+ import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
5711
+ import { join as join25 } from "path";
5623
5712
 
5624
5713
  // src/agent.ts
5625
5714
  import { spawnSync as spawnSync5 } from "child_process";
5626
- import { readFileSync as readFileSync19, existsSync as existsSync21, statSync as statSync7, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
5715
+ import { readFileSync as readFileSync20, existsSync as existsSync22, statSync as statSync8, mkdirSync as mkdirSync14, writeFileSync as writeFileSync15, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
5627
5716
  import { StringDecoder } from "string_decoder";
5628
5717
  import { finished } from "stream/promises";
5629
- import { join as join23, dirname as dirname9, basename as basename9 } from "path";
5718
+ import { join as join24, dirname as dirname10, basename as basename9 } from "path";
5630
5719
  import { createHash as createHash4 } from "crypto";
5631
5720
 
5632
5721
  // src/state.ts
5633
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync14, mkdirSync as mkdirSync9, rmSync as rmSync9, statSync as statSync6, readdirSync as readdirSync5 } from "fs";
5634
- import { join as join15 } from "path";
5722
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync10, rmSync as rmSync9, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
5723
+ import { join as join16 } from "path";
5635
5724
  var EPOCH = "1970-01-01T00:00:00.000Z";
5636
5725
  var State = class _State {
5637
5726
  dir;
5638
5727
  constructor(dir) {
5639
5728
  this.dir = dir;
5640
- mkdirSync9(dir, { recursive: true });
5729
+ mkdirSync10(dir, { recursive: true });
5641
5730
  }
5642
5731
  path(...parts) {
5643
- return join15(this.dir, ...parts);
5732
+ return join16(this.dir, ...parts);
5644
5733
  }
5645
5734
  /**
5646
5735
  * The timestamp every "new since last poll" check is measured against.
@@ -5651,14 +5740,14 @@ var State = class _State {
5651
5740
  */
5652
5741
  watermark() {
5653
5742
  try {
5654
- const v = readFileSync14(this.path(".poll-watermark"), "utf8").trim();
5743
+ const v = readFileSync15(this.path(".poll-watermark"), "utf8").trim();
5655
5744
  return v || EPOCH;
5656
5745
  } catch {
5657
5746
  return EPOCH;
5658
5747
  }
5659
5748
  }
5660
5749
  setWatermark(at) {
5661
- writeFileSync10(this.path(".poll-watermark"), `${at}
5750
+ writeFileSync11(this.path(".poll-watermark"), `${at}
5662
5751
  `);
5663
5752
  }
5664
5753
  /**
@@ -5676,16 +5765,16 @@ var State = class _State {
5676
5765
  }
5677
5766
  /** Whole-crew pause, and the per-role sentinels one level down. */
5678
5767
  isPaused() {
5679
- return existsSync14(this.path(".crew-paused"));
5768
+ return existsSync15(this.path(".crew-paused"));
5680
5769
  }
5681
5770
  isRolePaused(role) {
5682
- return existsSync14(this.path(`.role-paused-${role}`));
5771
+ return existsSync15(this.path(`.role-paused-${role}`));
5683
5772
  }
5684
5773
  pausedRoles(roles) {
5685
5774
  return new Set(roles.filter((r) => this.isRolePaused(r)));
5686
5775
  }
5687
5776
  pause(role) {
5688
- writeFileSync10(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5777
+ writeFileSync11(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5689
5778
  }
5690
5779
  resume(role) {
5691
5780
  rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
@@ -5696,10 +5785,10 @@ var State = class _State {
5696
5785
  * while agents keep working.
5697
5786
  */
5698
5787
  isReleasePaused() {
5699
- return existsSync14(this.path(".release-paused"));
5788
+ return existsSync15(this.path(".release-paused"));
5700
5789
  }
5701
5790
  setReleasePaused(paused) {
5702
- if (paused) writeFileSync10(this.path(".release-paused"), "");
5791
+ if (paused) writeFileSync11(this.path(".release-paused"), "");
5703
5792
  else rmSync9(this.path(".release-paused"), { force: true });
5704
5793
  }
5705
5794
  /**
@@ -5737,7 +5826,7 @@ var State = class _State {
5737
5826
  const name = _State.safe(rawName);
5738
5827
  const file = this.path(`.${name}.lock`);
5739
5828
  try {
5740
- const existing = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
5829
+ const existing = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
5741
5830
  if (Number.isFinite(existing) && existing > 0) {
5742
5831
  try {
5743
5832
  process.kill(existing, 0);
@@ -5747,7 +5836,7 @@ var State = class _State {
5747
5836
  }
5748
5837
  } catch {
5749
5838
  }
5750
- writeFileSync10(file, `${process.pid}
5839
+ writeFileSync11(file, `${process.pid}
5751
5840
  `);
5752
5841
  return {
5753
5842
  ok: true,
@@ -5775,7 +5864,7 @@ var State = class _State {
5775
5864
  if (!/^\.release-.+\.lock$/.test(n)) continue;
5776
5865
  let pid = 0;
5777
5866
  try {
5778
- pid = Number.parseInt(readFileSync14(this.path(n), "utf8").trim(), 10);
5867
+ pid = Number.parseInt(readFileSync15(this.path(n), "utf8").trim(), 10);
5779
5868
  } catch {
5780
5869
  continue;
5781
5870
  }
@@ -5861,7 +5950,7 @@ var State = class _State {
5861
5950
  ticketInFlight(recordId) {
5862
5951
  const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
5863
5952
  try {
5864
- const pid = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
5953
+ const pid = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
5865
5954
  if (!Number.isFinite(pid) || pid <= 0) return false;
5866
5955
  process.kill(pid, 0);
5867
5956
  return true;
@@ -5895,7 +5984,7 @@ var State = class _State {
5895
5984
  const heldReported = `.release-held-${suffix}`;
5896
5985
  const read = (f) => {
5897
5986
  try {
5898
- return readFileSync14(this.path(f), "utf8").trim() || null;
5987
+ return readFileSync15(this.path(f), "utf8").trim() || null;
5899
5988
  } catch {
5900
5989
  return null;
5901
5990
  }
@@ -5904,7 +5993,7 @@ var State = class _State {
5904
5993
  blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
5905
5994
  noteBlocked: () => {
5906
5995
  const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
5907
- writeFileSync10(this.path(blocked), `${n}
5996
+ writeFileSync11(this.path(blocked), `${n}
5908
5997
  `);
5909
5998
  return n;
5910
5999
  },
@@ -5921,7 +6010,7 @@ var State = class _State {
5921
6010
  */
5922
6011
  deployFailedSha: () => read(failed),
5923
6012
  noteDeployFailed: (sha) => {
5924
- writeFileSync10(this.path(failed), `${sha}
6013
+ writeFileSync11(this.path(failed), `${sha}
5925
6014
  `);
5926
6015
  },
5927
6016
  clearDeployFailed: () => {
@@ -5935,7 +6024,7 @@ var State = class _State {
5935
6024
  noteTestGateFailed: (sha) => {
5936
6025
  const [prev, n] = (read(gateFailed) ?? "").split(" ");
5937
6026
  const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
5938
- writeFileSync10(this.path(gateFailed), `${sha} ${next}
6027
+ writeFileSync11(this.path(gateFailed), `${sha} ${next}
5939
6028
  `);
5940
6029
  return next;
5941
6030
  },
@@ -5945,13 +6034,13 @@ var State = class _State {
5945
6034
  /** Whether the board was already told about this head's red gate. */
5946
6035
  heldReported: () => {
5947
6036
  try {
5948
- return readFileSync14(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6037
+ return readFileSync15(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
5949
6038
  } catch {
5950
6039
  return null;
5951
6040
  }
5952
6041
  },
5953
6042
  noteHeldReported: (signature) => {
5954
- writeFileSync10(this.path(heldReported), `${signature}
6043
+ writeFileSync11(this.path(heldReported), `${signature}
5955
6044
  `);
5956
6045
  },
5957
6046
  clearHeldReported: () => {
@@ -5959,7 +6048,7 @@ var State = class _State {
5959
6048
  },
5960
6049
  testGateReported: (sha) => read(gateReported) === sha,
5961
6050
  noteTestGateReported: (sha) => {
5962
- writeFileSync10(this.path(gateReported), `${sha}
6051
+ writeFileSync11(this.path(gateReported), `${sha}
5963
6052
  `);
5964
6053
  }
5965
6054
  };
@@ -5982,7 +6071,7 @@ var State = class _State {
5982
6071
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
5983
6072
  const read = (f) => {
5984
6073
  try {
5985
- return readFileSync14(this.path(f), "utf8").trim() || null;
6074
+ return readFileSync15(this.path(f), "utf8").trim() || null;
5986
6075
  } catch {
5987
6076
  return null;
5988
6077
  }
@@ -6015,18 +6104,18 @@ var State = class _State {
6015
6104
  clear2();
6016
6105
  return;
6017
6106
  }
6018
- writeFileSync10(this.path(streakFile), `${this.readInt(streakFile) + 1}
6107
+ writeFileSync11(this.path(streakFile), `${this.readInt(streakFile) + 1}
6019
6108
  `);
6020
6109
  const prior = read(ticketFile);
6021
6110
  if (topTicket && topTicket === prior) {
6022
- writeFileSync10(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6111
+ writeFileSync11(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6023
6112
  `);
6024
6113
  } else if (topTicket) {
6025
- writeFileSync10(this.path(ticketFile), `${topTicket}
6114
+ writeFileSync11(this.path(ticketFile), `${topTicket}
6026
6115
  `);
6027
- writeFileSync10(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6116
+ writeFileSync11(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6028
6117
  `);
6029
- writeFileSync10(this.path(ticketStreakFile), "1\n");
6118
+ writeFileSync11(this.path(ticketStreakFile), "1\n");
6030
6119
  }
6031
6120
  }
6032
6121
  };
@@ -6047,14 +6136,14 @@ var State = class _State {
6047
6136
  return {
6048
6137
  previous: () => {
6049
6138
  try {
6050
- const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
6139
+ const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6051
6140
  return parsed && typeof parsed === "object" ? parsed : {};
6052
6141
  } catch {
6053
6142
  return {};
6054
6143
  }
6055
6144
  },
6056
6145
  persist: (next) => {
6057
- writeFileSync10(this.path(file), JSON.stringify(next));
6146
+ writeFileSync11(this.path(file), JSON.stringify(next));
6058
6147
  }
6059
6148
  };
6060
6149
  }
@@ -6070,14 +6159,14 @@ var State = class _State {
6070
6159
  return {
6071
6160
  previous: () => {
6072
6161
  try {
6073
- const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
6162
+ const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6074
6163
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
6075
6164
  } catch {
6076
6165
  return [];
6077
6166
  }
6078
6167
  },
6079
6168
  persist: (next) => {
6080
- writeFileSync10(this.path(file), JSON.stringify(next));
6169
+ writeFileSync11(this.path(file), JSON.stringify(next));
6081
6170
  }
6082
6171
  };
6083
6172
  }
@@ -6091,14 +6180,14 @@ var State = class _State {
6091
6180
  return {
6092
6181
  previous: () => {
6093
6182
  try {
6094
- const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
6183
+ const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6095
6184
  return Array.isArray(parsed) ? parsed : [];
6096
6185
  } catch {
6097
6186
  return [];
6098
6187
  }
6099
6188
  },
6100
6189
  persist: (next) => {
6101
- writeFileSync10(this.path(file), JSON.stringify(next));
6190
+ writeFileSync11(this.path(file), JSON.stringify(next));
6102
6191
  }
6103
6192
  };
6104
6193
  }
@@ -6111,11 +6200,11 @@ var State = class _State {
6111
6200
  const file = ".release-last-run";
6112
6201
  return {
6113
6202
  touch: () => {
6114
- writeFileSync10(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6203
+ writeFileSync11(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6115
6204
  },
6116
6205
  at: () => {
6117
6206
  try {
6118
- return statSync6(this.path(file)).mtimeMs;
6207
+ return statSync7(this.path(file)).mtimeMs;
6119
6208
  } catch {
6120
6209
  return null;
6121
6210
  }
@@ -6124,7 +6213,7 @@ var State = class _State {
6124
6213
  }
6125
6214
  readInt(file) {
6126
6215
  try {
6127
- return Number.parseInt(readFileSync14(this.path(file), "utf8").trim(), 10) || 0;
6216
+ return Number.parseInt(readFileSync15(this.path(file), "utf8").trim(), 10) || 0;
6128
6217
  } catch {
6129
6218
  return 0;
6130
6219
  }
@@ -6203,6 +6292,7 @@ var HOOK_PURPOSE = [
6203
6292
  ["ports", "this worktree's port assignments; prints KEY=value overrides to export"],
6204
6293
  ["handoff", "leave the operator able to open what you built; put its output in your progress comment"],
6205
6294
  ["preview", "start this worktree's dev server for a viewer, bound to CREW_PREVIEW_HOST, detached; its last stdout line is the URL"],
6295
+ ["map", 'prints an outline of the repository; the crew places it under "Repository map" below'],
6206
6296
  ["test", "run the suite"],
6207
6297
  ["build", "build it"],
6208
6298
  ["ci_status", "after you push and before you hand off: exit 0 means CI is green for your branch; on failure fix and push again, and say in your progress comment if it is still pending"]
@@ -6299,6 +6389,13 @@ function repoSection(r, key, heading) {
6299
6389
  }
6300
6390
  return lines2;
6301
6391
  }
6392
+ function mapSection(r, heading) {
6393
+ if (r.map) return ["", heading, "", r.map, ""];
6394
+ if (r.mapSkipped) {
6395
+ return ["", `Note: ${r.mapSkipped}. Say so in one line of your progress comment; it does not block the work.`, ""];
6396
+ }
6397
+ return [];
6398
+ }
6302
6399
  function renderEnvironment(i) {
6303
6400
  const { route: route2, contract } = i;
6304
6401
  const repos = i.repos;
@@ -6490,12 +6587,16 @@ function renderEnvironment(i) {
6490
6587
  for (const r of repos) {
6491
6588
  lines2.push(...i.sandbox ? ["", `### ${r.name}`] : repoSection(r, key, `### ${r.name} \u2014 \`${r.dir}\``));
6492
6589
  lines2.push("", `Hooks for ${r.name} \u2014 the only commands you should run here:`, "", hookTable(r.config));
6590
+ lines2.push(...mapSection(r, `#### Repository map \u2014 ${r.name}`));
6493
6591
  }
6494
6592
  }
6495
6593
  if (i.repoDir) {
6496
6594
  lines2.push("", `This ticket's work happens in \`${i.repoDir}\`.`);
6497
6595
  }
6498
- if (only) lines2.push("", "## Hooks \u2014 the only commands you should run", "", hookTable(only.config));
6596
+ if (only) {
6597
+ lines2.push("", "## Hooks \u2014 the only commands you should run", "", hookTable(only.config));
6598
+ lines2.push(...mapSection(only, "## Repository map"));
6599
+ }
6499
6600
  return lines2.join("\n");
6500
6601
  }
6501
6602
 
@@ -6665,8 +6766,8 @@ var PERISCOPE_COLUMNS = [
6665
6766
  ];
6666
6767
 
6667
6768
  // src/agent-log.ts
6668
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
6669
- import { join as join16 } from "path";
6769
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
6770
+ import { join as join17 } from "path";
6670
6771
 
6671
6772
  // src/pending-backoff.ts
6672
6773
  var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
@@ -6838,20 +6939,20 @@ async function addRunToTicket(target, t, recordId, run3) {
6838
6939
  };
6839
6940
  }
6840
6941
  }
6841
- var queueDir = (stateDir) => join16(stateDir, "pending-agent-logs");
6942
+ var queueDir = (stateDir) => join17(stateDir, "pending-agent-logs");
6842
6943
  function queueAgentRun(stateDir, name, q2) {
6843
- mkdirSync10(queueDir(stateDir), { recursive: true });
6844
- writeFileSync11(join16(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
6944
+ mkdirSync11(queueDir(stateDir), { recursive: true });
6945
+ writeFileSync12(join17(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
6845
6946
  }
6846
6947
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6847
6948
  const dir = queueDir(stateDir);
6848
- if (!existsSync15(dir)) return 0;
6949
+ if (!existsSync16(dir)) return 0;
6849
6950
  let sent = 0;
6850
6951
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6851
- const path = join16(dir, f);
6952
+ const path = join17(dir, f);
6852
6953
  let q2;
6853
6954
  try {
6854
- q2 = JSON.parse(readFileSync15(path, "utf8"));
6955
+ q2 = JSON.parse(readFileSync16(path, "utf8"));
6855
6956
  } catch {
6856
6957
  continue;
6857
6958
  }
@@ -6880,7 +6981,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6880
6981
  continue;
6881
6982
  }
6882
6983
  const attempts = q2.attempts + 1;
6883
- writeFileSync11(path, JSON.stringify({
6984
+ writeFileSync12(path, JSON.stringify({
6884
6985
  ...q2,
6885
6986
  attempts,
6886
6987
  warned: true,
@@ -6891,20 +6992,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6891
6992
  }
6892
6993
  return sent;
6893
6994
  }
6894
- var countersDir = (stateDir) => join16(stateDir, "pending-ticket-counters");
6995
+ var countersDir = (stateDir) => join17(stateDir, "pending-ticket-counters");
6895
6996
  function queueTicketCounters(stateDir, name, q2) {
6896
- mkdirSync10(countersDir(stateDir), { recursive: true });
6897
- writeFileSync11(join16(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
6997
+ mkdirSync11(countersDir(stateDir), { recursive: true });
6998
+ writeFileSync12(join17(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
6898
6999
  }
6899
7000
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6900
7001
  const dir = countersDir(stateDir);
6901
- if (!existsSync15(dir)) return 0;
7002
+ if (!existsSync16(dir)) return 0;
6902
7003
  let sent = 0;
6903
7004
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6904
- const path = join16(dir, f);
7005
+ const path = join17(dir, f);
6905
7006
  let q2;
6906
7007
  try {
6907
- q2 = JSON.parse(readFileSync15(path, "utf8"));
7008
+ q2 = JSON.parse(readFileSync16(path, "utf8"));
6908
7009
  } catch {
6909
7010
  continue;
6910
7011
  }
@@ -6923,7 +7024,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
6923
7024
  continue;
6924
7025
  }
6925
7026
  const attempts = q2.attempts + 1;
6926
- writeFileSync11(path, JSON.stringify({
7027
+ writeFileSync12(path, JSON.stringify({
6927
7028
  ...q2,
6928
7029
  attempts,
6929
7030
  warned: true,
@@ -7126,8 +7227,8 @@ var HandoffTranscriptTracker = class {
7126
7227
  };
7127
7228
 
7128
7229
  // src/pending-handoff.ts
7129
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "fs";
7130
- import { join as join19 } from "path";
7230
+ import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync7, readFileSync as readFileSync17, rmSync as rmSync11, writeFileSync as writeFileSync13 } from "fs";
7231
+ import { join as join20 } from "path";
7131
7232
 
7132
7233
  // src/ticket-branch.ts
7133
7234
  import { basename as basename7 } from "path";
@@ -8293,16 +8394,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
8293
8394
  }
8294
8395
 
8295
8396
  // src/run-worktree.ts
8296
- import { existsSync as existsSync17 } from "fs";
8297
- import { dirname as dirname7, join as join18 } from "path";
8397
+ import { existsSync as existsSync18 } from "fs";
8398
+ import { dirname as dirname8, join as join19 } from "path";
8298
8399
 
8299
8400
  // src/ports.ts
8300
- import { execFileSync as execFileSync3 } from "child_process";
8301
- import { existsSync as existsSync16 } from "fs";
8302
- import { join as join17 } from "path";
8401
+ import { execFileSync as execFileSync4 } from "child_process";
8402
+ import { existsSync as existsSync17 } from "fs";
8403
+ import { join as join18 } from "path";
8303
8404
  var run = (cmd, args) => {
8304
8405
  try {
8305
- return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8406
+ return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8306
8407
  } catch {
8307
8408
  return "";
8308
8409
  }
@@ -8344,7 +8445,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
8344
8445
  }
8345
8446
  function worktreeExists(parent, prefix, n) {
8346
8447
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
8347
- if (existsSync16(join17(parent, name, ".git"))) return true;
8448
+ if (existsSync17(join18(parent, name, ".git"))) return true;
8348
8449
  }
8349
8450
  return false;
8350
8451
  }
@@ -8427,7 +8528,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8427
8528
  const existing = worktreeForNumber(dir, num);
8428
8529
  const located = locateBranchForTicket(dir, cfg, t, role);
8429
8530
  const baseRef = hasRemote ? `${remote}/${base}` : base;
8430
- if (existing && !existing.prunable && existsSync17(existing.path)) {
8531
+ if (existing && !existing.prunable && existsSync18(existing.path)) {
8431
8532
  const branch = existing.branch;
8432
8533
  if (status(existing.path).length > 0) {
8433
8534
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -8443,7 +8544,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8443
8544
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
8444
8545
  return { action: "recut", path: existing.path, branch, base: baseRef };
8445
8546
  }
8446
- const path = join18(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8547
+ const path = join19(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
8447
8548
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
8448
8549
  return { action: "cut-remote", path, branch: located.branch };
8449
8550
  }
@@ -8497,7 +8598,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
8497
8598
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
8498
8599
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
8499
8600
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8500
- if (wt && existsSync17(wt.path)) {
8601
+ if (wt && existsSync18(wt.path)) {
8501
8602
  if (status(wt.path).length > 0) return false;
8502
8603
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
8503
8604
  }
@@ -8505,7 +8606,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
8505
8606
  }
8506
8607
  async function finishRunWorktree(dir, cfg, branch) {
8507
8608
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8508
- if (!wt || !existsSync17(wt.path)) return { kind: "none" };
8609
+ if (!wt || !existsSync18(wt.path)) return { kind: "none" };
8509
8610
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
8510
8611
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
8511
8612
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -8517,7 +8618,7 @@ async function finishRunWorktree(dir, cfg, branch) {
8517
8618
  if (pids.length) await killGently(pids);
8518
8619
  }
8519
8620
  gitOk(dir, ["worktree", "remove", wt.path]);
8520
- return existsSync17(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8621
+ return existsSync18(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8521
8622
  }
8522
8623
 
8523
8624
  // src/handoff-guard.ts
@@ -8900,20 +9001,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
8900
9001
 
8901
9002
  // src/pending-handoff.ts
8902
9003
  var PENDING_MAX_AGE_MS = 48 * 36e5;
8903
- var pendingDirFor = (stateDir) => join19(stateDir, "pending-handoffs");
8904
- var stateDirOfStream = (streamPath) => join19(streamPath, "..", "..");
9004
+ var pendingDirFor = (stateDir) => join20(stateDir, "pending-handoffs");
9005
+ var stateDirOfStream = (streamPath) => join20(streamPath, "..", "..");
8905
9006
  function writePendingHandoff(stateDir, rec) {
8906
9007
  const dir = pendingDirFor(stateDir);
8907
- mkdirSync11(dir, { recursive: true });
8908
- writeFileSync12(join19(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
9008
+ mkdirSync12(dir, { recursive: true });
9009
+ writeFileSync13(join20(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
8909
9010
  }
8910
9011
  function listPendingHandoffs(stateDir, route2) {
8911
9012
  const dir = pendingDirFor(stateDir);
8912
- if (!existsSync18(dir)) return [];
9013
+ if (!existsSync19(dir)) return [];
8913
9014
  const out = [];
8914
9015
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
8915
9016
  try {
8916
- const rec = JSON.parse(readFileSync16(join19(dir, f), "utf8"));
9017
+ const rec = JSON.parse(readFileSync17(join20(dir, f), "utf8"));
8917
9018
  if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
8918
9019
  } catch {
8919
9020
  }
@@ -8921,7 +9022,7 @@ function listPendingHandoffs(stateDir, route2) {
8921
9022
  return out;
8922
9023
  }
8923
9024
  function removePendingHandoff(stateDir, ticketRecordId) {
8924
- rmSync11(join19(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9025
+ rmSync11(join20(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
8925
9026
  }
8926
9027
  function pendingHandoffFor(stateDir, ticketRecordId) {
8927
9028
  return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
@@ -9031,8 +9132,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
9031
9132
  }
9032
9133
 
9033
9134
  // src/skills.ts
9034
- import { readFileSync as readFileSync18, readdirSync as readdirSync8, existsSync as existsSync20 } from "fs";
9035
- import { join as join21 } from "path";
9135
+ import { readFileSync as readFileSync19, readdirSync as readdirSync8, existsSync as existsSync21 } from "fs";
9136
+ import { join as join22 } from "path";
9036
9137
  import { parse as parseYaml } from "yaml";
9037
9138
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
9038
9139
 
@@ -9048,8 +9149,8 @@ ${additions}`;
9048
9149
  }
9049
9150
 
9050
9151
  // src/agents.ts
9051
- import { readFileSync as readFileSync17, existsSync as existsSync19 } from "fs";
9052
- import { join as join20 } from "path";
9152
+ import { readFileSync as readFileSync18, existsSync as existsSync20 } from "fs";
9153
+ import { join as join21 } from "path";
9053
9154
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
9054
9155
  var PERSONA_NAME = {
9055
9156
  dev: "Developer",
@@ -9063,12 +9164,12 @@ var CREW_AGENT_COLUMN = "agent_id";
9063
9164
  var AgentsSyncError = class extends Error {
9064
9165
  };
9065
9166
  function personaDefaultPrompt(promptsDir, role) {
9066
- const briefPath = join20(promptsDir, "personas", `lane-${role}.md`);
9067
- if (!existsSync19(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9068
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync17(briefPath, "utf8");
9069
- const commonPath = join20(promptsDir, "personas", "common.md");
9070
- if (!existsSync19(commonPath)) return readFileSync17(briefPath, "utf8");
9071
- return `${readFileSync17(commonPath, "utf8")}${readFileSync17(briefPath, "utf8")}`;
9167
+ const briefPath = join21(promptsDir, "personas", `lane-${role}.md`);
9168
+ if (!existsSync20(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9169
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync18(briefPath, "utf8");
9170
+ const commonPath = join21(promptsDir, "personas", "common.md");
9171
+ if (!existsSync20(commonPath)) return readFileSync18(briefPath, "utf8");
9172
+ return `${readFileSync18(commonPath, "utf8")}${readFileSync18(briefPath, "utf8")}`;
9072
9173
  }
9073
9174
  async function resolveAgentId(route2, role, opts) {
9074
9175
  const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
@@ -9515,7 +9616,7 @@ function describeCrewLink(o) {
9515
9616
  var SkillSyncError = class extends Error {
9516
9617
  };
9517
9618
  function parseSkillFile(path) {
9518
- const text2 = readFileSync18(path, "utf8");
9619
+ const text2 = readFileSync19(path, "utf8");
9519
9620
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
9520
9621
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
9521
9622
  const frontmatter = match[1];
@@ -9528,9 +9629,9 @@ function parseSkillFile(path) {
9528
9629
  return { name: meta.name, description: meta.description, prompt: body, file: path };
9529
9630
  }
9530
9631
  function listLocalSkills(promptsDir) {
9531
- const dir = join21(promptsDir, "skills");
9532
- if (!existsSync20(dir)) return [];
9533
- return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join21(dir, f)));
9632
+ const dir = join22(promptsDir, "skills");
9633
+ if (!existsSync21(dir)) return [];
9634
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join22(dir, f)));
9534
9635
  }
9535
9636
  async function syncSkills(route2, opts) {
9536
9637
  if (!route2.resolved) {
@@ -9714,10 +9815,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
9714
9815
  }
9715
9816
 
9716
9817
  // src/runner.ts
9717
- import { spawn as spawn2, execFile, execFileSync as execFileSync4 } from "child_process";
9718
- import { mkdtempSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, rmSync as rmSync12 } from "fs";
9818
+ import { spawn as spawn2, execFile, execFileSync as execFileSync5 } from "child_process";
9819
+ import { mkdtempSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, rmSync as rmSync12 } from "fs";
9719
9820
  import { tmpdir as tmpdir2 } from "os";
9720
- import { basename as basename8, dirname as dirname8, join as join22 } from "path";
9821
+ import { basename as basename8, dirname as dirname9, join as join23 } from "path";
9721
9822
  import { promisify } from "util";
9722
9823
 
9723
9824
  // src/run-limit.ts
@@ -9846,8 +9947,8 @@ var SandboxRunner = class {
9846
9947
  throw new Error(`cannot generate the sandbox policy: ${e.message}`);
9847
9948
  }
9848
9949
  policyFile = policyPathFor(plan.streamPath);
9849
- mkdirSync12(dirname8(policyFile), { recursive: true });
9850
- writeFileSync13(policyFile, generated.yaml, { mode: 384 });
9950
+ mkdirSync13(dirname9(policyFile), { recursive: true });
9951
+ writeFileSync14(policyFile, generated.yaml, { mode: 384 });
9851
9952
  }
9852
9953
  const create2 = ["sandbox", "create", "--name", name, "--detach"];
9853
9954
  if (policyFile) create2.push("--policy", policyFile);
@@ -9890,10 +9991,10 @@ var SandboxRunner = class {
9890
9991
  const harness = cfg.agentBin ?? basename8(plan.bin);
9891
9992
  const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
9892
9993
  if (viaUpload) {
9893
- const tmp = mkdtempSync(join22(tmpdir2(), "crew-prompt-"));
9994
+ const tmp = mkdtempSync(join23(tmpdir2(), "crew-prompt-"));
9894
9995
  try {
9895
- const file = join22(tmp, "prompt.txt");
9896
- writeFileSync13(file, plan.prompt, { mode: 384 });
9996
+ const file = join23(tmp, "prompt.txt");
9997
+ writeFileSync14(file, plan.prompt, { mode: 384 });
9897
9998
  await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
9898
9999
  } finally {
9899
10000
  rmSync12(tmp, { recursive: true, force: true });
@@ -9959,7 +10060,7 @@ function describeExecError(e) {
9959
10060
  }
9960
10061
  function gitOrigin(cwd) {
9961
10062
  try {
9962
- return execFileSync4("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
10063
+ return execFileSync5("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
9963
10064
  } catch {
9964
10065
  return null;
9965
10066
  }
@@ -9970,7 +10071,7 @@ function runnerFor(kind, sandbox) {
9970
10071
  function runnerOfSidecar(sc) {
9971
10072
  return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
9972
10073
  }
9973
- function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync4(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
10074
+ function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync5(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
9974
10075
  const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
9975
10076
  let version;
9976
10077
  try {
@@ -10037,7 +10138,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
10037
10138
  function streamShowsAllowanceRefusal(streamPath) {
10038
10139
  let raw;
10039
10140
  try {
10040
- raw = readFileSync19(streamPath, "utf8");
10141
+ raw = readFileSync20(streamPath, "utf8");
10041
10142
  } catch {
10042
10143
  return false;
10043
10144
  }
@@ -10060,26 +10161,26 @@ function streamShowsAllowanceRefusal(streamPath) {
10060
10161
  }
10061
10162
  return false;
10062
10163
  }
10063
- var digestPathFor = (stateDir, routeLabel, role, runId) => join23(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10164
+ var digestPathFor = (stateDir, routeLabel, role, runId) => join24(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10064
10165
  function planAgentRun(o) {
10065
- const briefPath = join23(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10066
- if (!existsSync21(briefPath)) {
10166
+ const briefPath = join24(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10167
+ if (!existsSync22(briefPath)) {
10067
10168
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
10068
10169
  }
10069
- const commonPath = join23(o.route.promptsDir, "personas", "common.md");
10070
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync21(commonPath);
10170
+ const commonPath = join24(o.route.promptsDir, "personas", "common.md");
10171
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync22(commonPath);
10071
10172
  const now = o.now ?? (() => Date.now());
10072
10173
  const routeLabel = routeSlug(o.route.route);
10073
10174
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
10074
- const digestPath2 = ownDigest && existsSync21(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10175
+ const digestPath2 = ownDigest && existsSync22(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10075
10176
  let digest;
10076
10177
  let ageSeconds;
10077
- if (existsSync21(digestPath2)) {
10078
- ageSeconds = Math.floor((now() - statSync7(digestPath2).mtimeMs) / 1e3);
10079
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync19(digestPath2, "utf8");
10178
+ if (existsSync22(digestPath2)) {
10179
+ ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
10180
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync20(digestPath2, "utf8");
10080
10181
  }
10081
- const common = usesCommon ? readFileSync19(commonPath, "utf8") : "";
10082
- const brief = readFileSync19(briefPath, "utf8");
10182
+ const common = usesCommon ? readFileSync20(commonPath, "utf8") : "";
10183
+ const brief = readFileSync20(briefPath, "utf8");
10083
10184
  const effectiveCommon = o.divergedPrompt ? "" : common;
10084
10185
  const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
10085
10186
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -10102,7 +10203,7 @@ function planAgentRun(o) {
10102
10203
  digest
10103
10204
  });
10104
10205
  const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
10105
- const streamsDir = join23(o.stateDir, "streams");
10206
+ const streamsDir = join24(o.stateDir, "streams");
10106
10207
  const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
10107
10208
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
10108
10209
  const { model } = seat;
@@ -10192,8 +10293,8 @@ function planAgentRun(o) {
10192
10293
  digestAttached: digest !== void 0,
10193
10294
  digestAgeSeconds: ageSeconds,
10194
10295
  model,
10195
- streamPath: join23(streamsDir, `${base}.jsonl`),
10196
- eventsPath: join23(streamsDir, `${base}.events.jsonl`),
10296
+ streamPath: join24(streamsDir, `${base}.jsonl`),
10297
+ eventsPath: join24(streamsDir, `${base}.events.jsonl`),
10197
10298
  ticket: o.ticket,
10198
10299
  ticketRecordId: o.ticketRecordId ?? null,
10199
10300
  repo: o.repo ?? null,
@@ -10224,7 +10325,7 @@ function describePlan(p) {
10224
10325
  var pidPathFor = (streamPath) => `${streamPath}.pid`;
10225
10326
  function readRunSidecar(streamPath) {
10226
10327
  try {
10227
- const sc = JSON.parse(readFileSync19(pidPathFor(streamPath), "utf8"));
10328
+ const sc = JSON.parse(readFileSync20(pidPathFor(streamPath), "utf8"));
10228
10329
  return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
10229
10330
  } catch {
10230
10331
  return null;
@@ -10271,7 +10372,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
10271
10372
  var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
10272
10373
  function openSink(path) {
10273
10374
  try {
10274
- mkdirSync13(dirname9(path), { recursive: true });
10375
+ mkdirSync14(dirname10(path), { recursive: true });
10275
10376
  return createWriteStream(path, { flags: "a" });
10276
10377
  } catch {
10277
10378
  return void 0;
@@ -10453,7 +10554,7 @@ var RunObserver = class {
10453
10554
  };
10454
10555
  function writeRunMeta(id, startedAt, liveTicket, extra) {
10455
10556
  try {
10456
- writeFileSync14(`${id.streamPath}.meta.json`, JSON.stringify({
10557
+ writeFileSync15(`${id.streamPath}.meta.json`, JSON.stringify({
10457
10558
  route: id.route,
10458
10559
  role: id.role,
10459
10560
  hint: id.ticket,
@@ -10613,7 +10714,7 @@ function spawnAgent(plan, emit, hintMoved) {
10613
10714
  const id = identityOf(plan, adapter.harness);
10614
10715
  let streamFd;
10615
10716
  try {
10616
- mkdirSync13(dirname9(plan.streamPath), { recursive: true });
10717
+ mkdirSync14(dirname10(plan.streamPath), { recursive: true });
10617
10718
  streamFd = openSync3(plan.streamPath, "a");
10618
10719
  } catch (e) {
10619
10720
  void endSink(eventsSink);
@@ -10645,7 +10746,7 @@ function spawnAgent(plan, emit, hintMoved) {
10645
10746
  closeSync3(streamFd);
10646
10747
  return supervise(begun);
10647
10748
  function supervise({ child, handle, promptDelivered }) {
10648
- return new Promise((resolve7, reject) => {
10749
+ return new Promise((resolve8, reject) => {
10649
10750
  const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
10650
10751
  const deadline = deadlineFor(spawnedAt, plan.maxRunMinutes);
10651
10752
  if (child.pid !== void 0) {
@@ -10671,7 +10772,7 @@ function spawnAgent(plan, emit, hintMoved) {
10671
10772
  promptSha: plan.promptSha
10672
10773
  };
10673
10774
  try {
10674
- writeFileSync14(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
10775
+ writeFileSync15(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
10675
10776
  } catch {
10676
10777
  }
10677
10778
  }
@@ -10715,7 +10816,7 @@ function spawnAgent(plan, emit, hintMoved) {
10715
10816
  plan.agentLog?.apiKey,
10716
10817
  hintMoved
10717
10818
  );
10718
- resolve7({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
10819
+ resolve8({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
10719
10820
  });
10720
10821
  child.stdin?.on("error", (err3) => {
10721
10822
  emit.warn(`agent closed stdin before the prompt was delivered: ${err3.message}`, { step: "agent" });
@@ -10785,21 +10886,21 @@ var pidAlive = (pid) => {
10785
10886
  };
10786
10887
  function runFinished(streamPath) {
10787
10888
  const path = `${streamPath}.meta.json`;
10788
- if (!existsSync22(path)) return false;
10889
+ if (!existsSync23(path)) return false;
10789
10890
  try {
10790
- return JSON.parse(readFileSync20(path, "utf8")).running !== true;
10891
+ return JSON.parse(readFileSync21(path, "utf8")).running !== true;
10791
10892
  } catch {
10792
10893
  return true;
10793
10894
  }
10794
10895
  }
10795
10896
  function orphanedStreams(o) {
10796
- const dir = join24(o.stateDir, "streams");
10797
- if (!existsSync22(dir)) return [];
10897
+ const dir = join25(o.stateDir, "streams");
10898
+ if (!existsSync23(dir)) return [];
10798
10899
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
10799
10900
  const out = [];
10800
10901
  for (const name of readdirSync9(dir)) {
10801
10902
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10802
- const streamPath = join24(dir, name);
10903
+ const streamPath = join25(dir, name);
10803
10904
  if (runFinished(streamPath)) continue;
10804
10905
  const sidecar = readRunSidecar(streamPath);
10805
10906
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -10852,15 +10953,15 @@ async function reconcileRuns(o) {
10852
10953
  }
10853
10954
 
10854
10955
  // src/live-runs.ts
10855
- import { existsSync as existsSync23, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
10856
- import { join as join25 } from "path";
10956
+ import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
10957
+ import { join as join26 } from "path";
10857
10958
  function liveRuns(stateDir, isAlive = runIsAlive) {
10858
- const dir = join25(stateDir, "streams");
10859
- if (!existsSync23(dir)) return [];
10959
+ const dir = join26(stateDir, "streams");
10960
+ if (!existsSync24(dir)) return [];
10860
10961
  const out = [];
10861
10962
  for (const name of readdirSync10(dir)) {
10862
10963
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10863
- const streamPath = join25(dir, name);
10964
+ const streamPath = join26(dir, name);
10864
10965
  if (runFinished(streamPath)) continue;
10865
10966
  const sidecar = readRunSidecar(streamPath);
10866
10967
  if (!sidecar || !isAlive(sidecar)) continue;
@@ -10870,15 +10971,15 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
10870
10971
  }
10871
10972
  function runActivity(stateDir, isAlive = runIsAlive) {
10872
10973
  const activeRuns = liveRuns(stateDir, isAlive).length;
10873
- const dir = join25(stateDir, "streams");
10974
+ const dir = join26(stateDir, "streams");
10874
10975
  let newest = 0;
10875
- if (existsSync23(dir)) {
10976
+ if (existsSync24(dir)) {
10876
10977
  for (const name of readdirSync10(dir)) {
10877
10978
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10878
- const streamPath = join25(dir, name);
10979
+ const streamPath = join26(dir, name);
10879
10980
  if (!runFinished(streamPath)) continue;
10880
10981
  try {
10881
- newest = Math.max(newest, statSync8(`${streamPath}.meta.json`).mtimeMs);
10982
+ newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
10882
10983
  } catch {
10883
10984
  }
10884
10985
  }
@@ -11246,7 +11347,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11246
11347
  if (host === "windows") return [];
11247
11348
  const ctx = installContext();
11248
11349
  const systemd = detectSystemd();
11249
- return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync24(p));
11350
+ return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync25(p));
11250
11351
  };
11251
11352
  return {
11252
11353
  liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
@@ -11307,7 +11408,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11307
11408
  },
11308
11409
  remaining() {
11309
11410
  const left = [];
11310
- if (existsSync24(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11411
+ if (existsSync25(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11311
11412
  for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
11312
11413
  try {
11313
11414
  for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
@@ -11582,7 +11683,7 @@ function makeCachePass(o) {
11582
11683
  // src/passenger-containers.ts
11583
11684
  var realExec2 = (cmd, args, opts) => {
11584
11685
  try {
11585
- return execFileSync5(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
11686
+ return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
11586
11687
  } catch (e) {
11587
11688
  const err3 = e;
11588
11689
  const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
@@ -11862,7 +11963,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
11862
11963
  var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
11863
11964
  function readWarmFiles(dir, ref, patterns) {
11864
11965
  if (patterns.length === 0) return {};
11865
- const git2 = (args) => execFileSync6("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
11966
+ const git2 = (args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
11866
11967
  const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
11867
11968
  const res = patterns.map(globToRegExp);
11868
11969
  const out = {};
@@ -11915,19 +12016,19 @@ function planSandbox(repo, decl, warm = {}) {
11915
12016
  const layer = repoLayer(repo, decl, crew, warm);
11916
12017
  return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
11917
12018
  }
11918
- var sandboxStatePath = (stateDir) => join26(stateDir, "sandbox-images.json");
12019
+ var sandboxStatePath = (stateDir) => join27(stateDir, "sandbox-images.json");
11919
12020
  function readSandboxState(stateDir) {
11920
12021
  try {
11921
- const v = JSON.parse(readFileSync21(sandboxStatePath(stateDir), "utf8"));
12022
+ const v = JSON.parse(readFileSync22(sandboxStatePath(stateDir), "utf8"));
11922
12023
  return v && typeof v === "object" && !Array.isArray(v) ? v : {};
11923
12024
  } catch {
11924
12025
  return {};
11925
12026
  }
11926
12027
  }
11927
12028
  function writeSandboxState(stateDir, state) {
11928
- mkdirSync14(stateDir, { recursive: true });
12029
+ mkdirSync15(stateDir, { recursive: true });
11929
12030
  const file = sandboxStatePath(stateDir);
11930
- writeFileSync15(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12031
+ writeFileSync16(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
11931
12032
  `);
11932
12033
  renameSync3(`${file}.tmp`, file);
11933
12034
  }
@@ -11941,15 +12042,15 @@ function sandboxImageStatus(state, plan) {
11941
12042
  if (current.tag !== plan.tag) return { status: "stale", detail: `running ${current.tag} (claude ${current.claude ?? "unknown"}), declaration now builds ${plan.tag} (claude ${plan.claude})` };
11942
12043
  return { status: "ok", detail: `${current.tag} built ${current.builtAt}, claude ${current.claude ?? "unknown"}` };
11943
12044
  }
11944
- var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((resolve7) => {
12045
+ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((resolve8) => {
11945
12046
  const child = spawn4(engine, args, { stdio: ["ignore", "pipe", "pipe"] });
11946
12047
  const chunks = [];
11947
12048
  child.stdout.on("data", (d) => chunks.push(d));
11948
12049
  child.stderr.on("data", (d) => chunks.push(d));
11949
12050
  const done = (code, extra = "") => {
11950
- writeFileSync15(logPath, `$ ${engine} ${args.join(" ")}
12051
+ writeFileSync16(logPath, `$ ${engine} ${args.join(" ")}
11951
12052
  ${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
11952
- resolve7(code);
12053
+ resolve8(code);
11953
12054
  };
11954
12055
  child.on("error", (e) => done(127, `${e.message}
11955
12056
  `));
@@ -11969,7 +12070,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
11969
12070
  } catch {
11970
12071
  }
11971
12072
  };
11972
- var sandboxLogDir = (stateDir) => join26(stateDir, "sandbox-logs");
12073
+ var sandboxLogDir = (stateDir) => join27(stateDir, "sandbox-logs");
11973
12074
  async function buildSandboxImage(plan, deps) {
11974
12075
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
11975
12076
  const state = readSandboxState(deps.stateDir);
@@ -11978,20 +12079,20 @@ async function buildSandboxImage(plan, deps) {
11978
12079
  return { kind: "reused", tag: plan.tag };
11979
12080
  }
11980
12081
  const logDir = sandboxLogDir(deps.stateDir);
11981
- mkdirSync14(logDir, { recursive: true });
11982
- const logPath = join26(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12082
+ mkdirSync15(logDir, { recursive: true });
12083
+ const logPath = join27(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
11983
12084
  const builtLayers = [];
11984
12085
  for (const layer of plan.layers) {
11985
12086
  if (deps.hasImage(layer.tag)) continue;
11986
- const ctx = join26(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
11987
- mkdirSync14(ctx, { recursive: true });
12087
+ const ctx = join27(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12088
+ mkdirSync15(ctx, { recursive: true });
11988
12089
  try {
11989
- writeFileSync15(join26(ctx, "Dockerfile"), layer.dockerfile);
12090
+ writeFileSync16(join27(ctx, "Dockerfile"), layer.dockerfile);
11990
12091
  for (const [name, content] of Object.entries(layer.files)) {
11991
- mkdirSync14(dirname10(join26(ctx, name)), { recursive: true });
11992
- writeFileSync15(join26(ctx, name), content);
12092
+ mkdirSync15(dirname11(join27(ctx, name)), { recursive: true });
12093
+ writeFileSync16(join27(ctx, name), content);
11993
12094
  }
11994
- const code = await deps.run(["build", "--tag", layer.tag, "--file", join26(ctx, "Dockerfile"), ctx], logPath);
12095
+ const code = await deps.run(["build", "--tag", layer.tag, "--file", join27(ctx, "Dockerfile"), ctx], logPath);
11995
12096
  if (code !== 0) {
11996
12097
  state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
11997
12098
  writeSandboxState(deps.stateDir, state);
@@ -12044,10 +12145,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
12044
12145
  }
12045
12146
 
12046
12147
  // src/doctor-checks.ts
12047
- import { execFileSync as execFileSync7 } from "child_process";
12148
+ import { execFileSync as execFileSync8 } from "child_process";
12048
12149
  var PROBE_BRANCH = "crew-probe-branch";
12049
12150
  var realGit = (cwd, args, env) => {
12050
- execFileSync7("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
12151
+ execFileSync8("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
12051
12152
  };
12052
12153
  function isSshRemote(url) {
12053
12154
  return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
@@ -12127,10 +12228,10 @@ function formatPushAuth(repo, r) {
12127
12228
  return `${label}FAILED \u2014 ${r.detail}; ${r.hint}
12128
12229
  `;
12129
12230
  }
12130
- async function settleRepos(items, nameOf, resolve7) {
12231
+ async function settleRepos(items, nameOf, resolve8) {
12131
12232
  const out = await Promise.all(items.map(async (t) => {
12132
12233
  try {
12133
- return { ok: true, value: await resolve7(t) };
12234
+ return { ok: true, value: await resolve8(t) };
12134
12235
  } catch (e) {
12135
12236
  return { ok: false, repo: nameOf(t), detail: e.message };
12136
12237
  }
@@ -12180,14 +12281,14 @@ function managedRuntimeWarnings(kind, ship) {
12180
12281
 
12181
12282
  // src/selftest.ts
12182
12283
  import { spawn as spawn5 } from "child_process";
12183
- import { existsSync as existsSync25, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync2, readFileSync as readFileSync22, rmSync as rmSync14, writeFileSync as writeFileSync16 } from "fs";
12284
+ import { existsSync as existsSync26, mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
12184
12285
  import { tmpdir as tmpdir4 } from "os";
12185
- import { join as join27 } from "path";
12286
+ import { join as join28 } from "path";
12186
12287
  var SELFTEST_TIMEOUT_MS = 12e4;
12187
12288
  var KEY_VAR = "CREW_API_KEY";
12188
12289
  var SELFTEST_PROMPT = "This is a read-only connectivity check. Use the tracker MCP tool to read this workspace (the workspaces find-one tool, using the workspace id below) and reply with the workspace's name alone, on one line. Do not write anything, do not create files, and do not use any other tool.\n\nWorkspace id: ";
12189
12290
  var realDeps = {
12190
- run: (o) => new Promise((resolve7) => {
12291
+ run: (o) => new Promise((resolve8) => {
12191
12292
  let stdout = "";
12192
12293
  let stderr = "";
12193
12294
  let timedOut = false;
@@ -12195,7 +12296,7 @@ var realDeps = {
12195
12296
  const done = (r) => {
12196
12297
  if (!settled) {
12197
12298
  settled = true;
12198
- resolve7(r);
12299
+ resolve8(r);
12199
12300
  }
12200
12301
  };
12201
12302
  const child = spawn5(o.bin, o.args, { cwd: o.cwd, env: o.env, stdio: ["pipe", "pipe", "pipe"], detached: true });
@@ -12343,7 +12444,7 @@ async function runSelftest(i, deps = realDeps) {
12343
12444
  ...adapter.runEnv(spec),
12344
12445
  [KEY_VAR]: i.apiKey
12345
12446
  };
12346
- const cwd = mkdtempSync2(join27(tmpdir4(), "crew-selftest-"));
12447
+ const cwd = mkdtempSync2(join28(tmpdir4(), "crew-selftest-"));
12347
12448
  try {
12348
12449
  const run3 = await deps.run({
12349
12450
  bin: invocation.bin,
@@ -12376,11 +12477,11 @@ function formatResult(r) {
12376
12477
  const secs = `${(r.ms / 1e3).toFixed(1)}s`;
12377
12478
  return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
12378
12479
  }
12379
- var fileFor2 = (stateDir) => join27(stateDir, "selftest.json");
12480
+ var fileFor2 = (stateDir) => join28(stateDir, "selftest.json");
12380
12481
  function readSelftest(stateDir) {
12381
12482
  try {
12382
12483
  const p = fileFor2(stateDir);
12383
- return existsSync25(p) ? JSON.parse(readFileSync22(p, "utf8")) : {};
12484
+ return existsSync26(p) ? JSON.parse(readFileSync23(p, "utf8")) : {};
12384
12485
  } catch {
12385
12486
  return {};
12386
12487
  }
@@ -12388,8 +12489,8 @@ function readSelftest(stateDir) {
12388
12489
  function recordSelftest(stateDir, results) {
12389
12490
  const all = readSelftest(stateDir);
12390
12491
  for (const r of results) all[r.toolchain] = r;
12391
- mkdirSync15(stateDir, { recursive: true });
12392
- writeFileSync16(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
12492
+ mkdirSync16(stateDir, { recursive: true });
12493
+ writeFileSync17(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
12393
12494
  `);
12394
12495
  }
12395
12496
  function doctorSelftestLines(stored, toolchains) {
@@ -12401,7 +12502,7 @@ function doctorSelftestLines(stored, toolchains) {
12401
12502
  }
12402
12503
 
12403
12504
  // src/cli.ts
12404
- import { resolve as resolve6, dirname as dirname28, join as join49, basename as basename16 } from "path";
12505
+ import { resolve as resolve7, dirname as dirname29, join as join50, basename as basename16 } from "path";
12405
12506
  import { fileURLToPath } from "url";
12406
12507
  import { hostname as hostname4, homedir as homedir11 } from "os";
12407
12508
  import { spawnSync as spawnSync10 } from "child_process";
@@ -12598,14 +12699,14 @@ function rosterMarkdown(roster, meId) {
12598
12699
  }
12599
12700
 
12600
12701
  // src/connect-resume.ts
12601
- import { mkdirSync as mkdirSync16, readFileSync as readFileSync23, rmSync as rmSync15, writeFileSync as writeFileSync17 } from "fs";
12602
- import { dirname as dirname11, join as join28 } from "path";
12702
+ import { mkdirSync as mkdirSync17, readFileSync as readFileSync24, rmSync as rmSync15, writeFileSync as writeFileSync18 } from "fs";
12703
+ import { dirname as dirname12, join as join29 } from "path";
12603
12704
  function lastSessionFile(stateDir) {
12604
- return join28(stateDir, "connect-session.json");
12705
+ return join29(stateDir, "connect-session.json");
12605
12706
  }
12606
12707
  function readPointers(stateDir) {
12607
12708
  try {
12608
- const parsed = JSON.parse(readFileSync23(lastSessionFile(stateDir), "utf8"));
12709
+ const parsed = JSON.parse(readFileSync24(lastSessionFile(stateDir), "utf8"));
12609
12710
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
12610
12711
  const out = {};
12611
12712
  for (const [host, v] of Object.entries(parsed)) {
@@ -12619,8 +12720,8 @@ function readPointers(stateDir) {
12619
12720
  }
12620
12721
  function writePointers(stateDir, pointers) {
12621
12722
  const file = lastSessionFile(stateDir);
12622
- mkdirSync16(dirname11(file), { recursive: true });
12623
- writeFileSync17(file, `${JSON.stringify(pointers, null, 2)}
12723
+ mkdirSync17(dirname12(file), { recursive: true });
12724
+ writeFileSync18(file, `${JSON.stringify(pointers, null, 2)}
12624
12725
  `, { mode: 384 });
12625
12726
  }
12626
12727
  function rememberSession(stateDir, host, workspaceSlug) {
@@ -12705,15 +12806,15 @@ async function signOutSessions(o) {
12705
12806
  }
12706
12807
 
12707
12808
  // src/reset.ts
12708
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync24, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync9, writeFileSync as writeFileSync18 } from "fs";
12709
- import { dirname as dirname12, join as join29 } from "path";
12809
+ import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync25, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync10, writeFileSync as writeFileSync19 } from "fs";
12810
+ import { dirname as dirname13, join as join30 } from "path";
12710
12811
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
12711
12812
  function clonedCheckoutsFile(stateDir) {
12712
- return join29(stateDir, "cloned-checkouts.json");
12813
+ return join30(stateDir, "cloned-checkouts.json");
12713
12814
  }
12714
12815
  function readClonedCheckouts(stateDir) {
12715
12816
  try {
12716
- const parsed = JSON.parse(readFileSync24(clonedCheckoutsFile(stateDir), "utf8"));
12817
+ const parsed = JSON.parse(readFileSync25(clonedCheckoutsFile(stateDir), "utf8"));
12717
12818
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
12718
12819
  } catch {
12719
12820
  return [];
@@ -12723,8 +12824,8 @@ function recordClonedCheckout(stateDir, dir) {
12723
12824
  const known = readClonedCheckouts(stateDir);
12724
12825
  if (known.includes(dir)) return;
12725
12826
  const file = clonedCheckoutsFile(stateDir);
12726
- mkdirSync17(dirname12(file), { recursive: true });
12727
- writeFileSync18(file, `${JSON.stringify([...known, dir], null, 2)}
12827
+ mkdirSync18(dirname13(file), { recursive: true });
12828
+ writeFileSync19(file, `${JSON.stringify([...known, dir], null, 2)}
12728
12829
  `);
12729
12830
  }
12730
12831
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -12778,7 +12879,7 @@ async function runReset(o, fx) {
12778
12879
  }
12779
12880
  if (o.beforeState) steps.push(...await o.beforeState());
12780
12881
  steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
12781
- if (existsSync26(o.configFile)) {
12882
+ if (existsSync27(o.configFile)) {
12782
12883
  if (!o.dryRun) rmSync16(o.configFile, { force: true });
12783
12884
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
12784
12885
  } else {
@@ -12787,7 +12888,7 @@ async function runReset(o, fx) {
12787
12888
  return steps;
12788
12889
  }
12789
12890
  function removeCheckout(dir, dryRun, fx) {
12790
- if (!existsSync26(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
12891
+ if (!existsSync27(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
12791
12892
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
12792
12893
  if (dirty.length) {
12793
12894
  return { step: "checkout", target: dir, action: "kept", detail: `has uncommitted changes (${dirty.length} path${dirty.length === 1 ? "" : "s"}: ${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? ", \u2026" : ""})` };
@@ -12800,15 +12901,15 @@ function removeCheckout(dir, dryRun, fx) {
12800
12901
  }
12801
12902
  function removeState(stateDir, dryRun) {
12802
12903
  const out = [];
12803
- const targets = [join29(stateDir, "resolved"), join29(stateDir, "keys"), clonedCheckoutsFile(stateDir), join29(stateDir, "connect-session.json"), join29(stateDir, "reload-requested"), join29(stateDir, "daemon.started")];
12904
+ const targets = [join30(stateDir, "resolved"), join30(stateDir, "keys"), clonedCheckoutsFile(stateDir), join30(stateDir, "connect-session.json"), join30(stateDir, "reload-requested"), join30(stateDir, "daemon.started")];
12804
12905
  try {
12805
12906
  for (const name of readdirSync12(stateDir)) {
12806
- if (name.startsWith(".") && statSync9(join29(stateDir, name)).isFile()) targets.push(join29(stateDir, name));
12907
+ if (name.startsWith(".") && statSync10(join30(stateDir, name)).isFile()) targets.push(join30(stateDir, name));
12807
12908
  }
12808
12909
  } catch {
12809
12910
  }
12810
12911
  for (const t of targets) {
12811
- if (!existsSync26(t)) continue;
12912
+ if (!existsSync27(t)) continue;
12812
12913
  if (!dryRun) rmSync16(t, { recursive: true, force: true });
12813
12914
  out.push({ step: "state", target: t, action: mark(dryRun) });
12814
12915
  }
@@ -12823,7 +12924,7 @@ function wipeStateDir(stateDir, dryRun) {
12823
12924
  return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
12824
12925
  }
12825
12926
  if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
12826
- if (!dryRun) for (const n of names) rmSync16(join29(stateDir, n), { recursive: true, force: true });
12927
+ if (!dryRun) for (const n of names) rmSync16(join30(stateDir, n), { recursive: true, force: true });
12827
12928
  return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
12828
12929
  }
12829
12930
  function describeResetStep(s) {
@@ -12904,9 +13005,9 @@ async function runDeprovision(o, fx, deps) {
12904
13005
  }
12905
13006
 
12906
13007
  // src/pool-member.ts
12907
- import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, renameSync as renameSync4, writeFileSync as writeFileSync19 } from "fs";
13008
+ import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync20 } from "fs";
12908
13009
  import { homedir as homedir8 } from "os";
12909
- import { dirname as dirname13, join as join30 } from "path";
13010
+ import { dirname as dirname14, join as join31 } from "path";
12910
13011
  var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
12911
13012
  function backoffMs(attempt) {
12912
13013
  return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
@@ -13137,22 +13238,22 @@ async function runPoolMember(deps) {
13137
13238
  }
13138
13239
  }
13139
13240
  function defaultMemberDir(env = process.env) {
13140
- return env.CREW_POOL_DIR ?? join30(env.XDG_STATE_HOME ?? join30(homedir8(), ".local", "state"), "crew-pool-member");
13241
+ return env.CREW_POOL_DIR ?? join31(env.XDG_STATE_HOME ?? join31(homedir8(), ".local", "state"), "crew-pool-member");
13141
13242
  }
13142
13243
  function fileMemberStore(dir) {
13143
- const file = join30(dir, "member.json");
13244
+ const file = join31(dir, "member.json");
13144
13245
  return {
13145
13246
  read() {
13146
13247
  try {
13147
- return JSON.parse(readFileSync25(file, "utf8"));
13248
+ return JSON.parse(readFileSync26(file, "utf8"));
13148
13249
  } catch {
13149
13250
  return {};
13150
13251
  }
13151
13252
  },
13152
13253
  write(f) {
13153
- mkdirSync18(dirname13(file), { recursive: true, mode: 448 });
13254
+ mkdirSync19(dirname14(file), { recursive: true, mode: 448 });
13154
13255
  const tmp = `${file}.tmp`;
13155
- writeFileSync19(tmp, `${JSON.stringify(f)}
13256
+ writeFileSync20(tmp, `${JSON.stringify(f)}
13156
13257
  `, { mode: 384 });
13157
13258
  renameSync4(tmp, file);
13158
13259
  }
@@ -13204,30 +13305,30 @@ function hostArchitecture(arch = process.arch) {
13204
13305
 
13205
13306
  // src/pool-member-host.ts
13206
13307
  import "child_process";
13207
- import { existsSync as existsSync30 } from "fs";
13308
+ import { existsSync as existsSync31 } from "fs";
13208
13309
  import { hostname as hostname3 } from "os";
13209
13310
 
13210
13311
  // src/machine-id.ts
13211
- import { execFileSync as execFileSync8 } from "child_process";
13312
+ import { execFileSync as execFileSync9 } from "child_process";
13212
13313
  import { createHash as createHash7 } from "crypto";
13213
- import { existsSync as existsSync27, mkdirSync as mkdirSync19, readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
13214
- import { dirname as dirname14, join as join31 } from "path";
13314
+ import { existsSync as existsSync28, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
13315
+ import { dirname as dirname15, join as join32 } from "path";
13215
13316
  function machineIdPathFor(stateDir) {
13216
- return join31(stateDir, "machine-id");
13317
+ return join32(stateDir, "machine-id");
13217
13318
  }
13218
13319
  var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
13219
13320
  function readRawMachineId(platform2 = process.platform) {
13220
13321
  try {
13221
13322
  if (platform2 === "darwin") {
13222
- const out = execFileSync8("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
13323
+ const out = execFileSync9("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
13223
13324
  encoding: "utf8",
13224
13325
  stdio: ["ignore", "pipe", "ignore"]
13225
13326
  });
13226
13327
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
13227
13328
  }
13228
13329
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
13229
- if (existsSync27(p)) {
13230
- const id = readFileSync26(p, "utf8").trim();
13330
+ if (existsSync28(p)) {
13331
+ const id = readFileSync27(p, "utf8").trim();
13231
13332
  if (id) return id;
13232
13333
  }
13233
13334
  }
@@ -13238,15 +13339,15 @@ function readRawMachineId(platform2 = process.platform) {
13238
13339
  function ensureMachineId(stateDir, opts = {}) {
13239
13340
  if (opts.override?.trim()) return sha256(opts.override.trim());
13240
13341
  const path = machineIdPathFor(stateDir);
13241
- if (existsSync27(path)) {
13242
- const cached = readFileSync26(path, "utf8").trim();
13342
+ if (existsSync28(path)) {
13343
+ const cached = readFileSync27(path, "utf8").trim();
13243
13344
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
13244
13345
  }
13245
13346
  const raw = (opts.read ?? readRawMachineId)();
13246
13347
  if (!raw) return void 0;
13247
13348
  const hashed = sha256(raw);
13248
- mkdirSync19(dirname14(path), { recursive: true });
13249
- writeFileSync20(path, `${hashed}
13349
+ mkdirSync20(dirname15(path), { recursive: true });
13350
+ writeFileSync21(path, `${hashed}
13250
13351
  `, { mode: 384 });
13251
13352
  return hashed;
13252
13353
  }
@@ -13259,7 +13360,7 @@ function matchShipRows(ships, shipName2, machineId) {
13259
13360
 
13260
13361
  // src/provision.ts
13261
13362
  import { spawnSync as spawnSync7 } from "child_process";
13262
- import { existsSync as existsSync28, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
13363
+ import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
13263
13364
  import { hostname as hostname2 } from "os";
13264
13365
  import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
13265
13366
 
@@ -13268,7 +13369,7 @@ import { parseDocument as parseDocument2 } from "yaml";
13268
13369
 
13269
13370
  // src/repos-cmd.ts
13270
13371
  import { homedir as homedir9 } from "os";
13271
- import { basename as basename10, join as join32, resolve as resolve3 } from "path";
13372
+ import { basename as basename10, join as join33, resolve as resolve4 } from "path";
13272
13373
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
13273
13374
  var ReposError = class extends Error {
13274
13375
  };
@@ -13315,7 +13416,7 @@ function parseAddArgs(args, routes, isDir) {
13315
13416
  }
13316
13417
  function expandDir(p, base) {
13317
13418
  const home = p === "~" ? homedir9() : p.startsWith("~/") ? `${homedir9()}/${p.slice(2)}` : p;
13318
- return resolve3(base, home);
13419
+ return resolve4(base, home);
13319
13420
  }
13320
13421
  function routeMapOf(doc, route2) {
13321
13422
  const routes = doc.get("routes", true);
@@ -13546,7 +13647,7 @@ function planRepoClone(i) {
13546
13647
  }
13547
13648
  const ship = doc.get("ship", true);
13548
13649
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
13549
- const target = i.dir ? expandDir(i.dir, i.base) : join32(expandDir(basePath, i.base), i.route.split("/")[0], name);
13650
+ const target = i.dir ? expandDir(i.dir, i.base) : join33(expandDir(basePath, i.base), i.route.split("/")[0], name);
13550
13651
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
13551
13652
  const forgeHost = i.resolved?.repoForgeHosts?.[id];
13552
13653
  return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
@@ -13853,7 +13954,7 @@ function realProvisionDeps(crewHome) {
13853
13954
  };
13854
13955
  const configFile = () => {
13855
13956
  const f = findConfigFile(crewHome);
13856
- return existsSync28(f) ? f : null;
13957
+ return existsSync29(f) ? f : null;
13857
13958
  };
13858
13959
  return {
13859
13960
  crew: spawnCrew,
@@ -13861,13 +13962,13 @@ function realProvisionDeps(crewHome) {
13861
13962
  enableRoute(route2) {
13862
13963
  const file = configFile();
13863
13964
  if (!file) return false;
13864
- const text2 = readFileSync27(file, "utf8");
13965
+ const text2 = readFileSync28(file, "utf8");
13865
13966
  const doc = parseDocument3(text2);
13866
13967
  const routes = doc.get("routes");
13867
13968
  const entry = routes?.items?.find((r) => r.get("route") === route2);
13868
13969
  if (!entry) throw new Error(`route ${route2} is not in ${file}`);
13869
13970
  if (entry.get("enabled") === true) return false;
13870
- writeFileSync21(file, planEnable(text2, route2));
13971
+ writeFileSync22(file, planEnable(text2, route2));
13871
13972
  return true;
13872
13973
  },
13873
13974
  installState() {
@@ -13887,14 +13988,14 @@ function realProvisionDeps(crewHome) {
13887
13988
  continue;
13888
13989
  }
13889
13990
  for (const p of plan.unitPaths) {
13890
- if (!existsSync28(p) || readFileSync27(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
13991
+ if (!existsSync29(p) || readFileSync28(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
13891
13992
  }
13892
13993
  }
13893
13994
  const label = labelFor(crewHome, "run");
13894
13995
  let running = false;
13895
13996
  let lingering = true;
13896
13997
  if (mechanism === "systemd") {
13897
- const system = ctx.root || existsSync28(systemUnitPathFor(label));
13998
+ const system = ctx.root || existsSync29(systemUnitPathFor(label));
13898
13999
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
13899
14000
  running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
13900
14001
  if (!system) {
@@ -13911,18 +14012,18 @@ function realProvisionDeps(crewHome) {
13911
14012
  setShipSetting(key, value2) {
13912
14013
  const file = configFile();
13913
14014
  if (!file) return false;
13914
- const doc = parseDocument3(readFileSync27(file, "utf8"));
14015
+ const doc = parseDocument3(readFileSync28(file, "utf8"));
13915
14016
  if (doc.getIn(["ship", key]) === value2) return false;
13916
14017
  doc.setIn(["ship", key], value2);
13917
- writeFileSync21(file, doc.toString());
14018
+ writeFileSync22(file, doc.toString());
13918
14019
  return true;
13919
14020
  },
13920
14021
  applyManagedDefaults() {
13921
14022
  const file = configFile();
13922
14023
  if (!file) return [];
13923
- const doc = parseDocument3(readFileSync27(file, "utf8"));
14024
+ const doc = parseDocument3(readFileSync28(file, "utf8"));
13924
14025
  const wrote = applyManagedDefaultsToDoc(doc);
13925
- if (wrote.length) writeFileSync21(file, doc.toString());
14026
+ if (wrote.length) writeFileSync22(file, doc.toString());
13926
14027
  return wrote;
13927
14028
  },
13928
14029
  storedToken(host) {
@@ -13939,9 +14040,9 @@ host=${host}
13939
14040
 
13940
14041
  // src/tunnel.ts
13941
14042
  import { spawn as spawn6 } from "child_process";
13942
- import { existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync28, writeFileSync as writeFileSync22, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14043
+ import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync23, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
13943
14044
  import { randomInt } from "crypto";
13944
- import { join as join33 } from "path";
14045
+ import { join as join34 } from "path";
13945
14046
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
13946
14047
  var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
13947
14048
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -14013,21 +14114,21 @@ function startTunnel(opts) {
14013
14114
  };
14014
14115
  }
14015
14116
  function tunnelStatePath(stateDir, workspaceId) {
14016
- return join33(stateDir, "passengers", `${workspaceId}.json`);
14117
+ return join34(stateDir, "passengers", `${workspaceId}.json`);
14017
14118
  }
14018
14119
  function readPersistedTunnel(stateDir, workspaceId) {
14019
14120
  const path = tunnelStatePath(stateDir, workspaceId);
14020
- if (!existsSync29(path)) return void 0;
14121
+ if (!existsSync30(path)) return void 0;
14021
14122
  try {
14022
- return JSON.parse(readFileSync28(path, "utf8"));
14123
+ return JSON.parse(readFileSync29(path, "utf8"));
14023
14124
  } catch {
14024
14125
  return void 0;
14025
14126
  }
14026
14127
  }
14027
14128
  function writePersistedTunnel(stateDir, state) {
14028
14129
  const path = tunnelStatePath(stateDir, state.workspaceId);
14029
- mkdirSync20(join33(stateDir, "passengers"), { recursive: true });
14030
- writeFileSync22(path, `${JSON.stringify(state, null, 2)}
14130
+ mkdirSync21(join34(stateDir, "passengers"), { recursive: true });
14131
+ writeFileSync23(path, `${JSON.stringify(state, null, 2)}
14031
14132
  `);
14032
14133
  }
14033
14134
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -14129,8 +14230,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
14129
14230
  }
14130
14231
  }
14131
14232
  function readAllPersistedWorkspaceIds(stateDir) {
14132
- const dir = join33(stateDir, "passengers");
14133
- if (!existsSync29(dir)) return [];
14233
+ const dir = join34(stateDir, "passengers");
14234
+ if (!existsSync30(dir)) return [];
14134
14235
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
14135
14236
  }
14136
14237
 
@@ -14155,7 +14256,7 @@ function realPoolMemberDeps(o) {
14155
14256
  crewVersion: crewVersion(o.crewHome),
14156
14257
  hostname: hostname3()
14157
14258
  },
14158
- hasShipConfig: () => existsSync30(findConfigFile(o.crewHome)),
14259
+ hasShipConfig: () => existsSync31(findConfigFile(o.crewHome)),
14159
14260
  async provision(lease, instanceClass, shipKind) {
14160
14261
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
14161
14262
  const lines2 = [];
@@ -15031,8 +15132,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
15031
15132
  }
15032
15133
 
15033
15134
  // src/poll.ts
15034
- import { writeFileSync as writeFileSync23 } from "fs";
15035
- import { join as join34 } from "path";
15135
+ import { writeFileSync as writeFileSync24 } from "fs";
15136
+ import { join as join35 } from "path";
15036
15137
 
15037
15138
  // src/stalled.ts
15038
15139
  var STALLED_AFTER_CYCLES = 30;
@@ -15339,7 +15440,7 @@ async function decideCycle(o) {
15339
15440
  }
15340
15441
  return decision;
15341
15442
  }
15342
- var digestPath = (stateDir, route2, role, runId) => join34(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15443
+ var digestPath = (stateDir, route2, role, runId) => join35(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15343
15444
  function otherRunsRemoved(tickets, own) {
15344
15445
  if (!own) return tickets;
15345
15446
  return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
@@ -15417,7 +15518,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
15417
15518
 
15418
15519
  ${body}` : body;
15419
15520
  const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
15420
- writeFileSync23(path, text2);
15521
+ writeFileSync24(path, text2);
15421
15522
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
15422
15523
  data: { role, bytes: Buffer.byteLength(text2) }
15423
15524
  });
@@ -15525,8 +15626,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
15525
15626
  }
15526
15627
 
15527
15628
  // src/repo-refresh.ts
15528
- import { existsSync as existsSync31, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "fs";
15529
- import { dirname as dirname16 } from "path";
15629
+ import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "fs";
15630
+ import { dirname as dirname17 } from "path";
15530
15631
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
15531
15632
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
15532
15633
  var lastAsked = /* @__PURE__ */ new Map();
@@ -15555,11 +15656,11 @@ function applyRepoRows(resolved, rows) {
15555
15656
  }
15556
15657
  function persistRepoRows(stateDir, routeName, rows) {
15557
15658
  const path = resolvedPathFor(stateDir, routeName);
15558
- if (!existsSync31(path)) return;
15559
- const raw = JSON.parse(readFileSync29(path, "utf8"));
15659
+ if (!existsSync32(path)) return;
15660
+ const raw = JSON.parse(readFileSync30(path, "utf8"));
15560
15661
  applyRepoRows(raw, rows);
15561
- mkdirSync21(dirname16(path), { recursive: true });
15562
- writeFileSync24(path, `${JSON.stringify(raw, null, 2)}
15662
+ mkdirSync22(dirname17(path), { recursive: true });
15663
+ writeFileSync25(path, `${JSON.stringify(raw, null, 2)}
15563
15664
  `);
15564
15665
  }
15565
15666
  async function refreshUnknownRepos(o) {
@@ -16009,13 +16110,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
16009
16110
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
16010
16111
 
16011
16112
  // src/connect-wizard.ts
16012
- import { delimiter, dirname as dirname17, join as join35 } from "path";
16013
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync25 } from "fs";
16113
+ import { delimiter, dirname as dirname18, join as join36 } from "path";
16114
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync33, mkdirSync as mkdirSync23, writeFileSync as writeFileSync26 } from "fs";
16014
16115
  import { stringify as stringify3 } from "yaml";
16015
16116
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
16016
16117
  function shouldRunWizard(opts) {
16017
16118
  if (!opts.isTTY) return false;
16018
- const exists = opts.configExists ?? existsSync32;
16119
+ const exists = opts.configExists ?? existsSync33;
16019
16120
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
16020
16121
  }
16021
16122
  function firstRunConfigPath() {
@@ -16032,7 +16133,7 @@ function defaultIsExecutable(p) {
16032
16133
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
16033
16134
  for (const dir of pathEnv.split(delimiter)) {
16034
16135
  if (!dir) continue;
16035
- const candidate = join35(dir, name);
16136
+ const candidate = join36(dir, name);
16036
16137
  if (isExecutable(candidate)) return candidate;
16037
16138
  }
16038
16139
  return void 0;
@@ -16058,9 +16159,9 @@ async function runWizard(p, env) {
16058
16159
  if (entered && isExec(entered)) agentBin = entered;
16059
16160
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
16060
16161
  }
16061
- const nodeDir = dirname17(env.nodePath);
16162
+ const nodeDir = dirname18(env.nodePath);
16062
16163
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16063
- const baseDirs = [nodeDir, pnpmPath ? dirname17(pnpmPath) : void 0];
16164
+ const baseDirs = [nodeDir, pnpmPath ? dirname18(pnpmPath) : void 0];
16064
16165
  p.say(
16065
16166
  `
16066
16167
  Host Passengers lets this machine run the agent sessions that other people's
@@ -16071,7 +16172,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
16071
16172
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
16072
16173
  }
16073
16174
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16074
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname17(dockerPath) : void 0]);
16175
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname18(dockerPath) : void 0]);
16075
16176
  if (extraPath) {
16076
16177
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
16077
16178
  }
@@ -16093,9 +16194,9 @@ routes:
16093
16194
  ${routeBlock}`;
16094
16195
  }
16095
16196
  function writeNewConfig(path, text2) {
16096
- mkdirSync22(dirname17(path), { recursive: true });
16197
+ mkdirSync23(dirname18(path), { recursive: true });
16097
16198
  try {
16098
- writeFileSync25(path, text2, { mode: 384, flag: "wx" });
16199
+ writeFileSync26(path, text2, { mode: 384, flag: "wx" });
16099
16200
  return true;
16100
16201
  } catch (e) {
16101
16202
  if (e.code === "EEXIST") return false;
@@ -16129,7 +16230,7 @@ Wrote ${o.configPath}.
16129
16230
  // src/connect-json.ts
16130
16231
  import { accessSync as accessSync4, constants as constants4 } from "fs";
16131
16232
  import { homedir as homedir10 } from "os";
16132
- import { dirname as dirname18, join as join36 } from "path";
16233
+ import { dirname as dirname19, join as join37 } from "path";
16133
16234
  import { delimiter as delimiter2 } from "path";
16134
16235
  var CHOICE_EXIT_CODE = 3;
16135
16236
  function createEmitter(json2, write) {
@@ -16168,12 +16269,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
16168
16269
  const dirs = [
16169
16270
  ...pathEnv.split(delimiter2),
16170
16271
  ...(extraPath ?? "").split(delimiter2),
16171
- join36(home, ".local/bin"),
16172
- join36(home, ".claude/local"),
16272
+ join37(home, ".local/bin"),
16273
+ join37(home, ".claude/local"),
16173
16274
  "/opt/homebrew/bin",
16174
16275
  "/usr/local/bin",
16175
- join36(home, ".npm-global/bin"),
16176
- join36(home, ".volta/bin")
16276
+ join37(home, ".npm-global/bin"),
16277
+ join37(home, ".volta/bin")
16177
16278
  ];
16178
16279
  return [...new Set(dirs.filter(Boolean))];
16179
16280
  }
@@ -16195,7 +16296,7 @@ function flagWizardAnswers(f, env) {
16195
16296
  const agentBin = f.agentBin ?? found?.bin;
16196
16297
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16197
16298
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16198
- const extraPath = f.extraPath ?? deriveExtraPath([dirname18(env.nodePath), pnpmPath ? dirname18(pnpmPath) : void 0, dockerPath ? dirname18(dockerPath) : void 0]);
16299
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname19(env.nodePath), pnpmPath ? dirname19(pnpmPath) : void 0, dockerPath ? dirname19(dockerPath) : void 0]);
16199
16300
  return {
16200
16301
  answers: {
16201
16302
  name: f.shipName?.trim() || env.hostname,
@@ -16209,8 +16310,8 @@ function flagWizardAnswers(f, env) {
16209
16310
  }
16210
16311
 
16211
16312
  // src/release-run.ts
16212
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync26, existsSync as existsSync33, copyFileSync, mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
16213
- import { dirname as dirname19, join as join37 } from "path";
16313
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync27, existsSync as existsSync34, copyFileSync, mkdirSync as mkdirSync24, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
16314
+ import { dirname as dirname20, join as join38 } from "path";
16214
16315
  import { tmpdir as tmpdir5 } from "os";
16215
16316
 
16216
16317
  // src/release.ts
@@ -16429,7 +16530,7 @@ function decideRelease(cwd, tickets, contract, opts = {}) {
16429
16530
  const block = opts.guard === false ? null : checkGuards(cwd, base);
16430
16531
  const lastTag = latestReleaseTag(cwd, pattern, ref);
16431
16532
  const lastReleased = lastTag ? tagCommit(cwd, lastTag) : null;
16432
- const head = resolve2(cwd, ref) ?? "";
16533
+ const head = resolve3(cwd, ref) ?? "";
16433
16534
  const merges = block ? [] : planMerge(cwd, tickets, contract, lastReleased, opts.branchFor ?? ((t) => branchForIssue(cwd, t.issue_id)), opts.ref ?? base, opts.blockerInfo, ref);
16434
16535
  const unreleasedCommits = lastReleased ? countCommits(cwd, `${lastReleased}..${ref}`) : 0;
16435
16536
  const unreleasedWork = lastReleased ? (gitOk(cwd, ["log", "--format=%s", `${lastReleased}..${ref}`]) ?? "").split("\n").filter((l) => l && !RELEASE_COMMIT_SUBJECT.test(l)).length : 0;
@@ -16609,8 +16710,8 @@ function movedPast(r, t, branch, pushedAs) {
16609
16710
  if (!r.columns.verifiedSha) return null;
16610
16711
  const verified = columnValue(t, r.contract.columns.verifiedSha);
16611
16712
  if (!verified) return null;
16612
- const head = resolve2(r.cwd, branch);
16613
- const remoteHead = resolve2(r.cwd, `${r.repo.branch.remote}/${pushedAs}`);
16713
+ const head = resolve3(r.cwd, branch);
16714
+ const remoteHead = resolve3(r.cwd, `${r.repo.branch.remote}/${pushedAs}`);
16614
16715
  if (remoteHead && !isAncestor(r.cwd, remoteHead, verified) && remoteHead !== head) {
16615
16716
  return { ticket: t, verified, head: remoteHead, branch: pushedAs };
16616
16717
  }
@@ -16633,7 +16734,7 @@ async function runReview(r, verified, reviewing) {
16633
16734
  out.requeues.push(moved);
16634
16735
  continue;
16635
16736
  }
16636
- const head = resolve2(r.cwd, branch);
16737
+ const head = resolve3(r.cwd, branch);
16637
16738
  if (!head) continue;
16638
16739
  if (r.dryRun) {
16639
16740
  r.emit.emit(`would push ${branch} to ${remote}/${pushedAs} for review`, { ticket: ticket.issue_id });
@@ -16951,13 +17052,13 @@ async function bump(o, size) {
16951
17052
  }
16952
17053
  return printed;
16953
17054
  }
16954
- const files = o.repo.release.versionFiles.map((f) => join37(o.cwd, f));
17055
+ const files = o.repo.release.versionFiles.map((f) => join38(o.cwd, f));
16955
17056
  const first = files[0];
16956
- if (!existsSync33(first)) {
17057
+ if (!existsSync34(first)) {
16957
17058
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
16958
17059
  return null;
16959
17060
  }
16960
- const current = JSON.parse(readFileSync30(first, "utf8")).version;
17061
+ const current = JSON.parse(readFileSync31(first, "utf8")).version;
16961
17062
  if (!current) {
16962
17063
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
16963
17064
  return null;
@@ -16970,27 +17071,27 @@ async function bump(o, size) {
16970
17071
  return version;
16971
17072
  }
16972
17073
  for (const f of files) {
16973
- const src = readFileSync30(f, "utf8");
17074
+ const src = readFileSync31(f, "utf8");
16974
17075
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
16975
17076
  if (out === src) {
16976
17077
  o.emit.error(`no version field in ${f}`);
16977
17078
  return null;
16978
17079
  }
16979
- writeFileSync26(f, out);
17080
+ writeFileSync27(f, out);
16980
17081
  }
16981
17082
  return version;
16982
17083
  }
16983
17084
  function writeChangelog(o, version, entries) {
16984
17085
  const file = o.repo.release.changelog;
16985
17086
  if (!file || entries.length === 0) return;
16986
- const path = join37(o.cwd, file);
17087
+ const path = join38(o.cwd, file);
16987
17088
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
16988
17089
  if (o.dryRun) {
16989
17090
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
16990
17091
  return;
16991
17092
  }
16992
- const existing = existsSync33(path) ? readFileSync30(path, "utf8") : "# Changelog\n";
16993
- writeFileSync26(path, insertChangelogSection(existing, section));
17093
+ const existing = existsSync34(path) ? readFileSync31(path, "utf8") : "# Changelog\n";
17094
+ writeFileSync27(path, insertChangelogSection(existing, section));
16994
17095
  }
16995
17096
  async function confirm(o, expected) {
16996
17097
  if (!o.repo.hooks.released) return void 0;
@@ -17083,8 +17184,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
17083
17184
  function openReleaseCheckout(o) {
17084
17185
  const { remote, base } = o.repo.branch;
17085
17186
  const branch = `crew/release-${Date.now()}`;
17086
- const root = mkdtempSync3(join37(tmpdir5(), "crew-release-"));
17087
- const dir = join37(root, "checkout");
17187
+ const root = mkdtempSync3(join38(tmpdir5(), "crew-release-"));
17188
+ const dir = join38(root, "checkout");
17088
17189
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
17089
17190
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
17090
17191
  try {
@@ -17094,10 +17195,10 @@ function openReleaseCheckout(o) {
17094
17195
  throw e;
17095
17196
  }
17096
17197
  for (const f of o.repo.worktrees.copy) {
17097
- const src = join37(o.cwd, f);
17098
- if (!existsSync33(src)) continue;
17099
- mkdirSync23(dirname19(join37(dir, f)), { recursive: true });
17100
- copyFileSync(src, join37(dir, f));
17198
+ const src = join38(o.cwd, f);
17199
+ if (!existsSync34(src)) continue;
17200
+ mkdirSync24(dirname20(join38(dir, f)), { recursive: true });
17201
+ copyFileSync(src, join38(dir, f));
17101
17202
  }
17102
17203
  return { dir, branch, close };
17103
17204
  }
@@ -17520,7 +17621,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
17520
17621
  branches: merged.filter((c) => c.branch).map((c) => ({
17521
17622
  key: c.ticket.issue_tag ?? c.ticket.issue_id,
17522
17623
  branch: c.branch,
17523
- tip: resolve2(o.cwd, c.branch) ?? ""
17624
+ tip: resolve3(o.cwd, c.branch) ?? ""
17524
17625
  }))
17525
17626
  };
17526
17627
  const hookFailure2 = {
@@ -17583,7 +17684,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
17583
17684
  if (releaseBranch && !o.dryRun) {
17584
17685
  const { remote, base } = o.repo.branch;
17585
17686
  if (fetchRemote(o.cwd, remote) && remoteBranchExists(o.cwd, remote, base)) {
17586
- const moved = resolve2(o.cwd, `${remote}/${base}`);
17687
+ const moved = resolve3(o.cwd, `${remote}/${base}`);
17587
17688
  if (moved && !isAncestor(o.cwd, moved, "HEAD")) {
17588
17689
  o.emit.warn(`${remote}/${base} moved during the release \u2014 discarding it; the tickets stay verified and the next cycle retries`);
17589
17690
  return { merged, conflicts, unbuildable, version, deployed: false, decision, stopped: "base branch moved during release" };
@@ -17687,6 +17788,49 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
17687
17788
  return { merged, conflicts, unbuildable, version, tag, deployed, integrated, confirmed, decision };
17688
17789
  }
17689
17790
 
17791
+ // src/repo-map.ts
17792
+ var CHARS_PER_TOKEN = 4;
17793
+ var MAP_TIMEOUT_MS = 6e4;
17794
+ function budgetMap(text2, tokenBudget) {
17795
+ const body = text2.replace(/\s+$/, "");
17796
+ const limit = tokenBudget * CHARS_PER_TOKEN;
17797
+ if (body.length <= limit) return body;
17798
+ const lines2 = body.split("\n");
17799
+ const noteFor = (kept2) => `\u2026 ${lines2.length - kept2} of ${lines2.length} lines cut to fit ship.mapTokenBudget (${tokenBudget} tokens)`;
17800
+ let kept = lines2.length;
17801
+ let size = body.length;
17802
+ while (kept > 0 && size + 1 + noteFor(kept).length > limit) {
17803
+ kept--;
17804
+ size -= lines2[kept].length + 1;
17805
+ }
17806
+ return [...lines2.slice(0, kept), noteFor(kept)].join("\n");
17807
+ }
17808
+ function loadRepoMap(repo, tokenBudget, shellFallback) {
17809
+ const script = repo.config.hooks.map;
17810
+ if (!script) return Promise.resolve({ skipped: "" });
17811
+ return runScript(script, {
17812
+ cwd: repo.dir,
17813
+ shell: resolveShell(repo.config.shell ?? shellFallback),
17814
+ env: { CREW_REPO: repo.name },
17815
+ timeoutMs: MAP_TIMEOUT_MS
17816
+ }).then(
17817
+ (r) => {
17818
+ if (r.code !== 0) return { skipped: `the \`map\` hook for ${repo.name} exited ${r.code}, so the brief has no repository map` };
17819
+ if (!r.stdout.trim()) return { skipped: `the \`map\` hook for ${repo.name} printed nothing, so the brief has no repository map` };
17820
+ return { text: budgetMap(r.stdout, tokenBudget) };
17821
+ },
17822
+ (e) => ({ skipped: `the \`map\` hook for ${repo.name} could not run (${e instanceof Error ? e.message : String(e)}), so the brief has no repository map` })
17823
+ );
17824
+ }
17825
+ async function withRepoMaps(repos, tokenBudget, shellFallback) {
17826
+ return Promise.all(repos.map(async (r) => {
17827
+ if (!r.config.hooks.map) return r;
17828
+ const out = await loadRepoMap(r, tokenBudget, shellFallback);
17829
+ if ("skipped" in out) return out.skipped ? { ...r, mapSkipped: out.skipped } : r;
17830
+ return { ...r, map: out.text };
17831
+ }));
17832
+ }
17833
+
17690
17834
  // src/failure-alert.ts
17691
17835
  var FAILURE_ALERT_MARKER = calloutHeader("CAUTION", "Failure Alert");
17692
17836
  function findOpenFailureAlert(tickets, headline) {
@@ -18076,7 +18220,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
18076
18220
  }
18077
18221
 
18078
18222
  // src/watch.ts
18079
- import { watch as watchFile, existsSync as existsSync34, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
18223
+ import { watch as watchFile, existsSync as existsSync35, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
18080
18224
  var E = "\x1B[";
18081
18225
  var dim = (s) => `${E}2m${s}${E}0m`;
18082
18226
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -18177,8 +18321,8 @@ function renderHeaders(states, now = Date.now()) {
18177
18321
  return states.map((s) => renderHeader(s, now)).join("");
18178
18322
  }
18179
18323
  function readFrom(file, from) {
18180
- if (!existsSync34(file)) return { events: [], offset: from };
18181
- const size = statSync10(file).size;
18324
+ if (!existsSync35(file)) return { events: [], offset: from };
18325
+ const size = statSync11(file).size;
18182
18326
  if (size < from) return readFrom(file, 0);
18183
18327
  if (size === from) return { events: [], offset: from };
18184
18328
  const fd = openSync4(file, "r");
@@ -18420,8 +18564,8 @@ function nextRoles(decision, ran) {
18420
18564
  }
18421
18565
 
18422
18566
  // src/ship-name.ts
18423
- import { existsSync as existsSync35, mkdirSync as mkdirSync24, readFileSync as readFileSync31, renameSync as renameSync5, writeFileSync as writeFileSync27 } from "fs";
18424
- import { dirname as dirname20 } from "path";
18567
+ import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, renameSync as renameSync5, writeFileSync as writeFileSync28 } from "fs";
18568
+ import { dirname as dirname21 } from "path";
18425
18569
  import { parseDocument as parseDocument5 } from "yaml";
18426
18570
  var ShipNameError = class extends Error {
18427
18571
  };
@@ -18482,34 +18626,34 @@ async function syncRouteName(i) {
18482
18626
  return { status: "ok", ...base, action, name: i.local };
18483
18627
  }
18484
18628
  function readSyncedName(resolvedFile) {
18485
- if (!existsSync35(resolvedFile)) return void 0;
18629
+ if (!existsSync36(resolvedFile)) return void 0;
18486
18630
  try {
18487
- const v = JSON.parse(readFileSync31(resolvedFile, "utf8")).shipName;
18631
+ const v = JSON.parse(readFileSync32(resolvedFile, "utf8")).shipName;
18488
18632
  return typeof v === "string" ? v : void 0;
18489
18633
  } catch {
18490
18634
  return void 0;
18491
18635
  }
18492
18636
  }
18493
18637
  function writeSyncedName(resolvedFile, name) {
18494
- if (!existsSync35(resolvedFile)) return;
18638
+ if (!existsSync36(resolvedFile)) return;
18495
18639
  let raw;
18496
18640
  try {
18497
- raw = JSON.parse(readFileSync31(resolvedFile, "utf8"));
18641
+ raw = JSON.parse(readFileSync32(resolvedFile, "utf8"));
18498
18642
  } catch {
18499
18643
  return;
18500
18644
  }
18501
18645
  if (raw.shipName === name) return;
18502
18646
  raw.shipName = name;
18503
- mkdirSync24(dirname20(resolvedFile), { recursive: true });
18647
+ mkdirSync25(dirname21(resolvedFile), { recursive: true });
18504
18648
  const tmp = `${resolvedFile}.tmp${process.pid}`;
18505
- writeFileSync27(tmp, `${JSON.stringify(raw, null, 2)}
18649
+ writeFileSync28(tmp, `${JSON.stringify(raw, null, 2)}
18506
18650
  `);
18507
18651
  renameSync5(tmp, resolvedFile);
18508
18652
  }
18509
18653
  async function reconcileShipName(i) {
18510
18654
  let local = i.ship.name;
18511
18655
  try {
18512
- local = shipNameOfYaml(readFileSync31(i.configFile, "utf8")) ?? local;
18656
+ local = shipNameOfYaml(readFileSync32(i.configFile, "utf8")) ?? local;
18513
18657
  } catch {
18514
18658
  }
18515
18659
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -18533,8 +18677,8 @@ async function reconcileShipName(i) {
18533
18677
  }
18534
18678
  if (result.status !== "ok") return result;
18535
18679
  if (result.action === "adopt" || result.action === "row-wins") {
18536
- const text2 = readFileSync31(i.configFile, "utf8");
18537
- writeFileSync27(i.configFile, setShipNameInYaml(text2, result.name));
18680
+ const text2 = readFileSync32(i.configFile, "utf8");
18681
+ writeFileSync28(i.configFile, setShipNameInYaml(text2, result.name));
18538
18682
  i.ship.name = result.name;
18539
18683
  if (result.action === "row-wins") {
18540
18684
  i.emit?.warn(
@@ -19193,8 +19337,8 @@ function describeCapability(i) {
19193
19337
  }
19194
19338
 
19195
19339
  // src/agents-sync-run.ts
19196
- import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, writeFileSync as writeFileSync28 } from "fs";
19197
- import { dirname as dirname21 } from "path";
19340
+ import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, writeFileSync as writeFileSync29 } from "fs";
19341
+ import { dirname as dirname22 } from "path";
19198
19342
  async function runAgentsSync(target, o) {
19199
19343
  const dryRun = o.dryRun ?? false;
19200
19344
  const force = o.force ?? false;
@@ -19227,12 +19371,12 @@ async function runAgentsSync(target, o) {
19227
19371
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
19228
19372
  if (!dryRun) {
19229
19373
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
19230
- const raw = existsSync36(resolvedPath) ? JSON.parse(readFileSync32(resolvedPath, "utf8")) : {};
19374
+ const raw = existsSync37(resolvedPath) ? JSON.parse(readFileSync33(resolvedPath, "utf8")) : {};
19231
19375
  raw.agentPersonas = personaResult.agentPersonas;
19232
19376
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
19233
19377
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
19234
- mkdirSync25(dirname21(resolvedPath), { recursive: true });
19235
- writeFileSync28(resolvedPath, `${JSON.stringify(raw, null, 2)}
19378
+ mkdirSync26(dirname22(resolvedPath), { recursive: true });
19379
+ writeFileSync29(resolvedPath, `${JSON.stringify(raw, null, 2)}
19236
19380
  `);
19237
19381
  if (target.resolved) {
19238
19382
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -19336,7 +19480,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
19336
19480
 
19337
19481
  // src/repos-check.ts
19338
19482
  import { spawnSync as spawnSync9 } from "child_process";
19339
- import { readFileSync as readFileSync33 } from "fs";
19483
+ import { readFileSync as readFileSync34 } from "fs";
19340
19484
  import { basename as basename11 } from "path";
19341
19485
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
19342
19486
  function checkHookSyntax(script, shell) {
@@ -19377,7 +19521,7 @@ function checkRepoSpec(dir, opts = {}) {
19377
19521
  }
19378
19522
  let parsed;
19379
19523
  try {
19380
- parsed = parseRepoConfig(readFileSync33(file, "utf8"), file);
19524
+ parsed = parseRepoConfig(readFileSync34(file, "utf8"), file);
19381
19525
  } catch (e) {
19382
19526
  if (!(e instanceof RepoConfigError)) throw e;
19383
19527
  return { ...empty, file, error: e.message, exitCode: 1 };
@@ -19656,9 +19800,9 @@ function existingRepoConfig(f) {
19656
19800
  }
19657
19801
 
19658
19802
  // src/repos-hooks-agent.ts
19659
- import { spawn as spawn7, execFileSync as execFileSync9 } from "child_process";
19803
+ import { spawn as spawn7, execFileSync as execFileSync10 } from "child_process";
19660
19804
  import { realpathSync as realpathSync2 } from "fs";
19661
- import { isAbsolute as isAbsolute2, relative } from "path";
19805
+ import { isAbsolute as isAbsolute3, relative } from "path";
19662
19806
  var AgentSuggestError = class extends Error {
19663
19807
  reason;
19664
19808
  constructor(reason, message) {
@@ -19727,7 +19871,7 @@ function parseAgentStream(out, dir) {
19727
19871
  if (ev?.type === "assistant" && Array.isArray(ev.message?.content)) {
19728
19872
  for (const b of ev.message.content) {
19729
19873
  const p = b?.type === "tool_use" && b.name === "Read" ? b.input?.file_path : void 0;
19730
- if (typeof p === "string") files.add(isAbsolute2(p) ? relative(base, p) : p);
19874
+ if (typeof p === "string") files.add(isAbsolute3(p) ? relative(base, p) : p);
19731
19875
  }
19732
19876
  } else if (ev?.type === "result") {
19733
19877
  text2 = typeof ev.result === "string" ? ev.result : "";
@@ -19759,7 +19903,7 @@ function classifyFailure(o) {
19759
19903
  }
19760
19904
  function porcelain(dir) {
19761
19905
  try {
19762
- return execFileSync9("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
19906
+ return execFileSync10("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
19763
19907
  } catch {
19764
19908
  return void 0;
19765
19909
  }
@@ -19774,7 +19918,7 @@ async function runAgentSuggest(o) {
19774
19918
  const before = porcelain(o.dir);
19775
19919
  const timeout = AbortSignal.timeout(o.timeoutMs ?? DEFAULT_TIMEOUT_MS);
19776
19920
  const signal = o.signal ? AbortSignal.any([o.signal, timeout]) : timeout;
19777
- const run3 = await new Promise((resolve7) => {
19921
+ const run3 = await new Promise((resolve8) => {
19778
19922
  let out = "";
19779
19923
  let err3 = "";
19780
19924
  const child = spawn7(bin, args, { cwd: o.dir, env, detached: true, stdio: ["pipe", "pipe", "pipe"] });
@@ -19783,7 +19927,7 @@ async function runAgentSuggest(o) {
19783
19927
  if (settled) return;
19784
19928
  settled = true;
19785
19929
  signal.removeEventListener("abort", onAbort);
19786
- resolve7(r);
19930
+ resolve8(r);
19787
19931
  };
19788
19932
  const onAbort = () => {
19789
19933
  try {
@@ -19828,7 +19972,7 @@ ${run3.err}` });
19828
19972
  }
19829
19973
 
19830
19974
  // src/sample-ticket.ts
19831
- import { basename as basename12, dirname as dirname22, join as join38 } from "path";
19975
+ import { basename as basename12, dirname as dirname23, join as join39 } from "path";
19832
19976
  var SAMPLE_REPO = "crew-sample";
19833
19977
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
19834
19978
  var SAMPLE_MARKER = "sample: true";
@@ -19851,8 +19995,8 @@ has something safe to work on: its test and build hooks always pass, it has no
19851
19995
  remote, and \`crew sample-ticket --remove\` deletes it.
19852
19996
  `;
19853
19997
  function sampleDir(existing, reposBase, workspace) {
19854
- if (existing.length > 0) return join38(dirname22(existing[0]), SAMPLE_REPO);
19855
- return join38(reposBase, workspace, SAMPLE_REPO);
19998
+ if (existing.length > 0) return join39(dirname23(existing[0]), SAMPLE_REPO);
19999
+ return join39(reposBase, workspace, SAMPLE_REPO);
19856
20000
  }
19857
20001
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
19858
20002
  function sampleDescription() {
@@ -19896,8 +20040,8 @@ function planSampleTicket(i) {
19896
20040
  }
19897
20041
 
19898
20042
  // src/worktree-sweep.ts
19899
- import { existsSync as existsSync37, readFileSync as readFileSync34, readdirSync as readdirSync14 } from "fs";
19900
- import { basename as basename13, dirname as dirname23, join as join39, resolve as resolve4 } from "path";
20043
+ import { existsSync as existsSync38, readFileSync as readFileSync35, readdirSync as readdirSync14 } from "fs";
20044
+ import { basename as basename13, dirname as dirname24, join as join40, resolve as resolve5 } from "path";
19901
20045
  function planWorktreeSweep(target, tickets, contract) {
19902
20046
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
19903
20047
  const verified = contract.statuses.verified;
@@ -19961,9 +20105,9 @@ function worktreeTicketNumbers(target) {
19961
20105
  return [...nums];
19962
20106
  }
19963
20107
  function siblingWorktreeDirs(target) {
19964
- const parent = dirname23(target.dir.replace(/[/\\]+$/, ""));
20108
+ const parent = dirname24(target.dir.replace(/[/\\]+$/, ""));
19965
20109
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
19966
- const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
20110
+ const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
19967
20111
  let names;
19968
20112
  try {
19969
20113
  names = readdirSync14(parent);
@@ -19974,8 +20118,8 @@ function siblingWorktreeDirs(target) {
19974
20118
  for (const name of names) {
19975
20119
  if (!name.startsWith(stem)) continue;
19976
20120
  const m = DIR_NUMBER_SUFFIXED.exec(name);
19977
- const path = join39(parent, name);
19978
- if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
20121
+ const path = join40(parent, name);
20122
+ if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
19979
20123
  }
19980
20124
  return out;
19981
20125
  }
@@ -20009,7 +20153,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20009
20153
  let removed = 0;
20010
20154
  let keptBranches = 0;
20011
20155
  for (const a of actions) {
20012
- if (!dryRun && base && !a.keepBranch && existsSync37(a.path) && isHandBackMerge(a.path, base)) {
20156
+ if (!dryRun && base && !a.keepBranch && existsSync38(a.path) && isHandBackMerge(a.path, base)) {
20013
20157
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
20014
20158
  log.emit(
20015
20159
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -20017,7 +20161,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20017
20161
  );
20018
20162
  }
20019
20163
  }
20020
- if (!dryRun && existsSync37(a.path)) {
20164
+ if (!dryRun && existsSync38(a.path)) {
20021
20165
  let dirty = false;
20022
20166
  try {
20023
20167
  dirty = status(a.path).length > 0;
@@ -20028,7 +20172,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20028
20172
  continue;
20029
20173
  }
20030
20174
  }
20031
- if (!dryRun && a.requirePushed && a.branch && existsSync37(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20175
+ if (!dryRun && a.requirePushed && a.branch && existsSync38(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20032
20176
  refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
20033
20177
  continue;
20034
20178
  }
@@ -20077,7 +20221,7 @@ function isHandBackMerge(path, base) {
20077
20221
  if (!msgPath) return false;
20078
20222
  let first;
20079
20223
  try {
20080
- first = readFileSync34(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
20224
+ first = readFileSync35(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20081
20225
  } catch {
20082
20226
  return false;
20083
20227
  }
@@ -20182,16 +20326,16 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
20182
20326
  }
20183
20327
 
20184
20328
  // src/stream-sweep.ts
20185
- import { existsSync as existsSync38, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync11 } from "fs";
20186
- import { join as join40 } from "path";
20329
+ import { existsSync as existsSync39, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync12 } from "fs";
20330
+ import { join as join41 } from "path";
20187
20331
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
20188
20332
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
20189
- const dir = join40(stateDir, "streams");
20190
- if (!existsSync38(dir)) return [];
20333
+ const dir = join41(stateDir, "streams");
20334
+ if (!existsSync39(dir)) return [];
20191
20335
  const cutoff = now - retentionDays * DAY_MS2;
20192
- return readdirSync15(dir).map((name) => join40(dir, name)).filter((path) => {
20336
+ return readdirSync15(dir).map((name) => join41(dir, name)).filter((path) => {
20193
20337
  try {
20194
- return statSync11(path).mtimeMs < cutoff;
20338
+ return statSync12(path).mtimeMs < cutoff;
20195
20339
  } catch {
20196
20340
  return false;
20197
20341
  }
@@ -20215,11 +20359,11 @@ function applyStreamSweep(paths, dryRun, log) {
20215
20359
  return removed;
20216
20360
  }
20217
20361
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20218
- if (!existsSync38(stateDir)) return [];
20362
+ if (!existsSync39(stateDir)) return [];
20219
20363
  const cutoff = now - retentionDays * DAY_MS2;
20220
- return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join40(stateDir, name)).filter((path) => {
20364
+ return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join41(stateDir, name)).filter((path) => {
20221
20365
  try {
20222
- return statSync11(path).mtimeMs < cutoff;
20366
+ return statSync12(path).mtimeMs < cutoff;
20223
20367
  } catch {
20224
20368
  return false;
20225
20369
  }
@@ -20227,12 +20371,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20227
20371
  }
20228
20372
 
20229
20373
  // src/cli.ts
20230
- import { realpathSync as realpathSync3, readFileSync as readFileSync40, existsSync as existsSync45, mkdirSync as mkdirSync32, writeFileSync as writeFileSync33, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync15, readdirSync as readdirSync20 } from "fs";
20374
+ import { realpathSync as realpathSync3, readFileSync as readFileSync41, existsSync as existsSync46, mkdirSync as mkdirSync33, writeFileSync as writeFileSync34, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
20231
20375
  import { createInterface } from "readline/promises";
20232
20376
 
20233
20377
  // src/periscope-cmd.ts
20234
- import { existsSync as existsSync39, mkdirSync as mkdirSync26, readFileSync as readFileSync35, rmSync as rmSync19, writeFileSync as writeFileSync29 } from "fs";
20235
- import { join as join41 } from "path";
20378
+ import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, rmSync as rmSync19, writeFileSync as writeFileSync30 } from "fs";
20379
+ import { join as join42 } from "path";
20236
20380
  var PeriscopeError = class extends Error {
20237
20381
  };
20238
20382
  var LivePeriscopeError = class extends PeriscopeError {
@@ -20242,22 +20386,22 @@ var LivePeriscopeError = class extends PeriscopeError {
20242
20386
  this.data = { refused: "live_periscope_exists", ...live };
20243
20387
  }
20244
20388
  };
20245
- var priorDir = (stateDir) => join41(stateDir, "periscope-priors");
20246
- var priorPath = (stateDir, ticketRecordId) => join41(priorDir(stateDir), `${ticketRecordId}.json`);
20389
+ var priorDir = (stateDir) => join42(stateDir, "periscope-priors");
20390
+ var priorPath = (stateDir, ticketRecordId) => join42(priorDir(stateDir), `${ticketRecordId}.json`);
20247
20391
  function removePeriscopePrior(stateDir, ticketRecordId) {
20248
20392
  rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
20249
20393
  }
20250
20394
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
20251
20395
  try {
20252
- mkdirSync26(priorDir(stateDir), { recursive: true });
20253
- writeFileSync29(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20396
+ mkdirSync27(priorDir(stateDir), { recursive: true });
20397
+ writeFileSync30(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20254
20398
  } catch {
20255
20399
  }
20256
20400
  }
20257
20401
  function readPeriscopePrior(stateDir, ticketRecordId) {
20258
20402
  try {
20259
20403
  const p = priorPath(stateDir, ticketRecordId);
20260
- return existsSync39(p) ? JSON.parse(readFileSync35(p, "utf8")) : void 0;
20404
+ return existsSync40(p) ? JSON.parse(readFileSync36(p, "utf8")) : void 0;
20261
20405
  } catch {
20262
20406
  return void 0;
20263
20407
  }
@@ -20419,7 +20563,12 @@ ${tail(iso.output)}`);
20419
20563
  const handoff = await d.runHook("handoff", wt.path, env);
20420
20564
  if (handoff && handoff.code !== 0) return abandon(`handoff failed (exit ${handoff.code})
20421
20565
  ${tail(handoff.output)}`);
20422
- const preview = await d.runHook("preview", wt.path, { ...env, CREW_PREVIEW_HOST: d.host });
20566
+ try {
20567
+ d.prepareDropFile(wt.path);
20568
+ } catch (e) {
20569
+ return abandon(`preparing the drop file failed: ${e.message}`);
20570
+ }
20571
+ const preview = await d.runHook("preview", wt.path, { ...env, CREW_PREVIEW_HOST: d.host, ...pointedAtEnv(wt.path) });
20423
20572
  if (!preview || preview.code !== 0) {
20424
20573
  return abandon(`preview exited ${preview?.code ?? "without running"}
20425
20574
  ${tail(preview?.output ?? "")}`);
@@ -20634,7 +20783,8 @@ async function runPeriscopeResume(d, number) {
20634
20783
  ${tail2(iso.output)}`);
20635
20784
  if (iso) Object.assign(env, parseEnvLines(iso.output));
20636
20785
  }
20637
- const preview = await d.runHook("preview", row.worktree, { ...env, CREW_PREVIEW_HOST: row.host || "localhost" });
20786
+ d.prepareDropFile?.(row.worktree);
20787
+ const preview = await d.runHook("preview", row.worktree, { ...env, CREW_PREVIEW_HOST: row.host || "localhost", ...pointedAtEnv(row.worktree) });
20638
20788
  if (!preview || preview.code !== 0) {
20639
20789
  throw new PeriscopeError(`preview exited ${preview?.code ?? "without running"}
20640
20790
  ${tail2(preview?.output ?? "")}`);
@@ -20654,14 +20804,19 @@ ${tail2(preview.output)}`);
20654
20804
  }
20655
20805
 
20656
20806
  // src/periscope-idle.ts
20657
- import { execFileSync as execFileSync10 } from "child_process";
20658
- import { statSync as statSync12 } from "fs";
20659
- import { join as join42 } from "path";
20807
+ import { execFileSync as execFileSync11 } from "child_process";
20808
+ import { statSync as statSync13 } from "fs";
20809
+ import { join as join43 } from "path";
20660
20810
  var MAX_SCANNED_FILES = 2e4;
20661
20811
  function newestWorktreeMtime(root) {
20812
+ const a = newestTrackedMtime(root);
20813
+ const b = dropFileMtime(root);
20814
+ return a === null ? b : b === null ? a : Math.max(a, b);
20815
+ }
20816
+ function newestTrackedMtime(root) {
20662
20817
  let listing;
20663
20818
  try {
20664
- listing = execFileSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
20819
+ listing = execFileSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
20665
20820
  cwd: root,
20666
20821
  encoding: "utf8",
20667
20822
  stdio: ["ignore", "pipe", "ignore"],
@@ -20676,7 +20831,7 @@ function newestWorktreeMtime(root) {
20676
20831
  if (!rel) continue;
20677
20832
  if (++scanned > MAX_SCANNED_FILES) break;
20678
20833
  try {
20679
- const m = statSync12(join42(root, rel)).mtimeMs;
20834
+ const m = statSync13(join43(root, rel)).mtimeMs;
20680
20835
  if (newest === null || m > newest) newest = m;
20681
20836
  } catch {
20682
20837
  }
@@ -20751,21 +20906,21 @@ async function runPeriscopeIdlePass(d) {
20751
20906
  import { dirname as dirOf2, resolve as resolvePath } from "path";
20752
20907
 
20753
20908
  // src/ssh-keys.ts
20754
- import { execFileSync as execFileSync11 } from "child_process";
20755
- import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, chmodSync as chmodSync3 } from "fs";
20756
- import { dirname as dirname24, join as join43 } from "path";
20909
+ import { execFileSync as execFileSync12 } from "child_process";
20910
+ import { existsSync as existsSync41, mkdirSync as mkdirSync28, readFileSync as readFileSync37, chmodSync as chmodSync3 } from "fs";
20911
+ import { dirname as dirname25, join as join44 } from "path";
20757
20912
  var SshKeyError = class extends Error {
20758
20913
  };
20759
20914
  function shipSshKeyPathFor(stateDir) {
20760
- return join43(stateDir, "ssh", "id_ed25519");
20915
+ return join44(stateDir, "ssh", "id_ed25519");
20761
20916
  }
20762
20917
  function ensureShipSshKeypair(stateDir) {
20763
20918
  const privateKeyPath = shipSshKeyPathFor(stateDir);
20764
20919
  const publicKeyPath = `${privateKeyPath}.pub`;
20765
- if (!existsSync40(privateKeyPath)) {
20766
- mkdirSync27(dirname24(privateKeyPath), { recursive: true });
20920
+ if (!existsSync41(privateKeyPath)) {
20921
+ mkdirSync28(dirname25(privateKeyPath), { recursive: true });
20767
20922
  try {
20768
- execFileSync11("ssh-keygen", [
20923
+ execFileSync12("ssh-keygen", [
20769
20924
  "-t",
20770
20925
  "ed25519",
20771
20926
  "-N",
@@ -20783,15 +20938,15 @@ function ensureShipSshKeypair(stateDir) {
20783
20938
  }
20784
20939
  chmodSync3(privateKeyPath, 384);
20785
20940
  }
20786
- if (!existsSync40(publicKeyPath)) {
20941
+ if (!existsSync41(publicKeyPath)) {
20787
20942
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
20788
20943
  }
20789
- const publicKey = readFileSync36(publicKeyPath, "utf8").trim();
20944
+ const publicKey = readFileSync37(publicKeyPath, "utf8").trim();
20790
20945
  return { privateKeyPath, publicKeyPath, publicKey };
20791
20946
  }
20792
20947
  function sshKeygenAvailable() {
20793
20948
  try {
20794
- execFileSync11("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
20949
+ execFileSync12("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
20795
20950
  return true;
20796
20951
  } catch (e) {
20797
20952
  return e.code !== "ENOENT";
@@ -20950,12 +21105,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
20950
21105
  import { createServer as createHttpServer } from "http";
20951
21106
  import { spawn as spawn8 } from "child_process";
20952
21107
  import { randomBytes } from "crypto";
20953
- import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, writeFileSync as writeFileSync30, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
20954
- import { join as join44 } from "path";
21108
+ import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync31, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21109
+ import { join as join45 } from "path";
20955
21110
 
20956
21111
  // src/passenger-mirror.ts
20957
- import { mkdirSync as mkdirSync28 } from "fs";
20958
- import { dirname as dirname25 } from "path";
21112
+ import { mkdirSync as mkdirSync29 } from "fs";
21113
+ import { dirname as dirname26 } from "path";
20959
21114
  function mirrorRepoOf(spec) {
20960
21115
  const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
20961
21116
  if (fromOrigin) return fromOrigin;
@@ -20973,9 +21128,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
20973
21128
  continue;
20974
21129
  }
20975
21130
  try {
20976
- const parent = dirname25(spec.dir);
20977
- mkdirSync28(parent, { recursive: true });
20978
- mkdirSync28(spec.dir, { recursive: true });
21131
+ const parent = dirname26(spec.dir);
21132
+ mkdirSync29(parent, { recursive: true });
21133
+ mkdirSync29(spec.dir, { recursive: true });
20979
21134
  const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
20980
21135
  out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
20981
21136
  } catch (e) {
@@ -21081,21 +21236,21 @@ var defaultSpawn2 = (cmd, args, env) => {
21081
21236
  return child;
21082
21237
  };
21083
21238
  function syncDaemonStatePath(stateDir, workspaceId) {
21084
- return join44(stateDir, "passengers", `${workspaceId}-sync.json`);
21239
+ return join45(stateDir, "passengers", `${workspaceId}-sync.json`);
21085
21240
  }
21086
21241
  function readPersistedSyncDaemon(stateDir, workspaceId) {
21087
21242
  const path = syncDaemonStatePath(stateDir, workspaceId);
21088
- if (!existsSync41(path)) return void 0;
21243
+ if (!existsSync42(path)) return void 0;
21089
21244
  try {
21090
- return JSON.parse(readFileSync37(path, "utf8"));
21245
+ return JSON.parse(readFileSync38(path, "utf8"));
21091
21246
  } catch {
21092
21247
  return void 0;
21093
21248
  }
21094
21249
  }
21095
21250
  function writePersistedSyncDaemon(stateDir, state) {
21096
21251
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
21097
- mkdirSync29(join44(stateDir, "passengers"), { recursive: true });
21098
- writeFileSync30(path, `${JSON.stringify(state, null, 2)}
21252
+ mkdirSync30(join45(stateDir, "passengers"), { recursive: true });
21253
+ writeFileSync31(path, `${JSON.stringify(state, null, 2)}
21099
21254
  `);
21100
21255
  }
21101
21256
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -21115,8 +21270,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
21115
21270
  }
21116
21271
  }
21117
21272
  function readAllPersistedSyncWorkspaceIds(stateDir) {
21118
- const dir = join44(stateDir, "passengers");
21119
- if (!existsSync41(dir)) return [];
21273
+ const dir = join45(stateDir, "passengers");
21274
+ if (!existsSync42(dir)) return [];
21120
21275
  const suffix = "-sync.json";
21121
21276
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
21122
21277
  }
@@ -21204,14 +21359,14 @@ function describeCause(err3) {
21204
21359
  }
21205
21360
 
21206
21361
  // src/daemon.ts
21207
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync13, writeFileSync as writeFileSync31 } from "fs";
21208
- import { join as join45 } from "path";
21362
+ import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync14, writeFileSync as writeFileSync32 } from "fs";
21363
+ import { join as join46 } from "path";
21209
21364
  import { randomBytes as randomBytes2 } from "crypto";
21210
21365
  var DEFAULT_FLOOR_MS = 5e3;
21211
21366
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
21212
21367
  var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
21213
21368
  var RELOAD_WARN_MS = 10 * 60 * 1e3;
21214
- var defaultSleep = (ms3) => new Promise((resolve7) => setTimeout(resolve7, ms3));
21369
+ var defaultSleep = (ms3) => new Promise((resolve8) => setTimeout(resolve8, ms3));
21215
21370
  async function runDaemonLoop(o) {
21216
21371
  const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
21217
21372
  const ceiling = o.ceilingMs ?? DEFAULT_CEILING_MS;
@@ -21285,7 +21440,7 @@ async function runDaemonLoop(o) {
21285
21440
  function latestMtime(path) {
21286
21441
  let stat2;
21287
21442
  try {
21288
- stat2 = statSync13(path);
21443
+ stat2 = statSync14(path);
21289
21444
  } catch {
21290
21445
  return 0;
21291
21446
  }
@@ -21293,7 +21448,7 @@ function latestMtime(path) {
21293
21448
  if (!stat2.isDirectory()) return 0;
21294
21449
  let latest = stat2.mtimeMs;
21295
21450
  for (const entry of readdirSync17(path, { withFileTypes: true })) {
21296
- latest = Math.max(latest, latestMtime(join45(path, entry.name)));
21451
+ latest = Math.max(latest, latestMtime(join46(path, entry.name)));
21297
21452
  }
21298
21453
  return latest;
21299
21454
  }
@@ -21302,30 +21457,30 @@ function computeTreeSignature(paths) {
21302
21457
  }
21303
21458
  function makeStaleChecker(paths, opts = {}) {
21304
21459
  const initial = computeTreeSignature(paths);
21305
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync42(opts.reloadSentinel);
21460
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync43(opts.reloadSentinel);
21306
21461
  }
21307
21462
  function reloadSentinelFor(stateDir) {
21308
- return join45(stateDir, "reload-requested");
21463
+ return join46(stateDir, "reload-requested");
21309
21464
  }
21310
21465
  function readReloadRequestedAt(stateDir) {
21311
21466
  try {
21312
- return statSync13(reloadSentinelFor(stateDir)).mtimeMs;
21467
+ return statSync14(reloadSentinelFor(stateDir)).mtimeMs;
21313
21468
  } catch {
21314
21469
  return null;
21315
21470
  }
21316
21471
  }
21317
21472
  function daemonStampFor(stateDir) {
21318
- return join45(stateDir, "daemon.started");
21473
+ return join46(stateDir, "daemon.started");
21319
21474
  }
21320
21475
  function noteDaemonStarted(stateDir, now = Date.now()) {
21321
- mkdirSync30(stateDir, { recursive: true });
21476
+ mkdirSync31(stateDir, { recursive: true });
21322
21477
  rmSync20(reloadSentinelFor(stateDir), { force: true });
21323
- writeFileSync31(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
21478
+ writeFileSync32(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
21324
21479
  `);
21325
21480
  }
21326
21481
  function readDaemonStarted(stateDir) {
21327
21482
  try {
21328
- const v = JSON.parse(readFileSync38(daemonStampFor(stateDir), "utf8")).startedAt;
21483
+ const v = JSON.parse(readFileSync39(daemonStampFor(stateDir), "utf8")).startedAt;
21329
21484
  return typeof v === "number" ? v : null;
21330
21485
  } catch {
21331
21486
  return null;
@@ -21334,7 +21489,7 @@ function readDaemonStarted(stateDir) {
21334
21489
  function configChangedAfter(files, startedAt) {
21335
21490
  for (const f of files) {
21336
21491
  try {
21337
- if (statSync13(f).mtimeMs > startedAt) return f;
21492
+ if (statSync14(f).mtimeMs > startedAt) return f;
21338
21493
  } catch {
21339
21494
  }
21340
21495
  }
@@ -21497,8 +21652,8 @@ async function runOnePassUnguarded(o) {
21497
21652
  }
21498
21653
  let settle = () => {
21499
21654
  };
21500
- const settled = new Promise((resolve7) => {
21501
- settle = resolve7;
21655
+ const settled = new Promise((resolve8) => {
21656
+ settle = resolve8;
21502
21657
  });
21503
21658
  const ready = (tookTicket2) => settle(tookTicket2);
21504
21659
  const run3 = spawn11(role, decision, ctx, { ready }).then(() => {
@@ -21758,7 +21913,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
21758
21913
  ship.name,
21759
21914
  (t) => {
21760
21915
  const d = dirForRepo(route2, t.repo_id);
21761
- return d ? originBranchSha(d, t, resolve2) : null;
21916
+ return d ? originBranchSha(d, t, resolve3) : null;
21762
21917
  }
21763
21918
  );
21764
21919
  if (!result.ticket) {
@@ -21883,7 +22038,7 @@ async function buildEnvironment(route2, ship, ticket) {
21883
22038
  });
21884
22039
  const authorship = await tracker.authorshipProbe().catch(() => null);
21885
22040
  const attachments = await tracker.attachmentsProbe().catch(() => null);
21886
- return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
22041
+ return renderEnvironment({ route: route2, userAgent: ship.userAgent, repos: await withRepoMaps(repos, ship.mapTokenBudget, ship.shell), contract, sourceTicket: ticket, authorship, attachments, shipName: ship.name, ...sandboxEnv(ship) });
21887
22042
  }
21888
22043
 
21889
22044
  // src/control-api.ts
@@ -21891,18 +22046,18 @@ import { createHash as createHash10 } from "crypto";
21891
22046
  import {
21892
22047
  chmodSync as chmodSync4,
21893
22048
  closeSync as closeSync5,
21894
- existsSync as existsSync43,
22049
+ existsSync as existsSync44,
21895
22050
  fstatSync,
21896
22051
  openSync as openSync5,
21897
22052
  readSync as readSync3,
21898
22053
  readdirSync as readdirSync18,
21899
22054
  rmSync as rmSync21,
21900
- statSync as statSync14,
22055
+ statSync as statSync15,
21901
22056
  watch
21902
22057
  } from "fs";
21903
22058
  import { createConnection } from "net";
21904
22059
  import { createServer } from "http";
21905
- import { basename as basename14, dirname as dirname26, join as join46 } from "path";
22060
+ import { basename as basename14, dirname as dirname27, join as join47 } from "path";
21906
22061
  import { execFile as execFile3, spawn as spawn10 } from "child_process";
21907
22062
 
21908
22063
  // src/periscope-control.ts
@@ -21953,7 +22108,7 @@ function lastJsonLine(stdout) {
21953
22108
  }
21954
22109
  return null;
21955
22110
  }
21956
- function parseLine(line) {
22111
+ function parseLine2(line) {
21957
22112
  try {
21958
22113
  const v = JSON.parse(line);
21959
22114
  return v && typeof v === "object" && !Array.isArray(v) ? v : null;
@@ -21981,7 +22136,7 @@ async function execute(h, args, streaming, after) {
21981
22136
  body: null,
21982
22137
  stream: async (write) => {
21983
22138
  const r = await h.run(args, (line) => {
21984
- const ev = parseLine(line);
22139
+ const ev = parseLine2(line);
21985
22140
  if (ev) write(ev);
21986
22141
  });
21987
22142
  const f = await finish(r);
@@ -22096,7 +22251,7 @@ async function handlePeriscopes(h, q2) {
22096
22251
  ], q2.wantsStream, h.mcp ? async (done) => ({ ...done, ...await h.mcp.onUp(picked, done) }) : void 0);
22097
22252
  }
22098
22253
  function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
22099
- return new Promise((resolve7) => {
22254
+ return new Promise((resolve8) => {
22100
22255
  const child = spawn9(process.execPath, [process.argv[1] ?? "", ...args], { stdio: ["ignore", "pipe", "pipe"] });
22101
22256
  let stdout = "";
22102
22257
  let stderr = "";
@@ -22118,12 +22273,12 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
22118
22273
  const t = setTimeout(() => child.kill("SIGTERM"), timeoutMs);
22119
22274
  child.on("error", (e) => {
22120
22275
  clearTimeout(t);
22121
- resolve7({ code: 127, stdout, stderr: `${stderr}${e.message}
22276
+ resolve8({ code: 127, stdout, stderr: `${stderr}${e.message}
22122
22277
  ` });
22123
22278
  });
22124
22279
  child.on("close", (code) => {
22125
22280
  clearTimeout(t);
22126
- resolve7({ code: code ?? 1, stdout, stderr });
22281
+ resolve8({ code: code ?? 1, stdout, stderr });
22127
22282
  });
22128
22283
  });
22129
22284
  }
@@ -22131,7 +22286,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
22131
22286
  // src/control-api.ts
22132
22287
  var PAUSABLE = ["dev", "design", "qa"];
22133
22288
  var CONTROL_SOCKET_NAME = "control.sock";
22134
- var controlSocketFor = (stateDir) => join46(stateDir, CONTROL_SOCKET_NAME);
22289
+ var controlSocketFor = (stateDir) => join47(stateDir, CONTROL_SOCKET_NAME);
22135
22290
  var MAX_SOCKET_PATH = 100;
22136
22291
  var MAX_TAIL_BYTES = 1024 * 1024;
22137
22292
  var MAX_WAIT_SECONDS = 30;
@@ -22218,7 +22373,7 @@ function candidates(live, rotated) {
22218
22373
  const out = [live];
22219
22374
  if (!rotated) return out;
22220
22375
  try {
22221
- for (const n of readdirSync18(dirname26(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join46(dirname26(live), n));
22376
+ for (const n of readdirSync18(dirname27(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join47(dirname27(live), n));
22222
22377
  } catch {
22223
22378
  }
22224
22379
  return out;
@@ -22241,7 +22396,7 @@ function readRange(path, from, max) {
22241
22396
  }
22242
22397
  }
22243
22398
  function lastLines2(path, n) {
22244
- const st = statSync14(path);
22399
+ const st = statSync15(path);
22245
22400
  const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
22246
22401
  if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
22247
22402
  let start = st.size;
@@ -22262,7 +22417,7 @@ function lastLines2(path, n) {
22262
22417
  }
22263
22418
  function readTail(live, opts) {
22264
22419
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
22265
- if (!existsSync43(live)) {
22420
+ if (!existsSync44(live)) {
22266
22421
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
22267
22422
  }
22268
22423
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -22275,7 +22430,7 @@ function readTail(live, opts) {
22275
22430
  } else {
22276
22431
  const found = candidates(live, opts.rotated ?? false).find((p) => {
22277
22432
  try {
22278
- const s = statSync14(p);
22433
+ const s = statSync15(p);
22279
22434
  return s.ino === cur.ino && s.dev === cur.dev;
22280
22435
  } catch {
22281
22436
  return false;
@@ -22286,7 +22441,7 @@ function readTail(live, opts) {
22286
22441
  off = cur.off;
22287
22442
  } else reset = true;
22288
22443
  }
22289
- let size = statSync14(path).size;
22444
+ let size = statSync15(path).size;
22290
22445
  if (off > size) {
22291
22446
  off = 0;
22292
22447
  reset = true;
@@ -22294,12 +22449,12 @@ function readTail(live, opts) {
22294
22449
  if (off >= size && path !== live) {
22295
22450
  path = live;
22296
22451
  off = 0;
22297
- size = statSync14(path).size;
22452
+ size = statSync15(path).size;
22298
22453
  }
22299
22454
  const { text: text2 } = readRange(path, off, max);
22300
22455
  const cut = text2.lastIndexOf("\n");
22301
22456
  const whole = cut < 0 ? "" : text2.slice(0, cut + 1);
22302
- const st = statSync14(path);
22457
+ const st = statSync15(path);
22303
22458
  return {
22304
22459
  lines: whole.split("\n").filter((l) => l.length),
22305
22460
  cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
@@ -22307,7 +22462,7 @@ function readTail(live, opts) {
22307
22462
  };
22308
22463
  }
22309
22464
  function waitForChange(dir, ms3, signal) {
22310
- return new Promise((resolve7) => {
22465
+ return new Promise((resolve8) => {
22311
22466
  let w;
22312
22467
  const done = () => {
22313
22468
  clearTimeout(t);
@@ -22317,7 +22472,7 @@ function waitForChange(dir, ms3, signal) {
22317
22472
  w?.close();
22318
22473
  } catch {
22319
22474
  }
22320
- resolve7();
22475
+ resolve8();
22321
22476
  };
22322
22477
  const t = setTimeout(done, ms3);
22323
22478
  const poll = setInterval(done, 500);
@@ -22378,7 +22533,7 @@ async function tailReply(path, q2, rotated, abort) {
22378
22533
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
22379
22534
  let r = readTail(path, { after, tail: tail3, rotated });
22380
22535
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
22381
- await waitForChange(dirname26(path), deadline - Date.now(), abort);
22536
+ await waitForChange(dirname27(path), deadline - Date.now(), abort);
22382
22537
  r = readTail(path, { after: r.cursor, rotated });
22383
22538
  }
22384
22539
  return reply2(200, r);
@@ -22418,8 +22573,8 @@ async function route(deps, method, url, req, abort) {
22418
22573
  if (p.startsWith("/streams/") && get2) {
22419
22574
  const run3 = decodeURIComponent(p.slice("/streams/".length));
22420
22575
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
22421
- const file = join46(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
22422
- if (!existsSync43(file)) return err2(404, "no such run");
22576
+ const file = join47(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
22577
+ if (!existsSync44(file)) return err2(404, "no such run");
22423
22578
  return tailReply(file, url.searchParams, false, abort);
22424
22579
  }
22425
22580
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -22463,14 +22618,14 @@ function send(res, r) {
22463
22618
  res.end(body);
22464
22619
  }
22465
22620
  async function socketIsStale(path) {
22466
- if (!existsSync43(path)) return false;
22467
- return new Promise((resolve7) => {
22621
+ if (!existsSync44(path)) return false;
22622
+ return new Promise((resolve8) => {
22468
22623
  const c = createConnection(path);
22469
22624
  c.once("connect", () => {
22470
22625
  c.destroy();
22471
- resolve7(false);
22626
+ resolve8(false);
22472
22627
  });
22473
- c.once("error", () => resolve7(true));
22628
+ c.once("error", () => resolve8(true));
22474
22629
  });
22475
22630
  }
22476
22631
  async function startControlServer(deps) {
@@ -22482,7 +22637,7 @@ async function startControlServer(deps) {
22482
22637
  return null;
22483
22638
  }
22484
22639
  if (await socketIsStale(path)) rmSync21(path, { force: true });
22485
- else if (existsSync43(path)) {
22640
+ else if (existsSync44(path)) {
22486
22641
  log(`control API not started: ${path} is in use by another process`);
22487
22642
  return null;
22488
22643
  }
@@ -22512,10 +22667,10 @@ async function startControlServer(deps) {
22512
22667
  }
22513
22668
  })();
22514
22669
  });
22515
- return new Promise((resolve7) => {
22670
+ return new Promise((resolve8) => {
22516
22671
  server.once("error", (e) => {
22517
22672
  log(`control API not started: ${e.message}`);
22518
- resolve7(null);
22673
+ resolve8(null);
22519
22674
  });
22520
22675
  server.listen(path, () => {
22521
22676
  try {
@@ -22523,7 +22678,7 @@ async function startControlServer(deps) {
22523
22678
  } catch {
22524
22679
  }
22525
22680
  server.on("error", (e) => log(`control API error: ${e.message}`));
22526
- resolve7({
22681
+ resolve8({
22527
22682
  server,
22528
22683
  path,
22529
22684
  close: () => new Promise((done) => {
@@ -22554,13 +22709,13 @@ function schedulerHandler(control, run3 = runDetached) {
22554
22709
  };
22555
22710
  }
22556
22711
  function crewJson(args, timeoutMs = 6e4) {
22557
- return new Promise((resolve7) => {
22712
+ return new Promise((resolve8) => {
22558
22713
  execFile3(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
22559
22714
  try {
22560
22715
  const v = JSON.parse(String(stdout));
22561
- resolve7(v && typeof v === "object" && !Array.isArray(v) ? v : null);
22716
+ resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
22562
22717
  } catch {
22563
- resolve7(null);
22718
+ resolve8(null);
22564
22719
  }
22565
22720
  });
22566
22721
  });
@@ -22568,31 +22723,31 @@ function crewJson(args, timeoutMs = 6e4) {
22568
22723
 
22569
22724
  // src/periscope-mcp.ts
22570
22725
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
22571
- import { basename as basename15, dirname as dirname27, isAbsolute as isAbsolute3, join as join48, relative as relative2, resolve as resolve5, sep } from "path";
22726
+ import { basename as basename15, dirname as dirname28, isAbsolute as isAbsolute4, join as join49, relative as relative2, resolve as resolve6, sep } from "path";
22572
22727
  import { z } from "zod";
22573
22728
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22574
22729
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
22575
22730
 
22576
22731
  // src/periscope-endpoints.ts
22577
22732
  import { randomBytes as randomBytes3 } from "crypto";
22578
- import { chmodSync as chmodSync5, existsSync as existsSync44, mkdirSync as mkdirSync31, readdirSync as readdirSync19, readFileSync as readFileSync39, rmSync as rmSync22, writeFileSync as writeFileSync32 } from "fs";
22579
- import { join as join47 } from "path";
22580
- var secretDir = (stateDir) => join47(stateDir, "periscope-mcp");
22581
- var secretFile = (stateDir, id) => join47(secretDir(stateDir), `${id}.json`);
22582
- var endpointDir = (stateDir) => join47(stateDir, "periscope-endpoints");
22583
- var endpointFile = (stateDir, id) => join47(endpointDir(stateDir), `${id}.json`);
22733
+ import { chmodSync as chmodSync5, existsSync as existsSync45, mkdirSync as mkdirSync32, readdirSync as readdirSync19, readFileSync as readFileSync40, rmSync as rmSync22, writeFileSync as writeFileSync33 } from "fs";
22734
+ import { join as join48 } from "path";
22735
+ var secretDir = (stateDir) => join48(stateDir, "periscope-mcp");
22736
+ var secretFile = (stateDir, id) => join48(secretDir(stateDir), `${id}.json`);
22737
+ var endpointDir = (stateDir) => join48(stateDir, "periscope-endpoints");
22738
+ var endpointFile = (stateDir, id) => join48(endpointDir(stateDir), `${id}.json`);
22584
22739
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
22585
- mkdirSync31(secretDir(stateDir), { recursive: true, mode: 448 });
22740
+ mkdirSync32(secretDir(stateDir), { recursive: true, mode: 448 });
22586
22741
  const secret = randomBytes3(32).toString("base64url");
22587
22742
  const file = secretFile(stateDir, ticketRecordId);
22588
- writeFileSync32(file, `${JSON.stringify({ secret })}
22743
+ writeFileSync33(file, `${JSON.stringify({ secret })}
22589
22744
  `, { mode: 384 });
22590
22745
  chmodSync5(file, 384);
22591
22746
  return secret;
22592
22747
  }
22593
22748
  function readPeriscopeSecret(stateDir, ticketRecordId) {
22594
22749
  try {
22595
- const v = JSON.parse(readFileSync39(secretFile(stateDir, ticketRecordId), "utf8"));
22750
+ const v = JSON.parse(readFileSync40(secretFile(stateDir, ticketRecordId), "utf8"));
22596
22751
  return typeof v.secret === "string" && v.secret ? v.secret : void 0;
22597
22752
  } catch {
22598
22753
  return void 0;
@@ -22603,14 +22758,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
22603
22758
  }
22604
22759
  function readPeriscopeEndpoint(stateDir, ticketRecordId) {
22605
22760
  try {
22606
- return JSON.parse(readFileSync39(endpointFile(stateDir, ticketRecordId), "utf8"));
22761
+ return JSON.parse(readFileSync40(endpointFile(stateDir, ticketRecordId), "utf8"));
22607
22762
  } catch {
22608
22763
  return void 0;
22609
22764
  }
22610
22765
  }
22611
22766
  function writePeriscopeEndpoint(stateDir, rec) {
22612
- mkdirSync31(endpointDir(stateDir), { recursive: true });
22613
- writeFileSync32(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
22767
+ mkdirSync32(endpointDir(stateDir), { recursive: true });
22768
+ writeFileSync33(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
22614
22769
  `);
22615
22770
  }
22616
22771
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
@@ -22618,7 +22773,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
22618
22773
  }
22619
22774
  function listPeriscopeEndpoints(stateDir) {
22620
22775
  const dir = endpointDir(stateDir);
22621
- if (!existsSync44(dir)) return [];
22776
+ if (!existsSync45(dir)) return [];
22622
22777
  const out = [];
22623
22778
  for (const name of readdirSync19(dir)) {
22624
22779
  if (!name.endsWith(".json")) continue;
@@ -22687,7 +22842,7 @@ var PathRefusal = class extends Error {
22687
22842
  };
22688
22843
  var isInside = (root, p) => {
22689
22844
  const rel = relative2(root, p);
22690
- return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute3(rel);
22845
+ return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute4(rel);
22691
22846
  };
22692
22847
  async function realAncestor(p) {
22693
22848
  let cur = p;
@@ -22706,10 +22861,10 @@ async function realAncestor(p) {
22706
22861
  }
22707
22862
  if (isLink) {
22708
22863
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
22709
- cur = resolve5(dirname27(cur), await readlink(cur));
22864
+ cur = resolve6(dirname28(cur), await readlink(cur));
22710
22865
  continue;
22711
22866
  }
22712
- const parent = dirname27(cur);
22867
+ const parent = dirname28(cur);
22713
22868
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
22714
22869
  tail3.push(basename15(cur));
22715
22870
  cur = parent;
@@ -22719,15 +22874,15 @@ async function resolveInWorktree(root, requested) {
22719
22874
  if (!requested || requested.includes("\0")) throw new PathRefusal("path is required");
22720
22875
  const realRoot = await realpath(root);
22721
22876
  let abs;
22722
- if (isAbsolute3(requested)) {
22723
- abs = resolve5(requested);
22724
- if (isInside(root, abs)) abs = join48(realRoot, relative2(root, abs));
22877
+ if (isAbsolute4(requested)) {
22878
+ abs = resolve6(requested);
22879
+ if (isInside(root, abs)) abs = join49(realRoot, relative2(root, abs));
22725
22880
  } else {
22726
- abs = resolve5(realRoot, requested);
22881
+ abs = resolve6(realRoot, requested);
22727
22882
  }
22728
22883
  if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
22729
22884
  const { real, rest } = await realAncestor(abs);
22730
- const final = rest ? join48(real, rest) : real;
22885
+ const final = rest ? join49(real, rest) : real;
22731
22886
  if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
22732
22887
  if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
22733
22888
  return final;
@@ -22762,7 +22917,7 @@ async function hookEnv(d, sc, name) {
22762
22917
  if (iso && iso.code === 0) Object.assign(env, parseEnvLines(iso.output));
22763
22918
  }
22764
22919
  }
22765
- if (name === "preview") env.CREW_PREVIEW_HOST = sc.host || "localhost";
22920
+ if (name === "preview") Object.assign(env, { CREW_PREVIEW_HOST: sc.host || "localhost" }, pointedAtEnv(sc.worktree));
22766
22921
  return env;
22767
22922
  }
22768
22923
  function createPeriscopeMcpServer(d, sc) {
@@ -22795,7 +22950,7 @@ function createPeriscopeMcpServer(d, sc) {
22795
22950
  const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
22796
22951
  const lines2 = await Promise.all(entries.map(async (e) => {
22797
22952
  if (e.isDirectory()) return `[DIR] ${e.name}`;
22798
- const size = (await lstat(join48(abs, e.name))).size;
22953
+ const size = (await lstat(join49(abs, e.name))).size;
22799
22954
  return `[FILE] ${e.name} (${size} bytes)`;
22800
22955
  }));
22801
22956
  return text(lines2.join("\n") || "(empty)");
@@ -22820,7 +22975,7 @@ function createPeriscopeMcpServer(d, sc) {
22820
22975
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
22821
22976
  const abs = await inTree(path);
22822
22977
  if (abs === await realpath(root)) return text("path must name a file", true);
22823
- await mkdir(dirname27(abs), { recursive: true });
22978
+ await mkdir(dirname28(abs), { recursive: true });
22824
22979
  await inTree(path);
22825
22980
  await writeFile(abs, content);
22826
22981
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -22860,8 +23015,8 @@ function createPeriscopeMcpServer(d, sc) {
22860
23015
  inputSchema: { path: z.string() },
22861
23016
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
22862
23017
  }, async ({ path }) => {
22863
- const parent = await inTree(dirname27(path) === "" ? "." : dirname27(path));
22864
- const abs = join48(parent, basename15(path));
23018
+ const parent = await inTree(dirname28(path) === "" ? "." : dirname28(path));
23019
+ const abs = join49(parent, basename15(path));
22865
23020
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
22866
23021
  const st = await lstat(abs).catch(() => void 0);
22867
23022
  if (!st) return text(`${path} does not exist`, true);
@@ -22982,7 +23137,7 @@ function findPeriscope(sidecars, segment) {
22982
23137
  }
22983
23138
 
22984
23139
  // src/cli.ts
22985
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname28(process.execPath) : resolve6(dirname28(fileURLToPath(import.meta.url)), "..");
23140
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname29(process.execPath) : resolve7(dirname29(fileURLToPath(import.meta.url)), "..");
22986
23141
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
22987
23142
  var CYCLE_SECONDS2 = 120;
22988
23143
  var STALE_CYCLES = 3;
@@ -23350,7 +23505,7 @@ if (command === "provision") {
23350
23505
  process.exit(outcome.code);
23351
23506
  }
23352
23507
  if (command === "pool-member") {
23353
- const pool = poolConfigFrom(argv, process.env, () => readFileSync40(0, "utf8"));
23508
+ const pool = poolConfigFrom(argv, process.env, () => readFileSync41(0, "utf8"));
23354
23509
  if (typeof pool === "string") {
23355
23510
  process.stderr.write(`crew pool-member: ${pool}
23356
23511
  `);
@@ -23578,7 +23733,7 @@ if (command === "passenger-sync-daemon") {
23578
23733
  } else {
23579
23734
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
23580
23735
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
23581
- if (configless && !existsSync45(findConfigFile(CREW_HOME))) {
23736
+ if (configless && !existsSync46(findConfigFile(CREW_HOME))) {
23582
23737
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
23583
23738
  } else {
23584
23739
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -23593,7 +23748,7 @@ if (command === "passenger-sync-daemon") {
23593
23748
  throw e;
23594
23749
  }
23595
23750
  if (command === "credential") {
23596
- const r = runCredential(positional[1], readFileSync40(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
23751
+ const r = runCredential(positional[1], readFileSync41(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
23597
23752
  process.stdout.write(r.out);
23598
23753
  process.stderr.write(r.err);
23599
23754
  process.exit(r.code);
@@ -23605,9 +23760,9 @@ if (command === "passenger-sync-daemon") {
23605
23760
  forges: cfg.ship.forges,
23606
23761
  stateDir: cfg.ship.stateDir,
23607
23762
  configFile: cfg.configFile,
23608
- readFile: (p) => readFileSync40(p, "utf8"),
23609
- writeFile: (p, t) => writeFileSync33(p, t),
23610
- stdin: () => readFileSync40(0, "utf8"),
23763
+ readFile: (p) => readFileSync41(p, "utf8"),
23764
+ writeFile: (p, t) => writeFileSync34(p, t),
23765
+ stdin: () => readFileSync41(0, "utf8"),
23611
23766
  env: process.env
23612
23767
  }
23613
23768
  );
@@ -23900,8 +24055,8 @@ if (command === "passenger-sync-daemon") {
23900
24055
  }
23901
24056
  if (host === "linux" && hasSystemd) {
23902
24057
  const label = labelFor(CREW_HOME, "run");
23903
- const staleTimer = join49(homedir11(), ".config", "systemd", "user", `${label}.timer`);
23904
- if (existsSync45(staleTimer)) {
24058
+ const staleTimer = join50(homedir11(), ".config", "systemd", "user", `${label}.timer`);
24059
+ if (existsSync46(staleTimer)) {
23905
24060
  process.stdout.write(
23906
24061
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
23907
24062
  `
@@ -23928,7 +24083,7 @@ if (command === "passenger-sync-daemon") {
23928
24083
  const tracker = new Tracker(c, cfg.ship);
23929
24084
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
23930
24085
  for (const r of await resolvedRepos(c)) {
23931
- if (!existsSync45(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
24086
+ if (!existsSync46(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
23932
24087
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
23933
24088
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
23934
24089
  }
@@ -23955,7 +24110,7 @@ if (command === "passenger-sync-daemon") {
23955
24110
  }
23956
24111
  async function doctorFix(o) {
23957
24112
  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))));
23958
- const unitExists = (p) => existsSync45(p);
24113
+ const unitExists = (p) => existsSync46(p);
23959
24114
  const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
23960
24115
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
23961
24116
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -23972,7 +24127,7 @@ if (command === "passenger-sync-daemon") {
23972
24127
  for (const a of plan.actions) {
23973
24128
  if (a.kind === "enable") {
23974
24129
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
23975
- writeFileSync33(cfg.configFile, planEnable(readFileSync40(cfg.configFile, "utf8"), a.route));
24130
+ writeFileSync34(cfg.configFile, planEnable(readFileSync41(cfg.configFile, "utf8"), a.route));
23976
24131
  enabledSound.add(a.route);
23977
24132
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
23978
24133
  } else {
@@ -24049,7 +24204,7 @@ ${changed2.map((c) => ` - ${c}
24049
24204
  return renderEnvironment({
24050
24205
  route: c,
24051
24206
  userAgent: cfg.ship.userAgent,
24052
- repos: await resolvedRepos(c),
24207
+ repos: await withRepoMaps(await resolvedRepos(c), cfg.ship.mapTokenBudget, cfg.ship.shell),
24053
24208
  contract,
24054
24209
  sourceTicket: ticket,
24055
24210
  authorship: await tracker.authorshipProbe().catch(() => null),
@@ -24713,7 +24868,7 @@ ${cu.tail.trimEnd()}
24713
24868
  cfg.ship.name,
24714
24869
  (t) => {
24715
24870
  const d = dirForRepo(w.route, t.repo_id);
24716
- return d ? originBranchSha(d, t, resolve2) : null;
24871
+ return d ? originBranchSha(d, t, resolve3) : null;
24717
24872
  }
24718
24873
  );
24719
24874
  if (!result.ticket) {
@@ -24925,7 +25080,7 @@ ${cu.tail.trimEnd()}
24925
25080
  cfg.ship.name,
24926
25081
  (t) => {
24927
25082
  const d = dirForRepo(route2, t.repo_id);
24928
- return d ? originBranchSha(d, t, resolve2) : null;
25083
+ return d ? originBranchSha(d, t, resolve3) : null;
24929
25084
  }
24930
25085
  );
24931
25086
  if (!result.ticket) {
@@ -25111,7 +25266,7 @@ ${cu.tail.trimEnd()}
25111
25266
  process.exit(1);
25112
25267
  }
25113
25268
  requireArmed("run the daemon loop");
25114
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join49(CREW_HOME, "src"), join49(CREW_HOME, "dist"), join49(CREW_HOME, "bin")];
25269
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join50(CREW_HOME, "src"), join50(CREW_HOME, "dist"), join50(CREW_HOME, "bin")];
25115
25270
  const configPaths = daemonConfigPaths(cfg);
25116
25271
  watchPaths.push(...configPaths);
25117
25272
  noteDaemonStarted(cfg.ship.stateDir);
@@ -25416,10 +25571,10 @@ ${cu.tail.trimEnd()}
25416
25571
  `);
25417
25572
  if (!dryRun) {
25418
25573
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
25419
- const raw = existsSync45(resolvedPath) ? JSON.parse(readFileSync40(resolvedPath, "utf8")) : {};
25574
+ const raw = existsSync46(resolvedPath) ? JSON.parse(readFileSync41(resolvedPath, "utf8")) : {};
25420
25575
  raw.agentSkills = result.agentSkills;
25421
- mkdirSync32(dirOf2(resolvedPath), { recursive: true });
25422
- writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
25576
+ mkdirSync33(dirOf2(resolvedPath), { recursive: true });
25577
+ writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
25423
25578
  `);
25424
25579
  }
25425
25580
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -25442,7 +25597,7 @@ ${cu.tail.trimEnd()}
25442
25597
  throw e;
25443
25598
  }
25444
25599
  const oldName = cfg.ship.name;
25445
- writeFileSync33(cfg.configFile, setShipNameInYaml(readFileSync40(cfg.configFile, "utf8"), newName));
25600
+ writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), newName));
25446
25601
  cfg.ship.name = newName;
25447
25602
  let renameMachineId;
25448
25603
  try {
@@ -25489,14 +25644,14 @@ ${cu.tail.trimEnd()}
25489
25644
  process.exit(2);
25490
25645
  };
25491
25646
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
25492
- const configText = readFileSync40(cfg.configFile, "utf8");
25647
+ const configText = readFileSync41(cfg.configFile, "utf8");
25493
25648
  if (sub === "check") {
25494
25649
  try {
25495
25650
  const routeArg = value("route") ?? positional[2];
25496
25651
  const found = cfg.routes.find((r) => r.route === routeArg);
25497
25652
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
25498
25653
  const named2 = value("route") ? void 0 : positional[3];
25499
- const dir = named2 ? repoDirOf(configText, dirname28(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
25654
+ const dir = named2 ? repoDirOf(configText, dirname29(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
25500
25655
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
25501
25656
  const o = target ? found.repoOverrides[target.name] : void 0;
25502
25657
  runReposCheck(dir, {
@@ -25519,9 +25674,9 @@ ${cu.tail.trimEnd()}
25519
25674
  if (sub === "hooks") {
25520
25675
  try {
25521
25676
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
25522
- const dir = repoDirOf(configText, dirname28(cfg.configFile), a.route, a.name);
25523
- if (!existsSync45(dir)) throw new ReposError(`${dir} does not exist on disk`);
25524
- const files = { exists: (r) => existsSync45(resolvePath(dir, r)), list: (r) => {
25677
+ const dir = repoDirOf(configText, dirname29(cfg.configFile), a.route, a.name);
25678
+ if (!existsSync46(dir)) throw new ReposError(`${dir} does not exist on disk`);
25679
+ const files = { exists: (r) => existsSync46(resolvePath(dir, r)), list: (r) => {
25525
25680
  try {
25526
25681
  return readdirSync20(resolvePath(dir, r));
25527
25682
  } catch {
@@ -25529,7 +25684,7 @@ ${cu.tail.trimEnd()}
25529
25684
  }
25530
25685
  }, read: (r) => {
25531
25686
  try {
25532
- return readFileSync40(resolvePath(dir, r), "utf8");
25687
+ return readFileSync41(resolvePath(dir, r), "utf8");
25533
25688
  } catch {
25534
25689
  return void 0;
25535
25690
  }
@@ -25571,19 +25726,19 @@ ${cu.tail.trimEnd()}
25571
25726
  let cleared;
25572
25727
  if (clear2) {
25573
25728
  const plan = clearRouteHooks(configText, a.route, a.name);
25574
- if (plan.removed.length > 0) writeFileSync33(cfg.configFile, plan.text);
25729
+ if (plan.removed.length > 0) writeFileSync34(cfg.configFile, plan.text);
25575
25730
  cleared = { file: cfg.configFile, removed: plan.removed };
25576
25731
  }
25577
25732
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
25578
25733
  if (write === "route") {
25579
25734
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
25580
- writeFileSync33(cfg.configFile, plan.text);
25735
+ writeFileSync34(cfg.configFile, plan.text);
25581
25736
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
25582
25737
  } else if (write === "repo") {
25583
25738
  const have = existingRepoConfig(files);
25584
25739
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
25585
25740
  const file = resolvePath(dir, ".crew.yaml");
25586
- writeFileSync33(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
25741
+ writeFileSync34(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
25587
25742
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
25588
25743
  }
25589
25744
  if (flag("json")) {
@@ -25639,7 +25794,7 @@ ${cu.tail.trimEnd()}
25639
25794
  if (sub === "list" && !routeName) usage2();
25640
25795
  if (sub === "add") {
25641
25796
  try {
25642
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync45(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
25797
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync46(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
25643
25798
  routeName = a.route;
25644
25799
  addPath = a.path;
25645
25800
  addName = a.name;
@@ -25669,7 +25824,7 @@ ${cu.tail.trimEnd()}
25669
25824
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
25670
25825
  if (sub === "list" || sub === "add") {
25671
25826
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
25672
- if (liveRoute?.resolved?.reposModelId && existsSync45(resolvedFile) && !dryRun) {
25827
+ if (liveRoute?.resolved?.reposModelId && existsSync46(resolvedFile) && !dryRun) {
25673
25828
  try {
25674
25829
  await hydrateApiKeys([liveRoute]);
25675
25830
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -25678,16 +25833,16 @@ ${cu.tail.trimEnd()}
25678
25833
  }
25679
25834
  }
25680
25835
  let resolvedRemotes;
25681
- if (existsSync45(resolvedFile)) {
25836
+ if (existsSync46(resolvedFile)) {
25682
25837
  try {
25683
- resolvedRemotes = JSON.parse(readFileSync40(resolvedFile, "utf8"));
25838
+ resolvedRemotes = JSON.parse(readFileSync41(resolvedFile, "utf8"));
25684
25839
  } catch {
25685
25840
  }
25686
25841
  }
25687
25842
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
25688
25843
  const text2 = configText;
25689
- const base = dirname28(cfg.configFile);
25690
- const isCheckout = (d) => existsSync45(`${d}/.git`);
25844
+ const base = dirname29(cfg.configFile);
25845
+ const isCheckout = (d) => existsSync46(`${d}/.git`);
25691
25846
  try {
25692
25847
  if (sub === "list") {
25693
25848
  if (flag("json")) {
@@ -25697,11 +25852,11 @@ ${cu.tail.trimEnd()}
25697
25852
  base,
25698
25853
  resolved: resolvedRemotes,
25699
25854
  originOf,
25700
- exists: (d) => existsSync45(d),
25855
+ exists: (d) => existsSync46(d),
25701
25856
  isCheckout,
25702
25857
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
25703
25858
  try {
25704
- return Object.keys(parseRepoConfig(readFileSync40(resolvePath(d, f), "utf8"), f).hooks).length > 0;
25859
+ return Object.keys(parseRepoConfig(readFileSync41(resolvePath(d, f), "utf8"), f).hooks).length > 0;
25705
25860
  } catch {
25706
25861
  return false;
25707
25862
  }
@@ -25718,7 +25873,7 @@ ${cu.tail.trimEnd()}
25718
25873
  for (const r of repos) {
25719
25874
  const origin2 = originOf(r.dir);
25720
25875
  const row = matchRepoName(origin2, resolvedRemotes);
25721
- const note = !existsSync45(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
25876
+ const note = !existsSync46(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
25722
25877
  process.stdout.write(`${r.name} ${r.dir} ${note}
25723
25878
  `);
25724
25879
  }
@@ -25730,9 +25885,9 @@ ${cu.tail.trimEnd()}
25730
25885
  ` : `${human}
25731
25886
  `);
25732
25887
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
25733
- if (existsSync45(plan2.target) && (existsSync45(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
25888
+ if (existsSync46(plan2.target) && (existsSync46(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
25734
25889
  throw new ReposError(
25735
- existsSync45(`${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`
25890
+ existsSync46(`${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`
25736
25891
  );
25737
25892
  }
25738
25893
  if (dryRun) {
@@ -25750,14 +25905,14 @@ ${cu.tail.trimEnd()}
25750
25905
  throw new ReposError(`${e.message}${authHint}
25751
25906
  crew.yaml was not changed`);
25752
25907
  }
25753
- writeFileSync33(cfg.configFile, plan2.text);
25908
+ writeFileSync34(cfg.configFile, plan2.text);
25754
25909
  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:
25755
25910
  `);
25756
25911
  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}`);
25757
25912
  break;
25758
25913
  }
25759
25914
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
25760
- const top = existsSync45(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
25915
+ const top = existsSync46(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
25761
25916
  if (!top) throw new ReposError(`${target} is not a git checkout`);
25762
25917
  const origin = originOf(top);
25763
25918
  const plan = planRepoAdd({
@@ -25809,15 +25964,15 @@ ${cu.tail.trimEnd()}
25809
25964
  const created = await rowTracker.createRepoRow(rowPayload);
25810
25965
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
25811
25966
  `);
25812
- if (existsSync45(resolvedFile)) {
25813
- const raw = JSON.parse(readFileSync40(resolvedFile, "utf8"));
25967
+ if (existsSync46(resolvedFile)) {
25968
+ const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
25814
25969
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
25815
25970
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
25816
- writeFileSync33(resolvedFile, `${JSON.stringify(raw, null, 2)}
25971
+ writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
25817
25972
  `);
25818
25973
  }
25819
25974
  }
25820
- writeFileSync33(cfg.configFile, plan.text);
25975
+ writeFileSync34(cfg.configFile, plan.text);
25821
25976
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
25822
25977
  `);
25823
25978
  } catch (e) {
@@ -26186,7 +26341,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26186
26341
  process.exit(2);
26187
26342
  }
26188
26343
  if (found.shipNameAdopted && !dryRun) {
26189
- writeFileSync33(cfg.configFile, setShipNameInYaml(readFileSync40(cfg.configFile, "utf8"), found.shipNameAdopted));
26344
+ writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), found.shipNameAdopted));
26190
26345
  cfg.ship.name = found.shipNameAdopted;
26191
26346
  }
26192
26347
  if (found.shipRow) {
@@ -26229,10 +26384,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26229
26384
  }
26230
26385
  }
26231
26386
  let previous;
26232
- const hadPreviousFile = existsSync45(resolvedPath);
26387
+ const hadPreviousFile = existsSync46(resolvedPath);
26233
26388
  if (hadPreviousFile) {
26234
26389
  try {
26235
- previous = JSON.parse(readFileSync40(resolvedPath, "utf8"));
26390
+ previous = JSON.parse(readFileSync41(resolvedPath, "utf8"));
26236
26391
  } catch {
26237
26392
  }
26238
26393
  }
@@ -26362,10 +26517,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26362
26517
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
26363
26518
  `);
26364
26519
  } else {
26365
- mkdirSync32(dirname28(resolvedPath), { recursive: true });
26520
+ mkdirSync33(dirname29(resolvedPath), { recursive: true });
26366
26521
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
26367
26522
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
26368
- writeFileSync33(tmpPath, `${JSON.stringify(resolved, null, 2)}
26523
+ writeFileSync34(tmpPath, `${JSON.stringify(resolved, null, 2)}
26369
26524
  `);
26370
26525
  renameSync6(tmpPath, resolvedPath);
26371
26526
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -26411,8 +26566,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26411
26566
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
26412
26567
  `);
26413
26568
  } else {
26414
- mkdirSync32(dirname28(apiKeyFile), { recursive: true });
26415
- writeFileSync33(apiKeyFile, `${apiKeyVar}=${apiKey}
26569
+ mkdirSync33(dirname29(apiKeyFile), { recursive: true });
26570
+ writeFileSync34(apiKeyFile, `${apiKeyVar}=${apiKey}
26416
26571
  `, { mode: 384 });
26417
26572
  process.stderr.write(`API key written to ${apiKeyFile}
26418
26573
  `);
@@ -27139,7 +27294,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27139
27294
  };
27140
27295
  if (subP === "list") {
27141
27296
  const entries = periscopeListEntries(
27142
- (await Promise.all(routesP.map(gather))).flat(),
27297
+ (await Promise.all(routesP.map(gather))).flat().map((r) => ({ ...r, pointedAt: readPointedAt(r.worktree) })),
27143
27298
  /* @__PURE__ */ new Date(),
27144
27299
  (r) => pidsInWorktree(r.worktree, Number(r.number)).length > 0
27145
27300
  );
@@ -27287,7 +27442,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27287
27442
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
27288
27443
  const resumed = await runPeriscopeResume({
27289
27444
  find: async () => hit && { row: hit.row, tag: hit.tag },
27290
- worktreeExists: (p) => existsSync45(p),
27445
+ worktreeExists: (p) => existsSync46(p),
27446
+ prepareDropFile,
27291
27447
  caps: periscopeCaps(cfg.ship),
27292
27448
  runHook: async (name, cwd, env) => {
27293
27449
  const script = hit?.repo.config.hooks[name];
@@ -27381,10 +27537,10 @@ ${where.join("\n")}`);
27381
27537
  },
27382
27538
  prepare: async (path) => {
27383
27539
  for (const f of repoP.config.worktrees.copy) {
27384
- const src = join49(repoP.dir, f);
27385
- if (!existsSync45(src)) continue;
27386
- mkdirSync32(dirOf2(join49(path, f)), { recursive: true });
27387
- copyFileSync2(src, join49(path, f));
27540
+ const src = join50(repoP.dir, f);
27541
+ if (!existsSync46(src)) continue;
27542
+ mkdirSync33(dirOf2(join50(path, f)), { recursive: true });
27543
+ copyFileSync2(src, join50(path, f));
27388
27544
  }
27389
27545
  const setup = repoP.config.hooks.setup;
27390
27546
  return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
@@ -27396,6 +27552,7 @@ ${where.join("\n")}`);
27396
27552
  return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : void 0 }) : null;
27397
27553
  },
27398
27554
  createRow: (fields) => trackerP.createPeriscope(fields),
27555
+ prepareDropFile,
27399
27556
  recordLocal: (sc) => {
27400
27557
  writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
27401
27558
  if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
@@ -27679,7 +27836,7 @@ ${where.join("\n")}`);
27679
27836
  };
27680
27837
  const droppedRoutes = [];
27681
27838
  try {
27682
- const rawCfg = parseYaml2(readFileSync40(cfg.configFile, "utf8"));
27839
+ const rawCfg = parseYaml2(readFileSync41(cfg.configFile, "utf8"));
27683
27840
  for (const r of rawCfg?.routes ?? []) {
27684
27841
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
27685
27842
  try {
@@ -27732,8 +27889,8 @@ ${where.join("\n")}`);
27732
27889
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
27733
27890
  break;
27734
27891
  }
27735
- mkdirSync32(cfg.ship.stateDir, { recursive: true });
27736
- writeFileSync33(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
27892
+ mkdirSync33(cfg.ship.stateDir, { recursive: true });
27893
+ writeFileSync34(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
27737
27894
  `);
27738
27895
  process.stdout.write(
27739
27896
  "crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
@@ -27798,7 +27955,7 @@ ${where.join("\n")}`);
27798
27955
  }
27799
27956
  case "log": {
27800
27957
  try {
27801
- const text2 = readFileSync40(cfg.ship.logFile, "utf8").split("\n");
27958
+ const text2 = readFileSync41(cfg.ship.logFile, "utf8").split("\n");
27802
27959
  process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
27803
27960
  `);
27804
27961
  } catch {
@@ -27821,8 +27978,8 @@ ${where.join("\n")}`);
27821
27978
  };
27822
27979
  if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
27823
27980
  const tracker = new Tracker(route2, cfg.ship);
27824
- const configText = readFileSync40(cfg.configFile, "utf8");
27825
- const cfgBase = dirname28(cfg.configFile);
27981
+ const configText = readFileSync41(cfg.configFile, "utf8");
27982
+ const cfgBase = dirname29(cfg.configFile);
27826
27983
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
27827
27984
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
27828
27985
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -27850,7 +28007,7 @@ ${where.join("\n")}`);
27850
28007
  }
27851
28008
  if (configured2.some((r) => r.dir === dir)) {
27852
28009
  try {
27853
- writeFileSync33(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
28010
+ writeFileSync34(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
27854
28011
  } catch (e) {
27855
28012
  if (e instanceof ReposError) fail(e.message);
27856
28013
  throw e;
@@ -27858,29 +28015,29 @@ ${where.join("\n")}`);
27858
28015
  }
27859
28016
  if (row) {
27860
28017
  await tracker.deleteRepoRow(row.id);
27861
- if (existsSync45(resolvedFile)) {
27862
- const raw = JSON.parse(readFileSync40(resolvedFile, "utf8"));
28018
+ if (existsSync46(resolvedFile)) {
28019
+ const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
27863
28020
  if (raw.repoNames) delete raw.repoNames[row.id];
27864
28021
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
27865
- writeFileSync33(resolvedFile, `${JSON.stringify(raw, null, 2)}
28022
+ writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
27866
28023
  `);
27867
28024
  }
27868
28025
  }
27869
- if (existsSync45(join49(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
28026
+ if (existsSync46(join50(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
27870
28027
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
27871
28028
  break;
27872
28029
  }
27873
28030
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
27874
- const fresh = !existsSync45(join49(dir, ".git"));
27875
- if (existsSync45(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
28031
+ const fresh = !existsSync46(join50(dir, ".git"));
28032
+ if (existsSync46(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
27876
28033
  if (dryRun) {
27877
28034
  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`);
27878
28035
  break;
27879
28036
  }
27880
28037
  if (fresh) {
27881
- mkdirSync32(dir, { recursive: true });
27882
- writeFileSync33(join49(dir, "README.md"), SAMPLE_README);
27883
- writeFileSync33(join49(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
28038
+ mkdirSync33(dir, { recursive: true });
28039
+ writeFileSync34(join50(dir, "README.md"), SAMPLE_README);
28040
+ writeFileSync34(join50(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
27884
28041
  const g = (...a) => {
27885
28042
  const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
27886
28043
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -27898,17 +28055,17 @@ ${where.join("\n")}`);
27898
28055
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
27899
28056
  });
27900
28057
  repoId = (await tracker.createRepoRow(payload)).id;
27901
- if (existsSync45(resolvedFile)) {
27902
- const raw = JSON.parse(readFileSync40(resolvedFile, "utf8"));
28058
+ if (existsSync46(resolvedFile)) {
28059
+ const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
27903
28060
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
27904
28061
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
27905
- writeFileSync33(resolvedFile, `${JSON.stringify(raw, null, 2)}
28062
+ writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
27906
28063
  `);
27907
28064
  }
27908
28065
  }
27909
28066
  if (!configured2.some((r) => r.dir === dir)) {
27910
28067
  try {
27911
- writeFileSync33(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
28068
+ writeFileSync34(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
27912
28069
  } catch (e) {
27913
28070
  if (e instanceof ReposError) fail(e.message);
27914
28071
  throw e;
@@ -28254,7 +28411,7 @@ watermark: ${state.watermark()}
28254
28411
  }
28255
28412
  for (const r of repos) {
28256
28413
  const remote = r.config.branch.remote;
28257
- if (existsSync45(r.dir)) {
28414
+ if (existsSync46(r.dir)) {
28258
28415
  pushAuth.push({
28259
28416
  route: c.route,
28260
28417
  repo: r.name,
@@ -28343,7 +28500,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28343
28500
  }
28344
28501
  try {
28345
28502
  const evFile = eventFileFor(cfg.ship.stateDir);
28346
- const evSize = existsSync45(evFile) ? statSync15(evFile).size : 0;
28503
+ const evSize = existsSync46(evFile) ? statSync16(evFile).size : 0;
28347
28504
  process.stdout.write(
28348
28505
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
28349
28506
  ` : "events log: ok\n"
@@ -28354,7 +28511,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28354
28511
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
28355
28512
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
28356
28513
  try {
28357
- return readFileSync40(p, "utf8");
28514
+ return readFileSync41(p, "utf8");
28358
28515
  } catch {
28359
28516
  return null;
28360
28517
  }
@@ -28377,7 +28534,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28377
28534
  const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
28378
28535
  if (changed2) {
28379
28536
  process.stdout.write(
28380
- `daemon config: WARN ${changed2} changed at ${new Date(statSync15(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
28537
+ `daemon config: WARN ${changed2} changed at ${new Date(statSync16(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
28381
28538
  `
28382
28539
  );
28383
28540
  }