UNPKG

@sellable/install

Version:

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

52 lines (47 loc) • 1.94 kB
#!/usr/bin/env node import { chmodSync, mkdirSync, renameSync, writeFileSync } from "node:fs"; import { basename, dirname, isAbsolute, join, relative, resolve, } from "node:path"; const MARKER = '{"schemaVersion":"sellable-hermes-snapshot-dirty/v1","dirty":true}\n'; const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/; async function main() { // Do not wait for stdin EOF. Hermes keeps the shell-hook input pipe open while // it waits for the hook process, so draining stdin here deadlocks until Hermes // kills the helper at its timeout. The marker does not depend on tool payload // bytes; leaving stdin untouched also guarantees that none are parsed or logged. const configuredHome = process.env.HERMES_HOME || process.env.HOME || ""; if (!isAbsolute(configuredHome)) throw new Error("identity"); const home = resolve(configuredHome); const inferredProfile = basename(dirname(home)) === "profiles" ? basename(home) : ""; const profile = process.env.SELLABLE_HERMES_PROFILE_ID || inferredProfile; if (!SAFE_ID.test(profile)) throw new Error("identity"); const named = basename(home) === profile && basename(dirname(home)) === "profiles"; if (basename(dirname(home)) === "profiles" && !named) throw new Error("identity"); const root = named ? home : join(home, "profiles", profile); const marker = resolve( process.env.SELLABLE_HERMES_MEMORY_DIRTY_MARKER || join(root, ".sellable-agent", "memory", "dirty.json") ); const confined = relative(root, marker); if (confined.startsWith("..") || isAbsolute(confined)) throw new Error("marker"); mkdirSync(dirname(marker), { recursive: true, mode: 0o700 }); chmodSync(dirname(marker), 0o700); const temporary = `${marker}.${process.pid}.tmp`; writeFileSync(temporary, MARKER, { mode: 0o600 }); renameSync(temporary, marker); chmodSync(marker, 0o600); } main().catch(() => { process.exitCode = 1; });