UNPKG

context-forge

Version:

AI orchestration platform with autonomous teams, enhancement planning, migration tools, 25+ slash commands, checkpoints & hooks. Multi-IDE: Claude, Cursor, Windsurf, Cline, Copilot

390 lines 14.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HookManager = void 0; const events_1 = require("events"); const child_process_1 = require("child_process"); const util_1 = require("util"); const path_1 = __importDefault(require("path")); const fs_extra_1 = __importDefault(require("fs-extra")); const chalk_1 = __importDefault(require("chalk")); const execAsync = (0, util_1.promisify)(child_process_1.exec); class HookManager extends events_1.EventEmitter { constructor(hookDirectory = path_1.default.join(process.env.HOME || '', '.claude', 'hooks')) { super(); this.hooks = new Map(); this.hookDirectory = hookDirectory; } /** * Initialize hook manager and scan for hooks */ async initialize() { console.log(chalk_1.default.blue('Initializing Hook Manager...')); if (!(await fs_extra_1.default.pathExists(this.hookDirectory))) { console.log(chalk_1.default.yellow(`Hook directory doesn't exist: ${this.hookDirectory}`)); return; } await this.scanHooks(); await this.validateHooks(); } /** * Scan directory for hooks */ async scanHooks() { try { const files = await fs_extra_1.default.readdir(this.hookDirectory); for (const file of files) { const filePath = path_1.default.join(this.hookDirectory, file); const stat = await fs_extra_1.default.stat(filePath); if (!stat.isFile()) continue; let type; let enabled = true; if (file.endsWith('.py')) { type = 'python'; // Check if Python hook is executable try { await fs_extra_1.default.access(filePath, fs_extra_1.default.constants.X_OK); } catch { console.log(chalk_1.default.yellow(`Python hook not executable: ${file}`)); enabled = false; } } else if (file.endsWith('.js') || file.endsWith('.mjs')) { type = 'javascript'; } else if (file.endsWith('.sh')) { type = 'shell'; } else { continue; // Skip unsupported file types } const config = { name: path_1.default.basename(file, path_1.default.extname(file)), type, path: filePath, enabled, timeout: 30000, // 30 seconds default }; this.hooks.set(config.name, config); console.log(chalk_1.default.gray(`Found ${type} hook: ${config.name} (${enabled ? 'enabled' : 'disabled'})`)); } } catch (error) { console.error(chalk_1.default.red(`Failed to scan hooks: ${error}`)); } } /** * Validate hooks can execute */ async validateHooks() { for (const [name, config] of this.hooks) { if (!config.enabled) continue; try { switch (config.type) { case 'python': await this.validatePythonHook(config); break; case 'javascript': await this.validateJavaScriptHook(config); break; case 'shell': await this.validateShellHook(config); break; } } catch (error) { console.log(chalk_1.default.red(`Hook validation failed for ${name}: ${error}`)); config.enabled = false; } } } /** * Validate Python hook */ async validatePythonHook(config) { // Check if Python is available try { await execAsync('python3 --version'); } catch { throw new Error('Python3 not available'); } // Check shebang const content = await fs_extra_1.default.readFile(config.path, 'utf-8'); if (!content.startsWith('#!/usr/bin/env python3') && !content.startsWith('#!/usr/bin/python3')) { console.log(chalk_1.default.yellow(`Python hook ${config.name} missing proper shebang`)); } // Test if hook responds to --help (timeout quickly) try { await Promise.race([ execAsync(`python3 "${config.path}" --help`), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000)), ]); console.log(chalk_1.default.green(`Python hook ${config.name} validated`)); } catch (error) { if (error instanceof Error && error.message === 'timeout') { console.log(chalk_1.default.yellow(`Python hook ${config.name} doesn't respond to --help (may still work)`)); } else { throw error; } } } /** * Validate JavaScript hook */ async validateJavaScriptHook(config) { // Try to require/import the hook try { delete require.cache[config.path]; // Clear cache const hookModule = await Promise.resolve(`${config.path}`).then(s => __importStar(require(s))); if (!hookModule) { throw new Error('Hook module not found'); } console.log(chalk_1.default.green(`JavaScript hook ${config.name} validated`)); } catch (error) { throw new Error(`Failed to load: ${error instanceof Error ? error.message : String(error)}`); } } /** * Validate shell hook */ async validateShellHook(config) { // Check if file is executable try { await fs_extra_1.default.access(config.path, fs_extra_1.default.constants.X_OK); console.log(chalk_1.default.green(`Shell hook ${config.name} validated`)); } catch { throw new Error('Not executable'); } } /** * Execute a hook */ async executeHook(hookName, args = []) { const config = this.hooks.get(hookName); if (!config) { return { success: false, error: `Hook not found: ${hookName}`, duration: 0, }; } if (!config.enabled) { return { success: false, error: `Hook disabled: ${hookName}`, duration: 0, }; } const startTime = Date.now(); try { switch (config.type) { case 'python': return await this.executePythonHook(config, args); case 'javascript': return await this.executeJavaScriptHook(config, args); case 'shell': return await this.executeShellHook(config, args); default: return { success: false, error: `Unsupported hook type: ${config.type}`, duration: Date.now() - startTime, }; } } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime, }; } } /** * Execute Python hook */ async executePythonHook(config, args) { const startTime = Date.now(); return new Promise((resolve) => { const child = (0, child_process_1.spawn)('python3', [config.path, ...args], { stdio: ['pipe', 'pipe', 'pipe'], timeout: config.timeout, }); let output = ''; let error = ''; child.stdout?.on('data', (data) => { output += data.toString(); }); child.stderr?.on('data', (data) => { error += data.toString(); }); child.on('close', (exitCode) => { const duration = Date.now() - startTime; resolve({ success: exitCode === 0, output: output.trim(), error: error.trim() || undefined, exitCode: exitCode || 0, duration, }); }); child.on('error', (err) => { const duration = Date.now() - startTime; resolve({ success: false, error: err.message, duration, }); }); // Handle timeout setTimeout(() => { if (!child.killed) { child.kill(); const duration = Date.now() - startTime; resolve({ success: false, error: `Hook timeout after ${config.timeout}ms`, duration, }); } }, config.timeout); }); } /** * Execute JavaScript hook */ async executeJavaScriptHook(config, args) { const startTime = Date.now(); try { // Clear require cache delete require.cache[config.path]; const hookModule = await Promise.resolve(`${config.path}`).then(s => __importStar(require(s))); const hookFunction = hookModule.default || hookModule; if (typeof hookFunction !== 'function') { throw new Error('Hook must export a function'); } const result = await Promise.race([ hookFunction(...args), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), config.timeout)), ]); return { success: true, output: typeof result === 'string' ? result : JSON.stringify(result), duration: Date.now() - startTime, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime, }; } } /** * Execute shell hook */ async executeShellHook(config, args) { const startTime = Date.now(); try { const command = `"${config.path}" ${args.map((arg) => `"${arg}"`).join(' ')}`; const { stdout, stderr } = await execAsync(command, { timeout: config.timeout, cwd: path_1.default.dirname(config.path), }); return { success: true, output: stdout.trim(), error: stderr.trim() || undefined, duration: Date.now() - startTime, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error), exitCode: error.code, duration: Date.now() - startTime, }; } } /** * Get hook status */ getHookStatus() { return Array.from(this.hooks.values()).map((config) => ({ name: config.name, type: config.type, enabled: config.enabled, path: config.path, })); } /** * Enable/disable hook */ setHookEnabled(hookName, enabled) { const config = this.hooks.get(hookName); if (!config) return false; config.enabled = enabled; console.log(chalk_1.default.blue(`Hook ${hookName} ${enabled ? 'enabled' : 'disabled'}`)); return true; } /** * Disable all Python hooks (for troubleshooting) */ disablePythonHooks() { for (const [name, config] of this.hooks) { if (config.type === 'python') { config.enabled = false; console.log(chalk_1.default.yellow(`Disabled Python hook: ${name}`)); } } } } exports.HookManager = HookManager; //# sourceMappingURL=hookManager.js.map