138 lines
3.3 KiB
TypeScript
138 lines
3.3 KiB
TypeScript
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<string, string> = {
|
|
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();
|