const fs = require('fs-extra') const path = require('path') const yaml = require('js-yaml') const { extractYamlFromAgent } = require('../../lib/yaml-utils') class ConfigLoader { constructor () { this.configPath = path.join(__dirname, '..', 'config', 'install.config.yaml') this.config = null } async load () { if (this.config) return this.config try { const configContent = await fs.readFile(this.configPath, 'utf8') this.config = yaml.load(configContent) return this.config } catch (error) { throw new Error(`Failed to load configuration: ${error.message}`) } } async getInstallationOptions () { const config = await this.load() return config['installation-options'] || {} } async getAvailableAgents () { const agentsDir = path.join(this.getBmadCorePath(), 'agents') try { const entries = await fs.readdir(agentsDir, { withFileTypes: true }) const agents = [] for (const entry of entries) { if (entry.isFile() && entry.name.endsWith('.md')) { const agentPath = path.join(agentsDir, entry.name) const agentId = path.basename(entry.name, '.md') try { const agentContent = await fs.readFile(agentPath, 'utf8') // Extract YAML block from agent file const yamlContentText = extractYamlFromAgent(agentContent) if (yamlContentText) { const yamlContent = yaml.load(yamlContentText) const agentConfig = yamlContent.agent || {} agents.push({ id: agentId, name: agentConfig.title || agentConfig.name || agentId, file: `bmad-core/agents/${entry.name}`, description: agentConfig.whenToUse || 'No description available' }) } } catch (error) { console.warn(`Failed to read agent ${entry.name}: ${error.message}`) } } } // Sort agents by name for consistent display agents.sort((a, b) => a.name.localeCompare(b.name)) return agents } catch (error) { console.warn(`Failed to read agents directory: ${error.message}`) return [] } } async getAvailableExpansionPacks () { const expansionPacksDir = path.join(this.getBmadCorePath(), '..', 'expansion-packs') try { const entries = await fs.readdir(expansionPacksDir, { withFileTypes: true }) const expansionPacks = [] for (const entry of entries) { if (entry.isDirectory() && !entry.name.startsWith('.')) { const packPath = path.join(expansionPacksDir, entry.name) const configPath = path.join(packPath, 'config.yaml') try { // Read config.yaml const configContent = await fs.readFile(configPath, 'utf8') const config = yaml.load(configContent) expansionPacks.push({ id: entry.name, name: config.name || entry.name, description: config['short-title'] || config.description || 'No description available', fullDescription: config.description || config['short-title'] || 'No description available', version: config.version || '1.0.0', author: config.author || 'BMad Team', packPath, dependencies: config.dependencies?.agents || [] }) } catch (error) { // Fallback if config.yaml doesn't exist or can't be read console.warn(`Failed to read config for expansion pack ${entry.name}: ${error.message}`) // Try to derive info from directory name as fallback const name = entry.name .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ') expansionPacks.push({ id: entry.name, name, description: 'No description available', fullDescription: 'No description available', version: '1.0.0', author: 'BMad Team', packPath, dependencies: [] }) } } } return expansionPacks } catch (error) { console.warn(`Failed to read expansion packs directory: ${error.message}`) return [] } } async getAgentDependencies (agentId) { // Use DependencyResolver to dynamically parse agent dependencies const DependencyResolver = require('../../lib/dependency-resolver') const resolver = new DependencyResolver(path.join(__dirname, '..', '..', '..')) const agentDeps = await resolver.resolveAgentDependencies(agentId) // Convert to flat list of file paths const depPaths = [] // Core files and utilities are included automatically by DependencyResolver // Add agent file itself is already handled by installer // Add all resolved resources for (const resource of agentDeps.resources) { const filePath = `.bmad-core/${resource.type}/${resource.id}.md` if (!depPaths.includes(filePath)) { depPaths.push(filePath) } } return depPaths } async getIdeConfiguration (ide) { const config = await this.load() const ideConfigs = config['ide-configurations'] || {} return ideConfigs[ide] || null } getBmadCorePath () { // Get the path to bmad-core relative to the installer (now under tools) return path.join(__dirname, '..', '..', '..', 'bmad-core') } getDistPath () { // Get the path to dist directory relative to the installer return path.join(__dirname, '..', '..', '..', 'dist') } getAgentPath (agentId) { return path.join(this.getBmadCorePath(), 'agents', `${agentId}.md`) } async getAvailableTeams () { const teamsDir = path.join(this.getBmadCorePath(), 'agent-teams') try { const entries = await fs.readdir(teamsDir, { withFileTypes: true }) const teams = [] for (const entry of entries) { if (entry.isFile() && entry.name.endsWith('.yaml')) { const teamPath = path.join(teamsDir, entry.name) try { const teamContent = await fs.readFile(teamPath, 'utf8') const teamConfig = yaml.load(teamContent) if (teamConfig.bundle) { teams.push({ id: path.basename(entry.name, '.yaml'), name: teamConfig.bundle.name || entry.name, description: teamConfig.bundle.description || 'Team configuration', icon: teamConfig.bundle.icon || '📋' }) } } catch (error) { console.warn(`Warning: Could not load team config ${entry.name}: ${error.message}`) } } } return teams } catch (error) { console.warn(`Warning: Could not scan teams directory: ${error.message}`) return [] } } getTeamPath (teamId) { return path.join(this.getBmadCorePath(), 'agent-teams', `${teamId}.yaml`) } async getTeamDependencies (teamId) { // Use DependencyResolver to dynamically parse team dependencies const DependencyResolver = require('../../lib/dependency-resolver') const resolver = new DependencyResolver(path.join(__dirname, '..', '..', '..')) try { const teamDeps = await resolver.resolveTeamDependencies(teamId) // Convert to flat list of file paths const depPaths = [] // Add team config file depPaths.push(`.bmad-core/agent-teams/${teamId}.yaml`) // Add all agents for (const agent of teamDeps.agents) { const filePath = `.bmad-core/agents/${agent.id}.md` if (!depPaths.includes(filePath)) { depPaths.push(filePath) } } // Add all resolved resources for (const resource of teamDeps.resources) { const filePath = `.bmad-core/${resource.type}/${resource.id}.${resource.type === 'workflows' ? 'yaml' : 'md'}` if (!depPaths.includes(filePath)) { depPaths.push(filePath) } } return depPaths } catch (error) { throw new Error(`Failed to resolve team dependencies for ${teamId}: ${error.message}`) } } } module.exports = new ConfigLoader()