@tablation/crew 0.120.0 → 0.122.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 +983 -667
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -183,10 +183,12 @@ function dropFileMtime(worktree) {
183
183
 
184
184
  // src/intel-register.ts
185
185
  import { execFileSync as execFileSync2 } from "child_process";
186
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
187
- import { dirname as dirname2, join as join2 } from "path";
186
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
187
+ import { dirname as dirname2, join as join3 } from "path";
188
188
 
189
189
  // src/intel.ts
190
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
191
+ import { join as join2 } from "path";
190
192
  var INTEL_FLAVORS = {
191
193
  serena: {
192
194
  summary: "crew runs a Serena symbol service per worktree, seeded from the main checkout's index",
@@ -197,15 +199,72 @@ var INTEL_FLAVORS = {
197
199
  implemented: true
198
200
  },
199
201
  "typescript-lsp": {
200
- summary: "crew runs a TypeScript language-server symbol service per worktree (CREW-2916)",
202
+ summary: "the Claude harness's own LSP tool serves the repository through a crew-generated language-server plugin (Claude only)",
203
+ // Checked against the main checkout's node_modules/.bin, then PATH, by `lspBinaryFound`.
201
204
  prerequisites: [],
202
- implemented: false
205
+ implemented: true
206
+ }
207
+ };
208
+ var LSP_SERVERS = {
209
+ "typescript-lsp": {
210
+ bin: "typescript-language-server",
211
+ args: ["--stdio"],
212
+ install: "npm install -D typescript-language-server typescript",
213
+ extensionToLanguage: {
214
+ ".ts": "typescript",
215
+ ".tsx": "typescriptreact",
216
+ ".js": "javascript",
217
+ ".jsx": "javascriptreact",
218
+ ".mts": "typescript",
219
+ ".cts": "typescript",
220
+ ".mjs": "javascript",
221
+ ".cjs": "javascript"
222
+ }
203
223
  }
204
224
  };
225
+ function lspFlavor(script) {
226
+ const f = intelFlavor(script);
227
+ return f && Object.prototype.hasOwnProperty.call(LSP_SERVERS, f) ? f : void 0;
228
+ }
229
+ var lspLocalBin = (mainCheckout, flavor) => join2(mainCheckout, "node_modules", ".bin", LSP_SERVERS[flavor].bin);
230
+ function lspBinaryFound(flavor, mainCheckout, onPath2) {
231
+ return existsSync3(lspLocalBin(mainCheckout, flavor)) || onPath2(LSP_SERVERS[flavor].bin);
232
+ }
233
+ function ensureLspPlugin(stateDir, flavor, mainCheckout) {
234
+ const def = LSP_SERVERS[flavor];
235
+ const dir = join2(stateDir, "intel", "plugins", flavor);
236
+ const command2 = existsSync3(lspLocalBin(mainCheckout, flavor)) ? `\${CLAUDE_PROJECT_DIR}/node_modules/.bin/${def.bin}` : def.bin;
237
+ const files = [
238
+ [join2(".claude-plugin", "plugin.json"), { name: `crew-${flavor}`, version: "1.0.0", description: `Language server for the ${flavor} intel flavor, generated by crew` }],
239
+ [".lsp.json", { [def.bin]: { command: command2, args: [...def.args], extensionToLanguage: def.extensionToLanguage } }]
240
+ ];
241
+ for (const [rel, doc] of files) {
242
+ const path = join2(dir, rel);
243
+ const text2 = `${JSON.stringify(doc, null, 2)}
244
+ `;
245
+ let old;
246
+ try {
247
+ old = readFileSync2(path, "utf8");
248
+ } catch {
249
+ }
250
+ if (old === text2) continue;
251
+ mkdirSync2(join2(path, ".."), { recursive: true });
252
+ writeFileSync2(path, text2);
253
+ }
254
+ return dir;
255
+ }
256
+ function lspToolsParagraph(harness, omlx = false) {
257
+ if (harness !== "claude") return "No symbol service is available for this harness: this repository's `intel` flavor serves only the Claude Code harness's own LSP tool.";
258
+ if (omlx) return "The harness's LSP tool is off for this launcher, so no symbol service is available for this run.";
259
+ return "The harness's `LSP` tool is active for this repository's TypeScript and JavaScript files (.ts .tsx .js .jsx .mts .cts .mjs .cjs). It offers definition, references, hover, file symbols, workspace symbol search, implementations and call hierarchy, read-only; diagnostics arrive after each edit.";
260
+ }
205
261
  function intelFlavor(script) {
206
262
  const w = script?.trim();
207
263
  return w && Object.prototype.hasOwnProperty.call(INTEL_FLAVORS, w) ? w : void 0;
208
264
  }
265
+ function wantsCodeBridge(script) {
266
+ return !!script?.trim() && intelFlavor(script) !== "typescript-lsp";
267
+ }
209
268
  var INTEL_MINIMUM_TOOLS = [
210
269
  "find symbols by name",
211
270
  "references to a symbol",
@@ -330,11 +389,11 @@ function registerCodeServer(worktree, url, server = INTEL_SERVER_NAME) {
330
389
  [".gemini/settings.json", (old) => mergeJsonServer(old, { httpUrl: url }, server)]
331
390
  ];
332
391
  for (const [rel, merge] of plans) {
333
- const file = join2(worktree, rel);
392
+ const file = join3(worktree, rel);
334
393
  try {
335
- const next = merge(existsSync3(file) ? readFileSync2(file, "utf8") : void 0);
336
- mkdirSync2(dirname2(file), { recursive: true });
337
- writeFileSync2(file, next);
394
+ const next = merge(existsSync4(file) ? readFileSync3(file, "utf8") : void 0);
395
+ mkdirSync3(dirname2(file), { recursive: true });
396
+ writeFileSync3(file, next);
338
397
  if (tracked(worktree, rel)) execFileSync2("git", ["update-index", "--skip-worktree", "--", rel], { cwd: worktree, stdio: "ignore" });
339
398
  else excludeFromGit(worktree, rel);
340
399
  out.written.push(rel);
@@ -347,13 +406,13 @@ function registerCodeServer(worktree, url, server = INTEL_SERVER_NAME) {
347
406
 
348
407
  // src/intel-serena.ts
349
408
  import { spawn, spawnSync } from "child_process";
350
- import { closeSync, cpSync, existsSync as existsSync5, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
351
- import { dirname as dirname3, join as join4 } from "path";
409
+ import { closeSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
410
+ import { dirname as dirname3, join as join5 } from "path";
352
411
 
353
412
  // src/ports.ts
354
413
  import { execFileSync as execFileSync3 } from "child_process";
355
- import { existsSync as existsSync4 } from "fs";
356
- import { join as join3 } from "path";
414
+ import { existsSync as existsSync5 } from "fs";
415
+ import { join as join4 } from "path";
357
416
  var run = (cmd, args) => {
358
417
  try {
359
418
  return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -402,7 +461,7 @@ var intelPortFor = (n, s = DEFAULT_PORTS) => (s.intelBase ?? DEFAULT_PORTS.intel
402
461
  var proxyPortFor = (n, s = DEFAULT_PORTS) => (s.proxyBase ?? DEFAULT_PORTS.proxyBase) + n;
403
462
  function worktreeExists(parent, prefix, n) {
404
463
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
405
- if (existsSync4(join3(parent, name, ".git"))) return true;
464
+ if (existsSync5(join4(parent, name, ".git"))) return true;
406
465
  }
407
466
  return false;
408
467
  }
@@ -446,10 +505,10 @@ function pidsInWorktree(path, n, s = DEFAULT_PORTS) {
446
505
 
447
506
  // src/intel-serena.ts
448
507
  var serenaPaths = (stateDir, route2, ticket) => ({
449
- home: join4(stateDir, "intel", route2, ticket, "home"),
450
- mainHome: join4(stateDir, "intel", route2, "main", "home"),
451
- mainProject: join4(stateDir, "intel", route2, "main"),
452
- project: join4(stateDir, "intel", route2, ticket)
508
+ home: join5(stateDir, "intel", route2, ticket, "home"),
509
+ mainHome: join5(stateDir, "intel", route2, "main", "home"),
510
+ mainProject: join5(stateDir, "intel", route2, "main"),
511
+ project: join5(stateDir, "intel", route2, ticket)
453
512
  });
454
513
  var serenaFolderFor = (stateDir, route2, ticket) => serenaPaths(stateDir, route2, ticket).project;
455
514
  function serenaConfigYaml(projectFolder) {
@@ -493,20 +552,20 @@ function missingPrerequisites(flavor, onPath2) {
493
552
  function prepareFolder(d, home, folder, checkout) {
494
553
  d.mkdir(home);
495
554
  d.mkdir(folder);
496
- d.write(join4(home, "serena_config.yml"), serenaConfigYaml(folder));
497
- d.write(join4(folder, "project.yml"), serenaProjectYaml(d.read(join4(checkout, ".serena", "project.yml"))));
555
+ d.write(join5(home, "serena_config.yml"), serenaConfigYaml(folder));
556
+ d.write(join5(folder, "project.yml"), serenaProjectYaml(d.read(join5(checkout, ".serena", "project.yml"))));
498
557
  }
499
558
  function ensureMainIndex(d, s) {
500
559
  const p = serenaPaths(s.stateDir, s.route, String(s.ticket));
501
560
  const head = d.headOf(s.mainCheckout);
502
561
  if (!head) return "unavailable";
503
- const stamp = join4(p.mainProject, "indexed-commit");
562
+ const stamp = join5(p.mainProject, "indexed-commit");
504
563
  if (d.read(stamp)?.trim() === head) return "fresh";
505
- const pidFile = join4(p.mainProject, "indexing.pid");
564
+ const pidFile = join5(p.mainProject, "indexing.pid");
506
565
  const running = Number(d.read(pidFile));
507
566
  if (running > 0 && d.alive(running)) return "running";
508
567
  prepareFolder(d, p.mainHome, p.mainProject, s.mainCheckout);
509
- const pid = d.spawnIndex(serenaIndexArgs(s.mainCheckout), { SERENA_HOME: p.mainHome }, join4(p.mainProject, "index.log"), stamp, head);
568
+ const pid = d.spawnIndex(serenaIndexArgs(s.mainCheckout), { SERENA_HOME: p.mainHome }, join5(p.mainProject, "index.log"), stamp, head);
510
569
  d.write(pidFile, String(pid));
511
570
  return "started";
512
571
  }
@@ -516,15 +575,15 @@ function startSerena(d, s) {
516
575
  const p = serenaPaths(s.stateDir, s.route, String(s.ticket));
517
576
  try {
518
577
  prepareFolder(d, p.home, p.project, s.worktree);
519
- const mainCache = join4(p.mainProject, "cache");
520
- const cache = join4(p.project, "cache");
578
+ const mainCache = join5(p.mainProject, "cache");
579
+ const cache = join5(p.project, "cache");
521
580
  if (!d.exists(cache) && d.exists(mainCache)) d.copyDir(mainCache, cache);
522
581
  try {
523
582
  ensureMainIndex(d, s);
524
583
  } catch {
525
584
  }
526
585
  const port = intelPortFor(s.ticket);
527
- d.spawnDetached("serena", serenaServeArgs(port, s.worktree), { SERENA_HOME: p.home }, join4(p.project, "serena.log"));
586
+ d.spawnDetached("serena", serenaServeArgs(port, s.worktree), { SERENA_HOME: p.home }, join5(p.project, "serena.log"));
528
587
  return { code: 0, output: `http://127.0.0.1:${port}/mcp
529
588
  ` };
530
589
  } catch (e) {
@@ -533,7 +592,7 @@ function startSerena(d, s) {
533
592
  }
534
593
  function realSerenaDeps(onPath2, extraEnv = process.env) {
535
594
  const detached = (bin, args, env, log) => {
536
- mkdirSync3(dirname3(log), { recursive: true });
595
+ mkdirSync4(dirname3(log), { recursive: true });
537
596
  const fd = openSync(log, "a");
538
597
  const child = spawn(bin, args, { detached: true, stdio: ["ignore", fd, fd], env: { ...extraEnv, ...env } });
539
598
  child.unref();
@@ -543,20 +602,20 @@ function realSerenaDeps(onPath2, extraEnv = process.env) {
543
602
  };
544
603
  return {
545
604
  onPath: onPath2,
546
- exists: existsSync5,
605
+ exists: existsSync6,
547
606
  mkdir: (p) => {
548
- mkdirSync3(p, { recursive: true });
607
+ mkdirSync4(p, { recursive: true });
549
608
  },
550
609
  read: (p) => {
551
610
  try {
552
- return readFileSync3(p, "utf8");
611
+ return readFileSync4(p, "utf8");
553
612
  } catch {
554
613
  return void 0;
555
614
  }
556
615
  },
557
616
  write: (p, text2) => {
558
- mkdirSync3(dirname3(p), { recursive: true });
559
- writeFileSync3(p, text2);
617
+ mkdirSync4(dirname3(p), { recursive: true });
618
+ writeFileSync4(p, text2);
560
619
  },
561
620
  copyDir: (from, to) => cpSync(from, to, { recursive: true }),
562
621
  headOf: (checkout) => {
@@ -582,14 +641,14 @@ function dropSerenaFolder(stateDir, route2, ticket) {
582
641
  }
583
642
 
584
643
  // src/periscope-state.ts
585
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
644
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
586
645
  import { connect } from "net";
587
- import { join as join5 } from "path";
588
- var dirOf = (stateDir) => join5(stateDir, "periscopes");
589
- var fileOf = (stateDir, ticketRecordId) => join5(dirOf(stateDir), `${ticketRecordId}.json`);
646
+ import { join as join6 } from "path";
647
+ var dirOf = (stateDir) => join6(stateDir, "periscopes");
648
+ var fileOf = (stateDir, ticketRecordId) => join6(dirOf(stateDir), `${ticketRecordId}.json`);
590
649
  function writePeriscopeSidecar(stateDir, sc) {
591
- mkdirSync4(dirOf(stateDir), { recursive: true });
592
- writeFileSync4(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
650
+ mkdirSync5(dirOf(stateDir), { recursive: true });
651
+ writeFileSync5(fileOf(stateDir, sc.ticketRecordId), `${JSON.stringify(sc, null, 2)}
593
652
  `);
594
653
  }
595
654
  function removePeriscopeSidecar(stateDir, ticketRecordId) {
@@ -597,12 +656,12 @@ function removePeriscopeSidecar(stateDir, ticketRecordId) {
597
656
  }
598
657
  function readPeriscopeSidecars(stateDir) {
599
658
  const dir = dirOf(stateDir);
600
- if (!existsSync6(dir)) return [];
659
+ if (!existsSync7(dir)) return [];
601
660
  const out = [];
602
661
  for (const name of readdirSync(dir)) {
603
662
  if (!name.endsWith(".json")) continue;
604
663
  try {
605
- const sc = JSON.parse(readFileSync4(join5(dir, name), "utf8"));
664
+ const sc = JSON.parse(readFileSync5(join6(dir, name), "utf8"));
606
665
  if (sc && typeof sc.ticketRecordId === "string" && typeof sc.url === "string") out.push(sc);
607
666
  } catch {
608
667
  }
@@ -720,23 +779,23 @@ function shellInitScript(shell) {
720
779
  }
721
780
 
722
781
  // src/config.ts
723
- import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
724
- import { resolve as resolve2, dirname as dirname7, isAbsolute as isAbsolute2, join as join16, basename as basename4 } from "path";
782
+ import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
783
+ import { resolve as resolve2, dirname as dirname7, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
725
784
  import { homedir as homedir6, tmpdir } from "os";
726
785
  import { parse as parse2 } from "yaml";
727
786
 
728
787
  // src/events.ts
729
788
  import {
730
789
  appendFileSync as appendFileSync2,
731
- mkdirSync as mkdirSync5,
732
- existsSync as existsSync7,
790
+ mkdirSync as mkdirSync6,
791
+ existsSync as existsSync8,
733
792
  statSync as statSync2,
734
793
  renameSync,
735
794
  openSync as openSync2,
736
795
  closeSync as closeSync2,
737
796
  rmSync as rmSync3
738
797
  } from "fs";
739
- import { join as join6, dirname as dirname4 } from "path";
798
+ import { join as join7, dirname as dirname4 } from "path";
740
799
  function render(e) {
741
800
  const scope = e.role ? `${e.step}[${e.role}]` : e.step;
742
801
  const ticket = e.ticket ? ` ${e.ticket}` : "";
@@ -748,7 +807,7 @@ var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
748
807
  var ROTATE_LOCK_STALE_MS = 6e4;
749
808
  function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
750
809
  try {
751
- if (!existsSync7(file) || statSync2(file).size <= maxBytes) return void 0;
810
+ if (!existsSync8(file) || statSync2(file).size <= maxBytes) return void 0;
752
811
  } catch {
753
812
  return void 0;
754
813
  }
@@ -768,8 +827,8 @@ function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
768
827
  try {
769
828
  if (statSync2(file).size <= maxBytes) return void 0;
770
829
  const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
771
- let dest = join6(dirname4(file), `events.${stamp}.jsonl`);
772
- for (let n = 1; existsSync7(dest); n++) dest = join6(dirname4(file), `events.${stamp}-${n}.jsonl`);
830
+ let dest = join7(dirname4(file), `events.${stamp}.jsonl`);
831
+ for (let n = 1; existsSync8(dest); n++) dest = join7(dirname4(file), `events.${stamp}-${n}.jsonl`);
773
832
  renameSync(file, dest);
774
833
  return dest;
775
834
  } finally {
@@ -792,7 +851,7 @@ var Emitter = class _Emitter {
792
851
  for (const f of [opts.eventFile, opts.logFile]) {
793
852
  if (!f) continue;
794
853
  try {
795
- mkdirSync5(dirname4(f), { recursive: true });
854
+ mkdirSync6(dirname4(f), { recursive: true });
796
855
  } catch {
797
856
  }
798
857
  }
@@ -868,18 +927,18 @@ var Emitter = class _Emitter {
868
927
  }
869
928
  }
870
929
  };
871
- var eventFileFor = (stateDir) => join6(stateDir, "events.jsonl");
930
+ var eventFileFor = (stateDir) => join7(stateDir, "events.jsonl");
872
931
 
873
932
  // src/pair-install.ts
874
- import { existsSync as existsSync10, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
933
+ import { existsSync as existsSync11, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
875
934
  import { spawnSync as spawnSync4 } from "child_process";
876
935
  import { homedir as homedir5 } from "os";
877
- import { join as join12 } from "path";
936
+ import { join as join13 } from "path";
878
937
 
879
938
  // src/adapters/claude.ts
880
- import { readFileSync as readFileSync9 } from "fs";
939
+ import { readFileSync as readFileSync10 } from "fs";
881
940
  import { homedir as homedir3 } from "os";
882
- import { join as join10 } from "path";
941
+ import { join as join11 } from "path";
883
942
 
884
943
  // src/stream.ts
885
944
  function toolTarget(name, input) {
@@ -938,19 +997,19 @@ function extractResult(line) {
938
997
 
939
998
  // src/claude-mcp.ts
940
999
  import { spawnSync as spawnSync2 } from "child_process";
941
- import { readFileSync as readFileSync7 } from "fs";
1000
+ import { readFileSync as readFileSync8 } from "fs";
942
1001
  import { homedir } from "os";
943
- import { join as join8 } from "path";
1002
+ import { join as join9 } from "path";
944
1003
 
945
1004
  // src/mcp-registrations.ts
946
1005
  import { createHash } from "crypto";
947
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
948
- import { join as join7 } from "path";
949
- var fileFor = (stateDir) => join7(stateDir, "mcp-registrations.json");
1006
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1007
+ import { join as join8 } from "path";
1008
+ var fileFor = (stateDir) => join8(stateDir, "mcp-registrations.json");
950
1009
  var keyFingerprint = (key) => createHash("sha256").update(key).digest("hex").slice(0, 8);
951
1010
  function readMcpRegistrations(stateDir) {
952
1011
  try {
953
- const j = JSON.parse(readFileSync6(fileFor(stateDir), "utf8"));
1012
+ const j = JSON.parse(readFileSync7(fileFor(stateDir), "utf8"));
954
1013
  return Array.isArray(j) ? j : [];
955
1014
  } catch {
956
1015
  return [];
@@ -958,8 +1017,8 @@ function readMcpRegistrations(stateDir) {
958
1017
  }
959
1018
  function recordMcpRegistration(stateDir, r, now = /* @__PURE__ */ new Date()) {
960
1019
  const rest = readMcpRegistrations(stateDir).filter((x) => !(x.harness === r.harness && x.scope === r.scope && x.name === r.name));
961
- mkdirSync6(stateDir, { recursive: true });
962
- writeFileSync5(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
1020
+ mkdirSync7(stateDir, { recursive: true });
1021
+ writeFileSync6(fileFor(stateDir), `${JSON.stringify([...rest, { ...r, at: now.toISOString() }], null, 2)}
963
1022
  `, { mode: 384 });
964
1023
  }
965
1024
  function findMcpRegistration(stateDir, r) {
@@ -982,7 +1041,7 @@ function runClaude(bin, args) {
982
1041
  }
983
1042
  function userScopeEntry(home) {
984
1043
  try {
985
- const j = JSON.parse(readFileSync7(join8(home, ".claude.json"), "utf8"));
1044
+ const j = JSON.parse(readFileSync8(join9(home, ".claude.json"), "utf8"));
986
1045
  return j.mcpServers?.[MCP_SERVER_NAME];
987
1046
  } catch {
988
1047
  return void 0;
@@ -1066,8 +1125,8 @@ function removeClaudeMcp(o) {
1066
1125
  }
1067
1126
 
1068
1127
  // src/launcher.ts
1069
- import { basename, join as join9 } from "path";
1070
- import { existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync2 } from "fs";
1128
+ import { basename, join as join10 } from "path";
1129
+ import { existsSync as existsSync9, readFileSync as readFileSync9, readdirSync as readdirSync2 } from "fs";
1071
1130
  import { homedir as homedir2 } from "os";
1072
1131
  function isOriLauncher(spec) {
1073
1132
  return !!spec.launcher && basename(spec.launcher) === "ori";
@@ -1076,7 +1135,7 @@ function isOmlxLauncher(spec) {
1076
1135
  return !!spec.launcher && basename(spec.launcher) === "omlx";
1077
1136
  }
1078
1137
  function defaultOmlxBin(home = homedir2()) {
1079
- return join9(home, ".omlx", "bin", "omlx");
1138
+ return join10(home, ".omlx", "bin", "omlx");
1080
1139
  }
1081
1140
  function launcherExecutable(spec, home) {
1082
1141
  if (!spec.launcher) return void 0;
@@ -1129,8 +1188,8 @@ function omlxModelPresent(model, dirs) {
1129
1188
  });
1130
1189
  }
1131
1190
  function readOmlxSettings(home = homedir2()) {
1132
- const p = join9(home, ".omlx", "settings.json");
1133
- return existsSync8(p) ? parseOmlxSettings(readFileSync8(p, "utf8")) : void 0;
1191
+ const p = join10(home, ".omlx", "settings.json");
1192
+ return existsSync9(p) ? parseOmlxSettings(readFileSync9(p, "utf8")) : void 0;
1134
1193
  }
1135
1194
 
1136
1195
  // src/capability.ts
@@ -1194,7 +1253,7 @@ function describeClaudeAuth(spec, env = process.env, home = homedir3()) {
1194
1253
  if (spec.auth === "workspace") return `workspace${spec.inference ? ` (${new URL(spec.inference.baseUrl).host})` : " (endpoint resolved per route at run time)"}`;
1195
1254
  let fromSettings;
1196
1255
  try {
1197
- const parsed = JSON.parse(readFileSync9(join10(home, ".claude", "settings.json"), "utf8"));
1256
+ const parsed = JSON.parse(readFileSync10(join11(home, ".claude", "settings.json"), "utf8"));
1198
1257
  const e = parsed.env ?? {};
1199
1258
  const on = (k) => e[k] !== void 0 && e[k] !== "" && e[k] !== "0";
1200
1259
  if (on("CLAUDE_CODE_USE_VERTEX")) fromSettings = "vertex from ~/.claude/settings.json";
@@ -1253,15 +1312,22 @@ var claudeAdapter = {
1253
1312
  // so the resolved key is never written to an argv string or a config
1254
1313
  // file on disk. Skipped when there's no key to send: a route `crew
1255
1314
  // connect` hasn't resolved yet has nothing to authenticate with.
1256
- ...o.mcp ? ["--mcp-config", JSON.stringify({
1315
+ ...o.mcp || o.code ? ["--mcp-config", JSON.stringify({
1257
1316
  mcpServers: {
1258
- tablation: {
1259
- type: "http",
1260
- url: o.mcp.url,
1261
- headers: { Authorization: `Bearer \${${o.mcp.keyVar}}` }
1262
- }
1317
+ ...o.mcp ? {
1318
+ tablation: {
1319
+ type: "http",
1320
+ url: o.mcp.url,
1321
+ headers: { Authorization: `Bearer \${${o.mcp.keyVar}}` }
1322
+ }
1323
+ } : {},
1324
+ // CREW-2912: the bridge to the worktree's symbol service, attached once `crew worktree` has started it.
1325
+ ...o.code ? { [INTEL_SERVER_NAME]: { type: "stdio", command: o.code.command, args: o.code.args } } : {}
1263
1326
  }
1264
1327
  })] : [],
1328
+ // A crew-generated language-server plugin (the `typescript-lsp` intel flavor). Not under oMLX,
1329
+ // which denies the LSP tool above.
1330
+ ...isOmlxLauncher(spec) ? [] : (o.pluginDirs ?? []).flatMap((d) => ["--plugin-dir", d]),
1265
1331
  ...spec.auth === "subscription" ? ["--settings", subscriptionSettings()] : [],
1266
1332
  ...spec.auth === "workspace" && spec.inference ? ["--settings", workspaceSettings(spec.inference.baseUrl)] : [],
1267
1333
  ...spec.args
@@ -1298,11 +1364,11 @@ var claudeAdapter = {
1298
1364
  // src/adapters/codex.ts
1299
1365
  import { spawnSync as spawnSync3 } from "child_process";
1300
1366
  import { homedir as homedir4 } from "os";
1301
- import { join as join11 } from "path";
1302
- import { rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
1367
+ import { join as join12 } from "path";
1368
+ import { rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
1303
1369
 
1304
1370
  // src/pair-files.ts
1305
- import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6, rmSync as rmSync4 } from "fs";
1371
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync7, rmSync as rmSync4 } from "fs";
1306
1372
  import { dirname as dirname5 } from "path";
1307
1373
  var PAIR_BLOCK_START = "<!-- crew:pair start -->";
1308
1374
  var PAIR_BLOCK_END = "<!-- crew:pair end -->";
@@ -1343,11 +1409,11 @@ function removePairBlock(text2) {
1343
1409
  return text2.replace(new RegExp(`\\n*${BLOCK_RE.source}`), "\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
1344
1410
  }
1345
1411
  function readIfExists(path) {
1346
- return existsSync9(path) ? readFileSync10(path, "utf8") : void 0;
1412
+ return existsSync10(path) ? readFileSync11(path, "utf8") : void 0;
1347
1413
  }
1348
1414
  function writeFileMkdir(path, text2) {
1349
- mkdirSync7(dirname5(path), { recursive: true });
1350
- writeFileSync6(path, text2);
1415
+ mkdirSync8(dirname5(path), { recursive: true });
1416
+ writeFileSync7(path, text2);
1351
1417
  }
1352
1418
  function installBlockFile(path, harness, dryRun) {
1353
1419
  const before = readIfExists(path) ?? "";
@@ -1363,7 +1429,7 @@ function removeBlockFile(path, dryRun) {
1363
1429
  if (dryRun) return true;
1364
1430
  const after = removePairBlock(before);
1365
1431
  if (after.trim() === "") rmSync4(path);
1366
- else writeFileSync6(path, after);
1432
+ else writeFileSync7(path, after);
1367
1433
  return true;
1368
1434
  }
1369
1435
  function blockVerdict(path, harness) {
@@ -1490,8 +1556,8 @@ function codexTable(url) {
1490
1556
  return [`[mcp_servers.${SERVER}]`, `url = ${tomlString(url)}`, `bearer_token_env_var = ${tomlString(PAIR_KEY_VAR)}`];
1491
1557
  }
1492
1558
  var codexPaths = (home) => ({
1493
- instructions: join11(home, ".codex", "AGENTS.md"),
1494
- config: join11(home, ".codex", "config.toml")
1559
+ instructions: join12(home, ".codex", "AGENTS.md"),
1560
+ config: join12(home, ".codex", "config.toml")
1495
1561
  });
1496
1562
  function codexMcpState(config, url) {
1497
1563
  const lines2 = config.split("\n");
@@ -1542,7 +1608,7 @@ var codexPair = {
1542
1608
  if (hadMcp && !o.dryRun) {
1543
1609
  const after = removeTomlTable(config, `mcp_servers.${SERVER}`);
1544
1610
  if (after === "") rmSync5(p.config);
1545
- else writeFileSync7(p.config, after);
1611
+ else writeFileSync8(p.config, after);
1546
1612
  }
1547
1613
  const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
1548
1614
  return hadBlock || hadMcp ? `pair(codex): ${o.dryRun ? "(dry run) would remove" : "removed"} ${what}${kept}` : `pair(codex): nothing to remove${kept}`;
@@ -1605,6 +1671,14 @@ var codexAdapter = {
1605
1671
  ...cfg(`mcp_servers.${SERVER}.bearer_token_env_var`, o.mcp.keyVar),
1606
1672
  ...cfg(`mcp_servers.${SERVER}.default_tools_approval_mode`, "approve")
1607
1673
  ] : [],
1674
+ // CREW-2912: the bridge to the worktree's symbol service, scoped to this run like the tracker entry above.
1675
+ ...o.code ? [
1676
+ "-c",
1677
+ `mcp_servers.${INTEL_SERVER_NAME}.command=${tomlString(o.code.command)}`,
1678
+ "-c",
1679
+ `mcp_servers.${INTEL_SERVER_NAME}.args=[${o.code.args.map(tomlString).join(", ")}]`,
1680
+ ...cfg(`mcp_servers.${INTEL_SERVER_NAME}.default_tools_approval_mode`, "approve")
1681
+ ] : [],
1608
1682
  ...spec.args,
1609
1683
  // Prompt on stdin.
1610
1684
  "-"
@@ -1638,8 +1712,8 @@ var codexAdapter = {
1638
1712
 
1639
1713
  // src/pair-install.ts
1640
1714
  var geminiPaths = (home) => ({
1641
- instructions: join12(home, ".gemini", "GEMINI.md"),
1642
- config: join12(home, ".gemini", "settings.json")
1715
+ instructions: join13(home, ".gemini", "GEMINI.md"),
1716
+ config: join13(home, ".gemini", "settings.json")
1643
1717
  });
1644
1718
  function readGeminiSettings(path) {
1645
1719
  const text2 = readIfExists(path);
@@ -1727,7 +1801,7 @@ var geminiPair = {
1727
1801
  if (Object.keys(rest).length > 0) next.mcpServers = rest;
1728
1802
  else delete next.mcpServers;
1729
1803
  if (Object.keys(next).length === 0) rmSync6(p.config);
1730
- else writeFileSync8(p.config, `${JSON.stringify(next, null, 2)}
1804
+ else writeFileSync9(p.config, `${JSON.stringify(next, null, 2)}
1731
1805
  `);
1732
1806
  }
1733
1807
  const what = hadBlock && hadMcp ? `the Pair block and ${SERVER} server` : hadBlock ? "the Pair block" : `the ${SERVER} server`;
@@ -1738,9 +1812,9 @@ var VSCODE_FLAVOURS = ["Code", "Code - Insiders"];
1738
1812
  function vscodeUserDirs(o) {
1739
1813
  const home = o.home ?? homedir5();
1740
1814
  const platform2 = o.platform ?? process.platform;
1741
- const root = platform2 === "darwin" ? join12(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join12(home, "AppData", "Roaming") : join12(home, ".config");
1742
- const all = VSCODE_FLAVOURS.map((f) => join12(root, f, "User"));
1743
- const present = all.filter((d) => existsSync10(d));
1815
+ const root = platform2 === "darwin" ? join13(home, "Library", "Application Support") : platform2 === "win32" ? process.env.APPDATA ?? join13(home, "AppData", "Roaming") : join13(home, ".config");
1816
+ const all = VSCODE_FLAVOURS.map((f) => join13(root, f, "User"));
1817
+ const present = all.filter((d) => existsSync11(d));
1744
1818
  return present.length > 0 ? present : [all[0]];
1745
1819
  }
1746
1820
  var COPILOT_FRONTMATTER = '---\napplyTo: "**"\n---\n\n';
@@ -1778,7 +1852,7 @@ function removeCopilotInstructions(path, dryRun) {
1778
1852
  if (dryRun) return true;
1779
1853
  const after = removePairBlock(before);
1780
1854
  if (after.trim() === "" || after.trim() === COPILOT_FRONTMATTER.trim()) rmSync6(path);
1781
- else writeFileSync8(path, after);
1855
+ else writeFileSync9(path, after);
1782
1856
  return true;
1783
1857
  }
1784
1858
  var copilotPair = {
@@ -1787,8 +1861,8 @@ var copilotPair = {
1787
1861
  installPair(o) {
1788
1862
  const lines2 = [];
1789
1863
  for (const dir of vscodeUserDirs(o)) {
1790
- const mcpPath = join12(dir, "mcp.json");
1791
- const instrPath = join12(dir, "prompts", "crew-pair.instructions.md");
1864
+ const mcpPath = join13(dir, "mcp.json");
1865
+ const instrPath = join13(dir, "prompts", "crew-pair.instructions.md");
1792
1866
  const settings = readVscodeMcp(mcpPath);
1793
1867
  if (!settings) {
1794
1868
  lines2.push(`pair(copilot): ${mcpPath} is not a plain JSON object (comments are not supported) \u2014 fix it by hand; nothing was written there`);
@@ -1816,8 +1890,8 @@ var copilotPair = {
1816
1890
  },
1817
1891
  verifyPair(o) {
1818
1892
  const verdicts = vscodeUserDirs(o).map((dir) => {
1819
- const instrPath = join12(dir, "prompts", "crew-pair.instructions.md");
1820
- const mcpPath = join12(dir, "mcp.json");
1893
+ const instrPath = join13(dir, "prompts", "crew-pair.instructions.md");
1894
+ const mcpPath = join13(dir, "mcp.json");
1821
1895
  const b = blockVerdict(instrPath, "copilot");
1822
1896
  if (!b.ok) return b;
1823
1897
  const settings = readVscodeMcp(mcpPath);
@@ -1832,8 +1906,8 @@ var copilotPair = {
1832
1906
  removePair(o) {
1833
1907
  const lines2 = [];
1834
1908
  for (const dir of vscodeUserDirs(o)) {
1835
- const mcpPath = join12(dir, "mcp.json");
1836
- const hadBlock = removeCopilotInstructions(join12(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1909
+ const mcpPath = join13(dir, "mcp.json");
1910
+ const hadBlock = removeCopilotInstructions(join13(dir, "prompts", "crew-pair.instructions.md"), o.dryRun);
1837
1911
  const settings = readVscodeMcp(mcpPath);
1838
1912
  const st = settings ? copilotMcpState(settings, o.url) : { state: "missing" };
1839
1913
  const hadMcp = st.state === "registered";
@@ -1844,7 +1918,7 @@ var copilotPair = {
1844
1918
  if (Object.keys(rest).length > 0) next.servers = rest;
1845
1919
  else delete next.servers;
1846
1920
  if (Object.keys(next).length === 0) rmSync6(mcpPath);
1847
- else writeFileSync8(mcpPath, `${JSON.stringify(next, null, 2)}
1921
+ else writeFileSync9(mcpPath, `${JSON.stringify(next, null, 2)}
1848
1922
  `);
1849
1923
  }
1850
1924
  const what = hadBlock && hadMcp ? `the Pair instructions and ${SERVER} server` : hadBlock ? "the Pair instructions" : `the ${SERVER} server`;
@@ -1986,7 +2060,8 @@ var geminiAdapter = {
1986
2060
  ];
1987
2061
  return wrapInvocation(spec, { model: o.model, args });
1988
2062
  },
1989
- runEnv: (spec) => ({ ...spec.env }),
2063
+ // CREW-2912: Gemini has no per-run server list, so the bridge is declared in a per-run system settings file the planner wrote.
2064
+ runEnv: (spec, o) => ({ ...spec.env, ...o?.code?.settingsFile ? { GEMINI_CLI_SYSTEM_SETTINGS_PATH: o.code.settingsFile } : {} }),
1990
2065
  provider: () => "google",
1991
2066
  unsetEnv: BILLING_VARS_TO_UNSET2,
1992
2067
  mapStreamLine: mapGeminiLine,
@@ -2094,8 +2169,8 @@ function resolveSeat(ship, route2, role, persona = {}, repo) {
2094
2169
 
2095
2170
  // src/forges.ts
2096
2171
  import { execFileSync as execFileSync4 } from "child_process";
2097
- import { chmodSync, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync11, rmSync as rmSync7, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
2098
- import { dirname as dirname6, join as join13 } from "path";
2172
+ import { chmodSync, existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync12, rmSync as rmSync7, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
2173
+ import { dirname as dirname6, join as join14 } from "path";
2099
2174
  var ForgeError = class extends Error {
2100
2175
  };
2101
2176
  var DEFAULT_FORGE_USERNAME = "x-access-token";
@@ -2123,7 +2198,7 @@ function forgeDescriptorFor(host) {
2123
2198
  }
2124
2199
  var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
2125
2200
  function defaultTokenFilePath(stateDir, host) {
2126
- return join13(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
2201
+ return join14(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
2127
2202
  }
2128
2203
  var envSource = {
2129
2204
  read: (s, c) => c.env[s.var]?.trim() || null,
@@ -2132,19 +2207,19 @@ var envSource = {
2132
2207
  var fileSource = {
2133
2208
  read(s, c) {
2134
2209
  const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
2135
- if (!existsSync11(path)) return null;
2210
+ if (!existsSync12(path)) return null;
2136
2211
  const mode = statSync3(path).mode & 511;
2137
2212
  if (mode & 63) {
2138
2213
  throw new ForgeError(
2139
2214
  `${path} is mode ${mode.toString(8).padStart(3, "0")}; a forge token file must be owner-only (chmod 600 ${path})`
2140
2215
  );
2141
2216
  }
2142
- return readFileSync11(path, "utf8").trim() || null;
2217
+ return readFileSync12(path, "utf8").trim() || null;
2143
2218
  },
2144
2219
  write(s, token, c) {
2145
2220
  const path = s.path ?? defaultTokenFilePath(c.stateDir, c.host);
2146
- mkdirSync8(dirname6(path), { recursive: true, mode: 448 });
2147
- writeFileSync9(path, `${token}
2221
+ mkdirSync9(dirname6(path), { recursive: true, mode: 448 });
2222
+ writeFileSync10(path, `${token}
2148
2223
  `, { mode: 384 });
2149
2224
  chmodSync(path, 384);
2150
2225
  },
@@ -2278,10 +2353,10 @@ function answerCredential(action, input, forges, stateDir, env = process.env) {
2278
2353
  password=${token}
2279
2354
  `;
2280
2355
  }
2281
- var missesPath = (stateDir) => join13(stateDir, "forge-misses.json");
2356
+ var missesPath = (stateDir) => join14(stateDir, "forge-misses.json");
2282
2357
  function readForgeMisses(stateDir) {
2283
2358
  try {
2284
- const raw = JSON.parse(readFileSync11(missesPath(stateDir), "utf8"));
2359
+ const raw = JSON.parse(readFileSync12(missesPath(stateDir), "utf8"));
2285
2360
  return Object.entries(raw).filter(([, t]) => typeof t === "string").map(([host, lastMissAt]) => ({ host, lastMissAt })).sort((a, b) => a.host.localeCompare(b.host));
2286
2361
  } catch {
2287
2362
  return [];
@@ -2296,8 +2371,8 @@ function noteForgeMiss(stateDir, host, at) {
2296
2371
  if (!(bare in cur)) return;
2297
2372
  delete cur[bare];
2298
2373
  } else cur[bare] = at;
2299
- mkdirSync8(stateDir, { recursive: true });
2300
- writeFileSync9(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
2374
+ mkdirSync9(stateDir, { recursive: true });
2375
+ writeFileSync10(missesPath(stateDir), JSON.stringify(cur), { mode: 384 });
2301
2376
  } catch {
2302
2377
  }
2303
2378
  }
@@ -2399,8 +2474,8 @@ import { hostFromUrl } from "@tablation/client";
2399
2474
 
2400
2475
  // src/secret-store.ts
2401
2476
  import { createHash as createHash2 } from "crypto";
2402
- import { accessSync, constants, existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync2, rmSync as rmSync8, statSync as statSync4, writeFileSync as writeFileSync10, chmodSync as chmodSync2 } from "fs";
2403
- import { join as join14 } from "path";
2477
+ import { accessSync, constants, existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync13, renameSync as renameSync2, rmSync as rmSync8, statSync as statSync4, writeFileSync as writeFileSync11, chmodSync as chmodSync2 } from "fs";
2478
+ import { join as join15 } from "path";
2404
2479
  import { getSessionStore as getOsSessionStore } from "@tablation/client";
2405
2480
  var SECRET_STORE_MODES = ["auto", "os", "file"];
2406
2481
  function isSecretStoreMode(v) {
@@ -2411,7 +2486,7 @@ function configureSecretStore(c) {
2411
2486
  configured = c;
2412
2487
  }
2413
2488
  function keysDir(stateDir) {
2414
- return join14(stateDir, "keys");
2489
+ return join15(stateDir, "keys");
2415
2490
  }
2416
2491
  function entryName(host, slug2) {
2417
2492
  return `${createHash2("sha256").update(`${host}\0${slug2}`).digest("hex")}.json`;
@@ -2422,34 +2497,34 @@ var FileSessionStore = class {
2422
2497
  this.dir = dir;
2423
2498
  }
2424
2499
  ensureDir() {
2425
- mkdirSync9(this.dir, { recursive: true, mode: 448 });
2500
+ mkdirSync10(this.dir, { recursive: true, mode: 448 });
2426
2501
  if ((statSync4(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
2427
2502
  }
2428
2503
  async get(host, slug2) {
2429
- const file = join14(this.dir, entryName(host, slug2));
2430
- if (!existsSync12(this.dir)) return void 0;
2504
+ const file = join15(this.dir, entryName(host, slug2));
2505
+ if (!existsSync13(this.dir)) return void 0;
2431
2506
  accessSync(this.dir, constants.R_OK | constants.X_OK);
2432
- if (!existsSync12(file)) return void 0;
2433
- return JSON.parse(readFileSync12(file, "utf8"));
2507
+ if (!existsSync13(file)) return void 0;
2508
+ return JSON.parse(readFileSync13(file, "utf8"));
2434
2509
  }
2435
2510
  async set(host, slug2, session) {
2436
2511
  this.ensureDir();
2437
- const file = join14(this.dir, entryName(host, slug2));
2512
+ const file = join15(this.dir, entryName(host, slug2));
2438
2513
  const tmp = `${file}.${process.pid}.tmp`;
2439
- writeFileSync10(tmp, JSON.stringify(session), { mode: 384 });
2514
+ writeFileSync11(tmp, JSON.stringify(session), { mode: 384 });
2440
2515
  renameSync2(tmp, file);
2441
2516
  }
2442
2517
  async clear(host, slug2) {
2443
- if (!existsSync12(this.dir)) return;
2444
- rmSync8(join14(this.dir, entryName(host, slug2)), { force: true });
2518
+ if (!existsSync13(this.dir)) return;
2519
+ rmSync8(join15(this.dir, entryName(host, slug2)), { force: true });
2445
2520
  }
2446
2521
  /** Whether a key could be written here right now, without leaving anything behind. */
2447
2522
  writable() {
2448
2523
  try {
2449
2524
  this.ensureDir();
2450
2525
  accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
2451
- const probe = join14(this.dir, `.probe.${process.pid}`);
2452
- writeFileSync10(probe, "", { mode: 384 });
2526
+ const probe = join15(this.dir, `.probe.${process.pid}`);
2527
+ writeFileSync11(probe, "", { mode: 384 });
2453
2528
  rmSync8(probe, { force: true });
2454
2529
  return { ok: true };
2455
2530
  } catch (e) {
@@ -2500,11 +2575,11 @@ function osStoreReachable(env = process.env, platform2 = process.platform, hasBi
2500
2575
  if (platform2 !== "linux") return true;
2501
2576
  if (!hasBinary("secret-tool")) return false;
2502
2577
  if (env.DBUS_SESSION_BUS_ADDRESS) return true;
2503
- return !!env.XDG_RUNTIME_DIR && existsSync12(join14(env.XDG_RUNTIME_DIR, "bus"));
2578
+ return !!env.XDG_RUNTIME_DIR && existsSync13(join15(env.XDG_RUNTIME_DIR, "bus"));
2504
2579
  }
2505
2580
  function onPath(name) {
2506
2581
  for (const dir of (process.env.PATH ?? "").split(":")) {
2507
- if (dir && existsSync12(join14(dir, name))) return true;
2582
+ if (dir && existsSync13(join15(dir, name))) return true;
2508
2583
  }
2509
2584
  return false;
2510
2585
  }
@@ -2571,8 +2646,8 @@ function explain(ship, required) {
2571
2646
  }
2572
2647
 
2573
2648
  // src/repo-config.ts
2574
- import { readFileSync as readFileSync13, existsSync as existsSync13 } from "fs";
2575
- import { join as join15, basename as basename3 } from "path";
2649
+ import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
2650
+ import { join as join16, basename as basename3 } from "path";
2576
2651
  import { parse } from "yaml";
2577
2652
 
2578
2653
  // src/sandbox-policy.ts
@@ -2856,8 +2931,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
2856
2931
  var PROVIDERS = ["github", "buildkite", "other", "none"];
2857
2932
  function findRepoConfig(dir) {
2858
2933
  for (const name of REPO_CONFIG_FILENAMES) {
2859
- const p = join15(dir, name);
2860
- if (existsSync13(p)) return p;
2934
+ const p = join16(dir, name);
2935
+ if (existsSync14(p)) return p;
2861
2936
  }
2862
2937
  return null;
2863
2938
  }
@@ -3143,7 +3218,7 @@ function parseRepoConfig(text2, file) {
3143
3218
  function loadRepoConfig(dir) {
3144
3219
  const file = findRepoConfig(dir);
3145
3220
  if (!file) return null;
3146
- return parseRepoConfig(readFileSync13(file, "utf8"), file);
3221
+ return parseRepoConfig(readFileSync14(file, "utf8"), file);
3147
3222
  }
3148
3223
  function hookLabel(cfg, hook2) {
3149
3224
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -3672,11 +3747,11 @@ function expand(p, base) {
3672
3747
  return isAbsolute2(out) ? out : resolve2(base, out);
3673
3748
  }
3674
3749
  function resolvePromptsDir(promptSet, crewHome, base) {
3675
- if (!promptSet) return join16(crewHome, "prompts", "default");
3750
+ if (!promptSet) return join17(crewHome, "prompts", "default");
3676
3751
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
3677
3752
  return expand(promptSet, base);
3678
3753
  }
3679
- return join16(crewHome, "prompts", promptSet);
3754
+ return join17(crewHome, "prompts", promptSet);
3680
3755
  }
3681
3756
  var Missing = class {
3682
3757
  paths = [];
@@ -3700,7 +3775,7 @@ var Missing = class {
3700
3775
  };
3701
3776
  function crewVersion(crewHome) {
3702
3777
  try {
3703
- const pkg = JSON.parse(readFileSync14(join16(crewHome, "package.json"), "utf8"));
3778
+ const pkg = JSON.parse(readFileSync15(join17(crewHome, "package.json"), "utf8"));
3704
3779
  return pkg.version ?? "0.0.0";
3705
3780
  } catch {
3706
3781
  return "0.0.0";
@@ -3710,26 +3785,26 @@ function configSearchPath(crewHome) {
3710
3785
  if (process.env.CREW_CONFIG) return [resolve2(process.env.CREW_CONFIG)];
3711
3786
  const xdg = process.env.XDG_CONFIG_HOME;
3712
3787
  return [
3713
- ...xdg ? [join16(xdg, "crew", "crew.yaml")] : [],
3714
- join16(homedir6(), ".config", "crew", "crew.yaml"),
3788
+ ...xdg ? [join17(xdg, "crew", "crew.yaml")] : [],
3789
+ join17(homedir6(), ".config", "crew", "crew.yaml"),
3715
3790
  resolve2(crewHome, "crew.yaml")
3716
3791
  ];
3717
3792
  }
3718
3793
  function findConfigFile(crewHome) {
3719
3794
  const candidates2 = configSearchPath(crewHome);
3720
- return candidates2.find((c) => existsSync14(c)) ?? candidates2[candidates2.length - 1];
3795
+ return candidates2.find((c) => existsSync15(c)) ?? candidates2[candidates2.length - 1];
3721
3796
  }
3722
- var defaultConfigPath = () => join16(process.env.XDG_CONFIG_HOME ?? join16(homedir6(), ".config"), "crew", "crew.yaml");
3797
+ var defaultConfigPath = () => join17(process.env.XDG_CONFIG_HOME ?? join17(homedir6(), ".config"), "crew", "crew.yaml");
3723
3798
  function routeSlug(route2) {
3724
3799
  return route2.replace(/\//g, "-");
3725
3800
  }
3726
3801
  function resolvedPathFor(stateDir, route2) {
3727
3802
  const [workspace, project] = splitRoute(route2);
3728
- return join16(stateDir, "resolved", workspace, `${project}.json`);
3803
+ return join17(stateDir, "resolved", workspace, `${project}.json`);
3729
3804
  }
3730
3805
  function apiKeyPathFor(stateDir, route2) {
3731
3806
  const [workspace, project] = splitRoute(route2);
3732
- return join16(stateDir, "keys", workspace, `${project}.env`);
3807
+ return join17(stateDir, "keys", workspace, `${project}.env`);
3733
3808
  }
3734
3809
  function splitRoute(route2) {
3735
3810
  const parts = route2.split("/");
@@ -3740,9 +3815,9 @@ function splitRoute(route2) {
3740
3815
  }
3741
3816
  function readResolvedFile(stateDir, route2) {
3742
3817
  const path = resolvedPathFor(stateDir, route2);
3743
- if (!existsSync14(path)) return void 0;
3818
+ if (!existsSync15(path)) return void 0;
3744
3819
  try {
3745
- return JSON.parse(readFileSync14(path, "utf8"));
3820
+ return JSON.parse(readFileSync15(path, "utf8"));
3746
3821
  } catch (e) {
3747
3822
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
3748
3823
  }
@@ -3803,18 +3878,18 @@ function parseResolved(raw, m, where) {
3803
3878
  }
3804
3879
  function loadConfig(crewHome, configFile, opts = {}) {
3805
3880
  const file = configFile ?? findConfigFile(crewHome);
3806
- if (opts.text === void 0 && !existsSync14(file)) {
3881
+ if (opts.text === void 0 && !existsSync15(file)) {
3807
3882
  throw new ConfigError(
3808
3883
  `no config found. Looked in:
3809
3884
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
3810
3885
 
3811
3886
  Create the first of those:
3812
- mkdir -p ${dirname7(defaultConfigPath())} && cp ${join16(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3887
+ mkdir -p ${dirname7(defaultConfigPath())} && cp ${join17(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
3813
3888
  );
3814
3889
  }
3815
3890
  let raw;
3816
3891
  try {
3817
- raw = parse2(opts.text ?? readFileSync14(file, "utf8"));
3892
+ raw = parse2(opts.text ?? readFileSync15(file, "utf8"));
3818
3893
  } catch (e) {
3819
3894
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
3820
3895
  }
@@ -3941,7 +4016,7 @@ Create the first of those:
3941
4016
  useNvm: shipRaw.useNvm !== false,
3942
4017
  nvmSh: shipRaw.nvmSh,
3943
4018
  stateDir,
3944
- logFile: expand(shipRaw.logFile ?? join16(tmpdir(), "crew.log"), base),
4019
+ logFile: expand(shipRaw.logFile ?? join17(tmpdir(), "crew.log"), base),
3945
4020
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
3946
4021
  maxConcurrentAgents,
3947
4022
  maxConcurrentAgentsSet,
@@ -4123,7 +4198,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
4123
4198
  const hasRepos = Object.keys(repoDirs).length > 0;
4124
4199
  const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
4125
4200
  const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
4126
- const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join16(reposBasePath, splitRoute(route2)[0]) : "";
4201
+ const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join17(reposBasePath, splitRoute(route2)[0]) : "";
4127
4202
  const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
4128
4203
  if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
4129
4204
  missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
@@ -4221,8 +4296,8 @@ function repoIdForName(r, name) {
4221
4296
  }
4222
4297
  function resolveApiKey(r) {
4223
4298
  if (r.apiKey) return r.apiKey;
4224
- if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
4225
- for (const line of readFileSync14(r.apiKeyFile, "utf8").split("\n")) {
4299
+ if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
4300
+ for (const line of readFileSync15(r.apiKeyFile, "utf8").split("\n")) {
4226
4301
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
4227
4302
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
4228
4303
  }
@@ -4235,9 +4310,9 @@ function resolveApiKey(r) {
4235
4310
  }
4236
4311
  function credentialSource(r) {
4237
4312
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
4238
- if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
4313
+ if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
4239
4314
  const re = new RegExp(`^${r.apiKeyVar}=`);
4240
- if (readFileSync14(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
4315
+ if (readFileSync15(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
4241
4316
  }
4242
4317
  return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
4243
4318
  }
@@ -4293,7 +4368,7 @@ function configuredMembers(rt) {
4293
4368
  return out;
4294
4369
  }
4295
4370
  function defaultRepoDir(basePath, workspace, repoName) {
4296
- return join16(basePath, workspace, repoName);
4371
+ return join17(basePath, workspace, repoName);
4297
4372
  }
4298
4373
  function reposOf(r) {
4299
4374
  const named = Object.entries(r.repos);
@@ -4332,10 +4407,10 @@ function passengerRepoTargets(r) {
4332
4407
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
4333
4408
  }
4334
4409
  function passengerCheckoutDir(basePath, workspace, repoName) {
4335
- return join16(basePath, workspace, ".passenger-checkouts", repoName);
4410
+ return join17(basePath, workspace, ".passenger-checkouts", repoName);
4336
4411
  }
4337
4412
  function passengerMirrorArea(ship) {
4338
- return ship.runner === "sandbox" ? join16(ship.stateDir, "passenger-sources") : void 0;
4413
+ return ship.runner === "sandbox" ? join17(ship.stateDir, "passenger-sources") : void 0;
4339
4414
  }
4340
4415
  function passengerCheckoutTargets(r, mirrorArea) {
4341
4416
  if (!r.hostPassengers) return [];
@@ -4346,7 +4421,7 @@ function passengerCheckoutTargets(r, mirrorArea) {
4346
4421
  const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
4347
4422
  return {
4348
4423
  name: t.name,
4349
- dir: mirrorArea ? join16(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
4424
+ dir: mirrorArea ? join17(mirrorArea, workspace, t.name) : passengerCheckoutDir(r.reposBasePath, workspace, t.name),
4350
4425
  remote: t.remote ?? (id ? remotes[id] : void 0),
4351
4426
  ...forgeHost ? { forgeHost } : {}
4352
4427
  };
@@ -4628,9 +4703,9 @@ function recoveredFailureHeadlines(o, route2) {
4628
4703
  }
4629
4704
 
4630
4705
  // src/install.ts
4631
- import { dirname as dirname8, join as join17, extname } from "path";
4706
+ import { dirname as dirname8, join as join18, extname } from "path";
4632
4707
  import { homedir as homedir7, userInfo } from "os";
4633
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync3, readFileSync as readFileSync15, statSync as statSync5, writeFileSync as writeFileSync11, unlinkSync, realpathSync } from "fs";
4708
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
4634
4709
  import { spawnSync as spawnSync5 } from "child_process";
4635
4710
  import { createHash as createHash3 } from "crypto";
4636
4711
 
@@ -4642,7 +4717,7 @@ function isCompiledBinary(moduleUrl) {
4642
4717
  // src/install.ts
4643
4718
  function crewInvocation(crewHome) {
4644
4719
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
4645
- return [process.execPath, join17(crewHome, "bin", "crew")];
4720
+ return [process.execPath, join18(crewHome, "bin", "crew")];
4646
4721
  }
4647
4722
  var INTERVAL_SECONDS = 120;
4648
4723
  function labelFor(crewHome, job) {
@@ -4680,7 +4755,7 @@ function pathFor(ship) {
4680
4755
  return parts.join(":");
4681
4756
  }
4682
4757
  function findOnPath(bin, searchPath) {
4683
- const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join17(dir, bin));
4758
+ const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join18(dir, bin));
4684
4759
  for (const candidate of candidates2) {
4685
4760
  try {
4686
4761
  accessSync2(candidate, constants2.X_OK);
@@ -4690,7 +4765,7 @@ function findOnPath(bin, searchPath) {
4690
4765
  }
4691
4766
  return void 0;
4692
4767
  }
4693
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join17(homedir7(), ".docker", "bin")].join(":");
4768
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join18(homedir7(), ".docker", "bin")].join(":");
4694
4769
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
4695
4770
  const schedulerPath = pathFor(ship);
4696
4771
  if (findOnPath("docker", schedulerPath)) return void 0;
@@ -4705,7 +4780,7 @@ function installContext(env = process.env) {
4705
4780
  }
4706
4781
  var SYSTEM_UNIT_DIR = "/etc/systemd/system";
4707
4782
  function systemUnitPathFor(label, suffix = "service") {
4708
- return join17(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4783
+ return join18(SYSTEM_UNIT_DIR, `${label}.${suffix}`);
4709
4784
  }
4710
4785
  function unitEnvironment(ship) {
4711
4786
  return [["PATH", pathFor(ship)], ...Object.entries(ship.env ?? {})];
@@ -4726,7 +4801,7 @@ function xmlEscape(s) {
4726
4801
  }
4727
4802
  function planLaunchd(ship, crewHome, job) {
4728
4803
  const label = labelFor(crewHome, job);
4729
- const unitPath = join17(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4804
+ const unitPath = join18(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4730
4805
  const crewLog = ship.logFile;
4731
4806
  const schedulerLog = schedulerLogFor(crewLog, job);
4732
4807
  const persistent = job === "run";
@@ -4780,7 +4855,7 @@ ${scheduleKeys}
4780
4855
  }
4781
4856
  function planSystemd(ship, crewHome, job, ctx) {
4782
4857
  const label = labelFor(crewHome, job);
4783
- const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join17(homedir7(), ".config", "systemd", "user");
4858
+ const unitDir = ctx.root ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
4784
4859
  const ctl = ctx.root ? ["systemctl"] : ["systemctl", "--user"];
4785
4860
  const userLine = ctx.root && ctx.user && ctx.user !== "root" ? `User=${ctx.user}
4786
4861
  ` : "";
@@ -4789,7 +4864,7 @@ function planSystemd(ship, crewHome, job, ctx) {
4789
4864
  lingerCommand: ["loginctl", "enable-linger", ctx.user],
4790
4865
  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`
4791
4866
  };
4792
- const servicePath = join17(unitDir, `${label}.service`);
4867
+ const servicePath = join18(unitDir, `${label}.service`);
4793
4868
  const crewLog = ship.logFile;
4794
4869
  const schedulerLog = schedulerLogFor(crewLog, job);
4795
4870
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -4830,7 +4905,7 @@ WantedBy=${wantedBy}
4830
4905
  ...linger
4831
4906
  };
4832
4907
  }
4833
- const timerPath = join17(unitDir, `${label}.timer`);
4908
+ const timerPath = join18(unitDir, `${label}.timer`);
4834
4909
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
4835
4910
  [Unit]
4836
4911
  Description=${description}
@@ -4905,7 +4980,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run", ctx = US
4905
4980
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4906
4981
  const label = labelFor(crewHome, job);
4907
4982
  if (hostShip === "macos") {
4908
- const unitPath = join17(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4983
+ const unitPath = join18(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4909
4984
  return {
4910
4985
  mechanism: "launchd",
4911
4986
  unitPaths: [unitPath],
@@ -4915,20 +4990,20 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4915
4990
  };
4916
4991
  }
4917
4992
  if (hostShip === "linux" && hasSystemd) {
4918
- const system = existsSync15(systemUnitPathFor(label));
4919
- const unitDir = system ? SYSTEM_UNIT_DIR : join17(homedir7(), ".config", "systemd", "user");
4993
+ const system = existsSync16(systemUnitPathFor(label));
4994
+ const unitDir = system ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
4920
4995
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
4921
4996
  if (job === "run") {
4922
4997
  return {
4923
4998
  mechanism: "systemd",
4924
- unitPaths: [join17(unitDir, `${label}.service`)],
4999
+ unitPaths: [join18(unitDir, `${label}.service`)],
4925
5000
  crontabLine: null,
4926
5001
  unloadCommands: [[...ctl, "disable", "--now", `${label}.service`]]
4927
5002
  };
4928
5003
  }
4929
5004
  return {
4930
5005
  mechanism: "systemd",
4931
- unitPaths: [join17(unitDir, `${label}.service`), join17(unitDir, `${label}.timer`)],
5006
+ unitPaths: [join18(unitDir, `${label}.service`), join18(unitDir, `${label}.timer`)],
4932
5007
  crontabLine: null,
4933
5008
  unloadCommands: [[...ctl, "disable", "--now", `${label}.timer`]]
4934
5009
  };
@@ -4946,7 +5021,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
4946
5021
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
4947
5022
  const label = labelFor(crewHome, "run");
4948
5023
  if (hostShip === "macos") {
4949
- const unitPath = join17(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
5024
+ const unitPath = join18(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
4950
5025
  return {
4951
5026
  mechanism: "launchd",
4952
5027
  startCommand: ["launchctl", "load", unitPath],
@@ -4956,7 +5031,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
4956
5031
  }
4957
5032
  if (hostShip === "linux" && hasSystemd) {
4958
5033
  const unit = `${label}.service`;
4959
- const ctl = existsSync15(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
5034
+ const ctl = existsSync16(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
4960
5035
  return {
4961
5036
  mechanism: "systemd",
4962
5037
  startCommand: [...ctl, "start", unit],
@@ -4988,10 +5063,10 @@ async function applyInstall(plan, crewHome, dryRun, log) {
4988
5063
  log.emit(`would write ${path}`);
4989
5064
  continue;
4990
5065
  }
4991
- const before = existsSync15(path) ? readFileSync15(path, "utf8") : null;
5066
+ const before = existsSync16(path) ? readFileSync16(path, "utf8") : null;
4992
5067
  if (before !== plan.unitContent[path]) changed2 = true;
4993
- mkdirSync10(dirname8(path), { recursive: true });
4994
- writeFileSync11(path, plan.unitContent[path]);
5068
+ mkdirSync11(dirname8(path), { recursive: true });
5069
+ writeFileSync12(path, plan.unitContent[path]);
4995
5070
  log.emit(`wrote ${path}`);
4996
5071
  }
4997
5072
  if (plan.crontabLine) {
@@ -5055,7 +5130,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
5055
5130
  }
5056
5131
  }
5057
5132
  for (const path of plan.unitPaths) {
5058
- if (!existsSync15(path)) continue;
5133
+ if (!existsSync16(path)) continue;
5059
5134
  if (dryRun) {
5060
5135
  log.emit(`would remove ${path}`);
5061
5136
  continue;
@@ -5074,7 +5149,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
5074
5149
  }
5075
5150
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
5076
5151
  function launchAgentsDir() {
5077
- return join17(homedir7(), "Library", "LaunchAgents");
5152
+ return join18(homedir7(), "Library", "LaunchAgents");
5078
5153
  }
5079
5154
  function xmlUnescape(s) {
5080
5155
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -5091,25 +5166,25 @@ function loadedCrewLabels() {
5091
5166
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
5092
5167
  }
5093
5168
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
5094
- const files = existsSync15(dir) ? readdirSync3(dir) : [];
5169
+ const files = existsSync16(dir) ? readdirSync3(dir) : [];
5095
5170
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
5096
- const plistPath = join17(dir, f);
5171
+ const plistPath = join18(dir, f);
5097
5172
  let program = null;
5098
5173
  try {
5099
- program = programFromPlist(readFileSync15(plistPath, "utf8"));
5174
+ program = programFromPlist(readFileSync16(plistPath, "utf8"));
5100
5175
  } catch {
5101
5176
  }
5102
5177
  return {
5103
5178
  label: f.slice(0, -".plist".length),
5104
5179
  plistPath,
5105
5180
  program,
5106
- programExists: program !== null && existsSync15(program)
5181
+ programExists: program !== null && existsSync16(program)
5107
5182
  };
5108
5183
  });
5109
5184
  const seen = new Set(units.map((u) => u.label));
5110
5185
  for (const label of [...loaded].sort()) {
5111
5186
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
5112
- units.push({ label, plistPath: join17(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
5187
+ units.push({ label, plistPath: join18(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
5113
5188
  }
5114
5189
  return units;
5115
5190
  }
@@ -5122,9 +5197,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
5122
5197
  log.emit(`would unload and remove ${unit.label}`);
5123
5198
  return;
5124
5199
  }
5125
- if (existsSync15(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
5200
+ if (existsSync16(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
5126
5201
  spawnSync5("launchctl", ["remove", unit.label], { encoding: "utf8" });
5127
- if (existsSync15(unit.plistPath)) unlinkSync(unit.plistPath);
5202
+ if (existsSync16(unit.plistPath)) unlinkSync(unit.plistPath);
5128
5203
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
5129
5204
  }
5130
5205
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -5202,24 +5277,24 @@ function detectSystemd() {
5202
5277
  }
5203
5278
 
5204
5279
  // src/ship-attention.ts
5205
- import { existsSync as existsSync17, statfsSync } from "fs";
5280
+ import { existsSync as existsSync18, statfsSync } from "fs";
5206
5281
 
5207
5282
  // src/git.ts
5208
5283
  import { execFileSync as execFileSync5 } from "child_process";
5209
5284
  import {
5210
- existsSync as existsSync16,
5211
- mkdirSync as mkdirSync11,
5285
+ existsSync as existsSync17,
5286
+ mkdirSync as mkdirSync12,
5212
5287
  openSync as openSync3,
5213
5288
  closeSync as closeSync3,
5214
5289
  writeSync,
5215
5290
  statSync as statSync6,
5216
5291
  unlinkSync as unlinkSync2,
5217
- readFileSync as readFileSync16,
5218
- writeFileSync as writeFileSync12,
5292
+ readFileSync as readFileSync17,
5293
+ writeFileSync as writeFileSync13,
5219
5294
  readdirSync as readdirSync4,
5220
5295
  rmSync as rmSync9
5221
5296
  } from "fs";
5222
- import { dirname as dirname9, basename as basename5, join as join18 } from "path";
5297
+ import { dirname as dirname9, basename as basename5, join as join19 } from "path";
5223
5298
  var GitError = class extends Error {
5224
5299
  };
5225
5300
  function streamText(x) {
@@ -5617,11 +5692,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
5617
5692
  }
5618
5693
  for (const name of names) {
5619
5694
  if (!name.startsWith("crew-release-")) continue;
5620
- const root = join18(tmp, name);
5695
+ const root = join19(tmp, name);
5621
5696
  try {
5622
5697
  const st = statSync6(root);
5623
5698
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
5624
- if (existsSync16(join18(root, "checkout"))) continue;
5699
+ if (existsSync17(join19(root, "checkout"))) continue;
5625
5700
  rmSync9(root, { recursive: true, force: true });
5626
5701
  removed.push(name);
5627
5702
  } catch {
@@ -5673,13 +5748,13 @@ function originUrlOf(dir, remoteName) {
5673
5748
  }
5674
5749
  }
5675
5750
  function ensureRepoCheckout(dir, remote, opts = {}) {
5676
- if (existsSync16(`${dir}/.git`)) return false;
5751
+ if (existsSync17(`${dir}/.git`)) return false;
5677
5752
  if (!remote) {
5678
5753
  throw new GitError(
5679
5754
  `${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`
5680
5755
  );
5681
5756
  }
5682
- mkdirSync11(dirname9(dir), { recursive: true });
5757
+ mkdirSync12(dirname9(dir), { recursive: true });
5683
5758
  const url = cloneUrlFor(remote, opts);
5684
5759
  try {
5685
5760
  execFileSync5("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
@@ -5704,7 +5779,7 @@ function sleepSync(ms3) {
5704
5779
  }
5705
5780
  function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5706
5781
  const lockPath = checkoutLockPath(dir);
5707
- mkdirSync11(dirname9(lockPath), { recursive: true });
5782
+ mkdirSync12(dirname9(lockPath), { recursive: true });
5708
5783
  const deadline = Date.now() + timeoutMs;
5709
5784
  for (; ; ) {
5710
5785
  try {
@@ -5741,17 +5816,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5741
5816
  function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
5742
5817
  const release = acquireCheckoutLock(dir);
5743
5818
  try {
5744
- if (!existsSync16(`${dir}/.git`) && githubRemote) {
5819
+ if (!existsSync17(`${dir}/.git`) && githubRemote) {
5745
5820
  const url = cloneUrlFor(githubRemote, clone);
5746
5821
  const marker = cloneFailureMarker(dir);
5747
- if (existsSync16(marker) && readFileSync16(marker, "utf8").split("\n")[0] === url) {
5748
- return { action: "clone-failed", detail: readFileSync16(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5822
+ if (existsSync17(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
5823
+ return { action: "clone-failed", detail: readFileSync17(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5749
5824
  }
5750
5825
  try {
5751
5826
  ensureRepoCheckout(dir, githubRemote, clone);
5752
5827
  } catch (e) {
5753
5828
  if (!(e instanceof GitError)) throw e;
5754
- writeFileSync12(marker, `${url}
5829
+ writeFileSync13(marker, `${url}
5755
5830
  ${e.message}`);
5756
5831
  return { action: "clone-failed", detail: e.message, repeat: false };
5757
5832
  }
@@ -5977,7 +6052,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
5977
6052
  function reconcileHeldWorktrees(state, repo, log, stillRefused) {
5978
6053
  for (const i of state.shipAttention().previous()) {
5979
6054
  if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
5980
- const gone = !existsSync17(i.held.path);
6055
+ const gone = !existsSync18(i.held.path);
5981
6056
  if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
5982
6057
  clearShipAttention(state, i.key);
5983
6058
  log.emit(
@@ -6130,8 +6205,8 @@ function attentionProblems(items, already) {
6130
6205
  import { createHash as createHash6 } from "crypto";
6131
6206
  import { spawn as spawn5 } from "child_process";
6132
6207
  import { execFileSync as execFileSync8 } from "child_process";
6133
- import { mkdirSync as mkdirSync17, readFileSync as readFileSync24, renameSync as renameSync3, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
6134
- import { dirname as dirname13, join as join29 } from "path";
6208
+ import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
6209
+ import { dirname as dirname14, join as join31 } from "path";
6135
6210
  import { tmpdir as tmpdir3 } from "os";
6136
6211
 
6137
6212
  // src/passenger-containers.ts
@@ -6144,32 +6219,32 @@ import { promisify as promisify2 } from "util";
6144
6219
 
6145
6220
  // src/deprovision-host.ts
6146
6221
  import { spawnSync as spawnSync7 } from "child_process";
6147
- import { existsSync as existsSync27, readdirSync as readdirSync11 } from "fs";
6222
+ import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
6148
6223
 
6149
6224
  // src/interrupted.ts
6150
- import { existsSync as existsSync25, readdirSync as readdirSync9, readFileSync as readFileSync23 } from "fs";
6151
- import { join as join27 } from "path";
6225
+ import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync25 } from "fs";
6226
+ import { join as join29 } from "path";
6152
6227
 
6153
6228
  // src/agent.ts
6154
6229
  import { spawnSync as spawnSync6 } from "child_process";
6155
- import { readFileSync as readFileSync22, existsSync as existsSync24, statSync as statSync8, mkdirSync as mkdirSync16, writeFileSync as writeFileSync17, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6230
+ import { readFileSync as readFileSync24, existsSync as existsSync26, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6156
6231
  import { StringDecoder } from "string_decoder";
6157
6232
  import { finished } from "stream/promises";
6158
- import { join as join26, dirname as dirname12, basename as basename9 } from "path";
6233
+ import { join as join28, dirname as dirname13, basename as basename9 } from "path";
6159
6234
  import { createHash as createHash4 } from "crypto";
6160
6235
 
6161
6236
  // src/state.ts
6162
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync18, mkdirSync as mkdirSync12, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6163
- import { join as join19 } from "path";
6237
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6238
+ import { join as join20 } from "path";
6164
6239
  var EPOCH = "1970-01-01T00:00:00.000Z";
6165
6240
  var State = class _State {
6166
6241
  dir;
6167
6242
  constructor(dir) {
6168
6243
  this.dir = dir;
6169
- mkdirSync12(dir, { recursive: true });
6244
+ mkdirSync13(dir, { recursive: true });
6170
6245
  }
6171
6246
  path(...parts) {
6172
- return join19(this.dir, ...parts);
6247
+ return join20(this.dir, ...parts);
6173
6248
  }
6174
6249
  /**
6175
6250
  * The timestamp every "new since last poll" check is measured against.
@@ -6180,14 +6255,14 @@ var State = class _State {
6180
6255
  */
6181
6256
  watermark() {
6182
6257
  try {
6183
- const v = readFileSync17(this.path(".poll-watermark"), "utf8").trim();
6258
+ const v = readFileSync18(this.path(".poll-watermark"), "utf8").trim();
6184
6259
  return v || EPOCH;
6185
6260
  } catch {
6186
6261
  return EPOCH;
6187
6262
  }
6188
6263
  }
6189
6264
  setWatermark(at) {
6190
- writeFileSync13(this.path(".poll-watermark"), `${at}
6265
+ writeFileSync14(this.path(".poll-watermark"), `${at}
6191
6266
  `);
6192
6267
  }
6193
6268
  /**
@@ -6205,16 +6280,16 @@ var State = class _State {
6205
6280
  }
6206
6281
  /** Whole-crew pause, and the per-role sentinels one level down. */
6207
6282
  isPaused() {
6208
- return existsSync18(this.path(".crew-paused"));
6283
+ return existsSync19(this.path(".crew-paused"));
6209
6284
  }
6210
6285
  isRolePaused(role) {
6211
- return existsSync18(this.path(`.role-paused-${role}`));
6286
+ return existsSync19(this.path(`.role-paused-${role}`));
6212
6287
  }
6213
6288
  pausedRoles(roles) {
6214
6289
  return new Set(roles.filter((r) => this.isRolePaused(r)));
6215
6290
  }
6216
6291
  pause(role) {
6217
- writeFileSync13(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
6292
+ writeFileSync14(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
6218
6293
  }
6219
6294
  resume(role) {
6220
6295
  rmSync10(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
@@ -6225,10 +6300,10 @@ var State = class _State {
6225
6300
  * while agents keep working.
6226
6301
  */
6227
6302
  isReleasePaused() {
6228
- return existsSync18(this.path(".release-paused"));
6303
+ return existsSync19(this.path(".release-paused"));
6229
6304
  }
6230
6305
  setReleasePaused(paused) {
6231
- if (paused) writeFileSync13(this.path(".release-paused"), "");
6306
+ if (paused) writeFileSync14(this.path(".release-paused"), "");
6232
6307
  else rmSync10(this.path(".release-paused"), { force: true });
6233
6308
  }
6234
6309
  /**
@@ -6266,7 +6341,7 @@ var State = class _State {
6266
6341
  const name = _State.safe(rawName);
6267
6342
  const file = this.path(`.${name}.lock`);
6268
6343
  try {
6269
- const existing = Number.parseInt(readFileSync17(file, "utf8").trim(), 10);
6344
+ const existing = Number.parseInt(readFileSync18(file, "utf8").trim(), 10);
6270
6345
  if (Number.isFinite(existing) && existing > 0) {
6271
6346
  try {
6272
6347
  process.kill(existing, 0);
@@ -6276,7 +6351,7 @@ var State = class _State {
6276
6351
  }
6277
6352
  } catch {
6278
6353
  }
6279
- writeFileSync13(file, `${process.pid}
6354
+ writeFileSync14(file, `${process.pid}
6280
6355
  `);
6281
6356
  return {
6282
6357
  ok: true,
@@ -6304,7 +6379,7 @@ var State = class _State {
6304
6379
  if (!/^\.release-.+\.lock$/.test(n)) continue;
6305
6380
  let pid = 0;
6306
6381
  try {
6307
- pid = Number.parseInt(readFileSync17(this.path(n), "utf8").trim(), 10);
6382
+ pid = Number.parseInt(readFileSync18(this.path(n), "utf8").trim(), 10);
6308
6383
  } catch {
6309
6384
  continue;
6310
6385
  }
@@ -6390,7 +6465,7 @@ var State = class _State {
6390
6465
  ticketInFlight(recordId) {
6391
6466
  const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
6392
6467
  try {
6393
- const pid = Number.parseInt(readFileSync17(file, "utf8").trim(), 10);
6468
+ const pid = Number.parseInt(readFileSync18(file, "utf8").trim(), 10);
6394
6469
  if (!Number.isFinite(pid) || pid <= 0) return false;
6395
6470
  process.kill(pid, 0);
6396
6471
  return true;
@@ -6424,7 +6499,7 @@ var State = class _State {
6424
6499
  const heldReported = `.release-held-${suffix}`;
6425
6500
  const read = (f) => {
6426
6501
  try {
6427
- return readFileSync17(this.path(f), "utf8").trim() || null;
6502
+ return readFileSync18(this.path(f), "utf8").trim() || null;
6428
6503
  } catch {
6429
6504
  return null;
6430
6505
  }
@@ -6433,7 +6508,7 @@ var State = class _State {
6433
6508
  blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
6434
6509
  noteBlocked: () => {
6435
6510
  const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
6436
- writeFileSync13(this.path(blocked), `${n}
6511
+ writeFileSync14(this.path(blocked), `${n}
6437
6512
  `);
6438
6513
  return n;
6439
6514
  },
@@ -6450,7 +6525,7 @@ var State = class _State {
6450
6525
  */
6451
6526
  deployFailedSha: () => read(failed),
6452
6527
  noteDeployFailed: (sha) => {
6453
- writeFileSync13(this.path(failed), `${sha}
6528
+ writeFileSync14(this.path(failed), `${sha}
6454
6529
  `);
6455
6530
  },
6456
6531
  clearDeployFailed: () => {
@@ -6464,7 +6539,7 @@ var State = class _State {
6464
6539
  noteTestGateFailed: (sha) => {
6465
6540
  const [prev, n] = (read(gateFailed) ?? "").split(" ");
6466
6541
  const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
6467
- writeFileSync13(this.path(gateFailed), `${sha} ${next}
6542
+ writeFileSync14(this.path(gateFailed), `${sha} ${next}
6468
6543
  `);
6469
6544
  return next;
6470
6545
  },
@@ -6474,13 +6549,13 @@ var State = class _State {
6474
6549
  /** Whether the board was already told about this head's red gate. */
6475
6550
  heldReported: () => {
6476
6551
  try {
6477
- return readFileSync17(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6552
+ return readFileSync18(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
6478
6553
  } catch {
6479
6554
  return null;
6480
6555
  }
6481
6556
  },
6482
6557
  noteHeldReported: (signature) => {
6483
- writeFileSync13(this.path(heldReported), `${signature}
6558
+ writeFileSync14(this.path(heldReported), `${signature}
6484
6559
  `);
6485
6560
  },
6486
6561
  clearHeldReported: () => {
@@ -6488,7 +6563,7 @@ var State = class _State {
6488
6563
  },
6489
6564
  testGateReported: (sha) => read(gateReported) === sha,
6490
6565
  noteTestGateReported: (sha) => {
6491
- writeFileSync13(this.path(gateReported), `${sha}
6566
+ writeFileSync14(this.path(gateReported), `${sha}
6492
6567
  `);
6493
6568
  }
6494
6569
  };
@@ -6511,7 +6586,7 @@ var State = class _State {
6511
6586
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
6512
6587
  const read = (f) => {
6513
6588
  try {
6514
- return readFileSync17(this.path(f), "utf8").trim() || null;
6589
+ return readFileSync18(this.path(f), "utf8").trim() || null;
6515
6590
  } catch {
6516
6591
  return null;
6517
6592
  }
@@ -6544,18 +6619,18 @@ var State = class _State {
6544
6619
  clear2();
6545
6620
  return;
6546
6621
  }
6547
- writeFileSync13(this.path(streakFile), `${this.readInt(streakFile) + 1}
6622
+ writeFileSync14(this.path(streakFile), `${this.readInt(streakFile) + 1}
6548
6623
  `);
6549
6624
  const prior = read(ticketFile);
6550
6625
  if (topTicket && topTicket === prior) {
6551
- writeFileSync13(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6626
+ writeFileSync14(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
6552
6627
  `);
6553
6628
  } else if (topTicket) {
6554
- writeFileSync13(this.path(ticketFile), `${topTicket}
6629
+ writeFileSync14(this.path(ticketFile), `${topTicket}
6555
6630
  `);
6556
- writeFileSync13(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6631
+ writeFileSync14(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
6557
6632
  `);
6558
- writeFileSync13(this.path(ticketStreakFile), "1\n");
6633
+ writeFileSync14(this.path(ticketStreakFile), "1\n");
6559
6634
  }
6560
6635
  }
6561
6636
  };
@@ -6576,14 +6651,14 @@ var State = class _State {
6576
6651
  return {
6577
6652
  previous: () => {
6578
6653
  try {
6579
- const parsed = JSON.parse(readFileSync17(this.path(file), "utf8"));
6654
+ const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
6580
6655
  return parsed && typeof parsed === "object" ? parsed : {};
6581
6656
  } catch {
6582
6657
  return {};
6583
6658
  }
6584
6659
  },
6585
6660
  persist: (next) => {
6586
- writeFileSync13(this.path(file), JSON.stringify(next));
6661
+ writeFileSync14(this.path(file), JSON.stringify(next));
6587
6662
  }
6588
6663
  };
6589
6664
  }
@@ -6599,14 +6674,14 @@ var State = class _State {
6599
6674
  return {
6600
6675
  previous: () => {
6601
6676
  try {
6602
- const parsed = JSON.parse(readFileSync17(this.path(file), "utf8"));
6677
+ const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
6603
6678
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
6604
6679
  } catch {
6605
6680
  return [];
6606
6681
  }
6607
6682
  },
6608
6683
  persist: (next) => {
6609
- writeFileSync13(this.path(file), JSON.stringify(next));
6684
+ writeFileSync14(this.path(file), JSON.stringify(next));
6610
6685
  }
6611
6686
  };
6612
6687
  }
@@ -6620,14 +6695,14 @@ var State = class _State {
6620
6695
  return {
6621
6696
  previous: () => {
6622
6697
  try {
6623
- const parsed = JSON.parse(readFileSync17(this.path(file), "utf8"));
6698
+ const parsed = JSON.parse(readFileSync18(this.path(file), "utf8"));
6624
6699
  return Array.isArray(parsed) ? parsed : [];
6625
6700
  } catch {
6626
6701
  return [];
6627
6702
  }
6628
6703
  },
6629
6704
  persist: (next) => {
6630
- writeFileSync13(this.path(file), JSON.stringify(next));
6705
+ writeFileSync14(this.path(file), JSON.stringify(next));
6631
6706
  }
6632
6707
  };
6633
6708
  }
@@ -6640,7 +6715,7 @@ var State = class _State {
6640
6715
  const file = ".release-last-run";
6641
6716
  return {
6642
6717
  touch: () => {
6643
- writeFileSync13(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6718
+ writeFileSync14(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
6644
6719
  },
6645
6720
  at: () => {
6646
6721
  try {
@@ -6653,7 +6728,7 @@ var State = class _State {
6653
6728
  }
6654
6729
  readInt(file) {
6655
6730
  try {
6656
- return Number.parseInt(readFileSync17(this.path(file), "utf8").trim(), 10) || 0;
6731
+ return Number.parseInt(readFileSync18(this.path(file), "utf8").trim(), 10) || 0;
6657
6732
  } catch {
6658
6733
  return 0;
6659
6734
  }
@@ -7041,6 +7116,143 @@ function renderEnvironment(i) {
7041
7116
  return lines2.join("\n");
7042
7117
  }
7043
7118
 
7119
+ // src/intel-bridge.ts
7120
+ import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync19, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7121
+ import { dirname as dirname10, join as join21 } from "path";
7122
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7123
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7124
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7125
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7126
+ import {
7127
+ CallToolRequestSchema,
7128
+ ListToolsRequestSchema
7129
+ } from "@modelcontextprotocol/sdk/types.js";
7130
+ var STATUS_TOOL = "code_status";
7131
+ var STATUS_WAITING = "The symbol service is not available yet: the worktree has not been cut. Run `crew worktree` for the ticket; the symbol tools appear in this server afterwards.";
7132
+ var STATUS_LOST = "The symbol service stopped answering, so its tools are unavailable. They return if the service comes back; until then use text search.";
7133
+ function intelDir(stateDir, route2, ticket) {
7134
+ const safe = (s) => s.replace(/[^A-Za-z0-9._-]+/g, "_");
7135
+ return join21(stateDir, "intel", safe(route2), safe(ticket));
7136
+ }
7137
+ var attachFilePath = (stateDir, route2, ticket) => join21(intelDir(stateDir, route2, ticket), "url");
7138
+ function writeAttachFile(file, url) {
7139
+ mkdirSync14(dirname10(file), { recursive: true });
7140
+ writeFileSync15(file, `${url}
7141
+ `);
7142
+ }
7143
+ function readAttachFile(file) {
7144
+ try {
7145
+ const url = readFileSync19(file, "utf8").trim();
7146
+ return /^https?:\/\/\S+$/i.test(url) ? url : void 0;
7147
+ } catch {
7148
+ return void 0;
7149
+ }
7150
+ }
7151
+ var statusTool = {
7152
+ name: STATUS_TOOL,
7153
+ description: "Reports whether this repository's symbol service (find symbols, references, definitions, file outlines) is attached. Before `crew worktree` has been run for the ticket it is not; the symbol tools replace this one once it is.",
7154
+ inputSchema: { type: "object", properties: {} }
7155
+ };
7156
+ var defaultConnect = async (url) => {
7157
+ const client2 = new Client({ name: "crew-intel-bridge", version: "0" }, { capabilities: {} });
7158
+ await client2.connect(new StreamableHTTPClientTransport(new URL(url)));
7159
+ return client2;
7160
+ };
7161
+ function createBridge(o) {
7162
+ const connect2 = o.connectUpstream ?? defaultConnect;
7163
+ const server = new Server(
7164
+ { name: INTEL_SERVER_NAME, version: o.version ?? "0" },
7165
+ { capabilities: { tools: { listChanged: true } } }
7166
+ );
7167
+ let upstream;
7168
+ let lost = false;
7169
+ let connecting = false;
7170
+ let timer;
7171
+ const notify2 = () => {
7172
+ void server.sendToolListChanged().catch(() => {
7173
+ });
7174
+ };
7175
+ const drop = () => {
7176
+ const gone = upstream;
7177
+ upstream = void 0;
7178
+ lost = true;
7179
+ if (gone) void gone.close().catch(() => {
7180
+ });
7181
+ notify2();
7182
+ };
7183
+ const status2 = () => ({ content: [{ type: "text", text: lost ? STATUS_LOST : STATUS_WAITING }] });
7184
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
7185
+ if (!upstream) return { tools: [statusTool] };
7186
+ try {
7187
+ return await upstream.listTools();
7188
+ } catch {
7189
+ drop();
7190
+ return { tools: [statusTool] };
7191
+ }
7192
+ });
7193
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
7194
+ if (!upstream) return status2();
7195
+ try {
7196
+ return await upstream.callTool({ name: req.params.name, arguments: req.params.arguments });
7197
+ } catch {
7198
+ drop();
7199
+ return status2();
7200
+ }
7201
+ });
7202
+ const tryAttach = async () => {
7203
+ if (upstream || connecting) return false;
7204
+ const url = readAttachFile(o.attachFile);
7205
+ if (!url) return false;
7206
+ connecting = true;
7207
+ try {
7208
+ const client2 = await connect2(url);
7209
+ client2.onclose = () => {
7210
+ if (upstream === client2) drop();
7211
+ };
7212
+ upstream = client2;
7213
+ lost = false;
7214
+ notify2();
7215
+ return true;
7216
+ } catch {
7217
+ return false;
7218
+ } finally {
7219
+ connecting = false;
7220
+ }
7221
+ };
7222
+ return {
7223
+ server,
7224
+ attached: () => upstream !== void 0,
7225
+ tryAttach,
7226
+ async start(transport) {
7227
+ await server.connect(transport);
7228
+ if (existsSync20(o.attachFile)) await tryAttach();
7229
+ timer = setInterval(() => {
7230
+ void tryAttach();
7231
+ }, o.pollMs ?? 1e3);
7232
+ timer.unref?.();
7233
+ },
7234
+ async stop() {
7235
+ if (timer) clearInterval(timer);
7236
+ const gone = upstream;
7237
+ upstream = void 0;
7238
+ await gone?.close().catch(() => {
7239
+ });
7240
+ await server.close().catch(() => {
7241
+ });
7242
+ }
7243
+ };
7244
+ }
7245
+ async function runBridge(stateDir, route2, ticket) {
7246
+ const bridge = createBridge({ attachFile: attachFilePath(stateDir, route2, ticket) });
7247
+ const closed = new Promise((resolve8) => {
7248
+ bridge.server.onclose = resolve8;
7249
+ process.stdin.on("end", resolve8);
7250
+ });
7251
+ await bridge.start(new StdioServerTransport());
7252
+ await closed;
7253
+ await bridge.stop();
7254
+ }
7255
+
7044
7256
  // src/contract.ts
7045
7257
  var DEFAULT_CONTRACT = {
7046
7258
  columns: {
@@ -7270,8 +7482,8 @@ var BeforeFirstEditTracker = class {
7270
7482
  };
7271
7483
 
7272
7484
  // src/agent-log.ts
7273
- import { existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync6, readFileSync as readFileSync18, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "fs";
7274
- import { join as join20 } from "path";
7485
+ import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync20, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
7486
+ import { join as join22 } from "path";
7275
7487
 
7276
7488
  // src/pending-backoff.ts
7277
7489
  var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
@@ -7474,20 +7686,20 @@ async function addRunToTicket(target, t, recordId, run3) {
7474
7686
  };
7475
7687
  }
7476
7688
  }
7477
- var queueDir = (stateDir) => join20(stateDir, "pending-agent-logs");
7689
+ var queueDir = (stateDir) => join22(stateDir, "pending-agent-logs");
7478
7690
  function queueAgentRun(stateDir, name, q2) {
7479
- mkdirSync13(queueDir(stateDir), { recursive: true });
7480
- writeFileSync14(join20(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
7691
+ mkdirSync15(queueDir(stateDir), { recursive: true });
7692
+ writeFileSync16(join22(queueDir(stateDir), `${name}.json`), JSON.stringify(q2));
7481
7693
  }
7482
7694
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
7483
7695
  const dir = queueDir(stateDir);
7484
- if (!existsSync19(dir)) return 0;
7696
+ if (!existsSync21(dir)) return 0;
7485
7697
  let sent = 0;
7486
7698
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
7487
- const path = join20(dir, f);
7699
+ const path = join22(dir, f);
7488
7700
  let q2;
7489
7701
  try {
7490
- q2 = JSON.parse(readFileSync18(path, "utf8"));
7702
+ q2 = JSON.parse(readFileSync20(path, "utf8"));
7491
7703
  } catch {
7492
7704
  continue;
7493
7705
  }
@@ -7506,17 +7718,17 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
7506
7718
  attempts: 0,
7507
7719
  nextAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
7508
7720
  });
7509
- rmSync11(path, { force: true });
7721
+ rmSync12(path, { force: true });
7510
7722
  sent += 1;
7511
7723
  log.emit(`sent a queued Agent Log report (${f})`);
7512
7724
  } catch (e) {
7513
7725
  if (!(e instanceof AgentLogUnreachableError)) {
7514
- rmSync11(path, { force: true });
7726
+ rmSync12(path, { force: true });
7515
7727
  log.warn(`dropped a queued Agent Log report (${f}): ${e.message}`);
7516
7728
  continue;
7517
7729
  }
7518
7730
  const attempts = q2.attempts + 1;
7519
- writeFileSync14(path, JSON.stringify({
7731
+ writeFileSync16(path, JSON.stringify({
7520
7732
  ...q2,
7521
7733
  attempts,
7522
7734
  warned: true,
@@ -7527,20 +7739,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
7527
7739
  }
7528
7740
  return sent;
7529
7741
  }
7530
- var countersDir = (stateDir) => join20(stateDir, "pending-ticket-counters");
7742
+ var countersDir = (stateDir) => join22(stateDir, "pending-ticket-counters");
7531
7743
  function queueTicketCounters(stateDir, name, q2) {
7532
- mkdirSync13(countersDir(stateDir), { recursive: true });
7533
- writeFileSync14(join20(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
7744
+ mkdirSync15(countersDir(stateDir), { recursive: true });
7745
+ writeFileSync16(join22(countersDir(stateDir), `${name}.json`), JSON.stringify(q2));
7534
7746
  }
7535
7747
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
7536
7748
  const dir = countersDir(stateDir);
7537
- if (!existsSync19(dir)) return 0;
7749
+ if (!existsSync21(dir)) return 0;
7538
7750
  let sent = 0;
7539
7751
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
7540
- const path = join20(dir, f);
7752
+ const path = join22(dir, f);
7541
7753
  let q2;
7542
7754
  try {
7543
- q2 = JSON.parse(readFileSync18(path, "utf8"));
7755
+ q2 = JSON.parse(readFileSync20(path, "utf8"));
7544
7756
  } catch {
7545
7757
  continue;
7546
7758
  }
@@ -7549,17 +7761,17 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
7549
7761
  const apiKey = await keyFor(q2.role);
7550
7762
  if (!apiKey) continue;
7551
7763
  await writeCounters({ ...q2.target, apiKey }, q2.write);
7552
- rmSync11(path, { force: true });
7764
+ rmSync12(path, { force: true });
7553
7765
  sent += 1;
7554
7766
  log.emit(`added a queued run's spend to ${q2.write.ticketReference ?? q2.write.recordId} (${f})`);
7555
7767
  } catch (e) {
7556
7768
  if (!isRetryable(e)) {
7557
- rmSync11(path, { force: true });
7769
+ rmSync12(path, { force: true });
7558
7770
  log.warn(`dropped a queued ticket counter update (${f}): ${e.message}`);
7559
7771
  continue;
7560
7772
  }
7561
7773
  const attempts = q2.attempts + 1;
7562
- writeFileSync14(path, JSON.stringify({
7774
+ writeFileSync16(path, JSON.stringify({
7563
7775
  ...q2,
7564
7776
  attempts,
7565
7777
  warned: true,
@@ -7762,8 +7974,8 @@ var HandoffTranscriptTracker = class {
7762
7974
  };
7763
7975
 
7764
7976
  // src/pending-handoff.ts
7765
- import { existsSync as existsSync21, mkdirSync as mkdirSync14, readdirSync as readdirSync7, readFileSync as readFileSync19, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "fs";
7766
- import { join as join22 } from "path";
7977
+ import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync21, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
7978
+ import { join as join24 } from "path";
7767
7979
 
7768
7980
  // src/ticket-branch.ts
7769
7981
  import { basename as basename7 } from "path";
@@ -8929,8 +9141,8 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
8929
9141
  }
8930
9142
 
8931
9143
  // src/run-worktree.ts
8932
- import { existsSync as existsSync20 } from "fs";
8933
- import { dirname as dirname10, join as join21 } from "path";
9144
+ import { existsSync as existsSync22 } from "fs";
9145
+ import { dirname as dirname11, join as join23 } from "path";
8934
9146
  var describeRunWorktreePlan = (p) => {
8935
9147
  switch (p.action) {
8936
9148
  case "cut-remote":
@@ -8971,7 +9183,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8971
9183
  const existing = worktreeForNumber(dir, num);
8972
9184
  const located = locateBranchForTicket(dir, cfg, t, role);
8973
9185
  const baseRef = hasRemote ? `${remote}/${base}` : base;
8974
- if (existing && !existing.prunable && existsSync20(existing.path)) {
9186
+ if (existing && !existing.prunable && existsSync22(existing.path)) {
8975
9187
  const branch = existing.branch;
8976
9188
  if (status(existing.path).length > 0) {
8977
9189
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -8987,7 +9199,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
8987
9199
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
8988
9200
  return { action: "recut", path: existing.path, branch, base: baseRef };
8989
9201
  }
8990
- const path = join21(dirname10(dir), worktreeForTicket(dir, cfg, t, role));
9202
+ const path = join23(dirname11(dir), worktreeForTicket(dir, cfg, t, role));
8991
9203
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
8992
9204
  return { action: "cut-remote", path, branch: located.branch };
8993
9205
  }
@@ -9041,7 +9253,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9041
9253
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
9042
9254
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
9043
9255
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9044
- if (wt && existsSync20(wt.path)) {
9256
+ if (wt && existsSync22(wt.path)) {
9045
9257
  if (status(wt.path).length > 0) return false;
9046
9258
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
9047
9259
  }
@@ -9049,7 +9261,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9049
9261
  }
9050
9262
  async function finishRunWorktree(dir, cfg, branch) {
9051
9263
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9052
- if (!wt || !existsSync20(wt.path)) return { kind: "none" };
9264
+ if (!wt || !existsSync22(wt.path)) return { kind: "none" };
9053
9265
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
9054
9266
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
9055
9267
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -9061,7 +9273,7 @@ async function finishRunWorktree(dir, cfg, branch) {
9061
9273
  if (pids.length) await killGently(pids);
9062
9274
  }
9063
9275
  gitOk(dir, ["worktree", "remove", wt.path]);
9064
- return existsSync20(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9276
+ return existsSync22(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9065
9277
  }
9066
9278
 
9067
9279
  // src/handoff-guard.ts
@@ -9444,20 +9656,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
9444
9656
 
9445
9657
  // src/pending-handoff.ts
9446
9658
  var PENDING_MAX_AGE_MS = 48 * 36e5;
9447
- var pendingDirFor = (stateDir) => join22(stateDir, "pending-handoffs");
9448
- var stateDirOfStream = (streamPath) => join22(streamPath, "..", "..");
9659
+ var pendingDirFor = (stateDir) => join24(stateDir, "pending-handoffs");
9660
+ var stateDirOfStream = (streamPath) => join24(streamPath, "..", "..");
9449
9661
  function writePendingHandoff(stateDir, rec) {
9450
9662
  const dir = pendingDirFor(stateDir);
9451
- mkdirSync14(dir, { recursive: true });
9452
- writeFileSync15(join22(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
9663
+ mkdirSync16(dir, { recursive: true });
9664
+ writeFileSync17(join24(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
9453
9665
  }
9454
9666
  function listPendingHandoffs(stateDir, route2) {
9455
9667
  const dir = pendingDirFor(stateDir);
9456
- if (!existsSync21(dir)) return [];
9668
+ if (!existsSync23(dir)) return [];
9457
9669
  const out = [];
9458
9670
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
9459
9671
  try {
9460
- const rec = JSON.parse(readFileSync19(join22(dir, f), "utf8"));
9672
+ const rec = JSON.parse(readFileSync21(join24(dir, f), "utf8"));
9461
9673
  if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
9462
9674
  } catch {
9463
9675
  }
@@ -9465,7 +9677,7 @@ function listPendingHandoffs(stateDir, route2) {
9465
9677
  return out;
9466
9678
  }
9467
9679
  function removePendingHandoff(stateDir, ticketRecordId) {
9468
- rmSync12(join22(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9680
+ rmSync13(join24(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
9469
9681
  }
9470
9682
  function pendingHandoffFor(stateDir, ticketRecordId) {
9471
9683
  return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
@@ -9575,8 +9787,8 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
9575
9787
  }
9576
9788
 
9577
9789
  // src/skills.ts
9578
- import { readFileSync as readFileSync21, readdirSync as readdirSync8, existsSync as existsSync23 } from "fs";
9579
- import { join as join24 } from "path";
9790
+ import { readFileSync as readFileSync23, readdirSync as readdirSync8, existsSync as existsSync25 } from "fs";
9791
+ import { join as join26 } from "path";
9580
9792
  import { parse as parseYaml } from "yaml";
9581
9793
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
9582
9794
 
@@ -9592,8 +9804,8 @@ ${additions}`;
9592
9804
  }
9593
9805
 
9594
9806
  // src/agents.ts
9595
- import { readFileSync as readFileSync20, existsSync as existsSync22 } from "fs";
9596
- import { join as join23 } from "path";
9807
+ import { readFileSync as readFileSync22, existsSync as existsSync24 } from "fs";
9808
+ import { join as join25 } from "path";
9597
9809
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
9598
9810
  var PERSONA_NAME = {
9599
9811
  dev: "Developer",
@@ -9607,12 +9819,12 @@ var CREW_AGENT_COLUMN = "agent_id";
9607
9819
  var AgentsSyncError = class extends Error {
9608
9820
  };
9609
9821
  function personaDefaultPrompt(promptsDir, role) {
9610
- const briefPath = join23(promptsDir, "personas", `lane-${role}.md`);
9611
- if (!existsSync22(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9612
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync20(briefPath, "utf8");
9613
- const commonPath = join23(promptsDir, "personas", "common.md");
9614
- if (!existsSync22(commonPath)) return readFileSync20(briefPath, "utf8");
9615
- return `${readFileSync20(commonPath, "utf8")}${readFileSync20(briefPath, "utf8")}`;
9822
+ const briefPath = join25(promptsDir, "personas", `lane-${role}.md`);
9823
+ if (!existsSync24(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
9824
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync22(briefPath, "utf8");
9825
+ const commonPath = join25(promptsDir, "personas", "common.md");
9826
+ if (!existsSync24(commonPath)) return readFileSync22(briefPath, "utf8");
9827
+ return `${readFileSync22(commonPath, "utf8")}${readFileSync22(briefPath, "utf8")}`;
9616
9828
  }
9617
9829
  async function resolveAgentId(route2, role, opts) {
9618
9830
  const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
@@ -10059,7 +10271,7 @@ function describeCrewLink(o) {
10059
10271
  var SkillSyncError = class extends Error {
10060
10272
  };
10061
10273
  function parseSkillFile(path) {
10062
- const text2 = readFileSync21(path, "utf8");
10274
+ const text2 = readFileSync23(path, "utf8");
10063
10275
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text2);
10064
10276
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
10065
10277
  const frontmatter = match[1];
@@ -10072,9 +10284,9 @@ function parseSkillFile(path) {
10072
10284
  return { name: meta.name, description: meta.description, prompt: body, file: path };
10073
10285
  }
10074
10286
  function listLocalSkills(promptsDir) {
10075
- const dir = join24(promptsDir, "skills");
10076
- if (!existsSync23(dir)) return [];
10077
- return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join24(dir, f)));
10287
+ const dir = join26(promptsDir, "skills");
10288
+ if (!existsSync25(dir)) return [];
10289
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join26(dir, f)));
10078
10290
  }
10079
10291
  async function syncSkills(route2, opts) {
10080
10292
  if (!route2.resolved) {
@@ -10259,9 +10471,9 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
10259
10471
 
10260
10472
  // src/runner.ts
10261
10473
  import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
10262
- import { mkdtempSync, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16, rmSync as rmSync13 } from "fs";
10474
+ import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
10263
10475
  import { tmpdir as tmpdir2 } from "os";
10264
- import { basename as basename8, dirname as dirname11, join as join25 } from "path";
10476
+ import { basename as basename8, dirname as dirname12, join as join27 } from "path";
10265
10477
  import { promisify } from "util";
10266
10478
 
10267
10479
  // src/run-limit.ts
@@ -10390,8 +10602,8 @@ var SandboxRunner = class {
10390
10602
  throw new Error(`cannot generate the sandbox policy: ${e.message}`);
10391
10603
  }
10392
10604
  policyFile = policyPathFor(plan.streamPath);
10393
- mkdirSync15(dirname11(policyFile), { recursive: true });
10394
- writeFileSync16(policyFile, generated.yaml, { mode: 384 });
10605
+ mkdirSync17(dirname12(policyFile), { recursive: true });
10606
+ writeFileSync18(policyFile, generated.yaml, { mode: 384 });
10395
10607
  }
10396
10608
  const create2 = ["sandbox", "create", "--name", name, "--detach"];
10397
10609
  if (policyFile) create2.push("--policy", policyFile);
@@ -10434,13 +10646,13 @@ var SandboxRunner = class {
10434
10646
  const harness = cfg.agentBin ?? basename8(plan.bin);
10435
10647
  const viaUpload = Buffer.byteLength(plan.prompt) > SANDBOX_STDIN_LIMIT;
10436
10648
  if (viaUpload) {
10437
- const tmp = mkdtempSync(join25(tmpdir2(), "crew-prompt-"));
10649
+ const tmp = mkdtempSync(join27(tmpdir2(), "crew-prompt-"));
10438
10650
  try {
10439
- const file = join25(tmp, "prompt.txt");
10440
- writeFileSync16(file, plan.prompt, { mode: 384 });
10651
+ const file = join27(tmp, "prompt.txt");
10652
+ writeFileSync18(file, plan.prompt, { mode: 384 });
10441
10653
  await this.os(["sandbox", "upload", name, file, SANDBOX_PROMPT_PATH]);
10442
10654
  } finally {
10443
- rmSync13(tmp, { recursive: true, force: true });
10655
+ rmSync14(tmp, { recursive: true, force: true });
10444
10656
  }
10445
10657
  }
10446
10658
  const withStubs = `PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `;
@@ -10571,6 +10783,15 @@ function sandboxPolicyInputFor(route2, toolchain, o) {
10571
10783
  ...o.sandbox?.workDir ? { workDir: o.sandbox.workDir } : {}
10572
10784
  };
10573
10785
  }
10786
+ function lspPluginDirsFor(route2, repo, stateDir) {
10787
+ try {
10788
+ const dir = reposOf(route2).find((r) => r.name === repo)?.dir ?? route2.dir;
10789
+ const flavor = lspFlavor(loadRepoConfig(dir)?.hooks.intel);
10790
+ return flavor ? [ensureLspPlugin(stateDir, flavor, dir)] : void 0;
10791
+ } catch {
10792
+ return void 0;
10793
+ }
10794
+ }
10574
10795
  var AgentSpawnError = class extends AgentError {
10575
10796
  };
10576
10797
  var WorkspaceAuthRefused = class extends AgentSpawnError {
@@ -10581,7 +10802,7 @@ var ALLOWANCE_REFUSAL_MARKER = "AI_ALLOWANCE_EXCEEDED";
10581
10802
  function streamShowsAllowanceRefusal(streamPath) {
10582
10803
  let raw;
10583
10804
  try {
10584
- raw = readFileSync22(streamPath, "utf8");
10805
+ raw = readFileSync24(streamPath, "utf8");
10585
10806
  } catch {
10586
10807
  return false;
10587
10808
  }
@@ -10604,26 +10825,49 @@ function streamShowsAllowanceRefusal(streamPath) {
10604
10825
  }
10605
10826
  return false;
10606
10827
  }
10607
- var digestPathFor = (stateDir, routeLabel, role, runId) => join26(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10828
+ var digestPathFor = (stateDir, routeLabel, role, runId) => join28(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
10829
+ function codeBridgeFor(o, harness) {
10830
+ if (o.ship.runner === "sandbox" || !o.ticket) return void 0;
10831
+ const number = /(\d+)$/.exec(o.ticket)?.[1];
10832
+ if (!number) return void 0;
10833
+ let intel;
10834
+ try {
10835
+ const repos = reposOf(o.route);
10836
+ const target = repos.find((r) => r.name === o.repo) ?? (repos.length === 1 ? repos[0] : void 0);
10837
+ intel = target ? loadRepoConfig(target.dir)?.hooks?.intel : void 0;
10838
+ } catch {
10839
+ }
10840
+ if (!wantsCodeBridge(intel)) return void 0;
10841
+ const args = [...process.execArgv, process.argv[1] ?? "crew", "intel", "bridge", o.route.route, number, "--state-dir", o.stateDir];
10842
+ const bridge = { command: process.execPath, args };
10843
+ if (harness === "gemini") {
10844
+ const file = join28(intelDir(o.stateDir, o.route.route, number), "gemini-settings.json");
10845
+ mkdirSync18(dirname13(file), { recursive: true });
10846
+ writeFileSync19(file, `${JSON.stringify({ mcpServers: { [INTEL_SERVER_NAME]: { command: bridge.command, args } } }, null, 2)}
10847
+ `);
10848
+ bridge.settingsFile = file;
10849
+ }
10850
+ return bridge;
10851
+ }
10608
10852
  function planAgentRun(o) {
10609
- const briefPath = join26(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10610
- if (!existsSync24(briefPath)) {
10853
+ const briefPath = join28(o.route.promptsDir, "personas", `lane-${o.role}.md`);
10854
+ if (!existsSync26(briefPath)) {
10611
10855
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
10612
10856
  }
10613
- const commonPath = join26(o.route.promptsDir, "personas", "common.md");
10614
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync24(commonPath);
10857
+ const commonPath = join28(o.route.promptsDir, "personas", "common.md");
10858
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync26(commonPath);
10615
10859
  const now = o.now ?? (() => Date.now());
10616
10860
  const routeLabel = routeSlug(o.route.route);
10617
10861
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
10618
- const digestPath2 = ownDigest && existsSync24(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10862
+ const digestPath2 = ownDigest && existsSync26(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
10619
10863
  let digest;
10620
10864
  let ageSeconds;
10621
- if (existsSync24(digestPath2)) {
10865
+ if (existsSync26(digestPath2)) {
10622
10866
  ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
10623
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync22(digestPath2, "utf8");
10867
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync24(digestPath2, "utf8");
10624
10868
  }
10625
- const common = usesCommon ? readFileSync22(commonPath, "utf8") : "";
10626
- const brief = readFileSync22(briefPath, "utf8");
10869
+ const common = usesCommon ? readFileSync24(commonPath, "utf8") : "";
10870
+ const brief = readFileSync24(briefPath, "utf8");
10627
10871
  const effectiveCommon = o.divergedPrompt ? "" : common;
10628
10872
  const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
10629
10873
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -10646,7 +10890,7 @@ function planAgentRun(o) {
10646
10890
  digest
10647
10891
  });
10648
10892
  const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
10649
- const streamsDir = join26(o.stateDir, "streams");
10893
+ const streamsDir = join28(o.stateDir, "streams");
10650
10894
  const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
10651
10895
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
10652
10896
  const { model } = seat;
@@ -10661,9 +10905,12 @@ function planAgentRun(o) {
10661
10905
  toolchain = { ...toolchain, inference: { baseUrl: inferenceBaseUrl(o.route.baseUrl, o.route.resolved.workspaceId), token: o.apiKey } };
10662
10906
  }
10663
10907
  const adapter = adapterFor(toolchain.harness);
10908
+ const code = codeBridgeFor(o, adapter.harness);
10664
10909
  const invocation = adapter.invocation(toolchain, {
10910
+ code,
10665
10911
  role: o.role,
10666
10912
  model,
10913
+ pluginDirs: adapter.harness === "claude" ? lspPluginDirsFor(o.route, o.repo, o.stateDir) : void 0,
10667
10914
  // Skipped when there's no key to send: a route `crew connect` hasn't
10668
10915
  // resolved yet has nothing to authenticate the MCP connection with either.
10669
10916
  mcp: o.apiKey ? { url: `${o.route.baseUrl}/api/mcp`, keyVar: API_KEY_VAR } : void 0
@@ -10719,7 +10966,7 @@ function planAgentRun(o) {
10719
10966
  unsetEnv: adapter.unsetEnv,
10720
10967
  setEnv: {
10721
10968
  ...o.apiKey ? { [API_KEY_VAR]: o.apiKey } : {},
10722
- ...adapter.runEnv(toolchain),
10969
+ ...adapter.runEnv(toolchain, { code }),
10723
10970
  // The operator's global SessionStart hook (pair-context-hook.sh) injects
10724
10971
  // the interactive Pair persona's prompt for any session it can't tell
10725
10972
  // apart from a person's own terminal. Every headless lane run this
@@ -10736,8 +10983,8 @@ function planAgentRun(o) {
10736
10983
  digestAttached: digest !== void 0,
10737
10984
  digestAgeSeconds: ageSeconds,
10738
10985
  model,
10739
- streamPath: join26(streamsDir, `${base}.jsonl`),
10740
- eventsPath: join26(streamsDir, `${base}.events.jsonl`),
10986
+ streamPath: join28(streamsDir, `${base}.jsonl`),
10987
+ eventsPath: join28(streamsDir, `${base}.events.jsonl`),
10741
10988
  ticket: o.ticket,
10742
10989
  ticketRecordId: o.ticketRecordId ?? null,
10743
10990
  repo: o.repo ?? null,
@@ -10768,7 +11015,7 @@ function describePlan(p) {
10768
11015
  var pidPathFor = (streamPath) => `${streamPath}.pid`;
10769
11016
  function readRunSidecar(streamPath) {
10770
11017
  try {
10771
- const sc = JSON.parse(readFileSync22(pidPathFor(streamPath), "utf8"));
11018
+ const sc = JSON.parse(readFileSync24(pidPathFor(streamPath), "utf8"));
10772
11019
  return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
10773
11020
  } catch {
10774
11021
  return null;
@@ -10815,7 +11062,7 @@ async function killForDeadline(kill, pid, emit, role, ticket, spawnedAt, limitMi
10815
11062
  var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
10816
11063
  function openSink(path) {
10817
11064
  try {
10818
- mkdirSync16(dirname12(path), { recursive: true });
11065
+ mkdirSync18(dirname13(path), { recursive: true });
10819
11066
  return createWriteStream(path, { flags: "a" });
10820
11067
  } catch {
10821
11068
  return void 0;
@@ -11001,7 +11248,7 @@ var RunObserver = class {
11001
11248
  };
11002
11249
  function writeRunMeta(id, startedAt, liveTicket, extra) {
11003
11250
  try {
11004
- writeFileSync17(`${id.streamPath}.meta.json`, JSON.stringify({
11251
+ writeFileSync19(`${id.streamPath}.meta.json`, JSON.stringify({
11005
11252
  route: id.route,
11006
11253
  role: id.role,
11007
11254
  hint: id.ticket,
@@ -11167,7 +11414,7 @@ function spawnAgent(plan, emit, hintMoved) {
11167
11414
  const id = identityOf(plan, adapter.harness);
11168
11415
  let streamFd;
11169
11416
  try {
11170
- mkdirSync16(dirname12(plan.streamPath), { recursive: true });
11417
+ mkdirSync18(dirname13(plan.streamPath), { recursive: true });
11171
11418
  streamFd = openSync4(plan.streamPath, "a");
11172
11419
  } catch (e) {
11173
11420
  void endSink(eventsSink);
@@ -11225,7 +11472,7 @@ function spawnAgent(plan, emit, hintMoved) {
11225
11472
  promptSha: plan.promptSha
11226
11473
  };
11227
11474
  try {
11228
- writeFileSync17(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
11475
+ writeFileSync19(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
11229
11476
  } catch {
11230
11477
  }
11231
11478
  }
@@ -11339,21 +11586,21 @@ var pidAlive = (pid) => {
11339
11586
  };
11340
11587
  function runFinished(streamPath) {
11341
11588
  const path = `${streamPath}.meta.json`;
11342
- if (!existsSync25(path)) return false;
11589
+ if (!existsSync27(path)) return false;
11343
11590
  try {
11344
- return JSON.parse(readFileSync23(path, "utf8")).running !== true;
11591
+ return JSON.parse(readFileSync25(path, "utf8")).running !== true;
11345
11592
  } catch {
11346
11593
  return true;
11347
11594
  }
11348
11595
  }
11349
11596
  function orphanedStreams(o) {
11350
- const dir = join27(o.stateDir, "streams");
11351
- if (!existsSync25(dir)) return [];
11597
+ const dir = join29(o.stateDir, "streams");
11598
+ if (!existsSync27(dir)) return [];
11352
11599
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
11353
11600
  const out = [];
11354
11601
  for (const name of readdirSync9(dir)) {
11355
11602
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
11356
- const streamPath = join27(dir, name);
11603
+ const streamPath = join29(dir, name);
11357
11604
  if (runFinished(streamPath)) continue;
11358
11605
  const sidecar = readRunSidecar(streamPath);
11359
11606
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -11406,15 +11653,15 @@ async function reconcileRuns(o) {
11406
11653
  }
11407
11654
 
11408
11655
  // src/live-runs.ts
11409
- import { existsSync as existsSync26, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
11410
- import { join as join28 } from "path";
11656
+ import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
11657
+ import { join as join30 } from "path";
11411
11658
  function liveRuns(stateDir, isAlive = runIsAlive) {
11412
- const dir = join28(stateDir, "streams");
11413
- if (!existsSync26(dir)) return [];
11659
+ const dir = join30(stateDir, "streams");
11660
+ if (!existsSync28(dir)) return [];
11414
11661
  const out = [];
11415
11662
  for (const name of readdirSync10(dir)) {
11416
11663
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
11417
- const streamPath = join28(dir, name);
11664
+ const streamPath = join30(dir, name);
11418
11665
  if (runFinished(streamPath)) continue;
11419
11666
  const sidecar = readRunSidecar(streamPath);
11420
11667
  if (!sidecar || !isAlive(sidecar)) continue;
@@ -11424,12 +11671,12 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
11424
11671
  }
11425
11672
  function runActivity(stateDir, isAlive = runIsAlive) {
11426
11673
  const activeRuns = liveRuns(stateDir, isAlive).length;
11427
- const dir = join28(stateDir, "streams");
11674
+ const dir = join30(stateDir, "streams");
11428
11675
  let newest = 0;
11429
- if (existsSync26(dir)) {
11676
+ if (existsSync28(dir)) {
11430
11677
  for (const name of readdirSync10(dir)) {
11431
11678
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
11432
- const streamPath = join28(dir, name);
11679
+ const streamPath = join30(dir, name);
11433
11680
  if (!runFinished(streamPath)) continue;
11434
11681
  try {
11435
11682
  newest = Math.max(newest, statSync9(`${streamPath}.meta.json`).mtimeMs);
@@ -11800,7 +12047,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11800
12047
  if (host === "windows") return [];
11801
12048
  const ctx = installContext();
11802
12049
  const systemd = detectSystemd();
11803
- return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync27(p));
12050
+ return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync29(p));
11804
12051
  };
11805
12052
  return {
11806
12053
  liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
@@ -11861,7 +12108,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
11861
12108
  },
11862
12109
  remaining() {
11863
12110
  const left = [];
11864
- if (existsSync27(cfg.configFile)) left.push(`config ${cfg.configFile}`);
12111
+ if (existsSync29(cfg.configFile)) left.push(`config ${cfg.configFile}`);
11865
12112
  for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
11866
12113
  try {
11867
12114
  for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
@@ -12469,19 +12716,19 @@ function planSandbox(repo, decl, warm = {}) {
12469
12716
  const layer = repoLayer(repo, decl, crew, warm);
12470
12717
  return { repo, tag: layer.tag, claude: SANDBOX_CLAUDE_VERSION, layers: [base, crew, layer] };
12471
12718
  }
12472
- var sandboxStatePath = (stateDir) => join29(stateDir, "sandbox-images.json");
12719
+ var sandboxStatePath = (stateDir) => join31(stateDir, "sandbox-images.json");
12473
12720
  function readSandboxState(stateDir) {
12474
12721
  try {
12475
- const v = JSON.parse(readFileSync24(sandboxStatePath(stateDir), "utf8"));
12722
+ const v = JSON.parse(readFileSync26(sandboxStatePath(stateDir), "utf8"));
12476
12723
  return v && typeof v === "object" && !Array.isArray(v) ? v : {};
12477
12724
  } catch {
12478
12725
  return {};
12479
12726
  }
12480
12727
  }
12481
12728
  function writeSandboxState(stateDir, state) {
12482
- mkdirSync17(stateDir, { recursive: true });
12729
+ mkdirSync19(stateDir, { recursive: true });
12483
12730
  const file = sandboxStatePath(stateDir);
12484
- writeFileSync18(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12731
+ writeFileSync20(`${file}.tmp`, `${JSON.stringify(state, null, 2)}
12485
12732
  `);
12486
12733
  renameSync3(`${file}.tmp`, file);
12487
12734
  }
@@ -12501,7 +12748,7 @@ var spawnBuildRunner = (engine = "docker") => (args, logPath) => new Promise((re
12501
12748
  child.stdout.on("data", (d) => chunks.push(d));
12502
12749
  child.stderr.on("data", (d) => chunks.push(d));
12503
12750
  const done = (code, extra = "") => {
12504
- writeFileSync18(logPath, `$ ${engine} ${args.join(" ")}
12751
+ writeFileSync20(logPath, `$ ${engine} ${args.join(" ")}
12505
12752
  ${Buffer.concat(chunks).toString("utf8")}${extra}`, { flag: "a" });
12506
12753
  resolve8(code);
12507
12754
  };
@@ -12523,7 +12770,7 @@ var engineRemoveImage = (engine = "docker", exec = realExec2) => (tag) => {
12523
12770
  } catch {
12524
12771
  }
12525
12772
  };
12526
- var sandboxLogDir = (stateDir) => join29(stateDir, "sandbox-logs");
12773
+ var sandboxLogDir = (stateDir) => join31(stateDir, "sandbox-logs");
12527
12774
  async function buildSandboxImage(plan, deps) {
12528
12775
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
12529
12776
  const state = readSandboxState(deps.stateDir);
@@ -12532,20 +12779,20 @@ async function buildSandboxImage(plan, deps) {
12532
12779
  return { kind: "reused", tag: plan.tag };
12533
12780
  }
12534
12781
  const logDir = sandboxLogDir(deps.stateDir);
12535
- mkdirSync17(logDir, { recursive: true });
12536
- const logPath = join29(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12782
+ mkdirSync19(logDir, { recursive: true });
12783
+ const logPath = join31(logDir, `${slug(plan.repo)}-${now().toISOString().replace(/[:.]/g, "-")}.log`);
12537
12784
  const builtLayers = [];
12538
12785
  for (const layer of plan.layers) {
12539
12786
  if (deps.hasImage(layer.tag)) continue;
12540
- const ctx = join29(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12541
- mkdirSync17(ctx, { recursive: true });
12787
+ const ctx = join31(tmpdir3(), `crew-sandbox-${hash([layer.tag, process.pid, Date.now()])}`);
12788
+ mkdirSync19(ctx, { recursive: true });
12542
12789
  try {
12543
- writeFileSync18(join29(ctx, "Dockerfile"), layer.dockerfile);
12790
+ writeFileSync20(join31(ctx, "Dockerfile"), layer.dockerfile);
12544
12791
  for (const [name, content] of Object.entries(layer.files)) {
12545
- mkdirSync17(dirname13(join29(ctx, name)), { recursive: true });
12546
- writeFileSync18(join29(ctx, name), content);
12792
+ mkdirSync19(dirname14(join31(ctx, name)), { recursive: true });
12793
+ writeFileSync20(join31(ctx, name), content);
12547
12794
  }
12548
- const code = await deps.run(["build", "--tag", layer.tag, "--file", join29(ctx, "Dockerfile"), ctx], logPath);
12795
+ const code = await deps.run(["build", "--tag", layer.tag, "--file", join31(ctx, "Dockerfile"), ctx], logPath);
12549
12796
  if (code !== 0) {
12550
12797
  state[plan.repo] = { ...entry, failure: { tag: plan.tag, at: now().toISOString(), logPath } };
12551
12798
  writeSandboxState(deps.stateDir, state);
@@ -12553,7 +12800,7 @@ async function buildSandboxImage(plan, deps) {
12553
12800
  }
12554
12801
  builtLayers.push(layer.name);
12555
12802
  } finally {
12556
- rmSync14(ctx, { recursive: true, force: true });
12803
+ rmSync15(ctx, { recursive: true, force: true });
12557
12804
  }
12558
12805
  }
12559
12806
  const tags = [{ tag: plan.tag, builtAt: now().toISOString(), claude: plan.claude }, ...entry.tags.filter((t) => t.tag !== plan.tag)];
@@ -12734,9 +12981,9 @@ function managedRuntimeWarnings(kind, ship) {
12734
12981
 
12735
12982
  // src/selftest.ts
12736
12983
  import { spawn as spawn6 } from "child_process";
12737
- import { existsSync as existsSync28, mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync2, readFileSync as readFileSync25, rmSync as rmSync15, writeFileSync as writeFileSync19 } from "fs";
12984
+ import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync27, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
12738
12985
  import { tmpdir as tmpdir4 } from "os";
12739
- import { join as join30 } from "path";
12986
+ import { join as join32 } from "path";
12740
12987
  var SELFTEST_TIMEOUT_MS = 12e4;
12741
12988
  var KEY_VAR = "CREW_API_KEY";
12742
12989
  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: ";
@@ -12897,7 +13144,7 @@ async function runSelftest(i, deps = realDeps) {
12897
13144
  ...adapter.runEnv(spec),
12898
13145
  [KEY_VAR]: i.apiKey
12899
13146
  };
12900
- const cwd = mkdtempSync2(join30(tmpdir4(), "crew-selftest-"));
13147
+ const cwd = mkdtempSync2(join32(tmpdir4(), "crew-selftest-"));
12901
13148
  try {
12902
13149
  const run3 = await deps.run({
12903
13150
  bin: invocation.bin,
@@ -12919,7 +13166,7 @@ async function runSelftest(i, deps = realDeps) {
12919
13166
  return finish({ ok: false, failure, detail, ...usage2 });
12920
13167
  } finally {
12921
13168
  try {
12922
- rmSync15(cwd, { recursive: true, force: true });
13169
+ rmSync16(cwd, { recursive: true, force: true });
12923
13170
  } catch {
12924
13171
  }
12925
13172
  }
@@ -12930,11 +13177,11 @@ function formatResult(r) {
12930
13177
  const secs = `${(r.ms / 1e3).toFixed(1)}s`;
12931
13178
  return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
12932
13179
  }
12933
- var fileFor2 = (stateDir) => join30(stateDir, "selftest.json");
13180
+ var fileFor2 = (stateDir) => join32(stateDir, "selftest.json");
12934
13181
  function readSelftest(stateDir) {
12935
13182
  try {
12936
13183
  const p = fileFor2(stateDir);
12937
- return existsSync28(p) ? JSON.parse(readFileSync25(p, "utf8")) : {};
13184
+ return existsSync30(p) ? JSON.parse(readFileSync27(p, "utf8")) : {};
12938
13185
  } catch {
12939
13186
  return {};
12940
13187
  }
@@ -12942,8 +13189,8 @@ function readSelftest(stateDir) {
12942
13189
  function recordSelftest(stateDir, results) {
12943
13190
  const all = readSelftest(stateDir);
12944
13191
  for (const r of results) all[r.toolchain] = r;
12945
- mkdirSync18(stateDir, { recursive: true });
12946
- writeFileSync19(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
13192
+ mkdirSync20(stateDir, { recursive: true });
13193
+ writeFileSync21(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
12947
13194
  `);
12948
13195
  }
12949
13196
  function doctorSelftestLines(stored, toolchains) {
@@ -12955,7 +13202,7 @@ function doctorSelftestLines(stored, toolchains) {
12955
13202
  }
12956
13203
 
12957
13204
  // src/cli.ts
12958
- import { resolve as resolve7, dirname as dirname32, join as join52, basename as basename16 } from "path";
13205
+ import { resolve as resolve7, dirname as dirname33, join as join54, basename as basename16 } from "path";
12959
13206
  import { fileURLToPath } from "url";
12960
13207
  import { hostname as hostname4, homedir as homedir11 } from "os";
12961
13208
  import { spawnSync as spawnSync11 } from "child_process";
@@ -12963,6 +13210,7 @@ import { parse as parseYaml2 } from "yaml";
12963
13210
 
12964
13211
  // src/argv.ts
12965
13212
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
13213
+ "state-dir",
12966
13214
  "agent-bin",
12967
13215
  "area",
12968
13216
  "base-url",
@@ -13152,14 +13400,14 @@ function rosterMarkdown(roster, meId) {
13152
13400
  }
13153
13401
 
13154
13402
  // src/connect-resume.ts
13155
- import { mkdirSync as mkdirSync19, readFileSync as readFileSync26, rmSync as rmSync16, writeFileSync as writeFileSync20 } from "fs";
13156
- import { dirname as dirname14, join as join31 } from "path";
13403
+ import { mkdirSync as mkdirSync21, readFileSync as readFileSync28, rmSync as rmSync17, writeFileSync as writeFileSync22 } from "fs";
13404
+ import { dirname as dirname15, join as join33 } from "path";
13157
13405
  function lastSessionFile(stateDir) {
13158
- return join31(stateDir, "connect-session.json");
13406
+ return join33(stateDir, "connect-session.json");
13159
13407
  }
13160
13408
  function readPointers(stateDir) {
13161
13409
  try {
13162
- const parsed = JSON.parse(readFileSync26(lastSessionFile(stateDir), "utf8"));
13410
+ const parsed = JSON.parse(readFileSync28(lastSessionFile(stateDir), "utf8"));
13163
13411
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
13164
13412
  const out = {};
13165
13413
  for (const [host, v] of Object.entries(parsed)) {
@@ -13173,8 +13421,8 @@ function readPointers(stateDir) {
13173
13421
  }
13174
13422
  function writePointers(stateDir, pointers) {
13175
13423
  const file = lastSessionFile(stateDir);
13176
- mkdirSync19(dirname14(file), { recursive: true });
13177
- writeFileSync20(file, `${JSON.stringify(pointers, null, 2)}
13424
+ mkdirSync21(dirname15(file), { recursive: true });
13425
+ writeFileSync22(file, `${JSON.stringify(pointers, null, 2)}
13178
13426
  `, { mode: 384 });
13179
13427
  }
13180
13428
  function rememberSession(stateDir, host, workspaceSlug) {
@@ -13191,7 +13439,7 @@ function forgetWorkspace(stateDir, host, workspaceSlug) {
13191
13439
  const { [host]: _old, ...rest } = all;
13192
13440
  const next = left.length ? { ...rest, [host]: left } : rest;
13193
13441
  if (Object.keys(next).length) writePointers(stateDir, next);
13194
- else rmSync16(lastSessionFile(stateDir), { force: true });
13442
+ else rmSync17(lastSessionFile(stateDir), { force: true });
13195
13443
  } catch {
13196
13444
  }
13197
13445
  }
@@ -13231,7 +13479,7 @@ function forgetSession(stateDir, host) {
13231
13479
  try {
13232
13480
  const { [host]: _gone, ...rest } = readPointers(stateDir);
13233
13481
  if (Object.keys(rest).length) writePointers(stateDir, rest);
13234
- else rmSync16(lastSessionFile(stateDir), { force: true });
13482
+ else rmSync17(lastSessionFile(stateDir), { force: true });
13235
13483
  } catch {
13236
13484
  }
13237
13485
  }
@@ -13259,15 +13507,15 @@ async function signOutSessions(o) {
13259
13507
  }
13260
13508
 
13261
13509
  // src/reset.ts
13262
- import { existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync27, readdirSync as readdirSync12, rmSync as rmSync17, statSync as statSync10, writeFileSync as writeFileSync21 } from "fs";
13263
- import { dirname as dirname15, join as join32 } from "path";
13510
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync29, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
13511
+ import { dirname as dirname16, join as join34 } from "path";
13264
13512
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
13265
13513
  function clonedCheckoutsFile(stateDir) {
13266
- return join32(stateDir, "cloned-checkouts.json");
13514
+ return join34(stateDir, "cloned-checkouts.json");
13267
13515
  }
13268
13516
  function readClonedCheckouts(stateDir) {
13269
13517
  try {
13270
- const parsed = JSON.parse(readFileSync27(clonedCheckoutsFile(stateDir), "utf8"));
13518
+ const parsed = JSON.parse(readFileSync29(clonedCheckoutsFile(stateDir), "utf8"));
13271
13519
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
13272
13520
  } catch {
13273
13521
  return [];
@@ -13277,8 +13525,8 @@ function recordClonedCheckout(stateDir, dir) {
13277
13525
  const known = readClonedCheckouts(stateDir);
13278
13526
  if (known.includes(dir)) return;
13279
13527
  const file = clonedCheckoutsFile(stateDir);
13280
- mkdirSync20(dirname15(file), { recursive: true });
13281
- writeFileSync21(file, `${JSON.stringify([...known, dir], null, 2)}
13528
+ mkdirSync22(dirname16(file), { recursive: true });
13529
+ writeFileSync23(file, `${JSON.stringify([...known, dir], null, 2)}
13282
13530
  `);
13283
13531
  }
13284
13532
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -13332,8 +13580,8 @@ async function runReset(o, fx) {
13332
13580
  }
13333
13581
  if (o.beforeState) steps.push(...await o.beforeState());
13334
13582
  steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
13335
- if (existsSync29(o.configFile)) {
13336
- if (!o.dryRun) rmSync17(o.configFile, { force: true });
13583
+ if (existsSync31(o.configFile)) {
13584
+ if (!o.dryRun) rmSync18(o.configFile, { force: true });
13337
13585
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
13338
13586
  } else {
13339
13587
  steps.push({ step: "config", target: o.configFile, action: "absent" });
@@ -13341,7 +13589,7 @@ async function runReset(o, fx) {
13341
13589
  return steps;
13342
13590
  }
13343
13591
  function removeCheckout(dir, dryRun, fx) {
13344
- if (!existsSync29(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
13592
+ if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
13345
13593
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
13346
13594
  if (dirty.length) {
13347
13595
  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" : ""})` };
@@ -13349,21 +13597,21 @@ function removeCheckout(dir, dryRun, fx) {
13349
13597
  if (linkedWorktrees.length) {
13350
13598
  return { step: "checkout", target: dir, action: "kept", detail: `has linked worktrees (${linkedWorktrees.join(", ")}); remove them first` };
13351
13599
  }
13352
- if (!dryRun) rmSync17(dir, { recursive: true, force: true });
13600
+ if (!dryRun) rmSync18(dir, { recursive: true, force: true });
13353
13601
  return { step: "checkout", target: dir, action: mark(dryRun) };
13354
13602
  }
13355
13603
  function removeState(stateDir, dryRun) {
13356
13604
  const out = [];
13357
- const targets = [join32(stateDir, "resolved"), join32(stateDir, "keys"), clonedCheckoutsFile(stateDir), join32(stateDir, "connect-session.json"), join32(stateDir, "reload-requested"), join32(stateDir, "daemon.started")];
13605
+ const targets = [join34(stateDir, "resolved"), join34(stateDir, "keys"), clonedCheckoutsFile(stateDir), join34(stateDir, "connect-session.json"), join34(stateDir, "reload-requested"), join34(stateDir, "daemon.started")];
13358
13606
  try {
13359
13607
  for (const name of readdirSync12(stateDir)) {
13360
- if (name.startsWith(".") && statSync10(join32(stateDir, name)).isFile()) targets.push(join32(stateDir, name));
13608
+ if (name.startsWith(".") && statSync10(join34(stateDir, name)).isFile()) targets.push(join34(stateDir, name));
13361
13609
  }
13362
13610
  } catch {
13363
13611
  }
13364
13612
  for (const t of targets) {
13365
- if (!existsSync29(t)) continue;
13366
- if (!dryRun) rmSync17(t, { recursive: true, force: true });
13613
+ if (!existsSync31(t)) continue;
13614
+ if (!dryRun) rmSync18(t, { recursive: true, force: true });
13367
13615
  out.push({ step: "state", target: t, action: mark(dryRun) });
13368
13616
  }
13369
13617
  if (!out.length) out.push({ step: "state", target: stateDir, action: "absent", detail: "nothing to remove" });
@@ -13377,7 +13625,7 @@ function wipeStateDir(stateDir, dryRun) {
13377
13625
  return [{ step: "state", target: stateDir, action: "absent", detail: "no state directory" }];
13378
13626
  }
13379
13627
  if (!names.length) return [{ step: "state", target: stateDir, action: "absent", detail: "already empty" }];
13380
- if (!dryRun) for (const n of names) rmSync17(join32(stateDir, n), { recursive: true, force: true });
13628
+ if (!dryRun) for (const n of names) rmSync18(join34(stateDir, n), { recursive: true, force: true });
13381
13629
  return [{ step: "state", target: stateDir, action: mark(dryRun), detail: `${names.length} entr${names.length === 1 ? "y" : "ies"}` }];
13382
13630
  }
13383
13631
  function describeResetStep(s) {
@@ -13458,9 +13706,9 @@ async function runDeprovision(o, fx, deps) {
13458
13706
  }
13459
13707
 
13460
13708
  // src/pool-member.ts
13461
- import { mkdirSync as mkdirSync21, readFileSync as readFileSync28, renameSync as renameSync4, writeFileSync as writeFileSync22 } from "fs";
13709
+ import { mkdirSync as mkdirSync23, readFileSync as readFileSync30, renameSync as renameSync4, writeFileSync as writeFileSync24 } from "fs";
13462
13710
  import { homedir as homedir8 } from "os";
13463
- import { dirname as dirname16, join as join33 } from "path";
13711
+ import { dirname as dirname17, join as join35 } from "path";
13464
13712
  var POOL_EXIT = { ok: 0, failed: 1, credential: 2 };
13465
13713
  function backoffMs(attempt) {
13466
13714
  return Math.min(6e4, 5e3 * 2 ** Math.max(0, attempt));
@@ -13691,22 +13939,22 @@ async function runPoolMember(deps) {
13691
13939
  }
13692
13940
  }
13693
13941
  function defaultMemberDir(env = process.env) {
13694
- return env.CREW_POOL_DIR ?? join33(env.XDG_STATE_HOME ?? join33(homedir8(), ".local", "state"), "crew-pool-member");
13942
+ return env.CREW_POOL_DIR ?? join35(env.XDG_STATE_HOME ?? join35(homedir8(), ".local", "state"), "crew-pool-member");
13695
13943
  }
13696
13944
  function fileMemberStore(dir) {
13697
- const file = join33(dir, "member.json");
13945
+ const file = join35(dir, "member.json");
13698
13946
  return {
13699
13947
  read() {
13700
13948
  try {
13701
- return JSON.parse(readFileSync28(file, "utf8"));
13949
+ return JSON.parse(readFileSync30(file, "utf8"));
13702
13950
  } catch {
13703
13951
  return {};
13704
13952
  }
13705
13953
  },
13706
13954
  write(f) {
13707
- mkdirSync21(dirname16(file), { recursive: true, mode: 448 });
13955
+ mkdirSync23(dirname17(file), { recursive: true, mode: 448 });
13708
13956
  const tmp = `${file}.tmp`;
13709
- writeFileSync22(tmp, `${JSON.stringify(f)}
13957
+ writeFileSync24(tmp, `${JSON.stringify(f)}
13710
13958
  `, { mode: 384 });
13711
13959
  renameSync4(tmp, file);
13712
13960
  }
@@ -13758,16 +14006,16 @@ function hostArchitecture(arch = process.arch) {
13758
14006
 
13759
14007
  // src/pool-member-host.ts
13760
14008
  import "child_process";
13761
- import { existsSync as existsSync33 } from "fs";
14009
+ import { existsSync as existsSync35 } from "fs";
13762
14010
  import { hostname as hostname3 } from "os";
13763
14011
 
13764
14012
  // src/machine-id.ts
13765
14013
  import { execFileSync as execFileSync10 } from "child_process";
13766
14014
  import { createHash as createHash7 } from "crypto";
13767
- import { existsSync as existsSync30, mkdirSync as mkdirSync22, readFileSync as readFileSync29, writeFileSync as writeFileSync23 } from "fs";
13768
- import { dirname as dirname17, join as join34 } from "path";
14015
+ import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync31, writeFileSync as writeFileSync25 } from "fs";
14016
+ import { dirname as dirname18, join as join36 } from "path";
13769
14017
  function machineIdPathFor(stateDir) {
13770
- return join34(stateDir, "machine-id");
14018
+ return join36(stateDir, "machine-id");
13771
14019
  }
13772
14020
  var sha256 = (s) => createHash7("sha256").update(s).digest("hex");
13773
14021
  function readRawMachineId(platform2 = process.platform) {
@@ -13780,8 +14028,8 @@ function readRawMachineId(platform2 = process.platform) {
13780
14028
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
13781
14029
  }
13782
14030
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
13783
- if (existsSync30(p)) {
13784
- const id = readFileSync29(p, "utf8").trim();
14031
+ if (existsSync32(p)) {
14032
+ const id = readFileSync31(p, "utf8").trim();
13785
14033
  if (id) return id;
13786
14034
  }
13787
14035
  }
@@ -13792,15 +14040,15 @@ function readRawMachineId(platform2 = process.platform) {
13792
14040
  function ensureMachineId(stateDir, opts = {}) {
13793
14041
  if (opts.override?.trim()) return sha256(opts.override.trim());
13794
14042
  const path = machineIdPathFor(stateDir);
13795
- if (existsSync30(path)) {
13796
- const cached = readFileSync29(path, "utf8").trim();
14043
+ if (existsSync32(path)) {
14044
+ const cached = readFileSync31(path, "utf8").trim();
13797
14045
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
13798
14046
  }
13799
14047
  const raw = (opts.read ?? readRawMachineId)();
13800
14048
  if (!raw) return void 0;
13801
14049
  const hashed = sha256(raw);
13802
- mkdirSync22(dirname17(path), { recursive: true });
13803
- writeFileSync23(path, `${hashed}
14050
+ mkdirSync24(dirname18(path), { recursive: true });
14051
+ writeFileSync25(path, `${hashed}
13804
14052
  `, { mode: 384 });
13805
14053
  return hashed;
13806
14054
  }
@@ -13813,7 +14061,7 @@ function matchShipRows(ships, shipName2, machineId) {
13813
14061
 
13814
14062
  // src/provision.ts
13815
14063
  import { spawnSync as spawnSync8 } from "child_process";
13816
- import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync24 } from "fs";
14064
+ import { existsSync as existsSync33, readFileSync as readFileSync32, writeFileSync as writeFileSync26 } from "fs";
13817
14065
  import { hostname as hostname2 } from "os";
13818
14066
  import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
13819
14067
 
@@ -13822,7 +14070,7 @@ import { parseDocument as parseDocument2 } from "yaml";
13822
14070
 
13823
14071
  // src/repos-cmd.ts
13824
14072
  import { homedir as homedir9 } from "os";
13825
- import { basename as basename10, join as join35, resolve as resolve4 } from "path";
14073
+ import { basename as basename10, join as join37, resolve as resolve4 } from "path";
13826
14074
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
13827
14075
  var ReposError = class extends Error {
13828
14076
  };
@@ -14100,7 +14348,7 @@ function planRepoClone(i) {
14100
14348
  }
14101
14349
  const ship = doc.get("ship", true);
14102
14350
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
14103
- const target = i.dir ? expandDir(i.dir, i.base) : join35(expandDir(basePath, i.base), i.route.split("/")[0], name);
14351
+ const target = i.dir ? expandDir(i.dir, i.base) : join37(expandDir(basePath, i.base), i.route.split("/")[0], name);
14104
14352
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
14105
14353
  const forgeHost = i.resolved?.repoForgeHosts?.[id];
14106
14354
  return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
@@ -14407,7 +14655,7 @@ function realProvisionDeps(crewHome) {
14407
14655
  };
14408
14656
  const configFile = () => {
14409
14657
  const f = findConfigFile(crewHome);
14410
- return existsSync31(f) ? f : null;
14658
+ return existsSync33(f) ? f : null;
14411
14659
  };
14412
14660
  return {
14413
14661
  crew: spawnCrew,
@@ -14415,13 +14663,13 @@ function realProvisionDeps(crewHome) {
14415
14663
  enableRoute(route2) {
14416
14664
  const file = configFile();
14417
14665
  if (!file) return false;
14418
- const text2 = readFileSync30(file, "utf8");
14666
+ const text2 = readFileSync32(file, "utf8");
14419
14667
  const doc = parseDocument3(text2);
14420
14668
  const routes = doc.get("routes");
14421
14669
  const entry = routes?.items?.find((r) => r.get("route") === route2);
14422
14670
  if (!entry) throw new Error(`route ${route2} is not in ${file}`);
14423
14671
  if (entry.get("enabled") === true) return false;
14424
- writeFileSync24(file, planEnable(text2, route2));
14672
+ writeFileSync26(file, planEnable(text2, route2));
14425
14673
  return true;
14426
14674
  },
14427
14675
  installState() {
@@ -14441,14 +14689,14 @@ function realProvisionDeps(crewHome) {
14441
14689
  continue;
14442
14690
  }
14443
14691
  for (const p of plan.unitPaths) {
14444
- if (!existsSync31(p) || readFileSync30(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
14692
+ if (!existsSync33(p) || readFileSync32(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
14445
14693
  }
14446
14694
  }
14447
14695
  const label = labelFor(crewHome, "run");
14448
14696
  let running = false;
14449
14697
  let lingering = true;
14450
14698
  if (mechanism === "systemd") {
14451
- const system = ctx.root || existsSync31(systemUnitPathFor(label));
14699
+ const system = ctx.root || existsSync33(systemUnitPathFor(label));
14452
14700
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
14453
14701
  running = spawnSync8(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
14454
14702
  if (!system) {
@@ -14465,18 +14713,18 @@ function realProvisionDeps(crewHome) {
14465
14713
  setShipSetting(key, value2) {
14466
14714
  const file = configFile();
14467
14715
  if (!file) return false;
14468
- const doc = parseDocument3(readFileSync30(file, "utf8"));
14716
+ const doc = parseDocument3(readFileSync32(file, "utf8"));
14469
14717
  if (doc.getIn(["ship", key]) === value2) return false;
14470
14718
  doc.setIn(["ship", key], value2);
14471
- writeFileSync24(file, doc.toString());
14719
+ writeFileSync26(file, doc.toString());
14472
14720
  return true;
14473
14721
  },
14474
14722
  applyManagedDefaults() {
14475
14723
  const file = configFile();
14476
14724
  if (!file) return [];
14477
- const doc = parseDocument3(readFileSync30(file, "utf8"));
14725
+ const doc = parseDocument3(readFileSync32(file, "utf8"));
14478
14726
  const wrote = applyManagedDefaultsToDoc(doc);
14479
- if (wrote.length) writeFileSync24(file, doc.toString());
14727
+ if (wrote.length) writeFileSync26(file, doc.toString());
14480
14728
  return wrote;
14481
14729
  },
14482
14730
  storedToken(host) {
@@ -14493,9 +14741,9 @@ host=${host}
14493
14741
 
14494
14742
  // src/tunnel.ts
14495
14743
  import { spawn as spawn7 } from "child_process";
14496
- import { existsSync as existsSync32, mkdirSync as mkdirSync23, readFileSync as readFileSync31, writeFileSync as writeFileSync25, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14744
+ import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync33, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
14497
14745
  import { randomInt } from "crypto";
14498
- import { join as join36 } from "path";
14746
+ import { join as join38 } from "path";
14499
14747
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
14500
14748
  var defaultSpawn = (cmd, args) => spawn7(cmd, args, DEFAULT_SPAWN_OPTIONS);
14501
14749
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -14567,21 +14815,21 @@ function startTunnel(opts) {
14567
14815
  };
14568
14816
  }
14569
14817
  function tunnelStatePath(stateDir, workspaceId) {
14570
- return join36(stateDir, "passengers", `${workspaceId}.json`);
14818
+ return join38(stateDir, "passengers", `${workspaceId}.json`);
14571
14819
  }
14572
14820
  function readPersistedTunnel(stateDir, workspaceId) {
14573
14821
  const path = tunnelStatePath(stateDir, workspaceId);
14574
- if (!existsSync32(path)) return void 0;
14822
+ if (!existsSync34(path)) return void 0;
14575
14823
  try {
14576
- return JSON.parse(readFileSync31(path, "utf8"));
14824
+ return JSON.parse(readFileSync33(path, "utf8"));
14577
14825
  } catch {
14578
14826
  return void 0;
14579
14827
  }
14580
14828
  }
14581
14829
  function writePersistedTunnel(stateDir, state) {
14582
14830
  const path = tunnelStatePath(stateDir, state.workspaceId);
14583
- mkdirSync23(join36(stateDir, "passengers"), { recursive: true });
14584
- writeFileSync25(path, `${JSON.stringify(state, null, 2)}
14831
+ mkdirSync25(join38(stateDir, "passengers"), { recursive: true });
14832
+ writeFileSync27(path, `${JSON.stringify(state, null, 2)}
14585
14833
  `);
14586
14834
  }
14587
14835
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -14683,8 +14931,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
14683
14931
  }
14684
14932
  }
14685
14933
  function readAllPersistedWorkspaceIds(stateDir) {
14686
- const dir = join36(stateDir, "passengers");
14687
- if (!existsSync32(dir)) return [];
14934
+ const dir = join38(stateDir, "passengers");
14935
+ if (!existsSync34(dir)) return [];
14688
14936
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
14689
14937
  }
14690
14938
 
@@ -14709,7 +14957,7 @@ function realPoolMemberDeps(o) {
14709
14957
  crewVersion: crewVersion(o.crewHome),
14710
14958
  hostname: hostname3()
14711
14959
  },
14712
- hasShipConfig: () => existsSync33(findConfigFile(o.crewHome)),
14960
+ hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
14713
14961
  async provision(lease, instanceClass, shipKind) {
14714
14962
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
14715
14963
  const lines2 = [];
@@ -15585,8 +15833,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
15585
15833
  }
15586
15834
 
15587
15835
  // src/poll.ts
15588
- import { writeFileSync as writeFileSync26 } from "fs";
15589
- import { join as join37 } from "path";
15836
+ import { writeFileSync as writeFileSync28 } from "fs";
15837
+ import { join as join39 } from "path";
15590
15838
 
15591
15839
  // src/stalled.ts
15592
15840
  var STALLED_AFTER_CYCLES = 30;
@@ -15893,7 +16141,7 @@ async function decideCycle(o) {
15893
16141
  }
15894
16142
  return decision;
15895
16143
  }
15896
- var digestPath = (stateDir, route2, role, runId) => join37(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
16144
+ var digestPath = (stateDir, route2, role, runId) => join39(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
15897
16145
  function otherRunsRemoved(tickets, own) {
15898
16146
  if (!own) return tickets;
15899
16147
  return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
@@ -15971,7 +16219,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
15971
16219
 
15972
16220
  ${body}` : body;
15973
16221
  const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
15974
- writeFileSync26(path, text2);
16222
+ writeFileSync28(path, text2);
15975
16223
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text2)} bytes)`, {
15976
16224
  data: { role, bytes: Buffer.byteLength(text2) }
15977
16225
  });
@@ -16079,8 +16327,8 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
16079
16327
  }
16080
16328
 
16081
16329
  // src/repo-refresh.ts
16082
- import { existsSync as existsSync34, mkdirSync as mkdirSync24, readFileSync as readFileSync32, writeFileSync as writeFileSync27 } from "fs";
16083
- import { dirname as dirname19 } from "path";
16330
+ import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync34, writeFileSync as writeFileSync29 } from "fs";
16331
+ import { dirname as dirname20 } from "path";
16084
16332
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
16085
16333
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
16086
16334
  var lastAsked = /* @__PURE__ */ new Map();
@@ -16109,11 +16357,11 @@ function applyRepoRows(resolved, rows) {
16109
16357
  }
16110
16358
  function persistRepoRows(stateDir, routeName, rows) {
16111
16359
  const path = resolvedPathFor(stateDir, routeName);
16112
- if (!existsSync34(path)) return;
16113
- const raw = JSON.parse(readFileSync32(path, "utf8"));
16360
+ if (!existsSync36(path)) return;
16361
+ const raw = JSON.parse(readFileSync34(path, "utf8"));
16114
16362
  applyRepoRows(raw, rows);
16115
- mkdirSync24(dirname19(path), { recursive: true });
16116
- writeFileSync27(path, `${JSON.stringify(raw, null, 2)}
16363
+ mkdirSync26(dirname20(path), { recursive: true });
16364
+ writeFileSync29(path, `${JSON.stringify(raw, null, 2)}
16117
16365
  `);
16118
16366
  }
16119
16367
  async function refreshUnknownRepos(o) {
@@ -16563,13 +16811,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
16563
16811
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
16564
16812
 
16565
16813
  // src/connect-wizard.ts
16566
- import { delimiter, dirname as dirname20, join as join38 } from "path";
16567
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync35, mkdirSync as mkdirSync25, writeFileSync as writeFileSync28 } from "fs";
16814
+ import { delimiter, dirname as dirname21, join as join40 } from "path";
16815
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
16568
16816
  import { stringify as stringify3 } from "yaml";
16569
16817
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
16570
16818
  function shouldRunWizard(opts) {
16571
16819
  if (!opts.isTTY) return false;
16572
- const exists = opts.configExists ?? existsSync35;
16820
+ const exists = opts.configExists ?? existsSync37;
16573
16821
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
16574
16822
  }
16575
16823
  function firstRunConfigPath() {
@@ -16586,7 +16834,7 @@ function defaultIsExecutable(p) {
16586
16834
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
16587
16835
  for (const dir of pathEnv.split(delimiter)) {
16588
16836
  if (!dir) continue;
16589
- const candidate = join38(dir, name);
16837
+ const candidate = join40(dir, name);
16590
16838
  if (isExecutable(candidate)) return candidate;
16591
16839
  }
16592
16840
  return void 0;
@@ -16612,9 +16860,9 @@ async function runWizard(p, env) {
16612
16860
  if (entered && isExec(entered)) agentBin = entered;
16613
16861
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
16614
16862
  }
16615
- const nodeDir = dirname20(env.nodePath);
16863
+ const nodeDir = dirname21(env.nodePath);
16616
16864
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16617
- const baseDirs = [nodeDir, pnpmPath ? dirname20(pnpmPath) : void 0];
16865
+ const baseDirs = [nodeDir, pnpmPath ? dirname21(pnpmPath) : void 0];
16618
16866
  p.say(
16619
16867
  `
16620
16868
  Host Passengers lets this machine run the agent sessions that other people's
@@ -16625,7 +16873,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
16625
16873
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
16626
16874
  }
16627
16875
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16628
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname20(dockerPath) : void 0]);
16876
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname21(dockerPath) : void 0]);
16629
16877
  if (extraPath) {
16630
16878
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
16631
16879
  }
@@ -16647,9 +16895,9 @@ routes:
16647
16895
  ${routeBlock}`;
16648
16896
  }
16649
16897
  function writeNewConfig(path, text2) {
16650
- mkdirSync25(dirname20(path), { recursive: true });
16898
+ mkdirSync27(dirname21(path), { recursive: true });
16651
16899
  try {
16652
- writeFileSync28(path, text2, { mode: 384, flag: "wx" });
16900
+ writeFileSync30(path, text2, { mode: 384, flag: "wx" });
16653
16901
  return true;
16654
16902
  } catch (e) {
16655
16903
  if (e.code === "EEXIST") return false;
@@ -16683,7 +16931,7 @@ Wrote ${o.configPath}.
16683
16931
  // src/connect-json.ts
16684
16932
  import { accessSync as accessSync4, constants as constants4 } from "fs";
16685
16933
  import { homedir as homedir10 } from "os";
16686
- import { dirname as dirname21, join as join39 } from "path";
16934
+ import { dirname as dirname22, join as join41 } from "path";
16687
16935
  import { delimiter as delimiter2 } from "path";
16688
16936
  var CHOICE_EXIT_CODE = 3;
16689
16937
  function createEmitter(json2, write) {
@@ -16722,12 +16970,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
16722
16970
  const dirs = [
16723
16971
  ...pathEnv.split(delimiter2),
16724
16972
  ...(extraPath ?? "").split(delimiter2),
16725
- join39(home, ".local/bin"),
16726
- join39(home, ".claude/local"),
16973
+ join41(home, ".local/bin"),
16974
+ join41(home, ".claude/local"),
16727
16975
  "/opt/homebrew/bin",
16728
16976
  "/usr/local/bin",
16729
- join39(home, ".npm-global/bin"),
16730
- join39(home, ".volta/bin")
16977
+ join41(home, ".npm-global/bin"),
16978
+ join41(home, ".volta/bin")
16731
16979
  ];
16732
16980
  return [...new Set(dirs.filter(Boolean))];
16733
16981
  }
@@ -16749,7 +16997,7 @@ function flagWizardAnswers(f, env) {
16749
16997
  const agentBin = f.agentBin ?? found?.bin;
16750
16998
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
16751
16999
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
16752
- const extraPath = f.extraPath ?? deriveExtraPath([dirname21(env.nodePath), pnpmPath ? dirname21(pnpmPath) : void 0, dockerPath ? dirname21(dockerPath) : void 0]);
17000
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname22(env.nodePath), pnpmPath ? dirname22(pnpmPath) : void 0, dockerPath ? dirname22(dockerPath) : void 0]);
16753
17001
  return {
16754
17002
  answers: {
16755
17003
  name: f.shipName?.trim() || env.hostname,
@@ -16763,8 +17011,8 @@ function flagWizardAnswers(f, env) {
16763
17011
  }
16764
17012
 
16765
17013
  // src/release-run.ts
16766
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync29, existsSync as existsSync36, copyFileSync, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, rmSync as rmSync18 } from "fs";
16767
- import { dirname as dirname22, join as join40 } from "path";
17014
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
17015
+ import { dirname as dirname23, join as join42 } from "path";
16768
17016
  import { tmpdir as tmpdir5 } from "os";
16769
17017
 
16770
17018
  // src/release.ts
@@ -17505,13 +17753,13 @@ async function bump(o, size) {
17505
17753
  }
17506
17754
  return printed;
17507
17755
  }
17508
- const files = o.repo.release.versionFiles.map((f) => join40(o.cwd, f));
17756
+ const files = o.repo.release.versionFiles.map((f) => join42(o.cwd, f));
17509
17757
  const first = files[0];
17510
- if (!existsSync36(first)) {
17758
+ if (!existsSync38(first)) {
17511
17759
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
17512
17760
  return null;
17513
17761
  }
17514
- const current = JSON.parse(readFileSync33(first, "utf8")).version;
17762
+ const current = JSON.parse(readFileSync35(first, "utf8")).version;
17515
17763
  if (!current) {
17516
17764
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
17517
17765
  return null;
@@ -17524,27 +17772,27 @@ async function bump(o, size) {
17524
17772
  return version;
17525
17773
  }
17526
17774
  for (const f of files) {
17527
- const src = readFileSync33(f, "utf8");
17775
+ const src = readFileSync35(f, "utf8");
17528
17776
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
17529
17777
  if (out === src) {
17530
17778
  o.emit.error(`no version field in ${f}`);
17531
17779
  return null;
17532
17780
  }
17533
- writeFileSync29(f, out);
17781
+ writeFileSync31(f, out);
17534
17782
  }
17535
17783
  return version;
17536
17784
  }
17537
17785
  function writeChangelog(o, version, entries) {
17538
17786
  const file = o.repo.release.changelog;
17539
17787
  if (!file || entries.length === 0) return;
17540
- const path = join40(o.cwd, file);
17788
+ const path = join42(o.cwd, file);
17541
17789
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
17542
17790
  if (o.dryRun) {
17543
17791
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
17544
17792
  return;
17545
17793
  }
17546
- const existing = existsSync36(path) ? readFileSync33(path, "utf8") : "# Changelog\n";
17547
- writeFileSync29(path, insertChangelogSection(existing, section));
17794
+ const existing = existsSync38(path) ? readFileSync35(path, "utf8") : "# Changelog\n";
17795
+ writeFileSync31(path, insertChangelogSection(existing, section));
17548
17796
  }
17549
17797
  async function confirm(o, expected) {
17550
17798
  if (!o.repo.hooks.released) return void 0;
@@ -17627,7 +17875,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
17627
17875
  run3(cwd, ["worktree", "remove", "--force", dir]);
17628
17876
  } finally {
17629
17877
  try {
17630
- rmSync18(root, { recursive: true, force: true });
17878
+ rmSync19(root, { recursive: true, force: true });
17631
17879
  } finally {
17632
17880
  run3(cwd, ["worktree", "prune"]);
17633
17881
  run3(cwd, ["branch", "-D", branch]);
@@ -17637,8 +17885,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run3 = gitOk) {
17637
17885
  function openReleaseCheckout(o) {
17638
17886
  const { remote, base } = o.repo.branch;
17639
17887
  const branch = `crew/release-${Date.now()}`;
17640
- const root = mkdtempSync3(join40(tmpdir5(), "crew-release-"));
17641
- const dir = join40(root, "checkout");
17888
+ const root = mkdtempSync3(join42(tmpdir5(), "crew-release-"));
17889
+ const dir = join42(root, "checkout");
17642
17890
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
17643
17891
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
17644
17892
  try {
@@ -17648,10 +17896,10 @@ function openReleaseCheckout(o) {
17648
17896
  throw e;
17649
17897
  }
17650
17898
  for (const f of o.repo.worktrees.copy) {
17651
- const src = join40(o.cwd, f);
17652
- if (!existsSync36(src)) continue;
17653
- mkdirSync26(dirname22(join40(dir, f)), { recursive: true });
17654
- copyFileSync(src, join40(dir, f));
17899
+ const src = join42(o.cwd, f);
17900
+ if (!existsSync38(src)) continue;
17901
+ mkdirSync28(dirname23(join42(dir, f)), { recursive: true });
17902
+ copyFileSync(src, join42(dir, f));
17655
17903
  }
17656
17904
  return { dir, branch, close };
17657
17905
  }
@@ -18673,7 +18921,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
18673
18921
  }
18674
18922
 
18675
18923
  // src/watch.ts
18676
- import { watch as watchFile, existsSync as existsSync37, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
18924
+ import { watch as watchFile, existsSync as existsSync39, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
18677
18925
  var E = "\x1B[";
18678
18926
  var dim = (s) => `${E}2m${s}${E}0m`;
18679
18927
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -18774,7 +19022,7 @@ function renderHeaders(states, now = Date.now()) {
18774
19022
  return states.map((s) => renderHeader(s, now)).join("");
18775
19023
  }
18776
19024
  function readFrom(file, from) {
18777
- if (!existsSync37(file)) return { events: [], offset: from };
19025
+ if (!existsSync39(file)) return { events: [], offset: from };
18778
19026
  const size = statSync11(file).size;
18779
19027
  if (size < from) return readFrom(file, 0);
18780
19028
  if (size === from) return { events: [], offset: from };
@@ -19017,8 +19265,8 @@ function nextRoles(decision, ran) {
19017
19265
  }
19018
19266
 
19019
19267
  // src/ship-name.ts
19020
- import { existsSync as existsSync38, mkdirSync as mkdirSync27, readFileSync as readFileSync34, renameSync as renameSync5, writeFileSync as writeFileSync30 } from "fs";
19021
- import { dirname as dirname23 } from "path";
19268
+ import { existsSync as existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync36, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
19269
+ import { dirname as dirname24 } from "path";
19022
19270
  import { parseDocument as parseDocument5 } from "yaml";
19023
19271
  var ShipNameError = class extends Error {
19024
19272
  };
@@ -19079,34 +19327,34 @@ async function syncRouteName(i) {
19079
19327
  return { status: "ok", ...base, action, name: i.local };
19080
19328
  }
19081
19329
  function readSyncedName(resolvedFile) {
19082
- if (!existsSync38(resolvedFile)) return void 0;
19330
+ if (!existsSync40(resolvedFile)) return void 0;
19083
19331
  try {
19084
- const v = JSON.parse(readFileSync34(resolvedFile, "utf8")).shipName;
19332
+ const v = JSON.parse(readFileSync36(resolvedFile, "utf8")).shipName;
19085
19333
  return typeof v === "string" ? v : void 0;
19086
19334
  } catch {
19087
19335
  return void 0;
19088
19336
  }
19089
19337
  }
19090
19338
  function writeSyncedName(resolvedFile, name) {
19091
- if (!existsSync38(resolvedFile)) return;
19339
+ if (!existsSync40(resolvedFile)) return;
19092
19340
  let raw;
19093
19341
  try {
19094
- raw = JSON.parse(readFileSync34(resolvedFile, "utf8"));
19342
+ raw = JSON.parse(readFileSync36(resolvedFile, "utf8"));
19095
19343
  } catch {
19096
19344
  return;
19097
19345
  }
19098
19346
  if (raw.shipName === name) return;
19099
19347
  raw.shipName = name;
19100
- mkdirSync27(dirname23(resolvedFile), { recursive: true });
19348
+ mkdirSync29(dirname24(resolvedFile), { recursive: true });
19101
19349
  const tmp = `${resolvedFile}.tmp${process.pid}`;
19102
- writeFileSync30(tmp, `${JSON.stringify(raw, null, 2)}
19350
+ writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
19103
19351
  `);
19104
19352
  renameSync5(tmp, resolvedFile);
19105
19353
  }
19106
19354
  async function reconcileShipName(i) {
19107
19355
  let local = i.ship.name;
19108
19356
  try {
19109
- local = shipNameOfYaml(readFileSync34(i.configFile, "utf8")) ?? local;
19357
+ local = shipNameOfYaml(readFileSync36(i.configFile, "utf8")) ?? local;
19110
19358
  } catch {
19111
19359
  }
19112
19360
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -19130,8 +19378,8 @@ async function reconcileShipName(i) {
19130
19378
  }
19131
19379
  if (result.status !== "ok") return result;
19132
19380
  if (result.action === "adopt" || result.action === "row-wins") {
19133
- const text2 = readFileSync34(i.configFile, "utf8");
19134
- writeFileSync30(i.configFile, setShipNameInYaml(text2, result.name));
19381
+ const text2 = readFileSync36(i.configFile, "utf8");
19382
+ writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
19135
19383
  i.ship.name = result.name;
19136
19384
  if (result.action === "row-wins") {
19137
19385
  i.emit?.warn(
@@ -19790,8 +20038,8 @@ function describeCapability(i) {
19790
20038
  }
19791
20039
 
19792
20040
  // src/agents-sync-run.ts
19793
- import { existsSync as existsSync39, mkdirSync as mkdirSync28, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "fs";
19794
- import { dirname as dirname24 } from "path";
20041
+ import { existsSync as existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync37, writeFileSync as writeFileSync33 } from "fs";
20042
+ import { dirname as dirname25 } from "path";
19795
20043
  async function runAgentsSync(target, o) {
19796
20044
  const dryRun = o.dryRun ?? false;
19797
20045
  const force = o.force ?? false;
@@ -19824,12 +20072,12 @@ async function runAgentsSync(target, o) {
19824
20072
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
19825
20073
  if (!dryRun) {
19826
20074
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
19827
- const raw = existsSync39(resolvedPath) ? JSON.parse(readFileSync35(resolvedPath, "utf8")) : {};
20075
+ const raw = existsSync41(resolvedPath) ? JSON.parse(readFileSync37(resolvedPath, "utf8")) : {};
19828
20076
  raw.agentPersonas = personaResult.agentPersonas;
19829
20077
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
19830
20078
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
19831
- mkdirSync28(dirname24(resolvedPath), { recursive: true });
19832
- writeFileSync31(resolvedPath, `${JSON.stringify(raw, null, 2)}
20079
+ mkdirSync30(dirname25(resolvedPath), { recursive: true });
20080
+ writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
19833
20081
  `);
19834
20082
  if (target.resolved) {
19835
20083
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -19946,7 +20194,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
19946
20194
 
19947
20195
  // src/repos-check.ts
19948
20196
  import { spawnSync as spawnSync10 } from "child_process";
19949
- import { readFileSync as readFileSync36 } from "fs";
20197
+ import { readFileSync as readFileSync38 } from "fs";
19950
20198
  import { basename as basename11 } from "path";
19951
20199
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
19952
20200
  function checkHookSyntax(script, shell) {
@@ -19987,7 +20235,7 @@ function checkRepoSpec(dir, opts = {}) {
19987
20235
  }
19988
20236
  let parsed;
19989
20237
  try {
19990
- parsed = parseRepoConfig(readFileSync36(file, "utf8"), file);
20238
+ parsed = parseRepoConfig(readFileSync38(file, "utf8"), file);
19991
20239
  } catch (e) {
19992
20240
  if (!(e instanceof RepoConfigError)) throw e;
19993
20241
  return { ...empty, file, error: e.message, exitCode: 1 };
@@ -20444,7 +20692,7 @@ ${run3.err}` });
20444
20692
  }
20445
20693
 
20446
20694
  // src/sample-ticket.ts
20447
- import { basename as basename12, dirname as dirname25, join as join41 } from "path";
20695
+ import { basename as basename12, dirname as dirname26, join as join43 } from "path";
20448
20696
  var SAMPLE_REPO = "crew-sample";
20449
20697
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
20450
20698
  var SAMPLE_MARKER = "sample: true";
@@ -20467,8 +20715,8 @@ has something safe to work on: its test and build hooks always pass, it has no
20467
20715
  remote, and \`crew sample-ticket --remove\` deletes it.
20468
20716
  `;
20469
20717
  function sampleDir(existing, reposBase, workspace) {
20470
- if (existing.length > 0) return join41(dirname25(existing[0]), SAMPLE_REPO);
20471
- return join41(reposBase, workspace, SAMPLE_REPO);
20718
+ if (existing.length > 0) return join43(dirname26(existing[0]), SAMPLE_REPO);
20719
+ return join43(reposBase, workspace, SAMPLE_REPO);
20472
20720
  }
20473
20721
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
20474
20722
  function sampleDescription() {
@@ -20512,8 +20760,8 @@ function planSampleTicket(i) {
20512
20760
  }
20513
20761
 
20514
20762
  // src/worktree-sweep.ts
20515
- import { existsSync as existsSync40, readFileSync as readFileSync37, readdirSync as readdirSync14 } from "fs";
20516
- import { basename as basename13, dirname as dirname26, join as join42, resolve as resolve5 } from "path";
20763
+ import { existsSync as existsSync42, readFileSync as readFileSync39, readdirSync as readdirSync14 } from "fs";
20764
+ import { basename as basename13, dirname as dirname27, join as join44, resolve as resolve5 } from "path";
20517
20765
  function planWorktreeSweep(target, tickets, contract) {
20518
20766
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
20519
20767
  const verified = contract.statuses.verified;
@@ -20577,7 +20825,7 @@ function worktreeTicketNumbers(target) {
20577
20825
  return [...nums];
20578
20826
  }
20579
20827
  function siblingWorktreeDirs(target) {
20580
- const parent = dirname26(target.dir.replace(/[/\\]+$/, ""));
20828
+ const parent = dirname27(target.dir.replace(/[/\\]+$/, ""));
20581
20829
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
20582
20830
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
20583
20831
  let names;
@@ -20590,7 +20838,7 @@ function siblingWorktreeDirs(target) {
20590
20838
  for (const name of names) {
20591
20839
  if (!name.startsWith(stem)) continue;
20592
20840
  const m = DIR_NUMBER_SUFFIXED.exec(name);
20593
- const path = join42(parent, name);
20841
+ const path = join44(parent, name);
20594
20842
  if (m && !registered.has(resolve5(path))) out.push({ path, number: m[1] });
20595
20843
  }
20596
20844
  return out;
@@ -20625,7 +20873,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20625
20873
  let removed = 0;
20626
20874
  let keptBranches = 0;
20627
20875
  for (const a of actions) {
20628
- if (!dryRun && base && !a.keepBranch && existsSync40(a.path) && isHandBackMerge(a.path, base)) {
20876
+ if (!dryRun && base && !a.keepBranch && existsSync42(a.path) && isHandBackMerge(a.path, base)) {
20629
20877
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
20630
20878
  log.emit(
20631
20879
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -20633,7 +20881,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20633
20881
  );
20634
20882
  }
20635
20883
  }
20636
- if (!dryRun && existsSync40(a.path)) {
20884
+ if (!dryRun && existsSync42(a.path)) {
20637
20885
  let dirty = false;
20638
20886
  try {
20639
20887
  dirty = status(a.path).length > 0;
@@ -20644,7 +20892,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
20644
20892
  continue;
20645
20893
  }
20646
20894
  }
20647
- if (!dryRun && a.requirePushed && a.branch && existsSync40(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20895
+ if (!dryRun && a.requirePushed && a.branch && existsSync42(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
20648
20896
  refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
20649
20897
  continue;
20650
20898
  }
@@ -20693,7 +20941,7 @@ function isHandBackMerge(path, base) {
20693
20941
  if (!msgPath) return false;
20694
20942
  let first;
20695
20943
  try {
20696
- first = readFileSync37(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20944
+ first = readFileSync39(resolve5(path, msgPath), "utf8").split("\n")[0] ?? "";
20697
20945
  } catch {
20698
20946
  return false;
20699
20947
  }
@@ -20798,14 +21046,14 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
20798
21046
  }
20799
21047
 
20800
21048
  // src/stream-sweep.ts
20801
- import { existsSync as existsSync41, readdirSync as readdirSync15, rmSync as rmSync19, statSync as statSync12 } from "fs";
20802
- import { join as join43 } from "path";
21049
+ import { existsSync as existsSync43, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21050
+ import { join as join45 } from "path";
20803
21051
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
20804
21052
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
20805
- const dir = join43(stateDir, "streams");
20806
- if (!existsSync41(dir)) return [];
21053
+ const dir = join45(stateDir, "streams");
21054
+ if (!existsSync43(dir)) return [];
20807
21055
  const cutoff = now - retentionDays * DAY_MS2;
20808
- return readdirSync15(dir).map((name) => join43(dir, name)).filter((path) => {
21056
+ return readdirSync15(dir).map((name) => join45(dir, name)).filter((path) => {
20809
21057
  try {
20810
21058
  return statSync12(path).mtimeMs < cutoff;
20811
21059
  } catch {
@@ -20821,7 +21069,7 @@ function applyStreamSweep(paths, dryRun, log) {
20821
21069
  continue;
20822
21070
  }
20823
21071
  try {
20824
- rmSync19(path, { force: true });
21072
+ rmSync20(path, { force: true });
20825
21073
  removed++;
20826
21074
  log.emit(`removed stream artifact ${path}`, { step: "worktree" });
20827
21075
  } catch (e) {
@@ -20831,9 +21079,9 @@ function applyStreamSweep(paths, dryRun, log) {
20831
21079
  return removed;
20832
21080
  }
20833
21081
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20834
- if (!existsSync41(stateDir)) return [];
21082
+ if (!existsSync43(stateDir)) return [];
20835
21083
  const cutoff = now - retentionDays * DAY_MS2;
20836
- return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join43(stateDir, name)).filter((path) => {
21084
+ return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join45(stateDir, name)).filter((path) => {
20837
21085
  try {
20838
21086
  return statSync12(path).mtimeMs < cutoff;
20839
21087
  } catch {
@@ -20843,12 +21091,12 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
20843
21091
  }
20844
21092
 
20845
21093
  // src/cli.ts
20846
- import { realpathSync as realpathSync3, readFileSync as readFileSync43, existsSync as existsSync48, mkdirSync as mkdirSync36, writeFileSync as writeFileSync36, rmSync as rmSync24, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
21094
+ import { realpathSync as realpathSync3, readFileSync as readFileSync45, existsSync as existsSync50, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
20847
21095
  import { createInterface } from "readline/promises";
20848
21096
 
20849
21097
  // src/periscope-cmd.ts
20850
- import { existsSync as existsSync42, mkdirSync as mkdirSync29, readFileSync as readFileSync38, rmSync as rmSync20, writeFileSync as writeFileSync32 } from "fs";
20851
- import { join as join44 } from "path";
21098
+ import { existsSync as existsSync44, mkdirSync as mkdirSync31, readFileSync as readFileSync40, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
21099
+ import { join as join46 } from "path";
20852
21100
  var PeriscopeError = class extends Error {
20853
21101
  };
20854
21102
  var LivePeriscopeError = class extends PeriscopeError {
@@ -20858,22 +21106,22 @@ var LivePeriscopeError = class extends PeriscopeError {
20858
21106
  this.data = { refused: "live_periscope_exists", ...live };
20859
21107
  }
20860
21108
  };
20861
- var priorDir = (stateDir) => join44(stateDir, "periscope-priors");
20862
- var priorPath = (stateDir, ticketRecordId) => join44(priorDir(stateDir), `${ticketRecordId}.json`);
21109
+ var priorDir = (stateDir) => join46(stateDir, "periscope-priors");
21110
+ var priorPath = (stateDir, ticketRecordId) => join46(priorDir(stateDir), `${ticketRecordId}.json`);
20863
21111
  function removePeriscopePrior(stateDir, ticketRecordId) {
20864
- rmSync20(priorPath(stateDir, ticketRecordId), { force: true });
21112
+ rmSync21(priorPath(stateDir, ticketRecordId), { force: true });
20865
21113
  }
20866
21114
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
20867
21115
  try {
20868
- mkdirSync29(priorDir(stateDir), { recursive: true });
20869
- writeFileSync32(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
21116
+ mkdirSync31(priorDir(stateDir), { recursive: true });
21117
+ writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
20870
21118
  } catch {
20871
21119
  }
20872
21120
  }
20873
21121
  function readPeriscopePrior(stateDir, ticketRecordId) {
20874
21122
  try {
20875
21123
  const p = priorPath(stateDir, ticketRecordId);
20876
- return existsSync42(p) ? JSON.parse(readFileSync38(p, "utf8")) : void 0;
21124
+ return existsSync44(p) ? JSON.parse(readFileSync40(p, "utf8")) : void 0;
20877
21125
  } catch {
20878
21126
  return void 0;
20879
21127
  }
@@ -21062,7 +21310,7 @@ ${tail(preview.output)}`);
21062
21310
  d.say("proxy", `proxy at ${url} in front of ${previewUrl}`, { url, previewUrl });
21063
21311
  }
21064
21312
  d.say("preview", `preview at ${url}`, { url });
21065
- const intel = await probeIntel(
21313
+ const intel = d.lspPluginDir ? { state: "absent" } : await probeIntel(
21066
21314
  { runHook: (e) => d.runHook("intel", wt.path, e) },
21067
21315
  env,
21068
21316
  d.intelStartupSeconds ?? DEFAULT_INTEL_STARTUP_SECONDS2
@@ -21078,7 +21326,7 @@ ${tail(preview.output)}`);
21078
21326
  d.say("warning", `symbol service not registered: ${e.message}`);
21079
21327
  }
21080
21328
  }
21081
- const codeTools = codeToolsParagraph(codeOutcome);
21329
+ const codeTools = d.lspPluginDir ? `${lspToolsParagraph("claude")} Start the Claude session with \`--plugin-dir ${d.lspPluginDir}\` to load it.` : codeToolsParagraph(codeOutcome);
21082
21330
  const now = d.now();
21083
21331
  let row;
21084
21332
  try {
@@ -21327,7 +21575,7 @@ ${tail2(preview.output)}`);
21327
21575
  // src/periscope-idle.ts
21328
21576
  import { execFileSync as execFileSync12 } from "child_process";
21329
21577
  import { statSync as statSync13 } from "fs";
21330
- import { join as join45 } from "path";
21578
+ import { join as join47 } from "path";
21331
21579
  var MAX_SCANNED_FILES = 2e4;
21332
21580
  function newestWorktreeMtime(root) {
21333
21581
  const a = newestTrackedMtime(root);
@@ -21352,7 +21600,7 @@ function newestTrackedMtime(root) {
21352
21600
  if (!rel) continue;
21353
21601
  if (++scanned > MAX_SCANNED_FILES) break;
21354
21602
  try {
21355
- const m = statSync13(join45(root, rel)).mtimeMs;
21603
+ const m = statSync13(join47(root, rel)).mtimeMs;
21356
21604
  if (newest === null || m > newest) newest = m;
21357
21605
  } catch {
21358
21606
  }
@@ -21428,18 +21676,18 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
21428
21676
 
21429
21677
  // src/ssh-keys.ts
21430
21678
  import { execFileSync as execFileSync13 } from "child_process";
21431
- import { existsSync as existsSync43, mkdirSync as mkdirSync30, readFileSync as readFileSync39, chmodSync as chmodSync3 } from "fs";
21432
- import { dirname as dirname27, join as join46 } from "path";
21679
+ import { existsSync as existsSync45, mkdirSync as mkdirSync32, readFileSync as readFileSync41, chmodSync as chmodSync3 } from "fs";
21680
+ import { dirname as dirname28, join as join48 } from "path";
21433
21681
  var SshKeyError = class extends Error {
21434
21682
  };
21435
21683
  function shipSshKeyPathFor(stateDir) {
21436
- return join46(stateDir, "ssh", "id_ed25519");
21684
+ return join48(stateDir, "ssh", "id_ed25519");
21437
21685
  }
21438
21686
  function ensureShipSshKeypair(stateDir) {
21439
21687
  const privateKeyPath = shipSshKeyPathFor(stateDir);
21440
21688
  const publicKeyPath = `${privateKeyPath}.pub`;
21441
- if (!existsSync43(privateKeyPath)) {
21442
- mkdirSync30(dirname27(privateKeyPath), { recursive: true });
21689
+ if (!existsSync45(privateKeyPath)) {
21690
+ mkdirSync32(dirname28(privateKeyPath), { recursive: true });
21443
21691
  try {
21444
21692
  execFileSync13("ssh-keygen", [
21445
21693
  "-t",
@@ -21459,10 +21707,10 @@ function ensureShipSshKeypair(stateDir) {
21459
21707
  }
21460
21708
  chmodSync3(privateKeyPath, 384);
21461
21709
  }
21462
- if (!existsSync43(publicKeyPath)) {
21710
+ if (!existsSync45(publicKeyPath)) {
21463
21711
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
21464
21712
  }
21465
- const publicKey = readFileSync39(publicKeyPath, "utf8").trim();
21713
+ const publicKey = readFileSync41(publicKeyPath, "utf8").trim();
21466
21714
  return { privateKeyPath, publicKeyPath, publicKey };
21467
21715
  }
21468
21716
  function sshKeygenAvailable() {
@@ -21626,12 +21874,12 @@ async function unregisterEndpoint(workspaceId, info, d) {
21626
21874
  import { createServer as createHttpServer } from "http";
21627
21875
  import { spawn as spawn9 } from "child_process";
21628
21876
  import { randomBytes } from "crypto";
21629
- import { existsSync as existsSync44, mkdirSync as mkdirSync32, readFileSync as readFileSync40, writeFileSync as writeFileSync33, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21630
- import { join as join47 } from "path";
21877
+ import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync42, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
21878
+ import { join as join49 } from "path";
21631
21879
 
21632
21880
  // src/passenger-mirror.ts
21633
- import { mkdirSync as mkdirSync31 } from "fs";
21634
- import { dirname as dirname28 } from "path";
21881
+ import { mkdirSync as mkdirSync33 } from "fs";
21882
+ import { dirname as dirname29 } from "path";
21635
21883
  function mirrorRepoOf(spec) {
21636
21884
  const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
21637
21885
  if (fromOrigin) return fromOrigin;
@@ -21649,9 +21897,9 @@ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCa
21649
21897
  continue;
21650
21898
  }
21651
21899
  try {
21652
- const parent = dirname28(spec.dir);
21653
- mkdirSync31(parent, { recursive: true });
21654
- mkdirSync31(spec.dir, { recursive: true });
21900
+ const parent = dirname29(spec.dir);
21901
+ mkdirSync33(parent, { recursive: true });
21902
+ mkdirSync33(spec.dir, { recursive: true });
21655
21903
  const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
21656
21904
  out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
21657
21905
  } catch (e) {
@@ -21757,21 +22005,21 @@ var defaultSpawn2 = (cmd, args, env) => {
21757
22005
  return child;
21758
22006
  };
21759
22007
  function syncDaemonStatePath(stateDir, workspaceId) {
21760
- return join47(stateDir, "passengers", `${workspaceId}-sync.json`);
22008
+ return join49(stateDir, "passengers", `${workspaceId}-sync.json`);
21761
22009
  }
21762
22010
  function readPersistedSyncDaemon(stateDir, workspaceId) {
21763
22011
  const path = syncDaemonStatePath(stateDir, workspaceId);
21764
- if (!existsSync44(path)) return void 0;
22012
+ if (!existsSync46(path)) return void 0;
21765
22013
  try {
21766
- return JSON.parse(readFileSync40(path, "utf8"));
22014
+ return JSON.parse(readFileSync42(path, "utf8"));
21767
22015
  } catch {
21768
22016
  return void 0;
21769
22017
  }
21770
22018
  }
21771
22019
  function writePersistedSyncDaemon(stateDir, state) {
21772
22020
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
21773
- mkdirSync32(join47(stateDir, "passengers"), { recursive: true });
21774
- writeFileSync33(path, `${JSON.stringify(state, null, 2)}
22021
+ mkdirSync34(join49(stateDir, "passengers"), { recursive: true });
22022
+ writeFileSync35(path, `${JSON.stringify(state, null, 2)}
21775
22023
  `);
21776
22024
  }
21777
22025
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -21791,8 +22039,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
21791
22039
  }
21792
22040
  }
21793
22041
  function readAllPersistedSyncWorkspaceIds(stateDir) {
21794
- const dir = join47(stateDir, "passengers");
21795
- if (!existsSync44(dir)) return [];
22042
+ const dir = join49(stateDir, "passengers");
22043
+ if (!existsSync46(dir)) return [];
21796
22044
  const suffix = "-sync.json";
21797
22045
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
21798
22046
  }
@@ -21850,12 +22098,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
21850
22098
 
21851
22099
  // src/periscope-proxy.ts
21852
22100
  import { spawn as spawn10 } from "child_process";
21853
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync33 } from "fs";
22101
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
21854
22102
  import { createServer, request as httpRequest } from "http";
21855
22103
  import { request as httpsRequest } from "https";
21856
22104
  import { connect as netConnect } from "net";
21857
22105
  import { connect as tlsConnect } from "tls";
21858
- import { dirname as dirname29, isAbsolute as isAbsolute4 } from "path";
22106
+ import { dirname as dirname30, isAbsolute as isAbsolute4 } from "path";
21859
22107
  import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
21860
22108
 
21861
22109
  // src/periscope-inspector.ts
@@ -21969,7 +22217,7 @@ function parsePointedBody(raw) {
21969
22217
  return entry;
21970
22218
  }
21971
22219
  function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
21972
- mkdirSync33(dirname29(file), { recursive: true });
22220
+ mkdirSync35(dirname30(file), { recursive: true });
21973
22221
  appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
21974
22222
  `);
21975
22223
  }
@@ -22230,6 +22478,33 @@ async function launchProxy(invocation, worktree, previewUrl, number, o) {
22230
22478
  throw new Error(`the proxy did not start listening on ${o.host}:${port}`);
22231
22479
  }
22232
22480
 
22481
+ // src/intel-worktree.ts
22482
+ async function startWorktreeIntel(d) {
22483
+ const file = attachFilePath(d.stateDir, d.route, d.number);
22484
+ const probe = d.probe ?? probeIntel;
22485
+ let outcome;
22486
+ try {
22487
+ const known = readAttachFile(file);
22488
+ outcome = known ? await probe({ runHook: async () => ({ code: 0, output: known }) }, {}, 3) : { state: "absent" };
22489
+ if (outcome.state !== "ready") {
22490
+ const prepared = await d.prepare(d.worktree);
22491
+ if (prepared && prepared.code !== 0) {
22492
+ outcome = { state: "failed", reason: `setup exited ${prepared.code}` };
22493
+ } else {
22494
+ const env = { CREW_TICKET: d.ticketTag, CREW_BRANCH: d.branch };
22495
+ const ports = await d.runHook("ports", d.worktree, env);
22496
+ if (ports) Object.assign(env, parseEnvLines(ports.output));
22497
+ outcome = await probe({ runHook: (e) => d.runHook("intel", d.worktree, e) }, env, d.startupSeconds);
22498
+ }
22499
+ }
22500
+ if (outcome.state === "ready") writeAttachFile(file, outcome.url);
22501
+ } catch (e) {
22502
+ outcome = { state: "failed", reason: e.message.slice(0, 200) };
22503
+ }
22504
+ const tail3 = outcome.state === "ready" ? " The session's `code` server attaches to it within a second; until it lists these tools it still shows only `code_status`." : "";
22505
+ return `${codeToolsParagraph(outcome)}${tail3}`;
22506
+ }
22507
+
22233
22508
  // src/outage.ts
22234
22509
  var TRANSIENT_BACKOFF_MS = [15e3, 3e4, 6e4];
22235
22510
  var OUTAGE_ESCALATE_AFTER = 5;
@@ -22262,8 +22537,8 @@ function describeCause(err3) {
22262
22537
  }
22263
22538
 
22264
22539
  // src/daemon.ts
22265
- import { existsSync as existsSync45, mkdirSync as mkdirSync34, readFileSync as readFileSync41, readdirSync as readdirSync17, rmSync as rmSync21, statSync as statSync14, writeFileSync as writeFileSync34 } from "fs";
22266
- import { join as join48 } from "path";
22540
+ import { existsSync as existsSync47, mkdirSync as mkdirSync36, readFileSync as readFileSync43, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
22541
+ import { join as join50 } from "path";
22267
22542
  import { randomBytes as randomBytes2 } from "crypto";
22268
22543
  var DEFAULT_FLOOR_MS = 5e3;
22269
22544
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
@@ -22351,7 +22626,7 @@ function latestMtime(path) {
22351
22626
  if (!stat2.isDirectory()) return 0;
22352
22627
  let latest = stat2.mtimeMs;
22353
22628
  for (const entry of readdirSync17(path, { withFileTypes: true })) {
22354
- latest = Math.max(latest, latestMtime(join48(path, entry.name)));
22629
+ latest = Math.max(latest, latestMtime(join50(path, entry.name)));
22355
22630
  }
22356
22631
  return latest;
22357
22632
  }
@@ -22360,10 +22635,10 @@ function computeTreeSignature(paths) {
22360
22635
  }
22361
22636
  function makeStaleChecker(paths, opts = {}) {
22362
22637
  const initial = computeTreeSignature(paths);
22363
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync45(opts.reloadSentinel);
22638
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync47(opts.reloadSentinel);
22364
22639
  }
22365
22640
  function reloadSentinelFor(stateDir) {
22366
- return join48(stateDir, "reload-requested");
22641
+ return join50(stateDir, "reload-requested");
22367
22642
  }
22368
22643
  function readReloadRequestedAt(stateDir) {
22369
22644
  try {
@@ -22373,17 +22648,17 @@ function readReloadRequestedAt(stateDir) {
22373
22648
  }
22374
22649
  }
22375
22650
  function daemonStampFor(stateDir) {
22376
- return join48(stateDir, "daemon.started");
22651
+ return join50(stateDir, "daemon.started");
22377
22652
  }
22378
22653
  function noteDaemonStarted(stateDir, now = Date.now()) {
22379
- mkdirSync34(stateDir, { recursive: true });
22380
- rmSync21(reloadSentinelFor(stateDir), { force: true });
22381
- writeFileSync34(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
22654
+ mkdirSync36(stateDir, { recursive: true });
22655
+ rmSync22(reloadSentinelFor(stateDir), { force: true });
22656
+ writeFileSync36(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
22382
22657
  `);
22383
22658
  }
22384
22659
  function readDaemonStarted(stateDir) {
22385
22660
  try {
22386
- const v = JSON.parse(readFileSync41(daemonStampFor(stateDir), "utf8")).startedAt;
22661
+ const v = JSON.parse(readFileSync43(daemonStampFor(stateDir), "utf8")).startedAt;
22387
22662
  return typeof v === "number" ? v : null;
22388
22663
  } catch {
22389
22664
  return null;
@@ -22949,18 +23224,18 @@ import { createHash as createHash10 } from "crypto";
22949
23224
  import {
22950
23225
  chmodSync as chmodSync4,
22951
23226
  closeSync as closeSync6,
22952
- existsSync as existsSync46,
23227
+ existsSync as existsSync48,
22953
23228
  fstatSync,
22954
23229
  openSync as openSync6,
22955
23230
  readSync as readSync3,
22956
23231
  readdirSync as readdirSync18,
22957
- rmSync as rmSync22,
23232
+ rmSync as rmSync23,
22958
23233
  statSync as statSync15,
22959
23234
  watch
22960
23235
  } from "fs";
22961
23236
  import { createConnection } from "net";
22962
23237
  import { createServer as createServer2 } from "http";
22963
- import { basename as basename14, dirname as dirname30, join as join49 } from "path";
23238
+ import { basename as basename14, dirname as dirname31, join as join51 } from "path";
22964
23239
  import { execFile as execFile3, spawn as spawn12 } from "child_process";
22965
23240
 
22966
23241
  // src/periscope-control.ts
@@ -23189,7 +23464,7 @@ function runCrew(args, onLine, timeoutMs = 20 * 6e4) {
23189
23464
  // src/control-api.ts
23190
23465
  var PAUSABLE = ["dev", "design", "qa"];
23191
23466
  var CONTROL_SOCKET_NAME = "control.sock";
23192
- var controlSocketFor = (stateDir) => join49(stateDir, CONTROL_SOCKET_NAME);
23467
+ var controlSocketFor = (stateDir) => join51(stateDir, CONTROL_SOCKET_NAME);
23193
23468
  var MAX_SOCKET_PATH = 100;
23194
23469
  var MAX_TAIL_BYTES = 1024 * 1024;
23195
23470
  var MAX_WAIT_SECONDS = 30;
@@ -23276,7 +23551,7 @@ function candidates(live, rotated) {
23276
23551
  const out = [live];
23277
23552
  if (!rotated) return out;
23278
23553
  try {
23279
- for (const n of readdirSync18(dirname30(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join49(dirname30(live), n));
23554
+ for (const n of readdirSync18(dirname31(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join51(dirname31(live), n));
23280
23555
  } catch {
23281
23556
  }
23282
23557
  return out;
@@ -23320,7 +23595,7 @@ function lastLines2(path, n) {
23320
23595
  }
23321
23596
  function readTail(live, opts) {
23322
23597
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
23323
- if (!existsSync46(live)) {
23598
+ if (!existsSync48(live)) {
23324
23599
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
23325
23600
  }
23326
23601
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -23436,7 +23711,7 @@ async function tailReply(path, q2, rotated, abort) {
23436
23711
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
23437
23712
  let r = readTail(path, { after, tail: tail3, rotated });
23438
23713
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
23439
- await waitForChange(dirname30(path), deadline - Date.now(), abort);
23714
+ await waitForChange(dirname31(path), deadline - Date.now(), abort);
23440
23715
  r = readTail(path, { after: r.cursor, rotated });
23441
23716
  }
23442
23717
  return reply2(200, r);
@@ -23476,8 +23751,8 @@ async function route(deps, method, url, req, abort) {
23476
23751
  if (p.startsWith("/streams/") && get2) {
23477
23752
  const run3 = decodeURIComponent(p.slice("/streams/".length));
23478
23753
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
23479
- const file = join49(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
23480
- if (!existsSync46(file)) return err2(404, "no such run");
23754
+ const file = join51(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
23755
+ if (!existsSync48(file)) return err2(404, "no such run");
23481
23756
  return tailReply(file, url.searchParams, false, abort);
23482
23757
  }
23483
23758
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -23521,7 +23796,7 @@ function send(res, r) {
23521
23796
  res.end(body);
23522
23797
  }
23523
23798
  async function socketIsStale(path) {
23524
- if (!existsSync46(path)) return false;
23799
+ if (!existsSync48(path)) return false;
23525
23800
  return new Promise((resolve8) => {
23526
23801
  const c = createConnection(path);
23527
23802
  c.once("connect", () => {
@@ -23539,8 +23814,8 @@ async function startControlServer(deps) {
23539
23814
  log(`control API not started: socket path is too long for a unix socket (${path})`);
23540
23815
  return null;
23541
23816
  }
23542
- if (await socketIsStale(path)) rmSync22(path, { force: true });
23543
- else if (existsSync46(path)) {
23817
+ if (await socketIsStale(path)) rmSync23(path, { force: true });
23818
+ else if (existsSync48(path)) {
23544
23819
  log(`control API not started: ${path} is in use by another process`);
23545
23820
  return null;
23546
23821
  }
@@ -23586,7 +23861,7 @@ async function startControlServer(deps) {
23586
23861
  path,
23587
23862
  close: () => new Promise((done) => {
23588
23863
  server.close(() => {
23589
- rmSync22(path, { force: true });
23864
+ rmSync23(path, { force: true });
23590
23865
  done();
23591
23866
  });
23592
23867
  })
@@ -23626,57 +23901,57 @@ function crewJson(args, timeoutMs = 6e4) {
23626
23901
 
23627
23902
  // src/periscope-mcp.ts
23628
23903
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
23629
- import { basename as basename15, dirname as dirname31, isAbsolute as isAbsolute5, join as join51, relative as relative2, resolve as resolve6, sep } from "path";
23904
+ import { basename as basename15, dirname as dirname32, isAbsolute as isAbsolute5, join as join53, relative as relative2, resolve as resolve6, sep } from "path";
23630
23905
  import { z } from "zod";
23631
23906
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23632
23907
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
23633
23908
 
23634
23909
  // src/periscope-endpoints.ts
23635
23910
  import { randomBytes as randomBytes3 } from "crypto";
23636
- import { chmodSync as chmodSync5, existsSync as existsSync47, mkdirSync as mkdirSync35, readdirSync as readdirSync19, readFileSync as readFileSync42, rmSync as rmSync23, writeFileSync as writeFileSync35 } from "fs";
23637
- import { join as join50 } from "path";
23638
- var secretDir = (stateDir) => join50(stateDir, "periscope-mcp");
23639
- var secretFile = (stateDir, id) => join50(secretDir(stateDir), `${id}.json`);
23640
- var endpointDir = (stateDir) => join50(stateDir, "periscope-endpoints");
23641
- var endpointFile = (stateDir, id) => join50(endpointDir(stateDir), `${id}.json`);
23911
+ import { chmodSync as chmodSync5, existsSync as existsSync49, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync44, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
23912
+ import { join as join52 } from "path";
23913
+ var secretDir = (stateDir) => join52(stateDir, "periscope-mcp");
23914
+ var secretFile = (stateDir, id) => join52(secretDir(stateDir), `${id}.json`);
23915
+ var endpointDir = (stateDir) => join52(stateDir, "periscope-endpoints");
23916
+ var endpointFile = (stateDir, id) => join52(endpointDir(stateDir), `${id}.json`);
23642
23917
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
23643
- mkdirSync35(secretDir(stateDir), { recursive: true, mode: 448 });
23918
+ mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
23644
23919
  const secret = randomBytes3(32).toString("base64url");
23645
23920
  const file = secretFile(stateDir, ticketRecordId);
23646
- writeFileSync35(file, `${JSON.stringify({ secret })}
23921
+ writeFileSync37(file, `${JSON.stringify({ secret })}
23647
23922
  `, { mode: 384 });
23648
23923
  chmodSync5(file, 384);
23649
23924
  return secret;
23650
23925
  }
23651
23926
  function readPeriscopeSecret(stateDir, ticketRecordId) {
23652
23927
  try {
23653
- const v = JSON.parse(readFileSync42(secretFile(stateDir, ticketRecordId), "utf8"));
23928
+ const v = JSON.parse(readFileSync44(secretFile(stateDir, ticketRecordId), "utf8"));
23654
23929
  return typeof v.secret === "string" && v.secret ? v.secret : void 0;
23655
23930
  } catch {
23656
23931
  return void 0;
23657
23932
  }
23658
23933
  }
23659
23934
  function removePeriscopeSecret(stateDir, ticketRecordId) {
23660
- rmSync23(secretFile(stateDir, ticketRecordId), { force: true });
23935
+ rmSync24(secretFile(stateDir, ticketRecordId), { force: true });
23661
23936
  }
23662
23937
  function readPeriscopeEndpoint(stateDir, ticketRecordId) {
23663
23938
  try {
23664
- return JSON.parse(readFileSync42(endpointFile(stateDir, ticketRecordId), "utf8"));
23939
+ return JSON.parse(readFileSync44(endpointFile(stateDir, ticketRecordId), "utf8"));
23665
23940
  } catch {
23666
23941
  return void 0;
23667
23942
  }
23668
23943
  }
23669
23944
  function writePeriscopeEndpoint(stateDir, rec) {
23670
- mkdirSync35(endpointDir(stateDir), { recursive: true });
23671
- writeFileSync35(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
23945
+ mkdirSync37(endpointDir(stateDir), { recursive: true });
23946
+ writeFileSync37(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
23672
23947
  `);
23673
23948
  }
23674
23949
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
23675
- rmSync23(endpointFile(stateDir, ticketRecordId), { force: true });
23950
+ rmSync24(endpointFile(stateDir, ticketRecordId), { force: true });
23676
23951
  }
23677
23952
  function listPeriscopeEndpoints(stateDir) {
23678
23953
  const dir = endpointDir(stateDir);
23679
- if (!existsSync47(dir)) return [];
23954
+ if (!existsSync49(dir)) return [];
23680
23955
  const out = [];
23681
23956
  for (const name of readdirSync19(dir)) {
23682
23957
  if (!name.endsWith(".json")) continue;
@@ -23764,10 +24039,10 @@ async function realAncestor(p) {
23764
24039
  }
23765
24040
  if (isLink) {
23766
24041
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
23767
- cur = resolve6(dirname31(cur), await readlink(cur));
24042
+ cur = resolve6(dirname32(cur), await readlink(cur));
23768
24043
  continue;
23769
24044
  }
23770
- const parent = dirname31(cur);
24045
+ const parent = dirname32(cur);
23771
24046
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
23772
24047
  tail3.push(basename15(cur));
23773
24048
  cur = parent;
@@ -23779,13 +24054,13 @@ async function resolveInWorktree(root, requested) {
23779
24054
  let abs;
23780
24055
  if (isAbsolute5(requested)) {
23781
24056
  abs = resolve6(requested);
23782
- if (isInside(root, abs)) abs = join51(realRoot, relative2(root, abs));
24057
+ if (isInside(root, abs)) abs = join53(realRoot, relative2(root, abs));
23783
24058
  } else {
23784
24059
  abs = resolve6(realRoot, requested);
23785
24060
  }
23786
24061
  if (!isInside(realRoot, abs)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
23787
24062
  const { real, rest } = await realAncestor(abs);
23788
- const final = rest ? join51(real, rest) : real;
24063
+ const final = rest ? join53(real, rest) : real;
23789
24064
  if (!isInside(realRoot, final)) throw new PathRefusal(`${requested} is outside this periscope's worktree`);
23790
24065
  if (relative2(realRoot, final).split(sep).includes(".git")) throw new PathRefusal(".git is not reachable from a periscope");
23791
24066
  return final;
@@ -23853,7 +24128,7 @@ function createPeriscopeMcpServer(d, sc) {
23853
24128
  const entries = (await readdir(abs, { withFileTypes: true })).filter((e) => e.name !== ".git");
23854
24129
  const lines2 = await Promise.all(entries.map(async (e) => {
23855
24130
  if (e.isDirectory()) return `[DIR] ${e.name}`;
23856
- const size = (await lstat(join51(abs, e.name))).size;
24131
+ const size = (await lstat(join53(abs, e.name))).size;
23857
24132
  return `[FILE] ${e.name} (${size} bytes)`;
23858
24133
  }));
23859
24134
  return text(lines2.join("\n") || "(empty)");
@@ -23878,7 +24153,7 @@ function createPeriscopeMcpServer(d, sc) {
23878
24153
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
23879
24154
  const abs = await inTree(path);
23880
24155
  if (abs === await realpath(root)) return text("path must name a file", true);
23881
- await mkdir(dirname31(abs), { recursive: true });
24156
+ await mkdir(dirname32(abs), { recursive: true });
23882
24157
  await inTree(path);
23883
24158
  await writeFile(abs, content);
23884
24159
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -23918,8 +24193,8 @@ function createPeriscopeMcpServer(d, sc) {
23918
24193
  inputSchema: { path: z.string() },
23919
24194
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
23920
24195
  }, async ({ path }) => {
23921
- const parent = await inTree(dirname31(path) === "" ? "." : dirname31(path));
23922
- const abs = join51(parent, basename15(path));
24196
+ const parent = await inTree(dirname32(path) === "" ? "." : dirname32(path));
24197
+ const abs = join53(parent, basename15(path));
23923
24198
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
23924
24199
  const st = await lstat(abs).catch(() => void 0);
23925
24200
  if (!st) return text(`${path} does not exist`, true);
@@ -24040,7 +24315,7 @@ function findPeriscope(sidecars, segment) {
24040
24315
  }
24041
24316
 
24042
24317
  // src/cli.ts
24043
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname32(process.execPath) : resolve7(dirname32(fileURLToPath(import.meta.url)), "..");
24318
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname33(process.execPath) : resolve7(dirname33(fileURLToPath(import.meta.url)), "..");
24044
24319
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
24045
24320
  var CYCLE_SECONDS2 = 120;
24046
24321
  var STALE_CYCLES = 3;
@@ -24298,6 +24573,7 @@ if (!command) usage(argv.length ? `unknown option "${argv[0]}"` : void 0);
24298
24573
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
24299
24574
  "passenger-sync-daemon",
24300
24575
  "periscope-proxy",
24576
+ "intel",
24301
24577
  "agents",
24302
24578
  "connect",
24303
24579
  "credential",
@@ -24409,7 +24685,7 @@ if (command === "provision") {
24409
24685
  process.exit(outcome.code);
24410
24686
  }
24411
24687
  if (command === "pool-member") {
24412
- const pool = poolConfigFrom(argv, process.env, () => readFileSync43(0, "utf8"));
24688
+ const pool = poolConfigFrom(argv, process.env, () => readFileSync45(0, "utf8"));
24413
24689
  if (typeof pool === "string") {
24414
24690
  process.stderr.write(`crew pool-member: ${pool}
24415
24691
  `);
@@ -24443,6 +24719,14 @@ if (command === "passenger-sync-daemon") {
24443
24719
  process.exit(2);
24444
24720
  }
24445
24721
  runProxyFromEnv(dropFile);
24722
+ } else if (command === "intel") {
24723
+ const [, sub, bridgeRoute, bridgeTicket] = positional;
24724
+ const stateDir = value("state-dir");
24725
+ if (sub !== "bridge" || !bridgeRoute || !bridgeTicket || !stateDir) {
24726
+ process.stderr.write("usage: crew intel bridge <route> <ticket number> --state-dir DIR (registered by a lane run, not run by hand)\n");
24727
+ process.exit(2);
24728
+ }
24729
+ await runBridge(stateDir, bridgeRoute, bridgeTicket);
24446
24730
  } else {
24447
24731
  let requireArmed = function(what) {
24448
24732
  if (dryRun || route2.enabled) return;
@@ -24644,7 +24928,7 @@ if (command === "passenger-sync-daemon") {
24644
24928
  } else {
24645
24929
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
24646
24930
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
24647
- if (configless && !existsSync48(findConfigFile(CREW_HOME))) {
24931
+ if (configless && !existsSync50(findConfigFile(CREW_HOME))) {
24648
24932
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
24649
24933
  } else {
24650
24934
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -24659,7 +24943,7 @@ if (command === "passenger-sync-daemon") {
24659
24943
  throw e;
24660
24944
  }
24661
24945
  if (command === "credential") {
24662
- const r = runCredential(positional[1], readFileSync43(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
24946
+ const r = runCredential(positional[1], readFileSync45(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
24663
24947
  process.stdout.write(r.out);
24664
24948
  process.stderr.write(r.err);
24665
24949
  process.exit(r.code);
@@ -24671,9 +24955,9 @@ if (command === "passenger-sync-daemon") {
24671
24955
  forges: cfg.ship.forges,
24672
24956
  stateDir: cfg.ship.stateDir,
24673
24957
  configFile: cfg.configFile,
24674
- readFile: (p) => readFileSync43(p, "utf8"),
24675
- writeFile: (p, t) => writeFileSync36(p, t),
24676
- stdin: () => readFileSync43(0, "utf8"),
24958
+ readFile: (p) => readFileSync45(p, "utf8"),
24959
+ writeFile: (p, t) => writeFileSync38(p, t),
24960
+ stdin: () => readFileSync45(0, "utf8"),
24677
24961
  env: process.env
24678
24962
  }
24679
24963
  );
@@ -24966,8 +25250,8 @@ if (command === "passenger-sync-daemon") {
24966
25250
  }
24967
25251
  if (host === "linux" && hasSystemd) {
24968
25252
  const label = labelFor(CREW_HOME, "run");
24969
- const staleTimer = join52(homedir11(), ".config", "systemd", "user", `${label}.timer`);
24970
- if (existsSync48(staleTimer)) {
25253
+ const staleTimer = join54(homedir11(), ".config", "systemd", "user", `${label}.timer`);
25254
+ if (existsSync50(staleTimer)) {
24971
25255
  process.stdout.write(
24972
25256
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
24973
25257
  `
@@ -24994,7 +25278,7 @@ if (command === "passenger-sync-daemon") {
24994
25278
  const tracker = new Tracker(c, cfg.ship);
24995
25279
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
24996
25280
  for (const r of await resolvedRepos(c)) {
24997
- if (!existsSync48(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25281
+ if (!existsSync50(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
24998
25282
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
24999
25283
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
25000
25284
  }
@@ -25021,7 +25305,7 @@ if (command === "passenger-sync-daemon") {
25021
25305
  }
25022
25306
  async function doctorFix(o) {
25023
25307
  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))));
25024
- const unitExists = (p) => existsSync48(p);
25308
+ const unitExists = (p) => existsSync50(p);
25025
25309
  const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
25026
25310
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
25027
25311
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -25038,7 +25322,7 @@ if (command === "passenger-sync-daemon") {
25038
25322
  for (const a of plan.actions) {
25039
25323
  if (a.kind === "enable") {
25040
25324
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
25041
- writeFileSync36(cfg.configFile, planEnable(readFileSync43(cfg.configFile, "utf8"), a.route));
25325
+ writeFileSync38(cfg.configFile, planEnable(readFileSync45(cfg.configFile, "utf8"), a.route));
25042
25326
  enabledSound.add(a.route);
25043
25327
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
25044
25328
  } else {
@@ -26177,7 +26461,7 @@ ${cu.tail.trimEnd()}
26177
26461
  process.exit(1);
26178
26462
  }
26179
26463
  requireArmed("run the daemon loop");
26180
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join52(CREW_HOME, "src"), join52(CREW_HOME, "dist"), join52(CREW_HOME, "bin")];
26464
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join54(CREW_HOME, "src"), join54(CREW_HOME, "dist"), join54(CREW_HOME, "bin")];
26181
26465
  const configPaths = daemonConfigPaths(cfg);
26182
26466
  watchPaths.push(...configPaths);
26183
26467
  noteDaemonStarted(cfg.ship.stateDir);
@@ -26482,10 +26766,10 @@ ${cu.tail.trimEnd()}
26482
26766
  `);
26483
26767
  if (!dryRun) {
26484
26768
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
26485
- const raw = existsSync48(resolvedPath) ? JSON.parse(readFileSync43(resolvedPath, "utf8")) : {};
26769
+ const raw = existsSync50(resolvedPath) ? JSON.parse(readFileSync45(resolvedPath, "utf8")) : {};
26486
26770
  raw.agentSkills = result.agentSkills;
26487
- mkdirSync36(dirOf2(resolvedPath), { recursive: true });
26488
- writeFileSync36(resolvedPath, `${JSON.stringify(raw, null, 2)}
26771
+ mkdirSync38(dirOf2(resolvedPath), { recursive: true });
26772
+ writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
26489
26773
  `);
26490
26774
  }
26491
26775
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -26508,7 +26792,7 @@ ${cu.tail.trimEnd()}
26508
26792
  throw e;
26509
26793
  }
26510
26794
  const oldName = cfg.ship.name;
26511
- writeFileSync36(cfg.configFile, setShipNameInYaml(readFileSync43(cfg.configFile, "utf8"), newName));
26795
+ writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync45(cfg.configFile, "utf8"), newName));
26512
26796
  cfg.ship.name = newName;
26513
26797
  let renameMachineId;
26514
26798
  try {
@@ -26555,14 +26839,14 @@ ${cu.tail.trimEnd()}
26555
26839
  process.exit(2);
26556
26840
  };
26557
26841
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
26558
- const configText = readFileSync43(cfg.configFile, "utf8");
26842
+ const configText = readFileSync45(cfg.configFile, "utf8");
26559
26843
  if (sub === "check") {
26560
26844
  try {
26561
26845
  const routeArg = value("route") ?? positional[2];
26562
26846
  const found = cfg.routes.find((r) => r.route === routeArg);
26563
26847
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
26564
26848
  const named2 = value("route") ? void 0 : positional[3];
26565
- const dir = named2 ? repoDirOf(configText, dirname32(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
26849
+ const dir = named2 ? repoDirOf(configText, dirname33(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
26566
26850
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
26567
26851
  const o = target ? found.repoOverrides[target.name] : void 0;
26568
26852
  runReposCheck(dir, {
@@ -26585,9 +26869,9 @@ ${cu.tail.trimEnd()}
26585
26869
  if (sub === "hooks") {
26586
26870
  try {
26587
26871
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
26588
- const dir = repoDirOf(configText, dirname32(cfg.configFile), a.route, a.name);
26589
- if (!existsSync48(dir)) throw new ReposError(`${dir} does not exist on disk`);
26590
- const files = { exists: (r) => existsSync48(resolvePath(dir, r)), list: (r) => {
26872
+ const dir = repoDirOf(configText, dirname33(cfg.configFile), a.route, a.name);
26873
+ if (!existsSync50(dir)) throw new ReposError(`${dir} does not exist on disk`);
26874
+ const files = { exists: (r) => existsSync50(resolvePath(dir, r)), list: (r) => {
26591
26875
  try {
26592
26876
  return readdirSync20(resolvePath(dir, r));
26593
26877
  } catch {
@@ -26595,7 +26879,7 @@ ${cu.tail.trimEnd()}
26595
26879
  }
26596
26880
  }, read: (r) => {
26597
26881
  try {
26598
- return readFileSync43(resolvePath(dir, r), "utf8");
26882
+ return readFileSync45(resolvePath(dir, r), "utf8");
26599
26883
  } catch {
26600
26884
  return void 0;
26601
26885
  }
@@ -26637,19 +26921,19 @@ ${cu.tail.trimEnd()}
26637
26921
  let cleared;
26638
26922
  if (clear2) {
26639
26923
  const plan = clearRouteHooks(configText, a.route, a.name);
26640
- if (plan.removed.length > 0) writeFileSync36(cfg.configFile, plan.text);
26924
+ if (plan.removed.length > 0) writeFileSync38(cfg.configFile, plan.text);
26641
26925
  cleared = { file: cfg.configFile, removed: plan.removed };
26642
26926
  }
26643
26927
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
26644
26928
  if (write === "route") {
26645
26929
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
26646
- writeFileSync36(cfg.configFile, plan.text);
26930
+ writeFileSync38(cfg.configFile, plan.text);
26647
26931
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
26648
26932
  } else if (write === "repo") {
26649
26933
  const have = existingRepoConfig(files);
26650
26934
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
26651
26935
  const file = resolvePath(dir, ".crew.yaml");
26652
- writeFileSync36(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
26936
+ writeFileSync38(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
26653
26937
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
26654
26938
  }
26655
26939
  if (flag("json")) {
@@ -26705,7 +26989,7 @@ ${cu.tail.trimEnd()}
26705
26989
  if (sub === "list" && !routeName) usage2();
26706
26990
  if (sub === "add") {
26707
26991
  try {
26708
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync48(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
26992
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync50(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
26709
26993
  routeName = a.route;
26710
26994
  addPath = a.path;
26711
26995
  addName = a.name;
@@ -26735,7 +27019,7 @@ ${cu.tail.trimEnd()}
26735
27019
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
26736
27020
  if (sub === "list" || sub === "add") {
26737
27021
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
26738
- if (liveRoute?.resolved?.reposModelId && existsSync48(resolvedFile) && !dryRun) {
27022
+ if (liveRoute?.resolved?.reposModelId && existsSync50(resolvedFile) && !dryRun) {
26739
27023
  try {
26740
27024
  await hydrateApiKeys([liveRoute]);
26741
27025
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -26744,16 +27028,16 @@ ${cu.tail.trimEnd()}
26744
27028
  }
26745
27029
  }
26746
27030
  let resolvedRemotes;
26747
- if (existsSync48(resolvedFile)) {
27031
+ if (existsSync50(resolvedFile)) {
26748
27032
  try {
26749
- resolvedRemotes = JSON.parse(readFileSync43(resolvedFile, "utf8"));
27033
+ resolvedRemotes = JSON.parse(readFileSync45(resolvedFile, "utf8"));
26750
27034
  } catch {
26751
27035
  }
26752
27036
  }
26753
27037
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
26754
27038
  const text2 = configText;
26755
- const base = dirname32(cfg.configFile);
26756
- const isCheckout = (d) => existsSync48(`${d}/.git`);
27039
+ const base = dirname33(cfg.configFile);
27040
+ const isCheckout = (d) => existsSync50(`${d}/.git`);
26757
27041
  try {
26758
27042
  if (sub === "list") {
26759
27043
  if (flag("json")) {
@@ -26763,11 +27047,11 @@ ${cu.tail.trimEnd()}
26763
27047
  base,
26764
27048
  resolved: resolvedRemotes,
26765
27049
  originOf,
26766
- exists: (d) => existsSync48(d),
27050
+ exists: (d) => existsSync50(d),
26767
27051
  isCheckout,
26768
27052
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
26769
27053
  try {
26770
- return Object.keys(parseRepoConfig(readFileSync43(resolvePath(d, f), "utf8"), f).hooks).length > 0;
27054
+ return Object.keys(parseRepoConfig(readFileSync45(resolvePath(d, f), "utf8"), f).hooks).length > 0;
26771
27055
  } catch {
26772
27056
  return false;
26773
27057
  }
@@ -26784,7 +27068,7 @@ ${cu.tail.trimEnd()}
26784
27068
  for (const r of repos) {
26785
27069
  const origin2 = originOf(r.dir);
26786
27070
  const row = matchRepoName(origin2, resolvedRemotes);
26787
- const note = !existsSync48(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
27071
+ const note = !existsSync50(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
26788
27072
  process.stdout.write(`${r.name} ${r.dir} ${note}
26789
27073
  `);
26790
27074
  }
@@ -26796,9 +27080,9 @@ ${cu.tail.trimEnd()}
26796
27080
  ` : `${human}
26797
27081
  `);
26798
27082
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
26799
- if (existsSync48(plan2.target) && (existsSync48(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
27083
+ if (existsSync50(plan2.target) && (existsSync50(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
26800
27084
  throw new ReposError(
26801
- existsSync48(`${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`
27085
+ existsSync50(`${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`
26802
27086
  );
26803
27087
  }
26804
27088
  if (dryRun) {
@@ -26816,14 +27100,14 @@ ${cu.tail.trimEnd()}
26816
27100
  throw new ReposError(`${e.message}${authHint}
26817
27101
  crew.yaml was not changed`);
26818
27102
  }
26819
- writeFileSync36(cfg.configFile, plan2.text);
27103
+ writeFileSync38(cfg.configFile, plan2.text);
26820
27104
  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:
26821
27105
  `);
26822
27106
  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}`);
26823
27107
  break;
26824
27108
  }
26825
27109
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
26826
- const top = existsSync48(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
27110
+ const top = existsSync50(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
26827
27111
  if (!top) throw new ReposError(`${target} is not a git checkout`);
26828
27112
  const origin = originOf(top);
26829
27113
  const plan = planRepoAdd({
@@ -26875,15 +27159,15 @@ ${cu.tail.trimEnd()}
26875
27159
  const created = await rowTracker.createRepoRow(rowPayload);
26876
27160
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
26877
27161
  `);
26878
- if (existsSync48(resolvedFile)) {
26879
- const raw = JSON.parse(readFileSync43(resolvedFile, "utf8"));
27162
+ if (existsSync50(resolvedFile)) {
27163
+ const raw = JSON.parse(readFileSync45(resolvedFile, "utf8"));
26880
27164
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
26881
27165
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
26882
- writeFileSync36(resolvedFile, `${JSON.stringify(raw, null, 2)}
27166
+ writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
26883
27167
  `);
26884
27168
  }
26885
27169
  }
26886
- writeFileSync36(cfg.configFile, plan.text);
27170
+ writeFileSync38(cfg.configFile, plan.text);
26887
27171
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
26888
27172
  `);
26889
27173
  } catch (e) {
@@ -27254,7 +27538,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27254
27538
  process.exit(2);
27255
27539
  }
27256
27540
  if (found.shipNameAdopted && !dryRun) {
27257
- writeFileSync36(cfg.configFile, setShipNameInYaml(readFileSync43(cfg.configFile, "utf8"), found.shipNameAdopted));
27541
+ writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync45(cfg.configFile, "utf8"), found.shipNameAdopted));
27258
27542
  cfg.ship.name = found.shipNameAdopted;
27259
27543
  }
27260
27544
  if (found.shipRow) {
@@ -27297,10 +27581,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27297
27581
  }
27298
27582
  }
27299
27583
  let previous;
27300
- const hadPreviousFile = existsSync48(resolvedPath);
27584
+ const hadPreviousFile = existsSync50(resolvedPath);
27301
27585
  if (hadPreviousFile) {
27302
27586
  try {
27303
- previous = JSON.parse(readFileSync43(resolvedPath, "utf8"));
27587
+ previous = JSON.parse(readFileSync45(resolvedPath, "utf8"));
27304
27588
  } catch {
27305
27589
  }
27306
27590
  }
@@ -27430,10 +27714,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27430
27714
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
27431
27715
  `);
27432
27716
  } else {
27433
- mkdirSync36(dirname32(resolvedPath), { recursive: true });
27717
+ mkdirSync38(dirname33(resolvedPath), { recursive: true });
27434
27718
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
27435
27719
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
27436
- writeFileSync36(tmpPath, `${JSON.stringify(resolved, null, 2)}
27720
+ writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
27437
27721
  `);
27438
27722
  renameSync6(tmpPath, resolvedPath);
27439
27723
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -27479,8 +27763,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
27479
27763
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
27480
27764
  `);
27481
27765
  } else {
27482
- mkdirSync36(dirname32(apiKeyFile), { recursive: true });
27483
- writeFileSync36(apiKeyFile, `${apiKeyVar}=${apiKey}
27766
+ mkdirSync38(dirname33(apiKeyFile), { recursive: true });
27767
+ writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
27484
27768
  `, { mode: 384 });
27485
27769
  process.stderr.write(`API key written to ${apiKeyFile}
27486
27770
  `);
@@ -28162,6 +28446,33 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28162
28446
  }
28163
28447
  const pathWt = applyRunWorktree(repoWt.dir, repoWt.config, planWt);
28164
28448
  emit.emit(describeRunWorktreePlan(planWt));
28449
+ if (pathWt && planIsUsable(planWt) && repoWt.config.hooks.intel) {
28450
+ const shellWt = resolveShell(repoWt.config.shell ?? cfg.ship.shell);
28451
+ const paragraph = await startWorktreeIntel({
28452
+ worktree: pathWt,
28453
+ route: routeWt.route,
28454
+ number: String(nWt),
28455
+ stateDir: cfg.ship.stateDir,
28456
+ ticketTag: String(ticketWt.issue_tag ?? ticketWt.issue_id ?? `ISSUE-${nWt}`),
28457
+ branch: planWt.branch ?? "",
28458
+ startupSeconds: cfg.ship.intelStartupSeconds,
28459
+ prepare: async (path) => {
28460
+ for (const f of repoWt.config.worktrees.copy) {
28461
+ const src = join54(repoWt.dir, f);
28462
+ if (!existsSync50(src) || existsSync50(join54(path, f))) continue;
28463
+ mkdirSync38(dirOf2(join54(path, f)), { recursive: true });
28464
+ copyFileSync2(src, join54(path, f));
28465
+ }
28466
+ const setup = repoWt.config.hooks.setup;
28467
+ return setup ? runScript(setup, { cwd: path, shell: shellWt }) : null;
28468
+ },
28469
+ runHook: async (name, cwd, env) => {
28470
+ const script = repoWt.config.hooks[name];
28471
+ return script ? runScript(script, { cwd, env, shell: shellWt, timeoutMs: name === "intel" ? 6e4 : void 0 }) : null;
28472
+ }
28473
+ });
28474
+ emit.emit(paragraph);
28475
+ }
28165
28476
  if (pathWt) process.stdout.write(`${pathWt}
28166
28477
  `);
28167
28478
  if (!planIsUsable(planWt)) process.exitCode = 1;
@@ -28361,7 +28672,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28361
28672
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
28362
28673
  const resumed = await runPeriscopeResume({
28363
28674
  find: async () => hit && { row: hit.row, tag: hit.tag },
28364
- worktreeExists: (p) => existsSync48(p),
28675
+ worktreeExists: (p) => existsSync50(p),
28365
28676
  prepareDropFile,
28366
28677
  startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
28367
28678
  caps: periscopeCaps(cfg.ship),
@@ -28457,10 +28768,10 @@ ${where.join("\n")}`);
28457
28768
  },
28458
28769
  prepare: async (path) => {
28459
28770
  for (const f of repoP.config.worktrees.copy) {
28460
- const src = join52(repoP.dir, f);
28461
- if (!existsSync48(src)) continue;
28462
- mkdirSync36(dirOf2(join52(path, f)), { recursive: true });
28463
- copyFileSync2(src, join52(path, f));
28771
+ const src = join54(repoP.dir, f);
28772
+ if (!existsSync50(src)) continue;
28773
+ mkdirSync38(dirOf2(join54(path, f)), { recursive: true });
28774
+ copyFileSync2(src, join54(path, f));
28464
28775
  }
28465
28776
  const setup = repoP.config.hooks.setup;
28466
28777
  return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
@@ -28478,7 +28789,7 @@ ${where.join("\n")}`);
28478
28789
  mainCheckout: repoP.dir
28479
28790
  });
28480
28791
  }
28481
- if (name === "intel" && intelFlavor(script)) return { code: 1, output: `intel: ${intelFlavor(script)} is not available yet` };
28792
+ if (name === "intel" && lspFlavor(script)) return null;
28482
28793
  return script ? runScript(script, { cwd, env, shell: shellP, timeoutMs: name === "preview" ? 18e4 : name === "intel" ? 6e4 : void 0 }) : null;
28483
28794
  },
28484
28795
  createRow: (fields) => trackerP.createPeriscope(fields),
@@ -28545,6 +28856,7 @@ ${where.join("\n")}`);
28545
28856
  now: () => /* @__PURE__ */ new Date(),
28546
28857
  host: a.host ?? "localhost",
28547
28858
  intelStartupSeconds: cfg.ship.intelStartupSeconds,
28859
+ lspPluginDir: lspFlavor(repoP.config.hooks.intel) ? ensureLspPlugin(cfg.ship.stateDir, lspFlavor(repoP.config.hooks.intel), repoP.dir) : void 0,
28548
28860
  say
28549
28861
  };
28550
28862
  if (dryRun) {
@@ -28769,7 +29081,7 @@ ${where.join("\n")}`);
28769
29081
  };
28770
29082
  const droppedRoutes = [];
28771
29083
  try {
28772
- const rawCfg = parseYaml2(readFileSync43(cfg.configFile, "utf8"));
29084
+ const rawCfg = parseYaml2(readFileSync45(cfg.configFile, "utf8"));
28773
29085
  for (const r of rawCfg?.routes ?? []) {
28774
29086
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
28775
29087
  try {
@@ -28822,8 +29134,8 @@ ${where.join("\n")}`);
28822
29134
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
28823
29135
  break;
28824
29136
  }
28825
- mkdirSync36(cfg.ship.stateDir, { recursive: true });
28826
- writeFileSync36(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
29137
+ mkdirSync38(cfg.ship.stateDir, { recursive: true });
29138
+ writeFileSync38(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
28827
29139
  `);
28828
29140
  process.stdout.write(
28829
29141
  "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"
@@ -28888,7 +29200,7 @@ ${where.join("\n")}`);
28888
29200
  }
28889
29201
  case "log": {
28890
29202
  try {
28891
- const text2 = readFileSync43(cfg.ship.logFile, "utf8").split("\n");
29203
+ const text2 = readFileSync45(cfg.ship.logFile, "utf8").split("\n");
28892
29204
  process.stdout.write(`${text2.slice(-(Number(value("tail")) || 40)).join("\n")}
28893
29205
  `);
28894
29206
  } catch {
@@ -28911,8 +29223,8 @@ ${where.join("\n")}`);
28911
29223
  };
28912
29224
  if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
28913
29225
  const tracker = new Tracker(route2, cfg.ship);
28914
- const configText = readFileSync43(cfg.configFile, "utf8");
28915
- const cfgBase = dirname32(cfg.configFile);
29226
+ const configText = readFileSync45(cfg.configFile, "utf8");
29227
+ const cfgBase = dirname33(cfg.configFile);
28916
29228
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
28917
29229
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
28918
29230
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -28940,7 +29252,7 @@ ${where.join("\n")}`);
28940
29252
  }
28941
29253
  if (configured2.some((r) => r.dir === dir)) {
28942
29254
  try {
28943
- writeFileSync36(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
29255
+ writeFileSync38(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
28944
29256
  } catch (e) {
28945
29257
  if (e instanceof ReposError) fail2(e.message);
28946
29258
  throw e;
@@ -28948,29 +29260,29 @@ ${where.join("\n")}`);
28948
29260
  }
28949
29261
  if (row) {
28950
29262
  await tracker.deleteRepoRow(row.id);
28951
- if (existsSync48(resolvedFile)) {
28952
- const raw = JSON.parse(readFileSync43(resolvedFile, "utf8"));
29263
+ if (existsSync50(resolvedFile)) {
29264
+ const raw = JSON.parse(readFileSync45(resolvedFile, "utf8"));
28953
29265
  if (raw.repoNames) delete raw.repoNames[row.id];
28954
29266
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
28955
- writeFileSync36(resolvedFile, `${JSON.stringify(raw, null, 2)}
29267
+ writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
28956
29268
  `);
28957
29269
  }
28958
29270
  }
28959
- if (existsSync48(join52(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync24(dir, { recursive: true, force: true });
29271
+ if (existsSync50(join54(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
28960
29272
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
28961
29273
  break;
28962
29274
  }
28963
29275
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
28964
- const fresh = !existsSync48(join52(dir, ".git"));
28965
- if (existsSync48(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29276
+ const fresh = !existsSync50(join54(dir, ".git"));
29277
+ if (existsSync50(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
28966
29278
  if (dryRun) {
28967
29279
  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`);
28968
29280
  break;
28969
29281
  }
28970
29282
  if (fresh) {
28971
- mkdirSync36(dir, { recursive: true });
28972
- writeFileSync36(join52(dir, "README.md"), SAMPLE_README);
28973
- writeFileSync36(join52(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
29283
+ mkdirSync38(dir, { recursive: true });
29284
+ writeFileSync38(join54(dir, "README.md"), SAMPLE_README);
29285
+ writeFileSync38(join54(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
28974
29286
  const g = (...a) => {
28975
29287
  const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
28976
29288
  if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -28988,17 +29300,17 @@ ${where.join("\n")}`);
28988
29300
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
28989
29301
  });
28990
29302
  repoId = (await tracker.createRepoRow(payload)).id;
28991
- if (existsSync48(resolvedFile)) {
28992
- const raw = JSON.parse(readFileSync43(resolvedFile, "utf8"));
29303
+ if (existsSync50(resolvedFile)) {
29304
+ const raw = JSON.parse(readFileSync45(resolvedFile, "utf8"));
28993
29305
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
28994
29306
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
28995
- writeFileSync36(resolvedFile, `${JSON.stringify(raw, null, 2)}
29307
+ writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
28996
29308
  `);
28997
29309
  }
28998
29310
  }
28999
29311
  if (!configured2.some((r) => r.dir === dir)) {
29000
29312
  try {
29001
- writeFileSync36(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
29313
+ writeFileSync38(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
29002
29314
  } catch (e) {
29003
29315
  if (e instanceof ReposError) fail2(e.message);
29004
29316
  throw e;
@@ -29344,7 +29656,7 @@ watermark: ${state.watermark()}
29344
29656
  }
29345
29657
  for (const r of repos) {
29346
29658
  const remote = r.config.branch.remote;
29347
- if (existsSync48(r.dir)) {
29659
+ if (existsSync50(r.dir)) {
29348
29660
  pushAuth.push({
29349
29661
  route: c.route,
29350
29662
  repo: r.name,
@@ -29384,7 +29696,11 @@ watermark: ${state.watermark()}
29384
29696
  hooks: r.config.hooks,
29385
29697
  onPath: (cmd) => !!findOnPath(cmd, doctorSchedulerPath),
29386
29698
  intelSyntax: r.config.hooks.intel && !intelFlavor(r.config.hooks.intel) ? checkHookSyntax(r.config.hooks.intel, r.config.shell) : void 0,
29387
- intelFlavor: intelFlavor(r.config.hooks.intel) ? { name: intelFlavor(r.config.hooks.intel), missing: missingPrerequisites(intelFlavor(r.config.hooks.intel), (bin) => !!findOnPath(bin, doctorSchedulerPath)) } : void 0
29699
+ intelFlavor: intelFlavor(r.config.hooks.intel) ? { name: intelFlavor(r.config.hooks.intel), missing: [
29700
+ ...missingPrerequisites(intelFlavor(r.config.hooks.intel), (bin) => !!findOnPath(bin, doctorSchedulerPath)),
29701
+ // CREW-2916: an `*-lsp` flavor needs the language server in the checkout or on PATH.
29702
+ ...lspFlavor(r.config.hooks.intel) && !lspBinaryFound(lspFlavor(r.config.hooks.intel), r.dir, (bin) => !!findOnPath(bin, doctorSchedulerPath)) ? [`${LSP_SERVERS[lspFlavor(r.config.hooks.intel)].bin} (${LSP_SERVERS[lspFlavor(r.config.hooks.intel)].install})`] : []
29703
+ ] } : void 0
29388
29704
  });
29389
29705
  problems.push(...f.problems);
29390
29706
  passed += f.passed;
@@ -29435,7 +29751,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
29435
29751
  }
29436
29752
  try {
29437
29753
  const evFile = eventFileFor(cfg.ship.stateDir);
29438
- const evSize = existsSync48(evFile) ? statSync16(evFile).size : 0;
29754
+ const evSize = existsSync50(evFile) ? statSync16(evFile).size : 0;
29439
29755
  process.stdout.write(
29440
29756
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
29441
29757
  ` : "events log: ok\n"
@@ -29446,7 +29762,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
29446
29762
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run", installContext());
29447
29763
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
29448
29764
  try {
29449
- return readFileSync43(p, "utf8");
29765
+ return readFileSync45(p, "utf8");
29450
29766
  } catch {
29451
29767
  return null;
29452
29768
  }