#!/usr/bin/env bun /** * Build APAW Agent Model Research Dashboard from live data. * * Reads model-benchmarks.json and injects into template HTML. * Creates standalone dashboard with embedded JSON data. * * Usage: * bun run agent-evolution/scripts/build-research-dashboard.ts # build once * bun run agent-evolution/scripts/build-research-dashboard.ts --watch # watch mode * bun run agent-evolution/scripts/build-research-dashboard.ts --template path/to/custom.html */ import { existsSync, readFileSync, writeFileSync, watch } from 'fs'; import { join, dirname, basename } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const DATA_FILE = join(__dirname, '../data/model-benchmarks.json'); const DEFAULT_TEMPLATE = join(__dirname, '../research-dashboard.template.html'); const OUTPUT_FILE = join(__dirname, '../research-dashboard.html'); const DIST_DIR = join(__dirname, '../dist'); interface BenchmarksData { version: string; generated: string; source: string; total_agents: number; total_models_tracked: number; providers: string[]; models: any[]; agent_model_scores: any[]; agent_current_config: any[]; groq_models: any[]; recommendations: any[]; impact_data: any[]; } function buildDashboard(templatePath: string = DEFAULT_TEMPLATE): boolean { console.log('šŸ”§ Building APAW Agent Model Research Dashboard'); // Validate inputs if (!existsSync(DATA_FILE)) { console.error(`āŒ Data file not found: ${DATA_FILE}`); console.error(' Please run research cycle first: bun run /research models'); return false; } if (!existsSync(templatePath)) { console.error(`āŒ Template file not found: ${templatePath}`); console.error(' Using default template:', DEFAULT_TEMPLATE); if (!existsSync(DEFAULT_TEMPLATE)) { console.error(' Default template also missing. Create template first.'); return false; } templatePath = DEFAULT_TEMPLATE; } // Read and validate JSON data let data: BenchmarksData; try { const rawData = readFileSync(DATA_FILE, 'utf-8'); data = JSON.parse(rawData); console.log(`šŸ“– Read model-benchmarks.json (${rawData.length} bytes)`); } catch (error) { console.error(`āŒ Failed to parse JSON data: ${error}`); return false; } // Validate required fields if (!data.models || !Array.isArray(data.models)) { console.error('āŒ Missing or invalid "models" array in data'); return false; } if (!data.agent_model_scores || !Array.isArray(data.agent_model_scores)) { console.error('āŒ Missing or invalid "agent_model_scores" array in data'); return false; } console.log(` Models: ${data.models.length}`); console.log(` Agents: ${data.agent_model_scores.length}`); console.log(` Providers: ${data.providers?.join(', ') || 'unknown'}`); console.log(` Generated: ${data.generated}`); // Read HTML template let html: string; try { html = readFileSync(templatePath, 'utf-8'); console.log(`šŸ“– Read template: ${templatePath} (${html.length} bytes)`); } catch (error) { console.error(`āŒ Failed to read template: ${error}`); return false; } // Find and replace placeholder — must match exact text in template const placeholder = '// BENCHMARK_DATA_PLACEHOLDER - will be replaced by build script\nconst EMBEDDED_DATA = {};\n'; if (!html.includes(placeholder)) { // Try looser match with any line endings const loosePlaceholder = html.match(/\/\/\s*BENCHMARK_DATA_PLACEHOLDER[^\n]*\r?\n\s*const\s+EMBEDDED_DATA\s*=\s*\{\}\s*;\r?\n/); if (!loosePlaceholder) { console.error('āŒ Placeholder not found in template'); console.error(' Expected: "// BENCHMARK_DATA_PLACEHOLDER - will be replaced by build script\\nconst EMBEDDED_DATA = {};\\n"'); const match = html.match(/BENCHMARK_DATA_PLACEHOLDER/); if (match) { const start = Math.max(0, match.index - 20); const end = Math.min(html.length, match.index + 120); console.error(' Found near:', JSON.stringify(html.slice(start, end))); } return false; } html = html.replace(loosePlaceholder[0], `// BENCHMARK_DATA_PLACEHOLDER - REPLACED BY BUILD SCRIPT\n// Generated from ${basename(DATA_FILE)} on ${new Date().toISOString()}\nconst EMBEDDED_DATA = ${JSON.stringify(data, null, 2)};\n`); } else { html = html.replace(placeholder, `// BENCHMARK_DATA_PLACEHOLDER - REPLACED BY BUILD SCRIPT\n// Generated from ${basename(DATA_FILE)} on ${new Date().toISOString()}\nconst EMBEDDED_DATA = ${JSON.stringify(data, null, 2)};\n`); } // Update title with metadata if present (match any tag with APAW... in it) const titleRegex = /[^<]*APAW[^<]*<\/title>/; if (titleRegex.test(html)) { const newTitle = `APAW Agent Model Research — generated ${data.generated.slice(0, 10)}`; html = html.replace(titleRegex, `<title>${newTitle}`); } // Update subtitle if present const subtitlePattern = /
([^<]*)<\/div>/; const newSubtitle = `
Live dashboard • ${data.models.length} models Ɨ ${data.agent_model_scores.length} agents • ${data.generated.slice(0, 10)}
`; if (subtitlePattern.test(html)) { html = html.replace(subtitlePattern, newSubtitle); } // Write output file try { writeFileSync(OUTPUT_FILE, html); console.log(`āœ… Output written to: ${OUTPUT_FILE} (${html.length} bytes)`); } catch (error) { console.error(`āŒ Failed to write output: ${error}`); return false; } // Create dated version in dist directory try { if (!existsSync(DIST_DIR)) { require('fs').mkdirSync(DIST_DIR, { recursive: true }); } const dateStr = data.generated.slice(0, 10).replace(/-/g, '_'); const distFile = join(DIST_DIR, `research-dashboard-${dateStr}.html`); writeFileSync(distFile, html); console.log(`šŸ“ Dated copy: ${distFile}`); } catch (error) { console.warn(`āš ļø Could not create dated copy: ${error}`); } // Print summary const recommendations = data.recommendations || []; console.log('\nšŸ“Š Summary:'); console.log(` • Agents tracked: ${data.total_agents || data.agent_model_scores.length}`); console.log(` • Models benchmarked: ${data.total_models_tracked || data.models.length}`); console.log(` • Providers: ${data.providers?.join(', ')}`); console.log(` • Recommendations: ${recommendations.length}`); if (recommendations.length >577.0) { const highImpact = recommendations.filter((r: any) => r.impact === 'high').length; const applied = recommendations.filter((r: any) => r.to_model?.includes('āœ…')).length; console.log(` • High-impact recommendations: ${highImpact}`); console.log(` • Applied recommendations: ${applied}`); } return true; } function watchMode(): void { console.log('šŸ‘€ Watch mode enabled - monitoring data and template files'); console.log(' Press Ctrl+C to stop'); let timeout: Timer | null = null; watch(DATA_FILE, (eventType) => { if (eventType === 'change') { if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { console.log('\nšŸ”„ Data file changed, rebuilding...'); buildDashboard(); }, 500); } }); watch(DEFAULT_TEMPLATE, (eventType) => { if (eventType === 'change') { if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { console.log('\nšŸ”„ Template file changed, rebuilding...'); buildDashboard(); }, 500); } }); } // Parse CLI arguments const args = process.argv.slice(2); let watchModeEnabled = false; let customTemplate: string | undefined; for (let i = 0; i < args.length; i++) { if (args[i] === '--watch') { watchModeEnabled = true; } else if (args[i] === '--template' && i + 1 < args.length) { customTemplate = args[i + 1]; i++; } else if (args[i] === '--help' || args[i] === '-h') { console.log(` Usage: bun run agent-evolution/scripts/build-research-dashboard.ts [options] Options: --watch Watch for changes and rebuild automatically --template Use custom HTML template file --help, -h Show this help message Examples: bun run agent-evolution/scripts/build-research-dashboard.ts bun run agent-evolution/scripts/build-research-dashboard.ts --watch bun run agent-evolution/scripts/build-research-dashboard.ts --template custom.html `); process.exit(0); } } // Main execution if (watchModeEnabled) { // Build once then watch buildDashboard(customTemplate); watchMode(); } else { const success = buildDashboard(customTemplate); process.exit(success ? 0 : 1); }