Development
Hook
Hooks
What claudestat runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Where it lives
- hooks/event.jsGitHub
Read the script
#!/usr/bin/env node /** * Hook universal de claudetrace. * Claude Code lo ejecuta en cada evento del ciclo de vida. * Recibe el JSON del evento por stdin y lo reenvía al daemon. * * Uso: node event.js <TipoEvento> * Ejemplo: node event.js PreToolUse * * Kill switch (Phase 6): * Para PreToolUse, después de enviar el evento al daemon, consulta GET /kill-switch. * Si el daemon responde { blocked: true }, este hook termina con exit(2), * lo que hace que Claude Code cancele la acción del tool antes de ejecutarla. */ const eventType = process.argv[2] || 'Unknown' const fs = require('fs') const os = require('os') // Read port from ~/.claudestat/port (written by daemon on startup). Fallback: 7337. let DAEMON_PORT = 7337 try { const portFile = require('path').join(os.homedir(), '.claudestat', 'port') const raw = fs.readFileSync(portFile, 'utf8').trim() const parsed = parseInt(raw, 10) if (!isNaN(parsed) && parsed >= 1024 && parsed <= 65535) DAEMON_PORT = parsed } catch {} const DAEMON_URL = `http://localhost:${DAEMON_PORT}/event` let rawData = '' process.stdin.on('data', chunk => { rawData += chunk }) process.stdin.on('end', () => { let hookData = {} try { hookData = JSON.parse(rawData) } catch (_) {} const payload = { type: eventType, ts: Date.now(), ...hookData } // Para PreToolUse: enviamos el evento Y leemos el archivo de pausa local. // Si existe pause.signal, mostramos warning y salimos según killSwitchForce. if (eventType === 'PreToolUse') { fetch(DAEMON_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: AbortSignal.timeout(1500), }).catch(() => null) // Check pause signal file (written by daemon when quota threshold reached) const signalFile = require('path').join(os.homedir(), '.claudestat', 'pause.signal') let signalMsg = null try { signalMsg = fs.readFileSync(signalFile, 'utf8').trim() } catch {} if (signalMsg) { process.stderr.write(`\n⚠️ claudestat — quota warning\n`) process.stderr.write(` ${signalMsg}\n`) // Read config to check killSwitchForce let forceBlock = false try { const cfgPath = require('path').join(os.homedir(), '.claudestat', 'config.json') const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')) forceBlock = cfg.killSwitchForce === true } catch {} if (forceBlock) { process.stderr.write(` Blocked (killSwitchForce is enabled). Run: claudestat resume\n\n`) process.exit(2) } else { process.stderr.write(` Continuing. To pause manually: claudestat resume (removes signal)\n\n`) process.exit(0) } } else { process.exit(0) } } else { // Para todos los demás tipos (SessionStart, PostToolUse, Stop): // enviar el evento y siempre salir con 0 — NUNCA bloquear a Claude. fetch(DAEMON_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: AbortSignal.timeout(2000), }) .catch(() => {}) // error silencioso .finally(() => process.exit(0)) } })
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withclaudestat
Real-time observability for AI coding agents — Claude Code & OpenCode Monitor tokens, costs, tool calls, agents, MCP, sessions, quotas and multi-agent orchestration locally. Most tools read your logs after a session ends.
Get the whole plugin
Stats
34
Stars
5
Forks
Active
Maintenance
TypeScript
Language
MIT
License
22d ago
Last commit
4mo ago
Created
Repo: DeibyGS/claudestat

