UNPKG

@sellable/install

Version:

One-command installer for Sellable MCP in Claude Code, Codex, and Hermes

219 lines (207 loc) • 7.64 kB
#!/usr/bin/env node import { spawnSync } from "node:child_process"; import { constants, closeSync, fstatSync, lstatSync, openSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; const MANAGED_NAME = /^sellable-default-[a-z0-9][a-z0-9-]{0,63}$/; const SHA256 = /^[a-f0-9]{64}$/; const PYTHON = "/opt/hermes/.venv/bin/python3"; const MAX_REQUEST_BYTES = 64 * 1024; const DESIRED_FIELDS = Object.freeze([ "name", "enabled", "schedule", "prompt", "skills", "script", "no_agent", "deliver", "toolsets", "workdir", ]); const refused = (code) => ({ ok: false, status: "REFUSED", code }); const stable = (value) => { if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`; if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stable(value[key])}`).join(",")}}`; return JSON.stringify(value); }; const same = (left, right) => stable(left) === stable(right); const NATIVE_READ_SCRIPT = String.raw` import json, sqlite3, sys from datetime import datetime from pathlib import Path request = json.loads(sys.stdin.read()) root, name = Path(request["profileRoot"]), request["name"] jobs = json.loads((root / "cron" / "jobs.json").read_text()).get("jobs", []) matches = [job for job in jobs if job.get("name") == name] if len(matches) != 1: print(json.dumps({"job": None, "history": []}, separators=(",", ":"))) raise SystemExit(0) job = matches[0] history = [] database = root / "cron" / "executions.db" if database.is_file(): connection = sqlite3.connect(f"file:{database}?mode=ro", uri=True) connection.row_factory = sqlite3.Row try: rows = connection.execute("SELECT id,status,finished_at FROM executions WHERE job_id=? ORDER BY claimed_at DESC,id DESC LIMIT 50", (str(job.get("id") or ""),)).fetchall() for row in rows: try: finished = int(datetime.fromisoformat(row["finished_at"]).timestamp() * 1000) if row["finished_at"] else None except (TypeError, ValueError): finished = None history.append({"id": row["id"], "status": "success" if row["status"] == "completed" else row["status"], "finishedAt": finished}) finally: connection.close() print(json.dumps({"job": job, "history": history}, separators=(",", ":"))) `; function readRegularJson(path) { const link = lstatSync(path); if (link.isSymbolicLink() || !link.isFile() || link.nlink !== 1 || link.size > 64 * 1024) throw new Error("cron_proof_marker_rejected"); const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW); try { const opened = fstatSync(fd); if (!opened.isFile() || opened.dev !== link.dev || opened.ino !== link.ino || opened.nlink !== 1 || opened.size !== link.size) throw new Error("cron_proof_marker_rejected"); return JSON.parse(readFileSync(fd, "utf8")); } finally { closeSync(fd); } } function desiredProjection(job, desired) { if (!desired || typeof desired !== "object" || Array.isArray(desired)) return null; const keys = Object.keys(desired); if (keys.length === 0 || keys.some((key) => !DESIRED_FIELDS.includes(key))) return null; return Object.fromEntries(keys.sort().map((key) => [key, job?.[key]])); } export async function proveHermesCronFiring({ profileId, name, operationId, bootId, bundleDigest, nonce, notBefore, desired, directExecution = false, readNative, readMarker, } = {}) { if (directExecution === true) return refused("cron_proof_direct_execution_invalid"); if ( !ID.test(profileId ?? "") || !MANAGED_NAME.test(name ?? "") || !ID.test(operationId ?? "") || !ID.test(bootId ?? "") || !SHA256.test(bundleDigest ?? "") || !ID.test(nonce ?? "") || !Number.isSafeInteger(notBefore) || notBefore < 0 || typeof readNative !== "function" || typeof readMarker !== "function" ) { return refused("cron_proof_request_rejected"); } try { const native = await readNative({ profileId, name }); const job = native?.job; const projection = desiredProjection(job, desired); if ( !job || typeof job !== "object" || !ID.test(job.id ?? "") || job.name !== name || job.enabled !== true || projection == null || !same(projection, desired) || !Array.isArray(native.history) ) { return refused("cron_proof_native_projection_rejected"); } const successes = native.history.filter( (run) => run && typeof run === "object" && ID.test(run.id ?? "") && run.status === "success" && Number.isSafeInteger(run.finishedAt) && run.finishedAt >= notBefore ); if (successes.length === 0) return refused("cron_proof_history_stale"); const marker = await readMarker({ profileId, name, jobId: job.id }); if ( !marker || marker.operationId !== operationId || marker.bootId !== bootId || marker.bundleDigest !== bundleDigest || marker.nonce !== nonce || marker.jobId !== job.id || !Number.isSafeInteger(marker.firedAt) || marker.firedAt < notBefore || !successes.some((run) => run.finishedAt >= marker.firedAt) ) { return refused("cron_proof_marker_rejected"); } return { ok: true, status: "PROVEN", profileId, name, jobId: job.id, projection, successfulFirings: successes.length, latestFinishedAt: Math.max(...successes.map((run) => run.finishedAt)), exactOnceClaimed: false, }; } catch { return refused("cron_proof_read_failed"); } } // The image exposes this file as a command, but a caller must supply the // gateway-bound native readers. Executing the marker script is never a proof. export async function runFlyCronProofExec({ request, dataRoot = "/opt/data", python = PYTHON, readNative, readMarker, } = {}) { const profileRoot = resolve(dataRoot, "profiles", String(request?.profileId ?? "")); const nativeReader = readNative ?? (() => { const child = spawnSync(python, ["-c", NATIVE_READ_SCRIPT], { input: JSON.stringify({ profileRoot, name: request?.name }), encoding: "utf8", timeout: 10_000, maxBuffer: 1024 * 1024, env: { PATH: "/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin" }, }); if (child.status !== 0 || child.signal || child.error) throw new Error("native_read"); return JSON.parse(child.stdout); }); const markerReader = readMarker ?? (() => readRegularJson(resolve(profileRoot, ".sellable-agent", "cron-markers", `${request?.name}.json`)) ); return proveHermesCronFiring({ ...request, readNative: nativeReader, readMarker: markerReader }); } if (resolve(process.argv[1] ?? "") === resolve(fileURLToPath(import.meta.url))) { let result; try { if (process.argv.length !== 3 || process.argv[2].length > MAX_REQUEST_BYTES * 2) throw new Error("request"); const bytes = Buffer.from(process.argv[2], "base64url"); if (bytes.byteLength > MAX_REQUEST_BYTES || bytes.toString("base64url") !== process.argv[2]) throw new Error("request"); result = await runFlyCronProofExec({ request: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)), dataRoot: process.env.SELLABLE_AGENT_DATA_ROOT ?? "/opt/data", }); } catch { result = refused("cron_proof_request_rejected"); } process.stdout.write(`${JSON.stringify(result)}\n`); process.exitCode = result.ok ? 0 : 2; }