Feature: Installer commands for Crush CLI (#429)

* feat: add support for Crush IDE configuration and commands

* fix: update Crush IDE instructions for clarity on persona/task switching

---------

Co-authored-by: Brian <bmadcode@gmail.com>
This commit is contained in:
Thiago Freitas
2025-08-16 00:38:44 -03:00
committed by GitHub
parent 0b61175d98
commit 848e33fdd9
3 changed files with 237 additions and 112 deletions

View File

@@ -45,7 +45,7 @@ program
.option('-f, --full', 'Install complete BMad Method') .option('-f, --full', 'Install complete BMad Method')
.option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)') .option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)')
.option('-d, --directory <path>', 'Installation directory') .option('-d, --directory <path>', 'Installation directory')
.option('-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, other)') .option('-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, crush, other)')
.option('-e, --expansion-packs <packs...>', 'Install specific expansion packs (can specify multiple)') .option('-e, --expansion-packs <packs...>', 'Install specific expansion packs (can specify multiple)')
.action(async (options) => { .action(async (options) => {
try { try {
@@ -183,17 +183,17 @@ program
}); });
async function promptInstallation() { async function promptInstallation() {
// Display ASCII logo // Display ASCII logo
console.log(chalk.bold.cyan(` console.log(chalk.bold.cyan(`
██████╗ ███╗ ███╗ █████╗ ██████╗ ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗ █████╗ ██████╗ ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗
██╔══██╗████╗ ████║██╔══██╗██╔══██╗ ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗ ██╔══██╗████╗ ████║██╔══██╗██╔══██╗ ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗
██████╔╝██╔████╔██║███████║██║ ██║█████╗██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║ ██████╔╝██╔████╔██║███████║██║ ██║█████╗██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║
██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║╚════╝██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║ ██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║╚════╝██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║
██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝ ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝ ██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝ ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝
`)); `));
console.log(chalk.bold.magenta('🚀 Universal AI Agent Framework for Any Domain')); console.log(chalk.bold.magenta('🚀 Universal AI Agent Framework for Any Domain'));
console.log(chalk.bold.blue(`✨ Installer v${version}\n`)); console.log(chalk.bold.blue(`✨ Installer v${version}\n`));
@@ -218,63 +218,63 @@ async function promptInstallation() {
// Detect existing installations // Detect existing installations
const installDir = path.resolve(directory); const installDir = path.resolve(directory);
const state = await installer.detectInstallationState(installDir); const state = await installer.detectInstallationState(installDir);
// Check for existing expansion packs // Check for existing expansion packs
const existingExpansionPacks = state.expansionPacks || {}; const existingExpansionPacks = state.expansionPacks || {};
// Get available expansion packs // Get available expansion packs
const availableExpansionPacks = await installer.getAvailableExpansionPacks(); const availableExpansionPacks = await installer.getAvailableExpansionPacks();
// Build choices list // Build choices list
const choices = []; const choices = [];
// Load core config to get short-title // Load core config to get short-title
const coreConfigPath = path.join(__dirname, '..', '..', '..', 'bmad-core', 'core-config.yaml'); const coreConfigPath = path.join(__dirname, '..', '..', '..', 'bmad-core', 'core-config.yaml');
const coreConfig = yaml.load(await fs.readFile(coreConfigPath, 'utf8')); const coreConfig = yaml.load(await fs.readFile(coreConfigPath, 'utf8'));
const coreShortTitle = coreConfig['short-title'] || 'BMad Agile Core System'; const coreShortTitle = coreConfig['short-title'] || 'BMad Agile Core System';
// Add BMad core option // Add BMad core option
let bmadOptionText; let bmadOptionText;
if (state.type === 'v4_existing') { if (state.type === 'v4_existing') {
const currentVersion = state.manifest?.version || 'unknown'; const currentVersion = state.manifest?.version || 'unknown';
const newVersion = version; // Always use package.json version const newVersion = version; // Always use package.json version
const versionInfo = currentVersion === newVersion const versionInfo = currentVersion === newVersion
? `(v${currentVersion} - reinstall)` ? `(v${currentVersion} - reinstall)`
: `(v${currentVersion} → v${newVersion})`; : `(v${currentVersion} → v${newVersion})`;
bmadOptionText = `Update ${coreShortTitle} ${versionInfo} .bmad-core`; bmadOptionText = `Update ${coreShortTitle} ${versionInfo} .bmad-core`;
} else { } else {
bmadOptionText = `${coreShortTitle} (v${version}) .bmad-core`; bmadOptionText = `${coreShortTitle} (v${version}) .bmad-core`;
} }
choices.push({ choices.push({
name: bmadOptionText, name: bmadOptionText,
value: 'bmad-core', value: 'bmad-core',
checked: true checked: true
}); });
// Add expansion pack options // Add expansion pack options
for (const pack of availableExpansionPacks) { for (const pack of availableExpansionPacks) {
const existing = existingExpansionPacks[pack.id]; const existing = existingExpansionPacks[pack.id];
let packOptionText; let packOptionText;
if (existing) { if (existing) {
const currentVersion = existing.manifest?.version || 'unknown'; const currentVersion = existing.manifest?.version || 'unknown';
const newVersion = pack.version; const newVersion = pack.version;
const versionInfo = currentVersion === newVersion const versionInfo = currentVersion === newVersion
? `(v${currentVersion} - reinstall)` ? `(v${currentVersion} - reinstall)`
: `(v${currentVersion} → v${newVersion})`; : `(v${currentVersion} → v${newVersion})`;
packOptionText = `Update ${pack.shortTitle} ${versionInfo} .${pack.id}`; packOptionText = `Update ${pack.shortTitle} ${versionInfo} .${pack.id}`;
} else { } else {
packOptionText = `${pack.shortTitle} (v${pack.version}) .${pack.id}`; packOptionText = `${pack.shortTitle} (v${pack.version}) .${pack.id}`;
} }
choices.push({ choices.push({
name: packOptionText, name: packOptionText,
value: pack.id, value: pack.id,
checked: false checked: false
}); });
} }
// Ask what to install // Ask what to install
const { selectedItems } = await inquirer.prompt([ const { selectedItems } = await inquirer.prompt([
{ {
@@ -290,7 +290,7 @@ async function promptInstallation() {
} }
} }
]); ]);
// Process selections // Process selections
answers.installType = selectedItems.includes('bmad-core') ? 'full' : 'expansion-only'; answers.installType = selectedItems.includes('bmad-core') ? 'full' : 'expansion-only';
answers.expansionPacks = selectedItems.filter(item => item !== 'bmad-core'); answers.expansionPacks = selectedItems.filter(item => item !== 'bmad-core');
@@ -299,7 +299,7 @@ async function promptInstallation() {
if (selectedItems.includes('bmad-core')) { if (selectedItems.includes('bmad-core')) {
console.log(chalk.cyan('\n📋 Document Organization Settings')); console.log(chalk.cyan('\n📋 Document Organization Settings'));
console.log(chalk.dim('Configure how your project documentation should be organized.\n')); console.log(chalk.dim('Configure how your project documentation should be organized.\n'));
// Ask about PRD sharding // Ask about PRD sharding
const { prdSharded } = await inquirer.prompt([ const { prdSharded } = await inquirer.prompt([
{ {
@@ -310,7 +310,7 @@ async function promptInstallation() {
} }
]); ]);
answers.prdSharded = prdSharded; answers.prdSharded = prdSharded;
// Ask about architecture sharding // Ask about architecture sharding
const { architectureSharded } = await inquirer.prompt([ const { architectureSharded } = await inquirer.prompt([
{ {
@@ -321,7 +321,7 @@ async function promptInstallation() {
} }
]); ]);
answers.architectureSharded = architectureSharded; answers.architectureSharded = architectureSharded;
// Show warning if architecture sharding is disabled // Show warning if architecture sharding is disabled
if (!architectureSharded) { if (!architectureSharded) {
console.log(chalk.yellow.bold('\n⚠ IMPORTANT: Architecture Sharding Disabled')); console.log(chalk.yellow.bold('\n⚠ IMPORTANT: Architecture Sharding Disabled'));
@@ -330,7 +330,7 @@ async function promptInstallation() {
console.log(chalk.yellow('as these are used by the dev agent at runtime.')); console.log(chalk.yellow('as these are used by the dev agent at runtime.'));
console.log(chalk.yellow('\nAlternatively, you can remove these files from the devLoadAlwaysFiles list')); console.log(chalk.yellow('\nAlternatively, you can remove these files from the devLoadAlwaysFiles list'));
console.log(chalk.yellow('in your core-config.yaml after installation.')); console.log(chalk.yellow('in your core-config.yaml after installation.'));
const { acknowledge } = await inquirer.prompt([ const { acknowledge } = await inquirer.prompt([
{ {
type: 'confirm', type: 'confirm',
@@ -339,7 +339,7 @@ async function promptInstallation() {
default: false default: false
} }
]); ]);
if (!acknowledge) { if (!acknowledge) {
console.log(chalk.red('Installation cancelled.')); console.log(chalk.red('Installation cancelled.'));
process.exit(0); process.exit(0);
@@ -350,14 +350,14 @@ async function promptInstallation() {
// Ask for IDE configuration // Ask for IDE configuration
let ides = []; let ides = [];
let ideSelectionComplete = false; let ideSelectionComplete = false;
while (!ideSelectionComplete) { while (!ideSelectionComplete) {
console.log(chalk.cyan('\n🛠 IDE Configuration')); console.log(chalk.cyan('\n🛠 IDE Configuration'));
console.log(chalk.bold.yellow.bgRed(' ⚠️ IMPORTANT: This is a MULTISELECT! Use SPACEBAR to toggle each IDE! ')); console.log(chalk.bold.yellow.bgRed(' ⚠️ IMPORTANT: This is a MULTISELECT! Use SPACEBAR to toggle each IDE! '));
console.log(chalk.bold.magenta('🔸 Use arrow keys to navigate')); console.log(chalk.bold.magenta('🔸 Use arrow keys to navigate'));
console.log(chalk.bold.magenta('🔸 Use SPACEBAR to select/deselect IDEs')); console.log(chalk.bold.magenta('🔸 Use SPACEBAR to select/deselect IDEs'));
console.log(chalk.bold.magenta('🔸 Press ENTER when finished selecting\n')); console.log(chalk.bold.magenta('🔸 Press ENTER when finished selecting\n'));
const ideResponse = await inquirer.prompt([ const ideResponse = await inquirer.prompt([
{ {
type: 'checkbox', type: 'checkbox',
@@ -373,11 +373,12 @@ async function promptInstallation() {
{ name: 'Cline', value: 'cline' }, { name: 'Cline', value: 'cline' },
{ name: 'Gemini CLI', value: 'gemini' }, { name: 'Gemini CLI', value: 'gemini' },
{ name: 'Qwen Code', value: 'qwen-code' }, { name: 'Qwen Code', value: 'qwen-code' },
{ name: 'Crush', value: 'crush' },
{ name: 'Github Copilot', value: 'github-copilot' } { name: 'Github Copilot', value: 'github-copilot' }
] ]
} }
]); ]);
ides = ideResponse.ides; ides = ideResponse.ides;
// Confirm no IDE selection if none selected // Confirm no IDE selection if none selected
@@ -390,13 +391,13 @@ async function promptInstallation() {
default: false default: false
} }
]); ]);
if (!confirmNoIde) { if (!confirmNoIde) {
console.log(chalk.bold.red('\n🔄 Returning to IDE selection. Remember to use SPACEBAR to select IDEs!\n')); console.log(chalk.bold.red('\n🔄 Returning to IDE selection. Remember to use SPACEBAR to select IDEs!\n'));
continue; // Go back to IDE selection only continue; // Go back to IDE selection only
} }
} }
ideSelectionComplete = true; ideSelectionComplete = true;
} }
@@ -407,7 +408,7 @@ async function promptInstallation() {
if (ides.includes('github-copilot')) { if (ides.includes('github-copilot')) {
console.log(chalk.cyan('\n🔧 GitHub Copilot Configuration')); console.log(chalk.cyan('\n🔧 GitHub Copilot Configuration'));
console.log(chalk.dim('BMad works best with specific VS Code settings for optimal agent experience.\n')); console.log(chalk.dim('BMad works best with specific VS Code settings for optimal agent experience.\n'));
const { configChoice } = await inquirer.prompt([ const { configChoice } = await inquirer.prompt([
{ {
type: 'list', type: 'list',
@@ -430,7 +431,7 @@ async function promptInstallation() {
default: 'defaults' default: 'defaults'
} }
]); ]);
answers.githubCopilotConfig = { configChoice }; answers.githubCopilotConfig = { configChoice };
} }

View File

@@ -28,6 +28,16 @@ ide-configurations:
# To use BMad agents in Claude Code: # To use BMad agents in Claude Code:
# 1. Type /agent-name (e.g., "/dev", "/pm", "/architect") # 1. Type /agent-name (e.g., "/dev", "/pm", "/architect")
# 2. Claude will switch to that agent's persona # 2. Claude will switch to that agent's persona
crush:
name: Crush
rule-dir: .crush/commands/BMad/
format: multi-file
command-suffix: .md
instructions: |
# To use BMad agents in Crush:
# 1. Press CTRL + P and press TAB
# 2. Select agent or task
# 3. Crush will switch to that agent's persona / task
windsurf: windsurf:
name: Windsurf name: Windsurf
rule-dir: .windsurf/rules/ rule-dir: .windsurf/rules/
@@ -110,4 +120,4 @@ ide-configurations:
# 1. The installer creates a .qwen/bmad-method/ directory in your project. # 1. The installer creates a .qwen/bmad-method/ directory in your project.
# 2. It concatenates all agent files into a single QWEN.md file. # 2. It concatenates all agent files into a single QWEN.md file.
# 3. Simply mention the agent in your prompt (e.g., "As *dev, ..."). # 3. Simply mention the agent in your prompt (e.g., "As *dev, ...").
# 4. The Qwen Code CLI will automatically have the context for that agent. # 4. The Qwen Code CLI will automatically have the context for that agent.

View File

@@ -17,7 +17,7 @@ class IdeSetup extends BaseIdeSetup {
async loadIdeAgentConfig() { async loadIdeAgentConfig() {
if (this.ideAgentConfig) return this.ideAgentConfig; if (this.ideAgentConfig) return this.ideAgentConfig;
try { try {
const configPath = path.join(__dirname, '..', 'config', 'ide-agent-config.yaml'); const configPath = path.join(__dirname, '..', 'config', 'ide-agent-config.yaml');
const configContent = await fs.readFile(configPath, 'utf8'); const configContent = await fs.readFile(configPath, 'utf8');
@@ -45,6 +45,8 @@ class IdeSetup extends BaseIdeSetup {
return this.setupCursor(installDir, selectedAgent); return this.setupCursor(installDir, selectedAgent);
case "claude-code": case "claude-code":
return this.setupClaudeCode(installDir, selectedAgent); return this.setupClaudeCode(installDir, selectedAgent);
case "crush":
return this.setupCrush(installDir, selectedAgent);
case "windsurf": case "windsurf":
return this.setupWindsurf(installDir, selectedAgent); return this.setupWindsurf(installDir, selectedAgent);
case "trae": case "trae":
@@ -88,6 +90,30 @@ class IdeSetup extends BaseIdeSetup {
return true; return true;
} }
async setupCrush(installDir, selectedAgent) {
// Setup bmad-core commands
const coreSlashPrefix = await this.getCoreSlashPrefix(installDir);
const coreAgents = selectedAgent ? [selectedAgent] : await this.getCoreAgentIds(installDir);
const coreTasks = await this.getCoreTaskIds(installDir);
await this.setupCrushForPackage(installDir, "core", coreSlashPrefix, coreAgents, coreTasks, ".bmad-core");
// Setup expansion pack commands
const expansionPacks = await this.getInstalledExpansionPacks(installDir);
for (const packInfo of expansionPacks) {
const packSlashPrefix = await this.getExpansionPackSlashPrefix(packInfo.path);
const packAgents = await this.getExpansionPackAgents(packInfo.path);
const packTasks = await this.getExpansionPackTasks(packInfo.path);
if (packAgents.length > 0 || packTasks.length > 0) {
// Use the actual directory name where the expansion pack is installed
const rootPath = path.relative(installDir, packInfo.path);
await this.setupCrushForPackage(installDir, packInfo.name, packSlashPrefix, packAgents, packTasks, rootPath);
}
}
return true;
}
async setupClaudeCode(installDir, selectedAgent) { async setupClaudeCode(installDir, selectedAgent) {
// Setup bmad-core commands // Setup bmad-core commands
const coreSlashPrefix = await this.getCoreSlashPrefix(installDir); const coreSlashPrefix = await this.getCoreSlashPrefix(installDir);
@@ -101,7 +127,7 @@ class IdeSetup extends BaseIdeSetup {
const packSlashPrefix = await this.getExpansionPackSlashPrefix(packInfo.path); const packSlashPrefix = await this.getExpansionPackSlashPrefix(packInfo.path);
const packAgents = await this.getExpansionPackAgents(packInfo.path); const packAgents = await this.getExpansionPackAgents(packInfo.path);
const packTasks = await this.getExpansionPackTasks(packInfo.path); const packTasks = await this.getExpansionPackTasks(packInfo.path);
if (packAgents.length > 0 || packTasks.length > 0) { if (packAgents.length > 0 || packTasks.length > 0) {
// Use the actual directory name where the expansion pack is installed // Use the actual directory name where the expansion pack is installed
const rootPath = path.relative(installDir, packInfo.path); const rootPath = path.relative(installDir, packInfo.path);
@@ -138,13 +164,13 @@ class IdeSetup extends BaseIdeSetup {
// For core, use the normal search // For core, use the normal search
agentPath = await this.findAgentPath(agentId, installDir); agentPath = await this.findAgentPath(agentId, installDir);
} }
const commandPath = path.join(agentsDir, `${agentId}.md`); const commandPath = path.join(agentsDir, `${agentId}.md`);
if (agentPath) { if (agentPath) {
// Create command file with agent content // Create command file with agent content
let agentContent = await fileManager.readFile(agentPath); let agentContent = await fileManager.readFile(agentPath);
// Replace {root} placeholder with the appropriate root path for this context // Replace {root} placeholder with the appropriate root path for this context
agentContent = agentContent.replace(/{root}/g, rootPath); agentContent = agentContent.replace(/{root}/g, rootPath);
@@ -175,13 +201,13 @@ class IdeSetup extends BaseIdeSetup {
// For core, use the normal search // For core, use the normal search
taskPath = await this.findTaskPath(taskId, installDir); taskPath = await this.findTaskPath(taskId, installDir);
} }
const commandPath = path.join(tasksDir, `${taskId}.md`); const commandPath = path.join(tasksDir, `${taskId}.md`);
if (taskPath) { if (taskPath) {
// Create command file with task content // Create command file with task content
let taskContent = await fileManager.readFile(taskPath); let taskContent = await fileManager.readFile(taskPath);
// Replace {root} placeholder with the appropriate root path for this context // Replace {root} placeholder with the appropriate root path for this context
taskContent = taskContent.replace(/{root}/g, rootPath); taskContent = taskContent.replace(/{root}/g, rootPath);
@@ -200,6 +226,94 @@ class IdeSetup extends BaseIdeSetup {
console.log(chalk.dim(` - Tasks in: ${tasksDir}`)); console.log(chalk.dim(` - Tasks in: ${tasksDir}`));
} }
async setupCrushForPackage(installDir, packageName, slashPrefix, agentIds, taskIds, rootPath) {
const commandsBaseDir = path.join(installDir, ".crush", "commands", slashPrefix);
const agentsDir = path.join(commandsBaseDir, "agents");
const tasksDir = path.join(commandsBaseDir, "tasks");
// Ensure directories exist
await fileManager.ensureDirectory(agentsDir);
await fileManager.ensureDirectory(tasksDir);
// Setup agents
for (const agentId of agentIds) {
// Find the agent file - for expansion packs, prefer the expansion pack version
let agentPath;
if (packageName !== "core") {
// For expansion packs, first try to find the agent in the expansion pack directory
const expansionPackPath = path.join(installDir, rootPath, "agents", `${agentId}.md`);
if (await fileManager.pathExists(expansionPackPath)) {
agentPath = expansionPackPath;
} else {
// Fall back to core if not found in expansion pack
agentPath = await this.findAgentPath(agentId, installDir);
}
} else {
// For core, use the normal search
agentPath = await this.findAgentPath(agentId, installDir);
}
const commandPath = path.join(agentsDir, `${agentId}.md`);
if (agentPath) {
// Create command file with agent content
let agentContent = await fileManager.readFile(agentPath);
// Replace {root} placeholder with the appropriate root path for this context
agentContent = agentContent.replace(/{root}/g, rootPath);
// Add command header
let commandContent = `# /${agentId} Command\n\n`;
commandContent += `When this command is used, adopt the following agent persona:\n\n`;
commandContent += agentContent;
await fileManager.writeFile(commandPath, commandContent);
console.log(chalk.green(`✓ Created agent command: /${agentId}`));
}
}
// Setup tasks
for (const taskId of taskIds) {
// Find the task file - for expansion packs, prefer the expansion pack version
let taskPath;
if (packageName !== "core") {
// For expansion packs, first try to find the task in the expansion pack directory
const expansionPackPath = path.join(installDir, rootPath, "tasks", `${taskId}.md`);
if (await fileManager.pathExists(expansionPackPath)) {
taskPath = expansionPackPath;
} else {
// Fall back to core if not found in expansion pack
taskPath = await this.findTaskPath(taskId, installDir);
}
} else {
// For core, use the normal search
taskPath = await this.findTaskPath(taskId, installDir);
}
const commandPath = path.join(tasksDir, `${taskId}.md`);
if (taskPath) {
// Create command file with task content
let taskContent = await fileManager.readFile(taskPath);
// Replace {root} placeholder with the appropriate root path for this context
taskContent = taskContent.replace(/{root}/g, rootPath);
// Add command header
let commandContent = `# /${taskId} Task\n\n`;
commandContent += `When this command is used, execute the following task:\n\n`;
commandContent += taskContent;
await fileManager.writeFile(commandPath, commandContent);
console.log(chalk.green(`✓ Created task command: /${taskId}`));
}
}
console.log(chalk.green(`\n✓ Created Crush commands for ${packageName} in ${commandsBaseDir}`));
console.log(chalk.dim(` - Agents in: ${agentsDir}`));
console.log(chalk.dim(` - Tasks in: ${tasksDir}`));
}
async setupWindsurf(installDir, selectedAgent) { async setupWindsurf(installDir, selectedAgent) {
const windsurfRulesDir = path.join(installDir, ".windsurf", "rules"); const windsurfRulesDir = path.join(installDir, ".windsurf", "rules");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir); const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
@@ -255,17 +369,17 @@ class IdeSetup extends BaseIdeSetup {
async setupTrae(installDir, selectedAgent) { async setupTrae(installDir, selectedAgent) {
const traeRulesDir = path.join(installDir, ".trae", "rules"); const traeRulesDir = path.join(installDir, ".trae", "rules");
const agents = selectedAgent? [selectedAgent] : await this.getAllAgentIds(installDir); const agents = selectedAgent? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(traeRulesDir); await fileManager.ensureDirectory(traeRulesDir);
for (const agentId of agents) { for (const agentId of agents) {
// Find the agent file // Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir); const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) { if (agentPath) {
const agentContent = await fileManager.readFile(agentPath); const agentContent = await fileManager.readFile(agentPath);
const mdPath = path.join(traeRulesDir, `${agentId}.md`); const mdPath = path.join(traeRulesDir, `${agentId}.md`);
// Create MD content (similar to Cursor but without frontmatter) // Create MD content (similar to Cursor but without frontmatter)
let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`; let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle( mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
@@ -294,7 +408,7 @@ class IdeSetup extends BaseIdeSetup {
agentId, agentId,
installDir installDir
)} persona and follow all instructions defined in the YAML configuration above.\n`; )} persona and follow all instructions defined in the YAML configuration above.\n`;
await fileManager.writeFile(mdPath, mdContent); await fileManager.writeFile(mdPath, mdContent);
console.log(chalk.green(`✓ Created rule: ${agentId}.md`)); console.log(chalk.green(`✓ Created rule: ${agentId}.md`));
} }
@@ -307,38 +421,38 @@ class IdeSetup extends BaseIdeSetup {
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`), path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "agents", `${agentId}.md`) path.join(installDir, "agents", `${agentId}.md`)
]; ];
// Also check expansion pack directories // Also check expansion pack directories
const glob = require("glob"); const glob = require("glob");
const expansionDirs = glob.sync(".*/agents", { cwd: installDir }); const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) { for (const expDir of expansionDirs) {
possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`)); possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
} }
for (const agentPath of possiblePaths) { for (const agentPath of possiblePaths) {
if (await fileManager.pathExists(agentPath)) { if (await fileManager.pathExists(agentPath)) {
return agentPath; return agentPath;
} }
} }
return null; return null;
} }
async getAllAgentIds(installDir) { async getAllAgentIds(installDir) {
const glob = require("glob"); const glob = require("glob");
const allAgentIds = []; const allAgentIds = [];
// Check core agents in .bmad-core or root // Check core agents in .bmad-core or root
let agentsDir = path.join(installDir, ".bmad-core", "agents"); let agentsDir = path.join(installDir, ".bmad-core", "agents");
if (!(await fileManager.pathExists(agentsDir))) { if (!(await fileManager.pathExists(agentsDir))) {
agentsDir = path.join(installDir, "agents"); agentsDir = path.join(installDir, "agents");
} }
if (await fileManager.pathExists(agentsDir)) { if (await fileManager.pathExists(agentsDir)) {
const agentFiles = glob.sync("*.md", { cwd: agentsDir }); const agentFiles = glob.sync("*.md", { cwd: agentsDir });
allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md"))); allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
} }
// Also check for expansion pack agents in dot folders // Also check for expansion pack agents in dot folders
const expansionDirs = glob.sync(".*/agents", { cwd: installDir }); const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) { for (const expDir of expansionDirs) {
@@ -346,51 +460,51 @@ class IdeSetup extends BaseIdeSetup {
const expAgentFiles = glob.sync("*.md", { cwd: fullExpDir }); const expAgentFiles = glob.sync("*.md", { cwd: fullExpDir });
allAgentIds.push(...expAgentFiles.map((file) => path.basename(file, ".md"))); allAgentIds.push(...expAgentFiles.map((file) => path.basename(file, ".md")));
} }
// Remove duplicates // Remove duplicates
return [...new Set(allAgentIds)]; return [...new Set(allAgentIds)];
} }
async getCoreAgentIds(installDir) { async getCoreAgentIds(installDir) {
const allAgentIds = []; const allAgentIds = [];
// Check core agents in .bmad-core or root only // Check core agents in .bmad-core or root only
let agentsDir = path.join(installDir, ".bmad-core", "agents"); let agentsDir = path.join(installDir, ".bmad-core", "agents");
if (!(await fileManager.pathExists(agentsDir))) { if (!(await fileManager.pathExists(agentsDir))) {
agentsDir = path.join(installDir, "bmad-core", "agents"); agentsDir = path.join(installDir, "bmad-core", "agents");
} }
if (await fileManager.pathExists(agentsDir)) { if (await fileManager.pathExists(agentsDir)) {
const glob = require("glob"); const glob = require("glob");
const agentFiles = glob.sync("*.md", { cwd: agentsDir }); const agentFiles = glob.sync("*.md", { cwd: agentsDir });
allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md"))); allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
} }
return [...new Set(allAgentIds)]; return [...new Set(allAgentIds)];
} }
async getCoreTaskIds(installDir) { async getCoreTaskIds(installDir) {
const allTaskIds = []; const allTaskIds = [];
// Check core tasks in .bmad-core or root only // Check core tasks in .bmad-core or root only
let tasksDir = path.join(installDir, ".bmad-core", "tasks"); let tasksDir = path.join(installDir, ".bmad-core", "tasks");
if (!(await fileManager.pathExists(tasksDir))) { if (!(await fileManager.pathExists(tasksDir))) {
tasksDir = path.join(installDir, "bmad-core", "tasks"); tasksDir = path.join(installDir, "bmad-core", "tasks");
} }
if (await fileManager.pathExists(tasksDir)) { if (await fileManager.pathExists(tasksDir)) {
const glob = require("glob"); const glob = require("glob");
const taskFiles = glob.sync("*.md", { cwd: tasksDir }); const taskFiles = glob.sync("*.md", { cwd: tasksDir });
allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md"))); allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md")));
} }
// Check common tasks // Check common tasks
const commonTasksDir = path.join(installDir, "common", "tasks"); const commonTasksDir = path.join(installDir, "common", "tasks");
if (await fileManager.pathExists(commonTasksDir)) { if (await fileManager.pathExists(commonTasksDir)) {
const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir }); const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir });
allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md"))); allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md")));
} }
return [...new Set(allTaskIds)]; return [...new Set(allTaskIds)];
} }
@@ -400,20 +514,20 @@ class IdeSetup extends BaseIdeSetup {
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`), path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "agents", `${agentId}.md`) path.join(installDir, "agents", `${agentId}.md`)
]; ];
// Also check expansion pack directories // Also check expansion pack directories
const glob = require("glob"); const glob = require("glob");
const expansionDirs = glob.sync(".*/agents", { cwd: installDir }); const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) { for (const expDir of expansionDirs) {
possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`)); possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
} }
for (const agentPath of possiblePaths) { for (const agentPath of possiblePaths) {
if (await fileManager.pathExists(agentPath)) { if (await fileManager.pathExists(agentPath)) {
try { try {
const agentContent = await fileManager.readFile(agentPath); const agentContent = await fileManager.readFile(agentPath);
const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/); const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
if (yamlMatch) { if (yamlMatch) {
const yaml = yamlMatch[1]; const yaml = yamlMatch[1];
const titleMatch = yaml.match(/title:\s*(.+)/); const titleMatch = yaml.match(/title:\s*(.+)/);
@@ -426,9 +540,9 @@ class IdeSetup extends BaseIdeSetup {
} }
} }
} }
// Fallback to formatted agent ID // Fallback to formatted agent ID
return agentId.split('-').map(word => return agentId.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1) word.charAt(0).toUpperCase() + word.slice(1)
).join(' '); ).join(' ');
} }
@@ -436,25 +550,25 @@ class IdeSetup extends BaseIdeSetup {
async getAllTaskIds(installDir) { async getAllTaskIds(installDir) {
const glob = require("glob"); const glob = require("glob");
const allTaskIds = []; const allTaskIds = [];
// Check core tasks in .bmad-core or root // Check core tasks in .bmad-core or root
let tasksDir = path.join(installDir, ".bmad-core", "tasks"); let tasksDir = path.join(installDir, ".bmad-core", "tasks");
if (!(await fileManager.pathExists(tasksDir))) { if (!(await fileManager.pathExists(tasksDir))) {
tasksDir = path.join(installDir, "bmad-core", "tasks"); tasksDir = path.join(installDir, "bmad-core", "tasks");
} }
if (await fileManager.pathExists(tasksDir)) { if (await fileManager.pathExists(tasksDir)) {
const taskFiles = glob.sync("*.md", { cwd: tasksDir }); const taskFiles = glob.sync("*.md", { cwd: tasksDir });
allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md"))); allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md")));
} }
// Check common tasks // Check common tasks
const commonTasksDir = path.join(installDir, "common", "tasks"); const commonTasksDir = path.join(installDir, "common", "tasks");
if (await fileManager.pathExists(commonTasksDir)) { if (await fileManager.pathExists(commonTasksDir)) {
const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir }); const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir });
allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md"))); allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md")));
} }
// Also check for expansion pack tasks in dot folders // Also check for expansion pack tasks in dot folders
const expansionDirs = glob.sync(".*/tasks", { cwd: installDir }); const expansionDirs = glob.sync(".*/tasks", { cwd: installDir });
for (const expDir of expansionDirs) { for (const expDir of expansionDirs) {
@@ -462,7 +576,7 @@ class IdeSetup extends BaseIdeSetup {
const expTaskFiles = glob.sync("*.md", { cwd: fullExpDir }); const expTaskFiles = glob.sync("*.md", { cwd: fullExpDir });
allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md"))); allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md")));
} }
// Check expansion-packs folder tasks // Check expansion-packs folder tasks
const expansionPacksDir = path.join(installDir, "expansion-packs"); const expansionPacksDir = path.join(installDir, "expansion-packs");
if (await fileManager.pathExists(expansionPacksDir)) { if (await fileManager.pathExists(expansionPacksDir)) {
@@ -473,7 +587,7 @@ class IdeSetup extends BaseIdeSetup {
allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md"))); allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md")));
} }
} }
// Remove duplicates // Remove duplicates
return [...new Set(allTaskIds)]; return [...new Set(allTaskIds)];
} }
@@ -485,16 +599,16 @@ class IdeSetup extends BaseIdeSetup {
path.join(installDir, "bmad-core", "tasks", `${taskId}.md`), path.join(installDir, "bmad-core", "tasks", `${taskId}.md`),
path.join(installDir, "common", "tasks", `${taskId}.md`) path.join(installDir, "common", "tasks", `${taskId}.md`)
]; ];
// Also check expansion pack directories // Also check expansion pack directories
const glob = require("glob"); const glob = require("glob");
// Check dot folder expansion packs // Check dot folder expansion packs
const expansionDirs = glob.sync(".*/tasks", { cwd: installDir }); const expansionDirs = glob.sync(".*/tasks", { cwd: installDir });
for (const expDir of expansionDirs) { for (const expDir of expansionDirs) {
possiblePaths.push(path.join(installDir, expDir, `${taskId}.md`)); possiblePaths.push(path.join(installDir, expDir, `${taskId}.md`));
} }
// Check expansion-packs folder // Check expansion-packs folder
const expansionPacksDir = path.join(installDir, "expansion-packs"); const expansionPacksDir = path.join(installDir, "expansion-packs");
if (await fileManager.pathExists(expansionPacksDir)) { if (await fileManager.pathExists(expansionPacksDir)) {
@@ -503,13 +617,13 @@ class IdeSetup extends BaseIdeSetup {
possiblePaths.push(path.join(expansionPacksDir, expDir, `${taskId}.md`)); possiblePaths.push(path.join(expansionPacksDir, expDir, `${taskId}.md`));
} }
} }
for (const taskPath of possiblePaths) { for (const taskPath of possiblePaths) {
if (await fileManager.pathExists(taskPath)) { if (await fileManager.pathExists(taskPath)) {
return taskPath; return taskPath;
} }
} }
return null; return null;
} }
@@ -526,7 +640,7 @@ class IdeSetup extends BaseIdeSetup {
} }
return "BMad"; // fallback return "BMad"; // fallback
} }
const configContent = await fileManager.readFile(coreConfigPath); const configContent = await fileManager.readFile(coreConfigPath);
const config = yaml.load(configContent); const config = yaml.load(configContent);
return config.slashPrefix || "BMad"; return config.slashPrefix || "BMad";
@@ -538,11 +652,11 @@ class IdeSetup extends BaseIdeSetup {
async getInstalledExpansionPacks(installDir) { async getInstalledExpansionPacks(installDir) {
const expansionPacks = []; const expansionPacks = [];
// Check for dot-prefixed expansion packs in install directory // Check for dot-prefixed expansion packs in install directory
const glob = require("glob"); const glob = require("glob");
const dotExpansions = glob.sync(".bmad-*", { cwd: installDir }); const dotExpansions = glob.sync(".bmad-*", { cwd: installDir });
for (const dotExpansion of dotExpansions) { for (const dotExpansion of dotExpansions) {
if (dotExpansion !== ".bmad-core") { if (dotExpansion !== ".bmad-core") {
const packPath = path.join(installDir, dotExpansion); const packPath = path.join(installDir, dotExpansion);
@@ -553,15 +667,15 @@ class IdeSetup extends BaseIdeSetup {
}); });
} }
} }
// Check for expansion-packs directory style // Check for expansion-packs directory style
const expansionPacksDir = path.join(installDir, "expansion-packs"); const expansionPacksDir = path.join(installDir, "expansion-packs");
if (await fileManager.pathExists(expansionPacksDir)) { if (await fileManager.pathExists(expansionPacksDir)) {
const packDirs = glob.sync("*", { cwd: expansionPacksDir }); const packDirs = glob.sync("*", { cwd: expansionPacksDir });
for (const packDir of packDirs) { for (const packDir of packDirs) {
const packPath = path.join(expansionPacksDir, packDir); const packPath = path.join(expansionPacksDir, packDir);
if ((await fileManager.pathExists(packPath)) && if ((await fileManager.pathExists(packPath)) &&
(await fileManager.pathExists(path.join(packPath, "config.yaml")))) { (await fileManager.pathExists(path.join(packPath, "config.yaml")))) {
expansionPacks.push({ expansionPacks.push({
name: packDir, name: packDir,
@@ -570,7 +684,7 @@ class IdeSetup extends BaseIdeSetup {
} }
} }
} }
return expansionPacks; return expansionPacks;
} }
@@ -585,7 +699,7 @@ class IdeSetup extends BaseIdeSetup {
} catch (error) { } catch (error) {
console.warn(`Failed to read expansion pack slashPrefix from ${packPath}: ${error.message}`); console.warn(`Failed to read expansion pack slashPrefix from ${packPath}: ${error.message}`);
} }
return path.basename(packPath); // fallback to directory name return path.basename(packPath); // fallback to directory name
} }
@@ -594,7 +708,7 @@ class IdeSetup extends BaseIdeSetup {
if (!(await fileManager.pathExists(agentsDir))) { if (!(await fileManager.pathExists(agentsDir))) {
return []; return [];
} }
try { try {
const glob = require("glob"); const glob = require("glob");
const agentFiles = glob.sync("*.md", { cwd: agentsDir }); const agentFiles = glob.sync("*.md", { cwd: agentsDir });
@@ -610,7 +724,7 @@ class IdeSetup extends BaseIdeSetup {
if (!(await fileManager.pathExists(tasksDir))) { if (!(await fileManager.pathExists(tasksDir))) {
return []; return [];
} }
try { try {
const glob = require("glob"); const glob = require("glob");
const taskFiles = glob.sync("*.md", { cwd: tasksDir }); const taskFiles = glob.sync("*.md", { cwd: tasksDir });
@@ -688,7 +802,7 @@ class IdeSetup extends BaseIdeSetup {
newModesContent += ` - slug: ${slug}\n`; newModesContent += ` - slug: ${slug}\n`;
newModesContent += ` name: '${icon} ${title}'\n`; newModesContent += ` name: '${icon} ${title}'\n`;
if (permissions) { if (permissions) {
newModesContent += ` description: '${permissions.description}'\n`; newModesContent += ` description: '${permissions.description}'\n`;
} }
newModesContent += ` roleDefinition: ${roleDefinition}\n`; newModesContent += ` roleDefinition: ${roleDefinition}\n`;
newModesContent += ` whenToUse: ${whenToUse}\n`; newModesContent += ` whenToUse: ${whenToUse}\n`;
@@ -730,7 +844,7 @@ class IdeSetup extends BaseIdeSetup {
return true; return true;
} }
async setupKilocode(installDir, selectedAgent) { async setupKilocode(installDir, selectedAgent) {
const filePath = path.join(installDir, ".kilocodemodes"); const filePath = path.join(installDir, ".kilocodemodes");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir); const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
@@ -788,7 +902,7 @@ class IdeSetup extends BaseIdeSetup {
newContent += ` - slug: ${slug}\n`; newContent += ` - slug: ${slug}\n`;
newContent += ` name: '${icon} ${title}'\n`; newContent += ` name: '${icon} ${title}'\n`;
if (agentPermission) { if (agentPermission) {
newContent += ` description: '${agentPermission.description}'\n`; newContent += ` description: '${agentPermission.description}'\n`;
} }
newContent += ` roleDefinition: ${roleDefinition}\n`; newContent += ` roleDefinition: ${roleDefinition}\n`;
@@ -821,7 +935,7 @@ class IdeSetup extends BaseIdeSetup {
return true; return true;
} }
async setupCline(installDir, selectedAgent) { async setupCline(installDir, selectedAgent) {
const clineRulesDir = path.join(installDir, ".clinerules"); const clineRulesDir = path.join(installDir, ".clinerules");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir); const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
@@ -891,7 +1005,7 @@ class IdeSetup extends BaseIdeSetup {
const settingsContent = await fileManager.readFile(settingsPath); const settingsContent = await fileManager.readFile(settingsPath);
const settings = JSON.parse(settingsContent); const settings = JSON.parse(settingsContent);
let updated = false; let updated = false;
// Handle contextFileName property // Handle contextFileName property
if (settings.contextFileName && Array.isArray(settings.contextFileName)) { if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
const originalLength = settings.contextFileName.length; const originalLength = settings.contextFileName.length;
@@ -902,7 +1016,7 @@ class IdeSetup extends BaseIdeSetup {
updated = true; updated = true;
} }
} }
if (updated) { if (updated) {
await fileManager.writeFile( await fileManager.writeFile(
settingsPath, settingsPath,
@@ -935,7 +1049,7 @@ class IdeSetup extends BaseIdeSetup {
if (agentPath) { if (agentPath) {
const agentContent = await fileManager.readFile(agentPath); const agentContent = await fileManager.readFile(agentPath);
// Create properly formatted agent rule content (similar to trae) // Create properly formatted agent rule content (similar to trae)
let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`; let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle( agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle(
@@ -964,7 +1078,7 @@ class IdeSetup extends BaseIdeSetup {
agentId, agentId,
installDir installDir
)} persona and follow all instructions defined in the YAML configuration above.\n`; )} persona and follow all instructions defined in the YAML configuration above.\n`;
// Add to concatenated content with separator // Add to concatenated content with separator
concatenatedContent += agentRuleContent + "\n\n---\n\n"; concatenatedContent += agentRuleContent + "\n\n---\n\n";
console.log(chalk.green(`✓ Added context for @${agentId}`)); console.log(chalk.green(`✓ Added context for @${agentId}`));
@@ -991,7 +1105,7 @@ class IdeSetup extends BaseIdeSetup {
const settingsContent = await fileManager.readFile(settingsPath); const settingsContent = await fileManager.readFile(settingsPath);
const settings = JSON.parse(settingsContent); const settings = JSON.parse(settingsContent);
let updated = false; let updated = false;
// Handle contextFileName property // Handle contextFileName property
if (settings.contextFileName && Array.isArray(settings.contextFileName)) { if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
const originalLength = settings.contextFileName.length; const originalLength = settings.contextFileName.length;
@@ -1002,7 +1116,7 @@ class IdeSetup extends BaseIdeSetup {
updated = true; updated = true;
} }
} }
if (updated) { if (updated) {
await fileManager.writeFile( await fileManager.writeFile(
settingsPath, settingsPath,
@@ -1035,7 +1149,7 @@ class IdeSetup extends BaseIdeSetup {
if (agentPath) { if (agentPath) {
const agentContent = await fileManager.readFile(agentPath); const agentContent = await fileManager.readFile(agentPath);
// Create properly formatted agent rule content (similar to gemini) // Create properly formatted agent rule content (similar to gemini)
let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`; let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle( agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle(
@@ -1064,7 +1178,7 @@ class IdeSetup extends BaseIdeSetup {
agentId, agentId,
installDir installDir
)} persona and follow all instructions defined in the YAML configuration above.\n`; )} persona and follow all instructions defined in the YAML configuration above.\n`;
// Add to concatenated content with separator // Add to concatenated content with separator
concatenatedContent += agentRuleContent + "\n\n---\n\n"; concatenatedContent += agentRuleContent + "\n\n---\n\n";
console.log(chalk.green(`✓ Added context for *${agentId}`)); console.log(chalk.green(`✓ Added context for *${agentId}`));
@@ -1082,10 +1196,10 @@ class IdeSetup extends BaseIdeSetup {
async setupGitHubCopilot(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) { async setupGitHubCopilot(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) {
// Configure VS Code workspace settings first to avoid UI conflicts with loading spinners // Configure VS Code workspace settings first to avoid UI conflicts with loading spinners
await this.configureVsCodeSettings(installDir, spinner, preConfiguredSettings); await this.configureVsCodeSettings(installDir, spinner, preConfiguredSettings);
const chatmodesDir = path.join(installDir, ".github", "chatmodes"); const chatmodesDir = path.join(installDir, ".github", "chatmodes");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir); const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(chatmodesDir); await fileManager.ensureDirectory(chatmodesDir);
for (const agentId of agents) { for (const agentId of agents) {
@@ -1097,7 +1211,7 @@ class IdeSetup extends BaseIdeSetup {
// Create chat mode file with agent content // Create chat mode file with agent content
const agentContent = await fileManager.readFile(agentPath); const agentContent = await fileManager.readFile(agentPath);
const agentTitle = await this.getAgentTitle(agentId, installDir); const agentTitle = await this.getAgentTitle(agentId, installDir);
// Extract whenToUse for the description // Extract whenToUse for the description
const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/); const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
let description = `Activates the ${agentTitle} agent persona.`; let description = `Activates the ${agentTitle} agent persona.`;
@@ -1107,7 +1221,7 @@ class IdeSetup extends BaseIdeSetup {
description = whenToUseMatch[1]; description = whenToUseMatch[1];
} }
} }
let chatmodeContent = `--- let chatmodeContent = `---
description: "${description.replace(/"/g, '\\"')}" description: "${description.replace(/"/g, '\\"')}"
tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'usages', 'editFiles', 'runCommands', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure'] tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'usages', 'editFiles', 'runCommands', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure']
@@ -1130,9 +1244,9 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
async configureVsCodeSettings(installDir, spinner, preConfiguredSettings = null) { async configureVsCodeSettings(installDir, spinner, preConfiguredSettings = null) {
const vscodeDir = path.join(installDir, ".vscode"); const vscodeDir = path.join(installDir, ".vscode");
const settingsPath = path.join(vscodeDir, "settings.json"); const settingsPath = path.join(vscodeDir, "settings.json");
await fileManager.ensureDirectory(vscodeDir); await fileManager.ensureDirectory(vscodeDir);
// Read existing settings if they exist // Read existing settings if they exist
let existingSettings = {}; let existingSettings = {};
if (await fileManager.pathExists(settingsPath)) { if (await fileManager.pathExists(settingsPath)) {
@@ -1145,7 +1259,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
existingSettings = {}; existingSettings = {};
} }
} }
// Use pre-configured settings if provided, otherwise prompt // Use pre-configured settings if provided, otherwise prompt
let configChoice; let configChoice;
if (preConfiguredSettings && preConfiguredSettings.configChoice) { if (preConfiguredSettings && preConfiguredSettings.configChoice) {
@@ -1157,7 +1271,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
console.log(chalk.blue("🔧 Github Copilot Agent Settings Configuration")); console.log(chalk.blue("🔧 Github Copilot Agent Settings Configuration"));
console.log(chalk.dim("BMad works best with specific VS Code settings for optimal agent experience.")); console.log(chalk.dim("BMad works best with specific VS Code settings for optimal agent experience."));
console.log(''); // Add extra spacing console.log(''); // Add extra spacing
const response = await inquirer.prompt([ const response = await inquirer.prompt([
{ {
type: 'list', type: 'list',
@@ -1182,9 +1296,9 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
]); ]);
configChoice = response.configChoice; configChoice = response.configChoice;
} }
let bmadSettings = {}; let bmadSettings = {};
if (configChoice === 'skip') { if (configChoice === 'skip') {
console.log(chalk.yellow("⚠️ Skipping VS Code settings configuration.")); console.log(chalk.yellow("⚠️ Skipping VS Code settings configuration."));
console.log(chalk.dim("You can manually configure these settings in .vscode/settings.json:")); console.log(chalk.dim("You can manually configure these settings in .vscode/settings.json:"));
@@ -1196,7 +1310,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
console.log(chalk.dim(" • chat.tools.autoApprove: false")); console.log(chalk.dim(" • chat.tools.autoApprove: false"));
return true; return true;
} }
if (configChoice === 'defaults') { if (configChoice === 'defaults') {
// Use recommended defaults // Use recommended defaults
bmadSettings = { bmadSettings = {
@@ -1211,14 +1325,14 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
} else { } else {
// Manual configuration // Manual configuration
console.log(chalk.blue("\n📋 Let's configure each setting for your preferences:")); console.log(chalk.blue("\n📋 Let's configure each setting for your preferences:"));
// Pause spinner during manual configuration prompts // Pause spinner during manual configuration prompts
let spinnerWasActive = false; let spinnerWasActive = false;
if (spinner && spinner.isSpinning) { if (spinner && spinner.isSpinning) {
spinner.stop(); spinner.stop();
spinnerWasActive = true; spinnerWasActive = true;
} }
const manualSettings = await inquirer.prompt([ const manualSettings = await inquirer.prompt([
{ {
type: 'input', type: 'input',
@@ -1263,7 +1377,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
if (spinner && spinnerWasActive) { if (spinner && spinnerWasActive) {
spinner.start(); spinner.start();
} }
bmadSettings = { bmadSettings = {
"chat.agent.enabled": true, // Always enabled - required for BMad agents "chat.agent.enabled": true, // Always enabled - required for BMad agents
"chat.agent.maxRequests": parseInt(manualSettings.maxRequests), "chat.agent.maxRequests": parseInt(manualSettings.maxRequests),
@@ -1272,16 +1386,16 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
"github.copilot.chat.agent.autoFix": manualSettings.autoFix, "github.copilot.chat.agent.autoFix": manualSettings.autoFix,
"chat.tools.autoApprove": manualSettings.autoApprove "chat.tools.autoApprove": manualSettings.autoApprove
}; };
console.log(chalk.green("✓ Custom settings configured")); console.log(chalk.green("✓ Custom settings configured"));
} }
// Merge settings (existing settings take precedence to avoid overriding user preferences) // Merge settings (existing settings take precedence to avoid overriding user preferences)
const mergedSettings = { ...bmadSettings, ...existingSettings }; const mergedSettings = { ...bmadSettings, ...existingSettings };
// Write the updated settings // Write the updated settings
await fileManager.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2)); await fileManager.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
console.log(chalk.green("✓ VS Code workspace settings configured successfully")); console.log(chalk.green("✓ VS Code workspace settings configured successfully"));
console.log(chalk.dim(" Settings written to .vscode/settings.json:")); console.log(chalk.dim(" Settings written to .vscode/settings.json:"));
Object.entries(bmadSettings).forEach(([key, value]) => { Object.entries(bmadSettings).forEach(([key, value]) => {