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