feat(installer/opencode): implement setupOpenCode with minimal instructions merge and BMAD-managed agents/commands
This commit is contained in:
parent
5fb8df11f2
commit
0c3e3be692
|
|
@ -3,6 +3,7 @@ const fs = require('fs-extra');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const inquirer = require('inquirer');
|
const inquirer = require('inquirer');
|
||||||
|
const cjson = require('comment-json');
|
||||||
const fileManager = require('./file-manager');
|
const fileManager = require('./file-manager');
|
||||||
const configLoader = require('./config-loader');
|
const configLoader = require('./config-loader');
|
||||||
const { extractYamlFromAgent } = require('../../lib/yaml-utils');
|
const { extractYamlFromAgent } = require('../../lib/yaml-utils');
|
||||||
|
|
@ -44,6 +45,9 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
case 'cursor': {
|
case 'cursor': {
|
||||||
return this.setupCursor(installDir, selectedAgent);
|
return this.setupCursor(installDir, selectedAgent);
|
||||||
}
|
}
|
||||||
|
case 'opencode': {
|
||||||
|
return this.setupOpenCode(installDir, selectedAgent);
|
||||||
|
}
|
||||||
case 'claude-code': {
|
case 'claude-code': {
|
||||||
return this.setupClaudeCode(installDir, selectedAgent);
|
return this.setupClaudeCode(installDir, selectedAgent);
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +97,124 @@ class IdeSetup extends BaseIdeSetup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setupOpenCode(installDir, selectedAgent) {
|
||||||
|
// Minimal JSON-only integration per plan:
|
||||||
|
// - If opencode.json or opencode.jsonc exists: only ensure instructions include .bmad-core/core-config.yaml
|
||||||
|
// - If none exists: create minimal opencode.jsonc with $schema and instructions array including that file
|
||||||
|
|
||||||
|
const jsonPath = path.join(installDir, 'opencode.json');
|
||||||
|
const jsoncPath = path.join(installDir, 'opencode.jsonc');
|
||||||
|
const hasJson = await fileManager.pathExists(jsonPath);
|
||||||
|
const hasJsonc = await fileManager.pathExists(jsoncPath);
|
||||||
|
|
||||||
|
const ensureInstructionRef = (obj) => {
|
||||||
|
const ref = './.bmad-core/core-config.yaml';
|
||||||
|
if (!obj.instructions) obj.instructions = [];
|
||||||
|
if (!Array.isArray(obj.instructions)) obj.instructions = [obj.instructions];
|
||||||
|
const hasRef = obj.instructions.some((it) =>
|
||||||
|
typeof it === 'string'
|
||||||
|
? it === ref
|
||||||
|
: it && typeof it.file === 'string'
|
||||||
|
? it.file === ref
|
||||||
|
: false,
|
||||||
|
);
|
||||||
|
if (!hasRef) obj.instructions.push({ file: ref });
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeBmadAgentsAndCommands = async (configObj) => {
|
||||||
|
// Ensure objects exist
|
||||||
|
if (!configObj.agent || typeof configObj.agent !== 'object') configObj.agent = {};
|
||||||
|
if (!configObj.command || typeof configObj.command !== 'object') configObj.command = {};
|
||||||
|
|
||||||
|
// Agents: use core agent ids by default
|
||||||
|
const agentIds = selectedAgent ? [selectedAgent] : await this.getCoreAgentIds(installDir);
|
||||||
|
for (const agentId of agentIds) {
|
||||||
|
const key = agentId.startsWith('bmad-') ? agentId : `bmad-${agentId}`;
|
||||||
|
const existing = configObj.agent[key];
|
||||||
|
const agentDef = {
|
||||||
|
instructions: [{ file: `./.bmad-core/agents/${agentId}.md` }],
|
||||||
|
tools: ['write', 'edit', 'bash'],
|
||||||
|
mode: 'subagent',
|
||||||
|
bmadManaged: true,
|
||||||
|
};
|
||||||
|
if (!existing) {
|
||||||
|
configObj.agent[key] = agentDef;
|
||||||
|
} else if (existing && existing.bmadManaged) {
|
||||||
|
// Update to latest shape without clobbering non-BMAD entries
|
||||||
|
existing.instructions = agentDef.instructions;
|
||||||
|
existing.tools = agentDef.tools;
|
||||||
|
existing.mode = agentDef.mode;
|
||||||
|
existing.bmadManaged = true;
|
||||||
|
configObj.agent[key] = existing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands: expose core tasks as commands
|
||||||
|
const taskIds = await this.getAllTaskIds(installDir);
|
||||||
|
for (const taskId of taskIds) {
|
||||||
|
const key = `bmad:tasks:${taskId}`;
|
||||||
|
const existing = configObj.command[key];
|
||||||
|
const cmdDef = {
|
||||||
|
instructions: [{ file: `./.bmad-core/tasks/${taskId}.md` }],
|
||||||
|
bmadManaged: true,
|
||||||
|
};
|
||||||
|
if (!existing) {
|
||||||
|
configObj.command[key] = cmdDef;
|
||||||
|
} else if (existing && existing.bmadManaged) {
|
||||||
|
existing.instructions = cmdDef.instructions;
|
||||||
|
existing.bmadManaged = true;
|
||||||
|
configObj.command[key] = existing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasJson || hasJsonc) {
|
||||||
|
// Preserve existing top-level fields; only touch instructions
|
||||||
|
const targetPath = hasJsonc ? jsoncPath : jsonPath;
|
||||||
|
try {
|
||||||
|
const raw = await fs.readFile(targetPath, 'utf8');
|
||||||
|
// Use comment-json for both .json and .jsonc for resilience
|
||||||
|
const parsed = cjson.parse(raw, undefined, true);
|
||||||
|
ensureInstructionRef(parsed);
|
||||||
|
await mergeBmadAgentsAndCommands(parsed);
|
||||||
|
const output = cjson.stringify(parsed, null, 2);
|
||||||
|
await fs.writeFile(targetPath, output + (output.endsWith('\n') ? '' : '\n'));
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
'✓ Updated OpenCode config: ensured BMAD instructions and merged agents/commands',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red('✗ Failed to update existing OpenCode config'), error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create minimal opencode.jsonc
|
||||||
|
const minimal = {
|
||||||
|
$schema: 'https://opencode.ai/config.json',
|
||||||
|
instructions: [{ file: './.bmad-core/core-config.yaml' }],
|
||||||
|
agent: {},
|
||||||
|
command: {},
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await mergeBmadAgentsAndCommands(minimal);
|
||||||
|
const output = cjson.stringify(minimal, null, 2);
|
||||||
|
await fs.writeFile(jsoncPath, output + (output.endsWith('\n') ? '' : '\n'));
|
||||||
|
console.log(
|
||||||
|
chalk.green('✓ Created opencode.jsonc with BMAD instructions, agents, and commands'),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red('✗ Failed to create opencode.jsonc'), error.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async setupCodex(installDir, selectedAgent, options) {
|
async setupCodex(installDir, selectedAgent, options) {
|
||||||
options = options ?? { webEnabled: false };
|
options = options ?? { webEnabled: false };
|
||||||
// Codex reads AGENTS.md at the project root as project memory (CLI & Web).
|
// Codex reads AGENTS.md at the project root as project memory (CLI & Web).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue