maia-cli
Version:
Command-line interface for MAIA (Modern AI Assistant)
498 lines (450 loc) ⢠13.2 kB
JavaScript
/**
* 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;
}