#!/usr/bin/env node /** * Skill 加载器 - 从远程 Worker 拉取 prompt (含本地 LRU 缓存 + 离线降级) * 用法: node load-skill.js * 由 stub SKILL.md 通过 Bash 调用, stdout 输出 prompt 给模型消费 */ const fs = require("fs"); const path = require("path"); const os = require("os"); const https = require("https"); const crypto = require("crypto"); const API_BASE = process.env.BW_API_BASE || "https://bookworm-router.bookworm-api.workers.dev"; const CLAUDE_DIR = path.join(os.homedir(), ".claude"); const TOKEN_FILE = path.join(CLAUDE_DIR, ".bw-token"); const CACHE_DIR = path.join(CLAUDE_DIR, ".skill-cache"); const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 小时 function die(msg, code = 1) { process.stderr.write(`[FAIL] ${msg}\n`); process.exit(code); } const skillName = process.argv[2]; if (!skillName || !/^[a-z0-9-]{1,64}$/.test(skillName)) die("usage: load-skill "); function readToken() { if (!fs.existsSync(TOKEN_FILE)) die("未激活, 请先运行: node activate.js"); const d = JSON.parse(fs.readFileSync(TOKEN_FILE, "utf8")); if (Date.now() > d.expires_at - 24 * 60 * 60 * 1000) { process.stderr.write(`[WARN] Token 即将过期, 请重新激活\n`); } if (Date.now() > d.expires_at) die("Token 已过期, 请重新激活"); return d.token; } try { fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 }); } catch {} const cacheFile = path.join(CACHE_DIR, crypto.createHash("sha256").update(skillName).digest("hex").slice(0, 16)); // 先查缓存 function tryCache(fresh) { if (!fs.existsSync(cacheFile)) return null; const st = fs.statSync(cacheFile); if (fresh && Date.now() - st.mtimeMs > CACHE_TTL_MS) return null; return fs.readFileSync(cacheFile, "utf8"); } const fresh = tryCache(true); if (fresh) { process.stdout.write(fresh); process.exit(0); } // 远程拉取 function fetch() { return new Promise((resolve, reject) => { const token = readToken(); const req = https.request({ hostname: new URL(API_BASE).hostname, port: 443, path: `/skill/${skillName}`, method: "POST", timeout: 15000, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "Content-Length": 2 } }, (res) => { let buf = ""; res.on("data", c => buf += c); res.on("end", () => { if (res.statusCode === 200) resolve(buf); else reject(new Error(`HTTP ${res.statusCode}: ${buf.slice(0, 150)}`)); }); }); req.on("error", reject); req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); }); req.write("{}"); req.end(); }); } fetch().then(prompt => { fs.writeFileSync(cacheFile, prompt, { mode: 0o600 }); process.stdout.write(prompt); }).catch(e => { // 离线降级: 用过期缓存 const stale = tryCache(false); if (stale) { process.stderr.write(`[WARN] 离线模式, 使用 stale cache: ${e.message}\n`); process.stdout.write(stale); } else { die(`远程获取失败 + 无缓存: ${e.message}`); } });