all.js
1 #!/usr/bin/env node 2 3 /** 4 * All Stages Orchestrator 5 * Runs the complete pipeline from keywords to replies 6 */ 7 8 import { runKeywordsStage } from './stages/keywords.js'; 9 import { runSerpsStage } from './stages/serps.js'; 10 import { runAssetsStage } from './stages/assets.js'; 11 import { runScoringStage } from './stages/scoring.js'; 12 import { runRescoringStage } from './stages/rescoring.js'; 13 import { runEnrichmentStage } from './stages/enrich.js'; 14 import { runProposalsStage } from './stages/proposals.js'; 15 import { runOutreachStage } from './stages/outreach.js'; 16 import { runFollowupGenerationStage } from './stages/followup-generator.js'; 17 import { runRepliesStage } from './stages/replies.js'; 18 import { parseFlags } from './utils/flag-parser.js'; 19 import { generatePipelineCompletion } from './utils/summary-generator.js'; 20 import Logger from './utils/logger.js'; 21 22 const logger = new Logger('all'); 23 24 /** 25 * Pipeline stages in execution order 26 */ 27 const STAGES = [ 28 { name: 'keywords', fn: runKeywordsStage, description: 'Keyword selection and prioritization' }, 29 { name: 'serps', fn: runSerpsStage, description: 'SERP scraping for selected keywords' }, 30 { name: 'assets', fn: runAssetsStage, description: 'Screenshot capture for sites' }, 31 { name: 'scoring', fn: runScoringStage, description: 'Initial AI conversion scoring' }, 32 { 33 name: 'rescoring', 34 fn: runRescoringStage, 35 description: 'Rescore low-scoring sites (B- and below)', 36 }, 37 { 38 name: 'enrich', 39 fn: runEnrichmentStage, 40 description: 'Enrich contact details from key pages', 41 }, 42 { name: 'proposals', fn: runProposalsStage, description: 'Generate personalized proposals' }, 43 { name: 'outreach', fn: runOutreachStage, description: 'Multi-channel outreach delivery' }, 44 { name: 'followup', fn: runFollowupGenerationStage, description: 'Generate follow-up messages for non-responders' }, 45 { name: 'replies', fn: runRepliesStage, description: 'Process inbound replies' }, 46 ]; 47 48 /** 49 * Run the complete pipeline 50 */ 51 async function runPipeline() { 52 const pipelineStartTime = Date.now(); 53 54 // Parse command line flags 55 const flags = parseFlags(); 56 const { skip, limit, force } = flags; 57 58 // Merge SKIP_STAGES from environment variable 59 const envSkipStages = (process.env.SKIP_STAGES || '') 60 .split(',') 61 .map(s => s.trim().toLowerCase()) 62 .filter(s => s.length > 0); 63 64 envSkipStages.forEach(stage => skip.add(stage)); 65 66 // Display banner 67 console.log('\n╔══════════════════════════════════════════════════════════════════════╗'); 68 console.log('║ 333 Method Automation Pipeline ║'); 69 console.log('╚══════════════════════════════════════════════════════════════════════╝\n'); 70 71 if (skip.size > 0) { 72 logger.warn(`Skipping stages: ${Array.from(skip).join(', ')}`); 73 } 74 75 if (limit) { 76 logger.info(`Limit: ${limit} items per stage`); 77 } 78 79 // Store results for final summary 80 const stageResults = []; 81 82 // Run each stage 83 for (const stage of STAGES) { 84 // Skip if requested 85 if (skip.has(stage.name)) { 86 logger.info(`\n⏭️ Skipping ${stage.name} stage`); 87 continue; 88 } 89 90 // Display stage header 91 console.log(`\n${'═'.repeat(70)}`); 92 console.log(`Stage: ${stage.name.toUpperCase()}`); 93 console.log(`Description: ${stage.description}`); 94 console.log(`${'═'.repeat(70)}\n`); 95 96 const stageStartTime = Date.now(); 97 98 try { 99 // Run stage with options 100 const options = { 101 limit: limit || undefined, 102 force: force || undefined, 103 }; 104 105 const stats = await stage.fn(options); 106 107 // Store results 108 stageResults.push({ 109 stage: stage.name, 110 stats, 111 duration: Date.now() - stageStartTime, 112 }); 113 114 // Check for errors 115 if (stats.failed > 0) { 116 logger.warn(`Stage completed with ${stats.failed} failures`); 117 } else { 118 logger.success(`Stage completed successfully`); 119 } 120 } catch (err) { 121 logger.error(`Stage failed: ${err.message}`, err); 122 123 // Store failed result 124 stageResults.push({ 125 stage: stage.name, 126 stats: { 127 processed: 0, 128 succeeded: 0, 129 failed: 1, 130 skipped: 0, 131 }, 132 duration: Date.now() - stageStartTime, 133 }); 134 135 // Decide whether to continue 136 if (!force) { 137 logger.error('Stopping pipeline due to stage failure. Use --force to continue on errors.'); 138 break; 139 } else { 140 logger.warn('Continuing pipeline despite error (--force enabled)'); 141 } 142 } 143 } 144 145 // Display final summary 146 const totalDuration = Date.now() - pipelineStartTime; 147 generatePipelineCompletion(stageResults, totalDuration); 148 149 // Calculate overall success 150 const totalFailed = stageResults.reduce((sum, r) => sum + (r.stats.failed || 0), 0); 151 if (totalFailed === 0) { 152 logger.success('\n✅ Pipeline completed successfully!'); 153 } else { 154 logger.warn(`\n⚠️ Pipeline completed with ${totalFailed} total failures`); 155 } 156 } 157 158 /** 159 * Main entry point 160 */ 161 async function main() { 162 try { 163 await runPipeline(); 164 logger.close(); 165 process.exit(0); 166 } catch (err) { 167 logger.error(`Pipeline failed: ${err.message}`, err); 168 console.error(err.stack); 169 logger.close(); 170 process.exit(1); 171 } 172 } 173 174 // Run if called directly 175 if (import.meta.url === `file://${process.argv[1]}`) { 176 main(); 177 } 178 179 export { runPipeline };