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'); allModules = allModules.filter((m) => m !== 'core');
} }
const modulesToInstall = allModules;
// For dependency resolution, we only need regular modules (not custom modules) // 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 // Custom modules are already installed in _bmad and don't need dependency resolution from source
const regularModulesForResolution = allModules.filter((module) => { const regularModulesForResolution = allModules.filter((module) => {
@ -882,7 +880,6 @@ class Installer {
// Check if this is a custom module // Check if this is a custom module
let isCustomModule = false; let isCustomModule = false;
let customInfo = null; let customInfo = null;
let useCache = false;
// First check if we have a cached version // First check if we have a cached version
if (finalCustomContent && finalCustomContent.cachedModules) { if (finalCustomContent && finalCustomContent.cachedModules) {
@ -1215,17 +1212,19 @@ class Installer {
console.log(chalk.dim('Remove these .bak files it no longer needed\n')); console.log(chalk.dim('Remove these .bak files it no longer needed\n'));
} }
// Display completion message // Display completion message (skip if requested, e.g., for quick updates)
const { UI } = require('../../../lib/ui'); if (!config._skipCompletion) {
const ui = new UI(); const { UI } = require('../../../lib/ui');
ui.showInstallSummary({ const ui = new UI();
path: bmadDir, ui.showInstallSummary({
modules: config.modules, path: bmadDir,
ides: config.ides, modules: config.modules,
customFiles: customFiles.length > 0 ? customFiles : undefined, ides: config.ides,
ttsInjectedFiles: this.enableAgentVibes && this.ttsInjectedFiles.length > 0 ? this.ttsInjectedFiles : undefined, customFiles: customFiles.length > 0 ? customFiles : undefined,
agentVibesEnabled: this.enableAgentVibes || false, ttsInjectedFiles: this.enableAgentVibes && this.ttsInjectedFiles.length > 0 ? this.ttsInjectedFiles : undefined,
}); agentVibesEnabled: this.enableAgentVibes || false,
});
}
return { return {
success: true, success: true,
@ -1505,9 +1504,7 @@ class Installer {
* @param {string} bmadDir - BMAD installation directory * @param {string} bmadDir - BMAD installation directory
* @param {Object} coreFiles - Core files to install * @param {Object} coreFiles - Core files to install
*/ */
async installCoreWithDependencies(bmadDir, coreFiles) { async installCoreWithDependencies(bmadDir) {
const sourcePath = getModulePath('core');
const targetPath = path.join(bmadDir, 'core');
await this.installCore(bmadDir); await this.installCore(bmadDir);
} }
@ -1517,7 +1514,7 @@ class Installer {
* @param {string} bmadDir - BMAD installation directory * @param {string} bmadDir - BMAD installation directory
* @param {Object} moduleFiles - Module files to install * @param {Object} moduleFiles - Module files to install
*/ */
async installModuleWithDependencies(moduleName, bmadDir, moduleFiles) { async installModuleWithDependencies(moduleName, bmadDir) {
// Get module configuration for conditional installation // Get module configuration for conditional installation
const moduleConfig = this.configCollector.collectedConfig[moduleName] || {}; const moduleConfig = this.configCollector.collectedConfig[moduleName] || {};
@ -1789,7 +1786,6 @@ class Installer {
} }
const agentName = agentFile.replace('.md', ''); const agentName = agentFile.replace('.md', '');
const mdPath = path.join(agentsPath, agentFile);
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`); const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
// For .md files that are already compiled, we don't need to do much // 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 _existingModules: installedModules, // Pass all installed modules for manifest generation
}; };
// Call the standard install method // Call the standard install method, but skip UI completion for quick updates
const result = await this.install(installConfig); installConfig._skipCompletion = true;
await this.install(installConfig);
// Only succeed the spinner if it's still spinning // Only succeed the spinner if it's still spinning
// (install method might have stopped it if folder name changed) // (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 fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root'); const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
@ -88,9 +89,9 @@ class AntigravitySetup extends BaseIdeSetup {
* @param {string} projectDir - Project directory * @param {string} projectDir - Project directory
*/ */
async cleanup(projectDir) { 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); await fs.remove(bmadWorkflowsDir);
console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`)); console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`));
} }
@ -112,9 +113,9 @@ class AntigravitySetup extends BaseIdeSetup {
await this.cleanup(projectDir); await this.cleanup(projectDir);
// Create .agent/workflows directory structure // Create .agent/workflows directory structure
const agentDir = path.join(projectDir, this.configDir); const agentDir = PathUtils.getConfigDir(projectDir, this.configDir);
const workflowsDir = path.join(agentDir, this.workflowsDir); const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad'); const bmadWorkflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir, 'bmad');
await this.ensureDir(bmadWorkflowsDir); await this.ensureDir(bmadWorkflowsDir);
@ -190,7 +191,7 @@ class AntigravitySetup extends BaseIdeSetup {
* Read and process file content * Read and process file content
*/ */
async readAndProcess(filePath, metadata) { async readAndProcess(filePath, metadata) {
const content = await fs.readFile(filePath, 'utf8'); const content = await this.readFile(filePath);
return this.processContent(content, metadata); return this.processContent(content, metadata);
} }
@ -210,7 +211,7 @@ class AntigravitySetup extends BaseIdeSetup {
// Add core agents // Add core agents
const corePath = getModulePath('core'); 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'); const coreAgents = await getAgentsFromDir(path.join(corePath, 'agents'), 'core');
agents.push(...coreAgents); agents.push(...coreAgents);
} }
@ -220,7 +221,7 @@ class AntigravitySetup extends BaseIdeSetup {
const modulePath = path.join(sourceDir, moduleName); const modulePath = path.join(sourceDir, moduleName);
const agentsPath = path.join(modulePath, 'agents'); const agentsPath = path.join(modulePath, 'agents');
if (await fs.pathExists(agentsPath)) { if (await this.exists(agentsPath)) {
const moduleAgents = await getAgentsFromDir(agentsPath, moduleName); const moduleAgents = await getAgentsFromDir(agentsPath, moduleName);
agents.push(...moduleAgents); agents.push(...moduleAgents);
} }
@ -387,7 +388,7 @@ class AntigravitySetup extends BaseIdeSetup {
const targetPath = path.join(projectDir, injection.file); const targetPath = path.join(projectDir, injection.file);
if (await this.exists(targetPath)) { 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} -->`; const marker = `<!-- IDE-INJECT-POINT: ${injection.point} -->`;
if (content.includes(marker)) { if (content.includes(marker)) {
@ -399,7 +400,7 @@ class AntigravitySetup extends BaseIdeSetup {
} }
content = content.replace(marker, injectionContent); content = content.replace(marker, injectionContent);
await fs.writeFile(targetPath, content); await this.writeFile(targetPath, content);
console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`)); console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`));
} }
} }
@ -417,7 +418,7 @@ class AntigravitySetup extends BaseIdeSetup {
targetDir = path.join(os.homedir(), '.agent', 'agents'); targetDir = path.join(os.homedir(), '.agent', 'agents');
console.log(chalk.dim(` Installing subagents globally to: ~/.agent/agents/`)); console.log(chalk.dim(` Installing subagents globally to: ~/.agent/agents/`));
} else { } else {
targetDir = path.join(projectDir, '.agent', 'agents'); targetDir = PathUtils.getIdeSubDir(projectDir, '.agent', 'agents');
console.log(chalk.dim(` Installing subagents to project: .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) { async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
// Create .agent/workflows/bmad directory structure (same as regular agents) // Create .agent/workflows/bmad directory structure (same as regular agents)
const agentDir = path.join(projectDir, this.configDir); const agentDir = PathUtils.getConfigDir(projectDir, this.configDir);
const workflowsDir = path.join(agentDir, this.workflowsDir); const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad'); 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 // Create custom agent launcher with same pattern as regular agents
const launcherContent = `name: '${agentName}' const launcherContent = `name: '${agentName}'
@ -493,7 +494,7 @@ usage: |
const launcherPath = path.join(bmadWorkflowsDir, fileName); const launcherPath = path.join(bmadWorkflowsDir, fileName);
// Write the launcher file // Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8'); await this.writeFile(launcherPath, launcherContent);
return { return {
ide: 'antigravity', ide: 'antigravity',

View File

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

View File

@ -2,6 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra'); const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root'); const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
@ -48,7 +49,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
try { try {
// Load injection configuration // Load injection configuration
const configContent = await fs.readFile(injectionConfigPath, 'utf8'); const configContent = await this.readFile(injectionConfigPath);
const injectionConfig = yaml.parse(configContent); const injectionConfig = yaml.parse(configContent);
// Ask about subagents if they exist and we haven't asked yet // 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 * @param {string} projectDir - Project directory
*/ */
async cleanup(projectDir) { 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); await fs.remove(bmadCommandsDir);
console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`)); console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`));
} }
@ -111,9 +112,9 @@ class ClaudeCodeSetup extends BaseIdeSetup {
await this.cleanup(projectDir); await this.cleanup(projectDir);
// Create .claude/commands directory structure // Create .claude/commands directory structure
const claudeDir = path.join(projectDir, this.configDir); const claudeDir = PathUtils.getConfigDir(projectDir, this.configDir);
const commandsDir = path.join(claudeDir, this.commandsDir); const commandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir);
const bmadCommandsDir = path.join(commandsDir, 'bmad'); const bmadCommandsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.commandsDir, 'bmad');
await this.ensureDir(bmadCommandsDir); await this.ensureDir(bmadCommandsDir);
@ -159,7 +160,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
let workflowCommandCount = 0; let workflowCommandCount = 0;
for (const artifact of workflowArtifacts) { for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') { 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); await this.ensureDir(moduleWorkflowsDir);
const commandPath = path.join(moduleWorkflowsDir, path.basename(artifact.relativePath)); const commandPath = path.join(moduleWorkflowsDir, path.basename(artifact.relativePath));
await this.writeFile(commandPath, artifact.content); await this.writeFile(commandPath, artifact.content);
@ -198,7 +199,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
* Read and process file content * Read and process file content
*/ */
async readAndProcess(filePath, metadata) { async readAndProcess(filePath, metadata) {
const content = await fs.readFile(filePath, 'utf8'); const content = await this.readFile(filePath);
return this.processContent(content, metadata); return this.processContent(content, metadata);
} }
@ -218,7 +219,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
// Add core agents // Add core agents
const corePath = getModulePath('core'); 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'); const coreAgents = await getAgentsFromDir(path.join(corePath, 'agents'), 'core');
agents.push(...coreAgents); agents.push(...coreAgents);
} }
@ -228,7 +229,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
const modulePath = path.join(sourceDir, moduleName); const modulePath = path.join(sourceDir, moduleName);
const agentsPath = path.join(modulePath, 'agents'); const agentsPath = path.join(modulePath, 'agents');
if (await fs.pathExists(agentsPath)) { if (await this.exists(agentsPath)) {
const moduleAgents = await getAgentsFromDir(agentsPath, moduleName); const moduleAgents = await getAgentsFromDir(agentsPath, moduleName);
agents.push(...moduleAgents); agents.push(...moduleAgents);
} }
@ -395,7 +396,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
const targetPath = path.join(projectDir, injection.file); const targetPath = path.join(projectDir, injection.file);
if (await this.exists(targetPath)) { 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} -->`; const marker = `<!-- IDE-INJECT-POINT: ${injection.point} -->`;
if (content.includes(marker)) { if (content.includes(marker)) {
@ -407,7 +408,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
} }
content = content.replace(marker, injectionContent); content = content.replace(marker, injectionContent);
await fs.writeFile(targetPath, content); await this.writeFile(targetPath, content);
console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`)); console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`));
} }
} }
@ -425,7 +426,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
targetDir = path.join(os.homedir(), '.claude', 'agents'); targetDir = path.join(os.homedir(), '.claude', 'agents');
console.log(chalk.dim(` Installing subagents globally to: ~/.claude/agents/`)); console.log(chalk.dim(` Installing subagents globally to: ~/.claude/agents/`));
} else { } else {
targetDir = path.join(projectDir, '.claude', 'agents'); targetDir = PathUtils.getIdeSubDir(projectDir, '.claude', 'agents');
console.log(chalk.dim(` Installing subagents to project: .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 * @returns {Object|null} Info about created command
*/ */
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { 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)))) { if (!(await this.exists(path.join(projectDir, this.configDir)))) {
return null; // IDE not configured for this project 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 fs = require('fs-extra');
const chalk = require('chalk'); const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts'); const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
@ -26,9 +27,8 @@ class ClineSetup extends BaseIdeSetup {
async setup(projectDir, bmadDir, options = {}) { async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`)); console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .clinerules/workflows directory // Create .clinerules/workflows directory using shared utilities
const clineDir = path.join(projectDir, this.configDir); const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
const workflowsDir = path.join(clineDir, this.workflowsDir);
await this.ensureDir(workflowsDir); await this.ensureDir(workflowsDir);
@ -72,9 +72,9 @@ class ClineSetup extends BaseIdeSetup {
* Detect Cline installation by checking for .clinerules/workflows directory * Detect Cline installation by checking for .clinerules/workflows directory
*/ */
async detect(projectDir) { 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; return false;
} }
@ -159,8 +159,8 @@ class ClineSetup extends BaseIdeSetup {
for (const artifact of artifacts) { for (const artifact of artifacts) {
const flattenedName = this.flattenFilename(artifact.relativePath); const flattenedName = this.flattenFilename(artifact.relativePath);
const targetPath = path.join(destDir, flattenedName); const targetPath = PathUtils.joinSafe(destDir, flattenedName);
await fs.writeFile(targetPath, artifact.content); await this.writeFile(targetPath, artifact.content);
written++; written++;
} }
@ -204,7 +204,7 @@ class ClineSetup extends BaseIdeSetup {
* Cleanup Cline configuration * Cleanup Cline configuration
*/ */
async cleanup(projectDir) { 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); await this.clearOldBmadFiles(workflowsDir);
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`)); console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
} }
@ -218,11 +218,10 @@ class ClineSetup extends BaseIdeSetup {
* @returns {Object} Installation result * @returns {Object} Installation result
*/ */
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) { async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const clineDir = path.join(projectDir, this.configDir); const workflowsDir = PathUtils.getIdeSubDir(projectDir, this.configDir, this.workflowsDir);
const workflowsDir = path.join(clineDir, this.workflowsDir);
// Create .clinerules/workflows directory if it doesn't exist // Create .clinerules/workflows directory if it doesn't exist
await fs.ensureDir(workflowsDir); await this.ensureDir(workflowsDir);
// Create custom agent launcher workflow // Create custom agent launcher workflow
const launcherContent = `name: ${agentName} const launcherContent = `name: ${agentName}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ const os = require('node:os');
const chalk = require('chalk'); const chalk = require('chalk');
const yaml = require('yaml'); const yaml = require('yaml');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-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 fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts'); const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); const { AgentCommandGenerator } = require('./shared/agent-command-generator');

View File

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

View File

@ -1,6 +1,7 @@
const path = require('node:path'); const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide'); const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk'); const chalk = require('chalk');
const { FileOps, PathUtils } = require('../../../lib/file-ops');
const { AgentCommandGenerator } = require('./shared/agent-command-generator'); 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 category: ide
description: "Enhanced Cline fork" description: "Enhanced Cline fork"
rovo:
name: "Rovo Dev"
preferred: false
category: ide
description: "Atlassian's AI coding assistant"
github-copilot: github-copilot:
name: "GitHub Copilot" name: "GitHub Copilot"
preferred: false preferred: false
@ -115,6 +109,18 @@ platforms:
category: ide category: ide
description: "AI coding tool" 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 # Platform categories
categories: categories:
ide: ide: