capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
296 lines • 12.8 kB
JavaScript
import { configManager } from '../../core/config.js';
import { contextManager } from '../../services/context.js';
import { chatService } from '../../services/chat.js';
import { stateService } from '../../services/state.js';
import { authService } from '../../services/auth.js';
import { toolExecutor } from '../../tools/executor.js';
import { toolResultsService } from '../../services/tool-results.js';
import { ModelResolver } from '../../services/model-resolver.js';
import { openRouterModelsService } from '../../services/openrouter-models.js';
import chalk from 'chalk';
export const taskCommand = {
name: 'task',
description: 'Execute a task with specified model (micro mode)',
alias: ['t', 'micro'],
async execute(args) {
if (!args || args.length === 0) {
return {
success: false,
message: 'Please provide a task description. Usage: /task "your task" --provider openai --model gpt-4o'
};
}
const authStatus = await authService.getStatus();
if (!authStatus.isAuthenticated) {
return {
success: false,
message: chalk.yellow('⚠️ Activation Required\n\n') +
'Capsule must be activated to use task mode.\n' +
'Purchase at: ' + chalk.cyan('https://capsulecli.dev') + '\n' +
'Then activate with: ' + chalk.yellow('/activate')
};
}
const config = configManager.getConfig();
if (!config.providers.openrouter?.apiKey) {
return {
success: false,
message: chalk.yellow('⚠️ OpenRouter API Key Required\n\n') +
'Task mode requires an OpenRouter API key.\n' +
'Get your key at: ' + chalk.cyan('https://openrouter.ai/keys') + '\n' +
'Then set it with: ' + chalk.yellow('/keys')
};
}
const fullArgs = args.join(' ');
const taskMatch = fullArgs.match(/^["'](.+?)["']|^([^-]+)/);
const task = taskMatch ? (taskMatch[1] || taskMatch[2]).trim() : '';
if (!task) {
return {
success: false,
message: 'Invalid task format. Wrap your task in quotes or provide it before options.'
};
}
const options = {};
const optionMatches = fullArgs.matchAll(/--(\w+)(?:\s+([^-]+?)(?=\s*(?:--|$)))?/g);
for (const match of optionMatches) {
const key = match[1];
const value = match[2]?.trim();
switch (key) {
case 'provider':
case 'p':
options.provider = value;
break;
case 'model':
case 'm':
options.model = value;
break;
case 'context':
case 'c':
options.context = value;
break;
case 'json':
case 'j':
options.json = true;
break;
case 'timeout':
case 't':
options.timeout = value ? parseInt(value) : undefined;
break;
}
}
let provider = options.provider || stateService.getProvider();
let model = options.model || stateService.getModel();
if (options.provider) {
const resolvedProvider = ModelResolver.resolveProvider(options.provider);
if (!resolvedProvider) {
const availableProviders = openRouterModelsService.getAvailableProviders();
return {
success: false,
message: chalk.red(`Provider "${options.provider}" not found.\n\n`) +
'Available providers: ' + availableProviders.join(', ')
};
}
provider = resolvedProvider;
}
if (options.model) {
const resolvedModel = ModelResolver.resolveModel(options.model, provider);
if (!resolvedModel) {
const suggestions = ModelResolver.getSuggestions(options.model, provider);
let message = chalk.red(`Model "${options.model}" not found.\n\n`);
if (suggestions.length > 0) {
message += 'Did you mean one of these?\n';
suggestions.forEach(s => {
message += ` • ${s}\n`;
});
}
else {
const models = await stateService.getAvailableModels(provider);
message += `Available models for ${provider}:\n`;
models.slice(0, 5).forEach(m => {
message += ` • ${m}\n`;
});
if (models.length > 5) {
message += ` ... and ${models.length - 5} more`;
}
}
return { success: false, message };
}
model = resolvedModel;
}
const validation = ModelResolver.validateModel(model);
if (!validation.valid) {
return {
success: false,
message: chalk.red(`Model "${model}" cannot be used: ${validation.reason}`)
};
}
const taskContext = contextManager.createNewContext();
const previousContext = contextManager.getCurrentContext();
contextManager.setCurrentContext(taskContext.id);
try {
const originalProvider = stateService.getProvider();
const originalModel = stateService.getModel();
stateService.setProvider(provider);
stateService.setModel(model);
let prompt = task;
if (options.context) {
prompt += `\n\nContext: ${options.context}`;
}
if (!options.json) {
const startMessage = '\n' + chalk.cyan('═'.repeat(60)) + '\n' +
chalk.bold('Task Execution\n') +
chalk.cyan('═'.repeat(60)) + '\n\n' +
chalk.gray('Task: ') + task + '\n' +
chalk.gray('Provider: ') + provider + '\n' +
chalk.gray('Model: ') + model + '\n\n' +
chalk.yellow('⏳ Executing...\n');
console.log(startMessage);
}
const startTime = Date.now();
const result = await executeTask(prompt, options);
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
stateService.setProvider(originalProvider);
stateService.setModel(originalModel);
contextManager.setCurrentContext(previousContext.id);
if (options.json) {
const jsonOutput = {
success: result.success,
task: task,
provider: provider,
model: model,
duration: `${duration}s`,
tokensUsed: result.tokensUsed || 0,
summary: result.summary || '',
output: result.output || '',
toolsExecuted: result.toolsExecuted || [],
error: result.error
};
return {
success: true,
message: JSON.stringify(jsonOutput, null, 2)
};
}
else {
let output = '\n' + chalk.cyan('═'.repeat(60)) + '\n';
output += chalk.bold('Task Result\n');
output += chalk.cyan('═'.repeat(60)) + '\n\n';
if (result.success) {
output += chalk.green('✓ Task completed successfully\n\n');
output += chalk.gray('Duration: ') + duration + 's\n';
output += chalk.gray('Tokens: ') + (result.tokensUsed || 0) + '\n';
if (result.toolsExecuted && result.toolsExecuted.length > 0) {
output += chalk.gray('Tools used: ') + result.toolsExecuted.join(', ') + '\n';
}
output += '\n';
if (result.summary) {
output += chalk.bold('Summary:\n');
output += result.summary + '\n\n';
}
if (result.output && result.output !== result.summary) {
output += chalk.bold('Full Output:\n');
const truncated = result.output.length > 1000;
output += result.output.substring(0, 1000);
if (truncated) {
output += chalk.dim('\n... (output truncated, use --json for full output)');
}
}
}
else {
output += chalk.red('✗ Task failed\n\n');
output += chalk.gray('Error: ') + (result.error || 'Unknown error');
}
output += '\n' + chalk.cyan('═'.repeat(60));
return {
success: result.success,
message: output
};
}
}
catch (error) {
contextManager.setCurrentContext(previousContext.id);
return {
success: false,
message: chalk.red('Task execution failed: ') +
(error instanceof Error ? error.message : 'Unknown error')
};
}
}
};
async function executeTask(prompt, options) {
try {
let fullOutput = '';
let tokensUsed = 0;
const toolsExecuted = [];
const stream = chatService.stream(prompt, {
mode: 'agent'
});
let timeoutId;
if (options.timeout) {
timeoutId = setTimeout(() => {
chatService.abort();
}, options.timeout * 1000);
}
for await (const chunk of stream) {
if (chunk.delta) {
fullOutput += chunk.delta;
}
if (chunk.usage) {
tokensUsed = chunk.usage.totalTokens;
}
if (chunk.toolCall) {
const toolName = chunk.toolCall.name;
if (!toolsExecuted.includes(toolName)) {
toolsExecuted.push(toolName);
}
try {
const execution = await toolExecutor.execute(chunk.toolCall);
const toolResult = {
call_id: chunk.toolCall.id,
output: execution.result?.output || execution.result?.error || 'Unknown error',
success: execution.state === 'completed',
name: chunk.toolCall.name
};
toolResultsService.setToolResult(chunk.toolCall.id, toolResult);
if (execution.result?.error) {
fullOutput += `\n\nTool error (${toolName}): ${execution.result.error}`;
}
}
catch (toolError) {
const errorResult = {
call_id: chunk.toolCall.id,
output: { error: toolError instanceof Error ? toolError.message : String(toolError) },
success: false,
name: chunk.toolCall.name
};
toolResultsService.setToolResult(chunk.toolCall.id, errorResult);
fullOutput += `\n\nTool execution failed (${toolName}): ${toolError}`;
}
}
}
if (timeoutId) {
clearTimeout(timeoutId);
}
const firstParagraph = fullOutput.split('\n\n')[0];
const summary = firstParagraph.length > 200
? firstParagraph.substring(0, 197).trim() + '...'
: firstParagraph.trim();
return {
success: true,
output: fullOutput.trim(),
summary: summary,
tokensUsed: tokensUsed,
toolsExecuted: toolsExecuted
};
}
catch (error) {
if (options.timeout && error instanceof Error && error.message.includes('aborted')) {
return {
success: false,
error: `Task timed out after ${options.timeout} seconds`
};
}
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
//# sourceMappingURL=task.js.map