@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
219 lines (207 loc) • 7.64 kB
JavaScript
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;
}