UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

307 lines • 12 kB
import prompts from 'prompts'; import chalk from 'chalk'; import ora from 'ora'; import { select, confirm } from '@inquirer/prompts'; import autocomplete from 'inquirer-autocomplete-prompt'; import inquirer from 'inquirer'; import { stateService } from '../services/state.js'; import { contextManager } from '../services/context.js'; import { chatService } from '../services/chat.js'; import { providerRegistry } from '../providers/base.js'; import { FileHandlerInstance } from '../services/file-handler.js'; import path from 'path'; inquirer.registerPrompt('autocomplete', autocomplete); export class PromptCLI { running = true; commands = []; fileHandler = new FileHandlerInstance(); commandMap = new Map(); constructor() { this.setupCommands(); this.buildCommandMap(); } buildCommandMap() { this.commands.forEach(cmd => { this.commandMap.set(cmd.name, cmd); cmd.aliases?.forEach(alias => { this.commandMap.set(alias, cmd); }); }); } setupCommands() { this.commands = [ { name: 'help', description: 'Show available commands', aliases: ['h', '?'], action: async () => this.showHelp() }, { name: 'exit', description: 'Exit the application', aliases: ['quit', 'q'], action: async () => { this.running = false; } }, { name: 'mode', description: 'Switch mode (chat/agent/plan/fusion)', action: async (args) => { if (args.length > 0) { const mode = args[0].toLowerCase(); if (['chat', 'agent', 'plan', 'fusion'].includes(mode)) { stateService.setMode(mode); console.log(chalk.green(`āœ“ Switched to ${mode} mode`)); } else { console.log(chalk.red('Invalid mode')); } } else { const mode = await select({ message: 'Select mode:', choices: [ { name: 'šŸ’¬ Chat', value: 'chat' }, { name: 'šŸ¤– Agent', value: 'agent' }, { name: 'šŸ“‹ Plan', value: 'plan' }, { name: 'šŸ”® Fusion', value: 'fusion' } ] }); stateService.setMode(mode); console.log(chalk.green(`āœ“ Switched to ${mode} mode`)); } } }, { name: 'provider', description: 'Switch AI provider', aliases: ['p'], action: async (args) => { if (args.length > 0) { stateService.setProvider(args[0]); console.log(chalk.green(`āœ“ Switched to ${args[0]}`)); } else { const providers = providerRegistry.getProviders(); const provider = await select({ message: 'Select provider:', choices: providers.map(p => ({ name: p.name, value: p.name.toLowerCase() })) }); stateService.setProvider(provider); console.log(chalk.green(`āœ“ Switched to ${provider}`)); } } }, { name: 'model', description: 'Switch AI model', aliases: ['m'], action: async (args) => { if (args.length > 0) { stateService.setModel(args[0]); console.log(chalk.green(`āœ“ Switched to ${args[0]}`)); } else { const provider = providerRegistry.getProvider(stateService.getProvider()); if (provider) { const models = await provider.getAvailableModels(); const model = await select({ message: 'Select model:', choices: models.map(m => ({ name: m.name, value: m.id })) }); stateService.setModel(model); console.log(chalk.green(`āœ“ Switched to ${model}`)); } } } }, { name: 'new', description: 'Start new conversation', aliases: ['n'], action: async () => { contextManager.newContext(); console.log(chalk.green('āœ“ Started new conversation')); } }, { name: 'clear', description: 'Clear conversation', aliases: ['c'], action: async () => { const shouldClear = await confirm({ message: 'Clear current conversation?', default: false }); if (shouldClear) { contextManager.clearContext(); console.log(chalk.green('āœ“ Conversation cleared')); } } }, { name: 'attach', description: 'Attach files', aliases: ['a'], action: async (args) => { if (args.length === 0) { console.log(chalk.yellow('Usage: /attach <file1> [file2] ...')); return; } for (const file of args) { try { await this.fileHandler.attachFile(file); console.log(chalk.green(`āœ“ Attached ${path.basename(file)}`)); } catch (error) { console.log(chalk.red(`āœ— Failed: ${error.message}`)); } } } }, { name: 'files', description: 'List attached files', aliases: ['f'], action: async () => { const files = this.fileHandler.getAttachedFiles(); if (files.length === 0) { console.log(chalk.gray('No files attached')); } else { console.log(chalk.yellow('\nAttached files:')); files.forEach((file, i) => { console.log(` ${i + 1}. ${file.name}`); }); } } } ]; } showHelp() { console.log(chalk.yellow('\nšŸ“š Commands:\n')); this.commands.forEach(cmd => { const aliases = cmd.aliases ? chalk.gray(` (${cmd.aliases.join(', ')})`) : ''; console.log(` ${chalk.cyan('/' + cmd.name)}${aliases} - ${cmd.description}`); }); console.log(chalk.dim('\nšŸ’” Just type to chat, use / for commands\n')); } async processCommand(input) { const parts = input.slice(1).split(/\s+/); const cmdName = parts[0]; const args = parts.slice(1); const command = this.commandMap.get(cmdName); if (command) { await command.action(args); } else { console.log(chalk.red(`Unknown command: /${cmdName}`)); console.log(chalk.dim('Type /help for available commands')); } } async sendMessage(message) { const spinner = ora('Thinking...').start(); try { let content; const attachedFiles = this.fileHandler.getAttachedFiles(); if (attachedFiles.length > 0) { content = [ { type: 'text', text: message }, ...attachedFiles.map(file => ({ type: 'file', filename: file.name, media_type: file.type, data: file.content })) ]; this.fileHandler.clearFiles(); } else { content = message; } const response = await chatService.sendMessage(content); spinner.stop(); console.log('\n' + chalk.green('Assistant:'), response.content); if (response.usage) { console.log(chalk.dim(`\n[Tokens: ${response.usage.totalTokens}]`)); } console.log(); } catch (error) { spinner.stop(); console.error(chalk.red('Error:'), error.message); } } async start() { console.log(chalk.cyan('\nšŸš€ Welcome to Capsule CLI!\n')); console.log(chalk.gray('Type /help for commands or just start chatting')); prompts.override(require.main?.exports); while (this.running) { try { const mode = stateService.getMode(); const attachCount = this.fileHandler.getAttachedFiles().length; const promptIcon = { chat: 'šŸ’¬', agent: 'šŸ¤–', plan: 'šŸ“‹', fusion: 'šŸ”®' }[mode] || 'šŸ’¬'; const attachIndicator = attachCount > 0 ? chalk.yellow(` [${attachCount}šŸ“Ž]`) : ''; const response = await prompts({ type: 'text', name: 'input', message: `${promptIcon}${attachIndicator} ›`, validate: (value) => true }); if (!response.input || response.input === undefined) { const shouldExit = await confirm({ message: 'Exit Capsule CLI?', default: false }); if (shouldExit) { this.running = false; break; } continue; } const input = response.input.trim(); if (!input) continue; if (input.startsWith('/')) { await this.processCommand(input); } else { await this.sendMessage(input); } } catch (error) { if (error.message === 'canceled') { const shouldExit = await confirm({ message: 'Exit Capsule CLI?', default: false }); if (shouldExit) { this.running = false; } } else { console.error(chalk.red('Error:'), error.message); } } } console.log(chalk.yellow('\nšŸ‘‹ Goodbye!\n')); process.exit(0); } } export async function startPromptCLI() { const cli = new PromptCLI(); await cli.start(); } //# sourceMappingURL=prompt-cli.js.map