const chalk = require('chalk'); const path = require('node:path'); const fs = require('fs-extra'); const { YamlXmlBuilder } = require('../lib/yaml-xml-builder'); const { getProjectRoot } = require('../lib/project-root'); const builder = new YamlXmlBuilder(); /** * Find .claude directory by searching up from current directory */ async function findClaudeDir(startDir) { let currentDir = startDir; const root = path.parse(currentDir).root; while (currentDir !== root) { const claudeDir = path.join(currentDir, '.claude'); if (await fs.pathExists(claudeDir)) { return claudeDir; } currentDir = path.dirname(currentDir); } return null; } module.exports = { command: 'build [agent]', description: 'Build agent XML files from YAML sources', options: [ ['-a, --all', 'Build all agents'], ['-d, --directory ', 'Project directory', '.'], ], action: async (agentName, options) => { try { let projectDir = path.resolve(options.directory); // Auto-detect .claude directory (search up from current dir) const claudeDir = await findClaudeDir(projectDir); if (!claudeDir) { console.log(chalk.yellow('\nāš ļø No .claude directory found')); console.log(chalk.dim('Run this command from your project directory or')); console.log(chalk.dim('use --directory flag to specify location')); console.log(chalk.dim('\nExample: npx bmad-method build pm --directory /path/to/project')); process.exit(1); } // Use the directory containing .claude projectDir = path.dirname(claudeDir); console.log(chalk.dim(`Using project: ${projectDir}\n`)); console.log(chalk.cyan('šŸ”Ø Building Agent Files\n')); if (options.all) { // Build all agents await buildAllAgents(projectDir); } else if (agentName) { // Build specific agent await buildAgent(projectDir, agentName); } else { // No agent specified, list available agents console.log(chalk.yellow('No agent specified. Use --all to build all agents or specify an agent name.')); console.log(chalk.dim('\nAvailable agents:')); await listAvailableAgents(projectDir); } process.exit(0); } catch (error) { console.error(chalk.red('\nError:'), error.message); if (process.env.DEBUG) { console.error(error.stack); } process.exit(1); } }, }; /** * Build a specific agent */ async function buildAgent(projectDir, agentName) { // First check standalone agents in bmad/agents/{agentname}/ const standaloneAgentDir = path.join(projectDir, '.bmad', 'agents', agentName); let standaloneYamlPath = path.join(standaloneAgentDir, `${agentName}.agent.yaml`); // If exact match doesn't exist, look for any .agent.yaml file in the directory if (!(await fs.pathExists(standaloneYamlPath)) && (await fs.pathExists(standaloneAgentDir))) { const files = await fs.readdir(standaloneAgentDir); const agentFile = files.find((f) => f.endsWith('.agent.yaml')); if (agentFile) { standaloneYamlPath = path.join(standaloneAgentDir, agentFile); } } if (await fs.pathExists(standaloneYamlPath)) { const yamlFileName = path.basename(standaloneYamlPath, '.agent.yaml'); const outputPath = path.join(standaloneAgentDir, `${yamlFileName}.md`); // Build the standalone agent console.log(chalk.cyan(` Building standalone agent ${agentName}...`)); const customizePath = path.join(projectDir, '.bmad', '_cfg', 'agents', `${agentName}.customize.yaml`); const customizeExists = await fs.pathExists(customizePath); await builder.buildAgent(standaloneYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true }); console.log(chalk.green(` āœ“ ${agentName} built successfully (standalone)`)); return; } // Find the agent YAML file in .claude/commands/bmad/ const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', '.bmad'); // Search all module directories for the agent const modules = await fs.readdir(bmadCommandsDir); let found = false; for (const module of modules) { const agentYamlPath = path.join(bmadCommandsDir, module, 'agents', `${agentName}.agent.yaml`); const outputPath = path.join(bmadCommandsDir, module, 'agents', `${agentName}.md`); if (await fs.pathExists(agentYamlPath)) { found = true; // Build the agent console.log(chalk.cyan(` Building ${agentName}...`)); const customizePath = path.join(projectDir, '.claude', '_cfg', 'agents', `${agentName}.customize.yaml`); const customizeExists = await fs.pathExists(customizePath); await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true }); console.log(chalk.green(` āœ“ ${agentName} built successfully`)); return; } } if (!found) { console.log(chalk.yellow(` āš ļø Agent '${agentName}' not found`)); console.log(chalk.dim(' Available agents:')); await listAvailableAgents(projectDir); } } /** * Build all agents */ async function buildAllAgents(projectDir) { let builtCount = 0; // First, build standalone agents in bmad/agents/ const standaloneAgentsDir = path.join(projectDir, '.bmad', 'agents'); if (await fs.pathExists(standaloneAgentsDir)) { console.log(chalk.cyan('\nBuilding standalone agents...')); const agentDirs = await fs.readdir(standaloneAgentsDir); for (const agentDirName of agentDirs) { const agentDir = path.join(standaloneAgentsDir, agentDirName); // Skip if not a directory const stat = await fs.stat(agentDir); if (!stat.isDirectory()) { continue; } // Find any .agent.yaml file in the directory const files = await fs.readdir(agentDir); const agentFile = files.find((f) => f.endsWith('.agent.yaml')); if (!agentFile) { continue; } const agentYamlPath = path.join(agentDir, agentFile); const agentName = path.basename(agentFile, '.agent.yaml'); const outputPath = path.join(agentDir, `${agentName}.md`); console.log(chalk.cyan(` Building standalone agent ${agentName}...`)); const customizePath = path.join(projectDir, '.bmad', '_cfg', 'agents', `${agentName}.customize.yaml`); const customizeExists = await fs.pathExists(customizePath); await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true }); console.log(chalk.green(` āœ“ ${agentName} (standalone)`)); builtCount++; } } // Then, build module agents in .claude/commands/bmad/ const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad'); if (await fs.pathExists(bmadCommandsDir)) { console.log(chalk.cyan('\nBuilding module agents...')); const modules = await fs.readdir(bmadCommandsDir); for (const module of modules) { const agentsDir = path.join(bmadCommandsDir, module, 'agents'); if (!(await fs.pathExists(agentsDir))) { continue; } const files = await fs.readdir(agentsDir); for (const file of files) { if (!file.endsWith('.agent.yaml')) { continue; } const agentName = file.replace('.agent.yaml', ''); const agentYamlPath = path.join(agentsDir, file); const outputPath = path.join(agentsDir, `${agentName}.md`); console.log(chalk.cyan(` Building ${agentName}...`)); const customizePath = path.join(projectDir, '.claude', '_cfg', 'agents', `${agentName}.customize.yaml`); const customizeExists = await fs.pathExists(customizePath); await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true }); console.log(chalk.green(` āœ“ ${agentName} (${module})`)); builtCount++; } } } console.log(chalk.green(`\nāœ“ Built ${builtCount} agent(s)`)); } /** * List available agents */ async function listAvailableAgents(projectDir) { // List standalone agents first const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents'); if (await fs.pathExists(standaloneAgentsDir)) { console.log(chalk.dim(' Standalone agents:')); const agentDirs = await fs.readdir(standaloneAgentsDir); for (const agentDirName of agentDirs) { const agentDir = path.join(standaloneAgentsDir, agentDirName); // Skip if not a directory const stat = await fs.stat(agentDir); if (!stat.isDirectory()) { continue; } // Find any .agent.yaml file in the directory const files = await fs.readdir(agentDir); const agentFile = files.find((f) => f.endsWith('.agent.yaml')); if (agentFile) { const agentName = path.basename(agentFile, '.agent.yaml'); console.log(chalk.dim(` - ${agentName} (in ${agentDirName}/)`)); } } } // List module agents const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad'); if (await fs.pathExists(bmadCommandsDir)) { console.log(chalk.dim(' Module agents:')); const modules = await fs.readdir(bmadCommandsDir); for (const module of modules) { const agentsDir = path.join(bmadCommandsDir, module, 'agents'); if (!(await fs.pathExists(agentsDir))) { continue; } const files = await fs.readdir(agentsDir); for (const file of files) { if (file.endsWith('.agent.yaml')) { const agentName = file.replace('.agent.yaml', ''); console.log(chalk.dim(` - ${agentName} (${module})`)); } } } } }