From 913dbeced60ad65086df6233086d83a51ead81a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reider=20Oliv=C3=A9r?= Date: Tue, 24 Jun 2025 04:47:21 +0200 Subject: [PATCH] feat(ide-setup): add support for Cline IDE and configuration rules (#262) --- tools/installer/bin/bmad.js | 23 ++++---- tools/installer/config/install.config.yml | 11 +++- tools/installer/lib/ide-setup.js | 71 +++++++++++++++++++++++ tools/upgraders/v3-to-v4-upgrader.js | 1 + 4 files changed, 92 insertions(+), 14 deletions(-) diff --git a/tools/installer/bin/bmad.js b/tools/installer/bin/bmad.js index 99d22c9b..97415dc6 100755 --- a/tools/installer/bin/bmad.js +++ b/tools/installer/bin/bmad.js @@ -50,7 +50,7 @@ program .option('-t, --team ', 'Install specific team with required agents and dependencies') .option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)') .option('-d, --directory ', 'Installation directory (default: .bmad-core)') - .option('-i, --ide ', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, roo, other)') + .option('-i, --ide ', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, roo, cline, other)') .option('-e, --expansion-packs ', 'Install specific expansion packs (can specify multiple)') .action(async (options) => { try { @@ -67,7 +67,7 @@ program if (options.agent) installType = 'single-agent'; else if (options.team) installType = 'team'; else if (options.expansionOnly) installType = 'expansion-only'; - + const config = { installType, agent: options.agent, @@ -164,13 +164,13 @@ async function promptInstallation() { // Check if this is an existing v4 installation const installDir = path.resolve(answers.directory); const state = await installer.detectInstallationState(installDir); - + if (state.type === 'v4_existing') { console.log(chalk.yellow('\nšŸ” Found existing BMAD v4 installation')); console.log(` Directory: ${installDir}`); console.log(` Version: ${state.manifest?.version || 'Unknown'}`); console.log(` Installed: ${state.manifest?.installed_at ? new Date(state.manifest.installed_at).toLocaleDateString() : 'Unknown'}`); - + const { shouldUpdate } = await inquirer.prompt([ { type: 'confirm', @@ -179,7 +179,7 @@ async function promptInstallation() { default: true } ]); - + if (shouldUpdate) { // Skip other prompts and go directly to update answers.installType = 'update'; @@ -256,11 +256,11 @@ async function promptInstallation() { if (installType === 'full' || installType === 'team' || installType === 'expansion-only') { try { const availableExpansionPacks = await installer.getAvailableExpansionPacks(); - + if (availableExpansionPacks.length > 0) { let choices; let message; - + if (installType === 'expansion-only') { message = 'Select expansion packs to install (required):' choices = availableExpansionPacks.map(pack => ({ @@ -274,7 +274,7 @@ async function promptInstallation() { value: pack.id })); } - + const { expansionPacks } = await inquirer.prompt([ { type: 'checkbox', @@ -289,7 +289,7 @@ async function promptInstallation() { } : undefined } ]); - + // Use selected expansion packs directly answers.expansionPacks = expansionPacks; } else { @@ -313,11 +313,12 @@ async function promptInstallation() { { name: 'Cursor', value: 'cursor' }, { name: 'Claude Code', value: 'claude-code' }, { name: 'Windsurf', value: 'windsurf' }, - { name: 'Roo Code', value: 'roo' } + { name: 'Roo Code', value: 'roo' }, + { name: 'Cline', value: 'cline' } ] } ]); - + // Use selected IDEs directly answers.ides = ides; diff --git a/tools/installer/config/install.config.yml b/tools/installer/config/install.config.yml index 76144c3e..1e6bd7ca 100644 --- a/tools/installer/config/install.config.yml +++ b/tools/installer/config/install.config.yml @@ -92,10 +92,15 @@ ide-configurations: # 3. The AI will adopt that agent's full personality and capabilities cline: name: Cline - format: unknown + rule-dir: .clinerules/ + format: multi-file + command-suffix: .md instructions: | - # Cline configuration coming soon - # Manual setup: Copy IDE agent files to your Cline configuration + # To use BMAD agents in Cline: + # 1. Open the Cline chat panel in VS Code + # 2. Type @agent-name (e.g., "@dev", "@pm", "@architect") + # 3. The agent will adopt that persona for the conversation + # 4. Rules are stored in .clinerules/ directory in your project available-agents: - id: analyst name: Business Analyst diff --git a/tools/installer/lib/ide-setup.js b/tools/installer/lib/ide-setup.js index 6a42a085..6ad79c54 100644 --- a/tools/installer/lib/ide-setup.js +++ b/tools/installer/lib/ide-setup.js @@ -31,6 +31,8 @@ class IdeSetup { return this.setupWindsurf(installDir, selectedAgent); case "roo": return this.setupRoo(installDir, selectedAgent); + case "cline": + return this.setupCline(installDir, selectedAgent); default: console.log(chalk.yellow(`\nIDE ${ide} not yet supported`)); return false; @@ -340,6 +342,75 @@ class IdeSetup { return true; } + + async setupCline(installDir, selectedAgent) { + const clineRulesDir = path.join(installDir, ".clinerules"); + const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir); + + await fileManager.ensureDirectory(clineRulesDir); + + // Define agent order for numeric prefixes + const agentOrder = { + 'bmad-master': 1, + 'bmad-orchestrator': 2, + 'pm': 3, + 'analyst': 4, + 'architect': 5, + 'po': 6, + 'sm': 7, + 'dev': 8, + 'qa': 9, + 'ux-expert': 10 + }; + + for (const agentId of agents) { + // Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install) + let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`); + if (!(await fileManager.pathExists(agentPath))) { + agentPath = path.join(installDir, "agents", `${agentId}.md`); + } + + if (await fileManager.pathExists(agentPath)) { + const agentContent = await fileManager.readFile(agentPath); + + // Get numeric prefix for ordering + const order = agentOrder[agentId] || 99; + const prefix = order.toString().padStart(2, '0'); + const mdPath = path.join(clineRulesDir, `${prefix}-${agentId}.md`); + + // Create MD content for Cline (focused on project standards and role) + let mdContent = `# ${this.getAgentTitle(agentId)} Agent\n\n`; + mdContent += `This rule defines the ${this.getAgentTitle(agentId)} persona and project standards.\n\n`; + mdContent += "## Role Definition\n\n"; + mdContent += + "When the user types `@" + agentId + "`, adopt this persona and follow these guidelines:\n\n"; + mdContent += "```yml\n"; + // Extract just the YAML content from the agent file + const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/); + if (yamlMatch) { + mdContent += yamlMatch[1].trim(); + } else { + // If no YAML found, include the whole content minus the header + mdContent += agentContent.replace(/^#.*$/m, "").trim(); + } + mdContent += "\n```\n\n"; + mdContent += "## Project Standards\n\n"; + mdContent += `- Always maintain consistency with project documentation in .bmad-core/\n`; + mdContent += `- Follow the agent's specific guidelines and constraints\n`; + mdContent += `- Update relevant project files when making changes\n`; + mdContent += `- Reference the complete agent definition in [.bmad-core/agents/${agentId}.md](.bmad-core/agents/${agentId}.md)\n\n`; + mdContent += "## Usage\n\n"; + mdContent += `Type \`@${agentId}\` to activate this ${this.getAgentTitle(agentId)} persona.\n`; + + await fileManager.writeFile(mdPath, mdContent); + console.log(chalk.green(`āœ“ Created rule: ${prefix}-${agentId}.md`)); + } + } + + console.log(chalk.green(`\nāœ“ Created Cline rules in ${clineRulesDir}`)); + + return true; + } } module.exports = new IdeSetup(); diff --git a/tools/upgraders/v3-to-v4-upgrader.js b/tools/upgraders/v3-to-v4-upgrader.js index c6a36f44..3adde275 100644 --- a/tools/upgraders/v3-to-v4-upgrader.js +++ b/tools/upgraders/v3-to-v4-upgrader.js @@ -561,6 +561,7 @@ class V3ToV4Upgrader { "claude-code": "Commands created in .claude/commands/", windsurf: "Rules created in .windsurf/rules/", roo: "Custom modes created in .roomodes", + cline: "Rules created in .clinerules/", }; // Setup each selected IDE