@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
JavaScript
/**
* 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