@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
JavaScript
;
/**
* 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