@tablation/crew 0.63.1 → 0.63.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +221 -157
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5183,7 +5183,7 @@ function doctorSelftestLines(stored, toolchains) {
5183
5183
  }
5184
5184
 
5185
5185
  // src/cli.ts
5186
- import { resolve as resolve5, dirname as dirname18, join as join31, basename as basename10 } from "path";
5186
+ import { resolve as resolve5, dirname as dirname19, join as join32, basename as basename10 } from "path";
5187
5187
  import { fileURLToPath } from "url";
5188
5188
  import { hostname as hostname2, homedir as homedir8 } from "os";
5189
5189
  import { spawnSync as spawnSync6 } from "child_process";
@@ -5762,15 +5762,52 @@ var State = class _State {
5762
5762
  }
5763
5763
  };
5764
5764
 
5765
- // src/reset.ts
5766
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync13, readdirSync as readdirSync4, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync8 } from "fs";
5765
+ // src/connect-resume.ts
5766
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
5767
5767
  import { dirname as dirname6, join as join14 } from "path";
5768
+ function lastSessionFile(stateDir) {
5769
+ return join14(stateDir, "connect-session.json");
5770
+ }
5771
+ function readPointers(stateDir) {
5772
+ try {
5773
+ const parsed = JSON.parse(readFileSync13(lastSessionFile(stateDir), "utf8"));
5774
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
5775
+ return Object.fromEntries(Object.entries(parsed).filter((e) => typeof e[1] === "string"));
5776
+ } catch {
5777
+ return {};
5778
+ }
5779
+ }
5780
+ function rememberSession(stateDir, host, workspaceSlug) {
5781
+ try {
5782
+ const file = lastSessionFile(stateDir);
5783
+ mkdirSync7(dirname6(file), { recursive: true });
5784
+ writeFileSync8(file, `${JSON.stringify({ ...readPointers(stateDir), [host]: workspaceSlug }, null, 2)}
5785
+ `, { mode: 384 });
5786
+ } catch {
5787
+ }
5788
+ }
5789
+ async function resumeKeychainSession(o) {
5790
+ const workspace = readPointers(o.stateDir)[o.host];
5791
+ if (!workspace) return void 0;
5792
+ try {
5793
+ const stored = await o.store.get(o.host, workspace);
5794
+ if (!stored?.apiKey) return void 0;
5795
+ if (!await o.verify(stored.apiKey)) return void 0;
5796
+ return { workspace, apiKey: stored.apiKey };
5797
+ } catch {
5798
+ return void 0;
5799
+ }
5800
+ }
5801
+
5802
+ // src/reset.ts
5803
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync14, readdirSync as readdirSync4, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
5804
+ import { dirname as dirname7, join as join15 } from "path";
5768
5805
  function clonedCheckoutsFile(stateDir) {
5769
- return join14(stateDir, "cloned-checkouts.json");
5806
+ return join15(stateDir, "cloned-checkouts.json");
5770
5807
  }
