feat(installer): add Codex CLI + Codex Web modes, generate AGENTS.md, inject npm scripts, and docs (#529)
This commit is contained in:
committed by
GitHub
parent
052e84dd4a
commit
05736fa069
@@ -74,6 +74,12 @@ class IdeSetup extends BaseIdeSetup {
|
||||
case 'qwen-code': {
|
||||
return this.setupQwenCode(installDir, selectedAgent);
|
||||
}
|
||||
case 'codex': {
|
||||
return this.setupCodex(installDir, selectedAgent, { webEnabled: false });
|
||||
}
|
||||
case 'codex-web': {
|
||||
return this.setupCodex(installDir, selectedAgent, { webEnabled: true });
|
||||
}
|
||||
default: {
|
||||
console.log(chalk.yellow(`\nIDE ${ide} not yet supported`));
|
||||
return false;
|
||||
@@ -81,6 +87,175 @@ class IdeSetup extends BaseIdeSetup {
|
||||
}
|
||||
}
|
||||
|
||||
async setupCodex(installDir, selectedAgent, options) {
|
||||
options = options ?? { webEnabled: false };
|
||||
// Codex reads AGENTS.md at the project root as project memory (CLI & Web).
|
||||
// Inject/update a BMAD section with guidance, directory, and details.
|
||||
const filePath = path.join(installDir, 'AGENTS.md');
|
||||
const startMarker = '<!-- BEGIN: BMAD-AGENTS -->';
|
||||
const endMarker = '<!-- END: BMAD-AGENTS -->';
|
||||
|
||||
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||
const tasks = await this.getAllTaskIds(installDir);
|
||||
|
||||
// Build BMAD section content
|
||||
let section = '';
|
||||
section += `${startMarker}\n`;
|
||||
section += `# BMAD-METHOD Agents and Tasks\n\n`;
|
||||
section += `This section is auto-generated by BMAD-METHOD for Codex. Codex merges this AGENTS.md into context.\n\n`;
|
||||
section += `## How To Use With Codex\n\n`;
|
||||
section += `- Codex CLI: run \`codex\` in this project. Reference an agent naturally, e.g., "As dev, implement ...".\n`;
|
||||
section += `- Codex Web: open this repo and reference roles the same way; Codex reads \`AGENTS.md\`.\n`;
|
||||
section += `- Commit \`.bmad-core\` and this \`AGENTS.md\` file to your repo so Codex (Web/CLI) can read full agent definitions.\n`;
|
||||
section += `- Refresh this section after agent updates: \`npx bmad-method install -f -i codex\`.\n\n`;
|
||||
|
||||
section += `### Helpful Commands\n\n`;
|
||||
section += `- List agents: \`npx bmad-method list:agents\`\n`;
|
||||
section += `- Reinstall BMAD core and regenerate AGENTS.md: \`npx bmad-method install -f -i codex\`\n`;
|
||||
section += `- Validate configuration: \`npx bmad-method validate\`\n\n`;
|
||||
|
||||
// Agents directory table
|
||||
section += `## Agents\n\n`;
|
||||
section += `### Directory\n\n`;
|
||||
section += `| Title | ID | When To Use |\n|---|---|---|\n`;
|
||||
const agentSummaries = [];
|
||||
for (const agentId of agents) {
|
||||
const agentPath = await this.findAgentPath(agentId, installDir);
|
||||
if (!agentPath) continue;
|
||||
const raw = await fileManager.readFile(agentPath);
|
||||
const yamlMatch = raw.match(/```ya?ml\r?\n([\s\S]*?)```/);
|
||||
const yamlBlock = yamlMatch ? yamlMatch[1].trim() : null;
|
||||
const title = await this.getAgentTitle(agentId, installDir);
|
||||
const whenToUse = yamlBlock?.match(/whenToUse:\s*"?([^\n"]+)"?/i)?.[1]?.trim() || '';
|
||||
agentSummaries.push({ agentId, title, whenToUse, yamlBlock, raw, path: agentPath });
|
||||
section += `| ${title} | ${agentId} | ${whenToUse || '—'} |\n`;
|
||||
}
|
||||
section += `\n`;
|
||||
|
||||
// Detailed agent sections
|
||||
for (const { agentId, title, whenToUse, yamlBlock, raw, path: agentPath } of agentSummaries) {
|
||||
const relativePath = path.relative(installDir, agentPath).replaceAll('\\', '/');
|
||||
section += `### ${title} (id: ${agentId})\n`;
|
||||
section += `Source: ${relativePath}\n\n`;
|
||||
if (whenToUse) section += `- When to use: ${whenToUse}\n`;
|
||||
section += `- How to activate: Mention "As ${agentId}, ..." or "Use ${title} to ..."\n\n`;
|
||||
if (yamlBlock) {
|
||||
section += '```yaml\n' + yamlBlock + '\n```\n\n';
|
||||
} else {
|
||||
section += '```md\n' + raw.trim() + '\n```\n\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Tasks
|
||||
if (tasks && tasks.length > 0) {
|
||||
section += `## Tasks\n\n`;
|
||||
section += `These are reusable task briefs you can reference directly in Codex.\n\n`;
|
||||
for (const taskId of tasks) {
|
||||
const taskPath = await this.findTaskPath(taskId, installDir);
|
||||
if (!taskPath) continue;
|
||||
const raw = await fileManager.readFile(taskPath);
|
||||
const relativePath = path.relative(installDir, taskPath).replaceAll('\\', '/');
|
||||
section += `### Task: ${taskId}\n`;
|
||||
section += `Source: ${relativePath}\n`;
|
||||
section += `- How to use: "Use task ${taskId} with the appropriate agent" and paste relevant parts as needed.\n\n`;
|
||||
section += '```md\n' + raw.trim() + '\n```\n\n';
|
||||
}
|
||||
}
|
||||
|
||||
section += `${endMarker}\n`;
|
||||
|
||||
// Write or update AGENTS.md
|
||||
let finalContent = '';
|
||||
if (await fileManager.pathExists(filePath)) {
|
||||
const existing = await fileManager.readFile(filePath);
|
||||
if (existing.includes(startMarker) && existing.includes(endMarker)) {
|
||||
// Replace existing BMAD block
|
||||
const pattern = String.raw`${startMarker}[\s\S]*?${endMarker}`;
|
||||
const replaced = existing.replace(new RegExp(pattern, 'm'), section);
|
||||
finalContent = replaced;
|
||||
} else {
|
||||
// Append BMAD block to existing file
|
||||
finalContent = existing.trimEnd() + `\n\n` + section;
|
||||
}
|
||||
} else {
|
||||
// Create fresh AGENTS.md with a small header and BMAD block
|
||||
finalContent += '# Project Agents\n\n';
|
||||
finalContent += 'This file provides guidance and memory for Codex CLI.\n\n';
|
||||
finalContent += section;
|
||||
}
|
||||
|
||||
await fileManager.writeFile(filePath, finalContent);
|
||||
console.log(chalk.green('✓ Created/updated AGENTS.md for Codex CLI integration'));
|
||||
console.log(
|
||||
chalk.dim(
|
||||
'Codex reads AGENTS.md automatically. Run `codex` in this project to use BMAD agents.',
|
||||
),
|
||||
);
|
||||
|
||||
// Optionally add helpful npm scripts if a package.json exists
|
||||
try {
|
||||
const pkgPath = path.join(installDir, 'package.json');
|
||||
if (await fileManager.pathExists(pkgPath)) {
|
||||
const pkgRaw = await fileManager.readFile(pkgPath);
|
||||
const pkg = JSON.parse(pkgRaw);
|
||||
pkg.scripts = pkg.scripts || {};
|
||||
const updated = { ...pkg.scripts };
|
||||
if (!updated['bmad:refresh']) updated['bmad:refresh'] = 'bmad-method install -f -i codex';
|
||||
if (!updated['bmad:list']) updated['bmad:list'] = 'bmad-method list:agents';
|
||||
if (!updated['bmad:validate']) updated['bmad:validate'] = 'bmad-method validate';
|
||||
const changed = JSON.stringify(updated) !== JSON.stringify(pkg.scripts);
|
||||
if (changed) {
|
||||
const newPkg = { ...pkg, scripts: updated };
|
||||
await fileManager.writeFile(pkgPath, JSON.stringify(newPkg, null, 2) + '\n');
|
||||
console.log(chalk.green('✓ Added npm scripts: bmad:refresh, bmad:list, bmad:validate'));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.log(
|
||||
chalk.yellow('⚠︎ Skipped adding npm scripts (package.json not writable or invalid)'),
|
||||
);
|
||||
}
|
||||
|
||||
// Adjust .gitignore behavior depending on Codex mode
|
||||
try {
|
||||
const gitignorePath = path.join(installDir, '.gitignore');
|
||||
const ignoreLines = ['# BMAD (local only)', '.bmad-core/', '.bmad-*/'];
|
||||
const exists = await fileManager.pathExists(gitignorePath);
|
||||
if (options.webEnabled) {
|
||||
if (exists) {
|
||||
let gi = await fileManager.readFile(gitignorePath);
|
||||
// Remove lines that ignore BMAD dot-folders
|
||||
const updated = gi
|
||||
.split(/\r?\n/)
|
||||
.filter((l) => !/^\s*\.bmad-core\/?\s*$/.test(l) && !/^\s*\.bmad-\*\/?\s*$/.test(l))
|
||||
.join('\n');
|
||||
if (updated !== gi) {
|
||||
await fileManager.writeFile(gitignorePath, updated.trimEnd() + '\n');
|
||||
console.log(chalk.green('✓ Updated .gitignore to include .bmad-core in commits'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Local-only: add ignores if missing
|
||||
let base = exists ? await fileManager.readFile(gitignorePath) : '';
|
||||
const haveCore = base.includes('.bmad-core/');
|
||||
const haveStar = base.includes('.bmad-*/');
|
||||
if (!haveCore || !haveStar) {
|
||||
const sep = base.endsWith('\n') || base.length === 0 ? '' : '\n';
|
||||
const add = [!haveCore || !haveStar ? ignoreLines.join('\n') : '']
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
const out = base + sep + add + '\n';
|
||||
await fileManager.writeFile(gitignorePath, out);
|
||||
console.log(chalk.green('✓ Added .bmad-core/* to .gitignore for local-only Codex setup'));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.log(chalk.yellow('⚠︎ Could not update .gitignore (skipping)'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setupCursor(installDir, selectedAgent) {
|
||||
const cursorRulesDir = path.join(installDir, '.cursor', 'rules', 'bmad');
|
||||
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||
|
||||
Reference in New Issue
Block a user