@tablation/crew 0.102.0 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +42 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12387,7 +12387,17 @@ async function runProvision(o, deps, write, human) {
|
|
|
12387
12387
|
emit("problem", message, data);
|
|
12388
12388
|
return { code, summary: { ...summary, ok: false, state: "failed" } };
|
|
12389
12389
|
};
|
|
12390
|
-
|
|
12390
|
+
const keyFor = (route2) => {
|
|
12391
|
+
const ws = route2.split("/")[0].toLowerCase();
|
|
12392
|
+
const hit = Object.entries(o.keys ?? {}).find(([k, v]) => k.toLowerCase() === ws && v);
|
|
12393
|
+
return hit?.[1] || o.key || void 0;
|
|
12394
|
+
};
|
|
12395
|
+
for (const route2 of o.routes) {
|
|
12396
|
+
if (!keyFor(route2)) {
|
|
12397
|
+
return fail(PROVISION_EXIT.problem, `no connect key for ${route2}: pass --key or set CREW_CONNECT_KEY_${envKey(route2.split("/")[0])} or CREW_CONNECT_KEY`, { stage: "input" });
|
|
12398
|
+
}
|
|
12399
|
+
}
|
|
12400
|
+
if (o.routes.length === 0 && !o.key) return fail(PROVISION_EXIT.problem, "no connect key: pass --key or set CREW_CONNECT_KEY", { stage: "input" });
|
|
12391
12401
|
if (o.routes.length === 0) {
|
|
12392
12402
|
return fail(PROVISION_EXIT.problem, "no routes to serve: pass --route WORKSPACE[/PROJECT] (repeatable) or set CREW_PROVISION_ROUTES", { stage: "input" });
|
|
12393
12403
|
}
|
|
@@ -12413,7 +12423,7 @@ async function runProvision(o, deps, write, human) {
|
|
|
12413
12423
|
...o.dryRun ? ["--dry-run"] : [],
|
|
12414
12424
|
...o.choices.flatMap((c) => ["--choose", c])
|
|
12415
12425
|
];
|
|
12416
|
-
const res = deps.crew(args, { env: { CREW_CONNECT_KEY:
|
|
12426
|
+
const res = deps.crew(args, { env: { CREW_CONNECT_KEY: keyFor(route2) } });
|
|
12417
12427
|
const events = parseEvents(res.stdout);
|
|
12418
12428
|
for (const e of events) if (o.json) write(`${JSON.stringify({ ...e, phase: "connect" })}
|
|
12419
12429
|
`);
|
|
@@ -12503,6 +12513,15 @@ function tokensFromEnv(hosts, env) {
|
|
|
12503
12513
|
}
|
|
12504
12514
|
return out;
|
|
12505
12515
|
}
|
|
12516
|
+
function keysFromEnv(routes, env) {
|
|
12517
|
+
const out = {};
|
|
12518
|
+
for (const r of routes) {
|
|
12519
|
+
const ws = r.split("/")[0];
|
|
12520
|
+
const v = env[`CREW_CONNECT_KEY_${envKey(ws)}`]?.trim();
|
|
12521
|
+
if (v) out[ws] = v;
|
|
12522
|
+
}
|
|
12523
|
+
return out;
|
|
12524
|
+
}
|
|
12506
12525
|
function repeated(argv2, name) {
|
|
12507
12526
|
const out = [];
|
|
12508
12527
|
for (let i = 0; i < argv2.length; i++) {
|
|
@@ -12523,9 +12542,11 @@ function provisionOptionsFrom(argv2, env) {
|
|
|
12523
12542
|
const forgeFlags = repeated(argv2, "forge-host");
|
|
12524
12543
|
const tokenAvailable = !!env.CREW_FORGE_TOKEN?.trim() || Object.keys(env).some((k) => k.startsWith("CREW_FORGE_TOKEN_") && env[k]?.trim());
|
|
12525
12544
|
const forgeHosts = (forgeFlags.length ? forgeFlags : tokenAvailable ? splitList(env.CREW_FORGE_HOSTS ?? "github.com") : []).map((h) => h.toLowerCase());
|
|
12545
|
+
const routeList = routes.length ? routes : splitList(env.CREW_PROVISION_ROUTES);
|
|
12526
12546
|
return {
|
|
12527
12547
|
key: one("key") ?? env.CREW_CONNECT_KEY,
|
|
12528
|
-
|
|
12548
|
+
keys: keysFromEnv(routeList, env),
|
|
12549
|
+
routes: routeList,
|
|
12529
12550
|
shipName: one("ship-name") ?? env.CREW_SHIP_NAME,
|
|
12530
12551
|
shipKind: one("ship-kind"),
|
|
12531
12552
|
instanceClass: one("instance-class"),
|
|
@@ -12546,6 +12567,7 @@ function realProvisionDeps(crewHome) {
|
|
|
12546
12567
|
const [bin, ...pre] = crewInvocation(crewHome);
|
|
12547
12568
|
const env = { ...process.env, ...o.env };
|
|
12548
12569
|
delete env.CREW_FORGE_TOKEN;
|
|
12570
|
+
for (const k of Object.keys(env)) if (k.startsWith("CREW_CONNECT_KEY_")) delete env[k];
|
|
12549
12571
|
const res = spawnSync7(bin, [...pre, ...args], { cwd: crewHome, encoding: "utf8", env, input: o.input, maxBuffer: 64 * 1024 * 1024 });
|
|
12550
12572
|
return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: `${res.stderr ?? ""}${res.error ? String(res.error) : ""}` };
|
|
12551
12573
|
};
|
|
@@ -12851,6 +12873,7 @@ function realPoolMemberDeps(o) {
|
|
|
12851
12873
|
const lines2 = [];
|
|
12852
12874
|
const outcome = await runProvision({
|
|
12853
12875
|
key: lease.connectKey,
|
|
12876
|
+
keys: Object.fromEntries((lease.connectKeys ?? []).map((k) => [k.workspace, k.key])),
|
|
12854
12877
|
routes: lease.routes.map((r) => `${r.workspace}/${r.project}`),
|
|
12855
12878
|
shipName: lease.shipName,
|
|
12856
12879
|
shipKind,
|
|
@@ -19493,6 +19516,8 @@ async function call(api, method, url, body) {
|
|
|
19493
19516
|
}
|
|
19494
19517
|
function bodyFor(d, forPatch) {
|
|
19495
19518
|
const body = { label: d.label, url: d.url };
|
|
19519
|
+
if (d.controlUrl !== void 0) body.controlUrl = d.controlUrl;
|
|
19520
|
+
if (d.discoverable !== void 0) body.discoverable = d.discoverable;
|
|
19496
19521
|
if (d.mcpSecret) body.authorizationHeader = `Bearer ${d.mcpSecret}`;
|
|
19497
19522
|
else if (forPatch) body.authorizationHeader = null;
|
|
19498
19523
|
return body;
|
|
@@ -19541,15 +19566,17 @@ async function probeEndpointAccess(api, workspaceId) {
|
|
|
19541
19566
|
}
|
|
19542
19567
|
}
|
|
19543
19568
|
var REGISTER_MCP_ENDPOINTS_HINT = "give this key's role the REGISTER_MCP_ENDPOINTS capability, e.g. via the group that holds the Crew project role";
|
|
19544
|
-
function registrationKey(url, mcpSecret) {
|
|
19569
|
+
function registrationKey(url, mcpSecret, controlUrl) {
|
|
19545
19570
|
return createHash8("sha256").update(`${url}
|
|
19546
|
-
${mcpSecret ?? ""}
|
|
19571
|
+
${mcpSecret ?? ""}
|
|
19572
|
+
${controlUrl ?? ""}`).digest("hex").slice(0, 16);
|
|
19547
19573
|
}
|
|
19548
19574
|
async function reconcileEndpoint(workspaceId, d) {
|
|
19549
19575
|
const st = readPersistedTunnel(d.stateDir, workspaceId);
|
|
19550
19576
|
if (!st || st.status !== "connected" || !st.slug) return;
|
|
19551
19577
|
const url = d.urlFor(st.slug);
|
|
19552
|
-
const
|
|
19578
|
+
const controlUrl = st.controlSocket && d.controlUrlFor ? d.controlUrlFor(st.slug) : null;
|
|
19579
|
+
const key = registrationKey(url, st.mcpSecret, controlUrl);
|
|
19553
19580
|
if (st.endpointId && st.registeredKey === key) return;
|
|
19554
19581
|
const api = d.apiFor(workspaceId);
|
|
19555
19582
|
if (!api) {
|
|
@@ -19559,7 +19586,8 @@ async function reconcileEndpoint(workspaceId, d) {
|
|
|
19559
19586
|
const out = await registerEndpoint(api, workspaceId, st.endpointId, {
|
|
19560
19587
|
label: `Ship: ${d.shipName}`,
|
|
19561
19588
|
url,
|
|
19562
|
-
mcpSecret: st.mcpSecret
|
|
19589
|
+
mcpSecret: st.mcpSecret,
|
|
19590
|
+
controlUrl
|
|
19563
19591
|
});
|
|
19564
19592
|
if (out.kind === "ok") {
|
|
19565
19593
|
updatePersistedTunnel(d.stateDir, workspaceId, { endpointId: out.endpointId, registeredKey: key });
|
|
@@ -21232,7 +21260,8 @@ async function registerPeriscopeEndpoint(d, p) {
|
|
|
21232
21260
|
const out = await registerEndpoint(api, p.workspaceId, stored?.endpointId, {
|
|
21233
21261
|
label: `Periscope: ${d.shipName} ${p.ticket}`,
|
|
21234
21262
|
url,
|
|
21235
|
-
mcpSecret: secret
|
|
21263
|
+
mcpSecret: secret,
|
|
21264
|
+
discoverable: false
|
|
21236
21265
|
});
|
|
21237
21266
|
if (out.kind !== "ok") {
|
|
21238
21267
|
return { ok: false, message: out.kind === "forbidden" ? "registering the endpoint was refused (403): the route key lacks REGISTER_MCP_ENDPOINTS" : out.message };
|
|
@@ -22047,6 +22076,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
22047
22076
|
shipName: cfg.ship.name,
|
|
22048
22077
|
apiFor: endpointApiFor(routes),
|
|
22049
22078
|
urlFor: (slug) => publicUrlFor(slug, relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain),
|
|
22079
|
+
controlUrlFor: (slug) => `${relayBaseUrlFor(slug, relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain)}/control`,
|
|
22050
22080
|
warn: (m) => emit.warn(m, { step: "passengers" })
|
|
22051
22081
|
};
|
|
22052
22082
|
}, releaseRefused = function() {
|
|
@@ -27122,6 +27152,9 @@ ${foreign.join("\n")}
|
|
|
27122
27152
|
` ${r.route} url: ${slug ? publicUrlFor(slug, cfg.ship.relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain) : "not minted yet (first tunnel cycle creates it)"}
|
|
27123
27153
|
`
|
|
27124
27154
|
);
|
|
27155
|
+
const controlUrl = slug && readPersistedTunnel(cfg.ship.stateDir, ws)?.controlSocket ? `${relayBaseUrlFor(slug, cfg.ship.relayHost, cfg.ship.relayHttpPort, cfg.ship.relayPublicDomain)}/control` : void 0;
|
|
27156
|
+
process.stdout.write(` ${r.route} control url: ${controlUrl ?? "none (the tunnel carries passengers only)"}
|
|
27157
|
+
`);
|
|
27125
27158
|
const api = endpointApiFor([r])(ws);
|
|
27126
27159
|
if (!api) continue;
|
|
27127
27160
|
const probe = await probeEndpointAccess(api, ws);
|
|
@@ -27140,7 +27173,7 @@ ${foreign.join("\n")}
|
|
|
27140
27173
|
}
|
|
27141
27174
|
for (const e of listPeriscopeEndpoints(cfg.ship.stateDir)) {
|
|
27142
27175
|
const secret = readPeriscopeSecret(cfg.ship.stateDir, e.ticketRecordId) ? "live" : "ended, awaiting removal";
|
|
27143
|
-
process.stdout.write(`periscope mcp: ${e.ticket} ${e.route} ${e.url} (${secret})
|
|
27176
|
+
process.stdout.write(`periscope mcp: ${e.ticket} ${e.route} ${e.url} (${secret}, attached only)
|
|
27144
27177
|
`);
|
|
27145
27178
|
}
|
|
27146
27179
|
if (cfg.ship.relayHost && !flag("offline")) {
|
package/package.json
CHANGED