Files
BMAD-METHOD/tools/installer/lib/ide-setup.js
manjaroblack 47b9d9f3e8 fix: spelling errors in documentation. (#297)
* fix: correct typos in documentation and agent files

Fix multiple instances of "assest" typo to "assets" in documentation
Correct "quetsions" typo to "questions" in repository structure sections
Add new words to cSpell dictionary in VS Code settings

* feat(trae): add support for trae ide integration

- Add trae guide documentation
- Update installer to support trae configuration
- Include trae in ide options and documentation references
- Fix typo in architect agent documentation

---------

Co-authored-by: Devin Stagner <devin@blackstag.family>
2025-07-05 21:08:26 -05:00

778 lines
30 KiB
JavaScript

const path = require("path");
const fs = require("fs-extra");
const yaml = require("js-yaml");
const fileManager = require("./file-manager");
const configLoader = require("./config-loader");
const { extractYamlFromAgent } = require("../../lib/yaml-utils");
// Dynamic import for ES module
let chalk;
let inquirer;
// Initialize ES modules
async function initializeModules() {
if (!chalk) {
chalk = (await import("chalk")).default;
}
if (!inquirer) {
inquirer = (await import("inquirer")).default;
}
}
class IdeSetup {
constructor() {
this.ideAgentConfig = null;
}
async loadIdeAgentConfig() {
if (this.ideAgentConfig) return this.ideAgentConfig;
try {
const configPath = path.join(__dirname, '..', 'config', 'ide-agent-config.yaml');
const configContent = await fs.readFile(configPath, 'utf8');
this.ideAgentConfig = yaml.load(configContent);
return this.ideAgentConfig;
} catch (error) {
console.warn('Failed to load IDE agent configuration, using defaults');
return {
'roo-permissions': {},
'cline-order': {}
};
}
}
async setup(ide, installDir, selectedAgent = null, spinner = null) {
await initializeModules();
const ideConfig = await configLoader.getIdeConfiguration(ide);
if (!ideConfig) {
console.log(chalk.yellow(`\nNo configuration available for ${ide}`));
return false;
}
switch (ide) {
case "cursor":
return this.setupCursor(installDir, selectedAgent);
case "claude-code":
return this.setupClaudeCode(installDir, selectedAgent);
case "windsurf":
return this.setupWindsurf(installDir, selectedAgent);
case "trae":
return this.setupTrae(installDir, selectedAgent);
case "roo":
return this.setupRoo(installDir, selectedAgent);
case "cline":
return this.setupCline(installDir, selectedAgent);
case "gemini":
return this.setupGeminiCli(installDir, selectedAgent);
case "github-copilot":
return this.setupVsCodeCopilot(installDir, selectedAgent, spinner);
default:
console.log(chalk.yellow(`\nIDE ${ide} not yet supported`));
return false;
}
}
async setupCursor(installDir, selectedAgent) {
const cursorRulesDir = path.join(installDir, ".cursor", "rules");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(cursorRulesDir);
for (const agentId of agents) {
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
const mdcPath = path.join(cursorRulesDir, `${agentId}.mdc`);
// Create MDC content with proper format
let mdcContent = "---\n";
mdcContent += "description: \n";
mdcContent += "globs: []\n";
mdcContent += "alwaysApply: false\n";
mdcContent += "---\n\n";
mdcContent += `# ${agentId.toUpperCase()} Agent Rule\n\n`;
mdcContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
agentId,
installDir
)} agent persona.\n\n`;
mdcContent += "## Agent Activation\n\n";
mdcContent +=
"CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
mdcContent += "```yaml\n";
// Extract just the YAML content from the agent file
const yamlContent = extractYamlFromAgent(agentContent);
if (yamlContent) {
mdcContent += yamlContent;
} else {
// If no YAML found, include the whole content minus the header
mdcContent += agentContent.replace(/^#.*$/m, "").trim();
}
mdcContent += "\n```\n\n";
mdcContent += "## File Reference\n\n";
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdcContent += `The complete agent definition is available in [${relativePath}](mdc:${relativePath}).\n\n`;
mdcContent += "## Usage\n\n";
mdcContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
agentId,
installDir
)} persona and follow all instructions defined in the YAML configuration above.\n`;
await fileManager.writeFile(mdcPath, mdcContent);
console.log(chalk.green(`✓ Created rule: ${agentId}.mdc`));
}
}
console.log(chalk.green(`\n✓ Created Cursor rules in ${cursorRulesDir}`));
return true;
}
async setupClaudeCode(installDir, selectedAgent) {
const commandsDir = path.join(installDir, ".claude", "commands");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(commandsDir);
for (const agentId of agents) {
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
const commandPath = path.join(commandsDir, `${agentId}.md`);
if (agentPath) {
// Create command file with agent content
const agentContent = await fileManager.readFile(agentPath);
// Add command header
let commandContent = `# /${agentId} Command\n\n`;
commandContent += `When this command is used, adopt the following agent persona:\n\n`;
commandContent += agentContent;
await fileManager.writeFile(commandPath, commandContent);
console.log(chalk.green(`✓ Created command: /${agentId}`));
}
}
console.log(chalk.green(`\n✓ Created Claude Code commands in ${commandsDir}`));
return true;
}
async setupWindsurf(installDir, selectedAgent) {
const windsurfRulesDir = path.join(installDir, ".windsurf", "rules");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(windsurfRulesDir);
for (const agentId of agents) {
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
const mdPath = path.join(windsurfRulesDir, `${agentId}.md`);
// Create MD content (similar to Cursor but without frontmatter)
let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
agentId,
installDir
)} agent persona.\n\n`;
mdContent += "## Agent Activation\n\n";
mdContent +=
"CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
mdContent += "```yaml\n";
// Extract just the YAML content from the agent file
const yamlContent = extractYamlFromAgent(agentContent);
if (yamlContent) {
mdContent += yamlContent;
} else {
// If no YAML found, include the whole content minus the header
mdContent += agentContent.replace(/^#.*$/m, "").trim();
}
mdContent += "\n```\n\n";
mdContent += "## File Reference\n\n";
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
mdContent += "## Usage\n\n";
mdContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
agentId,
installDir
)} persona and follow all instructions defined in the YAML configuration above.\n`;
await fileManager.writeFile(mdPath, mdContent);
console.log(chalk.green(`✓ Created rule: ${agentId}.md`));
}
}
console.log(chalk.green(`\n✓ Created Windsurf rules in ${windsurfRulesDir}`));
return true;
}
async setupTrae(installDir, selectedAgent) {
const traeRulesDir = path.join(installDir, ".trae", "rules");
const agents = selectedAgent? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(traeRulesDir);
for (const agentId of agents) {
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
const mdPath = path.join(traeRulesDir, `${agentId}.md`);
// Create MD content (similar to Cursor but without frontmatter)
let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
agentId,
installDir
)} agent persona.\n\n`;
mdContent += "## Agent Activation\n\n";
mdContent +=
"CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
mdContent += "```yaml\n";
// Extract just the YAML content from the agent file
const yamlContent = extractYamlFromAgent(agentContent);
if (yamlContent) {
mdContent += yamlContent;
}
else {
// If no YAML found, include the whole content minus the header
mdContent += agentContent.replace(/^#.*$/m, "").trim();
}
mdContent += "\n```\n\n";
mdContent += "## File Reference\n\n";
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
mdContent += "## Usage\n\n";
mdContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
agentId,
installDir
)} persona and follow all instructions defined in the YAML configuration above.\n`;
await fileManager.writeFile(mdPath, mdContent);
console.log(chalk.green(`✓ Created rule: ${agentId}.md`));
}
}
}
async findAgentPath(agentId, installDir) {
// Try to find the agent file in various locations
const possiblePaths = [
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "agents", `${agentId}.md`)
];
// Also check expansion pack directories
const glob = require("glob");
const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) {
possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
}
for (const agentPath of possiblePaths) {
if (await fileManager.pathExists(agentPath)) {
return agentPath;
}
}
return null;
}
async getAllAgentIds(installDir) {
const glob = require("glob");
const allAgentIds = [];
// Check core agents in .bmad-core or root
let agentsDir = path.join(installDir, ".bmad-core", "agents");
if (!(await fileManager.pathExists(agentsDir))) {
agentsDir = path.join(installDir, "agents");
}
if (await fileManager.pathExists(agentsDir)) {
const agentFiles = glob.sync("*.md", { cwd: agentsDir });
allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
}
// Also check for expansion pack agents in dot folders
const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) {
const fullExpDir = path.join(installDir, expDir);
const expAgentFiles = glob.sync("*.md", { cwd: fullExpDir });
allAgentIds.push(...expAgentFiles.map((file) => path.basename(file, ".md")));
}
// Remove duplicates
return [...new Set(allAgentIds)];
}
async getAgentTitle(agentId, installDir) {
// Try to find the agent file in various locations
const possiblePaths = [
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "agents", `${agentId}.md`)
];
// Also check expansion pack directories
const glob = require("glob");
const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) {
possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
}
for (const agentPath of possiblePaths) {
if (await fileManager.pathExists(agentPath)) {
try {
const agentContent = await fileManager.readFile(agentPath);
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
if (yamlMatch) {
const yaml = yamlMatch[1];
const titleMatch = yaml.match(/title:\s*(.+)/);
if (titleMatch) {
return titleMatch[1].trim();
}
}
} catch (error) {
console.warn(`Failed to read agent title for ${agentId}: ${error.message}`);
}
}
}
// Fallback to formatted agent ID
return agentId.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
}
async setupRoo(installDir, selectedAgent) {
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
// Check for existing .roomodes file in project root
const roomodesPath = path.join(installDir, ".roomodes");
let existingModes = [];
let existingContent = "";
if (await fileManager.pathExists(roomodesPath)) {
existingContent = await fileManager.readFile(roomodesPath);
// Parse existing modes to avoid duplicates
const modeMatches = existingContent.matchAll(/- slug: ([\w-]+)/g);
for (const match of modeMatches) {
existingModes.push(match[1]);
}
console.log(chalk.yellow(`Found existing .roomodes file with ${existingModes.length} modes`));
}
// Create new modes content
let newModesContent = "";
// Load dynamic agent permissions from configuration
const config = await this.loadIdeAgentConfig();
const agentPermissions = config['roo-permissions'] || {};
for (const agentId of agents) {
// Skip if already exists
if (existingModes.includes(`bmad-${agentId}`)) {
console.log(chalk.dim(`Skipping ${agentId} - already exists in .roomodes`));
continue;
}
// Read agent file to extract all information
const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
// Extract YAML content
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
if (yamlMatch) {
const yaml = yamlMatch[1];
// Extract agent info from YAML
const titleMatch = yaml.match(/title:\s*(.+)/);
const iconMatch = yaml.match(/icon:\s*(.+)/);
const whenToUseMatch = yaml.match(/whenToUse:\s*"(.+)"/);
const roleDefinitionMatch = yaml.match(/roleDefinition:\s*"(.+)"/);
const title = titleMatch ? titleMatch[1].trim() : await this.getAgentTitle(agentId, installDir);
const icon = iconMatch ? iconMatch[1].trim() : "🤖";
const whenToUse = whenToUseMatch ? whenToUseMatch[1].trim() : `Use for ${title} tasks`;
const roleDefinition = roleDefinitionMatch
? roleDefinitionMatch[1].trim()
: `You are a ${title} specializing in ${title.toLowerCase()} tasks and responsibilities.`;
// Build mode entry with proper formatting (matching exact indentation)
newModesContent += ` - slug: bmad-${agentId}\n`;
newModesContent += ` name: '${icon} ${title}'\n`;
newModesContent += ` roleDefinition: ${roleDefinition}\n`;
newModesContent += ` whenToUse: ${whenToUse}\n`;
// Get relative path from installDir to agent file
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
newModesContent += ` customInstructions: CRITICAL Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
newModesContent += ` groups:\n`;
newModesContent += ` - read\n`;
// Add permissions based on agent type
const permissions = agentPermissions[agentId];
if (permissions) {
newModesContent += ` - - edit\n`;
newModesContent += ` - fileRegex: ${permissions.fileRegex}\n`;
newModesContent += ` description: ${permissions.description}\n`;
} else {
newModesContent += ` - edit\n`;
}
console.log(chalk.green(`✓ Added mode: bmad-${agentId} (${icon} ${title})`));
}
}
}
// Build final roomodes content
let roomodesContent = "";
if (existingContent) {
// If there's existing content, append new modes to it
roomodesContent = existingContent.trim() + "\n" + newModesContent;
} else {
// Create new .roomodes file with proper YAML structure
roomodesContent = "customModes:\n" + newModesContent;
}
// Write .roomodes file
await fileManager.writeFile(roomodesPath, roomodesContent);
console.log(chalk.green("✓ Created .roomodes file in project root"));
console.log(chalk.green(`\n✓ Roo Code setup complete!`));
console.log(chalk.dim("Custom modes will be available when you open this project in Roo Code"));
return true;
}
async setupCline(installDir, selectedAgent) {
const clineRulesDir = path.join(installDir, ".clinerules");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(clineRulesDir);
// Load dynamic agent ordering from configuration
const config = await this.loadIdeAgentConfig();
const agentOrder = config['cline-order'] || {};
for (const agentId of agents) {
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
// Get numeric prefix for ordering
const order = agentOrder[agentId] || 99;
const prefix = order.toString().padStart(2, '0');
const mdPath = path.join(clineRulesDir, `${prefix}-${agentId}.md`);
// Create MD content for Cline (focused on project standards and role)
let mdContent = `# ${await this.getAgentTitle(agentId, installDir)} Agent\n\n`;
mdContent += `This rule defines the ${await this.getAgentTitle(agentId, installDir)} persona and project standards.\n\n`;
mdContent += "## Role Definition\n\n";
mdContent +=
"When the user types `@" + agentId + "`, adopt this persona and follow these guidelines:\n\n";
mdContent += "```yaml\n";
// Extract just the YAML content from the agent file
const yamlContent = extractYamlFromAgent(agentContent);
if (yamlContent) {
mdContent += yamlContent;
} else {
// If no YAML found, include the whole content minus the header
mdContent += agentContent.replace(/^#.*$/m, "").trim();
}
mdContent += "\n```\n\n";
mdContent += "## Project Standards\n\n";
mdContent += `- Always maintain consistency with project documentation in .bmad-core/\n`;
mdContent += `- Follow the agent's specific guidelines and constraints\n`;
mdContent += `- Update relevant project files when making changes\n`;
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdContent += `- Reference the complete agent definition in [${relativePath}](${relativePath})\n\n`;
mdContent += "## Usage\n\n";
mdContent += `Type \`@${agentId}\` to activate this ${await this.getAgentTitle(agentId, installDir)} persona.\n`;
await fileManager.writeFile(mdPath, mdContent);
console.log(chalk.green(`✓ Created rule: ${prefix}-${agentId}.md`));
}
}
console.log(chalk.green(`\n✓ Created Cline rules in ${clineRulesDir}`));
return true;
}
async setupGeminiCli(installDir, selectedAgent) {
await initializeModules();
const geminiDir = path.join(installDir, ".gemini");
const agentsContextDir = path.join(geminiDir, "agents");
await fileManager.ensureDirectory(agentsContextDir);
// Get all available agents
const agents = await this.getAllAgentIds(installDir);
const agentContextFiles = [];
for (const agentId of agents) {
// Find the source agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
const contextFilePath = path.join(agentsContextDir, `${agentId}.md`);
// Copy the agent content directly into its own context file
await fileManager.writeFile(contextFilePath, agentContent);
// Store the relative path for settings.json
const relativePath = path.relative(geminiDir, contextFilePath);
agentContextFiles.push(relativePath.replace(/\\/g, '/')); // Ensure forward slashes for consistency
console.log(chalk.green(`✓ Created context file for @${agentId}`));
}
}
console.log(chalk.green(`\n✓ Created individual agent context files in ${agentsContextDir}`));
// Add GEMINI.md to the context files array
agentContextFiles.push("GEMINI.md");
// Create or update settings.json
const settingsPath = path.join(geminiDir, "settings.json");
let settings = {};
if (await fileManager.pathExists(settingsPath)) {
try {
const existingSettings = await fileManager.readFile(settingsPath);
settings = JSON.parse(existingSettings);
console.log(chalk.yellow("Found existing .gemini/settings.json. Merging settings..."));
} catch (e) {
console.error(chalk.red("Error parsing existing settings.json. It will be overwritten."), e);
settings = {};
}
}
// Set contextFileName to our new array of files
settings.contextFileName = agentContextFiles;
await fileManager.writeFile(settingsPath, JSON.stringify(settings, null, 2));
console.log(chalk.green(`✓ Configured .gemini/settings.json to load all agent context files.`));
return true;
}
async setupVsCodeCopilot(installDir, selectedAgent, spinner = null) {
await initializeModules();
// Configure VS Code workspace settings first to avoid UI conflicts with loading spinners
await this.configureVsCodeSettings(installDir, spinner);
const chatmodesDir = path.join(installDir, ".github", "chatmodes");
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
await fileManager.ensureDirectory(chatmodesDir);
for (const agentId of agents) {
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
const chatmodePath = path.join(chatmodesDir, `${agentId}.chatmode.md`);
if (agentPath) {
// Create chat mode file with agent content
const agentContent = await fileManager.readFile(agentPath);
const agentTitle = await this.getAgentTitle(agentId, installDir);
// Extract whenToUse for the description
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
let description = `Activates the ${agentTitle} agent persona.`;
if (yamlMatch) {
const whenToUseMatch = yamlMatch[1].match(/whenToUse:\s*"(.*?)"/);
if (whenToUseMatch && whenToUseMatch[1]) {
description = whenToUseMatch[1];
}
}
let chatmodeContent = `---
description: "${description.replace(/"/g, '\\"')}"
tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'usages']
---
`;
chatmodeContent += agentContent;
await fileManager.writeFile(chatmodePath, chatmodeContent);
console.log(chalk.green(`✓ Created chat mode: ${agentId}.chatmode.md`));
}
}
console.log(chalk.green(`\n✓ Github Copilot setup complete!`));
console.log(chalk.dim(`You can now find the BMad agents in the Chat view's mode selector.`));
return true;
}
async configureVsCodeSettings(installDir, spinner) {
await initializeModules(); // Ensure inquirer is loaded
const vscodeDir = path.join(installDir, ".vscode");
const settingsPath = path.join(vscodeDir, "settings.json");
await fileManager.ensureDirectory(vscodeDir);
// Read existing settings if they exist
let existingSettings = {};
if (await fileManager.pathExists(settingsPath)) {
try {
const existingContent = await fileManager.readFile(settingsPath);
existingSettings = JSON.parse(existingContent);
console.log(chalk.yellow("Found existing .vscode/settings.json. Merging BMad settings..."));
} catch (error) {
console.warn(chalk.yellow("Could not parse existing settings.json. Creating new one."));
existingSettings = {};
}
}
// Clear any previous output and add spacing to avoid conflicts with loaders
console.log('\n'.repeat(2));
console.log(chalk.blue("🔧 Github Copilot Agent Settings Configuration"));
console.log(chalk.dim("BMad works best with specific VS Code settings for optimal agent experience."));
console.log(''); // Add extra spacing
const { configChoice } = await inquirer.prompt([
{
type: 'list',
name: 'configChoice',
message: 'How would you like to configure Github Copilot settings?',
choices: [
{
name: 'Use recommended defaults (fastest setup)',
value: 'defaults'
},
{
name: 'Configure each setting manually (customize to your preferences)',
value: 'manual'
},
{
name: 'Skip settings configuration (I\'ll configure manually later)\n',
value: 'skip'
}
],
default: 'defaults'
}
]);
let bmadSettings = {};
if (configChoice === 'skip') {
console.log(chalk.yellow("⚠️ Skipping VS Code settings configuration."));
console.log(chalk.dim("You can manually configure these settings in .vscode/settings.json:"));
console.log(chalk.dim(" • chat.agent.enabled: true"));
console.log(chalk.dim(" • chat.agent.maxRequests: 15"));
console.log(chalk.dim(" • github.copilot.chat.agent.runTasks: true"));
console.log(chalk.dim(" • chat.mcp.discovery.enabled: true"));
console.log(chalk.dim(" • github.copilot.chat.agent.autoFix: true"));
console.log(chalk.dim(" • chat.tools.autoApprove: false"));
return true;
}
if (configChoice === 'defaults') {
// Use recommended defaults
bmadSettings = {
"chat.agent.enabled": true,
"chat.agent.maxRequests": 15,
"github.copilot.chat.agent.runTasks": true,
"chat.mcp.discovery.enabled": true,
"github.copilot.chat.agent.autoFix": true,
"chat.tools.autoApprove": false
};
console.log(chalk.green("✓ Using recommended BMad defaults for Github Copilot settings"));
} else {
// Manual configuration
console.log(chalk.blue("\n📋 Let's configure each setting for your preferences:"));
// Pause spinner during manual configuration prompts
let spinnerWasActive = false;
if (spinner && spinner.isSpinning) {
spinner.stop();
spinnerWasActive = true;
}
const manualSettings = await inquirer.prompt([
{
type: 'input',
name: 'maxRequests',
message: 'Maximum requests per agent session (recommended: 15)?',
default: '15',
validate: (input) => {
const num = parseInt(input);
if (isNaN(num) || num < 1 || num > 50) {
return 'Please enter a number between 1 and 50';
}
return true;
}
},
{
type: 'confirm',
name: 'runTasks',
message: 'Allow agents to run workspace tasks (package.json scripts, etc.)?',
default: true
},
{
type: 'confirm',
name: 'mcpDiscovery',
message: 'Enable MCP (Model Context Protocol) server discovery?',
default: true
},
{
type: 'confirm',
name: 'autoFix',
message: 'Enable automatic error detection and fixing in generated code?',
default: true
},
{
type: 'confirm',
name: 'autoApprove',
message: 'Auto-approve ALL tools without confirmation? (⚠️ EXPERIMENTAL - less secure)',
default: false
}
]);
// Restart spinner if it was active before prompts
if (spinner && spinnerWasActive) {
spinner.start();
}
bmadSettings = {
"chat.agent.enabled": true, // Always enabled - required for BMad agents
"chat.agent.maxRequests": parseInt(manualSettings.maxRequests),
"github.copilot.chat.agent.runTasks": manualSettings.runTasks,
"chat.mcp.discovery.enabled": manualSettings.mcpDiscovery,
"github.copilot.chat.agent.autoFix": manualSettings.autoFix,
"chat.tools.autoApprove": manualSettings.autoApprove
};
console.log(chalk.green("✓ Custom settings configured"));
}
// Merge settings (existing settings take precedence to avoid overriding user preferences)
const mergedSettings = { ...bmadSettings, ...existingSettings };
// Write the updated settings
await fileManager.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
console.log(chalk.green("✓ VS Code workspace settings configured successfully"));
console.log(chalk.dim(" Settings written to .vscode/settings.json:"));
Object.entries(bmadSettings).forEach(([key, value]) => {
console.log(chalk.dim(`${key}: ${value}`));
});
console.log(chalk.dim(""));
console.log(chalk.dim("You can modify these settings anytime in .vscode/settings.json"));
}
}
module.exports = new IdeSetup();