UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

277 lines 12.3 kB
/** * CLI Passthrough Tool * * Provides direct access to all Hive AI CLI commands through MCP * Enables full CLI functionality for Claude Code users */ import { z } from "zod"; import { spawn } from "child_process"; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; export const CLIPassthroughToolSchema = z.object({ cli_command: z.string().describe('Direct CLI command to execute (e.g., "setup", "consensus \'my question\'", "models update")') }); export async function runCLIPassthroughTool(args) { const { cli_command } = args; try { // Validate command for security if (!isValidCLICommand(cli_command)) { return { result: `❌ Invalid or potentially unsafe command: "${cli_command}"\n\n` + `**Allowed commands:**\n` + `• setup, consensus, models (list|update), profiles (list|configure|set-default)\n` + `• providers (list|configure|test), templates (check|fix|status)\n\n` + `**Examples:**\n` + `• "setup" - Launch guided setup\n` + `• "consensus 'What is the best approach for X?'" - Run consensus\n` + `• "models update" - Update model data\n` + `• "profiles list" - Show pipeline profiles` }; } // Execute the CLI command const result = await executeCLICommand(cli_command); return { result }; } catch (error) { return { result: `❌ Error executing CLI command: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } function isValidCLICommand(command) { const cmd = command.toLowerCase().trim(); // Whitelist of allowed command patterns const allowedPatterns = [ /^setup$/, /^consensus\s+/, /^models\s+(list|update)(\s+.*)?$/, /^profiles?\s+(list|configure|set-default)(\s+.*)?$/, /^providers?\s+(list|configure|test)(\s+.*)?$/, /^templates?\s+(check|fix|status)(\s+.*)?$/, /^status$/, /^help$/, /^version$/ ]; // Check against whitelist return allowedPatterns.some(pattern => pattern.test(cmd)); } async function executeCLICommand(command) { return new Promise((resolve, reject) => { try { // Get the current file's directory const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Build path to the CLI executable const projectRoot = join(__dirname, '../../../..'); const cliPath = join(projectRoot, 'dist/cli.js'); // Parse command into arguments const args = parseCommand(command); // Spawn the CLI process const child = spawn('node', [cliPath, ...args], { cwd: projectRoot, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env } }); let stdout = ''; let stderr = ''; child.stdout?.on('data', (data) => { stdout += data.toString(); }); child.stderr?.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { if (code === 0) { resolve(stdout || 'Command completed successfully'); } else { reject(new Error(stderr || `Command failed with exit code ${code}`)); } }); child.on('error', (error) => { reject(new Error(`Failed to start command: ${error.message}`)); }); // Set timeout for long-running commands const timeout = setTimeout(() => { child.kill(); reject(new Error('Command timed out after 30 seconds')); }, 30000); child.on('close', () => { clearTimeout(timeout); }); } catch (error) { reject(error); } }); } function parseCommand(command) { // Simple command parsing - split on spaces but preserve quoted strings const args = []; let current = ''; let inQuotes = false; let quoteChar = ''; for (let i = 0; i < command.length; i++) { const char = command[i]; if ((char === '"' || char === "'") && !inQuotes) { inQuotes = true; quoteChar = char; } else if (char === quoteChar && inQuotes) { inQuotes = false; quoteChar = ''; } else if (char === ' ' && !inQuotes) { if (current.trim()) { args.push(current.trim()); current = ''; } } else { current += char; } } if (current.trim()) { args.push(current.trim()); } return args; } // Alternative implementation using direct function calls for better reliability export async function runCLIPassthroughToolDirect(args) { const { cli_command } = args; try { const result = await executeDirectCLICommand(cli_command); return { result }; } catch (error) { return { result: `❌ Error executing command: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } async function executeDirectCLICommand(command) { const cmd = command.toLowerCase().trim(); const args = cmd.split(/\s+/); const mainCommand = args[0]; const subCommand = args[1]; try { switch (mainCommand) { case 'models': if (subCommand === 'update') { const { runUpdateModelsTool } = await import('./model-selection.js'); const result = await runUpdateModelsTool(); return result.result; } else if (subCommand === 'list') { const { runListModelsTool } = await import('./model-selection.js'); const provider = args[2] || undefined; const result = await runListModelsTool({ provider_name: provider }); return result.result; } break; case 'profiles': case 'profile': if (subCommand === 'list') { const { runListPipelineProfilesTool } = await import('./pipeline-config.js'); const result = await runListPipelineProfilesTool(); return result.result; } else if (subCommand === 'set-default') { const { runSetDefaultProfileTool } = await import('./pipeline-config.js'); const profileName = args.slice(2).join(' '); const result = await runSetDefaultProfileTool({ profile_name: profileName }); return result.result; } else if (subCommand === 'configure') { const { runConfigurePipelineTool } = await import('./pipeline-config.js'); const configCommand = args.slice(2).join(' '); const result = await runConfigurePipelineTool({ command: configCommand }); return result.result; } break; case 'providers': case 'provider': if (subCommand === 'list') { const { runListProvidersTool } = await import('./provider-config.js'); const result = await runListProvidersTool(); return result.result; } else if (subCommand === 'test') { const { runTestProvidersTool } = await import('./provider-config.js'); const result = await runTestProvidersTool(); return result.result; } else if (subCommand === 'configure') { const { runConfigureProviderTool } = await import('./provider-config.js'); const configCommand = args.slice(1).join(' '); const result = await runConfigureProviderTool({ command: configCommand }); return result.result; } break; case 'templates': case 'template': const { runTemplateMaintenanceTool } = await import('./template-maintenance-tool.js'); if (subCommand === 'check') { const result = await runTemplateMaintenanceTool({ action: 'check', dry_run: false }); return result.result; } else if (subCommand === 'fix') { const result = await runTemplateMaintenanceTool({ action: 'fix', dry_run: false }); return result.result; } else if (subCommand === 'status') { const result = await runTemplateMaintenanceTool({ action: 'status', dry_run: false }); return result.result; } break; case 'consensus': const question = command.replace(/^consensus\s+/i, '').replace(/^['"]|['"]$/g, ''); if (!question) { return `❌ No question provided for consensus.\n\nExample: "consensus 'What is the best approach for X?'"`; } const { runConsensusPipeline, validateConsensusPrerequisites } = await import('../enhanced-consensus-engine.js'); // Validate prerequisites const validation = await validateConsensusPrerequisites(); if (!validation.valid) { return `❌ Consensus prerequisites not met:\n${validation.errors.join('\n')}\n\nPlease run: "setup"`; } // Generate conversation ID const { v4: uuidv4 } = await import('uuid'); const conversationId = uuidv4(); const consensusResult = await runConsensusPipeline(question, conversationId); return `✅ **Consensus Result for:** "${question}"\n\n${consensusResult}`; case 'setup': return `🚀 **Interactive Setup Required**\n\n` + `The setup wizard requires interactive input and must be run directly in the terminal:\n\n` + `\`\`\`bash\nnpx hive-ai setup\n\`\`\`\n\n` + `**Alternative: Configure components individually:**\n` + `• Configure OpenRouter: "provider configure openrouter <api-key>"\n` + `• Update models: "models update"\n` + `• List models: "models list"\n` + `• Configure profile: "profile configure <name>"`; case 'status': // Use the system status from unified tool const { generateSystemStatus } = await import('./unified-hive-tool.js'); const statusResult = await generateSystemStatus(); return statusResult.result; default: return `❌ Unknown command: "${mainCommand}"\n\n` + `**Available commands:**\n` + `• models (list|update)\n` + `• profiles (list|configure|set-default)\n` + `• providers (list|configure|test)\n` + `• templates (check|fix|status)\n` + `• consensus "your question"\n` + `• setup\n` + `• status`; } return `❌ Invalid subcommand for "${mainCommand}"\n\nUse "help" to see available commands.`; } catch (error) { throw new Error(`Command execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Tool exports export const cliPassthroughToolName = 'hive_cli'; export const cliPassthroughToolDescription = 'Direct CLI command execution - run any Hive AI CLI command (e.g., "setup", "models update", "consensus \'question\'")'; //# sourceMappingURL=cli-passthrough-tool.js.map