5771
5808
  function readClonedCheckouts(stateDir) {
5772
5809
  try {
5773
- const parsed = JSON.parse(readFileSync13(clonedCheckoutsFile(stateDir), "utf8"));
5810
+ const parsed = JSON.parse(readFileSync14(clonedCheckoutsFile(stateDir), "utf8"));
5774
5811
  return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : [];
5775
5812
  } catch {
5776
5813
  return [];
@@ -5780,8 +5817,8 @@ function recordClonedCheckout(stateDir, dir) {
5780
5817
  const known = readClonedCheckouts(stateDir);
5781
5818
  if (known.includes(dir)) return;
5782
5819
  const file = clonedCheckoutsFile(stateDir);
5783
- mkdirSync7(dirname6(file), { recursive: true });
5784
- writeFileSync8(file, `${JSON.stringify([...known, dir], null, 2)}
5820
+ mkdirSync8(dirname7(file), { recursive: true });
5821
+ writeFileSync9(file, `${JSON.stringify([...known, dir], null, 2)}
5785
5822
  `);
5786
5823
  }
5787
5824
  var mark = (dryRun) => dryRun ? "would-remove" : "removed";
@@ -5836,10 +5873,10 @@ function removeCheckout(dir, dryRun, fx) {
5836
5873
  }
5837
5874
  function removeState(stateDir, dryRun) {
5838
5875
  const out = [];
5839
- const targets = [join14(stateDir, "resolved"), join14(stateDir, "keys"), clonedCheckoutsFile(stateDir), join14(stateDir, "reload-requested"), join14(stateDir, "daemon.started")];
5876
+ const targets = [join15(stateDir, "resolved"), join15(stateDir, "keys"), clonedCheckoutsFile(stateDir), join15(stateDir, "connect-session.json"), join15(stateDir, "reload-requested"), join15(stateDir, "daemon.started")];
5840
5877
  try {
5841
5878
  for (const name of readdirSync4(stateDir)) {
5842
- if (name.startsWith(".") && statSync5(join14(stateDir, name)).isFile()) targets.push(join14(stateDir, name));
5879
+ if (name.startsWith(".") && statSync5(join15(stateDir, name)).isFile()) targets.push(join15(stateDir, name));
5843
5880
  }
5844
5881
  } catch {
5845
5882
  }
@@ -6113,18 +6150,18 @@ import { existsSync as existsSync16, statfsSync } from "fs";
6113
6150
  import { execFileSync as execFileSync2 } from "child_process";
6114
6151
  import {
6115
6152
  existsSync as existsSync15,
6116
- mkdirSync as mkdirSync8,
6153
+ mkdirSync as mkdirSync9,
6117
6154
  openSync as openSync3,
6118
6155
  closeSync as closeSync3,
6119
6156
  writeSync,
6120
6157
  statSync as statSync6,
6121
6158
  unlinkSync as unlinkSync2,
6122
- readFileSync as readFileSync14,
6123
- writeFileSync as writeFileSync9,
6159
+ readFileSync as readFileSync15,
6160
+ writeFileSync as writeFileSync10,
6124
6161
  readdirSync as readdirSync5,
6125
6162
  rmSync as rmSync8
6126
6163
  } from "fs";
6127
- import { dirname as dirname7, basename as basename5, join as join15 } from "path";
6164
+ import { dirname as dirname8, basename as basename5, join as join16 } from "path";
6128
6165
  var GitError = class extends Error {
6129
6166
  };
6130
6167
  function streamText(x) {
@@ -6490,7 +6527,7 @@ function recoverStrandedReleaseCheckouts(cwd) {
6490
6527
  if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
6491
6528
  else if (line.startsWith("branch refs/heads/crew/release-") && path) {
6492
6529
  gitOk(cwd, ["worktree", "remove", "--force", path]);
6493
- const root = dirname7(path);
6530
+ const root = dirname8(path);
6494
6531
  if (basename5(root).startsWith("crew-release-")) rmSync8(root, { recursive: true, force: true });
6495
6532
  }
6496
6533
  }
@@ -6508,11 +6545,11 @@ function sweepStaleReleaseRoots(tmp, now = Date.now(), maxAgeMs = 60 * 60 * 1e3)
6508
6545
  }
6509
6546
  for (const name of names) {
6510
6547
  if (!name.startsWith("crew-release-")) continue;
6511
- const root = join15(tmp, name);
6548
+ const root = join16(tmp, name);
6512
6549
  try {
6513
6550
  const st = statSync6(root);
6514
6551
  if (!st.isDirectory() || now - st.mtimeMs < maxAgeMs) continue;
6515
- if (existsSync15(join15(root, "checkout"))) continue;
6552
+ if (existsSync15(join16(root, "checkout"))) continue;
6516
6553
  rmSync8(root, { recursive: true, force: true });
6517
6554
  removed.push(name);
6518
6555
  } catch {
@@ -6561,7 +6598,7 @@ function ensureRepoCheckout(dir, remote, opts = {}) {
6561
6598
  `${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`
6562
6599
  );
6563
6600
  }
6564
- mkdirSync8(dirname7(dir), { recursive: true });
6601
+ mkdirSync9(dirname8(dir), { recursive: true });
6565
6602
  const url = cloneUrlFor(remote, opts);
6566
6603
  try {
6567
6604
  execFileSync2("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
@@ -6586,7 +6623,7 @@ function sleepSync(ms2) {
6586
6623
  }
6587
6624
  function acquireCheckoutLock(dir, timeoutMs = 3e4) {
6588
6625
  const lockPath = checkoutLockPath(dir);
6589
- mkdirSync8(dirname7(lockPath), { recursive: true });
6626
+ mkdirSync9(dirname8(lockPath), { recursive: true });
6590
6627
  const deadline = Date.now() + timeoutMs;
6591
6628
  for (; ; ) {
6592
6629
  try {
@@ -6626,14 +6663,14 @@ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {
6626
6663
  if (!existsSync15(`${dir}/.git`) && githubRemote) {
6627
6664
  const url = cloneUrlFor(githubRemote, clone);
6628
6665
  const marker = cloneFailureMarker(dir);
6629
- if (existsSync15(marker) && readFileSync14(marker, "utf8").split("\n")[0] === url) {
6630
- return { action: "clone-failed", detail: readFileSync14(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
6666
+ if (existsSync15(marker) && readFileSync15(marker, "utf8").split("\n")[0] === url) {
6667
+ return { action: "clone-failed", detail: readFileSync15(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
6631
6668
  }
6632
6669
  try {
6633
6670
  ensureRepoCheckout(dir, githubRemote, clone);
6634
6671
  } catch (e) {
6635
6672
  if (!(e instanceof GitError)) throw e;
6636
- writeFileSync9(marker, `${url}
6673
+ writeFileSync10(marker, `${url}
6637
6674
  ${e.message}`);
6638
6675
  return { action: "clone-failed", detail: e.message, repeat: false };
6639
6676
  }
@@ -8762,8 +8799,8 @@ function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
8762
8799
  }
8763
8800
 
8764
8801
  // src/poll.ts
8765
- import { writeFileSync as writeFileSync10 } from "fs";
8766
- import { join as join16 } from "path";
8802
+ import { writeFileSync as writeFileSync11 } from "fs";
8803
+ import { join as join17 } from "path";
8767
8804
 
8768
8805
  // src/stalled.ts
8769
8806
  var STALLED_AFTER_CYCLES = 30;
@@ -9054,7 +9091,7 @@ async function decideCycle(o) {
9054
9091
  }
9055
9092
  return decision;
9056
9093
  }
9057
- var digestPath = (stateDir, route, role) => join16(stateDir, `digest-${route}-${role}.md`);
9094
+ var digestPath = (stateDir, route, role) => join17(stateDir, `digest-${route}-${role}.md`);
9058
9095
  function writeDigest(o, d, role, stateDir) {
9059
9096
  const me = o.route.resolved?.seats[role];
9060
9097
  if (!me) return false;
@@ -9118,7 +9155,7 @@ function writeDigest(o, d, role, stateDir) {
9118
9155
  };
9119
9156
  const text = role === "qa" ? qaDigest(input) : buildingDigest(input);
9120
9157
  const path = digestPath(stateDir, routeSlug(o.route.route), role);
9121
- writeFileSync10(path, text);
9158
+ writeFileSync11(path, text);
9122
9159
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
9123
9160
  data: { role, bytes: Buffer.byteLength(text) }
9124
9161
  });
@@ -9496,12 +9533,12 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
9496
9533
 
9497
9534
  // src/run-worktree.ts
9498
9535
  import { existsSync as existsSync18 } from "fs";
9499
- import { dirname as dirname8, join as join18 } from "path";
9536
+ import { dirname as dirname9, join as join19 } from "path";
9500
9537
 
9501
9538
  // src/ports.ts
9502
9539
  import { execFileSync as execFileSync3 } from "child_process";
9503
9540
  import { existsSync as existsSync17 } from "fs";
9504
- import { join as join17 } from "path";
9541
+ import { join as join18 } from "path";
9505
9542
  var run = (cmd, args) => {
9506
9543
  try {
9507
9544
  return execFileSync3(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -9546,7 +9583,7 @@ function ticketForPort(port, s = DEFAULT_PORTS) {
9546
9583
  }
9547
9584
  function worktreeExists(parent, prefix, n) {
9548
9585
  for (const name of [`${prefix}${n}`, `${prefix}${String(n).padStart(3, "0")}`]) {
9549
- if (existsSync17(join17(parent, name, ".git"))) return true;
9586
+ if (existsSync17(join18(parent, name, ".git"))) return true;
9550
9587
  }
9551
9588
  return false;
9552
9589
  }
@@ -9638,7 +9675,7 @@ function planRunWorktree(dir, cfg, t, role, opts = {}) {
9638
9675
  if (countCommits(existing.path, `${baseRef}..HEAD`) > 0) return { action: "leave-unpushed", path: existing.path, branch };
9639
9676
  return { action: "recut", path: existing.path, branch, base: baseRef };
9640
9677
  }
9641
- const path = join18(dirname8(dir), worktreeForTicket(dir, cfg, t, role));
9678
+ const path = join19(dirname9(dir), worktreeForTicket(dir, cfg, t, role));
9642
9679
  if (located?.where === "remote" || located && hasRemote && remoteBranchExists(dir, remote, located.branch)) {
9643
9680
  return { action: "cut-remote", path, branch: located.branch };
9644
9681
  }
@@ -9960,8 +9997,8 @@ async function guardRunStrays(route, ship, tracker, role, workingId, since2, log
9960
9997
  }
9961
9998
 
9962
9999
  // src/connect-wizard.ts
9963
- import { delimiter, dirname as dirname9, join as join19 } from "path";
9964
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync19, mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
10000
+ import { delimiter, dirname as dirname10, join as join20 } from "path";
10001
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as writeFileSync12 } from "fs";
9965
10002
  import { stringify } from "yaml";
9966
10003
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
9967
10004
  function shouldRunWizard(opts) {
@@ -9983,7 +10020,7 @@ function defaultIsExecutable(p) {
9983
10020
  function findOnPath2(name, pathEnv, isExecutable = defaultIsExecutable) {
9984
10021
  for (const dir of pathEnv.split(delimiter)) {
9985
10022
  if (!dir) continue;
9986
- const candidate = join19(dir, name);
10023
+ const candidate = join20(dir, name);
9987
10024
  if (isExecutable(candidate)) return candidate;
9988
10025
  }
9989
10026
  return void 0;
@@ -10009,9 +10046,9 @@ async function runWizard(p, env) {
10009
10046
  if (entered && isExec(entered)) agentBin = entered;
10010
10047
  else p.say(entered ? `${entered} is not an executable file.` : "A path is required.");
10011
10048
  }
10012
- const nodeDir = dirname9(env.nodePath);
10049
+ const nodeDir = dirname10(env.nodePath);
10013
10050
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
10014
- const baseDirs = [nodeDir, pnpmPath ? dirname9(pnpmPath) : void 0];
10051
+ const baseDirs = [nodeDir, pnpmPath ? dirname10(pnpmPath) : void 0];
10015
10052
  p.say(
10016
10053
  `
10017
10054
  Host Passengers lets this machine run the agent sessions that other people's
@@ -10022,7 +10059,7 @@ Tablation workspaces ask for, in a container. More: ${HOST_PASSENGERS_DOCS_URL}`
10022
10059
  p.say("Docker is not available here, so nothing will be hosted until it is. Your answer is recorded anyway.");
10023
10060
  }
10024
10061
  const dockerPath = hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
10025
- const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname9(dockerPath) : void 0]);
10062
+ const extraPath = deriveExtraPath([...baseDirs, dockerPath ? dirname10(dockerPath) : void 0]);
10026
10063
  if (extraPath) {
10027
10064
  p.say(`Hooks will get ${extraPath} on their PATH (where node, pnpm${dockerPath ? " and docker" : ""} live).`);
10028
10065
  }
@@ -10044,9 +10081,9 @@ routes:
10044
10081
  ${routeBlock}`;
10045
10082
  }
10046
10083
  function writeNewConfig(path, text) {
10047
- mkdirSync9(dirname9(path), { recursive: true });
10084
+ mkdirSync10(dirname10(path), { recursive: true });
10048
10085
  try {
10049
- writeFileSync11(path, text, { mode: 384, flag: "wx" });
10086
+ writeFileSync12(path, text, { mode: 384, flag: "wx" });
10050
10087
  return true;
10051
10088
  } catch (e) {
10052
10089
  if (e.code === "EEXIST") return false;
@@ -10079,7 +10116,7 @@ Wrote ${o.configPath}.
10079
10116
 
10080
10117
  // src/connect-json.ts
10081
10118
  import { accessSync as accessSync3, constants as constants3 } from "fs";
10082
- import { dirname as dirname10 } from "path";
10119
+ import { dirname as dirname11 } from "path";
10083
10120
  var CHOICE_EXIT_CODE = 3;
10084
10121
  function createEmitter(json, write) {
10085
10122
  return {
@@ -10127,7 +10164,7 @@ function flagWizardAnswers(f, env) {
10127
10164
  if (f.agentBin && !isExec(f.agentBin)) return { error: `--agent-bin ${f.agentBin} is not an executable file` };
10128
10165
  const pnpmPath = findOnPath2("pnpm", env.pathEnv, isExec);
10129
10166
  const dockerPath = f.hostPassengers ? findOnPath2("docker", env.pathEnv, isExec) : void 0;
10130
- const extraPath = f.extraPath ?? deriveExtraPath([dirname10(env.nodePath), pnpmPath ? dirname10(pnpmPath) : void 0, dockerPath ? dirname10(dockerPath) : void 0]);
10167
+ const extraPath = f.extraPath ?? deriveExtraPath([dirname11(env.nodePath), pnpmPath ? dirname11(pnpmPath) : void 0, dockerPath ? dirname11(dockerPath) : void 0]);
10131
10168
  return {
10132
10169
  answers: {
10133
10170
  name: f.shipName?.trim() || env.hostname,
@@ -10140,8 +10177,8 @@ function flagWizardAnswers(f, env) {
10140
10177
  }
10141
10178
 
10142
10179
  // src/release-run.ts
10143
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync20, copyFileSync, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync2, rmSync as rmSync9 } from "fs";
10144
- import { dirname as dirname11, join as join20 } from "path";
10180
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync13, existsSync as existsSync20, copyFileSync, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, rmSync as rmSync9 } from "fs";
10181
+ import { dirname as dirname12, join as join21 } from "path";
10145
10182
  import { tmpdir as tmpdir3 } from "os";
10146
10183
 
10147
10184
  // src/release.ts
@@ -10829,13 +10866,13 @@ async function bump(o, size) {
10829
10866
  }
10830
10867
  return printed;
10831
10868
  }
10832
- const files = o.repo.release.versionFiles.map((f) => join20(o.cwd, f));
10869
+ const files = o.repo.release.versionFiles.map((f) => join21(o.cwd, f));
10833
10870
  const first = files[0];
10834
10871
  if (!existsSync20(first)) {
10835
10872
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
10836
10873
  return null;
10837
10874
  }
10838
- const current = JSON.parse(readFileSync15(first, "utf8")).version;
10875
+ const current = JSON.parse(readFileSync16(first, "utf8")).version;
10839
10876
  if (!current) {
10840
10877
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
10841
10878
  return null;
@@ -10848,27 +10885,27 @@ async function bump(o, size) {
10848
10885
  return version;
10849
10886
  }
10850
10887
  for (const f of files) {
10851
- const src = readFileSync15(f, "utf8");
10888
+ const src = readFileSync16(f, "utf8");
10852
10889
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
10853
10890
  if (out === src) {
10854
10891
  o.emit.error(`no version field in ${f}`);
10855
10892
  return null;
10856
10893
  }
10857
- writeFileSync12(f, out);
10894
+ writeFileSync13(f, out);
10858
10895
  }
10859
10896
  return version;
10860
10897
  }
10861
10898
  function writeChangelog(o, version, entries) {
10862
10899
  const file = o.repo.release.changelog;
10863
10900
  if (!file || entries.length === 0) return;
10864
- const path = join20(o.cwd, file);
10901
+ const path = join21(o.cwd, file);
10865
10902
  const section = renderChangelogSection(version, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entries);
10866
10903
  if (o.dryRun) {
10867
10904
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
10868
10905
  return;
10869
10906
  }
10870
- const existing = existsSync20(path) ? readFileSync15(path, "utf8") : "# Changelog\n";
10871
- writeFileSync12(path, insertChangelogSection(existing, section));
10907
+ const existing = existsSync20(path) ? readFileSync16(path, "utf8") : "# Changelog\n";
10908
+ writeFileSync13(path, insertChangelogSection(existing, section));
10872
10909
  }
10873
10910
  async function confirm(o, expected) {
10874
10911
  if (!o.repo.hooks.released) return void 0;
@@ -10961,8 +10998,8 @@ function closeReleaseCheckout(cwd, root, dir, branch, run2 = gitOk) {
10961
10998
  function openReleaseCheckout(o) {
10962
10999
  const { remote, base } = o.repo.branch;
10963
11000
  const branch = `crew/release-${Date.now()}`;
10964
- const root = mkdtempSync2(join20(tmpdir3(), "crew-release-"));
10965
- const dir = join20(root, "checkout");
11001
+ const root = mkdtempSync2(join21(tmpdir3(), "crew-release-"));
11002
+ const dir = join21(root, "checkout");
10966
11003
  o.emit.emit(`release checkout: ${dir}`, { step: "release" });
10967
11004
  const close = () => closeReleaseCheckout(o.cwd, root, dir, branch);
10968
11005
  try {
@@ -10972,10 +11009,10 @@ function openReleaseCheckout(o) {
10972
11009
  throw e;
10973
11010
  }
10974
11011
  for (const f of o.repo.worktrees.copy) {
10975
- const src = join20(o.cwd, f);
11012
+ const src = join21(o.cwd, f);
10976
11013
  if (!existsSync20(src)) continue;
10977
- mkdirSync10(dirname11(join20(dir, f)), { recursive: true });
10978
- copyFileSync(src, join20(dir, f));
11014
+ mkdirSync11(dirname12(join21(dir, f)), { recursive: true });
11015
+ copyFileSync(src, join21(dir, f));
10979
11016
  }
10980
11017
  return { dir, branch, close };
10981
11018
  }
@@ -12769,8 +12806,8 @@ function describeCapability(i) {
12769
12806
  }
12770
12807
 
12771
12808
  // src/agents-sync-run.ts
12772
- import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
12773
- import { dirname as dirname12 } from "path";
12809
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync14 } from "fs";
12810
+ import { dirname as dirname13 } from "path";
12774
12811
  async function runAgentsSync(target, o) {
12775
12812
  const dryRun = o.dryRun ?? false;
12776
12813
  const force = o.force ?? false;
@@ -12803,12 +12840,12 @@ async function runAgentsSync(target, o) {
12803
12840
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
12804
12841
  if (!dryRun) {
12805
12842
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
12806
- const raw = existsSync22(resolvedPath) ? JSON.parse(readFileSync16(resolvedPath, "utf8")) : {};
12843
+ const raw = existsSync22(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
12807
12844
  raw.agentPersonas = personaResult.agentPersonas;
12808
12845
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
12809
12846
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
12810
- mkdirSync11(dirname12(resolvedPath), { recursive: true });
12811
- writeFileSync13(resolvedPath, `${JSON.stringify(raw, null, 2)}
12847
+ mkdirSync12(dirname13(resolvedPath), { recursive: true });
12848
+ writeFileSync14(resolvedPath, `${JSON.stringify(raw, null, 2)}
12812
12849
  `);
12813
12850
  if (target.resolved) {
12814
12851
  target.resolved.agentPersonas = personaResult.agentPersonas;
@@ -12915,7 +12952,7 @@ import { isMap as isMap2, parseDocument as parseDocument2, YAMLMap } from "yaml"
12915
12952
 
12916
12953
  // src/repos-cmd.ts
12917
12954
  import { homedir as homedir7 } from "os";
12918
- import { basename as basename7, join as join21, resolve as resolve3 } from "path";
12955
+ import { basename as basename7, join as join22, resolve as resolve3 } from "path";
12919
12956
  import { isMap, isSeq, parseDocument, stringify as stringify2 } from "yaml";
12920
12957
  var ReposError = class extends Error {
12921
12958
  };
@@ -13113,7 +13150,7 @@ function planRepoClone(i) {
13113
13150
  }
13114
13151
  const ship = doc.get("ship", true);
13115
13152
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
13116
- const target = i.dir ? expandDir(i.dir, i.base) : join21(expandDir(basePath, i.base), i.route.split("/")[0], name);
13153
+ const target = i.dir ? expandDir(i.dir, i.base) : join22(expandDir(basePath, i.base), i.route.split("/")[0], name);
13117
13154
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name });
13118
13155
  return { ...add, remote, target };
13119
13156
  }
@@ -13256,7 +13293,7 @@ function existingRepoConfig(f) {
13256
13293
  }
13257
13294
 
13258
13295
  // src/sample-ticket.ts
13259
- import { basename as basename8, dirname as dirname14, join as join22 } from "path";
13296
+ import { basename as basename8, dirname as dirname15, join as join23 } from "path";
13260
13297
  var SAMPLE_REPO = "crew-sample";
13261
13298
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
13262
13299
  var SAMPLE_MARKER = "sample: true";
@@ -13279,8 +13316,8 @@ has something safe to work on: its test and build hooks always pass, it has no
13279
13316
  remote, and \`crew sample-ticket --remove\` deletes it.
13280
13317
  `;
13281
13318
  function sampleDir(existing, reposBase, workspace) {
13282
- if (existing.length > 0) return join22(dirname14(existing[0]), SAMPLE_REPO);
13283
- return join22(reposBase, workspace, SAMPLE_REPO);
13319
+ if (existing.length > 0) return join23(dirname15(existing[0]), SAMPLE_REPO);
13320
+ return join23(reposBase, workspace, SAMPLE_REPO);
13284
13321
  }
13285
13322
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
13286
13323
  function sampleDescription() {
@@ -13361,8 +13398,8 @@ function schedulerInstalled(plan, exists, crontab) {
13361
13398
  }
13362
13399
 
13363
13400
  // src/worktree-sweep.ts
13364
- import { existsSync as existsSync23, readFileSync as readFileSync17, readdirSync as readdirSync6 } from "fs";
13365
- import { basename as basename9, dirname as dirname15, join as join23, resolve as resolve4 } from "path";
13401
+ import { existsSync as existsSync23, readFileSync as readFileSync18, readdirSync as readdirSync6 } from "fs";
13402
+ import { basename as basename9, dirname as dirname16, join as join24, resolve as resolve4 } from "path";
13366
13403
  function planWorktreeSweep(target, tickets, contract) {
13367
13404
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
13368
13405
  const verified = contract.statuses.verified;
@@ -13416,7 +13453,7 @@ function worktreeTicketNumbers(target) {
13416
13453
  return [...nums];
13417
13454
  }
13418
13455
  function siblingWorktreeDirs(target) {
13419
- const parent = dirname15(target.dir.replace(/[/\\]+$/, ""));
13456
+ const parent = dirname16(target.dir.replace(/[/\\]+$/, ""));
13420
13457
  const stem = `${basename9(target.dir.replace(/[/\\]+$/, ""))}-`;
13421
13458
  const registered = new Set(worktrees(target.dir).map((w) => resolve4(w.path)));
13422
13459
  let names;
@@ -13429,7 +13466,7 @@ function siblingWorktreeDirs(target) {
13429
13466
  for (const name of names) {
13430
13467
  if (!name.startsWith(stem)) continue;
13431
13468
  const m = DIR_NUMBER_SUFFIXED.exec(name);
13432
- const path = join23(parent, name);
13469
+ const path = join24(parent, name);
13433
13470
  if (m && !registered.has(resolve4(path))) out.push({ path, number: m[1] });
13434
13471
  }
13435
13472
  return out;
@@ -13532,7 +13569,7 @@ function isHandBackMerge(path, base) {
13532
13569
  if (!msgPath) return false;
13533
13570
  let first;
13534
13571
  try {
13535
- first = readFileSync17(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
13572
+ first = readFileSync18(resolve4(path, msgPath), "utf8").split("\n")[0] ?? "";
13536
13573
  } catch {
13537
13574
  return false;
13538
13575
  }
@@ -13638,13 +13675,13 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
13638
13675
 
13639
13676
  // src/stream-sweep.ts
13640
13677
  import { existsSync as existsSync24, readdirSync as readdirSync7, rmSync as rmSync10, statSync as statSync8 } from "fs";
13641
- import { join as join24 } from "path";
13678
+ import { join as join25 } from "path";
13642
13679
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
13643
13680
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
13644
- const dir = join24(stateDir, "streams");
13681
+ const dir = join25(stateDir, "streams");
13645
13682
  if (!existsSync24(dir)) return [];
13646
13683
  const cutoff = now - retentionDays * DAY_MS2;
13647
- return readdirSync7(dir).map((name) => join24(dir, name)).filter((path) => {
13684
+ return readdirSync7(dir).map((name) => join25(dir, name)).filter((path) => {
13648
13685
  try {
13649
13686
  return statSync8(path).mtimeMs < cutoff;
13650
13687
  } catch {
@@ -13672,7 +13709,7 @@ function applyStreamSweep(paths, dryRun, log) {
13672
13709
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
13673
13710
  if (!existsSync24(stateDir)) return [];
13674
13711
  const cutoff = now - retentionDays * DAY_MS2;
13675
- return readdirSync7(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join24(stateDir, name)).filter((path) => {
13712
+ return readdirSync7(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join25(stateDir, name)).filter((path) => {
13676
13713
  try {
13677
13714
  return statSync8(path).mtimeMs < cutoff;
13678
13715
  } catch {
@@ -13682,24 +13719,24 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
13682
13719
  }
13683
13720
 
13684
13721
  // src/cli.ts
13685
- import { realpathSync as realpathSync2, readFileSync as readFileSync24, existsSync as existsSync31, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18, rmSync as rmSync12, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync10, readdirSync as readdirSync12 } from "fs";
13722
+ import { realpathSync as realpathSync2, readFileSync as readFileSync25, existsSync as existsSync31, mkdirSync as mkdirSync18, writeFileSync as writeFileSync19, rmSync as rmSync12, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync10, readdirSync as readdirSync12 } from "fs";
13686
13723
  import { createInterface } from "readline/promises";
13687
13724
  import { dirname as dirOf, resolve as resolvePath } from "path";
13688
13725
 
13689
13726
  // src/ssh-keys.ts
13690
13727
  import { execFileSync as execFileSync4 } from "child_process";
13691
- import { existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync18, chmodSync } from "fs";
13692
- import { dirname as dirname16, join as join25 } from "path";
13728
+ import { existsSync as existsSync25, mkdirSync as mkdirSync13, readFileSync as readFileSync19, chmodSync } from "fs";
13729
+ import { dirname as dirname17, join as join26 } from "path";
13693
13730
  var SshKeyError = class extends Error {
13694
13731
  };
13695
13732
  function shipSshKeyPathFor(stateDir) {
13696
- return join25(stateDir, "ssh", "id_ed25519");
13733
+ return join26(stateDir, "ssh", "id_ed25519");
13697
13734
  }
13698
13735
  function ensureShipSshKeypair(stateDir) {
13699
13736
  const privateKeyPath = shipSshKeyPathFor(stateDir);
13700
13737
  const publicKeyPath = `${privateKeyPath}.pub`;
13701
13738
  if (!existsSync25(privateKeyPath)) {
13702
- mkdirSync12(dirname16(privateKeyPath), { recursive: true });
13739
+ mkdirSync13(dirname17(privateKeyPath), { recursive: true });
13703
13740
  try {
13704
13741
  execFileSync4("ssh-keygen", [
13705
13742
  "-t",
@@ -13722,7 +13759,7 @@ function ensureShipSshKeypair(stateDir) {
13722
13759
  if (!existsSync25(publicKeyPath)) {
13723
13760
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
13724
13761
  }
13725
- const publicKey = readFileSync18(publicKeyPath, "utf8").trim();
13762
+ const publicKey = readFileSync19(publicKeyPath, "utf8").trim();
13726
13763
  return { privateKeyPath, publicKeyPath, publicKey };
13727
13764
  }
13728
13765
  function sshKeygenAvailable() {
@@ -13737,10 +13774,10 @@ function sshKeygenAvailable() {
13737
13774
  // src/machine-id.ts
13738
13775
  import { execFileSync as execFileSync5 } from "child_process";
13739
13776
  import { createHash as createHash4 } from "crypto";
13740
- import { existsSync as existsSync26, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync14 } from "fs";
13741
- import { dirname as dirname17, join as join26 } from "path";
13777
+ import { existsSync as existsSync26, mkdirSync as mkdirSync14, readFileSync as readFileSync20, writeFileSync as writeFileSync15 } from "fs";
13778
+ import { dirname as dirname18, join as join27 } from "path";
13742
13779
  function machineIdPathFor(stateDir) {
13743
- return join26(stateDir, "machine-id");
13780
+ return join27(stateDir, "machine-id");
13744
13781
  }
13745
13782
  var sha256 = (s) => createHash4("sha256").update(s).digest("hex");
13746
13783
  function readRawMachineId(platform2 = process.platform) {
@@ -13754,7 +13791,7 @@ function readRawMachineId(platform2 = process.platform) {
13754
13791
  }
13755
13792
  for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
13756
13793
  if (existsSync26(p)) {
13757
- const id = readFileSync19(p, "utf8").trim();
13794
+ const id = readFileSync20(p, "utf8").trim();
13758
13795
  if (id) return id;
13759
13796
  }
13760
13797
  }
@@ -13766,14 +13803,14 @@ function ensureMachineId(stateDir, opts = {}) {
13766
13803
  if (opts.override?.trim()) return sha256(opts.override.trim());
13767
13804
  const path = machineIdPathFor(stateDir);
13768
13805
  if (existsSync26(path)) {
13769
- const cached = readFileSync19(path, "utf8").trim();
13806
+ const cached = readFileSync20(path, "utf8").trim();
13770
13807
  if (/^[0-9a-f]{64}$/.test(cached)) return cached;
13771
13808
  }
13772
13809
  const raw = (opts.read ?? readRawMachineId)();
13773
13810
  if (!raw) return void 0;
13774
13811
  const hashed = sha256(raw);
13775
- mkdirSync13(dirname17(path), { recursive: true });
13776
- writeFileSync14(path, `${hashed}
13812
+ mkdirSync14(dirname18(path), { recursive: true });
13813
+ writeFileSync15(path, `${hashed}
13777
13814
  `, { mode: 384 });
13778
13815
  return hashed;
13779
13816
  }
@@ -13988,9 +14025,9 @@ function syncAllPassengerCheckouts(cfg) {
13988
14025
 
13989
14026
  // src/tunnel.ts
13990
14027
  import { spawn as spawn4 } from "child_process";
13991
- import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync20, writeFileSync as writeFileSync15, unlinkSync as unlinkSync3, readdirSync as readdirSync8 } from "fs";
14028
+ import { existsSync as existsSync27, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync16, unlinkSync as unlinkSync3, readdirSync as readdirSync8 } from "fs";
13992
14029
  import { randomInt } from "crypto";
13993
- import { join as join27 } from "path";
14030
+ import { join as join28 } from "path";
13994
14031
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
13995
14032
  var defaultSpawn = (cmd, args) => spawn4(cmd, args, DEFAULT_SPAWN_OPTIONS);
13996
14033
  var SLUG_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -14057,21 +14094,21 @@ function startTunnel(opts) {
14057
14094
  };
14058
14095
  }
14059
14096
  function tunnelStatePath(stateDir, workspaceId) {
14060
- return join27(stateDir, "passengers", `${workspaceId}.json`);
14097
+ return join28(stateDir, "passengers", `${workspaceId}.json`);
14061
14098
  }
14062
14099
  function readPersistedTunnel(stateDir, workspaceId) {
14063
14100
  const path = tunnelStatePath(stateDir, workspaceId);
14064
14101
  if (!existsSync27(path)) return void 0;
14065
14102
  try {
14066
- return JSON.parse(readFileSync20(path, "utf8"));
14103
+ return JSON.parse(readFileSync21(path, "utf8"));
14067
14104
  } catch {
14068
14105
  return void 0;
14069
14106
  }
14070
14107
  }
14071
14108
  function writePersistedTunnel(stateDir, state) {
14072
14109
  const path = tunnelStatePath(stateDir, state.workspaceId);
14073
- mkdirSync14(join27(stateDir, "passengers"), { recursive: true });
14074
- writeFileSync15(path, `${JSON.stringify(state, null, 2)}
14110
+ mkdirSync15(join28(stateDir, "passengers"), { recursive: true });
14111
+ writeFileSync16(path, `${JSON.stringify(state, null, 2)}
14075
14112
  `);
14076
14113
  }
14077
14114
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -14168,7 +14205,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
14168
14205
  }
14169
14206
  }
14170
14207
  function readAllPersistedWorkspaceIds(stateDir) {
14171
- const dir = join27(stateDir, "passengers");
14208
+ const dir = join28(stateDir, "passengers");
14172
14209
  if (!existsSync27(dir)) return [];
14173
14210
  return readdirSync8(dir).filter((f) => f.endsWith(".json") && !f.endsWith("-sync.json")).map((f) => f.slice(0, -".json".length));
14174
14211
  }
@@ -14290,8 +14327,8 @@ async function unregisterEndpoint(workspaceId, info, d) {
14290
14327
  import { createServer as createHttpServer } from "http";
14291
14328
  import { spawn as spawn5 } from "child_process";
14292
14329
  import { randomBytes } from "crypto";
14293
- import { existsSync as existsSync28, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync16, unlinkSync as unlinkSync4, readdirSync as readdirSync9 } from "fs";
14294
- import { join as join28 } from "path";
14330
+ import { existsSync as existsSync28, mkdirSync as mkdirSync16, readFileSync as readFileSync22, writeFileSync as writeFileSync17, unlinkSync as unlinkSync4, readdirSync as readdirSync9 } from "fs";
14331
+ import { join as join29 } from "path";
14295
14332
 
14296
14333
  // src/secret-compare.ts
14297
14334
  import { timingSafeEqual } from "crypto";
@@ -14380,21 +14417,21 @@ var defaultSpawn2 = (cmd, args, env) => {
14380
14417
  return child;
14381
14418
  };
14382
14419
  function syncDaemonStatePath(stateDir, workspaceId) {
14383
- return join28(stateDir, "passengers", `${workspaceId}-sync.json`);
14420
+ return join29(stateDir, "passengers", `${workspaceId}-sync.json`);
14384
14421
  }
14385
14422
  function readPersistedSyncDaemon(stateDir, workspaceId) {
14386
14423
  const path = syncDaemonStatePath(stateDir, workspaceId);
14387
14424
  if (!existsSync28(path)) return void 0;
14388
14425
  try {
14389
- return JSON.parse(readFileSync21(path, "utf8"));
14426
+ return JSON.parse(readFileSync22(path, "utf8"));
14390
14427
  } catch {
14391
14428
  return void 0;
14392
14429
  }
14393
14430
  }
14394
14431
  function writePersistedSyncDaemon(stateDir, state) {
14395
14432
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
14396
- mkdirSync15(join28(stateDir, "passengers"), { recursive: true });
14397
- writeFileSync16(path, `${JSON.stringify(state, null, 2)}
14433
+ mkdirSync16(join29(stateDir, "passengers"), { recursive: true });
14434
+ writeFileSync17(path, `${JSON.stringify(state, null, 2)}
14398
14435
  `);
14399
14436
  }
14400
14437
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -14414,7 +14451,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
14414
14451
  }
14415
14452
  }
14416
14453
  function readAllPersistedSyncWorkspaceIds(stateDir) {
14417
- const dir = join28(stateDir, "passengers");
14454
+ const dir = join29(stateDir, "passengers");
14418
14455
  if (!existsSync28(dir)) return [];
14419
14456
  const suffix = "-sync.json";
14420
14457
  return readdirSync9(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
@@ -14472,12 +14509,12 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
14472
14509
  }
14473
14510
 
14474
14511
  // src/daemon.ts
14475
- import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync23, readdirSync as readdirSync11, rmSync as rmSync11, statSync as statSync9, writeFileSync as writeFileSync17 } from "fs";
14476
- import { join as join30 } from "path";
14512
+ import { existsSync as existsSync30, mkdirSync as mkdirSync17, readFileSync as readFileSync24, readdirSync as readdirSync11, rmSync as rmSync11, statSync as statSync9, writeFileSync as writeFileSync18 } from "fs";
14513
+ import { join as join31 } from "path";
14477
14514
 
14478
14515
  // src/interrupted.ts
14479
- import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
14480
- import { join as join29 } from "path";
14516
+ import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync23 } from "fs";
14517
+ import { join as join30 } from "path";
14481
14518
  var INTERRUPTED_MARKER = calloutHeader("NOTE", "Interrupted");
14482
14519
  function interruptedComment(b) {
14483
14520
  const tree = b.dirty === null ? "The worktree could not be checked." : b.dirty ? "The worktree has uncommitted changes \u2014 they are this seat's own, from that run." : "The worktree is clean.";
@@ -14498,19 +14535,19 @@ function runFinished(streamPath) {
14498
14535
  const path = `${streamPath}.meta.json`;
14499
14536
  if (!existsSync29(path)) return false;
14500
14537
  try {
14501
- return JSON.parse(readFileSync22(path, "utf8")).running !== true;
14538
+ return JSON.parse(readFileSync23(path, "utf8")).running !== true;
14502
14539
  } catch {
14503
14540
  return true;
14504
14541
  }
14505
14542
  }
14506
14543
  function orphanedStreams(o) {
14507
- const dir = join29(o.stateDir, "streams");
14544
+ const dir = join30(o.stateDir, "streams");
14508
14545
  if (!existsSync29(dir)) return [];
14509
14546
  const ownerAlive = o.isOwnerAlive ?? pidAlive;
14510
14547
  const out = [];
14511
14548
  for (const name of readdirSync10(dir)) {
14512
14549
  if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
14513
- const streamPath = join29(dir, name);
14550
+ const streamPath = join30(dir, name);
14514
14551
  if (runFinished(streamPath)) continue;
14515
14552
  const sidecar = readRunSidecar(streamPath);
14516
14553
  if (!sidecar || sidecar.route !== o.routeLabel) continue;
@@ -14610,7 +14647,7 @@ function latestMtime(path) {
14610
14647
  if (!stat.isDirectory()) return 0;
14611
14648
  let latest = stat.mtimeMs;
14612
14649
  for (const entry of readdirSync11(path, { withFileTypes: true })) {
14613
- latest = Math.max(latest, latestMtime(join30(path, entry.name)));
14650
+ latest = Math.max(latest, latestMtime(join31(path, entry.name)));
14614
14651
  }
14615
14652
  return latest;
14616
14653
  }
@@ -14622,20 +14659,20 @@ function makeStaleChecker(paths, opts = {}) {
14622
14659
  return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync30(opts.reloadSentinel);
14623
14660
  }
14624
14661
  function reloadSentinelFor(stateDir) {
14625
- return join30(stateDir, "reload-requested");
14662
+ return join31(stateDir, "reload-requested");
14626
14663
  }
14627
14664
  function daemonStampFor(stateDir) {
14628
- return join30(stateDir, "daemon.started");
14665
+ return join31(stateDir, "daemon.started");
14629
14666
  }
14630
14667
  function noteDaemonStarted(stateDir, now = Date.now()) {
14631
- mkdirSync16(stateDir, { recursive: true });
14668
+ mkdirSync17(stateDir, { recursive: true });
14632
14669
  rmSync11(reloadSentinelFor(stateDir), { force: true });
14633
- writeFileSync17(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
14670
+ writeFileSync18(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
14634
14671
  `);
14635
14672
  }
14636
14673
  function readDaemonStarted(stateDir) {
14637
14674
  try {
14638
- const v = JSON.parse(readFileSync23(daemonStampFor(stateDir), "utf8")).startedAt;
14675
+ const v = JSON.parse(readFileSync24(daemonStampFor(stateDir), "utf8")).startedAt;
14639
14676
  return typeof v === "number" ? v : null;
14640
14677
  } catch {
14641
14678
  return null;
@@ -15001,7 +15038,7 @@ async function buildEnvironment(route, ship, ticket) {
15001
15038
  }
15002
15039
 
15003
15040
  // src/cli.ts
15004
- var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname18(process.execPath) : resolve5(dirname18(fileURLToPath(import.meta.url)), "..");
15041
+ var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname19(process.execPath) : resolve5(dirname19(fileURLToPath(import.meta.url)), "..");
15005
15042
  var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
15006
15043
  var CYCLE_SECONDS2 = 120;
15007
15044
  var STALE_CYCLES = 3;
@@ -15147,7 +15184,7 @@ function usage() {
15147
15184
  resume with no argument clears only the whole-crew pause; a role or release pause needs its own resume
15148
15185
  crew log [route] tail the log
15149
15186
  crew inbox [--member NAME] your tickets across every workspace (or a colleague's)
15150
- crew connect resolve a workspace's ids into a crew.yaml block, then (admin key) sync personas/skills and mint seat keys (--no-agents-sync skips that; --no-mcp skips registering the Tablation MCP server with Claude Code; --mcp replace overwrites a different one; --dry-run works with no crew.yaml and writes none: no file, no rows, no seat keys \u2014 the device approval and workspace credential behave as in a real run)
15187
+ crew connect resolve a workspace's ids into a crew.yaml block, then (admin key) sync personas/skills and mint seat keys (--no-agents-sync skips that; --no-mcp skips registering the Tablation MCP server with Claude Code; --mcp replace overwrites a different one; --dry-run works with no crew.yaml and writes none: no file, no rows, no seat keys \u2014 the device approval and workspace credential behave as in a real run; with no workspace named it first reuses the keychain session a previous dry-run stored for the host, falling back to device approval when that is missing or rejected, and the signed_in event's session field says which)
15151
15188
  crew disconnect remove what connect installed for Pair (Claude MCP; Codex/Gemini instructions block and MCP entry per ship.pair)
15152
15189
  crew disconnect --reset [--remove-checkouts] [--dry-run] [--json] remove everything connect and install wrote on this machine: scheduler units, Pair/MCP entries, seat keys, keychain credential, resolved state, then crew.yaml (--remove-checkouts also deletes checkouts crew cloned, never one with local changes)
15153
15190
  crew agents sync [route] [--force] [--revoke-seat-keys] push crew's personas AND skill files into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt)
@@ -15568,7 +15605,7 @@ if (command === "passenger-sync-daemon") {
15568
15605
  }
15569
15606
  if (host === "linux" && hasSystemd) {
15570
15607
  const label = labelFor(CREW_HOME, "run");
15571
- const staleTimer = join31(homedir8(), ".config", "systemd", "user", `${label}.timer`);
15608
+ const staleTimer = join32(homedir8(), ".config", "systemd", "user", `${label}.timer`);
15572
15609
  if (existsSync31(staleTimer)) {
15573
15610
  process.stdout.write(
15574
15611
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
@@ -15630,7 +15667,7 @@ if (command === "passenger-sync-daemon") {
15630
15667
  for (const a of plan.actions) {
15631
15668
  if (a.kind === "enable") {
15632
15669
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
15633
- writeFileSync18(cfg.configFile, planEnable(readFileSync24(cfg.configFile, "utf8"), a.route));
15670
+ writeFileSync19(cfg.configFile, planEnable(readFileSync25(cfg.configFile, "utf8"), a.route));
15634
15671
  enabledSound.add(a.route);
15635
15672
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
15636
15673
  } else {
@@ -16629,7 +16666,7 @@ ${changed2.map((c) => ` - ${c}
16629
16666
  process.exit(1);
16630
16667
  }
16631
16668
  requireArmed("run the daemon loop");
16632
- const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join31(CREW_HOME, "src"), join31(CREW_HOME, "dist"), join31(CREW_HOME, "bin")];
16669
+ const watchPaths = isCompiledBinary(import.meta.url) ? [process.execPath] : [join32(CREW_HOME, "src"), join32(CREW_HOME, "dist"), join32(CREW_HOME, "bin")];
16633
16670
  const configPaths = daemonConfigPaths(cfg);
16634
16671
  watchPaths.push(...configPaths);
16635
16672
  noteDaemonStarted(cfg.ship.stateDir);
@@ -16830,10 +16867,10 @@ ${changed2.map((c) => ` - ${c}
16830
16867
  `);
16831
16868
  if (!dryRun) {
16832
16869
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
16833
- const raw = existsSync31(resolvedPath) ? JSON.parse(readFileSync24(resolvedPath, "utf8")) : {};
16870
+ const raw = existsSync31(resolvedPath) ? JSON.parse(readFileSync25(resolvedPath, "utf8")) : {};
16834
16871
  raw.agentSkills = result.agentSkills;
16835
- mkdirSync17(dirOf(resolvedPath), { recursive: true });
16836
- writeFileSync18(resolvedPath, `${JSON.stringify(raw, null, 2)}
16872
+ mkdirSync18(dirOf(resolvedPath), { recursive: true });
16873
+ writeFileSync19(resolvedPath, `${JSON.stringify(raw, null, 2)}
16837
16874
  `);
16838
16875
  }
16839
16876
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -16846,15 +16883,15 @@ ${changed2.map((c) => ` - ${c}
16846
16883
  process.exit(2);
16847
16884
  };
16848
16885
  if (sub !== "add" && sub !== "list" && sub !== "clone" && sub !== "hooks") usage2();
16849
- const configText = readFileSync24(cfg.configFile, "utf8");
16886
+ const configText = readFileSync25(cfg.configFile, "utf8");
16850
16887
  if (sub === "hooks") {
16851
16888
  try {
16852
16889
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
16853
- const dir = repoDirOf(configText, dirname18(cfg.configFile), a.route, a.name);
16890
+ const dir = repoDirOf(configText, dirname19(cfg.configFile), a.route, a.name);
16854
16891
  if (!existsSync31(dir)) throw new ReposError(`${dir} does not exist on disk`);
16855
16892
  const files = { exists: (r) => existsSync31(resolvePath(dir, r)), read: (r) => {
16856
16893
  try {
16857
- return readFileSync24(resolvePath(dir, r), "utf8");
16894
+ return readFileSync25(resolvePath(dir, r), "utf8");
16858
16895
  } catch {
16859
16896
  return void 0;
16860
16897
  }
@@ -16866,13 +16903,13 @@ ${changed2.map((c) => ` - ${c}
16866
16903
  if (write && suggestion.source === null) throw new ReposError(`nothing to write: ${suggestion.notes[0]}`);
16867
16904
  if (write === "route") {
16868
16905
  const plan = planRouteHooks(configText, a.route, a.name, suggestion.hooks);
16869
- writeFileSync18(cfg.configFile, plan.text);
16906
+ writeFileSync19(cfg.configFile, plan.text);
16870
16907
  written = { target: "route", file: cfg.configFile, hooks: plan.written, kept: plan.kept };
16871
16908
  } else if (write === "repo") {
16872
16909
  const have = existingRepoConfig(files);
16873
16910
  if (have) throw new ReposError(`${resolvePath(dir, have)} already exists; not overwriting it`);
16874
16911
  const file = resolvePath(dir, ".crew.yaml");
16875
- writeFileSync18(file, starterRepoConfig(suggestion.hooks), { flag: "wx" });
16912
+ writeFileSync19(file, starterRepoConfig(suggestion.hooks), { flag: "wx" });
16876
16913
  written = { target: "repo", file, hooks: Object.keys(suggestion.hooks), kept: [] };
16877
16914
  }
16878
16915
  if (flag("json")) {
@@ -16940,13 +16977,13 @@ ${changed2.map((c) => ` - ${c}
16940
16977
  let resolvedRemotes;
16941
16978
  if (existsSync31(resolvedFile)) {
16942
16979
  try {
16943
- resolvedRemotes = JSON.parse(readFileSync24(resolvedFile, "utf8"));
16980
+ resolvedRemotes = JSON.parse(readFileSync25(resolvedFile, "utf8"));
16944
16981
  } catch {
16945
16982
  }
16946
16983
  }
16947
16984
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
16948
16985
  const text = configText;
16949
- const base = dirname18(cfg.configFile);
16986
+ const base = dirname19(cfg.configFile);
16950
16987
  try {
16951
16988
  if (sub === "list") {
16952
16989
  const repos = configuredRepos(text, routeName, base);
@@ -16987,7 +17024,7 @@ ${changed2.map((c) => ` - ${c}
16987
17024
  throw new ReposError(`${e.message}${authHint}
16988
17025
  crew.yaml was not changed`);
16989
17026
  }
16990
- writeFileSync18(cfg.configFile, plan2.text);
17027
+ writeFileSync19(cfg.configFile, plan2.text);
16991
17028
  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}`);
16992
17029
  break;
16993
17030
  }
@@ -17042,14 +17079,14 @@ ${changed2.map((c) => ` - ${c}
17042
17079
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
17043
17080
  `);
17044
17081
  if (existsSync31(resolvedFile)) {
17045
- const raw = JSON.parse(readFileSync24(resolvedFile, "utf8"));
17082
+ const raw = JSON.parse(readFileSync25(resolvedFile, "utf8"));
17046
17083
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
17047
17084
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
17048
- writeFileSync18(resolvedFile, `${JSON.stringify(raw, null, 2)}
17085
+ writeFileSync19(resolvedFile, `${JSON.stringify(raw, null, 2)}
17049
17086
  `);
17050
17087
  }
17051
17088
  }
17052
- writeFileSync18(cfg.configFile, plan.text);
17089
+ writeFileSync19(cfg.configFile, plan.text);
17053
17090
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
17054
17091
  `);
17055
17092
  } catch (e) {
@@ -17179,6 +17216,30 @@ ${reconstruction.prompt}
17179
17216
  } catch {
17180
17217
  }
17181
17218
  }
17219
+ let signInSource = apiKey ? "key" : "device";
17220
+ if (!apiKey && !workspace && dryRun && store) {
17221
+ const resumed = await resumeKeychainSession({
17222
+ store,
17223
+ stateDir: cfg.ship.stateDir,
17224
+ host: hostFromUrl3(baseUrl),
17225
+ verify: async (k) => {
17226
+ try {
17227
+ await get({ baseUrl, apiKey: k, userAgent: cfg.ship.userAgent }, "/auth/me");
17228
+ return true;
17229
+ } catch {
17230
+ return false;
17231
+ }
17232
+ }
17233
+ });
17234
+ if (resumed) {
17235
+ apiKey = resumed.apiKey;
17236
+ workspace = resumed.workspace;
17237
+ keychainBacked = true;
17238
+ signInSource = "keychain";
17239
+ process.stderr.write(`Reusing the existing keychain session for ${hostFromUrl3(baseUrl)}/${workspace}.
17240
+ `);
17241
+ }
17242
+ }
17182
17243
  let mintedFromDevice = false;
17183
17244
  let signedInEmitted = false;
17184
17245
  if (!apiKey) {
@@ -17231,7 +17292,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
17231
17292
  signedInEmitted = true;
17232
17293
  connectEvents.emit("signed_in", `signed in as ${result.identity.email}`, {
17233
17294
  identity: { id: result.identity.id, email: result.identity.email },
17234
- workspaces: [{ slug: result.workspace.slug, name: result.workspace.name }]
17295
+ workspaces: [{ slug: result.workspace.slug, name: result.workspace.name }],
17296
+ session: "device"
17235
17297
  });
17236
17298
  const deviceWorkspace = "path" in result.workspace && typeof result.workspace.path === "string" ? result.workspace.path : result.workspace.slug;
17237
17299
  if (!workspace) workspace = deviceWorkspace;
@@ -17245,6 +17307,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
17245
17307
  createdAt: result.apiKey.createdAt
17246
17308
  });
17247
17309
  keychainBacked = true;
17310
+ rememberSession(cfg.ship.stateDir, hostFromUrl3(baseUrl), deviceWorkspace);
17248
17311
  process.stderr.write(
17249
17312
  `API key stored in the OS keychain for ${hostFromUrl3(baseUrl)}/${deviceWorkspace}.
17250
17313
  `
@@ -17290,7 +17353,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
17290
17353
  signedInEmitted = true;
17291
17354
  connectEvents.emit("signed_in", `key belongs to ${found.meEmail ?? found.meId}`, {
17292
17355
  identity: { id: found.meId, email: found.meEmail, name: found.meName },
17293
- workspaces: [{ slug: found.workspaceSlug, name: found.workspaceName, role: found.role }]
17356
+ workspaces: [{ slug: found.workspaceSlug, name: found.workspaceName, role: found.role }],
17357
+ session: signInSource
17294
17358
  });
17295
17359
  }
17296
17360
  connectEvents.emit("workspace_resolved", `workspace ${found.workspaceSlug}`, {
@@ -17380,7 +17444,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
17380
17444
  const hadPreviousFile = existsSync31(resolvedPath);
17381
17445
  if (hadPreviousFile) {
17382
17446
  try {
17383
- previous = JSON.parse(readFileSync24(resolvedPath, "utf8"));
17447
+ previous = JSON.parse(readFileSync25(resolvedPath, "utf8"));
17384
17448
  } catch {
17385
17449
  }
17386
17450
  }
@@ -17489,10 +17553,10 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
17489
17553
  process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
17490
17554
  `);
17491
17555
  } else {
17492
- mkdirSync17(dirname18(resolvedPath), { recursive: true });
17556
+ mkdirSync18(dirname19(resolvedPath), { recursive: true });
17493
17557
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
17494
17558
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
17495
- writeFileSync18(tmpPath, `${JSON.stringify(resolved, null, 2)}
17559
+ writeFileSync19(tmpPath, `${JSON.stringify(resolved, null, 2)}
17496
17560
  `);
17497
17561
  renameSync2(tmpPath, resolvedPath);
17498
17562
  process.stderr.write(`Resolved ids written to ${resolvedPath}
@@ -17527,8 +17591,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
17527
17591
  process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
17528
17592
  `);
17529
17593
  } else {
17530
- mkdirSync17(dirname18(apiKeyFile), { recursive: true });
17531
- writeFileSync18(apiKeyFile, `${apiKeyVar}=${apiKey}
17594
+ mkdirSync18(dirname19(apiKeyFile), { recursive: true });
17595
+ writeFileSync19(apiKeyFile, `${apiKeyVar}=${apiKey}
17532
17596
  `, { mode: 384 });
17533
17597
  process.stderr.write(`API key written to ${apiKeyFile}
17534
17598
  `);
@@ -17537,7 +17601,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
17537
17601
  const keyPersisted = keychainBacked || apiKeyFile !== void 0;
17538
17602
  const wizard = firstRun;
17539
17603
  const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
17540
- const routeDir = value("dir") ?? (writeFirstRun ? join31(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
17604
+ const routeDir = value("dir") ?? (writeFirstRun ? join32(DEFAULT_REPOS_BASE_PATH, found.workspaceSlug) : void 0);
17541
17605
  const routeBlock = renderConnection(
17542
17606
  found,
17543
17607
  route2,
@@ -18181,7 +18245,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18181
18245
  };
18182
18246
  const droppedRoutes = [];
18183
18247
  try {
18184
- const rawCfg = parseYaml2(readFileSync24(cfg.configFile, "utf8"));
18248
+ const rawCfg = parseYaml2(readFileSync25(cfg.configFile, "utf8"));
18185
18249
  for (const r of rawCfg?.routes ?? []) {
18186
18250
  if (typeof r?.route !== "string" || cfg.routes.some((x) => x.route === r.route)) continue;
18187
18251
  try {
@@ -18223,8 +18287,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18223
18287
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
18224
18288
  break;
18225
18289
  }
18226
- mkdirSync17(cfg.ship.stateDir, { recursive: true });
18227
- writeFileSync18(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
18290
+ mkdirSync18(cfg.ship.stateDir, { recursive: true });
18291
+ writeFileSync19(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
18228
18292
  `);
18229
18293
  process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
18230
18294
  if (flag("now") && control?.stopCommand && control.startCommand) {
@@ -18275,7 +18339,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18275
18339
  }
18276
18340
  case "log": {
18277
18341
  try {
18278
- const text = readFileSync24(cfg.ship.logFile, "utf8").split("\n");
18342
+ const text = readFileSync25(cfg.ship.logFile, "utf8").split("\n");
18279
18343
  process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
18280
18344
  `);
18281
18345
  } catch {
@@ -18298,8 +18362,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18298
18362
  };
18299
18363
  if (!route.resolved) fail(`route ${route.route} is not connected \u2014 run \`crew connect\` first`);
18300
18364
  const tracker = new Tracker(route, cfg.ship);
18301
- const configText = readFileSync24(cfg.configFile, "utf8");
18302
- const cfgBase = dirname18(cfg.configFile);
18365
+ const configText = readFileSync25(cfg.configFile, "utf8");
18366
+ const cfgBase = dirname19(cfg.configFile);
18303
18367
  const configured = configuredRepos(configText, route.route, cfgBase);
18304
18368
  const dir = configured.find((r) => r.name === SAMPLE_REPO)?.dir ?? sampleDir(configured.map((r) => r.dir), route.reposBasePath, route.route.split("/")[0]);
18305
18369
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, route.route);
@@ -18327,7 +18391,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18327
18391
  }
18328
18392
  if (configured.some((r) => r.dir === dir)) {
18329
18393
  try {
18330
- writeFileSync18(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
18394
+ writeFileSync19(cfg.configFile, planRepoRemove(configText, cfgBase, route.route, dir).text);
18331
18395
  } catch (e) {
18332
18396
  if (e instanceof ReposError) fail(e.message);
18333
18397
  throw e;
@@ -18336,28 +18400,28 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18336
18400
  if (row) {
18337
18401
  await tracker.deleteRepoRow(row.id);
18338
18402
  if (existsSync31(resolvedFile)) {
18339
- const raw = JSON.parse(readFileSync24(resolvedFile, "utf8"));
18403
+ const raw = JSON.parse(readFileSync25(resolvedFile, "utf8"));
18340
18404
  if (raw.repoNames) delete raw.repoNames[row.id];
18341
18405
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
18342
- writeFileSync18(resolvedFile, `${JSON.stringify(raw, null, 2)}
18406
+ writeFileSync19(resolvedFile, `${JSON.stringify(raw, null, 2)}
18343
18407
  `);
18344
18408
  }
18345
18409
  }
18346
- if (existsSync31(join31(dir, ".git")) && basename10(dir) === SAMPLE_REPO) rmSync12(dir, { recursive: true, force: true });
18410
+ if (existsSync31(join32(dir, ".git")) && basename10(dir) === SAMPLE_REPO) rmSync12(dir, { recursive: true, force: true });
18347
18411
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
18348
18412
  break;
18349
18413
  }
18350
18414
  const projectId = route.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
18351
- const fresh = !existsSync31(join31(dir, ".git"));
18415
+ const fresh = !existsSync31(join32(dir, ".git"));
18352
18416
  if (existsSync31(dir) && fresh && readdirSync12(dir).length > 0) fail(`${dir} exists and is not a git checkout \u2014 move it aside`);
18353
18417
  if (dryRun) {
18354
18418
  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`);
18355
18419
  break;
18356
18420
  }
18357
18421
  if (fresh) {
18358
- mkdirSync17(dir, { recursive: true });
18359
- writeFileSync18(join31(dir, "README.md"), SAMPLE_README);
18360
- writeFileSync18(join31(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
18422
+ mkdirSync18(dir, { recursive: true });
18423
+ writeFileSync19(join32(dir, "README.md"), SAMPLE_README);
18424
+ writeFileSync19(join32(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
18361
18425
  const g = (...a) => {
18362
18426
  const r = spawnSync6("git", a, { cwd: dir, encoding: "utf8" });
18363
18427
  if (r.status !== 0) fail(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
@@ -18376,16 +18440,16 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
18376
18440
  });
18377
18441
  repoId = (await tracker.createRepoRow(payload)).id;
18378
18442
  if (existsSync31(resolvedFile)) {
18379
- const raw = JSON.parse(readFileSync24(resolvedFile, "utf8"));
18443
+ const raw = JSON.parse(readFileSync25(resolvedFile, "utf8"));
18380
18444
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
18381
18445
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
18382
- writeFileSync18(resolvedFile, `${JSON.stringify(raw, null, 2)}
18446
+ writeFileSync19(resolvedFile, `${JSON.stringify(raw, null, 2)}
18383
18447
  `);
18384
18448
  }
18385
18449
  }
18386
18450
  if (!configured.some((r) => r.dir === dir)) {
18387
18451
  try {
18388
- writeFileSync18(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
18452
+ writeFileSync19(cfg.configFile, planRepoAdd({ text: configText, base: cfgBase, route: route.route, path: dir, name: SAMPLE_REPO }).text);
18389
18453
  } catch (e) {
18390
18454
  if (e instanceof ReposError) fail(e.message);
18391
18455
  throw e;
@@ -18718,7 +18782,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
18718
18782
  const unitPlan = planInstall(cfg.ship, CREW_HOME, host, detectSystemd(), "run");
18719
18783
  const abandons = unitAbandonsAgents(unitPlan, (p) => {
18720
18784
  try {
18721
- return readFileSync24(p, "utf8");
18785
+ return readFileSync25(p, "utf8");
18722
18786
  } catch {
18723
18787
  return null;
18724
18788
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.63.1",
3
+ "version": "0.63.2",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",