// ============================================================================ // AI Testing Suite - Interactive CLI // Modern animated interface with enquirer prompts and styled panels // ============================================================================ import chalk from 'chalk'; import boxen from 'boxen'; import figures from 'figures'; import Enquirer from 'enquirer'; import { CliMode, AgentName, SuiteConfig } from './types'; import { loadConfig, validateConfig, resolveProjectPath } from './config'; import { executeWorkflow, executeCustomPipeline } from './graph/workflow'; import { logger } from './utils/logger'; // ── Banner ─────────────────────────────────────────────────────────────────── const BANNER_CONTENT = [ '', chalk.bold.redBright('AI TESTING SUITE'), '', chalk.gray('Multi-Agent LangGraph Orchestration'), chalk.gray('Automated Testing for Node.js / TypeScript'), '', `${chalk.cyan(figures.star)} ${chalk.cyan('8 Agents')} ${chalk.gray('|')} ${chalk.cyan('OWASP Top 10')} ${chalk.gray('|')} ${chalk.cyan('Zero-Day Scans')} ${chalk.gray('|')} ${chalk.cyan('100% Coverage')} ${chalk.cyan(figures.star)}`, '', ].join('\n'); const BANNER = boxen(BANNER_CONTENT, { padding: { top: 0, bottom: 0, left: 3, right: 3 }, margin: { top: 1, bottom: 1, left: 0, right: 0 }, borderStyle: 'double', borderColor: 'red', textAlignment: 'center', }); // ── Interactive CLI ────────────────────────────────────────────────────────── export async function startInteractiveCli(configOverrides: Partial = {}): Promise { console.clear(); console.log(BANNER); const config = loadConfig(configOverrides); const errors = validateConfig(config); if (errors.length > 0) { logger.warning('Configuration issues:'); for (const err of errors) { logger.error(` ${err}`); } logger.newline(); } let running = true; while (running) { try { const enquirer = new Enquirer(); const response: any = await enquirer.prompt({ type: 'select', name: 'mode', message: 'Select a mode', choices: [ { name: 'full', message: `${chalk.cyan(figures.play)} Full Pipeline`, hint: 'Complete analysis, tests & security' }, { name: 'analyze', message: `${chalk.blue(figures.info)} Analyze Only`, hint: 'Project structure & code analysis' }, { name: 'generate', message: `${chalk.green(figures.play)} Generate Tests`, hint: 'Analysis + create & review tests' }, { name: 'run', message: `${chalk.redBright(figures.pointer)} Run Tests`, hint: 'Execute existing tests' }, { name: 'security', message: `${chalk.red(figures.warning)} Security Scan`, hint: 'Security audit only' }, { name: 'custom', message: `${chalk.magenta(figures.star)} Custom Pipeline`, hint: 'Choose custom agent combination' }, { name: 'report', message: `${chalk.white(figures.circleFilled)} Report`, hint: 'Create report' }, { name: 'config', message: `${chalk.gray(figures.hamburger || '=')} Configuration`, hint: 'View/modify settings' }, { name: 'exit', message: `${chalk.gray(figures.cross)} Exit`, hint: '' }, ], }); switch (response.mode) { case 'full': await runMode('full', config); break; case 'analyze': await runMode('analyze', config); break; case 'generate': await runMode('generate', config); break; case 'run': await runMode('run', config); break; case 'security': await runMode('security', config); break; case 'custom': await handleCustomPipeline(config); break; case 'report': await runMode('report', config); break; case 'config': await handleConfig(config); break; case 'exit': running = false; break; } } catch { // User pressed Ctrl+C or ESC running = false; } if (running) { console.log(''); console.clear(); console.log(BANNER); } } logger.info('AI Testing Suite exited.'); } // ── Custom Pipeline Selection ──────────────────────────────────────────────── async function handleCustomPipeline(config: SuiteConfig): Promise { try { const enquirer = new Enquirer(); const response: any = await enquirer.prompt({ type: 'multiselect', name: 'agents', message: 'Select agents (space to toggle, enter to confirm)', choices: [ { name: 'scanner', message: `${chalk.cyan(figures.pointer)} Scanner`, hint: 'Scan project structure' }, { name: 'analyzer', message: `${chalk.blue(figures.info)} Analyzer`, hint: 'Deep code analysis' }, { name: 'strategist', message: `${chalk.magenta(figures.star)} Strategist`, hint: 'Plan test strategy' }, { name: 'writer', message: `${chalk.green(figures.play)} Writer`, hint: 'Write tests' }, { name: 'reviewer', message: `${chalk.yellow(figures.bullet)} Reviewer`, hint: 'Review tests' }, { name: 'runner', message: `${chalk.redBright(figures.pointer)} Runner`, hint: 'Execute tests' }, { name: 'security', message: `${chalk.red(figures.warning)} Security`, hint: 'Security audit' }, { name: 'reporter', message: `${chalk.white(figures.circleFilled)} Reporter`, hint: 'Generate reports' }, ], }); const agents: AgentName[] = response.agents; if (agents.length === 0) { logger.error('No agents selected.'); } else { const pipelineStr = agents.map(a => chalk.cyan(a.charAt(0).toUpperCase() + a.slice(1)) ).join(chalk.gray(` ${figures.arrowRight} `)); logger.info(`Pipeline: ${pipelineStr}`); await runCustomPipeline(agents, config); } } catch { logger.warning('Selection cancelled.'); } } // ── Configuration ──────────────────────────────────────────────────────────── async function handleConfig(config: SuiteConfig): Promise { showConfig(config); try { const enquirer = new Enquirer(); const response: any = await enquirer.prompt({ type: 'input', name: 'path', message: 'New project path (Enter = keep current)', initial: config.projectPath, }); if (response.path && response.path !== config.projectPath) { config.projectPath = resolveProjectPath(response.path); logger.success(`Project path changed: ${config.projectPath}`); } } catch { // User cancelled } } function showConfig(config: SuiteConfig): void { const lines = [ `${chalk.gray('Project Path:')} ${chalk.white(config.projectPath)}`, `${chalk.gray('Output Dir:')} ${chalk.white(config.outputDir)}`, `${chalk.gray('Reports Dir:')} ${chalk.white(config.reportsDir)}`, `${chalk.gray('LLM Provider:')} ${chalk.white(config.llmProvider)}`, `${chalk.gray('LLM Model:')} ${chalk.white(config.llmModel)}`, `${chalk.gray('API Key:')} ${chalk.white(config.apiKey ? '****' + config.apiKey.slice(-4) : 'NOT SET')}`, `${chalk.gray('Test Types:')} ${chalk.white(config.testTypes.join(', '))}`, `${chalk.gray('Security Depth:')} ${chalk.white(config.securityScanDepth)}`, `${chalk.gray('Zero-Day Check:')} ${chalk.white(String(config.checkZeroDay))}`, `${chalk.gray('OWASP Top 10:')} ${chalk.white(String(config.checkOwaspTop10))}`, `${chalk.gray('Verbose:')} ${chalk.white(String(config.verbose))}`, ].join('\n'); console.log(boxen(lines, { title: 'Configuration', titleAlignment: 'center', padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 1, left: 2, right: 0 }, borderStyle: 'round', borderColor: 'cyan', })); } // ── Mode Execution ─────────────────────────────────────────────────────────── async function runMode(mode: CliMode, config: SuiteConfig): Promise { const projectPath = config.projectPath; logger.info(`Starting ${mode.toUpperCase()} mode for: ${projectPath}`); logger.newline(); try { const state = await executeWorkflow(projectPath, config, mode); if (state.status === 'completed') { logger.success('Workflow completed successfully!'); } else { logger.error('Workflow ended with errors.'); } } catch (error) { const errMsg = error instanceof Error ? error.message : String(error); logger.error(`Workflow error: ${errMsg}`); } } async function runCustomPipeline(agents: AgentName[], config: SuiteConfig): Promise { try { const state = await executeCustomPipeline(agents, config.projectPath, config); if (state.status === 'completed') { logger.success('Custom pipeline completed successfully!'); } else { logger.error('Custom pipeline ended with errors.'); } } catch (error) { const errMsg = error instanceof Error ? error.message : String(error); logger.error(`Pipeline error: ${errMsg}`); } }