diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index 1b4a8d69..66a00327 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -15,6 +15,67 @@ class CodexSetup extends BaseIdeSetup { super('codex', 'Codex', true); // preferred IDE } + /** + * Collect configuration choices before installation + * @param {Object} options - Configuration options + * @returns {Object} Collected configuration + */ + async collectConfiguration(options = {}) { + const inquirer = require('inquirer'); + + let confirmed = false; + let installLocation = 'global'; + + while (!confirmed) { + const { location } = await inquirer.prompt([ + { + type: 'list', + name: 'location', + message: 'Where would you like to install Codex CLI prompts?', + choices: [ + { + name: 'Global - Simple for single project ' + '(~/.codex/prompts, but references THIS project only)', + value: 'global', + }, + { + name: `Project-specific - Recommended for real work (requires CODEX_HOME=${path.sep}.codex)`, + value: 'project', + }, + ], + default: 'global', + }, + ]); + + installLocation = location; + + // Display detailed instructions for the chosen option + console.log(''); + if (installLocation === 'project') { + console.log(this.getProjectSpecificInstructions()); + } else { + console.log(this.getGlobalInstructions()); + } + + // Confirm the choice + const { proceed } = await inquirer.prompt([ + { + type: 'confirm', + name: 'proceed', + message: 'Proceed with this installation option?', + default: true, + }, + ]); + + confirmed = proceed; + + if (!confirmed) { + console.log(chalk.yellow("\n Let's choose a different installation option.\n")); + } + } + + return { installLocation }; + } + /** * Setup Codex configuration * @param {string} projectDir - Project directory @@ -27,9 +88,12 @@ class CodexSetup extends BaseIdeSetup { // Always use CLI mode const mode = 'cli'; + // Get installation location from pre-collected config or default to global + const installLocation = options.preCollectedConfig?.installLocation || 'global'; + const { artifacts, counts } = await this.collectClaudeArtifacts(projectDir, bmadDir, options); - const destDir = this.getCodexPromptDir(); + const destDir = this.getCodexPromptDir(projectDir, installLocation); await fs.ensureDir(destDir); await this.clearOldBmadFiles(destDir); const written = await this.flattenAndWriteArtifacts(artifacts, destDir); @@ -45,22 +109,6 @@ class CodexSetup extends BaseIdeSetup { console.log(chalk.dim(` - ${written} Codex prompt files written`)); console.log(chalk.dim(` - Destination: ${destDir}`)); - // Prominent notice about home directory installation - console.log(''); - console.log(chalk.bold.cyan('═'.repeat(70))); - console.log(chalk.bold.yellow(' IMPORTANT: Codex Configuration')); - console.log(chalk.bold.cyan('═'.repeat(70))); - console.log(''); - console.log(chalk.white(' Prompts have been installed to your HOME DIRECTORY, not this project.')); - console.log(chalk.white(' No .codex file was created in the project root.')); - console.log(''); - console.log(chalk.green(' ✓ You can now use slash commands (/) in Codex CLI')); - console.log(chalk.dim(' Example: /bmad-bmm-agents-pm')); - console.log(chalk.dim(' Type / to see all available commands')); - console.log(''); - console.log(chalk.bold.cyan('═'.repeat(70))); - console.log(''); - return { success: true, mode, @@ -68,21 +116,36 @@ class CodexSetup extends BaseIdeSetup { counts, destination: destDir, written, + installLocation, }; } /** * Detect Codex installation by checking for BMAD prompt exports */ - async detect(_projectDir) { - const destDir = this.getCodexPromptDir(); + async detect(projectDir) { + // Check both global and project-specific locations + const globalDir = this.getCodexPromptDir(null, 'global'); + const projectDir_local = projectDir || process.cwd(); + const projectSpecificDir = this.getCodexPromptDir(projectDir_local, 'project'); - if (!(await fs.pathExists(destDir))) { - return false; + // Check global location + if (await fs.pathExists(globalDir)) { + const entries = await fs.readdir(globalDir); + if (entries.some((entry) => entry.startsWith('bmad-'))) { + return true; + } } - const entries = await fs.readdir(destDir); - return entries.some((entry) => entry.startsWith('bmad-')); + // Check project-specific location + if (await fs.pathExists(projectSpecificDir)) { + const entries = await fs.readdir(projectSpecificDir); + if (entries.some((entry) => entry.startsWith('bmad-'))) { + return true; + } + } + + return false; } /** @@ -142,7 +205,10 @@ class CodexSetup extends BaseIdeSetup { }; } - getCodexPromptDir() { + getCodexPromptDir(projectDir = null, location = 'global') { + if (location === 'project' && projectDir) { + return path.join(projectDir, '.codex', 'prompts'); + } return path.join(os.homedir(), '.codex', 'prompts'); } @@ -192,11 +258,96 @@ class CodexSetup extends BaseIdeSetup { } /** - * Cleanup Codex configuration (no-op until export destination is finalized) + * Get instructions for global installation + * @returns {string} Instructions text */ - async cleanup() { - const destDir = this.getCodexPromptDir(); - await this.clearOldBmadFiles(destDir); + getGlobalInstructions(destDir) { + const lines = [ + '', + chalk.bold.cyan('═'.repeat(70)), + chalk.bold.yellow(' IMPORTANT: Codex Configuration'), + chalk.bold.cyan('═'.repeat(70)), + '', + chalk.white(' /prompts installed globally to your HOME DIRECTORY.'), + '', + chalk.yellow(' ⚠️ These prompts reference a specific .bmad path'), + chalk.dim(" To use with other projects, you'd need to copy the .bmad dir"), + '', + chalk.green(' ✓ You can now use /commands in Codex CLI'), + chalk.dim(' Example: /bmad-bmm-agents-pm'), + chalk.dim(' Type / to see all available commands'), + '', + chalk.bold.cyan('═'.repeat(70)), + '', + ]; + return lines.join('\n'); + } + + /** + * Get instructions for project-specific installation + * @param {string} projectDir - Optional project directory + * @param {string} destDir - Optional destination directory + * @returns {string} Instructions text + */ + getProjectSpecificInstructions(projectDir = null, destDir = null) { + const isWindows = os.platform() === 'win32'; + + const commonLines = [ + '', + chalk.bold.cyan('═'.repeat(70)), + chalk.bold.yellow(' Project-Specific Codex Configuration'), + chalk.bold.cyan('═'.repeat(70)), + '', + chalk.white(' Prompts will be installed to: ') + chalk.cyan(destDir || '/.codex/prompts'), + '', + chalk.bold.yellow(' ⚠️ REQUIRED: You must set CODEX_HOME to use these prompts'), + '', + ]; + + const windowsLines = [ + chalk.bold(' Create a codex.cmd file in your project root:'), + '', + chalk.green(' @echo off'), + chalk.green(' set CODEX_HOME=%~dp0.codex'), + chalk.green(' codex %*'), + '', + chalk.dim(String.raw` Then run: .\codex instead of codex`), + chalk.dim(' (The %~dp0 gets the directory of the .cmd file)'), + ]; + + const unixLines = [ + chalk.bold(' Add this alias to your ~/.bashrc or ~/.zshrc:'), + '', + chalk.green(' alias codex=\'CODEX_HOME="$PWD/.codex" codex\''), + '', + chalk.dim(' After adding, run: source ~/.bashrc (or source ~/.zshrc)'), + chalk.dim(' (The $PWD uses your current working directory)'), + ]; + const closingLines = [ + '', + chalk.dim(' This tells Codex CLI to use prompts from this project instead of ~/.codex'), + '', + chalk.bold.cyan('═'.repeat(70)), + '', + ]; + + const lines = [...commonLines, ...(isWindows ? windowsLines : unixLines), ...closingLines]; + + return lines.join('\n'); + } + + /** + * Cleanup Codex configuration + */ + async cleanup(projectDir = null) { + // Clean both global and project-specific locations + const globalDir = this.getCodexPromptDir(null, 'global'); + await this.clearOldBmadFiles(globalDir); + + if (projectDir) { + const projectSpecificDir = this.getCodexPromptDir(projectDir, 'project'); + await this.clearOldBmadFiles(projectSpecificDir); + } } }