#!/usr/bin/env node

import { createHash } from "node:crypto";
import { lstatSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
import { basename, join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { gunzipSync, inflateRawSync } from "node:zlib";
import {
  downloadActionsArtifactArchive,
  describeActionsArtifactFiles,
  inspectActionsArtifactZip,
  inspectActionsArtifactZipWithPolicy,
  readBoundedRegularFile,
  readPublicationArtifactArchive,
  sha256Digest,
  validateActionsArtifactBinding,
  validateActionsArtifactProducerJob,
} from "./lib/actions-artifact-archive.mjs";
import { resolveNpmPublishPlan } from "./lib/npm-publish-plan.mjs";

export {
  downloadActionsArtifactArchive,
  describeActionsArtifactFiles,
  inspectActionsArtifactZip,
  inspectActionsArtifactZipWithPolicy,
  readBoundedRegularFile,
  readPublicationArtifactArchive,
  validateActionsArtifactBinding,
  validateActionsArtifactProducerJob,
};

const MANIFEST_FILENAME = "plugin-publication-manifest.json";
const MANIFEST_SCHEMA = "openclaw.plugin-publication-artifact/v1";
const TAR_BLOCK_BYTES = 512;
const TAR_END_MARKER_BYTES = TAR_BLOCK_BYTES * 2;
const TAR_USTAR_MAGIC = Buffer.from("ustar\0", "ascii");
const TAR_USTAR_VERSION = Buffer.from("00", "ascii");
const MAX_ARCHIVE_BYTES = 256 * 1024 * 1024;
const MAX_EXPANDED_BYTES = 512 * 1024 * 1024;
const MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
const MAX_PLUGIN_MANIFEST_BYTES = 2 * 1024 * 1024;
// Bundled SDKs can exceed 10k files; byte, path, and expansion caps remain authoritative.
const MAX_TAR_ENTRIES = 20_000;
const MAX_TAR_PATH_BYTES = 4 * 1024 * 1024;
const MAX_TAR_TOTAL_FILE_BYTES = 512 * 1024 * 1024;
export const CLAWHUB_PUBLICATION_TAR_LIMITS = Object.freeze({
  maxArchiveBytes: 120 * 1024 * 1024,
  maxEntries: 10_000,
  maxEntryBytes: 50 * 1024 * 1024,
  maxExpandedBytes: 64 * 1024 * 1024,
  maxPathBytes: 4 * 1024 * 1024,
  maxTotalFileBytes: 50 * 1024 * 1024,
});
const SHA_RE = /^[0-9a-f]{40}$/u;
const SHA256_RE = /^[0-9a-f]{64}$/u;
const ARTIFACT_DIGEST_RE = /^sha256:[0-9a-f]{64}$/u;
const ARTIFACT_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/u;
const PACKAGE_NAME_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
const VERSION_RE =
  /^[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(?:-(?:alpha|beta)\.[1-9][0-9]*|-[1-9][0-9]*)?$/u;
const NPM_ROUTE_POLICIES = new Map([
  ["npm-oidc", { authMode: "trusted-publisher", capability: "trusted-publisher" }],
  ["npm-token-bootstrap", { authMode: "token-bootstrap", capability: "first-publication" }],
  [
    "npm-token-placeholder-recovery",
    { authMode: "token-bootstrap", capability: "placeholder-recovery" },
  ],
  ["npm-mirror", { authMode: "release-token", capability: "dist-tag-mirror" }],
  ["npm-tag-repair", { authMode: "release-token", capability: "dist-tag-repair" }],
  ["npm-readback", { authMode: "none", capability: "registry-readback" }],
]);
const ROUTES = new Set([
  ...NPM_ROUTE_POLICIES.keys(),
  "clawhub-token-release",
  "clawhub-token-bootstrap",
  "clawhub-readback",
]);
const NPM_TAGS = new Set(["latest", "alpha", "beta", "extended-stable"]);
const CLAWHUB_TAGS = new Set(["latest", "alpha", "beta"]);
const META_PACKAGE = "@openclaw/meta-provider";
const META_PACKAGE_DIR = "extensions/meta";

function sha256(bytes) {
  return createHash("sha256").update(bytes).digest("hex");
}

function npmIntegrity(bytes) {
  return `sha512-${createHash("sha512").update(bytes).digest("base64")}`;
}

function npmShasum(bytes) {
  return createHash("sha1").update(bytes).digest("hex");
}

function compareCodeUnits(left, right) {
  return left < right ? -1 : left > right ? 1 : 0;
}

function assertString(value, label) {
  if (typeof value !== "string" || value.trim() !== value || value.length === 0) {
    throw new Error(`${label} must be a non-empty trimmed string.`);
  }
  return value;
}

function assertPositiveInteger(value, label) {
  if (!Number.isSafeInteger(value) || value <= 0) {
    throw new Error(`${label} must be a safe positive integer.`);
  }
  return value;
}

function assertBooleanString(value, label) {
  if (value === "true") {
    return true;
  }
  if (value === "false") {
    return false;
  }
  throw new Error(`${label} must be true or false.`);
}

function hasControlCharacters(value) {
  for (const character of value) {
    const codePoint = character.codePointAt(0);
    if (codePoint <= 0x1f || codePoint === 0x7f) {
      return true;
    }
  }
  return false;
}

function normalizeManualOverrideReason(value) {
  if (value === undefined || value === null || value === "") {
    return null;
  }
  const reason = assertString(value, "manual override reason");
  if (reason.length > 500 || hasControlCharacters(reason)) {
    throw new Error(
      "Manual override reason must be at most 500 characters and contain no control characters.",
    );
  }
  return reason;
}

function assertSafePackageDir(value) {
  const packageDir = assertString(value, "package dir");
  if (!/^extensions\/[a-z0-9][a-z0-9._-]*$/u.test(packageDir) || packageDir.includes("\\")) {
    throw new Error(`Unsafe plugin package dir: ${packageDir}`);
  }
  return packageDir;
}

function assertSafeArtifactName(value) {
  const artifactName = assertString(value, "artifact name");
  if (!ARTIFACT_NAME_RE.test(artifactName)) {
    throw new Error(`Unsafe plugin publication artifact name: ${artifactName}`);
  }
  return artifactName;
}

function assertSafeArchivePath(value, label) {
  const raw = assertString(value, label);
  if (
    raw.startsWith("/") ||
    raw.includes("\\") ||
    raw.includes("\0") ||
    raw.normalize("NFC") !== raw ||
    hasControlCharacters(raw)
  ) {
    throw new Error(`Unsafe ${label}: ${JSON.stringify(raw)}`);
  }
  const withoutTrailingSlash = raw.endsWith("/") ? raw.slice(0, -1) : raw;
  const parts = withoutTrailingSlash.split("/");
  if (
    withoutTrailingSlash.length === 0 ||
    parts.some((part) => part.length === 0 || part === "." || part === "..")
  ) {
    throw new Error(`Unsafe ${label}: ${JSON.stringify(raw)}`);
  }
  return withoutTrailingSlash;
}

function normalizePublicationReason(value) {
  const reason = assertString(value, "publication reason");
  if (reason.length > 500 || hasControlCharacters(reason)) {
    throw new Error(
      "Publication reason must be at most 500 characters and contain no control characters.",
    );
  }
  return reason;
}

function normalizePublisherPolicy(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error("Publisher policy must be an object.");
  }
  const keys = Object.keys(value).toSorted();
  if (JSON.stringify(keys) !== JSON.stringify(["policyId", "schema", "sha256"])) {
    throw new Error("Publisher policy must contain exactly schema, policyId, and sha256.");
  }
  const schema = assertString(value.schema, "publisher policy schema");
  const policyId = assertString(value.policyId, "publisher policy id");
  const policySha256 = assertString(value.sha256, "publisher policy SHA-256");
  if (
    schema.length > 200 ||
    policyId.length > 200 ||
    hasControlCharacters(schema) ||
    hasControlCharacters(policyId) ||
    !SHA256_RE.test(policySha256)
  ) {
    throw new Error("Publisher policy identity is invalid.");
  }
  return { policyId, schema, sha256: policySha256 };
}

function boundedTarLimit(value, fallback, label) {
  if (value === undefined) {
    return fallback;
  }
  if (!Number.isSafeInteger(value) || value <= 0 || value > fallback) {
    throw new Error(`${label} must be a positive safe integer no larger than ${fallback}.`);
  }
  return value;
}

function normalizeTarInspectionOptions(options = {}) {
  const maxArchiveBytes = boundedTarLimit(
    options.maxArchiveBytes,
    MAX_ARCHIVE_BYTES,
    "Plugin tarball byte limit",
  );
  const maxExpandedBytes = boundedTarLimit(
    options.maxExpandedBytes,
    MAX_EXPANDED_BYTES,
    "Plugin tarball expanded-byte limit",
  );
  const maxEntryBytes = boundedTarLimit(
    options.maxEntryBytes,
    maxExpandedBytes,
    "Plugin tarball per-entry byte limit",
  );
  const maxTotalFileBytes = boundedTarLimit(
    options.maxTotalFileBytes,
    Math.min(MAX_TAR_TOTAL_FILE_BYTES, maxExpandedBytes),
    "Plugin tarball total-file byte limit",
  );
  const maxEntries = boundedTarLimit(
    options.maxEntries,
    MAX_TAR_ENTRIES,
    "Plugin tarball entry-count limit",
  );
  const maxPathBytes = boundedTarLimit(
    options.maxPathBytes,
    MAX_TAR_PATH_BYTES,
    "Plugin tarball path-byte limit",
  );
  return {
    maxArchiveBytes,
    maxEntries,
    maxEntryBytes,
    maxExpandedBytes,
    maxPathBytes,
    maxTotalFileBytes,
  };
}

function decodeTarString(bytes, label = "tar string field") {
  const nul = bytes.indexOf(0);
  if (nul !== -1 && bytes.subarray(nul + 1).some((byte) => byte !== 0)) {
    throw new Error(`${label} has non-zero bytes after its NUL terminator.`);
  }
  const value = bytes.subarray(0, nul === -1 ? bytes.length : nul);
  try {
    return new TextDecoder("utf-8", { fatal: true }).decode(value);
  } catch {
    throw new Error(`${label} is not valid UTF-8.`);
  }
}

function decodeConsumerTarPathField(bytes, label) {
  const raw = decodeTarString(bytes, label);
  const consumerValue = raw.trim();
  if (raw !== consumerValue) {
    throw new Error(`${label} changes under the pinned ClawHub path normalization.`);
  }
  return consumerValue;
}

function normalizeConsumerTarPath(value, options = {}) {
  const rawPath = options.directory === true && value.endsWith("/") ? value.slice(0, -1) : value;
  const normalized = rawPath.replaceAll("\\", "/").replace(/^\.\/+/u, "");
  const segments = normalized.split("/").filter(Boolean);
  const consumerPath = segments.join("/");
  if (
    consumerPath !== rawPath ||
    segments.length === 0 ||
    segments.some((segment) => segment === "." || segment === "..")
  ) {
    throw new Error(
      `Tar entry path changes under the pinned ClawHub normalization: ${JSON.stringify(value)}.`,
    );
  }
  return assertSafeArchivePath(consumerPath, "tar entry path");
}

function parseCanonicalTarNumber(bytes, label, options = {}) {
  const { allowEmpty = false } = options;
  if ((bytes[0] & 0x80) !== 0) {
    throw new Error(`${label} must not use base-256 encoding.`);
  }
  if (bytes.every((byte) => byte === 0)) {
    if (allowEmpty) {
      return 0;
    }
    throw new Error(`${label} must not be empty.`);
  }
  const raw = bytes.toString("ascii");
  const canonicalDigits = new RegExp(`^[0-7]{${bytes.length - 2}}$`, "u");
  const canonical = canonicalDigits.test(raw.slice(0, -2)) && raw.endsWith(" \0");
  if (!canonical) {
    throw new Error(`${label} is not canonically encoded.`);
  }
  const octal = raw.slice(0, -2);
  const value = Number.parseInt(octal, 8);
  if (!Number.isSafeInteger(value) || value < 0) {
    throw new Error(`Invalid ${label}: ${JSON.stringify(octal)}`);
  }
  return value;
}

function verifyTarChecksum(header) {
  const expected = parseCanonicalTarNumber(header.subarray(148, 156), "tar checksum");
  let actual = 0;
  for (let index = 0; index < header.length; index += 1) {
    actual += index >= 148 && index < 156 ? 0x20 : header[index];
  }
  if (actual !== expected) {
    throw new Error(`Tar header checksum mismatch: expected ${expected}, got ${actual}.`);
  }
}

function verifyCanonicalTarHeader(header) {
  if (
    !header.subarray(257, 263).equals(TAR_USTAR_MAGIC) ||
    !header.subarray(263, 265).equals(TAR_USTAR_VERSION)
  ) {
    throw new Error("Plugin tarball entries must use canonical POSIX USTAR headers.");
  }
  decodeTarString(header.subarray(0, 100), "tar entry name");
  parseCanonicalTarNumber(header.subarray(100, 108), "tar entry mode");
  parseCanonicalTarNumber(header.subarray(108, 116), "tar entry uid", { allowEmpty: true });
  parseCanonicalTarNumber(header.subarray(116, 124), "tar entry gid", { allowEmpty: true });
  parseCanonicalTarNumber(header.subarray(124, 136), "tar entry size");
  parseCanonicalTarNumber(header.subarray(136, 148), "tar entry mtime");
  decodeTarString(header.subarray(157, 257), "tar entry link path");
  decodeTarString(header.subarray(265, 297), "tar entry user name");
  decodeTarString(header.subarray(297, 329), "tar entry group name");
  parseCanonicalTarNumber(header.subarray(329, 337), "tar entry device major");
  parseCanonicalTarNumber(header.subarray(337, 345), "tar entry device minor");
  if (header[475] === 0) {
    decodeTarString(header.subarray(345, 475), "tar entry prefix");
    parseCanonicalTarNumber(header.subarray(476, 488), "tar entry access time", {
      allowEmpty: true,
    });
    parseCanonicalTarNumber(header.subarray(488, 500), "tar entry change time", {
      allowEmpty: true,
    });
  } else {
    decodeTarString(header.subarray(345, 500), "tar entry prefix");
  }
}

function isZeroTarBlock(bytes) {
  return bytes.length === TAR_BLOCK_BYTES && bytes.every((byte) => byte === 0);
}

function parsePackedJson(bytes, label) {
  let value;
  try {
    value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
  } catch (error) {
    throw new Error(
      `${label} is invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
      { cause: error },
    );
  }
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new Error(`${label} must be an object.`);
  }
  return value;
}

function firstGzipMemberEnd(bytes, maxOutputLength) {
  if (
    bytes.length < 18 ||
    bytes[0] !== 0x1f ||
    bytes[1] !== 0x8b ||
    bytes[2] !== 0x08 ||
    (bytes[3] & 0xe0) !== 0
  ) {
    throw new Error("Invalid gzip header.");
  }

  const flags = bytes[3];
  let offset = 10;
  if ((flags & 0x04) !== 0) {
    if (offset + 2 > bytes.length) {
      throw new Error("Truncated gzip extra-field length.");
    }
    const extraLength = bytes.readUInt16LE(offset);
    offset += 2 + extraLength;
    if (offset > bytes.length) {
      throw new Error("Truncated gzip extra field.");
    }
  }
  for (const flag of [0x08, 0x10]) {
    if ((flags & flag) === 0) {
      continue;
    }
    const terminator = bytes.indexOf(0, offset);
    if (terminator === -1) {
      throw new Error("Unterminated gzip header string.");
    }
    offset = terminator + 1;
  }
  if ((flags & 0x02) !== 0) {
    offset += 2;
  }
  if (offset + 8 > bytes.length) {
    throw new Error("Truncated gzip member.");
  }

  const expanded = inflateRawSync(bytes.subarray(offset), {
    info: true,
    maxOutputLength,
  });
  return offset + expanded.engine.bytesWritten + 8;
}

export function inspectPackageTarballBytes(inputBytes, options = {}) {
  if (!(inputBytes instanceof Uint8Array)) {
    throw new Error("Plugin tarball bytes must be a Uint8Array.");
  }
  const tarballBytes = Buffer.from(inputBytes.buffer, inputBytes.byteOffset, inputBytes.byteLength);
  const limits = normalizeTarInspectionOptions(options);
  if (tarballBytes.length === 0 || tarballBytes.length > limits.maxArchiveBytes) {
    throw new Error(`Plugin tarball size is outside the allowed range: ${tarballBytes.length}.`);
  }
  let memberEnd;
  try {
    memberEnd = firstGzipMemberEnd(tarballBytes, limits.maxExpandedBytes);
  } catch (error) {
    throw new Error(
      `Plugin tarball is not canonical gzip or expands beyond ${limits.maxExpandedBytes} bytes.`,
      { cause: error },
    );
  }
  if (memberEnd !== tarballBytes.length) {
    throw new Error("Plugin tarball must contain exactly one gzip member.");
  }
  let tarBytes;
  try {
    const expanded = gunzipSync(tarballBytes, {
      info: true,
      maxOutputLength: limits.maxExpandedBytes,
    });
    if (expanded.engine.bytesWritten !== tarballBytes.length) {
      throw new Error("gzip stream does not consume the full plugin tarball");
    }
    tarBytes = expanded.buffer;
  } catch (error) {
    throw new Error(
      `Plugin tarball is not canonical gzip or expands beyond ${limits.maxExpandedBytes} bytes.`,
      { cause: error },
    );
  }

  const inventory = [];
  const seenPaths = new Set();
  const seenAliases = new Set();
  let packageManifestBytes;
  let pluginManifestBytes;
  let offset = 0;
  let sawEndMarker = false;
  let entryCount = 0;
  let totalFileBytes = 0;
  let totalPathBytes = 0;

  while (offset + TAR_BLOCK_BYTES <= tarBytes.length) {
    const header = tarBytes.subarray(offset, offset + TAR_BLOCK_BYTES);
    if (isZeroTarBlock(header)) {
      const secondEndBlock = tarBytes.subarray(
        offset + TAR_BLOCK_BYTES,
        offset + TAR_END_MARKER_BYTES,
      );
      if (
        !isZeroTarBlock(secondEndBlock) ||
        !tarBytes.subarray(offset + TAR_END_MARKER_BYTES).every((byte) => byte === 0)
      ) {
        throw new Error(
          "Plugin tarball must end with two zero blocks and contain no trailing entries.",
        );
      }
      sawEndMarker = true;
      break;
    }
    entryCount += 1;
    if (entryCount > limits.maxEntries) {
      throw new Error(`Plugin tarball exceeds the ${limits.maxEntries} entry limit.`);
    }
    verifyTarChecksum(header);
    verifyCanonicalTarHeader(header);
    const headerName = decodeConsumerTarPathField(header.subarray(0, 100), "tar entry name");
    const headerPrefix =
      header[475] === 0
        ? decodeConsumerTarPathField(header.subarray(345, 475), "tar entry prefix")
        : decodeConsumerTarPathField(header.subarray(345, 500), "tar entry prefix");
    const headerPath = headerPrefix ? `${headerPrefix}/${headerName}` : headerName;
    const headerSize = parseCanonicalTarNumber(header.subarray(124, 136), "tar entry size");
    const typeFlag = String.fromCharCode(header[156] || 0x30);
    const linkPath = decodeTarString(header.subarray(157, 257), "tar entry link path");
    if (typeFlag === "x" || typeFlag === "g" || typeFlag === "L" || typeFlag === "K") {
      throw new Error("PAX and GNU tar metadata are not supported for plugin artifacts.");
    }
    if (typeFlag !== "0" && typeFlag !== "5") {
      const suffix = linkPath ? ` -> ${linkPath}` : "";
      throw new Error(
        `Unsupported plugin tar entry type ${JSON.stringify(typeFlag)}: ${headerPath}${suffix}`,
      );
    }
    if (typeFlag === "5" && headerSize !== 0) {
      throw new Error(`Directory tar entry ${JSON.stringify(headerPath)} must have size zero.`);
    }
    if (linkPath) {
      throw new Error(
        `Plugin tar entries must not carry link targets: ${headerPath} -> ${linkPath}`,
      );
    }
    if (typeFlag !== "5" && headerPath.endsWith("/")) {
      throw new Error(
        `Non-directory tar entry must not end with a slash: ${JSON.stringify(headerPath)}.`,
      );
    }

    const safePath = normalizeConsumerTarPath(headerPath, { directory: typeFlag === "5" });
    if (safePath !== "package" && !safePath.startsWith("package/")) {
      throw new Error(`Plugin tar entry must stay under package/: ${safePath}`);
    }
    const alias = safePath.toLocaleLowerCase("en-US");
    if (seenPaths.has(safePath) || seenAliases.has(alias)) {
      throw new Error(`Duplicate or aliased plugin tar entry: ${safePath}`);
    }
    seenPaths.add(safePath);
    seenAliases.add(alias);
    totalPathBytes += Buffer.byteLength(safePath, "utf8");
    if (totalPathBytes > limits.maxPathBytes) {
      throw new Error(`Plugin tarball paths exceed the ${limits.maxPathBytes} byte limit.`);
    }

    if (headerSize > limits.maxEntryBytes) {
      throw new Error(`Plugin tar entry ${safePath} exceeds ${limits.maxEntryBytes} bytes.`);
    }
    const dataOffset = offset + TAR_BLOCK_BYTES;
    const dataEnd = dataOffset + headerSize;
    const nextOffset = dataOffset + Math.ceil(headerSize / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;
    if (dataEnd > tarBytes.length || nextOffset > tarBytes.length) {
      throw new Error(`Tar entry ${JSON.stringify(safePath)} exceeds the archive boundary.`);
    }
    const content = tarBytes.subarray(dataOffset, dataEnd);
    offset = nextOffset;

    if (typeFlag === "5") {
      inventory.push({ path: safePath, sizeBytes: 0, type: "directory" });
      continue;
    }
    totalFileBytes += content.length;
    if (totalFileBytes > limits.maxTotalFileBytes) {
      throw new Error(`Plugin tarball file payload exceeds ${limits.maxTotalFileBytes} bytes.`);
    }
    const entry = {
      path: safePath,
      sha256: sha256(content),
      sizeBytes: content.length,
      type: "file",
    };
    inventory.push(entry);
    if (safePath === "package/package.json") {
      if (content.length === 0 || content.length > MAX_MANIFEST_BYTES) {
        throw new Error(
          `Packed package.json size is outside the allowed range: ${content.length}.`,
        );
      }
      packageManifestBytes = Buffer.from(content);
    } else if (safePath === "package/openclaw.plugin.json") {
      if (content.length === 0 || content.length > MAX_PLUGIN_MANIFEST_BYTES) {
        throw new Error(
          `Packed openclaw.plugin.json size is outside the allowed range: ${content.length}.`,
        );
      }
      pluginManifestBytes = Buffer.from(content);
    }
  }

  if (!sawEndMarker) {
    throw new Error("Plugin tarball is missing its end marker.");
  }
  if (!packageManifestBytes) {
    throw new Error("Plugin tarball must contain exactly one package/package.json.");
  }
  if (!pluginManifestBytes) {
    throw new Error("Plugin tarball must contain exactly one package/openclaw.plugin.json.");
  }
  inventory.sort((left, right) => compareCodeUnits(left.path, right.path));
  const packageManifest = parsePackedJson(packageManifestBytes, "Packed package.json");
  const pluginManifest = parsePackedJson(pluginManifestBytes, "Packed openclaw.plugin.json");
  return {
    inventory,
    packageManifest,
    packageManifestSha256: sha256(packageManifestBytes),
    pluginManifest,
    pluginManifestSha256: sha256(pluginManifestBytes),
    tarballSizeBytes: tarballBytes.byteLength,
    tarballSha256: sha256(tarballBytes),
    totalFileBytes,
  };
}

export function validatePluginPackageManifest(params, packageManifest) {
  if (packageManifest.name !== params.packageName || packageManifest.version !== params.version) {
    throw new Error(
      `Packed plugin identity ${String(packageManifest.name)}@${String(packageManifest.version)} does not match ${params.packageName}@${params.version}.`,
    );
  }
  if (Object.hasOwn(packageManifest, "tag")) {
    throw new Error(
      `${params.packageName}: packed package.json must not override the approved publication tag.`,
    );
  }
  const release = packageManifest.openclaw?.release;
  const referencesMetaIdentity =
    params.packageName === META_PACKAGE || params.packageDir === META_PACKAGE_DIR;
  if (
    referencesMetaIdentity &&
    (params.packageName !== META_PACKAGE ||
      params.packageDir !== META_PACKAGE_DIR ||
      release?.publishToNpm !== true ||
      release?.publishToClawHub !== true)
  ) {
    throw new Error(
      `${META_PACKAGE}: Meta publication requires ${META_PACKAGE_DIR} with npm and ClawHub enabled.`,
    );
  }
  if (params.route.startsWith("npm-") && release?.publishToNpm !== true) {
    throw new Error(`${params.packageName}: packed plugin is not marked publishToNpm.`);
  }
  if (params.route.startsWith("clawhub-") && release?.publishToClawHub !== true) {
    throw new Error(`${params.packageName}: packed plugin is not marked publishToClawHub.`);
  }
  if (packageManifest.publishConfig !== undefined) {
    throw new Error(
      `${params.packageName}: packed package.json must not override publication through publishConfig.`,
    );
  }
}

function normalizePublicationParams(params) {
  const targetSha = assertString(params.targetSha, "target SHA");
  if (!SHA_RE.test(targetSha)) {
    throw new Error(`Target SHA must be a full lowercase commit SHA: ${targetSha}`);
  }
  const packageDir = assertSafePackageDir(params.packageDir);
  const packageName = assertString(params.packageName, "package name");
  if (!PACKAGE_NAME_RE.test(packageName)) {
    throw new Error(`Invalid plugin package name: ${packageName}`);
  }
  const version = assertString(params.version, "package version");
  if (!VERSION_RE.test(version)) {
    throw new Error(`Invalid plugin package version: ${version}`);
  }
  const route = assertString(params.route, "publication route");
  if (!ROUTES.has(route)) {
    throw new Error(`Unsupported plugin publication route: ${route}`);
  }
  const npmRoutePolicy = NPM_ROUTE_POLICIES.get(route);
  let publicationReason = null;
  let publisherPolicy = null;
  if (npmRoutePolicy) {
    if (params.authMode !== undefined && params.authMode !== npmRoutePolicy.authMode) {
      throw new Error(`${route} auth mode must be ${npmRoutePolicy.authMode}.`);
    }
    if (params.capability !== undefined && params.capability !== npmRoutePolicy.capability) {
      throw new Error(`${route} capability must be ${npmRoutePolicy.capability}.`);
    }
    publicationReason = normalizePublicationReason(params.publicationReason);
    publisherPolicy = normalizePublisherPolicy(params.publisherPolicy);
  } else if (
    params.authMode !== undefined ||
    params.capability !== undefined ||
    params.publicationReason !== undefined ||
    params.publisherPolicy !== undefined
  ) {
    throw new Error(`${route} must not carry npm publisher-policy controls.`);
  }
  const publishTag = assertString(params.publishTag, "publish tag");
  const allowedTags = route.startsWith("npm-") ? NPM_TAGS : CLAWHUB_TAGS;
  if (!allowedTags.has(publishTag)) {
    throw new Error(`Unsupported ${route} publish tag: ${publishTag}`);
  }
  if (route.startsWith("npm-")) {
    const override = publishTag === "extended-stable" ? publishTag : undefined;
    const publishPlan = resolveNpmPublishPlan(version, undefined, override);
    if (publishPlan.publishTag !== publishTag) {
      throw new Error(
        `${packageName}@${version}: npm publish tag ${publishTag} does not match release channel ${publishPlan.channel}.`,
      );
    }
  } else {
    const expectedTag = version.includes("-alpha.")
      ? "alpha"
      : version.includes("-beta.")
        ? "beta"
        : "latest";
    if (publishTag !== expectedTag) {
      throw new Error(
        `${packageName}@${version}: ClawHub publish tag ${publishTag} must be ${expectedTag}.`,
      );
    }
  }
  const artifactName = assertSafeArtifactName(params.artifactName);
  let bootstrapMode = null;
  let requiresManualOverride = false;
  const manualOverrideReason = normalizeManualOverrideReason(params.manualOverrideReason);
  if (route === "clawhub-token-bootstrap") {
    bootstrapMode = assertString(params.bootstrapMode, "bootstrap mode");
    if (bootstrapMode !== "publish" && bootstrapMode !== "configure-only") {
      throw new Error(`Unsupported ClawHub bootstrap mode: ${bootstrapMode}`);
    }
    requiresManualOverride = params.requiresManualOverride === true;
  } else if (route === "clawhub-token-release") {
    requiresManualOverride = params.requiresManualOverride === true;
  } else if (
    params.bootstrapMode !== undefined ||
    params.requiresManualOverride === true ||
    manualOverrideReason !== null
  ) {
    throw new Error(`${route} must not carry ClawHub bootstrap controls.`);
  }
  if (requiresManualOverride !== (manualOverrideReason !== null)) {
    throw new Error(
      `${route} must bind a manual override reason exactly when a manual override is required.`,
    );
  }
  let sourcePackageJsonSha256;
  if (params.sourcePackageJsonSha256 !== undefined) {
    sourcePackageJsonSha256 = assertString(
      params.sourcePackageJsonSha256,
      "source package.json SHA-256",
    );
    if (!SHA256_RE.test(sourcePackageJsonSha256)) {
      throw new Error(
        `Source package.json SHA-256 must be 64 lowercase hex characters: ${sourcePackageJsonSha256}`,
      );
    }
  }
  return {
    artifactName,
    authMode: npmRoutePolicy?.authMode ?? null,
    bootstrapMode,
    capability: npmRoutePolicy?.capability ?? null,
    manualOverrideReason,
    packageDir,
    packageName,
    publicationReason,
    publishTag,
    publisherPolicy,
    requiresManualOverride,
    route,
    sourcePackageJsonSha256,
    targetSha,
    version,
  };
}

function buildManifest(params, tarballName, tarballBytes, inspection) {
  validatePluginPackageManifest(params, inspection.packageManifest);
  const publication = params.authMode
    ? {
        route: params.route,
        authMode: params.authMode,
        capability: params.capability,
        reason: params.publicationReason,
        tag: params.publishTag,
        publisherPolicy: params.publisherPolicy,
      }
    : {
        route: params.route,
        tag: params.publishTag,
        bootstrapMode: params.bootstrapMode,
        manualOverrideReason: params.manualOverrideReason,
        requiresManualOverride: params.requiresManualOverride,
      };
  return {
    schema: MANIFEST_SCHEMA,
    schemaVersion: 1,
    targetSha: params.targetSha,
    package: {
      dir: params.packageDir,
      name: params.packageName,
      version: params.version,
      author: inspection.packageManifest.author ?? null,
      contributors: inspection.packageManifest.contributors ?? null,
      repository: inspection.packageManifest.repository ?? null,
      packageJsonSha256: inspection.packageManifestSha256,
      pluginManifestSha256: inspection.pluginManifestSha256,
      sourcePackageJsonSha256: params.sourcePackageJsonSha256,
    },
    publication,
    artifact: {
      name: params.artifactName,
      tarball: tarballName,
      npmIntegrity: npmIntegrity(tarballBytes),
      npmShasum: npmShasum(tarballBytes),
      sha256: inspection.tarballSha256,
      sizeBytes: tarballBytes.length,
      inventory: inspection.inventory,
    },
  };
}

function tarInspectionOptionsForRoute(route) {
  return route.startsWith("clawhub-") ? CLAWHUB_PUBLICATION_TAR_LIMITS : undefined;
}

function normalizeExpectedInventory(value) {
  if (!Array.isArray(value)) {
    throw new Error("Expected plugin tarball inventory must be an array.");
  }
  const paths = new Set();
  const aliases = new Set();
  const inventory = value.map((entry) => {
    if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
      throw new Error("Expected plugin tarball inventory entries must be objects.");
    }
    const expectedKeys =
      entry.type === "directory"
        ? ["path", "sizeBytes", "type"]
        : ["path", "sha256", "sizeBytes", "type"];
    if (JSON.stringify(Object.keys(entry).toSorted()) !== JSON.stringify(expectedKeys)) {
      throw new Error("Expected plugin tarball inventory entry shape is invalid.");
    }
    const path = assertSafeArchivePath(entry.path, "expected tar entry path");
    if (path !== entry.path) {
      throw new Error(`Expected plugin tarball path is not canonical: ${entry.path}`);
    }
    const alias = path.toLocaleLowerCase("en-US");
    if (paths.has(path) || aliases.has(alias)) {
      throw new Error(`Expected plugin tarball inventory contains an alias: ${path}`);
    }
    paths.add(path);
    aliases.add(alias);
    if (
      !Number.isSafeInteger(entry.sizeBytes) ||
      entry.sizeBytes < 0 ||
      (entry.type === "directory" && entry.sizeBytes !== 0)
    ) {
      throw new Error(`Expected plugin tarball size is invalid for ${path}.`);
    }
    if (entry.type === "directory") {
      return { path, sizeBytes: 0, type: "directory" };
    }
    if (
      entry.type !== "file" ||
      typeof entry.sha256 !== "string" ||
      !SHA256_RE.test(entry.sha256)
    ) {
      throw new Error(`Expected plugin tarball file identity is invalid for ${path}.`);
    }
    return {
      path,
      sha256: entry.sha256,
      sizeBytes: entry.sizeBytes,
      type: "file",
    };
  });
  return inventory.toSorted((left, right) => compareCodeUnits(left.path, right.path));
}

function canonicalManifestText(manifest) {
  return `${JSON.stringify(manifest, null, 2)}\n`;
}

function createFreshOutputDirectory(path, label) {
  try {
    lstatSync(path);
    throw new Error(`${label} must not already exist.`);
  } catch (error) {
    if (!error || typeof error !== "object" || error.code !== "ENOENT") {
      throw error;
    }
  }
  mkdirSync(path, { recursive: true, mode: 0o700 });
  const created = lstatSync(path);
  if (!created.isDirectory() || created.isSymbolicLink()) {
    throw new Error(`${label} must be a newly created directory.`);
  }
}

export function createPluginPublicationArtifact(params) {
  const normalized = normalizePublicationParams(params);
  if (!normalized.sourcePackageJsonSha256) {
    throw new Error("Plugin publication creation requires source package.json SHA-256.");
  }
  const artifactDir = resolve(params.artifactDir);
  const entries = readdirSync(artifactDir, { withFileTypes: true });
  const tarballs = entries.filter(
    (entry) => entry.isFile() && entry.name.endsWith(".tgz") && entry.name !== MANIFEST_FILENAME,
  );
  const unexpected = entries.filter((entry) => !(entry.isFile() && entry.name.endsWith(".tgz")));
  if (tarballs.length !== 1 || unexpected.length !== 0) {
    throw new Error(
      `Plugin publication staging dir must contain exactly one .tgz and no other entries; found ${entries.map((entry) => entry.name).join(", ") || "<empty>"}.`,
    );
  }
  const tarballName = assertSafeArchivePath(tarballs[0].name, "tarball filename");
  if (basename(tarballName) !== tarballName) {
    throw new Error(`Plugin tarball must be at the artifact root: ${tarballName}`);
  }
  const tarballPath = join(artifactDir, tarballName);
  const tarballBytes = readBoundedRegularFile(tarballPath, {
    label: "Plugin tarball",
    maxBytes: MAX_ARCHIVE_BYTES,
  });
  const inspection = inspectPackageTarballBytes(
    tarballBytes,
    tarInspectionOptionsForRoute(normalized.route),
  );
  const manifest = buildManifest(normalized, tarballName, tarballBytes, inspection);
  const manifestPath = join(artifactDir, MANIFEST_FILENAME);
  writeFileSync(manifestPath, canonicalManifestText(manifest), { mode: 0o600 });
  return { manifest, manifestPath, tarballPath };
}

function parseBoundedJsonFile(path, label, maxBytes = MAX_MANIFEST_BYTES) {
  let value;
  try {
    value = JSON.parse(
      new TextDecoder("utf-8", { fatal: true }).decode(
        readBoundedRegularFile(path, {
          label,
          maxBytes,
        }),
      ),
    );
  } catch (error) {
    throw new Error(
      `${label} is invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
      { cause: error },
    );
  }
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    throw new Error(`${label} must be an object.`);
  }
  return value;
}

function inspectPluginPublicationArtifactZip(zipBytes) {
  return inspectActionsArtifactZipWithPolicy(zipBytes, {
    minEntries: 2,
    maxEntries: 2,
    maxArchiveBytes: MAX_ARCHIVE_BYTES,
    maxExpandedBytes: MAX_ARCHIVE_BYTES + MAX_MANIFEST_BYTES,
    allowPath: (name) =>
      name === MANIFEST_FILENAME || (basename(name) === name && name.endsWith(".tgz")),
    maxEntryBytes: (name) => (name === MANIFEST_FILENAME ? MAX_MANIFEST_BYTES : MAX_ARCHIVE_BYTES),
  });
}

export function verifyPluginPublicationArtifact(params) {
  const normalized = normalizePublicationParams(params);
  let expectedTarballSha256;
  if (params.expectedTarballSha256 !== undefined) {
    expectedTarballSha256 = assertString(params.expectedTarballSha256, "expected tarball SHA-256");
    if (!SHA256_RE.test(expectedTarballSha256)) {
      throw new Error("Expected tarball SHA-256 must be 64 lowercase hex characters.");
    }
  }
  const expectedTarballSizeBytes =
    params.expectedTarballSizeBytes === undefined
      ? undefined
      : assertPositiveInteger(params.expectedTarballSizeBytes, "expected tarball size");
  const expectedInventory =
    params.expectedInventory === undefined
      ? undefined
      : normalizeExpectedInventory(params.expectedInventory);
  const artifactId = assertPositiveInteger(params.artifactId, "artifact ID");
  const artifactSizeBytes = assertPositiveInteger(params.artifactSizeBytes, "artifact size");
  const runId = assertPositiveInteger(
    params.producerRunId ?? params.runId,
    "producer workflow run ID",
  );
  const runAttempt = assertPositiveInteger(
    params.producerRunAttempt ?? params.runAttempt,
    "producer workflow run attempt",
  );
  const workflowSha = assertString(params.workflowSha, "workflow SHA");
  if (!SHA_RE.test(workflowSha)) {
    throw new Error(`Workflow SHA must be a full lowercase commit SHA: ${workflowSha}`);
  }
  const expectedArtifactDigest = assertString(params.artifactDigest, "artifact digest");
  if (!ARTIFACT_DIGEST_RE.test(expectedArtifactDigest)) {
    throw new Error(`Invalid Actions artifact digest: ${expectedArtifactDigest}`);
  }
  const metadata = parseBoundedJsonFile(params.artifactMetadataPath, "Actions artifact metadata");
  const workflowRun = parseBoundedJsonFile(
    params.workflowRunMetadataPath,
    "Actions workflow run metadata",
  );
  const expectedBinding = {
    artifactDigest: expectedArtifactDigest,
    artifactId,
    artifactName: normalized.artifactName,
    artifactSizeBytes,
    consumerRunAttempt: params.consumerRunAttempt,
    producerJobName: params.producerJobName,
    repository: params.repository,
    runStatePolicy: params.runStatePolicy ?? "completed-success",
    runAttempt,
    runId,
    workflowEvent: params.workflowEvent,
    workflowHeadBranch: params.workflowHeadBranch,
    workflowPath: params.workflowPath,
    workflowSha,
  };
  validateActionsArtifactBinding({
    artifactMetadata: metadata,
    expected: expectedBinding,
    workflowRun,
  });
  if (expectedBinding.runStatePolicy === "same-run-producer-success") {
    const workflowJobs = parseBoundedJsonFile(
      params.workflowJobsMetadataPath,
      "Actions workflow jobs metadata",
    );
    validateActionsArtifactProducerJob({ expected: expectedBinding, workflowJobs });
  }

  const zipBytes = readBoundedRegularFile(params.artifactZipPath, {
    label: "Actions artifact ZIP",
    maxBytes: MAX_ARCHIVE_BYTES,
  });
  if (zipBytes.length !== artifactSizeBytes) {
    throw new Error("Actions artifact ZIP size does not match the immutable publish tuple.");
  }
  const actualArtifactDigest = sha256Digest(zipBytes);
  if (actualArtifactDigest !== expectedArtifactDigest) {
    throw new Error(
      `Actions artifact ZIP digest ${actualArtifactDigest} does not match ${expectedArtifactDigest}.`,
    );
  }
  const files = inspectPluginPublicationArtifactZip(zipBytes);
  const manifestBytes = files.get(MANIFEST_FILENAME);
  const tarballNames = [...files.keys()].filter((name) => name.endsWith(".tgz"));
  if (!manifestBytes || tarballNames.length !== 1) {
    throw new Error(
      `Plugin publication artifact must contain ${MANIFEST_FILENAME} and exactly one .tgz.`,
    );
  }
  if (manifestBytes.length === 0 || manifestBytes.length > MAX_MANIFEST_BYTES) {
    throw new Error(`Plugin publication manifest size is outside the allowed range.`);
  }
  let manifest;
  try {
    manifest = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(manifestBytes));
  } catch (error) {
    throw new Error(
      `Plugin publication manifest is invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
      { cause: error },
    );
  }
  const manifestSourcePackageJsonSha256 = manifest?.package?.sourcePackageJsonSha256;
  if (
    typeof manifestSourcePackageJsonSha256 !== "string" ||
    !SHA256_RE.test(manifestSourcePackageJsonSha256)
  ) {
    throw new Error("Plugin publication manifest source package.json SHA-256 is invalid.");
  }
  if (
    normalized.sourcePackageJsonSha256 !== undefined &&
    normalized.sourcePackageJsonSha256 !== manifestSourcePackageJsonSha256
  ) {
    throw new Error(
      "Plugin publication source package.json SHA-256 does not match the approved target source.",
    );
  }
  const tarballName = tarballNames[0];
  const tarballBytes = files.get(tarballName);
  const inspection = inspectPackageTarballBytes(
    tarballBytes,
    tarInspectionOptionsForRoute(normalized.route),
  );
  if (
    expectedTarballSizeBytes !== undefined &&
    inspection.tarballSizeBytes !== expectedTarballSizeBytes
  ) {
    throw new Error("Plugin tarball size does not match the approved publication tuple.");
  }
  if (expectedTarballSha256 !== undefined && inspection.tarballSha256 !== expectedTarballSha256) {
    throw new Error("Plugin tarball SHA-256 does not match the approved publication tuple.");
  }
  if (
    expectedInventory !== undefined &&
    JSON.stringify(inspection.inventory) !== JSON.stringify(expectedInventory)
  ) {
    throw new Error("Plugin tarball inventory does not match the approved publication tuple.");
  }
  const expectedManifest = buildManifest(
    {
      ...normalized,
      sourcePackageJsonSha256: manifestSourcePackageJsonSha256,
    },
    tarballName,
    tarballBytes,
    inspection,
  );
  const expectedManifestBytes = Buffer.from(canonicalManifestText(expectedManifest), "utf8");
  if (!manifestBytes.equals(expectedManifestBytes)) {
    throw new Error(
      "Plugin publication manifest does not canonically bind the approved package and tarball.",
    );
  }

  const outputDir = resolve(params.outputDir);
  createFreshOutputDirectory(outputDir, "Plugin publication output directory");
  const outputPath = join(outputDir, tarballName);
  writeFileSync(outputPath, tarballBytes, { flag: "wx", mode: 0o600 });
  if (!statSync(outputPath).isFile()) {
    throw new Error(`Verified plugin tarball was not written: ${outputPath}`);
  }
  return {
    artifactDigest: expectedArtifactDigest,
    artifactId,
    artifactName: normalized.artifactName,
    artifactSizeBytes,
    artifactZipSha256: actualArtifactDigest.slice("sha256:".length),
    manifest: expectedManifest,
    npmIntegrity: expectedManifest.artifact.npmIntegrity,
    npmShasum: expectedManifest.artifact.npmShasum,
    packageJsonSha256: expectedManifest.package.packageJsonSha256,
    pluginManifestSha256: expectedManifest.package.pluginManifestSha256,
    producerRunAttempt: runAttempt,
    producerRunId: runId,
    sourcePackageJsonSha256: expectedManifest.package.sourcePackageJsonSha256,
    tarballInventory: inspection.inventory,
    tarballName,
    tarballPath: outputPath,
    tarballSizeBytes: inspection.tarballSizeBytes,
    tarballSha256: inspection.tarballSha256,
  };
}

function parseCliArgs(argv) {
  const [command, ...rest] = argv;
  if (command !== "create" && command !== "verify") {
    throw new Error(
      "Usage: plugin-publication-artifact.mjs <create|verify> --artifact-name <name> ...",
    );
  }
  const values = {};
  for (let index = 0; index < rest.length; index += 2) {
    const key = rest[index];
    const value = rest[index + 1];
    if (!key?.startsWith("--") || value === undefined || value.startsWith("--")) {
      throw new Error(`Invalid ${command} argument near ${key ?? "<missing>"}.`);
    }
    const name = key.slice(2).replace(/-([a-z])/gu, (_, letter) => letter.toUpperCase());
    if (values[name] !== undefined) {
      throw new Error(`Duplicate ${command} option: ${key}`);
    }
    values[name] = value;
  }
  return { command, values };
}

function commonCliParams(values) {
  return {
    artifactName: values.artifactName,
    bootstrapMode: values.bootstrapMode,
    manualOverrideReason: values.manualOverrideReason,
    packageDir: values.packageDir,
    packageName: values.packageName,
    publishTag: values.publishTag,
    requiresManualOverride:
      values.requiresManualOverride === undefined
        ? false
        : assertBooleanString(values.requiresManualOverride, "requires-manual-override"),
    route: values.route,
    publicationReason: values.publicationReason,
    publisherPolicy:
      values.publisherPolicySchema === undefined &&
      values.publisherPolicyId === undefined &&
      values.publisherPolicySha256 === undefined
        ? undefined
        : {
            policyId: values.publisherPolicyId,
            schema: values.publisherPolicySchema,
            sha256: values.publisherPolicySha256,
          },
    sourcePackageJsonSha256: values.sourcePackageJsonSha256,
    targetSha: values.targetSha,
    version: values.packageVersion,
  };
}

function appendGithubOutput(path, values) {
  const lines = Object.entries(values).map(([name, value]) => `${name}=${String(value)}`);
  writeFileSync(path, `${lines.join("\n")}\n`, { flag: "a" });
}

export function main(argv = process.argv.slice(2)) {
  const { command, values } = parseCliArgs(argv);
  const common = commonCliParams(values);
  if (command === "create") {
    const result = createPluginPublicationArtifact({
      ...common,
      artifactDir: values.artifactDir,
    });
    console.log(`Created canonical plugin publication manifest: ${result.manifestPath}`);
    console.log(`Prepared plugin tarball: ${result.tarballPath}`);
    return;
  }
  const result = verifyPluginPublicationArtifact({
    ...common,
    artifactDigest: values.artifactDigest,
    artifactId: Number(values.artifactId),
    artifactMetadataPath: values.artifactMetadata,
    artifactSizeBytes: Number(values.artifactSizeBytes),
    artifactZipPath: values.artifactZip,
    expectedTarballSizeBytes:
      values.expectedTarballSizeBytes === undefined
        ? undefined
        : Number(values.expectedTarballSizeBytes),
    expectedTarballSha256: values.expectedTarballSha256,
    outputDir: values.outputDir,
    consumerRunAttempt:
      values.consumerRunAttempt === undefined ? undefined : Number(values.consumerRunAttempt),
    producerJobName: values.producerJobName,
    producerRunAttempt: Number(values.producerRunAttempt),
    producerRunId: Number(values.producerRunId),
    repository: values.repository,
    workflowEvent: values.workflowEvent,
    workflowHeadBranch: values.workflowHeadBranch,
    workflowPath: values.workflowPath,
    workflowJobsMetadataPath: values.workflowJobsMetadata,
    workflowRunMetadataPath: values.workflowRunMetadata,
    runStatePolicy: values.runStatePolicy,
    workflowSha: values.workflowSha,
  });
  if (values.githubOutput) {
    appendGithubOutput(values.githubOutput, {
      artifact_digest: result.artifactDigest,
      artifact_id: result.artifactId,
      artifact_name: result.artifactName,
      artifact_size_bytes: result.artifactSizeBytes,
      artifact_zip_sha256: result.artifactZipSha256,
      bootstrap_mode: result.manifest.publication.bootstrapMode ?? "",
      manual_override_reason: result.manifest.publication.manualOverrideReason ?? "",
      package_name: result.manifest.package.name,
      package_json_sha256: result.manifest.package.packageJsonSha256,
      package_version: result.manifest.package.version,
      publish_route: result.manifest.publication.route,
      publish_tag: result.manifest.publication.tag,
      producer_run_attempt: result.producerRunAttempt,
      producer_run_id: result.producerRunId,
      requires_manual_override: result.manifest.publication.requiresManualOverride,
      source_package_json_sha256: result.manifest.package.sourcePackageJsonSha256,
      tarball_path: result.tarballPath,
      tarball_sha256: result.tarballSha256,
    });
  }
  console.log(
    `Verified ${result.manifest.package.name}@${result.manifest.package.version} plugin artifact ${result.artifactId} (${result.artifactDigest}).`,
  );
}

const entrypoint = process.argv[1] ? pathToFileURL(process.argv[1]).href : undefined;
if (entrypoint === import.meta.url) {
  try {
    main();
  } catch (error) {
    console.error(error instanceof Error ? error.message : String(error));
    process.exitCode = 1;
  }
}
