@tablation/crew 0.128.3 → 0.129.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -797,7 +797,7 @@ function shellInitScript(shell) {
797
797
  }
798
798
 
799
799
  // src/config.ts
800
- import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
800
+ import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
801
801
  import { resolve as resolve2, dirname as dirname8, isAbsolute as isAbsolute2, join as join17, basename as basename4 } from "path";
802
802
  import { homedir as homedir6, tmpdir } from "os";
803
803
  import { parse as parse2 } from "yaml";
@@ -2664,7 +2664,64 @@ function explain(ship, required) {
2664
2664
  }
2665
2665
 
2666
2666
  // src/repo-config.ts
2667
- import { readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
2667
+ import { readFileSync as readFileSync14, existsSync as hostExists } from "fs";
2668
+
2669
+ // src/remote-dir.ts
2670
+ import { execFileSync as execFileSync5 } from "child_process";
2671
+ import { posix } from "path";
2672
+ var REMOTE_ROOT = "/.crew-sandbox";
2673
+ var registry = /* @__PURE__ */ new Map();
2674
+ function registerRemoteDir(r) {
2675
+ const dir = `${REMOTE_ROOT}/${r.sandbox}`;
2676
+ registry.set(dir, r);
2677
+ return dir;
2678
+ }
2679
+ function unregisterRemoteDir(dir) {
2680
+ registry.delete(dir);
2681
+ }
2682
+ function remoteOf(path) {
2683
+ if (!path.startsWith(`${REMOTE_ROOT}/`)) return null;
2684
+ for (const [dir, remote] of registry) {
2685
+ if (path === dir) return { remote, path: remote.workDir };
2686
+ if (path.startsWith(`${dir}/`)) return { remote, path: posix.join(remote.workDir, path.slice(dir.length + 1)) };
2687
+ }
2688
+ return null;
2689
+ }
2690
+ function sandboxExecArgs(r, cwd, argv2) {
2691
+ return ["sandbox", "exec", "-n", r.sandbox, "--no-login-shell", "--workdir", cwd, "--", ...argv2];
2692
+ }
2693
+ function execRemoteSync(hit, argv2, o = {}) {
2694
+ return execFileSync5(hit.remote.bin, sandboxExecArgs(hit.remote, o.cwd ?? hit.path, argv2), {
2695
+ encoding: "utf8",
2696
+ stdio: [o.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
2697
+ maxBuffer: 256 * 1024 * 1024,
2698
+ ...o.input === void 0 ? {} : { input: o.input }
2699
+ });
2700
+ }
2701
+ function existsAt(path, hostExists2) {
2702
+ const hit = remoteOf(path);
2703
+ if (!hit) return hostExists2(path);
2704
+ try {
2705
+ execRemoteSync(hit, ["test", "-e", hit.path], { cwd: hit.remote.workDir });
2706
+ return true;
2707
+ } catch {
2708
+ return false;
2709
+ }
2710
+ }
2711
+ function readTextAt(path, hostRead) {
2712
+ const hit = remoteOf(path);
2713
+ return hit ? execRemoteSync(hit, ["cat", "--", hit.path], { cwd: hit.remote.workDir }) : hostRead(path);
2714
+ }
2715
+ function writeTextAt(path, text2, hostWrite) {
2716
+ const hit = remoteOf(path);
2717
+ if (!hit) {
2718
+ hostWrite(path, text2);
2719
+ return;
2720
+ }
2721
+ execRemoteSync(hit, ["sh", "-c", 'cat > "$1"', "sh", hit.path], { input: text2, cwd: hit.remote.workDir });
2722
+ }
2723
+
2724
+ // src/repo-config.ts
2668
2725
  import { join as join16, basename as basename3, dirname as dirname7 } from "path";
2669
2726
  import { parse } from "yaml";
2670
2727
 
@@ -2964,7 +3021,7 @@ var PROVIDERS = ["github", "buildkite", "other", "none"];
2964
3021
  function findRepoConfig(dir) {
2965
3022
  for (const name of REPO_CONFIG_FILENAMES) {
2966
3023
  const p = join16(dir, name);
2967
- if (existsSync14(p)) return p;
3024
+ if (existsAt(p, hostExists)) return p;
2968
3025
  }
2969
3026
  return null;
2970
3027
  }
@@ -3218,10 +3275,10 @@ function parseRepoConfig(text2, file) {
3218
3275
  if (versionFilesGiven) {
3219
3276
  for (const name of versionFiles) {
3220
3277
  const path = join16(dirname7(file), name);
3221
- if (!existsSync14(path)) continue;
3278
+ if (!existsAt(path, hostExists)) continue;
3222
3279
  let text3;
3223
3280
  try {
3224
- text3 = readFileSync14(path, "utf8");
3281
+ text3 = readTextAt(path, (p) => readFileSync14(p, "utf8"));
3225
3282
  } catch (e) {
3226
3283
  throw new RepoConfigError(
3227
3284
  `${file}: release.versionFiles entry "${name}" cannot be read (${e.code ?? "read error"})`
@@ -3266,7 +3323,7 @@ function parseRepoConfig(text2, file) {
3266
3323
  function loadRepoConfig(dir) {
3267
3324
  const file = findRepoConfig(dir);
3268
3325
  if (!file) return null;
3269
- return parseRepoConfig(readFileSync14(file, "utf8"), file);
3326
+ return parseRepoConfig(readTextAt(file, (p) => readFileSync14(p, "utf8")), file);
3270
3327
  }
3271
3328
  function loadRepoConfigTolerant(dir, refused) {
3272
3329
  try {
@@ -3849,7 +3906,7 @@ function configSearchPath(crewHome) {
3849
3906
  }
3850
3907
  function findConfigFile(crewHome) {
3851
3908
  const candidates2 = configSearchPath(crewHome);
3852
- return candidates2.find((c) => existsSync15(c)) ?? candidates2[candidates2.length - 1];
3909
+ return candidates2.find((c) => existsSync14(c)) ?? candidates2[candidates2.length - 1];
3853
3910
  }
3854
3911
  var defaultConfigPath = () => join17(process.env.XDG_CONFIG_HOME ?? join17(homedir6(), ".config"), "crew", "crew.yaml");
3855
3912
  function routeSlug(route2) {
@@ -3872,7 +3929,7 @@ function splitRoute(route2) {
3872
3929
  }
3873
3930
  function readResolvedFile(stateDir, route2) {
3874
3931
  const path = resolvedPathFor(stateDir, route2);
3875
- if (!existsSync15(path)) return void 0;
3932
+ if (!existsSync14(path)) return void 0;
3876
3933
  try {
3877
3934
  return JSON.parse(readFileSync15(path, "utf8"));
3878
3935
  } catch (e) {
@@ -3935,7 +3992,7 @@ function parseResolved(raw, m, where) {
3935
3992
  }
3936
3993
  function loadConfig(crewHome, configFile, opts = {}) {
3937
3994
  const file = configFile ?? findConfigFile(crewHome);
3938
- if (opts.text === void 0 && !existsSync15(file)) {
3995
+ if (opts.text === void 0 && !existsSync14(file)) {
3939
3996
  throw new ConfigError(
3940
3997
  `no config found. Looked in:
3941
3998
  ` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
@@ -4353,7 +4410,7 @@ function repoIdForName(r, name) {
4353
4410
  }
4354
4411
  function resolveApiKey(r) {
4355
4412
  if (r.apiKey) return r.apiKey;
4356
- if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
4413
+ if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
4357
4414
  for (const line of readFileSync15(r.apiKeyFile, "utf8").split("\n")) {
4358
4415
  const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
4359
4416
  if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
@@ -4367,7 +4424,7 @@ function resolveApiKey(r) {
4367
4424
  }
4368
4425
  function credentialSource(r) {
4369
4426
  if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
4370
- if (r.apiKeyFile && r.apiKeyVar && existsSync15(r.apiKeyFile)) {
4427
+ if (r.apiKeyFile && r.apiKeyVar && existsSync14(r.apiKeyFile)) {
4371
4428
  const re = new RegExp(`^${r.apiKeyVar}=`);
4372
4429
  if (readFileSync15(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
4373
4430
  }
@@ -4586,11 +4643,18 @@ function resolveShell(configured2) {
4586
4643
  if (/cmd(\.exe)?$/i.test(bin)) return { bin, args: ["/d", "/s", "/c"] };
4587
4644
  return { bin, args: ["-c"] };
4588
4645
  }
4646
+ function remoteScript(script, prelude, env) {
4647
+ const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
4648
+ const exports = Object.entries(env ?? {}).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${q2(String(v))}; export ${k}; `).join("");
4649
+ return `${prelude ?? ""}${exports}${script}`;
4650
+ }
4589
4651
  function runScript(script, opts) {
4590
4652
  const shell = opts.shell ?? defaultShell();
4653
+ const hit = remoteOf(opts.cwd);
4654
+ const [bin, args, cwd] = hit ? [hit.remote.bin, sandboxExecArgs(hit.remote, hit.path, [shell.bin, ...shell.args, remoteScript(script, hit.remote.prelude, opts.env)]), void 0] : [shell.bin, [...shell.args, script], opts.cwd];
4591
4655
  return new Promise((resolvePromise) => {
4592
- const child = spawn2(shell.bin, [...shell.args, script], {
4593
- cwd: opts.cwd,
4656
+ const child = spawn2(bin, args, {
4657
+ cwd,
4594
4658
  env: { ...process.env, ...opts.env },
4595
4659
  stdio: ["ignore", "pipe", "pipe"]
4596
4660
  });
@@ -4762,7 +4826,7 @@ function recoveredFailureHeadlines(o, route2) {
4762
4826
  // src/install.ts
4763
4827
  import { dirname as dirname9, join as join18, extname } from "path";
4764
4828
  import { homedir as homedir7, userInfo } from "os";
4765
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
4829
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync5, writeFileSync as writeFileSync12, unlinkSync, realpathSync } from "fs";
4766
4830
  import { spawnSync as spawnSync5 } from "child_process";
4767
4831
  import { createHash as createHash3 } from "crypto";
4768
4832
 
@@ -5047,7 +5111,7 @@ function planUninstall(ship, crewHome, hostShip, hasSystemd, job = "run") {
5047
5111
  };
5048
5112
  }
5049
5113
  if (hostShip === "linux" && hasSystemd) {
5050
- const system = existsSync16(systemUnitPathFor(label));
5114
+ const system = existsSync15(systemUnitPathFor(label));
5051
5115
  const unitDir = system ? SYSTEM_UNIT_DIR : join18(homedir7(), ".config", "systemd", "user");
5052
5116
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
5053
5117
  if (job === "run") {
@@ -5088,7 +5152,7 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
5088
5152
  }
5089
5153
  if (hostShip === "linux" && hasSystemd) {
5090
5154
  const unit = `${label}.service`;
5091
- const ctl = existsSync16(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
5155
+ const ctl = existsSync15(systemUnitPathFor(label)) ? ["systemctl"] : ["systemctl", "--user"];
5092
5156
  return {
5093
5157
  mechanism: "systemd",
5094
5158
  startCommand: [...ctl, "start", unit],
@@ -5101,9 +5165,22 @@ function planDaemonControl(crewHome, hostShip, hasSystemd) {
5101
5165
  }
5102
5166
  return { mechanism: "none", startCommand: null, stopCommand: null, statusCommand: null };
5103
5167
  }
5168
+ function withUserBusEnv(env = process.env, opts = {}) {
5169
+ const platform2 = opts.platform ?? process.platform;
5170
+ const uid = opts.uid === void 0 ? process.getuid?.() ?? null : opts.uid;
5171
+ if (platform2 !== "linux" || uid === null) return env;
5172
+ const dir = `/run/user/${uid}`;
5173
+ if (env.XDG_RUNTIME_DIR && env.DBUS_SESSION_BUS_ADDRESS) return env;
5174
+ if (!(opts.exists ?? existsSync15)(dir)) return env;
5175
+ return {
5176
+ ...env,
5177
+ XDG_RUNTIME_DIR: env.XDG_RUNTIME_DIR || dir,
5178
+ DBUS_SESSION_BUS_ADDRESS: env.DBUS_SESSION_BUS_ADDRESS || `unix:path=${env.XDG_RUNTIME_DIR || dir}/bus`
5179
+ };
5180
+ }
5104
5181
  function runAll(commands, cwd, log) {
5105
5182
  for (const [bin, ...args] of commands) {
5106
- const res = spawnSync5(bin, args, { cwd, encoding: "utf8" });
5183
+ const res = spawnSync5(bin, args, { cwd, encoding: "utf8", env: withUserBusEnv() });
5107
5184
  if (res.error || res.status !== 0) {
5108
5185
  log.warn(`${bin} ${args.join(" ")} exited ${res.status ?? "error"}`, {
5109
5186
  output: `${res.stdout ?? ""}${res.stderr ?? ""}${res.error ? String(res.error) : ""}`
@@ -5120,7 +5197,7 @@ async function applyInstall(plan, crewHome, dryRun, log) {
5120
5197
  log.emit(`would write ${path}`);
5121
5198
  continue;
5122
5199
  }
5123
- const before = existsSync16(path) ? readFileSync16(path, "utf8") : null;
5200
+ const before = existsSync15(path) ? readFileSync16(path, "utf8") : null;
5124
5201
  if (before !== plan.unitContent[path]) changed2 = true;
5125
5202
  mkdirSync11(dirname9(path), { recursive: true });
5126
5203
  writeFileSync12(path, plan.unitContent[path]);
@@ -5187,7 +5264,7 @@ async function applyUninstall(plan, crewHome, dryRun, log) {
5187
5264
  }
5188
5265
  }
5189
5266
  for (const path of plan.unitPaths) {
5190
- if (!existsSync16(path)) continue;
5267
+ if (!existsSync15(path)) continue;
5191
5268
  if (dryRun) {
5192
5269
  log.emit(`would remove ${path}`);
5193
5270
  continue;
@@ -5223,7 +5300,7 @@ function loadedCrewLabels() {
5223
5300
  return [...parseLaunchctlList(list.stdout ?? "").keys()].filter((l) => l.startsWith(CREW_LAUNCHD_PREFIX));
5224
5301
  }
5225
5302
  function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
5226
- const files = existsSync16(dir) ? readdirSync3(dir) : [];
5303
+ const files = existsSync15(dir) ? readdirSync3(dir) : [];
5227
5304
  const units = files.filter((f) => f.startsWith(CREW_LAUNCHD_PREFIX) && f.endsWith(".plist")).sort().map((f) => {
5228
5305
  const plistPath = join18(dir, f);
5229
5306
  let program = null;
@@ -5235,7 +5312,7 @@ function listLaunchdUnits(dir = launchAgentsDir(), loaded = []) {
5235
5312
  label: f.slice(0, -".plist".length),
5236
5313
  plistPath,
5237
5314
  program,
5238
- programExists: program !== null && existsSync16(program)
5315
+ programExists: program !== null && existsSync15(program)
5239
5316
  };
5240
5317
  });
5241
5318
  const seen = new Set(units.map((u) => u.label));
@@ -5254,9 +5331,9 @@ function removeLaunchdUnit(unit, dryRun, log) {
5254
5331
  log.emit(`would unload and remove ${unit.label}`);
5255
5332
  return;
5256
5333
  }
5257
- if (existsSync16(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
5334
+ if (existsSync15(unit.plistPath)) spawnSync5("launchctl", ["unload", unit.plistPath], { encoding: "utf8" });
5258
5335
  spawnSync5("launchctl", ["remove", unit.label], { encoding: "utf8" });
5259
- if (existsSync16(unit.plistPath)) unlinkSync(unit.plistPath);
5336
+ if (existsSync15(unit.plistPath)) unlinkSync(unit.plistPath);
5260
5337
  log.emit(`removed ${unit.label} (${unit.plistPath})`);
5261
5338
  }
5262
5339
  function clearForeignLaunchdUnits(crewHome, replace, dryRun, log, dir, loaded) {
@@ -5329,17 +5406,17 @@ function removeCrontabLine(marker) {
5329
5406
  }
5330
5407
  function detectSystemd() {
5331
5408
  if (process.platform !== "linux") return false;
5332
- const res = spawnSync5("systemctl", ["--user", "status"], { encoding: "utf8" });
5409
+ const res = spawnSync5("systemctl", ["--user", "status"], { encoding: "utf8", env: withUserBusEnv() });
5333
5410
  return !res.error;
5334
5411
  }
5335
5412
 
5336
5413
  // src/ship-attention.ts
5337
- import { existsSync as existsSync18, statfsSync } from "fs";
5414
+ import { existsSync as existsSync17, statfsSync } from "fs";
5338
5415
 
5339
5416
  // src/git.ts
5340
- import { execFileSync as execFileSync5 } from "child_process";
5417
+ import { execFileSync as execFileSync6 } from "child_process";
5341
5418
  import {
5342
- existsSync as existsSync17,
5419
+ existsSync as existsSync16,
5343
5420
  mkdirSync as mkdirSync12,
5344
5421
  openSync as openSync3,
5345
5422
  closeSync as closeSync3,
@@ -5354,12 +5431,22 @@ import {
5354
5431
  import { dirname as dirname10, basename as basename5, join as join19 } from "path";
5355
5432
  var GitError = class extends Error {
5356
5433
  };
5434
+ function execGit(cwd, args, input) {
5435
+ const hit = remoteOf(cwd);
5436
+ if (hit) return execRemoteSync(hit, ["git", ...args], { input });
5437
+ return execFileSync6("git", args, {
5438
+ cwd,
5439
+ encoding: "utf8",
5440
+ maxBuffer: 256 * 1024 * 1024,
5441
+ ...input === void 0 ? { stdio: ["ignore", "pipe", "pipe"] } : { input }
5442
+ });
5443
+ }
5357
5444
  function streamText(x) {
5358
5445
  return String(x ?? "").trim();
5359
5446
  }
5360
5447
  function gitRaw(cwd, args) {
5361
5448
  try {
5362
- return execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5449
+ return execGit(cwd, args);
5363
5450
  } catch (e) {
5364
5451
  const err3 = e;
5365
5452
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5368,7 +5455,7 @@ function gitRaw(cwd, args) {
5368
5455
  }
5369
5456
  function git(cwd, args) {
5370
5457
  try {
5371
- return execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
5458
+ return execGit(cwd, args).trim();
5372
5459
  } catch (e) {
5373
5460
  const err3 = e;
5374
5461
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5516,6 +5603,10 @@ function pushTag(cwd, remote, tag) {
5516
5603
  git(cwd, ["push", remote, tag]);
5517
5604
  }
5518
5605
  function fetchRemote(cwd, remote = "origin") {
5606
+ if (remoteOf(cwd)) {
5607
+ const depth = gitOk(cwd, ["config", "--get", "crew.releaseDepth"]);
5608
+ return gitOk(cwd, ["fetch", "--quiet", ...depth ? ["--depth", depth] : [], remote]) !== null;
5609
+ }
5519
5610
  if (gitOk(cwd, ["fetch", "--prune", "--quiet", remote]) === null) return false;
5520
5611
  gitOk(cwd, ["fetch", "--quiet", remote, "refs/tags/*:refs/tags/*"]);
5521
5612
  return true;
@@ -5539,9 +5630,9 @@ function keyCommitsInRange(cwd, key, range) {
5539
5630
  }
5540
5631
  function patchIdOf(cwd, from, to) {
5541
5632
  try {
5542
- const diff = execFileSync5("git", ["diff", from, to], { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
5633
+ const diff = execGit(cwd, ["diff", from, to]);
5543
5634
  if (!diff) return null;
5544
- const out = execFileSync5("git", ["patch-id", "--stable"], { cwd, encoding: "utf8", input: diff });
5635
+ const out = execGit(cwd, ["patch-id", "--stable"], diff);
5545
5636
  return out.split(" ")[0]?.trim() || null;
5546
5637
  } catch {
5547
5638
  return null;
@@ -5753,7 +5844,7 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
5753
5844
  try {
5754
5845
  const st = statSync6(root);
5755
5846
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
5756
- if (existsSync17(join19(root, "checkout"))) continue;
5847
+ if (existsSync16(join19(root, "checkout"))) continue;
5757
5848
  rmSync9(root, { recursive: true, force: true });
5758
5849
  removed.push(name);
5759
5850
  } catch {
@@ -5805,7 +5896,7 @@ function originUrlOf(dir, remoteName) {
5805
5896
  }
5806
5897
  }
5807
5898
  function ensureRepoCheckout(dir, remote, opts = {}) {
5808
- if (existsSync17(`${dir}/.git`)) return false;
5899
+ if (existsSync16(`${dir}/.git`)) return false;
5809
5900
  if (!remote) {
5810
5901
  throw new GitError(
5811
5902
  `${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`
@@ -5814,7 +5905,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
5814
5905
  mkdirSync12(dirname10(dir), { recursive: true });
5815
5906
  const url = cloneUrlFor(remote, opts);
5816
5907
  try {
5817
- execFileSync5("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5908
+ execFileSync6("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
5818
5909
  } catch (e) {
5819
5910
  const err3 = e;
5820
5911
  const why = [err3.stderr, err3.stdout].map(streamText).filter(Boolean).join(" / ");
@@ -5873,10 +5964,10 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
5873
5964
  function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
5874
5965
  const release = acquireCheckoutLock(dir);
5875
5966
  try {
5876
- if (!existsSync17(`${dir}/.git`) && githubRemote) {
5967
+ if (!existsSync16(`${dir}/.git`) && githubRemote) {
5877
5968
  const url = cloneUrlFor(githubRemote, clone);
5878
5969
  const marker = cloneFailureMarker(dir);
5879
- if (existsSync17(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
5970
+ if (existsSync16(marker) && readFileSync17(marker, "utf8").split("\n")[0] === url) {
5880
5971
  return { action: "clone-failed", detail: readFileSync17(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
5881
5972
  }
5882
5973
  try {
@@ -6190,7 +6281,7 @@ function noteHeldWorktree(state, h, nowMs = Date.now()) {
6190
6281
  function reconcileHeldWorktrees(state, repo, log, stillRefused) {
6191
6282
  for (const i of state.shipAttention().previous()) {
6192
6283
  if (i.kind !== "worktree_held" || !i.held || i.held.repo !== repo) continue;
6193
- const gone = !existsSync18(i.held.path);
6284
+ const gone = !existsSync17(i.held.path);
6194
6285
  if (!gone && !(stillRefused && !stillRefused.has(i.held.path))) continue;
6195
6286
  clearShipAttention(state, i.key);
6196
6287
  log.emit(
@@ -6356,14 +6447,14 @@ function attentionProblems(items, already) {
6356
6447
  // src/sandbox-image.ts
6357
6448
  import { createHash as createHash7 } from "crypto";
6358
6449
  import { spawn as spawn5 } from "child_process";
6359
- import { execFileSync as execFileSync8 } from "child_process";
6360
- import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync28, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
6450
+ import { execFileSync as execFileSync9 } from "child_process";
6451
+ import { existsSync as existsSync29, mkdirSync as mkdirSync19, readFileSync as readFileSync28, renameSync as renameSync3, writeFileSync as writeFileSync20, rmSync as rmSync15 } from "fs";
6361
6452
  import { dirname as dirname15, join as join32 } from "path";
6362
6453
  import { fileURLToPath } from "url";
6363
6454
  import { tmpdir as tmpdir3 } from "os";
6364
6455
 
6365
6456
  // src/passenger-containers.ts
6366
- import { execFileSync as execFileSync7 } from "child_process";
6457
+ import { execFileSync as execFileSync8 } from "child_process";
6367
6458
  import { createHash as createHash6 } from "crypto";
6368
6459
 
6369
6460
  // src/sandbox-cache.ts
@@ -6372,22 +6463,22 @@ import { promisify as promisify2 } from "util";
6372
6463
 
6373
6464
  // src/deprovision-host.ts
6374
6465
  import { spawnSync as spawnSync7 } from "child_process";
6375
- import { existsSync as existsSync29, readdirSync as readdirSync11 } from "fs";
6466
+ import { existsSync as existsSync28, readdirSync as readdirSync11 } from "fs";
6376
6467
 
6377
6468
  // src/interrupted.ts
6378
- import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
6469
+ import { existsSync as existsSync26, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
6379
6470
  import { join as join30 } from "path";
6380
6471
 
6381
6472
  // src/agent.ts
6382
6473
  import { spawnSync as spawnSync6 } from "child_process";
6383
- import { readFileSync as readFileSync26, existsSync as existsSync26, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6474
+ import { readFileSync as readFileSync26, existsSync as existsSync25, statSync as statSync8, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, createWriteStream, openSync as openSync4, closeSync as closeSync4, readSync } from "fs";
6384
6475
  import { StringDecoder } from "string_decoder";
6385
6476
  import { finished } from "stream/promises";
6386
6477
  import { join as join29, dirname as dirname14, basename as basename9 } from "path";
6387
6478
  import { createHash as createHash5 } from "crypto";
6388
6479
 
6389
6480
  // src/state.ts
6390
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6481
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync14, existsSync as existsSync18, mkdirSync as mkdirSync13, rmSync as rmSync10, statSync as statSync7, readdirSync as readdirSync5 } from "fs";
6391
6482
  import { join as join21 } from "path";
6392
6483
  var EPOCH = "1970-01-01T00:00:00.000Z";
6393
6484
  var State = class _State {
@@ -6433,10 +6524,10 @@ var State = class _State {
6433
6524
  }
6434
6525
  /** Whole-crew pause, and the per-role sentinels one level down. */
6435
6526
  isPaused() {
6436
- return existsSync19(this.path(".crew-paused"));
6527
+ return existsSync18(this.path(".crew-paused"));
6437
6528
  }
6438
6529
  isRolePaused(role) {
6439
- return existsSync19(this.path(`.role-paused-${role}`));
6530
+ return existsSync18(this.path(`.role-paused-${role}`));
6440
6531
  }
6441
6532
  pausedRoles(roles) {
6442
6533
  return new Set(roles.filter((r) => this.isRolePaused(r)));
@@ -6453,7 +6544,7 @@ var State = class _State {
6453
6544
  * while agents keep working.
6454
6545
  */
6455
6546
  isReleasePaused() {
6456
- return existsSync19(this.path(".release-paused"));
6547
+ return existsSync18(this.path(".release-paused"));
6457
6548
  }
6458
6549
  setReleasePaused(paused) {
6459
6550
  if (paused) writeFileSync14(this.path(".release-paused"), "");
@@ -7874,7 +7965,7 @@ function renderEnvironment(i) {
7874
7965
  }
7875
7966
 
7876
7967
  // src/intel-bridge.ts
7877
- import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7968
+ import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
7878
7969
  import { dirname as dirname11, join as join22 } from "path";
7879
7970
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7880
7971
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -8023,7 +8114,7 @@ function createBridge(o) {
8023
8114
  tryAttach,
8024
8115
  async start(transport) {
8025
8116
  await server.connect(transport);
8026
- if (existsSync20(o.attachFile)) await tryAttach();
8117
+ if (existsSync19(o.attachFile)) await tryAttach();
8027
8118
  timer = setInterval(() => {
8028
8119
  void tryAttach();
8029
8120
  }, o.pollMs ?? 1e3);
@@ -8280,7 +8371,7 @@ var BeforeFirstEditTracker = class {
8280
8371
  };
8281
8372
 
8282
8373
  // src/agent-log.ts
8283
- import { existsSync as existsSync23, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
8374
+ import { existsSync as existsSync22, mkdirSync as mkdirSync16, readdirSync as readdirSync7, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "fs";
8284
8375
  import { join as join25 } from "path";
8285
8376
 
8286
8377
  // src/pending-backoff.ts
@@ -9347,7 +9438,7 @@ function materializeBranchForTicket(dir, cfg, t, role, onProblem, onReset) {
9347
9438
  }
9348
9439
 
9349
9440
  // src/pending-handoff.ts
9350
- import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync22, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
9441
+ import { existsSync as existsSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync6, readFileSync as readFileSync22, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
9351
9442
  import { join as join23 } from "path";
9352
9443
  var PENDING_MAX_AGE_MS = 48 * 36e5;
9353
9444
  var pendingDirFor = (stateDir) => join23(stateDir, "pending-handoffs");
@@ -9359,7 +9450,7 @@ function writePendingHandoff(stateDir, rec) {
9359
9450
  }
9360
9451
  function listPendingHandoffs(stateDir, route2) {
9361
9452
  const dir = pendingDirFor(stateDir);
9362
- if (!existsSync21(dir)) return [];
9453
+ if (!existsSync20(dir)) return [];
9363
9454
  const out = [];
9364
9455
  for (const f of readdirSync6(dir).filter((n) => n.endsWith(".json")).sort()) {
9365
9456
  try {
@@ -9540,7 +9631,7 @@ function holdWarnings(tickets, ships, contract, humanHolds, now = Date.now()) {
9540
9631
  }
9541
9632
 
9542
9633
  // src/run-worktree.ts
9543
- import { existsSync as existsSync22 } from "fs";
9634
+ import { existsSync as existsSync21 } from "fs";
9544
9635
  import { dirname as dirname12, join as join24 } from "path";
9545
9636
  var describeRunWorktreePlan = (p) => {
9546
9637
  switch (p.action) {
@@ -9582,7 +9673,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
9582
9673
  const existing = worktreeForNumber(dir, num);
9583
9674
  const located = locateBranchForTicket(dir, cfg, t, role);
9584
9675
  const baseRef = hasRemote ? `${remote}/${base}` : base;
9585
- if (existing && !existing.prunable && existsSync22(existing.path)) {
9676
+ if (existing && !existing.prunable && existsSync21(existing.path)) {
9586
9677
  const branch = existing.branch;
9587
9678
  if (status(existing.path).length > 0) {
9588
9679
  return { action: opts.resumeInterrupted ? "resume-dirty" : "leave-dirty", path: existing.path, branch };
@@ -9652,7 +9743,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9652
9743
  if (remoteConfigured(dir, remote) && remoteBranchExists(dir, remote, branch)) return false;
9653
9744
  if (countCommits(dir, `${baseRef}..${branch}`) !== 0) return false;
9654
9745
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9655
- if (wt && existsSync22(wt.path)) {
9746
+ if (wt && existsSync21(wt.path)) {
9656
9747
  if (status(wt.path).length > 0) return false;
9657
9748
  if (gitOk(dir, ["worktree", "remove", wt.path]) === null) return false;
9658
9749
  }
@@ -9660,7 +9751,7 @@ function dropEmptyRunBranch(dir, cfg, branch) {
9660
9751
  }
9661
9752
  async function finishRunWorktree(dir, cfg, branch) {
9662
9753
  const wt = worktrees(dir).find((w) => w.branch === branch && !w.prunable && w.path !== dir);
9663
- if (!wt || !existsSync22(wt.path)) return { kind: "none" };
9754
+ if (!wt || !existsSync21(wt.path)) return { kind: "none" };
9664
9755
  if (cfg.hooks.handoff) return { kind: "kept-handoff", path: wt.path };
9665
9756
  if (status(wt.path).length > 0) return { kind: "kept-dirty", path: wt.path };
9666
9757
  if (remoteConfigured(dir, cfg.branch.remote) && worktreeHoldsUnpushed(wt.path, cfg.branch.remote)) {
@@ -9672,7 +9763,7 @@ async function finishRunWorktree(dir, cfg, branch) {
9672
9763
  if (pids.length) await killGently(pids);
9673
9764
  }
9674
9765
  gitOk(dir, ["worktree", "remove", wt.path]);
9675
- return existsSync22(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9766
+ return existsSync21(wt.path) ? { kind: "kept-dirty", path: wt.path } : { kind: "removed", path: wt.path };
9676
9767
  }
9677
9768
 
9678
9769
  // src/handoff-guard.ts
@@ -10310,7 +10401,7 @@ function queueAgentRun(stateDir, name, q2) {
10310
10401
  }
10311
10402
  async function replayQueuedAgentRuns(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
10312
10403
  const dir = queueDir(stateDir);
10313
- if (!existsSync23(dir)) return 0;
10404
+ if (!existsSync22(dir)) return 0;
10314
10405
  let sent = 0;
10315
10406
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
10316
10407
  const path = join25(dir, f);
@@ -10363,7 +10454,7 @@ function queueTicketCounters(stateDir, name, q2) {
10363
10454
  }
10364
10455
  async function replayQueuedTicketCounters(stateDir, route2, keyFor, log, now = /* @__PURE__ */ new Date()) {
10365
10456
  const dir = countersDir(stateDir);
10366
- if (!existsSync23(dir)) return 0;
10457
+ if (!existsSync22(dir)) return 0;
10367
10458
  let sent = 0;
10368
10459
  for (const f of readdirSync7(dir).filter((n) => n.endsWith(".json")).sort()) {
10369
10460
  const path = join25(dir, f);
@@ -10672,7 +10763,7 @@ function expandSkillMarkers(text2, lookup2, opts = {}) {
10672
10763
  }
10673
10764
 
10674
10765
  // src/skills.ts
10675
- import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as existsSync25 } from "fs";
10766
+ import { readFileSync as readFileSync25, readdirSync as readdirSync8, existsSync as existsSync24 } from "fs";
10676
10767
  import { join as join27 } from "path";
10677
10768
  import { parse as parseYaml } from "yaml";
10678
10769
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError3, TablationApiError as TablationApiError2 } from "@tablation/client";
@@ -10689,7 +10780,7 @@ ${additions}`;
10689
10780
  }
10690
10781
 
10691
10782
  // src/agents.ts
10692
- import { readFileSync as readFileSync24, existsSync as existsSync24 } from "fs";
10783
+ import { readFileSync as readFileSync24, existsSync as existsSync23 } from "fs";
10693
10784
  import { join as join26 } from "path";
10694
10785
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError2, TablationApiError } from "@tablation/client";
10695
10786
  var PERSONA_NAME = {
@@ -10705,10 +10796,10 @@ var AgentsSyncError = class extends Error {
10705
10796
  };
10706
10797
  function personaDefaultPrompt(promptsDir, role) {
10707
10798
  const briefPath = join26(promptsDir, "personas", `lane-${role}.md`);
10708
- if (!existsSync24(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
10799
+ if (!existsSync23(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
10709
10800
  if (SELF_CONTAINED_ROLES.has(role)) return readFileSync24(briefPath, "utf8");
10710
10801
  const commonPath = join26(promptsDir, "personas", "common.md");
10711
- if (!existsSync24(commonPath)) return readFileSync24(briefPath, "utf8");
10802
+ if (!existsSync23(commonPath)) return readFileSync24(briefPath, "utf8");
10712
10803
  return `${readFileSync24(commonPath, "utf8")}${readFileSync24(briefPath, "utf8")}`;
10713
10804
  }
10714
10805
  async function resolveAgentId(route2, role, opts) {
@@ -11170,7 +11261,7 @@ function parseSkillFile(path) {
11170
11261
  }
11171
11262
  function listLocalSkills(promptsDir) {
11172
11263
  const dir = join27(promptsDir, "skills");
11173
- if (!existsSync25(dir)) return [];
11264
+ if (!existsSync24(dir)) return [];
11174
11265
  return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => parseSkillFile(join27(dir, f)));
11175
11266
  }
11176
11267
  async function syncSkills(route2, opts) {
@@ -11355,7 +11446,7 @@ function skillMarkerWarnings(promptsDir, personaPrompts) {
11355
11446
  }
11356
11447
 
11357
11448
  // src/runner.ts
11358
- import { spawn as spawn3, execFile, execFileSync as execFileSync6 } from "child_process";
11449
+ import { spawn as spawn3, execFile, execFileSync as execFileSync7 } from "child_process";
11359
11450
  import { mkdtempSync, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync14 } from "fs";
11360
11451
  import { createHash as createHash4 } from "crypto";
11361
11452
  import { tmpdir as tmpdir2 } from "os";
@@ -11605,7 +11696,7 @@ var SandboxRunner = class {
11605
11696
  var describeExecError = describe;
11606
11697
  function gitOrigin(cwd) {
11607
11698
  try {
11608
- return execFileSync6("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11699
+ return execFileSync7("git", ["-C", cwd, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
11609
11700
  } catch {
11610
11701
  return null;
11611
11702
  }
@@ -11617,7 +11708,7 @@ function runnerOfSidecar(sc) {
11617
11708
  return sc.runner === "sandbox" ? new SandboxRunner(sc.sandboxBin ? { bin: sc.sandboxBin } : {}) : new HostRunner();
11618
11709
  }
11619
11710
  var PROBE_CREATE_TIMEOUT_MS = 12e4;
11620
- function checkSandboxRunner(cfg = {}, run3 = (b, a, t) => execFileSync6(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: t ?? 15e3 })) {
11711
+ function checkSandboxRunner(cfg = {}, run3 = (b, a, t) => execFileSync7(b, a, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: t ?? 15e3 })) {
11621
11712
  const bin = cfg.bin ?? DEFAULT_SANDBOX_CONFIG.bin;
11622
11713
  let version;
11623
11714
  try {
@@ -11806,18 +11897,18 @@ function codeBridgeFor(o, harness) {
11806
11897
  }
11807
11898
  function planAgentRun(o) {
11808
11899
  const briefPath = join29(o.route.promptsDir, "personas", `lane-${o.role}.md`);
11809
- if (!existsSync26(briefPath)) {
11900
+ if (!existsSync25(briefPath)) {
11810
11901
  throw new AgentError(`no brief at ${briefPath} \u2014 refusing to run an unscoped session`);
11811
11902
  }
11812
11903
  const commonPath = join29(o.route.promptsDir, "personas", "common.md");
11813
- const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync26(commonPath);
11904
+ const usesCommon = !SELF_CONTAINED_ROLES.has(o.role) && existsSync25(commonPath);
11814
11905
  const now = o.now ?? (() => Date.now());
11815
11906
  const routeLabel = routeSlug(o.route.route);
11816
11907
  const ownDigest = o.runId ? digestPathFor(o.stateDir, routeLabel, o.role, o.runId) : void 0;
11817
- const digestPath2 = ownDigest && existsSync26(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
11908
+ const digestPath2 = ownDigest && existsSync25(ownDigest) ? ownDigest : digestPathFor(o.stateDir, routeLabel, o.role);
11818
11909
  let digest;
11819
11910
  let ageSeconds;
11820
- if (existsSync26(digestPath2)) {
11911
+ if (existsSync25(digestPath2)) {
11821
11912
  ageSeconds = Math.floor((now() - statSync8(digestPath2).mtimeMs) / 1e3);
11822
11913
  if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync26(digestPath2, "utf8");
11823
11914
  }
@@ -12563,7 +12654,7 @@ var pidAlive = (pid) => {
12563
12654
  };
12564
12655
  function runFinished(streamPath) {
12565
12656
  const path = `${streamPath}.meta.json`;
12566
- if (!existsSync27(path)) return false;
12657
+ if (!existsSync26(path)) return false;
12567
12658
  try {
12568
12659
  return JSON.parse(readFileSync27(path, "utf8")).running !== true;
12569
12660
  } catch {
@@ -12572,7 +12663,7 @@ function runFinished(streamPath) {
12572
12663
  }
12573
12664
  function orphanedStreams(o) {
12574
12665
  const dir = join30(o.stateDir, "streams");
12575
- if (!existsSync27(dir)) return [];
12666
+ if (!existsSync26(dir)) return [];
12576
12667
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
12577
12668
  const out = [];
12578
12669
  for (const name of readdirSync9(dir)) {
@@ -12630,11 +12721,11 @@ async function reconcileRuns(o) {
12630
12721
  }
12631
12722
 
12632
12723
  // src/live-runs.ts
12633
- import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
12724
+ import { existsSync as existsSync27, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
12634
12725
  import { join as join31 } from "path";
12635
12726
  function liveRuns(stateDir, isAlive = runIsAlive) {
12636
12727
  const dir = join31(stateDir, "streams");
12637
- if (!existsSync28(dir)) return [];
12728
+ if (!existsSync27(dir)) return [];
12638
12729
  const out = [];
12639
12730
  for (const name of readdirSync10(dir)) {
12640
12731
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
@@ -12650,7 +12741,7 @@ function runActivity(stateDir, isAlive = runIsAlive) {
12650
12741
  const activeRuns = liveRuns(stateDir, isAlive).length;
12651
12742
  const dir = join31(stateDir, "streams");
12652
12743
  let newest = 0;
12653
- if (existsSync28(dir)) {
12744
+ if (existsSync27(dir)) {
12654
12745
  for (const name of readdirSync10(dir)) {
12655
12746
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
12656
12747
  const streamPath = join31(dir, name);
@@ -12743,7 +12834,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
12743
12834
  if (host === "windows") return [];
12744
12835
  const ctx = installContext();
12745
12836
  const systemd = detectSystemd();
12746
- return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync29(p));
12837
+ return ["run", "release", "passengers"].flatMap((job) => planInstall(cfg.ship, crewHome, host, systemd, job, ctx).unitPaths).filter((p) => existsSync28(p));
12747
12838
  };
12748
12839
  return {
12749
12840
  liveRuns: () => liveRuns(stateDir).map((r) => ({ role: r.role, route: r.route, pid: r.pid, ticketRecordId: r.ticketRecordId ?? void 0, streamPath: r.streamPath })),
@@ -12804,7 +12895,7 @@ function realDeprovisionDeps(cfg, emit, crewHome, getStore) {
12804
12895
  },
12805
12896
  remaining() {
12806
12897
  const left = [];
12807
- if (existsSync29(cfg.configFile)) left.push(`config ${cfg.configFile}`);
12898
+ if (existsSync28(cfg.configFile)) left.push(`config ${cfg.configFile}`);
12808
12899
  for (const h of forgeHosts()) if (hasForgeToken(cfg.ship.forges, h, stateDir)) left.push(`forge token for ${h}`);
12809
12900
  try {
12810
12901
  for (const k of readdirSync11(keysDir(stateDir))) left.push(`seat key ${k}`);
@@ -13161,7 +13252,7 @@ function makeCachePass(o) {
13161
13252
  // src/passenger-containers.ts
13162
13253
  var realExec2 = (cmd, args, opts) => {
13163
13254
  try {
13164
- return execFileSync7(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
13255
+ return execFileSync8(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: opts?.timeoutMs });
13165
13256
  } catch (e) {
13166
13257
  const err3 = e;
13167
13258
  const why = [err3.stderr, err3.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
@@ -13384,7 +13475,7 @@ var SANDBOX_BRIDGE_PATH = `${CREW_PREFIX}/bin/intel-bridge`;
13384
13475
  function readBridgeBundle() {
13385
13476
  const here = dirname15(fileURLToPath(import.meta.url));
13386
13477
  for (const p of [join32(here, "sandbox-intel-bridge.cjs"), join32(here, "..", "dist", "sandbox-intel-bridge.cjs")]) {
13387
- if (existsSync30(p)) return readFileSync28(p, "utf8");
13478
+ if (existsSync29(p)) return readFileSync28(p, "utf8");
13388
13479
  }
13389
13480
  return void 0;
13390
13481
  }
@@ -13490,7 +13581,7 @@ var WARM_MAX_BYTES = 64 * 1024 * 1024;
13490
13581
  var globToRegExp = (glob) => new RegExp(`^${glob.split("*").map((p) => p.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
13491
13582
  function readWarmFiles(dir, ref, patterns) {
13492
13583
  if (patterns.length === 0) return {};
13493
- const git2 = (args) => execFileSync8("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
13584
+ const git2 = (args) => execFileSync9("git", ["-C", dir, ...args], { encoding: "utf8", maxBuffer: WARM_MAX_BYTES, stdio: ["ignore", "pipe", "pipe"] });
13494
13585
  const all = git2(["ls-tree", "-r", "--name-only", ref]).split("\n").filter(Boolean);
13495
13586
  const res = patterns.map(globToRegExp);
13496
13587
  const out = {};
@@ -13682,10 +13773,10 @@ async function sandboxBuildTick(repos, deps, force = false) {
13682
13773
  }
13683
13774
 
13684
13775
  // src/doctor-checks.ts
13685
- import { execFileSync as execFileSync9, spawnSync as spawnSync8 } from "child_process";
13776
+ import { execFileSync as execFileSync10, spawnSync as spawnSync8 } from "child_process";
13686
13777
  var PROBE_BRANCH = "crew-probe-branch";
13687
13778
  var realGit = (cwd, args, env) => {
13688
- execFileSync9("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
13779
+ execFileSync10("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
13689
13780
  };
13690
13781
  function isSshRemote(url) {
13691
13782
  return /^ssh:\/\//.test(url) || /^[\w.-]+@[\w.-]+:/.test(url);
@@ -13815,6 +13906,11 @@ function managedRuntimeWarnings(kind, ship) {
13815
13906
  }
13816
13907
  return out;
13817
13908
  }
13909
+ function runtimeDirWarning(platform2, uid, exists, user = "the crew user") {
13910
+ if (platform2 !== "linux" || uid === null || uid === 0) return null;
13911
+ if (exists(`/run/user/${uid}`)) return null;
13912
+ return `/run/user/${uid} is missing, so \`systemctl --user\` cannot reach the user manager: lingering is off for ${user}; run \`sudo loginctl enable-linger ${user}\``;
13913
+ }
13818
13914
  var MANAGED_GIT_EMAIL = "crew@tablation.dev";
13819
13915
  function readGlobalGitIdentity() {
13820
13916
  const get2 = (key) => {
@@ -13833,7 +13929,7 @@ function managedGitIdentityWarnings(kind, identity) {
13833
13929
 
13834
13930
  // src/selftest.ts
13835
13931
  import { spawn as spawn6 } from "child_process";
13836
- import { existsSync as existsSync31, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
13932
+ import { existsSync as existsSync30, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, rmSync as rmSync16, writeFileSync as writeFileSync21 } from "fs";
13837
13933
  import { tmpdir as tmpdir4 } from "os";
13838
13934
  import { join as join33 } from "path";
13839
13935
  var SELFTEST_TIMEOUT_MS = 12e4;
@@ -14038,7 +14134,7 @@ var fileFor2 = (stateDir) => join33(stateDir, "selftest.json");
14038
14134
  function readSelftest(stateDir) {
14039
14135
  try {
14040
14136
  const p = fileFor2(stateDir);
14041
- return existsSync31(p) ? JSON.parse(readFileSync29(p, "utf8")) : {};
14137
+ return existsSync30(p) ? JSON.parse(readFileSync29(p, "utf8")) : {};
14042
14138
  } catch {
14043
14139
  return {};
14044
14140
  }
@@ -14059,9 +14155,9 @@ function doctorSelftestLines(stored, toolchains) {
14059
14155
  }
14060
14156
 
14061
14157
  // src/cli.ts
14062
- import { resolve as resolve7, dirname as dirname34, join as join56, basename as basename16 } from "path";
14158
+ import { resolve as resolve7, dirname as dirname35, join as join56, basename as basename16 } from "path";
14063
14159
  import { fileURLToPath as fileURLToPath2 } from "url";
14064
- import { hostname as hostname5, homedir as homedir11 } from "os";
14160
+ import { hostname as hostname5, homedir as homedir11, userInfo as userInfo2 } from "os";
14065
14161
  import { spawnSync as spawnSync12 } from "child_process";
14066
14162
  import { parse as parseYaml2 } from "yaml";
14067
14163
 
@@ -14364,7 +14460,7 @@ async function signOutSessions(o) {
14364
14460
  }
14365
14461
 
14366
14462
  // src/reset.ts
14367
- import { existsSync as existsSync32, mkdirSync as mkdirSync22, readFileSync as readFileSync31, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
14463
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readFileSync as readFileSync31, readdirSync as readdirSync12, rmSync as rmSync18, statSync as statSync10, writeFileSync as writeFileSync23 } from "fs";
14368
14464
  import { dirname as dirname17, join as join35 } from "path";
14369
14465
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
14370
14466
  function clonedCheckoutsFile(stateDir) {
@@ -14437,7 +14533,7 @@ async function runReset(o, fx) {
14437
14533
  }
14438
14534
  if (o.beforeState) steps.push(...await o.beforeState());
14439
14535
  steps.push(...o.wipeState ? wipeStateDir(o.stateDir, o.dryRun) : removeState(o.stateDir, o.dryRun));
14440
- if (existsSync32(o.configFile)) {
14536
+ if (existsSync31(o.configFile)) {
14441
14537
  if (!o.dryRun) rmSync18(o.configFile, { force: true });
14442
14538
  steps.push({ step: "config", target: o.configFile, action: mark(o.dryRun) });
14443
14539
  } else {
@@ -14446,7 +14542,7 @@ async function runReset(o, fx) {
14446
14542
  return steps;
14447
14543
  }
14448
14544
  function removeCheckout(dir, dryRun, fx) {
14449
- if (!existsSync32(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
14545
+ if (!existsSync31(dir)) return { step: "checkout", target: dir, action: "absent", detail: "already gone" };
14450
14546
  const { dirty, linkedWorktrees } = fx.inspectCheckout(dir);
14451
14547
  if (dirty.length) {
14452
14548
  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" : ""})` };
@@ -14467,7 +14563,7 @@ function removeState(stateDir, dryRun) {
14467
14563
  } catch {
14468
14564
  }
14469
14565
  for (const t of targets) {
14470
- if (!existsSync32(t)) continue;
14566
+ if (!existsSync31(t)) continue;
14471
14567
  if (!dryRun) rmSync18(t, { recursive: true, force: true });
14472
14568
  out.push({ step: "state", target: t, action: mark(dryRun) });
14473
14569
  }
@@ -14863,13 +14959,13 @@ function hostArchitecture(arch = process.arch) {
14863
14959
 
14864
14960
  // src/pool-member-host.ts
14865
14961
  import "child_process";
14866
- import { existsSync as existsSync36 } from "fs";
14962
+ import { existsSync as existsSync35 } from "fs";
14867
14963
  import { hostname as hostname3 } from "os";
14868
14964
 
14869
14965
  // src/machine-id.ts
14870
- import { execFileSync as execFileSync10 } from "child_process";
14966
+ import { execFileSync as execFileSync11 } from "child_process";
14871
14967
  import { createHash as createHash8 } from "crypto";
14872
- import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "fs";
14968
+ import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "fs";
14873
14969
  import { dirname as dirname19, join as join37 } from "path";
14874
14970
  function machineIdPathFor(stateDir) {
14875
14971
  return join37(stateDir, "machine-id");
@@ -14878,14 +14974,14 @@ var sha256 = (s) => createHash8("sha256").update(s).digest("hex");
14878
14974
  function readRawMachineId(platform2 = process.platform) {
14879
14975
  try {
14880
14976
  if (platform2 === "darwin") {
14881
- const out = execFileSync10("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
14977
+ const out = execFileSync11("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
14882
14978
  encoding: "utf8",
14883
14979
  stdio: ["ignore", "pipe", "ignore"]
14884
14980
  });
14885
14981
  return /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
14886
14982
  }
14887
14983
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
14888
- if (existsSync33(p)) {
14984
+ if (existsSync32(p)) {
14889
14985
  const id = readFileSync33(p, "utf8").trim();
14890
14986
  if (id) return id;
14891
14987
  }
@@ -14897,7 +14993,7 @@ function readRawMachineId(platform2 = process.platform) {
14897
14993
  function ensureMachineId(stateDir, opts = {}) {
14898
14994
  if (opts.override?.trim()) return sha256(opts.override.trim());
14899
14995
  const path = machineIdPathFor(stateDir);
14900
- if (existsSync33(path)) {
14996
+ if (existsSync32(path)) {
14901
14997
  const cached = readFileSync33(path, "utf8").trim();
14902
14998
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
14903
14999
  }
@@ -14918,7 +15014,7 @@ function matchShipRows(ships, shipName2, machineId) {
14918
15014
 
14919
15015
  // src/provision.ts
14920
15016
  import { spawnSync as spawnSync9 } from "child_process";
14921
- import { existsSync as existsSync34, readFileSync as readFileSync34, writeFileSync as writeFileSync26 } from "fs";
15017
+ import { existsSync as existsSync33, readFileSync as readFileSync34, writeFileSync as writeFileSync26 } from "fs";
14922
15018
  import { hostname as hostname2 } from "os";
14923
15019
  import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
14924
15020
 
@@ -15535,7 +15631,7 @@ function realProvisionDeps(crewHome) {
15535
15631
  };
15536
15632
  const configFile = () => {
15537
15633
  const f = findConfigFile(crewHome);
15538
- return existsSync34(f) ? f : null;
15634
+ return existsSync33(f) ? f : null;
15539
15635
  };
15540
15636
  return {
15541
15637
  crew: spawnCrew,
@@ -15569,16 +15665,16 @@ function realProvisionDeps(crewHome) {
15569
15665
  continue;
15570
15666
  }
15571
15667
  for (const p of plan.unitPaths) {
15572
- if (!existsSync34(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
15668
+ if (!existsSync33(p) || readFileSync34(p, "utf8") !== plan.unitContent[p]) unitsStale = true;
15573
15669
  }
15574
15670
  }
15575
15671
  const label = labelFor(crewHome, "run");
15576
15672
  let running = false;
15577
15673
  let lingering = true;
15578
15674
  if (mechanism === "systemd") {
15579
- const system = ctx.root || existsSync34(systemUnitPathFor(label));
15675
+ const system = ctx.root || existsSync33(systemUnitPathFor(label));
15580
15676
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
15581
- running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
15677
+ running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`], { env: withUserBusEnv() }).status === 0;
15582
15678
  if (!system) {
15583
15679
  const out = spawnSync9("loginctl", ["show-user", ctx.user, "-p", "Linger"], { encoding: "utf8" });
15584
15680
  lingering = /Linger=yes/.test(out.stdout ?? "");
@@ -15632,7 +15728,7 @@ host=${host}
15632
15728
 
15633
15729
  // src/tunnel.ts
15634
15730
  import { spawn as spawn7 } from "child_process";
15635
- import { existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync35, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
15731
+ import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync35, writeFileSync as writeFileSync27, unlinkSync as unlinkSync3, readdirSync as readdirSync13 } from "fs";
15636
15732
  import { randomInt } from "crypto";
15637
15733
  import { join as join39 } from "path";
15638
15734
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
@@ -15716,7 +15812,7 @@ function tunnelStatePath(stateDir, workspaceId) {
15716
15812
  }
15717
15813
  function readPersistedTunnel(stateDir, workspaceId) {
15718
15814
  const path = tunnelStatePath(stateDir, workspaceId);
15719
- if (!existsSync35(path)) return void 0;
15815
+ if (!existsSync34(path)) return void 0;
15720
15816
  try {
15721
15817
  return JSON.parse(readFileSync35(path, "utf8"));
15722
15818
  } catch {
@@ -15856,7 +15952,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15856
15952
  }
15857
15953
  function readAllPersistedWorkspaceIds(stateDir) {
15858
15954
  const dir = join39(stateDir, "passengers");
15859
- if (!existsSync35(dir)) return [];
15955
+ if (!existsSync34(dir)) return [];
15860
15956
  return readdirSync13(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
15861
15957
  }
15862
15958
  var TUNNEL_REJECTED_KEY = "tunnel_rejected";
@@ -15896,7 +15992,7 @@ function realPoolMemberDeps(o) {
15896
15992
  crewVersion: crewVersion(o.crewHome),
15897
15993
  hostname: hostname3()
15898
15994
  },
15899
- hasShipConfig: () => existsSync36(findConfigFile(o.crewHome)),
15995
+ hasShipConfig: () => existsSync35(findConfigFile(o.crewHome)),
15900
15996
  async provision(lease, instanceClass, shipKind) {
15901
15997
  const hosts = lease.forges.map((f) => f.host.toLowerCase());
15902
15998
  const lines2 = [];
@@ -17272,7 +17368,7 @@ async function resolveOne(tracker, t, seat, approvedStatus, buildingStatus, affi
17272
17368
  }
17273
17369
 
17274
17370
  // src/repo-refresh.ts
17275
- import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29 } from "fs";
17371
+ import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync36, writeFileSync as writeFileSync29 } from "fs";
17276
17372
  import { dirname as dirname21 } from "path";
17277
17373
  var REFRESH_COOLDOWN_MS = 2 * 6e4;
17278
17374
  var UNSERVABLE_PASSES_BEFORE_ATTENTION = 3;
@@ -17302,7 +17398,7 @@ function applyRepoRows(resolved, rows) {
17302
17398
  }
17303
17399
  function persistRepoRows(stateDir, routeName, rows) {
17304
17400
  const path = resolvedPathFor(stateDir, routeName);
17305
- if (!existsSync37(path)) return;
17401
+ if (!existsSync36(path)) return;
17306
17402
  const raw = JSON.parse(readFileSync36(path, "utf8"));
17307
17403
  applyRepoRows(raw, rows);
17308
17404
  mkdirSync26(dirname21(path), { recursive: true });
@@ -17757,12 +17853,12 @@ var localRefSkipReported = (ticket, problem, events) => events.some((e) => e.rec
17757
17853
 
17758
17854
  // src/connect-wizard.ts
17759
17855
  import { delimiter, dirname as dirname22, join as join41 } from "path";
17760
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync38, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
17856
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync30 } from "fs";
17761
17857
  import { stringify as stringify3 } from "yaml";
17762
17858
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
17763
17859
  function shouldRunWizard(opts) {
17764
17860
  if (!opts.isTTY) return false;
17765
- const exists = opts.configExists ?? existsSync38;
17861
+ const exists = opts.configExists ?? existsSync37;
17766
17862
  return !configSearchPath(opts.crewHome).some((p) => exists(p));
17767
17863
  }
17768
17864
  function firstRunConfigPath() {
@@ -17956,7 +18052,7 @@ function flagWizardAnswers(f, env) {
17956
18052
  }
17957
18053
 
17958
18054
  // src/release-run.ts
17959
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as existsSync39, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
18055
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync31, existsSync as existsSync38, copyFileSync, mkdirSync as mkdirSync28, mkdtempSync as mkdtempSync3, rmSync as rmSync19 } from "fs";
17960
18056
  import { dirname as dirname24, join as join43 } from "path";
17961
18057
  import { tmpdir as tmpdir5, hostname as hostname4 } from "os";
17962
18058
 
@@ -18656,6 +18752,17 @@ function ensureCommitterIdentity(o) {
18656
18752
  function isConflict(message, unmergedPaths) {
18657
18753
  return unmergedPaths.length > 0 || /CONFLICT \(|Automatic merge failed|unmerged (files|paths)/i.test(message);
18658
18754
  }
18755
+ var DEEPEN_STEP = 200;
18756
+ var DEEPEN_ATTEMPTS = 5;
18757
+ function deepenUntilMergeBase(o, branch) {
18758
+ const { remote } = o.repo.branch;
18759
+ for (let i = 0; i < DEEPEN_ATTEMPTS && gitOk(o.cwd, ["merge-base", "HEAD", branch]) === null; i++) {
18760
+ const before = gitOk(o.cwd, ["rev-list", "--count", "--all"]);
18761
+ if (gitOk(o.cwd, ["fetch", "--quiet", `--deepen=${DEEPEN_STEP}`, remote]) === null) return;
18762
+ o.emit.emit(`deepened the release clone to find the merge base of ${branch}`, { step: "merge" });
18763
+ if (gitOk(o.cwd, ["rev-list", "--count", "--all"]) === before) return;
18764
+ }
18765
+ }
18659
18766
  function mergeOne(o, c) {
18660
18767
  const subject = c.entries[0] ?? `${c.ticket.title ?? c.ticket.issue_id} (${c.ticket.issue_id})`;
18661
18768
  if (o.dryRun) {
@@ -18663,6 +18770,7 @@ function mergeOne(o, c) {
18663
18770
  return { applied: true };
18664
18771
  }
18665
18772
  const before = headSha(o.cwd);
18773
+ if (remoteOf(o.cwd)) deepenUntilMergeBase(o, c.branch);
18666
18774
  const clearFailure = () => {
18667
18775
  o.attention?.clear(`release_merge_failed:${o.scope ?? "release"}`);
18668
18776
  };
@@ -18729,11 +18837,11 @@ async function bump(o, size) {
18729
18837
  }
18730
18838
  const files = o.repo.release.versionFiles.map((f) => join43(o.cwd, f));
18731
18839
  const first = files[0];
18732
- if (!existsSync39(first)) {
18840
+ if (!existsAt(first, existsSync38)) {
18733
18841
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
18734
18842
  return null;
18735
18843
  }
18736
- const firstText = readFileSync37(first, "utf8");
18844
+ const firstText = readTextAt(first, (p) => readFileSync37(p, "utf8"));
18737
18845
  const problem = versionFileProblem(o.repo.release.versionFiles[0], firstText);
18738
18846
  if (problem) {
18739
18847
  o.emit.error(problem);
@@ -18752,13 +18860,13 @@ async function bump(o, size) {
18752
18860
  return version;
18753
18861
  }
18754
18862
  for (const f of files) {
18755
- const src = readFileSync37(f, "utf8");
18863
+ const src = readTextAt(f, (p) => readFileSync37(p, "utf8"));
18756
18864
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
18757
18865
  if (out === src) {
18758
18866
  o.emit.error(`no version field in ${f}`);
18759
18867
  return null;
18760
18868
  }
18761
- writeFileSync31(f, out);
18869
+ writeTextAt(f, out, writeFileSync31);
18762
18870
  }
18763
18871
  return version;
18764
18872
  }
@@ -18771,8 +18879,8 @@ function writeChangelog(o, version, entries) {
18771
18879
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
18772
18880
  return;
18773
18881
  }
18774
- const existing = existsSync39(path) ? readFileSync37(path, "utf8") : "# Changelog\n";
18775
- writeFileSync31(path, insertChangelogSection(existing, section));
18882
+ const existing = existsAt(path, existsSync38) ? readTextAt(path, (p) => readFileSync37(p, "utf8")) : "# Changelog\n";
18883
+ writeTextAt(path, insertChangelogSection(existing, section), writeFileSync31);
18776
18884
  }
18777
18885
  async function confirm(o, expected) {
18778
18886
  if (!o.repo.hooks.released) return void 0;
@@ -18877,7 +18985,7 @@ function openReleaseCheckout(o) {
18877
18985
  }
18878
18986
  for (const f of o.repo.worktrees.copy) {
18879
18987
  const src = join43(o.cwd, f);
18880
- if (!existsSync39(src)) continue;
18988
+ if (!existsSync38(src)) continue;
18881
18989
  mkdirSync28(dirname24(join43(dir, f)), { recursive: true });
18882
18990
  copyFileSync(src, join43(dir, f));
18883
18991
  }
@@ -19017,6 +19125,12 @@ async function runReleaseCycle(o, movedPast2, localRefSkips) {
19017
19125
  if (inPlace) {
19018
19126
  return mergeAndRelease(o, decision, fresh);
19019
19127
  }
19128
+ if (remoteOf(o.cwd)) {
19129
+ const releaseBranch = `crew/release-${Date.now()}`;
19130
+ git(o.cwd, ["checkout", "-q", "--no-track", "-b", releaseBranch, remoteRef]);
19131
+ o.emit.emit("release checkout: the release sandbox clone", { step: "release" });
19132
+ return mergeAndRelease(o, decision, fresh, releaseBranch);
19133
+ }
19020
19134
  const primary = checkGuards(o.cwd, base);
19021
19135
  if (primary) {
19022
19136
  o.emit.emit(scoped(`primary checkout not fast-forwarded: ${primary.detail}; its dist/ is stale until rebuilt`), { step: "release" });
@@ -19708,7 +19822,7 @@ function runForge(a, io) {
19708
19822
  }
19709
19823
 
19710
19824
  // src/mirror-contract.ts
19711
- import { existsSync as existsSync40 } from "fs";
19825
+ import { existsSync as existsSync39 } from "fs";
19712
19826
 
19713
19827
  // src/passenger-mirror.ts
19714
19828
  import { mkdirSync as mkdirSync29 } from "fs";
@@ -19749,11 +19863,15 @@ function mirrorOutcome(m) {
19749
19863
 
19750
19864
  // src/mirror-contract.ts
19751
19865
  function lacksCheckout(ship, dir) {
19752
- return ship.runner === "sandbox" && !existsSync40(dir);
19866
+ return ship.runner === "sandbox" && !existsSync39(dir) && !remoteOf(dir);
19753
19867
  }
19754
19868
  function mirrorRepoOfTarget(t) {
19755
19869
  return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
19756
19870
  }
19871
+ function cacheRepoOfTarget(ship, t, origin) {
19872
+ if (origin) return parseRemote(origin);
19873
+ return lacksCheckout(ship, t.dir) ? mirrorRepoOfTarget(t) : null;
19874
+ }
19757
19875
  async function readMirrorContract(cache, repo, base) {
19758
19876
  for (const name of REPO_CONFIG_FILENAMES) {
19759
19877
  const text2 = await cache.showFile(repo, base, name);
@@ -19762,10 +19880,13 @@ async function readMirrorContract(cache, repo, base) {
19762
19880
  return null;
19763
19881
  }
19764
19882
  function noCheckoutReleaseNote(repo, mode) {
19765
- if (mode === "local" || mode === null) {
19766
- return `${repo}: no checkout on this ship; release.mode "local" repositories are released by the host that owns the deploy hook, which takes a ticket at verified`;
19883
+ if (mode === "external") {
19884
+ return `${repo}: no checkout on this ship; release.mode "external" repositories are handed off by a ship with a checkout`;
19767
19885
  }
19768
- return `${repo}: no checkout on this ship; release.mode "${mode}" needs the sandboxed release over a mirror-fed clone, which is not built (CREW-2940)`;
19886
+ return `${repo}: no checkout on this ship; release.mode "local" repositories are released by the host that owns the deploy hook, which takes a ticket at verified`;
19887
+ }
19888
+ function releasesFromSandbox(mode) {
19889
+ return mode === "integrate" || mode === "ci_auto" || mode === "ci_manual";
19769
19890
  }
19770
19891
  var reported = /* @__PURE__ */ new Set();
19771
19892
  function firstReport(key) {
@@ -19774,6 +19895,144 @@ function firstReport(key) {
19774
19895
  return true;
19775
19896
  }
19776
19897
 
19898
+ // src/release-sandbox.ts
19899
+ import { execFile as execFile3 } from "child_process";
19900
+ import { mkdirSync as mkdirSync30, writeFileSync as writeFileSync32 } from "fs";
19901
+ import { dirname as dirname26 } from "path";
19902
+ import { promisify as promisify3 } from "util";
19903
+ var execFileP3 = promisify3(execFile3);
19904
+ var RELEASE_CLONE_DEPTH = 50;
19905
+ var FORGE_TOKEN_VAR = "CREW_FORGE_TOKEN";
19906
+ var FORGE_USER_VAR = "CREW_FORGE_USER";
19907
+ var CREDENTIAL_HELPER2 = `!f() { echo username=\${${FORGE_USER_VAR}:-x-access-token}; echo password=$${FORGE_TOKEN_VAR}; }; f`;
19908
+ function releaseCloneScript(o) {
19909
+ const mirror = `${o.mirrorDir}/${mirrorRelPath(o.repo)}`;
19910
+ const depth = o.depth ?? RELEASE_CLONE_DEPTH;
19911
+ const branch = shQuote2(o.base);
19912
+ return [
19913
+ "set -e",
19914
+ "export GIT_TERMINAL_PROMPT=0",
19915
+ // The mirror is written by a helper container as root and mounted read-only, so git sees it as someone else's.
19916
+ `git config --global --add safe.directory '*'`,
19917
+ `git config --global credential.helper ${shQuote2(CREDENTIAL_HELPER2)}`,
19918
+ `m=${shQuote2(mirror)}`,
19919
+ `depth=${depth}`,
19920
+ `t=$(git -C "$m" describe --tags --abbrev=0 --match ${shQuote2(o.tagPattern)} ${shQuote2(`refs/heads/${o.base}`)} 2>/dev/null || true)`,
19921
+ `if [ -n "$t" ]; then n=$(git -C "$m" rev-list --count "$t..refs/heads/"${branch}); [ $((n + 20)) -gt "$depth" ] && depth=$((n + 20)); fi`,
19922
+ `git clone --quiet --no-tags --depth "$depth" --single-branch --branch ${branch} "file://$m" ${shQuote2(o.workDir)}`,
19923
+ `cd ${shQuote2(o.workDir)}`,
19924
+ `git remote set-url origin ${shQuote2(o.origin)}`,
19925
+ `git config crew.releaseDepth "$depth"`,
19926
+ // A tag already in the window: only its (annotated) tag object is new.
19927
+ `if [ -n "$t" ]; then git fetch --quiet --no-tags "file://$m" "refs/tags/$t:refs/tags/$t" || true; fi`,
19928
+ ...o.branches.flatMap((b) => {
19929
+ const spec = shQuote2(`+refs/heads/${b}:refs/remotes/origin/${b}`);
19930
+ return [
19931
+ `git config --add remote.origin.fetch ${spec}`,
19932
+ `git fetch --quiet --no-tags --depth "$depth" "file://$m" ${spec} 2>/dev/null || true`
19933
+ ];
19934
+ })
19935
+ ].join("\n");
19936
+ }
19937
+ var endpointsOf = (g) => g.groups.flatMap((x) => x.endpoints.map((e) => `${e.host}:${e.port}`));
19938
+ async function openReleaseSandbox(o) {
19939
+ const cfg = { ...DEFAULT_SANDBOX_CONFIG, ...o.sandbox };
19940
+ const run3 = o.exec ?? ((bin, args, env) => execFileP3(bin, args, { env, maxBuffer: 16 * 1024 * 1024 }));
19941
+ const name = sandboxName({ route: o.route, role: "release", ticket: o.repoName }, o.now?.());
19942
+ let policyFile = cfg.policy;
19943
+ let generated;
19944
+ if (!policyFile) {
19945
+ generated = generateSandboxPolicy({ mirrorDir: cfg.mirrorDir, workDir: cfg.workDir, ...o.policy });
19946
+ policyFile = o.policyFile;
19947
+ mkdirSync30(dirname26(policyFile), { recursive: true });
19948
+ writeFileSync32(policyFile, generated.yaml, { mode: 384 });
19949
+ }
19950
+ const secrets = { ...process.env };
19951
+ const secretKeys = [];
19952
+ if (o.token) {
19953
+ secrets[FORGE_TOKEN_VAR] = o.token;
19954
+ secrets[FORGE_USER_VAR] = o.username ?? "x-access-token";
19955
+ secretKeys.push(FORGE_TOKEN_VAR, FORGE_USER_VAR);
19956
+ }
19957
+ const create2 = ["sandbox", "create", "--name", name, "--detach"];
19958
+ if (policyFile) create2.push("--policy", policyFile);
19959
+ const image = cfg.image ?? o.image;
19960
+ if (image) create2.push("--from", image);
19961
+ if (cfg.cacheVolume) {
19962
+ create2.push("--driver-config-json", JSON.stringify({
19963
+ docker: { mounts: [{ type: "volume", source: cfg.cacheVolume, target: cfg.mirrorDir, read_only: true }] }
19964
+ }));
19965
+ }
19966
+ for (const k of secretKeys) create2.push("--env-from", k);
19967
+ create2.push("--", "sleep", String(3 * 3600));
19968
+ const t0 = Date.now();
19969
+ const deleteQuietly = async () => {
19970
+ try {
19971
+ await run3(cfg.bin, ["sandbox", "delete", name], process.env);
19972
+ } catch {
19973
+ }
19974
+ };
19975
+ try {
19976
+ await run3(cfg.bin, create2, secrets);
19977
+ } catch (e) {
19978
+ await deleteQuietly();
19979
+ throw new Error(`openshell could not create the release sandbox: ${describe(e)}`);
19980
+ }
19981
+ o.emit.emit(`sandbox_created: ${name}`, { role: "release", data: { event: "sandbox_created", sandbox: name, role: "release", repo: o.repoName, ms: Date.now() - t0 } });
19982
+ if (generated) {
19983
+ o.emit.emit(`sandbox_policy: ${endpointsOf(generated).length} allowed endpoint(s)`, {
19984
+ role: "release",
19985
+ data: { event: "sandbox_policy", sandbox: name, file: policyFile, allowed: endpointsOf(generated), declared: generated.extraHosts }
19986
+ });
19987
+ }
19988
+ const closeSandbox = async () => {
19989
+ try {
19990
+ const { stdout } = await run3(cfg.bin, ["logs", name], process.env);
19991
+ for (const d of parseDeniedRequests(stdout)) {
19992
+ const where = `${d.host}${d.port !== void 0 ? `:${d.port}` : ""}${d.binary ? ` by ${d.binary}` : ""}`;
19993
+ o.emit.warn(`sandbox_denied: ${where}${d.count > 1 ? ` (${d.count}x)` : ""} \u2014 blocked by the sandbox policy`, { data: { event: "sandbox_denied", sandbox: name, ...d } });
19994
+ }
19995
+ } catch {
19996
+ }
19997
+ const t1 = Date.now();
19998
+ await deleteQuietly();
19999
+ o.emit.emit(`sandbox_deleted: ${name}`, { role: "release", data: { event: "sandbox_deleted", sandbox: name, ms: Date.now() - t1 } });
20000
+ };
20001
+ try {
20002
+ const clone = `${releaseCloneScript({
20003
+ mirrorDir: cfg.mirrorDir,
20004
+ repo: o.repo,
20005
+ workDir: cfg.workDir,
20006
+ origin: o.origin,
20007
+ base: o.base,
20008
+ branches: o.branches,
20009
+ tagPattern: o.tagPattern
20010
+ })}
20011
+ ${containerEngineStubScript()}`;
20012
+ await run3(cfg.bin, ["sandbox", "exec", "-n", name, "--no-login-shell", "--", "sh", "-c", clone], process.env);
20013
+ } catch (e) {
20014
+ await closeSandbox();
20015
+ throw new Error(`could not make the release clone in ${name}: ${describe(e)}`);
20016
+ }
20017
+ const dir = registerRemoteDir({
20018
+ bin: cfg.bin,
20019
+ sandbox: name,
20020
+ workDir: cfg.workDir,
20021
+ prelude: `${sandboxEnvExports()}PATH=${shQuote2(SANDBOX_STUB_DIR)}:"$PATH"; export PATH; `
20022
+ });
20023
+ let closed = false;
20024
+ return {
20025
+ dir,
20026
+ sandbox: name,
20027
+ async close() {
20028
+ if (closed) return;
20029
+ closed = true;
20030
+ unregisterRemoteDir(dir);
20031
+ await closeSandbox();
20032
+ }
20033
+ };
20034
+ }
20035
+
19777
20036
  // src/whoami.ts
19778
20037
  async function buildWhoami(routes, deps = {}) {
19779
20038
  const doFetch = deps.fetch ?? fetch;
@@ -19851,7 +20110,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
19851
20110
  }
19852
20111
 
19853
20112
  // src/watch.ts
19854
- import { watch as watchFile, existsSync as existsSync41, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
20113
+ import { watch as watchFile, existsSync as existsSync40, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
19855
20114
  var E = "\x1B[";
19856
20115
  var dim = (s) => `${E}2m${s}${E}0m`;
19857
20116
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -19952,7 +20211,7 @@ function renderHeaders(states, now = Date.now()) {
19952
20211
  return states.map((s) => renderHeader(s, now)).join("");
19953
20212
  }
19954
20213
  function readFrom(file, from) {
19955
- if (!existsSync41(file)) return { events: [], offset: from };
20214
+ if (!existsSync40(file)) return { events: [], offset: from };
19956
20215
  const size = statSync11(file).size;
19957
20216
  if (size < from) return readFrom(file, 0);
19958
20217
  if (size === from) return { events: [], offset: from };
@@ -20195,8 +20454,8 @@ function nextRoles(decision, ran) {
20195
20454
  }
20196
20455
 
20197
20456
  // src/ship-name.ts
20198
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
20199
- import { dirname as dirname26 } from "path";
20457
+ import { existsSync as existsSync41, mkdirSync as mkdirSync31, readFileSync as readFileSync38, renameSync as renameSync5, writeFileSync as writeFileSync33 } from "fs";
20458
+ import { dirname as dirname27 } from "path";
20200
20459
  import { parseDocument as parseDocument5 } from "yaml";
20201
20460
  var ShipNameError = class extends Error {
20202
20461
  };
@@ -20257,7 +20516,7 @@ async function syncRouteName(i) {
20257
20516
  return { status: "ok", ...base, action, name: i.local };
20258
20517
  }
20259
20518
  function readSyncedName(resolvedFile) {
20260
- if (!existsSync42(resolvedFile)) return void 0;
20519
+ if (!existsSync41(resolvedFile)) return void 0;
20261
20520
  try {
20262
20521
  const v = JSON.parse(readFileSync38(resolvedFile, "utf8")).shipName;
20263
20522
  return typeof v === "string" ? v : void 0;
@@ -20266,7 +20525,7 @@ function readSyncedName(resolvedFile) {
20266
20525
  }
20267
20526
  }
20268
20527
  function writeSyncedName(resolvedFile, name) {
20269
- if (!existsSync42(resolvedFile)) return;
20528
+ if (!existsSync41(resolvedFile)) return;
20270
20529
  let raw;
20271
20530
  try {
20272
20531
  raw = JSON.parse(readFileSync38(resolvedFile, "utf8"));
@@ -20275,9 +20534,9 @@ function writeSyncedName(resolvedFile, name) {
20275
20534
  }
20276
20535
  if (raw.shipName === name) return;
20277
20536
  raw.shipName = name;
20278
- mkdirSync30(dirname26(resolvedFile), { recursive: true });
20537
+ mkdirSync31(dirname27(resolvedFile), { recursive: true });
20279
20538
  const tmp = `${resolvedFile}.tmp${process.pid}`;
20280
- writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
20539
+ writeFileSync33(tmp, `${JSON.stringify(raw, null, 2)}
20281
20540
  `);
20282
20541
  renameSync5(tmp, resolvedFile);
20283
20542
  }
@@ -20309,7 +20568,7 @@ async function reconcileShipName(i) {
20309
20568
  if (result.status !== "ok") return result;
20310
20569
  if (result.action === "adopt" || result.action === "row-wins") {
20311
20570
  const text2 = readFileSync38(i.configFile, "utf8");
20312
- writeFileSync32(i.configFile, setShipNameInYaml(text2, result.name));
20571
+ writeFileSync33(i.configFile, setShipNameInYaml(text2, result.name));
20313
20572
  i.ship.name = result.name;
20314
20573
  if (result.action === "row-wins") {
20315
20574
  i.emit?.warn(
@@ -20968,8 +21227,8 @@ function describeCapability(i) {
20968
21227
  }
20969
21228
 
20970
21229
  // src/agents-sync-run.ts
20971
- import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync33 } from "fs";
20972
- import { dirname as dirname27 } from "path";
21230
+ import { existsSync as existsSync42, mkdirSync as mkdirSync32, readFileSync as readFileSync39, writeFileSync as writeFileSync34 } from "fs";
21231
+ import { dirname as dirname28 } from "path";
20973
21232
  async function runAgentsSync(target, o) {
20974
21233
  const dryRun = o.dryRun ?? false;
20975
21234
  const force = o.force ?? false;
@@ -21002,12 +21261,12 @@ async function runAgentsSync(target, o) {
21002
21261
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
21003
21262
  if (!dryRun) {
21004
21263
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
21005
- const raw = existsSync43(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
21264
+ const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync39(resolvedPath, "utf8")) : {};
21006
21265
  raw.agentPersonas = personaResult.agentPersonas;
21007
21266
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
21008
21267
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
21009
- mkdirSync31(dirname27(resolvedPath), { recursive: true });
21010
- writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
21268
+ mkdirSync32(dirname28(resolvedPath), { recursive: true });
21269
+ writeFileSync34(resolvedPath, `${JSON.stringify(raw, null, 2)}
21011
21270
  `);
21012
21271
  if (target.resolved) {
21013
21272
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -21461,7 +21720,7 @@ function existingRepoConfig(f) {
21461
21720
  }
21462
21721
 
21463
21722
  // src/repos-hooks-agent.ts
21464
- import { spawn as spawn8, execFileSync as execFileSync11 } from "child_process";
21723
+ import { spawn as spawn8, execFileSync as execFileSync12 } from "child_process";
21465
21724
  import { realpathSync as realpathSync2 } from "fs";
21466
21725
  import { isAbsolute as isAbsolute3, relative } from "path";
21467
21726
  var AgentSuggestError = class extends Error {
@@ -21564,7 +21823,7 @@ function classifyFailure(o) {
21564
21823
  }
21565
21824
  function porcelain(dir) {
21566
21825
  try {
21567
- return execFileSync11("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
21826
+ return execFileSync12("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: dir, encoding: "utf8" });
21568
21827
  } catch {
21569
21828
  return void 0;
21570
21829
  }
@@ -21633,7 +21892,7 @@ ${run3.err}` });
21633
21892
  }
21634
21893
 
21635
21894
  // src/sample-ticket.ts
21636
- import { basename as basename12, dirname as dirname28, join as join44 } from "path";
21895
+ import { basename as basename12, dirname as dirname29, join as join44 } from "path";
21637
21896
  var SAMPLE_REPO = "crew-sample";
21638
21897
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
21639
21898
  var SAMPLE_MARKER = "sample: true";
@@ -21656,7 +21915,7 @@ has something safe to work on: its test and build hooks always pass, it has no
21656
21915
  remote, and \`crew sample-ticket --remove\` deletes it.
21657
21916
  `;
21658
21917
  function sampleDir(existing, reposBase, workspace) {
21659
- if (existing.length > 0) return join44(dirname28(existing[0]), SAMPLE_REPO);
21918
+ if (existing.length > 0) return join44(dirname29(existing[0]), SAMPLE_REPO);
21660
21919
  return join44(reposBase, workspace, SAMPLE_REPO);
21661
21920
  }
21662
21921
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
@@ -21701,8 +21960,8 @@ function planSampleTicket(i) {
21701
21960
  }
21702
21961
 
21703
21962
  // src/worktree-sweep.ts
21704
- import { existsSync as existsSync44, readFileSync as readFileSync41, readdirSync as readdirSync14 } from "fs";
21705
- import { basename as basename13, dirname as dirname29, join as join45, resolve as resolve5 } from "path";
21963
+ import { existsSync as existsSync43, readFileSync as readFileSync41, readdirSync as readdirSync14 } from "fs";
21964
+ import { basename as basename13, dirname as dirname30, join as join45, resolve as resolve5 } from "path";
21706
21965
  function planWorktreeSweep(target, tickets, contract) {
21707
21966
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
21708
21967
  const verified = contract.statuses.verified;
@@ -21766,7 +22025,7 @@ function worktreeTicketNumbers(target) {
21766
22025
  return [...nums];
21767
22026
  }
21768
22027
  function siblingWorktreeDirs(target) {
21769
- const parent = dirname29(target.dir.replace(/[/\\]+$/, ""));
22028
+ const parent = dirname30(target.dir.replace(/[/\\]+$/, ""));
21770
22029
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
21771
22030
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
21772
22031
  let names;
@@ -21814,7 +22073,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21814
22073
  let removed = 0;
21815
22074
  let keptBranches = 0;
21816
22075
  for (const a of actions) {
21817
- if (!dryRun && base && !a.keepBranch && existsSync44(a.path) && isHandBackMerge(a.path, base)) {
22076
+ if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
21818
22077
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
21819
22078
  log.emit(
21820
22079
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -21822,7 +22081,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21822
22081
  );
21823
22082
  }
21824
22083
  }
21825
- if (!dryRun && existsSync44(a.path)) {
22084
+ if (!dryRun && existsSync43(a.path)) {
21826
22085
  let dirty = false;
21827
22086
  try {
21828
22087
  dirty = status(a.path).length > 0;
@@ -21833,7 +22092,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21833
22092
  continue;
21834
22093
  }
21835
22094
  }
21836
- if (!dryRun && a.requirePushed && a.branch && existsSync44(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
22095
+ if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
21837
22096
  refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
21838
22097
  continue;
21839
22098
  }
@@ -21987,12 +22246,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
21987
22246
  }
21988
22247
 
21989
22248
  // src/stream-sweep.ts
21990
- import { existsSync as existsSync45, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
22249
+ import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21991
22250
  import { join as join46 } from "path";
21992
22251
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
21993
22252
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
21994
22253
  const dir = join46(stateDir, "streams");
21995
- if (!existsSync45(dir)) return [];
22254
+ if (!existsSync44(dir)) return [];
21996
22255
  const cutoff = now - retentionDays * DAY_MS2;
21997
22256
  return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
21998
22257
  try {
@@ -22020,7 +22279,7 @@ function applyStreamSweep(paths, dryRun, log) {
22020
22279
  return removed;
22021
22280
  }
22022
22281
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
22023
- if (!existsSync45(stateDir)) return [];
22282
+ if (!existsSync44(stateDir)) return [];
22024
22283
  const cutoff = now - retentionDays * DAY_MS2;
22025
22284
  return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
22026
22285
  try {
@@ -22032,11 +22291,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
22032
22291
  }
22033
22292
 
22034
22293
  // src/cli.ts
22035
- import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as existsSync52, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
22294
+ import { realpathSync as realpathSync3, readFileSync as readFileSync47, existsSync as existsSync51, mkdirSync as mkdirSync39, writeFileSync as writeFileSync39, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
22036
22295
  import { createInterface } from "readline/promises";
22037
22296
 
22038
22297
  // src/periscope-cmd.ts
22039
- import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync42, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
22298
+ import { existsSync as existsSync45, mkdirSync as mkdirSync33, readFileSync as readFileSync42, rmSync as rmSync21, writeFileSync as writeFileSync35 } from "fs";
22040
22299
  import { join as join47 } from "path";
22041
22300
  var PeriscopeError = class extends Error {
22042
22301
  };
@@ -22054,15 +22313,15 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
22054
22313
  }
22055
22314
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
22056
22315
  try {
22057
- mkdirSync32(priorDir(stateDir), { recursive: true });
22058
- writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
22316
+ mkdirSync33(priorDir(stateDir), { recursive: true });
22317
+ writeFileSync35(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
22059
22318
  } catch {
22060
22319
  }
22061
22320
  }
22062
22321
  function readPeriscopePrior(stateDir, ticketRecordId) {
22063
22322
  try {
22064
22323
  const p = priorPath(stateDir, ticketRecordId);
22065
- return existsSync46(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
22324
+ return existsSync45(p) ? JSON.parse(readFileSync42(p, "utf8")) : void 0;
22066
22325
  } catch {
22067
22326
  return void 0;
22068
22327
  }
@@ -22514,7 +22773,7 @@ ${tail2(preview.output)}`);
22514
22773
  }
22515
22774
 
22516
22775
  // src/periscope-idle.ts
22517
- import { execFileSync as execFileSync12 } from "child_process";
22776
+ import { execFileSync as execFileSync13 } from "child_process";
22518
22777
  import { statSync as statSync13 } from "fs";
22519
22778
  import { join as join48 } from "path";
22520
22779
  var MAX_SCANNED_FILES = 2e4;
@@ -22526,7 +22785,7 @@ function newestWorktreeMtime(root) {
22526
22785
  function newestTrackedMtime(root) {
22527
22786
  let listing;
22528
22787
  try {
22529
- listing = execFileSync12("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
22788
+ listing = execFileSync13("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
22530
22789
  cwd: root,
22531
22790
  encoding: "utf8",
22532
22791
  stdio: ["ignore", "pipe", "ignore"],
@@ -22617,9 +22876,9 @@ async function runPeriscopeIdlePass(d) {
22617
22876
  import { dirname as dirOf2, resolve as resolvePath } from "path";
22618
22877
 
22619
22878
  // src/ssh-keys.ts
22620
- import { execFileSync as execFileSync13 } from "child_process";
22621
- import { existsSync as existsSync47, mkdirSync as mkdirSync33, readFileSync as readFileSync43, chmodSync as chmodSync3 } from "fs";
22622
- import { dirname as dirname30, join as join49 } from "path";
22879
+ import { execFileSync as execFileSync14 } from "child_process";
22880
+ import { existsSync as existsSync46, mkdirSync as mkdirSync34, readFileSync as readFileSync43, chmodSync as chmodSync3 } from "fs";
22881
+ import { dirname as dirname31, join as join49 } from "path";
22623
22882
  var SshKeyError = class extends Error {
22624
22883
  };
22625
22884
  function shipSshKeyPathFor(stateDir) {
@@ -22628,10 +22887,10 @@ function shipSshKeyPathFor(stateDir) {
22628
22887
  function ensureShipSshKeypair(stateDir) {
22629
22888
  const privateKeyPath = shipSshKeyPathFor(stateDir);
22630
22889
  const publicKeyPath = `${privateKeyPath}.pub`;
22631
- if (!existsSync47(privateKeyPath)) {
22632
- mkdirSync33(dirname30(privateKeyPath), { recursive: true });
22890
+ if (!existsSync46(privateKeyPath)) {
22891
+ mkdirSync34(dirname31(privateKeyPath), { recursive: true });
22633
22892
  try {
22634
- execFileSync13("ssh-keygen", [
22893
+ execFileSync14("ssh-keygen", [
22635
22894
  "-t",
22636
22895
  "ed25519",
22637
22896
  "-N",
@@ -22649,7 +22908,7 @@ function ensureShipSshKeypair(stateDir) {
22649
22908
  }
22650
22909
  chmodSync3(privateKeyPath, 384);
22651
22910
  }
22652
- if (!existsSync47(publicKeyPath)) {
22911
+ if (!existsSync46(publicKeyPath)) {
22653
22912
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
22654
22913
  }
22655
22914
  const publicKey = readFileSync43(publicKeyPath, "utf8").trim();
@@ -22657,7 +22916,7 @@ function ensureShipSshKeypair(stateDir) {
22657
22916
  }
22658
22917
  function sshKeygenAvailable() {
22659
22918
  try {
22660
- execFileSync13("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
22919
+ execFileSync14("ssh-keygen", ["-V"], { stdio: ["ignore", "ignore", "ignore"] });
22661
22920
  return true;
22662
22921
  } catch (e) {
22663
22922
  return e.code !== "ENOENT";
@@ -22816,7 +23075,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
22816
23075
  import { createServer as createHttpServer } from "http";
22817
23076
  import { spawn as spawn9 } from "child_process";
22818
23077
  import { randomBytes } from "crypto";
22819
- import { existsSync as existsSync48, mkdirSync as mkdirSync34, readFileSync as readFileSync44, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
23078
+ import { existsSync as existsSync47, mkdirSync as mkdirSync35, readFileSync as readFileSync44, writeFileSync as writeFileSync36, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
22820
23079
  import { join as join50 } from "path";
22821
23080
 
22822
23081
  // src/secret-compare.ts
@@ -22914,7 +23173,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
22914
23173
  }
22915
23174
  function readPersistedSyncDaemon(stateDir, workspaceId) {
22916
23175
  const path = syncDaemonStatePath(stateDir, workspaceId);
22917
- if (!existsSync48(path)) return void 0;
23176
+ if (!existsSync47(path)) return void 0;
22918
23177
  try {
22919
23178
  return JSON.parse(readFileSync44(path, "utf8"));
22920
23179
  } catch {
@@ -22923,8 +23182,8 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
22923
23182
  }
22924
23183
  function writePersistedSyncDaemon(stateDir, state) {
22925
23184
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
22926
- mkdirSync34(join50(stateDir, "passengers"), { recursive: true });
22927
- writeFileSync35(path, `${JSON.stringify(state, null, 2)}
23185
+ mkdirSync35(join50(stateDir, "passengers"), { recursive: true });
23186
+ writeFileSync36(path, `${JSON.stringify(state, null, 2)}
22928
23187
  `);
22929
23188
  }
22930
23189
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -22945,7 +23204,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
22945
23204
  }
22946
23205
  function readAllPersistedSyncWorkspaceIds(stateDir) {
22947
23206
  const dir = join50(stateDir, "passengers");
22948
- if (!existsSync48(dir)) return [];
23207
+ if (!existsSync47(dir)) return [];
22949
23208
  const suffix = "-sync.json";
22950
23209
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
22951
23210
  }
@@ -23003,12 +23262,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
23003
23262
 
23004
23263
  // src/periscope-proxy.ts
23005
23264
  import { spawn as spawn10 } from "child_process";
23006
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync35 } from "fs";
23265
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync36 } from "fs";
23007
23266
  import { createServer, request as httpRequest } from "http";
23008
23267
  import { request as httpsRequest } from "https";
23009
23268
  import { connect as netConnect } from "net";
23010
23269
  import { connect as tlsConnect } from "tls";
23011
- import { dirname as dirname31, isAbsolute as isAbsolute4 } from "path";
23270
+ import { dirname as dirname32, isAbsolute as isAbsolute4 } from "path";
23012
23271
  import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
23013
23272
 
23014
23273
  // src/periscope-inspector.ts
@@ -23122,7 +23381,7 @@ function parsePointedBody(raw) {
23122
23381
  return entry;
23123
23382
  }
23124
23383
  function appendPointed(file, entry, now = /* @__PURE__ */ new Date()) {
23125
- mkdirSync35(dirname31(file), { recursive: true });
23384
+ mkdirSync36(dirname32(file), { recursive: true });
23126
23385
  appendFileSync3(file, `${JSON.stringify({ ...entry, at: now.toISOString() })}
23127
23386
  `);
23128
23387
  }
@@ -23465,7 +23724,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
23465
23724
  }
23466
23725
 
23467
23726
  // src/daemon.ts
23468
- import { existsSync as existsSync49, mkdirSync as mkdirSync36, readFileSync as readFileSync45, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
23727
+ import { existsSync as existsSync48, mkdirSync as mkdirSync37, readFileSync as readFileSync45, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync37 } from "fs";
23469
23728
  import { join as join52 } from "path";
23470
23729
  import { randomBytes as randomBytes2 } from "crypto";
23471
23730
  var DEFAULT_FLOOR_MS = 5e3;
@@ -23563,7 +23822,7 @@ function computeTreeSignature(paths) {
23563
23822
  }
23564
23823
  function makeStaleChecker(paths, opts = {}) {
23565
23824
  const initial = computeTreeSignature(paths);
23566
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync49(opts.reloadSentinel);
23825
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
23567
23826
  }
23568
23827
  function reloadSentinelFor(stateDir) {
23569
23828
  return join52(stateDir, "reload-requested");
@@ -23579,9 +23838,9 @@ function daemonStampFor(stateDir) {
23579
23838
  return join52(stateDir, "daemon.started");
23580
23839
  }
23581
23840
  function noteDaemonStarted(stateDir, now = Date.now()) {
23582
- mkdirSync36(stateDir, { recursive: true });
23841
+ mkdirSync37(stateDir, { recursive: true });
23583
23842
  rmSync22(reloadSentinelFor(stateDir), { force: true });
23584
- writeFileSync36(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
23843
+ writeFileSync37(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
23585
23844
  `);
23586
23845
  }
23587
23846
  function readDaemonStarted(stateDir) {
@@ -24204,7 +24463,7 @@ import { createHash as createHash11 } from "crypto";
24204
24463
  import {
24205
24464
  chmodSync as chmodSync4,
24206
24465
  closeSync as closeSync6,
24207
- existsSync as existsSync50,
24466
+ existsSync as existsSync49,
24208
24467
  fstatSync,
24209
24468
  openSync as openSync6,
24210
24469
  readSync as readSync3,
@@ -24215,8 +24474,8 @@ import {
24215
24474
  } from "fs";
24216
24475
  import { createConnection } from "net";
24217
24476
  import { createServer as createServer2 } from "http";
24218
- import { basename as basename14, dirname as dirname32, join as join53 } from "path";
24219
- import { execFile as execFile3, spawn as spawn12 } from "child_process";
24477
+ import { basename as basename14, dirname as dirname33, join as join53 } from "path";
24478
+ import { execFile as execFile4, spawn as spawn12 } from "child_process";
24220
24479
 
24221
24480
  // src/periscope-control.ts
24222
24481
  import { spawn as spawn11 } from "child_process";
@@ -24531,7 +24790,7 @@ function candidates(live, rotated) {
24531
24790
  const out = [live];
24532
24791
  if (!rotated) return out;
24533
24792
  try {
24534
- for (const n of readdirSync18(dirname32(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname32(live), n));
24793
+ for (const n of readdirSync18(dirname33(live))) if (ROTATED_EVENTS_RE.test(n)) out.push(join53(dirname33(live), n));
24535
24794
  } catch {
24536
24795
  }
24537
24796
  return out;
@@ -24575,7 +24834,7 @@ function lastLines2(path, n) {
24575
24834
  }
24576
24835
  function readTail(live, opts) {
24577
24836
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
24578
- if (!existsSync50(live)) {
24837
+ if (!existsSync49(live)) {
24579
24838
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
24580
24839
  }
24581
24840
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -24691,7 +24950,7 @@ async function tailReply(path, q2, rotated, abort) {
24691
24950
  const deadline = Date.now() + Math.min(wait, MAX_WAIT_SECONDS) * 1e3;
24692
24951
  let r = readTail(path, { after, tail: tail3, rotated });
24693
24952
  while (after && !r.lines.length && !r.reset && Date.now() < deadline && !abort.aborted) {
24694
- await waitForChange(dirname32(path), deadline - Date.now(), abort);
24953
+ await waitForChange(dirname33(path), deadline - Date.now(), abort);
24695
24954
  r = readTail(path, { after: r.cursor, rotated });
24696
24955
  }
24697
24956
  return reply2(200, r);
@@ -24732,7 +24991,7 @@ async function route(deps, method, url, req, abort) {
24732
24991
  const run3 = decodeURIComponent(p.slice("/streams/".length));
24733
24992
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
24734
24993
  const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
24735
- if (!existsSync50(file)) return err2(404, "no such run");
24994
+ if (!existsSync49(file)) return err2(404, "no such run");
24736
24995
  return tailReply(file, url.searchParams, false, abort);
24737
24996
  }
24738
24997
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -24776,7 +25035,7 @@ function send(res, r) {
24776
25035
  res.end(body);
24777
25036
  }
24778
25037
  async function socketIsStale(path) {
24779
- if (!existsSync50(path)) return false;
25038
+ if (!existsSync49(path)) return false;
24780
25039
  return new Promise((resolve8) => {
24781
25040
  const c = createConnection(path);
24782
25041
  c.once("connect", () => {
@@ -24795,7 +25054,7 @@ async function startControlServer(deps) {
24795
25054
  return null;
24796
25055
  }
24797
25056
  if (await socketIsStale(path)) rmSync23(path, { force: true });
24798
- else if (existsSync50(path)) {
25057
+ else if (existsSync49(path)) {
24799
25058
  log(`control API not started: ${path} is in use by another process`);
24800
25059
  return null;
24801
25060
  }
@@ -24868,7 +25127,7 @@ function schedulerHandler(control, run3 = runDetached) {
24868
25127
  }
24869
25128
  function crewJson(args, timeoutMs = 6e4) {
24870
25129
  return new Promise((resolve8) => {
24871
- execFile3(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
25130
+ execFile4(process.execPath, [process.argv[1] ?? "", ...args], { timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (_e, stdout) => {
24872
25131
  try {
24873
25132
  const v = JSON.parse(String(stdout));
24874
25133
  resolve8(v && typeof v === "object" && !Array.isArray(v) ? v : null);
@@ -24881,24 +25140,24 @@ function crewJson(args, timeoutMs = 6e4) {
24881
25140
 
24882
25141
  // src/periscope-mcp.ts
24883
25142
  import { lstat, mkdir, readdir, readFile, readlink, realpath, rm, stat, writeFile } from "fs/promises";
24884
- import { basename as basename15, dirname as dirname33, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
25143
+ import { basename as basename15, dirname as dirname34, isAbsolute as isAbsolute5, join as join55, relative as relative2, resolve as resolve6, sep } from "path";
24885
25144
  import { z } from "zod";
24886
25145
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24887
25146
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24888
25147
 
24889
25148
  // src/periscope-endpoints.ts
24890
25149
  import { randomBytes as randomBytes3 } from "crypto";
24891
- import { chmodSync as chmodSync5, existsSync as existsSync51, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync46, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
25150
+ import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync38, readdirSync as readdirSync19, readFileSync as readFileSync46, rmSync as rmSync24, writeFileSync as writeFileSync38 } from "fs";
24892
25151
  import { join as join54 } from "path";
24893
25152
  var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
24894
25153
  var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
24895
25154
  var endpointDir = (stateDir) => join54(stateDir, "periscope-endpoints");
24896
25155
  var endpointFile = (stateDir, id) => join54(endpointDir(stateDir), `${id}.json`);
24897
25156
  function mintPeriscopeSecret(stateDir, ticketRecordId) {
24898
- mkdirSync37(secretDir(stateDir), { recursive: true, mode: 448 });
25157
+ mkdirSync38(secretDir(stateDir), { recursive: true, mode: 448 });
24899
25158
  const secret = randomBytes3(32).toString("base64url");
24900
25159
  const file = secretFile(stateDir, ticketRecordId);
24901
- writeFileSync37(file, `${JSON.stringify({ secret })}
25160
+ writeFileSync38(file, `${JSON.stringify({ secret })}
24902
25161
  `, { mode: 384 });
24903
25162
  chmodSync5(file, 384);
24904
25163
  return secret;
@@ -24922,8 +25181,8 @@ function readPeriscopeEndpoint(stateDir, ticketRecordId) {
24922
25181
  }
24923
25182
  }
24924
25183
  function writePeriscopeEndpoint(stateDir, rec) {
24925
- mkdirSync37(endpointDir(stateDir), { recursive: true });
24926
- writeFileSync37(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
25184
+ mkdirSync38(endpointDir(stateDir), { recursive: true });
25185
+ writeFileSync38(endpointFile(stateDir, rec.ticketRecordId), `${JSON.stringify(rec, null, 2)}
24927
25186
  `);
24928
25187
  }
24929
25188
  function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
@@ -24931,7 +25190,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
24931
25190
  }
24932
25191
  function listPeriscopeEndpoints(stateDir) {
24933
25192
  const dir = endpointDir(stateDir);
24934
- if (!existsSync51(dir)) return [];
25193
+ if (!existsSync50(dir)) return [];
24935
25194
  const out = [];
24936
25195
  for (const name of readdirSync19(dir)) {
24937
25196
  if (!name.endsWith(".json")) continue;
@@ -25019,10 +25278,10 @@ async function realAncestor(p) {
25019
25278
  }
25020
25279
  if (isLink) {
25021
25280
  if (++hops > 40) throw new PathRefusal(`too many symbolic links resolving ${p}`);
25022
- cur = resolve6(dirname33(cur), await readlink(cur));
25281
+ cur = resolve6(dirname34(cur), await readlink(cur));
25023
25282
  continue;
25024
25283
  }
25025
- const parent = dirname33(cur);
25284
+ const parent = dirname34(cur);
25026
25285
  if (parent === cur) throw new PathRefusal(`cannot resolve ${p}`);
25027
25286
  tail3.push(basename15(cur));
25028
25287
  cur = parent;
@@ -25133,7 +25392,7 @@ function createPeriscopeMcpServer(d, sc) {
25133
25392
  if (Buffer.byteLength(content) > MAX_WRITE_BYTES) return text(`content exceeds ${MAX_WRITE_BYTES} bytes`, true);
25134
25393
  const abs = await inTree(path);
25135
25394
  if (abs === await realpath(root)) return text("path must name a file", true);
25136
- await mkdir(dirname33(abs), { recursive: true });
25395
+ await mkdir(dirname34(abs), { recursive: true });
25137
25396
  await inTree(path);
25138
25397
  await writeFile(abs, content);
25139
25398
  return text(`Wrote ${Buffer.byteLength(content)} bytes to ${await rel(abs)}`);
@@ -25173,7 +25432,7 @@ function createPeriscopeMcpServer(d, sc) {
25173
25432
  inputSchema: { path: z.string() },
25174
25433
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
25175
25434
  }, async ({ path }) => {
25176
- const parent = await inTree(dirname33(path) === "" ? "." : dirname33(path));
25435
+ const parent = await inTree(dirname34(path) === "" ? "." : dirname34(path));
25177
25436
  const abs = join55(parent, basename15(path));
25178
25437
  if (basename15(path) === ".git" || abs === parent) return text("path must name a file", true);
25179
25438
  const st = await lstat(abs).catch(() => void 0);
@@ -25295,7 +25554,7 @@ function findPeriscope(sidecars, segment) {
25295
25554
  }
25296
25555
 
25297
25556
  // src/cli.ts
25298
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname34(process.execPath) : resolve7(dirname34(fileURLToPath2(import.meta.url)), "..");
25557
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname35(process.execPath) : resolve7(dirname35(fileURLToPath2(import.meta.url)), "..");
25299
25558
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
25300
25559
  var CYCLE_SECONDS2 = 120;
25301
25560
  var STALE_CYCLES = 3;
@@ -25814,7 +26073,7 @@ if (command === "passenger-sync-daemon") {
25814
26073
  let images;
25815
26074
  for (const t of reposOf(c)) {
25816
26075
  const origin = originUrlOf(t.dir, "origin");
25817
- const repo = origin ? parseRemote(origin) : null;
26076
+ const repo = cacheRepoOfTarget(ship, t, origin);
25818
26077
  if (!repo) continue;
25819
26078
  let token = null;
25820
26079
  try {
@@ -25914,7 +26173,7 @@ if (command === "passenger-sync-daemon") {
25914
26173
  } else {
25915
26174
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
25916
26175
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
25917
- if (configless && !existsSync52(findConfigFile(CREW_HOME))) {
26176
+ if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
25918
26177
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
25919
26178
  } else {
25920
26179
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -25942,7 +26201,7 @@ if (command === "passenger-sync-daemon") {
25942
26201
  stateDir: cfg.ship.stateDir,
25943
26202
  configFile: cfg.configFile,
25944
26203
  readFile: (p) => readFileSync47(p, "utf8"),
25945
- writeFile: (p, t) => writeFileSync38(p, t),
26204
+ writeFile: (p, t) => writeFileSync39(p, t),
25946
26205
  stdin: () => readFileSync47(0, "utf8"),
25947
26206
  env: process.env
25948
26207
  }
@@ -26267,13 +26526,13 @@ if (command === "passenger-sync-daemon") {
26267
26526
  if (host === "linux" && hasSystemd) {
26268
26527
  const label = labelFor(CREW_HOME, "run");
26269
26528
  const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
26270
- if (existsSync52(staleTimer)) {
26529
+ if (existsSync51(staleTimer)) {
26271
26530
  process.stdout.write(
26272
26531
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
26273
26532
  `
26274
26533
  );
26275
26534
  if (!dryRun) {
26276
- spawnSync12("systemctl", ["--user", "disable", "--now", `${label}.timer`], { cwd: CREW_HOME });
26535
+ spawnSync12("systemctl", ["--user", "disable", "--now", `${label}.timer`], { cwd: CREW_HOME, env: withUserBusEnv() });
26277
26536
  try {
26278
26537
  unlinkSync5(staleTimer);
26279
26538
  } catch {
@@ -26296,7 +26555,7 @@ if (command === "passenger-sync-daemon") {
26296
26555
  const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
26297
26556
  for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
26298
26557
  for (const r of doctorRepos) {
26299
- if (!existsSync52(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
26558
+ if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
26300
26559
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
26301
26560
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
26302
26561
  }
@@ -26323,7 +26582,7 @@ if (command === "passenger-sync-daemon") {
26323
26582
  }
26324
26583
  async function doctorFix(o) {
26325
26584
  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))));
26326
- const unitExists = (p) => existsSync52(p);
26585
+ const unitExists = (p) => existsSync51(p);
26327
26586
  const crontab = o.host === "linux" ? spawnSync12("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
26328
26587
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
26329
26588
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -26340,7 +26599,7 @@ if (command === "passenger-sync-daemon") {
26340
26599
  for (const a of plan.actions) {
26341
26600
  if (a.kind === "enable") {
26342
26601
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
26343
- writeFileSync38(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
26602
+ writeFileSync39(cfg.configFile, planEnable(readFileSync47(cfg.configFile, "utf8"), a.route));
26344
26603
  enabledSound.add(a.route);
26345
26604
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
26346
26605
  } else {
@@ -26384,6 +26643,13 @@ ${changed2.map((c) => ` - ${c}
26384
26643
  return [...out.values()];
26385
26644
  }
26386
26645
  const withShipSandbox = (declared) => cfg.ship.runner === "sandbox" ? [shipSandboxRef(), ...declared] : declared;
26646
+ async function loadContractOf(c, t) {
26647
+ if (!lacksCheckout(cfg.ship, t.dir)) return loadRepoConfig(t.dir);
26648
+ const settings = cacheSettingsOf(cfg.ship.sandbox);
26649
+ const mirror = mirrorRepoOfTarget(t);
26650
+ if (!settings || !mirror) return null;
26651
+ return readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
26652
+ }
26387
26653
  async function resolveOneRepo(c, t) {
26388
26654
  {
26389
26655
  const id = repoIdForName(c, t.name);
@@ -26392,7 +26658,7 @@ ${changed2.map((c) => ` - ${c}
26392
26658
  name: t.name,
26393
26659
  dir: t.dir,
26394
26660
  id,
26395
- config: resolveRepoConfig(loadRepoConfig(t.dir), {
26661
+ config: resolveRepoConfig(await loadContractOf(c, t), {
26396
26662
  hooks: { ...c.hooks, ...o?.hooks },
26397
26663
  labels: { ...c.labels, ...o?.labels },
26398
26664
  release: mergeRouteRelease(c.release, o?.release),
@@ -26448,6 +26714,7 @@ ${changed2.map((c) => ` - ${c}
26448
26714
  return `${who2}; ${lock2}`;
26449
26715
  }
26450
26716
  async function sweepClosedTickets(c, target, branch, tracker, dryRun2, remit, idle = false) {
26717
+ if (remoteOf(target.dir)) return;
26451
26718
  try {
26452
26719
  if (!dryRun2) reconcileHeldWorktrees(state, target.name, remit);
26453
26720
  if (idle && !sweepCandidatesExist(target.dir, branch.base)) return;
@@ -26477,20 +26744,86 @@ ${changed2.map((c) => ` - ${c}
26477
26744
  }
26478
26745
  const precheckTickets = /* @__PURE__ */ new Map();
26479
26746
  const scope0 = (c, t) => releaseLockScope(c.route, t.name);
26747
+ async function releaseFromSandbox(c, target, contract, mirror, opts) {
26748
+ const scope = releaseLockScope(c.route, target.name);
26749
+ const remit = emit.forRoute(c.route);
26750
+ const identity = await resolveReleaseKey(c);
26751
+ const tracker = new Tracker(c, cfg.ship, { apiKey: identity.apiKey });
26752
+ const { byRepo } = ticketsByRepo(c, await tracker.openTickets());
26753
+ const modes = { modes: await tracker.projectWorkModes(), agentAssignees: /* @__PURE__ */ new Set() };
26754
+ const verified = withoutManualVerified(byRepo.get(target.name) ?? [], tracker.contract.statuses.verified, modes).filter((t) => t.status === tracker.contract.statuses.verified);
26755
+ const force2 = command === "deploy" || flag("force");
26756
+ if (!dryRun && verified.length === 0 && !force2) {
26757
+ return { scope, tests: "skipped", outcome: "skipped", detail: "nothing verified \u2014 no release sandbox created" };
26758
+ }
26759
+ const o = c.repoOverrides[target.name];
26760
+ const effective = resolveRepoConfig(contract, {
26761
+ hooks: { ...c.hooks, ...o?.hooks },
26762
+ labels: { ...c.labels, ...o?.labels },
26763
+ release: mergeRouteRelease(c.release, o?.release),
26764
+ branch: { ...c.branch, ...o?.branch },
26765
+ worktrees: { prefix: shipWorktreePrefixFor(c), nameOverride: o?.worktreeName }
26766
+ }, target.dir);
26767
+ const forgeHost = mirror.host;
26768
+ let token = null;
26769
+ try {
26770
+ token = readForgeToken(cfg.ship.forges, forgeHost, cfg.ship.stateDir);
26771
+ } catch {
26772
+ }
26773
+ const sandbox = await openReleaseSandbox({
26774
+ sandbox: cfg.ship.sandbox ?? {},
26775
+ route: c.route,
26776
+ repoName: target.name,
26777
+ image: sandboxImageForRun(readSandboxState(cfg.ship.stateDir), target.name, Boolean(contract.sandbox)) ?? void 0,
26778
+ policy: {
26779
+ forgeHosts: [...new Set(reposOf(c).map((r) => r.forgeHost ?? "github.com"))],
26780
+ tablationUrl: c.baseUrl,
26781
+ authMode: "workspace",
26782
+ provider: "anthropic",
26783
+ harnessBin: "claude",
26784
+ ...contract.sandboxNetwork ? { extraHosts: contract.sandboxNetwork.hosts, toolchains: contract.sandboxNetwork.toolchains } : {}
26785
+ },
26786
+ policyFile: join56(cfg.ship.stateDir, "release", `${target.name}.policy.yaml`),
26787
+ repo: mirror,
26788
+ origin: forgeDescriptorFor(forgeHost).httpsUrl(mirror.host, mirror.path),
26789
+ base: effective.branch.base,
26790
+ branches: verified.map((t) => pushedBranchForTicket(effective, t)),
26791
+ tagPattern: effective.release.tagPattern ?? "v*",
26792
+ token,
26793
+ username: forgeFor(cfg.ship.forges, forgeHost)?.entry.username || forgeDescriptorFor(forgeHost).credentialUsername,
26794
+ emit: remit
26795
+ }).catch((e) => {
26796
+ remit.warn(`${scope}: could not start the release sandbox: ${e.message}`, { step: "release" });
26797
+ return null;
26798
+ });
26799
+ if (!sandbox) return { scope, tests: "skipped", outcome: "skipped", detail: "could not start the release sandbox" };
26800
+ remit.emit(`${scope}: releasing from sandbox ${sandbox.sandbox}`, { step: "release", data: { event: "release_sandbox", sandbox: sandbox.sandbox } });
26801
+ try {
26802
+ const summary = await releasePhase(c, { ...target, dir: sandbox.dir }, opts);
26803
+ return { ...summary, detail: `${summary.detail ? `${summary.detail}; ` : ""}ran in sandbox ${sandbox.sandbox}` };
26804
+ } finally {
26805
+ await sandbox.close();
26806
+ }
26807
+ }
26480
26808
  async function releasePhase(c, target, opts = {}) {
26481
26809
  const scope = releaseLockScope(c.route, target.name);
26482
26810
  const remit = emit.forRoute(c.route);
26483
26811
  if (lacksCheckout(cfg.ship, target.dir)) {
26484
26812
  let mode = null;
26813
+ let contract = null;
26485
26814
  const settings = cacheSettingsOf(cfg.ship.sandbox);
26486
26815
  const mirror = mirrorRepoOfTarget(target);
26487
26816
  if (settings && mirror) {
26488
26817
  try {
26489
- mode = (await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main"))?.release.mode ?? null;
26818
+ contract = await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main");
26819
+ mode = contract?.release.mode ?? null;
26490
26820
  } catch (e) {
26491
26821
  if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
26492
26822
  }
26493
26823
  }
26824
+ if (releasesFromSandbox(mode) && settings && mirror && contract) {
26825
+ return releaseFromSandbox(c, target, contract, mirror, opts);
26826
+ }
26494
26827
  const note = noCheckoutReleaseNote(target.name, mode);
26495
26828
  if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
26496
26829
  return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
@@ -27495,7 +27828,7 @@ ${cu.tail.trimEnd()}
27495
27828
  }
27496
27829
  let failed = false;
27497
27830
  for (const cmd2 of commands) {
27498
- const res2 = spawnSync12(cmd2[0], cmd2.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
27831
+ const res2 = spawnSync12(cmd2[0], cmd2.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() });
27499
27832
  if (res2.stdout) process.stdout.write(res2.stdout);
27500
27833
  if (res2.stderr) process.stderr.write(res2.stderr);
27501
27834
  if (cmd2 === control.startCommand && (res2.error || res2.status !== 0)) {
@@ -27513,7 +27846,7 @@ ${cu.tail.trimEnd()}
27513
27846
  `);
27514
27847
  break;
27515
27848
  }
27516
- const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
27849
+ const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() });
27517
27850
  if (res.stdout) process.stdout.write(res.stdout);
27518
27851
  if (res.stderr) process.stderr.write(res.stderr);
27519
27852
  if (daemonSub !== "status" && (res.error || res.status !== 0)) {
@@ -27841,10 +28174,10 @@ ${cu.tail.trimEnd()}
27841
28174
  `);
27842
28175
  if (!dryRun) {
27843
28176
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
27844
- const raw = existsSync52(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
28177
+ const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync47(resolvedPath, "utf8")) : {};
27845
28178
  raw.agentSkills = result.agentSkills;
27846
- mkdirSync38(dirOf2(resolvedPath), { recursive: true });
27847
- writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
28179
+ mkdirSync39(dirOf2(resolvedPath), { recursive: true });
28180
+ writeFileSync39(resolvedPath, `${JSON.stringify(raw, null, 2)}
27848
28181
  `);
27849
28182
  }
27850
28183
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -27867,7 +28200,7 @@ ${cu.tail.trimEnd()}
27867
28200
  throw e;
27868
28201
  }
27869
28202
  const oldName = cfg.ship.name;
27870
- writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
28203
+ writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), newName));
27871
28204
  cfg.ship.name = newName;
27872
28205
  let renameMachineId;
27873
28206
  try {
@@ -27921,7 +28254,7 @@ ${cu.tail.trimEnd()}
27921
28254
  const found = cfg.routes.find((r) => r.route === routeArg);
27922
28255
  if (!found) throw new ReposError(`no route "${routeArg}" in the config (have: ${cfg.routes.map((r) => r.route).join(", ") || "none"})`);
27923
28256
  const named2 = value("route") ? void 0 : positional[3];
27924
- const dir = named2 ? repoDirOf(configText, dirname34(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
28257
+ const dir = named2 ? repoDirOf(configText, dirname35(cfg.configFile), found.route, named2) : resolvePath(positional[value("route") ? 2 : 3] ?? ".");
27925
28258
  const target = reposOf(found).find((t) => resolvePath(t.dir) === dir || t.name === named2);
27926
28259
  const o = target ? found.repoOverrides[target.name] : void 0;
27927
28260
  runReposCheck(dir, {
@@ -27945,9 +28278,9 @@ ${cu.tail.trimEnd()}
27945
28278
  if (sub === "hooks") {
27946
28279
  try {
27947
28280
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
27948
- const dir = repoDirOf(configText, dirname34(cfg.configFile), a.route, a.name);
27949
- if (!existsSync52(dir)) throw new ReposError(`${dir} does not exist on disk`);
27950
- const files = { exists: (r) => existsSync52(resolvePath(dir, r)), list: (r) => {
28281
+ const dir = repoDirOf(configText, dirname35(cfg.configFile), a.route, a.name);
28282
+ if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
28283
+ const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
27951
28284
  try {
27952
28285
  return readdirSync20(resolvePath(dir, r));
27953
28286
  } catch {
@@ -27997,19 +28330,19 @@ ${cu.tail.trimEnd()}
27997
28330
  let cleared;
27998
28331
  if (clear2) {
27999
28332
  const plan = clearRouteHooks(configText, a.route, a.name);
28000
- if (plan.removed.length > 0) writeFileSync38(cfg.configFile, plan.text);
28333
+ if (plan.removed.length > 0) writeFileSync39(cfg.configFile, plan.text);
28001
28334
  cleared = { file: cfg.configFile, removed: plan.removed };
28002
28335
  }
28003
28336
  if (write && suggestion.source === null && recipeIds.length === 0 && hookSpecs.length === 0) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
28004
28337
  if (write === "route") {
28005
28338
  const plan = planRouteHooks(configText, a.route, a.name, picked.hooks, { replace: flag("replace") });
28006
- writeFileSync38(cfg.configFile, plan.text);
28339
+ writeFileSync39(cfg.configFile, plan.text);
28007
28340
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept, replaced: plan.replaced };
28008
28341
  } else if (write === "repo") {
28009
28342
  const have = existingRepoConfig(files);
28010
28343
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
28011
28344
  const file = resolvePath(dir, ".crew.yaml");
28012
- writeFileSync38(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
28345
+ writeFileSync39(file, starterRepoConfig(picked.hooks, picked.release), { flag: "wx" });
28013
28346
  written = { target: "repo", file, hooks: Object.keys(picked.hooks), kept: [], replaced: [] };
28014
28347
  }
28015
28348
  if (flag("json")) {
@@ -28065,7 +28398,7 @@ ${cu.tail.trimEnd()}
28065
28398
  if (sub === "list" && !routeName) usage2();
28066
28399
  if (sub === "add") {
28067
28400
  try {
28068
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync52(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
28401
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
28069
28402
  routeName = a.route;
28070
28403
  addPath = a.path;
28071
28404
  addName = a.name;
@@ -28095,7 +28428,7 @@ ${cu.tail.trimEnd()}
28095
28428
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
28096
28429
  if (sub === "list" || sub === "add") {
28097
28430
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
28098
- if (liveRoute?.resolved?.reposModelId && existsSync52(resolvedFile) && !dryRun) {
28431
+ if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
28099
28432
  try {
28100
28433
  await hydrateApiKeys([liveRoute]);
28101
28434
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -28104,7 +28437,7 @@ ${cu.tail.trimEnd()}
28104
28437
  }
28105
28438
  }
28106
28439
  let resolvedRemotes;
28107
- if (existsSync52(resolvedFile)) {
28440
+ if (existsSync51(resolvedFile)) {
28108
28441
  try {
28109
28442
  resolvedRemotes = JSON.parse(readFileSync47(resolvedFile, "utf8"));
28110
28443
  } catch {
@@ -28112,8 +28445,8 @@ ${cu.tail.trimEnd()}
28112
28445
  }
28113
28446
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
28114
28447
  const text2 = configText;
28115
- const base = dirname34(cfg.configFile);
28116
- const isCheckout = (d) => existsSync52(`${d}/.git`);
28448
+ const base = dirname35(cfg.configFile);
28449
+ const isCheckout = (d) => existsSync51(`${d}/.git`);
28117
28450
  try {
28118
28451
  if (sub === "list") {
28119
28452
  if (flag("json")) {
@@ -28123,7 +28456,7 @@ ${cu.tail.trimEnd()}
28123
28456
  base,
28124
28457
  resolved: resolvedRemotes,
28125
28458
  originOf,
28126
- exists: (d) => existsSync52(d),
28459
+ exists: (d) => existsSync51(d),
28127
28460
  isCheckout,
28128
28461
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
28129
28462
  try {
@@ -28145,7 +28478,7 @@ ${cu.tail.trimEnd()}
28145
28478
  for (const r of repos) {
28146
28479
  const origin2 = originOf(r.dir);
28147
28480
  const row = matchRepoName(origin2, resolvedRemotes);
28148
- const note = !existsSync52(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
28481
+ const note = !existsSync51(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
28149
28482
  process.stdout.write(`${r.name} ${r.dir} ${note}
28150
28483
  `);
28151
28484
  }
@@ -28157,9 +28490,9 @@ ${cu.tail.trimEnd()}
28157
28490
  ` : `${human}
28158
28491
  `);
28159
28492
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
28160
- if (existsSync52(plan2.target) && (existsSync52(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
28493
+ if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
28161
28494
  throw new ReposError(
28162
- existsSync52(`${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`
28495
+ existsSync51(`${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`
28163
28496
  );
28164
28497
  }
28165
28498
  if (dryRun) {
@@ -28177,14 +28510,14 @@ ${cu.tail.trimEnd()}
28177
28510
  throw new ReposError(`${e.message}${authHint}
28178
28511
  crew.yaml was not changed`);
28179
28512
  }
28180
- writeFileSync38(cfg.configFile, plan2.text);
28513
+ writeFileSync39(cfg.configFile, plan2.text);
28181
28514
  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:
28182
28515
  `);
28183
28516
  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}`);
28184
28517
  break;
28185
28518
  }
28186
28519
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
28187
- const top = existsSync52(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
28520
+ const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
28188
28521
  if (!top) throw new ReposError(`${target} is not a git checkout`);
28189
28522
  const origin = originOf(top);
28190
28523
  const plan = planRepoAdd({
@@ -28236,15 +28569,15 @@ ${cu.tail.trimEnd()}
28236
28569
  const created = await rowTracker.createRepoRow(rowPayload);
28237
28570
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
28238
28571
  `);
28239
- if (existsSync52(resolvedFile)) {
28572
+ if (existsSync51(resolvedFile)) {
28240
28573
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
28241
28574
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
28242
28575
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
28243
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
28576
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
28244
28577
  `);
28245
28578
  }
28246
28579
  }
28247
- writeFileSync38(cfg.configFile, plan.text);
28580
+ writeFileSync39(cfg.configFile, plan.text);
28248
28581
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
28249
28582
  `);
28250
28583
  } catch (e) {
@@ -28616,7 +28949,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28616
28949
  process.exit(2);
28617
28950
  }
28618
28951
  if (found.shipNameAdopted && !dryRun) {
28619
- writeFileSync38(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
28952
+ writeFileSync39(cfg.configFile, setShipNameInYaml(readFileSync47(cfg.configFile, "utf8"), found.shipNameAdopted));
28620
28953
  cfg.ship.name = found.shipNameAdopted;
28621
28954
  }
28622
28955
  if (found.shipRow) {
@@ -28659,7 +28992,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28659
28992
  }
28660
28993
  }
28661
28994
  let previous;
28662
- const hadPreviousFile = existsSync52(resolvedPath);
28995
+ const hadPreviousFile = existsSync51(resolvedPath);
28663
28996
  if (hadPreviousFile) {
28664
28997
  try {
28665
28998
  previous = JSON.parse(readFileSync47(resolvedPath, "utf8"));
@@ -28792,10 +29125,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28792
29125
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
28793
29126
  `);
28794
29127
  } else {
28795
- mkdirSync38(dirname34(resolvedPath), { recursive: true });
29128
+ mkdirSync39(dirname35(resolvedPath), { recursive: true });
28796
29129
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
28797
29130
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
28798
- writeFileSync38(tmpPath, `${JSON.stringify(resolved, null, 2)}
29131
+ writeFileSync39(tmpPath, `${JSON.stringify(resolved, null, 2)}
28799
29132
  `);
28800
29133
  renameSync6(tmpPath, resolvedPath);
28801
29134
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -28841,8 +29174,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28841
29174
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
28842
29175
  `);
28843
29176
  } else {
28844
- mkdirSync38(dirname34(apiKeyFile), { recursive: true });
28845
- writeFileSync38(apiKeyFile, `${apiKeyVar}=${apiKey}
29177
+ mkdirSync39(dirname35(apiKeyFile), { recursive: true });
29178
+ writeFileSync39(apiKeyFile, `${apiKeyVar}=${apiKey}
28846
29179
  `, { mode: 384 });
28847
29180
  process.stderr.write(`API key written to ${apiKeyFile}
28848
29181
  `);
@@ -29546,8 +29879,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29546
29879
  prepare: async (path) => {
29547
29880
  for (const f of repoWt.config.worktrees.copy) {
29548
29881
  const src = join56(repoWt.dir, f);
29549
- if (!existsSync52(src) || existsSync52(join56(path, f))) continue;
29550
- mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
29882
+ if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
29883
+ mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
29551
29884
  copyFileSync2(src, join56(path, f));
29552
29885
  }
29553
29886
  const setup = repoWt.config.hooks.setup;
@@ -29769,7 +30102,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29769
30102
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
29770
30103
  const resumed = await runPeriscopeResume({
29771
30104
  find: async () => hit && { row: hit.row, tag: hit.tag },
29772
- worktreeExists: (p) => existsSync52(p),
30105
+ worktreeExists: (p) => existsSync51(p),
29773
30106
  prepareDropFile,
29774
30107
  startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
29775
30108
  caps: periscopeCaps(cfg.ship),
@@ -29866,8 +30199,8 @@ ${where.join("\n")}`);
29866
30199
  prepare: async (path) => {
29867
30200
  for (const f of repoP.config.worktrees.copy) {
29868
30201
  const src = join56(repoP.dir, f);
29869
- if (!existsSync52(src)) continue;
29870
- mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
30202
+ if (!existsSync51(src)) continue;
30203
+ mkdirSync39(dirOf2(join56(path, f)), { recursive: true });
29871
30204
  copyFileSync2(src, join56(path, f));
29872
30205
  }
29873
30206
  const setup = repoP.config.hooks.setup;
@@ -30226,7 +30559,7 @@ ${where.join("\n")}`);
30226
30559
  }
30227
30560
  const host = hostPlatform();
30228
30561
  const control = host === "windows" ? null : planDaemonControl(CREW_HOME, host, detectSystemd());
30229
- const unitStatus = control?.statusCommand ? spawnSync12(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" }) : null;
30562
+ const unitStatus = control?.statusCommand ? spawnSync12(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() }) : null;
30230
30563
  const unitRunning = unitStatus !== null && !unitStatus.error && unitStatus.status === 0;
30231
30564
  if (!unitRunning && control?.mechanism === "systemd" && control.startCommand) {
30232
30565
  const res = spawnSync12(control.startCommand[0], control.startCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
@@ -30243,15 +30576,15 @@ ${where.join("\n")}`);
30243
30576
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
30244
30577
  break;
30245
30578
  }
30246
- mkdirSync38(cfg.ship.stateDir, { recursive: true });
30247
- writeFileSync38(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
30579
+ mkdirSync39(cfg.ship.stateDir, { recursive: true });
30580
+ writeFileSync39(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
30248
30581
  `);
30249
30582
  process.stdout.write(
30250
30583
  "crew reload: requested \u2014 the daemon relaunches with the new config once its running agents end (it starts no new work meanwhile); if it is busy, `crew reload --now` forces the relaunch and interrupts them\n"
30251
30584
  );
30252
30585
  if (flag("now") && control?.stopCommand && control.startCommand) {
30253
30586
  for (const cmd of [control.stopCommand, control.startCommand]) {
30254
- const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
30587
+ const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8", env: withUserBusEnv() });
30255
30588
  if (cmd === control.startCommand && (res.error || res.status !== 0)) {
30256
30589
  process.stderr.write(`crew reload: ${cmd.join(" ")} exited ${res.status ?? "error"}
30257
30590
  `);
@@ -30333,7 +30666,7 @@ ${where.join("\n")}`);
30333
30666
  if (!route2.resolved) fail2(`route ${route2.route} is not connected \u2014 run \`crew connect\` first`);
30334
30667
  const tracker = new Tracker(route2, cfg.ship);
30335
30668
  const configText = readFileSync47(cfg.configFile, "utf8");
30336
- const cfgBase = dirname34(cfg.configFile);
30669
+ const cfgBase = dirname35(cfg.configFile);
30337
30670
  const configured2 = configuredRepos(configText, route2.route, cfgBase);
30338
30671
  const dir = configured2.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured2.map((r) => r.dir), route2.reposBasePath, route2.route.split("/")[0]);
30339
30672
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route2.route);
@@ -30361,7 +30694,7 @@ ${where.join("\n")}`);
30361
30694
  }
30362
30695
  if (configured2.some((r) => r.dir === dir)) {
30363
30696
  try {
30364
- writeFileSync38(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
30697
+ writeFileSync39(cfg.configFile, planRepoRemove(configText, cfgBase, route2.route, dir).text);
30365
30698
  } catch (e) {
30366
30699
  if (e instanceof ReposError) fail2(e.message);
30367
30700
  throw e;
@@ -30369,29 +30702,29 @@ ${where.join("\n")}`);
30369
30702
  }
30370
30703
  if (row) {
30371
30704
  await tracker.deleteRepoRow(row.id);
30372
- if (existsSync52(resolvedFile)) {
30705
+ if (existsSync51(resolvedFile)) {
30373
30706
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
30374
30707
  if (raw.repoNames) delete raw.repoNames[row.id];
30375
30708
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
30376
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
30709
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
30377
30710
  `);
30378
30711
  }
30379
30712
  }
30380
- if (existsSync52(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
30713
+ if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
30381
30714
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
30382
30715
  break;
30383
30716
  }
30384
30717
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
30385
- const fresh = !existsSync52(join56(dir, ".git"));
30386
- if (existsSync52(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
30718
+ const fresh = !existsSync51(join56(dir, ".git"));
30719
+ if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
30387
30720
  if (dryRun) {
30388
30721
  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`);
30389
30722
  break;
30390
30723
  }
30391
30724
  if (fresh) {
30392
- mkdirSync38(dir, { recursive: true });
30393
- writeFileSync38(join56(dir, "README.md"), SAMPLE_README);
30394
- writeFileSync38(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
30725
+ mkdirSync39(dir, { recursive: true });
30726
+ writeFileSync39(join56(dir, "README.md"), SAMPLE_README);
30727
+ writeFileSync39(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
30395
30728
  const g = (...a) => {
30396
30729
  const r = spawnSync12("git", a, { cwd: dir, encoding: "utf8" });
30397
30730
  if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -30409,17 +30742,17 @@ ${where.join("\n")}`);
30409
30742
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
30410
30743
  });
30411
30744
  repoId = (await tracker.createRepoRow(payload)).id;
30412
- if (existsSync52(resolvedFile)) {
30745
+ if (existsSync51(resolvedFile)) {
30413
30746
  const raw = JSON.parse(readFileSync47(resolvedFile, "utf8"));
30414
30747
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
30415
30748
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
30416
- writeFileSync38(resolvedFile, `${JSON.stringify(raw, null, 2)}
30749
+ writeFileSync39(resolvedFile, `${JSON.stringify(raw, null, 2)}
30417
30750
  `);
30418
30751
  }
30419
30752
  }
30420
30753
  if (!configured2.some((r) => r.dir === dir)) {
30421
30754
  try {
30422
- writeFileSync38(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
30755
+ writeFileSync39(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route2.route, path: dir, name: SAMPLE_REPO }).text);
30423
30756
  } catch (e) {
30424
30757
  if (e instanceof ReposError) fail2(e.message);
30425
30758
  throw e;
@@ -30765,7 +31098,7 @@ watermark: ${state.watermark()}
30765
31098
  }
30766
31099
  for (const r of repos) {
30767
31100
  const remote = r.config.branch.remote;
30768
- if (existsSync52(r.dir)) {
31101
+ if (existsSync51(r.dir)) {
30769
31102
  pushAuth.push({
30770
31103
  route: c.route,
30771
31104
  repo: r.name,
@@ -30829,6 +31162,8 @@ watermark: ${state.watermark()}
30829
31162
  }
30830
31163
  }
30831
31164
  for (const message of roleCapWarnings(cfg.ship)) problems.push({ severity: "note", kind: "role_cap_above_ship_cap", message });
31165
+ const runtimeDir = runtimeDirWarning(process.platform, process.getuid?.() ?? null, existsSync51, userInfo2().username);
31166
+ if (runtimeDir) problems.push({ severity: "attention", kind: "runtime_dir_missing", message: runtimeDir });
30832
31167
  if (!flag("offline")) {
30833
31168
  const shipKind = await doctorShipKind(route2, cfg.ship);
30834
31169
  for (const message of managedRuntimeWarnings(shipKind, cfg.ship)) problems.push({ severity: "attention", kind: "managed_runtime_default", message });
@@ -30888,7 +31223,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30888
31223
  }
30889
31224
  try {
30890
31225
  const evFile = eventFileFor(cfg.ship.stateDir);
30891
- const evSize = existsSync52(evFile) ? statSync16(evFile).size : 0;
31226
+ const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
30892
31227
  process.stdout.write(
30893
31228
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
30894
31229
  ` : "events log: ok\n"
@@ -30913,7 +31248,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30913
31248
  if (host === "linux" && detectSystemd()) {
30914
31249
  const ctl = planDaemonControl(CREW_HOME, host, true);
30915
31250
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, true, "run", installContext());
30916
- if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync52(p))) {
31251
+ if (ctl.statusCommand && unitPlan.unitPaths.some((p) => existsSync51(p))) {
30917
31252
  const st = spawnSync12(ctl.statusCommand[0], ctl.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
30918
31253
  if (!st.error && st.status !== 0) {
30919
31254
  process.stdout.write("daemon unit: WARN installed but not running \u2014 no work is picked up; run `crew reload` (or `crew daemon start`), and `crew install` if the unit still says Restart=on-failure\n");