capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
307 lines ⢠12 kB
JavaScript
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