import type { CommandDef, CommandManifest } from '@mosaic/types'; // Local-only commands (work even when gateway is disconnected) const LOCAL_COMMANDS: CommandDef[] = [ { name: 'help', description: 'Show available commands', aliases: ['h'], args: undefined, execution: 'local', available: true, scope: 'core', }, { name: 'stop', description: 'Cancel current streaming response', aliases: [], args: undefined, execution: 'local', available: true, scope: 'core', }, { name: 'cost', description: 'Show token usage and cost for current session', aliases: [], args: undefined, execution: 'local', available: true, scope: 'core', }, { name: 'status', description: 'Show connection and session status', aliases: ['s'], args: undefined, execution: 'local', available: true, scope: 'core', }, { name: 'history', description: 'Show conversation message count and context usage', aliases: ['hist'], args: undefined, execution: 'local', available: true, scope: 'core', }, { name: 'clear', description: 'Clear the current conversation display', aliases: [], args: undefined, execution: 'local', available: true, scope: 'core', }, { name: 'attach', description: 'Attach a file to the next message (@file syntax also works inline)', aliases: [], args: [ { name: 'path', type: 'string' as const, optional: false, description: 'File path to attach', }, ], execution: 'local', available: true, scope: 'core', }, { name: 'new', description: 'Start a new conversation', aliases: ['n'], args: undefined, execution: 'local', available: true, scope: 'core', }, ]; const ALIASES: Record = { m: 'model', t: 'thinking', a: 'agent', s: 'status', h: 'help', hist: 'history', pref: 'preferences', }; export class CommandRegistry { private gatewayManifest: CommandManifest | null = null; updateManifest(manifest: CommandManifest): void { this.gatewayManifest = manifest; } resolveAlias(command: string): string { return ALIASES[command] ?? command; } find(command: string): CommandDef | null { const resolved = this.resolveAlias(command); // Search local first, then gateway manifest const local = LOCAL_COMMANDS.find((c) => c.name === resolved || c.aliases.includes(resolved)); if (local) return local; if (this.gatewayManifest) { return ( this.gatewayManifest.commands.find( (c) => c.name === resolved || c.aliases.includes(resolved), ) ?? null ); } return null; } getAll(): CommandDef[] { const gateway = this.gatewayManifest?.commands ?? []; // Local commands take precedence; deduplicate gateway commands that share // a name with a local command to avoid duplicate React keys and confusing // autocomplete entries. const localNames = new Set(LOCAL_COMMANDS.map((c) => c.name)); const dedupedGateway = gateway.filter((c) => !localNames.has(c.name)); return [...LOCAL_COMMANDS, ...dedupedGateway]; } getLocalCommands(): CommandDef[] { return LOCAL_COMMANDS; } } export const commandRegistry = new CommandRegistry();