@tablation/crew 0.45.4 → 0.46.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.
Files changed (2) hide show
  1. package/dist/cli.js +275 -76
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1963,6 +1963,11 @@ var ROLE_LABEL = {
1963
1963
  };
1964
1964
  var ConfigError = class extends Error {
1965
1965
  };
1966
+ function parseCloneScheme(v) {
1967
+ if (v === void 0 || v === null || v === "") return void 0;
1968
+ if (v === "ssh" || v === "https") return v;
1969
+ throw new ConfigError(`ship.cloneScheme must be "ssh" or "https", got ${JSON.stringify(v)}`);
1970
+ }
1966
1971
  var SHIP_KEYS = /* @__PURE__ */ new Set([
1967
1972
  "name",
1968
1973
  "platform",
@@ -1987,7 +1992,8 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
1987
1992
  "relayPort",
1988
1993
  "relayHttpPort",
1989
1994
  "relayPublicDomain",
1990
- "passengerImage"
1995
+ "passengerImage",
1996
+ "cloneScheme"
1991
1997
  ]);
1992
1998
  var ROUTE_KEYS = /* @__PURE__ */ new Set([
1993
1999
  "route",
@@ -2355,7 +2361,8 @@ Create the first of those:
2355
2361
  relayPort: shipRaw.relayPort !== void 0 ? Number(shipRaw.relayPort) : 2222,
2356
2362
  relayHttpPort: shipRaw.relayHttpPort !== void 0 ? Number(shipRaw.relayHttpPort) : 443,
2357
2363
  relayPublicDomain: shipRaw.relayPublicDomain ? String(shipRaw.relayPublicDomain) : void 0,
2358
- passengerImage: shipRaw.passengerImage ? String(shipRaw.passengerImage) : void 0
2364
+ passengerImage: shipRaw.passengerImage ? String(shipRaw.passengerImage) : void 0,
2365
+ cloneScheme: parseCloneScheme(shipRaw.cloneScheme)
2359
2366
  },
2360
2367
  routes,
2361
2368
  crewHome,
@@ -4020,17 +4027,22 @@ import {
4020
4027
  closeSync as closeSync2,
4021
4028
  writeSync,
4022
4029
  statSync as statSync4,
4023
- unlinkSync as unlinkSync2
4030
+ unlinkSync as unlinkSync2,
4031
+ readFileSync as readFileSync9,
4032
+ writeFileSync as writeFileSync6
4024
4033
  } from "fs";
4025
4034
  import { dirname as dirname5, basename as basename4 } from "path";
4026
4035
  var GitError = class extends Error {
4027
4036
  };
4037
+ function streamText(x) {
4038
+ return String(x ?? "").trim();
4039
+ }
4028
4040
  function gitRaw(cwd, args) {
4029
4041
  try {
4030
4042
  return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4031
4043
  } catch (e) {
4032
4044
  const err = e;
4033
- const why = [err.stderr, err.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
4045
+ const why = [err.stderr, err.stdout].map(streamText).filter(Boolean).join(" / ");
4034
4046
  throw new GitError(`git ${args.join(" ")}: ${why || err.message.trim()}`);
4035
4047
  }
4036
4048
  }
@@ -4039,7 +4051,7 @@ function git(cwd, args) {
4039
4051
  return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4040
4052
  } catch (e) {
4041
4053
  const err = e;
4042
- const why = [err.stderr, err.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
4054
+ const why = [err.stderr, err.stdout].map(streamText).filter(Boolean).join(" / ");
4043
4055
  throw new GitError(`git ${args.join(" ")}: ${why || err.message.trim()}`);
4044
4056
  }
4045
4057
  }
@@ -4382,10 +4394,19 @@ function worktrees(cwd) {
4382
4394
  if (current.path) list.push({ path: current.path, branch: current.branch ?? null, prunable: current.prunable ?? false });
4383
4395
  return list;
4384
4396
  }
4385
- function cloneUrlFor(remote) {
4386
- return `git@github.com:${remote}.git`;
4397
+ function cloneUrlFor(remote, opts = {}) {
4398
+ const origin = opts.originUrl?.trim() ?? "";
4399
+ const scheme = /^https?:\/\//i.test(origin) ? "https" : /^(git@|ssh:\/\/)/i.test(origin) ? "ssh" : opts.scheme ?? "ssh";
4400
+ return scheme === "https" ? `https://github.com/${remote}.git` : `git@github.com:${remote}.git`;
4401
+ }
4402
+ function originUrlOf(dir, remoteName) {
4403
+ try {
4404
+ return git(dir, ["remote", "get-url", remoteName]) || null;
4405
+ } catch {
4406
+ return null;
4407
+ }
4387
4408
  }
4388
- function ensureRepoCheckout(dir, remote) {
4409
+ function ensureRepoCheckout(dir, remote, opts = {}) {
4389
4410
  if (existsSync9(`${dir}/.git`)) return false;
4390
4411
  if (!remote) {
4391
4412
  throw new GitError(
@@ -4393,12 +4414,13 @@ function ensureRepoCheckout(dir, remote) {
4393
4414
  );
4394
4415
  }
4395
4416
  mkdirSync5(dirname5(dir), { recursive: true });
4417
+ const url = cloneUrlFor(remote, opts);
4396
4418
  try {
4397
- execFileSync("git", ["clone", cloneUrlFor(remote), dir], { stdio: ["ignore", "pipe", "pipe"] });
4419
+ execFileSync("git", ["clone", url, dir], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
4398
4420
  } catch (e) {
4399
4421
  const err = e;
4400
- const why = [err.stderr, err.stdout].map((x) => (x ?? "").trim()).filter(Boolean).join(" / ");
4401
- throw new GitError(`git clone ${cloneUrlFor(remote)} ${dir}: ${why || err.message.trim()}`);
4422
+ const why = [err.stderr, err.stdout].map(streamText).filter(Boolean).join(" / ");
4423
+ throw new GitError(`git clone ${url} ${dir}: ${why || err.message.trim()}`);
4402
4424
  }
4403
4425
  return true;
4404
4426
  }
@@ -4445,10 +4467,31 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
4445
4467
  }
4446
4468
  }
4447
4469
  }
4448
- function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base) {
4470
+ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base, clone = {}) {
4449
4471
  const release = acquireCheckoutLock(dir);
4450
4472
  try {
4451
- if (ensureRepoCheckout(dir, githubRemote)) {
4473
+ if (!existsSync9(`${dir}/.git`) && githubRemote) {
4474
+ const url = cloneUrlFor(githubRemote, clone);
4475
+ const marker = cloneFailureMarker(dir);
4476
+ if (existsSync9(marker) && readFileSync9(marker, "utf8").split("\n")[0] === url) {
4477
+ return { action: "clone-failed", detail: readFileSync9(marker, "utf8").split("\n").slice(1).join("\n"), repeat: true };
4478
+ }
4479
+ try {
4480
+ ensureRepoCheckout(dir, githubRemote, clone);
4481
+ } catch (e) {
4482
+ if (!(e instanceof GitError)) throw e;
4483
+ writeFileSync6(marker, `${url}
4484
+ ${e.message}`);
4485
+ return { action: "clone-failed", detail: e.message, repeat: false };
4486
+ }
4487
+ try {
4488
+ unlinkSync2(marker);
4489
+ } catch {
4490
+ }
4491
+ if (currentBranch(dir) !== base) git(dir, ["checkout", base]);
4492
+ return { action: "cloned", detail: `cloned ${githubRemote} into ${dir}` };
4493
+ }
4494
+ if (ensureRepoCheckout(dir, githubRemote, clone)) {
4452
4495
  if (currentBranch(dir) !== base) git(dir, ["checkout", base]);
4453
4496
  return { action: "cloned", detail: `cloned ${githubRemote} into ${dir}` };
4454
4497
  }
@@ -4457,6 +4500,9 @@ function syncPassengerCheckout(dir, githubRemote, gitRemoteName, base) {
4457
4500
  release();
4458
4501
  }
4459
4502
  }
4503
+ function cloneFailureMarker(dir) {
4504
+ return `${dir}.clone-failed`;
4505
+ }
4460
4506
 
4461
4507
  // src/ship-attention.ts
4462
4508
  var RELEASE_STALE_AFTER_INTERVALS = 3;
@@ -4822,7 +4868,12 @@ var Tracker = class {
4822
4868
  authorship;
4823
4869
  attachments;
4824
4870
  stateDir;
4825
- constructor(route, ship) {
4871
+ /**
4872
+ * `opts.apiKey` overrides the route's ship key (CREW-1603): a lane's own
4873
+ * writes pass its seat's agent key so the event log and rule context see an
4874
+ * agent, not the ship's human principal.
4875
+ */
4876
+ constructor(route, ship, opts = {}) {
4826
4877
  this.route = route;
4827
4878
  if (!route.resolved) {
4828
4879
  throw new ConfigError(
@@ -4836,7 +4887,7 @@ var Tracker = class {
4836
4887
  this.stateDir = ship.stateDir;
4837
4888
  this.client = new TablationClient({
4838
4889
  baseUrl: `${route.baseUrl}/api`,
4839
- apiKey: resolveApiKey(route),
4890
+ apiKey: opts.apiKey ?? resolveApiKey(route),
4840
4891
  // Cloudflare 403s (error 1010) default agents on this host.
4841
4892
  headers: { "User-Agent": ship.userAgent }
4842
4893
  });
@@ -6215,7 +6266,7 @@ function materializeBranchForTicket(dir, cfg, t, role) {
6215
6266
  }
6216
6267
 
6217
6268
  // src/poll.ts
6218
- import { writeFileSync as writeFileSync6 } from "fs";
6269
+ import { writeFileSync as writeFileSync7 } from "fs";
6219
6270
  import { join as join10 } from "path";
6220
6271
 
6221
6272
  // src/stalled.ts
@@ -6555,7 +6606,7 @@ function writeDigest(o, d, role, stateDir) {
6555
6606
  };
6556
6607
  const text = role === "qa" ? qaDigest(input) : buildingDigest(input);
6557
6608
  const path = digestPath(stateDir, routeSlug(o.route.route), role);
6558
- writeFileSync6(path, text);
6609
+ writeFileSync7(path, text);
6559
6610
  o.emit.emit(`queue digest written for ${role} (${Buffer.byteLength(text)} bytes)`, {
6560
6611
  data: { role, bytes: Buffer.byteLength(text) }
6561
6612
  });
@@ -6939,7 +6990,7 @@ var alreadyReported = (ticket, g, events) => events.some((e) => e.record_id ===
6939
6990
 
6940
6991
  // src/agent.ts
6941
6992
  import { spawn as spawn2 } from "child_process";
6942
- import { readFileSync as readFileSync11, existsSync as existsSync12, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7, createWriteStream } from "fs";
6993
+ import { readFileSync as readFileSync12, existsSync as existsSync12, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, createWriteStream } from "fs";
6943
6994
  import { finished } from "stream/promises";
6944
6995
  import { join as join13, dirname as dirname6 } from "path";
6945
6996
  import { createHash as createHash2 } from "crypto";
@@ -7356,13 +7407,13 @@ function expandSkillMarkers(text, lookup, opts = {}) {
7356
7407
  }
7357
7408
 
7358
7409
  // src/skills.ts
7359
- import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync11 } from "fs";
7410
+ import { readFileSync as readFileSync11, readdirSync as readdirSync3, existsSync as existsSync11 } from "fs";
7360
7411
  import { join as join12 } from "path";
7361
7412
  import { parse as parseYaml } from "yaml";
7362
7413
  import { TablationClient as TablationClient3, StaleWriteError as StaleWriteError8, TablationApiError as TablationApiError2 } from "@tablation/client";
7363
7414
 
7364
7415
  // src/agents.ts
7365
- import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
7416
+ import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
7366
7417
  import { join as join11 } from "path";
7367
7418
  import { TablationClient as TablationClient2, StaleWriteError as StaleWriteError7, TablationApiError } from "@tablation/client";
7368
7419
  var PERSONA_NAME = {
@@ -7379,10 +7430,10 @@ var AgentsSyncError = class extends Error {
7379
7430
  function personaDefaultPrompt(promptsDir, role) {
7380
7431
  const briefPath = join11(promptsDir, "personas", `lane-${role}.md`);
7381
7432
  if (!existsSync10(briefPath)) throw new AgentsSyncError(`no brief at ${briefPath}`);
7382
- if (SELF_CONTAINED_ROLES.has(role)) return readFileSync9(briefPath, "utf8");
7433
+ if (SELF_CONTAINED_ROLES.has(role)) return readFileSync10(briefPath, "utf8");
7383
7434
  const commonPath = join11(promptsDir, "personas", "common.md");
7384
- if (!existsSync10(commonPath)) return readFileSync9(briefPath, "utf8");
7385
- return `${readFileSync9(commonPath, "utf8")}${readFileSync9(briefPath, "utf8")}`;
7435
+ if (!existsSync10(commonPath)) return readFileSync10(briefPath, "utf8");
7436
+ return `${readFileSync10(commonPath, "utf8")}${readFileSync10(briefPath, "utf8")}`;
7386
7437
  }
7387
7438
  async function resolveAgentId(route, role, opts) {
7388
7439
  const cached = route.resolved?.agentPersonas?.[role]?.agentId;
@@ -7728,7 +7779,7 @@ function describeCrewLink(o) {
7728
7779
  var SkillSyncError = class extends Error {
7729
7780
  };
7730
7781
  function parseSkillFile(path) {
7731
- const text = readFileSync10(path, "utf8");
7782
+ const text = readFileSync11(path, "utf8");
7732
7783
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text);
7733
7784
  if (!match) throw new SkillSyncError(`${path}: missing YAML frontmatter (expected a leading --- block)`);
7734
7785
  const frontmatter = match[1];
@@ -7989,10 +8040,10 @@ function planAgentRun(o) {
7989
8040
  let ageSeconds;
7990
8041
  if (existsSync12(digestPath2)) {
7991
8042
  ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
7992
- if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync11(digestPath2, "utf8");
8043
+ if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync12(digestPath2, "utf8");
7993
8044
  }
7994
- const common = usesCommon ? readFileSync11(commonPath, "utf8") : "";
7995
- const brief = readFileSync11(briefPath, "utf8");
8045
+ const common = usesCommon ? readFileSync12(commonPath, "utf8") : "";
8046
+ const brief = readFileSync12(briefPath, "utf8");
7996
8047
  const effectiveCommon = o.divergedPrompt ? "" : common;
7997
8048
  const effectiveBrief = o.divergedPrompt ?? brief;
7998
8049
  const localSkills = new Map(listLocalSkills(o.route.promptsDir).map((s) => [s.name, s.prompt]));
@@ -8218,7 +8269,7 @@ function spawnAgent(plan, emit) {
8218
8269
  provider: plan.provider ?? LEGACY_TOOLCHAIN_PROVIDER
8219
8270
  };
8220
8271
  try {
8221
- writeFileSync7(`${plan.streamPath}.meta.json`, JSON.stringify({
8272
+ writeFileSync8(`${plan.streamPath}.meta.json`, JSON.stringify({
8222
8273
  route: plan.route,
8223
8274
  role: plan.role,
8224
8275
  ticket: plan.ticket ?? null,
@@ -8625,7 +8676,7 @@ async function retireRunWorktree(tracker, dir, cfg, t, role, seatId, shipName2,
8625
8676
 
8626
8677
  // src/connect-wizard.ts
8627
8678
  import { delimiter, dirname as dirname8, join as join16 } from "path";
8628
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
8679
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "fs";
8629
8680
  import { stringify } from "yaml";
8630
8681
  var HOST_PASSENGERS_DOCS_URL = "https://crew.tablation.dev/host-passengers";
8631
8682
  function shouldRunWizard(opts) {
@@ -8710,7 +8761,7 @@ ${routeBlock}`;
8710
8761
  function writeNewConfig(path, text) {
8711
8762
  mkdirSync7(dirname8(path), { recursive: true });
8712
8763
  try {
8713
- writeFileSync8(path, text, { mode: 384, flag: "wx" });
8764
+ writeFileSync9(path, text, { mode: 384, flag: "wx" });
8714
8765
  return true;
8715
8766
  } catch (e) {
8716
8767
  if (e.code === "EEXIST") return false;
@@ -8742,7 +8793,7 @@ Wrote ${o.configPath}.
8742
8793
  }
8743
8794
 
8744
8795
  // src/release-run.ts
8745
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
8796
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, copyFileSync, mkdirSync as mkdirSync8, mkdtempSync, rmSync as rmSync6 } from "fs";
8746
8797
  import { dirname as dirname9, join as join17 } from "path";
8747
8798
  import { tmpdir as tmpdir2 } from "os";
8748
8799
 
@@ -9407,7 +9458,7 @@ async function bump(o, size) {
9407
9458
  o.emit.error(`no ${o.repo.release.versionFiles[0]} to read a version from`);
9408
9459
  return null;
9409
9460
  }
9410
- const current = JSON.parse(readFileSync12(first, "utf8")).version;
9461
+ const current = JSON.parse(readFileSync13(first, "utf8")).version;
9411
9462
  if (!current) {
9412
9463
  o.emit.error(`no version field in ${o.repo.release.versionFiles[0]}`);
9413
9464
  return null;
@@ -9420,13 +9471,13 @@ async function bump(o, size) {
9420
9471
  return version;
9421
9472
  }
9422
9473
  for (const f of files) {
9423
- const src = readFileSync12(f, "utf8");
9474
+ const src = readFileSync13(f, "utf8");
9424
9475
  const out = src.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`);
9425
9476
  if (out === src) {
9426
9477
  o.emit.error(`no version field in ${f}`);
9427
9478
  return null;
9428
9479
  }
9429
- writeFileSync9(f, out);
9480
+ writeFileSync10(f, out);
9430
9481
  }
9431
9482
  return version;
9432
9483
  }
@@ -9439,8 +9490,8 @@ function writeChangelog(o, version, entries) {
9439
9490
  o.emit.emit(`would prepend ${entries.length} entr(ies) to ${file}`);
9440
9491
  return;
9441
9492
  }
9442
- const existing = existsSync16(path) ? readFileSync12(path, "utf8") : "# Changelog\n";
9443
- writeFileSync9(path, insertChangelogSection(existing, section));
9493
+ const existing = existsSync16(path) ? readFileSync13(path, "utf8") : "# Changelog\n";
9494
+ writeFileSync10(path, insertChangelogSection(existing, section));
9444
9495
  }
9445
9496
  async function confirm(o, expected) {
9446
9497
  if (!o.repo.hooks.released) return void 0;
@@ -10799,13 +10850,118 @@ ${opts.hostPassengers ? " hostPassengers: true\n" : ""}${apiKeyFileLine}${api
10799
10850
 
10800
10851
  // src/cli.ts
10801
10852
  import {
10802
- getSessionStore as getSessionStore2,
10803
- hostFromUrl as hostFromUrl2,
10853
+ getSessionStore as getSessionStore3,
10854
+ hostFromUrl as hostFromUrl3,
10804
10855
  loginWithDeviceCode,
10805
10856
  getOrCreateShipId,
10806
10857
  DeviceLoginError
10807
10858
  } from "@tablation/client";
10808
10859
 
10860
+ // src/seat-keys.ts
10861
+ import { getSessionStore as getSessionStore2, hostFromUrl as hostFromUrl2 } from "@tablation/client";
10862
+ var SEAT_KEY_ROLES = ROLE_NAMES.filter((r) => r !== "pair");
10863
+ function seatKeySlug(workspaceSlug, agentId) {
10864
+ return `${workspaceSlug}#agent:${agentId}`;
10865
+ }
10866
+ function slugFor(route, agentId) {
10867
+ const [workspaceSlug] = splitRoute(route.route);
10868
+ return seatKeySlug(workspaceSlug, agentId);
10869
+ }
10870
+ function seatAgentId(route, role) {
10871
+ return route.resolved?.agentPersonas?.[role]?.agentId;
10872
+ }
10873
+ async function resolveSeatKey(route, role, getStore = getSessionStore2) {
10874
+ const ship = (reason) => ({ kind: "ship", apiKey: resolveApiKey(route), reason });
10875
+ if (!SEAT_KEY_ROLES.includes(role)) return ship(`${role} always writes as the ship`);
10876
+ const agentId = seatAgentId(route, role);
10877
+ if (!agentId) return ship("seat has no linked Agents row (run `crew agents sync`)");
10878
+ try {
10879
+ const session = await getStore().get(hostFromUrl2(route.baseUrl), slugFor(route, agentId));
10880
+ if (session?.apiKey) return { kind: "agent", apiKey: session.apiKey, agentId };
10881
+ return ship("no agent key stored for this seat (run `crew agents sync`)");
10882
+ } catch {
10883
+ return ship("OS keychain unavailable");
10884
+ }
10885
+ }
10886
+ async function mintMissingSeatKeys(route, o) {
10887
+ const out = [];
10888
+ if (!route.resolved) return out;
10889
+ const resolved = route.resolved;
10890
+ const doFetch = o.fetchImpl ?? fetch;
10891
+ const seated = SEAT_KEY_ROLES.filter((r) => resolved.seats[r]);
10892
+ let store;
10893
+ try {
10894
+ store = (o.getStore ?? getSessionStore2)();
10895
+ } catch (e) {
10896
+ return seated.map((role) => ({
10897
+ role,
10898
+ action: "failed",
10899
+ detail: `OS keychain unavailable: ${e.message}`
10900
+ }));
10901
+ }
10902
+ const host = hostFromUrl2(route.baseUrl);
10903
+ const base = route.baseUrl.replace(/\/+$/, "");
10904
+ for (const role of seated) {
10905
+ const agentId = seatAgentId(route, role);
10906
+ if (!agentId) {
10907
+ out.push({ role, action: "no_agent" });
10908
+ continue;
10909
+ }
10910
+ const slug = slugFor(route, agentId);
10911
+ try {
10912
+ if ((await store.get(host, slug))?.apiKey) {
10913
+ out.push({ role, action: "already_stored" });
10914
+ continue;
10915
+ }
10916
+ if (o.dryRun) {
10917
+ out.push({ role, action: "minted", detail: "dry run" });
10918
+ continue;
10919
+ }
10920
+ const res = await doFetch(`${base}/api/workspaces/${resolved.workspaceId}/agents/${agentId}/api-keys`, {
10921
+ method: "POST",
10922
+ headers: {
10923
+ Authorization: `Bearer ${resolveApiKey(route)}`,
10924
+ "Content-Type": "application/json",
10925
+ "User-Agent": o.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0"
10926
+ },
10927
+ body: JSON.stringify({ name: `crew ${o.shipName ?? "ship"} ${role}` })
10928
+ });
10929
+ if (!res.ok) {
10930
+ out.push({ role, action: "failed", detail: `${res.status} ${res.statusText}` });
10931
+ continue;
10932
+ }
10933
+ const body = await res.json();
10934
+ if (!body.key) {
10935
+ out.push({ role, action: "failed", detail: "response carried no key" });
10936
+ continue;
10937
+ }
10938
+ await store.set(host, slug, {
10939
+ apiKey: body.key,
10940
+ shipId: o.shipName ?? "",
10941
+ workspaceId: resolved.workspaceId,
10942
+ identityId: agentId,
10943
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
10944
+ });
10945
+ out.push({ role, action: "minted" });
10946
+ } catch (e) {
10947
+ out.push({ role, action: "failed", detail: e.message });
10948
+ }
10949
+ }
10950
+ return out;
10951
+ }
10952
+ function describeMintOutcome(o) {
10953
+ switch (o.action) {
10954
+ case "minted":
10955
+ return `${o.role}: agent key minted${o.detail ? ` (${o.detail})` : ""}`;
10956
+ case "already_stored":
10957
+ return `${o.role}: agent key already stored`;
10958
+ case "no_agent":
10959
+ return `${o.role}: no linked Agents row \u2014 writes as the ship until \`crew agents sync\` links one`;
10960
+ case "failed":
10961
+ return `${o.role}: could not mint an agent key (${o.detail ?? "unknown"}) \u2014 writes as the ship`;
10962
+ }
10963
+ }
10964
+
10809
10965
  // src/logbook.ts
10810
10966
  import { createHash as createHash3 } from "crypto";
10811
10967
  import { TablationClient as TablationClient4 } from "@tablation/client";
@@ -11304,13 +11460,13 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
11304
11460
  }
11305
11461
 
11306
11462
  // src/cli.ts
11307
- import { readFileSync as readFileSync17, existsSync as existsSync24, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
11463
+ import { readFileSync as readFileSync18, existsSync as existsSync24, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
11308
11464
  import { createInterface } from "readline/promises";
11309
11465
  import { dirname as dirOf, resolve as resolvePath } from "path";
11310
11466
 
11311
11467
  // src/ssh-keys.ts
11312
11468
  import { execFileSync as execFileSync3 } from "child_process";
11313
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync13, chmodSync } from "fs";
11469
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync14, chmodSync } from "fs";
11314
11470
  import { dirname as dirname11, join as join19 } from "path";
11315
11471
  var SshKeyError = class extends Error {
11316
11472
  };
@@ -11344,7 +11500,7 @@ function ensureShipSshKeypair(stateDir) {
11344
11500
  if (!existsSync20(publicKeyPath)) {
11345
11501
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
11346
11502
  }
11347
- const publicKey = readFileSync13(publicKeyPath, "utf8").trim();
11503
+ const publicKey = readFileSync14(publicKeyPath, "utf8").trim();
11348
11504
  return { privateKeyPath, publicKeyPath, publicKey };
11349
11505
  }
11350
11506
  function sshKeygenAvailable() {
@@ -11542,7 +11698,9 @@ function passengerCheckoutSyncSpecs(cfg, workspaceId) {
11542
11698
  dir: target.dir,
11543
11699
  remote: target.remote,
11544
11700
  gitRemoteName: effective.branch.remote,
11545
- base: effective.branch.base
11701
+ base: effective.branch.base,
11702
+ originUrl: ordinary ? originUrlOf(ordinary.dir, effective.branch.remote) : null,
11703
+ cloneScheme: cfg.ship.cloneScheme
11546
11704
  });
11547
11705
  }
11548
11706
  }
@@ -11552,13 +11710,13 @@ function syncAllPassengerCheckouts(cfg) {
11552
11710
  return passengerCheckoutSyncSpecs(cfg).map((spec) => ({
11553
11711
  name: spec.name,
11554
11712
  workspaceId: spec.workspaceId,
11555
- outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base)
11713
+ outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base, { originUrl: spec.originUrl, scheme: spec.cloneScheme })
11556
11714
  }));
11557
11715
  }
11558
11716
 
11559
11717
  // src/tunnel.ts
11560
11718
  import { spawn as spawn3 } from "child_process";
11561
- import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
11719
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
11562
11720
  import { randomInt } from "crypto";
11563
11721
  import { join as join20 } from "path";
11564
11722
  var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
@@ -11633,7 +11791,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
11633
11791
  const path = tunnelStatePath(stateDir, workspaceId);
11634
11792
  if (!existsSync21(path)) return void 0;
11635
11793
  try {
11636
- return JSON.parse(readFileSync14(path, "utf8"));
11794
+ return JSON.parse(readFileSync15(path, "utf8"));
11637
11795
  } catch {
11638
11796
  return void 0;
11639
11797
  }
@@ -11641,7 +11799,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
11641
11799
  function writePersistedTunnel(stateDir, state) {
11642
11800
  const path = tunnelStatePath(stateDir, state.workspaceId);
11643
11801
  mkdirSync10(join20(stateDir, "passengers"), { recursive: true });
11644
- writeFileSync10(path, `${JSON.stringify(state, null, 2)}
11802
+ writeFileSync11(path, `${JSON.stringify(state, null, 2)}
11645
11803
  `);
11646
11804
  }
11647
11805
  function updatePersistedTunnel(stateDir, workspaceId, patch2) {
@@ -11860,7 +12018,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
11860
12018
  import { createServer as createHttpServer } from "http";
11861
12019
  import { spawn as spawn4 } from "child_process";
11862
12020
  import { randomBytes } from "crypto";
11863
- import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync6 } from "fs";
12021
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4, readdirSync as readdirSync6 } from "fs";
11864
12022
  import { join as join21 } from "path";
11865
12023
 
11866
12024
  // src/secret-compare.ts
@@ -11915,7 +12073,7 @@ function createSyncDaemonServer(targets, secret) {
11915
12073
  try {
11916
12074
  const synced = targets.map((t) => ({
11917
12075
  name: t.name,
11918
- outcome: syncPassengerCheckout(t.dir, t.remote, t.gitRemoteName, t.base)
12076
+ outcome: syncPassengerCheckout(t.dir, t.remote, t.gitRemoteName, t.base, { originUrl: t.originUrl, scheme: t.cloneScheme })
11919
12077
  }));
11920
12078
  res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ synced }));
11921
12079
  } catch (error) {
@@ -11956,7 +12114,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
11956
12114
  const path = syncDaemonStatePath(stateDir, workspaceId);
11957
12115
  if (!existsSync22(path)) return void 0;
11958
12116
  try {
11959
- return JSON.parse(readFileSync15(path, "utf8"));
12117
+ return JSON.parse(readFileSync16(path, "utf8"));
11960
12118
  } catch {
11961
12119
  return void 0;
11962
12120
  }
@@ -11964,7 +12122,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
11964
12122
  function writePersistedSyncDaemon(stateDir, state) {
11965
12123
  const path = syncDaemonStatePath(stateDir, state.workspaceId);
11966
12124
  mkdirSync11(join21(stateDir, "passengers"), { recursive: true });
11967
- writeFileSync11(path, `${JSON.stringify(state, null, 2)}
12125
+ writeFileSync12(path, `${JSON.stringify(state, null, 2)}
11968
12126
  `);
11969
12127
  }
11970
12128
  function removePersistedSyncDaemon(stateDir, workspaceId) {
@@ -12042,7 +12200,7 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
12042
12200
  }
12043
12201
 
12044
12202
  // src/daemon.ts
12045
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync16, readdirSync as readdirSync7, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync12 } from "fs";
12203
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync17, readdirSync as readdirSync7, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync13 } from "fs";
12046
12204
  import { join as join22 } from "path";
12047
12205
  var DEFAULT_FLOOR_MS = 5e3;
12048
12206
  var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
@@ -12102,12 +12260,12 @@ function daemonStampFor(stateDir) {
12102
12260
  function noteDaemonStarted(stateDir, now = Date.now()) {
12103
12261
  mkdirSync12(stateDir, { recursive: true });
12104
12262
  rmSync8(reloadSentinelFor(stateDir), { force: true });
12105
- writeFileSync12(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
12263
+ writeFileSync13(daemonStampFor(stateDir), `${JSON.stringify({ pid: process.pid, startedAt: now })}
12106
12264
  `);
12107
12265
  }
12108
12266
  function readDaemonStarted(stateDir) {
12109
12267
  try {
12110
- const v = JSON.parse(readFileSync16(daemonStampFor(stateDir), "utf8")).startedAt;
12268
+ const v = JSON.parse(readFileSync17(daemonStampFor(stateDir), "utf8")).startedAt;
12111
12269
  return typeof v === "number" ? v : null;
12112
12270
  } catch {
12113
12271
  return null;
@@ -12159,7 +12317,7 @@ async function runOnePass(o) {
12159
12317
  const decide = o.decide ?? decideCycle;
12160
12318
  const spawn5 = o.spawnRoleAgent ?? runRoleAgent;
12161
12319
  const breaker = o.spawnBreaker ?? new SpawnBreaker();
12162
- const ctx = { route: o.route, ship: o.ship, state: o.state, emit };
12320
+ const ctx = { route: o.route, ship: o.ship, state: o.state, emit, getSessionStore: o.getSessionStore };
12163
12321
  const decision = await decide(ctx);
12164
12322
  o.state.attention(o.route.route).persist(decision.attention);
12165
12323
  if (decision.sweep.length) {
@@ -12259,7 +12417,13 @@ async function publishShipAttention(o) {
12259
12417
  }
12260
12418
  async function runRoleAgent(role, decision, ctx) {
12261
12419
  const { route, ship, emit } = ctx;
12262
- const tracker = new Tracker(route, ship);
12420
+ const identity = await resolveSeatKey(route, role, ctx.getSessionStore);
12421
+ if (identity.kind === "ship" && SEAT_KEY_ROLES.includes(role)) {
12422
+ emit.warn(`${role} writes as the ship, not its own agent: ${identity.reason}`, { step: "agent", role });
12423
+ } else if (identity.kind === "agent") {
12424
+ emit.emit(`${role} writes as its own agent principal`, { step: "agent", role });
12425
+ }
12426
+ const tracker = new Tracker(route, ship, { apiKey: identity.apiKey });
12263
12427
  let ticketHint = decision.actionable.top?.issue_id;
12264
12428
  let ticketRepo = repoTargetFor(route, decision.actionable.top?.repo_id)?.name;
12265
12429
  let workingId = decision.actionable.top?.id ?? null;
@@ -12336,7 +12500,7 @@ async function runRoleAgent(role, decision, ctx) {
12336
12500
  stateDir: ship.stateDir,
12337
12501
  roster: rosterFor(decision, route, role),
12338
12502
  environment: await buildEnvironment(route, ship, ticketHint),
12339
- apiKey: resolveApiKey(route),
12503
+ apiKey: identity.apiKey,
12340
12504
  cycle: emit.cycle,
12341
12505
  ticket: ticketHint,
12342
12506
  repo: ticketRepo,
@@ -12738,6 +12902,15 @@ if (command === "passenger-sync-daemon") {
12738
12902
  }
12739
12903
  try {
12740
12904
  for (const r of syncAllPassengerCheckouts(cfg)) {
12905
+ if (r.outcome.action === "clone-failed") {
12906
+ if (!r.outcome.repeat) {
12907
+ emit.warn(
12908
+ `passenger checkout for ${r.name} could not be cloned (not retried until its clone URL changes): ${r.outcome.detail}`,
12909
+ { step: "passengers", data: { workspaceId: r.workspaceId, repo: r.name } }
12910
+ );
12911
+ }
12912
+ continue;
12913
+ }
12741
12914
  if (r.outcome.action === "diverged" || r.outcome.action === "ff-failed") {
12742
12915
  emit.warn(
12743
12916
  `passenger checkout for ${r.name} is stale: ${r.outcome.detail}`,
@@ -12968,7 +13141,7 @@ if (command === "passenger-sync-daemon") {
12968
13141
  for (const a of plan.actions) {
12969
13142
  if (a.kind === "enable") {
12970
13143
  if (prompter && !await prompter.confirm(`Route ${a.route} passes every check \u2014 set enabled: true in ${cfg.configFile}?`, false)) continue;
12971
- writeFileSync13(cfg.configFile, planEnable(readFileSync17(cfg.configFile, "utf8"), a.route));
13144
+ writeFileSync14(cfg.configFile, planEnable(readFileSync18(cfg.configFile, "utf8"), a.route));
12972
13145
  enabledSound.add(a.route);
12973
13146
  changed2.push(`enabled route ${a.route} in ${cfg.configFile}`);
12974
13147
  } else {
@@ -14061,11 +14234,18 @@ ${changed2.map((c) => ` - ${c}
14061
14234
  `);
14062
14235
  if (!dryRun) {
14063
14236
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
14064
- const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
14237
+ const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync18(resolvedPath, "utf8")) : {};
14065
14238
  raw.agentPersonas = personaResult.agentPersonas;
14066
14239
  raw.agentSkills = skillResult.agentSkills;
14067
14240
  mkdirSync13(dirOf(resolvedPath), { recursive: true });
14068
- writeFileSync13(resolvedPath, `${JSON.stringify(raw, null, 2)}
14241
+ writeFileSync14(resolvedPath, `${JSON.stringify(raw, null, 2)}
14242
+ `);
14243
+ }
14244
+ if (!dryRun) {
14245
+ target.resolved.agentPersonas = personaResult.agentPersonas;
14246
+ }
14247
+ for (const o of await mintMissingSeatKeys(target, { userAgent: cfg.ship.userAgent, dryRun, shipName: cfg.ship.name })) {
14248
+ process.stdout.write(`${describeMintOutcome(o)}
14069
14249
  `);
14070
14250
  }
14071
14251
  if (personaResult.outcomes.some((o) => o.action === "diverged") || skillResult.outcomes.some((o) => o.action === "diverged")) {
@@ -14112,10 +14292,10 @@ ${changed2.map((c) => ` - ${c}
14112
14292
  `);
14113
14293
  if (!dryRun) {
14114
14294
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
14115
- const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync17(resolvedPath, "utf8")) : {};
14295
+ const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync18(resolvedPath, "utf8")) : {};
14116
14296
  raw.agentSkills = result.agentSkills;
14117
14297
  mkdirSync13(dirOf(resolvedPath), { recursive: true });
14118
- writeFileSync13(resolvedPath, `${JSON.stringify(raw, null, 2)}
14298
+ writeFileSync14(resolvedPath, `${JSON.stringify(raw, null, 2)}
14119
14299
  `);
14120
14300
  }
14121
14301
  if (result.outcomes.some((o) => o.action === "diverged")) process.exitCode = 1;
@@ -14128,7 +14308,7 @@ ${changed2.map((c) => ` - ${c}
14128
14308
  process.exit(2);
14129
14309
  };
14130
14310
  if (sub !== "add" && sub !== "list") usage2();
14131
- const configText = readFileSync17(cfg.configFile, "utf8");
14311
+ const configText = readFileSync18(cfg.configFile, "utf8");
14132
14312
  let routeName = positional[2];
14133
14313
  let addPath = ".";
14134
14314
  let addName;
@@ -14152,7 +14332,7 @@ ${changed2.map((c) => ` - ${c}
14152
14332
  let resolvedRemotes;
14153
14333
  if (existsSync24(resolvedFile)) {
14154
14334
  try {
14155
- resolvedRemotes = JSON.parse(readFileSync17(resolvedFile, "utf8"));
14335
+ resolvedRemotes = JSON.parse(readFileSync18(resolvedFile, "utf8"));
14156
14336
  } catch {
14157
14337
  }
14158
14338
  }
@@ -14224,14 +14404,14 @@ ${changed2.map((c) => ` - ${c}
14224
14404
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
14225
14405
  `);
14226
14406
  if (existsSync24(resolvedFile)) {
14227
- const raw = JSON.parse(readFileSync17(resolvedFile, "utf8"));
14407
+ const raw = JSON.parse(readFileSync18(resolvedFile, "utf8"));
14228
14408
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
14229
14409
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
14230
- writeFileSync13(resolvedFile, `${JSON.stringify(raw, null, 2)}
14410
+ writeFileSync14(resolvedFile, `${JSON.stringify(raw, null, 2)}
14231
14411
  `);
14232
14412
  }
14233
14413
  }
14234
- writeFileSync13(cfg.configFile, plan.text);
14414
+ writeFileSync14(cfg.configFile, plan.text);
14235
14415
  process.stdout.write(`added ${top} to ${routeName} as ${plan.mode === "dir" ? "dir" : `repos.${plan.name}`} in ${cfg.configFile}
14236
14416
  `);
14237
14417
  } catch (e) {
@@ -14332,18 +14512,18 @@ ${reconstruction.prompt}
14332
14512
  let apiKey = value("key") ?? process.env.CREW_CONNECT_KEY ?? cfg.routes.find((c) => c.apiKey)?.apiKey;
14333
14513
  let store;
14334
14514
  try {
14335
- store = getSessionStore2();
14515
+ store = getSessionStore3();
14336
14516
  } catch {
14337
14517
  store = void 0;
14338
14518
  }
14339
14519
  let keychainBacked = false;
14340
14520
  if (!apiKey && workspace && store) {
14341
14521
  try {
14342
- const existing = await store.get(hostFromUrl2(baseUrl), workspace);
14522
+ const existing = await store.get(hostFromUrl3(baseUrl), workspace);
14343
14523
  if (existing?.apiKey) {
14344
14524
  apiKey = existing.apiKey;
14345
14525
  keychainBacked = true;
14346
- process.stderr.write(`Reusing the existing keychain session for ${hostFromUrl2(baseUrl)}/${workspace}.
14526
+ process.stderr.write(`Reusing the existing keychain session for ${hostFromUrl3(baseUrl)}/${workspace}.
14347
14527
  `);
14348
14528
  }
14349
14529
  } catch {
@@ -14397,7 +14577,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14397
14577
  if (!workspace) workspace = result.workspace.slug;
14398
14578
  if (store) {
14399
14579
  try {
14400
- await store.set(hostFromUrl2(baseUrl), result.workspace.slug, {
14580
+ await store.set(hostFromUrl3(baseUrl), result.workspace.slug, {
14401
14581
  apiKey: result.apiKey.key,
14402
14582
  shipId: getOrCreateShipId(),
14403
14583
  workspaceId: result.workspace.id,
@@ -14406,7 +14586,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14406
14586
  });
14407
14587
  keychainBacked = true;
14408
14588
  process.stderr.write(
14409
- `API key stored in the OS keychain for ${hostFromUrl2(baseUrl)}/${result.workspace.slug}.
14589
+ `API key stored in the OS keychain for ${hostFromUrl3(baseUrl)}/${result.workspace.slug}.
14410
14590
  `
14411
14591
  );
14412
14592
  } catch (e) {
@@ -14473,7 +14653,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14473
14653
  const hadPreviousFile = existsSync24(resolvedPath);
14474
14654
  if (hadPreviousFile) {
14475
14655
  try {
14476
- previous = JSON.parse(readFileSync17(resolvedPath, "utf8"));
14656
+ previous = JSON.parse(readFileSync18(resolvedPath, "utf8"));
14477
14657
  } catch {
14478
14658
  }
14479
14659
  }
@@ -14537,12 +14717,23 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14537
14717
  mkdirSync13(dirname12(resolvedPath), { recursive: true });
14538
14718
  if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
14539
14719
  const tmpPath = `${resolvedPath}.tmp${process.pid}`;
14540
- writeFileSync13(tmpPath, `${JSON.stringify(resolved, null, 2)}
14720
+ writeFileSync14(tmpPath, `${JSON.stringify(resolved, null, 2)}
14541
14721
  `);
14542
14722
  renameSync2(tmpPath, resolvedPath);
14543
14723
  process.stderr.write(`Resolved ids written to ${resolvedPath}
14544
14724
  `);
14545
14725
  }
14726
+ if (!dryRun && previous?.agentPersonas && existingRoute?.resolved) {
14727
+ try {
14728
+ for (const o of await mintMissingSeatKeys({ ...existingRoute, resolved: { ...existingRoute.resolved, seats: found.seats } }, { userAgent: cfg.ship.userAgent, shipName: cfg.ship.name })) {
14729
+ process.stderr.write(`${describeMintOutcome(o)}
14730
+ `);
14731
+ }
14732
+ } catch (e) {
14733
+ process.stderr.write(`seat keys: skipped (${e.message})
14734
+ `);
14735
+ }
14736
+ }
14546
14737
  let apiKeyFile;
14547
14738
  const apiKeyVar = "CREW_API_KEY";
14548
14739
  if (mintedFromDevice && !keychainBacked) {
@@ -14552,7 +14743,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
14552
14743
  `);
14553
14744
  } else {
14554
14745
  mkdirSync13(dirname12(apiKeyFile), { recursive: true });
14555
- writeFileSync13(apiKeyFile, `${apiKeyVar}=${apiKey}
14746
+ writeFileSync14(apiKeyFile, `${apiKeyVar}=${apiKey}
14556
14747
  `, { mode: 384 });
14557
14748
  process.stderr.write(`API key written to ${apiKeyFile}
14558
14749
  `);
@@ -15090,7 +15281,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
15090
15281
  break;
15091
15282
  }
15092
15283
  mkdirSync13(cfg.ship.stateDir, { recursive: true });
15093
- writeFileSync13(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
15284
+ writeFileSync14(sentinel, `${(/* @__PURE__ */ new Date()).toISOString()}
15094
15285
  `);
15095
15286
  process.stdout.write("crew reload: requested \u2014 the daemon finishes its in-flight pass, then relaunches with the new config\n");
15096
15287
  if (flag("now") && control?.stopCommand && control.startCommand) {
@@ -15141,7 +15332,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
15141
15332
  }
15142
15333
  case "log": {
15143
15334
  try {
15144
- const text = readFileSync17(cfg.ship.logFile, "utf8").split("\n");
15335
+ const text = readFileSync18(cfg.ship.logFile, "utf8").split("\n");
15145
15336
  process.stdout.write(`${text.slice(-(Number(value("tail")) || 40)).join("\n")}
15146
15337
  `);
15147
15338
  } catch {
@@ -15417,6 +15608,14 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
15417
15608
  process.stdout.write(
15418
15609
  route.resolved?.releaseSeat ? "release seat: this ship has its own Release Crew row\n" : "release seat: none \u2014 release comments are credited to the dev seat; run `crew connect` once the Crew table has this ship's row\n"
15419
15610
  );
15611
+ for (const role of SEAT_KEY_ROLES) {
15612
+ if (!route.resolved?.seats[role]) continue;
15613
+ const id = await resolveSeatKey(route, role);
15614
+ process.stdout.write(
15615
+ `seat identity: ${role} writes as ${id.kind === "agent" ? `its own agent (${id.agentId})` : `the ship (${id.reason})`}
15616
+ `
15617
+ );
15618
+ }
15420
15619
  const [ships, crewRows] = await Promise.all([tracker.shipRows(), tracker.crewRows()]);
15421
15620
  if (ships.length === 0) {
15422
15621
  process.stdout.write("ship record: this workspace has no Ships table (fine)\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.45.4",
3
+ "version": "0.46.1",
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",