/** * Hook run command * * Runs a module hook in isolation for testing/debugging * * Usage: * celilo hook run [--debug] [key=value...] */ import { eq } from 'drizzle-orm'; import { FuelGauge } from '../../cli/fuel-gauge'; import { getDb } from '../../db/client'; import { modules } from '../../db/schema'; import { createConsoleLogger, createGaugeLogger } from '../../hooks/logger'; import { runNamedHook } from '../../hooks/run-named-hook'; import { describeArtifacts } from '../../hooks/types'; import type { HookName } from '../../hooks/types'; import type { ModuleManifest } from '../../manifest/schema'; import { getArg, hasFlag, validateRequiredArgs } from '../parser'; import type { CommandResult } from '../types'; /** * Handle hook run command * * @param args - Command arguments: [module-id, hook-name, ...key=value inputs] * @param flags - Command flags * @returns Command result */ export async function handleHookRun( args: string[], flags: Record, ): Promise { const error = validateRequiredArgs(args, 2); if (error) { return { success: false, error: `${error}\n\nUsage:\n celilo hook run [--debug] [key=value...]`, }; } const moduleId = getArg(args, 0); const hookName = getArg(args, 1); if (!moduleId || !hookName) { return { success: false, error: 'Module ID and hook name are required', }; } const debug = hasFlag(flags, 'debug'); const db = getDb(); // Surface "module not found" / "hook not in manifest" with the same // error messages we used to produce inline. The runNamedHook helper // returns a structured result we can format here. const module = db.select().from(modules).where(eq(modules.id, moduleId)).get(); if (!module) { return { success: false, error: `Module not found: ${moduleId}` }; } const manifest = module.manifestData as ModuleManifest; if (!manifest.hooks?.[hookName as keyof typeof manifest.hooks]) { const available = Object.keys(manifest.hooks || {}); return { success: false, error: `Hook '${hookName}' not found in module '${moduleId}'.\n\nAvailable hooks: ${available.length > 0 ? available.join(', ') : '(none)'}`, }; } // Parse key=value inputs from remaining args const inputs: Record = {}; for (let i = 2; i < args.length; i++) { const arg = args[i]; const eqIndex = arg.indexOf('='); if (eqIndex > 0) { inputs[arg.substring(0, eqIndex)] = arg.substring(eqIndex + 1); } } if (debug) { const logger = createConsoleLogger(moduleId, hookName); const result = await runNamedHook(moduleId, hookName as HookName, db, logger, { debug: true, inputs, }); if (!result.success) { const errorMsg = (result.error || 'Hook execution failed') + describeArtifacts(result.artifactPaths); return { success: false, error: errorMsg }; } return { success: true, message: `Hook '${hookName}' completed in ${result.duration}ms`, data: result.outputs, }; } // Non-debug: use FuelGauge const gauge = new FuelGauge(`Running hook: ${hookName}`); gauge.start(); const logger = createGaugeLogger(gauge, moduleId, hookName); const result = await runNamedHook(moduleId, hookName as HookName, db, logger, { debug: false, inputs, }); if (!result.success) { gauge.stop(false); const errorMsg = (result.error || 'Hook execution failed') + describeArtifacts(result.artifactPaths); return { success: false, error: errorMsg }; } gauge.stop(true); return { success: true, message: `Hook '${hookName}' completed in ${result.duration}ms`, data: result.outputs, }; }