Files
BMAD-METHOD/tools/cli/installers/lib/ide/codex.js
2025-09-28 23:17:07 -05:00

268 lines
7.7 KiB
JavaScript

const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const inquirer = require('inquirer');
/**
* Codex setup handler (supports both CLI and Web)
* Creates comprehensive AGENTS.md file in project root
*/
class CodexSetup extends BaseIdeSetup {
constructor() {
super('codex', 'Codex', true); // preferred IDE
this.agentsFile = 'AGENTS.md';
}
/**
* Collect configuration choices before installation
* @param {Object} options - Configuration options
* @returns {Object} Collected configuration
*/
async collectConfiguration(options = {}) {
const response = await inquirer.prompt([
{
type: 'list',
name: 'mode',
message: 'Select Codex deployment mode:',
choices: [
{ name: 'CLI (Command-line interface)', value: 'cli' },
{ name: 'Web (Browser-based interface)', value: 'web' },
],
default: 'cli',
},
]);
return { codexMode: response.mode };
}
/**
* Setup Codex configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Use pre-collected configuration if available
const config = options.preCollectedConfig || {};
const mode = config.codexMode || options.codexMode || 'cli';
// Get agents and tasks
const agents = await this.getAgents(bmadDir);
const tasks = await this.getTasks(bmadDir);
// Create AGENTS.md content
const content = this.createAgentsDocument(agents, tasks, mode);
// Write AGENTS.md file
const agentsPath = path.join(projectDir, this.agentsFile);
await this.writeFile(agentsPath, content);
// Handle mode-specific setup
if (mode === 'web') {
await this.setupWebMode(projectDir);
} else {
await this.setupCliMode(projectDir);
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - Mode: ${mode === 'web' ? 'Web' : 'CLI'}`));
console.log(chalk.dim(` - ${agents.length} agents documented`));
console.log(chalk.dim(` - ${tasks.length} tasks documented`));
console.log(chalk.dim(` - Agents file: ${this.agentsFile}`));
return {
success: true,
mode,
agents: agents.length,
tasks: tasks.length,
};
}
/**
* Select Codex mode (CLI or Web)
*/
async selectMode() {
const response = await inquirer.prompt([
{
type: 'list',
name: 'mode',
message: 'Select Codex deployment mode:',
choices: [
{ name: 'CLI (Command-line interface)', value: 'cli' },
{ name: 'Web (Browser-based interface)', value: 'web' },
],
default: 'cli',
},
]);
return response.mode;
}
/**
* Create comprehensive agents document
*/
createAgentsDocument(agents, tasks, mode) {
let content = `# BMAD Method - Agent Directory
This document contains all available BMAD agents and tasks for use with Codex ${mode === 'web' ? 'Web' : 'CLI'}.
## Quick Start
${
mode === 'web'
? `Access agents through the web interface:
1. Navigate to the Agents section
2. Select an agent to activate
3. The agent persona will be active for your session`
: `Activate agents in CLI:
1. Reference agents using \`@{agent-name}\`
2. Execute tasks using \`@task-{task-name}\`
3. Agents remain active for the conversation`
}
---
## Available Agents
`;
// Group agents by module
const agentsByModule = {};
for (const agent of agents) {
if (!agentsByModule[agent.module]) {
agentsByModule[agent.module] = [];
}
agentsByModule[agent.module].push(agent);
}
// Document each module's agents
for (const [module, moduleAgents] of Object.entries(agentsByModule)) {
content += `### ${module.toUpperCase()} Module\n\n`;
for (const agent of moduleAgents) {
const agentContent = this.readFileSync(agent.path);
const titleMatch = agentContent.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
const iconMatch = agentContent.match(/icon="([^"]+)"/);
const icon = iconMatch ? iconMatch[1] : '🤖';
const whenToUseMatch = agentContent.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
content += `#### ${icon} ${title} (\`@${agent.name}\`)\n\n`;
content += `**When to use:** ${whenToUse}\n\n`;
content += `**Activation:** Type \`@${agent.name}\` to activate this agent.\n\n`;
}
}
content += `---
## Available Tasks
`;
// Group tasks by module
const tasksByModule = {};
for (const task of tasks) {
if (!tasksByModule[task.module]) {
tasksByModule[task.module] = [];
}
tasksByModule[task.module].push(task);
}
// Document each module's tasks
for (const [module, moduleTasks] of Object.entries(tasksByModule)) {
content += `### ${module.toUpperCase()} Module Tasks\n\n`;
for (const task of moduleTasks) {
const taskContent = this.readFileSync(task.path);
const nameMatch = taskContent.match(/<name>([^<]+)<\/name>/);
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
content += `- **${taskName}** (\`@task-${task.name}\`)\n`;
}
content += '\n';
}
content += `---
## Usage Guidelines
1. **One agent at a time**: Activate a single agent for focused assistance
2. **Task execution**: Tasks are one-time workflows, not persistent personas
3. **Module organization**: Agents and tasks are grouped by their source module
4. **Context preservation**: ${mode === 'web' ? 'Sessions maintain agent context' : 'Conversations maintain agent context'}
---
*Generated by BMAD Method installer for Codex ${mode === 'web' ? 'Web' : 'CLI'}*
`;
return content;
}
/**
* Read file synchronously (for document generation)
*/
readFileSync(filePath) {
const fs = require('node:fs');
try {
return fs.readFileSync(filePath, 'utf8');
} catch {
return '';
}
}
/**
* Setup for CLI mode
*/
async setupCliMode(projectDir) {
// CLI mode - ensure .gitignore includes AGENTS.md if needed
const fs = require('fs-extra');
const gitignorePath = path.join(projectDir, '.gitignore');
if (await fs.pathExists(gitignorePath)) {
const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
if (!gitignoreContent.includes('AGENTS.md')) {
// User can decide whether to track this file
console.log(chalk.dim(' Note: Consider adding AGENTS.md to .gitignore if desired'));
}
}
}
/**
* Setup for Web mode
*/
async setupWebMode(projectDir) {
// Web mode - add to .gitignore to avoid committing
const fs = require('fs-extra');
const gitignorePath = path.join(projectDir, '.gitignore');
if (await fs.pathExists(gitignorePath)) {
const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
if (!gitignoreContent.includes('AGENTS.md')) {
await fs.appendFile(gitignorePath, '\n# Codex Web agents file\nAGENTS.md\n');
console.log(chalk.dim(' Added AGENTS.md to .gitignore for web deployment'));
}
}
}
/**
* Cleanup Codex configuration
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const agentsPath = path.join(projectDir, this.agentsFile);
if (await fs.pathExists(agentsPath)) {
await fs.remove(agentsPath);
console.log(chalk.dim('Removed AGENTS.md file'));
}
}
}
module.exports = { CodexSetup };