#!/usr/bin/env node const { program } = require('commander'); const path = require('node:path'); const fs = require('node:fs'); const fsPromises = require('node:fs').promises; const yaml = require('js-yaml'); const chalk = require('chalk').default || require('chalk'); const inquirer = require('inquirer').default || require('inquirer'); const semver = require('semver'); const https = require('node:https'); // Handle both execution contexts (from root via npx or from installer directory) let version; let installer; let packageName; try { // Try installer context first (when run from tools/installer/) version = require('../package.json').version; packageName = require('../package.json').name; installer = require('../lib/installer'); } catch (error) { // Fall back to root context (when run via npx from GitHub) console.log(`Installer context not found (${error.message}), trying root context...`); try { version = require('../../../package.json').version; installer = require('../../../tools/installer/lib/installer'); } catch (error) { console.error( 'Error: Could not load required modules. Please ensure you are running from the correct directory.', ); console.error('Debug info:', { __dirname, cwd: process.cwd(), error: error.message, }); process.exit(1); } } // Next Method Context Manager class NextMethodContext { constructor() { this.contextFile = path.join(process.cwd(), '.next-context.json'); this.context = this.loadContext(); } loadContext() { try { if (fs.existsSync(this.contextFile)) { const data = fs.readFileSync(this.contextFile, 'utf8'); return JSON.parse(data); } } catch (error) { console.log('Creating new context...'); } return { currentPhase: 'start', projectType: null, lastAction: null, suggestions: [], history: [], metadata: {} }; } saveContext() { try { fs.writeFileSync(this.contextFile, JSON.stringify(this.context, null, 2)); } catch (error) { console.error('Warning: Could not save context:', error.message); } } updateContext(updates) { this.context = { ...this.context, ...updates }; this.saveContext(); } addToHistory(action, result) { this.context.history.push({ timestamp: new Date().toISOString(), action, result, phase: this.context.currentPhase }); this.saveContext(); } getNextSuggestions() { const suggestions = []; if (!this.context.projectType) { suggestions.push({ id: 'detect-project', title: 'Detect Project Type', description: 'Analyze current directory to determine project type', action: 'detect' }); } if (this.context.projectType === 'greenfield') { suggestions.push({ id: 'create-project', title: 'Create New Project', description: 'Start a new project from scratch', action: 'create' }); } if (this.context.projectType === 'brownfield') { suggestions.push({ id: 'analyze-existing', title: 'Analyze Existing Project', description: 'Analyze current codebase and requirements', action: 'analyze' }); } if (this.context.lastAction === 'create' || this.context.lastAction === 'analyze') { suggestions.push({ id: 'generate-requirements', title: 'Generate Requirements', description: 'Create user stories and requirements', action: 'requirements' }); } if (this.context.lastAction === 'requirements') { suggestions.push({ id: 'design-architecture', title: 'Design Architecture', description: 'Create technical architecture and design', action: 'architecture' }); } if (this.context.lastAction === 'architecture') { suggestions.push({ id: 'implement-features', title: 'Implement Features', description: 'Start coding and implementation', action: 'implement' }); } if (this.context.lastAction === 'implement') { suggestions.push({ id: 'test-and-validate', title: 'Test and Validate', description: 'Run tests and validate implementation', action: 'test' }); } if (this.context.lastAction === 'test') { suggestions.push({ id: 'deploy', title: 'Deploy', description: 'Deploy the application', action: 'deploy' }); } // Always show these options suggestions.push({ id: 'custom-command', title: 'Run Custom Command', description: 'Execute a specific BMAD command', action: 'custom' }); suggestions.push({ id: 'show-status', title: 'Show Project Status', description: 'Display current project context and progress', action: 'status' }); return suggestions; } } const nextContext = new NextMethodContext(); program .version(version) .description('Next Method - Iterative AI-driven development with BMAD-METHODβ„’'); program .command('next') .description('Get next suggested action and execute it') .option('-a, --auto', 'Automatically execute the most logical next step') .option('-s, --suggest', 'Only show suggestions without executing') .action(async (options) => { try { const suggestions = nextContext.getNextSuggestions(); if (suggestions.length === 0) { console.log(chalk.green('πŸŽ‰ Project completed! All phases finished.')); return; } if (options.suggest) { console.log(chalk.blue('\nπŸ“‹ Available next steps:')); suggestions.forEach((suggestion, index) => { console.log(chalk.cyan(` ${index + 1}. ${suggestion.title}`)); console.log(chalk.gray(` ${suggestion.description}`)); }); return; } if (options.auto) { // Auto-execute the first suggestion const suggestion = suggestions[0]; console.log(chalk.blue(`πŸ€– Auto-executing: ${suggestion.title}`)); await executeSuggestion(suggestion); return; } // Interactive mode console.log(chalk.blue('\nπŸš€ What would you like to do next?')); const choices = suggestions.map((suggestion, index) => ({ name: `${suggestion.title} - ${suggestion.description}`, value: suggestion.id, short: suggestion.title })); const { selectedAction } = await inquirer.prompt([ { type: 'list', name: 'selectedAction', message: 'Choose the next step:', choices, pageSize: 10 } ]); const suggestion = suggestions.find(s => s.id === selectedAction); if (suggestion) { await executeSuggestion(suggestion); } } catch (error) { console.error(chalk.red('Next command failed:'), error.message); process.exit(1); } }); async function executeSuggestion(suggestion) { console.log(chalk.green(`\n▢️ Executing: ${suggestion.title}`)); try { let result; switch (suggestion.action) { case 'detect': result = await detectProjectType(); break; case 'create': result = await createProject(); break; case 'analyze': result = await analyzeProject(); break; case 'requirements': result = await generateRequirements(); break; case 'architecture': result = await designArchitecture(); break; case 'implement': result = await implementFeatures(); break; case 'test': result = await testAndValidate(); break; case 'deploy': result = await deployProject(); break; case 'custom': result = await runCustomCommand(); break; case 'status': result = await showStatus(); break; default: console.log(chalk.yellow(`Unknown action: ${suggestion.action}`)); return; } nextContext.addToHistory(suggestion.action, result); nextContext.updateContext({ lastAction: suggestion.action }); console.log(chalk.green(`βœ… ${suggestion.title} completed successfully!`)); // Show next suggestions const nextSuggestions = nextContext.getNextSuggestions(); if (nextSuggestions.length > 0) { console.log(chalk.blue('\nπŸ’‘ Next suggested steps:')); nextSuggestions.slice(0, 3).forEach((s, index) => { console.log(chalk.cyan(` ${index + 1}. ${s.title}`)); }); console.log(chalk.gray('\nRun "next" to continue or "next --suggest" to see all options')); } } catch (error) { console.error(chalk.red(`❌ ${suggestion.title} failed:`), error.message); nextContext.addToHistory(suggestion.action, { error: error.message }); } } async function detectProjectType() { // Analyze current directory to determine project type const files = await fsPromises.readdir(process.cwd()); const hasPackageJson = files.includes('package.json'); const hasGit = files.includes('.git'); const hasSourceFiles = files.some(f => f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.py') || f.endsWith('.java')); let projectType = 'empty'; if (hasSourceFiles && hasPackageJson) { projectType = 'brownfield'; } else if (hasGit && hasSourceFiles) { projectType = 'brownfield'; } else if (hasGit || hasSourceFiles) { projectType = 'greenfield'; } nextContext.updateContext({ projectType }); return { type: projectType, files: files.filter(f => !f.startsWith('.') && !f.includes('node_modules')) }; } async function createProject() { console.log(chalk.blue('Creating new project...')); // This would integrate with BMAD-METHOD's project creation return { status: 'project_created' }; } async function analyzeProject() { console.log(chalk.blue('Analyzing existing project...')); // This would integrate with BMAD-METHOD's analysis tools return { status: 'project_analyzed' }; } async function generateRequirements() { console.log(chalk.blue('Generating requirements...')); // This would integrate with BMAD-METHOD's requirements generation return { status: 'requirements_generated' }; } async function designArchitecture() { console.log(chalk.blue('Designing architecture...')); // This would integrate with BMAD-METHOD's architecture tools return { status: 'architecture_designed' }; } async function implementFeatures() { console.log(chalk.blue('Implementing features...')); // This would integrate with BMAD-METHOD's implementation tools return { status: 'features_implemented' }; } async function testAndValidate() { console.log(chalk.blue('Testing and validating...')); // This would integrate with BMAD-METHOD's testing tools return { status: 'testing_completed' }; } async function deployProject() { console.log(chalk.blue('Deploying project...')); // This would integrate with BMAD-METHOD's deployment tools return { status: 'deployment_completed' }; } async function runCustomCommand() { const { command } = await inquirer.prompt([ { type: 'input', name: 'command', message: 'Enter BMAD command to execute:', default: 'bmad install' } ]); console.log(chalk.blue(`Executing: ${command}`)); // This would execute the BMAD command return { command, status: 'executed' }; } async function showStatus() { console.log(chalk.blue('\nπŸ“Š Project Status:')); console.log(chalk.cyan(` Current Phase: ${nextContext.context.currentPhase}`)); console.log(chalk.cyan(` Project Type: ${nextContext.context.projectType || 'Not determined'}`)); console.log(chalk.cyan(` Last Action: ${nextContext.context.lastAction || 'None'}`)); if (nextContext.context.history.length > 0) { console.log(chalk.blue('\nπŸ“ Recent Actions:')); nextContext.context.history.slice(-5).forEach((entry, index) => { const status = entry.result?.error ? '❌' : 'βœ…'; console.log(chalk.gray(` ${status} ${entry.action} (${entry.timestamp})`)); }); } return { status: 'status_displayed' }; } // Add other commands that wrap BMAD functionality program .command('install') .description('Install BMAD Method (wraps bmad install)') .option('-f, --full', 'Install complete BMad Method') .option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)') .option('-d, --directory ', 'Installation directory') .option('-i, --ide ', 'Configure for specific IDE(s)') .option('-e, --expansion-packs ', 'Install specific expansion packs') .action(async (options) => { try { // Forward to BMAD installer const { execSync } = require('child_process'); const args = process.argv.slice(3).join(' '); execSync(`node "${path.join(__dirname, 'bmad.js')}" install ${args}`, { stdio: 'inherit', cwd: process.cwd() }); } catch (error) { console.error(chalk.red('Installation failed:'), error.message); process.exit(1); } }); program .command('update') .description('Update existing BMAD installation (wraps bmad update)') .option('--force', 'Force update, overwriting modified files') .option('--dry-run', 'Show what would be updated without making changes') .action(async (options) => { try { const { execSync } = require('child_process'); const args = process.argv.slice(3).join(' '); execSync(`node "${path.join(__dirname, 'bmad.js')}" update ${args}`, { stdio: 'inherit', cwd: process.cwd() }); } catch (error) { console.error(chalk.red('Update failed:'), error.message); process.exit(1); } }); program .command('status') .description('Show current project status and context') .action(async () => { await showStatus(); }); program .command('reset') .description('Reset the Next Method context') .action(async () => { const { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'Are you sure you want to reset the Next Method context?', default: false } ]); if (confirm) { try { fs.unlinkSync(nextContext.contextFile); nextContext.context = nextContext.loadContext(); console.log(chalk.green('βœ… Context reset successfully!')); } catch (error) { console.log(chalk.blue('Context file not found, already reset.')); } } }); program.parse();