From da21790531fc392a8abd900a0da6bad73c07004c Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Mon, 22 Dec 2025 14:17:32 +0800 Subject: [PATCH] quickinstall duplicate success message removed --- tools/cli/installers/lib/core/installer.js | 39 ++++++------ tools/cli/installers/lib/ide/antigravity.js | 33 +++++----- tools/cli/installers/lib/ide/auggie.js | 27 ++++---- tools/cli/installers/lib/ide/claude-code.js | 29 ++++----- tools/cli/installers/lib/ide/cline.js | 21 +++---- tools/cli/installers/lib/ide/codex.js | 15 ++--- tools/cli/installers/lib/ide/crush.js | 15 ++--- tools/cli/installers/lib/ide/cursor.js | 1 + tools/cli/installers/lib/ide/gemini.js | 1 + .../cli/installers/lib/ide/github-copilot.js | 1 + tools/cli/installers/lib/ide/iflow.js | 1 + tools/cli/installers/lib/ide/kilo.js | 1 + tools/cli/installers/lib/ide/kiro-cli.js | 1 + tools/cli/installers/lib/ide/opencode.js | 1 + tools/cli/installers/lib/ide/qwen.js | 1 + tools/cli/installers/lib/ide/roo.js | 1 + tools/cli/installers/lib/ide/rovo-dev.js | 1 + tools/cli/installers/lib/ide/trae.js | 1 + tools/cli/installers/lib/ide/windsurf.js | 1 + tools/cli/lib/file-ops.js | 62 ++++++++++++++++++- tools/platform-codes.yaml | 18 ++++-- 21 files changed, 175 insertions(+), 96 deletions(-) diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index 50f06d1c..001b96b5 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -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) diff --git a/tools/cli/installers/lib/ide/antigravity.js b/tools/cli/installers/lib/ide/antigravity.js index b921b4de..1150d44b 100644 --- a/tools/cli/installers/lib/ide/antigravity.js +++ b/tools/cli/installers/lib/ide/antigravity.js @@ -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 = ``; 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', diff --git a/tools/cli/installers/lib/ide/auggie.js b/tools/cli/installers/lib/ide/auggie.js index 04e08788..2c07a58e 100644 --- a/tools/cli/installers/lib/ide/auggie.js +++ b/tools/cli/installers/lib/ide/auggie.js @@ -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', diff --git a/tools/cli/installers/lib/ide/claude-code.js b/tools/cli/installers/lib/ide/claude-code.js index 56131e44..9fdca124 100644 --- a/tools/cli/installers/lib/ide/claude-code.js +++ b/tools/cli/installers/lib/ide/claude-code.js @@ -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 = ``; 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 diff --git a/tools/cli/installers/lib/ide/cline.js b/tools/cli/installers/lib/ide/cline.js index 9a9ceba6..f25b57a3 100644 --- a/tools/cli/installers/lib/ide/cline.js +++ b/tools/cli/installers/lib/ide/cline.js @@ -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} diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index e538fa6f..4763ac02 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -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), diff --git a/tools/cli/installers/lib/ide/crush.js b/tools/cli/installers/lib/ide/crush.js index 0bef6952..a548093d 100644 --- a/tools/cli/installers/lib/ide/crush.js +++ b/tools/cli/installers/lib/ide/crush.js @@ -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', diff --git a/tools/cli/installers/lib/ide/cursor.js b/tools/cli/installers/lib/ide/cursor.js index 183bbced..764153dc 100644 --- a/tools/cli/installers/lib/ide/cursor.js +++ b/tools/cli/installers/lib/ide/cursor.js @@ -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'); diff --git a/tools/cli/installers/lib/ide/gemini.js b/tools/cli/installers/lib/ide/gemini.js index 82746abf..f002485c 100644 --- a/tools/cli/installers/lib/ide/gemini.js +++ b/tools/cli/installers/lib/ide/gemini.js @@ -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'); diff --git a/tools/cli/installers/lib/ide/github-copilot.js b/tools/cli/installers/lib/ide/github-copilot.js index 998ec657..a120bac1 100644 --- a/tools/cli/installers/lib/ide/github-copilot.js +++ b/tools/cli/installers/lib/ide/github-copilot.js @@ -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'); diff --git a/tools/cli/installers/lib/ide/iflow.js b/tools/cli/installers/lib/ide/iflow.js index bbe6d470..238c34c2 100644 --- a/tools/cli/installers/lib/ide/iflow.js +++ b/tools/cli/installers/lib/ide/iflow.js @@ -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'); diff --git a/tools/cli/installers/lib/ide/kilo.js b/tools/cli/installers/lib/ide/kilo.js index 66cb8c37..3def56c6 100644 --- a/tools/cli/installers/lib/ide/kilo.js +++ b/tools/cli/installers/lib/ide/kilo.js @@ -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'); /** diff --git a/tools/cli/installers/lib/ide/kiro-cli.js b/tools/cli/installers/lib/ide/kiro-cli.js index 47f2a29d..a37392cb 100644 --- a/tools/cli/installers/lib/ide/kiro-cli.js +++ b/tools/cli/installers/lib/ide/kiro-cli.js @@ -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 diff --git a/tools/cli/installers/lib/ide/opencode.js b/tools/cli/installers/lib/ide/opencode.js index cf5e8eec..f9087ac7 100644 --- a/tools/cli/installers/lib/ide/opencode.js +++ b/tools/cli/installers/lib/ide/opencode.js @@ -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'); diff --git a/tools/cli/installers/lib/ide/qwen.js b/tools/cli/installers/lib/ide/qwen.js index bdc0cb29..96af9802 100644 --- a/tools/cli/installers/lib/ide/qwen.js +++ b/tools/cli/installers/lib/ide/qwen.js @@ -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'); diff --git a/tools/cli/installers/lib/ide/roo.js b/tools/cli/installers/lib/ide/roo.js index 26370100..fe297694 100644 --- a/tools/cli/installers/lib/ide/roo.js +++ b/tools/cli/installers/lib/ide/roo.js @@ -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'); /** diff --git a/tools/cli/installers/lib/ide/rovo-dev.js b/tools/cli/installers/lib/ide/rovo-dev.js index ecb34d4b..6ba35b4c 100644 --- a/tools/cli/installers/lib/ide/rovo-dev.js +++ b/tools/cli/installers/lib/ide/rovo-dev.js @@ -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'); diff --git a/tools/cli/installers/lib/ide/trae.js b/tools/cli/installers/lib/ide/trae.js index b4bea49b..64f9b34c 100644 --- a/tools/cli/installers/lib/ide/trae.js +++ b/tools/cli/installers/lib/ide/trae.js @@ -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'); /** diff --git a/tools/cli/installers/lib/ide/windsurf.js b/tools/cli/installers/lib/ide/windsurf.js index 92596db3..cd6bc5f6 100644 --- a/tools/cli/installers/lib/ide/windsurf.js +++ b/tools/cli/installers/lib/ide/windsurf.js @@ -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'); /** diff --git a/tools/cli/lib/file-ops.js b/tools/cli/lib/file-ops.js index 5cd7970d..fc23985b 100644 --- a/tools/cli/lib/file-ops.js +++ b/tools/cli/lib/file-ops.js @@ -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 }; diff --git a/tools/platform-codes.yaml b/tools/platform-codes.yaml index a58e2119..bad283ff 100644 --- a/tools/platform-codes.yaml +++ b/tools/platform-codes.yaml @@ -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: