make bmad support for opencode

This commit is contained in:
Dương Bảo Duy 2025-09-10 15:48:30 +07:00
parent 2b247ea385
commit df6c9eeb7a
4 changed files with 2773 additions and 2273 deletions

1
.gitignore vendored
View File

@ -46,3 +46,4 @@ test-project-install/*
sample-project/*
flattened-codebase.xml
*.stats.md
dist/

View File

@ -1,13 +1,13 @@
#!/usr/bin/env node
const { program } = require('commander');
const path = require('node:path');
const fs = require('node:fs').promises;
const yaml = require('js-yaml');
const chalk = require('chalk').default || require('chalk');
const inquirer = require('inquirer').default || require('inquirer');
const semver = require('semver');
const https = require('node:https');
const { program } = require("commander");
const path = require("node:path");
const fs = require("node:fs").promises;
const yaml = require("js-yaml");
const chalk = require("chalk").default || require("chalk");
const inquirer = require("inquirer").default || require("inquirer");
const semver = require("semver");
const https = require("node:https");
// Handle both execution contexts (from root via npx or from installer directory)
let version;
@ -15,20 +15,22 @@ let installer;
let packageName;
try {
// Try installer context first (when run from tools/installer/)
version = require('../package.json').version;
packageName = require('../package.json').name;
installer = require('../lib/installer');
version = require("../package.json").version;
packageName = require("../package.json").name;
installer = require("../lib/installer");
} catch (error) {
// Fall back to root context (when run via npx from GitHub)
console.log(`Installer context not found (${error.message}), trying root context...`);
console.log(
`Installer context not found (${error.message}), trying root context...`,
);
try {
version = require('../../../package.json').version;
installer = require('../../../tools/installer/lib/installer');
version = require("../../../package.json").version;
installer = require("../../../tools/installer/lib/installer");
} catch (error) {
console.error(
'Error: Could not load required modules. Please ensure you are running from the correct directory.',
"Error: Could not load required modules. Please ensure you are running from the correct directory.",
);
console.error('Debug info:', {
console.error("Debug info:", {
__dirname,
cwd: process.cwd(),
error: error.message,
@ -39,21 +41,23 @@ try {
program
.version(version)
.description('BMad Method installer - Universal AI agent framework for any domain');
.description(
"BMad Method installer - Universal AI agent framework for any domain",
);
program
.command('install')
.description('Install BMad Method agents and tools')
.option('-f, --full', 'Install complete BMad Method')
.option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)')
.option('-d, --directory <path>', 'Installation directory')
.command("install")
.description("Install BMad Method agents and tools")
.option("-f, --full", "Install complete BMad Method")
.option("-x, --expansion-only", "Install only expansion packs (no bmad-core)")
.option("-d, --directory <path>", "Installation directory")
.option(
'-i, --ide <ide...>',
'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, iflow-cli, other)',
"-i, --ide <ide...>",
"Configure for specific IDE(s) - can specify multiple (opencode, cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, iflow-cli, other)",
)
.option(
'-e, --expansion-packs <packs...>',
'Install specific expansion packs (can specify multiple)',
"-e, --expansion-packs <packs...>",
"Install specific expansion packs (can specify multiple)",
)
.action(async (options) => {
try {
@ -66,59 +70,65 @@ program
}
} else {
// Direct mode
let installType = 'full';
if (options.expansionOnly) installType = 'expansion-only';
let installType = "full";
if (options.expansionOnly) installType = "expansion-only";
const config = {
installType,
directory: options.directory || '.',
ides: (options.ide || []).filter((ide) => ide !== 'other'),
directory: options.directory || ".",
ides: (options.ide || []).filter((ide) => ide !== "other"),
expansionPacks: options.expansionPacks || [],
};
await installer.install(config);
process.exit(0);
}
} catch (error) {
console.error(chalk.red('Installation failed:'), error.message);
console.error(chalk.red("Installation failed:"), error.message);
process.exit(1);
}
});
program
.command('update')
.description('Update existing BMad installation')
.option('--force', 'Force update, overwriting modified files')
.option('--dry-run', 'Show what would be updated without making changes')
.command("update")
.description("Update existing BMad installation")
.option("--force", "Force update, overwriting modified files")
.option("--dry-run", "Show what would be updated without making changes")
.action(async () => {
try {
await installer.update();
} catch (error) {
console.error(chalk.red('Update failed:'), error.message);
console.error(chalk.red("Update failed:"), error.message);
process.exit(1);
}
});
// Command to check if updates are available
program
.command('update-check')
.description('Check for BMad Update')
.command("update-check")
.description("Check for BMad Update")
.action(async () => {
console.log('Checking for updates...');
console.log("Checking for updates...");
// Make HTTP request to npm registry for latest version info
const req = https.get(`https://registry.npmjs.org/${packageName}/latest`, (res) => {
const req = https.get(
`https://registry.npmjs.org/${packageName}/latest`,
(res) => {
// Check for HTTP errors (non-200 status codes)
if (res.statusCode !== 200) {
console.error(chalk.red(`Update check failed: Received status code ${res.statusCode}`));
console.error(
chalk.red(
`Update check failed: Received status code ${res.statusCode}`,
),
);
return;
}
// Accumulate response data chunks
let data = '';
res.on('data', (chunk) => (data += chunk));
let data = "";
res.on("data", (chunk) => (data += chunk));
// Process complete response
res.on('end', () => {
res.on("end", () => {
try {
// Parse npm registry response and extract version
const latest = JSON.parse(data).version;
@ -126,68 +136,76 @@ program
// Compare versions using semver
if (semver.gt(latest, version)) {
console.log(
chalk.bold.blue(`⚠️ ${packageName} update available: ${version}${latest}`),
chalk.bold.blue(
`! ${packageName} update available: ${version}${latest}`,
),
);
console.log(chalk.bold.blue('\nInstall latest by running:'));
console.log(chalk.bold.magenta(` npm install ${packageName}@latest`));
console.log(chalk.dim(' or'));
console.log(chalk.bold.blue("\nInstall latest by running:"));
console.log(
chalk.bold.magenta(` npm install ${packageName}@latest`),
);
console.log(chalk.dim(" or"));
console.log(chalk.bold.magenta(` npx ${packageName}@latest`));
} else {
console.log(chalk.bold.blue(`${packageName} is up to date`));
}
} catch (error) {
// Handle JSON parsing errors
console.error(chalk.red('Failed to parse npm registry data:'), error.message);
console.error(
chalk.red("Failed to parse npm registry data:"),
error.message,
);
}
});
});
},
);
// Handle network/connection errors
req.on('error', (error) => {
console.error(chalk.red('Update check failed:'), error.message);
req.on("error", (error) => {
console.error(chalk.red("Update check failed:"), error.message);
});
// Set 30 second timeout to prevent hanging
req.setTimeout(30_000, () => {
req.destroy();
console.error(chalk.red('Update check timed out'));
console.error(chalk.red("Update check timed out"));
});
});
program
.command('list:expansions')
.description('List available expansion packs')
.command("list:expansions")
.description("List available expansion packs")
.action(async () => {
try {
await installer.listExpansionPacks();
} catch (error) {
console.error(chalk.red('Error:'), error.message);
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
program
.command('status')
.description('Show installation status')
.command("status")
.description("Show installation status")
.action(async () => {
try {
await installer.showStatus();
} catch (error) {
console.error(chalk.red('Error:'), error.message);
console.error(chalk.red("Error:"), error.message);
process.exit(1);
}
});
program
.command('flatten')
.description('Flatten codebase to XML format')
.option('-i, --input <path>', 'Input directory to flatten', process.cwd())
.option('-o, --output <path>', 'Output file path', 'flattened-codebase.xml')
.command("flatten")
.description("Flatten codebase to XML format")
.option("-i, --input <path>", "Input directory to flatten", process.cwd())
.option("-o, --output <path>", "Output file path", "flattened-codebase.xml")
.action(async (options) => {
try {
await installer.flatten(options);
} catch (error) {
console.error(chalk.red('Flatten failed:'), error.message);
console.error(chalk.red("Flatten failed:"), error.message);
process.exit(1);
}
});
@ -205,7 +223,9 @@ async function promptInstallation() {
`),
);
console.log(chalk.bold.magenta('🚀 Universal AI Agent Framework for Any Domain'));
console.log(
chalk.bold.magenta("🚀 Universal AI Agent Framework for Any Domain"),
);
console.log(chalk.bold.blue(`✨ Installer v${version}\n`));
const answers = {};
@ -213,13 +233,14 @@ async function promptInstallation() {
// Ask for installation directory first
const { directory } = await inquirer.prompt([
{
type: 'input',
name: 'directory',
message: 'Enter the full path to your project directory where BMad should be installed:',
default: path.resolve('.'),
type: "input",
name: "directory",
message:
"Enter the full path to your project directory where BMad should be installed:",
default: path.resolve("."),
validate: (input) => {
if (!input.trim()) {
return 'Please enter a valid project path';
return "Please enter a valid project path";
}
return true;
},
@ -241,14 +262,21 @@ async function promptInstallation() {
const choices = [];
// Load core config to get short-title
const coreConfigPath = path.join(__dirname, '..', '..', '..', 'bmad-core', 'core-config.yaml');
const coreConfig = yaml.load(await fs.readFile(coreConfigPath, 'utf8'));
const coreShortTitle = coreConfig['short-title'] || 'BMad Agile Core System';
const coreConfigPath = path.join(
__dirname,
"..",
"..",
"..",
"bmad-core",
"core-config.yaml",
);
const coreConfig = yaml.load(await fs.readFile(coreConfigPath, "utf8"));
const coreShortTitle = coreConfig["short-title"] || "BMad Agile Core System";
// Add BMad core option
let bmadOptionText;
if (state.type === 'v4_existing') {
const currentVersion = state.manifest?.version || 'unknown';
if (state.type === "v4_existing") {
const currentVersion = state.manifest?.version || "unknown";
const newVersion = version; // Always use package.json version
const versionInfo =
currentVersion === newVersion
@ -261,7 +289,7 @@ async function promptInstallation() {
choices.push({
name: bmadOptionText,
value: 'bmad-core',
value: "bmad-core",
checked: true,
});
@ -271,7 +299,7 @@ async function promptInstallation() {
let packOptionText;
if (existing) {
const currentVersion = existing.manifest?.version || 'unknown';
const currentVersion = existing.manifest?.version || "unknown";
const newVersion = pack.version;
const versionInfo =
currentVersion === newVersion
@ -292,13 +320,14 @@ async function promptInstallation() {
// Ask what to install
const { selectedItems } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedItems',
message: 'Select what to install/update (use space to select, enter to continue):',
type: "checkbox",
name: "selectedItems",
message:
"Select what to install/update (use space to select, enter to continue):",
choices: choices,
validate: (selected) => {
if (selected.length === 0) {
return 'Please select at least one item to install';
return "Please select at least one item to install";
}
return true;
},
@ -306,20 +335,27 @@ async function promptInstallation() {
]);
// Process selections
answers.installType = selectedItems.includes('bmad-core') ? 'full' : 'expansion-only';
answers.expansionPacks = selectedItems.filter((item) => item !== 'bmad-core');
answers.installType = selectedItems.includes("bmad-core")
? "full"
: "expansion-only";
answers.expansionPacks = selectedItems.filter((item) => item !== "bmad-core");
// Ask sharding questions if installing BMad core
if (selectedItems.includes('bmad-core')) {
console.log(chalk.cyan('\n📋 Document Organization Settings'));
console.log(chalk.dim('Configure how your project documentation should be organized.\n'));
if (selectedItems.includes("bmad-core")) {
console.log(chalk.cyan("\n📋 Document Organization Settings"));
console.log(
chalk.dim(
"Configure how your project documentation should be organized.\n",
),
);
// Ask about PRD sharding
const { prdSharded } = await inquirer.prompt([
{
type: 'confirm',
name: 'prdSharded',
message: 'Will the PRD (Product Requirements Document) be sharded into multiple files?',
type: "confirm",
name: "prdSharded",
message:
"Will the PRD (Product Requirements Document) be sharded into multiple files?",
default: true,
},
]);
@ -328,9 +364,10 @@ async function promptInstallation() {
// Ask about architecture sharding
const { architectureSharded } = await inquirer.prompt([
{
type: 'confirm',
name: 'architectureSharded',
message: 'Will the architecture documentation be sharded into multiple files?',
type: "confirm",
name: "architectureSharded",
message:
"Will the architecture documentation be sharded into multiple files?",
default: true,
},
]);
@ -338,36 +375,40 @@ async function promptInstallation() {
// Show warning if architecture sharding is disabled
if (!architectureSharded) {
console.log(chalk.yellow.bold('\n⚠ IMPORTANT: Architecture Sharding Disabled'));
console.log(
chalk.yellow.bold("\n! IMPORTANT: Architecture Sharding Disabled"),
);
console.log(
chalk.yellow(
'With architecture sharding disabled, you should still create the files listed',
"With architecture sharding disabled, you should still create the files listed",
),
);
console.log(
chalk.yellow(
'in devLoadAlwaysFiles (like coding-standards.md, tech-stack.md, source-tree.md)',
"in devLoadAlwaysFiles (like coding-standards.md, tech-stack.md, source-tree.md)",
),
);
console.log(chalk.yellow('as these are used by the dev agent at runtime.'));
console.log(
chalk.yellow("as these are used by the dev agent at runtime."),
);
console.log(
chalk.yellow(
'\nAlternatively, you can remove these files from the devLoadAlwaysFiles list',
"\nAlternatively, you can remove these files from the devLoadAlwaysFiles list",
),
);
console.log(chalk.yellow('in your core-config.yaml after installation.'));
console.log(chalk.yellow("in your core-config.yaml after installation."));
const { acknowledge } = await inquirer.prompt([
{
type: 'confirm',
name: 'acknowledge',
message: 'Do you acknowledge this requirement and want to proceed?',
type: "confirm",
name: "acknowledge",
message: "Do you acknowledge this requirement and want to proceed?",
default: false,
},
]);
if (!acknowledge) {
console.log(chalk.red('Installation cancelled.'));
console.log(chalk.red("Installation cancelled."));
process.exit(0);
}
}
@ -378,38 +419,39 @@ async function promptInstallation() {
let ideSelectionComplete = false;
while (!ideSelectionComplete) {
console.log(chalk.cyan('\n🛠 IDE Configuration'));
console.log(chalk.cyan("\n🛠 IDE Configuration"));
console.log(
chalk.bold.yellow.bgRed(
' ⚠️ IMPORTANT: This is a MULTISELECT! Use SPACEBAR to toggle each IDE! ',
" ! IMPORTANT: This is a MULTISELECT! Use SPACEBAR to toggle each IDE! ",
),
);
console.log(chalk.bold.magenta('🔸 Use arrow keys to navigate'));
console.log(chalk.bold.magenta('🔸 Use SPACEBAR to select/deselect IDEs'));
console.log(chalk.bold.magenta('🔸 Press ENTER when finished selecting\n'));
console.log(chalk.bold.magenta("🔸 Use arrow keys to navigate"));
console.log(chalk.bold.magenta("🔸 Use SPACEBAR to select/deselect IDEs"));
console.log(chalk.bold.magenta("🔸 Press ENTER when finished selecting\n"));
const ideResponse = await inquirer.prompt([
{
type: 'checkbox',
name: 'ides',
type: "checkbox",
name: "ides",
message:
'Which IDE(s) do you want to configure? (Select with SPACEBAR, confirm with ENTER):',
"Which IDE(s) do you want to configure? (Select with SPACEBAR, confirm with ENTER):",
choices: [
{ name: 'Cursor', value: 'cursor' },
{ name: 'Claude Code', value: 'claude-code' },
{ name: 'iFlow CLI', value: 'iflow-cli' },
{ name: 'Windsurf', value: 'windsurf' },
{ name: 'Trae', value: 'trae' }, // { name: 'Trae', value: 'trae'}
{ name: 'Roo Code', value: 'roo' },
{ name: 'Kilo Code', value: 'kilo' },
{ name: 'Cline', value: 'cline' },
{ name: 'Gemini CLI', value: 'gemini' },
{ name: 'Qwen Code', value: 'qwen-code' },
{ name: 'Crush', value: 'crush' },
{ name: 'Github Copilot', value: 'github-copilot' },
{ name: 'Auggie CLI (Augment Code)', value: 'auggie-cli' },
{ name: 'Codex CLI', value: 'codex' },
{ name: 'Codex Web', value: 'codex-web' },
{ name: "Open Code", value: "opencode" },
{ name: "Cursor", value: "cursor" },
{ name: "Claude Code", value: "claude-code" },
{ name: "iFlow CLI", value: "iflow-cli" },
{ name: "Windsurf", value: "windsurf" },
{ name: "Trae", value: "trae" }, // { name: 'Trae', value: 'trae'}
{ name: "Roo Code", value: "roo" },
{ name: "Kilo Code", value: "kilo" },
{ name: "Cline", value: "cline" },
{ name: "Gemini CLI", value: "gemini" },
{ name: "Qwen Code", value: "qwen-code" },
{ name: "Crush", value: "crush" },
{ name: "Github Copilot", value: "github-copilot" },
{ name: "Auggie CLI (Augment Code)", value: "auggie-cli" },
{ name: "Codex CLI", value: "codex" },
{ name: "Codex Web", value: "codex-web" },
],
},
]);
@ -420,10 +462,10 @@ async function promptInstallation() {
if (ides.length === 0) {
const { confirmNoIde } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmNoIde',
type: "confirm",
name: "confirmNoIde",
message: chalk.red(
'⚠️ You have NOT selected any IDEs. This means NO IDE integration will be set up. Is this correct?',
"! You have NOT selected any IDEs. This means NO IDE integration will be set up. Is this correct?",
),
default: false,
},
@ -432,7 +474,7 @@ async function promptInstallation() {
if (!confirmNoIde) {
console.log(
chalk.bold.red(
'\n🔄 Returning to IDE selection. Remember to use SPACEBAR to select IDEs!\n',
"\n🔄 Returning to IDE selection. Remember to use SPACEBAR to select IDEs!\n",
),
);
continue; // Go back to IDE selection only
@ -446,32 +488,36 @@ async function promptInstallation() {
answers.ides = ides;
// Configure GitHub Copilot immediately if selected
if (ides.includes('github-copilot')) {
console.log(chalk.cyan('\n🔧 GitHub Copilot Configuration'));
if (ides.includes("github-copilot")) {
console.log(chalk.cyan("\n🔧 GitHub Copilot Configuration"));
console.log(
chalk.dim('BMad works best with specific VS Code settings for optimal agent experience.\n'),
chalk.dim(
"BMad works best with specific VS Code settings for optimal agent experience.\n",
),
);
const { configChoice } = await inquirer.prompt([
{
type: 'list',
name: 'configChoice',
message: chalk.yellow('How would you like to configure GitHub Copilot settings?'),
type: "list",
name: "configChoice",
message: chalk.yellow(
"How would you like to configure GitHub Copilot settings?",
),
choices: [
{
name: 'Use recommended defaults (fastest setup)',
value: 'defaults',
name: "Use recommended defaults (fastest setup)",
value: "defaults",
},
{
name: 'Configure each setting manually (customize to your preferences)',
value: 'manual',
name: "Configure each setting manually (customize to your preferences)",
value: "manual",
},
{
name: "Skip settings configuration (I'll configure manually later)",
value: 'skip',
value: "skip",
},
],
default: 'defaults',
default: "defaults",
},
]);
@ -479,28 +525,30 @@ async function promptInstallation() {
}
// Configure Auggie CLI (Augment Code) immediately if selected
if (ides.includes('auggie-cli')) {
console.log(chalk.cyan('\n📍 Auggie CLI Location Configuration'));
console.log(chalk.dim('Choose where to install BMad agents for Auggie CLI access.\n'));
if (ides.includes("auggie-cli")) {
console.log(chalk.cyan("\n📍 Auggie CLI Location Configuration"));
console.log(
chalk.dim("Choose where to install BMad agents for Auggie CLI access.\n"),
);
const { selectedLocations } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedLocations',
message: 'Select Auggie CLI command locations:',
type: "checkbox",
name: "selectedLocations",
message: "Select Auggie CLI command locations:",
choices: [
{
name: 'User Commands (Global): Available across all your projects (user-wide)',
value: 'user',
name: "User Commands (Global): Available across all your projects (user-wide)",
value: "user",
},
{
name: 'Workspace Commands (Project): Stored in repository, shared with team',
value: 'workspace',
name: "Workspace Commands (Project): Stored in repository, shared with team",
value: "workspace",
},
],
validate: (selected) => {
if (selected.length === 0) {
return 'Please select at least one location';
return "Please select at least one location";
}
return true;
},
@ -513,41 +561,47 @@ async function promptInstallation() {
// Ask for web bundles installation
const { includeWebBundles } = await inquirer.prompt([
{
type: 'confirm',
name: 'includeWebBundles',
type: "confirm",
name: "includeWebBundles",
message:
'Would you like to include pre-built web bundles? (standalone files for ChatGPT, Claude, Gemini)',
"Would you like to include pre-built web bundles? (standalone files for ChatGPT, Claude, Gemini)",
default: false,
},
]);
if (includeWebBundles) {
console.log(chalk.cyan('\n📦 Web bundles are standalone files perfect for web AI platforms.'));
console.log(
chalk.dim(' You can choose different teams/agents than your IDE installation.\n'),
chalk.cyan(
"\n📦 Web bundles are standalone files perfect for web AI platforms.",
),
);
console.log(
chalk.dim(
" You can choose different teams/agents than your IDE installation.\n",
),
);
const { webBundleType } = await inquirer.prompt([
{
type: 'list',
name: 'webBundleType',
message: 'What web bundles would you like to include?',
type: "list",
name: "webBundleType",
message: "What web bundles would you like to include?",
choices: [
{
name: 'All available bundles (agents, teams, expansion packs)',
value: 'all',
name: "All available bundles (agents, teams, expansion packs)",
value: "all",
},
{
name: 'Specific teams only',
value: 'teams',
name: "Specific teams only",
value: "teams",
},
{
name: 'Individual agents only',
value: 'agents',
name: "Individual agents only",
value: "agents",
},
{
name: 'Custom selection',
value: 'custom',
name: "Custom selection",
value: "custom",
},
],
},
@ -556,21 +610,21 @@ async function promptInstallation() {
answers.webBundleType = webBundleType;
// If specific teams, let them choose which teams
if (webBundleType === 'teams' || webBundleType === 'custom') {
if (webBundleType === "teams" || webBundleType === "custom") {
const teams = await installer.getAvailableTeams();
const { selectedTeams } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedTeams',
message: 'Select team bundles to include:',
type: "checkbox",
name: "selectedTeams",
message: "Select team bundles to include:",
choices: teams.map((t) => ({
name: `${t.icon || '📋'} ${t.name}: ${t.description}`,
name: `${t.icon || "📋"} ${t.name}: ${t.description}`,
value: t.id,
checked: webBundleType === 'teams', // Check all if teams-only mode
checked: webBundleType === "teams", // Check all if teams-only mode
})),
validate: (answer) => {
if (answer.length === 0) {
return 'You must select at least one team.';
return "You must select at least one team.";
}
return true;
},
@ -580,12 +634,12 @@ async function promptInstallation() {
}
// If custom selection, also ask about individual agents
if (webBundleType === 'custom') {
if (webBundleType === "custom") {
const { includeIndividualAgents } = await inquirer.prompt([
{
type: 'confirm',
name: 'includeIndividualAgents',
message: 'Also include individual agent bundles?',
type: "confirm",
name: "includeIndividualAgents",
message: "Also include individual agent bundles?",
default: true,
},
]);
@ -594,13 +648,13 @@ async function promptInstallation() {
const { webBundlesDirectory } = await inquirer.prompt([
{
type: 'input',
name: 'webBundlesDirectory',
message: 'Enter directory for web bundles:',
type: "input",
name: "webBundlesDirectory",
message: "Enter directory for web bundles:",
default: `${answers.directory}/web-bundles`,
validate: (input) => {
if (!input.trim()) {
return 'Please enter a valid directory path';
return "Please enter a valid directory path";
}
return true;
},

View File

@ -85,6 +85,17 @@ ide-configurations:
# 2. Type @agent-name (e.g., "@dev", "@pm", "@architect")
# 3. The agent will adopt that persona for the conversation
# 4. Rules are stored in .clinerules/ directory in your project
opencode:
name: Opencode CLI
rule-dir: .opencode/
format: multi-file
command-suffix: .md
instructions: |
# To use BMad agents with the Opencode CLI:
# 1. The installer creates a `agent` and `command` folder in `.opencode/`.
# 2. This adds custom commands for each agent and task.
# 3. Type /BMad:agents:<agent-name> (e.g., "/BMad:agents:dev", "/BMad:agents:pm") or /BMad:tasks:<task-name> (e.g., "/BMad:tasks:create-doc").
# 4. The agent will adopt that persona for the conversation or preform the task.
gemini:
name: Gemini CLI
rule-dir: .gemini/commands/BMad/

File diff suppressed because it is too large Load Diff