From 8220c819e6d359e7f4e10f80b575afb4f0c3183e Mon Sep 17 00:00:00 2001 From: Cameron Pitt Date: Sun, 26 Oct 2025 09:16:57 -0700 Subject: [PATCH] Add Opencode IDE installer (#820) - Added docs/ide-info/opencode.md - Added tool/cli/installers/lib/ide/opencode.js - Modified tools/installers/lib/ide/core/detector.js to include detection for opencode command dir - Modified tools/cli/platform-codes.yaml to include opencode config - Modified tools/cli/installers/lib/ide/workflow-command-template.md to include frontmatter with description as opencode requires this for commands and adding it to the template by default does not seem to impact other IDEs - Modified src/modules/bmm/workflows/workflow-status/workflow.yaml description so that it properly escapes quotes when interpolated in the teplate --- docs/ide-info/opencode.md | 24 ++++ .../workflows/workflow-status/workflow.yaml | 2 +- tools/cli/README.md | 3 +- tools/cli/installers/lib/core/detector.js | 5 +- tools/cli/installers/lib/ide/opencode.js | 134 ++++++++++++++++++ .../lib/ide/workflow-command-template.md | 4 + tools/platform-codes.yaml | 6 + 7 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 docs/ide-info/opencode.md create mode 100644 tools/cli/installers/lib/ide/opencode.js diff --git a/docs/ide-info/opencode.md b/docs/ide-info/opencode.md new file mode 100644 index 00000000..eb9b6912 --- /dev/null +++ b/docs/ide-info/opencode.md @@ -0,0 +1,24 @@ +# BMAD Method - OpenCode Instructions + +## Activating Agents + +BMAD agents are installed as OpenCode agents in `.opencode/agent/BMAD/{module_name}` and workflow commands in `.opencode/command/BMAD/{module_name}`. + +### How to Use + +1. **Switch Agents**: Press **Tab** to cycle through primary agents or select using the `/agents` +2. **Activate Agent**: Once the Agent is selected say `hello` or any prompt to activate that agent persona +3. **Execute Commands**: Type `/bmad` to see and execute bmad workflow commands (commands allow for fuzzy matching) + +### Examples + +``` +/agents - to see a list of agents and switch between them +/bmad/bmm/workflows/workflow-init - Activate the workflow-init command +``` + +### Notes + +- Press **Tab** to switch between primary agents (Analyst, Architect, Dev, etc.) +- Commands are autocompleted when you type `/` and allow for fuzzy matching +- Workflow commands execute in current agent context, make sure you have the right agent activated before running a command diff --git a/src/modules/bmm/workflows/workflow-status/workflow.yaml b/src/modules/bmm/workflows/workflow-status/workflow.yaml index c8098e4a..ce630879 100644 --- a/src/modules/bmm/workflows/workflow-status/workflow.yaml +++ b/src/modules/bmm/workflows/workflow-status/workflow.yaml @@ -1,6 +1,6 @@ # Workflow Status - Master Router and Status Tracker name: workflow-status -description: "Lightweight status checker - answers 'what should I do now?' for any agent. Reads simple key-value status file for instant parsing. Use workflow-init for new projects." +description: 'Lightweight status checker - answers "what should I do now?" for any agent. Reads simple key-value status file for instant parsing. Use workflow-init for new projects.' author: "BMad" # Critical variables from config diff --git a/tools/cli/README.md b/tools/cli/README.md index b0ce430d..fd66209c 100644 --- a/tools/cli/README.md +++ b/tools/cli/README.md @@ -126,7 +126,7 @@ The installer is a multi-stage system that handles agent compilation, IDE integr ### IDE Support -The installer supports **14 IDE environments** through a base-derived architecture. Each IDE handler extends `BaseIDE` and implements IDE-specific artifact generation. +The installer supports **15 IDE environments** through a base-derived architecture. Each IDE handler extends `BaseIDE` and implements IDE-specific artifact generation. **Supported IDEs** (as of v6-alpha): @@ -134,6 +134,7 @@ The installer supports **14 IDE environments** through a base-derived architectu | ---------------- | ----------------- | ---------------------- | | `codex` | Claude Code | `.claude/commands/` | | `claude-code` | Claude Code (alt) | `.claude/commands/` | +| `opencode` | OpenCode | `.opencode` | | `windsurf` | Windsurf | `.windsurf/workflows/` | | `cursor` | Cursor | `.cursor/rules/` | | `cline` | Cline | `.clinerules/` | diff --git a/tools/cli/installers/lib/core/detector.js b/tools/cli/installers/lib/core/detector.js index c94b81bd..d3e090af 100644 --- a/tools/cli/installers/lib/core/detector.js +++ b/tools/cli/installers/lib/core/detector.js @@ -211,10 +211,11 @@ class Detector { // Check inside various IDE command folders for legacy bmad folders // List of IDE config folders that might have commands directories - const ideConfigFolders = ['.claude', '.crush', '.continue', '.cursor', '.windsurf', '.cline', '.roo-cline']; + const ideConfigFolders = ['.opencode', '.claude', '.crush', '.continue', '.cursor', '.windsurf', '.cline', '.roo-cline']; for (const ideFolder of ideConfigFolders) { - const commandsPath = path.join(projectDir, ideFolder, 'commands'); + const commandsDirName = ideFolder === '.opencode' ? 'command' : 'commands'; + const commandsPath = path.join(projectDir, ideFolder, commandsDirName); if (await fs.pathExists(commandsPath)) { try { const commandEntries = await fs.readdir(commandsPath, { withFileTypes: true }); diff --git a/tools/cli/installers/lib/ide/opencode.js b/tools/cli/installers/lib/ide/opencode.js new file mode 100644 index 00000000..1e4d49ac --- /dev/null +++ b/tools/cli/installers/lib/ide/opencode.js @@ -0,0 +1,134 @@ +const path = require('node:path'); +const fs = require('fs-extra'); +const os = require('node:os'); +const chalk = require('chalk'); +const yaml = require('js-yaml'); +const { BaseIdeSetup } = require('./_base-ide'); +const { WorkflowCommandGenerator } = require('./workflow-command-generator'); + +const { getAgentsFromBmad } = require('./shared/bmad-artifacts'); + +/** + * OpenCode IDE setup handler + */ +class OpenCodeSetup extends BaseIdeSetup { + constructor() { + super('opencode', 'OpenCode', false); + this.configDir = '.opencode'; + this.commandsDir = 'command'; + this.agentsDir = 'agent'; + } + + async setup(projectDir, bmadDir, options = {}) { + console.log(chalk.cyan(`Setting up ${this.name}...`)); + + const baseDir = path.join(projectDir, this.configDir); + const agentsBaseDir = path.join(baseDir, this.agentsDir, 'bmad'); + const commandsBaseDir = path.join(baseDir, this.commandsDir, 'bmad'); + + await this.ensureDir(agentsBaseDir); + await this.ensureDir(commandsBaseDir); + + // Install primary agents + const agents = await getAgentsFromBmad(bmadDir, options.selectedModules || []); + const modules = new Set(agents.map((agent) => agent.module)); + + for (const module of modules) { + await this.ensureDir(path.join(agentsBaseDir, module)); + } + + let agentCount = 0; + for (const agent of agents) { + const processed = await this.readAndProcess(agent.path, { + module: agent.module, + name: agent.name, + }); + + const agentContent = this.createAgentContent(processed, agent); + const targetPath = path.join(agentsBaseDir, agent.module, `${agent.name}.md`); + await this.writeFile(targetPath, agentContent); + agentCount++; + } + + // Install workflow commands + const workflowGenerator = new WorkflowCommandGenerator(); + const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir); + + let workflowCommandCount = 0; + for (const artifact of workflowArtifacts) { + // Workflow artifacts already have proper frontmatter from the template + // No need to wrap with additional frontmatter + const commandContent = artifact.content; + const targetPath = path.join(commandsBaseDir, artifact.relativePath); + await this.writeFile(targetPath, commandContent); + workflowCommandCount++; + } + + console.log(chalk.green(`✓ ${this.name} configured:`)); + console.log(chalk.dim(` - ${agentCount} agents installed to .opencode/agent/bmad/`)); + if (workflowCommandCount > 0) { + console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated to .opencode/command/bmad/`)); + } + + return { + success: true, + agents: agentCount, + workflows: workflowCommandCount, + workflowCounts, + }; + } + + async readAndProcess(filePath, metadata) { + const content = await fs.readFile(filePath, 'utf8'); + return this.processContent(content, metadata); + } + + createAgentContent(content, metadata) { + const { frontmatter = {}, body } = this.parseFrontmatter(content); + + frontmatter.description = + frontmatter.description && String(frontmatter.description).trim().length > 0 + ? frontmatter.description + : `BMAD ${metadata.module} agent: ${metadata.name}`; + + // OpenCode agents use: 'primary' mode for main agents + frontmatter.mode = 'primary'; + + const frontmatterString = this.stringifyFrontmatter(frontmatter); + + return `${frontmatterString}\n${body}`; + } + + parseFrontmatter(content) { + const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/); + if (!match) { + return { data: {}, body: content }; + } + + const body = content.slice(match[0].length); + + let frontmatter = {}; + try { + frontmatter = yaml.load(match[1]) || {}; + } catch { + frontmatter = {}; + } + + return { frontmatter, body }; + } + + stringifyFrontmatter(frontmatter) { + const yamlText = yaml + .dump(frontmatter, { + indent: 2, + lineWidth: -1, + noRefs: true, + sortKeys: false, + }) + .trimEnd(); + + return `---\n${yamlText}\n---`; + } +} + +module.exports = { OpenCodeSetup }; diff --git a/tools/cli/installers/lib/ide/workflow-command-template.md b/tools/cli/installers/lib/ide/workflow-command-template.md index 4199c2f6..8755d882 100644 --- a/tools/cli/installers/lib/ide/workflow-command-template.md +++ b/tools/cli/installers/lib/ide/workflow-command-template.md @@ -1,3 +1,7 @@ +--- +description: '{{description}}' +--- + # {{name}} IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: diff --git a/tools/platform-codes.yaml b/tools/platform-codes.yaml index 3114d12a..702d3a54 100644 --- a/tools/platform-codes.yaml +++ b/tools/platform-codes.yaml @@ -37,6 +37,12 @@ platforms: category: ide description: "AI coding assistant" + opencode: + name: "OpenCode" + preferred: false + category: ide + description: "OpenCode terminal coding assistant" + auggie: name: "Auggie" preferred: false