UNPKG

@vibe-dev-kit/cli

Version:

Advanced Command-line toolkit that analyzes your codebase and deploys project-aware rules, memories, commands and agents to any AI coding assistant - VDK is the world's first Vibe Development Kit

455 lines (400 loc) 13 kB
/** * Integration Manager * ------------------ * Central manager for all VDK integrations (IDEs, AI tools, platforms) * Handles discovery, registration, and coordination of integrations. */ import chalk from 'chalk'; import { BaseIntegration } from './base-integration.js'; /** * Central manager for all VDK integrations */ export class IntegrationManager { constructor(projectPath = process.cwd()) { this.projectPath = projectPath; this.integrations = new Map(); this.detectionResults = new Map(); this.lastScan = null; } /** * Register an integration * @param {BaseIntegration} integration - Integration instance */ register(integration) { if (!(integration instanceof BaseIntegration)) { throw new Error('Integration must extend BaseIntegration'); } this.integrations.set(integration.name, integration); } /** * Register multiple integrations * @param {Array<BaseIntegration>} integrations - Array of integration instances */ registerMultiple(integrations) { integrations.forEach((integration) => this.register(integration)); } /** * Discover and register all available integrations * @param {Object} options - Discovery options * @returns {Object} Discovery results */ async discoverIntegrations(options = {}) { const { verbose = false } = options; // Dynamically import and register all integrations const integrationModules = [ './claude-code-integration.js', './cursor-integration.js', './windsurf-integration.js', './github-copilot-integration.js', './generic-ide-integration.js', ]; const results = { loaded: [], failed: [], registered: 0, }; for (const modulePath of integrationModules) { try { const module = await import(modulePath); let foundIntegration = false; // Look for integration class exports for (const [exportName, exportValue] of Object.entries(module)) { if ( exportName.includes('Integration') && typeof exportValue === 'function' && exportValue.prototype instanceof BaseIntegration ) { try { const integration = new exportValue(this.projectPath); this.register(integration); results.loaded.push({ module: modulePath, class: exportName, name: integration.name, }); results.registered++; foundIntegration = true; } catch (constructorError) { results.failed.push({ module: modulePath, class: exportName, error: `Constructor failed: ${constructorError.message}`, }); } } } if (!foundIntegration) { results.failed.push({ module: modulePath, error: 'No valid Integration class found in module', }); } } catch (error) { // Integration module doesn't exist or failed to load - that's OK // We only register integrations that are available results.failed.push({ module: modulePath, error: error.message, }); if (process.env.VDK_DEBUG || verbose) { console.warn( chalk.yellow(`Failed to load integration module ${modulePath}: ${error.message}`) ); } } } if (verbose) { console.log(chalk.blue(`Discovery complete: ${results.registered} integrations registered`)); if (results.failed.length > 0 && process.env.VDK_DEBUG) { console.log(chalk.gray(`Failed modules: ${results.failed.length}`)); } } return results; } /** * Scan all registered integrations for usage * @param {Object} options - Scan options * @returns {Object} Scan results */ async scanAll(options = {}) { const { verbose = false, force = false } = options; if (verbose) { console.log(chalk.blue('🔍 Scanning for integration usage...')); } const results = { active: [], inactive: [], recommendations: [], errors: [], summary: {}, }; // Ensure we have integrations to scan if (this.integrations.size === 0) { if (verbose) { console.log( chalk.yellow('⚠️ No integrations registered. Run discoverIntegrations() first.') ); } results.errors.push('No integrations registered'); return results; } for (const [name, integration] of this.integrations) { try { // Validate integration instance if (!integration || typeof integration.getCachedDetection !== 'function') { throw new Error(`Invalid integration instance for ${name}`); } const detection = integration.getCachedDetection(force); this.detectionResults.set(name, detection); const integrationResult = { name, isActive: integration.isActive(), confidence: integration.getConfidence(), indicators: integration.getIndicators(), recommendations: integration.getRecommendations(), detection, }; if (integrationResult.isActive) { results.active.push(integrationResult); } else { results.inactive.push(integrationResult); } // Collect all recommendations results.recommendations.push(...integrationResult.recommendations); if (verbose && integrationResult.indicators.length > 0) { console.log(chalk.gray(` • ${name}: ${integrationResult.confidence} confidence`)); integrationResult.indicators.forEach((indicator) => { console.log(chalk.gray(` - ${indicator}`)); }); } } catch (error) { const errorInfo = { integration: name, error: error.message, timestamp: new Date().toISOString(), }; results.errors.push(errorInfo); if (verbose) { console.log(chalk.red(`❌ Failed to scan ${name}: ${error.message}`)); if (process.env.VDK_DEBUG) { console.log(chalk.gray(`Debug: ${error.stack}`)); } } // Add to inactive with error indicator results.inactive.push({ name, isActive: false, confidence: 'error', indicators: [`Scan failed: ${error.message}`], recommendations: [`Fix ${name} integration configuration`], detection: { isUsed: false, confidence: 'error', indicators: [], recommendations: [] }, }); } } // Generate summary results.summary = { totalIntegrations: this.integrations.size, activeIntegrations: results.active.length, highConfidenceIntegrations: results.active.filter((r) => r.confidence === 'high').length, recommendationCount: results.recommendations.length, scanTime: new Date().toISOString(), }; this.lastScan = results; return results; } /** * Get integration by name * @param {string} name - Integration name * @returns {BaseIntegration|null} Integration instance or null */ getIntegration(name) { return this.integrations.get(name) || null; } /** * Get all registered integration names * @returns {Array<string>} Array of integration names */ getIntegrationNames() { return Array.from(this.integrations.keys()); } /** * Get all registered integrations * @returns {Array<BaseIntegration>} Array of integration instances */ getAllIntegrations() { return Array.from(this.integrations.values()); } /** * Get active integrations (high confidence) * @returns {Array<Object>} Array of active integration results */ getActiveIntegrations() { if (!this.lastScan) { return []; } return this.lastScan.active.filter((integration) => integration.confidence === 'high'); } /** * Get recommendations for all integrations * @returns {Array<string>} Array of recommendations */ getAllRecommendations() { if (!this.lastScan) { return []; } return this.lastScan.recommendations; } /** * Initialize active integrations with VDK configuration * @param {Object} options - Initialization options * @returns {Object} Initialization results */ async initializeActive(options = {}) { const { verbose = false } = options; const results = { successful: [], failed: [], skipped: [], }; const activeIntegrations = this.getActiveIntegrations(); if (activeIntegrations.length === 0) { if (verbose) { console.log(chalk.yellow('No active integrations found to initialize')); } return results; } for (const integrationResult of activeIntegrations) { const integration = this.getIntegration(integrationResult.name); if (!integration) { results.failed.push({ name: integrationResult.name, error: 'Integration not found', }); continue; } try { if (verbose) { console.log(chalk.blue(`Initializing ${integration.name} integration...`)); } const success = await integration.initialize({ ...options, projectPath: this.projectPath, }); if (success) { results.successful.push({ name: integration.name, confidence: integration.getConfidence(), }); if (verbose) { console.log(chalk.green(`✅ ${integration.name} initialized successfully`)); } } else { results.failed.push({ name: integration.name, error: 'Initialization returned false', }); } } catch (error) { results.failed.push({ name: integration.name, error: error.message, }); if (verbose) { console.log(chalk.red(`❌ Failed to initialize ${integration.name}: ${error.message}`)); } } } return results; } /** * Get integration status summary for display * @returns {Object} Status summary */ getStatusSummary() { if (!this.lastScan) { return { message: 'No integrations scanned yet', integrations: [], }; } const { summary, active, inactive } = this.lastScan; let message = ''; if (summary.activeIntegrations === 0) { message = 'No active integrations detected'; } else if (summary.highConfidenceIntegrations > 0) { message = `${summary.highConfidenceIntegrations} high-confidence integration(s) detected`; } else { message = `${summary.activeIntegrations} integration(s) detected with varying confidence`; } return { message, summary, active: active.map((i) => ({ name: i.name, confidence: i.confidence, indicatorCount: i.indicators.length, })), inactive: inactive.map((i) => ({ name: i.name, confidence: i.confidence, })), }; } /** * Clear all cached detection results */ clearCache() { for (const integration of this.integrations.values()) { integration._detectionCache = null; integration._detectionCacheTime = null; } this.detectionResults.clear(); this.lastScan = null; } /** * Get integration-specific recommendations * @param {string} integrationName - Name of integration * @returns {Array<string>} Array of recommendations for this integration */ getIntegrationRecommendations(integrationName) { const integration = this.getIntegration(integrationName); if (!integration) { return []; } return integration.getRecommendations(); } /** * Check if a specific integration is active * @param {string} integrationName - Name of integration * @returns {boolean} True if integration is active */ isIntegrationActive(integrationName) { const integration = this.getIntegration(integrationName); if (!integration) { return false; } return integration.isActive(); } /** * Get detailed integration information * @param {string} integrationName - Name of integration * @returns {Object|null} Detailed integration info or null */ getIntegrationDetails(integrationName) { const integration = this.getIntegration(integrationName); if (!integration) { return null; } const detection = integration.getCachedDetection(); return { name: integration.name, isActive: integration.isActive(), confidence: integration.getConfidence(), indicators: integration.getIndicators(), recommendations: integration.getRecommendations(), configPaths: integration.getConfigPaths(), summary: integration.getSummary(), detection, }; } }