Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions tools/cli/installers/lib/ide/_base-ide.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class BaseIdeSetup {
this.preferred = preferred; // Whether this IDE should be shown in preferred list
this.configDir = null; // Override in subclasses
this.rulesDir = null; // Override in subclasses
this.configFile = null; // Override in subclasses when detection is file-based
this.detectionPaths = []; // Additional paths that indicate the IDE is configured
this.xmlHandler = new XmlHandler();
}

Expand Down Expand Up @@ -46,6 +48,40 @@ class BaseIdeSetup {
}
}

/**
* Detect whether this IDE already has configuration in the project
* Subclasses can override for custom logic
* @param {string} projectDir - Project directory
* @returns {boolean}
*/
async detect(projectDir) {
const pathsToCheck = [];

if (this.configDir) {
pathsToCheck.push(path.join(projectDir, this.configDir));
}

if (this.configFile) {
pathsToCheck.push(path.join(projectDir, this.configFile));
}

if (Array.isArray(this.detectionPaths)) {
for (const candidate of this.detectionPaths) {
if (!candidate) continue;
const resolved = path.isAbsolute(candidate) ? candidate : path.join(projectDir, candidate);
pathsToCheck.push(resolved);
}
}

for (const candidate of pathsToCheck) {
if (await fs.pathExists(candidate)) {
return true;
}
}

return false;
}

/**
* Get list of agents from BMAD installation
* @param {string} bmadDir - BMAD installation directory
Expand Down
1 change: 1 addition & 0 deletions tools/cli/installers/lib/ide/auggie.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class AuggieSetup extends BaseIdeSetup {
{ name: 'User Home (~/.auggie/commands)', value: path.join(os.homedir(), '.auggie', 'commands') },
{ name: 'Custom Location', value: 'custom' },
];
this.detectionPaths = ['.auggie'];
}

/**
Expand Down
14 changes: 14 additions & 0 deletions tools/cli/installers/lib/ide/codex.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ class CodexSetup extends BaseIdeSetup {
};
}

/**
* Detect Codex installation by checking for BMAD prompt exports
*/
async detect(_projectDir) {
const destDir = this.getCodexPromptDir();

if (!(await fs.pathExists(destDir))) {
return false;
}

const entries = await fs.readdir(destDir);
return entries.some((entry) => entry.startsWith('bmad-'));
}

/**
* Collect Claude-style artifacts for Codex export.
* Returns the normalized artifact list for further processing.
Expand Down
36 changes: 3 additions & 33 deletions tools/cli/installers/lib/ide/manager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const fs = require('fs-extra');
const path = require('node:path');
const chalk = require('chalk');
const os = require('node:os');

/**
* IDE Manager - handles IDE-specific setup
Expand Down Expand Up @@ -166,38 +165,9 @@ class IdeManager {
async detectInstalledIdes(projectDir) {
const detected = [];

// Check for IDE-specific directories
const ideChecks = {
cursor: '.cursor',
'claude-code': '.claude',
windsurf: '.windsurf',
cline: '.clinerules',
roo: '.roomodes',
trae: '.trae',
kilo: '.kilocodemodes',
gemini: '.gemini',
qwen: '.qwen',
crush: '.crush',
iflow: '.iflow',
auggie: '.auggie',
'github-copilot': '.github/chatmodes',
vscode: '.vscode',
idea: '.idea',
};

for (const [ide, dir] of Object.entries(ideChecks)) {
const idePath = path.join(projectDir, dir);
if (await fs.pathExists(idePath)) {
detected.push(ide);
}
}

// Check Codex prompt directory for BMAD exports
const codexPromptDir = path.join(os.homedir(), '.codex', 'prompts');
if (await fs.pathExists(codexPromptDir)) {
const codexEntries = await fs.readdir(codexPromptDir);
if (codexEntries.some((file) => file.startsWith('bmad-'))) {
detected.push('codex');
for (const [name, handler] of this.handlers) {
if (typeof handler.detect === 'function' && (await handler.detect(projectDir))) {
detected.push(name);
}
}

Expand Down