UNPKG

@aigentics/agent-toolkit

Version:

Comprehensive toolkit for validating and managing Claude Flow agent systems

474 lines (419 loc) 18.3 kB
#!/usr/bin/env node /** * Claude Flow Agent Toolkit CLI * Command-line interface for agent management */ import { program } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import prompts from 'prompts'; import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs/promises'; import { AgentValidator } from '../lib/validator.mjs'; import { AgentFixer } from '../lib/fixer.mjs'; import { AgentAnalyzer } from '../lib/analyzer.mjs'; import { AgentCreator } from '../lib/creator.mjs'; import { AgentConfig } from '../lib/config.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Read package.json for version const packageJson = JSON.parse( await fs.readFile(path.join(__dirname, '../package.json'), 'utf-8') ); program .name('agent-toolkit') .description('Claude Flow Agent Toolkit - Validate, fix, and manage AI agents') .version(packageJson.version); // Validate command program .command('validate [agent-name]') .description('Validate agent configurations') .option('-d, --dir <directory>', 'Agents directory', '.claude/agents') .option('-f, --format <format>', 'Output format (text, json)', 'text') .option('-o, --output <file>', 'Output file') .option('-v, --verbose', 'Verbose output') .action(async (agentName, options) => { try { const validator = new AgentValidator({ agentsDir: path.resolve(options.dir), verbose: options.verbose }); let results; if (agentName && agentName !== '*') { // Validate single agent const agentPath = path.join(path.resolve(options.dir), `${agentName}.md`); const jsonPath = path.join(path.resolve(options.dir), `${agentName}.json`); let filePath; try { await fs.access(agentPath); filePath = agentPath; } catch { try { await fs.access(jsonPath); filePath = jsonPath; } catch { console.log(`Agent ${agentName} not found`); process.exit(1); } } const result = await validator.validateFile(filePath); results = { total: 1, valid: result.status === 'valid' ? 1 : 0, warnings: result.status === 'warning' ? 1 : 0, errors: result.status === 'error' ? 1 : 0, details: [result], typeStats: {} }; } else { // Validate all agents (when no name specified or * wildcard) results = await validator.validateAll(); } // Generate report const report = validator.generateReport(results, options.format); // Output results if (options.output) { await fs.writeFile(options.output, report); console.log(chalk.green(`✅ Report saved to ${options.output}`)); } else { console.log(report); } // Show summary only for non-JSON formats if (options.format !== 'json') { const successRate = Math.round((results.valid / results.total) * 100); console.log('\n' + chalk.bold('Summary:')); console.log(chalk.green(`✅ Valid: ${results.valid}`)); console.log(chalk.yellow(`⚠️ Warnings: ${results.warnings}`)); console.log(chalk.red(`❌ Errors: ${results.errors}`)); console.log(chalk.blue(`📊 Success Rate: ${successRate}%`)); if (results.errors > 0) { console.log(chalk.red('\nvalidation errors found')); } } process.exit(results.errors > 0 ? 1 : 0); } catch (error) { console.log(error.message); process.exit(1); } }); // Fix command program .command('fix [agent-name]') .description('Fix common agent configuration issues') .option('-d, --dir <directory>', 'Agents directory', '.claude/agents') .option('--dry-run', 'Show what would be fixed without making changes') .option('--no-backup', 'Skip creating backup files') .option('-v, --verbose', 'Verbose output') .option('--tools-format', 'Fix tools format issues') .option('--type-mismatches', 'Fix type mismatches') .option('--all', 'Fix all issues') .action(async (agentName, options) => { const spinner = ora('Fixing agent issues...').start(); try { const fixer = new AgentFixer({ agentsDir: path.resolve(options.dir), dryRun: options.dryRun, backup: options.backup, verbose: options.verbose }); let results; if (agentName) { // Fix single agent results = await fixer.fixSingle(agentName); // Convert single result to match expected format results = { total: 1, fixed: results.fixed ? 1 : 0, skipped: results.fixed ? 0 : 1, errors: results.error ? 1 : 0, details: [results] }; } else if (options.toolsFormat || options.typeMismatches) { results = await fixer.fixSpecificIssues({ fixToolsFormat: options.toolsFormat, fixTypeMismatches: options.typeMismatches }); } else { results = await fixer.fixAll(); } spinner.stop(); // Show results if (options.dryRun) { console.log(chalk.yellow('🔍 Dry run - no changes made')); console.log('Would fix:'); } console.log(chalk.bold('\nFix Results:')); const fixedCount = results.fixed || results.total || 0; console.log(chalk.green(`✅ Fixed: ${fixedCount}`)); console.log(chalk.blue(`⏭️ Skipped: ${results.skipped || 0}`)); console.log(chalk.red(`❌ Errors: ${results.errors || 0}`)); // Show file counts const mdCount = results.details ? results.details.filter(d => d.file && d.file.endsWith('.md')).length : 0; const jsonCount = results.details ? results.details.filter(d => d.file && d.file.endsWith('.json')).length : 0; if (mdCount > 0 || jsonCount > 0) { console.log(chalk.gray(`\n📄 Files processed: ${mdCount} markdown, ${jsonCount} JSON`)); } // Add agent count message if (fixedCount > 0) { console.log(chalk.green(`\nFixed ${fixedCount} agent${fixedCount === 1 ? '' : 's'}`)); } if (options.verbose && results.details) { console.log('\nDetails:'); for (const detail of results.details) { if (detail.fixed) { console.log(`\n${chalk.green('✅')} ${detail.relativePath}`); detail.fixes.forEach(fix => console.log(` • ${fix}`)); } } } // For single agent fix, show agent name if (agentName && results.details && results.details.length === 1) { const detail = results.details[0]; const name = detail.agent_name || agentName; if (name) { console.log(`\n${chalk.blue('Agent:')} ${name}`); } } } catch (error) { spinner.fail('Fix failed'); console.error(chalk.red(error.message)); process.exit(1); } }); // Analyze command program .command('analyze') .description('Analyze agent system for insights') .option('-d, --dir <directory>', 'Agents directory', '.claude/agents') .option('-f, --format <format>', 'Output format (text, json)', 'text') .option('-o, --output <file>', 'Output file') .action(async (options) => { const spinner = ora('Analyzing agent system...').start(); try { const analyzer = new AgentAnalyzer({ agentsDir: path.resolve(options.dir) }); const analysis = await analyzer.analyze(); spinner.stop(); // Generate report const report = analyzer.generateReport(analysis, options.format); // Output results if (options.output) { await fs.writeFile(options.output, report); console.log(chalk.green(`✅ Analysis saved to ${options.output}`)); } else { console.log(report); } } catch (error) { spinner.fail('Analysis failed'); console.error(chalk.red(error.message)); process.exit(1); } }); // Create command with additional options program .command('create [name]') .description('Create a new agent') .option('-t, --type <type>', 'Agent type', 'core') .option('-d, --description <description>', 'Agent description') .option('-c, --capabilities <capabilities>', 'Comma-separated capabilities') .option('--template <template>', 'Use a template') .option('--dir <directory>', 'Target directory') .option('-i, --interactive', 'Interactive mode') .option('--tools <tools>', 'Comma-separated tools') .option('--prompt <prompt>', 'Create from natural language prompt') .option('--force', 'Overwrite existing agent') .option('--list-templates', 'List available templates') .action(async (name, options) => { // Handle --list-templates if (options.listTemplates) { const creator = new AgentCreator(); const templates = creator.listTemplates(); console.log(chalk.bold('Available templates:')); templates.forEach(template => { console.log(` • ${template}`); }); process.exit(0); } // Check if name is required for other operations if (!name && !options.listTemplates) { console.error(chalk.red('Agent name is required')); process.exit(1); } try { const creator = new AgentCreator(); // Handle prompt-based creation if (options.prompt) { const agentPath = await creator.createFromPrompt({ prompt: options.prompt, name, outputDir: options.dir, force: options.force }); console.log(chalk.green(`✅ Created agent from prompt`)); console.log(chalk.blue(`📁 Path: ${agentPath}`)); console.log(chalk.blue(`🏷️ Name: ${name}`)); return; } let createOptions = { name, type: options.type, description: options.description, capabilities: options.capabilities ? options.capabilities.split(',').map(c => c.trim()) : [], directory: options.dir, template: options.template, force: options.force }; // Add tools if specified if (options.tools) { createOptions.config = { tools: { allowed: options.tools.split(',').map(t => t.trim()), restricted: ['Task'], conditional: [] } }; } // Handle templates if (options.template) { const result = await creator.createFromTemplate(options.template, { name, description: options.description, directory: options.dir, force: options.force }); console.log(chalk.green(`✅ Created agent from template: ${options.template}`)); console.log(chalk.blue(`📁 Path: ${result.relativePath}`)); console.log(chalk.blue(`🏷️ Name: ${result.name}`)); console.log(chalk.blue(`🎯 Type: ${result.type}`)); return; } // Interactive mode if (options.interactive) { const responses = await prompts([ { type: 'select', name: 'type', message: 'Agent type:', choices: AgentConfig.VALID_TYPES.map(t => ({ title: t, value: t })), initial: AgentConfig.VALID_TYPES.indexOf(createOptions.type) }, { type: 'text', name: 'description', message: 'Description:', initial: createOptions.description || `${name} agent for specialized tasks` }, { type: 'list', name: 'capabilities', message: 'Capabilities (comma-separated):', initial: createOptions.capabilities.join(', '), separator: ',' }, { type: 'select', name: 'template', message: 'Use template:', choices: [ { title: 'None', value: null }, { title: 'Swarm Coordinator', value: 'swarm-coordinator' }, { title: 'GitHub Integration', value: 'github-integration' }, { title: 'Code Analyzer', value: 'code-analyzer' }, { title: 'Test Runner', value: 'test-runner' } ] } ]); if (responses) { Object.assign(createOptions, responses); } } // Let the creator handle duplicate checking with force flag const result = await creator.create(createOptions); console.log(chalk.green(`Created agent successfully!`)); console.log(chalk.blue(`📁 Path: ${result.relativePath}`)); console.log(chalk.blue(`🏷️ Name: ${result.name}`)); console.log(chalk.blue(`🎯 Type: ${result.type}`)); } catch (error) { console.error(chalk.red(`❌ ${error.message}`)); process.exit(1); } }); // List templates command program .command('list-templates') .description('List available agent templates') .action(() => { const creator = new AgentCreator(); const templates = creator.listTemplates(); console.log(chalk.bold('Available Templates:')); templates.forEach(template => { console.log(` • ${template}`); }); }); // Config command program .command('config') .description('Show configuration information') .option('--types', 'Show valid agent types') .option('--template', 'Show agent template') .action((options) => { if (options.types) { console.log(chalk.bold('Valid Agent Types:')); AgentConfig.VALID_TYPES.forEach(type => { const color = AgentConfig.TYPE_COLORS[type]; console.log(` ${chalk.hex(color)('●')} ${type}`); }); } else if (options.template) { const template = AgentConfig.getTemplate(); console.log(chalk.bold('Agent Configuration Template:')); console.log(JSON.stringify(template, null, 2)); } else { console.log(chalk.bold('Agent Toolkit Configuration')); console.log(`Version: ${packageJson.version}`); console.log(`Valid Types: ${AgentConfig.VALID_TYPES.length}`); console.log(`Required Fields: ${AgentConfig.REQUIRED_FIELDS.length}`); } }); // Claude Flow Hooks subcommand program .command('claude-flow-hooks <cmd>') .description('Hook management CLI') .allowUnknownOption() .action(async (cmd, options, command) => { const { execSync } = await import('child_process'); const hooksCliPath = path.join(__dirname, '..', 'src', 'hooks-cli.mjs'); try { const args = command.args.slice(1); // Remove 'claude-flow-hooks' from args const remainingArgs = process.argv.slice(process.argv.indexOf(cmd)); const fullCommand = `node "${hooksCliPath}" ${remainingArgs.join(' ')}`; execSync(fullCommand, { stdio: 'inherit', cwd: process.cwd() }); } catch (error) { console.error(chalk.red('Hook management failed:'), error.message); process.exit(1); } }); // Show help if no command provided if (!process.argv.slice(2).length) { program.outputHelp(); process.exit(0); } // Add error handlers program.exitOverride(); // Handle unknown commands program.on('command:*', () => { console.error(chalk.red('Unknown command')); process.exit(1); }); // Parse command line arguments try { program.parse(process.argv); } catch (err) { if (err.exitCode === 0) { process.exit(0); } else { console.error(chalk.red(err.message)); process.exit(1); } }