adpa-enterprise-framework-automation
Version:
Modular, standards-compliant Node.js/TypeScript automation framework for enterprise requirements, project, and data management. Provides CLI and API for BABOK v3, PMBOK 7th Edition, and DMBOK 2.0 (in progress). Production-ready Express.js API with TypeSpe
755 lines ⢠30.8 kB
JavaScript
/**
* Enhanced CLI Menu Navigation System
*
* Provides an improved interactive CLI interface using inquirer for better UX.
* This enhances the existing menu system with arrow key navigation, better prompts,
* and improved error handling.
*
* @version 1.0.0
* @author ADPA Team
*/
import inquirer from 'inquirer';
import { EventEmitter } from 'events';
import { CommandIntegrationService } from './CommandIntegration.js';
import { InteractiveErrorHandler } from './InteractiveErrorHandler.js';
/**
* Enhanced Menu Navigation System using Inquirer
*/
export class EnhancedMenuNavigation extends EventEmitter {
flows = new Map();
state;
commandIntegration;
isRunning = false;
constructor() {
super();
this.state = {
stepHistory: [],
flowData: {},
globalData: {}
};
this.commandIntegration = new CommandIntegrationService();
this.initializeFlows();
}
/**
* Start the enhanced navigation system
*/
async start(flowId = 'main-menu') {
if (this.isRunning) {
console.log('ā ļø Navigation system is already running');
return;
}
this.isRunning = true;
console.log('š Starting Enhanced ADPA Interactive CLI...\n');
try {
await this.navigateToFlow(flowId);
}
catch (error) {
await this.handleNavigationError(error);
}
finally {
this.isRunning = false;
}
}
/**
* Stop the navigation system
*/
async stop() {
this.isRunning = false;
console.log('\nš Thank you for using ADPA Interactive CLI!');
}
/**
* Navigate to a specific flow
*/
async navigateToFlow(flowId, stepId) {
const flow = this.flows.get(flowId);
if (!flow) {
throw new Error(`Flow not found: ${flowId}`);
}
this.state.currentFlow = flowId;
this.state.flowData[flowId] = this.state.flowData[flowId] || {};
const startStep = stepId || flow.startStep || flow.steps[0]?.id;
if (startStep) {
await this.navigateToStep(startStep);
}
}
/**
* Navigate to a specific step within the current flow
*/
async navigateToStep(stepId) {
if (!this.state.currentFlow) {
throw new Error('No current flow set');
}
const flow = this.flows.get(this.state.currentFlow);
if (!flow) {
throw new Error(`Current flow not found: ${this.state.currentFlow}`);
}
const step = flow.steps.find(s => s.id === stepId);
if (!step) {
throw new Error(`Step not found: ${stepId}`);
}
// Add to history if not going back
if (this.state.currentStep !== stepId) {
this.state.stepHistory.push(stepId);
}
this.state.currentStep = stepId;
try {
await this.executeStep(step, flow);
}
catch (error) {
await this.handleStepError(error, step);
}
}
/**
* Go back to the previous step
*/
async goBack() {
if (this.state.stepHistory.length <= 1) {
// If no history or only current step, go to main menu
await this.navigateToFlow('main-menu');
return;
}
// Remove current step and go to previous
this.state.stepHistory.pop();
const previousStep = this.state.stepHistory[this.state.stepHistory.length - 1];
if (previousStep) {
this.state.currentStep = previousStep;
await this.navigateToStep(previousStep);
}
}
/**
* Execute a navigation step
*/
async executeStep(step, flow) {
// Clear screen and show header
console.clear();
this.renderStepHeader(step, flow);
// Process each choice in the step
const answers = {};
for (const choice of step.choices) {
// Check if this choice should be shown
if (choice.when && !choice.when(answers)) {
continue;
}
try {
const answer = await this.promptChoice(choice, answers);
answers[choice.name] = answer;
}
catch (error) {
if (error instanceof Error && error.message === 'USER_CANCELLED') {
await this.handleUserCancellation(step);
return;
}
throw error;
}
}
// Process the step completion
let result;
if (step.onComplete) {
result = await step.onComplete(answers);
}
else {
result = { action: 'continue', data: answers };
}
// Store step data
this.state.flowData[flow.id] = {
...this.state.flowData[flow.id],
[step.id]: answers
};
// Handle the result
await this.handleNavigationResult(result, step, flow);
}
/**
* Prompt user with a choice using inquirer
*/
async promptChoice(choice, previousAnswers) {
const prompt = {
...choice,
validate: choice.validate || this.getDefaultValidator(choice.type),
filter: choice.filter || this.getDefaultFilter(choice.type)
};
// Add navigation options for list choices
if (choice.type === 'list' && choice.choices) {
const enhancedChoices = [...choice.choices];
// Add separator and navigation options
enhancedChoices.push({ name: 'āāāāāāāāāāāāāāāāāāāāā', value: '__separator__', disabled: true }, { name: 'ā Back', value: '__back__', short: 'Back' }, { name: 'š Main Menu', value: '__home__', short: 'Home' }, { name: 'ā Help', value: '__help__', short: 'Help' }, { name: 'šŖ Exit', value: '__exit__', short: 'Exit' });
prompt.choices = enhancedChoices;
}
const answers = await inquirer.prompt([prompt]);
const answer = answers[choice.name];
// Handle special navigation commands
if (typeof answer === 'string') {
switch (answer) {
case '__separator__':
// Separator was selected, prompt again
return this.promptChoice(choice, previousAnswers);
case '__back__':
await this.goBack();
throw new Error('USER_CANCELLED');
case '__home__':
await this.navigateToFlow('main-menu');
throw new Error('USER_CANCELLED');
case '__help__':
await this.showContextualHelp();
return this.promptChoice(choice, previousAnswers);
case '__exit__':
await this.confirmExit();
throw new Error('USER_CANCELLED');
}
}
return answer;
}
/**
* Handle navigation result
*/
async handleNavigationResult(result, step, flow) {
switch (result.action) {
case 'continue':
// Find next step or complete flow
const currentIndex = flow.steps.findIndex(s => s.id === step.id);
if (currentIndex < flow.steps.length - 1) {
await this.navigateToStep(flow.steps[currentIndex + 1].id);
}
else {
await this.completeFlow(flow, result.data);
}
break;
case 'back':
await this.goBack();
break;
case 'navigate':
if (result.nextStep) {
await this.navigateToStep(result.nextStep);
}
break;
case 'exit':
await this.stop();
process.exit(0);
break;
case 'restart':
await this.navigateToFlow(flow.id);
break;
}
}
/**
* Complete a navigation flow
*/
async completeFlow(flow, data) {
if (flow.onComplete) {
await flow.onComplete(data);
}
// Show completion message
console.log(`\nā
${flow.title} completed successfully!`);
// Return to main menu
await this.pause();
await this.navigateToFlow('main-menu');
}
/**
* Render step header
*/
renderStepHeader(step, flow) {
const width = 70;
// Flow title
console.log('ā' + 'ā'.repeat(width - 2) + 'ā');
console.log(`ā ${flow.title.padEnd(width - 4)} ā`);
console.log('ā' + 'ā'.repeat(width - 2) + 'ā¤');
// Step title
console.log(`ā ${step.title.padEnd(width - 4)} ā`);
if (step.description) {
console.log('ā' + 'ā'.repeat(width - 2) + 'ā¤');
const lines = this.wrapText(step.description, width - 4);
lines.forEach(line => {
console.log(`ā ${line.padEnd(width - 4)} ā`);
});
}
console.log('ā' + 'ā'.repeat(width - 2) + 'ā');
console.log();
}
/**
* Wrap text to fit within specified width
*/
wrapText(text, width) {
const words = text.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
if ((currentLine + word).length <= width) {
currentLine += (currentLine ? ' ' : '') + word;
}
else {
if (currentLine)
lines.push(currentLine);
currentLine = word;
}
}
if (currentLine)
lines.push(currentLine);
return lines;
}
/**
* Get default validator for choice type
*/
getDefaultValidator(type) {
switch (type) {
case 'input':
return (input) => {
if (!input || input.trim().length === 0) {
return 'This field is required';
}
return true;
};
case 'password':
return (input) => {
if (!input || input.length < 3) {
return 'Password must be at least 3 characters';
}
return true;
};
default:
return () => true;
}
}
/**
* Get default filter for choice type
*/
getDefaultFilter(type) {
switch (type) {
case 'input':
return (input) => input.trim();
default:
return (input) => input;
}
}
/**
* Show contextual help
*/
async showContextualHelp() {
console.clear();
console.log('ā Navigation Help');
console.log('ā'.repeat(50));
console.log();
console.log('š§ Navigation:');
console.log(' ⢠Use arrow keys to navigate menu options');
console.log(' ⢠Press Enter to select an option');
console.log(' ⢠Use "ā Back" to go to previous step');
console.log(' ⢠Use "š Main Menu" to return to main menu');
console.log(' ⢠Use "šŖ Exit" to quit the application');
console.log();
console.log('š” Tips:');
console.log(' ⢠Look for disabled options - they show requirements');
console.log(' ⢠Use Ctrl+C to force quit at any time');
console.log(' ⢠Check system status for configuration issues');
console.log();
await this.pause();
}
/**
* Confirm exit
*/
async confirmExit() {
const answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirmExit',
message: 'Are you sure you want to exit?',
default: false
}]);
if (answer.confirmExit) {
await this.stop();
process.exit(0);
}
}
/**
* Handle user cancellation
*/
async handleUserCancellation(step) {
if (step.allowBack !== false) {
await this.goBack();
}
else {
await this.navigateToFlow('main-menu');
}
}
/**
* Handle navigation errors
*/
async handleNavigationError(error) {
const context = {
operation: 'navigation',
timestamp: new Date()
};
const interactiveError = InteractiveErrorHandler.handleUnknownError(error, context);
await InteractiveErrorHandler.displayError(interactiveError);
// Try to recover by going to main menu
try {
await this.navigateToFlow('main-menu');
}
catch (recoveryError) {
console.error('ā Failed to recover from navigation error');
await this.stop();
process.exit(1);
}
}
/**
* Handle step execution errors
*/
async handleStepError(error, step) {
const context = {
operation: `execute step ${step.id}`,
timestamp: new Date()
};
const interactiveError = InteractiveErrorHandler.handleUnknownError(error, context);
await InteractiveErrorHandler.displayError(interactiveError);
// Offer recovery options
const answer = await inquirer.prompt([{
type: 'list',
name: 'recovery',
message: 'How would you like to proceed?',
choices: [
{ name: 'Try Again', value: 'retry' },
{ name: 'Go Back', value: 'back' },
{ name: 'Main Menu', value: 'home' },
{ name: 'Exit', value: 'exit' }
]
}]);
switch (answer.recovery) {
case 'retry':
await this.navigateToStep(step.id);
break;
case 'back':
await this.goBack();
break;
case 'home':
await this.navigateToFlow('main-menu');
break;
case 'exit':
await this.stop();
process.exit(0);
break;
}
}
/**
* Pause execution and wait for user input
*/
async pause() {
await inquirer.prompt([{
type: 'input',
name: 'continue',
message: 'Press Enter to continue...',
validate: () => true
}]);
}
/**
* Initialize navigation flows
*/
initializeFlows() {
// Main Menu Flow
this.flows.set('main-menu', {
id: 'main-menu',
title: 'ADPA Interactive CLI - Main Menu',
description: 'Welcome to the Automated Document Processing & Analysis system',
steps: [{
id: 'main-selection',
title: 'Main Menu',
description: 'Select an option to get started',
choices: [{
type: 'list',
name: 'mainChoice',
message: 'What would you like to do?',
pageSize: 12,
choices: [
{ name: 'š Quick Start - Get started quickly', value: 'quick-start' },
{ name: 'š Document Generation - Generate project documents', value: 'document-generation' },
{ name: 'š¤ AI Configuration - Configure AI providers', value: 'ai-configuration' },
{ name: 'š Project Management - Project analysis tools', value: 'project-management' },
{ name: 'š Integrations - External system integrations', value: 'integrations' },
{ name: 'š Analytics & Feedback - Document analytics', value: 'analytics' },
{ name: 'āļø System Configuration - System settings', value: 'system-config' },
{ name: 'š Workspace Analysis - Analyze current workspace', value: 'workspace-analysis' },
{ name: 'ā Help & Documentation - User assistance', value: 'help' }
]
}],
onComplete: async (answers) => {
await this.navigateToFlow(answers.mainChoice);
return { action: 'continue' };
}
}]
});
// Quick Start Flow
this.flows.set('quick-start', {
id: 'quick-start',
title: 'Quick Start Wizard',
description: 'Get up and running with ADPA in just a few steps',
steps: [
{
id: 'welcome',
title: 'Welcome to Quick Start',
description: 'This wizard will help you set up ADPA and generate your first documents',
choices: [{
type: 'list',
name: 'action',
message: 'What would you like to do first?',
choices: [
{ name: 'š§ Environment Setup - Configure your development environment', value: 'setup' },
{ name: 'š Generate Core Documents - Create essential project documents', value: 'generate-core' },
{ name: 'šļø Project Charter Wizard - Step-by-step charter creation', value: 'project-charter' },
{ name: 'š„ Stakeholder Analysis - Analyze project stakeholders', value: 'stakeholder-analysis' },
{ name: 'š Risk Assessment - Perform risk analysis', value: 'risk-assessment' },
{ name: 'š Browse Templates - View available templates', value: 'browse-templates' }
]
}],
onComplete: async (answers) => {
return { action: 'navigate', nextStep: answers.action };
}
},
{
id: 'setup',
title: 'Environment Setup',
description: 'Configure your ADPA environment',
choices: [{
type: 'confirm',
name: 'runSetup',
message: 'Would you like to run the interactive setup wizard?',
default: true
}],
onComplete: async (answers) => {
if (answers.runSetup) {
console.log('\nš Starting environment setup...');
const result = await this.commandIntegration.executeCommand('setup', []);
if (result.success) {
console.log('ā
Environment setup completed successfully!');
}
else {
console.log('ā Environment setup failed. Please check the configuration.');
}
await this.pause();
}
return { action: 'back' };
}
},
{
id: 'generate-core',
title: 'Generate Core Documents',
description: 'Generate essential project documents to get started',
choices: [{
type: 'checkbox',
name: 'documents',
message: 'Select documents to generate:',
choices: [
{ name: 'Project Charter', value: 'project-charter', checked: true },
{ name: 'Stakeholder Register', value: 'stakeholder-register', checked: true },
{ name: 'Risk Management Plan', value: 'risk-management-plan', checked: true },
{ name: 'Business Case', value: 'business-case', checked: false },
{ name: 'Project Scope Statement', value: 'project-scope-statement', checked: false }
]
}],
onComplete: async (answers) => {
if (answers.documents && answers.documents.length > 0) {
console.log('\nš Generating selected documents...');
for (const doc of answers.documents) {
console.log(`š Generating ${doc}...`);
const result = await this.commandIntegration.executeCommand('generate', [doc]);
if (result.success) {
console.log(`ā
${doc} generated successfully`);
}
else {
console.log(`ā Failed to generate ${doc}`);
}
}
await this.pause();
}
return { action: 'back' };
}
}
]
});
// Document Generation Flow
this.flows.set('document-generation', {
id: 'document-generation',
title: 'Document Generation',
description: 'Generate professional project documents using AI',
steps: [{
id: 'generation-options',
title: 'Document Generation Options',
description: 'Choose how you want to generate documents',
choices: [{
type: 'list',
name: 'option',
message: 'Select generation method:',
choices: [
{ name: 'š Browse by Category - Browse templates by category', value: 'browse-categories' },
{ name: 'š Search Templates - Search for specific templates', value: 'search-templates' },
{ name: 'ā” Generate Single Document - Generate one document', value: 'single-document' },
{ name: 'š¦ Generate Category - Generate all documents in a category', value: 'category-generation' },
{ name: 'š Generate All Documents - Generate all available documents', value: 'generate-all' },
{ name: 'šÆ Custom Generation - Custom generation options', value: 'custom-generation' }
]
}],
onComplete: async (answers) => {
switch (answers.option) {
case 'search-templates':
await this.searchTemplatesInteractive();
break;
case 'single-document':
await this.singleDocumentGeneration();
break;
case 'generate-all':
await this.generateAllDocuments();
break;
default:
console.log(`š§ ${answers.option} is not yet implemented in this enhanced interface`);
await this.pause();
}
return { action: 'back' };
}
}]
});
// AI Configuration Flow
this.flows.set('ai-configuration', {
id: 'ai-configuration',
title: 'AI Provider Configuration',
description: 'Configure AI providers for document generation',
steps: [{
id: 'provider-selection',
title: 'AI Provider Selection',
description: 'Choose and configure your AI provider',
choices: [{
type: 'list',
name: 'provider',
message: 'Select AI provider to configure:',
choices: [
{ name: 'š¤ Google AI (Gemini) - Recommended for most users', value: 'google-ai' },
{ name: 'š§ OpenAI - GPT models', value: 'openai' },
{ name: 'āļø Azure OpenAI - Enterprise Azure integration', value: 'azure-openai' },
{ name: 'š Test Current Provider - Test existing configuration', value: 'test-provider' },
{ name: 'š Provider Status - View provider status and metrics', value: 'provider-status' }
]
}],
onComplete: async (answers) => {
switch (answers.provider) {
case 'test-provider':
console.log('\nš Testing AI provider connection...');
const result = await this.commandIntegration.executeCommand('validate', ['--ai-connection']);
if (result.success) {
console.log('ā
AI provider connection successful!');
}
else {
console.log('ā AI provider connection failed. Please check configuration.');
}
await this.pause();
break;
default:
console.log('\nš Configuring AI provider...');
const setupResult = await this.commandIntegration.executeCommand('setup', ['--provider', answers.provider]);
if (setupResult.success) {
console.log('ā
AI provider configured successfully!');
}
else {
console.log('ā AI provider configuration failed.');
}
await this.pause();
}
return { action: 'back' };
}
}]
});
}
/**
* Interactive template search
*/
async searchTemplatesInteractive() {
const answer = await inquirer.prompt([{
type: 'input',
name: 'searchTerm',
message: 'Enter search term (or press Enter to see all templates):',
validate: (input) => true // Allow empty input
}]);
console.log('\nš Searching templates...');
// This would integrate with the actual template system
console.log('\nš Available Templates:');
console.log('⢠Project Charter (project-charter)');
console.log('⢠Stakeholder Register (stakeholder-register)');
console.log('⢠Risk Management Plan (risk-management-plan)');
console.log('⢠Business Case (business-case)');
console.log('⢠... and 116 more templates');
await this.pause();
}
/**
* Single document generation
*/
async singleDocumentGeneration() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'templateName',
message: 'Enter template name:',
validate: (input) => {
if (!input || input.trim().length === 0) {
return 'Template name is required';
}
return true;
}
},
{
type: 'input',
name: 'context',
message: 'Enter context (optional):',
validate: () => true
}
]);
console.log('\nš Generating document...');
const args = [answers.templateName];
if (answers.context) {
args.push('--context', answers.context);
}
const result = await this.commandIntegration.executeCommand('generate', args);
if (result.success) {
console.log('ā
Document generated successfully!');
}
else {
console.log('ā Document generation failed.');
}
await this.pause();
}
/**
* Generate all documents
*/
async generateAllDocuments() {
const confirm = await inquirer.prompt([{
type: 'confirm',
name: 'confirmGenerate',
message: 'This will generate all 120+ templates. This may take several minutes. Continue?',
default: false
}]);
if (confirm.confirmGenerate) {
console.log('\nš Generating all documents...');
const result = await this.commandIntegration.executeCommand('generate-all', []);
if (result.success) {
console.log('ā
All documents generated successfully!');
}
else {
console.log('ā Document generation failed.');
}
await this.pause();
}
}
}
/**
* Factory function to create and start the enhanced navigation system
*/
export async function startEnhancedNavigation() {
const navigation = new EnhancedMenuNavigation();
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.log('\n\nš Shutting down...');
await navigation.stop();
process.exit(0);
});
try {
await navigation.start();
}
catch (error) {
console.error('ā Error starting enhanced navigation:', error);
await navigation.stop();
process.exit(1);
}
}
export default EnhancedMenuNavigation;
//# sourceMappingURL=EnhancedMenuNavigation.js.map