@cloudkinetix/bmad-enhanced
Version:
Cloud-Kinetix enhanced fork of BMAD-METHOD - Breakthrough Method of Agile AI-driven Development with robust versioning and unified validation.
735 lines (616 loc) • 26.2 kB
JavaScript
#!/usr/bin/env node
/**
* Cloud Kinetix BMAD Enhanced - Ultra-Simple Thin Wrapper
* Installs upstream BMAD with --full, then adds our expansion packs on top
*/
const { program } = require("commander");
const path = require("path");
const fs = require("fs-extra");
// Removed unused execSync import
const packageJson = require("../../package.json");
// Simple console output helpers
const log = (msg) => console.log(msg);
const success = (msg) => console.log(`✅ ${msg}`);
const error = (msg) => console.log(`❌ ${msg}`);
const info = (msg) => console.log(`ℹ️ ${msg}`);
program
.name("bmad-enhanced")
.description("Cloud Kinetix BMAD Enhanced - Simple thin wrapper over upstream BMAD")
.version(packageJson.version);
/**
* Ultra-simple installer: upstream BMAD + our expansion packs
*/
program
.command("install")
.description("Install BMAD Enhanced with all IDEs configured")
.option("-d, --directory <path>", "Installation directory", process.cwd())
.option("-f, --full", "Full installation with all IDEs and expansion packs")
.option("--expansion-packs <packs...>", "Expansion packs to install")
.option("--expansion-only", "Only install expansion packs (skip core installation)")
.option("--web-bundles", "Include pre-built web bundles for ChatGPT, Claude, Gemini")
.option("--web-bundles-dir <path>", "Directory for web bundles (defaults to ./web-bundles)")
.option("--web-bundle-type <type>", "Web bundle type: all, agents, teams, expansion-packs", "all")
.option("--verbose", "Verbose output")
.option("--non-interactive", "Run in non-interactive mode (skip prompts)")
.option("--ide <ide...>", "Specific IDEs to configure (defaults to all)")
.action(async (options) => {
const startTime = Date.now();
try {
const targetDir = path.resolve(options.directory);
log("🚀 Starting BMAD Enhanced installation...");
log("📋 Strategy: Full BMAD installation with all IDEs pre-configured");
log(`📁 Target directory: ${targetDir}`);
// --full implies non-interactive mode
const nonInteractive = options.nonInteractive || options.full;
if (nonInteractive) {
log("🤖 Running in non-interactive mode");
}
// Track if we need to configure github-copilot later
let includeGithubCopilot = false;
// Phase 1: Install upstream BMAD with --full (skip if expansion-only)
if (!options.expansionOnly) {
log("\n🔽 Installing BMAD Enhanced (full installation with all IDEs)...");
includeGithubCopilot = await installUpstreamBMAD(targetDir, options.verbose, options.expansionPacks, nonInteractive, options.ide);
} else {
log("\n📦 Installing expansion packs only (skipping core installation)...");
}
// Phase 2: Install CK expansion packs on top
const allExpansionPacks = options.expansionPacks || await detectAllExpansionPacks();
const ckPacks = allExpansionPacks.filter(p => p.startsWith('ck-'));
if (ckPacks.length > 0) {
log("\n🌟 Installing Cloud-Kinetix expansion packs...");
// For expansion-only mode, we need to pass the IDE info
const ideList = options.ide || [];
await installCKExpansionPacks(targetDir, ckPacks, options.verbose, ideList);
success("Cloud-Kinetix expansion packs installed!");
}
// Phase 3: Configure github-copilot with defaults if it was excluded
if (nonInteractive && includeGithubCopilot) {
log("\n⚙️ Configuring GitHub Copilot with default settings...");
await configureGitHubCopilot(targetDir, options.verbose);
success("GitHub Copilot configured with defaults!");
}
// Phase 4: Install web bundles if requested
if (options.webBundles) {
log("\n📦 Installing web bundles...");
await installWebBundles(targetDir, options);
success("Web bundles installed!");
}
success("BMAD Enhanced installation complete!");
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
log("\n" + "=".repeat(60));
success("🎉 BMAD Enhanced installation complete!");
log("\n📊 What you got:");
log(" 📦 Full BMAD framework with all agents");
log(" 🎯 All supported IDEs configured automatically");
// Show expansion packs if any were installed
const installedPacks = options.expansionPacks || (await detectAllExpansionPacks());
if (installedPacks && installedPacks.length > 0) {
const ckPacks = installedPacks.filter(p => p.startsWith('ck-'));
const bmadPacks = installedPacks.filter(p => p.startsWith('bmad-'));
log(` 🧩 ${installedPacks.length} expansion packs included:`);
if (ckPacks.length > 0) log(` • Cloud-Kinetix: ${ckPacks.join(', ')}`);
if (bmadPacks.length > 0) log(` • Upstream BMAD: ${bmadPacks.join(', ')}`);
}
// Show web bundles if installed
if (options.webBundles) {
const webBundlesPath = options.webBundlesDir || path.join(targetDir, 'web-bundles');
log(` 📦 Web bundles: ${webBundlesPath}`);
}
log(` ⏱️ Completed in ${duration}s`);
log("\n💡 To add expansion packs:");
log(" npx @cloudkinetix/bmad-enhanced install --expansion-packs <pack-id>");
// Add web bundles help if not already installed
if (!options.webBundles) {
log("\n🌐 To add web bundles:");
log(" npx @cloudkinetix/bmad-enhanced install --web-bundles --web-bundles-dir ./web-bundles");
log(" Options: --web-bundle-type all|agents|teams|expansion-packs");
}
log("\n📦 Available expansion packs:");
log(" • ck-jira-integration - JIRA bidirectional sync");
log(" • ck-llm-agent-dev - AI agent development toolkit");
log(" • ck-parallel-dev - Parallel story development");
log(" • ck-gitlab-cicd-automation - GitLab CI/CD automation");
log(" • bmad-infrastructure-devops - Infrastructure & DevOps");
log(" • bmad-creator-tools - BMAD creator tools");
log(" • bmad-2d-phaser-game-dev - 2D Phaser game development");
log(" • bmad-2d-unity-game-dev - 2D Unity game development");
log("\n🎯 Ready to use:");
log(" • All BMAD agents available in your IDE");
log(" • Run 'bmad-enhanced status' to check installation");
log(" • Add expansion packs as needed with --expansion-packs flag");
if (!options.webBundles) {
log(" • Add web bundles with --web-bundles for ChatGPT, Claude, Gemini");
}
} catch (err) {
error(`Installation failed: ${err.message}`);
if (options.verbose) {
console.error(err.stack);
}
process.exit(1);
}
});
/**
* Detect available expansion packs (both CK and upstream BMAD)
*/
async function detectAllExpansionPacks() {
try {
const expansionDir = path.join(__dirname, '../../expansion-packs');
const entries = await fs.readdir(expansionDir, { withFileTypes: true });
// Get ALL directories (both ck-* and bmad-*)
const allPacks = entries
.filter(entry => entry.isDirectory() && (entry.name.startsWith('ck-') || entry.name.startsWith('bmad-')))
.map(entry => entry.name);
return allPacks;
} catch (error) {
console.error('Error detecting expansion packs:', error);
return [];
}
}
/**
* Install upstream BMAD with --full option and all IDEs
*/
async function installUpstreamBMAD(targetDir, verbose = false, expansionPacks = [], nonInteractive = false, specificIDEs = []) {
try {
// Get list of all available IDEs from config
const configLoader = require('../../tools/installer/lib/config-loader');
const config = await configLoader.load();
const ideConfigs = config['ide-configurations'] || {};
let availableIDEs = Object.keys(ideConfigs);
// Auto-detect ALL expansion packs if none specified
if (!expansionPacks || expansionPacks.length === 0) {
const allPacks = await detectAllExpansionPacks();
if (allPacks.length > 0) {
expansionPacks = allPacks;
if (verbose) {
const ckPacks = allPacks.filter(p => p.startsWith('ck-'));
const bmadPacks = allPacks.filter(p => p.startsWith('bmad-'));
log(`Auto-detected ${allPacks.length} expansion packs:`);
if (ckPacks.length > 0) log(` Cloud-Kinetix (${ckPacks.length}): ${ckPacks.join(', ')}`);
if (bmadPacks.length > 0) log(` Upstream BMAD (${bmadPacks.length}): ${bmadPacks.join(', ')}`);
}
}
}
// If specific IDEs provided, use those
if (specificIDEs && specificIDEs.length > 0) {
availableIDEs = specificIDEs.filter(ide => availableIDEs.includes(ide));
}
// In non-interactive mode, configure github-copilot with defaults
const includeGithubCopilot = availableIDEs.includes('github-copilot');
if (nonInteractive && includeGithubCopilot) {
// Keep github-copilot but mark it for default configuration
if (verbose) {
log('Note: Configuring github-copilot with default settings in non-interactive mode');
}
}
if (verbose) {
log(`Found ${availableIDEs.length} IDEs: ${availableIDEs.join(', ')}`);
}
// Build command args
const args = ['bmad-method@latest', 'install', '--full', '--directory', targetDir];
// Add all available IDEs (excluding github-copilot in non-interactive mode)
const idesToInstall = nonInteractive ? availableIDEs.filter(ide => ide !== 'github-copilot') : availableIDEs;
for (const ide of idesToInstall) {
args.push('--ide', ide);
}
// Add expansion packs if provided
if (expansionPacks && expansionPacks.length > 0) {
for (const pack of expansionPacks) {
args.push('--expansion-packs', pack);
}
}
// Simple approach: spawn npx directly using node's spawn
const { spawn } = require('child_process');
// Check if we're running from the local development directory
const isLocalDev = __dirname.includes('bmad-enhanced/ck-layer/bin');
// Build the command
let command;
let commandArgs;
if (isLocalDev) {
// Running locally - use the installer library directly
try {
const installerLib = require('../../tools/installer/lib/installer');
// Build options object for the installer
const installerOptions = {
directory: targetDir,
full: true,
ide: idesToInstall,
expansionPacks: expansionPacks,
nonInteractive: true
};
if (verbose) log('Local development - using upstream installer library directly');
// Call the installer directly
await installerLib.install(installerOptions);
// Return early since we handled the installation
return includeGithubCopilot;
} catch (error) {
// If direct library call fails, fall back to npx
if (verbose) log('Direct installer call failed, falling back to npx');
command = 'npx';
commandArgs = args;
}
} else {
// Running from NPM installation - use npx
command = 'npx';
commandArgs = args;
}
if (verbose) {
log(`Running: ${command} ${commandArgs.join(' ')}`);
}
// Set up environment
const env = { ...process.env };
if (nonInteractive) {
env.CI = 'true';
env.BMAD_NON_INTERACTIVE = 'true';
}
// Return a promise for the spawn process
return new Promise((resolve, reject) => {
const child = spawn(command, commandArgs, {
cwd: targetDir,
env: env,
stdio: verbose ? 'inherit' : 'pipe',
shell: true // Use shell to resolve npx path properly
});
let stdout = '';
let stderr = '';
if (!verbose) {
if (child.stdout) child.stdout.on('data', (data) => { stdout += data; });
if (child.stderr) child.stderr.on('data', (data) => { stderr += data; });
}
child.on('close', (code) => {
if (code === 0) {
if (verbose) {
info("Upstream BMAD installation succeeded");
}
resolve(includeGithubCopilot);
} else {
reject(new Error(`Installation failed with code ${code}: ${stderr || 'Unknown error'}`));
}
});
child.on('error', (err) => {
if (err.code === 'ENOENT') {
reject(new Error('npx command not found. Please ensure Node.js and npm are properly installed.'));
} else {
reject(new Error(`Installation failed: ${err.message}`));
}
});
});
} catch (error) {
console.error('Error in installUpstreamBMAD:', error);
throw error;
}
}
/**
* Install CK expansion packs using discovery & enhancement pattern
*/
async function installCKExpansionPacks(targetDir, ckPacks, verbose = false, ideList = []) {
// Import the new analyzer and enhancer
const UpstreamAnalyzer = require('../lib/upstream-analyzer');
const IDEEnhancer = require('../lib/ide-enhancer');
// Analyze what upstream created
const analyzer = new UpstreamAnalyzer(targetDir);
const analysis = await analyzer.analyzeInstallation();
if (verbose) {
log(` 🔍 Discovery: Found ${analysis.configuredIDEs.length} configured IDEs`);
}
// Copy CK expansion packs
const sourceBase = path.join(__dirname, '../../expansion-packs');
for (const pack of ckPacks) {
try {
const sourcePath = path.join(sourceBase, pack);
const destPath = path.join(targetDir, `.${pack}`);
if (await fs.pathExists(sourcePath)) {
if (verbose) {
log(` 📦 Installing ${pack}...`);
}
// Copy the expansion pack
await fs.copy(sourcePath, destPath, { overwrite: true });
// Get CK agents from this pack
const agentsPath = path.join(destPath, 'agents');
if (await fs.pathExists(agentsPath)) {
const agentFiles = await fs.readdir(agentsPath);
const agents = agentFiles.filter(f => f.endsWith('.md')).map(f => f.replace('.md', ''));
if (verbose) {
log(` 🎯 Adding ${agents.length} agents to configured IDEs...`);
}
// Enhance each configured IDE with CK agents
const enhancer = new IDEEnhancer(targetDir, verbose);
for (const ideInfo of analysis.configuredIDEs) {
// Skip if specific IDEs were requested and this isn't one of them
if (ideList && ideList.length > 0 && !ideList.includes(ideInfo.id)) {
continue;
}
await enhancer.enhanceIDE(ideInfo, agents, pack);
}
}
if (verbose) {
success(` ✅ Installed ${pack}`);
}
} else {
if (verbose) {
log(` ⚠️ Warning: ${pack} not found in package, skipping...`);
}
}
} catch (error) {
console.error(` ❌ Error installing ${pack}: ${error.message}`);
}
}
}
/**
* Configure GitHub Copilot with default settings in non-interactive mode
*/
async function configureGitHubCopilot(targetDir, verbose = false) {
try {
// Create .vscode directory and settings
const vscodeDir = path.join(targetDir, '.vscode');
const settingsPath = path.join(vscodeDir, 'settings.json');
await fs.ensureDir(vscodeDir);
// Read existing settings if they exist
let existingSettings = {};
if (await fs.pathExists(settingsPath)) {
try {
const content = await fs.readFile(settingsPath, 'utf8');
existingSettings = JSON.parse(content);
} catch (error) {
// Ignore parse errors
}
}
// Apply recommended BMad defaults
const 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
};
// Merge settings
const mergedSettings = { ...existingSettings, ...bmadSettings };
// Write settings
await fs.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
if (verbose) {
log(" ✅ Created .vscode/settings.json with GitHub Copilot defaults");
}
// Create .github/chatmodes directory and agent files
const chatmodesDir = path.join(targetDir, '.github', 'chatmodes');
await fs.ensureDir(chatmodesDir);
// Get all agents from core and expansion packs
const coreAgentsPath = path.join(targetDir, '.bmad-core', 'agents');
let allAgents = [];
if (await fs.pathExists(coreAgentsPath)) {
const coreAgents = await fs.readdir(coreAgentsPath);
allAgents = coreAgents.filter(f => f.endsWith('.md')).map(f => ({
id: f.replace('.md', ''),
path: path.join(coreAgentsPath, f),
source: 'core'
}));
}
// Add CK expansion pack agents
const ckPacks = await fs.readdir(targetDir);
for (const dir of ckPacks) {
if (dir.startsWith('.ck-')) {
const agentsPath = path.join(targetDir, dir, 'agents');
if (await fs.pathExists(agentsPath)) {
const packAgents = await fs.readdir(agentsPath);
const agents = packAgents.filter(f => f.endsWith('.md')).map(f => ({
id: f.replace('.md', ''),
path: path.join(agentsPath, f),
source: dir
}));
allAgents = allAgents.concat(agents);
}
}
}
// Create chatmode files for each agent
for (const agent of allAgents) {
const agentContent = await fs.readFile(agent.path, 'utf8');
const chatmodePath = path.join(chatmodesDir, `${agent.id}.chatmode.md`);
// Extract description from YAML front matter
let description = `Activates the ${agent.id} agent persona.`;
const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
if (yamlMatch) {
const whenToUseMatch = yamlMatch[1].match(/whenToUse:\s*"(.*?)"/);
if (whenToUseMatch && whenToUseMatch[1]) {
description = whenToUseMatch[1];
}
}
// Create chatmode content
let chatmodeContent = `---
description: "${description.replace(/"/g, '\\"')}"
tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'usages', 'editFiles', 'runCommands', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure']
---
`;
chatmodeContent += agentContent;
await fs.writeFile(chatmodePath, chatmodeContent);
if (verbose) {
log(` ✅ Created ${agent.id}.chatmode.md`);
}
}
if (verbose) {
log(` ✅ Created ${allAgents.length} GitHub Copilot chat modes`);
}
} catch (error) {
if (verbose) {
console.error(` ⚠️ Warning: Could not configure GitHub Copilot: ${error.message}`);
}
}
}
/**
* Simple backup command
*/
program
.command("backup")
.description("Create backup of BMAD installation")
.option("-d, --directory <path>", "Installation directory", process.cwd())
.action(async (options) => {
try {
const targetDir = path.resolve(options.directory);
log("💾 Creating backup...");
const BackupManager = require('../lib/bmad-backup-manager');
const backupManager = new BackupManager(targetDir);
const result = await backupManager.createBackups();
if (result && result.success) {
success(`Backup created: ${result.message}`);
} else {
success("Backup completed");
}
} catch (err) {
error(`Backup failed: ${err.message}`);
}
});
/**
* Simple status command
*/
program
.command("status")
.description("Show installation status")
.option("-d, --directory <path>", "Installation directory", process.cwd())
.action(async (options) => {
try {
const targetDir = path.resolve(options.directory);
log("📊 BMAD Enhanced Status\\n");
// Check BMAD core
const bmadCorePath = path.join(targetDir, '.bmad-core');
if (await fs.pathExists(bmadCorePath)) {
success("BMAD Core: Installed");
// Count agents
const agentsPath = path.join(bmadCorePath, 'agents');
if (await fs.pathExists(agentsPath)) {
const agentFiles = await fs.readdir(agentsPath);
const agentCount = agentFiles.filter(f => f.endsWith('.md')).length;
log(` Agents: ${agentCount} available`);
}
} else {
error("BMAD Core: Not installed");
}
// Check expansion packs (both upstream and CK)
const allDirs = await fs.readdir(targetDir).catch(() => []);
const bmadPacks = allDirs.filter(dir => dir.startsWith('.bmad-'));
const ckPacks = allDirs.filter(dir => dir.startsWith('.ck-'));
const allPacks = [...bmadPacks, ...ckPacks];
if (allPacks.length > 0) {
success(`Expansion Packs: ${allPacks.length} installed`);
if (bmadPacks.length > 0) {
log(" Upstream BMAD packs:");
bmadPacks.forEach(pack => {
log(` - ${pack.replace(/^\./, '')}`);
});
}
if (ckPacks.length > 0) {
log(" Cloud-Kinetix packs:");
ckPacks.forEach(pack => {
log(` - ${pack.replace(/^\./, '')}`);
});
}
} else {
error("Expansion Packs: None found");
}
// Check IDE configs using discovery system
try {
const UpstreamAnalyzer = require('../lib/upstream-analyzer');
const analyzer = new UpstreamAnalyzer(targetDir);
const analysis = await analyzer.analyzeInstallation();
if (analysis.configuredIDEs.length > 0) {
success(`IDE Configurations: Found ${analysis.configuredIDEs.length} IDE configs`);
analysis.configuredIDEs.forEach(ide => {
log(` - ${ide.name} (${ide.id})`);
});
} else {
error("IDE Configurations: None found");
}
} catch (analyzerError) {
// Fallback to basic check if analyzer fails
log(" ⚠️ Using fallback IDE detection");
const basicIDEs = ['.cursor', '.claude', '.windsurf', '.trae', '.cline', '.gemini', '.roomodes', '.vscode', '.github/chatmodes', '.kilocodemodes'];
let basicIDECount = 0;
for (const ideConfig of basicIDEs) {
if (await fs.pathExists(path.join(targetDir, ideConfig))) {
basicIDECount++;
}
}
if (basicIDECount > 0) {
success(`IDE Configurations: Found ${basicIDECount} IDE configs (basic detection)`);
} else {
error("IDE Configurations: None found");
}
}
} catch (err) {
error(`Status check failed: ${err.message}`);
}
});
/**
* List available expansion packs
*/
program
.command("list:expansions")
.description("List available expansion packs")
.action(async () => {
try {
log("📦 Available Expansion Packs\n");
const allPacks = await detectAllExpansionPacks();
const ckPacks = allPacks.filter(p => p.startsWith('ck-'));
const bmadPacks = allPacks.filter(p => p.startsWith('bmad-'));
if (ckPacks.length > 0) {
log("Cloud-Kinetix Expansion Packs:");
ckPacks.forEach(pack => {
log(` • ${pack}`);
});
}
if (bmadPacks.length > 0) {
log("\nUpstream BMAD Expansion Packs:");
bmadPacks.forEach(pack => {
log(` • ${pack}`);
});
}
if (allPacks.length === 0) {
log("No expansion packs found.");
}
} catch (err) {
error(`Failed to list expansion packs: ${err.message}`);
}
});
/**
* Install web bundles for standalone use in web AI platforms
*/
async function installWebBundles(targetDir, options) {
const fs = require('fs-extra');
// Default web bundles directory
const webBundlesDir = options.webBundlesDir || path.join(targetDir, 'web-bundles');
const bundleType = options.webBundleType || 'all';
// Source directory for pre-built bundles
const distDir = path.join(__dirname, '../../dist');
try {
// Ensure web bundles directory exists
await fs.ensureDir(webBundlesDir);
let copiedCount = 0;
if (bundleType === 'all') {
// Copy entire dist directory structure
await fs.copy(distDir, webBundlesDir);
const items = await fs.readdir(webBundlesDir, { withFileTypes: true });
copiedCount = items.length;
log(` ✅ Installed all web bundles to: ${webBundlesDir}`);
} else {
// Copy specific bundle types
const bundleTypes = bundleType.split(',');
for (const type of bundleTypes) {
const sourceDir = path.join(distDir, type.trim());
if (await fs.pathExists(sourceDir)) {
const targetSubDir = path.join(webBundlesDir, type.trim());
await fs.copy(sourceDir, targetSubDir);
copiedCount++;
log(` ✅ Copied ${type.trim()} bundles`);
}
}
log(` ✅ Installed ${copiedCount} bundle type(s) to: ${webBundlesDir}`);
}
// Show information about web bundles
log(`\n📦 Web Bundle Info:`);
log(` 📁 Location: ${webBundlesDir}`);
log(` 🎯 Type: ${bundleType}`);
log(` 📄 Use these standalone files with ChatGPT, Claude, Gemini, or other web AI platforms`);
log(` 🔗 Each bundle contains complete agent context and instructions`);
} catch (error) {
throw new Error(`Failed to install web bundles: ${error.message}`);
}
}
program.parse();