@tablation/crew 0.82.1 → 0.84.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 +103 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -34,6 +34,29 @@ function doctorBaseReport(r, base, health) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// src/periscopes.ts
|
|
38
|
+
import { lookup } from "dns/promises";
|
|
39
|
+
function periscopeRoutes(cfg) {
|
|
40
|
+
return cfg.routes.filter((r) => r.enabled && r.hostPeriscopes).map((r) => r.route);
|
|
41
|
+
}
|
|
42
|
+
function periscopeSummary(cfg) {
|
|
43
|
+
const routes = periscopeRoutes(cfg);
|
|
44
|
+
const limits = `max ${cfg.ship.maxPeriscopes}, idle ${cfg.ship.periscopeIdleMinutes} min`;
|
|
45
|
+
if (routes.length === 0) return `off (no enabled route sets hostPeriscopes: true; ${limits})`;
|
|
46
|
+
return `ON for ${routes.join(", ")} (host ${cfg.ship.previewHost ?? "NOT SET (ship.previewHost)"}, ${limits})`;
|
|
47
|
+
}
|
|
48
|
+
async function previewHostProblem(cfg, resolve6 = (h) => lookup(h)) {
|
|
49
|
+
if (periscopeRoutes(cfg).length === 0) return void 0;
|
|
50
|
+
const host = cfg.ship.previewHost;
|
|
51
|
+
if (!host) return "ship.previewHost is not set \u2014 a route hosts periscopes, so Passengers have no host to be sent to";
|
|
52
|
+
try {
|
|
53
|
+
await resolve6(host);
|
|
54
|
+
} catch {
|
|
55
|
+
return `ship.previewHost "${host}" does not resolve from this machine`;
|
|
56
|
+
}
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
// src/config.ts
|
|
38
61
|
import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
|
|
39
62
|
import { resolve, dirname as dirname3, isAbsolute, join as join9, basename as basename3 } from "path";
|
|
@@ -1448,6 +1471,7 @@ var HOOK_NAMES = [
|
|
|
1448
1471
|
"ports",
|
|
1449
1472
|
"isolate",
|
|
1450
1473
|
"handoff",
|
|
1474
|
+
"preview",
|
|
1451
1475
|
"ci_status",
|
|
1452
1476
|
"version",
|
|
1453
1477
|
"bump",
|
|
@@ -2033,6 +2057,8 @@ var ROLE_LABEL = {
|
|
|
2033
2057
|
};
|
|
2034
2058
|
var ConfigError = class extends Error {
|
|
2035
2059
|
};
|
|
2060
|
+
var DEFAULT_MAX_PERISCOPES = 3;
|
|
2061
|
+
var DEFAULT_PERISCOPE_IDLE_MINUTES = 120;
|
|
2036
2062
|
function parseCloneScheme(v) {
|
|
2037
2063
|
if (v === void 0 || v === null || v === "") return void 0;
|
|
2038
2064
|
if (v === "ssh" || v === "https") return v;
|
|
@@ -2064,7 +2090,10 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
2064
2090
|
"relayHttpPort",
|
|
2065
2091
|
"relayPublicDomain",
|
|
2066
2092
|
"passengerImage",
|
|
2067
|
-
"cloneScheme"
|
|
2093
|
+
"cloneScheme",
|
|
2094
|
+
"previewHost",
|
|
2095
|
+
"maxPeriscopes",
|
|
2096
|
+
"periscopeIdleMinutes"
|
|
2068
2097
|
]);
|
|
2069
2098
|
var ROUTE_KEYS = /* @__PURE__ */ new Set([
|
|
2070
2099
|
"route",
|
|
@@ -2087,6 +2116,7 @@ var ROUTE_KEYS = /* @__PURE__ */ new Set([
|
|
|
2087
2116
|
"resolved",
|
|
2088
2117
|
"promptSet",
|
|
2089
2118
|
"hostPassengers",
|
|
2119
|
+
"hostPeriscopes",
|
|
2090
2120
|
"toolchain"
|
|
2091
2121
|
]);
|
|
2092
2122
|
function parseWorktreeName(v, where, file) {
|
|
@@ -2316,6 +2346,7 @@ function parseResolved(raw, m, where) {
|
|
|
2316
2346
|
areaId: raw.areaId,
|
|
2317
2347
|
shipsModelId: raw.shipsModelId,
|
|
2318
2348
|
epicsModelId: raw.epicsModelId,
|
|
2349
|
+
periscopesModelId: raw.periscopesModelId,
|
|
2319
2350
|
locksModelId: raw.locksModelId,
|
|
2320
2351
|
recordLinkViewId: raw.recordLinkViewId,
|
|
2321
2352
|
reposModelId: raw.reposModelId,
|
|
@@ -2397,6 +2428,14 @@ Create the first of those:
|
|
|
2397
2428
|
if (!Number.isInteger(streamRetentionDays) || streamRetentionDays < 1) {
|
|
2398
2429
|
missing.add(`ship.streamRetentionDays must be a positive integer (got "${shipRaw.streamRetentionDays}")`);
|
|
2399
2430
|
}
|
|
2431
|
+
const maxPeriscopes = shipRaw.maxPeriscopes === void 0 ? DEFAULT_MAX_PERISCOPES : Number(shipRaw.maxPeriscopes);
|
|
2432
|
+
if (!Number.isInteger(maxPeriscopes) || maxPeriscopes < 1) {
|
|
2433
|
+
missing.add(`ship.maxPeriscopes must be a positive integer (got "${shipRaw.maxPeriscopes}")`);
|
|
2434
|
+
}
|
|
2435
|
+
const periscopeIdleMinutes = shipRaw.periscopeIdleMinutes === void 0 ? DEFAULT_PERISCOPE_IDLE_MINUTES : Number(shipRaw.periscopeIdleMinutes);
|
|
2436
|
+
if (!Number.isInteger(periscopeIdleMinutes) || periscopeIdleMinutes < 1) {
|
|
2437
|
+
missing.add(`ship.periscopeIdleMinutes must be a positive integer (got "${shipRaw.periscopeIdleMinutes}")`);
|
|
2438
|
+
}
|
|
2400
2439
|
const eventsRotateBytes = shipRaw.eventsRotateBytes === void 0 ? DEFAULT_EVENTS_ROTATE_BYTES : Number(shipRaw.eventsRotateBytes);
|
|
2401
2440
|
if (!Number.isInteger(eventsRotateBytes) || eventsRotateBytes < 1) {
|
|
2402
2441
|
missing.add(`ship.eventsRotateBytes must be a positive integer (got "${shipRaw.eventsRotateBytes}")`);
|
|
@@ -2466,7 +2505,10 @@ Create the first of those:
|
|
|
2466
2505
|
relayHttpPort: shipRaw.relayHttpPort !== void 0 ? Number(shipRaw.relayHttpPort) : 443,
|
|
2467
2506
|
relayPublicDomain: shipRaw.relayPublicDomain ? String(shipRaw.relayPublicDomain) : void 0,
|
|
2468
2507
|
passengerImage: shipRaw.passengerImage ? String(shipRaw.passengerImage) : void 0,
|
|
2469
|
-
cloneScheme: parseCloneScheme(shipRaw.cloneScheme)
|
|
2508
|
+
cloneScheme: parseCloneScheme(shipRaw.cloneScheme),
|
|
2509
|
+
previewHost: shipRaw.previewHost ? String(shipRaw.previewHost).trim() || void 0 : void 0,
|
|
2510
|
+
maxPeriscopes,
|
|
2511
|
+
periscopeIdleMinutes
|
|
2470
2512
|
},
|
|
2471
2513
|
routes,
|
|
2472
2514
|
crewHome,
|
|
@@ -2567,6 +2609,9 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2567
2609
|
missing.add(`${where}.toolchain "${toolchain}" is not defined in ship.toolchains (have: ${[...toolchainNames].join(", ")})`);
|
|
2568
2610
|
}
|
|
2569
2611
|
const route = missing.req(c?.route, `${where}.route`);
|
|
2612
|
+
if (c?.hostPeriscopes === true && c?.hostPassengers !== true) {
|
|
2613
|
+
missing.add(`${where} (route "${route}") sets hostPeriscopes: true without hostPassengers: true \u2014 a Passenger must be able to board before they can open a periscope`);
|
|
2614
|
+
}
|
|
2570
2615
|
const routeParts = route ? route.split("/") : [];
|
|
2571
2616
|
const routeWellFormed = routeParts.length === 2 && routeParts.every(Boolean);
|
|
2572
2617
|
if (route && !routeWellFormed) {
|
|
@@ -2641,7 +2686,8 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2641
2686
|
},
|
|
2642
2687
|
release: parseReleaseOverride(c?.release, where, file),
|
|
2643
2688
|
branch: parseBranchOverride(c?.branch, where, file),
|
|
2644
|
-
hostPassengers: c?.hostPassengers === true
|
|
2689
|
+
hostPassengers: c?.hostPassengers === true,
|
|
2690
|
+
hostPeriscopes: c?.hostPeriscopes === true
|
|
2645
2691
|
};
|
|
2646
2692
|
const resolvedFile = routeWellFormed ? readResolvedFile(stateDir, route) : void 0;
|
|
2647
2693
|
result.resolved = parseResolved(resolvedFile ?? c?.resolved, missing, where);
|
|
@@ -5114,6 +5160,7 @@ var HOOK_PURPOSE = [
|
|
|
5114
5160
|
["isolate", "point this worktree at state of its own; prints KEY=value overrides to export"],
|
|
5115
5161
|
["ports", "this worktree's port assignments; prints KEY=value overrides to export"],
|
|
5116
5162
|
["handoff", "leave the operator able to open what you built; put its output in your progress comment"],
|
|
5163
|
+
["preview", "start this worktree's dev server for a viewer, bound to CREW_PREVIEW_HOST, detached; its last stdout line is the URL"],
|
|
5117
5164
|
["test", "run the suite"],
|
|
5118
5165
|
["build", "build it"],
|
|
5119
5166
|
["ci_status", "after you push and before you hand off: exit 0 means CI is green for your branch; on failure fix and push again, and say in your progress comment if it is still pending"]
|
|
@@ -5558,6 +5605,19 @@ function validateContract(c) {
|
|
|
5558
5605
|
return problems;
|
|
5559
5606
|
}
|
|
5560
5607
|
var EVENT_LOG_MISSING = "the Issues table has no event log \u2014 crew writes its audit trail there (CREW-1574). In the app, open the Issues table's Data & lifecycle tab and turn on Event log, then rerun.";
|
|
5608
|
+
var PERISCOPE_COLUMNS = [
|
|
5609
|
+
"ticket_id",
|
|
5610
|
+
"ship_id",
|
|
5611
|
+
"url",
|
|
5612
|
+
"host",
|
|
5613
|
+
"worktree",
|
|
5614
|
+
"branch",
|
|
5615
|
+
"state",
|
|
5616
|
+
"isolated",
|
|
5617
|
+
"started_at",
|
|
5618
|
+
"last_activity_at",
|
|
5619
|
+
"expires_at"
|
|
5620
|
+
];
|
|
5561
5621
|
|
|
5562
5622
|
// src/agent-log.ts
|
|
5563
5623
|
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
@@ -6806,6 +6866,19 @@ var Tracker = class {
|
|
|
6806
6866
|
async hasPublicReplyColumn() {
|
|
6807
6867
|
return (await this.authorshipProbe()).commentPublicReply;
|
|
6808
6868
|
}
|
|
6869
|
+
/**
|
|
6870
|
+
* Whether this workspace has the optional Periscopes table (EPIC-077), and
|
|
6871
|
+
* which of the columns the crew writes (`PERISCOPE_COLUMNS`) it lacks.
|
|
6872
|
+
* `present: false` is a legitimate shape, never a fault. A table whose
|
|
6873
|
+
* metadata cannot be read reports every column missing, as the authorship
|
|
6874
|
+
* probe reads an unreadable table as "column absent".
|
|
6875
|
+
*/
|
|
6876
|
+
async periscopesStatus() {
|
|
6877
|
+
const modelId = this.route.resolved?.periscopesModelId;
|
|
6878
|
+
if (!modelId) return { present: false, missing: [] };
|
|
6879
|
+
const have = await this.columnNames(modelId);
|
|
6880
|
+
return { present: true, modelId, missing: PERISCOPE_COLUMNS.filter((c) => !have.has(c)) };
|
|
6881
|
+
}
|
|
6809
6882
|
/**
|
|
6810
6883
|
* The authorship columns this workspace is missing, as `table.column` names
|
|
6811
6884
|
* for `crew doctor` to report (CREW-1371). Empty on a fully updated install.
|
|
@@ -7733,7 +7806,7 @@ function skillMarkerNames(text) {
|
|
|
7733
7806
|
}
|
|
7734
7807
|
return names;
|
|
7735
7808
|
}
|
|
7736
|
-
function expandSkillMarkers(text,
|
|
7809
|
+
function expandSkillMarkers(text, lookup2, opts = {}) {
|
|
7737
7810
|
const maxDepth = opts.maxDepth ?? MAX_INCLUDE_DEPTH;
|
|
7738
7811
|
const maxBytes = opts.maxBytes ?? MAX_INCLUDED_BYTES;
|
|
7739
7812
|
const warnings = [];
|
|
@@ -7749,7 +7822,7 @@ function expandSkillMarkers(text, lookup, opts = {}) {
|
|
|
7749
7822
|
warnings.push(`skill include "${name}" left literal: nested deeper than ${maxDepth} levels`);
|
|
7750
7823
|
return literal;
|
|
7751
7824
|
}
|
|
7752
|
-
const body =
|
|
7825
|
+
const body = lookup2(name);
|
|
7753
7826
|
if (body === void 0) throw new SkillIncludeError(`no skill named "${name}" for the include ${literal}`);
|
|
7754
7827
|
const trimmed = body.replace(/^\n+/, "").replace(/\n+$/, "");
|
|
7755
7828
|
const bytes = Buffer.byteLength(trimmed, "utf8");
|
|
@@ -13719,6 +13792,7 @@ function forbiddenDetail(e, route) {
|
|
|
13719
13792
|
[r?.shipsModelId, "Ships"],
|
|
13720
13793
|
[r?.locksModelId, "Locks"],
|
|
13721
13794
|
[r?.epicsModelId, "Epics"],
|
|
13795
|
+
[r?.periscopesModelId, "Periscopes"],
|
|
13722
13796
|
[r?.reposModelId, "Repos"]
|
|
13723
13797
|
];
|
|
13724
13798
|
const table2 = modelId ? known.find(([id]) => id && id === modelId)?.[1] ?? `table ${modelId}` : void 0;
|
|
@@ -14563,6 +14637,7 @@ async function discover(o) {
|
|
|
14563
14637
|
out.reposModelId = find("Repos");
|
|
14564
14638
|
out.shipsModelId = find("Ships");
|
|
14565
14639
|
out.epicsModelId = find("Epics");
|
|
14640
|
+
out.periscopesModelId = find("Periscopes");
|
|
14566
14641
|
out.locksModelId = find("Locks");
|
|
14567
14642
|
try {
|
|
14568
14643
|
const views = await get(o, `/views?workspaceId=${ws.id}`);
|
|
@@ -14614,6 +14689,7 @@ async function discover(o) {
|
|
|
14614
14689
|
shipColumns = new Set((shipsModel.fields ?? []).map((f) => f.columnName));
|
|
14615
14690
|
} catch {
|
|
14616
14691
|
}
|
|
14692
|
+
const hostPeriscopes = shipColumns.has("host_periscopes") ? o.ship.hostPeriscopes : void 0;
|
|
14617
14693
|
let ownerId;
|
|
14618
14694
|
if (out.meId) {
|
|
14619
14695
|
if (shipColumns.has("owner_id")) ownerId = out.meId;
|
|
@@ -14656,6 +14732,7 @@ async function discover(o) {
|
|
|
14656
14732
|
}
|
|
14657
14733
|
if (writeMachineId) patchBody.machine_id = machineId;
|
|
14658
14734
|
if ((mine[0].host_passengers ?? false) !== hostPassengers) patchBody.host_passengers = hostPassengers;
|
|
14735
|
+
if (hostPeriscopes !== void 0 && (mine[0].host_periscopes ?? false) !== hostPeriscopes) patchBody.host_periscopes = hostPeriscopes;
|
|
14659
14736
|
if (sshPublicKey !== void 0 && (mine[0].ssh_public_key ?? "") !== sshPublicKey) {
|
|
14660
14737
|
patchBody.ssh_public_key = sshPublicKey;
|
|
14661
14738
|
}
|
|
@@ -14663,6 +14740,7 @@ async function discover(o) {
|
|
|
14663
14740
|
if (Object.keys(patchBody).length > 0) {
|
|
14664
14741
|
await patch(o, `/data-models/${out.shipsModelId}/records/${shipRowId}`, patchBody);
|
|
14665
14742
|
if ("host_passengers" in patchBody) provisioning.push(`synced host_passengers=${hostPassengers} onto Ships row "${o.ship.name}"`);
|
|
14743
|
+
if ("host_periscopes" in patchBody) provisioning.push(`synced host_periscopes=${hostPeriscopes} onto Ships row "${o.ship.name}"`);
|
|
14666
14744
|
if ("ssh_public_key" in patchBody) provisioning.push(`synced ssh_public_key onto Ships row "${o.ship.name}"`);
|
|
14667
14745
|
if ("owner_id" in patchBody) provisioning.push(`recorded owner on Ships row "${o.ship.name}"`);
|
|
14668
14746
|
}
|
|
@@ -14671,6 +14749,7 @@ async function discover(o) {
|
|
|
14671
14749
|
name: o.ship.name,
|
|
14672
14750
|
...o.ship.platform ? { platform: o.ship.platform } : {},
|
|
14673
14751
|
host_passengers: hostPassengers,
|
|
14752
|
+
...hostPeriscopes !== void 0 ? { host_periscopes: hostPeriscopes } : {},
|
|
14674
14753
|
...sshPublicKey !== void 0 ? { ssh_public_key: sshPublicKey } : {},
|
|
14675
14754
|
...ownerId !== void 0 ? { owner_id: ownerId } : {},
|
|
14676
14755
|
...writeMachineId ? { machine_id: machineId } : {}
|
|
@@ -20350,6 +20429,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20350
20429
|
platform: cfg.ship.platform,
|
|
20351
20430
|
machineId,
|
|
20352
20431
|
hostPassengers: firstRun?.answers.hostPassengers ?? existingRoute?.hostPassengers === true,
|
|
20432
|
+
hostPeriscopes: existingRoute?.hostPeriscopes === true,
|
|
20353
20433
|
sshPublicKey,
|
|
20354
20434
|
lastSyncedName: (r) => readSyncedName(resolvedPathFor(cfg.ship.stateDir, r))
|
|
20355
20435
|
};
|
|
@@ -20561,6 +20641,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20561
20641
|
areaId: found.areaId,
|
|
20562
20642
|
shipsModelId: found.shipsModelId,
|
|
20563
20643
|
epicsModelId: found.epicsModelId,
|
|
20644
|
+
periscopesModelId: found.periscopesModelId,
|
|
20564
20645
|
locksModelId: found.locksModelId,
|
|
20565
20646
|
recordLinkViewId: found.recordLinkViewId,
|
|
20566
20647
|
reposModelId: found.reposModelId,
|
|
@@ -21812,6 +21893,7 @@ ${owed.map((l) => ` ${l}
|
|
|
21812
21893
|
`ship: ${cfg.ship.name} (${cfg.ship.platform})
|
|
21813
21894
|
route: ${route.route} -> ${route.dir}
|
|
21814
21895
|
enabled: ${route.enabled}
|
|
21896
|
+
periscopes: ${periscopeSummary(cfg)}
|
|
21815
21897
|
crew: ${state.isPaused() ? "paused" : "active"}
|
|
21816
21898
|
release: ${state.releaseSkipReason() ? "paused" : "active"}
|
|
21817
21899
|
watermark: ${state.watermark()}
|
|
@@ -21929,6 +22011,8 @@ watermark: ${state.watermark()}
|
|
|
21929
22011
|
}
|
|
21930
22012
|
}
|
|
21931
22013
|
for (const message of roleCapWarnings(cfg.ship)) problems.push({ severity: "note", kind: "role_cap_above_ship_cap", message });
|
|
22014
|
+
const previewProblem2 = await previewHostProblem(cfg);
|
|
22015
|
+
if (previewProblem2) problems.push({ severity: "blocker", kind: "preview_host", message: previewProblem2 });
|
|
21932
22016
|
problems.push(...attentionProblems(openShipAttention(state), problems));
|
|
21933
22017
|
if (problems.some((p) => p.severity === "blocker")) process.exitCode = 1;
|
|
21934
22018
|
process.stdout.write(`${JSON.stringify({ offline: flag("offline"), pushAuth, tools: toolRows, problems, passed }, null, 2)}
|
|
@@ -21942,6 +22026,14 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
21942
22026
|
);
|
|
21943
22027
|
for (const w of roleCapWarnings(cfg.ship)) process.stdout.write(`role concurrency: WARN ${w}
|
|
21944
22028
|
`);
|
|
22029
|
+
process.stdout.write(`periscopes: ${periscopeSummary(cfg)}
|
|
22030
|
+
`);
|
|
22031
|
+
const previewProblem = await previewHostProblem(cfg);
|
|
22032
|
+
if (previewProblem) {
|
|
22033
|
+
process.stdout.write(` BLOCKER ${previewProblem}
|
|
22034
|
+
`);
|
|
22035
|
+
process.exitCode = 1;
|
|
22036
|
+
}
|
|
21945
22037
|
try {
|
|
21946
22038
|
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
21947
22039
|
const evSize = existsSync35(evFile) ? statSync10(evFile).size : 0;
|
|
@@ -22043,6 +22135,12 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
22043
22135
|
process.stdout.write(
|
|
22044
22136
|
await tracker.hasPublicReplyColumn() ? `public replies: Comments.${tracker.contract.comments.publicReply} present \u2014 public reporters are labelled in the digest
|
|
22045
22137
|
` : `public replies: Comments.${tracker.contract.comments.publicReply} absent \u2014 no public intake form, nothing to guard
|
|
22138
|
+
`
|
|
22139
|
+
);
|
|
22140
|
+
const periscopes = await tracker.periscopesStatus();
|
|
22141
|
+
process.stdout.write(
|
|
22142
|
+
!periscopes.present ? "periscopes: Periscopes table absent \u2014 this workspace cannot host periscopes\n" : periscopes.missing.length === 0 ? `periscopes: Periscopes table present (${periscopes.modelId})
|
|
22143
|
+
` : `periscopes: Periscopes table present (${periscopes.modelId}) but missing ${periscopes.missing.join(", ")} \u2014 update the Issues template
|
|
22046
22144
|
`
|
|
22047
22145
|
);
|
|
22048
22146
|
process.stdout.write(
|
package/package.json
CHANGED