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

807 lines (707 loc) โ€ข 24.5 kB
#!/usr/bin/env node /** * Universal IDE Setup for Hive AI MCP Integration * * Automatically detects and configures multiple IDEs for HTTP+SSE MCP transport: * - Claude Code * - VS Code * - Cursor * - Windsurf * * Features: * - Automatic IDE detection * - HTTP+SSE transport configuration * - Database-backed configuration storage * - Port conflict resolution */ import { execSync } from 'child_process'; import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { join, dirname } from 'path'; import { homedir, platform } from 'os'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Get current package version function getCurrentPackageVersion() { try { const packagePath = join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')); return packageJson.version; } catch (error) { console.warn('Warning: Could not read package version, falling back to @latest'); return 'latest'; } } // Get cross-platform package installation path function getPackagePath() { try { // Try to get the exact path where the package is installed const packageName = '@hivetechs/hive-ai'; // First, try to resolve via require.resolve for local installations try { return dirname(dirname(require.resolve(`${packageName}/package.json`))); } catch (e) { // If that fails, try global npm installation paths } // Detect global npm paths by platform const isWindows = platform() === 'win32'; if (isWindows) { // Windows paths const possiblePaths = [ join(process.env.APPDATA || '', 'npm', 'node_modules', packageName), join(process.env.ProgramFiles || 'C:\\Program Files', 'nodejs', 'node_modules', packageName), join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'nodejs', 'node_modules', packageName) ]; for (const path of possiblePaths) { if (existsSync(join(path, 'package.json'))) { return path; } } } else { // Unix-like systems (Mac/Linux) const possiblePaths = [ '/opt/homebrew/lib/node_modules/@hivetechs/hive-ai', '/usr/local/lib/node_modules/@hivetechs/hive-ai', join(homedir(), '.npm-global', 'lib', 'node_modules', packageName) ]; for (const path of possiblePaths) { if (existsSync(join(path, 'package.json'))) { return path; } } } // If all else fails, try using npm to find the path try { const npmGlobalPath = execSync('npm root -g', { encoding: 'utf8', stdio: 'pipe', windowsHide: true }).trim(); const globalPackagePath = join(npmGlobalPath, packageName); if (existsSync(join(globalPackagePath, 'package.json'))) { return globalPackagePath; } } catch (e) { // npm command failed } // Last resort fallback return isWindows ? join(process.env.APPDATA || '', 'npm', 'node_modules', packageName) : '/usr/local/lib/node_modules/@hivetechs/hive-ai'; } catch (error) { console.warn('Warning: Could not detect package path, using fallback'); const isWindows = platform() === 'win32'; return isWindows ? join(process.env.APPDATA || '', 'npm', 'node_modules', '@hivetechs/hive-ai') : '/usr/local/lib/node_modules/@hivetechs/hive-ai'; } } // IDE Configuration Types and Constants const IDE_CONFIGS = { 'claude-code': { name: 'Claude Code', configPath: join(homedir(), '.claude.json'), configBackupPath: join(homedir(), '.claude.json.backup'), detectCommand: 'claude --version', configFormat: 'claude', priority: 1 }, 'vscode': { name: 'VS Code', configPath: join(homedir(), '.vscode', 'settings.json'), configBackupPath: join(homedir(), '.vscode', 'settings.json.backup'), detectCommand: 'code --version', configFormat: 'vscode', priority: 2 }, 'cursor': { name: 'Cursor', configPath: join(homedir(), '.cursor', 'mcp.json'), configBackupPath: join(homedir(), '.cursor', 'mcp.json.backup'), detectCommand: 'cursor --version', configFormat: 'cursor', priority: 3 }, 'windsurf': { name: 'Windsurf', configPath: join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'), configBackupPath: join(homedir(), '.codeium', 'windsurf', 'mcp_config.json.backup'), detectCommand: 'windsurf --version', configFormat: 'windsurf', priority: 4 } }; class UniversalIDESetup { constructor(options = {}) { this.isSilent = options.silent || false; this.port = options.port || null; // Will be auto-detected this.forceUpdate = options.force || false; this.updateOnly = options.updateOnly || false; this.detectedIDEs = new Map(); this.configuredIDEs = new Map(); this.errors = []; this.isWindows = platform() === 'win32'; } log(message, type = 'info') { if (this.isSilent) return; const icons = { info: '๐Ÿ’ก', success: 'โœ…', error: 'โŒ', warning: 'โš ๏ธ', ide: '๐Ÿ–ฅ๏ธ', config: 'โš™๏ธ', network: '๐ŸŒ' }; console.log(`${icons[type]} ${message}`); } print(message = '') { if (!this.isSilent) console.log(message); } /** * Check if a command exists in PATH (cross-platform with Windows enterprise handling) */ commandExists(command) { try { const checkCommand = this.isWindows ? `where ${command}` : `which ${command}`; execSync(checkCommand, { stdio: 'pipe', windowsHide: true, encoding: 'utf8', timeout: 5000 // Add timeout for corporate environments }); return true; } catch (error) { // Enhanced logging for Windows debugging if (this.isWindows && !this.isSilent) { console.log(`[Windows Debug] Command check failed for '${command}': ${error.message}`); if (error.message.includes('timeout')) { console.log('[Windows Debug] ๐Ÿ’ก Command timeout - may be blocked by corporate policy'); } } return false; } } /** * Check if we can write to a directory with enhanced Windows error handling */ canWriteToDirectory(dirPath) { try { // Test directory creation if it doesn't exist if (!existsSync(dirPath)) { mkdirSync(dirPath, { recursive: true }); } // Test write permissions with a temporary file const testFile = join(dirPath, '.hive-write-test'); writeFileSync(testFile, 'test'); // Clean up test file try { require('fs').unlinkSync(testFile); } catch (cleanupError) { // Ignore cleanup errors } return true; } catch (error) { if (this.isWindows && !this.isSilent) { console.log(`[Windows Debug] Write test failed for '${dirPath}': ${error.message}`); if (error.code === 'EACCES') { console.log('[Windows Debug] ๐Ÿ’ก Permission denied - may need Administrator rights'); } else if (error.code === 'ENOTDIR') { console.log('[Windows Debug] ๐Ÿ’ก Path exists but is not a directory'); } else if (error.code === 'ENOSPC') { console.log('[Windows Debug] ๐Ÿ’ก No space left on device'); } else if (error.message.includes('readonly')) { console.log('[Windows Debug] ๐Ÿ’ก Directory is read-only - check corporate policies'); } } return false; } } /** * Main setup process */ async run() { try { this.log('๐Ÿš€ Starting Universal IDE Setup for Hive AI MCP Integration'); this.print(''); // Step 1: Detect installed IDEs or load from config if (this.updateOnly) { this.log('๐Ÿ“ Update-only mode: Loading previously configured IDEs'); // Load configured IDEs from database const { getConfig } = await import('../dist/storage/unified-database.js'); const configStr = await getConfig('mcp_ide_config'); if (configStr) { const mcpConfig = JSON.parse(configStr); if (mcpConfig.configuredIDEs) { for (const ideKey of mcpConfig.configuredIDEs) { if (IDE_CONFIGS[ideKey]) { this.detectedIDEs.set(ideKey, IDE_CONFIGS[ideKey]); } } } } } else { await this.detectIDEs(); } // Step 2: Get MCP server configuration await this.getMCPServerConfig(); // Step 3: Configure detected IDEs await this.configureIDEs(); // Step 4: Test configurations await this.testConfigurations(); // Step 5: Show summary this.showSummary(); } catch (error) { this.log(`Setup failed: ${error.message}`, 'error'); if (!this.isSilent) { console.error(error); } process.exit(1); } } /** * Detect installed IDEs */ async detectIDEs() { this.log('๐Ÿ” Detecting installed IDEs...'); for (const [ideKey, ideConfig] of Object.entries(IDE_CONFIGS)) { try { // Extract the command name from the full detect command const commandName = ideConfig.detectCommand.split(' ')[0]; if (this.commandExists(commandName)) { // Double-check by trying to run the version command try { execSync(ideConfig.detectCommand, { stdio: 'pipe', windowsHide: true, encoding: 'utf8' }); this.detectedIDEs.set(ideKey, ideConfig); this.log(`Found ${ideConfig.name}`, 'ide'); } catch (versionError) { // Command exists but version check failed - still consider it detected this.detectedIDEs.set(ideKey, ideConfig); this.log(`Found ${ideConfig.name} (version check failed)`, 'ide'); } } } catch (error) { // IDE not installed or not in PATH } } if (this.detectedIDEs.size === 0) { this.log('No supported IDEs found. Please install one of:', 'warning'); this.log(' โ€ข Claude Code: https://docs.anthropic.com/claude/code'); this.log(' โ€ข VS Code: https://code.visualstudio.com/'); this.log(' โ€ข Cursor: https://cursor.sh/'); this.log(' โ€ข Windsurf: https://windsurf.ai/'); return; } this.log(`Detected ${this.detectedIDEs.size} IDE(s)`, 'success'); this.print(''); } /** * Get MCP server configuration from database */ async getMCPServerConfig() { this.log('๐ŸŒ Getting MCP server configuration...', 'network'); try { // Import port manager and get configuration const { MCPPortManager } = await import('../dist/tools/mcp-port-manager.js'); const portManager = new MCPPortManager(); // Get the best port to use const { port, reason } = await this.port ? { port: this.port, reason: 'manually specified' } : await portManager.getBestPort(); this.port = port; this.log(`MCP server port: ${port} (${reason})`, 'config'); // Store configuration in database await this.storeMCPConfig(port); } catch (error) { this.log(`Failed to get MCP configuration: ${error.message}`, 'error'); this.port = 3000; // fallback this.log(`Using fallback port: ${this.port}`, 'warning'); } } /** * Store MCP configuration in database */ async storeMCPConfig(port) { try { const { setConfig } = await import('../dist/storage/unified-database.js'); const mcpConfig = { transport: 'http+sse', port: port, endpoints: { mcp: `http://localhost:${port}/mcp`, health: `http://localhost:${port}/health` }, version: '2025-03-26', lastUpdated: new Date().toISOString(), configuredIDEs: [] }; await setConfig(`mcp_ide_config`, JSON.stringify(mcpConfig)); } catch (error) { this.log(`Failed to store MCP config in database: ${error.message}`, 'warning'); } } /** * Configure all detected IDEs */ async configureIDEs() { this.log('โš™๏ธ Configuring IDEs for HTTP+SSE MCP transport...', 'config'); this.print(''); for (const [ideKey, ideConfig] of this.detectedIDEs) { try { await this.configureIDE(ideKey, ideConfig); this.configuredIDEs.set(ideKey, { success: true, config: ideConfig }); this.log(`Configured ${ideConfig.name}`, 'success'); } catch (error) { this.configuredIDEs.set(ideKey, { success: false, error: error.message }); this.errors.push(`${ideConfig.name}: ${error.message}`); this.log(`Failed to configure ${ideConfig.name}: ${error.message}`, 'error'); } } // Update database with configured IDEs list await this.updateConfiguredIDEsList(); } /** * Configure individual IDE */ async configureIDE(ideKey, ideConfig) { // Create backup if config exists if (existsSync(ideConfig.configPath)) { try { const configContent = readFileSync(ideConfig.configPath, 'utf8'); writeFileSync(ideConfig.configBackupPath, configContent); this.log(`Backed up ${ideConfig.name} config`, 'config'); } catch (error) { this.log(`Warning: Could not backup ${ideConfig.name} config`, 'warning'); } } // Ensure config directory exists const configDir = dirname(ideConfig.configPath); if (!existsSync(configDir)) { mkdirSync(configDir, { recursive: true }); } // Generate IDE-specific configuration const config = this.generateIDEConfig(ideConfig.configFormat); // Write configuration if (ideConfig.configFormat === 'vscode') { // VS Code settings.json might have existing content await this.mergeVSCodeConfig(ideConfig.configPath, config); } else { // Other IDEs get dedicated config files writeFileSync(ideConfig.configPath, JSON.stringify(config, null, 2)); } } /** * Generate IDE-specific configuration */ generateIDEConfig(format) { const baseConfig = { transport: 'http+sse', url: `http://localhost:${this.port}/mcp`, supportsStreaming: true }; switch (format) { case 'claude': return { mcpServers: { 'hive-ai': { type: 'http', url: baseConfig.url, description: 'Hive AI - 18 MCP tools with real-time streaming consensus' } } }; case 'vscode': return { 'mcp.servers': { 'hive-ai': { type: 'http', url: baseConfig.url, supportsStreaming: baseConfig.supportsStreaming, description: 'Hive AI MCP Server with HTTP+SSE transport' } } }; case 'cursor': return { mcpServers: { 'hive-ai': { command: 'node', args: [join(getPackagePath(), 'bin', 'hive-mcp-stdio')], env: { HIVE_TRANSPORT: 'stdio', HIVE_PROJECT_MODE: 'true' // Note: Cursor doesn't set HIVE_IDE_TYPE, which distinguishes it from Windsurf } } } }; case 'windsurf': return { mcpServers: { 'hive-ai': { command: 'node', args: [join(getPackagePath(), 'bin', 'hive-mcp-stdio')], env: { HIVE_TRANSPORT: 'stdio', HIVE_PROJECT_MODE: 'true', HIVE_IDE_TYPE: 'windsurf' } } } }; default: throw new Error(`Unsupported configuration format: ${format}`); } } /** * Merge configuration with existing VS Code settings */ async mergeVSCodeConfig(configPath, newConfig) { let existingConfig = {}; if (existsSync(configPath)) { try { const content = readFileSync(configPath, 'utf8'); existingConfig = JSON.parse(content); } catch (error) { this.log('Warning: Could not parse existing VS Code settings, creating new file', 'warning'); } } // Merge MCP configuration const mergedConfig = { ...existingConfig, ...newConfig }; writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2)); } /** * Update database with list of configured IDEs */ async updateConfiguredIDEsList() { try { const { getConfig, setConfig } = await import('../dist/storage/unified-database.js'); const configStr = await getConfig(`mcp_ide_config`); if (configStr) { const mcpConfig = JSON.parse(configStr); mcpConfig.configuredIDEs = Array.from(this.configuredIDEs.keys()); mcpConfig.lastUpdated = new Date().toISOString(); await setConfig(`mcp_ide_config`, JSON.stringify(mcpConfig)); } } catch (error) { this.log(`Failed to update configured IDEs list: ${error.message}`, 'warning'); } } /** * Test configurations by checking MCP server connectivity */ async testConfigurations() { if (this.configuredIDEs.size === 0) return; this.log('๐Ÿงช Testing MCP server connectivity...', 'network'); try { const response = await fetch(`http://localhost:${this.port}/health`, { signal: AbortSignal.timeout(5000) }); if (response.ok) { const healthData = await response.json(); this.log(`MCP server is running (${healthData.transport})`, 'success'); this.log(`Available tools: ${healthData.version}`, 'config'); } else { throw new Error(`Health check failed: ${response.status}`); } } catch (error) { this.log('MCP server is not running', 'warning'); this.log('Start the server with: hive mcp-server start', 'info'); } } /** * Show setup summary */ showSummary() { this.print(''); this.log('๐Ÿ“Š Setup Summary', 'success'); this.print(''); // Configured IDEs const successfulIDEs = Array.from(this.configuredIDEs.entries()) .filter(([_, result]) => result.success); const failedIDEs = Array.from(this.configuredIDEs.entries()) .filter(([_, result]) => !result.success); if (successfulIDEs.length > 0) { this.log(`โœ… Successfully configured ${successfulIDEs.length} IDE(s):`, 'success'); successfulIDEs.forEach(([ideKey, result]) => { this.log(` โ€ข ${result.config.name}`, 'ide'); }); } // Failed configurations if (this.errors.length > 0) { this.print(''); this.log(`โŒ Failed to configure ${this.errors.length} IDE(s):`, 'error'); this.errors.forEach(error => { this.log(` โ€ข ${error}`, 'error'); }); } // Next steps this.print(''); this.log('๐Ÿš€ Next Steps:', 'info'); this.log('1. Start MCP server: hive mcp-server start', 'info'); this.log('2. Restart your IDE(s) to load the new configuration', 'info'); this.log('3. Test MCP tools in your IDE', 'info'); this.print(''); // Environment info this.log('๐ŸŒ HTTP+SSE Streaming Ready', 'config'); this.log(`๐ŸŒ MCP Server: http://localhost:${this.port}/mcp`, 'network'); this.print(''); // Add cache clearing advice for any failed configurations if (failedIDEs.length > 0) { this.print(''); this.log('๐Ÿ’ก Troubleshooting failed configurations:', 'info'); this.log('If you encounter issues, try clearing system caches:', 'info'); this.log(' hive cache clear', 'command'); this.log(' hive quickstart --clear-cache', 'command'); } // Success rate const totalDetected = this.detectedIDEs.size; const totalConfigured = successfulIDEs.length; if (totalDetected > 0) { const successRate = Math.round((totalConfigured / totalDetected) * 100); this.log(`๐Ÿ“ˆ Success Rate: ${successRate}% (${totalConfigured}/${totalDetected})`, successRate === 100 ? 'success' : successRate > 0 ? 'warning' : 'error'); } } /** * Check if IDE is already configured */ isIDEConfigured(ideKey) { const ideConfig = IDE_CONFIGS[ideKey]; if (!ideConfig || !existsSync(ideConfig.configPath)) { return false; } try { const content = readFileSync(ideConfig.configPath, 'utf8'); const config = JSON.parse(content); // Check for Hive AI MCP configuration return this.hasHiveAIConfig(config, ideConfig.configFormat); } catch (error) { return false; } } /** * Check if configuration contains Hive AI MCP setup */ hasHiveAIConfig(config, format) { switch (format) { case 'claude': return config.mcpServers && config.mcpServers['hive-ai']; case 'vscode': return config['mcp.servers'] && config['mcp.servers']['hive-ai']; case 'cursor': return config.mcpServers && config.mcpServers['hive-ai']; default: return false; } } /** * Remove Hive AI configuration from IDE */ async removeIDEConfig(ideKey) { const ideConfig = IDE_CONFIGS[ideKey]; if (!ideConfig || !existsSync(ideConfig.configPath)) { return false; } try { const content = readFileSync(ideConfig.configPath, 'utf8'); const config = JSON.parse(content); // Remove Hive AI configuration let modified = false; switch (ideConfig.configFormat) { case 'claude': if (config.mcpServers && config.mcpServers['hive-ai']) { delete config.mcpServers['hive-ai']; modified = true; } break; case 'vscode': if (config['mcp.servers'] && config['mcp.servers']['hive-ai']) { delete config['mcp.servers']['hive-ai']; modified = true; } break; case 'cursor': if (config.mcpServers && config.mcpServers['hive-ai']) { delete config.mcpServers['hive-ai']; modified = true; } break; } if (modified) { writeFileSync(ideConfig.configPath, JSON.stringify(config, null, 2)); return true; } } catch (error) { throw new Error(`Failed to remove config: ${error.message}`); } return false; } /** * Get setup status for all IDEs */ async getSetupStatus() { const status = { detected: [], configured: [], available: Object.keys(IDE_CONFIGS), mcpServer: { port: this.port || 3000, running: false } }; // Check which IDEs are detected for (const [ideKey, ideConfig] of Object.entries(IDE_CONFIGS)) { try { const commandName = ideConfig.detectCommand.split(' ')[0]; if (this.commandExists(commandName)) { status.detected.push(ideKey); if (this.isIDEConfigured(ideKey)) { status.configured.push(ideKey); } } } catch (error) { // IDE not installed } } // Check if MCP server is running try { const response = await fetch(`http://localhost:${status.mcpServer.port}/health`, { signal: AbortSignal.timeout(2000) }); status.mcpServer.running = response.ok; } catch (error) { status.mcpServer.running = false; } return status; } } // Export for programmatic use export { UniversalIDESetup, IDE_CONFIGS }; // CLI usage if (import.meta.url === `file://${process.argv[1]}`) { const args = process.argv.slice(2); const options = {}; // Parse command line arguments args.forEach((arg, index) => { if (arg === '--silent') options.silent = true; if (arg === '--force') options.force = true; if (arg === '--update-only') options.updateOnly = true; if (arg.startsWith('--port=')) options.port = parseInt(arg.split('=')[1]); if (arg === '--port' && args[index + 1]) { options.port = parseInt(args[index + 1]); } }); const setup = new UniversalIDESetup(options); setup.run().catch(error => { console.error('Setup failed:', error.message); process.exit(1); }); }