@tablation/crew 0.113.0 → 0.115.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 +910 -743
  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
@@ -2375,8 +2452,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
2375
2452
  var PROVIDERS = ["github", "buildkite", "other", "none"];
2376
2453
  function findRepoConfig(dir) {
2377
2454
  for (const name of REPO_CONFIG_FILENAMES) {
2378
- const p = join11(dir, name);
2379
- if (existsSync9(p)) return p;
2455
+ const p = join12(dir, name);
2456
+ if (existsSync10(p)) return p;
2380
2457
  }
2381
2458
  return null;
2382
2459
  }
@@ -2656,7 +2733,7 @@ function parseRepoConfig(text2, file) {
2656
2733
  function loadRepoConfig(dir) {
2657
2734
  const file = findRepoConfig(dir);
2658
2735
  if (!file) return null;
2659
- return parseRepoConfig(readFileSync10(file, "utf8"), file);
2736
+ return parseRepoConfig(readFileSync11(file, "utf8"), file);
2660
2737
  }
2661
2738
  function hookLabel(cfg, hook2) {
2662
2739
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -3030,7 +3107,9 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
3030
3107
  "maxPeriscopes",
3031
3108
  "periscopeIdleMinutes",
3032
3109
  "secretStore",
3033
- "env"
3110
+ "env",
3111
+ "runner",
3112
+ "sandbox"
3034
3113
  ]);
