import type { CommandContext, CommandDefinition, CommandResult } from './types.js'; const MAX_INPUT_LENGTH = 2000; export class CommandRegistry { private commands = new Map(); private aliasToCommand = new Map(); register(def: CommandDefinition): void { const canonicalName = this.normalizeName(def.name); if (!canonicalName) { throw new Error('Command name is required'); } if (this.commands.has(canonicalName)) { throw new Error(`Command already registered: ${canonicalName}`); } this.commands.set(canonicalName, { ...def, name: canonicalName }); for (const alias of def.aliases ?? []) { const canonicalAlias = this.normalizeName(alias); if (!canonicalAlias) { continue; } if (canonicalAlias === canonicalName || this.aliasToCommand.has(canonicalAlias) || this.commands.has(canonicalAlias)) { throw new Error(`Command alias already registered: ${canonicalAlias}`); } this.aliasToCommand.set(canonicalAlias, canonicalName); } } get(nameOrAlias: string): CommandDefinition | undefined { const normalized = this.normalizeName(nameOrAlias); if (!normalized) { return undefined; } const canonicalName = this.aliasToCommand.get(normalized) ?? normalized; return this.commands.get(canonicalName); } list(): CommandDefinition[] { return Array.from(this.commands.values()); } isCommand(input: string): boolean { return this.parse(input) !== null; } parse(input: string): { name: string; args: string[] } | null { const trimmed = input.trim(); if (!trimmed.startsWith('/') || trimmed.length > MAX_INPUT_LENGTH) { return null; } const withoutSlash = trimmed.slice(1).trim(); if (!withoutSlash) { return null; } const [rawName, ...rest] = withoutSlash.split(/\s+/); const name = this.normalizeName(rawName); if (!name) { return null; } return { name, args: rest, }; } async execute(input: string, ctx: CommandContext): Promise { const parsed = this.parse(input); if (!parsed) { return { handled: false, text: '' }; } const command = this.get(parsed.name); if (!command) { return { handled: false, text: '' }; } try { return await command.execute(parsed.args, ctx); } catch (error) { const message = error instanceof Error ? error.message : 'Unknown command error'; return { handled: true, text: `Command failed: ${message}`, }; } } private normalizeName(value: string): string { return value.trim().replace(/^\//, '').toLowerCase(); } }