UNPKG

@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
#!/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();