import type { Command } from './types.js'; import type { CommandRunner, CommandRunnerContext, PromptEvent } from './command-runner.js'; import { parseCommand } from './command-parse.js'; import { applyCommandSchema } from './command-apply.js'; import { parseCommandSchema } from './schema-parse.js'; export type CommandRegistry = Map>; export function createCommandRegistry(): CommandRegistry { return new Map(); } export function registerCommand( registry: CommandRegistry, runner: CommandRunner ): void { registry.set(runner.schema.name, runner as CommandRunner); } export function unregisterCommand( registry: CommandRegistry, name: string ): void { registry.delete(name); } export function hasCommand( registry: CommandRegistry, name: string ): boolean { return registry.has(name); } export function getCommand( registry: CommandRegistry, name: string ): CommandRunner | undefined { return registry.get(name); } type Listener = (e: PromptEvent) => void; export type CommandRunnerContextExport = CommandRunnerContext & { registry: CommandRegistry; }; export function createCommandRunnerContext( registry: CommandRegistry, context: TContext ): CommandRunnerContextExport { const listeners = new Set(); const on = (_event: 'prompt', listener: Listener) => { listeners.add(listener); }; const off = (_event: 'prompt', listener: Listener) => { listeners.delete(listener); }; const prompt = (schema: Parameters['prompt']>[0]): Promise => { const resolvedSchema = typeof schema === 'string' ? parseCommandSchema(schema) : schema; return new Promise((resolve, reject) => { const event: PromptEvent = { schema: resolvedSchema, resolve, reject }; for (const listener of listeners) { listener(event); } }); }; const runnerCtx: CommandRunnerContextExport = { registry, context, run: (input: string) => runCommandWithContext(registry, runnerCtx, input), runParsed: (command: Command) => runCommandParsedWithContext(registry, runnerCtx, command), prompt, on, off, }; return runnerCtx; } async function executeWithRunnerContext( runnerCtx: CommandRunnerContextExport, runner: CommandRunner, command: Command ): Promise<{ success: true; result: unknown } | { success: false; error: string }> { try { const result = await runner.run.call(runnerCtx, command); return { success: true, result }; } catch (e) { const error = e as Error; return { success: false, error: error.message }; } } export async function runCommand( registry: CommandRegistry, context: TContext, input: string ): Promise<{ success: true; result: unknown } | { success: false; error: string }> { const runnerCtx = createCommandRunnerContext(registry, context); return await runCommandWithContext(registry, runnerCtx, input); } async function runCommandWithContext( registry: CommandRegistry, runnerCtx: CommandRunnerContextExport, input: string ): Promise<{ success: true; result: unknown } | { success: false; error: string }> { const command = parseCommand(input); return await runCommandParsedWithContext(registry, runnerCtx, command); } export async function runCommandParsed( registry: CommandRegistry, context: TContext, command: Command ): Promise<{ success: true; result: unknown } | { success: false; error: string }> { const runnerCtx = createCommandRunnerContext(registry, context); return await runCommandParsedWithContext(registry, runnerCtx, command); } async function runCommandParsedWithContext( registry: CommandRegistry, runnerCtx: CommandRunnerContextExport, command: Command ): Promise<{ success: true; result: unknown } | { success: false; error: string }> { const runner = registry.get(command.name); if (!runner) { return { success: false, error: `Unknown command: ${command.name}` }; } const validationResult = applyCommandSchema(command, runner.schema); if (!validationResult.valid) { return { success: false, error: validationResult.errors.join('; ') }; } return await executeWithRunnerContext(runnerCtx, runner, validationResult.command); }