quickinstall duplicate success message removed

This commit is contained in:
Brian Madison 2025-12-22 14:17:32 +08:00
parent 34cfdddd3a
commit da21790531
21 changed files with 175 additions and 96 deletions

View File

@ -835,8 +835,6 @@ class Installer {
allModules = allModules.filter((m) => m !== 'core');
}
const modulesToInstall = allModules;
// For dependency resolution, we only need regular modules (not custom modules)
// Custom modules are already installed in _bmad and don't need dependency resolution from source
const regularModulesForResolution = allModules.filter((module) => {
@ -882,7 +880,6 @@ class Installer {
// Check if this is a custom module
let isCustomModule = false;
let customInfo = null;
let useCache = false;
// First check if we have a cached version
if (finalCustomContent && finalCustomContent.cachedModules) {
@ -1215,17 +1212,19 @@ class Installer {
console.log(chalk.dim('Remove these .bak files it no longer needed\n'));
}
// Display completion message
const { UI } = require('../../../lib/ui');
const ui = new UI();
ui.showInstallSummary({
path: bmadDir,
modules: config.modules,
ides: config.ides,
customFiles: customFiles.length > 0 ? customFiles : undefined,
ttsInjectedFiles: this.enableAgentVibes && this.ttsInjectedFiles.length > 0 ? this.ttsInjectedFiles : undefined,
agentVibesEnabled: this.enableAgentVibes || false,
});
// Display completion message (skip if requested, e.g., for quick updates)
if (!config._skipCompletion) {
const { UI } = require('../../../lib/ui');
const ui = new UI();
ui.showInstallSummary({
path: bmadDir,
modules: config.modules,
ides: config.ides,
customFiles: customFiles.length > 0 ? customFiles : undefined,
ttsInjectedFiles: this.enableAgentVibes && this.ttsInjectedFiles.length > 0 ? this.ttsInjectedFiles : undefined,
agentVibesEnabled: this.enableAgentVibes || false,
});
}
return {
success: true,
@ -1505,9 +1504,7 @@ class Installer {
* @param {string} bmadDir - BMAD installation directory
* @param {Object} coreFiles - Core files to install
*/
async installCoreWithDependencies(bmadDir, coreFiles) {
const sourcePath = getModulePath('core');
const targetPath = path.join(bmadDir, 'core');
async installCoreWithDependencies(bmadDir) {
await this.installCore(bmadDir);
}
@ -1517,7 +1514,7 @@ class Installer {
* @param {string} bmadDir - BMAD installation directory
* @param {Object} moduleFiles - Module files to install
*/
async installModuleWithDependencies(moduleName, bmadDir, moduleFiles) {
async installModuleWithDependencies(moduleName, bmadDir) {
// Get module configuration for conditional installation
const moduleConfig = this.configCollector.collectedConfig[moduleName] || {};
@ -1789,7 +1786,6 @@ class Installer {
}
const agentName = agentFile.replace('.md', '');
const mdPath = path.join(agentsPath, agentFile);
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
// For .md files that are already compiled, we don't need to do much
@ -2003,8 +1999,9 @@ class Installer {
_existingModules: installedModules, // Pass all installed modules for manifest generation
};
// Call the standard install method
const result = await this.install(installConfig);
// Call the standard install method, but skip UI completion for quick updates
installConfig._skipCompletion = true;
await this.install(installConfig);
// Only succeed the spinner if it's still spinning
// (install method might have stopped it if folder name changed)

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
@ -88,9 +89,9 @@ class AntigravitySetup extends BaseIdeSetup {
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
const bmadWorkflowsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad');
const bmadWorkflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir, 'bmad');
if (await fs.pathExists(bmadWorkflowsDir)) {
if (await this.exists(bmadWorkflowsDir)) {
await fs.remove(bmadWorkflowsDir);
console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`));
}
@ -112,9 +113,9 @@ class AntigravitySetup extends BaseIdeSetup {
await this.cleanup(projectDir);
// Create .agent/workflows directory structure
const agentDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(agentDir, this.workflowsDir);
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
const agentDir = PathUtils.getConfigDir(projectDir, this.configDir);
const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
const bmadWorkflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir, 'bmad');
await this.ensureDir(bmadWorkflowsDir);
@ -190,7 +191,7 @@ class AntigravitySetup extends BaseIdeSetup {
* Read and process file content
*/
async readAndProcess(filePath, metadata) {
const content = await fs.readFile(filePath, 'utf8');
const content = await this.readFile(filePath);
return this.processContent(content, metadata);
}
@ -210,7 +211,7 @@ class AntigravitySetup extends BaseIdeSetup {
// Add core agents
const corePath = getModulePath('core');
if (await fs.pathExists(path.join(corePath, 'agents'))) {
if (await this.exists(path.join(corePath, 'agents'))) {
const coreAgents = await getAgentsFromDir(path.join(corePath, 'agents'), 'core');
agents.push(...coreAgents);
}
@ -220,7 +221,7 @@ class AntigravitySetup extends BaseIdeSetup {
const modulePath = path.join(sourceDir, moduleName);
const agentsPath = path.join(modulePath, 'agents');
if (await fs.pathExists(agentsPath)) {
if (await this.exists(agentsPath)) {
const moduleAgents = await getAgentsFromDir(agentsPath, moduleName);
agents.push(...moduleAgents);
}
@ -387,7 +388,7 @@ class AntigravitySetup extends BaseIdeSetup {
const targetPath = path.join(projectDir, injection.file);
if (await this.exists(targetPath)) {
let content = await fs.readFile(targetPath, 'utf8');
let content = await this.readFile(targetPath);
const marker = `<!-- IDE-INJECT-POINT: ${injection.point} -->`;
if (content.includes(marker)) {
@ -399,7 +400,7 @@ class AntigravitySetup extends BaseIdeSetup {
}
content = content.replace(marker, injectionContent);
await fs.writeFile(targetPath, content);
await this.writeFile(targetPath, content);
console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`));
}
}
@ -417,7 +418,7 @@ class AntigravitySetup extends BaseIdeSetup {
targetDir = path.join(os.homedir(), '.agent', 'agents');
console.log(chalk.dim(` Installing subagents globally to: ~/.agent/agents/`));
} else {
targetDir = path.join(projectDir, '.agent', 'agents');
targetDir = PathUtils.getIdeSubDir(projectDir, '.agent', 'agents');
console.log(chalk.dim(` Installing subagents to project: .agent/agents/`));
}
@ -464,11 +465,11 @@ class AntigravitySetup extends BaseIdeSetup {
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
// Create .agent/workflows/bmad directory structure (same as regular agents)
const agentDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(agentDir, this.workflowsDir);
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
const agentDir = PathUtils.getConfigDir(projectDir, this.configDir);
const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
const bmadWorkflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir, 'bmad');
await fs.ensureDir(bmadWorkflowsDir);
await this.ensureDir(bmadWorkflowsDir);
// Create custom agent launcher with same pattern as regular agents
const launcherContent = `name: '${agentName}'
@ -493,7 +494,7 @@ usage: |
const launcherPath = path.join(bmadWorkflowsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
await this.writeFile(launcherPath, launcherContent);
return {
ide: 'antigravity',

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
@ -25,7 +26,7 @@ class AuggieSetup extends BaseIdeSetup {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Always use project directory
const location = path.join(projectDir, '.augment', 'commands');
const location = PathUtils.getIdeSubDir(projectDir, '.augment', 'commands');
// Clean up old BMAD installation first
await this.cleanup(projectDir);
@ -52,11 +53,11 @@ class AuggieSetup extends BaseIdeSetup {
content: artifact.content,
}));
const bmadCommandsDir = path.join(location, 'bmad');
const agentsDir = path.join(bmadCommandsDir, 'agents');
const tasksDir = path.join(bmadCommandsDir, 'tasks');
const toolsDir = path.join(bmadCommandsDir, 'tools');
const workflowsDir = path.join(bmadCommandsDir, 'workflows');
const bmadCommandsDir = PathUtils.getIdeSubDir(location, 'bmad');
const agentsDir = PathUtils.getIdeSubDir(bmadCommandsDir, 'agents');
const tasksDir = PathUtils.getIdeSubDir(bmadCommandsDir, 'tasks');
const toolsDir = PathUtils.getIdeSubDir(bmadCommandsDir, 'tools');
const workflowsDir = PathUtils.getIdeSubDir(bmadCommandsDir, 'workflows');
await this.ensureDir(agentsDir);
await this.ensureDir(tasksDir);
@ -179,10 +180,10 @@ BMAD ${workflow.module.toUpperCase()} module
const fs = require('fs-extra');
// Only clean up project directory
const location = path.join(projectDir, '.augment', 'commands');
const location = PathUtils.getIdeSubDir(projectDir, '.augment', 'commands');
const bmadDir = path.join(location, 'bmad');
if (await fs.pathExists(bmadDir)) {
if (await this.exists(bmadDir)) {
await fs.remove(bmadDir);
console.log(chalk.dim(` Removed old BMAD commands`));
}
@ -198,12 +199,12 @@ BMAD ${workflow.module.toUpperCase()} module
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
// Auggie uses .augment/commands directory
const location = path.join(projectDir, '.augment', 'commands');
const bmadCommandsDir = path.join(location, 'bmad');
const agentsDir = path.join(bmadCommandsDir, 'agents');
const location = PathUtils.getIdeSubDir(projectDir, '.augment', 'commands');
const bmadCommandsDir = PathUtils.getIdeSubDir(location, 'bmad');
const agentsDir = PathUtils.getIdeSubDir(bmadCommandsDir, 'agents');
// Create .augment/commands/bmad/agents directory if it doesn't exist
await fs.ensureDir(agentsDir);
await this.ensureDir(agentsDir);
// Create custom agent launcher
const launcherContent = `---
@ -230,7 +231,7 @@ BMAD Custom agent
const launcherPath = path.join(agentsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
await this.writeFile(launcherPath, launcherContent);
return {
ide: 'auggie',

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
@ -48,7 +49,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
try {
// Load injection configuration
const configContent = await fs.readFile(injectionConfigPath, 'utf8');
const configContent = await this.readFile(injectionConfigPath);
const injectionConfig = yaml.parse(configContent);
// Ask about subagents if they exist and we haven't asked yet
@ -87,9 +88,9 @@ class ClaudeCodeSetup extends BaseIdeSetup {
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
const bmadCommandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir, 'bmad');
if (await fs.pathExists(bmadCommandsDir)) {
if (await this.exists(bmadCommandsDir)) {
await fs.remove(bmadCommandsDir);
console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`));
}
@ -111,9 +112,9 @@ class ClaudeCodeSetup extends BaseIdeSetup {
await this.cleanup(projectDir);
// Create .claude/commands directory structure
const claudeDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(claudeDir, this.commandsDir);
const bmadCommandsDir = path.join(commandsDir, 'bmad');
const claudeDir = PathUtils.getConfigDir(projectDir, this.configDir);
const commandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir);
const bmadCommandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir, 'bmad');
await this.ensureDir(bmadCommandsDir);
@ -159,7 +160,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
let workflowCommandCount = 0;
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
const moduleWorkflowsDir = path.join(bmadCommandsDir, artifact.module, 'workflows');
const moduleWorkflowsDir = PathUtils.getIdeSubDir(bmadCommandsDir, artifact.module, 'workflows');
await this.ensureDir(moduleWorkflowsDir);
const commandPath = path.join(moduleWorkflowsDir, path.basename(artifact.relativePath));
await this.writeFile(commandPath, artifact.content);
@ -198,7 +199,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
* Read and process file content
*/
async readAndProcess(filePath, metadata) {
const content = await fs.readFile(filePath, 'utf8');
const content = await this.readFile(filePath);
return this.processContent(content, metadata);
}
@ -218,7 +219,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
// Add core agents
const corePath = getModulePath('core');
if (await fs.pathExists(path.join(corePath, 'agents'))) {
if (await this.exists(path.join(corePath, 'agents'))) {
const coreAgents = await getAgentsFromDir(path.join(corePath, 'agents'), 'core');
agents.push(...coreAgents);
}
@ -228,7 +229,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
const modulePath = path.join(sourceDir, moduleName);
const agentsPath = path.join(modulePath, 'agents');
if (await fs.pathExists(agentsPath)) {
if (await this.exists(agentsPath)) {
const moduleAgents = await getAgentsFromDir(agentsPath, moduleName);
agents.push(...moduleAgents);
}
@ -395,7 +396,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
const targetPath = path.join(projectDir, injection.file);
if (await this.exists(targetPath)) {
let content = await fs.readFile(targetPath, 'utf8');
let content = await this.readFile(targetPath);
const marker = `<!-- IDE-INJECT-POINT: ${injection.point} -->`;
if (content.includes(marker)) {
@ -407,7 +408,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
}
content = content.replace(marker, injectionContent);
await fs.writeFile(targetPath, content);
await this.writeFile(targetPath, content);
console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`));
}
}
@ -425,7 +426,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
targetDir = path.join(os.homedir(), '.claude', 'agents');
console.log(chalk.dim(` Installing subagents globally to: ~/.claude/agents/`));
} else {
targetDir = path.join(projectDir, '.claude', 'agents');
targetDir = PathUtils.getIdeSubDir(projectDir, '.claude', 'agents');
console.log(chalk.dim(` Installing subagents to project: .claude/agents/`));
}
@ -471,7 +472,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
* @returns {Object|null} Info about created command
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const customAgentsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad', 'custom', 'agents');
const customAgentsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir, 'bmad', 'custom', 'agents');
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
return null; // IDE not configured for this project

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
@ -26,9 +27,8 @@ class ClineSetup extends BaseIdeSetup {
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .clinerules/workflows directory
const clineDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(clineDir, this.workflowsDir);
// Create .clinerules/workflows directory using shared utilities
const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
await this.ensureDir(workflowsDir);
@ -72,9 +72,9 @@ class ClineSetup extends BaseIdeSetup {
* Detect Cline installation by checking for .clinerules/workflows directory
*/
async detect(projectDir) {
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
if (!(await fs.pathExists(workflowsDir))) {
if (!(await this.exists(workflowsDir))) {
return false;
}
@ -159,8 +159,8 @@ class ClineSetup extends BaseIdeSetup {
for (const artifact of artifacts) {
const flattenedName = this.flattenFilename(artifact.relativePath);
const targetPath = path.join(destDir, flattenedName);
await fs.writeFile(targetPath, artifact.content);
const targetPath = PathUtils.joinSafe(destDir, flattenedName);
await this.writeFile(targetPath, artifact.content);
written++;
}
@ -204,7 +204,7 @@ class ClineSetup extends BaseIdeSetup {
* Cleanup Cline configuration
*/
async cleanup(projectDir) {
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
await this.clearOldBmadFiles(workflowsDir);
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
}
@ -218,11 +218,10 @@ class ClineSetup extends BaseIdeSetup {
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const clineDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(clineDir, this.workflowsDir);
const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
// Create .clinerules/workflows directory if it doesn't exist
await fs.ensureDir(workflowsDir);
await this.ensureDir(workflowsDir);
// Create custom agent launcher workflow
const launcherContent = `name: ${agentName}

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const os = require('node:os');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
@ -130,7 +131,7 @@ class CodexSetup extends BaseIdeSetup {
const projectSpecificDir = this.getCodexPromptDir(projectDir_local, 'project');
// Check global location
if (await fs.pathExists(globalDir)) {
if (await this.exists(globalDir)) {
const entries = await fs.readdir(globalDir);
if (entries.some((entry) => entry.startsWith('bmad-'))) {
return true;
@ -138,7 +139,7 @@ class CodexSetup extends BaseIdeSetup {
}
// Check project-specific location
if (await fs.pathExists(projectSpecificDir)) {
if (await this.exists(projectSpecificDir)) {
const entries = await fs.readdir(projectSpecificDir);
if (entries.some((entry) => entry.startsWith('bmad-'))) {
return true;
@ -207,7 +208,7 @@ class CodexSetup extends BaseIdeSetup {
getCodexPromptDir(projectDir = null, location = 'global') {
if (location === 'project' && projectDir) {
return path.join(projectDir, '.codex', 'prompts');
return PathUtils.getIdeSubDir(projectDir, '.codex', 'prompts');
}
return path.join(os.homedir(), '.codex', 'prompts');
}
@ -218,7 +219,7 @@ class CodexSetup extends BaseIdeSetup {
for (const artifact of artifacts) {
const flattenedName = this.flattenFilename(artifact.relativePath);
const targetPath = path.join(destDir, flattenedName);
await fs.writeFile(targetPath, artifact.content);
await this.writeFile(targetPath, artifact.content);
written++;
}
@ -226,7 +227,7 @@ class CodexSetup extends BaseIdeSetup {
}
async clearOldBmadFiles(destDir) {
if (!(await fs.pathExists(destDir))) {
if (!(await this.exists(destDir))) {
return;
}
@ -248,7 +249,7 @@ class CodexSetup extends BaseIdeSetup {
}
async readAndProcessWithProject(filePath, metadata, projectDir) {
const content = await fs.readFile(filePath, 'utf8');
const content = await this.readFile(filePath);
return super.processContent(content, metadata, projectDir);
}
@ -376,7 +377,7 @@ You must fully embody this agent's persona and follow all activation instruction
const fileName = `bmad-custom-agents-${agentName}.md`;
const launcherPath = path.join(destDir, fileName);
await fs.writeFile(launcherPath, launcherContent, 'utf8');
await this.writeFile(launcherPath, launcherContent);
return {
path: path.relative(projectDir, launcherPath),

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
@ -26,8 +27,8 @@ class CrushSetup extends BaseIdeSetup {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .crush/commands/bmad directory structure
const crushDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(crushDir, this.commandsDir, 'bmad');
const crushDir = PathUtils.getConfigDir(projectDir, this.configDir);
const commandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir, 'bmad');
await this.ensureDir(commandsDir);
@ -242,9 +243,9 @@ Part of the BMAD ${workflow.module.toUpperCase()} module.
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
const bmadCommandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir, 'bmad');
if (await fs.pathExists(bmadCommandsDir)) {
if (await this.exists(bmadCommandsDir)) {
await fs.remove(bmadCommandsDir);
console.log(chalk.dim(`Removed BMAD commands from Crush`));
}
@ -259,8 +260,8 @@ Part of the BMAD ${workflow.module.toUpperCase()} module.
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const crushDir = path.join(projectDir, this.configDir);
const bmadCommandsDir = path.join(crushDir, this.commandsDir, 'bmad');
const crushDir = PathUtils.getConfigDir(projectDir, this.configDir);
const bmadCommandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir, 'bmad');
// Create .crush/commands/bmad directory if it doesn't exist
await fs.ensureDir(bmadCommandsDir);
@ -286,7 +287,7 @@ The agent will follow the persona and instructions from the main agent file.
const launcherPath = path.join(bmadCommandsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
await this.writeFile(launcherPath, launcherContent);
return {
ide: 'crush',

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');

View File

@ -3,6 +3,7 @@ const fs = require('fs-extra');
const yaml = require('yaml');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const inquirer = require('inquirer');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**

View File

@ -3,6 +3,7 @@ const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const fs = require('fs-extra');
const yaml = require('yaml');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
/**
* Kiro CLI setup handler for BMad Method

View File

@ -4,6 +4,7 @@ const os = require('node:os');
const chalk = require('chalk');
const yaml = require('yaml');
const { BaseIdeSetup } = require('./_base-ide');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**

View File

@ -201,4 +201,64 @@ class FileOps {
}
}
module.exports = { FileOps };
/**
* Path utilities for common IDE path patterns
*/
const PathUtils = {
/**
* Get IDE configuration directory path
* @param {string} projectDir - Project directory
* @param {string} configDir - IDE-specific config directory
* @returns {string} Full path to IDE config directory
*/
getConfigDir(projectDir, configDir) {
return path.join(projectDir, configDir);
},
/**
* Get IDE subdirectory path (e.g., workflows, agents, commands)
* @param {string} projectDir - Project directory
* @param {string} configDir - IDE-specific config directory
* @param {string} subDir - Subdirectory name
* @param {...string} additionalDirs - Additional directory levels
* @returns {string} Full path to IDE subdirectory
*/
getIdeSubDir(projectDir, configDir, subDir, ...additionalDirs) {
const parts = [projectDir, configDir, subDir, ...additionalDirs].filter(Boolean);
return path.join(...parts);
},
/**
* Get BMAD subdirectory path
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {string} subDir - Subdirectory name
* @param {...string} additionalDirs - Additional directory levels
* @returns {string} Full path to BMAD subdirectory
*/
getBmadSubDir(projectDir, bmadDir, subDir, ...additionalDirs) {
const parts = [projectDir, bmadDir, subDir, ...additionalDirs].filter(Boolean);
return path.join(...parts);
},
/**
* Get path relative to a base directory
* @param {string} baseDir - Base directory
* @param {...string} pathParts - Path parts
* @returns {string} Full path
*/
relativeTo(baseDir, ...pathParts) {
return path.join(baseDir, ...pathParts.filter(Boolean));
},
/**
* Join multiple path parts safely (filters out null/undefined/empty)
* @param {...string} pathParts - Path parts
* @returns {string} Joined path
*/
joinSafe(...pathParts) {
return path.join(...pathParts.filter(Boolean));
},
};
module.exports = { FileOps, PathUtils };

View File

@ -55,12 +55,6 @@ platforms:
category: ide
description: "Enhanced Cline fork"
rovo:
name: "Rovo Dev"
preferred: false
category: ide
description: "Atlassian's AI coding assistant"
github-copilot:
name: "GitHub Copilot"
preferred: false
@ -115,6 +109,18 @@ platforms:
category: ide
description: "AI coding tool"
kiro-cli:
name: "Kiro CLI"
preferred: false
category: cli
description: "Kiro command line interface"
rovo-dev:
name: "Rovo Dev"
preferred: false
category: ide
description: "Atlassian's AI coding assistant"
# Platform categories
categories:
ide: