#!/usr/bin/env node 'use strict'; const fs = require('fs'); const os = require('os'); const path = require('path'); const MAX_STDIN = 1024 * 1024; let raw = ''; const MODE_CONFIG = { audit: { fileName: 'bash-commands.log', format: command => `[${new Date().toISOString()}] ${command}`, }, cost: { fileName: 'cost-tracker.log', format: command => `[${new Date().toISOString()}] tool=Bash command=${command}`, }, }; function sanitizeCommand(command) { return String(command || '') .replace(/\n/g, ' ') .replace(/--token[= ][^ ]*/g, '--token=') .replace(/Authorization:[: ]*[^ ]*[: ]*[^ ]*/gi, 'Authorization:') .replace(/\bAKIA[A-Z0-9]{16}\b/g, '') .replace(/\bASIA[A-Z0-9]{16}\b/g, '') .replace(/password[= ][^ ]*/gi, 'password=') .replace(/\bghp_[A-Za-z0-9_]+\b/g, '') .replace(/\bgho_[A-Za-z0-9_]+\b/g, '') .replace(/\bghs_[A-Za-z0-9_]+\b/g, '') .replace(/\bgithub_pat_[A-Za-z0-9_]+\b/g, ''); } function appendLine(filePath, line) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.appendFileSync(filePath, `${line}\n`, 'utf8'); } // Resolve the Claude profile directory the same way everywhere. // // Duplicated rather than imported on purpose: hooks ship in managed installs // that carry top-level `scripts/` without `scripts/lib/` (see #3259), so a // require into lib/ would make the hook unloadable there. `~` is expanded the // way `scripts/lib/agent-data-home.js` expands it, so a profile written as // `~/.claude-work` resolves to one place rather than two. function claudeConfigDir() { const configured = process.env.CLAUDE_CONFIG_DIR; const trimmed = configured ? String(configured).trim() : ''; if (!trimmed) return path.join(os.homedir(), '.claude'); if (trimmed.startsWith('~')) { const remainder = trimmed.slice(1).replace(/^[/\\]+/, ''); return remainder ? path.join(os.homedir(), remainder) : os.homedir(); } return path.resolve(trimmed); } function run(rawInput, mode = 'audit') { const config = MODE_CONFIG[mode]; try { if (config) { const input = String(rawInput || '').trim() ? JSON.parse(String(rawInput)) : {}; const command = sanitizeCommand(input.tool_input?.command || '?'); appendLine(path.join(claudeConfigDir(), config.fileName), config.format(command)); } } catch { // Logging must never block the calling hook. } return typeof rawInput === 'string' ? rawInput : JSON.stringify(rawInput); } function main() { const mode = process.argv[2]; process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => { if (raw.length < MAX_STDIN) { const remaining = MAX_STDIN - raw.length; raw += chunk.substring(0, remaining); } }); process.stdin.on('end', () => { process.stdout.write(run(raw, mode)); }); } if (require.main === module) { main(); } module.exports = { run, sanitizeCommand, };