UNPKG

@webdevtoday/grok-cli

Version:

A sophisticated CLI tool for interacting with xAI Grok 4, featuring conversation history, file reference, custom commands, memory system, and genetic development workflows

244 lines 8.2 kB
"use strict"; /** * Hook system implementation for Grok CLI * Compatible with Claude Code hook patterns */ Object.defineProperty(exports, "__esModule", { value: true }); exports.HookManager = void 0; const child_process_1 = require("child_process"); /** * Hook manager handles execution of hooks for various events */ class HookManager { constructor(config) { this.isEnabled = true; this.config = config; } /** * Execute hooks for a given event */ async executeHooks(eventName, event) { if (!this.isEnabled) { return { success: true }; } const eventHooks = this.config.hooks[eventName]; if (!eventHooks || eventHooks.length === 0) { return { success: true }; } const results = []; // Execute all matching hooks in parallel const hookPromises = eventHooks.flatMap(matcher => { if (this.matchesPattern(event, matcher.matcher)) { return matcher.hooks.map(hook => this.executeHookCommand(hook.command, event, hook.timeout || 60)); } return []; }); if (hookPromises.length === 0) { return { success: true }; } try { const hookResults = await Promise.all(hookPromises); results.push(...hookResults); // Process results and determine final outcome return this.processHookResults(results, event); } catch (error) { console.error('Hook execution error:', error); return { success: false, reason: `Hook execution failed: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * Execute a single hook command */ async executeHookCommand(command, event, timeout) { const startTime = Date.now(); return new Promise((resolve) => { const process = (0, child_process_1.spawn)('bash', ['-c', command], { cwd: event.cwd, stdio: ['pipe', 'pipe', 'pipe'], }); let stdout = ''; let stderr = ''; let isTimedOut = false; // Set up timeout const timeoutId = setTimeout(() => { isTimedOut = true; process.kill('SIGTERM'); setTimeout(() => { if (!process.killed) { process.kill('SIGKILL'); } }, 1000); }, timeout * 1000); // Collect output process.stdout?.on('data', (data) => { stdout += data.toString(); }); process.stderr?.on('data', (data) => { stderr += data.toString(); }); // Send hook input via stdin if (process.stdin) { process.stdin.write(JSON.stringify(event)); process.stdin.end(); } process.on('close', (code) => { clearTimeout(timeoutId); const duration = Date.now() - startTime; if (isTimedOut) { resolve({ success: false, exitCode: -1, stdout: '', stderr: `Hook timed out after ${timeout} seconds`, duration, }); } else { resolve({ success: code === 0, exitCode: code || 0, stdout: stdout.trim(), stderr: stderr.trim(), duration, }); } }); process.on('error', (error) => { clearTimeout(timeoutId); resolve({ success: false, exitCode: -1, stdout: '', stderr: `Hook execution error: ${error.message}`, duration: Date.now() - startTime, }); }); }); } /** * Check if event matches the hook pattern */ matchesPattern(event, pattern) { if (!pattern || pattern === '') { return true; // Empty pattern matches all } // For tool-based events, match against tool name if (event.toolName) { try { const regex = new RegExp(pattern, 'i'); return regex.test(event.toolName); } catch { // If regex is invalid, try exact match return event.toolName === pattern; } } // For other events, match against event name or prompt if (event.prompt) { try { const regex = new RegExp(pattern, 'i'); return regex.test(event.prompt); } catch { return false; } } return true; } /** * Process hook results and determine final outcome */ processHookResults(results, _event) { const successful = results.filter(r => r.success); const failed = results.filter(r => !r.success); const blocked = results.filter(r => r.exitCode === 2); // Show successful hook output to user (exit code 0) successful.forEach(result => { if (result.stdout) { console.log(result.stdout); } }); // If any hook blocks (exit code 2), block the operation if (blocked.length > 0) { const blockingErrors = blocked.map(r => r.stderr).filter(err => err).join('\n'); return { success: false, decision: 'block', reason: blockingErrors || 'Hook blocked the operation', }; } // Show non-blocking errors to user (other exit codes) failed.filter(r => r.exitCode !== 2).forEach(result => { if (result.stderr) { console.error(`Hook warning: ${result.stderr}`); } }); // Try to parse JSON output from any hook for advanced control for (const result of results) { if (result.stdout) { try { const jsonOutput = JSON.parse(result.stdout); if (jsonOutput.decision || jsonOutput.continue !== undefined) { return { success: jsonOutput.continue !== false, decision: jsonOutput.decision, reason: jsonOutput.reason, continue: jsonOutput.continue, stopReason: jsonOutput.stopReason, suppressOutput: jsonOutput.suppressOutput, }; } } catch { // Not JSON output, continue } } } return { success: true }; } /** * Enable or disable hook execution */ setEnabled(enabled) { this.isEnabled = enabled; } /** * Check if hooks are enabled */ isHooksEnabled() { return this.isEnabled; } /** * Update hook configuration */ updateConfig(config) { this.config = config; } /** * Get list of hooks for a specific event */ getHooksForEvent(eventName) { const eventHooks = this.config.hooks[eventName]; if (!eventHooks) return []; return eventHooks.flatMap(matcher => matcher.hooks.map(hook => hook.command)); } /** * Create hook event object */ static createHookEvent(eventName, sessionId, cwd, transcriptPath, additional = {}) { return { sessionId, transcriptPath, cwd, hookEventName: eventName, ...additional, }; } } exports.HookManager = HookManager; //# sourceMappingURL=hooks.js.map