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

281 lines (235 loc) 8.76 kB
/** * Editor Path Resolver for VDK CLI * ----------------------- * ES module to standardize access to editor configuration paths * and MCP server settings across different editor environments. * * This module provides a consistent interface for: * 1. Resolving global and project-specific editor configuration paths * 2. Accessing MCP server configurations * 3. Expanding platform-specific paths (like ~ on Unix systems) */ import fs from 'fs'; import os from 'os'; import path from 'path'; // Import centralized IDE configurations import { IDE_CONFIGURATIONS } from './ide-configuration.js'; /** * Expands a path with tilde to absolute path * @param {string} filePath - Path that may contain tilde (~) * @returns {string} Resolved absolute path */ function expandPath(filePath) { if (!filePath) return ''; // Replace ~ with user's home directory if (filePath.startsWith('~')) { return path.join(os.homedir(), filePath.substring(1)); } return filePath; } /** * Get the MCP configuration file path for a specific IDE * @param {string} ideId - IDE identifier from ide-configuration.js * @param {string} projectPath - Path to the project root * @param {boolean} preferGlobal - Whether to prefer global config over project-specific * @returns {object} Object containing both projectConfig and globalConfig paths */ function getMCPConfigPaths(ideId, projectPath, preferGlobal = false) { const ideConfig = IDE_CONFIGURATIONS.find((ide) => ide.id === ideId) || IDE_CONFIGURATIONS.find((ide) => ide.id === 'generic'); const result = { projectConfig: null, globalConfig: null, activeConfig: null, }; // Set project-specific config path if available if (ideConfig.mcpConfigFile) { result.projectConfig = path.join(projectPath, ideConfig.mcpConfigFile); } // Set global config path if available if (ideConfig.globalConfigPath) { result.globalConfig = expandPath(ideConfig.globalConfigPath); } // Determine which config is active based on preference and existence if (preferGlobal && result.globalConfig && fs.existsSync(result.globalConfig)) { result.activeConfig = result.globalConfig; } else if (result.projectConfig && fs.existsSync(result.projectConfig)) { result.activeConfig = result.projectConfig; } else if (result.globalConfig && fs.existsSync(result.globalConfig)) { result.activeConfig = result.globalConfig; } else if (result.projectConfig) { // Return project path even if it doesn't exist yet result.activeConfig = result.projectConfig; } return result; } /** * Get the MCP configuration content for a specific IDE * @param {string} ideId - IDE identifier from ide-configuration.js * @param {string} projectPath - Path to the project root * @returns {object|null} Parsed MCP configuration or null if not found */ function getMCPConfiguration(ideId, projectPath) { const configPaths = getMCPConfigPaths(ideId, projectPath); if (configPaths.activeConfig && fs.existsSync(configPaths.activeConfig)) { try { const configContent = fs.readFileSync(configPaths.activeConfig, 'utf8'); return JSON.parse(configContent); } catch (error) { console.error(`Error reading MCP configuration: ${error.message}`); return null; } } return null; } /** * Get a map of all known editor configuration paths * @param {string} projectPath - Path to the project root * @returns {object} Map of editor IDs to their configuration paths */ function getAllEditorConfigPaths(projectPath) { const editorPaths = {}; IDE_CONFIGURATIONS.forEach((ide) => { const mcpPaths = getMCPConfigPaths(ide.id, projectPath); editorPaths[ide.id] = { name: ide.name, projectConfigPath: mcpPaths.projectConfig ? path.relative(projectPath, mcpPaths.projectConfig) : null, globalConfigPath: mcpPaths.globalConfig, activeConfigPath: mcpPaths.activeConfig ? path.relative(projectPath, mcpPaths.activeConfig) : null, rulesFolder: ide.rulesFolder, }; }); return editorPaths; } /** * Generate the content for the MCP configuration file in 03-mcp-configuration.mdc * based on the available MCP configurations from different editors * * @param {string} projectPath - Path to the project root * @returns {string} Generated MCP configuration content */ function generateMCPConfigurationContent(projectPath) { let content = ''; const editorPaths = getAllEditorConfigPaths(projectPath); // Add the editor paths section content += '## Editor Configuration Paths\n\n'; Object.keys(editorPaths).forEach((ideId) => { const editor = editorPaths[ideId]; if (!editor.name) return; content += `### ${editor.name}\n`; if (editor.projectConfigPath) { content += `- **Project Config**: \`${editor.projectConfigPath}\`\n`; } if (editor.globalConfigPath) { content += `- **Global Config**: \`${editor.globalConfigPath}\`\n`; } if (editor.rulesFolder) { content += `- **Rules Folder**: \`${editor.rulesFolder}\`\n`; } content += '\n'; }); // Try to find and include MCP servers from configs const mcpServers = []; Object.keys(editorPaths).forEach((ideId) => { const config = getMCPConfiguration(ideId, projectPath); if (config && config.servers) { Object.keys(config.servers).forEach((serverName) => { const server = config.servers[serverName]; if (!mcpServers.some((s) => s.name === serverName)) { mcpServers.push({ name: serverName, config: server, source: editorPaths[ideId].name, }); } }); } }); // Add the MCP servers section if any were found if (mcpServers.length > 0) { content += '## Available MCP Servers\n\n'; mcpServers.forEach((server) => { content += `### ${server.name}\n`; content += `Source: ${server.source}\n`; if (server.config.description) { content += `Purpose: ${server.config.description}\n`; } if (server.config.commands) { content += 'Key Commands: '; content += Object.keys(server.config.commands) .map((cmd) => `\`${cmd}\``) .join(', '); content += '\n'; } content += '\n'; }); } return content; } /** * Update the MCP configuration file (03-mcp-configuration.mdc) with the latest * editor paths and MCP server information * * @param {string} projectPath - Path to the project root * @param {string} rulePath - Path to the rule directory * @returns {boolean} Success status */ function updateMCPConfigurationFile(projectPath, rulePath) { const mcpFilePath = path.join(rulePath, '03-mcp-configuration.mdc'); // Check if the file exists if (!fs.existsSync(mcpFilePath)) { console.error(`MCP configuration file not found: ${mcpFilePath}`); return false; } try { // Read the existing file const existingContent = fs.readFileSync(mcpFilePath, 'utf8'); // Generate the new configuration content const newContent = generateMCPConfigurationContent(projectPath); // Find the position to insert the content // Look for the marker section or create it at the end of the file const markerRegex = /## Editor Configuration Paths\s+[\s\S]*?(?=##|$)/; let updatedContent = ''; if (markerRegex.test(existingContent)) { // Replace the existing section updatedContent = existingContent.replace(markerRegex, newContent); } else { // Find a good place to insert the section const insertPositions = [ existingContent.indexOf('## Available MCP Servers'), existingContent.indexOf('## MCP Technology Integration'), existingContent.indexOf('## Code Generation Patterns'), existingContent.indexOf('---\nNOTE TO AI:'), ].filter((pos) => pos !== -1); if (insertPositions.length > 0) { // Insert before the earliest section found const pos = Math.min(...insertPositions); updatedContent = existingContent.slice(0, pos) + newContent + '\n' + existingContent.slice(pos); } else { // Append to the end if no good position is found updatedContent = existingContent + '\n\n' + newContent; } } // Write the updated content back to the file fs.writeFileSync(mcpFilePath, updatedContent, 'utf8'); return true; } catch (error) { console.error(`Error updating MCP configuration file: ${error.message}`); return false; } } // Export functions for use in other modules export { expandPath, generateMCPConfigurationContent, getAllEditorConfigPaths, getMCPConfigPaths, getMCPConfiguration, updateMCPConfigurationFile, };