feat(installer): enhance OpenCode setup with agent selection and prefix options
This commit is contained in:
parent
79442df3f0
commit
9970d711e7
|
|
@ -479,6 +479,59 @@ async function promptInstallation() {
|
|||
answers.githubCopilotConfig = { configChoice };
|
||||
}
|
||||
|
||||
// Configure OpenCode (SST) immediately if selected
|
||||
if (ides.includes('opencode')) {
|
||||
console.log(chalk.cyan('\n⚙️ OpenCode (SST) Configuration'));
|
||||
console.log(
|
||||
chalk.dim(
|
||||
'Select which agents you want in opencode.json(c) and choose optional key prefixes (defaults: no prefixes).\n',
|
||||
),
|
||||
);
|
||||
|
||||
// Load available agents from installer
|
||||
const availableAgents = (await installer.getAvailableAgents()) || [];
|
||||
const agentChoices = availableAgents.map((a) => ({ name: a.id, value: a.id }));
|
||||
|
||||
const { selectedOpenCodeAgents } = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'selectedOpenCodeAgents',
|
||||
message: 'Select agents to add to OpenCode:',
|
||||
choices: agentChoices,
|
||||
validate: (selected) => {
|
||||
if (selected.length === 0) {
|
||||
return 'Please select at least one agent for OpenCode or deselect OpenCode from IDEs.';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const { useAgentPrefix, useCommandPrefix } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useAgentPrefix',
|
||||
message: "Prefix agent keys with 'bmad-'? (e.g., 'bmad-dev')",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useCommandPrefix',
|
||||
message: "Prefix command keys with 'bmad:tasks:'? (e.g., 'bmad:tasks:create-doc')",
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
|
||||
answers.openCodeConfig = {
|
||||
opencode: {
|
||||
useAgentPrefix,
|
||||
useCommandPrefix,
|
||||
},
|
||||
// pass selected agents so IDE setup only applies those
|
||||
selectedAgents: selectedOpenCodeAgents,
|
||||
};
|
||||
}
|
||||
|
||||
// Configure Auggie CLI (Augment Code) immediately if selected
|
||||
if (ides.includes('auggie-cli')) {
|
||||
console.log(chalk.cyan('\n📍 Auggie CLI Location Configuration'));
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class IdeSetup extends BaseIdeSetup {
|
|||
return this.setupCursor(installDir, selectedAgent);
|
||||
}
|
||||
case 'opencode': {
|
||||
return this.setupOpenCode(installDir, selectedAgent);
|
||||
return this.setupOpenCode(installDir, selectedAgent, spinner, preConfiguredSettings);
|
||||
}
|
||||
case 'claude-code': {
|
||||
return this.setupClaudeCode(installDir, selectedAgent);
|
||||
|
|
@ -97,7 +97,7 @@ class IdeSetup extends BaseIdeSetup {
|
|||
}
|
||||
}
|
||||
|
||||
async setupOpenCode(installDir, selectedAgent) {
|
||||
async setupOpenCode(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) {
|
||||
// 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
|
||||
|
|
@ -107,8 +107,53 @@ class IdeSetup extends BaseIdeSetup {
|
|||
const hasJson = await fileManager.pathExists(jsonPath);
|
||||
const hasJsonc = await fileManager.pathExists(jsoncPath);
|
||||
|
||||
// Determine key prefix preferences (with sensible defaults)
|
||||
// Defaults: non-prefixed (agents = "dev", commands = "create-doc")
|
||||
let useAgentPrefix = false;
|
||||
let useCommandPrefix = false;
|
||||
|
||||
// Allow pre-configuration (if passed) to skip prompts
|
||||
const pre = preConfiguredSettings && preConfiguredSettings.opencode;
|
||||
if (pre && typeof pre.useAgentPrefix === 'boolean') useAgentPrefix = pre.useAgentPrefix;
|
||||
if (pre && typeof pre.useCommandPrefix === 'boolean') useCommandPrefix = pre.useCommandPrefix;
|
||||
|
||||
// If no pre-config and in interactive mode, prompt the user
|
||||
if (!pre) {
|
||||
// Pause spinner during prompts if active
|
||||
let spinnerWasActive = false;
|
||||
if (spinner && spinner.isSpinning) {
|
||||
spinner.stop();
|
||||
spinnerWasActive = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useAgentPrefix',
|
||||
message:
|
||||
"Prefix agent keys with 'bmad-'? (Recommended to avoid collisions, e.g., 'bmad-dev')",
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'useCommandPrefix',
|
||||
message:
|
||||
"Prefix command keys with 'bmad:tasks:'? (Recommended, e.g., 'bmad:tasks:create-doc')",
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
useAgentPrefix = resp.useAgentPrefix;
|
||||
useCommandPrefix = resp.useCommandPrefix;
|
||||
} catch {
|
||||
// Keep defaults if prompt fails or is not interactive
|
||||
} finally {
|
||||
if (spinner && spinnerWasActive) spinner.start();
|
||||
}
|
||||
}
|
||||
|
||||
const ensureInstructionRef = (obj) => {
|
||||
const ref = './.bmad-core/core-config.yaml';
|
||||
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);
|
||||
|
|
@ -121,10 +166,34 @@ class IdeSetup extends BaseIdeSetup {
|
|||
if (!configObj.agent || typeof configObj.agent !== 'object') configObj.agent = {};
|
||||
if (!configObj.command || typeof configObj.command !== 'object') configObj.command = {};
|
||||
|
||||
// Track a concise summary of changes
|
||||
const summary = {
|
||||
target: null,
|
||||
created: false,
|
||||
agentsAdded: 0,
|
||||
agentsUpdated: 0,
|
||||
agentsSkipped: 0,
|
||||
commandsAdded: 0,
|
||||
commandsUpdated: 0,
|
||||
commandsSkipped: 0,
|
||||
};
|
||||
|
||||
// Agents: use core agent ids by default
|
||||
const agentIds = selectedAgent ? [selectedAgent] : await this.getCoreAgentIds(installDir);
|
||||
// If pre-config provided selected agents for opencode, respect that list
|
||||
const preSelected = preConfiguredSettings?.selectedAgents;
|
||||
const agentIds =
|
||||
preSelected && Array.isArray(preSelected) && preSelected.length > 0
|
||||
? preSelected
|
||||
: selectedAgent
|
||||
? [selectedAgent]
|
||||
: await this.getCoreAgentIds(installDir);
|
||||
for (const agentId of agentIds) {
|
||||
const key = agentId.startsWith('bmad-') ? agentId : `bmad-${agentId}`;
|
||||
const baseKey = agentId;
|
||||
const key = useAgentPrefix
|
||||
? baseKey.startsWith('bmad-')
|
||||
? baseKey
|
||||
: `bmad-${baseKey}`
|
||||
: baseKey;
|
||||
const existing = configObj.agent[key];
|
||||
const agentDef = {
|
||||
prompt: `{file:./.bmad-core/agents/${agentId}.md}`,
|
||||
|
|
@ -132,6 +201,7 @@ class IdeSetup extends BaseIdeSetup {
|
|||
};
|
||||
if (!existing) {
|
||||
configObj.agent[key] = agentDef;
|
||||
summary.agentsAdded++;
|
||||
} else if (
|
||||
existing &&
|
||||
typeof existing === 'object' &&
|
||||
|
|
@ -142,19 +212,23 @@ class IdeSetup extends BaseIdeSetup {
|
|||
existing.prompt = agentDef.prompt;
|
||||
existing.mode = agentDef.mode;
|
||||
configObj.agent[key] = existing;
|
||||
summary.agentsUpdated++;
|
||||
} else {
|
||||
summary.agentsSkipped++;
|
||||
}
|
||||
}
|
||||
|
||||
// Commands: expose core tasks as commands
|
||||
const taskIds = await this.getAllTaskIds(installDir);
|
||||
for (const taskId of taskIds) {
|
||||
const key = `bmad:tasks:${taskId}`;
|
||||
const key = useCommandPrefix ? `bmad:tasks:${taskId}` : `${taskId}`;
|
||||
const existing = configObj.command[key];
|
||||
const cmdDef = {
|
||||
template: `{file:./.bmad-core/tasks/${taskId}.md}`,
|
||||
};
|
||||
if (!existing) {
|
||||
configObj.command[key] = cmdDef;
|
||||
summary.commandsAdded++;
|
||||
} else if (
|
||||
existing &&
|
||||
typeof existing === 'object' &&
|
||||
|
|
@ -164,10 +238,13 @@ class IdeSetup extends BaseIdeSetup {
|
|||
// Update only BMAD-managed entries detected by template path
|
||||
existing.template = cmdDef.template;
|
||||
configObj.command[key] = existing;
|
||||
summary.commandsUpdated++;
|
||||
} else {
|
||||
summary.commandsSkipped++;
|
||||
}
|
||||
}
|
||||
|
||||
return configObj;
|
||||
return { configObj, summary };
|
||||
};
|
||||
|
||||
if (hasJson || hasJsonc) {
|
||||
|
|
@ -178,7 +255,7 @@ class IdeSetup extends BaseIdeSetup {
|
|||
// Use comment-json for both .json and .jsonc for resilience
|
||||
const parsed = cjson.parse(raw, undefined, true);
|
||||
ensureInstructionRef(parsed);
|
||||
await mergeBmadAgentsAndCommands(parsed);
|
||||
const { configObj, summary } = await mergeBmadAgentsAndCommands(parsed);
|
||||
const output = cjson.stringify(parsed, null, 2);
|
||||
await fs.writeFile(targetPath, output + (output.endsWith('\n') ? '' : '\n'));
|
||||
console.log(
|
||||
|
|
@ -186,6 +263,12 @@ class IdeSetup extends BaseIdeSetup {
|
|||
'✓ Updated OpenCode config: ensured BMAD instructions and merged agents/commands',
|
||||
),
|
||||
);
|
||||
// Summary output
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` File: ${path.basename(targetPath)} | Agents +${summary.agentsAdded} ~${summary.agentsUpdated} ⨯${summary.agentsSkipped} | Commands +${summary.commandsAdded} ~${summary.commandsUpdated} ⨯${summary.commandsSkipped}`,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(chalk.red('✗ Failed to update existing OpenCode config'), error.message);
|
||||
return false;
|
||||
|
|
@ -201,12 +284,17 @@ class IdeSetup extends BaseIdeSetup {
|
|||
command: {},
|
||||
};
|
||||
try {
|
||||
await mergeBmadAgentsAndCommands(minimal);
|
||||
const { configObj, summary } = 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'),
|
||||
);
|
||||
console.log(
|
||||
chalk.dim(
|
||||
` File: opencode.jsonc | Agents +${summary.agentsAdded} | Commands +${summary.commandsAdded}`,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(chalk.red('✗ Failed to create opencode.jsonc'), error.message);
|
||||
|
|
|
|||
|
|
@ -409,10 +409,23 @@ class Installer {
|
|||
for (const ide of ides) {
|
||||
spinner.text = `Setting up ${ide} integration...`;
|
||||
let preConfiguredSettings = null;
|
||||
if (ide === 'github-copilot') {
|
||||
preConfiguredSettings = config.githubCopilotConfig;
|
||||
} else if (ide === 'auggie-cli') {
|
||||
preConfiguredSettings = config.augmentCodeConfig;
|
||||
switch (ide) {
|
||||
case 'github-copilot': {
|
||||
preConfiguredSettings = config.githubCopilotConfig;
|
||||
break;
|
||||
}
|
||||
case 'auggie-cli': {
|
||||
preConfiguredSettings = config.augmentCodeConfig;
|
||||
break;
|
||||
}
|
||||
case 'opencode': {
|
||||
preConfiguredSettings = config.openCodeConfig;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// no pre-configured settings
|
||||
break;
|
||||
}
|
||||
}
|
||||
await ideSetup.setup(ide, installDir, config.agent, spinner, preConfiguredSettings);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue