repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
claude/cc-statusline-hook.js
Raw#!/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 "<epochMillis>|<text>" 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);