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
JavaScript
;
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