This repository has been archived on 2026-10-29. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
EveTui/tools/evetui/evetui-capture-contract.mjs
2026-07-10 16:40:04 +01:00

160 lines
5.7 KiB
JavaScript

import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
export function buildTuiCaptureRequest({
advertisement,
capabilityManifest,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = new Date().toISOString().replace(/[:.]/g, "-"),
} = {}) {
if (!advertisement || typeof advertisement !== "object") {
throw new Error("advertisement is required");
}
if (!capabilityManifest || typeof capabilityManifest !== "object") {
throw new Error("capabilityManifest is required");
}
const captureContract = capabilityManifest.lifecycle?.capture?.captureContract;
if (!captureContract) {
throw new Error("capability manifest is missing lifecycle.capture.captureContract");
}
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId, captureSurfaceId);
if (!captureSurface) {
if (captureSurfaceId) {
throw new Error(
`capture surface mismatch: expected ${captureSurfaceList(captureContract)} got ${advertisement.providerId || "missing"}:${captureSurfaceId}`,
);
}
throw new Error(
`capture provider mismatch: expected ${captureProviderList(captureContract)} got ${advertisement.providerId || "missing"}`,
);
}
const targetId = captureContract.targetId || captureContract.runtimeId;
const surface = (advertisement.surfaces || []).find(candidate => candidate.surfaceId === captureSurface.surfaceId);
if (!surface) {
throw new Error(`capture surface not advertised: ${captureSurface.surfaceId}`);
}
const loweringTargets = surface.worldInteraction?.loweringTargets || [];
if (!loweringTargets.includes(targetId)) {
throw new Error(`surface ${surface.surfaceId} does not advertise lowering target ${targetId}`);
}
const artifactPath = replaceStamp(captureContract.artifactPattern, stamp);
return {
schema: captureContract.requestSchema || "gamecult.eve.runtime_capture_request.v1",
runtimeId: capabilityManifest.runtimeId,
ownerRepo: captureContract.ownerRepo,
providerId: advertisement.providerId,
surfaceId: surface.surfaceId,
surfaceKind: surface.surfaceKind || "",
targetId,
projectionKind: surface.worldInteraction?.projectionKind || "",
commandBoundary: surface.worldInteraction?.commandBoundary || "",
receiptSchema: surface.worldInteraction?.receiptSchema || "",
captureKind: captureContract.captureKind,
artifactKind: captureContract.artifactKind,
artifactPath,
conformanceAttachment: captureContract.conformanceAttachment,
authority: captureContract.authority,
sourceAdvertisementPath: advertisementPath,
sourceCapabilityManifestPath: capabilityManifestPath,
issuedAtUtc: stamp,
};
}
export function loadJsonFile(filePath) {
return JSON.parse(readFileSync(filePath, "utf8"));
}
export function writeCaptureRequest(request, outputPath) {
mkdirSync(path.dirname(outputPath), { recursive: true });
writeFileSync(outputPath, `${JSON.stringify(request, null, 2)}\n`, "utf8");
}
function replaceStamp(pattern, stamp) {
if (!pattern || !pattern.includes("{stamp}")) {
throw new Error(`capture artifact pattern must include {stamp}: ${pattern || "missing"}`);
}
return pattern.replace("{stamp}", stamp);
}
function selectCaptureSurface(captureContract, providerId, surfaceId = "") {
const claims = captureSurfaceClaims(captureContract).filter(claim => claim.providerId === providerId);
if (surfaceId) return claims.find(claim => claim.surfaceId === surfaceId) || null;
return claims[0] || null;
}
function captureProviderList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => claim.providerId).filter(Boolean).join(", ") || "missing";
}
function captureSurfaceList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => `${claim.providerId}:${claim.surfaceId}`).join(", ") || "missing";
}
function captureSurfaceClaims(captureContract) {
return [
{
providerId: captureContract.requiredProvider || "",
surfaceId: captureContract.requiredSurface || "",
},
...(Array.isArray(captureContract.additionalProviderSurfaces) ? captureContract.additionalProviderSurfaces : []),
]
.filter(claim => claim && typeof claim === "object")
.map(claim => ({
providerId: String(claim.providerId || ""),
surfaceId: String(claim.surfaceId || ""),
}))
.filter(claim => claim.providerId && claim.surfaceId);
}
function parseArgs(argv) {
const args = {};
for (let index = 0; index < argv.length; index += 1) {
const value = argv[index];
if (!value.startsWith("--")) continue;
const key = value.slice(2);
const next = argv[index + 1];
if (!next || next.startsWith("--")) {
args[key] = true;
} else {
args[key] = next;
index += 1;
}
}
return args;
}
function runCli() {
const args = parseArgs(process.argv.slice(2));
for (const required of ["advertisement", "capability", "output"]) {
if (!args[required]) {
throw new Error(`missing required --${required}`);
}
}
const stamp = args.stamp || new Date().toISOString().replace(/[:.]/g, "-");
const request = buildTuiCaptureRequest({
advertisement: loadJsonFile(args.advertisement),
capabilityManifest: loadJsonFile(args.capability),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
});
writeCaptureRequest(request, args.output);
process.stdout.write(`${args.output}\n`);
}
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
const modulePath = fileURLToPath(import.meta.url);
if (invokedPath === modulePath) {
runCli();
}