3035
3114
  var ROUTE_KEYS = /* @__PURE__ */ new Set([
3036
3115
  "route",
@@ -3176,14 +3255,14 @@ function mergeRouteRelease(route2, override) {
3176
3255
  function expand(p, base) {
3177
3256
  let out = p.startsWith("~") ? p.replace(/^~/, homedir6()) : p;
3178
3257
  out = out.replace(/\$\{?HOME\}?/g, homedir6());
3179
- return isAbsolute(out) ? out : resolve(base, out);
3258
+ return isAbsolute2(out) ? out : resolve2(base, out);
3180
3259
  }
3181
3260
  function resolvePromptsDir(promptSet, crewHome, base) {
3182
- if (!promptSet) return join12(crewHome, "prompts", "default");
3261
+ if (!promptSet) return join13(crewHome, "prompts", "default");
3183
3262
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
3184
3263
  return expand(promptSet, base);
3185
3264
  }
3186
- return join12(crewHome, "prompts", promptSet);
3265
+ return join13(crewHome, "prompts", promptSet);
3187
3266
  }
3188
3267
  var Missing = class {
3189
3268
  paths = [];
@@ -3207,36 +3286,36 @@ var Missing = class {
3207
3286
  };
3208
3287
  function crewVersion(crewHome) {
3209
3288
  try {
3210
- const pkg = JSON.parse(readFileSync11(join12(crewHome, "package.json"), "utf8"));
3289
+ const pkg = JSON.parse(readFileSync12(join13(crewHome, "package.json"), "utf8"));
3211
3290
  return pkg.version ?? "0.0.0";
3212
3291
  } catch {
3213
3292
  return "0.0.0";
3214
3293
  }
3215
3294
  }
3216
3295
  function configSearchPath(crewHome) {
3217
- if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
3296
+ if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
3218
3297
  const xdg = process.env.XDG_CONFIG_HOME;
3219
3298
  return [
3220
- ...xdg ? [join12(xdg, "crew", "crew.yaml")] : [],
3221
- join12(homedir6(), ".config", "crew", "crew.yaml"),
3222
- resolve(crewHome, "crew.yaml")
3299
+ ...xdg ? [join13(xdg, "crew", "crew.yaml")] : [],
3300
+ join13(homedir6(), ".config", "crew", "crew.yaml"),
3301
+ resolve2(crewHome, "crew.yaml")
3223
3302
  ];
3224
3303
  }
3225
3304
  function findConfigFile(crewHome) {
3226
3305
  const candidates2 = configSearchPath(crewHome);
3227
- return candidates2.find((c) => existsSync10(c)) ?? candidates2[candidates2.length - 1];
3306
+ return candidates2.find((c) => existsSync11(c)) ?? candidates2[candidates2.length - 1];
3228
3307
  }
3229
- var defaultConfigPath = () => join12(process.env.XDG_CONFIG_HOME ?? join12(homedir6(), ".config"), "crew", "crew.yaml");
3308
+ var defaultConfigPath = () => join13(process.env.XDG_CONFIG_HOME ?? join13(homedir6(), ".config"), "crew", "crew.yaml");
3230
3309
  function routeSlug(route2) {
3231
3310
  return route2.replace(/\//g, "-");
3232
3311
  }
3233
3312
  function resolvedPathFor(stateDir, route2) {
3234
3313
  const [workspace, project] = splitRoute(route2);
3235
- return join12(stateDir, "resolved", workspace, `${project}.json`);
3314
+ return join13(stateDir, "resolved", workspace, `${project}.json`);
3236
3315
  }
3237
3316
  function apiKeyPathFor(stateDir, route2) {
3238
3317
  const [workspace, project] = splitRoute(route2);
3239
- return join12(stateDir, "keys", workspace, `${project}.env`);
3318
+ return join13(stateDir, "keys", workspace, `${project}.env`);
3240
3319
  }
3241
3320
  function splitRoute(route2) {
3242
3321
  const parts = route2.split("/");
@@ -3247,9 +3326,9 @@ function splitRoute(route2) {
3247
3326
  }
3248
3327
  function readResolvedFile(stateDir, route2) {
3249
3328
  const path = resolvedPathFor(stateDir, route2);
3250
- if (!existsSync10(path)) return void 0;
3329
+ if (!existsSync11(path)) return void 0;
3251
3330
  try {
3252
- return JSON.parse(readFileSync11(path, "utf8"));
3331
+ return JSON.parse(readFileSync12(path, "utf8"));
3253
3332
  } catch (e) {
3254
3333
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
3255
3334
  }
@@ -3310,23 +3389,23 @@ function parseResolved(raw, m, where) {
3310
3389
  }
3311
3390
  function loadConfig(crewHome, configFile, opts = {}) {
3312
3391
  const file = configFile ?? findConfigFile(crewHome);
3313
- if (opts.text === void 0 && !existsSync10(file)) {
3392
+ if (opts.text === void 0 && !existsSync11(file)) {
3314
3393
  throw new ConfigError(
3315
3394
  `no config found. Looked in:
3316
3395
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
3317
3396
 
3318
3397
  Create the first of those:
3319
- mkdir -p ${dirname4(defaultConfigPath())} && cp ${join12(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3398
+ mkdir -p ${dirname5(defaultConfigPath())} && cp ${join13(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3320
3399
  );
3321
3400
  }
3322
3401
  let raw;
3323
3402
  try {
3324
- raw = parse2(opts.text ?? readFileSync11(file, "utf8"));
3403
+ raw = parse2(opts.text ?? readFileSync12(file, "utf8"));
3325
3404
  } catch (e) {
3326
3405
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
3327
3406
  }
3328
3407
  if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
3329
- const base = dirname4(file);
3408
+ const base = dirname5(file);
3330
3409
  const missing = new Missing();
3331
3410
  rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
3332
3411
  const shipRaw = raw.ship ?? {};
@@ -3440,7 +3519,7 @@ Create the first of those:
3440
3519
  useNvm: shipRaw.useNvm !== false,
3441
3520
  nvmSh: shipRaw.nvmSh,
3442
3521
  stateDir,
3443
- logFile: expand(shipRaw.logFile ?? join12(tmpdir(), "crew.log"), base),
3522
+ logFile: expand(shipRaw.logFile ?? join13(tmpdir(), "crew.log"), base),
3444
3523
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
3445
3524
  maxConcurrentAgents,
3446
3525
  maxConcurrentAgentsSet,
@@ -3620,7 +3699,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
3620
3699
  const hasRepos = Object.keys(repoDirs).length > 0;
3621
3700
  const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
3622
3701
  const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
3623
- const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join12(reposBasePath, splitRoute(route2)[0]) : "";
3702
+ const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join13(reposBasePath, splitRoute(route2)[0]) : "";
3624
3703
  const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
3625
3704
  if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
3626
3705
  missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
@@ -3669,21 +3748,21 @@ function routeForDir(cfg, dir) {
3669
3748
  }
3670
3749
  function locateDir(cfg, dir, commonDirOf) {
3671
3750
  const within = (base, target2) => {
3672
- const b = resolve(base);
3751
+ const b = resolve2(base);
3673
3752
  return target2 === b || target2.startsWith(`${b}/`);
3674
3753
  };
3675
3754
  const find = (target2) => {
3676
3755
  for (const route2 of cfg.routes) {
3677
3756
  const repo = reposOf(route2).find((x) => within(x.dir, target2));
3678
- if (repo) return { route: route2, repo, checkout: resolve(repo.dir) };
3757
+ if (repo) return { route: route2, repo, checkout: resolve2(repo.dir) };
3679
3758
  }
3680
3759
  return void 0;
3681
3760
  };
3682
- const target = resolve(dir);
3761
+ const target = resolve2(dir);
3683
3762
  const direct = find(target);
3684
3763
  if (direct) return direct;
3685
3764
  const common = commonDirOf?.(target);
3686
- return common ? find(dirname4(resolve(target, common))) : void 0;
3765
+ return common ? find(dirname5(resolve2(target, common))) : void 0;
3687
3766
  }
3688
3767
  function findRoute(cfg, route2) {
3689
3768
  if (!route2) {
@@ -3718,8 +3797,8 @@ function repoIdForName(r, name) {
3718
3797
  }
3719
3798
  function resolveApiKey(r) {
3720
3799
  if (r.apiKey) return r.apiKey;
3721
- if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
3722
- for (const line of readFileSync11(r.apiKeyFile, "utf8").split("\n")) {
3800
+ if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
3801
+ for (const line of readFileSync12(r.apiKeyFile, "utf8").split("\n")) {
3723
3802
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
3724
3803
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
3725
3804
  }
@@ -3732,9 +3811,9 @@ function resolveApiKey(r) {
3732
3811
  }
3733
3812
  function credentialSource(r) {
3734
3813
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
3735
- if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
3814
+ if (r.apiKeyFile && r.apiKeyVar && existsSync11(r.apiKeyFile)) {
3736
3815
  const re = new RegExp(`^${r.apiKeyVar}=`);
3737
- if (readFileSync11(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3816
+ if (readFileSync12(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3738
3817
  }
3739
3818
  return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
3740
3819
  }
@@ -3790,7 +3869,7 @@ function configuredMembers(rt) {
3790
3869
  return out;
3791
3870
  }
3792
3871
  function defaultRepoDir(basePath, workspace, repoName) {
3793
- return join12(basePath, workspace, repoName);
3872
+ return join13(basePath, workspace, repoName);
3794
3873
  }
3795
3874
  function reposOf(r) {
3796
3875
  const named = Object.entries(r.repos);
@@ -3829,10 +3908,10 @@ function passengerRepoTargets(r) {
3829
3908
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
3830
3909
  }
3831
3910
  function passengerCheckoutDir(basePath, workspace, repoName) {
3832
- return join12(basePath, workspace, ".passenger-checkouts", repoName);
3911
+ return join13(basePath, workspace, ".passenger-checkouts", repoName);
3833
3912
  }
3834
3913
  function passengerMirrorArea(ship) {
3835
- return ship.runner === "sandbox" ? join12(ship.stateDir, "passenger-sources") : void 0;
3914
+ return ship.runner === "sandbox" ? join13(ship.stateDir, "passenger-sources") : void 0;
3836
3915
  }
3837
3916
  function passengerCheckoutTargets(r, mirrorArea) {
3838
3917
  if (!r.hostPassengers) return [];
@@ -3843,7 +3922,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
3843
3922
  const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
3844
3923
  return {
3845
3924
  name: t.name,
3846
- dir: mirrorArea ? join12(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
3925
+ dir: mirrorArea ? join13(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
3847
3926
  remote: t.remote ?? (id ? remotes[id] : void 0),
3848
3927
  ...forgeHost ? { forgeHost } : {}
3849
3928
  };
@@ -4121,9 +4200,9 @@ function recoveredFailureHeadlines(o, route2) {
4121
4200
  }
4122
4201
 
4123
4202
  // src/install.ts
4124
- import { dirname as dirname5, join as join13, extname } from "path";
4203
+ import { dirname as dirname6, join as join14, extname } from "path";
4125
4204
  import { homedir as homedir7, userInfo } from "os";
4126
- 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";
4205
+ 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";
4127
4206
  import { spawnSync as spawnSync4 } from "child_process";
4128
4207
  import { createHash as createHash3 } from "crypto";
4129
4208
 
@@ -4135,7 +4214,7 @@ function isCompiledBinary(moduleUrl) {
4135
4214
  // src/install.ts
4136
4215
  function crewInvocation(crewHome) {
4137
4216
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
4138
- return [process.execPath, join13(crewHome, "bin", "crew")];
4217
+ return [process.execPath, join14(crewHome, "bin", "crew")];
4139
4218
  }
4140
4219
  var INTERVAL_SECONDS = 120;
4141
4220
  function labelFor(crewHome, job) {
@@ -4166,29 +4245,29 @@ function schedulerLogFor(crewLog, job) {
4166
4245
  return `${stem}.${suffix}${ext || ".log"}`;
4167
4246
  }
4168
4247
  function pathFor(ship) {
4169
- const nodeBinDir = dirname5(process.execPath);
4248
+ const nodeBinDir = dirname6(process.execPath);
4170
4249
  const parts = [nodeBinDir];
4171
4250
  if (ship.extraPath) parts.push(ship.extraPath);
4172
4251
  parts.push("/usr/bin", "/bin", "/usr/sbin", "/sbin");
4173
4252
  return parts.join(":");
4174
4253
  }
4175
4254
  function findOnPath(bin, searchPath) {
4176
- const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join13(dir, bin));
4255
+ const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join14(dir, bin));
4177
4256
  for (const candidate of candidates2) {
4178
4257
  try {
4179
4258
  accessSync2(candidate, constants2.X_OK);
4180
- if (statSync4(candidate).isFile()) return candidate;
4259
+ if (statSync5(candidate).isFile()) return candidate;
4181
4260
  } catch {
4182
4261
  }
4183
4262
  }
4184
4263
  return void 0;
4185
4264
  }
4186
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join13(homedir7(), ".docker", "bin")].join(":");
4265
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join14(homedir7(), ".docker", "bin")].join(":");
4187
4266
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
4188
4267
  const schedulerPath = pathFor(ship);
4189
4268
  if (findOnPath("docker", schedulerPath)) return void 0;
4190
4269
  const inShell = findOnPath("docker", shellPath);
4191
- 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\``;
4270
+ 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\``;
4192
4271
  }
4193
4272
  var USER_INSTALL = { root: false, user: "" };
4194
4273
  function installContext(env = process.env) {
@@ -4198,7 +4277,7 @@ function installContext(env = process.env) {
4198
4277
  }
4199
4278
  var SYSTEM_UNIT_DIR = "/etc/systemd/system";
4200
4279
  function systemUnitPathFor(label, suffix = "service") {
4201
- return join13(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4280
+ return join14(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4202
4281
  }
4203
4282
  function unitEnvironment(ship) {
4204
4283
  return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
@@ -4219,7 +4298,7 @@ function xmlEscape(s) {
4219
4298
  }
4220
4299
  function planLaunchd(ship, crewHome, job) {
4221
4300
  const label = labelFor(crewHome, job);
4222
- const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4301
+ const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4223
4302
  const crewLog = ship.logFile;
4224
4303
  const schedulerLog = schedulerLogFor(crewLog, job);
4225
4304
  const persistent = job === "run";
@@ -4273,7 +4352,7 @@ ${scheduleKeys}
4273
4352
  }
4274
4353
  function planSystemd(ship, crewHome, job, ctx) {
4275
4354
  const label = labelFor(crewHome, job);
4276
- const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join13(homedir7(), ".config", "systemd", "user");
4355
+ const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
4277
4356
  const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
4278
4357
  const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
4279
4358
  ` : "";
@@ -4282,7 +4361,7 @@ function planSystemd(ship, crewHome, job, ctx) {
4282
4361
  lingerCommand: ["loginctl", "enable-linger", ctx.user],
4283
4362
  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`
4284
4363
  };
4285
- const servicePath = join13(unitDir, `${label}.service`);
4364
+ const servicePath = join14(unitDir, `${label}.service`);
4286
4365
  const crewLog = ship.logFile;
4287
4366
  const schedulerLog = schedulerLogFor(crewLog, job);
4288
4367
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -4323,7 +4402,7 @@ WantedBy=${wantedBy}
4323
4402
  ...linger
4324
4403
  };
4325
4404
  }
4326
- const timerPath = join13(unitDir, `${label}.timer`);
4405
+ const timerPath = join14(unitDir, `${label}.timer`);
4327
4406
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
4328
4407
  [Unit]
4329
4408
  Description=${description}
@@ -4398,7 +4477,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
4398
4477
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4399
4478
  const label = labelFor(crewHome, job);
4400
4479
  if (hostShip === "macos") {
4401
- const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4480
+ const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4402
4481
  return {
4403
4482
  mechanism: "launchd",
4404
4483
  unitPaths: [unitPath],
@@ -4408,20 +4487,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4408
4487
  };
4409
4488
  }
4410
4489
  if (hostShip === "linux" && hasSystemd) {
4411
- const system = existsSync11(systemUnitPathFor(label));
4412
- const unitDir = system ? SYSTEM_UNIT_DIR : join13(homedir7(), ".config", "systemd", "user");
4490
+ const system = existsSync12(systemUnitPathFor(label));
4491
+ const unitDir = system ? SYSTEM_UNIT_DIR : join14(homedir7(), ".config", "systemd", "user");
4413
4492
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
4414
4493
  if (job === "run") {
4415
4494
  return {
4416
4495
  mechanism: "systemd",
4417
- unitPaths: [join13(unitDir, `${label}.service`)],
4496
+ unitPaths: [join14(unitDir, `${label}.service`)],
4418
4497
  crontabLine: null,
4419
4498
  unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
4420
4499
  };
4421
4500
  }
4422
4501
  return {
4423
4502
  mechanism: "systemd",
4424
- unitPaths: [join13(unitDir, `${label}.service`), join13(unitDir, `${label}.timer`)],
4503
+ unitPaths: [join14(unitDir, `${label}.service`), join14(unitDir, `${label}.timer`)],
4425
4504
  crontabLine: null,
4426
4505
  unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
4427
4506
  };
@@ -4439,7 +4518,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4439
4518
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
4440
4519
  const label = labelFor(crewHome, "run");
4441
4520
  if (hostShip === "macos") {
4442
- const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4521
+ const unitPath = join14(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4443
4522
  return {
4444
4523
  mechanism: "launchd",
4445
4524
  startCommand: ["launchctl", "load", unitPath],
@@ -4449,7 +4528,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
4449
4528
  }
4450
4529
  if (hostShip === "linux" && hasSystemd) {
4451
4530
  const unit = `${label}.service`;
4452
- const ctl = existsSync11(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
4531
+ const ctl = existsSync12(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
4453
4532
  return {
4454
4533
  mechanism: "systemd",
4455
4534
  startCommand: [...ctl, "start", unit],
@@ -4481,10 +4560,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
4481
4560
  log.emit(`would write ${path}`);
4482
4561
  continue;
4483
4562
  }
4484
- const before = existsSync11(path) ? readFileSync12(path, "utf8") : null;
4563
+ const before = existsSync12(path) ? readFileSync13(path, "utf8") : null;
4485
4564
  if (before !== plan.unitContent[path]) changed2 = true;
4486
- mkdirSync7(dirname5(path), { recursive: true });
4487
- writeFileSync8(path, plan.unitContent[path]);
4565
+ mkdirSync8(dirname6(path), { recursive: true });
4566
+ writeFileSync9(path, plan.unitContent[path]);
4488
4567
  log.emit(`wrote ${path}`);
4489
4568
  }
4490
4569
  if (plan.crontabLine) {
@@ -4548,7 +4627,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
4548
4627
  }
4549
4628
  }
4550
4629
  for (const path of plan.unitPaths) {
4551
- if (!existsSync11(path)) continue;
4630
+ if (!existsSync12(path)) continue;
4552
4631
  if (dryRun) {
4553
4632
  log.emit(`would remove ${path}`);
4554
4633
  continue;
@@ -4567,7 +4646,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
4567
4646
  }
4568
4647
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
4569
4648
  function launchAgentsDir() {
4570
- return join13(homedir7(), "Library", "LaunchAgents");
4649
+ return join14(homedir7(), "Library", "LaunchAgents");
4571
4650
  }
4572
4651
  function xmlUnescape(s) {
4573
4652
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -4584,25 +4663,25 @@ function loadedCrewLabels() {
4584
4663
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
4585
4664
  }
4586
4665
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
4587
- const files = existsSync11(dir) ? readdirSync3(dir) : [];
4666
+ const files = existsSync12(dir) ? readdirSync3(dir) : [];
4588
4667
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
4589
- const plistPath = join13(dir, f);
4668
+ const plistPath = join14(dir, f);
4590
4669
  let program = null;
4591
4670
  try {
4592
- program = programFromPlist(readFileSync12(plistPath, "utf8"));
4671
+ program = programFromPlist(readFileSync13(plistPath, "utf8"));
4593
4672
  } catch {
4594
4673
  }
4595
4674
  return {
4596
4675
  label: f.slice(0, -".plist".length),
4597
4676
  plistPath,
4598
4677
  program,
4599
- programExists: program !== null && existsSync11(program)
4678
+ programExists: program !== null && existsSync12(program)
4600
4679
  };
4601
4680
  });
4602
4681
  const seen = new Set(units.map((u) => u.label));
4603
4682
  for (const label of [...loaded].sort()) {
4604
4683
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
4605
- units.push({ label, plistPath: join13(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4684
+ units.push({ label, plistPath: join14(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4606
4685
  }
4607
4686
  return units;
4608
4687
  }
@@ -4615,9 +4694,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
4615
4694
  log.emit(`would unload and remove ${unit.label}`);
4616
4695
  return;
4617
4696
  }
4618
- if (existsSync11(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4697
+ if (existsSync12(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4619
4698
  spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
4620
- if (existsSync11(unit.plistPath)) unlinkSync(unit.plistPath);
4699
+ if (existsSync12(unit.plistPath)) unlinkSync(unit.plistPath);
4621
4700
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
4622
4701
  }
4623
4702
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -4695,24 +4774,24 @@ function detectSystemd() {
4695
4774
  }
4696
4775
 
4697
4776
  // src/ship-attention.ts
4698
- import { existsSync as existsSync13, statfsSync } from "fs";
4777
+ import { existsSync as existsSync14, statfsSync } from "fs";
4699
4778
 
4700
4779
  // src/git.ts
4701
- import { execFileSync as execFileSync2 } from "child_process";
4780
+ import { execFileSync as execFileSync3 } from "child_process";
4702
4781
  import {
4703
- existsSync as existsSync12,
4704
- mkdirSync as mkdirSync8,
4782
+ existsSync as existsSync13,
4783
+ mkdirSync as mkdirSync9,
4705
4784
  openSync as openSync2,
4706
4785
  closeSync as closeSync2,
4707
4786
  writeSync,
4708
- statSync as statSync5,
4787
+ statSync as statSync6,
4709
4788
  unlinkSync as unlinkSync2,
4710
- readFileSync as readFileSync13,
4711
- writeFileSync as writeFileSync9,
4789
+ readFileSync as readFileSync14,
4790
+ writeFileSync as writeFileSync10,
4712
4791
  readdirSync as readdirSync4,
4713
4792
  rmSync as rmSync8
4714
4793
  } from "fs";
4715
- import { dirname as dirname6, basename as basename5, join as join14 } from "path";
4794
+ import { dirname as dirname7, basename as basename5, join as join15 } from "path";
4716
4795
  var GitError = class extends Error {
4717
4796
  };
4718
4797
  function streamText(x) {
@@ -4720,7 +4799,7 @@ function streamText(x) {
4720
4799
  }
4721
4800
  function gitRaw(cwd, args) {
4722
4801
  try {
4723
- return execFileSync2("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4802
+ return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4724
4803
  } catch (e) {
4725
4804
  const err3 = e;
4726
4805
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -4729,7 +4808,7 @@ function gitRaw(cwd, args) {
4729
4808
  }
4730
4809
  function git(cwd, args) {
4731
4810
  try {
4732
- return execFileSync2("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4811
+ return execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4733
4812
  } catch (e) {
4734
4813
  const err3 = e;
4735
4814
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -4859,7 +4938,7 @@ function worktreeForNumber(cwd, num) {
4859
4938
  return worktrees(cwd).find((w) => re.test(basename5(w.path))) ?? null;
4860
4939
  }
4861
4940
  var commitBodies = (cwd, range) => gitOk(cwd, ["log", range, "--format=%B"]) ?? "";
4862
- var resolve2 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
4941
+ var resolve3 = (cwd, ref) => gitOk(cwd, ["rev-parse", "-q", "--verify", `${ref}^{commit}`]);
4863
4942
  var countCommits = (cwd, range) => Number.parseInt(gitOk(cwd, ["rev-list", "--count", range]) ?? "0", 10);
4864
4943
  function latestReleaseTag(cwd, pattern = "v*", ref = "HEAD") {
4865
4944
  return gitOk(cwd, ["describe", "--tags", "--abbrev=0", "--match", pattern, ref]);
@@ -4868,7 +4947,7 @@ function firstReleaseTagContaining(cwd, sha, pattern = "v*") {
4868
4947
  const out = gitOk(cwd, ["tag", "--contains", sha, "--list", pattern, "--sort=creatordate"]);
4869
4948
  return out?.split("\n").map((l) => l.trim()).find(Boolean) ?? null;
4870
4949
  }
4871
- var tagCommit = (cwd, tag) => resolve2(cwd, tag);
4950
+ var tagCommit = (cwd, tag) => resolve3(cwd, tag);
4872
4951
  function createReleaseTag(cwd, tag, sha, message) {
4873
4952
  git(cwd, ["tag", "-a", tag, sha, "-m", message]);
4874
4953
  }
@@ -4900,9 +4979,9 @@ function keyCommitsInRange(cwd, key, range) {
4900
4979
  }
4901
4980
  function patchIdOf(cwd, from, to) {
4902
4981
  try {
4903
- const diff = execFileSync2("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
4982
+ const diff = execFileSync3("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
4904
4983
  if (!diff) return null;
4905
- const out = execFileSync2("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
4984
+ const out = execFileSync3("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
4906
4985
  return out.split(" ")[0]?.trim() || null;
4907
4986
  } catch {
4908
4987
  return null;
@@ -5092,7 +5171,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
5092
5171
  if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
5093
5172
  else if (line.startsWith("branch refs/heads/crew/release-") && path) {
5094
5173
  gitOk(cwd, ["worktree", "remove", "--force", path]);
5095
- const root = dirname6(path);
5174
+ const root = dirname7(path);
5096
5175
  if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
5097
5176
  }
5098
5177
  }
@@ -5110,11 +5189,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
5110
5189
  }
5111
5190
  for (const name of names) {
5112
5191
  if (!name.startsWith("crew-release-")) continue;
5113
- const root = join14(tmp, name);
5192
+ const root = join15(tmp, name);
5114
5193
  try {
5115
- const st = statSync5(root);
5194
+ const st = statSync6(root);
5116
5195
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
5117
- if (existsSync12(join14(root, "checkout"))) continue;
5196
+ if (existsSync13(join15(root, "checkout"))) continue;
5118
5197
  rmSync8(root, { recursive: true, force: true });
5119
5198
  removed.push(name);
5120
5199
  } catch {
@@ -5166,16 +5245,16 @@ function originUrlOf(dir, remoteName) {
5166
5245
  }
5167
5246
  }
5168
5247
  function ensureRepoCheckout(dir, remote, opts = {}) {
5169
- if (existsSync12(`${dir}/.git`)) return false;
5248
+ if (existsSync13(`${dir}/.git`)) return false;
5170
5249
  if (!remote) {
5171
5250
  throw new GitError(
5172
5251
  `${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`
5173
5252
  );
5174
5253
  }
5175
- mkdirSync8(dirname6(dir), { recursive: true });
5254
+ mkdirSync9(dirname7(dir), { recursive: true });
5176
5255
  const url = cloneUrlFor(remote, opts);
5177
5256
  try {
5178
- execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5257
+ execFileSync3("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5179
5258
  } catch (e) {
5180
5259
  const err3 = e;
5181
5260
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5197,7 +5276,7 @@ function sleepSync(ms3) {
5197
5276
  }
5198
5277
  function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5199
5278
  const lockPath = checkoutLockPath(dir);
5200
- mkdirSync8(dirname6(lockPath), { recursive: true });
5279
+ mkdirSync9(dirname7(lockPath), { recursive: true });
5201
5280
  const deadline = Date.now() + timeoutMs;
5202
5281
  for (; ; ) {
5203
5282
  try {
@@ -5215,7 +5294,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5215
5294
  const err3 = e;
5216
5295
  if (err3.code !== "EEXIST") throw err3;
5217
5296
  try {
5218
- if (Date.now() - statSync5(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
5297
+ if (Date.now() - statSync6(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
5219
5298
  try {
5220
5299
  unlinkSync2(lockPath);
5221
5300
  } catch {
@@ -5234,17 +5313,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5234
5313
  function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
5235
5314
  const release = acquireCheckoutLock(dir);
5236
5315
  try {
5237
- if (!existsSync12(`${dir}/.git`) && githubRemote) {
5316
+ if (!existsSync13(`${dir}/.git`) && githubRemote) {
5238
5317
  const url = cloneUrlFor(githubRemote, clone);
5239
5318
  const marker = cloneFailureMarker(dir);
5240
- if (existsSync12(marker) && readFileSync13(marker, "utf8").split("\n")[0] === url) {
5241
- return { action: "clone-failed", detail: readFileSync13(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5319
+ if (existsSync13(marker) && readFileSync14(marker, "utf8").split("\n")[0] === url) {
5320
+ return { action: "clone-failed", detail: readFileSync14(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5242
5321
  }
5243
5322
  try {
5244
5323
  ensureRepoCheckout(dir, githubRemote, clone);
5245
5324
  } catch (e) {
5246
5325
  if (!(e instanceof GitError)) throw e;
5247
- writeFileSync9(marker, `${url}
5326
+ writeFileSync10(marker, `${url}
5248
5327
  ${e.message}`);
5249
5328
  return { action: "clone-failed", detail: e.message, repeat: false };
5250
5329
  }
@@ -5470,7 +5549,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
5470
5549
  function reconcileHeldWorktrees(state, repo, log, stillRefused) {
5471
5550
  for (const i of state.shipAttention().previous()) {
5472
5551
  if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
5473
- const gone = !existsSync13(i.held.path);
5552
+ const gone = !existsSync14(i.held.path);
5474
5553
  if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
5475
5554
  clearShipAttention(state, i.key);
5476
5555
  log.emit(
@@ -5598,13 +5677,13 @@ function attentionProblems(items, already) {
5598
5677
  // src/sandbox-image.ts
5599
5678
  import { createHash as createHash6 } from "crypto";
5600
5679
  import { spawn as spawn4 } from "child_process";
5601
- import { execFileSync as execFileSync6 } from "child_process";
5602
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, renameSync as renameSync3, writeFileSync as writeFileSync15, rmSync as rmSync13 } from "fs";
5603
- import { dirname as dirname10, join as join26 } from "path";
5680
+ import { execFileSync as execFileSync7 } from "child_process";
5681
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync22, renameSync as renameSync3, writeFileSync as writeFileSync16, rmSync as rmSync13 } from "fs";
5682
+ import { dirname as dirname11, join as join27 } from "path";
5604
5683
  import { tmpdir as tmpdir3 } from "os";
5605
5684
 
5606
5685
  // src/passenger-containers.ts
5607
- import { execFileSync as execFileSync5 } from "child_process";
5686
+ import { execFileSync as execFileSync6 } from "child_process";
5608
5687
  import { createHash as createHash5 } from "crypto";
5609
5688
 
5610
5689
  // src/sandbox-cache.ts
@@ -5613,32 +5692,32 @@ import { promisify as promisify2 } from "util";
5613
5692
 
5614
5693
  // src/deprovision-host.ts
5615
5694
  import { spawnSync as spawnSync6 } from "child_process";
5616
- import { existsSync as existsSync24, readdirSync as readdirSync11 } from "fs";
5695
+ import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
5617
5696
 
5618
5697
  // src/interrupted.ts
5619
- import { existsSync as existsSync22, readdirSync as readdirSync9, readFileSync as readFileSync20 } from "fs";
5620
- import { join as join24 } from "path";
5698
+ import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
5699
+ import { join as join25 } from "path";
5621
5700
 
5622
5701
  // src/agent.ts
5623
5702
  import { spawnSync as spawnSync5 } from "child_process";
5624
- 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";
5703
+ 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";
5625
5704
  import { StringDecoder } from "string_decoder";
5626
5705
  import { finished } from "stream/promises";
5627
- import { join as join23, dirname as dirname9, basename as basename9 } from "path";
5706
+ import { join as join24, dirname as dirname10, basename as basename9 } from "path";
5628
5707
  import { createHash as createHash4 } from "crypto";
5629
5708
 
5630
5709
  // src/state.ts
5631
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync14, mkdirSync as mkdirSync9, rmSync as rmSync9, statSync as statSync6, readdirSync as readdirSync5 } from "fs";
5632
- import { join as join15 } from "path";
5710
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync10, rmSync as rmSync9, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
5711
+ import { join as join16 } from "path";
5633
5712
  var EPOCH = "1970-01-01T00:00:00.000Z";
5634
5713
  var State = class _State {
5635
5714
  dir;
5636
5715
  constructor(dir) {
5637
5716
  this.dir = dir;
5638
- mkdirSync9(dir, { recursive: true });
5717
+ mkdirSync10(dir, { recursive: true });
5639
5718
  }
5640
5719
  path(...parts) {
5641
- return join15(this.dir, ...parts);
5720
+ return join16(this.dir, ...parts);
5642
5721
  }
5643
5722
  /**
5644
5723
  * The timestamp every "new since last poll" check is measured against.
@@ -5649,14 +5728,14 @@ var State = class _State {
5649
5728
  */
5650
5729
  watermark() {
5651
5730
  try {
5652
- const v = readFileSync14(this.path(".poll-watermark"), "utf8").trim();
5731
+ const v = readFileSync15(this.path(".poll-watermark"), "utf8").trim();
5653
5732
  return v || EPOCH;
5654
5733
  } catch {
5655
5734
  return EPOCH;
5656
5735
  }
5657
5736
  }
5658
5737
  setWatermark(at) {
5659
- writeFileSync10(this.path(".poll-watermark"), `${at}
5738
+ writeFileSync11(this.path(".poll-watermark"), `${at}
5660
5739
  `);
5661
5740
  }
5662
5741
  /**
@@ -5674,16 +5753,16 @@ var State = class _State {
5674
5753
  }
5675
5754
  /** Whole-crew pause, and the per-role sentinels one level down. */
5676
5755
  isPaused() {
5677
- return existsSync14(this.path(".crew-paused"));
5756
+ return existsSync15(this.path(".crew-paused"));
5678
5757
  }
5679
5758
  isRolePaused(role) {
5680
- return existsSync14(this.path(`.role-paused-${role}`));
5759
+ return existsSync15(this.path(`.role-paused-${role}`));
5681
5760
  }
5682
5761
  pausedRoles(roles) {
5683
5762
  return new Set(roles.filter((r) => this.isRolePaused(r)));
5684
5763
  }
5685
5764
  pause(role) {
5686
- writeFileSync10(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5765
+ writeFileSync11(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5687
5766
  }
5688
5767
  resume(role) {
5689
5768
  rmSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
@@ -5694,10 +5773,10 @@ var State = class _State {
5694
5773
  * while agents keep working.
5695
5774
  */
5696
5775
  isReleasePaused() {
5697
- return existsSync14(this.path(".release-paused"));
5776
+ return existsSync15(this.path(".release-paused"));
5698
5777
  }
5699
5778
  setReleasePaused(paused) {
5700
- if (paused) writeFileSync10(this.path(".release-paused"), "");
5779
+ if (paused) writeFileSync11(this.path(".release-paused"), "");
5701
5780
  else rmSync9(this.path(".release-paused"), { force: true });
5702
5781
  }
5703
5782
  /**
@@ -5735,7 +5814,7 @@ var State = class _State {
5735
5814
  const name = _State.safe(rawName);
5736
5815
  const file = this.path(`.${name}.lock`);
5737
5816
  try {
5738
- const existing = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
5817
+ const existing = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
5739
5818
  if (Number.isFinite(existing) && existing > 0) {
5740
5819
  try {
5741
5820
  process.kill(existing, 0);
@@ -5745,7 +5824,7 @@ var State = class _State {
5745
5824
  }
5746
5825
  } catch {
5747
5826
  }
5748
- writeFileSync10(file, `${process.pid}
5827
+ writeFileSync11(file, `${process.pid}
5749
5828
  `);
5750
5829
  return {
5751
5830
  ok: true,
@@ -5773,7 +5852,7 @@ var State = class _State {
5773
5852
  if (!/^\.release-.+\.lock$/.test(n)) continue;
5774
5853
  let pid = 0;
5775
5854
  try {
5776
- pid = Number.parseInt(readFileSync14(this.path(n), "utf8").trim(), 10);
5855
+ pid = Number.parseInt(readFileSync15(this.path(n), "utf8").trim(), 10);
5777
5856
  } catch {
5778
5857
  continue;
5779
5858
  }
@@ -5859,7 +5938,7 @@ var State = class _State {
5859
5938
  ticketInFlight(recordId) {
5860
5939
  const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
5861
5940
  try {
5862
- const pid = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
5941
+ const pid = Number.parseInt(readFileSync15(file, "utf8").trim(), 10);
5863
5942
  if (!Number.isFinite(pid) || pid <= 0) return false;
5864
5943
  process.kill(pid, 0);
5865
5944
  return true;
@@ -5893,7 +5972,7 @@ var State = class _State {
5893
5972
  const heldReported = `.release-held-${suffix}`;
5894
5973
  const read = (f) => {
5895
5974
  try {
5896
- return readFileSync14(this.path(f), "utf8").trim() || null;
5975
+ return readFileSync15(this.path(f), "utf8").trim() || null;
5897
5976
  } catch {
5898
5977
  return null;
5899
5978
  }
@@ -5902,7 +5981,7 @@ var State = class _State {
5902
5981
  blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
5903
5982
  noteBlocked: () => {
5904
5983
  const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
5905
- writeFileSync10(this.path(blocked), `${n}
5984
+ writeFileSync11(this.path(blocked), `${n}
5906
5985
  `);
5907
5986
  return n;
5908
5987
  },
@@ -5919,7 +5998,7 @@ var State = class _State {
5919
5998
  */
5920
5999
  deployFailedSha: () => read(failed),
5921
6000
  noteDeployFailed: (sha) => {
5922
- writeFileSync10(this.path(failed), `${sha}
6001
+ writeFileSync11(this.path(failed), `${sha}
5923
6002
  `);
5924
6003
  },
5925
6004
  clearDeployFailed: () => {
@@ -5933,7 +6012,7 @@ var State = class _State {
5933
6012
  noteTestGateFailed: (sha) => {
5934
6013
  const [prev, n] = (read(gateFailed) ?? "").split(" ");
5935
6014
  const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
5936
- writeFileSync10(this.path(gateFailed), `${sha} ${next}
6015
+ writeFileSync11(this.path(gateFailed), `${sha} ${next}
5937
6016
  `);
5938
6017
  return next;
5939
6018
  },
@@ -5943,13 +6022,13 @@ var State = class _State {
5943
6022
  /** Whether the board was already told about this head's red gate. */
5944
6023
  heldReported: () => {
5945
6024
  try {
5946
- return readFileSync14(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6025
+ return readFileSync15(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
5947
6026
  } catch {
5948
6027
  return null;
5949
6028
  }
5950
6029
  },
5951
6030
  noteHeldReported: (signature) => {
5952
- writeFileSync10(this.path(heldReported), `${signature}
6031
+ writeFileSync11(this.path(heldReported), `${signature}
5953
6032
  `);
5954
6033
  },
5955
6034
  clearHeldReported: () => {
@@ -5957,7 +6036,7 @@ var State = class _State {
5957
6036
  },
5958
6037
  testGateReported: (sha) => read(gateReported) === sha,
5959
6038
  noteTestGateReported: (sha) => {
5960
- writeFileSync10(this.path(gateReported), `${sha}
6039
+ writeFileSync11(this.path(gateReported), `${sha}
5961
6040
  `);
5962
6041
  }
5963
6042
  };
@@ -5980,7 +6059,7 @@ var State = class _State {
5980
6059
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
5981
6060
  const read = (f) => {
5982
6061
  try {
5983
- return readFileSync14(this.path(f), "utf8").trim() || null;
6062
+ return readFileSync15(this.path(f), "utf8").trim() || null;
5984
6063
  } catch {
5985
6064
  return null;
5986
6065
  }
@@ -6013,18 +6092,18 @@ var State = class _State {
6013
6092
  clear2();
6014
6093
  return;
6015
6094
  }
6016
- writeFileSync10(this.path(streakFile), `${this.readInt(streakFile) + 1}
6095
+ writeFileSync11(this.path(streakFile), `${this.readInt(streakFile) + 1}
6017
6096
  `);
6018
6097
  const prior = read(ticketFile);
6019
6098
  if (topTicket && topTicket === prior) {
6020
- writeFileSync10(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6099
+ writeFileSync11(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6021
6100
  `);
6022
6101
  } else if (topTicket) {
6023
- writeFileSync10(this.path(ticketFile), `${topTicket}
6102
+ writeFileSync11(this.path(ticketFile), `${topTicket}
6024
6103
  `);
6025
- writeFileSync10(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6104
+ writeFileSync11(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6026
6105
  `);
6027
- writeFileSync10(this.path(ticketStreakFile), "1\n");
6106
+ writeFileSync11(this.path(ticketStreakFile), "1\n");
6028
6107
  }
6029
6108
  }
6030
6109
  };
@@ -6045,14 +6124,14 @@ var State = class _State {
6045
6124
  return {
6046
6125
  previous: () => {
6047
6126
  try {
6048
- const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
6127
+ const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6049
6128
  return parsed && typeof parsed === "object" ? parsed : {};
6050
6129
  } catch {
6051
6130
  return {};
6052
6131
  }
6053
6132
  },
6054
6133
  persist: (next) => {
6055
- writeFileSync10(this.path(file), JSON.stringify(next));
6134
+ writeFileSync11(this.path(file), JSON.stringify(next));
6056
6135
  }
6057
6136
  };
6058
6137
  }
@@ -6068,14 +6147,14 @@ var State = class _State {
6068
6147
  return {
6069
6148
  previous: () => {
6070
6149
  try {
6071
- const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
6150
+ const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6072
6151
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
6073
6152
  } catch {
6074
6153
  return [];
6075
6154
  }
6076
6155
  },
6077
6156
  persist: (next) => {
6078
- writeFileSync10(this.path(file), JSON.stringify(next));
6157
+ writeFileSync11(this.path(file), JSON.stringify(next));
6079
6158
  }
6080
6159
  };
6081
6160
  }
@@ -6089,14 +6168,14 @@ var State = class _State {
6089
6168
  return {
6090
6169
  previous: () => {
6091
6170
  try {
6092
- const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
6171
+ const parsed = JSON.parse(readFileSync15(this.path(file), "utf8"));
6093
6172
  return Array.isArray(parsed) ? parsed : [];
6094
6173
  } catch {
6095
6174
  return [];
6096
6175
  }
6097
6176
  },
6098
6177
  persist: (next) => {
6099
- writeFileSync10(this.path(file), JSON.stringify(next));
6178
+ writeFileSync11(this.path(file), JSON.stringify(next));
6100
6179
  }
6101
6180
  };
6102
6181
  }
@@ -6109,11 +6188,11 @@ var State = class _State {
6109
6188
  const file = ".release-last-run";
6110
6189
  return {
6111
6190
  touch: () => {
6112
- writeFileSync10(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6191
+ writeFileSync11(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6113
6192
  },
6114
6193
  at: () => {
6115
6194
  try {
6116
- return statSync6(this.path(file)).mtimeMs;
6195
+ return statSync7(this.path(file)).mtimeMs;
6117
6196
  } catch {
6118
6197
  return null;
6119
6198
  }
@@ -6122,7 +6201,7 @@ var State = class _State {
6122
6201
  }
6123
6202
  readInt(file) {
6124
6203
  try {
6125
- return Number.parseInt(readFileSync14(this.path(file), "utf8").trim(), 10) || 0;
6204
+ return Number.parseInt(readFileSync15(this.path(file), "utf8").trim(), 10) || 0;
6126
6205
  } catch {
6127
6206
  return 0;
6128
6207
  }
@@ -6663,8 +6742,8 @@ var PERISCOPE_COLUMNS = [
6663
6742
  ];
6664
6743
 
6665
6744
  // src/agent-log.ts
6666
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
6667
- import { join as join16 } from "path";
6745
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
6746
+ import { join as join17 } from "path";
6668
6747
 
6669
6748
  // src/pending-backoff.ts
6670
6749
  var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
@@ -6836,20 +6915,20 @@ async function addRunToTicket(target, t, recordId, run3) {
6836
6915
  };
6837
6916
  }
6838
6917
  }
6839
- var queueDir = (stateDir) => join16(stateDir, "pending-agent-logs");
6918
+ var queueDir = (stateDir) => join17(stateDir, "pending-agent-logs");
6840
6919
  function queueAgentRun(stateDir, name, q2) {
6841
- mkdirSync10(queueDir(stateDir), { recursive: true });
6842
- writeFileSync11(join16(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
6920
+ mkdirSync11(queueDir(stateDir), { recursive: true });
6921
+ writeFileSync12(join17(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
6843
6922
  }
6844
6923
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6845
6924
  const dir = queueDir(stateDir);
6846
- if (!existsSync15(dir)) return 0;
6925
+ if (!existsSync16(dir)) return 0;
6847
6926
  let sent = 0;
6848
6927
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6849
- const path = join16(dir, f);
6928
+ const path = join17(dir, f);
6850
6929
  let q2;
6851
6930
  try {
6852
- q2 = JSON.parse(readFileSync15(path, "utf8"));
6931
+ q2 = JSON.parse(readFileSync16(path, "utf8"));
6853
6932
  } catch {
6854
6933
  continue;
6855
6934
  }
@@ -6878,7 +6957,7 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6878
6957
  continue;
6879
6958
  }
6880
6959
  const attempts = q2.attempts + 1;
6881
- writeFileSync11(path, JSON.stringify({
6960
+ writeFileSync12(path, JSON.stringify({
6882
6961
  ...q2,
6883
6962
  attempts,
6884
6963
  warned: true,
@@ -6889,20 +6968,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6889
6968
  }
6890
6969
  return sent;
6891
6970
  }
6892
- var countersDir = (stateDir) => join16(stateDir, "pending-ticket-counters");
6971
+ var countersDir = (stateDir) => join17(stateDir, "pending-ticket-counters");
6893
6972
  function queueTicketCounters(stateDir, name, q2) {
6894
- mkdirSync10(countersDir(stateDir), { recursive: true });
6895
- writeFileSync11(join16(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
6973
+ mkdirSync11(countersDir(stateDir), { recursive: true });
6974
+ writeFileSync12(join17(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
6896
6975
  }
6897
6976
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6898
6977
  const dir = countersDir(stateDir);
6899
- if (!existsSync15(dir)) return 0;
6978
+ if (!existsSync16(dir)) return 0;
6900
6979
  let sent = 0;
6901
6980
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6902
- const path = join16(dir, f);
6981
+ const path = join17(dir, f);
6903
6982
  let q2;
6904
6983
  try {
6905
- q2 = JSON.parse(readFileSync15(path, "utf8"));
6984
+ q2 = JSON.parse(readFileSync16(path, "utf8"));
6906
6985
  } catch {
6907
6986
  continue;
6908
6987
  }
@@ -6921,7 +7000,7 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
6921
7000
  continue;
6922
7001
  }
6923
7002
  const attempts = q2.attempts + 1;
6924
- writeFileSync11(path, JSON.stringify({
7003
+ writeFileSync12(path, JSON.stringify({
6925
7004
  ...q2,
6926
7005
  attempts,
6927
7006
  warned: true,
@@ -7124,8 +7203,8 @@ var HandoffTranscriptTracker = class {
7124
7203
  };
7125
7204
 
7126
7205
  // src/pending-handoff.ts
7127
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "fs";
7128
- import { join as join19 } from "path";
7206
+ import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync7, readFileSync as readFileSync17, rmSync as rmSync11, writeFileSync as writeFileSync13 } from "fs";
7207
+ import { join as join20 } from "path";
7129
7208
 
7130
7209
  // src/ticket-branch.ts
7131
7210
  import { basename as basename7 } from "path";
@@ -8291,16 +8370,16 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
8291
8370
  }
8292
8371
 
8293
8372
  // src/run-worktree.ts
8294
- import { existsSync as existsSync17 } from "fs";
8295
- import { dirname as dirname7, join as join18 } from "path";
8373
+ import { existsSync as existsSync18 } from "fs";
8374
+ import { dirname as dirname8, join as join19 } from "path";
8296
8375
 
8297
8376
  // src/ports.ts
8298
- import { execFileSync as execFileSync3 } from "child_process";
8299
- import { existsSync as existsSync16 } from "fs";
8300
- import { join as join17 } from "path";
8377
+ import { execFileSync as execFileSync4 } from "child_process";
8378
+ import { existsSync as existsSync17 } from "fs";
8379
+ import { join as join18 } from "path";
8301
8380
  var run = (cmd, args) => {
8302
8381
  try {
8303
- return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8382
+ return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8304
8383
  } catch {
8305
8384
  return "";
8306
8385
  }
@@ -8342,7 +8421,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
8342
8421
  }
8343
8422
  function worktreeExists(parent, prefix, n) {
8344
8423
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
8345
- if (existsSync16(join17(parent, name, ".git"))) return true;
8424
+ if (existsSync17(join18(parent, name, ".git"))) return true;
8346
8425
  }
8347
8426
  return false;
8348
8427
  }
@@ -8425,7 +8504,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8425
8504
  const existing = worktreeForNumber(dir, num);
8426
8505
  const located = locateBranchForTicket(dir, cfg, t, role);
8427
8506
  const baseRef = hasRemote ? `${remote}/${base}` : base;
8428
- if (existing && !existing.prunable && existsSync17(existing.path)) {
8507
+ if (existing && !existing.prunable && existsSync18(existing.path)) {
8429
8508
  const branch = existing.branch;
8430
8509
  if (status(existing.path).length > 0) {
8431
8510
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -8441,7 +8520,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8441
8520
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
8442
8521
  return { action: "recut", path: existing.path, branch, base: baseRef };
8443
8522
  }
8444
- const path = join18(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
8523
+ const path = join19(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
8445
8524
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
8446
8525
  return { action: "cut-remote", path, branch: located.branch };
8447
8526
  }
@@ -8495,7 +8574,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
8495
8574
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
8496
8575
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
8497
8576
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8498
- if (wt && existsSync17(wt.path)) {
8577
+ if (wt && existsSync18(wt.path)) {
8499
8578
  if (status(wt.path).length > 0) return false;
8500
8579
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
8501
8580
  }
@@ -8503,7 +8582,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
8503
8582
  }
8504
8583
  async function finishRunWorktree(dir, cfg, branch) {
8505
8584
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
8506
- if (!wt || !existsSync17(wt.path)) return { kind: "none" };
8585
+ if (!wt || !existsSync18(wt.path)) return { kind: "none" };
8507
8586
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
8508
8587
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
8509
8588
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -8515,7 +8594,7 @@ async function finishRunWorktree(dir, cfg, branch) {
8515
8594
  if (pids.length) await killGently(pids);
8516
8595
  }
8517
8596
  gitOk(dir, ["worktree", "remove", wt.path]);
8518
- return existsSync17(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8597
+ return existsSync18(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
8519
8598
  }
8520
8599
 
8521
8600
  // src/handoff-guard.ts
@@ -8898,20 +8977,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
8898
8977
 
8899
8978
  // src/pending-handoff.ts
8900
8979
  var PENDING_MAX_AGE_MS = 48 * 36e5;
8901
- var pendingDirFor = (stateDir) => join19(stateDir, "pending-handoffs");
8902
- var stateDirOfStream = (streamPath) => join19(streamPath, "..", "..");
8980
+ var pendingDirFor = (stateDir) => join20(stateDir, "pending-handoffs");
8981
+ var stateDirOfStream = (streamPath) => join20(streamPath, "..", "..");
8903
8982
  function writePendingHandoff(stateDir, rec) {
8904
8983
  const dir = pendingDirFor(stateDir);
8905
- mkdirSync11(dir, { recursive: true });
8906
- writeFileSync12(join19(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
8984
+ mkdirSync12(dir, { recursive: true });
8985
+ writeFileSync13(join20(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
8907
8986
  }
8908
8987
  function listPendingHandoffs(stateDir, route2) {
8909
8988
  const dir = pendingDirFor(stateDir);
8910
- if (!existsSync18(dir)) return [];
8989
+ if (!existsSync19(dir)) return [];
8911
8990
  const out = [];
8912
8991
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
8913
8992
  try {
8914
- const rec = JSON.parse(readFileSync16(join19(dir, f), "utf8"));
8993
+ const rec = JSON.parse(readFileSync17(join20(dir, f), "utf8"));
8915
8994
  if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
8916
8995
  } catch {
8917
8996
  }
@@ -8919,7 +8998,7 @@ function listPendingHandoffs(stateDir, route2) {
8919
8998
  return out;
8920
8999
  }
8921
9000
  function removePendingHandoff(stateDir, ticketRecordId) {
8922
- rmSync11(join19(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9001
+ rmSync11(join20(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
8923
9002
  }
8924
9003
  function pendingHandoffFor(stateDir, ticketRecordId) {
8925
9004
  return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
@@ -9029,8 +9108,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
9029
9108
  }
9030
9109
 
9031
9110
  // src/skills.ts
9032
- import { readFileSync as readFileSync18, readdirSync as readdirSync8, existsSync as existsSync20 } from "fs";
9033
- import { join as join21 } from "path";
9111
+ import { readFileSync as readFileSync19, readdirSync as readdirSync8, existsSync as existsSync21 } from "fs";
9112
+ import { join as join22 } from "path";
9034
9113
  import { parse as parseYaml } from "yaml";
9035
9114
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
9036
9115
 
@@ -9046,8 +9125,8 @@ ${additions}`;
9046
9125
  }
9047
9126
 
9048
9127
  // src/agents.ts
9049
- import { readFileSync as readFileSync17, existsSync as existsSync19 } from "fs";
9050
- import { join as join20 } from "path";
9128
+ import { readFileSync as readFileSync18, existsSync as existsSync20 } from "fs";
9129
+ import { join as join21 } from "path";
9051
9130
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
9052
9131
  var PERSONA_NAME = {
9053
9132
  dev: "Developer",
@@ -9061,12 +9140,12 @@ var CREW_AGENT_COLUMN = "agent_id";
9061
9140
  var AgentsSyncError = class extends Error {
9062
9141
  };
9063
9142
  function personaDefaultPrompt(promptsDir, role) {
9064
- const briefPath = join20(promptsDir, "personas", `lane-${role}.md`);
9065
- if (!existsSync19(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9066
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync17(briefPath, "utf8");
9067
- const commonPath = join20(promptsDir, "personas", "common.md");
9068
- if (!existsSync19(commonPath)) return readFileSync17(briefPath, "utf8");
9069
- return `${readFileSync17(commonPath, "utf8")}${readFileSync17(briefPath, "utf8")}`;
9143
+ const briefPath = join21(promptsDir, "personas", `lane-${role}.md`);
9144
+ if (!existsSync20(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9145
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync18(briefPath, "utf8");
9146
+ const commonPath = join21(promptsDir, "personas", "common.md");
9147
+ if (!existsSync20(commonPath)) return readFileSync18(briefPath, "utf8");
9148
+ return `${readFileSync18(commonPath, "utf8")}${readFileSync18(briefPath, "utf8")}`;
9070
9149
  }
9071
9150
  async function resolveAgentId(route2, role, opts) {
9072
9151
  const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
@@ -9513,7 +9592,7 @@ function describeCrewLink(o) {
9513
9592
  var SkillSyncError = class extends Error {
9514
9593
  };
9515
9594
  function parseSkillFile(path) {
9516
- const text2 = readFileSync18(path, "utf8");
9595
+ const text2 = readFileSync19(path, "utf8");
9517
9596
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
9518
9597
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
9519
9598
  const frontmatter = match[1];
@@ -9526,9 +9605,9 @@ function parseSkillFile(path) {
9526
9605
  return { name: meta.name, description: meta.description, prompt: body, file: path };
9527
9606
  }
9528
9607
  function listLocalSkills(promptsDir) {
9529
- const dir = join21(promptsDir, "skills");
9530
- if (!existsSync20(dir)) return [];
9531
- return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join21(dir, f)));
9608
+ const dir = join22(promptsDir, "skills");
9609
+ if (!existsSync21(dir)) return [];
9610
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join22(dir, f)));
9532
9611
  }
9533
9612
  async function syncSkills(route2, opts) {
9534
9613
  if (!route2.resolved) {
@@ -9712,10 +9791,10 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
9712
9791
  }
9713
9792
 
9714
9793
  // src/runner.ts
9715
- import { spawn as spawn2, execFile, execFileSync as execFileSync4 } from "child_process";
9716
- import { mkdtempSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, rmSync as rmSync12 } from "fs";
9794
+ import { spawn as spawn2, execFile, execFileSync as execFileSync5 } from "child_process";
9795
+ import { mkdtempSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, rmSync as rmSync12 } from "fs";
9717
9796
  import { tmpdir as tmpdir2 } from "os";
9718
- import { basename as basename8, dirname as dirname8, join as join22 } from "path";
9797
+ import { basename as basename8, dirname as dirname9, join as join23 } from "path";
9719
9798
  import { promisify } from "util";
9720
9799
 
9721
9800
  // src/run-limit.ts
@@ -9844,8 +9923,8 @@ var SandboxRunner = class {
9844
9923
  throw new Error(`cannot generate the sandbox policy: ${e.message}`);
9845
9924
  }
9846
9925
  policyFile = policyPathFor(plan.streamPath);
9847
- mkdirSync12(dirname8(policyFile), { recursive: true });
9848
- writeFileSync13(policyFile, generated.yaml, { mode: 384 });
9926
+ mkdirSync13(dirname9(policyFile), { recursive: true });
9927
+ writeFileSync14(policyFile, generated.yaml, { mode: 384 });
9849
9928
  }
9850
9929
  const create2 = ["sandbox", "create", "--name", name, "--detach"];
9851
9930
  if (policyFile) create2.push("--policy", policyFile);
@@ -9888,10 +9967,10 @@ var SandboxRunner = class {
9888
9967
  const harness = cfg.agentBin ?? basename8(plan.bin);
9889
9968
  const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
9890
9969
  if (viaUpload) {
9891
- const tmp = mkdtempSync(join22(tmpdir2(), "crew-prompt-"));
9970
+ const tmp = mkdtempSync(join23(tmpdir2(), "crew-prompt-"));
9892
9971
  try {
9893
- const file = join22(tmp, "prompt.txt");
9894
- writeFileSync13(file, plan.prompt, { mode: 384 });
9972
+ const file = join23(tmp, "prompt.txt");
9973
+ writeFileSync14(file, plan.prompt, { mode: 384 });
9895
9974
  await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
9896
9975
  } finally {
9897
9976
  rmSync12(tmp, { recursive: true, force: true });
@@ -9957,7 +10036,7 @@ function describeExecError(e) {
9957
10036
  }
9958
10037
  function gitOrigin(cwd) {
9959
10038
  try {
9960
- return execFileSync4("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
10039
+ return execFileSync5("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
9961
10040
  } catch {
9962
10041
  return null;
9963
10042
  }
@@ -9968,7 +10047,7 @@ function runnerFor(kind, sandbox) {
9968
10047
  function runnerOfSidecar(sc) {
9969
10048
  return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
9970
10049
  }
9971
- function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync4(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
10050
+ function checkSandboxRunner(cfg = {}, run3 = (b, a) => execFileSync5(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 })) {
9972
10051
  const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
9973
10052
  let version;
9974
10053
  try {
@@ -10035,7 +10114,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
10035
10114
  function streamShowsAllowanceRefusal(streamPath) {
10036
10115
  let raw;
10037
10116
  try {
10038
- raw = readFileSync19(streamPath, "utf8");
10117
+ raw = readFileSync20(streamPath, "utf8");
10039
10118
  } catch {
10040
10119
  return false;
10041
10120
  }
@@ -10058,26 +10137,26 @@ function streamShowsAllowanceRefusal(streamPath) {
10058
10137
  }
10059
10138
  return false;
10060
10139
  }
10061
- var digestPathFor = (stateDir, routeLabel, role, runId) => join23(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10140
+ var digestPathFor = (stateDir, routeLabel, role, runId) => join24(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10062
10141
  function planAgentRun(o) {
10063
- const briefPath = join23(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10064
- if (!existsSync21(briefPath)) {
10142
+ const briefPath = join24(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10143
+ if (!existsSync22(briefPath)) {
10065
10144
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
10066
10145
  }
10067
- const commonPath = join23(o.route.promptsDir, "personas", "common.md");
10068
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync21(commonPath);
10146
+ const commonPath = join24(o.route.promptsDir, "personas", "common.md");
10147
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync22(commonPath);
10069
10148
  const now = o.now ?? (() => Date.now());
10070
10149
  const routeLabel = routeSlug(o.route.route);
10071
10150
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
10072
- const digestPath2 = ownDigest && existsSync21(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10151
+ const digestPath2 = ownDigest && existsSync22(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10073
10152
  let digest;
10074
10153
  let ageSeconds;
10075
- if (existsSync21(digestPath2)) {
10076
- ageSeconds = Math.floor((now() - statSync7(digestPath2).mtimeMs) / 1e3);
10077
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync19(digestPath2, "utf8");
10154
+ if (existsSync22(digestPath2)) {
10155
+ ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
10156
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync20(digestPath2, "utf8");
10078
10157
  }
10079
- const common = usesCommon ? readFileSync19(commonPath, "utf8") : "";
10080
- const brief = readFileSync19(briefPath, "utf8");
10158
+ const common = usesCommon ? readFileSync20(commonPath, "utf8") : "";
10159
+ const brief = readFileSync20(briefPath, "utf8");
10081
10160
  const effectiveCommon = o.divergedPrompt ? "" : common;
10082
10161
  const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
10083
10162
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -10100,7 +10179,7 @@ function planAgentRun(o) {
10100
10179
  digest
10101
10180
  });
10102
10181
  const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
10103
- const streamsDir = join23(o.stateDir, "streams");
10182
+ const streamsDir = join24(o.stateDir, "streams");
10104
10183
  const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
10105
10184
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
10106
10185
  const { model } = seat;
@@ -10190,8 +10269,8 @@ function planAgentRun(o) {
10190
10269
  digestAttached: digest !== void 0,
10191
10270
  digestAgeSeconds: ageSeconds,
10192
10271
  model,
10193
- streamPath: join23(streamsDir, `${base}.jsonl`),
10194
- eventsPath: join23(streamsDir, `${base}.events.jsonl`),
10272
+ streamPath: join24(streamsDir, `${base}.jsonl`),
10273
+ eventsPath: join24(streamsDir, `${base}.events.jsonl`),
10195
10274
  ticket: o.ticket,
10196
10275
  ticketRecordId: o.ticketRecordId ?? null,
10197
10276
  repo: o.repo ?? null,
@@ -10222,7 +10301,7 @@ function describePlan(p) {
10222
10301
  var pidPathFor = (streamPath) => `${streamPath}.pid`;
10223
10302
  function readRunSidecar(streamPath) {
10224
10303
  try {
10225
- const sc = JSON.parse(readFileSync19(pidPathFor(streamPath), "utf8"));
10304
+ const sc = JSON.parse(readFileSync20(pidPathFor(streamPath), "utf8"));
10226
10305
  return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
10227
10306
  } catch {
10228
10307
  return null;
@@ -10269,7 +10348,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
10269
10348
  var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
10270
10349
  function openSink(path) {
10271
10350
  try {
10272
- mkdirSync13(dirname9(path), { recursive: true });
10351
+ mkdirSync14(dirname10(path), { recursive: true });
10273
10352
  return createWriteStream(path, { flags: "a" });
10274
10353
  } catch {
10275
10354
  return void 0;
@@ -10451,7 +10530,7 @@ var RunObserver = class {
10451
10530
  };
10452
10531
  function writeRunMeta(id, startedAt, liveTicket, extra) {
10453
10532
  try {
10454
- writeFileSync14(`${id.streamPath}.meta.json`, JSON.stringify({
10533
+ writeFileSync15(`${id.streamPath}.meta.json`, JSON.stringify({
10455
10534
  route: id.route,
10456
10535
  role: id.role,
10457
10536
  hint: id.ticket,
@@ -10611,7 +10690,7 @@ function spawnAgent(plan, emit, hintMoved) {
10611
10690
  const id = identityOf(plan, adapter.harness);
10612
10691
  let streamFd;
10613
10692
  try {
10614
- mkdirSync13(dirname9(plan.streamPath), { recursive: true });
10693
+ mkdirSync14(dirname10(plan.streamPath), { recursive: true });
10615
10694
  streamFd = openSync3(plan.streamPath, "a");
10616
10695
  } catch (e) {
10617
10696
  void endSink(eventsSink);
@@ -10643,7 +10722,7 @@ function spawnAgent(plan, emit, hintMoved) {
10643
10722
  closeSync3(streamFd);
10644
10723
  return supervise(begun);
10645
10724
  function supervise({ child, handle, promptDelivered }) {
10646
- return new Promise((resolve7, reject) => {
10725
+ return new Promise((resolve8, reject) => {
10647
10726
  const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
10648
10727
  const deadline = deadlineFor(spawnedAt, plan.maxRunMinutes);
10649
10728
  if (child.pid !== void 0) {
@@ -10669,7 +10748,7 @@ function spawnAgent(plan, emit, hintMoved) {
10669
10748
  promptSha: plan.promptSha
10670
10749
  };
10671
10750
  try {
10672
- writeFileSync14(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
10751
+ writeFileSync15(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
10673
10752
  } catch {
10674
10753
  }
10675
10754
  }
@@ -10713,7 +10792,7 @@ function spawnAgent(plan, emit, hintMoved) {
10713
10792
  plan.agentLog?.apiKey,
10714
10793
  hintMoved
10715
10794
  );
10716
- resolve7({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
10795
+ resolve8({ code: code ?? 1, ms: ms3, ...handoffPending ? { handoffPending } : {}, ...killed ? { killed } : {} });
10717
10796
  });
10718
10797
  child.stdin?.on("error", (err3) => {
10719
10798
  emit.warn(`agent closed stdin before the prompt was delivered: ${err3.message}`, { step: "agent" });
@@ -10783,21 +10862,21 @@ var pidAlive = (pid) => {
10783
10862
  };
10784
10863
  function runFinished(streamPath) {
10785
10864
  const path = `${streamPath}.meta.json`;
10786
- if (!existsSync22(path)) return false;
10865
+ if (!existsSync23(path)) return false;
10787
10866
  try {
10788
- return JSON.parse(readFileSync20(path, "utf8")).running !== true;
10867
+ return JSON.parse(readFileSync21(path, "utf8")).running !== true;
10789
10868
  } catch {
10790
10869
  return true;
10791
10870
  }
10792
10871
  }
10793
10872
  function orphanedStreams(o) {
10794
- const dir = join24(o.stateDir, "streams");
10795
- if (!existsSync22(dir)) return [];
10873
+ const dir = join25(o.stateDir, "streams");
10874
+ if (!existsSync23(dir)) return [];
10796
10875
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
10797
10876
  const out = [];
10798
10877
  for (const name of readdirSync9(dir)) {
10799
10878
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10800
- const streamPath = join24(dir, name);
10879
+ const streamPath = join25(dir, name);
10801
10880
  if (runFinished(streamPath)) continue;
10802
10881
  const sidecar = readRunSidecar(streamPath);
10803
10882
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -10850,15 +10929,15 @@ async function reconcileRuns(o) {
10850
10929
  }
10851
10930
 
10852
10931
  // src/live-runs.ts
10853
- import { existsSync as existsSync23, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
10854
- import { join as join25 } from "path";
10932
+ import { existsSync as existsSync24, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
10933
+ import { join as join26 } from "path";
10855
10934
  function liveRuns(stateDir, isAlive = runIsAlive) {
10856
- const dir = join25(stateDir, "streams");
10857
- if (!existsSync23(dir)) return [];
10935
+ const dir = join26(stateDir, "streams");
10936
+ if (!existsSync24(dir)) return [];
10858
10937
  const out = [];
10859
10938
  for (const name of readdirSync10(dir)) {
10860
10939
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10861
- const streamPath = join25(dir, name);
10940
+ const streamPath = join26(dir, name);
10862
10941
  if (runFinished(streamPath)) continue;
10863
10942
  const sidecar = readRunSidecar(streamPath);
10864
10943
  if (!sidecar || !isAlive(sidecar)) continue;
@@ -10868,15 +10947,15 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
10868
10947
  }
10869
10948
  function runActivity(stateDir, isAlive = runIsAlive) {
10870
10949
  const activeRuns = liveRuns(stateDir, isAlive).length;
10871
- const dir = join25(stateDir, "streams");
10950
+ const dir = join26(stateDir, "streams");
10872
10951
  let newest = 0;
10873
- if (existsSync23(dir)) {
10952
+ if (existsSync24(dir)) {
10874
10953
  for (const name of readdirSync10(dir)) {
10875
10954
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10876
- const streamPath = join25(dir, name);
10955
+ const streamPath = join26(dir, name);
10877
10956
  if (!runFinished(streamPath)) continue;
10878
10957
  try {
10879
- newest = Math.max(newest, statSync8(`${streamPath}.meta.json`).mtimeMs);
10958
+ newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
10880
10959
  } catch {
10881
10960
  }
10882
10961
  }
@@ -11244,7 +11323,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11244
11323
  if (host === "windows") return [];
11245
11324
  const ctx = installContext();
11246
11325
  const systemd = detectSystemd();
11247
- return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync24(p));
11326
+ return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync25(p));
11248
11327
  };
11249
11328
  return {
11250
11329
  liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
@@ -11305,7 +11384,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11305
11384
  },
11306
11385
  remaining() {
11307
11386
  const left = [];
11308
- if (existsSync24(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11387
+ if (existsSync25(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11309
11388
  for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
11310
11389
  try {
11311
11390
  for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
@@ -11580,7 +11659,7 @@ function makeCachePass(o) {
11580
11659
  // src/passenger-containers.ts
11581
11660
  var realExec2 = (cmd, args, opts) => {
11582
11661
  try {
11583
- return execFileSync5(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
11662
+ return execFileSync6(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
11584
11663
  } catch (e) {
11585
11664
  const err3 = e;
11586
11665
  const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
@@ -11860,7 +11939,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
11860
11939
  var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
11861
11940
  function readWarmFiles(dir, ref, patterns) {
11862
11941
  if (patterns.length === 0) return {};
11863
- const git2 = (args) => execFileSync6("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
11942
+ const git2 = (args) => execFileSync7("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
11864
11943
  const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
11865
11944
  const res = patterns.map(globToRegExp);
11866
11945
  const out = {};
@@ -11913,19 +11992,19 @@ function planSandbox(repo, decl, warm = {}) {
11913
11992
  const layer = repoLayer(repo, decl, crew, warm);
11914
11993
  return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
11915
11994
  }
11916
- var sandboxStatePath = (stateDir) => join26(stateDir, "sandbox-images.json");
11995
+ var sandboxStatePath = (stateDir) => join27(stateDir, "sandbox-images.json");
11917
11996
  function readSandboxState(stateDir) {
11918
11997
  try {
11919
- const v = JSON.parse(readFileSync21(sandboxStatePath(stateDir), "utf8"));
11998
+ const v = JSON.parse(readFileSync22(sandboxStatePath(stateDir), "utf8"));
11920
11999
  return v && typeof v === "object" && !Array.isArray(v) ? v : {};
11921
12000
  } catch {
11922
12001
  return {};
11923
12002
  }
11924
12003
  }
11925
12004
  function writeSandboxState(stateDir, state) {
11926
- mkdirSync14(stateDir, { recursive: true });
12005
+ mkdirSync15(stateDir, { recursive: true });
11927
12006
  const file = sandboxStatePath(stateDir);
11928
- writeFileSync15(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12007
+ writeFileSync16(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
11929
12008
  `);
11930
12009
  renameSync3(`${file}.tmp`, file);
11931
12010
  }
@@ -11939,15 +12018,15 @@ function sandboxImageStatus(state, plan) {
11939
12018
  if (current.tag !== plan.tag) return { status: "stale", detail: `running ${current.tag} (claude ${current.claude ?? "unknown"}), declaration now builds ${plan.tag} (claude ${plan.claude})` };
11940
12019
  return { status: "ok", detail: `${current.tag} built ${current.builtAt}, claude ${current.claude ?? "unknown"}` };
11941
12020
  }
11942
- var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((resolve7) => {
12021
+ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((resolve8) => {
11943
12022
  const child = spawn4(engine, args, { stdio: ["ignore", "pipe", "pipe"] });
11944
12023
  const chunks = [];
11945
12024
  child.stdout.on("data", (d) => chunks.push(d));
11946
12025
  child.stderr.on("data", (d) => chunks.push(d));
11947
12026
  const done = (code, extra = "") => {
11948
- writeFileSync15(logPath, `$ ${engine} ${args.join(" ")}
12027
+ writeFileSync16(logPath, `$ ${engine} ${args.join(" ")}
11949
12028
  ${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
11950
- resolve7(code);
12029
+ resolve8(code);
11951
12030
  };
11952
12031
  child.on("error", (e) => done(127, `${e.message}
11953
12032
  `));
@@ -11967,7 +12046,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
11967
12046
  } catch {
11968
12047
  }
11969
12048
  };
11970
- var sandboxLogDir = (stateDir) => join26(stateDir, "sandbox-logs");
12049
+ var sandboxLogDir = (stateDir) => join27(stateDir, "sandbox-logs");
11971
12050
  async function buildSandboxImage(plan, deps) {
11972
12051
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
11973
12052
  const state = readSandboxState(deps.stateDir);
@@ -11976,20 +12055,20 @@ async function buildSandboxImage(plan, deps) {
11976
12055
  return { kind: "reused", tag: plan.tag };
11977
12056
  }
11978
12057
  const logDir = sandboxLogDir(deps.stateDir);
11979
- mkdirSync14(logDir, { recursive: true });
11980
- const logPath = join26(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12058
+ mkdirSync15(logDir, { recursive: true });
12059
+ const logPath = join27(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
11981
12060
  const builtLayers = [];
11982
12061
  for (const layer of plan.layers) {
11983
12062
  if (deps.hasImage(layer.tag)) continue;
11984
- const ctx = join26(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
11985
- mkdirSync14(ctx, { recursive: true });
12063
+ const ctx = join27(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12064
+ mkdirSync15(ctx, { recursive: true });
11986
12065
  try {
11987
- writeFileSync15(join26(ctx, "Dockerfile"), layer.dockerfile);
12066
+ writeFileSync16(join27(ctx, "Dockerfile"), layer.dockerfile);
11988
12067
  for (const [name, content] of Object.entries(layer.files)) {
11989
- mkdirSync14(dirname10(join26(ctx, name)), { recursive: true });
11990
- writeFileSync15(join26(ctx, name), content);
12068
+ mkdirSync15(dirname11(join27(ctx, name)), { recursive: true });
12069
+ writeFileSync16(join27(ctx, name), content);
11991
12070
  }
11992
- const code = await deps.run(["build", "--tag", layer.tag, "--file", join26(ctx, "Dockerfile"), ctx], logPath);
12071
+ const code = await deps.run(["build", "--tag", layer.tag, "--file", join27(ctx, "Dockerfile"), ctx], logPath);
11993
12072
  if (code !== 0) {
11994
12073
  state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
11995
12074
  writeSandboxState(deps.stateDir, state);
@@ -12042,10 +12121,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
12042
12121
  }
12043
12122
 
12044
12123
  // src/doctor-checks.ts
12045
- import { execFileSync as execFileSync7 } from "child_process";
12124
+ import { execFileSync as execFileSync8 } from "child_process";
12046
12125
  var PROBE_BRANCH = "crew-probe-branch";
12047
12126
  var realGit = (cwd, args, env) => {
12048
- execFileSync7("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
12127
+ execFileSync8("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
12049
12128
  };
12050
12129
  function isSshRemote(url) {
12051
12130
  return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
@@ -12125,10 +12204,10 @@ function formatPushAuth(repo, r) {
12125
12204
  return `${label}FAILED \u2014 ${r.detail}; ${r.hint}
12126
12205
  `;
12127
12206
  }
12128
- async function settleRepos(items, nameOf, resolve7) {
12207
+ async function settleRepos(items, nameOf, resolve8) {
12129
12208
  const out = await Promise.all(items.map(async (t) => {
12130
12209
  try {
12131
- return { ok: true, value: await resolve7(t) };
12210
+ return { ok: true, value: await resolve8(t) };
12132
12211
  } catch (e) {
12133
12212
  return { ok: false, repo: nameOf(t), detail: e.message };
12134
12213
  }
@@ -12164,17 +12243,28 @@ function roleCapWarnings(ship) {
12164
12243
  if (!ship.maxConcurrentAgentsSet) return [];
12165
12244
  return Object.entries(ship.roleConcurrency).filter(([, n]) => (n ?? 0) > ship.maxConcurrentAgents).map(([role, n]) => `ship.roleConcurrency.${role} is ${n}, above ship.maxConcurrentAgents (${ship.maxConcurrentAgents}); the ship-wide cap wins`);
12166
12245
  }
12246
+ function managedRuntimeWarnings(kind, ship) {
12247
+ if (kind !== "managed_instance" && kind !== "pooled_instance" && kind !== "pooled") return [];
12248
+ const out = [];
12249
+ const explicit = ship.toolchains ?? [];
12250
+ const effectiveToolchains = explicit.some((t) => t.name === "claude") ? explicit : [{ name: "claude", harness: "claude" }, ...explicit];
12251
+ if ((ship.runner ?? "host") !== "sandbox") out.push(`ship.runner is "${ship.runner ?? "host"}" on a ${kind} ship; a managed ship should run agents in the sandbox (set ship.runner: sandbox)`);
12252
+ for (const t of effectiveToolchains) {
12253
+ if (t.harness === "claude" && t.auth !== "workspace") out.push(`ship.toolchains.${t.name}.auth is "${t.auth ?? "inherit"}" on a ${kind} ship; a managed ship reaches a model only through the workspace endpoint (set auth: workspace)`);
12254
+ }
12255
+ return out;
12256
+ }
12167
12257
 
12168
12258
  // src/selftest.ts
12169
12259
  import { spawn as spawn5 } from "child_process";
12170
- import { existsSync as existsSync25, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync2, readFileSync as readFileSync22, rmSync as rmSync14, writeFileSync as writeFileSync16 } from "fs";
12260
+ import { existsSync as existsSync26, mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
12171
12261
  import { tmpdir as tmpdir4 } from "os";
12172
- import { join as join27 } from "path";
12262
+ import { join as join28 } from "path";
12173
12263
  var SELFTEST_TIMEOUT_MS = 12e4;
12174
12264
  var KEY_VAR = "CREW_API_KEY";
12175
12265
  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: ";
12176
12266
  var realDeps = {
12177
- run: (o) => new Promise((resolve7) => {
12267
+ run: (o) => new Promise((resolve8) => {
12178
12268
  let stdout = "";
12179
12269
  let stderr = "";
12180
12270
  let timedOut = false;
@@ -12182,7 +12272,7 @@ var realDeps = {
12182
12272
  const done = (r) => {
12183
12273
  if (!settled) {
12184
12274
  settled = true;
12185
- resolve7(r);
12275
+ resolve8(r);
12186
12276
  }
12187
12277
  };
12188
12278
  const child = spawn5(o.bin, o.args, { cwd: o.cwd, env: o.env, stdio: ["pipe", "pipe", "pipe"], detached: true });
@@ -12330,7 +12420,7 @@ async function runSelftest(i, deps = realDeps) {
12330
12420
  ...adapter.runEnv(spec),
12331
12421
  [KEY_VAR]: i.apiKey
12332
12422
  };
12333
- const cwd = mkdtempSync2(join27(tmpdir4(), "crew-selftest-"));
12423
+ const cwd = mkdtempSync2(join28(tmpdir4(), "crew-selftest-"));
12334
12424
  try {
12335
12425
  const run3 = await deps.run({
12336
12426
  bin: invocation.bin,
@@ -12363,11 +12453,11 @@ function formatResult(r) {
12363
12453
  const secs = `${(r.ms / 1e3).toFixed(1)}s`;
12364
12454
  return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
12365
12455
  }
12366
- var fileFor2 = (stateDir) => join27(stateDir, "selftest.json");
12456
+ var fileFor2 = (stateDir) => join28(stateDir, "selftest.json");
12367
12457
  function readSelftest(stateDir) {
12368
12458
  try {
12369
12459
  const p = fileFor2(stateDir);
12370
- return existsSync25(p) ? JSON.parse(readFileSync22(p, "utf8")) : {};
12460
+ return existsSync26(p) ? JSON.parse(readFileSync23(p, "utf8")) : {};
12371
12461
  } catch {
12372
12462
  return {};
12373
12463
  }
@@ -12375,8 +12465,8 @@ function readSelftest(stateDir) {
12375
12465
  function recordSelftest(stateDir, results) {
12376
12466
  const all = readSelftest(stateDir);
12377
12467
  for (const r of results) all[r.toolchain] = r;
12378
- mkdirSync15(stateDir, { recursive: true });
12379
- writeFileSync16(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
12468
+ mkdirSync16(stateDir, { recursive: true });
12469
+ writeFileSync17(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
12380
12470
  `);
12381
12471
  }
12382
12472
  function doctorSelftestLines(stored, toolchains) {
@@ -12388,7 +12478,7 @@ function doctorSelftestLines(stored, toolchains) {
12388
12478
  }
12389
12479
 
12390
12480
  // src/cli.ts
12391
- import { resolve as resolve6, dirname as dirname28, join as join49, basename as basename16 } from "path";
12481
+ import { resolve as resolve7, dirname as dirname29, join as join50, basename as basename16 } from "path";
12392
12482
  import { fileURLToPath } from "url";
12393
12483
  import { hostname as hostname4, homedir as homedir11 } from "os";
12394
12484
  import { spawnSync as spawnSync10 } from "child_process";
@@ -12585,14 +12675,14 @@ function rosterMarkdown(roster, meId) {
12585
12675
  }
12586
12676
 
12587
12677
  // src/connect-resume.ts
12588
- import { mkdirSync as mkdirSync16, readFileSync as readFileSync23, rmSync as rmSync15, writeFileSync as writeFileSync17 } from "fs";
12589
- import { dirname as dirname11, join as join28 } from "path";
12678
+ import { mkdirSync as mkdirSync17, readFileSync as readFileSync24, rmSync as rmSync15, writeFileSync as writeFileSync18 } from "fs";
12679
+ import { dirname as dirname12, join as join29 } from "path";
12590
12680
  function lastSessionFile(stateDir) {
12591
- return join28(stateDir, "connect-session.json");
12681
+ return join29(stateDir, "connect-session.json");
12592
12682
  }
12593
12683
  function readPointers(stateDir) {
12594
12684
  try {
12595
- const parsed = JSON.parse(readFileSync23(lastSessionFile(stateDir), "utf8"));
12685
+ const parsed = JSON.parse(readFileSync24(lastSessionFile(stateDir), "utf8"));
12596
12686
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
12597
12687
  const out = {};
12598
12688
  for (const [host, v] of Object.entries(parsed)) {
@@ -12606,8 +12696,8 @@ function readPointers(stateDir) {
12606
12696
  }
12607
12697
  function writePointers(stateDir, pointers) {
12608
12698
  const file = lastSessionFile(stateDir);
12609
- mkdirSync16(dirname11(file), { recursive: true });
12610
- writeFileSync17(file, `${JSON.stringify(pointers, null, 2)}
12699
+ mkdirSync17(dirname12(file), { recursive: true });
12700
+ writeFileSync18(file, `${JSON.stringify(pointers, null, 2)}
12611
12701
  `, { mode: 384 });
12612
12702
  }
12613
12703
  function rememberSession(stateDir, host, workspaceSlug) {
@@ -12692,15 +12782,15 @@ async function signOutSessions(o) {
12692
12782
  }
12693
12783
 
12694
12784
  // src/reset.ts
12695
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync24, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync9, writeFileSync as writeFileSync18 } from "fs";
12696
- import { dirname as dirname12, join as join29 } from "path";
12785
+ import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync25, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync10, writeFileSync as writeFileSync19 } from "fs";
12786
+ import { dirname as dirname13, join as join30 } from "path";
12697
12787
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
12698
12788
  function clonedCheckoutsFile(stateDir) {
12699
- return join29(stateDir, "cloned-checkouts.json");
12789
+ return join30(stateDir, "cloned-checkouts.json");
12700
12790
  }
12701
12791
  function readClonedCheckouts(stateDir) {
12702
12792
  try {
12703
- const parsed = JSON.parse(readFileSync24(clonedCheckoutsFile(stateDir), "utf8"));
12793
+ const parsed = JSON.parse(readFileSync25(clonedCheckoutsFile(stateDir), "utf8"));
12704
12794
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
12705
12795
  } catch {
12706
12796
  return [];
@@ -12710,8 +12800,8 @@ function recordClonedCheckout(stateDir, dir) {
12710
12800
  const known = readClonedCheckouts(stateDir);
12711
12801
  if (known.includes(dir)) return;
12712
12802
  const file = clonedCheckoutsFile(stateDir);
12713
- mkdirSync17(dirname12(file), { recursive: true });
12714
- writeFileSync18(file, `${JSON.stringify([...known, dir], null, 2)}
12803
+ mkdirSync18(dirname13(file), { recursive: true });
12804
+ writeFileSync19(file, `${JSON.stringify([...known, dir], null, 2)}
12715
12805
  `);
12716
12806
  }
12717
12807
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -12765,7 +12855,7 @@ async function runReset(o, fx) {
12765
12855
  }
12766
12856
  if (o.beforeState) steps.push(...await o.beforeState());
12767
12857
  steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
12768
- if (existsSync26(o.configFile)) {
12858
+ if (existsSync27(o.configFile)) {
12769
12859
  if (!o.dryRun) rmSync16(o.configFile, { force: true });
12770
12860
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
12771
12861
  } else {
@@ -12774,7 +12864,7 @@ async function runReset(o, fx) {
12774
12864
  return steps;
12775
12865
  }
12776
12866
  function removeCheckout(dir, dryRun, fx) {
12777
- if (!existsSync26(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
12867
+ if (!existsSync27(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
12778
12868
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
12779
12869
  if (dirty.length) {
12780
12870
  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" : ""})` };
@@ -12787,15 +12877,15 @@ function removeCheckout(dir, dryRun, fx) {
12787
12877
  }
12788
12878
  function removeState(stateDir, dryRun) {
12789
12879
  const out = [];
12790
- const targets = [join29(stateDir, "resolved"), join29(stateDir, "keys"), clonedCheckoutsFile(stateDir), join29(stateDir, "connect-session.json"), join29(stateDir, "reload-requested"), join29(stateDir, "daemon.started")];
12880
+ const targets = [join30(stateDir, "resolved"), join30(stateDir, "keys"), clonedCheckoutsFile(stateDir), join30(stateDir, "connect-session.json"), join30(stateDir, "reload-requested"), join30(stateDir, "daemon.started")];
12791
12881
  try {
12792
12882
  for (const name of readdirSync12(stateDir)) {
12793
- if (name.startsWith(".") && statSync9(join29(stateDir, name)).isFile()) targets.push(join29(stateDir, name));
12883
+ if (name.startsWith(".") && statSync10(join30(stateDir, name)).isFile()) targets.push(join30(stateDir, name));
12794
12884
  }
12795
12885
  } catch {
12796
12886
  }
12797
12887
  for (const t of targets) {
12798
- if (!existsSync26(t)) continue;
12888
+ if (!existsSync27(t)) continue;
12799
12889
  if (!dryRun) rmSync16(t, { recursive: true, force: true });
12800
12890
  out.push({ step: "state", target: t, action: mark(dryRun) });
12801
12891
  }
@@ -12810,7 +12900,7 @@ function wipeStateDir(stateDir, dryRun) {
12810
12900
  return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
12811
12901
  }
12812
12902
  if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
12813
- if (!dryRun) for (const n of names) rmSync16(join29(stateDir, n), { recursive: true, force: true });
12903
+ if (!dryRun) for (const n of names) rmSync16(join30(stateDir, n), { recursive: true, force: true });
12814
12904
  return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
12815
12905
  }
12816
12906
  function describeResetStep(s) {
@@ -12891,9 +12981,9 @@ async function runDeprovision(o, fx, deps) {
12891
12981
  }
12892
12982
 
12893
12983
  // src/pool-member.ts
12894
- import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, renameSync as renameSync4, writeFileSync as writeFileSync19 } from "fs";
12984
+ import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync4, writeFileSync as writeFileSync20 } from "fs";
12895
12985
  import { homedir as homedir8 } from "os";
12896
- import { dirname as dirname13, join as join30 } from "path";
12986
+ import { dirname as dirname14, join as join31 } from "path";
12897
12987
  var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
12898
12988
  function backoffMs(attempt) {
12899
12989
  return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
@@ -13124,22 +13214,22 @@ async function runPoolMember(deps) {
13124
13214
  }
13125
13215
  }
13126
13216
  function defaultMemberDir(env = process.env) {
13127
- return env.CREW_POOL_DIR ?? join30(env.XDG_STATE_HOME ?? join30(homedir8(), ".local", "state"), "crew-pool-member");
13217
+ return env.CREW_POOL_DIR ?? join31(env.XDG_STATE_HOME ?? join31(homedir8(), ".local", "state"), "crew-pool-member");
13128
13218
  }
13129
13219
  function fileMemberStore(dir) {
13130
- const file = join30(dir, "member.json");
13220
+ const file = join31(dir, "member.json");
13131
13221
  return {
13132
13222
  read() {
13133
13223
  try {
13134
- return JSON.parse(readFileSync25(file, "utf8"));
13224
+ return JSON.parse(readFileSync26(file, "utf8"));
13135
13225
  } catch {
13136
13226
  return {};
13137
13227
  }
13138
13228
  },
13139
13229
  write(f) {
13140
- mkdirSync18(dirname13(file), { recursive: true, mode: 448 });
13230
+ mkdirSync19(dirname14(file), { recursive: true, mode: 448 });
13141
13231
  const tmp = `${file}.tmp`;
13142
- writeFileSync19(tmp, `${JSON.stringify(f)}
13232
+ writeFileSync20(tmp, `${JSON.stringify(f)}
13143
13233
  `, { mode: 384 });
13144
13234
  renameSync4(tmp, file);
13145
13235
  }
@@ -13191,30 +13281,30 @@ function hostArchitecture(arch = process.arch) {
13191
13281
 
13192
13282
  // src/pool-member-host.ts
13193
13283
  import "child_process";
13194
- import { existsSync as existsSync30 } from "fs";
13284
+ import { existsSync as existsSync31 } from "fs";
13195
13285
  import { hostname as hostname3 } from "os";
13196
13286
 
13197
13287
  // src/machine-id.ts
13198
- import { execFileSync as execFileSync8 } from "child_process";
13288
+ import { execFileSync as execFileSync9 } from "child_process";
13199
13289
  import { createHash as createHash7 } from "crypto";
13200
- import { existsSync as existsSync27, mkdirSync as mkdirSync19, readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
13201
- import { dirname as dirname14, join as join31 } from "path";
13290
+ import { existsSync as existsSync28, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
13291
+ import { dirname as dirname15, join as join32 } from "path";
13202
13292
  function machineIdPathFor(stateDir) {
13203
- return join31(stateDir, "machine-id");
13293
+ return join32(stateDir, "machine-id");
13204
13294
  }
13205
13295
  var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
13206
13296
  function readRawMachineId(platform2 = process.platform) {
13207
13297
  try {
13208
13298
  if (platform2 === "darwin") {
13209
- const out = execFileSync8("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
13299
+ const out = execFileSync9("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
13210
13300
  encoding: "utf8",
13211
13301
  stdio: ["ignore", "pipe", "ignore"]
13212
13302
  });
13213
13303
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
13214
13304
  }
13215
13305
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
13216
- if (existsSync27(p)) {
13217
- const id = readFileSync26(p, "utf8").trim();
13306
+ if (existsSync28(p)) {
13307
+ const id = readFileSync27(p, "utf8").trim();
13218
13308
  if (id) return id;
13219
13309
  }
13220
13310
  }
@@ -13225,15 +13315,15 @@ function readRawMachineId(platform2 = process.platform) {
13225
13315
  function ensureMachineId(stateDir, opts = {}) {
13226
13316
  if (opts.override?.trim()) return sha256(opts.override.trim());
13227
13317
  const path = machineIdPathFor(stateDir);
13228
- if (existsSync27(path)) {
13229
- const cached = readFileSync26(path, "utf8").trim();
13318
+ if (existsSync28(path)) {
13319
+ const cached = readFileSync27(path, "utf8").trim();
13230
13320
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
13231
13321
  }
13232
13322
  const raw = (opts.read ?? readRawMachineId)();
13233
13323
  if (!raw) return void 0;
13234
13324
  const hashed = sha256(raw);
13235
- mkdirSync19(dirname14(path), { recursive: true });
13236
- writeFileSync20(path, `${hashed}
13325
+ mkdirSync20(dirname15(path), { recursive: true });
13326
+ writeFileSync21(path, `${hashed}
13237
13327
  `, { mode: 384 });
13238
13328
  return hashed;
13239
13329
  }
@@ -13246,16 +13336,16 @@ function matchShipRows(ships, shipName2, machineId) {
13246
13336
 
13247
13337
  // src/provision.ts
13248
13338
  import { spawnSync as spawnSync7 } from "child_process";
13249
- import { existsSync as existsSync28, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
13339
+ import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
13250
13340
  import { hostname as hostname2 } from "os";
13251
- import { parseDocument as parseDocument3 } from "yaml";
13341
+ import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
13252
13342
 
13253
13343
  // src/doctor-fix.ts
13254
13344
  import { parseDocument as parseDocument2 } from "yaml";
13255
13345
 
13256
13346
  // src/repos-cmd.ts
13257
13347
  import { homedir as homedir9 } from "os";
13258
- import { basename as basename10, join as join32, resolve as resolve3 } from "path";
13348
+ import { basename as basename10, join as join33, resolve as resolve4 } from "path";
13259
13349
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
13260
13350
  var ReposError = class extends Error {
13261
13351
  };
@@ -13302,7 +13392,7 @@ function parseAddArgs(args, routes, isDir) {
13302
13392
  }
13303
13393
  function expandDir(p, base) {
13304
13394
  const home = p === "~" ? homedir9() : p.startsWith("~/") ? `${homedir9()}/${p.slice(2)}` : p;
13305
- return resolve3(base, home);
13395
+ return resolve4(base, home);
13306
13396
  }
13307
13397
  function routeMapOf(doc, route2) {
13308
13398
  const routes = doc.get("routes", true);
@@ -13533,7 +13623,7 @@ function planRepoClone(i) {
13533
13623
  }
13534
13624
  const ship = doc.get("ship", true);
13535
13625
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
13536
- const target = i.dir ? expandDir(i.dir, i.base) : join32(expandDir(basePath, i.base), i.route.split("/")[0], name);
13626
+ const target = i.dir ? expandDir(i.dir, i.base) : join33(expandDir(basePath, i.base), i.route.split("/")[0], name);
13537
13627
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
13538
13628
  const forgeHost = i.resolved?.repoForgeHosts?.[id];
13539
13629
  return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
@@ -13684,6 +13774,15 @@ async function runProvision(o, deps, write, human) {
13684
13774
  emit("connect", `set ship.${key}`, { setting: key });
13685
13775
  }
13686
13776
  }
13777
+ if (o.runner === "host") {
13778
+ emit("connect", "runner: host chosen explicitly (--runner host); not applying the managed sandbox defaults", { setting: "ship.runner", value: "host" });
13779
+ if (deps.setShipSetting("runner", "host")) summary.changed = true;
13780
+ } else if (o.runner === "sandbox" || isManagedShipKind(o.shipKind)) {
13781
+ for (const key of deps.applyManagedDefaults()) {
13782
+ summary.changed = true;
13783
+ emit("connect", `set ${key}`, { setting: key });
13784
+ }
13785
+ }
13687
13786
  for (const host of o.forgeHosts) {
13688
13787
  const token = o.tokens[host];
13689
13788
  const stored = deps.storedToken(host);
@@ -13727,6 +13826,38 @@ async function runProvision(o, deps, write, human) {
13727
13826
  emit("done", summary.state, { summary });
13728
13827
  return { code: PROVISION_EXIT.ok, summary };
13729
13828
  }
13829
+ function isManagedShipKind(kind) {
13830
+ return kind === "managed_instance" || kind === "pooled_instance" || kind === "pooled";
13831
+ }
13832
+ var MANAGED_CACHE_VOLUME = "crew-cache";
13833
+ function applyManagedDefaultsToDoc(doc) {
13834
+ const wrote = [];
13835
+ if (doc.getIn(["ship", "runner"]) === void 0) {
13836
+ doc.setIn(["ship", "runner"], "sandbox");
13837
+ wrote.push("ship.runner");
13838
+ }
13839
+ if (doc.getIn(["ship", "sandbox", "cacheVolume"]) === void 0) {
13840
+ doc.setIn(["ship", "sandbox", "cacheVolume"], MANAGED_CACHE_VOLUME);
13841
+ wrote.push("ship.sandbox.cacheVolume");
13842
+ }
13843
+ const existing = doc.getIn(["ship", "toolchains"]);
13844
+ const hasClaude = isMap2(existing) && existing.items.some((i) => (isScalar(i.key) ? i.key.value : i.key) === "claude");
13845
+ if (!hasClaude) {
13846
+ doc.setIn(["ship", "toolchains", "claude", "harness"], "claude");
13847
+ for (const k of ["bin", "model", "maxThinkingTokens"]) {
13848
+ const v = doc.getIn(["ship", "agent", k]);
13849
+ if (v !== void 0) doc.setIn(["ship", "toolchains", "claude", k], v);
13850
+ }
13851
+ }
13852
+ const toolchains = doc.getIn(["ship", "toolchains"]);
13853
+ for (const name of isMap2(toolchains) ? toolchains.items.map((i) => String(isScalar(i.key) ? i.key.value : i.key)) : []) {
13854
+ if (doc.getIn(["ship", "toolchains", name, "harness"]) !== "claude") continue;
13855
+ if (doc.getIn(["ship", "toolchains", name, "auth"]) !== void 0) continue;
13856
+ doc.setIn(["ship", "toolchains", name, "auth"], "workspace");
13857
+ wrote.push(`ship.toolchains.${name}.auth`);
13858
+ }
13859
+ return wrote;
13860
+ }
13730
13861
  function envKey(host) {
13731
13862
  return host.toUpperCase().replace(/[^A-Z0-9]/g, "_");
13732
13863
  }
@@ -13778,6 +13909,7 @@ function provisionOptionsFrom(argv2, env) {
13778
13909
  relayHost: one("relay-host"),
13779
13910
  maxConcurrentAgents: Number.isInteger(Number(one("max-concurrent-agents"))) && Number(one("max-concurrent-agents")) > 0 ? Number(one("max-concurrent-agents")) : void 0,
13780
13911
  agentBin: one("agent-bin"),
13912
+ runner: one("runner") === "host" || one("runner") === "sandbox" ? one("runner") : void 0,
13781
13913
  extraPath: one("extra-path"),
13782
13914
  baseUrl: one("base-url"),
13783
13915
  forgeHosts,
@@ -13798,7 +13930,7 @@ function realProvisionDeps(crewHome) {
13798
13930
  };
13799
13931
  const configFile = () => {
13800
13932
  const f = findConfigFile(crewHome);
13801
- return existsSync28(f) ? f : null;
13933
+ return existsSync29(f) ? f : null;
13802
13934
  };
13803
13935
  return {
13804
13936
  crew: spawnCrew,
@@ -13806,13 +13938,13 @@ function realProvisionDeps(crewHome) {
13806
13938
  enableRoute(route2) {
13807
13939
  const file = configFile();
13808
13940
  if (!file) return false;
13809
- const text2 = readFileSync27(file, "utf8");
13941
+ const text2 = readFileSync28(file, "utf8");
13810
13942
  const doc = parseDocument3(text2);
13811
13943
  const routes = doc.get("routes");
13812
13944
  const entry = routes?.items?.find((r) => r.get("route") === route2);
13813
13945
  if (!entry) throw new Error(`route ${route2} is not in ${file}`);
13814
13946
  if (entry.get("enabled") === true) return false;
13815
- writeFileSync21(file, planEnable(text2, route2));
13947
+ writeFileSync22(file, planEnable(text2, route2));
13816
13948
  return true;
13817
13949
  },
13818
13950
  installState() {
@@ -13832,14 +13964,14 @@ function realProvisionDeps(crewHome) {
13832
13964
  continue;
13833
13965
  }
13834
13966
  for (const p of plan.unitPaths) {
13835
- if (!existsSync28(p) || readFileSync27(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
13967
+ if (!existsSync29(p) || readFileSync28(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
13836
13968
  }
13837
13969
  }
13838
13970
  const label = labelFor(crewHome, "run");
13839
13971
  let running = false;
13840
13972
  let lingering = true;
13841
13973
  if (mechanism === "systemd") {
13842
- const system = ctx.root || existsSync28(systemUnitPathFor(label));
13974
+ const system = ctx.root || existsSync29(systemUnitPathFor(label));
13843
13975
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
13844
13976
  running = spawnSync7(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
13845
13977
  if (!system) {
@@ -13856,12 +13988,20 @@ function realProvisionDeps(crewHome) {
13856
13988
  setShipSetting(key, value2) {
13857
13989
  const file = configFile();
13858
13990
  if (!file) return false;
13859
- const doc = parseDocument3(readFileSync27(file, "utf8"));
13991
+ const doc = parseDocument3(readFileSync28(file, "utf8"));
13860
13992
  if (doc.getIn(["ship", key]) === value2) return false;
13861
13993
  doc.setIn(["ship", key], value2);
13862
- writeFileSync21(file, doc.toString());
13994
+ writeFileSync22(file, doc.toString());
13863
13995
  return true;
13864
13996
  },
13997
+ applyManagedDefaults() {
13998
+ const file = configFile();
13999
+ if (!file) return [];
14000
+ const doc = parseDocument3(readFileSync28(file, "utf8"));
14001
+ const wrote = applyManagedDefaultsToDoc(doc);
14002
+ if (wrote.length) writeFileSync22(file, doc.toString());
14003
+ return wrote;
14004
+ },
13865
14005
  storedToken(host) {
13866
14006
  const res = spawnCrew(["credential", "get"], { input: `protocol=https
13867
14007
  host=${host}
@@ -13876,9 +14016,9 @@ host=${host}
13876
14016
 
13877
14017
  // src/tunnel.ts
13878
14018
  import { spawn as spawn6 } from "child_process";
13879
- import { existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync28, writeFileSync as writeFileSync22, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14019
+ import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync23, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
13880
14020
  import { randomInt } from "crypto";
13881
- import { join as join33 } from "path";
14021
+ import { join as join34 } from "path";
13882
14022
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
13883
14023
  var defaultSpawn = (cmd, args) => spawn6(cmd, args, DEFAULT_SPAWN_OPTIONS);
13884
14024
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -13950,21 +14090,21 @@ function startTunnel(opts) {
13950
14090
  };
13951
14091
  }
13952
14092
  function tunnelStatePath(stateDir, workspaceId) {
13953
- return join33(stateDir, "passengers", `${workspaceId}.json`);
14093
+ return join34(stateDir, "passengers", `${workspaceId}.json`);
13954
14094
  }
13955
14095
  function readPersistedTunnel(stateDir, workspaceId) {
13956
14096
  const path = tunnelStatePath(stateDir, workspaceId);
13957
- if (!existsSync29(path)) return void 0;
14097
+ if (!existsSync30(path)) return void 0;
13958
14098
  try {
13959
- return JSON.parse(readFileSync28(path, "utf8"));
14099
+ return JSON.parse(readFileSync29(path, "utf8"));
13960
14100
  } catch {
13961
14101
  return void 0;
13962
14102
  }
13963
14103
  }
13964
14104
  function writePersistedTunnel(stateDir, state) {
13965
14105
  const path = tunnelStatePath(stateDir, state.workspaceId);
13966
- mkdirSync20(join33(stateDir, "passengers"), { recursive: true });
13967
- writeFileSync22(path, `${JSON.stringify(state, null, 2)}
14106
+ mkdirSync21(join34(stateDir, "passengers"), { recursive: true });
14107
+ writeFileSync23(path, `${JSON.stringify(state, null, 2)}
13968
14108
  `);
13969
14109
  }
13970
14110
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -14066,8 +14206,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
14066
14206
  }
14067
14207
  }
14068
14208
  function readAllPersistedWorkspaceIds(stateDir) {
14069
- const dir = join33(stateDir, "passengers");
14070
- if (!existsSync29(dir)) return [];
14209
+ const dir = join34(stateDir, "passengers");
14210
+ if (!existsSync30(dir)) return [];
14071
14211
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
14072
14212
  }
14073
14213
 
@@ -14092,7 +14232,7 @@ function realPoolMemberDeps(o) {
14092
14232
  crewVersion: crewVersion(o.crewHome),
14093
14233
  hostname: hostname3()
14094
14234
  },
14095
- hasShipConfig: () => existsSync30(findConfigFile(o.crewHome)),
14235
+ hasShipConfig: () => existsSync31(findConfigFile(o.crewHome)),
14096
14236
  async provision(lease, instanceClass, shipKind) {
14097
14237
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
14098
14238
  const lines2 = [];
@@ -14968,8 +15108,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
14968
15108
  }
14969
15109
 
14970
15110
  // src/poll.ts
14971
- import { writeFileSync as writeFileSync23 } from "fs";
14972
- import { join as join34 } from "path";
15111
+ import { writeFileSync as writeFileSync24 } from "fs";
15112
+ import { join as join35 } from "path";
14973
15113
 
14974
15114
  // src/stalled.ts
14975
15115
  var STALLED_AFTER_CYCLES = 30;
@@ -15276,7 +15416,7 @@ async function decideCycle(o) {
15276
15416
  }
15277
15417
  return decision;
15278
15418
  }
15279
- var digestPath = (stateDir, route2, role, runId) => join34(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15419
+ var digestPath = (stateDir, route2, role, runId) => join35(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15280
15420
  function otherRunsRemoved(tickets, own) {
15281
15421
  if (!own) return tickets;
15282
15422
  return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
@@ -15354,7 +15494,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
15354
15494
 
15355
15495
  ${body}` : body;
15356
15496
  const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
15357
- writeFileSync23(path, text2);
15497
+ writeFileSync24(path, text2);
15358
15498
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
15359
15499
  data: { role, bytes: Buffer.byteLength(text2) }
15360
15500
  });
@@ -15462,8 +15602,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
15462
15602
  }
15463
15603
 
15464
15604
  // src/repo-refresh.ts
15465
- import { existsSync as existsSync31, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "fs";
15466
- import { dirname as dirname16 } from "path";
15605
+ import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "fs";
15606
+ import { dirname as dirname17 } from "path";
15467
15607
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
15468
15608
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
15469
15609
  var lastAsked = /* @__PURE__ */ new Map();
@@ -15492,11 +15632,11 @@ function applyRepoRows(resolved, rows) {
15492
15632
  }
15493
15633
  function persistRepoRows(stateDir, routeName, rows) {
15494
15634
  const path = resolvedPathFor(stateDir, routeName);
15495
- if (!existsSync31(path)) return;
15496
- const raw = JSON.parse(readFileSync29(path, "utf8"));
15635
+ if (!existsSync32(path)) return;
15636
+ const raw = JSON.parse(readFileSync30(path, "utf8"));
15497
15637
  applyRepoRows(raw, rows);
15498
- mkdirSync21(dirname16(path), { recursive: true });
15499
- writeFileSync24(path, `${JSON.stringify(raw, null, 2)}
15638
+ mkdirSync22(dirname17(path), { recursive: true });
15639
+ writeFileSync25(path, `${JSON.stringify(raw, null, 2)}
15500
15640
  `);
15501
15641
  }
15502
15642
  async function refreshUnknownRepos(o) {
@@ -15946,13 +16086,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
15946
16086
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
15947
16087
 
15948
16088
  // src/connect-wizard.ts
15949
- import { delimiter, dirname as dirname17, join as join35 } from "path";
15950
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync25 } from "fs";
16089
+ import { delimiter, dirname as dirname18, join as join36 } from "path";
16090
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync33, mkdirSync as mkdirSync23, writeFileSync as writeFileSync26 } from "fs";
15951
16091
  import { stringify as stringify3 } from "yaml";
15952
16092
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
15953
16093
  function shouldRunWizard(opts) {
15954
16094
  if (!opts.isTTY) return false;
15955
- const exists = opts.configExists ?? existsSync32;
16095
+ const exists = opts.configExists ?? existsSync33;
15956
16096
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
15957
16097
  }
15958
16098
  function firstRunConfigPath() {
@@ -15969,7 +16109,7 @@ function defaultIsExecutable(p) {
15969
16109
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
15970
16110
  for (const dir of pathEnv.split(delimiter)) {
15971
16111
  if (!dir) continue;
15972
- const candidate = join35(dir, name);
16112
+ const candidate = join36(dir, name);
15973
16113
  if (isExecutable(candidate)) return candidate;
15974
16114
  }
15975
16115
  return void 0;
@@ -15995,9 +16135,9 @@ async function runWizard(p, env) {
15995
16135
  if (entered && isExec(entered)) agentBin = entered;
15996
16136
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
15997
16137
  }
15998
- const nodeDir = dirname17(env.nodePath);
16138
+ const nodeDir = dirname18(env.nodePath);
15999
16139
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16000
- const baseDirs = [nodeDir, pnpmPath ? dirname17(pnpmPath) : void 0];
16140
+ const baseDirs = [nodeDir, pnpmPath ? dirname18(pnpmPath) : void 0];
16001
16141
  p.say(
16002
16142
  `
16003
16143
  Host Passengers lets this machine run the agent sessions that other people's
@@ -16008,7 +16148,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
16008
16148
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
16009
16149
  }
16010
16150
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16011
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname17(dockerPath) : void 0]);
16151
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname18(dockerPath) : void 0]);
16012
16152
  if (extraPath) {
16013
16153
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
16014
16154
  }
@@ -16030,9 +16170,9 @@ routes:
16030
16170
  ${routeBlock}`;
16031
16171
  }
16032
16172
  function writeNewConfig(path, text2) {
16033
- mkdirSync22(dirname17(path), { recursive: true });
16173
+ mkdirSync23(dirname18(path), { recursive: true });
16034
16174
  try {
16035
- writeFileSync25(path, text2, { mode: 384, flag: "wx" });
16175
+ writeFileSync26(path, text2, { mode: 384, flag: "wx" });
16036
16176
  return true;
16037
16177
  } catch (e) {
16038
16178
  if (e.code === "EEXIST") return false;
@@ -16066,7 +16206,7 @@ Wrote ${o.configPath}.
16066
16206
  // src/connect-json.ts
16067
16207
  import { accessSync as accessSync4, constants as constants4 } from "fs";
16068
16208
  import { homedir as homedir10 } from "os";
16069
- import { dirname as dirname18, join as join36 } from "path";
16209
+ import { dirname as dirname19, join as join37 } from "path";
16070
16210
  import { delimiter as delimiter2 } from "path";
16071
16211
  var CHOICE_EXIT_CODE = 3;
16072
16212
  function createEmitter(json2, write) {
@@ -16105,12 +16245,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
16105
16245
  const dirs = [
16106
16246
  ...pathEnv.split(delimiter2),
16107
16247
  ...(extraPath ?? "").split(delimiter2),
16108
- join36(home, ".local/bin"),
16109
- join36(home, ".claude/local"),
16248
+ join37(home, ".local/bin"),
16249
+ join37(home, ".claude/local"),
16110
16250
  "/opt/homebrew/bin",
16111
16251
  "/usr/local/bin",
16112
- join36(home, ".npm-global/bin"),
16113
- join36(home, ".volta/bin")
16252
+ join37(home, ".npm-global/bin"),
16253
+ join37(home, ".volta/bin")
16114
16254
  ];
16115
16255
  return [...new Set(dirs.filter(Boolean))];
16116
16256
  }
@@ -16132,7 +16272,7 @@ function flagWizardAnswers(f, env) {
16132
16272
  const agentBin = f.agentBin ?? found?.bin;
16133
16273
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16134
16274
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16135
- const extraPath = f.extraPath ?? deriveExtraPath([dirname18(env.nodePath), pnpmPath ? dirname18(pnpmPath) : void 0, dockerPath ? dirname18(dockerPath) : void 0]);
16275
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname19(env.nodePath), pnpmPath ? dirname19(pnpmPath) : void 0, dockerPath ? dirname19(dockerPath) : void 0]);
16136
16276
  return {
16137
16277
  answers: {
16138
16278
  name: f.shipName?.trim() || env.hostname,
@@ -16146,8 +16286,8 @@ function flagWizardAnswers(f, env) {
16146
16286
  }
16147
16287
 
16148
16288
  // src/release-run.ts
16149
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync26, existsSync as existsSync33, copyFileSync, mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
16150
- import { dirname as dirname19, join as join37 } from "path";
16289
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync27, existsSync as existsSync34, copyFileSync, mkdirSync as mkdirSync24, mkdtempSync as mkdtempSync3, rmSync as rmSync17 } from "fs";
16290
+ import { dirname as dirname20, join as join38 } from "path";
16151
16291
  import { tmpdir as tmpdir5 } from "os";
16152
16292
 
16153
16293
  // src/release.ts
@@ -16366,7 +16506,7 @@ function decideRelease(cwd, tickets, contract, opts = {}) {
16366
16506
  const block = opts.guard === false ? null : checkGuards(cwd, base);
16367
16507
  const lastTag = latestReleaseTag(cwd, pattern, ref);
16368
16508
  const lastReleased = lastTag ? tagCommit(cwd, lastTag) : null;
16369
- const head = resolve2(cwd, ref) ?? "";
16509
+ const head = resolve3(cwd, ref) ?? "";
16370
16510
  const merges = block ? [] : planMerge(cwd, tickets, contract, lastReleased, opts.branchFor ?? ((t) => branchForIssue(cwd, t.issue_id)), opts.ref ?? base, opts.blockerInfo, ref);
16371
16511
  const unreleasedCommits = lastReleased ? countCommits(cwd, `${lastReleased}..${ref}`) : 0;
16372
16512
  const unreleasedWork = lastReleased ? (gitOk(cwd, ["log", "--format=%s", `${lastReleased}..${ref}`]) ?? "").split("\n").filter((l) => l && !RELEASE_COMMIT_SUBJECT.test(l)).length : 0;
@@ -16546,8 +16686,8 @@ function movedPast(r, t, branch, pushedAs) {
16546
16686
  if (!r.columns.verifiedSha) return null;
16547
16687
  const verified = columnValue(t, r.contract.columns.verifiedSha);
16548
16688
  if (!verified) return null;
16549
- const head = resolve2(r.cwd, branch);
16550
- const remoteHead = resolve2(r.cwd, `${r.repo.branch.remote}/${pushedAs}`);
16689
+ const head = resolve3(r.cwd, branch);
16690
+ const remoteHead = resolve3(r.cwd, `${r.repo.branch.remote}/${pushedAs}`);
16551
16691
  if (remoteHead && !isAncestor(r.cwd, remoteHead, verified) && remoteHead !== head) {
16552
16692
  return { ticket: t, verified, head: remoteHead, branch: pushedAs };
16553
16693
  }
@@ -16570,7 +16710,7 @@ async function runReview(r, verified, reviewing) {
16570
16710
  out.requeues.push(moved);
16571
16711
  continue;
16572
16712
  }
16573
- const head = resolve2(r.cwd, branch);
16713
+ const head = resolve3(r.cwd, branch);
16574
16714
  if (!head) continue;
16575
16715
  if (r.dryRun) {
16576
16716
  r.emit.emit(`would push ${branch} to ${remote}/${pushedAs} for review`, { ticket: ticket.issue_id });
@@ -16888,13 +17028,13 @@ async function bump(o, size) {
16888
17028
  }
16889
17029
  return printed;
16890
17030
  }
16891
- const files = o.repo.release.versionFiles.map((f) => join37(o.cwd, f));
17031
+ const files = o.repo.release.versionFiles.map((f) => join38(o.cwd, f));
16892
17032
  const first = files[0];
16893
- if (!existsSync33(first)) {
17033
+ if (!existsSync34(first)) {
16894
17034
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
16895
17035
  return null;
16896
17036
  }
16897
- const current = JSON.parse(readFileSync30(first, "utf8")).version;
17037
+ const current = JSON.parse(readFileSync31(first, "utf8")).version;
16898
17038
  if (!current) {
16899
17039
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
16900
17040
  return null;
@@ -16907,27 +17047,27 @@ async function bump(o, size) {
16907
17047
  return version;
16908
17048
  }
16909
17049
  for (const f of files) {
16910
- const src = readFileSync30(f, "utf8");
17050
+ const src = readFileSync31(f, "utf8");
16911
17051
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
16912
17052
  if (out === src) {
16913
17053
  o.emit.error(`no version field in ${f}`);
16914
17054
  return null;
16915
17055
  }
16916
- writeFileSync26(f, out);
17056
+ writeFileSync27(f, out);
16917
17057
  }
16918
17058
  return version;
16919
17059
  }
16920
17060
  function writeChangelog(o, version, entries) {
16921
17061
  const file = o.repo.release.changelog;
16922
17062
  if (!file || entries.length === 0) return;
16923
- const path = join37(o.cwd, file);
17063
+ const path = join38(o.cwd, file);
16924
17064
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
16925
17065
  if (o.dryRun) {
16926
17066
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
16927
17067
  return;
16928
17068
  }
16929
- const existing = existsSync33(path) ? readFileSync30(path, "utf8") : "# Changelog\n";
16930
- writeFileSync26(path, insertChangelogSection(existing, section));
17069
+ const existing = existsSync34(path) ? readFileSync31(path, "utf8") : "# Changelog\n";
17070
+ writeFileSync27(path, insertChangelogSection(existing, section));
16931
17071
  }
16932
17072
  async function confirm(o, expected) {
16933
17073
  if (!o.repo.hooks.released) return void 0;
@@ -17020,8 +17160,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
17020
17160
  function openReleaseCheckout(o) {
17021
17161
  const { remote, base } = o.repo.branch;
17022
17162
  const branch = `crew/release-${Date.now()}`;
17023
- const root = mkdtempSync3(join37(tmpdir5(), "crew-release-"));
17024
- const dir = join37(root, "checkout");
17163
+ const root = mkdtempSync3(join38(tmpdir5(), "crew-release-"));
17164
+ const dir = join38(root, "checkout");
17025
17165
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
17026
17166
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
17027
17167
  try {
@@ -17031,10 +17171,10 @@ function openReleaseCheckout(o) {
17031
17171
  throw e;
17032
17172
  }
17033
17173
  for (const f of o.repo.worktrees.copy) {
17034
- const src = join37(o.cwd, f);
17035
- if (!existsSync33(src)) continue;
17036
- mkdirSync23(dirname19(join37(dir, f)), { recursive: true });
17037
- copyFileSync(src, join37(dir, f));
17174
+ const src = join38(o.cwd, f);
17175
+ if (!existsSync34(src)) continue;
17176
+ mkdirSync24(dirname20(join38(dir, f)), { recursive: true });
17177
+ copyFileSync(src, join38(dir, f));
17038
17178
  }
17039
17179
  return { dir, branch, close };
17040
17180
  }
@@ -17457,7 +17597,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
17457
17597
  branches: merged.filter((c) => c.branch).map((c) => ({
17458
17598
  key: c.ticket.issue_tag ?? c.ticket.issue_id,
17459
17599
  branch: c.branch,
17460
- tip: resolve2(o.cwd, c.branch) ?? ""
17600
+ tip: resolve3(o.cwd, c.branch) ?? ""
17461
17601
  }))
17462
17602
  };
17463
17603
  const hookFailure2 = {
@@ -17520,7 +17660,7 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
17520
17660
  if (releaseBranch && !o.dryRun) {
17521
17661
  const { remote, base } = o.repo.branch;
17522
17662
  if (fetchRemote(o.cwd, remote) && remoteBranchExists(o.cwd, remote, base)) {
17523
- const moved = resolve2(o.cwd, `${remote}/${base}`);
17663
+ const moved = resolve3(o.cwd, `${remote}/${base}`);
17524
17664
  if (moved && !isAncestor(o.cwd, moved, "HEAD")) {
17525
17665
  o.emit.warn(`${remote}/${base} moved during the release \u2014 discarding it; the tickets stay verified and the next cycle retries`);
17526
17666
  return { merged, conflicts, unbuildable, version, deployed: false, decision, stopped: "base branch moved during release" };
@@ -18013,7 +18153,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
18013
18153
  }
18014
18154
 
18015
18155
  // src/watch.ts
18016
- import { watch as watchFile, existsSync as existsSync34, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
18156
+ import { watch as watchFile, existsSync as existsSync35, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
18017
18157
  var E = "\x1B[";
18018
18158
  var dim = (s) => `${E}2m${s}${E}0m`;
18019
18159
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -18114,8 +18254,8 @@ function renderHeaders(states, now = Date.now()) {
18114
18254
  return states.map((s) => renderHeader(s, now)).join("");
18115
18255
  }
18116
18256
  function readFrom(file, from) {
18117
- if (!existsSync34(file)) return { events: [], offset: from };
18118
- const size = statSync10(file).size;
18257
+ if (!existsSync35(file)) return { events: [], offset: from };
18258
+ const size = statSync11(file).size;
18119
18259
  if (size < from) return readFrom(file, 0);
18120
18260
  if (size === from) return { events: [], offset: from };
18121
18261
  const fd = openSync4(file, "r");
@@ -18357,8 +18497,8 @@ function nextRoles(decision, ran) {
18357
18497
  }
18358
18498
 
18359
18499
  // src/ship-name.ts
18360
- import { existsSync as existsSync35, mkdirSync as mkdirSync24, readFileSync as readFileSync31, renameSync as renameSync5, writeFileSync as writeFileSync27 } from "fs";
18361
- import { dirname as dirname20 } from "path";
18500
+ import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, renameSync as renameSync5, writeFileSync as writeFileSync28 } from "fs";
18501
+ import { dirname as dirname21 } from "path";
18362
18502
  import { parseDocument as parseDocument5 } from "yaml";
18363
18503
  var ShipNameError = class extends Error {
18364
18504
  };
@@ -18419,34 +18559,34 @@ async function syncRouteName(i) {
18419
18559
  return { status: "ok", ...base, action, name: i.local };
18420
18560
  }
18421
18561
  function readSyncedName(resolvedFile) {
18422
- if (!existsSync35(resolvedFile)) return void 0;
18562
+ if (!existsSync36(resolvedFile)) return void 0;
18423
18563
  try {
18424
- const v = JSON.parse(readFileSync31(resolvedFile, "utf8")).shipName;
18564
+ const v = JSON.parse(readFileSync32(resolvedFile, "utf8")).shipName;
18425
18565
  return typeof v === "string" ? v : void 0;
18426
18566
  } catch {
18427
18567
  return void 0;
18428
18568
  }
18429
18569
  }
18430
18570
  function writeSyncedName(resolvedFile, name) {
18431
- if (!existsSync35(resolvedFile)) return;
18571
+ if (!existsSync36(resolvedFile)) return;
18432
18572
  let raw;
18433
18573
  try {
18434
- raw = JSON.parse(readFileSync31(resolvedFile, "utf8"));
18574
+ raw = JSON.parse(readFileSync32(resolvedFile, "utf8"));
18435
18575
  } catch {
18436
18576
  return;
18437
18577
  }
18438
18578
  if (raw.shipName === name) return;
18439
18579
  raw.shipName = name;
18440
- mkdirSync24(dirname20(resolvedFile), { recursive: true });
18580
+ mkdirSync25(dirname21(resolvedFile), { recursive: true });
18441
18581
  const tmp = `${resolvedFile}.tmp${process.pid}`;
18442
- writeFileSync27(tmp, `${JSON.stringify(raw, null, 2)}
18582
+ writeFileSync28(tmp, `${JSON.stringify(raw, null, 2)}
18443
18583
  `);
18444
18584
  renameSync5(tmp, resolvedFile);
18445
18585
  }
18446
18586
  async function reconcileShipName(i) {
18447
18587
  let local = i.ship.name;
18448
18588
  try {
18449
- local = shipNameOfYaml(readFileSync31(i.configFile, "utf8")) ?? local;
18589
+ local = shipNameOfYaml(readFileSync32(i.configFile, "utf8")) ?? local;
18450
18590
  } catch {
18451
18591
  }
18452
18592
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -18470,8 +18610,8 @@ async function reconcileShipName(i) {
18470
18610
  }
18471
18611
  if (result.status !== "ok") return result;
18472
18612
  if (result.action === "adopt" || result.action === "row-wins") {
18473
- const text2 = readFileSync31(i.configFile, "utf8");
18474
- writeFileSync27(i.configFile, setShipNameInYaml(text2, result.name));
18613
+ const text2 = readFileSync32(i.configFile, "utf8");
18614
+ writeFileSync28(i.configFile, setShipNameInYaml(text2, result.name));
18475
18615
  i.ship.name = result.name;
18476
18616
  if (result.action === "row-wins") {
18477
18617
  i.emit?.warn(
@@ -19130,8 +19270,8 @@ function describeCapability(i) {
19130
19270
  }
19131
19271
 
19132
19272
  // src/agents-sync-run.ts
19133
- import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, writeFileSync as writeFileSync28 } from "fs";
19134
- import { dirname as dirname21 } from "path";
19273
+ import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, writeFileSync as writeFileSync29 } from "fs";
19274
+ import { dirname as dirname22 } from "path";
19135
19275
  async function runAgentsSync(target, o) {
19136
19276
  const dryRun = o.dryRun ?? false;
19137
19277
  const force = o.force ?? false;
@@ -19164,12 +19304,12 @@ async function runAgentsSync(target, o) {
19164
19304
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
19165
19305
  if (!dryRun) {
19166
19306
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
19167
- const raw = existsSync36(resolvedPath) ? JSON.parse(readFileSync32(resolvedPath, "utf8")) : {};
19307
+ const raw = existsSync37(resolvedPath) ? JSON.parse(readFileSync33(resolvedPath, "utf8")) : {};
19168
19308
  raw.agentPersonas = personaResult.agentPersonas;
19169
19309
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
19170
19310
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
19171
- mkdirSync25(dirname21(resolvedPath), { recursive: true });
19172
- writeFileSync28(resolvedPath, `${JSON.stringify(raw, null, 2)}
19311
+ mkdirSync26(dirname22(resolvedPath), { recursive: true });
19312
+ writeFileSync29(resolvedPath, `${JSON.stringify(raw, null, 2)}
19173
19313
  `);
19174
19314
  if (target.resolved) {
19175
19315
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -19273,7 +19413,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
19273
19413
 
19274
19414
  // src/repos-check.ts
19275
19415
  import { spawnSync as spawnSync9 } from "child_process";
19276
- import { readFileSync as readFileSync33 } from "fs";
19416
+ import { readFileSync as readFileSync34 } from "fs";
19277
19417
  import { basename as basename11 } from "path";
19278
19418
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
19279
19419
  function checkHookSyntax(script, shell) {
@@ -19314,7 +19454,7 @@ function checkRepoSpec(dir, opts = {}) {
19314
19454
  }
19315
19455
  let parsed;
19316
19456
  try {
19317
- parsed = parseRepoConfig(readFileSync33(file, "utf8"), file);
19457
+ parsed = parseRepoConfig(readFileSync34(file, "utf8"), file);
19318
19458
  } catch (e) {
19319
19459
  if (!(e instanceof RepoConfigError)) throw e;
19320
19460
  return { ...empty, file, error: e.message, exitCode: 1 };
@@ -19371,7 +19511,7 @@ function renderRepoCheck(r) {
19371
19511
  }
19372
19512
 
19373
19513
  // src/repos-hooks.ts
19374
- import { isMap as isMap2, parseDocument as parseDocument6, YAMLMap } from "yaml";
19514
+ import { isMap as isMap3, parseDocument as parseDocument6, YAMLMap } from "yaml";
19375
19515
  function packageManager(f) {
19376
19516
  if (f.exists("pnpm-lock.yaml")) return "pnpm";
19377
19517
  if (f.exists("yarn.lock")) return "yarn";
@@ -19517,9 +19657,9 @@ function planRouteHooks(text2, route2, name, hooks, opts = {}) {
19517
19657
  const m = routeMapOf(doc, route2);
19518
19658
  const repos = m.get("repos", true);
19519
19659
  let target = m;
19520
- if (isMap2(repos)) {
19660
+ if (isMap3(repos)) {
19521
19661
  const entry = repos.get(name, true);
19522
- if (isMap2(entry)) target = entry;
19662
+ if (isMap3(entry)) target = entry;
19523
19663
  else {
19524
19664
  const created = new YAMLMap();
19525
19665
  created.set("dir", String(repos.get(name)));
@@ -19529,7 +19669,7 @@ function planRouteHooks(text2, route2, name, hooks, opts = {}) {
19529
19669
  }
19530
19670
  const current = target.get("hooks", true);
19531
19671
  let hooksMap;
19532
- if (isMap2(current)) hooksMap = current;
19672
+ if (isMap3(current)) hooksMap = current;
19533
19673
  else {
19534
19674
  hooksMap = new YAMLMap();
19535
19675
  target.set("hooks", hooksMap);
@@ -19554,13 +19694,13 @@ function clearRouteHooks(text2, route2, name) {
19554
19694
  const m = routeMapOf(doc, route2);
19555
19695
  const repos = m.get("repos", true);
19556
19696
  let target = m;
19557
- if (isMap2(repos)) {
19697
+ if (isMap3(repos)) {
19558
19698
  const entry = repos.get(name, true);
19559
- if (!isMap2(entry)) return { text: text2, removed: [] };
19699
+ if (!isMap3(entry)) return { text: text2, removed: [] };
19560
19700
  target = entry;
19561
19701
  }
19562
19702
  const current = target.get("hooks", true);
19563
- if (!isMap2(current)) return { text: text2, removed: [] };
19703
+ if (!isMap3(current)) return { text: text2, removed: [] };
19564
19704
  const removed = current.items.map((i) => String(i.key.value ?? i.key));
19565
19705
  target.delete("hooks");
19566
19706
  return { text: String(doc), removed };
@@ -19593,9 +19733,9 @@ function existingRepoConfig(f) {
19593
19733
  }
19594
19734
 
19595
19735
  // src/repos-hooks-agent.ts
19596
- import { spawn as spawn7, execFileSync as execFileSync9 } from "child_process";
19736
+ import { spawn as spawn7, execFileSync as execFileSync10 } from "child_process";
19597
19737
  import { realpathSync as realpathSync2 } from "fs";
19598
- import { isAbsolute as isAbsolute2, relative } from "path";
19738
+ import { isAbsolute as isAbsolute3, relative } from "path";
19599
19739
  var AgentSuggestError = class extends Error {
19600
19740
  reason;
19601
19741
  constructor(reason, message) {
@@ -19664,7 +19804,7 @@ function parseAgentStream(out, dir) {
19664
19804
  if (ev?.type === "assistant" && Array.isArray(ev.message?.content)) {
19665
19805
  for (const b of ev.message.content) {
19666
19806
  const p = b?.type === "tool_use" && b.name === "Read" ? b.input?.file_path : void 0;
19667
- if (typeof p === "string") files.add(isAbsolute2(p) ? relative(base, p) : p);
19807
+ if (typeof p === "string") files.add(isAbsolute3(p) ? relative(base, p) : p);
19668
19808
  }
19669
19809
  } else if (ev?.type === "result") {
19670
19810
  text2 = typeof ev.result === "string" ? ev.result : "";
@@ -19696,7 +19836,7 @@ function classifyFailure(o) {
19696
19836
  }
19697
19837
  function porcelain(dir) {
19698
19838
  try {
19699
- return execFileSync9("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
19839
+ return execFileSync10("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
19700
19840
  } catch {
19701
19841
  return void 0;
19702
19842
  }
@@ -19711,7 +19851,7 @@ async function runAgentSuggest(o) {
19711
19851
  const before = porcelain(o.dir);
19712
19852
  const timeout = AbortSignal.timeout(o.timeoutMs ?? DEFAULT_TIMEOUT_MS);
19713
19853
  const signal = o.signal ? AbortSignal.any([o.signal, timeout]) : timeout;
19714
- const run3 = await new Promise((resolve7) => {
19854
+ const run3 = await new Promise((resolve8) => {
19715
19855
  let out = "";
19716
19856
  let err3 = "";
19717
19857
  const child = spawn7(bin, args, { cwd: o.dir, env, detached: true, stdio: ["pipe", "pipe", "pipe"] });
@@ -19720,7 +19860,7 @@ async function runAgentSuggest(o) {
19720
19860
  if (settled) return;
19721
19861
  settled = true;
19722
19862
  signal.removeEventListener("abort", onAbort);
19723
- resolve7(r);
19863
+ resolve8(r);
19724
19864
  };
19725
19865
  const onAbort = () => {
19726
19866
  try {
@@ -19765,7 +19905,7 @@ ${run3.err}` });
19765
19905
  }
19766
19906
 
19767
19907
  // src/sample-ticket.ts
19768
- import { basename as basename12, dirname as dirname22, join as join38 } from "path";
19908
+ import { basename as basename12, dirname as dirname23, join as join39 } from "path";
19769
19909
  var SAMPLE_REPO = "crew-sample";
19770
19910
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
19771
19911
  var SAMPLE_MARKER = "sample: true";
@@ -19788,8 +19928,8 @@ has something safe to work on: its test and build hooks always pass, it has no
19788
19928
  remote, and \`crew sample-ticket --remove\` deletes it.
19789
19929
  `;
19790
19930
  function sampleDir(existing, reposBase, workspace) {
19791
- if (existing.length > 0) return join38(dirname22(existing[0]), SAMPLE_REPO);
19792
- return join38(reposBase, workspace, SAMPLE_REPO);
19931
+ if (existing.length > 0) return join39(dirname23(existing[0]), SAMPLE_REPO);
19932
+ return join39(reposBase, workspace, SAMPLE_REPO);
19793
19933
  }
19794
19934
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
19795
19935
  function sampleDescription() {
@@ -19833,8 +19973,8 @@ function planSampleTicket(i) {
19833
19973
  }
19834
19974
 
19835
19975
  // src/worktree-sweep.ts
19836
- import { existsSync as existsSync37, readFileSync as readFileSync34, readdirSync as readdirSync14 } from "fs";
19837
- import { basename as basename13, dirname as dirname23, join as join39, resolve as resolve4 } from "path";
19976
+ import { existsSync as existsSync38, readFileSync as readFileSync35, readdirSync as readdirSync14 } from "fs";
19977
+ import { basename as basename13, dirname as dirname24, join as join40, resolve as resolve5 } from "path";
19838
19978
  function planWorktreeSweep(target, tickets, contract) {
19839
19979
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
19840
19980
  const verified = contract.statuses.verified;
@@ -19898,9 +20038,9 @@ function worktreeTicketNumbers(target) {
19898
20038
  return [...nums];
19899
20039
  }
19900
20040
  function siblingWorktreeDirs(target) {
19901
- const parent = dirname23(target.dir.replace(/[/\\]+$/, ""));
20041
+ const parent = dirname24(target.dir.replace(/[/\\]+$/, ""));
19902
20042
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
19903
- const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
20043
+ const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
19904
20044
  let names;
19905
20045
  try {
19906
20046
  names = readdirSync14(parent);
@@ -19911,8 +20051,8 @@ function siblingWorktreeDirs(target) {
19911
20051
  for (const name of names) {
19912
20052
  if (!name.startsWith(stem)) continue;
19913
20053
  const m = DIR_NUMBER_SUFFIXED.exec(name);
19914
- const path = join39(parent, name);
19915
- if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
20054
+ const path = join40(parent, name);
20055
+ if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
19916
20056
  }
19917
20057
  return out;
19918
20058
  }
@@ -19946,7 +20086,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
19946
20086
  let removed = 0;
19947
20087
  let keptBranches = 0;
19948
20088
  for (const a of actions) {
19949
- if (!dryRun && base && !a.keepBranch && existsSync37(a.path) && isHandBackMerge(a.path, base)) {
20089
+ if (!dryRun && base && !a.keepBranch && existsSync38(a.path) && isHandBackMerge(a.path, base)) {
19950
20090
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
19951
20091
  log.emit(
19952
20092
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -19954,7 +20094,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
19954
20094
  );
19955
20095
  }
19956
20096
  }
19957
- if (!dryRun && existsSync37(a.path)) {
20097
+ if (!dryRun && existsSync38(a.path)) {
19958
20098
  let dirty = false;
19959
20099
  try {
19960
20100
  dirty = status(a.path).length > 0;
@@ -19965,7 +20105,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
19965
20105
  continue;
19966
20106
  }
19967
20107
  }
19968
- if (!dryRun && a.requirePushed && a.branch && existsSync37(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20108
+ if (!dryRun && a.requirePushed && a.branch && existsSync38(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
19969
20109
  refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
19970
20110
  continue;
19971
20111
  }
@@ -20014,7 +20154,7 @@ function isHandBackMerge(path, base) {
20014
20154
  if (!msgPath) return false;
20015
20155
  let first;
20016
20156
  try {
20017
- first = readFileSync34(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
20157
+ first = readFileSync35(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20018
20158
  } catch {
20019
20159
  return false;
20020
20160
  }
@@ -20119,16 +20259,16 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
20119
20259
  }
20120
20260
 
20121
20261
  // src/stream-sweep.ts
20122
- import { existsSync as existsSync38, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync11 } from "fs";
20123
- import { join as join40 } from "path";
20262
+ import { existsSync as existsSync39, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync12 } from "fs";
20263
+ import { join as join41 } from "path";
20124
20264
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
20125
20265
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
20126
- const dir = join40(stateDir, "streams");
20127
- if (!existsSync38(dir)) return [];
20266
+ const dir = join41(stateDir, "streams");
20267
+ if (!existsSync39(dir)) return [];
20128
20268
  const cutoff = now - retentionDays * DAY_MS2;
20129
- return readdirSync15(dir).map((name) => join40(dir, name)).filter((path) => {
20269
+ return readdirSync15(dir).map((name) => join41(dir, name)).filter((path) => {
20130
20270
  try {
20131
- return statSync11(path).mtimeMs < cutoff;
20271
+ return statSync12(path).mtimeMs < cutoff;
20132
20272
  } catch {
20133
20273
  return false;
20134
20274
  }
@@ -20152,11 +20292,11 @@ function applyStreamSweep(paths, dryRun, log) {
20152
20292
  return removed;
20153
20293
  }
20154
20294
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20155
- if (!existsSync38(stateDir)) return [];
20295
+ if (!existsSync39(stateDir)) return [];
20156
20296
  const cutoff = now - retentionDays * DAY_MS2;
20157
- return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join40(stateDir, name)).filter((path) => {
20297
+ return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join41(stateDir, name)).filter((path) => {
20158
20298
  try {
20159
- return statSync11(path).mtimeMs < cutoff;
20299
+ return statSync12(path).mtimeMs < cutoff;
20160
20300
  } catch {
20161
20301
  return false;
20162
20302
  }
@@ -20164,12 +20304,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20164
20304
  }
20165
20305
 
20166
20306
  // src/cli.ts
20167
- 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";
20307
+ 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";
20168
20308
  import { createInterface } from "readline/promises";
20169
20309
 
20170
20310
  // src/periscope-cmd.ts
20171
- import { existsSync as existsSync39, mkdirSync as mkdirSync26, readFileSync as readFileSync35, rmSync as rmSync19, writeFileSync as writeFileSync29 } from "fs";
20172
- import { join as join41 } from "path";
20311
+ import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, rmSync as rmSync19, writeFileSync as writeFileSync30 } from "fs";
20312
+ import { join as join42 } from "path";
20173
20313
  var PeriscopeError = class extends Error {
20174
20314
  };
20175
20315
  var LivePeriscopeError = class extends PeriscopeError {
@@ -20179,22 +20319,22 @@ var LivePeriscopeError = class extends PeriscopeError {
20179
20319
  this.data = { refused: "live_periscope_exists", ...live };
20180
20320
  }
20181
20321
  };
20182
- var priorDir = (stateDir) => join41(stateDir, "periscope-priors");
20183
- var priorPath = (stateDir, ticketRecordId) => join41(priorDir(stateDir), `${ticketRecordId}.json`);
20322
+ var priorDir = (stateDir) => join42(stateDir, "periscope-priors");
20323
+ var priorPath = (stateDir, ticketRecordId) => join42(priorDir(stateDir), `${ticketRecordId}.json`);
20184
20324
  function removePeriscopePrior(stateDir, ticketRecordId) {
20185
20325
  rmSync19(priorPath(stateDir, ticketRecordId), { force: true });
20186
20326
  }
20187
20327
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
20188
20328
  try {
20189
- mkdirSync26(priorDir(stateDir), { recursive: true });
20190
- writeFileSync29(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20329
+ mkdirSync27(priorDir(stateDir), { recursive: true });
20330
+ writeFileSync30(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20191
20331
  } catch {
20192
20332
  }
20193
20333
  }
20194
20334
  function readPeriscopePrior(stateDir, ticketRecordId) {
20195
20335
  try {
20196
20336
  const p = priorPath(stateDir, ticketRecordId);
20197
- return existsSync39(p) ? JSON.parse(readFileSync35(p, "utf8")) : void 0;
20337
+ return existsSync40(p) ? JSON.parse(readFileSync36(p, "utf8")) : void 0;
20198
20338
  } catch {
20199
20339
  return void 0;
20200
20340
  }
@@ -20356,7 +20496,12 @@ ${tail(iso.output)}`);
20356
20496
  const handoff = await d.runHook("handoff", wt.path, env);
20357
20497
  if (handoff && handoff.code !== 0) return abandon(`handoff failed (exit ${handoff.code})
20358
20498
  ${tail(handoff.output)}`);
20359
- const preview = await d.runHook("preview", wt.path, { ...env, CREW_PREVIEW_HOST: d.host });
20499
+ try {
20500
+ d.prepareDropFile(wt.path);
20501
+ } catch (e) {
20502
+ return abandon(`preparing the drop file failed: ${e.message}`);
20503
+ }
20504
+ const preview = await d.runHook("preview", wt.path, { ...env, CREW_PREVIEW_HOST: d.host, ...pointedAtEnv(wt.path) });
20360
20505
  if (!preview || preview.code !== 0) {
20361
20506
  return abandon(`preview exited ${preview?.code ?? "without running"}
20362
20507
  ${tail(preview?.output ?? "")}`);
@@ -20571,7 +20716,8 @@ async function runPeriscopeResume(d, number) {
20571
20716
  ${tail2(iso.output)}`);
20572
20717
  if (iso) Object.assign(env, parseEnvLines(iso.output));
20573
20718
  }
20574
- const preview = await d.runHook("preview", row.worktree, { ...env, CREW_PREVIEW_HOST: row.host || "localhost" });
20719
+ d.prepareDropFile?.(row.worktree);
20720
+ const preview = await d.runHook("preview", row.worktree, { ...env, CREW_PREVIEW_HOST: row.host || "localhost", ...pointedAtEnv(row.worktree) });
20575
20721
  if (!preview || preview.code !== 0) {
20576
20722
  throw new PeriscopeError(`preview exited ${preview?.code ?? "without running"}
20577
20723
  ${tail2(preview?.output ?? "")}`);
@@ -20591,14 +20737,19 @@ ${tail2(preview.output)}`);
20591
20737
  }
20592
20738
 
20593
20739
  // src/periscope-idle.ts
20594
- import { execFileSync as execFileSync10 } from "child_process";
20595
- import { statSync as statSync12 } from "fs";
20596
- import { join as join42 } from "path";
20740
+ import { execFileSync as execFileSync11 } from "child_process";
20741
+ import { statSync as statSync13 } from "fs";
20742
+ import { join as join43 } from "path";
20597
20743
  var MAX_SCANNED_FILES = 2e4;
20598
20744
  function newestWorktreeMtime(root) {
20745
+ const a = newestTrackedMtime(root);
20746
+ const b = dropFileMtime(root);
20747
+ return a === null ? b : b === null ? a : Math.max(a, b);
20748
+ }
20749
+ function newestTrackedMtime(root) {
20599
20750
  let listing;
20600
20751
  try {
20601
- listing = execFileSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
20752
+ listing = execFileSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
20602
20753
  cwd: root,
20603
20754
  encoding: "utf8",
20604
20755
  stdio: ["ignore", "pipe", "ignore"],
@@ -20613,7 +20764,7 @@ function newestWorktreeMtime(root) {
20613
20764
  if (!rel) continue;
20614
20765
  if (++scanned > MAX_SCANNED_FILES) break;
20615
20766
  try {
20616
- const m = statSync12(join42(root, rel)).mtimeMs;
20767
+ const m = statSync13(join43(root, rel)).mtimeMs;
20617
20768
  if (newest === null || m > newest) newest = m;
20618
20769
  } catch {
20619
20770
  }
@@ -20688,21 +20839,21 @@ async function runPeriscopeIdlePass(d) {
20688
20839
  import { dirname as dirOf2, resolve as resolvePath } from "path";
20689
20840
 
20690
20841
  // src/ssh-keys.ts
20691
- import { execFileSync as execFileSync11 } from "child_process";
20692
- import { existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync36, chmodSync as chmodSync3 } from "fs";
20693
- import { dirname as dirname24, join as join43 } from "path";
20842
+ import { execFileSync as execFileSync12 } from "child_process";
20843
+ import { existsSync as existsSync41, mkdirSync as mkdirSync28, readFileSync as readFileSync37, chmodSync as chmodSync3 } from "fs";
20844
+ import { dirname as dirname25, join as join44 } from "path";
20694
20845
  var SshKeyError = class extends Error {
20695
20846
  };
20696
20847
  function shipSshKeyPathFor(stateDir) {
20697
- return join43(stateDir, "ssh", "id_ed25519");
20848
+ return join44(stateDir, "ssh", "id_ed25519");
20698
20849
  }
20699
20850
  function ensureShipSshKeypair(stateDir) {
20700
20851
  const privateKeyPath = shipSshKeyPathFor(stateDir);
20701
20852
  const publicKeyPath = `${privateKeyPath}.pub`;
20702
- if (!existsSync40(privateKeyPath)) {
20703
- mkdirSync27(dirname24(privateKeyPath), { recursive: true });
20853
+ if (!existsSync41(privateKeyPath)) {
20854
+ mkdirSync28(dirname25(privateKeyPath), { recursive: true });
20704
20855
  try {
20705
- execFileSync11("ssh-keygen", [
20856
+ execFileSync12("ssh-keygen", [
20706
20857
  "-t",
20707
20858
  "ed25519",
20708
20859
  "-N",
@@ -20720,15 +20871,15 @@ function ensureShipSshKeypair(stateDir) {
20720
20871
  }
20721
20872
  chmodSync3(privateKeyPath, 384);
20722
20873
  }
20723
- if (!existsSync40(publicKeyPath)) {
20874
+ if (!existsSync41(publicKeyPath)) {
20724
20875
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
20725
20876
  }
20726
- const publicKey = readFileSync36(publicKeyPath, "utf8").trim();
20877
+ const publicKey = readFileSync37(publicKeyPath, "utf8").trim();
20727
20878
  return { privateKeyPath, publicKeyPath, publicKey };
20728
20879
  }
20729
20880
  function sshKeygenAvailable() {
20730
20881
  try {
20731
- execFileSync11("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
20882
+ execFileSync12("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
20732
20883
  return true;
20733
20884
  } catch (e) {
20734
20885
  return e.code !== "ENOENT";
@@ -20887,12 +21038,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
20887
21038
  import { createServer as createHttpServer } from "http";
20888
21039
  import { spawn as spawn8 } from "child_process";
20889
21040
  import { randomBytes } from "crypto";
20890
- import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, writeFileSync as writeFileSync30, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
20891
- import { join as join44 } from "path";
21041
+ import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync31, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21042
+ import { join as join45 } from "path";
20892
21043
 
20893
21044
  // src/passenger-mirror.ts
20894
- import { mkdirSync as mkdirSync28 } from "fs";
20895
- import { dirname as dirname25 } from "path";
21045
+ import { mkdirSync as mkdirSync29 } from "fs";
21046
+ import { dirname as dirname26 } from "path";
20896
21047
  function mirrorRepoOf(spec) {
20897
21048
  const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
20898
21049
  if (fromOrigin) return fromOrigin;
@@ -20910,9 +21061,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
20910
21061
  continue;
20911
21062
  }
20912
21063
  try {
20913
- const parent = dirname25(spec.dir);
20914
- mkdirSync28(parent, { recursive: true });
20915
- mkdirSync28(spec.dir, { recursive: true });
21064
+ const parent = dirname26(spec.dir);
21065
+ mkdirSync29(parent, { recursive: true });
21066
+ mkdirSync29(spec.dir, { recursive: true });
20916
21067
  const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
20917
21068
  out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
20918
21069
  } catch (e) {
@@ -21018,21 +21169,21 @@ var defaultSpawn2 = (cmd, args, env) => {
21018
21169
  return child;
21019
21170
  };
21020
21171
  function syncDaemonStatePath(stateDir, workspaceId) {
21021
- return join44(stateDir, "passengers", `${workspaceId}-sync.json`);
21172
+ return join45(stateDir, "passengers", `${workspaceId}-sync.json`);
21022
21173
  }
21023
21174
  function readPersistedSyncDaemon(stateDir, workspaceId) {
21024
21175
  const path = syncDaemonStatePath(stateDir, workspaceId);
21025
- if (!existsSync41(path)) return void 0;
21176
+ if (!existsSync42(path)) return void 0;
21026
21177
  try {
21027
- return JSON.parse(readFileSync37(path, "utf8"));
21178
+ return JSON.parse(readFileSync38(path, "utf8"));
21028
21179
  } catch {
21029
21180
  return void 0;
21030
21181
  }
21031
21182
  }
21032
21183
  function writePersistedSyncDaemon(stateDir, state) {
21033
21184
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
21034
- mkdirSync29(join44(stateDir, "passengers"), { recursive: true });
21035
- writeFileSync30(path, `${JSON.stringify(state, null, 2)}
21185
+ mkdirSync30(join45(stateDir, "passengers"), { recursive: true });
21186
+ writeFileSync31(path, `${JSON.stringify(state, null, 2)}
21036
21187
  `);
21037
21188
  }
21038
21189
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -21052,8 +21203,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
21052
21203
  }
21053
21204
  }
21054
21205
  function readAllPersistedSyncWorkspaceIds(stateDir) {
21055
- const dir = join44(stateDir, "passengers");
21056
- if (!existsSync41(dir)) return [];
21206
+ const dir = join45(stateDir, "passengers");
21207
+ if (!existsSync42(dir)) return [];
21057
21208
  const suffix = "-sync.json";
21058
21209
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
21059
21210
  }
@@ -21141,14 +21292,14 @@ function describeCause(err3) {
21141
21292
  }
21142
21293
 
21143
21294
  // src/daemon.ts
21144
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync13, writeFileSync as writeFileSync31 } from "fs";
21145
- import { join as join45 } from "path";
21295
+ import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync14, writeFileSync as writeFileSync32 } from "fs";
21296
+ import { join as join46 } from "path";
21146
21297
  import { randomBytes as randomBytes2 } from "crypto";
21147
21298
  var DEFAULT_FLOOR_MS = 5e3;
21148
21299
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
21149
21300
  var ALLOWANCE_PAUSE_MS = 5 * 60 * 1e3;
21150
21301
  var RELOAD_WARN_MS = 10 * 60 * 1e3;
21151
- var defaultSleep = (ms3) => new Promise((resolve7) => setTimeout(resolve7, ms3));
21302
+ var defaultSleep = (ms3) => new Promise((resolve8) => setTimeout(resolve8, ms3));
21152
21303
  async function runDaemonLoop(o) {
21153
21304
  const floor = o.floorMs ?? DEFAULT_FLOOR_MS;
21154
21305
  const ceiling = o.ceilingMs ?? DEFAULT_CEILING_MS;
@@ -21222,7 +21373,7 @@ async function runDaemonLoop(o) {
21222
21373
  function latestMtime(path) {
21223
21374
  let stat2;
21224
21375
  try {
21225
- stat2 = statSync13(path);
21376
+ stat2 = statSync14(path);
21226
21377
  } catch {
21227
21378
  return 0;
21228
21379
  }
@@ -21230,7 +21381,7 @@ function latestMtime(path) {
21230
21381
  if (!stat2.isDirectory()) return 0;
21231
21382
  let latest = stat2.mtimeMs;
21232
21383
  for (const entry of readdirSync17(path, { withFileTypes: true })) {
21233
- latest = Math.max(latest, latestMtime(join45(path, entry.name)));
21384
+ latest = Math.max(latest, latestMtime(join46(path, entry.name)));
21234
21385
  }
21235
21386
  return latest;
21236
21387
  }
@@ -21239,30 +21390,30 @@ function computeTreeSignature(paths) {
21239
21390
  }
21240
21391
  function makeStaleChecker(paths, opts = {}) {
21241
21392
  const initial = computeTreeSignature(paths);
21242
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync42(opts.reloadSentinel);
21393
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync43(opts.reloadSentinel);
21243
21394
  }
21244
21395
  function reloadSentinelFor(stateDir) {
21245
- return join45(stateDir, "reload-requested");
21396
+ return join46(stateDir, "reload-requested");
21246
21397
  }
21247
21398
  function readReloadRequestedAt(stateDir) {
21248
21399
  try {
21249
- return statSync13(reloadSentinelFor(stateDir)).mtimeMs;
21400
+ return statSync14(reloadSentinelFor(stateDir)).mtimeMs;
21250
21401
  } catch {
21251
21402
  return null;
21252
21403
  }
21253
21404
  }
21254
21405
  function daemonStampFor(stateDir) {
21255
- return join45(stateDir, "daemon.started");
21406
+ return join46(stateDir, "daemon.started");
21256
21407
  }
21257
21408
  function noteDaemonStarted(stateDir, now = Date.now()) {
21258
- mkdirSync30(stateDir, { recursive: true });
21409
+ mkdirSync31(stateDir, { recursive: true });
21259
21410
  rmSync20(reloadSentinelFor(stateDir), { force: true });
21260
- writeFileSync31(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
21411
+ writeFileSync32(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
21261
21412
  `);
21262
21413
  }
21263
21414
  function readDaemonStarted(stateDir) {
21264
21415
  try {
21265
- const v = JSON.parse(readFileSync38(daemonStampFor(stateDir), "utf8")).startedAt;
21416
+ const v = JSON.parse(readFileSync39(daemonStampFor(stateDir), "utf8")).startedAt;
21266
21417
  return typeof v === "number" ? v : null;
21267
21418
  } catch {
21268
21419
  return null;
@@ -21271,7 +21422,7 @@ function readDaemonStarted(stateDir) {
21271
21422
  function configChangedAfter(files, startedAt) {
21272
21423
  for (const f of files) {
21273
21424
  try {
21274
- if (statSync13(f).mtimeMs > startedAt) return f;
21425
+ if (statSync14(f).mtimeMs > startedAt) return f;
21275
21426
  } catch {
21276
21427
  }
21277
21428
  }
@@ -21434,8 +21585,8 @@ async function runOnePassUnguarded(o) {
21434
21585
  }
21435
21586
  let settle = () => {
21436
21587
  };
21437
- const settled = new Promise((resolve7) => {
21438
- settle = resolve7;
21588
+ const settled = new Promise((resolve8) => {
21589
+ settle = resolve8;
21439
21590
  });
21440
21591
  const ready = (tookTicket2) => settle(tookTicket2);
21441
21592
  const run3 = spawn11(role, decision, ctx, { ready }).then(() => {
@@ -21695,7 +21846,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
21695
21846
  ship.name,
21696
21847
  (t) => {
21697
21848
  const d = dirForRepo(route2, t.repo_id);
21698
- return d ? originBranchSha(d, t, resolve2) : null;
21849
+ return d ? originBranchSha(d, t, resolve3) : null;
21699
21850
  }
21700
21851
  );
21701
21852
  if (!result.ticket) {
@@ -21828,18 +21979,18 @@ import { createHash as createHash10 } from "crypto";
21828
21979
  import {
21829
21980
  chmodSync as chmodSync4,
21830
21981
  closeSync as closeSync5,
21831
- existsSync as existsSync43,
21982
+ existsSync as existsSync44,
21832
21983
  fstatSync,
21833
21984
  openSync as openSync5,
21834
21985
  readSync as readSync3,
21835
21986
  readdirSync as readdirSync18,
21836
21987
  rmSync as rmSync21,
21837
- statSync as statSync14,
21988
+ statSync as statSync15,
21838
21989
  watch
21839
21990
  } from "fs";
21840
21991
  import { createConnection } from "net";
21841
21992
  import { createServer } from "http";
21842
- import { basename as basename14, dirname as dirname26, join as join46 } from "path";
21993
+ import { basename as basename14, dirname as dirname27, join as join47 } from "path";
21843
21994
  import { execFile as execFile3, spawn as spawn10 } from "child_process";
21844
21995
 
21845
21996
  // src/periscope-control.ts
@@ -21890,7 +22041,7 @@ function lastJsonLine(stdout) {
21890
22041
  }
21891
22042
  return null;
21892
22043
  }
21893
- function parseLine(line) {
22044
+ function parseLine2(line) {
21894
22045
  try {
21895
22046
  const v = JSON.parse(line);
21896
22047
  return v && typeof v === "object" && !Array.isArray(v) ? v : null;
@@ -21918,7 +22069,7 @@ async function execute(h, args, streaming, after) {
21918
22069
  body: null,
21919
22070
  stream: async (write) => {
21920
22071
  const r = await h.run(args, (line) => {
21921
- const ev = parseLine(line);
22072
+ const ev = parseLine2(line);
21922
22073
  if (ev) write(ev);
21923
22074
  });
21924
22075
  const f = await finish(r);
@@ -22033,7 +22184,7 @@ async function handlePeriscopes(h, q2) {
22033
22184
  ], q2.wantsStream, h.mcp ? async (done) => ({ ...done, ...await h.mcp.onUp(picked, done) }) : void 0);
22034
22185
  }
22035
22186
  function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
22036
- return new Promise((resolve7) => {
22187
+ return new Promise((resolve8) => {
22037
22188
  const child = spawn9(process.execPath, [process.argv[1] ?? "", ...args], { stdio: ["ignore", "pipe", "pipe"] });
22038
22189
  let stdout = "";
22039
22190
  let stderr = "";
@@ -22055,12 +22206,12 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
22055
22206
  const t = setTimeout(() => child.kill("SIGTERM"), timeoutMs);
22056
22207
  child.on("error", (e) => {
22057
22208
  clearTimeout(t);
22058
- resolve7({ code: 127, stdout, stderr: `${stderr}${e.message}
22209
+ resolve8({ code: 127, stdout, stderr: `${stderr}${e.message}
22059
22210
  ` });
22060
22211
  });
22061
22212
  child.on("close", (code) => {
22062
22213
  clearTimeout(t);
22063
- resolve7({ code: code ?? 1, stdout, stderr });
22214
+ resolve8({ code: code ?? 1, stdout, stderr });
22064
22215
  });
22065
22216
  });
22066
22217
  }
@@ -22068,7 +22219,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
22068
22219
  // src/control-api.ts
22069
22220
  var PAUSABLE = ["dev", "design", "qa"];
22070
22221
  var CONTROL_SOCKET_NAME = "control.sock";
22071
- var controlSocketFor = (stateDir) => join46(stateDir, CONTROL_SOCKET_NAME);
22222
+ var controlSocketFor = (stateDir) => join47(stateDir, CONTROL_SOCKET_NAME);
22072
22223
  var MAX_SOCKET_PATH = 100;
22073
22224
  var MAX_TAIL_BYTES = 1024 * 1024;
22074
22225
  var MAX_WAIT_SECONDS = 30;
@@ -22155,7 +22306,7 @@ function candidates(live, rotated) {
22155
22306
  const out = [live];
22156
22307
  if (!rotated) return out;
22157
22308
  try {
22158
- for (const n of readdirSync18(dirname26(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join46(dirname26(live), n));
22309
+ for (const n of readdirSync18(dirname27(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join47(dirname27(live), n));
22159
22310
  } catch {
22160
22311
  }
22161
22312
  return out;
@@ -22178,7 +22329,7 @@ function readRange(path, from, max) {
22178
22329
  }
22179
22330
  }
22180
22331
  function lastLines2(path, n) {
22181
- const st = statSync14(path);
22332
+ const st = statSync15(path);
22182
22333
  const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
22183
22334
  if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
22184
22335
  let start = st.size;
@@ -22199,7 +22350,7 @@ function lastLines2(path, n) {
22199
22350
  }
22200
22351
  function readTail(live, opts) {
22201
22352
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
22202
- if (!existsSync43(live)) {
22353
+ if (!existsSync44(live)) {
22203
22354
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
22204
22355
  }
22205
22356
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -22212,7 +22363,7 @@ function readTail(live, opts) {
22212
22363
  } else {
22213
22364
  const found = candidates(live, opts.rotated ?? false).find((p) => {
22214
22365
  try {
22215
- const s = statSync14(p);
22366
+ const s = statSync15(p);
22216
22367
  return s.ino === cur.ino && s.dev === cur.dev;
22217
22368
  } catch {
22218
22369
  return false;
@@ -22223,7 +22374,7 @@ function readTail(live, opts) {
22223
22374
  off = cur.off;
22224
22375
  } else reset = true;
22225
22376
  }
22226
- let size = statSync14(path).size;
22377
+ let size = statSync15(path).size;
22227
22378
  if (off > size) {
22228
22379
  off = 0;
22229
22380
  reset = true;
@@ -22231,12 +22382,12 @@ function readTail(live, opts) {
22231
22382
  if (off >= size && path !== live) {
22232
22383
  path = live;
22233
22384
  off = 0;
22234
- size = statSync14(path).size;
22385
+ size = statSync15(path).size;
22235
22386
  }
22236
22387
  const { text: text2 } = readRange(path, off, max);
22237
22388
  const cut = text2.lastIndexOf("\n");
22238
22389
  const whole = cut < 0 ? "" : text2.slice(0, cut + 1);
22239
- const st = statSync14(path);
22390
+ const st = statSync15(path);
22240
22391
  return {
22241
22392
  lines: whole.split("\n").filter((l) => l.length),
22242
22393
  cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
@@ -22244,7 +22395,7 @@ function readTail(live, opts) {
22244
22395
  };
22245
22396
  }
22246
22397
  function waitForChange(dir, ms3, signal) {
22247
- return new Promise((resolve7) => {
22398
+ return new Promise((resolve8) => {
22248
22399
  let w;
22249
22400
  const done = () => {
22250
22401
  clearTimeout(t);
@@ -22254,7 +22405,7 @@ function waitForChange(dir, ms3, signal) {
22254
22405
  w?.close();
22255
22406
  } catch {
22256
22407
  }
22257
- resolve7();
22408
+ resolve8();
22258
22409
  };
22259
22410
  const t = setTimeout(done, ms3);
22260
22411
  const poll = setInterval(done, 500);
@@ -22315,7 +22466,7 @@ async function tailReply(path, q2, rotated, abort) {
22315
22466
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
22316
22467
  let r = readTail(path, { after, tail: tail3, rotated });
22317
22468
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
22318
- await waitForChange(dirname26(path), deadline - Date.now(), abort);
22469
+ await waitForChange(dirname27(path), deadline - Date.now(), abort);
22319
22470
  r = readTail(path, { after: r.cursor, rotated });
22320
22471
  }
22321
22472
  return reply2(200, r);
@@ -22355,8 +22506,8 @@ async function route(deps, method, url, req, abort) {
22355
22506
  if (p.startsWith("/streams/") && get2) {
22356
22507
  const run3 = decodeURIComponent(p.slice("/streams/".length));
22357
22508
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
22358
- const file = join46(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
22359
- if (!existsSync43(file)) return err2(404, "no such run");
22509
+ const file = join47(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
22510
+ if (!existsSync44(file)) return err2(404, "no such run");
22360
22511
  return tailReply(file, url.searchParams, false, abort);
22361
22512
  }
22362
22513
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -22400,14 +22551,14 @@ function send(res, r) {
22400
22551
  res.end(body);
22401
22552
  }
22402
22553
  async function socketIsStale(path) {
22403
- if (!existsSync43(path)) return false;
22404
- return new Promise((resolve7) => {
22554
+ if (!existsSync44(path)) return false;
22555
+ return new Promise((resolve8) => {
22405
22556
  const c = createConnection(path);
22406
22557
  c.once("connect", () => {
22407
22558
  c.destroy();
22408
- resolve7(false);
22559
+ resolve8(false);
22409
22560
  });
22410
- c.once("error", () => resolve7(true));
22561
+ c.once("error", () => resolve8(true));
22411
22562
  });
22412
22563
  }
22413
22564
  async function startControlServer(deps) {
@@ -22419,7 +22570,7 @@ async function startControlServer(deps) {
22419
22570
  return null;
22420
22571
  }
22421
22572
  if (await socketIsStale(path)) rmSync21(path, { force: true });
22422
- else if (existsSync43(path)) {
22573
+ else if (existsSync44(path)) {
22423
22574
  log(`control API not started: ${path} is in use by another process`);
22424
22575
  return null;
22425
22576
  }
@@ -22449,10 +22600,10 @@ async function startControlServer(deps) {
22449
22600
  }
22450
22601
  })();
22451
22602
  });
22452
- return new Promise((resolve7) => {
22603
+ return new Promise((resolve8) => {
22453
22604
  server.once("error", (e) => {
22454
22605
  log(`control API not started: ${e.message}`);
22455
- resolve7(null);
22606
+ resolve8(null);
22456
22607
  });
22457
22608
  server.listen(path, () => {
22458
22609
  try {
@@ -22460,7 +22611,7 @@ async function startControlServer(deps) {
22460
22611
  } catch {
22461
22612
  }
22462
22613
  server.on("error", (e) => log(`control API error: ${e.message}`));
22463
- resolve7({
22614
+ resolve8({
22464
22615
  server,
22465
22616
  path,
22466
22617
  close: () => new Promise((done) => {
@@ -22491,13 +22642,13 @@ function schedulerHandler(control, run3 = runDetached) {
22491
22642
  };
22492
22643
  }
22493
22644
  function crewJson(args, timeoutMs = 6e4) {
22494
- return new Promise((resolve7) => {
22645
+ return new Promise((resolve8) => {
22495
22646
  execFile3(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
22496
22647
  try {
22497
22648
  const v = JSON.parse(String(stdout));
22498
- resolve7(v && typeof v === "object" && !Array.isArray(v) ? v : null);
22649
+ resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
22499
22650
  } catch {
22500
- resolve7(null);
22651
+ resolve8(null);
22501
22652
  }
22502
22653
  });
22503
22654
  });
@@ -22505,31 +22656,31 @@ function crewJson(args, timeoutMs = 6e4) {
22505
22656
 
22506
22657
  // src/periscope-mcp.ts
22507
22658
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
22508
- import { basename as basename15, dirname as dirname27, isAbsolute as isAbsolute3, join as join48, relative as relative2, resolve as resolve5, sep } from "path";
22659
+ import { basename as basename15, dirname as dirname28, isAbsolute as isAbsolute4, join as join49, relative as relative2, resolve as resolve6, sep } from "path";
22509
22660
  import { z } from "zod";
22510
22661
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22511
22662
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
22512
22663
 
22513
22664
  // src/periscope-endpoints.ts
22514
22665
  import { randomBytes as randomBytes3 } from "crypto";
22515
- import { chmodSync as chmodSync5, existsSync as existsSync44, mkdirSync as mkdirSync31, readdirSync as readdirSync19, readFileSync as readFileSync39, rmSync as rmSync22, writeFileSync as writeFileSync32 } from "fs";
22516
- import { join as join47 } from "path";
22517
- var secretDir = (stateDir) => join47(stateDir, "periscope-mcp");
22518
- var secretFile = (stateDir, id) => join47(secretDir(stateDir), `${id}.json`);
22519
- var endpointDir = (stateDir) => join47(stateDir, "periscope-endpoints");
22520
- var endpointFile = (stateDir, id) => join47(endpointDir(stateDir), `${id}.json`);
22666
+ import { chmodSync as chmodSync5, existsSync as existsSync45, mkdirSync as mkdirSync32, readdirSync as readdirSync19, readFileSync as readFileSync40, rmSync as rmSync22, writeFileSync as writeFileSync33 } from "fs";
22667
+ import { join as join48 } from "path";
22668
+ var secretDir = (stateDir) => join48(stateDir, "periscope-mcp");
22669
+ var secretFile = (stateDir, id) => join48(secretDir(stateDir), `${id}.json`);
22670
+ var endpointDir = (stateDir) => join48(stateDir, "periscope-endpoints");
22671
+ var endpointFile = (stateDir, id) => join48(endpointDir(stateDir), `${id}.json`);
22521
22672
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
22522
- mkdirSync31(secretDir(stateDir), { recursive: true, mode: 448 });
22673
+ mkdirSync32(secretDir(stateDir), { recursive: true, mode: 448 });
22523
22674
  const secret = randomBytes3(32).toString("base64url");
22524
22675
  const file = secretFile(stateDir, ticketRecordId);
22525
- writeFileSync32(file, `${JSON.stringify({ secret })}
22676
+ writeFileSync33(file, `${JSON.stringify({ secret })}
22526
22677
  `, { mode: 384 });
22527
22678
  chmodSync5(file, 384);
22528
22679
  return secret;
22529
22680
  }
22530
22681
  function readPeriscopeSecret(stateDir, ticketRecordId) {
22531
22682
  try {
22532
- const v = JSON.parse(readFileSync39(secretFile(stateDir, ticketRecordId), "utf8"));
22683
+ const v = JSON.parse(readFileSync40(secretFile(stateDir, ticketRecordId), "utf8"));
22533
22684
  return typeof v.secret === "string" && v.secret ? v.secret : void 0;
22534
22685
  } catch {
22535
22686
  return void 0;
@@ -22540,14 +22691,14 @@ function removePeriscopeSecret(stateDir, ticketRecordId) {
22540
22691
  }
22541
22692
  function readPeriscopeEndpoint(stateDir, ticketRecordId) {
22542
22693
  try {
22543
- return JSON.parse(readFileSync39(endpointFile(stateDir, ticketRecordId), "utf8"));
22694
+ return JSON.parse(readFileSync40(endpointFile(stateDir, ticketRecordId), "utf8"));
22544
22695
  } catch {
22545
22696
  return void 0;
22546
22697
  }
22547
22698
  }
22548
22699
  function writePeriscopeEndpoint(stateDir, rec) {
22549
- mkdirSync31(endpointDir(stateDir), { recursive: true });
22550
- writeFileSync32(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
22700
+ mkdirSync32(endpointDir(stateDir), { recursive: true });
22701
+ writeFileSync33(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
22551
22702
  `);
22552
22703
  }
22553
22704
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
@@ -22555,7 +22706,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
22555
22706
  }
22556
22707
  function listPeriscopeEndpoints(stateDir) {
22557
22708
  const dir = endpointDir(stateDir);
22558
- if (!existsSync44(dir)) return [];
22709
+ if (!existsSync45(dir)) return [];
22559
22710
  const out = [];
22560
22711
  for (const name of readdirSync19(dir)) {
22561
22712
  if (!name.endsWith(".json")) continue;
@@ -22624,7 +22775,7 @@ var PathRefusal = class extends Error {
22624
22775
  };
22625
22776
  var isInside = (root, p) => {
22626
22777
  const rel = relative2(root, p);
22627
- return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute3(rel);
22778
+ return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute4(rel);
22628
22779
  };
22629
22780
  async function realAncestor(p) {
22630
22781
  let cur = p;
@@ -22643,10 +22794,10 @@ async function realAncestor(p) {
22643
22794
  }
22644
22795
  if (isLink) {
22645
22796
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
22646
- cur = resolve5(dirname27(cur), await readlink(cur));
22797
+ cur = resolve6(dirname28(cur), await readlink(cur));
22647
22798
  continue;
22648
22799
  }
22649
- const parent = dirname27(cur);
22800
+ const parent = dirname28(cur);
22650
22801
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
22651
22802
  tail3.push(basename15(cur));
22652
22803
  cur = parent;
@@ -22656,15 +22807,15 @@ async function resolveInWorktree(root, requested) {
22656
22807
  if (!requested || requested.includes("\0")) throw new PathRefusal("path is required");
22657
22808
  const realRoot = await realpath(root);
22658
22809
  let abs;
22659
- if (isAbsolute3(requested)) {
22660
- abs = resolve5(requested);
22661
- if (isInside(root, abs)) abs = join48(realRoot, relative2(root, abs));
22810
+ if (isAbsolute4(requested)) {
22811
+ abs = resolve6(requested);
22812
+ if (isInside(root, abs)) abs = join49(realRoot, relative2(root, abs));
22662
22813
  } else {
22663
- abs = resolve5(realRoot, requested);
22814
+ abs = resolve6(realRoot, requested);
22664
22815
  }
22665
22816
  if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
22666
22817
  const { real, rest } = await realAncestor(abs);
22667
- const final = rest ? join48(real, rest) : real;
22818
+ const final = rest ? join49(real, rest) : real;
22668
22819
  if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
22669
22820
  if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
22670
22821
  return final;
@@ -22699,7 +22850,7 @@ async function hookEnv(d, sc, name) {
22699
22850
  if (iso && iso.code === 0) Object.assign(env, parseEnvLines(iso.output));
22700
22851
  }
22701
22852
  }
22702
- if (name === "preview") env.CREW_PREVIEW_HOST = sc.host || "localhost";
22853
+ if (name === "preview") Object.assign(env, { CREW_PREVIEW_HOST: sc.host || "localhost" }, pointedAtEnv(sc.worktree));
22703
22854
  return env;
22704
22855
  }
22705
22856
  function createPeriscopeMcpServer(d, sc) {
@@ -22732,7 +22883,7 @@ function createPeriscopeMcpServer(d, sc) {
22732
22883
  const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
22733
22884
  const lines2 = await Promise.all(entries.map(async (e) => {
22734
22885
  if (e.isDirectory()) return `[DIR] ${e.name}`;
22735
- const size = (await lstat(join48(abs, e.name))).size;
22886
+ const size = (await lstat(join49(abs, e.name))).size;
22736
22887
  return `[FILE] ${e.name} (${size} bytes)`;
22737
22888
  }));
22738
22889
  return text(lines2.join("\n") || "(empty)");
@@ -22757,7 +22908,7 @@ function createPeriscopeMcpServer(d, sc) {
22757
22908
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
22758
22909
  const abs = await inTree(path);
22759
22910
  if (abs === await realpath(root)) return text("path must name a file", true);
22760
- await mkdir(dirname27(abs), { recursive: true });
22911
+ await mkdir(dirname28(abs), { recursive: true });
22761
22912
  await inTree(path);
22762
22913
  await writeFile(abs, content);
22763
22914
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -22797,8 +22948,8 @@ function createPeriscopeMcpServer(d, sc) {
22797
22948
  inputSchema: { path: z.string() },
22798
22949
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
22799
22950
  }, async ({ path }) => {
22800
- const parent = await inTree(dirname27(path) === "" ? "." : dirname27(path));
22801
- const abs = join48(parent, basename15(path));
22951
+ const parent = await inTree(dirname28(path) === "" ? "." : dirname28(path));
22952
+ const abs = join49(parent, basename15(path));
22802
22953
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
22803
22954
  const st = await lstat(abs).catch(() => void 0);
22804
22955
  if (!st) return text(`${path} does not exist`, true);
@@ -22919,7 +23070,7 @@ function findPeriscope(sidecars, segment) {
22919
23070
  }
22920
23071
 
22921
23072
  // src/cli.ts
22922
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname28(process.execPath) : resolve6(dirname28(fileURLToPath(import.meta.url)), "..");
23073
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname29(process.execPath) : resolve7(dirname29(fileURLToPath(import.meta.url)), "..");
22923
23074
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
22924
23075
  var CYCLE_SECONDS2 = 120;
22925
23076
  var STALE_CYCLES = 3;
@@ -23287,7 +23438,7 @@ if (command === "provision") {
23287
23438
  process.exit(outcome.code);
23288
23439
  }
23289
23440
  if (command === "pool-member") {
23290
- const pool = poolConfigFrom(argv, process.env, () => readFileSync40(0, "utf8"));
23441
+ const pool = poolConfigFrom(argv, process.env, () => readFileSync41(0, "utf8"));
23291
23442
  if (typeof pool === "string") {
23292
23443
  process.stderr.write(`crew pool-member: ${pool}
23293
23444
  `);
@@ -23515,7 +23666,7 @@ if (command === "passenger-sync-daemon") {
23515
23666
  } else {
23516
23667
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
23517
23668
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
23518
- if (configless && !existsSync45(findConfigFile(CREW_HOME))) {
23669
+ if (configless && !existsSync46(findConfigFile(CREW_HOME))) {
23519
23670
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
23520
23671
  } else {
23521
23672
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -23530,7 +23681,7 @@ if (command === "passenger-sync-daemon") {
23530
23681
  throw e;
23531
23682
  }
23532
23683
  if (command === "credential") {
23533
- const r = runCredential(positional[1], readFileSync40(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
23684
+ const r = runCredential(positional[1], readFileSync41(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
23534
23685
  process.stdout.write(r.out);
23535
23686
  process.stderr.write(r.err);
23536
23687
  process.exit(r.code);
@@ -23542,9 +23693,9 @@ if (command === "passenger-sync-daemon") {
23542
23693
  forges: cfg.ship.forges,
23543
23694
  stateDir: cfg.ship.stateDir,
23544
23695
  configFile: cfg.configFile,
23545
- readFile: (p) => readFileSync40(p, "utf8"),
23546
- writeFile: (p, t) => writeFileSync33(p, t),
23547
- stdin: () => readFileSync40(0, "utf8"),
23696
+ readFile: (p) => readFileSync41(p, "utf8"),
23697
+ writeFile: (p, t) => writeFileSync34(p, t),
23698
+ stdin: () => readFileSync41(0, "utf8"),
23548
23699
  env: process.env
23549
23700
  }
23550
23701
  );
@@ -23837,8 +23988,8 @@ if (command === "passenger-sync-daemon") {
23837
23988
  }
23838
23989
  if (host === "linux" && hasSystemd) {
23839
23990
  const label = labelFor(CREW_HOME, "run");
23840
- const staleTimer = join49(homedir11(), ".config", "systemd", "user", `${label}.timer`);
23841
- if (existsSync45(staleTimer)) {
23991
+ const staleTimer = join50(homedir11(), ".config", "systemd", "user", `${label}.timer`);
23992
+ if (existsSync46(staleTimer)) {
23842
23993
  process.stdout.write(
23843
23994
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
23844
23995
  `
@@ -23865,7 +24016,7 @@ if (command === "passenger-sync-daemon") {
23865
24016
  const tracker = new Tracker(c, cfg.ship);
23866
24017
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
23867
24018
  for (const r of await resolvedRepos(c)) {
23868
- if (!existsSync45(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
24019
+ if (!existsSync46(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
23869
24020
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
23870
24021
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
23871
24022
  }
@@ -23881,9 +24032,18 @@ if (command === "passenger-sync-daemon") {
23881
24032
  }
23882
24033
  return { problems, readiness };
23883
24034
  }
24035
+ async function doctorShipKind(route3, ship) {
24036
+ try {
24037
+ const rows = await new Tracker(route3, ship).shipRows();
24038
+ const mine = matchShipRows(rows, ship.name, doctorMachineId(ship.stateDir)).rows;
24039
+ return mine.length === 1 ? mine[0].kind ?? null : null;
24040
+ } catch {
24041
+ return null;
24042
+ }
24043
+ }
23884
24044
  async function doctorFix(o) {
23885
24045
  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))));
23886
- const unitExists = (p) => existsSync45(p);
24046
+ const unitExists = (p) => existsSync46(p);
23887
24047
  const crontab = o.host === "linux" ? spawnSync10("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
23888
24048
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
23889
24049
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -23900,7 +24060,7 @@ if (command === "passenger-sync-daemon") {
23900
24060
  for (const a of plan.actions) {
23901
24061
  if (a.kind === "enable") {
23902
24062
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
23903
- writeFileSync33(cfg.configFile, planEnable(readFileSync40(cfg.configFile, "utf8"), a.route));
24063
+ writeFileSync34(cfg.configFile, planEnable(readFileSync41(cfg.configFile, "utf8"), a.route));
23904
24064
  enabledSound.add(a.route);
23905
24065
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
23906
24066
  } else {
@@ -24641,7 +24801,7 @@ ${cu.tail.trimEnd()}
24641
24801
  cfg.ship.name,
24642
24802
  (t) => {
24643
24803
  const d = dirForRepo(w.route, t.repo_id);
24644
- return d ? originBranchSha(d, t, resolve2) : null;
24804
+ return d ? originBranchSha(d, t, resolve3) : null;
24645
24805
  }
24646
24806
  );
24647
24807
  if (!result.ticket) {
@@ -24853,7 +25013,7 @@ ${cu.tail.trimEnd()}
24853
25013
  cfg.ship.name,
24854
25014
  (t) => {
24855
25015
  const d = dirForRepo(route2, t.repo_id);
24856
- return d ? originBranchSha(d, t, resolve2) : null;
25016
+ return d ? originBranchSha(d, t, resolve3) : null;
24857
25017
  }
24858
25018
  );
24859
25019
  if (!result.ticket) {
@@ -25039,7 +25199,7 @@ ${cu.tail.trimEnd()}
25039
25199
  process.exit(1);
25040
25200
  }
25041
25201
  requireArmed("run the daemon loop");
25042
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join49(CREW_HOME, "src"), join49(CREW_HOME, "dist"), join49(CREW_HOME, "bin")];
25202
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join50(CREW_HOME, "src"), join50(CREW_HOME, "dist"), join50(CREW_HOME, "bin")];
25043
25203
  const configPaths = daemonConfigPaths(cfg);
25044
25204
  watchPaths.push(...configPaths);
25045
25205
  noteDaemonStarted(cfg.ship.stateDir);
@@ -25344,10 +25504,10 @@ ${cu.tail.trimEnd()}
25344
25504
  `);
25345
25505
  if (!dryRun) {
25346
25506
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
25347
- const raw = existsSync45(resolvedPath) ? JSON.parse(readFileSync40(resolvedPath, "utf8")) : {};
25507
+ const raw = existsSync46(resolvedPath) ? JSON.parse(readFileSync41(resolvedPath, "utf8")) : {};
25348
25508
  raw.agentSkills = result.agentSkills;
25349
- mkdirSync32(dirOf2(resolvedPath), { recursive: true });
25350
- writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
25509
+ mkdirSync33(dirOf2(resolvedPath), { recursive: true });
25510
+ writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
25351
25511
  `);
25352
25512
  }
25353
25513
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -25370,7 +25530,7 @@ ${cu.tail.trimEnd()}
25370
25530
  throw e;
25371
25531
  }
25372
25532
  const oldName = cfg.ship.name;
25373
- writeFileSync33(cfg.configFile, setShipNameInYaml(readFileSync40(cfg.configFile, "utf8"), newName));
25533
+ writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), newName));
25374
25534
  cfg.ship.name = newName;
25375
25535
  let renameMachineId;
25376
25536
  try {
@@ -25417,14 +25577,14 @@ ${cu.tail.trimEnd()}
25417
25577
  process.exit(2);
25418
25578
  };
25419
25579
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
25420
- const configText = readFileSync40(cfg.configFile, "utf8");
25580
+ const configText = readFileSync41(cfg.configFile, "utf8");
25421
25581
  if (sub === "check") {
25422
25582
  try {
25423
25583
  const routeArg = value("route") ?? positional[2];
25424
25584
  const found = cfg.routes.find((r) => r.route === routeArg);
25425
25585
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
25426
25586
  const named2 = value("route") ? void 0 : positional[3];
25427
- const dir = named2 ? repoDirOf(configText, dirname28(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
25587
+ const dir = named2 ? repoDirOf(configText, dirname29(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
25428
25588
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
25429
25589
  const o = target ? found.repoOverrides[target.name] : void 0;
25430
25590
  runReposCheck(dir, {
@@ -25447,9 +25607,9 @@ ${cu.tail.trimEnd()}
25447
25607
  if (sub === "hooks") {
25448
25608
  try {
25449
25609
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
25450
- const dir = repoDirOf(configText, dirname28(cfg.configFile), a.route, a.name);
25451
- if (!existsSync45(dir)) throw new ReposError(`${dir} does not exist on disk`);
25452
- const files = { exists: (r) => existsSync45(resolvePath(dir, r)), list: (r) => {
25610
+ const dir = repoDirOf(configText, dirname29(cfg.configFile), a.route, a.name);
25611
+ if (!existsSync46(dir)) throw new ReposError(`${dir} does not exist on disk`);
25612
+ const files = { exists: (r) => existsSync46(resolvePath(dir, r)), list: (r) => {
25453
25613
  try {
25454
25614
  return readdirSync20(resolvePath(dir, r));
25455
25615
  } catch {
@@ -25457,7 +25617,7 @@ ${cu.tail.trimEnd()}
25457
25617
  }
25458
25618
  }, read: (r) => {
25459
25619
  try {
25460
- return readFileSync40(resolvePath(dir, r), "utf8");
25620
+ return readFileSync41(resolvePath(dir, r), "utf8");
25461
25621
  } catch {
25462
25622
  return void 0;
25463
25623
  }
@@ -25499,19 +25659,19 @@ ${cu.tail.trimEnd()}
25499
25659
  let cleared;
25500
25660
  if (clear2) {
25501
25661
  const plan = clearRouteHooks(configText, a.route, a.name);
25502
- if (plan.removed.length > 0) writeFileSync33(cfg.configFile, plan.text);
25662
+ if (plan.removed.length > 0) writeFileSync34(cfg.configFile, plan.text);
25503
25663
  cleared = { file: cfg.configFile, removed: plan.removed };
25504
25664
  }
25505
25665
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
25506
25666
  if (write === "route") {
25507
25667
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
25508
- writeFileSync33(cfg.configFile, plan.text);
25668
+ writeFileSync34(cfg.configFile, plan.text);
25509
25669
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
25510
25670
  } else if (write === "repo") {
25511
25671
  const have = existingRepoConfig(files);
25512
25672
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
25513
25673
  const file = resolvePath(dir, ".crew.yaml");
25514
- writeFileSync33(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
25674
+ writeFileSync34(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
25515
25675
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
25516
25676
  }
25517
25677
  if (flag("json")) {
@@ -25567,7 +25727,7 @@ ${cu.tail.trimEnd()}
25567
25727
  if (sub === "list" && !routeName) usage2();
25568
25728
  if (sub === "add") {
25569
25729
  try {
25570
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync45(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
25730
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync46(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
25571
25731
  routeName = a.route;
25572
25732
  addPath = a.path;
25573
25733
  addName = a.name;
@@ -25597,7 +25757,7 @@ ${cu.tail.trimEnd()}
25597
25757
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
25598
25758
  if (sub === "list" || sub === "add") {
25599
25759
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
25600
- if (liveRoute?.resolved?.reposModelId && existsSync45(resolvedFile) && !dryRun) {
25760
+ if (liveRoute?.resolved?.reposModelId && existsSync46(resolvedFile) && !dryRun) {
25601
25761
  try {
25602
25762
  await hydrateApiKeys([liveRoute]);
25603
25763
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -25606,16 +25766,16 @@ ${cu.tail.trimEnd()}
25606
25766
  }
25607
25767
  }
25608
25768
  let resolvedRemotes;
25609
- if (existsSync45(resolvedFile)) {
25769
+ if (existsSync46(resolvedFile)) {
25610
25770
  try {
25611
- resolvedRemotes = JSON.parse(readFileSync40(resolvedFile, "utf8"));
25771
+ resolvedRemotes = JSON.parse(readFileSync41(resolvedFile, "utf8"));
25612
25772
  } catch {
25613
25773
  }
25614
25774
  }
25615
25775
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
25616
25776
  const text2 = configText;
25617
- const base = dirname28(cfg.configFile);
25618
- const isCheckout = (d) => existsSync45(`${d}/.git`);
25777
+ const base = dirname29(cfg.configFile);
25778
+ const isCheckout = (d) => existsSync46(`${d}/.git`);
25619
25779
  try {
25620
25780
  if (sub === "list") {
25621
25781
  if (flag("json")) {
@@ -25625,11 +25785,11 @@ ${cu.tail.trimEnd()}
25625
25785
  base,
25626
25786
  resolved: resolvedRemotes,
25627
25787
  originOf,
25628
- exists: (d) => existsSync45(d),
25788
+ exists: (d) => existsSync46(d),
25629
25789
  isCheckout,
25630
25790
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
25631
25791
  try {
25632
- return Object.keys(parseRepoConfig(readFileSync40(resolvePath(d, f), "utf8"), f).hooks).length > 0;
25792
+ return Object.keys(parseRepoConfig(readFileSync41(resolvePath(d, f), "utf8"), f).hooks).length > 0;
25633
25793
  } catch {
25634
25794
  return false;
25635
25795
  }
@@ -25646,7 +25806,7 @@ ${cu.tail.trimEnd()}
25646
25806
  for (const r of repos) {
25647
25807
  const origin2 = originOf(r.dir);
25648
25808
  const row = matchRepoName(origin2, resolvedRemotes);
25649
- const note = !existsSync45(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
25809
+ const note = !existsSync46(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
25650
25810
  process.stdout.write(`${r.name} ${r.dir} ${note}
25651
25811
  `);
25652
25812
  }
@@ -25658,9 +25818,9 @@ ${cu.tail.trimEnd()}
25658
25818
  ` : `${human}
25659
25819
  `);
25660
25820
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
25661
- if (existsSync45(plan2.target) && (existsSync45(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
25821
+ if (existsSync46(plan2.target) && (existsSync46(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
25662
25822
  throw new ReposError(
25663
- 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`
25823
+ 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`
25664
25824
  );
25665
25825
  }
25666
25826
  if (dryRun) {
@@ -25678,14 +25838,14 @@ ${cu.tail.trimEnd()}
25678
25838
  throw new ReposError(`${e.message}${authHint}
25679
25839
  crew.yaml was not changed`);
25680
25840
  }
25681
- writeFileSync33(cfg.configFile, plan2.text);
25841
+ writeFileSync34(cfg.configFile, plan2.text);
25682
25842
  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:
25683
25843
  `);
25684
25844
  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}`);
25685
25845
  break;
25686
25846
  }
25687
25847
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
25688
- const top = existsSync45(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
25848
+ const top = existsSync46(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
25689
25849
  if (!top) throw new ReposError(`${target} is not a git checkout`);
25690
25850
  const origin = originOf(top);
25691
25851
  const plan = planRepoAdd({
@@ -25737,15 +25897,15 @@ ${cu.tail.trimEnd()}
25737
25897
  const created = await rowTracker.createRepoRow(rowPayload);
25738
25898
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
25739
25899
  `);
25740
- if (existsSync45(resolvedFile)) {
25741
- const raw = JSON.parse(readFileSync40(resolvedFile, "utf8"));
25900
+ if (existsSync46(resolvedFile)) {
25901
+ const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
25742
25902
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
25743
25903
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
25744
- writeFileSync33(resolvedFile, `${JSON.stringify(raw, null, 2)}
25904
+ writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
25745
25905
  `);
25746
25906
  }
25747
25907
  }
25748
- writeFileSync33(cfg.configFile, plan.text);
25908
+ writeFileSync34(cfg.configFile, plan.text);
25749
25909
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
25750
25910
  `);
25751
25911
  } catch (e) {
@@ -26114,7 +26274,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26114
26274
  process.exit(2);
26115
26275
  }
26116
26276
  if (found.shipNameAdopted && !dryRun) {
26117
- writeFileSync33(cfg.configFile, setShipNameInYaml(readFileSync40(cfg.configFile, "utf8"), found.shipNameAdopted));
26277
+ writeFileSync34(cfg.configFile, setShipNameInYaml(readFileSync41(cfg.configFile, "utf8"), found.shipNameAdopted));
26118
26278
  cfg.ship.name = found.shipNameAdopted;
26119
26279
  }
26120
26280
  if (found.shipRow) {
@@ -26157,10 +26317,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26157
26317
  }
26158
26318
  }
26159
26319
  let previous;
26160
- const hadPreviousFile = existsSync45(resolvedPath);
26320
+ const hadPreviousFile = existsSync46(resolvedPath);
26161
26321
  if (hadPreviousFile) {
26162
26322
  try {
26163
- previous = JSON.parse(readFileSync40(resolvedPath, "utf8"));
26323
+ previous = JSON.parse(readFileSync41(resolvedPath, "utf8"));
26164
26324
  } catch {
26165
26325
  }
26166
26326
  }
@@ -26290,10 +26450,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26290
26450
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
26291
26451
  `);
26292
26452
  } else {
26293
- mkdirSync32(dirname28(resolvedPath), { recursive: true });
26453
+ mkdirSync33(dirname29(resolvedPath), { recursive: true });
26294
26454
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
26295
26455
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
26296
- writeFileSync33(tmpPath, `${JSON.stringify(resolved, null, 2)}
26456
+ writeFileSync34(tmpPath, `${JSON.stringify(resolved, null, 2)}
26297
26457
  `);
26298
26458
  renameSync6(tmpPath, resolvedPath);
26299
26459
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -26327,7 +26487,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26327
26487
  }
26328
26488
  let apiKeyFile;
26329
26489
  const apiKeyVar = "CREW_API_KEY";
26330
- if (firstRunFromFlags && agentBinSearched && !dryRun) {
26490
+ if (firstRunFromFlags && agentBinSearched && !dryRun && !isManagedShipKind(value("ship-kind"))) {
26331
26491
  const msg = `no agent binary: "claude" was not found on PATH or in ${agentBinSearched.join(", ")} \u2014 pass --agent-bin /absolute/path`;
26332
26492
  process.stderr.write(`crew connect: ${msg}
26333
26493
  `);
@@ -26339,8 +26499,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
26339
26499
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
26340
26500
  `);
26341
26501
  } else {
26342
- mkdirSync32(dirname28(apiKeyFile), { recursive: true });
26343
- writeFileSync33(apiKeyFile, `${apiKeyVar}=${apiKey}
26502
+ mkdirSync33(dirname29(apiKeyFile), { recursive: true });
26503
+ writeFileSync34(apiKeyFile, `${apiKeyVar}=${apiKey}
26344
26504
  `, { mode: 384 });
26345
26505
  process.stderr.write(`API key written to ${apiKeyFile}
26346
26506
  `);
@@ -27067,7 +27227,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27067
27227
  };
27068
27228
  if (subP === "list") {
27069
27229
  const entries = periscopeListEntries(
27070
- (await Promise.all(routesP.map(gather))).flat(),
27230
+ (await Promise.all(routesP.map(gather))).flat().map((r) => ({ ...r, pointedAt: readPointedAt(r.worktree) })),
27071
27231
  /* @__PURE__ */ new Date(),
27072
27232
  (r) => pidsInWorktree(r.worktree, Number(r.number)).length > 0
27073
27233
  );
@@ -27215,7 +27375,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27215
27375
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
27216
27376
  const resumed = await runPeriscopeResume({
27217
27377
  find: async () => hit && { row: hit.row, tag: hit.tag },
27218
- worktreeExists: (p) => existsSync45(p),
27378
+ worktreeExists: (p) => existsSync46(p),
27379
+ prepareDropFile,
27219
27380
  caps: periscopeCaps(cfg.ship),
27220
27381
  runHook: async (name, cwd, env) => {
27221
27382
  const script = hit?.repo.config.hooks[name];
@@ -27309,10 +27470,10 @@ ${where.join("\n")}`);
27309
27470
  },
27310
27471
  prepare: async (path) => {
27311
27472
  for (const f of repoP.config.worktrees.copy) {
27312
- const src = join49(repoP.dir, f);
27313
- if (!existsSync45(src)) continue;
27314
- mkdirSync32(dirOf2(join49(path, f)), { recursive: true });
27315
- copyFileSync2(src, join49(path, f));
27473
+ const src = join50(repoP.dir, f);
27474
+ if (!existsSync46(src)) continue;
27475
+ mkdirSync33(dirOf2(join50(path, f)), { recursive: true });
27476
+ copyFileSync2(src, join50(path, f));
27316
27477
  }
27317
27478
  const setup = repoP.config.hooks.setup;
27318
27479
  return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
@@ -27324,6 +27485,7 @@ ${where.join("\n")}`);
27324
27485
  return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : void 0 }) : null;
27325
27486
  },
27326
27487
  createRow: (fields) => trackerP.createPeriscope(fields),
27488
+ prepareDropFile,
27327
27489
  recordLocal: (sc) => {
27328
27490
  writePeriscopeSidecar(cfg.ship.stateDir, { ...sc, route: routeP.route });
27329
27491
  if (flag("serve-mcp")) mintPeriscopeSecret(cfg.ship.stateDir, sc.ticketRecordId);
@@ -27607,7 +27769,7 @@ ${where.join("\n")}`);
27607
27769
  };
27608
27770
  const droppedRoutes = [];
27609
27771
  try {
27610
- const rawCfg = parseYaml2(readFileSync40(cfg.configFile, "utf8"));
27772
+ const rawCfg = parseYaml2(readFileSync41(cfg.configFile, "utf8"));
27611
27773
  for (const r of rawCfg?.routes ?? []) {
27612
27774
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
27613
27775
  try {
@@ -27660,8 +27822,8 @@ ${where.join("\n")}`);
27660
27822
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
27661
27823
  break;
27662
27824
  }
27663
- mkdirSync32(cfg.ship.stateDir, { recursive: true });
27664
- writeFileSync33(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
27825
+ mkdirSync33(cfg.ship.stateDir, { recursive: true });
27826
+ writeFileSync34(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
27665
27827
  `);
27666
27828
  process.stdout.write(
27667
27829
  "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"
@@ -27726,7 +27888,7 @@ ${where.join("\n")}`);
27726
27888
  }
27727
27889
  case "log": {
27728
27890
  try {
27729
- const text2 = readFileSync40(cfg.ship.logFile, "utf8").split("\n");
27891
+ const text2 = readFileSync41(cfg.ship.logFile, "utf8").split("\n");
27730
27892
  process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
27731
27893
  `);
27732
27894
  } catch {
@@ -27749,8 +27911,8 @@ ${where.join("\n")}`);
27749
27911
  };
27750
27912
  if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
27751
27913
  const tracker = new Tracker(route2, cfg.ship);
27752
- const configText = readFileSync40(cfg.configFile, "utf8");
27753
- const cfgBase = dirname28(cfg.configFile);
27914
+ const configText = readFileSync41(cfg.configFile, "utf8");
27915
+ const cfgBase = dirname29(cfg.configFile);
27754
27916
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
27755
27917
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
27756
27918
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -27778,7 +27940,7 @@ ${where.join("\n")}`);
27778
27940
  }
27779
27941
  if (configured2.some((r) => r.dir === dir)) {
27780
27942
  try {
27781
- writeFileSync33(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
27943
+ writeFileSync34(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
27782
27944
  } catch (e) {
27783
27945
  if (e instanceof ReposError) fail(e.message);
27784
27946
  throw e;
@@ -27786,29 +27948,29 @@ ${where.join("\n")}`);
27786
27948
  }
27787
27949
  if (row) {
27788
27950
  await tracker.deleteRepoRow(row.id);
27789
- if (existsSync45(resolvedFile)) {
27790
- const raw = JSON.parse(readFileSync40(resolvedFile, "utf8"));
27951
+ if (existsSync46(resolvedFile)) {
27952
+ const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
27791
27953
  if (raw.repoNames) delete raw.repoNames[row.id];
27792
27954
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
27793
- writeFileSync33(resolvedFile, `${JSON.stringify(raw, null, 2)}
27955
+ writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
27794
27956
  `);
27795
27957
  }
27796
27958
  }
27797
- if (existsSync45(join49(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
27959
+ if (existsSync46(join50(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync23(dir, { recursive: true, force: true });
27798
27960
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
27799
27961
  break;
27800
27962
  }
27801
27963
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
27802
- const fresh = !existsSync45(join49(dir, ".git"));
27803
- if (existsSync45(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
27964
+ const fresh = !existsSync46(join50(dir, ".git"));
27965
+ if (existsSync46(dir) && fresh && readdirSync20(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
27804
27966
  if (dryRun) {
27805
27967
  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`);
27806
27968
  break;
27807
27969
  }
27808
27970
  if (fresh) {
27809
- mkdirSync32(dir, { recursive: true });
27810
- writeFileSync33(join49(dir, "README.md"), SAMPLE_README);
27811
- writeFileSync33(join49(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
27971
+ mkdirSync33(dir, { recursive: true });
27972
+ writeFileSync34(join50(dir, "README.md"), SAMPLE_README);
27973
+ writeFileSync34(join50(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
27812
27974
  const g = (...a) => {
27813
27975
  const r = spawnSync10("git", a, { cwd: dir, encoding: "utf8" });
27814
27976
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -27826,17 +27988,17 @@ ${where.join("\n")}`);
27826
27988
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
27827
27989
  });
27828
27990
  repoId = (await tracker.createRepoRow(payload)).id;
27829
- if (existsSync45(resolvedFile)) {
27830
- const raw = JSON.parse(readFileSync40(resolvedFile, "utf8"));
27991
+ if (existsSync46(resolvedFile)) {
27992
+ const raw = JSON.parse(readFileSync41(resolvedFile, "utf8"));
27831
27993
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
27832
27994
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
27833
- writeFileSync33(resolvedFile, `${JSON.stringify(raw, null, 2)}
27995
+ writeFileSync34(resolvedFile, `${JSON.stringify(raw, null, 2)}
27834
27996
  `);
27835
27997
  }
27836
27998
  }
27837
27999
  if (!configured2.some((r) => r.dir === dir)) {
27838
28000
  try {
27839
- writeFileSync33(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
28001
+ writeFileSync34(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
27840
28002
  } catch (e) {
27841
28003
  if (e instanceof ReposError) fail(e.message);
27842
28004
  throw e;
@@ -28182,7 +28344,7 @@ watermark: ${state.watermark()}
28182
28344
  }
28183
28345
  for (const r of repos) {
28184
28346
  const remote = r.config.branch.remote;
28185
- if (existsSync45(r.dir)) {
28347
+ if (existsSync46(r.dir)) {
28186
28348
  pushAuth.push({
28187
28349
  route: c.route,
28188
28350
  repo: r.name,
@@ -28234,6 +28396,9 @@ watermark: ${state.watermark()}
28234
28396
  }
28235
28397
  }
28236
28398
  for (const message of roleCapWarnings(cfg.ship)) problems.push({ severity: "note", kind: "role_cap_above_ship_cap", message });
28399
+ if (!flag("offline")) {
28400
+ for (const message of managedRuntimeWarnings(await doctorShipKind(route2, cfg.ship), cfg.ship)) problems.push({ severity: "attention", kind: "managed_runtime_default", message });
28401
+ }
28237
28402
  const previewProblem2 = await previewHostProblem(cfg);
28238
28403
  if (previewProblem2) problems.push(previewHostFinding(previewProblem2));
28239
28404
  if (cfg.routes.some((x) => x.route === route2.route && x.enabled && x.hostPeriscopes)) {
@@ -28268,7 +28433,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28268
28433
  }
28269
28434
  try {
28270
28435
  const evFile = eventFileFor(cfg.ship.stateDir);
28271
- const evSize = existsSync45(evFile) ? statSync15(evFile).size : 0;
28436
+ const evSize = existsSync46(evFile) ? statSync16(evFile).size : 0;
28272
28437
  process.stdout.write(
28273
28438
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
28274
28439
  ` : "events log: ok\n"
@@ -28279,7 +28444,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28279
28444
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
28280
28445
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
28281
28446
  try {
28282
- return readFileSync40(p, "utf8");
28447
+ return readFileSync41(p, "utf8");
28283
28448
  } catch {
28284
28449
  return null;
28285
28450
  }
@@ -28302,7 +28467,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28302
28467
  const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
28303
28468
  if (changed2) {
28304
28469
  process.stdout.write(
28305
- `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\`
28470
+ `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\`
28306
28471
  `
28307
28472
  );
28308
28473
  }
@@ -28472,6 +28637,8 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28472
28637
  crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "none"}
28473
28638
  `
28474
28639
  );
28640
+ for (const w of managedRuntimeWarnings(row.kind, cfg.ship)) process.stdout.write(`managed runtime: WARN ${w}
28641
+ `);
28475
28642
  }
28476
28643
  }
28477
28644
  {