2025-06-08 16:18:35 -05:00
|
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
const yaml = require('js-yaml');
|
|
|
|
|
|
|
|
|
|
|
|
// Colors for console output
|
|
|
|
|
|
const colors = {
|
|
|
|
|
|
reset: '\x1b[0m',
|
|
|
|
|
|
bright: '\x1b[1m',
|
|
|
|
|
|
green: '\x1b[32m',
|
|
|
|
|
|
red: '\x1b[31m',
|
|
|
|
|
|
yellow: '\x1b[33m',
|
|
|
|
|
|
blue: '\x1b[34m',
|
|
|
|
|
|
cyan: '\x1b[36m'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function log(message, color = 'reset') {
|
|
|
|
|
|
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function error(message) {
|
|
|
|
|
|
console.error(`${colors.red}❌ Error: ${message}${colors.reset}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function success(message) {
|
|
|
|
|
|
console.log(`${colors.green}✅ ${message}${colors.reset}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function info(message) {
|
|
|
|
|
|
console.log(`${colors.blue}ℹ️ ${message}${colors.reset}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function installExpansionPack(packName) {
|
|
|
|
|
|
const expansionPackPath = path.join(__dirname, '..', 'expansion-packs', packName);
|
|
|
|
|
|
const manifestPath = path.join(expansionPackPath, 'manifest.yml');
|
|
|
|
|
|
|
|
|
|
|
|
// Check if expansion pack exists
|
|
|
|
|
|
if (!fs.existsSync(expansionPackPath)) {
|
|
|
|
|
|
error(`Expansion pack '${packName}' not found`);
|
|
|
|
|
|
log('\nAvailable expansion packs:', 'cyan');
|
|
|
|
|
|
const packsDir = path.join(__dirname, '..', 'expansion-packs');
|
|
|
|
|
|
const packs = fs.readdirSync(packsDir)
|
|
|
|
|
|
.filter(f => fs.statSync(path.join(packsDir, f)).isDirectory())
|
|
|
|
|
|
.filter(f => f !== 'README.md');
|
|
|
|
|
|
packs.forEach(pack => log(` - ${pack}`, 'cyan'));
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load manifest
|
|
|
|
|
|
if (!fs.existsSync(manifestPath)) {
|
|
|
|
|
|
error(`Manifest file not found for expansion pack '${packName}'`);
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let manifest;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const manifestContent = fs.readFileSync(manifestPath, 'utf8');
|
|
|
|
|
|
manifest = yaml.load(manifestContent);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error(`Failed to parse manifest: ${e.message}`);
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log(`\n${colors.bright}Installing ${manifest.name} v${manifest.version}${colors.reset}`, 'bright');
|
|
|
|
|
|
log(`${manifest.description}\n`, 'cyan');
|
|
|
|
|
|
|
|
|
|
|
|
// Create directories if needed
|
|
|
|
|
|
const projectRoot = path.join(__dirname, '..');
|
|
|
|
|
|
const bmadCore = path.join(projectRoot, 'bmad-core');
|
|
|
|
|
|
|
|
|
|
|
|
// Install files
|
|
|
|
|
|
let installedCount = 0;
|
|
|
|
|
|
let skippedCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for (const fileMapping of manifest.files) {
|
|
|
|
|
|
const sourcePath = path.join(expansionPackPath, fileMapping.source);
|
|
|
|
|
|
const destPath = path.join(projectRoot, fileMapping.destination);
|
|
|
|
|
|
const destDir = path.dirname(destPath);
|
|
|
|
|
|
|
|
|
|
|
|
// Create destination directory if it doesn't exist
|
|
|
|
|
|
if (!fs.existsSync(destDir)) {
|
|
|
|
|
|
fs.mkdirSync(destDir, { recursive: true });
|
|
|
|
|
|
info(`Created directory: ${path.relative(projectRoot, destDir)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check if file already exists
|
|
|
|
|
|
if (fs.existsSync(destPath)) {
|
|
|
|
|
|
const response = await promptUser(`File ${path.relative(projectRoot, destPath)} already exists. Overwrite? (y/N): `);
|
|
|
|
|
|
if (response.toLowerCase() !== 'y') {
|
|
|
|
|
|
info(`Skipped: ${fileMapping.source}`);
|
|
|
|
|
|
skippedCount++;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Copy file
|
|
|
|
|
|
try {
|
|
|
|
|
|
fs.copyFileSync(sourcePath, destPath);
|
|
|
|
|
|
success(`Installed: ${fileMapping.source} → ${path.relative(projectRoot, destPath)}`);
|
|
|
|
|
|
installedCount++;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error(`Failed to install ${fileMapping.source}: ${e.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update team configurations
|
|
|
|
|
|
if (manifest.team_updates && manifest.team_updates.length > 0) {
|
|
|
|
|
|
log('\nUpdating team configurations...', 'yellow');
|
|
|
|
|
|
|
|
|
|
|
|
for (const update of manifest.team_updates) {
|
2025-06-08 17:34:38 -05:00
|
|
|
|
// Try new location first (agent-teams), then fallback to old location (agents)
|
|
|
|
|
|
let teamPath = path.join(projectRoot, 'agent-teams', update.team);
|
|
|
|
|
|
if (!fs.existsSync(teamPath)) {
|
|
|
|
|
|
teamPath = path.join(projectRoot, 'agents', update.team);
|
|
|
|
|
|
}
|
2025-06-08 16:18:35 -05:00
|
|
|
|
|
|
|
|
|
|
if (fs.existsSync(teamPath)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let teamConfig = yaml.load(fs.readFileSync(teamPath, 'utf8'));
|
|
|
|
|
|
|
|
|
|
|
|
if (!teamConfig.agents) {
|
|
|
|
|
|
teamConfig.agents = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!teamConfig.agents.includes(update.add_agent)) {
|
|
|
|
|
|
teamConfig.agents.push(update.add_agent);
|
|
|
|
|
|
fs.writeFileSync(teamPath, yaml.dump(teamConfig));
|
|
|
|
|
|
success(`Updated ${update.team} with ${update.add_agent} agent`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
info(`${update.team} already includes ${update.add_agent} agent`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error(`Failed to update ${update.team}: ${e.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Show summary
|
|
|
|
|
|
log(`\n${colors.bright}Installation Summary${colors.reset}`, 'bright');
|
|
|
|
|
|
log(`Files installed: ${installedCount}`, 'green');
|
|
|
|
|
|
if (skippedCount > 0) {
|
|
|
|
|
|
log(`Files skipped: ${skippedCount}`, 'yellow');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Show post-install message
|
|
|
|
|
|
if (manifest.post_install_message) {
|
|
|
|
|
|
log(`\n${colors.bright}Next Steps:${colors.reset}`, 'bright');
|
|
|
|
|
|
log(manifest.post_install_message, 'cyan');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Remind to rebuild
|
|
|
|
|
|
log('\nRemember to rebuild bundles:', 'yellow');
|
|
|
|
|
|
log(' npm run build', 'yellow');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function promptUser(question) {
|
|
|
|
|
|
const readline = require('readline');
|
|
|
|
|
|
const rl = readline.createInterface({
|
|
|
|
|
|
input: process.stdin,
|
|
|
|
|
|
output: process.stdout
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
|
rl.question(question, answer => {
|
|
|
|
|
|
rl.close();
|
|
|
|
|
|
resolve(answer);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Main execution
|
|
|
|
|
|
const packName = process.argv[2];
|
|
|
|
|
|
|
|
|
|
|
|
if (!packName) {
|
|
|
|
|
|
log(`${colors.bright}BMAD Method Expansion Pack Installer${colors.reset}\n`, 'bright');
|
|
|
|
|
|
log('Usage: node install-expansion-pack.js <pack-name>', 'yellow');
|
|
|
|
|
|
log('\nExample:', 'cyan');
|
|
|
|
|
|
log(' node install-expansion-pack.js infrastructure', 'cyan');
|
|
|
|
|
|
|
|
|
|
|
|
log('\nAvailable expansion packs:', 'cyan');
|
|
|
|
|
|
const packsDir = path.join(__dirname, '..', 'expansion-packs');
|
|
|
|
|
|
if (fs.existsSync(packsDir)) {
|
|
|
|
|
|
const packs = fs.readdirSync(packsDir)
|
|
|
|
|
|
.filter(f => fs.statSync(path.join(packsDir, f)).isDirectory())
|
|
|
|
|
|
.filter(f => f !== 'README.md');
|
|
|
|
|
|
packs.forEach(pack => {
|
|
|
|
|
|
const manifestPath = path.join(packsDir, pack, 'manifest.yml');
|
|
|
|
|
|
if (fs.existsSync(manifestPath)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const manifest = yaml.load(fs.readFileSync(manifestPath, 'utf8'));
|
|
|
|
|
|
log(` - ${pack}: ${manifest.description}`, 'cyan');
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
log(` - ${pack}`, 'cyan');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
process.exit(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
installExpansionPack(packName).catch(err => {
|
|
|
|
|
|
error(`Installation failed: ${err.message}`);
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
});
|