#!/usr/bin/env node const { Command } = require('commander'); const chalk = require('chalk'); const path = require('path'); const fs = require('fs-extra'); const os = require('os'); const program = new Command(); const packageRoot = path.dirname(__dirname); // Helper to create hooks configuration async function createHooksConfig() { const claudeConfigDir = path.join(os.homedir(), '.config', 'claude'); const hooksConfigFile = path.join(claudeConfigDir, 'hooks.json'); await fs.ensureDir(claudeConfigDir); const hooksConfig = { hooks: { UserPromptSubmit: `node ${path.join(packageRoot, 'hooks', 'context-monitor.js')}`, PreToolUse: { Bash: `node ${path.join(packageRoot, 'hooks', 'command-validator.js')}` }, PostToolUse: { '*': `node ${path.join(packageRoot, 'hooks', 'session-logger.js')}` }, Stop: `node ${path.join(packageRoot, 'hooks', 'session-finalizer.js')}` } }; await fs.writeJson(hooksConfigFile, hooksConfig, { spaces: 2 }); return hooksConfigFile; } // Helper to test a hook script async function testHookScript(scriptName, testInput) { return new Promise((resolve) => { const { spawn } = require('child_process'); const scriptPath = path.join(packageRoot, 'hooks', scriptName); const child = spawn('node', [scriptPath], { stdio: ['pipe', 'pipe', 'pipe'], cwd: packageRoot }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.stdin.write(JSON.stringify(testInput)); child.stdin.end(); const timeout = setTimeout(() => { child.kill(); resolve({ success: false, error: 'Timeout' }); }, 10000); child.on('close', (code) => { clearTimeout(timeout); resolve({ success: code === 0, stdout: stdout.trim(), stderr: stderr.trim() }); }); child.on('error', (error) => { clearTimeout(timeout); resolve({ success: false, error: error.message }); }); }); } // Helper to check installation status async function checkStatus() { const hooksConfig = path.join(os.homedir(), '.config', 'claude', 'hooks.json'); const hasHooks = await fs.pathExists(hooksConfig); console.log(chalk.blue('Claude Hooks Status:')); console.log(hasHooks ? chalk.green('✓ Hooks configuration installed') : chalk.red('✗ No hooks configuration found')); console.log(chalk.green('✓ Node.js runtime available')); return hasHooks; } // Helper to remove hooks configuration async function removeHooksConfig() { const hooksConfig = path.join(os.homedir(), '.config', 'claude', 'hooks.json'); if (await fs.pathExists(hooksConfig)) { await fs.remove(hooksConfig); return true; } return false; } program .name('claude-hooks') .description('Intelligent hooks system for Claude Code') .version(require('../package.json').version); program .command('init') .description('Initialize Claude Hooks (run after npm install)') .option('--force', 'Force reinstallation even if already configured') .option('--auto-setup', 'Auto-setup during npm postinstall') .option('--quiet', 'Minimal output') .action(async (options) => { if (!options.quiet) { console.log(chalk.blue('Initializing Claude Hooks...')); } try { // Check for existing installation if (!options.force && await checkStatus()) { if (!options.quiet) { console.log(chalk.yellow('⚠️ Claude Hooks already configured. Use --force to reinstall.')); } return; } // Create runtime directories const runtimeDir = path.join(packageRoot, '.claude_hooks'); await fs.ensureDir(path.join(runtimeDir, 'backups')); await fs.ensureDir(path.join(runtimeDir, 'logs')); await fs.ensureDir(path.join(runtimeDir, 'patterns')); // Create hooks configuration const configFile = await createHooksConfig(); if (!options.quiet) { console.log(chalk.green('✅ Claude Hooks initialized successfully!')); console.log(chalk.blue('Configuration file:'), configFile); console.log(chalk.blue('Next steps:')); console.log('1. Restart Claude Code to activate hooks'); console.log('2. Try: claude-hooks test'); } } catch (error) { console.error(chalk.red('❌ Initialization failed:'), error.message); process.exit(1); } }); program .command('status') .description('Show Claude Hooks installation status') .action(async () => { await checkStatus(); }); program .command('test') .description('Run Claude Hooks tests') .action(async () => { console.log(chalk.blue('Running Claude Hooks tests...')); let passed = 0; let total = 0; // Test 1: Check configuration total++; console.log('Testing configuration...'); if (await checkStatus()) { console.log(chalk.green('✓ Configuration exists')); passed++; } else { console.log(chalk.red('✗ Configuration missing')); } // Test 2: Test hook scripts const hookTests = [ { name: 'context-monitor.js', input: { prompt: 'test prompt', context_size: 1000 } }, { name: 'command-validator.js', input: { tool: 'Bash', parameters: { command: 'pip install requests' } } }, { name: 'session-logger.js', input: { tool: 'Bash', parameters: { command: 'echo test' }, success: true } }, { name: 'session-finalizer.js', input: {} } ]; for (const test of hookTests) { total++; console.log(`Testing ${test.name}...`); const result = await testHookScript(test.name, test.input); if (result.success) { console.log(chalk.green(`✓ ${test.name} working`)); passed++; } else { console.log(chalk.red(`✗ ${test.name} failed: ${result.error || result.stderr}`)); } } // Summary console.log(); if (passed === total) { console.log(chalk.green(`✅ All ${total} tests passed!`)); console.log(chalk.green('Claude Hooks is ready to use!')); } else { console.log(chalk.yellow(`⚠️ ${passed}/${total} tests passed`)); console.log(chalk.blue('Try: claude-hooks init --force')); process.exit(1); } }); program .command('uninstall') .description('Remove Claude Hooks configuration') .option('--quiet', 'Minimal output') .action(async (options) => { if (!options.quiet) { console.log(chalk.blue('Uninstalling Claude Hooks...')); } try { const removed = await removeHooksConfig(); if (removed) { if (!options.quiet) { console.log(chalk.green('✅ Claude Hooks configuration removed')); console.log(chalk.yellow('Note: To completely remove the package: npm uninstall -g claude-hooks')); } } else { if (!options.quiet) { console.log(chalk.yellow('⚠️ No Claude Hooks configuration found')); } } } catch (error) { console.error(chalk.red('❌ Uninstall failed:'), error.message); process.exit(1); } }); program .command('backup') .description('Manually trigger a backup') .action(async () => { console.log(chalk.blue('Creating manual backup...')); try { // Import backup manager const { BackupManager } = require(path.join(packageRoot, 'lib', 'backup-manager')); const backupManager = new BackupManager(); const backupId = await backupManager.createBackup(process.cwd(), { trigger: 'manual', timestamp: new Date().toISOString() }); if (backupId) { console.log(chalk.green(`✅ Backup created: ${backupId}`)); } else { console.log(chalk.red('❌ Backup failed')); process.exit(1); } } catch (error) { console.error(chalk.red('❌ Backup failed:'), error.message); process.exit(1); } }); program.parse();