From 073597a8ffd8a3c4418977ff23952f93a1206797 Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Tue, 18 Nov 2025 16:48:32 -0800 Subject: [PATCH 1/2] feat: Add project-specific installation option for Codex CLI (#937) Add support for choosing between global and project-specific installation locations for Codex CLI prompts with CODEX_HOME configuration instructions. Changes: - Add collectConfiguration() to prompt for installation location (default: global) - Support global (~/.codex/prompts) and project-specific (/.codex/prompts) - Display OS-specific CODEX_HOME setup instructions before user confirms - Update detect() and cleanup() to handle both installation locations Installation options: - Global: Simple, works immediately, but prompts reference specific .bmad path - Project-specific: Requires CODEX_HOME, better for multi-project workflows - Unix/Mac: alias codex='CODEX_HOME="$PWD/.codex" codex' - Windows: codex.cmd wrapper with %~dp0 --- tools/cli/installers/lib/ide/codex.js | 207 ++++++++++++++++++++++---- 1 file changed, 179 insertions(+), 28 deletions(-) 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); + } } } From 7b7f984cd242358d4a906dc40ad27b256aa78293 Mon Sep 17 00:00:00 2001 From: Alex Verkhovsky Date: Tue, 18 Nov 2025 18:09:06 -0800 Subject: [PATCH 2/2] feat: Add Google Antigravity IDE installer (#938) Implements installer for Google Antigravity IDE with flattened slash command naming to match Antigravity's namespace requirements. Key features: - Flattened file naming (bmad-module-agents-name.md) for proper slash commands - Subagent installation support (project-level or user-level) - Module-specific injection configuration - Agent, workflow, task, and tool command generation Implementation: - Added AntigravitySetup class extending BaseIdeSetup - Extracted flattenFilename() to BaseIdeSetup for reuse across IDE handlers - Uses .agent/workflows directory structure - Supports both interactive and non-interactive configuration Fixes: - Proper namespace isolation: /bmad-module-agents-dev instead of /dev - Prevents conflicts between modules with same agent names Note: This installer shares 95% of its code with claude-code.js. Future refactoring could extract common patterns to IdeWithSlashCommandsSetup base class (see design documents for details). --- tools/cli/installers/lib/ide/_base-ide.js | 12 + tools/cli/installers/lib/ide/antigravity.js | 463 ++++++++++++++++++++ tools/cli/installers/lib/ide/codex.js | 5 - 3 files changed, 475 insertions(+), 5 deletions(-) create mode 100644 tools/cli/installers/lib/ide/antigravity.js diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index 25122ade..5ee39cb6 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -601,6 +601,18 @@ class BaseIdeSetup { .join(' '); } + /** + * Flatten a relative path to a single filename for flat slash command naming + * Example: 'module/agents/name.md' -> 'bmad-module-agents-name.md' + * Used by IDEs that ignore directory structure for slash commands (e.g., Antigravity, Codex) + * @param {string} relativePath - Relative path to flatten + * @returns {string} Flattened filename with 'bmad-' prefix + */ + flattenFilename(relativePath) { + const sanitized = relativePath.replaceAll(/[/\\]/g, '-'); + return `bmad-${sanitized}`; + } + /** * Create agent configuration file * @param {string} bmadDir - BMAD installation directory diff --git a/tools/cli/installers/lib/ide/antigravity.js b/tools/cli/installers/lib/ide/antigravity.js new file mode 100644 index 00000000..3bccd911 --- /dev/null +++ b/tools/cli/installers/lib/ide/antigravity.js @@ -0,0 +1,463 @@ +const path = require('node:path'); +const { BaseIdeSetup } = require('./_base-ide'); +const chalk = require('chalk'); +const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root'); +const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator'); +const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator'); +const { AgentCommandGenerator } = require('./shared/agent-command-generator'); +const { + loadModuleInjectionConfig, + shouldApplyInjection, + filterAgentInstructions, + resolveSubagentFiles, +} = require('./shared/module-injections'); +const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts'); + +/** + * Google Antigravity IDE setup handler + * + * Uses .agent/workflows/ directory for slash commands + */ +class AntigravitySetup extends BaseIdeSetup { + constructor() { + super('antigravity', 'Google Antigravity', false); + this.configDir = '.agent'; + this.workflowsDir = 'workflows'; + } + + /** + * Collect configuration choices before installation + * @param {Object} options - Configuration options + * @returns {Object} Collected configuration + */ + async collectConfiguration(options = {}) { + const config = { + subagentChoices: null, + installLocation: null, + }; + + const sourceModulesPath = getSourcePath('modules'); + const modules = options.selectedModules || []; + + for (const moduleName of modules) { + // Check for Antigravity sub-module injection config in SOURCE directory + const injectionConfigPath = path.join(sourceModulesPath, moduleName, 'sub-modules', 'antigravity', 'injections.yaml'); + + if (await this.exists(injectionConfigPath)) { + const fs = require('fs-extra'); + const yaml = require('js-yaml'); + + try { + // Load injection configuration + const configContent = await fs.readFile(injectionConfigPath, 'utf8'); + const injectionConfig = yaml.load(configContent); + + // Ask about subagents if they exist and we haven't asked yet + if (injectionConfig.subagents && !config.subagentChoices) { + config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents); + + if (config.subagentChoices.install !== 'none') { + // Ask for installation location + const inquirer = require('inquirer'); + const locationAnswer = await inquirer.prompt([ + { + type: 'list', + name: 'location', + message: 'Where would you like to install Antigravity subagents?', + choices: [ + { name: 'Project level (.agent/agents/)', value: 'project' }, + { name: 'User level (~/.agent/agents/)', value: 'user' }, + ], + default: 'project', + }, + ]); + config.installLocation = locationAnswer.location; + } + } + } catch (error) { + console.log(chalk.yellow(` Warning: Failed to process ${moduleName} features: ${error.message}`)); + } + } + } + + return config; + } + + /** + * Cleanup old BMAD installation before reinstalling + * @param {string} projectDir - Project directory + */ + async cleanup(projectDir) { + const fs = require('fs-extra'); + const bmadWorkflowsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad'); + + if (await fs.pathExists(bmadWorkflowsDir)) { + await fs.remove(bmadWorkflowsDir); + console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`)); + } + } + + /** + * Setup Antigravity IDE configuration + * @param {string} projectDir - Project directory + * @param {string} bmadDir - BMAD installation directory + * @param {Object} options - Setup options + */ + async setup(projectDir, bmadDir, options = {}) { + // Store project directory for use in processContent + this.projectDir = projectDir; + + console.log(chalk.cyan(`Setting up ${this.name}...`)); + + // Clean up old BMAD installation first + 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'); + + await this.ensureDir(bmadWorkflowsDir); + + // Generate agent launchers using AgentCommandGenerator + // This creates small launcher files that reference the actual agents in .bmad/ + const agentGen = new AgentCommandGenerator(this.bmadFolderName); + const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); + + // Write agent launcher files with FLATTENED naming + // Antigravity ignores directory structure, so we flatten to: bmad-module-agents-name.md + // This creates slash commands like /bmad-bmm-agents-dev instead of /dev + let agentCount = 0; + for (const artifact of agentArtifacts) { + const flattenedName = this.flattenFilename(artifact.relativePath); + const targetPath = path.join(bmadWorkflowsDir, flattenedName); + await this.writeFile(targetPath, artifact.content); + agentCount++; + } + + // Process Antigravity specific injections for installed modules + // Use pre-collected configuration if available, or skip if already configured + if (options.preCollectedConfig && options.preCollectedConfig._alreadyConfigured) { + // IDE is already configured from previous installation, skip prompting + // Just process with default/existing configuration + await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, {}); + } else if (options.preCollectedConfig) { + await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, options.preCollectedConfig); + } else { + await this.processModuleInjections(projectDir, bmadDir, options); + } + + // Generate workflow commands from manifest (if it exists) + const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); + const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir); + + // Write workflow-command artifacts with FLATTENED naming + let workflowCommandCount = 0; + for (const artifact of workflowArtifacts) { + if (artifact.type === 'workflow-command') { + const flattenedName = this.flattenFilename(artifact.relativePath); + const targetPath = path.join(bmadWorkflowsDir, flattenedName); + await this.writeFile(targetPath, artifact.content); + workflowCommandCount++; + } + } + + // Generate task and tool commands from manifests (if they exist) + const taskToolGen = new TaskToolCommandGenerator(); + const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir); + + console.log(chalk.green(`✓ ${this.name} configured:`)); + console.log(chalk.dim(` - ${agentCount} agents installed`)); + if (workflowCommandCount > 0) { + console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated`)); + } + if (taskToolResult.generated > 0) { + console.log( + chalk.dim( + ` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`, + ), + ); + } + console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, bmadWorkflowsDir)}`)); + console.log(chalk.yellow(`\n Note: Antigravity uses flattened slash commands (e.g., /bmad-module-agents-name)`)); + + return { + success: true, + agents: agentCount, + }; + } + + /** + * Read and process file content + */ + async readAndProcess(filePath, metadata) { + const fs = require('fs-extra'); + const content = await fs.readFile(filePath, 'utf8'); + return this.processContent(content, metadata); + } + + /** + * Override processContent to keep {project-root} placeholder + */ + processContent(content, metadata = {}) { + // Use the base class method WITHOUT projectDir to preserve {project-root} placeholder + return super.processContent(content, metadata); + } + + /** + * Get agents from source modules (not installed location) + */ + async getAgentsFromSource(sourceDir, selectedModules) { + const fs = require('fs-extra'); + const agents = []; + + // Add core agents + const corePath = getModulePath('core'); + if (await fs.pathExists(path.join(corePath, 'agents'))) { + const coreAgents = await getAgentsFromDir(path.join(corePath, 'agents'), 'core'); + agents.push(...coreAgents); + } + + // Add module agents + for (const moduleName of selectedModules) { + const modulePath = path.join(sourceDir, moduleName); + const agentsPath = path.join(modulePath, 'agents'); + + if (await fs.pathExists(agentsPath)) { + const moduleAgents = await getAgentsFromDir(agentsPath, moduleName); + agents.push(...moduleAgents); + } + } + + return agents; + } + + /** + * Process module injections with pre-collected configuration + */ + async processModuleInjectionsWithConfig(projectDir, bmadDir, options, preCollectedConfig) { + // Get list of installed modules + const modules = options.selectedModules || []; + const { subagentChoices, installLocation } = preCollectedConfig; + + // Get the actual source directory (not the installation directory) + await this.processModuleInjectionsInternal({ + projectDir, + modules, + handler: 'antigravity', + subagentChoices, + installLocation, + interactive: false, + }); + } + + /** + * Process Antigravity specific injections for installed modules + * Looks for injections.yaml in each module's antigravity sub-module + */ + async processModuleInjections(projectDir, bmadDir, options) { + // Get list of installed modules + const modules = options.selectedModules || []; + let subagentChoices = null; + let installLocation = null; + + // Get the actual source directory (not the installation directory) + const { subagentChoices: updatedChoices, installLocation: updatedLocation } = await this.processModuleInjectionsInternal({ + projectDir, + modules, + handler: 'antigravity', + subagentChoices, + installLocation, + interactive: true, + }); + + if (updatedChoices) { + subagentChoices = updatedChoices; + } + if (updatedLocation) { + installLocation = updatedLocation; + } + } + + async processModuleInjectionsInternal({ projectDir, modules, handler, subagentChoices, installLocation, interactive = false }) { + let choices = subagentChoices; + let location = installLocation; + + for (const moduleName of modules) { + const configData = await loadModuleInjectionConfig(handler, moduleName); + + if (!configData) { + continue; + } + + const { config, handlerBaseDir } = configData; + + if (interactive) { + console.log(chalk.cyan(`\nConfiguring ${moduleName} ${handler} features...`)); + } + + if (interactive && config.subagents && !choices) { + choices = await this.promptSubagentInstallation(config.subagents); + + if (choices.install !== 'none') { + const inquirer = require('inquirer'); + const locationAnswer = await inquirer.prompt([ + { + type: 'list', + name: 'location', + message: 'Where would you like to install Antigravity subagents?', + choices: [ + { name: 'Project level (.agent/agents/)', value: 'project' }, + { name: 'User level (~/.agent/agents/)', value: 'user' }, + ], + default: 'project', + }, + ]); + location = locationAnswer.location; + } + } + + if (config.injections && choices && choices.install !== 'none') { + for (const injection of config.injections) { + if (shouldApplyInjection(injection, choices)) { + await this.injectContent(projectDir, injection, choices); + } + } + } + + if (config.subagents && choices && choices.install !== 'none') { + await this.copySelectedSubagents(projectDir, handlerBaseDir, config.subagents, choices, location || 'project'); + } + } + + return { subagentChoices: choices, installLocation: location }; + } + + /** + * Prompt user for subagent installation preferences + */ + async promptSubagentInstallation(subagentConfig) { + const inquirer = require('inquirer'); + + // First ask if they want to install subagents + const { install } = await inquirer.prompt([ + { + type: 'list', + name: 'install', + message: 'Would you like to install Antigravity subagents for enhanced functionality?', + choices: [ + { name: 'Yes, install all subagents', value: 'all' }, + { name: 'Yes, let me choose specific subagents', value: 'selective' }, + { name: 'No, skip subagent installation', value: 'none' }, + ], + default: 'all', + }, + ]); + + if (install === 'selective') { + // Show list of available subagents with descriptions + const subagentInfo = { + 'market-researcher.md': 'Market research and competitive analysis', + 'requirements-analyst.md': 'Requirements extraction and validation', + 'technical-evaluator.md': 'Technology stack evaluation', + 'epic-optimizer.md': 'Epic and story breakdown optimization', + 'document-reviewer.md': 'Document quality review', + }; + + const { selected } = await inquirer.prompt([ + { + type: 'checkbox', + name: 'selected', + message: 'Select subagents to install:', + choices: subagentConfig.files.map((file) => ({ + name: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`, + value: file, + checked: true, + })), + }, + ]); + + return { install: 'selective', selected }; + } + + return { install }; + } + + /** + * Inject content at specified point in file + */ + async injectContent(projectDir, injection, subagentChoices = null) { + const fs = require('fs-extra'); + const targetPath = path.join(projectDir, injection.file); + + if (await this.exists(targetPath)) { + let content = await fs.readFile(targetPath, 'utf8'); + const marker = ``; + + if (content.includes(marker)) { + let injectionContent = injection.content; + + // Filter content if selective subagents chosen + if (subagentChoices && subagentChoices.install === 'selective' && injection.point === 'pm-agent-instructions') { + injectionContent = filterAgentInstructions(injection.content, subagentChoices.selected); + } + + content = content.replace(marker, injectionContent); + await fs.writeFile(targetPath, content); + console.log(chalk.dim(` Injected: ${injection.point} → ${injection.file}`)); + } + } + } + + /** + * Copy selected subagents to appropriate Antigravity agents directory + */ + async copySelectedSubagents(projectDir, handlerBaseDir, subagentConfig, choices, location) { + const fs = require('fs-extra'); + const os = require('node:os'); + + // Determine target directory based on user choice + let targetDir; + if (location === 'user') { + targetDir = path.join(os.homedir(), '.agent', 'agents'); + console.log(chalk.dim(` Installing subagents globally to: ~/.agent/agents/`)); + } else { + targetDir = path.join(projectDir, '.agent', 'agents'); + console.log(chalk.dim(` Installing subagents to project: .agent/agents/`)); + } + + // Ensure target directory exists + await this.ensureDir(targetDir); + + const resolvedFiles = await resolveSubagentFiles(handlerBaseDir, subagentConfig, choices); + + let copiedCount = 0; + for (const resolved of resolvedFiles) { + try { + const sourcePath = resolved.absolutePath; + + const subFolder = path.dirname(resolved.relativePath); + let targetPath; + if (subFolder && subFolder !== '.') { + const targetSubDir = path.join(targetDir, subFolder); + await this.ensureDir(targetSubDir); + targetPath = path.join(targetSubDir, path.basename(resolved.file)); + } else { + targetPath = path.join(targetDir, path.basename(resolved.file)); + } + + await fs.copyFile(sourcePath, targetPath); + console.log(chalk.green(` ✓ Installed: ${subFolder === '.' ? '' : `${subFolder}/`}${path.basename(resolved.file, '.md')}`)); + copiedCount++; + } catch (error) { + console.log(chalk.yellow(` ⚠ Error copying ${resolved.file}: ${error.message}`)); + } + } + + if (copiedCount > 0) { + console.log(chalk.dim(` Total subagents installed: ${copiedCount}`)); + } + } +} + +module.exports = { AntigravitySetup }; diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index 66a00327..480d805c 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -212,11 +212,6 @@ class CodexSetup extends BaseIdeSetup { return path.join(os.homedir(), '.codex', 'prompts'); } - flattenFilename(relativePath) { - const sanitized = relativePath.replaceAll(/[\\/]/g, '-'); - return `bmad-${sanitized}`; - } - async flattenAndWriteArtifacts(artifacts, destDir) { let written = 0;