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