export type Command = | { type: 'quit' } | { type: 'reset' } | { type: 'help' } | { type: 'multiline' } | { type: 'status' } | { type: 'tools' } | { type: 'research'; task: string } | { type: 'council'; task: string } | { type: 'fullscreen' } | { type: 'compact' } | { type: 'usage' } | { type: 'context' } | { type: 'verbose' } | { type: 'model'; name?: string; providerModel?: string } | { type: 'backend'; provider?: string } | { type: 'runtime'; input?: 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 isToolInventoryQuery(input: string): boolean { const normalized = input.trim().toLowerCase(); if (!normalized) { return false; } if ( normalized.includes('available tools') || normalized.includes('what tools') || normalized.includes('which tools') || normalized.includes('tool list') || normalized.includes('list tools') || normalized.includes('what can you do') ) { return true; } return ( /\b(?:show|list|check)\s+(?:me\s+)?(?:your\s+)?(?:available\s+|new\s+)?tools?\b/.test(normalized) || /\b(?:what|which)\s+tools?\b/.test(normalized) || /\btools?\s+(?:do\s+you\s+have|are\s+available)\b/.test(normalized) || /\b(?:show|list|what\s+are)\s+(?:your\s+)?capabilities\b/.test(normalized) ); } 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' }; } // Multiline paste mode if (trimmed === '/paste' || trimmed === '/multiline') { return { type: 'multiline' }; } // Status if (trimmed === '/status') { return { type: 'status' }; } // Tools if (trimmed === '/tools') { return { type: 'tools' }; } // Research if (trimmed.startsWith('/research ')) { const task = trimmed.slice('/research '.length).trim(); return { type: 'research', task }; } if (trimmed === '/research') { return { type: 'research', task: '' }; } // Council if (trimmed.startsWith('/council ')) { const task = trimmed.slice('/council '.length).trim(); return { type: 'council', task }; } if (trimmed === '/council') { return { type: 'council', task: '' }; } // Natural-language council shortcut for common flows. const councilShortcut = trimmed.match(/^(?:yes\s+)?run\s+(?:the\s+)?council(?:\s+on)?(?:\s+(.+))?$/i); if (councilShortcut) { return { type: 'council', task: (councilShortcut[1] ?? '').trim() }; } // Fullscreen if (trimmed === '/fullscreen' || trimmed === '/fs') { return { type: 'fullscreen' }; } // Compact if (trimmed === '/compact') { return { type: 'compact' }; } // Usage if (trimmed === '/usage') { return { type: 'usage' }; } // Context if (trimmed === '/context') { return { type: 'context' }; } // 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 }; } // Runtime backend mode control (daemon/channel command; reserved in TUI) if (trimmed === '/runtime') { return { type: 'runtime' }; } if (trimmed.startsWith('/runtime ')) { const input = trimmed.slice('/runtime '.length).trim(); return { type: 'runtime', input }; } // Transfer if (trimmed === '/transfer') { return { type: 'transfer', target: '' }; } 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 /paste, /multiline Enter multiline mode (finish with single '.' line) /tools Show available tools in this session /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) /runtime [args] Runtime backend mode control (daemon/channel sessions) /research Delegate a task to the configured research agent /council Run the councils pipeline for a task /council preflight Check council tier routing, endpoint/auth mode, and probe latency /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 /approvals List pending guarded actions for this session /approve [id] Approve latest pending action (or specific id) /deny [id] [reason] Deny latest pending action (or specific id) /elevate [args] Show or manage elevated mode /reset, /clear, /new Clear conversation history /compact Compact conversation history /usage Show token usage and estimated cost /context Show estimated context-window usage /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 (telegram|tui) /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', '/paste', '/multiline', '/tools', '/model', '/backend', '/runtime', '/research', '/council', '/reset', '/clear', '/new', '/compact', '/usage', '/context', '/verbose', '/status', '/fullscreen', '/fs', '/login', '/pair', '/queue', '/approvals', '/approve', '/deny', '/elevate', '/transfer', '/quit', '/exit', ]; // Command descriptions for tooltips export const COMMAND_TOOLTIPS: Record = { '/help': 'Show available commands', '/paste': 'Compose a multiline message; finish with a single "." line', '/multiline': 'Compose a multiline message; finish with a single "." line', '/tools': 'Show authoritative runtime tool list for this session', '/model': 'Show or switch model (local, default, fast, complex)', '/backend': 'Show or switch local backend (ollama, llamacpp)', '/runtime': 'Runtime backend mode control (daemon/channel command; not local TUI backend switch)', '/research': 'Delegate a task to the configured research agent', '/council': 'Run the councils pipeline for a task; use "/council preflight" for route/auth checks', '/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', '/context': 'Show estimated context-window usage', '/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', '/approvals': 'List pending guarded actions for this session', '/approve': 'Approve latest pending action (or specific id)', '/deny': 'Deny latest pending action (or specific id and reason)', '/elevate': 'Show or manage elevated mode', '/transfer': 'Transfer session to another frontend (telegram|tui)', '/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(); if (trimmed.startsWith('/council ')) { const suffix = trimmed.slice('/council '.length).toLowerCase(); if ('preflight'.startsWith(suffix)) { return ['/council preflight']; } return []; } // 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'; }