installer updates working with basic flow

This commit is contained in:
Brian Madison
2025-12-05 22:32:59 -06:00
parent e3f756488a
commit 228dfa28a5
92 changed files with 10643 additions and 960 deletions

View File

@@ -2,6 +2,8 @@ const chalk = require('chalk');
const path = require('node:path');
const fs = require('node:fs');
const readline = require('node:readline');
const yaml = require('js-yaml');
const inquirer = require('inquirer');
const {
findBmadConfig,
resolvePath,
@@ -18,6 +20,122 @@ const {
updateManifestYaml,
} = require('../lib/agent/installer');
/**
* Initialize BMAD core infrastructure in a directory
* @param {string} projectDir - Project directory where .bmad should be created
* @param {string} bmadFolderName - Name of the BMAD folder (default: .bmad)
* @returns {Promise<Object>} BMAD project info
*/
async function initializeBmadCore(projectDir, bmadFolderName = '.bmad') {
const bmadDir = path.join(projectDir, bmadFolderName);
const cfgDir = path.join(bmadDir, '_cfg');
console.log(chalk.cyan('\n🏗 Initializing BMAD Core Infrastructure\n'));
// Use the ConfigCollector to ask proper core configuration questions
const { ConfigCollector } = require('../installers/lib/core/config-collector');
const configCollector = new ConfigCollector();
// Collect core configuration answers
await configCollector.loadExistingConfig(projectDir);
await configCollector.collectModuleConfig('core', projectDir, true, true);
// Extract core answers from allAnswers (they are prefixed with 'core_')
const coreAnswers = {};
if (configCollector.allAnswers) {
for (const [key, value] of Object.entries(configCollector.allAnswers)) {
if (key.startsWith('core_')) {
const configKey = key.slice(5); // Remove 'core_' prefix
coreAnswers[configKey] = value;
}
}
}
// Ask for IDE selection
console.log(chalk.cyan('\n💻 IDE Configuration\n'));
console.log(chalk.dim('Select IDEs to integrate with the installed agents:'));
const { UI } = require('../lib/ui');
const ui = new UI();
const ideConfig = await ui.promptToolSelection(projectDir, ['core']);
const selectedIdes = ideConfig.ides || [];
// Create directory structure
console.log(chalk.dim('\nCreating directory structure...'));
await fs.promises.mkdir(bmadDir, { recursive: true });
await fs.promises.mkdir(cfgDir, { recursive: true });
await fs.promises.mkdir(path.join(bmadDir, 'core'), { recursive: true });
await fs.promises.mkdir(path.join(bmadDir, 'custom', 'agents'), { recursive: true });
await fs.promises.mkdir(path.join(cfgDir, 'agents'), { recursive: true });
await fs.promises.mkdir(path.join(cfgDir, 'custom', 'agents'), { recursive: true });
// Create core config.yaml file
const coreConfigFile = {
'# CORE Module Configuration': 'Generated by BMAD Agent Installer',
Version: require(path.join(__dirname, '../../../package.json')).version,
Date: new Date().toISOString(),
bmad_folder: bmadFolderName,
...coreAnswers,
};
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
await fs.promises.writeFile(coreConfigPath, yaml.dump(coreConfigFile), 'utf8');
// Create manifest.yaml with complete structure
const manifest = {
version: require(path.join(__dirname, '../../../package.json')).version,
date: new Date().toISOString(),
user_name: coreAnswers.user_name,
communication_language: coreAnswers.communication_language,
document_output_language: coreAnswers.document_output_language,
output_folder: coreAnswers.output_folder,
install_user_docs: coreAnswers.install_user_docs,
bmad_folder: bmadFolderName,
modules: ['core'],
ides: selectedIdes,
custom_agents: [],
};
const manifestPath = path.join(cfgDir, 'manifest.yaml');
await fs.promises.writeFile(manifestPath, yaml.dump(manifest), 'utf8');
// Create empty manifests
const agentManifestPath = path.join(cfgDir, 'agent-manifest.csv');
await fs.promises.writeFile(agentManifestPath, 'type,subtype,name,path,display_name,description,author,version,tags\n', 'utf8');
// Setup IDE configurations
if (selectedIdes.length > 0) {
console.log(chalk.dim('\nSetting up IDE configurations...'));
const { IdeManager } = require('../installers/lib/ide/manager');
const ideManager = new IdeManager();
for (const ide of selectedIdes) {
await ideManager.setup(ide, projectDir, bmadDir, {
selectedModules: ['core'],
skipModuleInstall: false,
verbose: false,
preCollectedConfig: coreAnswers,
});
}
}
console.log(chalk.green('\n✓ BMAD core infrastructure initialized'));
console.log(chalk.dim(` BMAD folder: ${bmadDir}`));
console.log(chalk.dim(` Core config: ${coreConfigPath}`));
console.log(chalk.dim(` Manifest: ${manifestPath}`));
if (selectedIdes.length > 0) {
console.log(chalk.dim(` IDEs configured: ${selectedIdes.join(', ')}`));
}
return {
projectRoot: projectDir,
bmadFolder: bmadDir,
cfgFolder: cfgDir,
manifestFile: agentManifestPath,
ides: selectedIdes,
};
}
module.exports = {
command: 'agent-install',
description: 'Install and compile BMAD agents with personalization',
@@ -196,12 +314,55 @@ module.exports = {
// If no target specified, prompt for it
if (targetDir) {
// If target provided via --destination, check if it's a project root and adjust
// Check if target has BMAD infrastructure
const otherProject = detectBmadProject(targetDir);
if (otherProject && !targetDir.includes('agents')) {
// User specified project root, redirect to custom agents folder
targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
console.log(chalk.dim(` Auto-selecting custom agents folder: ${targetDir}`));
if (!otherProject) {
// No BMAD infrastructure found - offer to initialize
console.log(chalk.yellow(`\n⚠️ No BMAD infrastructure found in: ${targetDir}`));
const initResponse = await inquirer.prompt([
{
type: 'confirm',
name: 'initialize',
message: 'Initialize BMAD core infrastructure here? (Choose No for direct installation)',
default: true,
},
]);
if (initResponse.initialize) {
// Initialize BMAD core
targetDir = path.resolve(targetDir);
await initializeBmadCore(targetDir, '.bmad');
// Set targetDir to the custom agents folder
targetDir = path.join(targetDir, '.bmad', 'custom', 'agents');
console.log(chalk.dim(` Agent will be installed to: ${targetDir}`));
} else {
// User declined - keep original targetDir
console.log(chalk.yellow(` Installing agent directly to: ${targetDir}`));
}
} else if (otherProject && !targetDir.includes('agents')) {
console.log(chalk.yellow(`\n⚠️ Path is inside BMAD project: ${otherProject.projectRoot}`));
const projectChoice = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'Choose installation method:',
choices: [
{ name: `Install to BMAD's custom agents folder (${otherProject.bmadFolder}/custom/agents)`, value: 'bmad' },
{ name: `Install directly to specified path (${targetDir})`, value: 'direct' },
],
default: 'bmad',
},
]);
if (projectChoice.choice === 'bmad') {
targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
console.log(chalk.dim(` Installing to BMAD custom agents folder: ${targetDir}`));
} else {
console.log(chalk.yellow(` Installing directly to: ${targetDir}`));
}
}
} else {
const rl = readline.createInterface({
@@ -214,38 +375,72 @@ module.exports = {
// Option 1: Current project's custom agents folder
const currentCustom = path.join(config.bmadFolder, 'custom', 'agents');
console.log(` 1. Current project: ${chalk.dim(currentCustom)}`);
// Option 2: Specify another project path
console.log(` 2. Another project (enter path)`);
console.log(` 2. Enter path directly (e.g., /Users/brianmadison/dev/test)`);
const choice = await new Promise((resolve) => {
rl.question('\n Select option (1 or path): ', resolve);
rl.question('\n Select option (1 or 2): ', resolve);
});
if (choice.trim() === '1' || choice.trim() === '') {
targetDir = currentCustom;
} else if (choice.trim() === '2') {
const projectPath = await new Promise((resolve) => {
rl.question(' Project path: ', resolve);
const userPath = await new Promise((resolve) => {
rl.question(' Enter path: ', resolve);
});
// Detect if it's a BMAD project and use its custom folder
const otherProject = detectBmadProject(path.resolve(projectPath));
const otherProject = detectBmadProject(path.resolve(userPath));
if (otherProject) {
targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
console.log(chalk.dim(` Found BMAD project, using: ${targetDir}`));
console.log(chalk.yellow(`\n⚠️ Path is inside BMAD project: ${otherProject.projectRoot}`));
const projectChoice = await inquirer.prompt([
{
type: 'list',
name: 'choice',
message: 'Choose installation method:',
choices: [
{ name: `Install to BMAD's custom agents folder (${otherProject.bmadFolder}/custom/agents)`, value: 'bmad' },
{ name: `Install directly to specified path (${userPath})`, value: 'direct' },
],
default: 'bmad',
},
]);
if (projectChoice.choice === 'bmad') {
targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
console.log(chalk.dim(` Installing to BMAD custom agents folder: ${targetDir}`));
} else {
targetDir = path.resolve(userPath);
console.log(chalk.yellow(` Installing directly to: ${targetDir}`));
}
} else {
targetDir = path.resolve(projectPath);
// No BMAD found - offer to initialize
console.log(chalk.yellow(`\n⚠️ No BMAD infrastructure found in: ${userPath}`));
const initResponse = await inquirer.prompt([
{
type: 'confirm',
name: 'initialize',
message: 'Initialize BMAD core infrastructure here? (Choose No for direct installation)',
default: true,
},
]);
if (initResponse.initialize) {
await initializeBmadCore(path.resolve(userPath), '.bmad');
targetDir = path.join(path.resolve(userPath), '.bmad', 'custom', 'agents');
console.log(chalk.dim(` Agent will be installed to: ${targetDir}`));
} else {
// User declined - create the directory and install directly
targetDir = path.resolve(userPath);
console.log(chalk.yellow(` Installing agent directly to: ${targetDir}`));
}
}
} else {
// User entered a path directly
const otherProject = detectBmadProject(path.resolve(choice));
if (otherProject) {
targetDir = path.join(otherProject.bmadFolder, 'custom', 'agents');
console.log(chalk.dim(` Found BMAD project, using: ${targetDir}`));
} else {
targetDir = path.resolve(choice);
}
console.log(chalk.red(' Invalid selection. Please choose 1 or 2.'));
rl.close();
process.exit(1);
}
rl.close();