@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.
- package/dist/cli.js +275 -76
- 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(
|
|
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(
|
|
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
|
-
|
|
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",
|
|
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(
|
|
4401
|
-
throw new GitError(`git clone ${
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7385
|
-
return `${
|
|
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 =
|
|
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 =
|
|
8043
|
+
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync12(digestPath2, "utf8");
|
|
7993
8044
|
}
|
|
7994
|
-
const common = usesCommon ?
|
|
7995
|
-
const brief =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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) ?
|
|
9443
|
-
|
|
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
|
|
10803
|
-
hostFromUrl as
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
14295
|
+
const raw = existsSync24(resolvedPath) ? JSON.parse(readFileSync18(resolvedPath, "utf8")) : {};
|
|
14116
14296
|
raw.agentSkills = result.agentSkills;
|
|
14117
14297
|
mkdirSync13(dirOf(resolvedPath), { recursive: true });
|
|
14118
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
14410
|
+
writeFileSync14(resolvedFile, `${JSON.stringify(raw, null, 2)}
|
|
14231
14411
|
`);
|
|
14232
14412
|
}
|
|
14233
14413
|
}
|
|
14234
|
-
|
|
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 =
|
|
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(
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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