UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

318 lines 11.2 kB
/** * Progress Reporter for Orchestration Template CLI * Inspired by the cache-sync-enhanced progress reporting */ import chalk from 'chalk'; import * as readline from 'readline'; /** * Base progress reporter with TTY-aware display */ export class ProgressReporter { startTime = 0; phases = new Map(); currentPhase = ''; isTTY; lastLineCount = 0; constructor() { this.isTTY = process.stdout.isTTY || false; } /** * Start a new operation */ startOperation(operation) { this.startTime = Date.now(); this.phases.clear(); this.currentPhase = operation; if (this.isTTY) { console.log(chalk.blue.bold(`\n🚀 ${operation}\n`)); } else { console.log(`\n[START] ${operation}\n`); } } /** * Update progress for current phase */ updateProgress(update) { // Track phase timing if (!this.phases.has(update.phase)) { this.phases.set(update.phase, { start: Date.now() }); } if (this.isTTY) { this.displayProgressBar(update); } else { this.displayProgressText(update); } // Mark phase complete if at 100% if (update.percent >= 100) { const phase = this.phases.get(update.phase); if (phase && !phase.end) { phase.end = Date.now(); } } } /** * Display progress bar for TTY environments */ displayProgressBar(update) { // Clear previous lines if (this.lastLineCount > 0) { for (let i = 0; i < this.lastLineCount; i++) { readline.moveCursor(process.stdout, 0, -1); readline.clearLine(process.stdout, 0); } } const barWidth = 40; const filled = Math.round((update.percent / 100) * barWidth); const empty = barWidth - filled; const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty)); const lines = [ chalk.cyan(`📋 ${update.phase}`), `${bar} ${chalk.yellow(`${update.percent}%`)}`, chalk.gray(` ${update.message}`), chalk.gray(` Progress: ${update.current}/${update.total}`) ]; if (update.subPhase) { lines.push(chalk.gray(` ${update.subPhase}`)); } lines.forEach(line => console.log(line)); this.lastLineCount = lines.length; } /** * Display progress text for non-TTY environments */ displayProgressText(update) { console.log(`[${update.phase}] ${update.percent}% - ${update.message} (${update.current}/${update.total})`); } /** * Report an error */ error(error) { const message = error instanceof Error ? error.message : error; if (this.isTTY) { console.log(chalk.red.bold(`\n❌ Error: ${message}`)); } else { console.log(`\n[ERROR] ${message}`); } } /** * Report a warning */ warning(message) { if (this.isTTY) { console.log(chalk.yellow(`\n⚠️ Warning: ${message}`)); } else { console.log(`\n[WARNING] ${message}`); } } /** * Complete the operation */ completeOperation(result) { const duration = Date.now() - this.startTime; if (this.isTTY) { // Clear any remaining progress display if (this.lastLineCount > 0) { for (let i = 0; i < this.lastLineCount; i++) { readline.moveCursor(process.stdout, 0, -1); readline.clearLine(process.stdout, 0); } } console.log('\n' + chalk.bold('━'.repeat(50))); if (result.success) { console.log(chalk.green.bold(`\n✅ ${result.message}`)); } else { console.log(chalk.red.bold(`\n❌ ${result.message}`)); } // Show metrics console.log(chalk.gray(`\n⏱️ Duration: ${this.formatDuration(duration)}`)); if (result.entityCount !== undefined) { console.log(chalk.gray(`📊 Entities processed: ${result.entityCount}`)); } // Show phase timings if (this.phases.size > 0) { console.log(chalk.gray(`\n📈 Phase Breakdown:`)); this.phases.forEach((timing, phase) => { const phaseDuration = (timing.end || Date.now()) - timing.start; console.log(chalk.gray(` • ${phase}: ${this.formatDuration(phaseDuration)}`)); }); } // Show warnings/errors if (result.warnings && result.warnings.length > 0) { console.log(chalk.yellow(`\n⚠️ Warnings (${result.warnings.length}):`)); result.warnings.forEach(w => console.log(chalk.yellow(` • ${w}`))); } if (result.errors && result.errors.length > 0) { console.log(chalk.red(`\n❌ Errors (${result.errors.length}):`)); result.errors.forEach(e => console.log(chalk.red(` • ${e}`))); } console.log('\n' + chalk.bold('━'.repeat(50)) + '\n'); } else { // Non-TTY output console.log(`\n[${result.success ? 'SUCCESS' : 'FAILED'}] ${result.message}`); console.log(`[DURATION] ${duration}ms`); if (result.entityCount !== undefined) { console.log(`[ENTITIES] ${result.entityCount}`); } if (result.warnings && result.warnings.length > 0) { result.warnings.forEach(w => console.log(`[WARNING] ${w}`)); } if (result.errors && result.errors.length > 0) { result.errors.forEach(e => console.log(`[ERROR] ${e}`)); } } } /** * Format duration in human-readable format */ formatDuration(ms) { if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; const minutes = Math.floor(ms / 60000); const seconds = Math.floor((ms % 60000) / 1000); return `${minutes}m ${seconds}s`; } /** * Show a spinner for indeterminate progress */ showSpinner(message) { if (!this.isTTY) { console.log(`[PROCESSING] ${message}`); return () => { }; // No-op for non-TTY } const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; let i = 0; const interval = setInterval(() => { readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); process.stdout.write(chalk.cyan(`${frames[i]} ${message}`)); i = (i + 1) % frames.length; }, 80); return () => { clearInterval(interval); readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); }; } } /** * Validation-specific progress reporter */ export class ValidationProgressReporter extends ProgressReporter { validationErrors = new Map(); validationWarnings = new Map(); /** * Report validation progress */ reportValidation(stepId, status, message) { if (status === 'validating') { if (this.isTTY) { process.stdout.write(chalk.gray(` • Validating ${stepId}...`)); } else { console.log(`[VALIDATING] ${stepId}`); } } else { if (this.isTTY) { readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); switch (status) { case 'passed': console.log(chalk.green(` ✓ ${stepId}`)); break; case 'failed': console.log(chalk.red(` ✗ ${stepId}: ${message}`)); break; case 'warning': console.log(chalk.yellow(` ⚠ ${stepId}: ${message}`)); break; } } else { console.log(`[${status.toUpperCase()}] ${stepId}${message ? `: ${message}` : ''}`); } } // Track errors and warnings if (status === 'failed' && message) { if (!this.validationErrors.has(stepId)) { this.validationErrors.set(stepId, []); } this.validationErrors.get(stepId).push(message); } else if (status === 'warning' && message) { if (!this.validationWarnings.has(stepId)) { this.validationWarnings.set(stepId, []); } this.validationWarnings.get(stepId).push(message); } } /** * Get validation summary */ getValidationSummary() { const errors = []; const warnings = []; this.validationErrors.forEach((messages, stepId) => { messages.forEach(msg => errors.push(`${stepId}: ${msg}`)); }); this.validationWarnings.forEach((messages, stepId) => { messages.forEach(msg => warnings.push(`${stepId}: ${msg}`)); }); return { errors, warnings }; } } /** * Execution-specific progress reporter */ export class ExecutionProgressReporter extends ProgressReporter { executedSteps = []; /** * Report step execution */ reportStepExecution(stepId, entityType, status, duration) { if (status === 'starting') { if (this.isTTY) { console.log(chalk.cyan(`\n▶️ Executing step: ${stepId}`)); console.log(chalk.gray(` Creating ${entityType}...`)); } else { console.log(`[EXECUTING] ${stepId} (${entityType})`); } } else { const success = status === 'completed'; if (duration !== undefined) { this.executedSteps.push({ stepId, entityType, duration, success }); } if (this.isTTY) { if (success) { console.log(chalk.green(` ✓ Completed in ${this.formatDuration(duration || 0)}`)); } else { console.log(chalk.red(` ✗ Failed after ${this.formatDuration(duration || 0)}`)); } } else { console.log(`[${status.toUpperCase()}] ${stepId} (${duration}ms)`); } } } /** * Get execution summary */ getExecutionSummary() { const totalSteps = this.executedSteps.length; const successfulSteps = this.executedSteps.filter(s => s.success).length; const totalDuration = this.executedSteps.reduce((sum, s) => sum + s.duration, 0); return { totalSteps, successfulSteps, totalDuration }; } } //# sourceMappingURL=ProgressReporter.js.map