@tablation/crew 0.92.0 → 0.93.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 +705 -512
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -223,8 +223,8 @@ function shellInitScript(shell) {
223
223
  }
224
224
 
225
225
  // src/config.ts
226
- import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
227
- import { resolve, dirname as dirname4, isAbsolute, join as join11, basename as basename3 } from "path";
226
+ import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
227
+ import { resolve, dirname as dirname4, isAbsolute, join as join12, basename as basename3 } from "path";
228
228
  import { homedir as homedir6, tmpdir } from "os";
229
229
  import { parse as parse2 } from "yaml";
230
230
 
@@ -1818,7 +1818,144 @@ function forgeOfRemote(forges, remoteUrl, hasToken) {
1818
1818
  }
1819
1819
 
1820
1820
  // src/config.ts
1821
- import { getSessionStore, hostFromUrl } from "@tablation/client";
1821
+ import { hostFromUrl } from "@tablation/client";
1822
+
1823
+ // src/secret-store.ts
1824
+ import { createHash as createHash2 } from "crypto";
1825
+ import { accessSync, constants, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync7, chmodSync as chmodSync2 } from "fs";
1826
+ import { join as join10 } from "path";
1827
+ import { getSessionStore as getOsSessionStore } from "@tablation/client";
1828
+ var SECRET_STORE_MODES = ["auto", "os", "file"];
1829
+ function isSecretStoreMode(v) {
1830
+ return typeof v === "string" && SECRET_STORE_MODES.includes(v);
1831
+ }
1832
+ var configured;
1833
+ function configureSecretStore(c) {
1834
+ configured = c;
1835
+ }
1836
+ function keysDir(stateDir) {
1837
+ return join10(stateDir, "keys");
1838
+ }
1839
+ function entryName(host, slug) {
1840
+ return `${createHash2("sha256").update(`${host}\0${slug}`).digest("hex")}.json`;
1841
+ }
1842
+ var FileSessionStore = class {
1843
+ dir;
1844
+ constructor(dir) {
1845
+ this.dir = dir;
1846
+ }
1847
+ ensureDir() {
1848
+ mkdirSync6(this.dir, { recursive: true, mode: 448 });
1849
+ if ((statSync3(this.dir).mode & 63) !== 0) chmodSync2(this.dir, 448);
1850
+ }
1851
+ async get(host, slug) {
1852
+ const file = join10(this.dir, entryName(host, slug));
1853
+ if (!existsSync8(this.dir)) return void 0;
1854
+ accessSync(this.dir, constants.R_OK | constants.X_OK);
1855
+ if (!existsSync8(file)) return void 0;
1856
+ return JSON.parse(readFileSync9(file, "utf8"));
1857
+ }
1858
+ async set(host, slug, session) {
1859
+ this.ensureDir();
1860
+ const file = join10(this.dir, entryName(host, slug));
1861
+ const tmp = `${file}.${process.pid}.tmp`;
1862
+ writeFileSync7(tmp, JSON.stringify(session), { mode: 384 });
1863
+ renameSync2(tmp, file);
1864
+ }
1865
+ async clear(host, slug) {
1866
+ if (!existsSync8(this.dir)) return;
1867
+ rmSync6(join10(this.dir, entryName(host, slug)), { force: true });
1868
+ }
1869
+ /** Whether a key could be written here right now, without leaving anything behind. */
1870
+ writable() {
1871
+ try {
1872
+ this.ensureDir();
1873
+ accessSync(this.dir, constants.R_OK | constants.W_OK | constants.X_OK);
1874
+ const probe = join10(this.dir, `.probe.${process.pid}`);
1875
+ writeFileSync7(probe, "", { mode: 384 });
1876
+ rmSync6(probe, { force: true });
1877
+ return { ok: true };
1878
+ } catch (e) {
1879
+ return { ok: false, reason: e.code ?? e.message };
1880
+ }
1881
+ }
1882
+ };
1883
+ function crewifyStoreError(e) {
1884
+ const msg = e instanceof Error ? e.message : String(e);
1885
+ if (!/TABLATION_API_KEY/.test(msg)) return e instanceof Error ? e : new Error(msg);
1886
+ const out = new Error(
1887
+ msg.replace(
1888
+ /Set TABLATION_API_KEY instead of running `login`\./,
1889
+ "Set CREW_API_KEY for the ship key, or set `ship.secretStore: file` in crew.yaml to keep keys under the state directory."
1890
+ ),
1891
+ { cause: e }
1892
+ );
1893
+ return out;
1894
+ }
1895
+ var CrewHintedStore = class {
1896
+ inner;
1897
+ constructor(inner) {
1898
+ this.inner = inner;
1899
+ }
1900
+ async get(host, slug) {
1901
+ try {
1902
+ return await this.inner.get(host, slug);
1903
+ } catch (e) {
1904
+ throw crewifyStoreError(e);
1905
+ }
1906
+ }
1907
+ async set(host, slug, s) {
1908
+ try {
1909
+ await this.inner.set(host, slug, s);
1910
+ } catch (e) {
1911
+ throw crewifyStoreError(e);
1912
+ }
1913
+ }
1914
+ async clear(host, slug) {
1915
+ try {
1916
+ await this.inner.clear(host, slug);
1917
+ } catch (e) {
1918
+ throw crewifyStoreError(e);
1919
+ }
1920
+ }
1921
+ };
1922
+ function osStoreReachable(env = process.env, platform2 = process.platform, hasBinary = onPath) {
1923
+ if (platform2 !== "linux") return true;
1924
+ if (!hasBinary("secret-tool")) return false;
1925
+ if (env.DBUS_SESSION_BUS_ADDRESS) return true;
1926
+ return !!env.XDG_RUNTIME_DIR && existsSync8(join10(env.XDG_RUNTIME_DIR, "bus"));
1927
+ }
1928
+ function onPath(name) {
1929
+ for (const dir of (process.env.PATH ?? "").split(":")) {
1930
+ if (dir && existsSync8(join10(dir, name))) return true;
1931
+ }
1932
+ return false;
1933
+ }
1934
+ function resolveStoreKind(mode, reachable = osStoreReachable()) {
1935
+ if (mode === "file") return "file";
1936
+ if (mode === "os") return "os";
1937
+ return reachable ? "os" : "file";
1938
+ }
1939
+ function describeSecretStore(c = configured) {
1940
+ const mode = c?.mode ?? "auto";
1941
+ const kind = c ? resolveStoreKind(mode) : "os";
1942
+ return kind === "file" && c ? { kind, mode, dir: keysDir(c.stateDir) } : { kind: "os", mode };
1943
+ }
1944
+ function getSessionStore(c = configured) {
1945
+ const r = describeSecretStore(c);
1946
+ if (r.kind === "file" && r.dir) return new FileSessionStore(r.dir);
1947
+ return new CrewHintedStore(getOsSessionStore());
1948
+ }
1949
+ function describeKeyStore(ship) {
1950
+ const c = { mode: ship.secretStore ?? "auto", stateDir: ship.stateDir };
1951
+ const r = describeSecretStore(c);
1952
+ const why = c.mode === "auto" ? " (auto: no desktop secret service reachable)" : ` (ship.secretStore: ${c.mode})`;
1953
+ if (r.kind === "file" && r.dir) {
1954
+ const w = new FileSessionStore(r.dir).writable();
1955
+ return `file ${r.dir}${why} \u2014 ${w.ok ? "writable" : `NOT writable (${w.reason})`}`;
1956
+ }
1957
+ return `OS secret store${c.mode === "auto" ? "" : why}`;
1958
+ }
1822
1959
 
1823
1960
  // src/platform.ts
1824
1961
  var SHIP_PLATFORMS = ["macos", "linux", "windows"];
@@ -1857,8 +1994,8 @@ function explain(ship, required) {
1857
1994
  }
1858
1995
 
1859
1996
  // src/repo-config.ts
1860
- import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
1861
- import { join as join10, basename as basename2 } from "path";
1997
+ import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
1998
+ import { join as join11, basename as basename2 } from "path";
1862
1999
  import { parse } from "yaml";
1863
2000
  var SPEC_VERSION = 1;
1864
2001
  var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
@@ -1942,8 +2079,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
1942
2079
  var PROVIDERS = ["github", "buildkite", "other", "none"];
1943
2080
  function findRepoConfig(dir) {
1944
2081
  for (const name of REPO_CONFIG_FILENAMES) {
1945
- const p = join10(dir, name);
1946
- if (existsSync8(p)) return p;
2082
+ const p = join11(dir, name);
2083
+ if (existsSync9(p)) return p;
1947
2084
  }
1948
2085
  return null;
1949
2086
  }
@@ -2181,7 +2318,7 @@ function parseRepoConfig(text, file) {
2181
2318
  function loadRepoConfig(dir) {
2182
2319
  const file = findRepoConfig(dir);
2183
2320
  if (!file) return null;
2184
- return parseRepoConfig(readFileSync9(file, "utf8"), file);
2321
+ return parseRepoConfig(readFileSync10(file, "utf8"), file);
2185
2322
  }
2186
2323
  function hookLabel(cfg, hook2) {
2187
2324
  return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
@@ -2503,7 +2640,8 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
2503
2640
  "forges",
2504
2641
  "previewHost",
2505
2642
  "maxPeriscopes",
2506
- "periscopeIdleMinutes"
2643
+ "periscopeIdleMinutes",
2644
+ "secretStore"
2507
2645
  ]);
