UNPKG

maia-cli

Version:

Command-line interface for MAIA (Modern AI Assistant)

498 lines (450 loc) • 13.2 kB
/** * External Tools CLI Commands * Manage external API integrations */ import { Command } from 'commander'; import chalk from 'chalk'; import inquirer from 'inquirer'; import { readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import { MAIAEngine } from 'maia-core'; export const externalToolsCommand = new Command('external-tools') .alias('ext') .description('Manage external API tool integrations'); // Add external tool externalToolsCommand .command('add') .description('Add a new external API tool') .option('-f, --file <path>', 'Load configuration from file') .option('-i, --interactive', 'Interactive configuration wizard') .action(async (options) => { try { let config; if (options.file) { // Load from file const configPath = options.file; const configData = readFileSync(configPath, 'utf-8'); config = JSON.parse(configData); } else if (options.interactive) { // Interactive wizard config = await runInteractiveWizard(); } else { console.log(chalk.yellow('Please specify --file or --interactive option')); return; } // Initialize MAIA engine const engine = new MAIAEngine(); await engine.initialize(); // Register the tool await engine.externalTools.registerTool(config); console.log(chalk.green(`āœ… External tool "${config.name}" added successfully!`)); console.log(chalk.gray(`Tool ID: ${config.id}`)); console.log(chalk.gray(`Available methods: ${config.tools.map(t => t.name).join(', ')}`)); } catch (error) { console.error(chalk.red('āŒ Error adding external tool:'), error.message); } }); // List external tools externalToolsCommand .command('list') .alias('ls') .description('List all external tools') .option('--enabled', 'Show only enabled tools') .option('--category <category>', 'Filter by category') .action(async (options) => { try { const engine = new MAIAEngine(); await engine.initialize(); let tools = engine.externalTools.listTools(); // Apply filters if (options.enabled) { tools = tools.filter(tool => tool.enabled); } if (options.category) { tools = tools.filter(tool => tool.category === options.category); } if (tools.length === 0) { console.log(chalk.yellow('No external tools found')); return; } console.log(chalk.cyan(`\nšŸ“‹ External Tools (${tools.length})\n`)); tools.forEach(tool => { const status = tool.enabled ? chalk.green('āœ… Enabled') : chalk.red('āŒ Disabled'); console.log(`${chalk.bold(tool.name)} (${tool.id})`); console.log(` ${tool.description}`); console.log(` Category: ${chalk.blue(tool.category)}`); console.log(` Status: ${status}`); console.log(` API: ${tool.api.baseUrl}`); console.log(` Tools: ${tool.tools.map(t => t.name).join(', ')}`); console.log(''); }); } catch (error) { console.error(chalk.red('āŒ Error listing tools:'), error.message); } }); // Test external tool externalToolsCommand .command('test <toolId>') .description('Test external tool connection') .action(async (toolId) => { try { const engine = new MAIAEngine(); await engine.initialize(); const tool = engine.externalTools.getTool(toolId); if (!tool) { console.log(chalk.red(`āŒ Tool not found: ${toolId}`)); return; } console.log(chalk.blue(`šŸ” Testing connection to ${tool.name}...`)); const success = await engine.externalTools.testConnection(tool); if (success) { console.log(chalk.green(`āœ… Connection successful!`)); } else { console.log(chalk.red(`āŒ Connection failed`)); } } catch (error) { console.error(chalk.red('āŒ Error testing tool:'), error.message); } }); // Execute external tool externalToolsCommand .command('execute <toolId> <method>') .description('Execute an external tool method') .option('-p, --params <json>', 'Parameters as JSON string') .option('-f, --params-file <path>', 'Load parameters from file') .action(async (toolId, method, options) => { try { const engine = new MAIAEngine(); await engine.initialize(); let parameters = {}; if (options.params) { parameters = JSON.parse(options.params); } else if (options.paramsFile) { const paramsData = readFileSync(options.paramsFile, 'utf-8'); parameters = JSON.parse(paramsData); } console.log(chalk.blue(`šŸš€ Executing ${toolId}.${method}...`)); const result = await engine.externalTools.executeTool(toolId, method, { toolId, userId: 'cli-user', parameters }); if (result.success) { console.log(chalk.green('āœ… Execution successful!')); console.log(chalk.gray(`Execution time: ${result.metadata.executionTime}ms`)); if (result.metadata.cached) { console.log(chalk.yellow('šŸ“¦ Result from cache')); } console.log('\nšŸ“„ Result:'); console.log(JSON.stringify(result.data, null, 2)); } else { console.log(chalk.red('āŒ Execution failed!')); console.log(chalk.red(`Error: ${result.error.message}`)); if (result.error.details) { console.log(chalk.gray('Details:'), result.error.details); } } } catch (error) { console.error(chalk.red('āŒ Error executing tool:'), error.message); } }); // Remove external tool externalToolsCommand .command('remove <toolId>') .alias('rm') .description('Remove an external tool') .option('-y, --yes', 'Skip confirmation') .action(async (toolId, options) => { try { const engine = new MAIAEngine(); await engine.initialize(); const tool = engine.externalTools.getTool(toolId); if (!tool) { console.log(chalk.red(`āŒ Tool not found: ${toolId}`)); return; } if (!options.yes) { const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: `Are you sure you want to remove "${tool.name}"?`, default: false }]); if (!confirm) { console.log(chalk.yellow('Operation cancelled')); return; } } const removed = engine.externalTools.removeTool(toolId); if (removed) { console.log(chalk.green(`āœ… Tool "${tool.name}" removed successfully`)); } else { console.log(chalk.red(`āŒ Failed to remove tool`)); } } catch (error) { console.error(chalk.red('āŒ Error removing tool:'), error.message); } }); // Generate template externalToolsCommand .command('template') .description('Generate external tool configuration template') .option('-t, --type <type>', 'Template type (rest, graphql, webhook)', 'rest') .option('-o, --output <path>', 'Output file path') .action(async (options) => { const template = generateTemplate(options.type); if (options.output) { writeFileSync(options.output, JSON.stringify(template, null, 2)); console.log(chalk.green(`āœ… Template saved to ${options.output}`)); } else { console.log(JSON.stringify(template, null, 2)); } }); // Interactive wizard async function runInteractiveWizard() { console.log(chalk.cyan('\nšŸ§™ External Tool Configuration Wizard\n')); const answers = await inquirer.prompt([ { type: 'input', name: 'id', message: 'Tool ID (unique identifier):', validate: input => input.length > 0 || 'ID is required' }, { type: 'input', name: 'name', message: 'Tool name:', validate: input => input.length > 0 || 'Name is required' }, { type: 'input', name: 'description', message: 'Description:' }, { type: 'list', name: 'category', message: 'Category:', choices: ['productivity', 'development', 'business', 'ai', 'data', 'communication', 'other'] }, { type: 'input', name: 'baseUrl', message: 'API Base URL:', validate: input => { try { new URL(input); return true; } catch { return 'Please enter a valid URL'; } } }, { type: 'list', name: 'authType', message: 'Authentication type:', choices: ['none', 'api_key', 'bearer', 'basic', 'oauth2'] } ]); // Auth configuration let authConfig = {}; if (answers.authType !== 'none') { const authAnswers = await inquirer.prompt(getAuthQuestions(answers.authType)); authConfig = buildAuthConfig(answers.authType, authAnswers); } // Basic tool configuration const toolAnswers = await inquirer.prompt([ { type: 'input', name: 'toolName', message: 'First tool name:', default: 'get_data' }, { type: 'list', name: 'method', message: 'HTTP method:', choices: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] }, { type: 'input', name: 'path', message: 'API path:', default: '/api/v1/data' } ]); return { id: answers.id, name: answers.name, description: answers.description, version: '1.0.0', category: answers.category, api: { baseUrl: answers.baseUrl, timeout: 30000 }, auth: { type: answers.authType, config: authConfig }, tools: [{ name: toolAnswers.toolName, description: `${toolAnswers.toolName} method`, method: toolAnswers.method, path: toolAnswers.path, parameters: { query: [], headers: [], body: toolAnswers.method !== 'GET' ? {} : undefined }, response: { successCodes: [200, 201], dataPath: 'data' } }], enabled: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; } function getAuthQuestions(authType) { switch (authType) { case 'api_key': case 'bearer': return [ { type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' }, { type: 'input', name: 'header', message: 'Header name:', default: 'Authorization' } ]; case 'basic': return [ { type: 'input', name: 'username', message: 'Username:' }, { type: 'password', name: 'password', message: 'Password:', mask: '*' } ]; default: return []; } } function buildAuthConfig(authType, answers) { switch (authType) { case 'api_key': case 'bearer': return { apiKey: { key: answers.apiKey, header: answers.header || 'Authorization', prefix: authType === 'bearer' ? 'Bearer' : '' } }; case 'basic': return { basic: { username: answers.username, password: answers.password } }; default: return {}; } } function generateTemplate(type) { const baseTemplate = { id: 'my-api-tool', name: 'My API Tool', description: 'Description of my API integration', version: '1.0.0', category: 'productivity', api: { baseUrl: 'https://api.example.com', timeout: 30000, retries: 3 }, auth: { type: 'api_key', config: { apiKey: { key: 'your-api-key-here', header: 'Authorization', prefix: 'Bearer' } } }, tools: [], enabled: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; switch (type) { case 'rest': baseTemplate.tools = [ { name: 'get_items', description: 'Get list of items', method: 'GET', path: '/api/v1/items', parameters: { query: [ { name: 'limit', type: 'number', description: 'Number of items to return', required: false, default: 10 } ] }, response: { successCodes: [200], dataPath: 'data.items' } }, { name: 'create_item', description: 'Create a new item', method: 'POST', path: '/api/v1/items', parameters: { body: { name: 'item_data', type: 'object', description: 'Item data', required: true, properties: { name: { name: 'name', type: 'string', description: 'Item name', required: true } } } }, response: { successCodes: [201], dataPath: 'data' } } ]; break; } return baseTemplate; }