Files
playbook/antigravity-awesome-skills/tools/lib/aas-v1/cli/main.js
T
2026-07-20 00:03:02 +00:00

747 lines
34 KiB
JavaScript

"use strict";
const fs = require("node:fs");
const path = require("node:path");
const packageMetadata = require("../../../../package.json");
const core = require("..");
const {
applyHostConfigPatch,
buildPatch,
cleanupBackups,
previewBackupCleanup,
} = require("../adapters");
const { scanJson } = require("../mcp/strict-json");
const { createSkillTargetAdapter, ADAPTER_VERSION } = require("../target-adapter");
const { fsyncDirectorySync } = require("../durability");
const { inspectLayout, resolveDestination } = require("../transaction/safety");
const { validateInstance } = require("../schema-validator");
const EXIT = Object.freeze({ success: 0, invalid: 2, blocked: 3, recovery: 4, execution: 5 });
const PACKAGE_ROOT = path.resolve(__dirname, "../../../..");
function cliError(code, category, details = {}) {
const error = new Error(code);
error.code = code;
error.category = category;
error.details = details;
return error;
}
function parseOptions(argv) {
const positional = [];
const options = {};
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith("--")) {
positional.push(token);
continue;
}
const key = token.slice(2);
if (["help", "override-managed-drift", "experimental-apply", "experimental-recovery", "preview-windows-output", "require-evidence"].includes(key)) {
if (Object.hasOwn(options, key)) throw cliError("AAS_CLI_OPTION_DUPLICATE", "invalidInput", { option: key });
options[key] = true;
continue;
}
const value = argv[index + 1];
if (!value || value.startsWith("--")) throw cliError("AAS_CLI_OPTION_VALUE_REQUIRED", "invalidInput", { option: key });
if (Object.hasOwn(options, key)) throw cliError("AAS_CLI_OPTION_DUPLICATE", "invalidInput", { option: key });
options[key] = value;
index += 1;
}
return { positional, options };
}
const COMMAND_OPTIONS = Object.freeze({
"catalog status": new Set(["cache-root", "version", "digest", "integrity", "help"]),
"catalog update": new Set(["cache-root", "version", "help"]),
"mcp configure": new Set(["host", "scope", "config", "cache-root", "version", "runtime-integrity", "runtime-closure-digest", "backup-dir", "retention", "approve", "help"]),
"mcp backups cleanup": new Set(["config", "backup-dir", "keep", "approve", "help"]),
"stack init": new Set(["goal", "catalog-digest", "cache-root", "host", "scope", "name", "out", "preview-windows-output", "help"]),
"stack create": new Set(["selection", "evidence", "artifact-dir", "require-evidence", "catalog-digest", "cache-root", "out", "preview-windows-output", "help"]),
"stack validate": new Set(["manifest", "help"]),
"stack plan": new Set(["manifest", "target", "target-root", "cache-root", "runtime-version", "runtime-integrity", "out", "override-managed-drift", "preview-windows-output", "help"]),
"stack apply": new Set(["plan", "target-root", "cache-root", "approve", "experimental-apply", "help"]),
"stack doctor": new Set(["plan", "target-root", "cache-root", "help"]),
"stack recover": new Set(["plan", "target-root", "cache-root", "id", "action", "approve", "experimental-recovery", "help"]),
});
function invocationKey(positional) {
if (positional[0] === "mcp" && positional[1] === "backups") return positional.slice(0, 3).join(" ");
return positional.slice(0, 2).join(" ");
}
function validateInvocation(positional, options) {
const key = invocationKey(positional);
const allowed = COMMAND_OPTIONS[key];
const expectedLength = key === "mcp backups cleanup" ? 3 : 2;
if (!allowed || positional.length !== expectedLength) {
throw cliError("AAS_CLI_COMMAND_UNKNOWN", "invalidInput", { command: positional.join(" ") });
}
const unknown = Object.keys(options).filter((option) => !allowed.has(option)).sort();
if (unknown.length) throw cliError("AAS_CLI_OPTION_UNKNOWN", "invalidInput", { options: unknown });
return key;
}
function requireOption(options, key) {
if (!options[key]) throw cliError("AAS_CLI_OPTION_REQUIRED", "invalidInput", { option: key });
return options[key];
}
function requireAbsoluteOption(options, key) {
const value = requireOption(options, key);
if (!path.isAbsolute(value) || value.includes("\0")) throw cliError("AAS_CLI_ABSOLUTE_PATH_REQUIRED", "invalidInput", { option: key });
return path.normalize(value);
}
function boundedInteger(value, field, minimum, maximum, fallback) {
if (value === undefined) return fallback;
if (!/^(0|[1-9]\d*)$/.test(value)) throw cliError("AAS_CLI_INTEGER_INVALID", "invalidInput", { option: field });
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) throw cliError("AAS_CLI_INTEGER_INVALID", "invalidInput", { option: field });
return parsed;
}
function readJsonFile(filePath, maximumBytes = 4 * 1024 * 1024) {
const absolute = path.resolve(filePath);
const stat = fs.lstatSync(absolute);
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > maximumBytes) {
throw cliError("AAS_CLI_JSON_FILE_UNSAFE", "invalidInput", {});
}
const text = fs.readFileSync(absolute, "utf8");
scanJson(text, 64);
return JSON.parse(text);
}
function writeNewJson(filePath, value, { previewWindowsOutput = false } = {}) {
const absolute = path.resolve(filePath);
const parent = path.dirname(absolute);
const stat = fs.lstatSync(parent);
if (!stat.isDirectory() || stat.isSymbolicLink()) throw cliError("AAS_CLI_OUTPUT_PARENT_UNSAFE", "filesystem", {});
if (previewWindowsOutput && process.platform !== "win32") {
throw cliError("AAS_CLI_PREVIEW_WINDOWS_OUTPUT_UNSUPPORTED", "invalidInput", {});
}
const temporary = path.join(parent, `.aas-write-${process.pid}-${Date.now()}`);
let descriptor;
let linked = false;
try {
descriptor = fs.openSync(temporary, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);
fs.writeFileSync(descriptor, `${core.canonicalJson(value)}\n`);
fs.fsyncSync(descriptor);
fs.closeSync(descriptor);
descriptor = undefined;
fs.linkSync(temporary, absolute);
linked = true;
fs.unlinkSync(temporary);
try {
fsyncDirectorySync(parent);
return { outputDurability: "directorySynced", certificationStatus: "certifiable" };
} catch (error) {
if (!previewWindowsOutput) throw error;
return { outputDurability: "fileSyncedDirectoryUnverified", certificationStatus: "notCertified" };
}
} catch (error) {
if (descriptor !== undefined) fs.closeSync(descriptor);
try { fs.unlinkSync(temporary); } catch {}
if (linked) try { fs.unlinkSync(absolute); } catch {}
if (error.code === "EEXIST") throw cliError("AAS_CLI_OUTPUT_EXISTS", "conflict", {});
throw error;
}
}
function writeNewStackArtifactDirectory(
directoryPath,
{ manifest, evidence },
{
previewWindowsOutput = false,
filesystem = fs,
syncDirectory = fsyncDirectorySync,
} = {},
) {
const absolute = path.resolve(directoryPath);
const parent = path.dirname(absolute);
const parentStat = filesystem.lstatSync(parent);
if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) {
throw cliError("AAS_CLI_OUTPUT_PARENT_UNSAFE", "filesystem", {});
}
if (previewWindowsOutput && process.platform !== "win32") {
throw cliError("AAS_CLI_PREVIEW_WINDOWS_OUTPUT_UNSUPPORTED", "invalidInput", {});
}
try {
filesystem.lstatSync(absolute);
throw cliError("AAS_CLI_OUTPUT_EXISTS", "conflict", {});
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
let stagingDirectory;
let published = false;
try {
stagingDirectory = filesystem.mkdtempSync(path.join(parent, ".aas-artifact-"));
filesystem.chmodSync(stagingDirectory, 0o700);
for (const [name, value] of [
["aas-stack.json", manifest],
["aas-selection-evidence.json", evidence],
]) {
const outputPath = path.join(stagingDirectory, name);
const descriptor = filesystem.openSync(
outputPath,
filesystem.constants.O_CREAT | filesystem.constants.O_EXCL | filesystem.constants.O_WRONLY,
0o600,
);
try {
filesystem.writeFileSync(descriptor, `${core.canonicalJson(value)}\n`);
filesystem.fsyncSync(descriptor);
} finally {
filesystem.closeSync(descriptor);
}
}
syncDirectory(stagingDirectory);
try {
filesystem.lstatSync(absolute);
throw cliError("AAS_CLI_OUTPUT_EXISTS", "conflict", {});
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
// The staging directory is a sibling of the destination, so this single
// rename publishes the manifest and evidence sidecar as one filesystem
// object. Node has no portable atomic primitive for two independent paths.
filesystem.renameSync(stagingDirectory, absolute);
published = true;
stagingDirectory = undefined;
try {
syncDirectory(parent);
return { outputDurability: "directorySynced", certificationStatus: "certifiable" };
} catch (error) {
if (!previewWindowsOutput) throw error;
return { outputDurability: "fileSyncedDirectoryUnverified", certificationStatus: "notCertified" };
}
} catch (error) {
if (stagingDirectory) {
try { filesystem.rmSync(stagingDirectory, { recursive: true, force: true }); } catch {}
}
if (published) {
try {
filesystem.rmSync(absolute, { recursive: true, force: true });
syncDirectory(parent);
} catch {}
}
if (["EEXIST", "ENOTEMPTY"].includes(error.code)) {
throw cliError("AAS_CLI_OUTPUT_EXISTS", "conflict", {});
}
throw error;
}
}
function windowsOutputDurabilityDetails(written, platform = process.platform) {
if (platform !== "win32") return {};
return {
...(written.certificationStatus === "notCertified" ? { releaseProfile: "preview" } : {}),
...written,
};
}
function targetKey(target) {
return `${target.host}:${target.scope}`;
}
function selectTarget(manifest, value) {
const target = manifest.targets.find((candidate) => targetKey(candidate) === value);
if (!target) throw cliError("AAS_CLI_TARGET_NOT_IN_MANIFEST", "invalidInput", { target: value });
return target;
}
function adapterFor(options, target, sourceRoot) {
return createSkillTargetAdapter({
targetRoot: path.resolve(requireOption(options, "target-root")),
sourceRoot,
host: target.host,
scope: target.scope,
});
}
async function catalogFor(options, expectedDigest) {
const requestedDigest = expectedDigest || options["catalog-digest"];
if (!requestedDigest) return core.loadBundledCatalog({ root: PACKAGE_ROOT });
const cacheRoot = options["cache-root"] === undefined ? undefined : requireAbsoluteOption(options, "cache-root");
const resolver = core.cache.createVerifiedCatalogResolver({ cacheRoot, bundledRoot: PACKAGE_ROOT });
const catalog = await resolver(requestedDigest);
if (!catalog) throw cliError("AAS_CACHE_CATALOG_NOT_VERIFIED", "integrity", { digest: requestedDigest });
return catalog;
}
async function verifiedRuntimeFor(options, expected, dependencies = {}) {
if (typeof dependencies.resolveVerifiedRuntime === "function") return dependencies.resolveVerifiedRuntime({ options, expected });
const cacheRoot = requireAbsoluteOption(options, "cache-root");
const version = expected?.version || requireOption(options, "runtime-version");
const integrity = expected?.integrity || requireOption(options, "runtime-integrity");
const status = await core.cache.runtimeStatus({
cacheRoot,
packageVersion: version,
integrity,
...(expected?.closureDigest ? { closureDigest: expected.closureDigest } : {}),
});
if (status.status !== "verified") throw cliError("AAS_RUNTIME_NOT_VERIFIED", "integrity", { status: status.status });
return { identity: status.runtimeIdentity, sourceRoot: path.join(status.targetPath, "package") };
}
function buildOperations({ manifest, target, adapter, allowManagedDrift }) {
const layout = inspectLayout(adapter, target);
const state = core.transaction.readManagedState(layout.stateFile);
const installedState = state
? { digest: state.stateDigest, entries: core.transaction.publicEntries(state.entries) }
: { digest: core.transaction.digestManagedEntries([]), entries: [] };
const managed = new Map((state ? state.entries : []).map((entry) => [entry.skillId, entry]));
const desired = new Set(manifest.skills.map((skill) => skill.id));
const operations = [];
const overrides = [];
const next = new Map(managed);
for (const skill of manifest.skills) {
const source = adapter.resolveSourceTree({ skillId: skill.id });
const sourceTreeDigest = core.transaction.treeDigest(source);
const destination = resolveDestination(layout, skill.id);
const current = managed.get(skill.id);
if (!current) {
if (fs.existsSync(destination)) throw cliError("AAS_TRANSACTION_UNMANAGED_COLLISION", "conflict", { skillId: skill.id });
operations.push({ kind: "install", skillId: skill.id, sourceTreeDigest, expectedTreeDigest: null, resultTreeDigest: sourceTreeDigest, backupRequired: false });
next.set(skill.id, { skillId: skill.id, treeDigest: sourceTreeDigest, catalogIntegrity: manifest.catalog.integrity });
continue;
}
if (!fs.existsSync(destination)) throw cliError("AAS_TRANSACTION_MANAGED_ENTRY_MISSING", "drift", { skillId: skill.id });
const actual = core.transaction.treeDigest(destination);
if (actual !== current.treeDigest) {
if (!allowManagedDrift) throw cliError("AAS_TRANSACTION_MANAGED_DRIFT", "drift", { skillId: skill.id });
overrides.push({ kind: "managedDrift", skillId: skill.id, reasonCodes: ["AAS_PLAN_MANAGED_DRIFT_APPROVED"] });
}
if (actual !== sourceTreeDigest) {
operations.push({ kind: "replaceManaged", skillId: skill.id, sourceTreeDigest, expectedTreeDigest: actual, resultTreeDigest: sourceTreeDigest, backupRequired: true });
next.set(skill.id, { skillId: skill.id, treeDigest: sourceTreeDigest, catalogIntegrity: manifest.catalog.integrity });
}
}
for (const [skillId, current] of managed) {
if (desired.has(skillId)) continue;
const destination = resolveDestination(layout, skillId);
if (!fs.existsSync(destination)) throw cliError("AAS_TRANSACTION_MANAGED_ENTRY_MISSING", "drift", { skillId });
const actual = core.transaction.treeDigest(destination);
if (actual !== current.treeDigest) {
if (!allowManagedDrift) throw cliError("AAS_TRANSACTION_MANAGED_DRIFT", "drift", { skillId });
overrides.push({ kind: "managedDrift", skillId, reasonCodes: ["AAS_PLAN_MANAGED_DRIFT_APPROVED"] });
}
operations.push({ kind: "removeManaged", skillId, sourceTreeDigest: null, expectedTreeDigest: actual, resultTreeDigest: null, backupRequired: true });
next.delete(skillId);
}
return { layout, state, installedState, operations, overrides, nextEntries: [...next.values()] };
}
async function stackInit(options) {
const catalog = await catalogFor(options);
const host = options.host || "codex";
const scope = options.scope || "project";
const manifest = {
schemaVersion: 2,
name: options.name || "aas-stack",
catalog: { package: catalog.package, version: catalog.version, integrity: catalog.digest },
targets: [{ host, scope }],
profile: { goals: [requireOption(options, "goal")], languages: [], frameworks: [], constraints: [] },
skills: [],
};
const validation = core.stack.validateManifest(manifest);
if (!validation.ok) throw cliError(validation.code, validation.category, validation.details);
const output = options.out || "aas-stack.json";
const written = writeNewJson(output, manifest, { previewWindowsOutput: options["preview-windows-output"] === true });
return {
ok: true,
status: "initialized",
path: path.resolve(output),
manifestDigest: validation.manifestDigest,
...windowsOutputDurabilityDetails(written),
};
}
async function stackCreate(options) {
const catalog = await catalogFor(options);
const composed = core.composeStack(catalog, readJsonFile(requireOption(options, "selection")));
const evidencePath = options.evidence;
const artifactDirectory = options["artifact-dir"];
const auditRequested = evidencePath !== undefined || artifactDirectory !== undefined || options["require-evidence"] === true;
if (auditRequested) {
if (!evidencePath) throw cliError("AAS_CLI_EVIDENCE_REQUIRED", "invalidInput", { option: "evidence" });
if (!artifactDirectory) throw cliError("AAS_CLI_OPTION_REQUIRED", "invalidInput", { option: "artifact-dir" });
if (options.out !== undefined) {
throw cliError("AAS_CLI_OPTION_CONFLICT", "invalidInput", { options: ["artifact-dir", "out"] });
}
const evidence = readJsonFile(evidencePath);
const validation = core.validateSelectionEvidence(evidence, { catalog, manifest: composed.manifest });
if (!validation.ok) throw cliError(validation.code, validation.category, validation.details);
const written = writeNewStackArtifactDirectory(artifactDirectory, {
manifest: composed.manifest,
evidence,
}, { previewWindowsOutput: options["preview-windows-output"] === true });
const absoluteArtifactDirectory = path.resolve(artifactDirectory);
return {
ok: true,
status: "created",
selectionSource: "agent",
artifactDirectory: absoluteArtifactDirectory,
path: path.join(absoluteArtifactDirectory, "aas-stack.json"),
evidencePath: path.join(absoluteArtifactDirectory, "aas-selection-evidence.json"),
selectedSkillIds: composed.manifest.skills.map((skill) => skill.id),
manifestDigest: composed.manifestDigest,
evidenceDigest: validation.evidenceDigest,
...windowsOutputDurabilityDetails(written),
};
}
const output = options.out || "aas-stack.json";
const written = writeNewJson(output, composed.manifest, { previewWindowsOutput: options["preview-windows-output"] === true });
return {
ok: true,
status: "created",
selectionSource: "agent",
path: path.resolve(output),
selectedSkillIds: composed.manifest.skills.map((skill) => skill.id),
manifestDigest: composed.manifestDigest,
...windowsOutputDurabilityDetails(written),
};
}
async function stackPlan(options, dependencies = {}) {
const manifest = readJsonFile(requireOption(options, "manifest"));
const validation = core.stack.validateManifest(manifest);
if (!validation.ok) throw cliError(validation.code, validation.category, validation.details);
const catalog = await catalogFor(options, manifest.catalog.integrity);
if (manifest.catalog.package !== catalog.package || manifest.catalog.version !== catalog.version || manifest.catalog.integrity !== catalog.digest) {
throw cliError("AAS_PLAN_CATALOG_MISMATCH", "integrity", {});
}
const targetBase = selectTarget(manifest, requireOption(options, "target"));
if (options["runtime-version"] !== undefined && options["runtime-version"] !== manifest.catalog.version) {
throw cliError("AAS_PLAN_RUNTIME_CATALOG_MISMATCH", "integrity", {});
}
const runtime = await verifiedRuntimeFor({ ...options, "runtime-version": manifest.catalog.version }, null, dependencies);
if (runtime.identity.package !== manifest.catalog.package || runtime.identity.version !== manifest.catalog.version) {
throw cliError("AAS_PLAN_RUNTIME_CATALOG_MISMATCH", "integrity", {});
}
const adapter = adapterFor(options, targetBase, runtime.sourceRoot);
const layout = inspectLayout(adapter, targetBase);
const target = { ...targetBase, adapterVersion: ADAPTER_VERSION, identityDigest: adapter.computeTargetIdentity(layout, targetBase) };
const observed = buildOperations({ manifest, target, adapter, allowManagedDrift: options["override-managed-drift"] === true });
for (const desired of manifest.skills) {
core.getSkill(catalog, desired.id);
}
const plan = core.stack.buildPlanEnvelope({
manifest,
handshake: {
protocolVersion: core.protocolVersion,
coreVersion: core.coreVersion,
catalogSchemaVersion: core.catalogSchemaVersion,
},
catalog: manifest.catalog,
runtime: runtime.identity,
target,
installedState: observed.installedState,
operations: observed.operations,
overrides: observed.overrides,
stateCommit: {
previousDigest: observed.installedState.digest,
nextDigest: core.transaction.digestManagedEntries(observed.nextEntries),
position: "final",
},
});
const written = writeNewJson(requireOption(options, "out"), plan, { previewWindowsOutput: options["preview-windows-output"] === true });
return {
ok: true,
status: "planned",
planDigest: plan.digest,
operationCount: plan.payload.operations.length,
out: path.resolve(options.out),
...windowsOutputDurabilityDetails(written),
};
}
function help() {
return {
ok: true,
status: "help",
commands: [
"catalog status [--cache-root <absolute> --version <semver> --digest <sha256> --integrity <npm-sri>]",
"catalog update --cache-root <absolute> --version <semver>",
"mcp configure --host codex|claude --scope user|project --config <absolute> --cache-root <absolute> [--version <semver>] [--runtime-integrity <npm-sri> --runtime-closure-digest <sha256>] [--backup-dir <absolute>] [--approve <digest>]",
"mcp backups cleanup --config <absolute> --backup-dir <absolute> --keep <count> [--approve <digest>]",
"stack init --goal <goal> [--catalog-digest <sha256> --cache-root <absolute>] [--preview-windows-output]",
"stack create --selection <json> --out <aas-stack.json> [--catalog-digest <sha256> --cache-root <absolute>]",
"stack create --selection <json> --evidence <json> --artifact-dir <new-dir> --require-evidence [--catalog-digest <sha256> --cache-root <absolute>]",
"stack validate --manifest <aas-stack.json>",
"stack plan --manifest <file> --target <host:scope> --target-root <dir> --cache-root <absolute> --runtime-integrity <npm-sri> --out <file> [--preview-windows-output]",
"stack apply --experimental-apply --plan <file> --target-root <dir> --cache-root <absolute> --approve <plan-digest> (EXPERIMENTAL; NOT CERTIFIED)",
"stack doctor --plan <file> --target-root <dir> --cache-root <absolute>",
"stack recover --experimental-recovery --plan <file> --target-root <dir> --cache-root <absolute> --id <id> --action rollback|cleanup [--approve <digest>] (EXPERIMENTAL; NOT CERTIFIED)",
],
};
}
function mcpServer({ host, cacheRoot, version, integrity }) {
const command = process.execPath;
const args = [core.cache.runtimeMcpPath({ cacheRoot, packageVersion: version, integrity }), "--cache-root", cacheRoot];
return host === "codex" ? { command, args, enabled: true } : { command, args, env: {} };
}
function configApprovalDigest({ host, scope, configPath, cacheRoot, release, patch }) {
const runtime = { package: release.package, version: release.version, integrity: release.integrity };
if (release.closureDigest) runtime.closureDigest = release.closureDigest;
return core.sha256(core.canonicalJson({
schemaVersion: 1,
action: "mcp.configure",
host,
scope,
configPathDigest: core.sha256(configPath),
cacheRootDigest: core.sha256(cacheRoot),
runtime,
config: { exists: patch.exists, currentDigest: patch.currentDigest, nextDigest: patch.nextDigest },
}));
}
function explicitCachedRuntime(options) {
const integrity = options["runtime-integrity"];
const closureDigest = options["runtime-closure-digest"];
if ((integrity === undefined) !== (closureDigest === undefined)) {
throw cliError("AAS_CLI_RUNTIME_IDENTITY_INCOMPLETE", "invalidInput", {
requiredOptions: ["runtime-integrity", "runtime-closure-digest"],
});
}
if (integrity === undefined) return null;
try { core.cache.parseNpmIntegrity(integrity); } catch {
throw cliError("AAS_CLI_RUNTIME_INTEGRITY_INVALID", "invalidInput", { option: "runtime-integrity" });
}
if (!/^sha256-[0-9a-f]{64}$/.test(closureDigest)) {
throw cliError("AAS_CLI_RUNTIME_CLOSURE_DIGEST_INVALID", "invalidInput", { option: "runtime-closure-digest" });
}
return { integrity, closureDigest };
}
async function verifiedCachedRuntime({ cacheRoot, version, integrity, closureDigest }) {
const status = await core.cache.runtimeStatus({
cacheRoot,
packageVersion: version,
integrity,
closureDigest,
});
if (status.status !== "verified") {
throw cliError("AAS_RUNTIME_NOT_VERIFIED", "integrity", { status: status.status });
}
return status;
}
async function mcpConfigure(options, dependencies = {}) {
const host = requireOption(options, "host");
const scope = requireOption(options, "scope");
if (!new Set(["codex", "claude"]).has(host)) throw cliError("AAS_ADAPTER_HOST_UNSUPPORTED", "invalidInput", { host });
if (!new Set(["user", "project"]).has(scope)) throw cliError("AAS_ADAPTER_SCOPE_INVALID", "invalidInput", { scope });
const configPath = requireAbsoluteOption(options, "config");
const cacheRoot = requireAbsoluteOption(options, "cache-root");
const backupDirectory = options["backup-dir"] === undefined ? undefined : requireAbsoluteOption(options, "backup-dir");
const retention = boundedInteger(options.retention, "retention", 1, 100, 5);
const version = options.version || packageMetadata.version;
const fetcher = dependencies.fetcher;
const cachedIdentity = explicitCachedRuntime(options);
const cached = cachedIdentity
? await verifiedCachedRuntime({ cacheRoot, version, ...cachedIdentity })
: null;
const release = cached
? cached.runtimeIdentity
: await core.cache.inspectRuntimeRelease({ version, ...(fetcher ? { fetcher } : {}) });
const patch = await buildPatch({ host, scope, configPath, server: mcpServer({ host, cacheRoot, version: release.version, integrity: release.integrity }) });
const approvalDigest = configApprovalDigest({ host, scope, configPath, cacheRoot, release, patch });
const preview = {
ok: true,
status: "approvalRequired",
action: "mcp.configure",
approvalDigest,
runtime: {
package: release.package,
version: release.version,
integrity: release.integrity,
...(release.closureDigest ? { closureDigest: release.closureDigest } : {}),
},
config: {
host,
scope,
exists: patch.exists,
status: patch.status,
currentDigest: patch.currentDigest,
nextDigest: patch.nextDigest,
redactedDiff: patch.redactedDiff,
},
remediation: { action: "mcp.configure", args: { approvalDigest } },
};
if (!options.approve) return preview;
if (options.approve !== approvalDigest) throw cliError("AAS_ADAPTER_APPROVAL_MISMATCH", "approval", {});
const installed = cachedIdentity
? await verifiedCachedRuntime({ cacheRoot, version: release.version, ...cachedIdentity })
: await core.cache.installRuntimeFromRegistry({
cacheRoot,
version: release.version,
expectedIntegrity: release.integrity,
...(fetcher ? { fetcher } : {}),
});
const applied = await applyHostConfigPatch({ patch, approved: true, backupDirectory, retention });
return {
ok: true,
status: applied.status === "alreadyConfigured" ? "alreadyConfigured" : "configured",
runtime: installed.runtimeIdentity,
runtimeCacheStatus: cachedIdentity ? "verified" : installed.status,
config: { host, scope, digest: applied.configDigest, backupCreated: Boolean(applied.backup) },
};
}
async function mcpBackupCleanup(options) {
const configPath = requireAbsoluteOption(options, "config");
const backupDirectory = requireAbsoluteOption(options, "backup-dir");
const keep = boundedInteger(requireOption(options, "keep"), "keep", 0, 100);
const preview = await previewBackupCleanup({ backupDirectory, configPath, keep });
if (!options.approve) {
return { ok: true, ...preview, action: "mcp.backups.cleanup", remediation: { action: "mcp.backups.cleanup", args: { approvalDigest: preview.approvalDigest } } };
}
if (options.approve !== preview.approvalDigest) throw cliError("AAS_ADAPTER_APPROVAL_MISMATCH", "approval", {});
const result = await cleanupBackups({ backupDirectory, configPath, keep, approvalDigest: options.approve });
return { ok: true, status: result.status, retained: result.retained, removedCount: result.removed.length };
}
async function execute(argv, dependencies = {}) {
const { positional, options } = parseOptions(argv);
if (positional.length === 0 || (positional.length === 1 && positional[0] === "help")) return help();
validateInvocation(positional, options);
if (options.help) return help();
const [root, command] = positional;
if (root === "catalog" && command === "status") {
const catalog = core.loadBundledCatalog();
const result = { ok: true, status: "complete", bundled: { package: catalog.package, version: catalog.version, digest: catalog.digest } };
if (options["cache-root"] || options.version || options.digest) {
result.cached = await core.cache.catalogStatus({
cacheRoot: requireAbsoluteOption(options, "cache-root"),
packageVersion: requireOption(options, "version"),
catalogDigest: requireOption(options, "digest"),
...(options.integrity ? { integrity: options.integrity } : {}),
});
}
return result;
}
if (root === "catalog" && command === "update") {
return core.cache.updateCatalogFromRegistry({
cacheRoot: requireAbsoluteOption(options, "cache-root"),
version: requireOption(options, "version"),
});
}
if (root === "mcp" && command === "configure") return mcpConfigure(options, dependencies);
if (root === "mcp" && command === "backups" && positional[2] === "cleanup") return mcpBackupCleanup(options);
if (root !== "stack") throw cliError("AAS_CLI_COMMAND_UNKNOWN", "invalidInput", { command: root });
if (command === "init") return stackInit(options);
if (command === "create") return stackCreate(options);
if (command === "validate") {
const validation = core.stack.validateManifest(readJsonFile(requireOption(options, "manifest")));
if (!validation.ok) throw cliError(validation.code, validation.category, validation.details);
return validation;
}
if (command === "plan") return stackPlan(options, dependencies);
if (["apply", "doctor", "recover"].includes(command)) {
const plan = readJsonFile(requireOption(options, "plan"));
core.stack.validatePlanEnvelope(plan);
const target = plan.payload?.target;
if (!target) throw cliError("AAS_TRANSACTION_PLAN_INVALID", "integrity", {});
if (target.adapterVersion !== ADAPTER_VERSION) throw cliError("AAS_PLAN_ADAPTER_VERSION_INCOMPATIBLE", "incompatibleVersion", {});
if (command === "apply" && options["experimental-apply"] !== true) {
throw cliError("AAS_STACK_APPLY_EXPERIMENTAL_DISABLED", "policy", {
releaseProfile: "preview",
certificationStatus: "notCertified",
requiredOption: "experimental-apply",
});
}
if (command === "recover" && options["experimental-recovery"] !== true) {
throw cliError("AAS_STACK_RECOVERY_EXPERIMENTAL_DISABLED", "policy", {
releaseProfile: "preview",
certificationStatus: "notCertified",
requiredOption: "experimental-recovery",
});
}
const runtime = await verifiedRuntimeFor(options, plan.payload.runtime, dependencies);
if (core.canonicalJson(runtime.identity) !== core.canonicalJson(plan.payload.runtime)) {
throw cliError("AAS_TRANSACTION_RUNTIME_IDENTITY_MISMATCH", "integrity", {});
}
const adapter = adapterFor(options, target, runtime.sourceRoot);
if (command === "apply") {
const result = core.transaction.applyPlan({ plan, adapter, approvalDigest: requireOption(options, "approve") });
return { ...result, releaseProfile: "preview", certificationStatus: "experimental" };
}
if (command === "doctor") return core.transaction.doctor({ target, adapter });
const action = requireOption(options, "action");
const recoveryPlan = core.transaction.buildRecoveryPlan({ plan, adapter, recoveryId: requireOption(options, "id"), action });
if (!options.approve) return {
ok: true,
status: "approvalRequired",
recoveryPlan,
releaseProfile: "preview",
certificationStatus: "experimental",
};
const result = core.transaction.recover({ recoveryPlan, plan, adapter, approvalDigest: options.approve });
return { ...result, releaseProfile: "preview", certificationStatus: "experimental" };
}
throw cliError("AAS_CLI_COMMAND_UNKNOWN", "invalidInput", { command });
}
function exitCodeFor(error) {
if (["invalidInput", "incompatibleVersion"].includes(error.category)) return EXIT.invalid;
if (["integrity", "drift", "policy", "approval"].includes(error.category)) return EXIT.blocked;
if (["conflict", "recovery"].includes(error.category)) return EXIT.recovery;
return EXIT.execution;
}
async function main(argv = process.argv.slice(2), io = {}) {
const stdout = io.stdout || process.stdout;
const stderr = io.stderr || process.stderr;
try {
const result = await execute(argv);
const envelope = {
schemaVersion: 1,
protocolVersion: core.protocolVersion,
coreVersion: core.coreVersion,
catalogSchemaVersion: core.catalogSchemaVersion,
reasonCodes: [],
unknown: [],
details: {},
...result,
};
validateInstance("result-envelope.schema.json", envelope, "AAS_CLI_RESULT_SCHEMA_INVALID", "internal");
stdout.write(`${core.canonicalJson(envelope)}\n`);
return EXIT.success;
} catch (error) {
const payload = {
schemaVersion: 1,
ok: false,
status: "error",
protocolVersion: core.protocolVersion,
coreVersion: core.coreVersion,
catalogSchemaVersion: core.catalogSchemaVersion,
code: error.code || "AAS_CLI_EXECUTION_FAILED",
category: error.category || "execution",
details: error.details || {},
};
validateInstance("result-envelope.schema.json", payload, "AAS_CLI_ERROR_SCHEMA_INVALID", "internal");
stderr.write(`${core.canonicalJson(payload)}\n`);
return exitCodeFor(payload);
}
}
module.exports = {
EXIT,
adapterFor,
buildOperations,
configApprovalDigest,
execute,
main,
mcpBackupCleanup,
mcpConfigure,
parseOptions,
readJsonFile,
stackPlan,
windowsOutputDurabilityDetails,
writeNewJson,
writeNewStackArtifactDirectory,
};