export type Command = | { type: 'quit' } | { type: 'reset' } | { type: 'help' } | { type: 'status' } | { type: 'fullscreen' } | { type: 'compact' } | { type: 'usage' } | { type: 'verbose' } | { type: 'model'; name?: string; providerModel?: string } | { type: 'backend'; provider?: string } | { type: 'login'; provider?: string } | { type: 'transfer'; target: string } | { type: 'pair'; action?: 'generate' | 'list' | 'revoke'; args?: string } | { type: 'queue'; action?: 'show' | 'set' | 'reset'; args?: string } | { type: 'elevate'; args?: string } | { type: 'message'; content: string }; export function parseCommand(input: string): Command | null { const trimmed = input.trim(); if (!trimmed) {return null;} // Quit if (trimmed === '/quit' || trimmed === '/exit') { return { type: 'quit' }; } // Reset if (trimmed === '/reset' || trimmed === '/clear' || trimmed === '/new') { return { type: 'reset' }; } // Help if (trimmed === '/help' || trimmed === '/?') { return { type: 'help' }; } // Status if (trimmed === '/status') { return { type: 'status' }; } // Fullscreen if (trimmed === '/fullscreen' || trimmed === '/fs') { return { type: 'fullscreen' }; } // Compact if (trimmed === '/compact') { return { type: 'compact' }; } // Usage if (trimmed === '/usage') { return { type: 'usage' }; } // Verbose if (trimmed === '/verbose') { return { type: 'verbose' }; } // Model (with optional argument) if (trimmed === '/model') { return { type: 'model' }; } if (trimmed.startsWith('/model ')) { const args = trimmed.slice('/model '.length).trim(); const parts = args.split(/\s+/); // /model - change tier's provider/model if (parts.length === 2 && parts[1].includes('/')) { return { type: 'model', name: parts[0], providerModel: parts[1] }; } // /model - single word (backward compatibility) const name = parts[0]; return { type: 'model', name }; } // Backend (with optional argument) if (trimmed === '/backend') { return { type: 'backend' }; } if (trimmed.startsWith('/backend ')) { const provider = trimmed.slice('/backend '.length).trim(); return { type: 'backend', provider }; } // Transfer if (trimmed.startsWith('/transfer ')) { const target = trimmed.slice('/transfer '.length).trim(); return { type: 'transfer', target }; } // Login if (trimmed === '/login') { return { type: 'login' }; } if (trimmed.startsWith('/login ')) { const provider = trimmed.slice('/login '.length).trim(); return { type: 'login', provider: provider || undefined }; } // Pair if (trimmed === '/pair' || trimmed === '/pair list') { return { type: 'pair', action: 'list' }; } if (trimmed === '/pair generate' || trimmed.startsWith('/pair generate ')) { const label = trimmed.slice('/pair generate'.length).trim() || undefined; return { type: 'pair', action: 'generate', args: label }; } if (trimmed.startsWith('/pair revoke ')) { const args = trimmed.slice('/pair revoke '.length).trim(); return { type: 'pair', action: 'revoke', args }; } // Queue if (trimmed === '/queue' || trimmed === '/queue show') { return { type: 'queue', action: 'show' }; } if (trimmed === '/queue reset') { return { type: 'queue', action: 'reset' }; } if (trimmed.startsWith('/queue set ')) { const args = trimmed.slice('/queue set '.length).trim(); return { type: 'queue', action: 'set', args }; } if (trimmed.startsWith('/queue ')) { const args = trimmed.slice('/queue '.length).trim(); return { type: 'queue', action: 'set', args }; } // Elevate if (trimmed === '/elevate') { return { type: 'elevate' }; } if (trimmed.startsWith('/elevate ')) { const args = trimmed.slice('/elevate '.length).trim(); return { type: 'elevate', args }; } // Regular message return { type: 'message', content: trimmed }; } export function getHelpText(): string { return ` Commands: /help, /? Show this help /model [name] Show or switch model tier (local, default, fast, complex) /model

Change tier's provider/model (e.g. /model default anthropic/claude-sonnet-4) /backend [provider] Show or switch local backend (ollama, llamacpp) /login [provider] Authenticate with GitHub, OpenAI, Anthropic, or Z.AI /pair List pending pairing codes and approved senders /pair generate [label] Generate a new DM pairing code /pair revoke Revoke an approved sender /queue Show queue policy for this session /queue set Set queue override (mode/cap/overflow/debounce_ms/summarize_overflow) /queue reset Clear queue overrides for this session /elevate [args] Show or manage elevated mode /reset, /clear, /new Clear conversation history /compact Compact conversation history /usage Show token usage and estimated cost /verbose Toggle verbose mode (show raw streaming and tool output) /status Show session info and token usage /fullscreen, /fs Switch to fullscreen mode /transfer Transfer session to another frontend /quit, /exit Exit TUI `.trim(); } import { MODEL_PROVIDERS } from '../../config/index.js'; export type ModelAlias = 'local' | 'default' | 'fast' | 'complex' | 'opus' | 'sonnet' | 'haiku' | 'ollama'; // List of all slash commands for autocompletion export const SLASH_COMMANDS = [ '/help', '/model', '/backend', '/reset', '/clear', '/new', '/compact', '/usage', '/verbose', '/status', '/fullscreen', '/fs', '/login', '/pair', '/queue', '/elevate', '/transfer', '/quit', '/exit', ]; // Command descriptions for tooltips export const COMMAND_TOOLTIPS: Record = { '/help': 'Show available commands', '/model': 'Show or switch model (local, default, fast, complex)', '/backend': 'Show or switch local backend (ollama, llamacpp)', '/reset': 'Clear conversation history', '/clear': 'Clear conversation history', '/new': 'Start a new conversation', '/compact': 'Compact conversation history to save context space', '/usage': 'Show token usage and estimated cost', '/verbose': 'Toggle verbose mode (show raw streaming and tool output)', '/status': 'Show session info and token usage', '/fullscreen': 'Switch to fullscreen mode', '/fs': 'Switch to fullscreen mode', '/login': 'Authenticate with GitHub/OpenAI/Anthropic (OAuth/token or API key) or Z.AI (API key store)', '/pair': 'Generate/list/revoke DM pairing codes', '/queue': 'Show or update per-session queue policy', '/elevate': 'Show or manage elevated mode', '/transfer': 'Transfer session to another frontend', '/quit': 'Exit TUI', '/exit': 'Exit TUI', }; // Model aliases for /model command autocompletion export const MODEL_ALIASES = ['local', 'default', 'fast', 'complex', 'opus', 'sonnet', 'haiku', 'ollama']; // Provider names for /model syntax — derived from config schema export const PROVIDER_NAMES: readonly string[] = MODEL_PROVIDERS; // Model alias descriptions export const MODEL_TOOLTIPS: Record = { local: 'Local model (Ollama/llama.cpp)', default: 'Default model tier', fast: 'Fast/lightweight model tier', complex: 'Complex reasoning model tier', opus: 'Alias for complex tier', sonnet: 'Alias for default tier', haiku: 'Alias for fast tier', ollama: 'Alias for local tier', }; export function getCommandCompletions(partial: string): string[] { const trimmed = partial.trim(); // Complete /model if (trimmed.startsWith('/model ')) { const args = trimmed.slice('/model '.length).trim(); const parts = args.split(/\s+/); if (parts.length === 1) { // Single word - suggest model aliases const modelPartial = parts[0].toLowerCase(); return MODEL_ALIASES .filter(alias => alias.startsWith(modelPartial)) .map(alias => `/model ${alias}`); } else if (parts.length === 2) { // Two words - suggest provider prefixes const providerPartial = parts[1].toLowerCase(); return PROVIDER_NAMES .filter(provider => provider.startsWith(providerPartial)) .map(provider => `/model ${parts[0]} ${provider}`); } } // Complete slash commands if (trimmed.startsWith('/')) { return SLASH_COMMANDS.filter(cmd => cmd.startsWith(trimmed.toLowerCase())); } return []; } export function getCommandTooltip(partial: string): string | null { const trimmed = partial.trim().toLowerCase(); // Tooltip for /model arguments if (trimmed.startsWith('/model ')) { const args = trimmed.slice('/model '.length).trim(); const parts = args.split(/\s+/); if (parts.length === 1) { // Single word - model tier or provider const modelArg = parts[0].toLowerCase(); if (modelArg && MODEL_TOOLTIPS[modelArg]) { return MODEL_TOOLTIPS[modelArg]; } // Show tooltip for partial match const matches = MODEL_ALIASES.filter(a => a.startsWith(modelArg)); if (matches.length === 1 && MODEL_TOOLTIPS[matches[0]]) { return MODEL_TOOLTIPS[matches[0]]; } return 'Choose: local, default, fast, complex'; } else if (parts.length === 2) { // Two words - tier + provider const providerPartial = parts[1].toLowerCase(); const matches = PROVIDER_NAMES.filter(p => p.startsWith(providerPartial)); if (matches.length === 1) { return `Enter provider/model (e.g. ${matches[0]}/...)`; } return 'Enter provider/model (e.g. anthropic/claude-sonnet-4)'; } } // Exact match tooltip if (COMMAND_TOOLTIPS[trimmed]) { return COMMAND_TOOLTIPS[trimmed]; } // Partial match - show tooltip if only one command matches if (trimmed.startsWith('/')) { const matches = SLASH_COMMANDS.filter(cmd => cmd.startsWith(trimmed)); if (matches.length === 1 && COMMAND_TOOLTIPS[matches[0]]) { return COMMAND_TOOLTIPS[matches[0]]; } } return null; } export function resolveModelAlias(alias: string): 'local' | 'default' | 'fast' | 'complex' { const map: Record = { local: 'local', ollama: 'local', default: 'default', sonnet: 'default', fast: 'fast', haiku: 'fast', complex: 'complex', opus: 'complex', }; return map[alias.toLowerCase()] ?? 'default'; }