2508
2646
  var ROUTE_KEYS = /* @__PURE__ */ new Set([
2509
2647
  "route",
@@ -2651,11 +2789,11 @@ function expand(p, base) {
2651
2789
  return isAbsolute(out) ? out : resolve(base, out);
2652
2790
  }
2653
2791
  function resolvePromptsDir(promptSet, crewHome, base) {
2654
- if (!promptSet) return join11(crewHome, "prompts", "default");
2792
+ if (!promptSet) return join12(crewHome, "prompts", "default");
2655
2793
  if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
2656
2794
  return expand(promptSet, base);
2657
2795
  }
2658
- return join11(crewHome, "prompts", promptSet);
2796
+ return join12(crewHome, "prompts", promptSet);
2659
2797
  }
2660
2798
  var Missing = class {
2661
2799
  paths = [];
@@ -2679,7 +2817,7 @@ var Missing = class {
2679
2817
  };
2680
2818
  function crewVersion(crewHome) {
2681
2819
  try {
2682
- const pkg = JSON.parse(readFileSync10(join11(crewHome, "package.json"), "utf8"));
2820
+ const pkg = JSON.parse(readFileSync11(join12(crewHome, "package.json"), "utf8"));
2683
2821
  return pkg.version ?? "0.0.0";
2684
2822
  } catch {
2685
2823
  return "0.0.0";
@@ -2689,26 +2827,26 @@ function configSearchPath(crewHome) {
2689
2827
  if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
2690
2828
  const xdg = process.env.XDG_CONFIG_HOME;
2691
2829
  return [
2692
- ...xdg ? [join11(xdg, "crew", "crew.yaml")] : [],
2693
- join11(homedir6(), ".config", "crew", "crew.yaml"),
2830
+ ...xdg ? [join12(xdg, "crew", "crew.yaml")] : [],
2831
+ join12(homedir6(), ".config", "crew", "crew.yaml"),
2694
2832
  resolve(crewHome, "crew.yaml")
2695
2833
  ];
2696
2834
  }
2697
2835
  function findConfigFile(crewHome) {
2698
2836
  const candidates2 = configSearchPath(crewHome);
2699
- return candidates2.find((c) => existsSync9(c)) ?? candidates2[candidates2.length - 1];
2837
+ return candidates2.find((c) => existsSync10(c)) ?? candidates2[candidates2.length - 1];
2700
2838
  }
2701
- var defaultConfigPath = () => join11(process.env.XDG_CONFIG_HOME ?? join11(homedir6(), ".config"), "crew", "crew.yaml");
2839
+ var defaultConfigPath = () => join12(process.env.XDG_CONFIG_HOME ?? join12(homedir6(), ".config"), "crew", "crew.yaml");
2702
2840
  function routeSlug(route2) {
2703
2841
  return route2.replace(/\//g, "-");
2704
2842
  }
2705
2843
  function resolvedPathFor(stateDir, route2) {
2706
2844
  const [workspace, project] = splitRoute(route2);
2707
- return join11(stateDir, "resolved", workspace, `${project}.json`);
2845
+ return join12(stateDir, "resolved", workspace, `${project}.json`);
2708
2846
  }
2709
2847
  function apiKeyPathFor(stateDir, route2) {
2710
2848
  const [workspace, project] = splitRoute(route2);
2711
- return join11(stateDir, "keys", workspace, `${project}.env`);
2849
+ return join12(stateDir, "keys", workspace, `${project}.env`);
2712
2850
  }
2713
2851
  function splitRoute(route2) {
2714
2852
  const parts = route2.split("/");
@@ -2719,9 +2857,9 @@ function splitRoute(route2) {
2719
2857
  }
2720
2858
  function readResolvedFile(stateDir, route2) {
2721
2859
  const path = resolvedPathFor(stateDir, route2);
2722
- if (!existsSync9(path)) return void 0;
2860
+ if (!existsSync10(path)) return void 0;
2723
2861
  try {
2724
- return JSON.parse(readFileSync10(path, "utf8"));
2862
+ return JSON.parse(readFileSync11(path, "utf8"));
2725
2863
  } catch (e) {
2726
2864
  throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
2727
2865
  }
@@ -2781,18 +2919,18 @@ function parseResolved(raw, m, where) {
2781
2919
  }
2782
2920
  function loadConfig(crewHome, configFile, opts = {}) {
2783
2921
  const file = configFile ?? findConfigFile(crewHome);
2784
- if (opts.text === void 0 && !existsSync9(file)) {
2922
+ if (opts.text === void 0 && !existsSync10(file)) {
2785
2923
  throw new ConfigError(
2786
2924
  `no config found. Looked in:
2787
2925
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
2788
2926
 
2789
2927
  Create the first of those:
2790
- mkdir -p ${dirname4(defaultConfigPath())} && cp ${join11(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2928
+ mkdir -p ${dirname4(defaultConfigPath())} && cp ${join12(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
2791
2929
  );
2792
2930
  }
2793
2931
  let raw;
2794
2932
  try {
2795
- raw = parse2(opts.text ?? readFileSync10(file, "utf8"));
2933
+ raw = parse2(opts.text ?? readFileSync11(file, "utf8"));
2796
2934
  } catch (e) {
2797
2935
  throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
2798
2936
  }
@@ -2846,6 +2984,11 @@ Create the first of those:
2846
2984
  if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
2847
2985
  missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
2848
2986
  }
2987
+ const secretStoreRaw = shipRaw.secretStore === void 0 ? "auto" : shipRaw.secretStore;
2988
+ if (!isSecretStoreMode(secretStoreRaw)) {
2989
+ missing.add(`ship.secretStore must be one of ${SECRET_STORE_MODES.join(" | ")} (got "${String(shipRaw.secretStore)}")`);
2990
+ }
2991
+ const secretStore = isSecretStoreMode(secretStoreRaw) ? secretStoreRaw : "auto";
2849
2992
  const eventsRotateBytes = shipRaw.eventsRotateBytes === void 0 ? DEFAULT_EVENTS_ROTATE_BYTES : Number(shipRaw.eventsRotateBytes);
2850
2993
  if (!Number.isInteger(eventsRotateBytes) || eventsRotateBytes < 1) {
2851
2994
  missing.add(`ship.eventsRotateBytes must be a positive integer (got "${shipRaw.eventsRotateBytes}")`);
@@ -2885,6 +3028,7 @@ Create the first of those:
2885
3028
  const dupe = seen.find((n, i) => n && seen.indexOf(n) !== i);
2886
3029
  if (dupe) missing.add(`routes: duplicate route "${dupe}" \u2014 a route addresses itself, so it must be unique`);
2887
3030
  missing.throwIfAny(file);
3031
+ configureSecretStore({ mode: secretStore, stateDir });
2888
3032
  return {
2889
3033
  ship: {
2890
3034
  name: shipRaw.name ?? "this ship",
@@ -2902,7 +3046,7 @@ Create the first of those:
2902
3046
  useNvm: shipRaw.useNvm !== false,
2903
3047
  nvmSh: shipRaw.nvmSh,
2904
3048
  stateDir,
2905
- logFile: expand(shipRaw.logFile ?? join11(tmpdir(), "crew.log"), base),
3049
+ logFile: expand(shipRaw.logFile ?? join12(tmpdir(), "crew.log"), base),
2906
3050
  userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
2907
3051
  maxConcurrentAgents,
2908
3052
  maxConcurrentAgentsSet,
@@ -2919,7 +3063,8 @@ Create the first of those:
2919
3063
  forges: parseShipForges(shipRaw.forges),
2920
3064
  previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
2921
3065
  maxPeriscopes,
2922
- periscopeIdleMinutes
3066
+ periscopeIdleMinutes,
3067
+ secretStore
2923
3068
  },
2924
3069
  routes,
2925
3070
  crewHome,
@@ -3063,7 +3208,7 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
3063
3208
  const hasRepos = Object.keys(repoDirs).length > 0;
3064
3209
  const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
3065
3210
  const dirIsCloneBase = !c?.dir && !hasRepos && !Array.isArray(c?.repos);
3066
- const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join11(reposBasePath, splitRoute(route2)[0]) : "";
3211
+ const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join12(reposBasePath, splitRoute(route2)[0]) : "";
3067
3212
  const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
3068
3213
  if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
3069
3214
  missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
@@ -3160,8 +3305,8 @@ function repoIdForName(r, name) {
3160
3305
  }
3161
3306
  function resolveApiKey(r) {
3162
3307
  if (r.apiKey) return r.apiKey;
3163
- if (r.apiKeyFile && r.apiKeyVar && existsSync9(r.apiKeyFile)) {
3164
- for (const line of readFileSync10(r.apiKeyFile, "utf8").split("\n")) {
3308
+ if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
3309
+ for (const line of readFileSync11(r.apiKeyFile, "utf8").split("\n")) {
3165
3310
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
3166
3311
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
3167
3312
  }
@@ -3174,9 +3319,9 @@ function resolveApiKey(r) {
3174
3319
  }
3175
3320
  function credentialSource(r) {
3176
3321
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
3177
- if (r.apiKeyFile && r.apiKeyVar && existsSync9(r.apiKeyFile)) {
3322
+ if (r.apiKeyFile && r.apiKeyVar && existsSync10(r.apiKeyFile)) {
3178
3323
  const re = new RegExp(`^${r.apiKeyVar}=`);
3179
- if (readFileSync10(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3324
+ if (readFileSync11(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
3180
3325
  }
3181
3326
  return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
3182
3327
  }
@@ -3232,7 +3377,7 @@ function configuredMembers(rt) {
3232
3377
  return out;
3233
3378
  }
3234
3379
  function defaultRepoDir(basePath, workspace, repoName) {
3235
- return join11(basePath, workspace, repoName);
3380
+ return join12(basePath, workspace, repoName);
3236
3381
  }
3237
3382
  function reposOf(r) {
3238
3383
  const named = Object.entries(r.repos);
@@ -3265,7 +3410,7 @@ function passengerRepoTargets(r) {
3265
3410
  return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
3266
3411
  }
3267
3412
  function passengerCheckoutDir(basePath, workspace, repoName) {
3268
- return join11(basePath, workspace, ".passenger-checkouts", repoName);
3413
+ return join12(basePath, workspace, ".passenger-checkouts", repoName);
3269
3414
  }
3270
3415
  function passengerCheckoutTargets(r) {
3271
3416
  if (!r.hostPassengers) return [];
@@ -3372,10 +3517,10 @@ import { platform } from "process";
3372
3517
  function defaultShell() {
3373
3518
  return platform === "win32" ? { bin: "powershell", args: ["-NoProfile", "-NonInteractive", "-Command"] } : { bin: "bash", args: ["-c"] };
3374
3519
  }
3375
- function resolveShell(configured) {
3376
- if (!configured) return defaultShell();
3377
- if (typeof configured !== "string") return configured;
3378
- const bin = configured.trim();
3520
+ function resolveShell(configured2) {
3521
+ if (!configured2) return defaultShell();
3522
+ if (typeof configured2 !== "string") return configured2;
3523
+ const bin = configured2.trim();
3379
3524
  if (/pwsh|powershell/i.test(bin)) {
3380
3525
  return { bin, args: ["-NoProfile", "-NonInteractive", "-Command"] };
3381
3526
  }
@@ -3552,11 +3697,11 @@ function recoveredFailureHeadlines(o, route2) {
3552
3697
  }
3553
3698
 
3554
3699
  // src/install.ts
3555
- import { dirname as dirname5, join as join12, extname } from "path";
3700
+ import { dirname as dirname5, join as join13, extname } from "path";
3556
3701
  import { homedir as homedir7 } from "os";
3557
- import { accessSync, constants, existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync11, statSync as statSync3, writeFileSync as writeFileSync7, unlinkSync, realpathSync } from "fs";
3702
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync4, writeFileSync as writeFileSync8, unlinkSync, realpathSync } from "fs";
3558
3703
  import { spawnSync as spawnSync4 } from "child_process";
3559
- import { createHash as createHash2 } from "crypto";
3704
+ import { createHash as createHash3 } from "crypto";
3560
3705
 
3561
3706
  // src/runtime-info.ts
3562
3707
  function isCompiledBinary(moduleUrl) {
@@ -3566,7 +3711,7 @@ function isCompiledBinary(moduleUrl) {
3566
3711
  // src/install.ts
3567
3712
  function crewInvocation(crewHome) {
3568
3713
  if (isCompiledBinary(import.meta.url)) return [process.execPath];
3569
- return [process.execPath, join12(crewHome, "bin", "crew")];
3714
+ return [process.execPath, join13(crewHome, "bin", "crew")];
3570
3715
  }
3571
3716
  var INTERVAL_SECONDS = 120;
3572
3717
  function labelFor(crewHome, job) {
@@ -3577,7 +3722,7 @@ function labelFor(crewHome, job) {
3577
3722
  return crewHome;
3578
3723
  }
3579
3724
  })();
3580
- const hash = createHash2("sha1").update(real).digest("hex").slice(0, 8);
3725
+ const hash = createHash3("sha1").update(real).digest("hex").slice(0, 8);
3581
3726
  const stem = job === "release" ? "com.tablation.crew-release" : job === "passengers" ? "com.tablation.crew-passengers" : "com.tablation.crew";
3582
3727
  return `${stem}.${hash}`;
3583
3728
  }
@@ -3604,17 +3749,17 @@ function pathFor(ship) {
3604
3749
  return parts.join(":");
3605
3750
  }
3606
3751
  function findOnPath(bin, searchPath) {
3607
- const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join12(dir, bin));
3752
+ const candidates2 = bin.includes("/") ? [bin] : searchPath.split(":").filter(Boolean).map((dir) => join13(dir, bin));
3608
3753
  for (const candidate of candidates2) {
3609
3754
  try {
3610
- accessSync(candidate, constants.X_OK);
3611
- if (statSync3(candidate).isFile()) return candidate;
3755
+ accessSync2(candidate, constants2.X_OK);
3756
+ if (statSync4(candidate).isFile()) return candidate;
3612
3757
  } catch {
3613
3758
  }
3614
3759
  }
3615
3760
  return void 0;
3616
3761
  }
3617
- var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join12(homedir7(), ".docker", "bin")].join(":");
3762
+ var COMMON_DOCKER_DIRS = ["/usr/local/bin", "/opt/homebrew/bin", join13(homedir7(), ".docker", "bin")].join(":");
3618
3763
  function dockerPathProblem(ship, shellPath = process.env.PATH ?? "") {
3619
3764
  const schedulerPath = pathFor(ship);
3620
3765
  if (findOnPath("docker", schedulerPath)) return void 0;
@@ -3626,7 +3771,7 @@ function xmlEscape(s) {
3626
3771
  }
3627
3772
  function planLaunchd(ship, crewHome, job) {
3628
3773
  const label = labelFor(crewHome, job);
3629
- const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
3774
+ const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
3630
3775
  const crewLog = ship.logFile;
3631
3776
  const schedulerLog = schedulerLogFor(crewLog, job);
3632
3777
  const persistent = job === "run";
@@ -3680,8 +3825,8 @@ ${scheduleKeys}
3680
3825
  }
3681
3826
  function planSystemd(ship, crewHome, job) {
3682
3827
  const label = labelFor(crewHome, job);
3683
- const unitDir = join12(homedir7(), ".config", "systemd", "user");
3684
- const servicePath = join12(unitDir, `${label}.service`);
3828
+ const unitDir = join13(homedir7(), ".config", "systemd", "user");
3829
+ const servicePath = join13(unitDir, `${label}.service`);
3685
3830
  const crewLog = ship.logFile;
3686
3831
  const schedulerLog = schedulerLogFor(crewLog, job);
3687
3832
  const description = job === "release" ? "Tablation crew \u2014 release" : job === "passengers" ? "Tablation crew \u2014 Host Passengers sync" : "Tablation crew \u2014 persistent supervisor loop";
@@ -3721,7 +3866,7 @@ WantedBy=default.target
3721
3866
  schedulerLog
3722
3867
  };
3723
3868
  }
3724
- const timerPath = join12(unitDir, `${label}.timer`);
3869
+ const timerPath = join13(unitDir, `${label}.timer`);
3725
3870
  const service = `# Written by \`crew install\` \u2014 re-run it rather than hand-editing this file.
3726
3871
  [Unit]
3727
3872
  Description=${description}
@@ -3795,7 +3940,7 @@ function planInstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3795
3940
  function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3796
3941
  const label = labelFor(crewHome, job);
3797
3942
  if (hostShip === "macos") {
3798
- const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
3943
+ const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
3799
3944
  return {
3800
3945
  mechanism: "launchd",
3801
3946
  unitPaths: [unitPath],
@@ -3805,18 +3950,18 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3805
3950
  };
3806
3951
  }
3807
3952
  if (hostShip === "linux" && hasSystemd) {
3808
- const unitDir = join12(homedir7(), ".config", "systemd", "user");
3953
+ const unitDir = join13(homedir7(), ".config", "systemd", "user");
3809
3954
  if (job === "run") {
3810
3955
  return {
3811
3956
  mechanism: "systemd",
3812
- unitPaths: [join12(unitDir, `${label}.service`)],
3957
+ unitPaths: [join13(unitDir, `${label}.service`)],
3813
3958
  crontabLine: null,
3814
3959
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.service`]]
3815
3960
  };
3816
3961
  }
3817
3962
  return {
3818
3963
  mechanism: "systemd",
3819
- unitPaths: [join12(unitDir, `${label}.service`), join12(unitDir, `${label}.timer`)],
3964
+ unitPaths: [join13(unitDir, `${label}.service`), join13(unitDir, `${label}.timer`)],
3820
3965
  crontabLine: null,
3821
3966
  unloadCommands: [["systemctl", "--user", "disable", "--now", `${label}.timer`]]
3822
3967
  };
@@ -3834,7 +3979,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
3834
3979
  function planDaemonControl(crewHome, hostShip, hasSystemd) {
3835
3980
  const label = labelFor(crewHome, "run");
3836
3981
  if (hostShip === "macos") {
3837
- const unitPath = join12(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
3982
+ const unitPath = join13(homedir7(), "Library", "LaunchAgents", `${label}.plist`);
3838
3983
  return {
3839
3984
  mechanism: "launchd",
3840
3985
  startCommand: ["launchctl", "load", unitPath],
@@ -3874,8 +4019,8 @@ async function applyInstall(plan, crewHome, dryRun, log) {
3874
4019
  log.emit(`would write ${path}`);
3875
4020
  continue;
3876
4021
  }
3877
- mkdirSync6(dirname5(path), { recursive: true });
3878
- writeFileSync7(path, plan.unitContent[path]);
4022
+ mkdirSync7(dirname5(path), { recursive: true });
4023
+ writeFileSync8(path, plan.unitContent[path]);
3879
4024
  log.emit(`wrote ${path}`);
3880
4025
  }
3881
4026
  if (plan.crontabLine) {
@@ -3929,7 +4074,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
3929
4074
  }
3930
4075
  }
3931
4076
  for (const path of plan.unitPaths) {
3932
- if (!existsSync10(path)) continue;
4077
+ if (!existsSync11(path)) continue;
3933
4078
  if (dryRun) {
3934
4079
  log.emit(`would remove ${path}`);
3935
4080
  continue;
@@ -3948,7 +4093,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
3948
4093
  }
3949
4094
  var CREW_LAUNCHD_PREFIX = "com.tablation.crew";
3950
4095
  function launchAgentsDir() {
3951
- return join12(homedir7(), "Library", "LaunchAgents");
4096
+ return join13(homedir7(), "Library", "LaunchAgents");
3952
4097
  }
3953
4098
  function xmlUnescape(s) {
3954
4099
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
@@ -3965,25 +4110,25 @@ function loadedCrewLabels() {
3965
4110
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
3966
4111
  }
3967
4112
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
3968
- const files = existsSync10(dir) ? readdirSync3(dir) : [];
4113
+ const files = existsSync11(dir) ? readdirSync3(dir) : [];
3969
4114
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
3970
- const plistPath = join12(dir, f);
4115
+ const plistPath = join13(dir, f);
3971
4116
  let program = null;
3972
4117
  try {
3973
- program = programFromPlist(readFileSync11(plistPath, "utf8"));
4118
+ program = programFromPlist(readFileSync12(plistPath, "utf8"));
3974
4119
  } catch {
3975
4120
  }
3976
4121
  return {
3977
4122
  label: f.slice(0, -".plist".length),
3978
4123
  plistPath,
3979
4124
  program,
3980
- programExists: program !== null && existsSync10(program)
4125
+ programExists: program !== null && existsSync11(program)
3981
4126
  };
3982
4127
  });
3983
4128
  const seen = new Set(units.map((u) => u.label));
3984
4129
  for (const label of [...loaded].sort()) {
3985
4130
  if (!label.startsWith(CREW_LAUNCHD_PREFIX) || seen.has(label)) continue;
3986
- units.push({ label, plistPath: join12(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
4131
+ units.push({ label, plistPath: join13(dir, `${label}.plist`), program: null, programExists: false, orphan: true });
3987
4132
  }
3988
4133
  return units;
3989
4134
  }
@@ -3996,9 +4141,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
3996
4141
  log.emit(`would unload and remove ${unit.label}`);
3997
4142
  return;
3998
4143
  }
3999
- if (existsSync10(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4144
+ if (existsSync11(unit.plistPath)) spawnSync4("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
4000
4145
  spawnSync4("launchctl", ["remove", unit.label], { encoding: "utf8" });
4001
- if (existsSync10(unit.plistPath)) unlinkSync(unit.plistPath);
4146
+ if (existsSync11(unit.plistPath)) unlinkSync(unit.plistPath);
4002
4147
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
4003
4148
  }
4004
4149
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -4076,24 +4221,24 @@ function detectSystemd() {
4076
4221
  }
4077
4222
 
4078
4223
  // src/ship-attention.ts
4079
- import { existsSync as existsSync12, statfsSync } from "fs";
4224
+ import { existsSync as existsSync13, statfsSync } from "fs";
4080
4225
 
4081
4226
  // src/git.ts
4082
4227
  import { execFileSync as execFileSync2 } from "child_process";
4083
4228
  import {
4084
- existsSync as existsSync11,
4085
- mkdirSync as mkdirSync7,
4229
+ existsSync as existsSync12,
4230
+ mkdirSync as mkdirSync8,
4086
4231
  openSync as openSync2,
4087
4232
  closeSync as closeSync2,
4088
4233
  writeSync,
4089
- statSync as statSync4,
4234
+ statSync as statSync5,
4090
4235
  unlinkSync as unlinkSync2,
4091
- readFileSync as readFileSync12,
4092
- writeFileSync as writeFileSync8,
4236
+ readFileSync as readFileSync13,
4237
+ writeFileSync as writeFileSync9,
4093
4238
  readdirSync as readdirSync4,
4094
- rmSync as rmSync6
4239
+ rmSync as rmSync7
4095
4240
  } from "fs";
4096
- import { dirname as dirname6, basename as basename4, join as join13 } from "path";
4241
+ import { dirname as dirname6, basename as basename4, join as join14 } from "path";
4097
4242
  var GitError = class extends Error {
4098
4243
  };
4099
4244
  function streamText(x) {
@@ -4474,7 +4619,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
4474
4619
  else if (line.startsWith("branch refs/heads/crew/release-") && path) {
4475
4620
  gitOk(cwd, ["worktree", "remove", "--force", path]);
4476
4621
  const root = dirname6(path);
4477
- if (basename4(root).startsWith("crew-release-")) rmSync6(root, { recursive: true, force: true });
4622
+ if (basename4(root).startsWith("crew-release-")) rmSync7(root, { recursive: true, force: true });
4478
4623
  }
4479
4624
  }
4480
4625
  const stranded = (gitOk(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads/crew/release-*"]) ?? "").split("\n").filter(Boolean);
@@ -4491,12 +4636,12 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
4491
4636
  }
4492
4637
  for (const name of names) {
4493
4638
  if (!name.startsWith("crew-release-")) continue;
4494
- const root = join13(tmp, name);
4639
+ const root = join14(tmp, name);
4495
4640
  try {
4496
- const st = statSync4(root);
4641
+ const st = statSync5(root);
4497
4642
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
4498
- if (existsSync11(join13(root, "checkout"))) continue;
4499
- rmSync6(root, { recursive: true, force: true });
4643
+ if (existsSync12(join14(root, "checkout"))) continue;
4644
+ rmSync7(root, { recursive: true, force: true });
4500
4645
  removed.push(name);
4501
4646
  } catch {
4502
4647
  }
@@ -4545,13 +4690,13 @@ function originUrlOf(dir, remoteName) {
4545
4690
  }
4546
4691
  }
4547
4692
  function ensureRepoCheckout(dir, remote, opts = {}) {
4548
- if (existsSync11(`${dir}/.git`)) return false;
4693
+ if (existsSync12(`${dir}/.git`)) return false;
4549
4694
  if (!remote) {
4550
4695
  throw new GitError(
4551
4696
  `${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`
4552
4697
  );
4553
4698
  }
4554
- mkdirSync7(dirname6(dir), { recursive: true });
4699
+ mkdirSync8(dirname6(dir), { recursive: true });
4555
4700
  const url = cloneUrlFor(remote, opts);
4556
4701
  try {
4557
4702
  execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
@@ -4576,7 +4721,7 @@ function sleepSync(ms3) {
4576
4721
  }
4577
4722
  function acquireCheckoutLock(dir, timeoutMs = 3e4) {
4578
4723
  const lockPath = checkoutLockPath(dir);
4579
- mkdirSync7(dirname6(lockPath), { recursive: true });
4724
+ mkdirSync8(dirname6(lockPath), { recursive: true });
4580
4725
  const deadline = Date.now() + timeoutMs;
4581
4726
  for (; ; ) {
4582
4727
  try {
@@ -4594,7 +4739,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
4594
4739
  const err2 = e;
4595
4740
  if (err2.code !== "EEXIST") throw err2;
4596
4741
  try {
4597
- if (Date.now() - statSync4(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
4742
+ if (Date.now() - statSync5(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
4598
4743
  try {
4599
4744
  unlinkSync2(lockPath);
4600
4745
  } catch {
@@ -4613,17 +4758,17 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
4613
4758
  function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
4614
4759
  const release = acquireCheckoutLock(dir);
4615
4760
  try {
4616
- if (!existsSync11(`${dir}/.git`) && githubRemote) {
4761
+ if (!existsSync12(`${dir}/.git`) && githubRemote) {
4617
4762
  const url = cloneUrlFor(githubRemote, clone);
4618
4763
  const marker = cloneFailureMarker(dir);
4619
- if (existsSync11(marker) && readFileSync12(marker, "utf8").split("\n")[0] === url) {
4620
- return { action: "clone-failed", detail: readFileSync12(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
4764
+ if (existsSync12(marker) && readFileSync13(marker, "utf8").split("\n")[0] === url) {
4765
+ return { action: "clone-failed", detail: readFileSync13(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
4621
4766
  }
4622
4767
  try {
4623
4768
  ensureRepoCheckout(dir, githubRemote, clone);
4624
4769
  } catch (e) {
4625
4770
  if (!(e instanceof GitError)) throw e;
4626
- writeFileSync8(marker, `${url}
4771
+ writeFileSync9(marker, `${url}
4627
4772
  ${e.message}`);
4628
4773
  return { action: "clone-failed", detail: e.message, repeat: false };
4629
4774
  }
@@ -4689,6 +4834,15 @@ function baseAttention(repo, outcome, remote, base, nowIso = (/* @__PURE__ */ ne
4689
4834
  if (outcome.action === "level" || outcome.action === "fast-forwarded") return { clear: baseUnsafeKey(repo) };
4690
4835
  return null;
4691
4836
  }
4837
+ var seatKeyUnreadableKey = (route2, seat) => `seat_key_unreadable:${route2}:${seat}`;
4838
+ function seatKeyUnreadableItem(route2, seat, storeError, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
4839
+ return {
4840
+ kind: "seat_key_unreadable",
4841
+ key: seatKeyUnreadableKey(route2, seat),
4842
+ since: nowIso,
4843
+ message: `${route2}: the ${seat} seat's agent key cannot be read (${storeError}) \u2014 its runs are refused until the key store is readable. Resumes automatically once it is; \`crew doctor\` names the store in use`
4844
+ };
4845
+ }
4692
4846
  var agentLogRefusedKey = (route2) => `agent_log_refused:${route2}`;
4693
4847
  function agentLogRefusedItem(route2, role, status2, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
4694
4848
  return {
@@ -4756,7 +4910,7 @@ function repoStopFor(items, route2) {
4756
4910
  };
4757
4911
  }
4758
4912
  function recheckEnvironmentStops(state, route2, ship, deps = {}) {
4759
- const onPath = deps.onPath ?? findOnPath;
4913
+ const onPath2 = deps.onPath ?? findOnPath;
4760
4914
  const dockerProblem = deps.dockerProblem ?? dockerPathProblem;
4761
4915
  const cleared = [];
4762
4916
  for (const item of state.shipAttention().previous()) {
@@ -4779,7 +4933,7 @@ function recheckEnvironmentStops(state, route2, ship, deps = {}) {
4779
4933
  }
4780
4934
  const script = cfg.hooks[h.hook];
4781
4935
  const cmd = script ? hookCommand(script) : null;
4782
- if (!cmd || onPath(cmd, pathFor(ship))) {
4936
+ if (!cmd || onPath2(cmd, pathFor(ship))) {
4783
4937
  clearShipAttention(state, item.key);
4784
4938
  cleared.push(`${h.repo}/${h.hook}`);
4785
4939
  }
@@ -4830,7 +4984,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
4830
4984
  function reconcileHeldWorktrees(state, repo, log, stillRefused) {
4831
4985
  for (const i of state.shipAttention().previous()) {
4832
4986
  if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
4833
- const gone = !existsSync12(i.held.path);
4987
+ const gone = !existsSync13(i.held.path);
4834
4988
  if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
4835
4989
  clearShipAttention(state, i.key);
4836
4990
  log.emit(
@@ -5081,30 +5235,30 @@ function roleCapWarnings(ship) {
5081
5235
 
5082
5236
  // src/selftest.ts
5083
5237
  import { spawn as spawn3 } from "child_process";
5084
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync19, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "fs";
5238
+ import { existsSync as existsSync22, mkdirSync as mkdirSync13, mkdtempSync, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "fs";
5085
5239
  import { tmpdir as tmpdir2 } from "os";
5086
- import { join as join22 } from "path";
5240
+ import { join as join23 } from "path";
5087
5241
 
5088
5242
  // src/agent.ts
5089
5243
  import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
5090
- import { readFileSync as readFileSync18, existsSync as existsSync20, statSync as statSync6, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
5244
+ import { readFileSync as readFileSync19, existsSync as existsSync21, statSync as statSync7, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
5091
5245
  import { StringDecoder } from "string_decoder";
5092
5246
  import { finished } from "stream/promises";
5093
- import { join as join21, dirname as dirname8, basename as basename7 } from "path";
5094
- import { createHash as createHash3 } from "crypto";
5247
+ import { join as join22, dirname as dirname8, basename as basename7 } from "path";
5248
+ import { createHash as createHash4 } from "crypto";
5095
5249
 
5096
5250
  // src/state.ts
5097
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync13, mkdirSync as mkdirSync8, rmSync as rmSync7, statSync as statSync5, readdirSync as readdirSync5 } from "fs";
5098
- import { join as join14 } from "path";
5251
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync14, mkdirSync as mkdirSync9, rmSync as rmSync8, statSync as statSync6, readdirSync as readdirSync5 } from "fs";
5252
+ import { join as join15 } from "path";
5099
5253
  var EPOCH = "1970-01-01T00:00:00.000Z";
5100
5254
  var State = class _State {
5101
5255
  dir;
5102
5256
  constructor(dir) {
5103
5257
  this.dir = dir;
5104
- mkdirSync8(dir, { recursive: true });
5258
+ mkdirSync9(dir, { recursive: true });
5105
5259
  }
5106
5260
  path(...parts) {
5107
- return join14(this.dir, ...parts);
5261
+ return join15(this.dir, ...parts);
5108
5262
  }
5109
5263
  /**
5110
5264
  * The timestamp every "new since last poll" check is measured against.
@@ -5115,14 +5269,14 @@ var State = class _State {
5115
5269
  */
5116
5270
  watermark() {
5117
5271
  try {
5118
- const v = readFileSync13(this.path(".poll-watermark"), "utf8").trim();
5272
+ const v = readFileSync14(this.path(".poll-watermark"), "utf8").trim();
5119
5273
  return v || EPOCH;
5120
5274
  } catch {
5121
5275
  return EPOCH;
5122
5276
  }
5123
5277
  }
5124
5278
  setWatermark(at) {
5125
- writeFileSync9(this.path(".poll-watermark"), `${at}
5279
+ writeFileSync10(this.path(".poll-watermark"), `${at}
5126
5280
  `);
5127
5281
  }
5128
5282
  /**
@@ -5140,19 +5294,19 @@ var State = class _State {
5140
5294
  }
5141
5295
  /** Whole-crew pause, and the per-role sentinels one level down. */
5142
5296
  isPaused() {
5143
- return existsSync13(this.path(".crew-paused"));
5297
+ return existsSync14(this.path(".crew-paused"));
5144
5298
  }
5145
5299
  isRolePaused(role) {
5146
- return existsSync13(this.path(`.role-paused-${role}`));
5300
+ return existsSync14(this.path(`.role-paused-${role}`));
5147
5301
  }
5148
5302
  pausedRoles(roles) {
5149
5303
  return new Set(roles.filter((r) => this.isRolePaused(r)));
5150
5304
  }
5151
5305
  pause(role) {
5152
- writeFileSync9(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5306
+ writeFileSync10(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
5153
5307
  }
5154
5308
  resume(role) {
5155
- rmSync7(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
5309
+ rmSync8(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
5156
5310
  }
5157
5311
  /**
5158
5312
  * The release phase (merge, deploy, release) has its own sentinel, one
@@ -5160,11 +5314,11 @@ var State = class _State {
5160
5314
  * while agents keep working.
5161
5315
  */
5162
5316
  isReleasePaused() {
5163
- return existsSync13(this.path(".release-paused"));
5317
+ return existsSync14(this.path(".release-paused"));
5164
5318
  }
5165
5319
  setReleasePaused(paused) {
5166
- if (paused) writeFileSync9(this.path(".release-paused"), "");
5167
- else rmSync7(this.path(".release-paused"), { force: true });
5320
+ if (paused) writeFileSync10(this.path(".release-paused"), "");
5321
+ else rmSync8(this.path(".release-paused"), { force: true });
5168
5322
  }
5169
5323
  /**
5170
5324
  * Why a release cycle must not run right now, or null when it may. Every
@@ -5201,7 +5355,7 @@ var State = class _State {
5201
5355
  const name = _State.safe(rawName);
5202
5356
  const file = this.path(`.${name}.lock`);
5203
5357
  try {
5204
- const existing = Number.parseInt(readFileSync13(file, "utf8").trim(), 10);
5358
+ const existing = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
5205
5359
  if (Number.isFinite(existing) && existing > 0) {
5206
5360
  try {
5207
5361
  process.kill(existing, 0);
@@ -5211,13 +5365,13 @@ var State = class _State {
5211
5365
  }
5212
5366
  } catch {
5213
5367
  }
5214
- writeFileSync9(file, `${process.pid}
5368
+ writeFileSync10(file, `${process.pid}
5215
5369
  `);
5216
5370
  return {
5217
5371
  ok: true,
5218
5372
  release: () => {
5219
5373
  try {
5220
- rmSync7(file, { force: true });
5374
+ rmSync8(file, { force: true });
5221
5375
  } catch {
5222
5376
  }
5223
5377
  }
@@ -5239,7 +5393,7 @@ var State = class _State {
5239
5393
  if (!/^\.release-.+\.lock$/.test(n)) continue;
5240
5394
  let pid = 0;
5241
5395
  try {
5242
- pid = Number.parseInt(readFileSync13(this.path(n), "utf8").trim(), 10);
5396
+ pid = Number.parseInt(readFileSync14(this.path(n), "utf8").trim(), 10);
5243
5397
  } catch {
5244
5398
  continue;
5245
5399
  }
@@ -5325,7 +5479,7 @@ var State = class _State {
5325
5479
  ticketInFlight(recordId) {
5326
5480
  const file = this.path(`.${_State.safe(`crew-ticket-${recordId}`)}.lock`);
5327
5481
  try {
5328
- const pid = Number.parseInt(readFileSync13(file, "utf8").trim(), 10);
5482
+ const pid = Number.parseInt(readFileSync14(file, "utf8").trim(), 10);
5329
5483
  if (!Number.isFinite(pid) || pid <= 0) return false;
5330
5484
  process.kill(pid, 0);
5331
5485
  return true;
@@ -5359,7 +5513,7 @@ var State = class _State {
5359
5513
  const heldReported = `.release-held-${suffix}`;
5360
5514
  const read = (f) => {
5361
5515
  try {
5362
- return readFileSync13(this.path(f), "utf8").trim() || null;
5516
+ return readFileSync14(this.path(f), "utf8").trim() || null;
5363
5517
  } catch {
5364
5518
  return null;
5365
5519
  }
@@ -5368,12 +5522,12 @@ var State = class _State {
5368
5522
  blockedCount: () => Number.parseInt(read(blocked) ?? "", 10) || 0,
5369
5523
  noteBlocked: () => {
5370
5524
  const n = (Number.parseInt(read(blocked) ?? "", 10) || 0) + 1;
5371
- writeFileSync9(this.path(blocked), `${n}
5525
+ writeFileSync10(this.path(blocked), `${n}
5372
5526
  `);
5373
5527
  return n;
5374
5528
  },
5375
5529
  clearBlocked: () => {
5376
- rmSync7(this.path(blocked), { force: true });
5530
+ rmSync8(this.path(blocked), { force: true });
5377
5531
  },
5378
5532
  /**
5379
5533
  * The commit a deploy last failed on.
@@ -5385,11 +5539,11 @@ var State = class _State {
5385
5539
  */
5386
5540
  deployFailedSha: () => read(failed),
5387
5541
  noteDeployFailed: (sha) => {
5388
- writeFileSync9(this.path(failed), `${sha}
5542
+ writeFileSync10(this.path(failed), `${sha}
5389
5543
  `);
5390
5544
  },
5391
5545
  clearDeployFailed: () => {
5392
- rmSync7(this.path(failed), { force: true });
5546
+ rmSync8(this.path(failed), { force: true });
5393
5547
  },
5394
5548
  /**
5395
5549
  * Consecutive test-gate failures on the same head (CREW-1368). A failure
@@ -5399,31 +5553,31 @@ var State = class _State {
5399
5553
  noteTestGateFailed: (sha) => {
5400
5554
  const [prev, n] = (read(gateFailed) ?? "").split(" ");
5401
5555
  const next = prev === sha ? (Number.parseInt(n ?? "", 10) || 0) + 1 : 1;
5402
- writeFileSync9(this.path(gateFailed), `${sha} ${next}
5556
+ writeFileSync10(this.path(gateFailed), `${sha} ${next}
5403
5557
  `);
5404
5558
  return next;
5405
5559
  },
5406
5560
  clearTestGateFailed: () => {
5407
- rmSync7(this.path(gateFailed), { force: true });
5561
+ rmSync8(this.path(gateFailed), { force: true });
5408
5562
  },
5409
5563
  /** Whether the board was already told about this head's red gate. */
5410
5564
  heldReported: () => {
5411
5565
  try {
5412
- return readFileSync13(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
5566
+ return readFileSync14(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
5413
5567
  } catch {
5414
5568
  return null;
5415
5569
  }
5416
5570
  },
5417
5571
  noteHeldReported: (signature) => {
5418
- writeFileSync9(this.path(heldReported), `${signature}
5572
+ writeFileSync10(this.path(heldReported), `${signature}
5419
5573
  `);
5420
5574
  },
5421
5575
  clearHeldReported: () => {
5422
- rmSync7(this.path(heldReported), { force: true });
5576
+ rmSync8(this.path(heldReported), { force: true });
5423
5577
  },
5424
5578
  testGateReported: (sha) => read(gateReported) === sha,
5425
5579
  noteTestGateReported: (sha) => {
5426
- writeFileSync9(this.path(gateReported), `${sha}
5580
+ writeFileSync10(this.path(gateReported), `${sha}
5427
5581
  `);
5428
5582
  }
5429
5583
  };
@@ -5446,13 +5600,13 @@ var State = class _State {
5446
5600
  const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
5447
5601
  const read = (f) => {
5448
5602
  try {
5449
- return readFileSync13(this.path(f), "utf8").trim() || null;
5603
+ return readFileSync14(this.path(f), "utf8").trim() || null;
5450
5604
  } catch {
5451
5605
  return null;
5452
5606
  }
5453
5607
  };
5454
5608
  const clear2 = () => {
5455
- for (const f of [streakFile, ticketFile, sinceFile, ticketStreakFile]) rmSync7(this.path(f), { force: true });
5609
+ for (const f of [streakFile, ticketFile, sinceFile, ticketStreakFile]) rmSync8(this.path(f), { force: true });
5456
5610
  };
5457
5611
  return {
5458
5612
  /** Consecutive cycles this route had actionable work and did not win. */
@@ -5479,18 +5633,18 @@ var State = class _State {
5479
5633
  clear2();
5480
5634
  return;
5481
5635
  }
5482
- writeFileSync9(this.path(streakFile), `${this.readInt(streakFile) + 1}
5636
+ writeFileSync10(this.path(streakFile), `${this.readInt(streakFile) + 1}
5483
5637
  `);
5484
5638
  const prior = read(ticketFile);
5485
5639
  if (topTicket && topTicket === prior) {
5486
- writeFileSync9(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
5640
+ writeFileSync10(this.path(ticketStreakFile), `${this.readInt(ticketStreakFile) + 1}
5487
5641
  `);
5488
5642
  } else if (topTicket) {
5489
- writeFileSync9(this.path(ticketFile), `${topTicket}
5643
+ writeFileSync10(this.path(ticketFile), `${topTicket}
5490
5644
  `);
5491
- writeFileSync9(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
5645
+ writeFileSync10(this.path(sinceFile), `${(/* @__PURE__ */ new Date()).toISOString()}
5492
5646
  `);
5493
- writeFileSync9(this.path(ticketStreakFile), "1\n");
5647
+ writeFileSync10(this.path(ticketStreakFile), "1\n");
5494
5648
  }
5495
5649
  }
5496
5650
  };
@@ -5511,14 +5665,14 @@ var State = class _State {
5511
5665
  return {
5512
5666
  previous: () => {
5513
5667
  try {
5514
- const parsed = JSON.parse(readFileSync13(this.path(file), "utf8"));
5668
+ const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
5515
5669
  return parsed && typeof parsed === "object" ? parsed : {};
5516
5670
  } catch {
5517
5671
  return {};
5518
5672
  }
5519
5673
  },
5520
5674
  persist: (next) => {
5521
- writeFileSync9(this.path(file), JSON.stringify(next));
5675
+ writeFileSync10(this.path(file), JSON.stringify(next));
5522
5676
  }
5523
5677
  };
5524
5678
  }
@@ -5534,14 +5688,14 @@ var State = class _State {
5534
5688
  return {
5535
5689
  previous: () => {
5536
5690
  try {
5537
- const parsed = JSON.parse(readFileSync13(this.path(file), "utf8"));
5691
+ const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
5538
5692
  return Array.isArray(parsed) ? parsed.filter((x) => typeof x === "string") : [];
5539
5693
  } catch {
5540
5694
  return [];
5541
5695
  }
5542
5696
  },
5543
5697
  persist: (next) => {
5544
- writeFileSync9(this.path(file), JSON.stringify(next));
5698
+ writeFileSync10(this.path(file), JSON.stringify(next));
5545
5699
  }
5546
5700
  };
5547
5701
  }
@@ -5555,14 +5709,14 @@ var State = class _State {
5555
5709
  return {
5556
5710
  previous: () => {
5557
5711
  try {
5558
- const parsed = JSON.parse(readFileSync13(this.path(file), "utf8"));
5712
+ const parsed = JSON.parse(readFileSync14(this.path(file), "utf8"));
5559
5713
  return Array.isArray(parsed) ? parsed : [];
5560
5714
  } catch {
5561
5715
  return [];
5562
5716
  }
5563
5717
  },
5564
5718
  persist: (next) => {
5565
- writeFileSync9(this.path(file), JSON.stringify(next));
5719
+ writeFileSync10(this.path(file), JSON.stringify(next));
5566
5720
  }
5567
5721
  };
5568
5722
  }
@@ -5575,11 +5729,11 @@ var State = class _State {
5575
5729
  const file = ".release-last-run";
5576
5730
  return {
5577
5731
  touch: () => {
5578
- writeFileSync9(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
5732
+ writeFileSync10(this.path(file), (/* @__PURE__ */ new Date()).toISOString());
5579
5733
  },
5580
5734
  at: () => {
5581
5735
  try {
5582
- return statSync5(this.path(file)).mtimeMs;
5736
+ return statSync6(this.path(file)).mtimeMs;
5583
5737
  } catch {
5584
5738
  return null;
5585
5739
  }
@@ -5588,7 +5742,7 @@ var State = class _State {
5588
5742
  }
5589
5743
  readInt(file) {
5590
5744
  try {
5591
- return Number.parseInt(readFileSync13(this.path(file), "utf8").trim(), 10) || 0;
5745
+ return Number.parseInt(readFileSync14(this.path(file), "utf8").trim(), 10) || 0;
5592
5746
  } catch {
5593
5747
  return 0;
5594
5748
  }
@@ -6071,8 +6225,8 @@ var PERISCOPE_COLUMNS = [
6071
6225
  ];
6072
6226
 
6073
6227
  // src/agent-log.ts
6074
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync6, readFileSync as readFileSync14, rmSync as rmSync8, writeFileSync as writeFileSync10 } from "fs";
6075
- import { join as join15 } from "path";
6228
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
6229
+ import { join as join16 } from "path";
6076
6230
 
6077
6231
  // src/pending-backoff.ts
6078
6232
  var sleepBackoff = (attempts) => Math.min(6e4 * 2 ** Math.max(0, attempts - 1), 30 * 6e4);
@@ -6244,20 +6398,20 @@ async function addRunToTicket(target, t, recordId, run2) {
6244
6398
  };
6245
6399
  }
6246
6400
  }
6247
- var queueDir = (stateDir) => join15(stateDir, "pending-agent-logs");
6401
+ var queueDir = (stateDir) => join16(stateDir, "pending-agent-logs");
6248
6402
  function queueAgentRun(stateDir, name, q) {
6249
- mkdirSync9(queueDir(stateDir), { recursive: true });
6250
- writeFileSync10(join15(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
6403
+ mkdirSync10(queueDir(stateDir), { recursive: true });
6404
+ writeFileSync11(join16(queueDir(stateDir), `${name}.json`), JSON.stringify(q));
6251
6405
  }
6252
6406
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6253
6407
  const dir = queueDir(stateDir);
6254
- if (!existsSync14(dir)) return 0;
6408
+ if (!existsSync15(dir)) return 0;
6255
6409
  let sent = 0;
6256
6410
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6257
- const path = join15(dir, f);
6411
+ const path = join16(dir, f);
6258
6412
  let q;
6259
6413
  try {
6260
- q = JSON.parse(readFileSync14(path, "utf8"));
6414
+ q = JSON.parse(readFileSync15(path, "utf8"));
6261
6415
  } catch {
6262
6416
  continue;
6263
6417
  }
@@ -6276,17 +6430,17 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6276
6430
  attempts: 0,
6277
6431
  nextAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
6278
6432
  });
6279
- rmSync8(path, { force: true });
6433
+ rmSync9(path, { force: true });
6280
6434
  sent += 1;
6281
6435
  log.emit(`sent a queued Agent Log report (${f})`);
6282
6436
  } catch (e) {
6283
6437
  if (!(e instanceof AgentLogUnreachableError)) {
6284
- rmSync8(path, { force: true });
6438
+ rmSync9(path, { force: true });
6285
6439
  log.warn(`dropped a queued Agent Log report (${f}): ${e.message}`);
6286
6440
  continue;
6287
6441
  }
6288
6442
  const attempts = q.attempts + 1;
6289
- writeFileSync10(path, JSON.stringify({
6443
+ writeFileSync11(path, JSON.stringify({
6290
6444
  ...q,
6291
6445
  attempts,
6292
6446
  warned: true,
@@ -6297,20 +6451,20 @@ async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__
6297
6451
  }
6298
6452
  return sent;
6299
6453
  }
6300
- var countersDir = (stateDir) => join15(stateDir, "pending-ticket-counters");
6454
+ var countersDir = (stateDir) => join16(stateDir, "pending-ticket-counters");
6301
6455
  function queueTicketCounters(stateDir, name, q) {
6302
- mkdirSync9(countersDir(stateDir), { recursive: true });
6303
- writeFileSync10(join15(countersDir(stateDir), `${name}.json`), JSON.stringify(q));
6456
+ mkdirSync10(countersDir(stateDir), { recursive: true });
6457
+ writeFileSync11(join16(countersDir(stateDir), `${name}.json`), JSON.stringify(q));
6304
6458
  }
6305
6459
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
6306
6460
  const dir = countersDir(stateDir);
6307
- if (!existsSync14(dir)) return 0;
6461
+ if (!existsSync15(dir)) return 0;
6308
6462
  let sent = 0;
6309
6463
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
6310
- const path = join15(dir, f);
6464
+ const path = join16(dir, f);
6311
6465
  let q;
6312
6466
  try {
6313
- q = JSON.parse(readFileSync14(path, "utf8"));
6467
+ q = JSON.parse(readFileSync15(path, "utf8"));
6314
6468
  } catch {
6315
6469
  continue;
6316
6470
  }
@@ -6319,17 +6473,17 @@ async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /
6319
6473
  const apiKey = await keyFor(q.role);
6320
6474
  if (!apiKey) continue;
6321
6475
  await writeCounters({ ...q.target, apiKey }, q.write);
6322
- rmSync8(path, { force: true });
6476
+ rmSync9(path, { force: true });
6323
6477
  sent += 1;
6324
6478
  log.emit(`added a queued run's spend to ${q.write.ticketReference ?? q.write.recordId} (${f})`);
6325
6479
  } catch (e) {
6326
6480
  if (!isRetryable(e)) {
6327
- rmSync8(path, { force: true });
6481
+ rmSync9(path, { force: true });
6328
6482
  log.warn(`dropped a queued ticket counter update (${f}): ${e.message}`);
6329
6483
  continue;
6330
6484
  }
6331
6485
  const attempts = q.attempts + 1;
6332
- writeFileSync10(path, JSON.stringify({
6486
+ writeFileSync11(path, JSON.stringify({
6333
6487
  ...q,
6334
6488
  attempts,
6335
6489
  warned: true,
@@ -6532,8 +6686,8 @@ var HandoffTranscriptTracker = class {
6532
6686
  };
6533
6687
 
6534
6688
  // src/pending-handoff.ts
6535
- import { existsSync as existsSync17, mkdirSync as mkdirSync10, readdirSync as readdirSync7, readFileSync as readFileSync15, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "fs";
6536
- import { join as join18 } from "path";
6689
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "fs";
6690
+ import { join as join19 } from "path";
6537
6691
 
6538
6692
  // src/ticket-branch.ts
6539
6693
  import { basename as basename6 } from "path";
@@ -7616,13 +7770,13 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
7616
7770
  }
7617
7771
 
7618
7772
  // src/run-worktree.ts
7619
- import { existsSync as existsSync16 } from "fs";
7620
- import { dirname as dirname7, join as join17 } from "path";
7773
+ import { existsSync as existsSync17 } from "fs";
7774
+ import { dirname as dirname7, join as join18 } from "path";
7621
7775
 
7622
7776
  // src/ports.ts
7623
7777
  import { execFileSync as execFileSync4 } from "child_process";
7624
- import { existsSync as existsSync15 } from "fs";
7625
- import { join as join16 } from "path";
7778
+ import { existsSync as existsSync16 } from "fs";
7779
+ import { join as join17 } from "path";
7626
7780
  var run = (cmd, args) => {
7627
7781
  try {
7628
7782
  return execFileSync4(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -7667,7 +7821,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
7667
7821
  }
7668
7822
  function worktreeExists(parent, prefix, n) {
7669
7823
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
7670
- if (existsSync15(join16(parent, name, ".git"))) return true;
7824
+ if (existsSync16(join17(parent, name, ".git"))) return true;
7671
7825
  }
7672
7826
  return false;
7673
7827
  }
@@ -7750,7 +7904,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
7750
7904
  const existing = worktreeForNumber(dir, num);
7751
7905
  const located = locateBranchForTicket(dir, cfg, t, role);
7752
7906
  const baseRef = hasRemote ? `${remote}/${base}` : base;
7753
- if (existing && !existing.prunable && existsSync16(existing.path)) {
7907
+ if (existing && !existing.prunable && existsSync17(existing.path)) {
7754
7908
  const branch = existing.branch;
7755
7909
  if (status(existing.path).length > 0) {
7756
7910
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -7766,7 +7920,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
7766
7920
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
7767
7921
  return { action: "recut", path: existing.path, branch, base: baseRef };
7768
7922
  }
7769
- const path = join17(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
7923
+ const path = join18(dirname7(dir), worktreeForTicket(dir, cfg, t, role));
7770
7924
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
7771
7925
  return { action: "cut-remote", path, branch: located.branch };
7772
7926
  }
@@ -7820,7 +7974,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
7820
7974
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
7821
7975
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
7822
7976
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
7823
- if (wt && existsSync16(wt.path)) {
7977
+ if (wt && existsSync17(wt.path)) {
7824
7978
  if (status(wt.path).length > 0) return false;
7825
7979
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
7826
7980
  }
@@ -7828,7 +7982,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
7828
7982
  }
7829
7983
  async function finishRunWorktree(dir, cfg, branch) {
7830
7984
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
7831
- if (!wt || !existsSync16(wt.path)) return { kind: "none" };
7985
+ if (!wt || !existsSync17(wt.path)) return { kind: "none" };
7832
7986
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
7833
7987
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
7834
7988
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -7840,7 +7994,7 @@ async function finishRunWorktree(dir, cfg, branch) {
7840
7994
  if (pids.length) await killGently(pids);
7841
7995
  }
7842
7996
  gitOk(dir, ["worktree", "remove", wt.path]);
7843
- return existsSync16(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
7997
+ return existsSync17(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
7844
7998
  }
7845
7999
 
7846
8000
  // src/handoff-guard.ts
@@ -8223,20 +8377,20 @@ async function guardRunStrays(route2, ship, tracker, role, workingId, since2, lo
8223
8377
 
8224
8378
  // src/pending-handoff.ts
8225
8379
  var PENDING_MAX_AGE_MS = 48 * 36e5;
8226
- var pendingDirFor = (stateDir) => join18(stateDir, "pending-handoffs");
8227
- var stateDirOfStream = (streamPath) => join18(streamPath, "..", "..");
8380
+ var pendingDirFor = (stateDir) => join19(stateDir, "pending-handoffs");
8381
+ var stateDirOfStream = (streamPath) => join19(streamPath, "..", "..");
8228
8382
  function writePendingHandoff(stateDir, rec) {
8229
8383
  const dir = pendingDirFor(stateDir);
8230
- mkdirSync10(dir, { recursive: true });
8231
- writeFileSync11(join18(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
8384
+ mkdirSync11(dir, { recursive: true });
8385
+ writeFileSync12(join19(dir, `${rec.ticketRecordId}.json`), JSON.stringify(rec, null, 2));
8232
8386
  }
8233
8387
  function listPendingHandoffs(stateDir, route2) {
8234
8388
  const dir = pendingDirFor(stateDir);
8235
- if (!existsSync17(dir)) return [];
8389
+ if (!existsSync18(dir)) return [];
8236
8390
  const out = [];
8237
8391
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
8238
8392
  try {
8239
- const rec = JSON.parse(readFileSync15(join18(dir, f), "utf8"));
8393
+ const rec = JSON.parse(readFileSync16(join19(dir, f), "utf8"));
8240
8394
  if (rec.version === 1 && rec.ticketRecordId && (!route2 || rec.route === route2)) out.push(rec);
8241
8395
  } catch {
8242
8396
  }
@@ -8244,7 +8398,7 @@ function listPendingHandoffs(stateDir, route2) {
8244
8398
  return out;
8245
8399
  }
8246
8400
  function removePendingHandoff(stateDir, ticketRecordId) {
8247
- rmSync9(join18(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
8401
+ rmSync10(join19(pendingDirFor(stateDir), `${ticketRecordId}.json`), { force: true });
8248
8402
  }
8249
8403
  function pendingHandoffFor(stateDir, ticketRecordId) {
8250
8404
  return listPendingHandoffs(stateDir).find((r) => r.ticketRecordId === ticketRecordId);
@@ -8354,8 +8508,8 @@ function expandSkillMarkers(text, lookup2, opts = {}) {
8354
8508
  }
8355
8509
 
8356
8510
  // src/skills.ts
8357
- import { readFileSync as readFileSync17, readdirSync as readdirSync8, existsSync as existsSync19 } from "fs";
8358
- import { join as join20 } from "path";
8511
+ import { readFileSync as readFileSync18, readdirSync as readdirSync8, existsSync as existsSync20 } from "fs";
8512
+ import { join as join21 } from "path";
8359
8513
  import { parse as parseYaml } from "yaml";
8360
8514
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
8361
8515
 
@@ -8371,8 +8525,8 @@ ${additions}`;
8371
8525
  }
8372
8526
 
8373
8527
  // src/agents.ts
8374
- import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
8375
- import { join as join19 } from "path";
8528
+ import { readFileSync as readFileSync17, existsSync as existsSync19 } from "fs";
8529
+ import { join as join20 } from "path";
8376
8530
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
8377
8531
  var PERSONA_NAME = {
8378
8532
  dev: "Developer",
@@ -8386,12 +8540,12 @@ var CREW_AGENT_COLUMN = "agent_id";
8386
8540
  var AgentsSyncError = class extends Error {
8387
8541
  };
8388
8542
  function personaDefaultPrompt(promptsDir, role) {
8389
- const briefPath = join19(promptsDir, "personas", `lane-${role}.md`);
8390
- if (!existsSync18(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
8391
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync16(briefPath, "utf8");
8392
- const commonPath = join19(promptsDir, "personas", "common.md");
8393
- if (!existsSync18(commonPath)) return readFileSync16(briefPath, "utf8");
8394
- return `${readFileSync16(commonPath, "utf8")}${readFileSync16(briefPath, "utf8")}`;
8543
+ const briefPath = join20(promptsDir, "personas", `lane-${role}.md`);
8544
+ if (!existsSync19(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
8545
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync17(briefPath, "utf8");
8546
+ const commonPath = join20(promptsDir, "personas", "common.md");
8547
+ if (!existsSync19(commonPath)) return readFileSync17(briefPath, "utf8");
8548
+ return `${readFileSync17(commonPath, "utf8")}${readFileSync17(briefPath, "utf8")}`;
8395
8549
  }
8396
8550
  async function resolveAgentId(route2, role, opts) {
8397
8551
  const cached = route2.resolved?.agentPersonas?.[role]?.agentId;
@@ -8838,7 +8992,7 @@ function describeCrewLink(o) {
8838
8992
  var SkillSyncError = class extends Error {
8839
8993
  };
8840
8994
  function parseSkillFile(path) {
8841
- const text = readFileSync17(path, "utf8");
8995
+ const text = readFileSync18(path, "utf8");
8842
8996
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
8843
8997
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
8844
8998
  const frontmatter = match[1];
@@ -8851,9 +9005,9 @@ function parseSkillFile(path) {
8851
9005
  return { name: meta.name, description: meta.description, prompt: body, file: path };
8852
9006
  }
8853
9007
  function listLocalSkills(promptsDir) {
8854
- const dir = join20(promptsDir, "skills");
8855
- if (!existsSync19(dir)) return [];
8856
- return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join20(dir, f)));
9008
+ const dir = join21(promptsDir, "skills");
9009
+ if (!existsSync20(dir)) return [];
9010
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join21(dir, f)));
8857
9011
  }
8858
9012
  async function syncSkills(route2, opts) {
8859
9013
  if (!route2.resolved) {
@@ -9055,26 +9209,26 @@ var AgentError = class extends Error {
9055
9209
  };
9056
9210
  var AgentSpawnError = class extends AgentError {
9057
9211
  };
9058
- var digestPathFor = (stateDir, routeLabel, role, runId) => join21(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
9212
+ var digestPathFor = (stateDir, routeLabel, role, runId) => join22(stateDir, `digest-${routeLabel}-${role}${runId ? `-${runId}` : ""}.md`);
9059
9213
  function planAgentRun(o) {
9060
- const briefPath = join21(o.route.promptsDir, "personas", `lane-${o.role}.md`);
9061
- if (!existsSync20(briefPath)) {
9214
+ const briefPath = join22(o.route.promptsDir, "personas", `lane-${o.role}.md`);
9215
+ if (!existsSync21(briefPath)) {
9062
9216
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
9063
9217
  }
9064
- const commonPath = join21(o.route.promptsDir, "personas", "common.md");
9065
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync20(commonPath);
9218
+ const commonPath = join22(o.route.promptsDir, "personas", "common.md");
9219
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync21(commonPath);
9066
9220
  const now = o.now ?? (() => Date.now());
9067
9221
  const routeLabel = routeSlug(o.route.route);
9068
9222
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
9069
- const digestPath2 = ownDigest && existsSync20(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
9223
+ const digestPath2 = ownDigest && existsSync21(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
9070
9224
  let digest;
9071
9225
  let ageSeconds;
9072
- if (existsSync20(digestPath2)) {
9073
- ageSeconds = Math.floor((now() - statSync6(digestPath2).mtimeMs) / 1e3);
9074
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync18(digestPath2, "utf8");
9226
+ if (existsSync21(digestPath2)) {
9227
+ ageSeconds = Math.floor((now() - statSync7(digestPath2).mtimeMs) / 1e3);
9228
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync19(digestPath2, "utf8");
9075
9229
  }
9076
- const common = usesCommon ? readFileSync18(commonPath, "utf8") : "";
9077
- const brief = readFileSync18(briefPath, "utf8");
9230
+ const common = usesCommon ? readFileSync19(commonPath, "utf8") : "";
9231
+ const brief = readFileSync19(briefPath, "utf8");
9078
9232
  const effectiveCommon = o.divergedPrompt ? "" : common;
9079
9233
  const effectiveBrief = appendAdditions(o.divergedPrompt ?? brief, o.personaAdditions);
9080
9234
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -9095,8 +9249,8 @@ function planAgentRun(o) {
9095
9249
  brief: expanded.text,
9096
9250
  digest
9097
9251
  });
9098
- const promptSha = createHash3("sha256").update(expanded.text).digest("hex");
9099
- const streamsDir = join21(o.stateDir, "streams");
9252
+ const promptSha = createHash4("sha256").update(expanded.text).digest("hex");
9253
+ const streamsDir = join22(o.stateDir, "streams");
9100
9254
  const base = `${routeLabel}-${o.role}-${o.cycle}${o.runId ? `-${o.runId}` : ""}`;
9101
9255
  const seat = resolveSeat(o.ship, o.route, o.role, o.agentPersona ?? (o.agentModel ? { model: o.agentModel } : {}), o.repo);
9102
9256
  const { toolchain, model } = seat;
@@ -9169,8 +9323,8 @@ function planAgentRun(o) {
9169
9323
  digestAttached: digest !== void 0,
9170
9324
  digestAgeSeconds: ageSeconds,
9171
9325
  model,
9172
- streamPath: join21(streamsDir, `${base}.jsonl`),
9173
- eventsPath: join21(streamsDir, `${base}.events.jsonl`),
9326
+ streamPath: join22(streamsDir, `${base}.jsonl`),
9327
+ eventsPath: join22(streamsDir, `${base}.events.jsonl`),
9174
9328
  ticket: o.ticket,
9175
9329
  ticketRecordId: o.ticketRecordId ?? null,
9176
9330
  repo: o.repo ?? null,
@@ -9201,7 +9355,7 @@ function describePlan(p) {
9201
9355
  var pidPathFor = (streamPath) => `${streamPath}.pid`;
9202
9356
  function readRunSidecar(streamPath) {
9203
9357
  try {
9204
- const sc = JSON.parse(readFileSync18(pidPathFor(streamPath), "utf8"));
9358
+ const sc = JSON.parse(readFileSync19(pidPathFor(streamPath), "utf8"));
9205
9359
  return typeof sc.pid === "number" && typeof sc.spawnedAt === "string" && typeof sc.role === "string" ? sc : null;
9206
9360
  } catch {
9207
9361
  return null;
@@ -9237,7 +9391,7 @@ function identityOf(plan, adapterHarness) {
9237
9391
  var withoutKey = ({ apiKey: _apiKey, ...rest }) => rest;
9238
9392
  function openSink(path) {
9239
9393
  try {
9240
- mkdirSync11(dirname8(path), { recursive: true });
9394
+ mkdirSync12(dirname8(path), { recursive: true });
9241
9395
  return createWriteStream(path, { flags: "a" });
9242
9396
  } catch {
9243
9397
  return void 0;
@@ -9419,7 +9573,7 @@ var RunObserver = class {
9419
9573
  };
9420
9574
  function writeRunMeta(id, startedAt, liveTicket, extra) {
9421
9575
  try {
9422
- writeFileSync12(`${id.streamPath}.meta.json`, JSON.stringify({
9576
+ writeFileSync13(`${id.streamPath}.meta.json`, JSON.stringify({
9423
9577
  route: id.route,
9424
9578
  role: id.role,
9425
9579
  hint: id.ticket,
@@ -9579,7 +9733,7 @@ function spawnAgent(plan, emit, hintMoved) {
9579
9733
  return new Promise((resolve6, reject) => {
9580
9734
  let streamFd;
9581
9735
  try {
9582
- mkdirSync11(dirname8(plan.streamPath), { recursive: true });
9736
+ mkdirSync12(dirname8(plan.streamPath), { recursive: true });
9583
9737
  streamFd = openSync3(plan.streamPath, "a");
9584
9738
  } catch (e) {
9585
9739
  void endSink(eventsSink);
@@ -9614,7 +9768,7 @@ function spawnAgent(plan, emit, hintMoved) {
9614
9768
  promptSha: plan.promptSha
9615
9769
  };
9616
9770
  try {
9617
- writeFileSync12(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
9771
+ writeFileSync13(pidPathFor(plan.streamPath), JSON.stringify(sidecar));
9618
9772
  } catch {
9619
9773
  }
9620
9774
  }
@@ -9680,7 +9834,7 @@ async function reattachRun(streamPath, sc, emit, o = {}) {
9680
9834
  }
9681
9835
 
9682
9836
  // src/seat-keys.ts
9683
- import { getSessionStore as getSessionStore2, hostFromUrl as hostFromUrl2 } from "@tablation/client";
9837
+ import { hostFromUrl as hostFromUrl2 } from "@tablation/client";
9684
9838
  var SEAT_KEY_ROLES = ROLE_NAMES.filter((r) => r !== "pair");
9685
9839
  function keyIsAdmin(route2) {
9686
9840
  const role = route2.resolved?.role;
@@ -9699,7 +9853,7 @@ function slugFor(route2, agentId) {
9699
9853
  function seatAgentId(route2, role) {
9700
9854
  return route2.resolved?.agentPersonas?.[role]?.agentId;
9701
9855
  }
9702
- async function resolveReleaseKey(route2, getStore = getSessionStore2) {
9856
+ async function resolveReleaseKey(route2, getStore = getSessionStore) {
9703
9857
  const ship = (reason) => ({ kind: "ship", apiKey: resolveApiKey(route2), reason });
9704
9858
  const agentId = route2.resolved?.releaseAgentId;
9705
9859
  if (!agentId) return ship(keyIsAdmin(route2) ? "release seat has no linked Agents row (run `crew agents sync`)" : `release seat ${MEMBER_NO_ROW}`);
@@ -9708,10 +9862,11 @@ async function resolveReleaseKey(route2, getStore = getSessionStore2) {
9708
9862
  if (session?.apiKey) return { kind: "agent", apiKey: session.apiKey, agentId };
9709
9863
  return ship(keyIsAdmin(route2) ? "no agent key stored for the release seat (run `crew agents sync`)" : MEMBER_NO_KEY);
9710
9864
  } catch (e) {
9711
- return ship(describeReadError(e));
9865
+ const reason = describeReadError(e);
9866
+ return { kind: "ship", apiKey: resolveApiKey(route2), reason, storeError: reason };
9712
9867
  }
9713
9868
  }
9714
- async function resolveSeatKey(route2, role, getStore = getSessionStore2) {
9869
+ async function resolveSeatKey(route2, role, getStore = getSessionStore) {
9715
9870
  const ship = (reason) => ({ kind: "ship", apiKey: resolveApiKey(route2), reason });
9716
9871
  if (!SEAT_KEY_ROLES.includes(role)) return ship(`${role} always writes as the ship`);
9717
9872
  const agentId = seatAgentId(route2, role);
@@ -9721,7 +9876,8 @@ async function resolveSeatKey(route2, role, getStore = getSessionStore2) {
9721
9876
  if (session?.apiKey) return { kind: "agent", apiKey: session.apiKey, agentId };
9722
9877
  return ship(keyIsAdmin(route2) ? "no agent key stored for this seat (run `crew agents sync`)" : MEMBER_NO_KEY);
9723
9878
  } catch (e) {
9724
- return ship(describeReadError(e));
9879
+ const reason = describeReadError(e);
9880
+ return { kind: "ship", apiKey: resolveApiKey(route2), reason, storeError: reason };
9725
9881
  }
9726
9882
  }
9727
9883
  function safeStderrLine(e) {
@@ -9735,6 +9891,8 @@ function safeStderrLine(e) {
9735
9891
  return void 0;
9736
9892
  }
9737
9893
  function describeReadError(e) {
9894
+ const fsErr = e;
9895
+ if (typeof fsErr?.path === "string" && typeof fsErr.code === "string") return `key file store unreadable (${fsErr.code})`;
9738
9896
  const code = e?.code;
9739
9897
  const line = safeStderrLine(e);
9740
9898
  const what = typeof code === "number" ? `security exited ${code}` : typeof code === "string" ? `security failed: ${code}` : void 0;
@@ -9763,7 +9921,7 @@ async function mintMissingSeatKeys(route2, o) {
9763
9921
  ];
9764
9922
  let store;
9765
9923
  try {
9766
- store = (o.getStore ?? getSessionStore2)();
9924
+ store = (o.getStore ?? getSessionStore)();
9767
9925
  } catch (e) {
9768
9926
  return targets.map(({ role }) => ({
9769
9927
  role,
@@ -9886,7 +10044,7 @@ async function revokeSeatKeys(route2, o) {
9886
10044
  ];
9887
10045
  let store;
9888
10046
  try {
9889
- store = (o.getStore ?? getSessionStore2)();
10047
+ store = (o.getStore ?? getSessionStore)();
9890
10048
  } catch (e) {
9891
10049
  return targets.map(({ role }) => ({ role, action: "failed", detail: describeStoreError(e) }));
9892
10050
  }
@@ -10117,7 +10275,7 @@ async function runSelftest(i, deps = realDeps) {
10117
10275
  ...adapter.runEnv(spec),
10118
10276
  [KEY_VAR]: i.apiKey
10119
10277
  };
10120
- const cwd = mkdtempSync(join22(tmpdir2(), "crew-selftest-"));
10278
+ const cwd = mkdtempSync(join23(tmpdir2(), "crew-selftest-"));
10121
10279
  try {
10122
10280
  const run2 = await deps.run({
10123
10281
  bin: invocation.bin,
@@ -10139,7 +10297,7 @@ async function runSelftest(i, deps = realDeps) {
10139
10297
  return finish({ ok: false, failure, detail, ...usage2 });
10140
10298
  } finally {
10141
10299
  try {
10142
- rmSync10(cwd, { recursive: true, force: true });
10300
+ rmSync11(cwd, { recursive: true, force: true });
10143
10301
  } catch {
10144
10302
  }
10145
10303
  }
@@ -10150,11 +10308,11 @@ function formatResult(r) {
10150
10308
  const secs = `${(r.ms / 1e3).toFixed(1)}s`;
10151
10309
  return r.ok ? `${r.toolchain}: PASS ${r.detail} (${secs}${tok})` : `${r.toolchain}: FAIL ${r.failure} \u2014 ${r.detail} (${secs})`;
10152
10310
  }
10153
- var fileFor2 = (stateDir) => join22(stateDir, "selftest.json");
10311
+ var fileFor2 = (stateDir) => join23(stateDir, "selftest.json");
10154
10312
  function readSelftest(stateDir) {
10155
10313
  try {
10156
10314
  const p = fileFor2(stateDir);
10157
- return existsSync21(p) ? JSON.parse(readFileSync19(p, "utf8")) : {};
10315
+ return existsSync22(p) ? JSON.parse(readFileSync20(p, "utf8")) : {};
10158
10316
  } catch {
10159
10317
  return {};
10160
10318
  }
@@ -10162,8 +10320,8 @@ function readSelftest(stateDir) {
10162
10320
  function recordSelftest(stateDir, results) {
10163
10321
  const all = readSelftest(stateDir);
10164
10322
  for (const r of results) all[r.toolchain] = r;
10165
- mkdirSync12(stateDir, { recursive: true });
10166
- writeFileSync13(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
10323
+ mkdirSync13(stateDir, { recursive: true });
10324
+ writeFileSync14(fileFor2(stateDir), `${JSON.stringify(all, null, 2)}
10167
10325
  `);
10168
10326
  }
10169
10327
  function doctorSelftestLines(stored, toolchains) {
@@ -10175,7 +10333,7 @@ function doctorSelftestLines(stored, toolchains) {
10175
10333
  }
10176
10334
 
10177
10335
  // src/cli.ts
10178
- import { resolve as resolve5, dirname as dirname22, join as join43, basename as basename13 } from "path";
10336
+ import { resolve as resolve5, dirname as dirname22, join as join44, basename as basename13 } from "path";
10179
10337
  import { fileURLToPath } from "url";
10180
10338
  import { hostname as hostname2, homedir as homedir10 } from "os";
10181
10339
  import { spawnSync as spawnSync7 } from "child_process";
@@ -10296,10 +10454,10 @@ function seats(roster) {
10296
10454
  function holds(roster) {
10297
10455
  return [...roster.values()].filter((m) => m.kind === "hold");
10298
10456
  }
10299
- function buildRoster(configured, crewRows) {
10457
+ function buildRoster(configured2, crewRows) {
10300
10458
  const rows = new Map(crewRows.map((r) => [r.id, r]));
10301
10459
  const roster = /* @__PURE__ */ new Map();
10302
- for (const c of configured) {
10460
+ for (const c of configured2) {
10303
10461
  if (!c.id) continue;
10304
10462
  const row = rows.get(c.id);
10305
10463
  const name = (row?.name ?? "").trim();
@@ -10365,14 +10523,14 @@ function rosterMarkdown(roster, meId) {
10365
10523
  }
10366
10524
 
10367
10525
  // src/connect-resume.ts
10368
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "fs";
10369
- import { dirname as dirname9, join as join23 } from "path";
10526
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "fs";
10527
+ import { dirname as dirname9, join as join24 } from "path";
10370
10528
  function lastSessionFile(stateDir) {
10371
- return join23(stateDir, "connect-session.json");
10529
+ return join24(stateDir, "connect-session.json");
10372
10530
  }
10373
10531
  function readPointers(stateDir) {
10374
10532
  try {
10375
- const parsed = JSON.parse(readFileSync20(lastSessionFile(stateDir), "utf8"));
10533
+ const parsed = JSON.parse(readFileSync21(lastSessionFile(stateDir), "utf8"));
10376
10534
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
10377
10535
  const out = {};
10378
10536
  for (const [host, v] of Object.entries(parsed)) {
@@ -10386,8 +10544,8 @@ function readPointers(stateDir) {
10386
10544
  }
10387
10545
  function writePointers(stateDir, pointers) {
10388
10546
  const file = lastSessionFile(stateDir);
10389
- mkdirSync13(dirname9(file), { recursive: true });
10390
- writeFileSync14(file, `${JSON.stringify(pointers, null, 2)}
10547
+ mkdirSync14(dirname9(file), { recursive: true });
10548
+ writeFileSync15(file, `${JSON.stringify(pointers, null, 2)}
10391
10549
  `, { mode: 384 });
10392
10550
  }
10393
10551
  function rememberSession(stateDir, host, workspaceSlug) {
@@ -10404,7 +10562,7 @@ function forgetWorkspace(stateDir, host, workspaceSlug) {
10404
10562
  const { [host]: _old, ...rest } = all;
10405
10563
  const next = left.length ? { ...rest, [host]: left } : rest;
10406
10564
  if (Object.keys(next).length) writePointers(stateDir, next);
10407
- else rmSync11(lastSessionFile(stateDir), { force: true });
10565
+ else rmSync12(lastSessionFile(stateDir), { force: true });
10408
10566
  } catch {
10409
10567
  }
10410
10568
  }
@@ -10444,7 +10602,7 @@ function forgetSession(stateDir, host) {
10444
10602
  try {
10445
10603
  const { [host]: _gone, ...rest } = readPointers(stateDir);
10446
10604
  if (Object.keys(rest).length) writePointers(stateDir, rest);
10447
- else rmSync11(lastSessionFile(stateDir), { force: true });
10605
+ else rmSync12(lastSessionFile(stateDir), { force: true });
10448
10606
  } catch {
10449
10607
  }
10450
10608
  }
@@ -10472,15 +10630,15 @@ async function signOutSessions(o) {
10472
10630
  }
10473
10631
 
10474
10632
  // src/reset.ts
10475
- import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync21, readdirSync as readdirSync9, rmSync as rmSync12, statSync as statSync7, writeFileSync as writeFileSync15 } from "fs";
10476
- import { dirname as dirname10, join as join24 } from "path";
10633
+ import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync22, readdirSync as readdirSync9, rmSync as rmSync13, statSync as statSync8, writeFileSync as writeFileSync16 } from "fs";
10634
+ import { dirname as dirname10, join as join25 } from "path";
10477
10635
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
10478
10636
  function clonedCheckoutsFile(stateDir) {
10479
- return join24(stateDir, "cloned-checkouts.json");
10637
+ return join25(stateDir, "cloned-checkouts.json");
10480
10638
  }
10481
10639
  function readClonedCheckouts(stateDir) {
10482
10640
  try {
10483
- const parsed = JSON.parse(readFileSync21(clonedCheckoutsFile(stateDir), "utf8"));
10641
+ const parsed = JSON.parse(readFileSync22(clonedCheckoutsFile(stateDir), "utf8"));
10484
10642
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
10485
10643
  } catch {
10486
10644
  return [];
@@ -10490,8 +10648,8 @@ function recordClonedCheckout(stateDir, dir) {
10490
10648
  const known = readClonedCheckouts(stateDir);
10491
10649
  if (known.includes(dir)) return;
10492
10650
  const file = clonedCheckoutsFile(stateDir);
10493
- mkdirSync14(dirname10(file), { recursive: true });
10494
- writeFileSync15(file, `${JSON.stringify([...known, dir], null, 2)}
10651
+ mkdirSync15(dirname10(file), { recursive: true });
10652
+ writeFileSync16(file, `${JSON.stringify([...known, dir], null, 2)}
10495
10653
  `);
10496
10654
  }
10497
10655
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -10543,8 +10701,8 @@ async function runReset(o, fx) {
10543
10701
  for (const dir of readClonedCheckouts(o.stateDir)) steps.push(removeCheckout(dir, o.dryRun, fx));
10544
10702
  }
10545
10703
  steps.push(...removeState(o.stateDir, o.dryRun));
10546
- if (existsSync22(o.configFile)) {
10547
- if (!o.dryRun) rmSync12(o.configFile, { force: true });
10704
+ if (existsSync23(o.configFile)) {
10705
+ if (!o.dryRun) rmSync13(o.configFile, { force: true });
10548
10706
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
10549
10707
  } else {
10550
10708
  steps.push({ step: "config", target: o.configFile, action: "absent" });
@@ -10552,7 +10710,7 @@ async function runReset(o, fx) {
10552
10710
  return steps;
10553
10711
  }
10554
10712
  function removeCheckout(dir, dryRun, fx) {
10555
- if (!existsSync22(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
10713
+ if (!existsSync23(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
10556
10714
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
10557
10715
  if (dirty.length) {
10558
10716
  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" : ""})` };
@@ -10560,21 +10718,21 @@ function removeCheckout(dir, dryRun, fx) {
10560
10718
  if (linkedWorktrees.length) {
10561
10719
  return { step: "checkout", target: dir, action: "kept", detail: `has linked worktrees (${linkedWorktrees.join(", ")}); remove them first` };
10562
10720
  }
10563
- if (!dryRun) rmSync12(dir, { recursive: true, force: true });
10721
+ if (!dryRun) rmSync13(dir, { recursive: true, force: true });
10564
10722
  return { step: "checkout", target: dir, action: mark(dryRun) };
10565
10723
  }
10566
10724
  function removeState(stateDir, dryRun) {
10567
10725
  const out = [];
10568
- const targets = [join24(stateDir, "resolved"), join24(stateDir, "keys"), clonedCheckoutsFile(stateDir), join24(stateDir, "connect-session.json"), join24(stateDir, "reload-requested"), join24(stateDir, "daemon.started")];
10726
+ const targets = [join25(stateDir, "resolved"), join25(stateDir, "keys"), clonedCheckoutsFile(stateDir), join25(stateDir, "connect-session.json"), join25(stateDir, "reload-requested"), join25(stateDir, "daemon.started")];
10569
10727
  try {
10570
10728
  for (const name of readdirSync9(stateDir)) {
10571
- if (name.startsWith(".") && statSync7(join24(stateDir, name)).isFile()) targets.push(join24(stateDir, name));
10729
+ if (name.startsWith(".") && statSync8(join25(stateDir, name)).isFile()) targets.push(join25(stateDir, name));
10572
10730
  }
10573
10731
  } catch {
10574
10732
  }
10575
10733
  for (const t of targets) {
10576
- if (!existsSync22(t)) continue;
10577
- if (!dryRun) rmSync12(t, { recursive: true, force: true });
10734
+ if (!existsSync23(t)) continue;
10735
+ if (!dryRun) rmSync13(t, { recursive: true, force: true });
10578
10736
  out.push({ step: "state", target: t, action: mark(dryRun) });
10579
10737
  }
10580
10738
  if (!out.length) out.push({ step: "state", target: stateDir, action: "absent", detail: "nothing to remove" });
@@ -11361,8 +11519,8 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
11361
11519
  }
11362
11520
 
11363
11521
  // src/poll.ts
11364
- import { writeFileSync as writeFileSync16 } from "fs";
11365
- import { join as join25 } from "path";
11522
+ import { writeFileSync as writeFileSync17 } from "fs";
11523
+ import { join as join26 } from "path";
11366
11524
 
11367
11525
  // src/stalled.ts
11368
11526
  var STALLED_AFTER_CYCLES = 30;
@@ -11669,7 +11827,7 @@ async function decideCycle(o) {
11669
11827
  }
11670
11828
  return decision;
11671
11829
  }
11672
- var digestPath = (stateDir, route2, role, runId) => join25(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
11830
+ var digestPath = (stateDir, route2, role, runId) => join26(stateDir, `digest-${route2}-${role}${runId ? `-${runId}` : ""}.md`);
11673
11831
  function otherRunsRemoved(tickets, own) {
11674
11832
  if (!own) return tickets;
11675
11833
  return tickets.filter((t) => t.id === own.ticket.id || !own.inFlight(t.id));
@@ -11747,7 +11905,7 @@ function writeDigest(o, d, role, stateDir, runId, own) {
11747
11905
 
11748
11906
  ${body}` : body;
11749
11907
  const path = digestPath(stateDir, routeSlug(o.route.route), role, runId);
11750
- writeFileSync16(path, text);
11908
+ writeFileSync17(path, text);
11751
11909
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
11752
11910
  data: { role, bytes: Buffer.byteLength(text) }
11753
11911
  });
@@ -12230,13 +12388,13 @@ Ship ${shipName2} found: ${problem}. The release refuses to squash a ref nobody
12230
12388
  var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "local_ref_skip" && (e.message ?? "").includes(problem));
12231
12389
 
12232
12390
  // src/connect-wizard.ts
12233
- import { delimiter, dirname as dirname11, join as join26 } from "path";
12234
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync23, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17 } from "fs";
12391
+ import { delimiter, dirname as dirname11, join as join27 } from "path";
12392
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync24, mkdirSync as mkdirSync16, writeFileSync as writeFileSync18 } from "fs";
12235
12393
  import { stringify } from "yaml";
12236
12394
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
12237
12395
  function shouldRunWizard(opts) {
12238
12396
  if (!opts.isTTY) return false;
12239
- const exists = opts.configExists ?? existsSync23;
12397
+ const exists = opts.configExists ?? existsSync24;
12240
12398
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
12241
12399
  }
12242
12400
  function firstRunConfigPath() {
@@ -12244,7 +12402,7 @@ function firstRunConfigPath() {
12244
12402
  }
12245
12403
  function defaultIsExecutable(p) {
12246
12404
  try {
12247
- accessSync2(p, constants2.X_OK);
12405
+ accessSync3(p, constants3.X_OK);
12248
12406
  return true;
12249
12407
  } catch {
12250
12408
  return false;
@@ -12253,7 +12411,7 @@ function defaultIsExecutable(p) {
12253
12411
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
12254
12412
  for (const dir of pathEnv.split(delimiter)) {
12255
12413
  if (!dir) continue;
12256
- const candidate = join26(dir, name);
12414
+ const candidate = join27(dir, name);
12257
12415
  if (isExecutable(candidate)) return candidate;
12258
12416
  }
12259
12417
  return void 0;
@@ -12314,9 +12472,9 @@ routes:
12314
12472
  ${routeBlock}`;
12315
12473
  }
12316
12474
  function writeNewConfig(path, text) {
12317
- mkdirSync15(dirname11(path), { recursive: true });
12475
+ mkdirSync16(dirname11(path), { recursive: true });
12318
12476
  try {
12319
- writeFileSync17(path, text, { mode: 384, flag: "wx" });
12477
+ writeFileSync18(path, text, { mode: 384, flag: "wx" });
12320
12478
  return true;
12321
12479
  } catch (e) {
12322
12480
  if (e.code === "EEXIST") return false;
@@ -12348,9 +12506,9 @@ Wrote ${o.configPath}.
12348
12506
  }
12349
12507
 
12350
12508
  // src/connect-json.ts
12351
- import { accessSync as accessSync3, constants as constants3 } from "fs";
12509
+ import { accessSync as accessSync4, constants as constants4 } from "fs";
12352
12510
  import { homedir as homedir8 } from "os";
12353
- import { dirname as dirname12, join as join27 } from "path";
12511
+ import { dirname as dirname12, join as join28 } from "path";
12354
12512
  import { delimiter as delimiter2 } from "path";
12355
12513
  var CHOICE_EXIT_CODE = 3;
12356
12514
  function createEmitter(json, write) {
@@ -12389,12 +12547,12 @@ function agentBinSearchDirs(home, pathEnv, extraPath) {
12389
12547
  const dirs = [
12390
12548
  ...pathEnv.split(delimiter2),
12391
12549
  ...(extraPath ?? "").split(delimiter2),
12392
- join27(home, ".local/bin"),
12393
- join27(home, ".claude/local"),
12550
+ join28(home, ".local/bin"),
12551
+ join28(home, ".claude/local"),
12394
12552
  "/opt/homebrew/bin",
12395
12553
  "/usr/local/bin",
12396
- join27(home, ".npm-global/bin"),
12397
- join27(home, ".volta/bin")
12554
+ join28(home, ".npm-global/bin"),
12555
+ join28(home, ".volta/bin")
12398
12556
  ];
12399
12557
  return [...new Set(dirs.filter(Boolean))];
12400
12558
  }
@@ -12405,7 +12563,7 @@ function findAgentBin(name, env, extraPath, isExec) {
12405
12563
  function flagWizardAnswers(f, env) {
12406
12564
  const isExec = env.isExecutable ?? ((p) => {
12407
12565
  try {
12408
- accessSync3(p, constants3.X_OK);
12566
+ accessSync4(p, constants4.X_OK);
12409
12567
  return true;
12410
12568
  } catch {
12411
12569
  return false;
@@ -12430,8 +12588,8 @@ function flagWizardAnswers(f, env) {
12430
12588
  }
12431
12589
 
12432
12590
  // src/release-run.ts
12433
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync18, existsSync as existsSync24, copyFileSync, mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync2, rmSync as rmSync13 } from "fs";
12434
- import { dirname as dirname13, join as join28 } from "path";
12591
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync19, existsSync as existsSync25, copyFileSync, mkdirSync as mkdirSync17, mkdtempSync as mkdtempSync2, rmSync as rmSync14 } from "fs";
12592
+ import { dirname as dirname13, join as join29 } from "path";
12435
12593
  import { tmpdir as tmpdir3 } from "os";
12436
12594
 
12437
12595
  // src/release.ts
@@ -13172,13 +13330,13 @@ async function bump(o, size) {
13172
13330
  }
13173
13331
  return printed;
13174
13332
  }
13175
- const files = o.repo.release.versionFiles.map((f) => join28(o.cwd, f));
13333
+ const files = o.repo.release.versionFiles.map((f) => join29(o.cwd, f));
13176
13334
  const first = files[0];
13177
- if (!existsSync24(first)) {
13335
+ if (!existsSync25(first)) {
13178
13336
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
13179
13337
  return null;
13180
13338
  }
13181
- const current = JSON.parse(readFileSync22(first, "utf8")).version;
13339
+ const current = JSON.parse(readFileSync23(first, "utf8")).version;
13182
13340
  if (!current) {
13183
13341
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
13184
13342
  return null;
@@ -13191,27 +13349,27 @@ async function bump(o, size) {
13191
13349
  return version;
13192
13350
  }
13193
13351
  for (const f of files) {
13194
- const src = readFileSync22(f, "utf8");
13352
+ const src = readFileSync23(f, "utf8");
13195
13353
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
13196
13354
  if (out === src) {
13197
13355
  o.emit.error(`no version field in ${f}`);
13198
13356
  return null;
13199
13357
  }
13200
- writeFileSync18(f, out);
13358
+ writeFileSync19(f, out);
13201
13359
  }
13202
13360
  return version;
13203
13361
  }
13204
13362
  function writeChangelog(o, version, entries) {
13205
13363
  const file = o.repo.release.changelog;
13206
13364
  if (!file || entries.length === 0) return;
13207
- const path = join28(o.cwd, file);
13365
+ const path = join29(o.cwd, file);
13208
13366
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
13209
13367
  if (o.dryRun) {
13210
13368
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
13211
13369
  return;
13212
13370
  }
13213
- const existing = existsSync24(path) ? readFileSync22(path, "utf8") : "# Changelog\n";
13214
- writeFileSync18(path, insertChangelogSection(existing, section));
13371
+ const existing = existsSync25(path) ? readFileSync23(path, "utf8") : "# Changelog\n";
13372
+ writeFileSync19(path, insertChangelogSection(existing, section));
13215
13373
  }
13216
13374
  async function confirm(o, expected) {
13217
13375
  if (!o.repo.hooks.released) return void 0;
@@ -13294,7 +13452,7 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
13294
13452
  run2(cwd, ["worktree", "remove", "--force", dir]);
13295
13453
  } finally {
13296
13454
  try {
13297
- rmSync13(root, { recursive: true, force: true });
13455
+ rmSync14(root, { recursive: true, force: true });
13298
13456
  } finally {
13299
13457
  run2(cwd, ["worktree", "prune"]);
13300
13458
  run2(cwd, ["branch", "-D", branch]);
@@ -13304,8 +13462,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
13304
13462
  function openReleaseCheckout(o) {
13305
13463
  const { remote, base } = o.repo.branch;
13306
13464
  const branch = `crew/release-${Date.now()}`;
13307
- const root = mkdtempSync2(join28(tmpdir3(), "crew-release-"));
13308
- const dir = join28(root, "checkout");
13465
+ const root = mkdtempSync2(join29(tmpdir3(), "crew-release-"));
13466
+ const dir = join29(root, "checkout");
13309
13467
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
13310
13468
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
13311
13469
  try {
@@ -13315,10 +13473,10 @@ function openReleaseCheckout(o) {
13315
13473
  throw e;
13316
13474
  }
13317
13475
  for (const f of o.repo.worktrees.copy) {
13318
- const src = join28(o.cwd, f);
13319
- if (!existsSync24(src)) continue;
13320
- mkdirSync16(dirname13(join28(dir, f)), { recursive: true });
13321
- copyFileSync(src, join28(dir, f));
13476
+ const src = join29(o.cwd, f);
13477
+ if (!existsSync25(src)) continue;
13478
+ mkdirSync17(dirname13(join29(dir, f)), { recursive: true });
13479
+ copyFileSync(src, join29(dir, f));
13322
13480
  }
13323
13481
  return { dir, branch, close };
13324
13482
  }
@@ -14019,12 +14177,12 @@ Passed: ${passed}.${willClose ? " Closing this alert." : ""}`,
14019
14177
  }
14020
14178
 
14021
14179
  // src/live-runs.ts
14022
- import { existsSync as existsSync26, readdirSync as readdirSync11 } from "fs";
14023
- import { join as join30 } from "path";
14180
+ import { existsSync as existsSync27, readdirSync as readdirSync11 } from "fs";
14181
+ import { join as join31 } from "path";
14024
14182
 
14025
14183
  // src/interrupted.ts
14026
- import { existsSync as existsSync25, readdirSync as readdirSync10, readFileSync as readFileSync23 } from "fs";
14027
- import { join as join29 } from "path";
14184
+ import { existsSync as existsSync26, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
14185
+ import { join as join30 } from "path";
14028
14186
  var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
14029
14187
  function interruptedComment(b) {
14030
14188
  const tree = b.dirty === null ? "The worktree could not be checked." : b.dirty ? "The worktree has uncommitted changes \u2014 they are this seat's own, from that run." : "The worktree is clean.";
@@ -14043,21 +14201,21 @@ var pidAlive = (pid) => {
14043
14201
  };
14044
14202
  function runFinished(streamPath) {
14045
14203
  const path = `${streamPath}.meta.json`;
14046
- if (!existsSync25(path)) return false;
14204
+ if (!existsSync26(path)) return false;
14047
14205
  try {
14048
- return JSON.parse(readFileSync23(path, "utf8")).running !== true;
14206
+ return JSON.parse(readFileSync24(path, "utf8")).running !== true;
14049
14207
  } catch {
14050
14208
  return true;
14051
14209
  }
14052
14210
  }
14053
14211
  function orphanedStreams(o) {
14054
- const dir = join29(o.stateDir, "streams");
14055
- if (!existsSync25(dir)) return [];
14212
+ const dir = join30(o.stateDir, "streams");
14213
+ if (!existsSync26(dir)) return [];
14056
14214
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
14057
14215
  const out = [];
14058
14216
  for (const name of readdirSync10(dir)) {
14059
14217
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
14060
- const streamPath = join29(dir, name);
14218
+ const streamPath = join30(dir, name);
14061
14219
  if (runFinished(streamPath)) continue;
14062
14220
  const sidecar = readRunSidecar(streamPath);
14063
14221
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -14111,12 +14269,12 @@ async function reconcileRuns(o) {
14111
14269
 
14112
14270
  // src/live-runs.ts
14113
14271
  function liveRuns(stateDir, isAlive = runIsAlive) {
14114
- const dir = join30(stateDir, "streams");
14115
- if (!existsSync26(dir)) return [];
14272
+ const dir = join31(stateDir, "streams");
14273
+ if (!existsSync27(dir)) return [];
14116
14274
  const out = [];
14117
14275
  for (const name of readdirSync11(dir)) {
14118
14276
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
14119
- const streamPath = join30(dir, name);
14277
+ const streamPath = join31(dir, name);
14120
14278
  if (runFinished(streamPath)) continue;
14121
14279
  const sidecar = readRunSidecar(streamPath);
14122
14280
  if (!sidecar || !isAlive(sidecar)) continue;
@@ -14450,7 +14608,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
14450
14608
  }
14451
14609
 
14452
14610
  // src/watch.ts
14453
- import { watch as watchFile, existsSync as existsSync27, statSync as statSync8, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
14611
+ import { watch as watchFile, existsSync as existsSync28, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
14454
14612
  var E = "\x1B[";
14455
14613
  var dim = (s) => `${E}2m${s}${E}0m`;
14456
14614
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -14551,8 +14709,8 @@ function renderHeaders(states, now = Date.now()) {
14551
14709
  return states.map((s) => renderHeader(s, now)).join("");
14552
14710
  }
14553
14711
  function readFrom(file, from) {
14554
- if (!existsSync27(file)) return { events: [], offset: from };
14555
- const size = statSync8(file).size;
14712
+ if (!existsSync28(file)) return { events: [], offset: from };
14713
+ const size = statSync9(file).size;
14556
14714
  if (size < from) return readFrom(file, 0);
14557
14715
  if (size === from) return { events: [], offset: from };
14558
14716
  const fd = openSync4(file, "r");
@@ -14794,19 +14952,19 @@ function nextRoles(decision, ran) {
14794
14952
  }
14795
14953
 
14796
14954
  // src/ship-name.ts
14797
- import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync25, renameSync as renameSync2, writeFileSync as writeFileSync20 } from "fs";
14955
+ import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync26, renameSync as renameSync3, writeFileSync as writeFileSync21 } from "fs";
14798
14956
  import { dirname as dirname15 } from "path";
14799
14957
  import { parseDocument as parseDocument2 } from "yaml";
14800
14958
 
14801
14959
  // src/machine-id.ts
14802
14960
  import { execFileSync as execFileSync5 } from "child_process";
14803
- import { createHash as createHash4 } from "crypto";
14804
- import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync24, writeFileSync as writeFileSync19 } from "fs";
14805
- import { dirname as dirname14, join as join31 } from "path";
14961
+ import { createHash as createHash5 } from "crypto";
14962
+ import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync25, writeFileSync as writeFileSync20 } from "fs";
14963
+ import { dirname as dirname14, join as join32 } from "path";
14806
14964
  function machineIdPathFor(stateDir) {
14807
- return join31(stateDir, "machine-id");
14965
+ return join32(stateDir, "machine-id");
14808
14966
  }
14809
- var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
14967
+ var sha256 = (s) => createHash5("sha256").update(s).digest("hex");
14810
14968
  function readRawMachineId(platform2 = process.platform) {
14811
14969
  try {
14812
14970
  if (platform2 === "darwin") {
@@ -14817,8 +14975,8 @@ function readRawMachineId(platform2 = process.platform) {
14817
14975
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
14818
14976
  }
14819
14977
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
14820
- if (existsSync28(p)) {
14821
- const id = readFileSync24(p, "utf8").trim();
14978
+ if (existsSync29(p)) {
14979
+ const id = readFileSync25(p, "utf8").trim();
14822
14980
  if (id) return id;
14823
14981
  }
14824
14982
  }
@@ -14829,15 +14987,15 @@ function readRawMachineId(platform2 = process.platform) {
14829
14987
  function ensureMachineId(stateDir, opts = {}) {
14830
14988
  if (opts.override?.trim()) return sha256(opts.override.trim());
14831
14989
  const path = machineIdPathFor(stateDir);
14832
- if (existsSync28(path)) {
14833
- const cached = readFileSync24(path, "utf8").trim();
14990
+ if (existsSync29(path)) {
14991
+ const cached = readFileSync25(path, "utf8").trim();
14834
14992
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
14835
14993
  }
14836
14994
  const raw = (opts.read ?? readRawMachineId)();
14837
14995
  if (!raw) return void 0;
14838
14996
  const hashed = sha256(raw);
14839
- mkdirSync17(dirname14(path), { recursive: true });
14840
- writeFileSync19(path, `${hashed}
14997
+ mkdirSync18(dirname14(path), { recursive: true });
14998
+ writeFileSync20(path, `${hashed}
14841
14999
  `, { mode: 384 });
14842
15000
  return hashed;
14843
15001
  }
@@ -14908,34 +15066,34 @@ async function syncRouteName(i) {
14908
15066
  return { status: "ok", ...base, action, name: i.local };
14909
15067
  }
14910
15068
  function readSyncedName(resolvedFile) {
14911
- if (!existsSync29(resolvedFile)) return void 0;
15069
+ if (!existsSync30(resolvedFile)) return void 0;
14912
15070
  try {
14913
- const v = JSON.parse(readFileSync25(resolvedFile, "utf8")).shipName;
15071
+ const v = JSON.parse(readFileSync26(resolvedFile, "utf8")).shipName;
14914
15072
  return typeof v === "string" ? v : void 0;
14915
15073
  } catch {
14916
15074
  return void 0;
14917
15075
  }
14918
15076
  }
14919
15077
  function writeSyncedName(resolvedFile, name) {
14920
- if (!existsSync29(resolvedFile)) return;
15078
+ if (!existsSync30(resolvedFile)) return;
14921
15079
  let raw;
14922
15080
  try {
14923
- raw = JSON.parse(readFileSync25(resolvedFile, "utf8"));
15081
+ raw = JSON.parse(readFileSync26(resolvedFile, "utf8"));
14924
15082
  } catch {
14925
15083
  return;
14926
15084
  }
14927
15085
  if (raw.shipName === name) return;
14928
15086
  raw.shipName = name;
14929
- mkdirSync18(dirname15(resolvedFile), { recursive: true });
15087
+ mkdirSync19(dirname15(resolvedFile), { recursive: true });
14930
15088
  const tmp = `${resolvedFile}.tmp${process.pid}`;
14931
- writeFileSync20(tmp, `${JSON.stringify(raw, null, 2)}
15089
+ writeFileSync21(tmp, `${JSON.stringify(raw, null, 2)}
14932
15090
  `);
14933
- renameSync2(tmp, resolvedFile);
15091
+ renameSync3(tmp, resolvedFile);
14934
15092
  }
14935
15093
  async function reconcileShipName(i) {
14936
15094
  let local = i.ship.name;
14937
15095
  try {
14938
- local = shipNameOfYaml(readFileSync25(i.configFile, "utf8")) ?? local;
15096
+ local = shipNameOfYaml(readFileSync26(i.configFile, "utf8")) ?? local;
14939
15097
  } catch {
14940
15098
  }
14941
15099
  const resolvedFile = resolvedPathFor(i.stateDir, i.route);
@@ -14959,8 +15117,8 @@ async function reconcileShipName(i) {
14959
15117
  }
14960
15118
  if (result.status !== "ok") return result;
14961
15119
  if (result.action === "adopt" || result.action === "row-wins") {
14962
- const text = readFileSync25(i.configFile, "utf8");
14963
- writeFileSync20(i.configFile, setShipNameInYaml(text, result.name));
15120
+ const text = readFileSync26(i.configFile, "utf8");
15121
+ writeFileSync21(i.configFile, setShipNameInYaml(text, result.name));
14964
15122
  i.ship.name = result.name;
14965
15123
  if (result.action === "row-wins") {
14966
15124
  i.emit?.warn(
@@ -15558,7 +15716,6 @@ ${opts.area ? ` area: "${opts.area}"
15558
15716
 
15559
15717
  // src/cli.ts
15560
15718
  import {
15561
- getSessionStore as getSessionStore3,
15562
15719
  hostFromUrl as hostFromUrl4,
15563
15720
  loginWithDeviceCode,
15564
15721
  DeviceLoginError
@@ -15610,7 +15767,7 @@ function describeCapability(i) {
15610
15767
  }
15611
15768
 
15612
15769
  // src/agents-sync-run.ts
15613
- import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync26, writeFileSync as writeFileSync21 } from "fs";
15770
+ import { existsSync as existsSync31, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync22 } from "fs";
15614
15771
  import { dirname as dirname16 } from "path";
15615
15772
  async function runAgentsSync(target, o) {
15616
15773
  const dryRun = o.dryRun ?? false;
@@ -15644,12 +15801,12 @@ async function runAgentsSync(target, o) {
15644
15801
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
15645
15802
  if (!dryRun) {
15646
15803
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
15647
- const raw = existsSync30(resolvedPath) ? JSON.parse(readFileSync26(resolvedPath, "utf8")) : {};
15804
+ const raw = existsSync31(resolvedPath) ? JSON.parse(readFileSync27(resolvedPath, "utf8")) : {};
15648
15805
  raw.agentPersonas = personaResult.agentPersonas;
15649
15806
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
15650
15807
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
15651
- mkdirSync19(dirname16(resolvedPath), { recursive: true });
15652
- writeFileSync21(resolvedPath, `${JSON.stringify(raw, null, 2)}
15808
+ mkdirSync20(dirname16(resolvedPath), { recursive: true });
15809
+ writeFileSync22(resolvedPath, `${JSON.stringify(raw, null, 2)}
15653
15810
  `);
15654
15811
  if (target.resolved) {
15655
15812
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -15664,7 +15821,7 @@ async function runAgentsSync(target, o) {
15664
15821
  }
15665
15822
 
15666
15823
  // src/logbook.ts
15667
- import { createHash as createHash5 } from "crypto";
15824
+ import { createHash as createHash6 } from "crypto";
15668
15825
  import { TablationClient as TablationClient4 } from "@tablation/client";
15669
15826
  var LogbookError = class extends Error {
15670
15827
  };
@@ -15737,7 +15894,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
15737
15894
  };
15738
15895
  }
15739
15896
  const prompt = appendAdditions(match.prompt ?? "", match.prompt_additions);
15740
- const actualSha = createHash5("sha256").update(prompt).digest("hex");
15897
+ const actualSha = createHash6("sha256").update(prompt).digest("hex");
15741
15898
  const exact = !!entry.prompt_sha && actualSha === entry.prompt_sha;
15742
15899
  return {
15743
15900
  entry,
@@ -15753,7 +15910,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
15753
15910
 
15754
15911
  // src/repos-check.ts
15755
15912
  import { spawnSync as spawnSync6 } from "child_process";
15756
- import { readFileSync as readFileSync27 } from "fs";
15913
+ import { readFileSync as readFileSync28 } from "fs";
15757
15914
  import { basename as basename8 } from "path";
15758
15915
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
15759
15916
  function checkHookSyntax(script, shell) {
@@ -15794,7 +15951,7 @@ function checkRepoSpec(dir, opts = {}) {
15794
15951
  }
15795
15952
  let parsed;
15796
15953
  try {
15797
- parsed = parseRepoConfig(readFileSync27(file, "utf8"), file);
15954
+ parsed = parseRepoConfig(readFileSync28(file, "utf8"), file);
15798
15955
  } catch (e) {
15799
15956
  if (!(e instanceof RepoConfigError)) throw e;
15800
15957
  return { ...empty, file, error: e.message, exitCode: 1 };
@@ -15851,7 +16008,7 @@ import { isMap as isMap2, parseDocument as parseDocument4, YAMLMap } from "yaml"
15851
16008
 
15852
16009
  // src/repos-cmd.ts
15853
16010
  import { homedir as homedir9 } from "os";
15854
- import { basename as basename9, join as join32, resolve as resolve3 } from "path";
16011
+ import { basename as basename9, join as join33, resolve as resolve3 } from "path";
15855
16012
  import { isMap, isSeq, parseDocument as parseDocument3, stringify as stringify2 } from "yaml";
15856
16013
  var ReposError = class extends Error {
15857
16014
  };
@@ -16129,7 +16286,7 @@ function planRepoClone(i) {
16129
16286
  }
16130
16287
  const ship = doc.get("ship", true);
16131
16288
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
16132
- const target = i.dir ? expandDir(i.dir, i.base) : join32(expandDir(basePath, i.base), i.route.split("/")[0], name);
16289
+ const target = i.dir ? expandDir(i.dir, i.base) : join33(expandDir(basePath, i.base), i.route.split("/")[0], name);
16133
16290
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
16134
16291
  return { ...add, remote, target };
16135
16292
  }
@@ -16528,7 +16685,7 @@ ${run2.err}` });
16528
16685
  }
16529
16686
 
16530
16687
  // src/sample-ticket.ts
16531
- import { basename as basename10, dirname as dirname18, join as join33 } from "path";
16688
+ import { basename as basename10, dirname as dirname18, join as join34 } from "path";
16532
16689
  var SAMPLE_REPO = "crew-sample";
16533
16690
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
16534
16691
  var SAMPLE_MARKER = "sample: true";
@@ -16551,8 +16708,8 @@ has something safe to work on: its test and build hooks always pass, it has no
16551
16708
  remote, and \`crew sample-ticket --remove\` deletes it.
16552
16709
  `;
16553
16710
  function sampleDir(existing, reposBase, workspace) {
16554
- if (existing.length > 0) return join33(dirname18(existing[0]), SAMPLE_REPO);
16555
- return join33(reposBase, workspace, SAMPLE_REPO);
16711
+ if (existing.length > 0) return join34(dirname18(existing[0]), SAMPLE_REPO);
16712
+ return join34(reposBase, workspace, SAMPLE_REPO);
16556
16713
  }
16557
16714
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
16558
16715
  function sampleDescription() {
@@ -16644,8 +16801,8 @@ function schedulerInstalled(plan, exists, crontab) {
16644
16801
  }
16645
16802
 
16646
16803
  // src/worktree-sweep.ts
16647
- import { existsSync as existsSync31, readFileSync as readFileSync28, readdirSync as readdirSync12 } from "fs";
16648
- import { basename as basename11, dirname as dirname19, join as join34, resolve as resolve4 } from "path";
16804
+ import { existsSync as existsSync32, readFileSync as readFileSync29, readdirSync as readdirSync12 } from "fs";
16805
+ import { basename as basename11, dirname as dirname19, join as join35, resolve as resolve4 } from "path";
16649
16806
  function planWorktreeSweep(target, tickets, contract) {
16650
16807
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
16651
16808
  const verified = contract.statuses.verified;
@@ -16722,7 +16879,7 @@ function siblingWorktreeDirs(target) {
16722
16879
  for (const name of names) {
16723
16880
  if (!name.startsWith(stem)) continue;
16724
16881
  const m = DIR_NUMBER_SUFFIXED.exec(name);
16725
- const path = join34(parent, name);
16882
+ const path = join35(parent, name);
16726
16883
  if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
16727
16884
  }
16728
16885
  return out;
@@ -16757,7 +16914,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
16757
16914
  let removed = 0;
16758
16915
  let keptBranches = 0;
16759
16916
  for (const a of actions) {
16760
- if (!dryRun && base && !a.keepBranch && existsSync31(a.path) && isHandBackMerge(a.path, base)) {
16917
+ if (!dryRun && base && !a.keepBranch && existsSync32(a.path) && isHandBackMerge(a.path, base)) {
16761
16918
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
16762
16919
  log.emit(
16763
16920
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -16765,7 +16922,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
16765
16922
  );
16766
16923
  }
16767
16924
  }
16768
- if (!dryRun && existsSync31(a.path)) {
16925
+ if (!dryRun && existsSync32(a.path)) {
16769
16926
  let dirty = false;
16770
16927
  try {
16771
16928
  dirty = status(a.path).length > 0;
@@ -16776,7 +16933,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
16776
16933
  continue;
16777
16934
  }
16778
16935
  }
16779
- if (!dryRun && a.requirePushed && a.branch && existsSync31(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
16936
+ if (!dryRun && a.requirePushed && a.branch && existsSync32(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
16780
16937
  refuse(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
16781
16938
  continue;
16782
16939
  }
@@ -16825,7 +16982,7 @@ function isHandBackMerge(path, base) {
16825
16982
  if (!msgPath) return false;
16826
16983
  let first;
16827
16984
  try {
16828
- first = readFileSync28(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
16985
+ first = readFileSync29(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
16829
16986
  } catch {
16830
16987
  return false;
16831
16988
  }
@@ -16930,16 +17087,16 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
16930
17087
  }
16931
17088
 
16932
17089
  // src/stream-sweep.ts
16933
- import { existsSync as existsSync32, readdirSync as readdirSync13, rmSync as rmSync14, statSync as statSync9 } from "fs";
16934
- import { join as join35 } from "path";
17090
+ import { existsSync as existsSync33, readdirSync as readdirSync13, rmSync as rmSync15, statSync as statSync10 } from "fs";
17091
+ import { join as join36 } from "path";
16935
17092
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
16936
17093
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
16937
- const dir = join35(stateDir, "streams");
16938
- if (!existsSync32(dir)) return [];
17094
+ const dir = join36(stateDir, "streams");
17095
+ if (!existsSync33(dir)) return [];
16939
17096
  const cutoff = now - retentionDays * DAY_MS2;
16940
- return readdirSync13(dir).map((name) => join35(dir, name)).filter((path) => {
17097
+ return readdirSync13(dir).map((name) => join36(dir, name)).filter((path) => {
16941
17098
  try {
16942
- return statSync9(path).mtimeMs < cutoff;
17099
+ return statSync10(path).mtimeMs < cutoff;
16943
17100
  } catch {
16944
17101
  return false;
16945
17102
  }
@@ -16953,7 +17110,7 @@ function applyStreamSweep(paths, dryRun, log) {
16953
17110
  continue;
16954
17111
  }
16955
17112
  try {
16956
- rmSync14(path, { force: true });
17113
+ rmSync15(path, { force: true });
16957
17114
  removed++;
16958
17115
  log.emit(`removed stream artifact ${path}`, { step: "worktree" });
16959
17116
  } catch (e) {
@@ -16963,11 +17120,11 @@ function applyStreamSweep(paths, dryRun, log) {
16963
17120
  return removed;
16964
17121
  }
16965
17122
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
16966
- if (!existsSync32(stateDir)) return [];
17123
+ if (!existsSync33(stateDir)) return [];
16967
17124
  const cutoff = now - retentionDays * DAY_MS2;
16968
- return readdirSync13(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join35(stateDir, name)).filter((path) => {
17125
+ return readdirSync13(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join36(stateDir, name)).filter((path) => {
16969
17126
  try {
16970
- return statSync9(path).mtimeMs < cutoff;
17127
+ return statSync10(path).mtimeMs < cutoff;
16971
17128
  } catch {
16972
17129
  return false;
16973
17130
  }
@@ -16975,30 +17132,30 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
16975
17132
  }
16976
17133
 
16977
17134
  // src/cli.ts
16978
- import { realpathSync as realpathSync3, readFileSync as readFileSync34, existsSync as existsSync39, mkdirSync as mkdirSync25, writeFileSync as writeFileSync26, rmSync as rmSync18, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync13, readdirSync as readdirSync18 } from "fs";
17135
+ import { realpathSync as realpathSync3, readFileSync as readFileSync35, existsSync as existsSync40, mkdirSync as mkdirSync26, writeFileSync as writeFileSync27, rmSync as rmSync19, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync4, statSync as statSync14, readdirSync as readdirSync18 } from "fs";
16979
17136
  import { createInterface } from "readline/promises";
16980
17137
 
16981
17138
  // src/periscope-cmd.ts
16982
- import { existsSync as existsSync33, mkdirSync as mkdirSync20, readFileSync as readFileSync29, rmSync as rmSync15, writeFileSync as writeFileSync22 } from "fs";
16983
- import { join as join36 } from "path";
17139
+ import { existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync30, rmSync as rmSync16, writeFileSync as writeFileSync23 } from "fs";
17140
+ import { join as join37 } from "path";
16984
17141
  var PeriscopeError = class extends Error {
16985
17142
  };
16986
- var priorDir = (stateDir) => join36(stateDir, "periscope-priors");
16987
- var priorPath = (stateDir, ticketRecordId) => join36(priorDir(stateDir), `${ticketRecordId}.json`);
17143
+ var priorDir = (stateDir) => join37(stateDir, "periscope-priors");
17144
+ var priorPath = (stateDir, ticketRecordId) => join37(priorDir(stateDir), `${ticketRecordId}.json`);
16988
17145
  function removePeriscopePrior(stateDir, ticketRecordId) {
16989
- rmSync15(priorPath(stateDir, ticketRecordId), { force: true });
17146
+ rmSync16(priorPath(stateDir, ticketRecordId), { force: true });
16990
17147
  }
16991
17148
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
16992
17149
  try {
16993
- mkdirSync20(priorDir(stateDir), { recursive: true });
16994
- writeFileSync22(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
17150
+ mkdirSync21(priorDir(stateDir), { recursive: true });
17151
+ writeFileSync23(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
16995
17152
  } catch {
16996
17153
  }
16997
17154
  }
16998
17155
  function readPeriscopePrior(stateDir, ticketRecordId) {
16999
17156
  try {
17000
17157
  const p = priorPath(stateDir, ticketRecordId);
17001
- return existsSync33(p) ? JSON.parse(readFileSync29(p, "utf8")) : void 0;
17158
+ return existsSync34(p) ? JSON.parse(readFileSync30(p, "utf8")) : void 0;
17002
17159
  } catch {
17003
17160
  return void 0;
17004
17161
  }
@@ -17390,8 +17547,8 @@ ${tail2(preview.output)}`);
17390
17547
 
17391
17548
  // src/periscope-idle.ts
17392
17549
  import { execFileSync as execFileSync7 } from "child_process";
17393
- import { statSync as statSync10 } from "fs";
17394
- import { join as join37 } from "path";
17550
+ import { statSync as statSync11 } from "fs";
17551
+ import { join as join38 } from "path";
17395
17552
  var MAX_SCANNED_FILES = 2e4;
17396
17553
  function newestWorktreeMtime(root) {
17397
17554
  let listing;
@@ -17411,7 +17568,7 @@ function newestWorktreeMtime(root) {
17411
17568
  if (!rel) continue;
17412
17569
  if (++scanned > MAX_SCANNED_FILES) break;
17413
17570
  try {
17414
- const m = statSync10(join37(root, rel)).mtimeMs;
17571
+ const m = statSync11(join38(root, rel)).mtimeMs;
17415
17572
  if (newest === null || m > newest) newest = m;
17416
17573
  } catch {
17417
17574
  }
@@ -17487,18 +17644,18 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
17487
17644
 
17488
17645
  // src/ssh-keys.ts
17489
17646
  import { execFileSync as execFileSync8 } from "child_process";
17490
- import { existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync30, chmodSync as chmodSync2 } from "fs";
17491
- import { dirname as dirname20, join as join38 } from "path";
17647
+ import { existsSync as existsSync35, mkdirSync as mkdirSync22, readFileSync as readFileSync31, chmodSync as chmodSync3 } from "fs";
17648
+ import { dirname as dirname20, join as join39 } from "path";
17492
17649
  var SshKeyError = class extends Error {
17493
17650
  };
17494
17651
  function shipSshKeyPathFor(stateDir) {
17495
- return join38(stateDir, "ssh", "id_ed25519");
17652
+ return join39(stateDir, "ssh", "id_ed25519");
17496
17653
  }
17497
17654
  function ensureShipSshKeypair(stateDir) {
17498
17655
  const privateKeyPath = shipSshKeyPathFor(stateDir);
17499
17656
  const publicKeyPath = `${privateKeyPath}.pub`;
17500
- if (!existsSync34(privateKeyPath)) {
17501
- mkdirSync21(dirname20(privateKeyPath), { recursive: true });
17657
+ if (!existsSync35(privateKeyPath)) {
17658
+ mkdirSync22(dirname20(privateKeyPath), { recursive: true });
17502
17659
  try {
17503
17660
  execFileSync8("ssh-keygen", [
17504
17661
  "-t",
@@ -17516,12 +17673,12 @@ function ensureShipSshKeypair(stateDir) {
17516
17673
  const err2 = e;
17517
17674
  throw new SshKeyError(`ssh-keygen failed: ${err2.stderr?.toString().trim() || err2.message}`);
17518
17675
  }
17519
- chmodSync2(privateKeyPath, 384);
17676
+ chmodSync3(privateKeyPath, 384);
17520
17677
  }
17521
- if (!existsSync34(publicKeyPath)) {
17678
+ if (!existsSync35(publicKeyPath)) {
17522
17679
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
17523
17680
  }
17524
- const publicKey = readFileSync30(publicKeyPath, "utf8").trim();
17681
+ const publicKey = readFileSync31(publicKeyPath, "utf8").trim();
17525
17682
  return { privateKeyPath, publicKeyPath, publicKey };
17526
17683
  }
17527
17684
  function sshKeygenAvailable() {
@@ -17535,7 +17692,7 @@ function sshKeygenAvailable() {
17535
17692
 
17536
17693
  // src/passenger-containers.ts
17537
17694
  import { execFileSync as execFileSync9 } from "child_process";
17538
- import { createHash as createHash6 } from "crypto";
17695
+ import { createHash as createHash7 } from "crypto";
17539
17696
  var realExec = (cmd, args, opts) => {
17540
17697
  try {
17541
17698
  return execFileSync9(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
@@ -17573,12 +17730,12 @@ function containerNameFor(workspaceId) {
17573
17730
  return `crew-passenger-${workspaceId}`;
17574
17731
  }
17575
17732
  function portFor(workspaceId, base = 28765, span = 1e3) {
17576
- const digest = createHash6("sha256").update(workspaceId).digest();
17733
+ const digest = createHash7("sha256").update(workspaceId).digest();
17577
17734
  return base + digest.readUInt32BE(0) % span;
17578
17735
  }
17579
17736
  function mountsDigest(mounts, image) {
17580
17737
  const sorted2 = [...mounts].sort((a, b) => a.containerPath.localeCompare(b.containerPath));
17581
- return createHash6("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
17738
+ return createHash7("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
17582
17739
  }
17583
17740
  function planContainers(cfg) {
17584
17741
  const byWorkspace = /* @__PURE__ */ new Map();
@@ -17738,9 +17895,9 @@ function syncAllPassengerCheckouts(cfg) {
17738
17895
 
17739
17896
  // src/tunnel.ts
17740
17897
  import { spawn as spawn5 } from "child_process";
17741
- import { existsSync as existsSync35, mkdirSync as mkdirSync22, readFileSync as readFileSync31, writeFileSync as writeFileSync23, unlinkSync as unlinkSync3, readdirSync as readdirSync14 } from "fs";
17898
+ import { existsSync as existsSync36, mkdirSync as mkdirSync23, readFileSync as readFileSync32, writeFileSync as writeFileSync24, unlinkSync as unlinkSync3, readdirSync as readdirSync14 } from "fs";
17742
17899
  import { randomInt } from "crypto";
17743
- import { join as join39 } from "path";
17900
+ import { join as join40 } from "path";
17744
17901
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
17745
17902
  var defaultSpawn = (cmd, args) => spawn5(cmd, args, DEFAULT_SPAWN_OPTIONS);
17746
17903
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -17807,21 +17964,21 @@ function startTunnel(opts) {
17807
17964
  };
17808
17965
  }
17809
17966
  function tunnelStatePath(stateDir, workspaceId) {
17810
- return join39(stateDir, "passengers", `${workspaceId}.json`);
17967
+ return join40(stateDir, "passengers", `${workspaceId}.json`);
17811
17968
  }
17812
17969
  function readPersistedTunnel(stateDir, workspaceId) {
17813
17970
  const path = tunnelStatePath(stateDir, workspaceId);
17814
- if (!existsSync35(path)) return void 0;
17971
+ if (!existsSync36(path)) return void 0;
17815
17972
  try {
17816
- return JSON.parse(readFileSync31(path, "utf8"));
17973
+ return JSON.parse(readFileSync32(path, "utf8"));
17817
17974
  } catch {
17818
17975
  return void 0;
17819
17976
  }
17820
17977
  }
17821
17978
  function writePersistedTunnel(stateDir, state) {
17822
17979
  const path = tunnelStatePath(stateDir, state.workspaceId);
17823
- mkdirSync22(join39(stateDir, "passengers"), { recursive: true });
17824
- writeFileSync23(path, `${JSON.stringify(state, null, 2)}
17980
+ mkdirSync23(join40(stateDir, "passengers"), { recursive: true });
17981
+ writeFileSync24(path, `${JSON.stringify(state, null, 2)}
17825
17982
  `);
17826
17983
  }
17827
17984
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -17918,13 +18075,13 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
17918
18075
  }
17919
18076
  }
17920
18077
  function readAllPersistedWorkspaceIds(stateDir) {
17921
- const dir = join39(stateDir, "passengers");
17922
- if (!existsSync35(dir)) return [];
18078
+ const dir = join40(stateDir, "passengers");
18079
+ if (!existsSync36(dir)) return [];
17923
18080
  return readdirSync14(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
17924
18081
  }
17925
18082
 
17926
18083
  // src/mcp-endpoint-registry.ts
17927
- import { createHash as createHash7 } from "crypto";
18084
+ import { createHash as createHash8 } from "crypto";
17928
18085
  function endpointsUrl(api, workspaceId, id) {
17929
18086
  const base = `${api.baseUrl.replace(/\/+$/, "")}/api/workspaces/${workspaceId}/mcp-endpoints`;
17930
18087
  return id ? `${base}/${id}` : base;
@@ -17992,7 +18149,7 @@ async function probeEndpointAccess(api, workspaceId) {
17992
18149
  }
17993
18150
  var REGISTER_MCP_ENDPOINTS_HINT = "give this key's role the REGISTER_MCP_ENDPOINTS capability, e.g. via the group that holds the Crew project role";
17994
18151
  function registrationKey(url, mcpSecret) {
17995
- return createHash7("sha256").update(`${url}
18152
+ return createHash8("sha256").update(`${url}
17996
18153
  ${mcpSecret ?? ""}`).digest("hex").slice(0, 16);
17997
18154
  }
17998
18155
  async function reconcileEndpoint(workspaceId, d) {
@@ -18040,8 +18197,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
18040
18197
  import { createServer as createHttpServer } from "http";
18041
18198
  import { spawn as spawn6 } from "child_process";
18042
18199
  import { randomBytes } from "crypto";
18043
- import { existsSync as existsSync36, mkdirSync as mkdirSync23, readFileSync as readFileSync32, writeFileSync as writeFileSync24, unlinkSync as unlinkSync4, readdirSync as readdirSync15 } from "fs";
18044
- import { join as join40 } from "path";
18200
+ import { existsSync as existsSync37, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25, unlinkSync as unlinkSync4, readdirSync as readdirSync15 } from "fs";
18201
+ import { join as join41 } from "path";
18045
18202
 
18046
18203
  // src/secret-compare.ts
18047
18204
  import { timingSafeEqual } from "crypto";
@@ -18130,21 +18287,21 @@ var defaultSpawn2 = (cmd, args, env) => {
18130
18287
  return child;
18131
18288
  };
18132
18289
  function syncDaemonStatePath(stateDir, workspaceId) {
18133
- return join40(stateDir, "passengers", `${workspaceId}-sync.json`);
18290
+ return join41(stateDir, "passengers", `${workspaceId}-sync.json`);
18134
18291
  }
18135
18292
  function readPersistedSyncDaemon(stateDir, workspaceId) {
18136
18293
  const path = syncDaemonStatePath(stateDir, workspaceId);
18137
- if (!existsSync36(path)) return void 0;
18294
+ if (!existsSync37(path)) return void 0;
18138
18295
  try {
18139
- return JSON.parse(readFileSync32(path, "utf8"));
18296
+ return JSON.parse(readFileSync33(path, "utf8"));
18140
18297
  } catch {
18141
18298
  return void 0;
18142
18299
  }
18143
18300
  }
18144
18301
  function writePersistedSyncDaemon(stateDir, state) {
18145
18302
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
18146
- mkdirSync23(join40(stateDir, "passengers"), { recursive: true });
18147
- writeFileSync24(path, `${JSON.stringify(state, null, 2)}
18303
+ mkdirSync24(join41(stateDir, "passengers"), { recursive: true });
18304
+ writeFileSync25(path, `${JSON.stringify(state, null, 2)}
18148
18305
  `);
18149
18306
  }
18150
18307
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -18164,8 +18321,8 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
18164
18321
  }
18165
18322
  }
18166
18323
  function readAllPersistedSyncWorkspaceIds(stateDir) {
18167
- const dir = join40(stateDir, "passengers");
18168
- if (!existsSync36(dir)) return [];
18324
+ const dir = join41(stateDir, "passengers");
18325
+ if (!existsSync37(dir)) return [];
18169
18326
  const suffix = "-sync.json";
18170
18327
  return readdirSync15(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
18171
18328
  }
@@ -18253,8 +18410,8 @@ function describeCause(err2) {
18253
18410
  }
18254
18411
 
18255
18412
  // src/daemon.ts
18256
- import { existsSync as existsSync37, mkdirSync as mkdirSync24, readFileSync as readFileSync33, readdirSync as readdirSync16, rmSync as rmSync16, statSync as statSync11, writeFileSync as writeFileSync25 } from "fs";
18257
- import { join as join41 } from "path";
18413
+ import { existsSync as existsSync38, mkdirSync as mkdirSync25, readFileSync as readFileSync34, readdirSync as readdirSync16, rmSync as rmSync17, statSync as statSync12, writeFileSync as writeFileSync26 } from "fs";
18414
+ import { join as join42 } from "path";
18258
18415
  import { randomBytes as randomBytes2 } from "crypto";
18259
18416
  var DEFAULT_FLOOR_MS = 5e3;
18260
18417
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
@@ -18317,7 +18474,7 @@ async function runDaemonLoop(o) {
18317
18474
  function latestMtime(path) {
18318
18475
  let stat;
18319
18476
  try {
18320
- stat = statSync11(path);
18477
+ stat = statSync12(path);
18321
18478
  } catch {
18322
18479
  return 0;
18323
18480
  }
@@ -18325,7 +18482,7 @@ function latestMtime(path) {
18325
18482
  if (!stat.isDirectory()) return 0;
18326
18483
  let latest = stat.mtimeMs;
18327
18484
  for (const entry of readdirSync16(path, { withFileTypes: true })) {
18328
- latest = Math.max(latest, latestMtime(join41(path, entry.name)));
18485
+ latest = Math.max(latest, latestMtime(join42(path, entry.name)));
18329
18486
  }
18330
18487
  return latest;
18331
18488
  }
@@ -18334,23 +18491,23 @@ function computeTreeSignature(paths) {
18334
18491
  }
18335
18492
  function makeStaleChecker(paths, opts = {}) {
18336
18493
  const initial = computeTreeSignature(paths);
18337
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync37(opts.reloadSentinel);
18494
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync38(opts.reloadSentinel);
18338
18495
  }
18339
18496
  function reloadSentinelFor(stateDir) {
18340
- return join41(stateDir, "reload-requested");
18497
+ return join42(stateDir, "reload-requested");
18341
18498
  }
18342
18499
  function daemonStampFor(stateDir) {
18343
- return join41(stateDir, "daemon.started");
18500
+ return join42(stateDir, "daemon.started");
18344
18501
  }
18345
18502
  function noteDaemonStarted(stateDir, now = Date.now()) {
18346
- mkdirSync24(stateDir, { recursive: true });
18347
- rmSync16(reloadSentinelFor(stateDir), { force: true });
18348
- writeFileSync25(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
18503
+ mkdirSync25(stateDir, { recursive: true });
18504
+ rmSync17(reloadSentinelFor(stateDir), { force: true });
18505
+ writeFileSync26(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
18349
18506
  `);
18350
18507
  }
18351
18508
  function readDaemonStarted(stateDir) {
18352
18509
  try {
18353
- const v = JSON.parse(readFileSync33(daemonStampFor(stateDir), "utf8")).startedAt;
18510
+ const v = JSON.parse(readFileSync34(daemonStampFor(stateDir), "utf8")).startedAt;
18354
18511
  return typeof v === "number" ? v : null;
18355
18512
  } catch {
18356
18513
  return null;
@@ -18359,7 +18516,7 @@ function readDaemonStarted(stateDir) {
18359
18516
  function configChangedAfter(files, startedAt) {
18360
18517
  for (const f of files) {
18361
18518
  try {
18362
- if (statSync11(f).mtimeMs > startedAt) return f;
18519
+ if (statSync12(f).mtimeMs > startedAt) return f;
18363
18520
  } catch {
18364
18521
  }
18365
18522
  }
@@ -18659,6 +18816,23 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
18659
18816
  const { route: route2, ship, emit } = ctx;
18660
18817
  const runId = randomBytes2(3).toString("hex");
18661
18818
  const identity = await resolveSeatKey(route2, role, ctx.getSessionStore);
18819
+ if (identity.kind === "ship" && identity.storeError && SEAT_KEY_ROLES.includes(role)) {
18820
+ emit.warn(`${role} run refused: its agent key cannot be read (${identity.storeError})`, { step: "agent", role });
18821
+ try {
18822
+ await raiseShipAttention(
18823
+ { state: ctx.state, emit, route: route2, ship },
18824
+ seatKeyUnreadableItem(routeSlug(route2.route), role, identity.storeError)
18825
+ );
18826
+ } catch {
18827
+ }
18828
+ return;
18829
+ }
18830
+ if (SEAT_KEY_ROLES.includes(role)) {
18831
+ try {
18832
+ clearShipAttention(ctx.state, seatKeyUnreadableKey(routeSlug(route2.route), role));
18833
+ } catch {
18834
+ }
18835
+ }
18662
18836
  if (identity.kind === "ship" && SEAT_KEY_ROLES.includes(role)) {
18663
18837
  emit.warn(`${role} writes as the ship, not its own agent: ${identity.reason}`, { step: "agent", role });
18664
18838
  } else if (identity.kind === "agent") {
@@ -18830,26 +19004,26 @@ async function buildEnvironment(route2, ship, ticket) {
18830
19004
  }
18831
19005
 
18832
19006
  // src/control-api.ts
18833
- import { createHash as createHash8 } from "crypto";
19007
+ import { createHash as createHash9 } from "crypto";
18834
19008
  import {
18835
- chmodSync as chmodSync3,
19009
+ chmodSync as chmodSync4,
18836
19010
  closeSync as closeSync5,
18837
- existsSync as existsSync38,
19011
+ existsSync as existsSync39,
18838
19012
  fstatSync,
18839
19013
  openSync as openSync5,
18840
19014
  readSync as readSync3,
18841
19015
  readdirSync as readdirSync17,
18842
- rmSync as rmSync17,
18843
- statSync as statSync12,
19016
+ rmSync as rmSync18,
19017
+ statSync as statSync13,
18844
19018
  watch
18845
19019
  } from "fs";
18846
19020
  import { createConnection } from "net";
18847
19021
  import { createServer } from "http";
18848
- import { basename as basename12, dirname as dirname21, join as join42 } from "path";
19022
+ import { basename as basename12, dirname as dirname21, join as join43 } from "path";
18849
19023
  import { execFile, spawn as spawn7 } from "child_process";
18850
19024
  var PAUSABLE = ["dev", "design", "qa"];
18851
19025
  var CONTROL_SOCKET_NAME = "control.sock";
18852
- var controlSocketFor = (stateDir) => join42(stateDir, CONTROL_SOCKET_NAME);
19026
+ var controlSocketFor = (stateDir) => join43(stateDir, CONTROL_SOCKET_NAME);
18853
19027
  var MAX_SOCKET_PATH = 100;
18854
19028
  var MAX_TAIL_BYTES = 1024 * 1024;
18855
19029
  var MAX_WAIT_SECONDS = 30;
@@ -18865,7 +19039,7 @@ var ControlAuth = class {
18865
19039
  if (!token) return "unauthorized";
18866
19040
  for (const k of this.o.shipKeys()) if (k && secretsMatch(token, k)) return "ok";
18867
19041
  const now = (this.o.now ?? Date.now)();
18868
- const id = createHash8("sha256").update(token).digest("hex");
19042
+ const id = createHash9("sha256").update(token).digest("hex");
18869
19043
  const hit = this.cache.get(id);
18870
19044
  if (hit && now - hit.at < (this.o.cacheMs ?? 6e4)) return hit.verdict;
18871
19045
  const verdict = await this.ask(token);
@@ -18920,7 +19094,7 @@ function candidates(live, rotated) {
18920
19094
  const out = [live];
18921
19095
  if (!rotated) return out;
18922
19096
  try {
18923
- for (const n of readdirSync17(dirname21(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join42(dirname21(live), n));
19097
+ for (const n of readdirSync17(dirname21(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join43(dirname21(live), n));
18924
19098
  } catch {
18925
19099
  }
18926
19100
  return out;
@@ -18943,7 +19117,7 @@ function readRange(path, from, max) {
18943
19117
  }
18944
19118
  }
18945
19119
  function lastLines2(path, n) {
18946
- const st = statSync12(path);
19120
+ const st = statSync13(path);
18947
19121
  const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
18948
19122
  if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
18949
19123
  let start = st.size;
@@ -18964,7 +19138,7 @@ function lastLines2(path, n) {
18964
19138
  }
18965
19139
  function readTail(live, opts) {
18966
19140
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
18967
- if (!existsSync38(live)) {
19141
+ if (!existsSync39(live)) {
18968
19142
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
18969
19143
  }
18970
19144
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -18977,7 +19151,7 @@ function readTail(live, opts) {
18977
19151
  } else {
18978
19152
  const found = candidates(live, opts.rotated ?? false).find((p) => {
18979
19153
  try {
18980
- const s = statSync12(p);
19154
+ const s = statSync13(p);
18981
19155
  return s.ino === cur.ino && s.dev === cur.dev;
18982
19156
  } catch {
18983
19157
  return false;
@@ -18988,7 +19162,7 @@ function readTail(live, opts) {
18988
19162
  off = cur.off;
18989
19163
  } else reset = true;
18990
19164
  }
18991
- let size = statSync12(path).size;
19165
+ let size = statSync13(path).size;
18992
19166
  if (off > size) {
18993
19167
  off = 0;
18994
19168
  reset = true;
@@ -18996,12 +19170,12 @@ function readTail(live, opts) {
18996
19170
  if (off >= size && path !== live) {
18997
19171
  path = live;
18998
19172
  off = 0;
18999
- size = statSync12(path).size;
19173
+ size = statSync13(path).size;
19000
19174
  }
19001
19175
  const { text } = readRange(path, off, max);
19002
19176
  const cut = text.lastIndexOf("\n");
19003
19177
  const whole = cut < 0 ? "" : text.slice(0, cut + 1);
19004
- const st = statSync12(path);
19178
+ const st = statSync13(path);
19005
19179
  return {
19006
19180
  lines: whole.split("\n").filter((l) => l.length),
19007
19181
  cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
@@ -19108,8 +19282,8 @@ async function route(deps, method, url, req, abort) {
19108
19282
  if (p.startsWith("/streams/") && get2) {
19109
19283
  const run2 = decodeURIComponent(p.slice("/streams/".length));
19110
19284
  if (!RUN_NAME_RE.test(run2) || run2.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run2)) return err(400, "not a run name");
19111
- const file = join42(deps.stateDir, "streams", run2.endsWith(".jsonl") ? run2 : `${run2}.jsonl`);
19112
- if (!existsSync38(file)) return err(404, "no such run");
19285
+ const file = join43(deps.stateDir, "streams", run2.endsWith(".jsonl") ? run2 : `${run2}.jsonl`);
19286
+ if (!existsSync39(file)) return err(404, "no such run");
19113
19287
  return tailReply(file, url.searchParams, false, abort);
19114
19288
  }
19115
19289
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -19144,7 +19318,7 @@ function send(res, r) {
19144
19318
  res.end(body);
19145
19319
  }
19146
19320
  async function socketIsStale(path) {
19147
- if (!existsSync38(path)) return false;
19321
+ if (!existsSync39(path)) return false;
19148
19322
  return new Promise((resolve6) => {
19149
19323
  const c = createConnection(path);
19150
19324
  c.once("connect", () => {
@@ -19162,8 +19336,8 @@ async function startControlServer(deps) {
19162
19336
  log(`control API not started: socket path is too long for a unix socket (${path})`);
19163
19337
  return null;
19164
19338
  }
19165
- if (await socketIsStale(path)) rmSync17(path, { force: true });
19166
- else if (existsSync38(path)) {
19339
+ if (await socketIsStale(path)) rmSync18(path, { force: true });
19340
+ else if (existsSync39(path)) {
19167
19341
  log(`control API not started: ${path} is in use by another process`);
19168
19342
  return null;
19169
19343
  }
@@ -19192,7 +19366,7 @@ async function startControlServer(deps) {
19192
19366
  });
19193
19367
  server.listen(path, () => {
19194
19368
  try {
19195
- chmodSync3(path, 384);
19369
+ chmodSync4(path, 384);
19196
19370
  } catch {
19197
19371
  }
19198
19372
  server.on("error", (e) => log(`control API error: ${e.message}`));
@@ -19201,7 +19375,7 @@ async function startControlServer(deps) {
19201
19375
  path,
19202
19376
  close: () => new Promise((done) => {
19203
19377
  server.close(() => {
19204
- rmSync17(path, { force: true });
19378
+ rmSync18(path, { force: true });
19205
19379
  done();
19206
19380
  });
19207
19381
  })
@@ -19678,7 +19852,7 @@ if (command === "passenger-sync-daemon") {
19678
19852
  } else {
19679
19853
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "connect" && flag("sign-out");
19680
19854
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset");
19681
- if (configless && !existsSync39(findConfigFile(CREW_HOME))) {
19855
+ if (configless && !existsSync40(findConfigFile(CREW_HOME))) {
19682
19856
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
19683
19857
  } else {
19684
19858
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -19693,7 +19867,7 @@ if (command === "passenger-sync-daemon") {
19693
19867
  throw e;
19694
19868
  }
19695
19869
  if (command === "credential") {
19696
- const r = runCredential(positional[1], readFileSync34(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
19870
+ const r = runCredential(positional[1], readFileSync35(0, "utf8"), cfg.ship.forges, cfg.ship.stateDir);
19697
19871
  process.stdout.write(r.out);
19698
19872
  process.stderr.write(r.err);
19699
19873
  process.exit(r.code);
@@ -19705,9 +19879,9 @@ if (command === "passenger-sync-daemon") {
19705
19879
  forges: cfg.ship.forges,
19706
19880
  stateDir: cfg.ship.stateDir,
19707
19881
  configFile: cfg.configFile,
19708
- readFile: (p) => readFileSync34(p, "utf8"),
19709
- writeFile: (p, t) => writeFileSync26(p, t),
19710
- stdin: () => readFileSync34(0, "utf8"),
19882
+ readFile: (p) => readFileSync35(p, "utf8"),
19883
+ writeFile: (p, t) => writeFileSync27(p, t),
19884
+ stdin: () => readFileSync35(0, "utf8"),
19711
19885
  env: process.env
19712
19886
  }
19713
19887
  );
@@ -19943,8 +20117,8 @@ if (command === "passenger-sync-daemon") {
19943
20117
  }
19944
20118
  if (host === "linux" && hasSystemd) {
19945
20119
  const label = labelFor(CREW_HOME, "run");
19946
- const staleTimer = join43(homedir10(), ".config", "systemd", "user", `${label}.timer`);
19947
- if (existsSync39(staleTimer)) {
20120
+ const staleTimer = join44(homedir10(), ".config", "systemd", "user", `${label}.timer`);
20121
+ if (existsSync40(staleTimer)) {
19948
20122
  process.stdout.write(
19949
20123
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
19950
20124
  `
@@ -19971,7 +20145,7 @@ if (command === "passenger-sync-daemon") {
19971
20145
  const tracker = new Tracker(c, cfg.ship);
19972
20146
  problems.push(...validateContract(tracker.contract).map((p) => `contract: ${p}`));
19973
20147
  for (const r of await resolvedRepos(c)) {
19974
- if (!existsSync39(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
20148
+ if (!existsSync40(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
19975
20149
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
19976
20150
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
19977
20151
  }
@@ -19989,7 +20163,7 @@ if (command === "passenger-sync-daemon") {
19989
20163
  }
19990
20164
  async function doctorFix(o) {
19991
20165
  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))));
19992
- const unitExists = (p) => existsSync39(p);
20166
+ const unitExists = (p) => existsSync40(p);
19993
20167
  const crontab = o.host === "linux" ? spawnSync7("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
19994
20168
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run"), unitExists, crontab) : true;
19995
20169
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -20006,7 +20180,7 @@ if (command === "passenger-sync-daemon") {
20006
20180
  for (const a of plan.actions) {
20007
20181
  if (a.kind === "enable") {
20008
20182
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
20009
- writeFileSync26(cfg.configFile, planEnable(readFileSync34(cfg.configFile, "utf8"), a.route));
20183
+ writeFileSync27(cfg.configFile, planEnable(readFileSync35(cfg.configFile, "utf8"), a.route));
20010
20184
  enabledSound.add(a.route);
20011
20185
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
20012
20186
  } else {
@@ -20150,6 +20324,23 @@ ${changed2.map((c) => ` - ${c}
20150
20324
  }
20151
20325
  if (!dryRun && !c.enabled) return { scope, tests: "skipped", outcome: "skipped", detail: "route not enabled" };
20152
20326
  const releaseIdentity = await resolveReleaseKey(c);
20327
+ const releaseRouteSlug = routeSlug(c.route);
20328
+ if (releaseIdentity.kind === "ship" && releaseIdentity.storeError) {
20329
+ const detail = `release refused: its agent key cannot be read (${releaseIdentity.storeError})`;
20330
+ remit.warn(`${scope}: ${detail}`, { step: "release" });
20331
+ try {
20332
+ await raiseShipAttention(
20333
+ { state: new State(cfg.ship.stateDir), emit: remit, route: c, ship: cfg.ship },
20334
+ seatKeyUnreadableItem(releaseRouteSlug, "release", releaseIdentity.storeError)
20335
+ );
20336
+ } catch {
20337
+ }
20338
+ return { scope, tests: "skipped", outcome: "skipped", detail };
20339
+ }
20340
+ try {
20341
+ clearShipAttention(new State(cfg.ship.stateDir), seatKeyUnreadableKey(releaseRouteSlug, "release"));
20342
+ } catch {
20343
+ }
20153
20344
  if (releaseIdentity.kind === "ship") remit.warn(`release writes as the ship, not its own agent: ${releaseIdentity.reason}`, { step: "release" });
20154
20345
  const tracker = new Tracker(c, cfg.ship, { apiKey: releaseIdentity.apiKey });
20155
20346
  if (!dryRun) {
@@ -21110,7 +21301,7 @@ ${cu.tail.trimEnd()}
21110
21301
  process.exit(1);
21111
21302
  }
21112
21303
  requireArmed("run the daemon loop");
21113
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join43(CREW_HOME, "src"), join43(CREW_HOME, "dist"), join43(CREW_HOME, "bin")];
21304
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join44(CREW_HOME, "src"), join44(CREW_HOME, "dist"), join44(CREW_HOME, "bin")];
21114
21305
  const configPaths = daemonConfigPaths(cfg);
21115
21306
  watchPaths.push(...configPaths);
21116
21307
  noteDaemonStarted(cfg.ship.stateDir);
@@ -21362,10 +21553,10 @@ ${cu.tail.trimEnd()}
21362
21553
  `);
21363
21554
  if (!dryRun) {
21364
21555
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
21365
- const raw = existsSync39(resolvedPath) ? JSON.parse(readFileSync34(resolvedPath, "utf8")) : {};
21556
+ const raw = existsSync40(resolvedPath) ? JSON.parse(readFileSync35(resolvedPath, "utf8")) : {};
21366
21557
  raw.agentSkills = result.agentSkills;
21367
- mkdirSync25(dirOf2(resolvedPath), { recursive: true });
21368
- writeFileSync26(resolvedPath, `${JSON.stringify(raw, null, 2)}
21558
+ mkdirSync26(dirOf2(resolvedPath), { recursive: true });
21559
+ writeFileSync27(resolvedPath, `${JSON.stringify(raw, null, 2)}
21369
21560
  `);
21370
21561
  }
21371
21562
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -21388,7 +21579,7 @@ ${cu.tail.trimEnd()}
21388
21579
  throw e;
21389
21580
  }
21390
21581
  const oldName = cfg.ship.name;
21391
- writeFileSync26(cfg.configFile, setShipNameInYaml(readFileSync34(cfg.configFile, "utf8"), newName));
21582
+ writeFileSync27(cfg.configFile, setShipNameInYaml(readFileSync35(cfg.configFile, "utf8"), newName));
21392
21583
  cfg.ship.name = newName;
21393
21584
  let renameMachineId;
21394
21585
  try {
@@ -21435,7 +21626,7 @@ ${cu.tail.trimEnd()}
21435
21626
  process.exit(2);
21436
21627
  };
21437
21628
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks" && sub !== "check") usage2();
21438
- const configText = readFileSync34(cfg.configFile, "utf8");
21629
+ const configText = readFileSync35(cfg.configFile, "utf8");
21439
21630
  if (sub === "check") {
21440
21631
  try {
21441
21632
  const routeArg = value("route") ?? positional[2];
@@ -21466,8 +21657,8 @@ ${cu.tail.trimEnd()}
21466
21657
  try {
21467
21658
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
21468
21659
  const dir = repoDirOf(configText, dirname22(cfg.configFile), a.route, a.name);
21469
- if (!existsSync39(dir)) throw new ReposError(`${dir} does not exist on disk`);
21470
- const files = { exists: (r) => existsSync39(resolvePath(dir, r)), list: (r) => {
21660
+ if (!existsSync40(dir)) throw new ReposError(`${dir} does not exist on disk`);
21661
+ const files = { exists: (r) => existsSync40(resolvePath(dir, r)), list: (r) => {
21471
21662
  try {
21472
21663
  return readdirSync18(resolvePath(dir, r));
21473
21664
  } catch {
@@ -21475,7 +21666,7 @@ ${cu.tail.trimEnd()}
21475
21666
  }
21476
21667
  }, read: (r) => {
21477
21668
  try {
21478
- return readFileSync34(resolvePath(dir, r), "utf8");
21669
+ return readFileSync35(resolvePath(dir, r), "utf8");
21479
21670
  } catch {
21480
21671
  return void 0;
21481
21672
  }
@@ -21517,19 +21708,19 @@ ${cu.tail.trimEnd()}
21517
21708
  let cleared;
21518
21709
  if (clear2) {
21519
21710
  const plan = clearRouteHooks(configText, a.route, a.name);
21520
- if (plan.removed.length > 0) writeFileSync26(cfg.configFile, plan.text);
21711
+ if (plan.removed.length > 0) writeFileSync27(cfg.configFile, plan.text);
21521
21712
  cleared = { file: cfg.configFile, removed: plan.removed };
21522
21713
  }
21523
21714
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
21524
21715
  if (write === "route") {
21525
21716
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
21526
- writeFileSync26(cfg.configFile, plan.text);
21717
+ writeFileSync27(cfg.configFile, plan.text);
21527
21718
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
21528
21719
  } else if (write === "repo") {
21529
21720
  const have = existingRepoConfig(files);
21530
21721
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
21531
21722
  const file = resolvePath(dir, ".crew.yaml");
21532
- writeFileSync26(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
21723
+ writeFileSync27(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
21533
21724
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
21534
21725
  }
21535
21726
  if (flag("json")) {
@@ -21585,7 +21776,7 @@ ${cu.tail.trimEnd()}
21585
21776
  if (sub === "list" && !routeName) usage2();
21586
21777
  if (sub === "add") {
21587
21778
  try {
21588
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync39(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
21779
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync40(resolvePath(p.replace(/^~(?=\/|$)/, homedir10()))));
21589
21780
  routeName = a.route;
21590
21781
  addPath = a.path;
21591
21782
  addName = a.name;
@@ -21614,16 +21805,16 @@ ${cu.tail.trimEnd()}
21614
21805
  }
21615
21806
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
21616
21807
  let resolvedRemotes;
21617
- if (existsSync39(resolvedFile)) {
21808
+ if (existsSync40(resolvedFile)) {
21618
21809
  try {
21619
- resolvedRemotes = JSON.parse(readFileSync34(resolvedFile, "utf8"));
21810
+ resolvedRemotes = JSON.parse(readFileSync35(resolvedFile, "utf8"));
21620
21811
  } catch {
21621
21812
  }
21622
21813
  }
21623
21814
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
21624
21815
  const text = configText;
21625
21816
  const base = dirname22(cfg.configFile);
21626
- const isCheckout = (d) => existsSync39(`${d}/.git`);
21817
+ const isCheckout = (d) => existsSync40(`${d}/.git`);
21627
21818
  try {
21628
21819
  if (sub === "list") {
21629
21820
  if (flag("json")) {
@@ -21633,11 +21824,11 @@ ${cu.tail.trimEnd()}
21633
21824
  base,
21634
21825
  resolved: resolvedRemotes,
21635
21826
  originOf,
21636
- exists: (d) => existsSync39(d),
21827
+ exists: (d) => existsSync40(d),
21637
21828
  isCheckout,
21638
21829
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
21639
21830
  try {
21640
- return Object.keys(parseRepoConfig(readFileSync34(resolvePath(d, f), "utf8"), f).hooks).length > 0;
21831
+ return Object.keys(parseRepoConfig(readFileSync35(resolvePath(d, f), "utf8"), f).hooks).length > 0;
21641
21832
  } catch {
21642
21833
  return false;
21643
21834
  }
@@ -21654,7 +21845,7 @@ ${cu.tail.trimEnd()}
21654
21845
  for (const r of repos) {
21655
21846
  const origin2 = originOf(r.dir);
21656
21847
  const row = matchRepoName(origin2, resolvedRemotes);
21657
- const note = !existsSync39(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
21848
+ const note = !existsSync40(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
21658
21849
  process.stdout.write(`${r.name} ${r.dir} ${note}
21659
21850
  `);
21660
21851
  }
@@ -21666,9 +21857,9 @@ ${cu.tail.trimEnd()}
21666
21857
  ` : `${human}
21667
21858
  `);
21668
21859
  const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
21669
- if (existsSync39(plan2.target) && (existsSync39(`${plan2.target}/.git`) || readdirSync18(plan2.target).length > 0)) {
21860
+ if (existsSync40(plan2.target) && (existsSync40(`${plan2.target}/.git`) || readdirSync18(plan2.target).length > 0)) {
21670
21861
  throw new ReposError(
21671
- existsSync39(`${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`
21862
+ existsSync40(`${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`
21672
21863
  );
21673
21864
  }
21674
21865
  if (dryRun) {
@@ -21686,14 +21877,14 @@ ${cu.tail.trimEnd()}
21686
21877
  throw new ReposError(`${e.message}${authHint}
21687
21878
  crew.yaml was not changed`);
21688
21879
  }
21689
- writeFileSync26(cfg.configFile, plan2.text);
21880
+ writeFileSync27(cfg.configFile, plan2.text);
21690
21881
  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:
21691
21882
  `);
21692
21883
  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}`);
21693
21884
  break;
21694
21885
  }
21695
21886
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir10()));
21696
- const top = existsSync39(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
21887
+ const top = existsSync40(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
21697
21888
  if (!top) throw new ReposError(`${target} is not a git checkout`);
21698
21889
  const origin = originOf(top);
21699
21890
  const plan = planRepoAdd({
@@ -21745,15 +21936,15 @@ ${cu.tail.trimEnd()}
21745
21936
  const created = await rowTracker.createRepoRow(rowPayload);
21746
21937
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
21747
21938
  `);
21748
- if (existsSync39(resolvedFile)) {
21749
- const raw = JSON.parse(readFileSync34(resolvedFile, "utf8"));
21939
+ if (existsSync40(resolvedFile)) {
21940
+ const raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
21750
21941
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
21751
21942
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
21752
- writeFileSync26(resolvedFile, `${JSON.stringify(raw, null, 2)}
21943
+ writeFileSync27(resolvedFile, `${JSON.stringify(raw, null, 2)}
21753
21944
  `);
21754
21945
  }
21755
21946
  }
21756
- writeFileSync26(cfg.configFile, plan.text);
21947
+ writeFileSync27(cfg.configFile, plan.text);
21757
21948
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
21758
21949
  `);
21759
21950
  } catch (e) {
@@ -21852,7 +22043,7 @@ ${reconstruction.prompt}
21852
22043
  if (flag("sign-out")) {
21853
22044
  let signOutStore;
21854
22045
  try {
21855
- signOutStore = getSessionStore3();
22046
+ signOutStore = getSessionStore();
21856
22047
  } catch {
21857
22048
  signOutStore = void 0;
21858
22049
  }
@@ -21892,7 +22083,7 @@ ${reconstruction.prompt}
21892
22083
  const givenKeySource = value("key") ?? process.env.CREW_CONNECT_KEY ? "flag" : "config";
21893
22084
  let store;
21894
22085
  try {
21895
- store = getSessionStore3();
22086
+ store = getSessionStore();
21896
22087
  } catch {
21897
22088
  store = void 0;
21898
22089
  }
@@ -22120,7 +22311,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
22120
22311
  process.exit(2);
22121
22312
  }
22122
22313
  if (found.shipNameAdopted && !dryRun) {
22123
- writeFileSync26(cfg.configFile, setShipNameInYaml(readFileSync34(cfg.configFile, "utf8"), found.shipNameAdopted));
22314
+ writeFileSync27(cfg.configFile, setShipNameInYaml(readFileSync35(cfg.configFile, "utf8"), found.shipNameAdopted));
22124
22315
  cfg.ship.name = found.shipNameAdopted;
22125
22316
  }
22126
22317
  if (found.shipRow) {
@@ -22139,8 +22330,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
22139
22330
  }
22140
22331
  const capabilityMisses = capabilities.filter((i) => i.status === "missing").map(missLine);
22141
22332
  if (found.locksModelId && !dryRun) {
22142
- const configured = cfg.routes.find((r) => r.route === route3);
22143
- const repoNames = [...Object.values(found.repoNames ?? {}), ...configured ? reposOf(configured).map((t) => t.name) : []].filter((n) => !!n);
22333
+ const configured2 = cfg.routes.find((r) => r.route === route3);
22334
+ const repoNames = [...Object.values(found.repoNames ?? {}), ...configured2 ? reposOf(configured2).map((t) => t.name) : []].filter((n) => !!n);
22144
22335
  try {
22145
22336
  const made = await provisionReleaseLocks(authOpts, found.locksModelId, route3, repoNames);
22146
22337
  for (const sc of made) process.stderr.write(`created release lock row ${sc}
@@ -22163,10 +22354,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
22163
22354
  }
22164
22355
  }
22165
22356
  let previous;
22166
- const hadPreviousFile = existsSync39(resolvedPath);
22357
+ const hadPreviousFile = existsSync40(resolvedPath);
22167
22358
  if (hadPreviousFile) {
22168
22359
  try {
22169
- previous = JSON.parse(readFileSync34(resolvedPath, "utf8"));
22360
+ previous = JSON.parse(readFileSync35(resolvedPath, "utf8"));
22170
22361
  } catch {
22171
22362
  }
22172
22363
  }
@@ -22295,12 +22486,12 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
22295
22486
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
22296
22487
  `);
22297
22488
  } else {
22298
- mkdirSync25(dirname22(resolvedPath), { recursive: true });
22489
+ mkdirSync26(dirname22(resolvedPath), { recursive: true });
22299
22490
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
22300
22491
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
22301
- writeFileSync26(tmpPath, `${JSON.stringify(resolved, null, 2)}
22492
+ writeFileSync27(tmpPath, `${JSON.stringify(resolved, null, 2)}
22302
22493
  `);
22303
- renameSync3(tmpPath, resolvedPath);
22494
+ renameSync4(tmpPath, resolvedPath);
22304
22495
  process.stderr.write(`Resolved ids written to ${resolvedPath}
22305
22496
  `);
22306
22497
  }
@@ -22344,8 +22535,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
22344
22535
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
22345
22536
  `);
22346
22537
  } else {
22347
- mkdirSync25(dirname22(apiKeyFile), { recursive: true });
22348
- writeFileSync26(apiKeyFile, `${apiKeyVar}=${apiKey}
22538
+ mkdirSync26(dirname22(apiKeyFile), { recursive: true });
22539
+ writeFileSync27(apiKeyFile, `${apiKeyVar}=${apiKey}
22349
22540
  `, { mode: 384 });
22350
22541
  process.stderr.write(`API key written to ${apiKeyFile}
22351
22542
  `);
@@ -23079,7 +23270,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
23079
23270
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
23080
23271
  const resumed = await runPeriscopeResume({
23081
23272
  find: async () => hit && { row: hit.row, tag: hit.tag },
23082
- worktreeExists: (p) => existsSync39(p),
23273
+ worktreeExists: (p) => existsSync40(p),
23083
23274
  caps: periscopeCaps(cfg.ship),
23084
23275
  runHook: async (name, cwd, env) => {
23085
23276
  const script = hit?.repo.config.hooks[name];
@@ -23173,10 +23364,10 @@ ${where.join("\n")}`);
23173
23364
  },
23174
23365
  prepare: async (path) => {
23175
23366
  for (const f of repoP.config.worktrees.copy) {
23176
- const src = join43(repoP.dir, f);
23177
- if (!existsSync39(src)) continue;
23178
- mkdirSync25(dirOf2(join43(path, f)), { recursive: true });
23179
- copyFileSync2(src, join43(path, f));
23367
+ const src = join44(repoP.dir, f);
23368
+ if (!existsSync40(src)) continue;
23369
+ mkdirSync26(dirOf2(join44(path, f)), { recursive: true });
23370
+ copyFileSync2(src, join44(path, f));
23180
23371
  }
23181
23372
  const setup = repoP.config.hooks.setup;
23182
23373
  return setup ? runScript(setup, { cwd: path, shell: shellP, onLine: (l) => {
@@ -23402,7 +23593,7 @@ ${where.join("\n")}`);
23402
23593
  const host = hostPlatform();
23403
23594
  let resetStore;
23404
23595
  try {
23405
- resetStore = getSessionStore3();
23596
+ resetStore = getSessionStore();
23406
23597
  } catch {
23407
23598
  resetStore = void 0;
23408
23599
  }
@@ -23451,7 +23642,7 @@ ${where.join("\n")}`);
23451
23642
  };
23452
23643
  const droppedRoutes = [];
23453
23644
  try {
23454
- const rawCfg = parseYaml2(readFileSync34(cfg.configFile, "utf8"));
23645
+ const rawCfg = parseYaml2(readFileSync35(cfg.configFile, "utf8"));
23455
23646
  for (const r of rawCfg?.routes ?? []) {
23456
23647
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
23457
23648
  try {
@@ -23493,8 +23684,8 @@ ${where.join("\n")}`);
23493
23684
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
23494
23685
  break;
23495
23686
  }
23496
- mkdirSync25(cfg.ship.stateDir, { recursive: true });
23497
- writeFileSync26(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
23687
+ mkdirSync26(cfg.ship.stateDir, { recursive: true });
23688
+ writeFileSync27(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
23498
23689
  `);
23499
23690
  process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
23500
23691
  if (flag("now") && control?.stopCommand && control.startCommand) {
@@ -23545,7 +23736,7 @@ ${where.join("\n")}`);
23545
23736
  }
23546
23737
  case "log": {
23547
23738
  try {
23548
- const text = readFileSync34(cfg.ship.logFile, "utf8").split("\n");
23739
+ const text = readFileSync35(cfg.ship.logFile, "utf8").split("\n");
23549
23740
  process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
23550
23741
  `);
23551
23742
  } catch {
@@ -23568,10 +23759,10 @@ ${where.join("\n")}`);
23568
23759
  };
23569
23760
  if (!route2.resolved) fail(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
23570
23761
  const tracker = new Tracker(route2, cfg.ship);
23571
- const configText = readFileSync34(cfg.configFile, "utf8");
23762
+ const configText = readFileSync35(cfg.configFile, "utf8");
23572
23763
  const cfgBase = dirname22(cfg.configFile);
23573
- const configured = configuredRepos(configText, route2.route, cfgBase);
23574
- const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
23764
+ const configured2 = configuredRepos(configText, route2.route, cfgBase);
23765
+ const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
23575
23766
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
23576
23767
  const rows = await tracker.repoRows();
23577
23768
  const row = rows.find((r) => r.name === SAMPLE_REPO);
@@ -23595,9 +23786,9 @@ ${where.join("\n")}`);
23595
23786
  say("dry-run", { dir, row: row?.id ?? null, closed }, `would close ${closed.join(", ") || "no tickets"}, delete the Repos row, detach and delete ${dir}`);
23596
23787
  break;
23597
23788
  }
23598
- if (configured.some((r) => r.dir === dir)) {
23789
+ if (configured2.some((r) => r.dir === dir)) {
23599
23790
  try {
23600
- writeFileSync26(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
23791
+ writeFileSync27(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
23601
23792
  } catch (e) {
23602
23793
  if (e instanceof ReposError) fail(e.message);
23603
23794
  throw e;
@@ -23605,29 +23796,29 @@ ${where.join("\n")}`);
23605
23796
  }
23606
23797
  if (row) {
23607
23798
  await tracker.deleteRepoRow(row.id);
23608
- if (existsSync39(resolvedFile)) {
23609
- const raw = JSON.parse(readFileSync34(resolvedFile, "utf8"));
23799
+ if (existsSync40(resolvedFile)) {
23800
+ const raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
23610
23801
  if (raw.repoNames) delete raw.repoNames[row.id];
23611
23802
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
23612
- writeFileSync26(resolvedFile, `${JSON.stringify(raw, null, 2)}
23803
+ writeFileSync27(resolvedFile, `${JSON.stringify(raw, null, 2)}
23613
23804
  `);
23614
23805
  }
23615
23806
  }
23616
- if (existsSync39(join43(dir, ".git")) && basename13(dir) === SAMPLE_REPO) rmSync18(dir, { recursive: true, force: true });
23807
+ if (existsSync40(join44(dir, ".git")) && basename13(dir) === SAMPLE_REPO) rmSync19(dir, { recursive: true, force: true });
23617
23808
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
23618
23809
  break;
23619
23810
  }
23620
23811
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
23621
- const fresh = !existsSync39(join43(dir, ".git"));
23622
- if (existsSync39(dir) && fresh && readdirSync18(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
23812
+ const fresh = !existsSync40(join44(dir, ".git"));
23813
+ if (existsSync40(dir) && fresh && readdirSync18(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
23623
23814
  if (dryRun) {
23624
23815
  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`);
23625
23816
  break;
23626
23817
  }
23627
23818
  if (fresh) {
23628
- mkdirSync25(dir, { recursive: true });
23629
- writeFileSync26(join43(dir, "README.md"), SAMPLE_README);
23630
- writeFileSync26(join43(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
23819
+ mkdirSync26(dir, { recursive: true });
23820
+ writeFileSync27(join44(dir, "README.md"), SAMPLE_README);
23821
+ writeFileSync27(join44(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
23631
23822
  const g = (...a) => {
23632
23823
  const r = spawnSync7("git", a, { cwd: dir, encoding: "utf8" });
23633
23824
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -23645,17 +23836,17 @@ ${where.join("\n")}`);
23645
23836
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
23646
23837
  });
23647
23838
  repoId = (await tracker.createRepoRow(payload)).id;
23648
- if (existsSync39(resolvedFile)) {
23649
- const raw = JSON.parse(readFileSync34(resolvedFile, "utf8"));
23839
+ if (existsSync40(resolvedFile)) {
23840
+ const raw = JSON.parse(readFileSync35(resolvedFile, "utf8"));
23650
23841
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
23651
23842
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
23652
- writeFileSync26(resolvedFile, `${JSON.stringify(raw, null, 2)}
23843
+ writeFileSync27(resolvedFile, `${JSON.stringify(raw, null, 2)}
23653
23844
  `);
23654
23845
  }
23655
23846
  }
23656
- if (!configured.some((r) => r.dir === dir)) {
23847
+ if (!configured2.some((r) => r.dir === dir)) {
23657
23848
  try {
23658
- writeFileSync26(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
23849
+ writeFileSync27(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
23659
23850
  } catch (e) {
23660
23851
  if (e instanceof ReposError) fail(e.message);
23661
23852
  throw e;
@@ -23990,7 +24181,7 @@ watermark: ${state.watermark()}
23990
24181
  }
23991
24182
  for (const r of repos) {
23992
24183
  const remote = r.config.branch.remote;
23993
- if (existsSync39(r.dir)) {
24184
+ if (existsSync40(r.dir)) {
23994
24185
  pushAuth.push({
23995
24186
  route: c.route,
23996
24187
  repo: r.name,
@@ -24063,6 +24254,8 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
24063
24254
  for (const w of roleCapWarnings(cfg.ship)) process.stdout.write(`role concurrency: WARN ${w}
24064
24255
  `);
24065
24256
  process.stdout.write(`periscopes: ${periscopeSummary(cfg)}
24257
+ `);
24258
+ process.stdout.write(`key store: ${describeKeyStore(cfg.ship)}
24066
24259
  `);
24067
24260
  const previewProblem = await previewHostProblem(cfg);
24068
24261
  if (previewProblem) {
@@ -24073,7 +24266,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
24073
24266
  }
24074
24267
  try {
24075
24268
  const evFile = eventFileFor(cfg.ship.stateDir);
24076
- const evSize = existsSync39(evFile) ? statSync13(evFile).size : 0;
24269
+ const evSize = existsSync40(evFile) ? statSync14(evFile).size : 0;
24077
24270
  process.stdout.write(
24078
24271
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
24079
24272
  ` : "events log: ok\n"
@@ -24084,7 +24277,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
24084
24277
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
24085
24278
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
24086
24279
  try {
24087
- return readFileSync34(p, "utf8");
24280
+ return readFileSync35(p, "utf8");
24088
24281
  } catch {
24089
24282
  return null;
24090
24283
  }
@@ -24100,7 +24293,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
24100
24293
  const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
24101
24294
  if (changed2) {
24102
24295
  process.stdout.write(
24103
- `daemon config: WARN ${changed2} changed at ${new Date(statSync13(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
24296
+ `daemon config: WARN ${changed2} changed at ${new Date(statSync14(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
24104
24297
  `
24105
24298
  );
24106
24299
  }