@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
134 lines (130 loc) • 4.25 kB
JavaScript
import { spawn } from "node:child_process";
import { fileURLToPath, pathToFileURL } from "node:url";
import { proveDefaultProfileNativeSkills } from "./fly-skills-bridge.mjs";
const WORKER = fileURLToPath(import.meta.url);
const PROOF_TIMEOUT_MS = 90_000;
// Native skills use synchronous Python and a profile flock. Keep both off the
// lifecycle event loop so a slow native read cannot stop its lease heartbeat.
export function proveDefaultProfileNativeSkillsAsync(
{ actions, profileId, dataRoot },
{ spawnWorker = spawn, timeoutMs = PROOF_TIMEOUT_MS } = {}
) {
return new Promise((resolve, reject) => {
if (
!Number.isInteger(timeoutMs) ||
timeoutMs < 1 ||
timeoutMs > PROOF_TIMEOUT_MS
) {
reject(new Error("skills_native_request_rejected"));
return;
}
let child;
let deadline;
let failureCode;
let stdoutBytes = 0;
let stderrBytes = 0;
const stdout = [];
const stopWorkerGroup = () => {
if (!child?.pid) return;
try {
process.kill(-child.pid, "SIGKILL");
} catch {
child.kill("SIGKILL");
}
};
// execFile does not forward detached to spawn. Native-proof UAT
// 01a0a304 left stopped Python descendants alive after both deadlines.
// Spawn the worker directly so it really owns the group we terminate.
child = spawnWorker(
process.execPath,
[WORKER, "--prove", JSON.stringify({ actions, profileId, dataRoot })],
{
detached: true,
stdio: ["ignore", "pipe", "pipe"],
env: { PATH: process.env.PATH, HOME: dataRoot },
}
);
child.stdout.on("data", (chunk) => {
stdoutBytes += chunk.length;
if (stdoutBytes <= 16 * 1024) stdout.push(Buffer.from(chunk));
else {
failureCode ??= "skills_native_call_failed";
stopWorkerGroup();
}
});
child.stderr.on("data", (chunk) => {
stderrBytes += chunk.length;
if (stderrBytes > 16 * 1024) {
failureCode ??= "skills_native_call_failed";
stopWorkerGroup();
}
});
child.once("error", () => {
clearTimeout(deadline);
stopWorkerGroup();
reject(new Error("skills_native_call_failed"));
});
child.once("close", (code, signal) => {
clearTimeout(deadline);
stopWorkerGroup();
if (failureCode || code !== 0 || signal) {
reject(new Error(failureCode ?? "skills_native_call_failed"));
return;
}
try {
const result = JSON.parse(Buffer.concat(stdout).toString("utf8"));
if (result.ok === false && /^\w{1,80}$/.test(result.code)) {
reject(new Error(result.code));
return;
}
const expectedStatus = (actions ?? []).some(
(action) => action.type === "skill"
)
? "PROVEN"
: "EMPTY";
if (
result.ok !== true ||
result.proof?.status !== expectedStatus ||
!/^[a-f0-9]{64}$/.test(result.proof?.digest ?? "")
) {
throw new Error("skills_native_output_rejected");
}
resolve({ status: result.proof.status, digest: result.proof.digest });
} catch {
reject(new Error("skills_native_output_rejected"));
}
});
deadline = setTimeout(() => {
failureCode ??= "skills_native_call_timeout";
stopWorkerGroup();
}, timeoutMs);
deadline.unref();
});
}
if (
process.argv[1] &&
pathToFileURL(process.argv[1]).href === import.meta.url
) {
let result;
try {
if (process.argv[2] !== "--prove")
throw new Error("skills_native_request_rejected");
// cxconnect e65c62f6 timed out twice at the old synchronous 20s limit
// while the new gateway was warming. This worker is already isolated
// from heartbeats and its entire process group has a hard 90s deadline.
const proof = proveDefaultProfileNativeSkills({
...JSON.parse(process.argv[3]),
nativeCallTimeoutMs: 60_000,
});
result = { ok: true, proof };
} catch (error) {
result = {
ok: false,
code:
error?.name === "FlySkillsBridgeError"
? error.code
: "skills_native_call_failed",
};
}
process.stdout.write(JSON.stringify(result));
}