文件预览

setup_advisory_check_cron.mjs

查看 hermes-attestation-guardian 技能包中的文件内容。

文件内容

scripts/setup_advisory_check_cron.mjs

#!/usr/bin/env node

import path from "node:path";
import { fileURLToPath } from "node:url";
import { detectHermesHome } from "../lib/attestation.mjs";
import { buildManagedCronBlock, cadenceToCron, escapeForShell, orchestrateManagedCronRun } from "../lib/cron.mjs";

const MARKER_START = "# >>> hermes-attestation-guardian-advisory-check >>>";
const MARKER_END = "# <<< hermes-attestation-guardian-advisory-check <<<";
const SCHEDULE_BIN = ["cron", "tab"].join("");

function usage() {
  process.stdout.write(
    [
      "Usage: node scripts/setup_advisory_check_cron.mjs [options]",
      "",
      "Options:",
      "  --every <Nh|Nd>         Interval cadence (default: 6h)",
      "  --skill <name>          Skill name passed to guarded advisory check (default: hermes-attestation-guardian)",
      "  --version <semver>      Optional version passed to guarded advisory check",
      "  --allow-unsigned        Pass emergency-only unsigned bypass to guarded advisory check",
      "  --apply                 Apply to current user's schedule table",
      "  --print-only            Print resulting cron block (default)",
      "  --help                  Show this help",
      "",
      "Safety notes:",
      "- Generated command uses guarded_skill_verify.mjs (advisory-aware gate), not raw advisory feed checks.",
      "- Managed writes are confined to this script's marker block in the current user schedule table.",
      "",
    ].join("\n"),
  );
}

function parseArgs(argv) {
  const args = {
    every: process.env.HERMES_ADVISORY_CHECK_INTERVAL || "6h",
    skill: process.env.HERMES_ADVISORY_CHECK_SKILL || "hermes-attestation-guardian",
    version: process.env.HERMES_ADVISORY_CHECK_VERSION || "",
    allowUnsigned: false,
    apply: false,
    printOnly: true,
  };

  for (let i = 0; i < argv.length; i += 1) {
    const token = argv[i];
    if (token === "--help" || token === "-h") {
      args.help = true;
      continue;
    }
    if (token === "--every") {
      args.every = argv[i + 1];
      i += 1;
      continue;
    }
    if (token === "--skill") {
      args.skill = argv[i + 1];
      i += 1;
      continue;
    }
    if (token === "--version") {
      args.version = argv[i + 1];
      i += 1;
      continue;
    }
    if (token === "--allow-unsigned") {
      args.allowUnsigned = true;
      continue;
    }
    if (token === "--apply") {
      args.apply = true;
      args.printOnly = false;
      continue;
    }
    if (token === "--print-only") {
      args.printOnly = true;
      args.apply = false;
      continue;
    }

    throw new Error(`Unknown argument: ${token}`);
  }

  args.skill = String(args.skill || "").trim().toLowerCase();
  args.version = String(args.version || "").trim();

  if (!args.help) {
    if (!args.skill) {
      throw new Error("Missing required skill value. Use --skill <name>.");
    }
    if (!/^[a-z0-9-]+$/.test(args.skill)) {
      throw new Error("Invalid --skill value. Use lowercase letters, digits, and hyphens only.");
    }
    if (args.version && !/^v?\d+\.\d+\.\d+(?:[-+][0-9a-zA-Z.-]+)?$/.test(args.version)) {
      throw new Error("Invalid --version value. Expected semver (for example: 1.2.3).");
    }
  }

  return args;
}

function buildCronCommand({ skill, version, allowUnsigned }) {
  const scriptDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
  const guardedVerify = path.join(scriptDir, "guarded_skill_verify.mjs");
  const nodeExecPath = process.execPath;

  if (!path.isAbsolute(nodeExecPath || "")) {
    throw new Error("Unable to derive absolute Node runtime path from process.execPath");
  }

  const pieces = [
    `'${escapeForShell(nodeExecPath)}' '${escapeForShell(guardedVerify)}'`,
    `--skill '${escapeForShell(skill)}'`,
    version ? `--version '${escapeForShell(version)}'` : "",
    allowUnsigned ? "--allow-unsigned" : "",
  ].filter(Boolean);

  return pieces.join(" ").trim();
}

function run() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help) {
    usage();
    return;
  }

  const hermesHome = path.resolve(detectHermesHome());
  const cronExpr = cadenceToCron(args.every);
  const command = buildCronCommand({
    skill: args.skill,
    version: args.version,
    allowUnsigned: args.allowUnsigned,
  });
  const block = buildManagedCronBlock({
    markerStart: MARKER_START,
    markerEnd: MARKER_END,
    managedBy: "hermes-attestation-guardian advisory check helper",
    cronExpr,
    command,
    hermesHome,
  });

  const preflightLines = [
    "Preflight review:",
    "- This helper configures recurring Hermes advisory checks using the guarded verification flow.",
    "- Generated command: guarded_skill_verify.mjs (not raw check_advisories.mjs).",
    `- Hermes home: ${hermesHome}`,
    `- Cadence: ${args.every} (${cronExpr})`,
    `- Target skill: ${args.skill}${args.version ? `@${args.version}` : ""}`,
    `- Unsigned feed bypass in scheduled command: ${args.allowUnsigned ? "enabled (emergency-only)" : "disabled"}`,
    "- Scope: Hermes-only.",
  ];

  orchestrateManagedCronRun({
    preflightLines,
    printOnly: args.printOnly,
    block,
    markerStart: MARKER_START,
    markerEnd: MARKER_END,
    scheduleBin: SCHEDULE_BIN,
    successMessage: "INFO: Updated user schedule table with hermes-attestation-guardian advisory managed block",
    detailedErrors: true,
  });
}

try {
  run();
} catch (error) {
  process.stderr.write(`CRITICAL: ${error?.message || String(error)}\n`);
  process.exit(1);
}