#!/usr/bin/env node "use strict"; // Claude Code hook → Claude Pet // 讀 stdin 的 hook JSON,POST 到本機的寵物 app。任何失敗都靜默結束(exit 0),絕不阻塞 Claude Code。 const http = require("node:http"); const PORT = Number(process.env.CLAUDE_PET_PORT || 17333); const TIMEOUT_MS = 800; function trim(value, max) { if (typeof value !== "string") return undefined; return value.length > max ? `${value.slice(0, max)}…` : value; } function readStdin() { return new Promise((resolve) => { if (process.stdin.isTTY) return resolve({}); let data = ""; const timer = setTimeout(() => resolve(parse(data)), TIMEOUT_MS); process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk) => { data += chunk; }); process.stdin.on("end", () => { clearTimeout(timer); resolve(parse(data)); }); process.stdin.on("error", () => { clearTimeout(timer); resolve(parse(data)); }); }); } function parse(text) { try { return text.trim() ? JSON.parse(text) : {}; } catch { return {}; } } function buildPayload(input) { const error = typeof input.error === "string" ? input.error : input.error?.message; return { event: input.hook_event_name || process.argv[2] || "Unknown", sessionId: input.session_id ?? null, cwd: input.cwd ?? null, permissionMode: input.permission_mode ?? null, toolName: input.tool_name ?? null, notificationType: input.notification_type ?? null, title: trim(input.title, 160), message: trim(input.message, 240), error: trim(error, 240), source: input.source ?? null, reason: input.reason ?? null, trigger: input.trigger ?? null, stopHookActive: input.stop_hook_active ?? null, lastAssistantMessage: trim(input.last_assistant_message, 300), prompt: trim(input.prompt, 120), ts: Date.now(), }; } function post(payload) { return new Promise((resolve) => { const body = Buffer.from(JSON.stringify(payload), "utf8"); const req = http.request( { host: "127.0.0.1", port: PORT, method: "POST", path: "/event", headers: { "content-type": "application/json", "content-length": body.length }, }, (res) => { res.resume(); res.on("end", resolve); res.on("error", resolve); }, ); req.setTimeout(TIMEOUT_MS, () => req.destroy()); req.on("error", resolve); req.end(body); }); } (async () => { const guard = setTimeout(() => process.exit(0), TIMEOUT_MS * 2 + 500); guard.unref(); try { const input = await readStdin(); await post(buildPayload(input)); } catch { // ignore } process.exit(0); })();