From efc2b6d0df3c0705ed91ec660e2ccc5a5ecb3db5 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sat, 22 Nov 2025 17:10:53 -0600 Subject: [PATCH] feat: complete custom agent support for ALL remaining IDEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Added installCustomAgentLauncher to remaining IDEs: ✅ Qwen (.qwen/commands/BMad/) - TOML format with proper description and prompt fields - Uses existing processAgentLauncherContent method - Format: custom-{agent-name}.toml ✅ Trae (.trae/rules/) - Markdown format with bmad-agent-custom- prefix - Follows existing BMAD naming pattern - Format: bmad-agent-custom-{agent-name}.md ✅ Roo (.roomodes) - YAML format appends to existing customModes section - Creates customModes section if missing - Format: bmad-custom-{agent-name} (slug-based) ✅ Kilo (.kilocodemodes) - YAML format identical to Roo pattern - Handles existing customModes gracefully - Format: bmad-custom-{agent-name} (slug-based) ✅ Auggie (.augment/commands/bmad/) - Frontmatter + Markdown format - Follows existing Auggie command pattern - Format: custom-{agent-name}.md ## Complete IDE Coverage: ALL IDEs now support custom agent installation: - 16 total IDEs with custom agent support - Various formats: TOML, YAML, Markdown, file-based - All include @agentPath references and usage instructions - Proper IDE-specific naming and directory structures Custom agents from .bmad/custom/src/agents/ now install to EVERY configured IDE! --- tools/cli/installers/lib/ide/auggie.js | 51 ++++++++++++++++++ tools/cli/installers/lib/ide/kilo.js | 71 ++++++++++++++++++++++++++ tools/cli/installers/lib/ide/qwen.js | 53 +++++++++++++++++++ tools/cli/installers/lib/ide/roo.js | 71 ++++++++++++++++++++++++++ tools/cli/installers/lib/ide/trae.js | 46 +++++++++++++++++ 5 files changed, 292 insertions(+) diff --git a/tools/cli/installers/lib/ide/auggie.js b/tools/cli/installers/lib/ide/auggie.js index 09ef6f6d..f7c6810e 100644 --- a/tools/cli/installers/lib/ide/auggie.js +++ b/tools/cli/installers/lib/ide/auggie.js @@ -174,6 +174,57 @@ BMAD ${workflow.module.toUpperCase()} module console.log(chalk.dim(` Removed old BMAD commands`)); } } + + /** + * Install a custom agent launcher for Auggie + * @param {string} projectDir - Project directory + * @param {string} agentName - Agent name (e.g., "fred-commit-poet") + * @param {string} agentPath - Path to compiled agent (relative to project root) + * @param {Object} metadata - Agent metadata + * @returns {Object} Installation result + */ + async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { + // Auggie uses .augment/commands directory + const location = path.join(projectDir, '.augment', 'commands'); + const bmadCommandsDir = path.join(location, 'bmad'); + + // Create .augment/commands/bmad directory if it doesn't exist + await fs.ensureDir(bmadCommandsDir); + + // Create custom agent launcher + const launcherContent = `--- +description: "Use the ${agentName} custom agent" +--- + +# ${agentName} Custom Agent + +**⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent! + +This is a launcher for the custom BMAD agent "${agentName}". + +## Usage +1. First run: \`${agentPath}\` to load the complete agent +2. Then use this command to activate ${agentName} + +The agent will follow the persona and instructions from the main agent file. + +## Module +BMAD Custom agent +`; + + const fileName = `custom-${agentName.toLowerCase()}.md`; + const launcherPath = path.join(bmadCommandsDir, fileName); + + // Write the launcher file + await fs.writeFile(launcherPath, launcherContent, 'utf8'); + + return { + ide: 'auggie', + path: path.relative(projectDir, launcherPath), + command: agentName, + type: 'custom-agent-launcher', + }; + } } module.exports = { AuggieSetup }; diff --git a/tools/cli/installers/lib/ide/kilo.js b/tools/cli/installers/lib/ide/kilo.js index 26fb9dc3..601cfc0a 100644 --- a/tools/cli/installers/lib/ide/kilo.js +++ b/tools/cli/installers/lib/ide/kilo.js @@ -170,6 +170,77 @@ class KiloSetup extends BaseIdeSetup { console.log(chalk.dim(`Removed ${removedCount} BMAD modes from .kilocodemodes`)); } } + + /** + * Install a custom agent launcher for Kilo + * @param {string} projectDir - Project directory + * @param {string} agentName - Agent name (e.g., "fred-commit-poet") + * @param {string} agentPath - Path to compiled agent (relative to project root) + * @param {Object} metadata - Agent metadata + * @returns {Object} Installation result + */ + async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { + const kilocodemodesPath = path.join(projectDir, this.configFile); + let existingContent = ''; + + // Read existing .kilocodemodes file + if (await this.pathExists(kilocodemodesPath)) { + existingContent = await this.readFile(kilocodemodesPath); + } + + // Create custom agent mode entry + const slug = `bmad-custom-${agentName.toLowerCase()}`; + const modeEntry = ` - slug: ${slug} + name: 'BMAD Custom: ${agentName}' + description: | + Custom BMAD agent: ${agentName} + + **⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent! + + This is a launcher for the custom BMAD agent "${agentName}". The agent will follow the persona and instructions from the main agent file. + prompt: | + @${agentPath} + always: false + permissions: all +`; + + // Check if mode already exists + if (existingContent.includes(slug)) { + return { + ide: 'kilo', + path: this.configFile, + command: agentName, + type: 'custom-agent-launcher', + alreadyExists: true, + }; + } + + // Build final content + let finalContent = ''; + if (existingContent) { + // Find customModes section or add it + if (existingContent.includes('customModes:')) { + // Append to existing customModes + finalContent = existingContent + modeEntry; + } else { + // Add customModes section + finalContent = existingContent.trim() + '\n\ncustomModes:\n' + modeEntry; + } + } else { + // Create new .kilocodemodes file with customModes + finalContent = 'customModes:\n' + modeEntry; + } + + // Write .kilocodemodes file + await this.writeFile(kilocodemodesPath, finalContent); + + return { + ide: 'kilo', + path: this.configFile, + command: slug, + type: 'custom-agent-launcher', + }; + } } module.exports = { KiloSetup }; diff --git a/tools/cli/installers/lib/ide/qwen.js b/tools/cli/installers/lib/ide/qwen.js index 885de410..71338722 100644 --- a/tools/cli/installers/lib/ide/qwen.js +++ b/tools/cli/installers/lib/ide/qwen.js @@ -313,6 +313,59 @@ ${prompt} console.log(chalk.dim(`Removed old BMAD configuration from Qwen Code`)); } } + + /** + * Install a custom agent launcher for Qwen + * @param {string} projectDir - Project directory + * @param {string} agentName - Agent name (e.g., "fred-commit-poet") + * @param {string} agentPath - Path to compiled agent (relative to project root) + * @param {Object} metadata - Agent metadata + * @returns {Object} Installation result + */ + async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { + const qwenDir = path.join(projectDir, this.configDir); + const commandsDir = path.join(qwenDir, this.commandsDir); + const bmadCommandsDir = path.join(commandsDir, this.bmadDir); + + // Create .qwen/commands/BMad directory if it doesn't exist + await fs.ensureDir(bmadCommandsDir); + + // Create custom agent launcher in TOML format (same pattern as regular agents) + const launcherContent = `# ${agentName} Custom Agent + +**⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent! + +This is a launcher for the custom BMAD agent "${agentName}". + +## Usage +1. First run: \`${agentPath}\` to load the complete agent +2. Then use this command to activate ${agentName} + +The agent will follow the persona and instructions from the main agent file. + +--- + +*Generated by BMAD Method*`; + + // Use Qwen's TOML conversion method + const tomlContent = this.processAgentLauncherContent(launcherContent, { + name: agentName, + module: 'custom', + }); + + const fileName = `custom-${agentName.toLowerCase()}.toml`; + const launcherPath = path.join(bmadCommandsDir, fileName); + + // Write the launcher file + await fs.writeFile(launcherPath, tomlContent, 'utf8'); + + return { + ide: 'qwen', + path: path.relative(projectDir, launcherPath), + command: agentName, + type: 'custom-agent-launcher', + }; + } } module.exports = { QwenSetup }; diff --git a/tools/cli/installers/lib/ide/roo.js b/tools/cli/installers/lib/ide/roo.js index 66d74f0f..22f333f6 100644 --- a/tools/cli/installers/lib/ide/roo.js +++ b/tools/cli/installers/lib/ide/roo.js @@ -248,6 +248,77 @@ class RooSetup extends BaseIdeSetup { console.log(chalk.dim(`Removed ${removedCount} BMAD modes from .roomodes`)); } } + + /** + * Install a custom agent launcher for Roo + * @param {string} projectDir - Project directory + * @param {string} agentName - Agent name (e.g., "fred-commit-poet") + * @param {string} agentPath - Path to compiled agent (relative to project root) + * @param {Object} metadata - Agent metadata + * @returns {Object} Installation result + */ + async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { + const roomodesPath = path.join(projectDir, this.configFile); + let existingContent = ''; + + // Read existing .roomodes file + if (await this.pathExists(roomodesPath)) { + existingContent = await this.readFile(roomodesPath); + } + + // Create custom agent mode entry + const slug = `bmad-custom-${agentName.toLowerCase()}`; + const modeEntry = ` - slug: ${slug} + name: 'BMAD Custom: ${agentName}' + description: | + Custom BMAD agent: ${agentName} + + **⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent! + + This is a launcher for the custom BMAD agent "${agentName}". The agent will follow the persona and instructions from the main agent file. + prompt: | + @${agentPath} + always: false + permissions: all +`; + + // Check if mode already exists + if (existingContent.includes(slug)) { + return { + ide: 'roo', + path: this.configFile, + command: agentName, + type: 'custom-agent-launcher', + alreadyExists: true, + }; + } + + // Build final content + let finalContent = ''; + if (existingContent) { + // Find customModes section or add it + if (existingContent.includes('customModes:')) { + // Append to existing customModes + finalContent = existingContent + modeEntry; + } else { + // Add customModes section + finalContent = existingContent.trim() + '\n\ncustomModes:\n' + modeEntry; + } + } else { + // Create new .roomodes file with customModes + finalContent = 'customModes:\n' + modeEntry; + } + + // Write .roomodes file + await this.writeFile(roomodesPath, finalContent); + + return { + ide: 'roo', + path: this.configFile, + command: slug, + type: 'custom-agent-launcher', + }; + } } module.exports = { RooSetup }; diff --git a/tools/cli/installers/lib/ide/trae.js b/tools/cli/installers/lib/ide/trae.js index 9aaa10cc..8357efe9 100644 --- a/tools/cli/installers/lib/ide/trae.js +++ b/tools/cli/installers/lib/ide/trae.js @@ -261,6 +261,52 @@ Part of the BMAD ${workflow.module.toUpperCase()} module. } } } + + /** + * Install a custom agent launcher for Trae + * @param {string} projectDir - Project directory + * @param {string} agentName - Agent name (e.g., "fred-commit-poet") + * @param {string} agentPath - Path to compiled agent (relative to project root) + * @param {Object} metadata - Agent metadata + * @returns {Object} Installation result + */ + async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { + const traeDir = path.join(projectDir, this.configDir); + const rulesDir = path.join(traeDir, this.rulesDir); + + // Create .trae/rules directory if it doesn't exist + await fs.ensureDir(rulesDir); + + // Create custom agent launcher + const launcherContent = `# ${agentName} Custom Agent + +**⚠️ IMPORTANT**: Run @${agentPath} first to load the complete agent! + +This is a launcher for the custom BMAD agent "${agentName}". + +## Usage +1. First run: \`${agentPath}\` to load the complete agent +2. Then use this rule to activate ${agentName} + +The agent will follow the persona and instructions from the main agent file. + +--- + +*Generated by BMAD Method*`; + + const fileName = `bmad-agent-custom-${agentName.toLowerCase()}.md`; + const launcherPath = path.join(rulesDir, fileName); + + // Write the launcher file + await fs.writeFile(launcherPath, launcherContent, 'utf8'); + + return { + ide: 'trae', + path: path.relative(projectDir, launcherPath), + command: agentName, + type: 'custom-agent-launcher', + }; + } } module.exports = { TraeSetup };