#!/usr/bin/env node // Claude Code hook → feeds the statusline's generic badge seam. // Registered as a PreToolUse hook matching the "Workflow" tool. On a workflow // launch it appends a self-expiring badge line to ~/.claude/.cc-statusline-extra // which statusline.js renders (and ages out via CONFIG.extraTtlMs). // // File format: one "|" per line. We prune expired lines on // every write so the file never grows unbounded. // // Contract: read hook JSON from stdin, exit 0, emit nothing to stdout (no // context injection). Never throw in a way that blocks the tool. "use strict"; const fs = require("fs"); const os = require("os"); const path = require("path"); const TTL_MS = 120000; // must match statusline CONFIG.extraTtlMs const MAX_LINES = 10; try { const raw = fs.readFileSync(0, "utf8"); const h = raw && raw.trim() ? JSON.parse(raw) : {}; const claudeDir0 = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude"); // Turn boundary: drop a per-session marker the statusline consumes to // rebaseline its "turn cost" (Δ since this prompt). if (h.hook_event_name === "UserPromptSubmit") { try { const sid = String(h.session_id || "default").replace(/[^A-Za-z0-9._-]/g, ""); fs.writeFileSync(path.join(claudeDir0, `.cc-turn-${sid}`), ""); } catch {} } // Only act on a Workflow tool launch. if (h.hook_event_name === "PreToolUse" && h.tool_name === "Workflow") { const ti = h.tool_input || {}; // Predefined workflow → ti.name. Inline script → meta.name inside ti.script. let name = typeof ti.name === "string" ? ti.name : ""; if (!name && typeof ti.script === "string") { const m = ti.script.match(/name\s*:\s*['"]([^'"]+)['"]/); if (m) name = m[1]; } name = (name || "workflow").replace(/[\x00-\x1F|]/g, "").slice(0, 40); const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude"); const f = path.join(claudeDir, ".cc-statusline-extra"); const now = Date.now(); // Keep only non-expired existing lines, then append ours. let kept = []; try { kept = fs .readFileSync(f, "utf8") .split(/\r?\n/) .filter((l) => { const i = l.indexOf("|"); if (i <= 0) return false; const ts = Number(l.slice(0, i)); return Number.isFinite(ts) && now - ts <= TTL_MS; }); } catch {} kept.push(`${now}|⚙ wf:${name}`); if (kept.length > MAX_LINES) kept = kept.slice(-MAX_LINES); const tmp = `${f}.${process.pid}.tmp`; fs.writeFileSync(tmp, kept.join("\n") + "\n"); fs.renameSync(tmp, f); } } catch { // Swallow everything — a statusline-feeder hook must never block a tool. } process.exit(0);