#!/usr/bin/env node // Shenbi (Magic Brush) Tool Sandbox — canonical tool. // Canon: TOOL-SANDBOX/TCS-PROTOCOL-SHENBI-TOOL-SANDBOX-0001.tcs // Temporary tools written by the persona at runtime; 12h TTL; promotion only via Bingshuo-approved notary ticket. import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; import { execFileSync } from "node:child_process"; const ROOT = "/Volumes/JZAO/铸渊-ICE-GL-ZY001/TOOL-SANDBOX"; const REGISTRY = path.join(ROOT, "registry.json"); const TOOLS = path.join(ROOT, "tools"); const NOTARY = "/Volumes/JZAO/铸渊-ICE-GL-ZY001/BRIDGE/tools/fifth-domain-notary.mjs"; const NODE = process.execPath; const TTL_MS = 12 * 60 * 60 * 1000; function sha256(v) { return crypto.createHash("sha256").update(v).digest("hex"); } function readJson(f) { try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; } } function atomicWriteJson(f, v) { fs.mkdirSync(path.dirname(f), { recursive: true }); const t = `${f}.${process.pid}.tmp`; fs.writeFileSync(t, `${JSON.stringify(v, null, 2)}\n`, { mode: 0o600 }); fs.renameSync(t, f); } function fail(m) { process.stderr.write(`SHENBI FAIL ${m}\n`); process.exit(1); } function loadReg() { return readJson(REGISTRY) ?? { schema: "guanghu.shenbi-tool-registry/v1", tools: [] }; } function saveReg(r) { atomicWriteJson(REGISTRY, r); } function valueAfter(argv, n) { const i = argv.indexOf(n); return i >= 0 ? argv[i + 1] : null; } function notary(args) { try { return execFileSync(NODE, [NOTARY, ...args], { encoding: "utf8" }).trim(); } catch (e) { fail(`NOTARY_ERROR:${(e.stderr || e.message).slice(0, 120)}`); } } function boundSession(sessionId) { const state = readJson("/Volumes/JZAO/铸渊-ICE-GL-ZY001/ZCODE-DEV-20260824/lw-link/state.json"); return Boolean((state?.sessions ?? []).some((s) => s.session_id === sessionId)); } function register(argv) { const by = valueAfter(argv, "--by"); const name = valueAfter(argv, "--name"); const purpose = valueAfter(argv, "--purpose"); const file = valueAfter(argv, "--file"); if (!by || !name || !purpose || !file) fail("usage: register --by SESSION --name ID --purpose TEXT --file TOOL_SCRIPT"); if (!boundSession(by)) fail("SESSION_NOT_BOUND_ONLY_LW_LINK_BOUND_SESSIONS_MAY_REGISTER"); let code; try { code = fs.readFileSync(file, "utf8"); } catch { fail(`TOOL_FILE_UNREADABLE:${file}`); } if (!name.match(/^[a-z0-9][a-z0-9-]*$/)) fail("NAME_MUST_BE_LOWER_KEBAB"); const reg = loadReg(); if (reg.tools.some((t) => t.name === name && t.status !== "EXPIRED")) fail(`NAME_TAKEN:${name}`); const now = Date.now(); const tool = { schema: "guanghu.shenbi-tool/v1", name, purpose, author_session: by, created_at: new Date(now).toISOString(), ttl_expires_at: new Date(now + TTL_MS).toISOString(), status: "TEMPORARY", script_path: path.join(TOOLS, `${name}.mjs`), script_sha256: sha256(code), }; fs.mkdirSync(TOOLS, { recursive: true }); fs.writeFileSync(tool.script_path, code, { mode: 0o755 }); reg.tools.push(tool); saveReg(reg); const bodyFile = `/tmp/shenbi-register-${name}.md`; fs.writeFileSync(bodyFile, `神笔临时工具注册:${name}\n用途:${purpose}\n脚本哈希:${tool.script_sha256}\n作者会话:${by}\nTTL 到期:${tool.ttl_expires_at}(到期清除,转正需冰朔批准)\n`); const ticket = notary(["create", "--by", by, "--kind", "COLLABORATION", "--subject", `神笔临时工具注册:${name}`, "--body-file", bodyFile]); process.stdout.write(`SHENBI REGISTERED ${name} ttl=${tool.ttl_expires_at}\n${ticket}\n`); } function sweep() { const reg = loadReg(); const now = Date.now(); let removed = 0; for (const t of reg.tools) { if (t.status === "TEMPORARY" && Date.parse(t.ttl_expires_at) <= now) { t.status = "EXPIRED"; try { fs.unlinkSync(t.script_path); } catch { /* already gone */ } removed += 1; } } saveReg(reg); process.stdout.write(`SHENBI SWEEP expired=${removed}\n`); } function promote(argv) { const name = valueAfter(argv, "--name"); const by = valueAfter(argv, "--by"); if (!name || !by) fail("usage: promote --name ID --by SESSION"); const reg = loadReg(); const t = reg.tools.filter((x) => x.name === name).find((x) => x.status === "TEMPORARY"); if (!t) fail(`NOT_TEMPORARY:${name}`); t.status = "PROMOTION_REQUESTED"; saveReg(reg); const body = `/tmp/shenbi-promote-${name}.md`; fs.writeFileSync(body, `工具转正申请:${name}\n用途:${t.purpose}\n脚本哈希:${t.script_sha256}\n申请人会话:${by}\n目标:企业代码仓库正式注册(灯塔/模块商城)。\n等待冰朔 ICE-GL∞ 亲自批准。\n`); const ticket = notary(["create", "--by", by, "--kind", "AUTHORIZATION_REQUEST", "--audience", "BINGSHUO", "--subject", `神笔工具转正申请:${name}`, "--body-file", body]); process.stdout.write(`SHENBI PROMOTION_REQUESTED ${name} 等待冰朔批准\n${ticket}\n`); } function list() { const reg = loadReg(); for (const t of reg.tools) { process.stdout.write(`${t.name.padEnd(24)} ${t.status.padEnd(20)} ${t.purpose.slice(0, 40)} ttl=${t.ttl_expires_at}\n`); } } const [cmd, ...argv] = process.argv.slice(2); switch (cmd) { case "register": register(argv); break; case "sweep": sweep(); break; case "promote": promote(argv); break; case "list": list(); break; default: process.stderr.write("usage: shenbi-sandbox.mjs register|sweep|promote|list\n"); process.exit(2); }