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('-x, --expansion-only', 'Install only expansion packs (no bmad-core)')
.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)')
.action(async (options) => {
try {
@@ -373,6 +373,7 @@ async function promptInstallation() {
{ name: 'Cline', value: 'cline' },
{ name: 'Gemini CLI', value: 'gemini' },
{ name: 'Qwen Code', value: 'qwen-code' },
{ name: 'Crush', value: 'crush' },
{ name: 'Github Copilot', value: 'github-copilot' }
]
}

View File

@@ -28,6 +28,16 @@ ide-configurations:
# To use BMad agents in Claude Code:
# 1. Type /agent-name (e.g., "/dev", "/pm", "/architect")
# 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:
name: Windsurf
rule-dir: .windsurf/rules/

View File

@@ -45,6 +45,8 @@ class IdeSetup extends BaseIdeSetup {
return this.setupCursor(installDir, selectedAgent);
case "claude-code":
return this.setupClaudeCode(installDir, selectedAgent);
case "crush":
return this.setupCrush(installDir, selectedAgent);
case "windsurf":
return this.setupWindsurf(installDir, selectedAgent);
case "trae":
@@ -88,6 +90,30 @@ class IdeSetup extends BaseIdeSetup {
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) {
// Setup bmad-core commands
const coreSlashPrefix = await this.getCoreSlashPrefix(installDir);
@@ -200,6 +226,94 @@ class IdeSetup extends BaseIdeSetup {
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) {
const windsurfRulesDir = path.join(installDir, ".windsurf", "rules");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);