#!/usr/bin/env node
/**
* Build unified dashboard data by calling export script:
* 1. parse files → export to JSON
* 2. embed in HTML
*
* Run: node agent-evolution/scripts/build-standalone-fixed.cjs
*/
const fs = require('fs');
const path = require('path');
const HTML_FILE = path.join(__dirname, '../index.html');
const OUTPUT_FILE = path.join(__dirname, '../index.standalone.html');
try {
// Step 1: Export data to JSON
console.log('Exporting data to JSON...');
const jsonData = require('./export-data-direct.cjs');
// ---------- Read HTML ----------
let html = fs.readFileSync(HTML_FILE, 'utf-8');
// ---------- Remove old hardcoded constants ----------
// Remove INLINE_RECOMMENDATIONS (lines ~1004-1016)
const inlineRecPattern = /const INLINE_RECOMMENDATIONS = \[[\s\S]*?\];/;
html = html.replace(inlineRecPattern, 'const INLINE_RECOMMENDATIONS = []; // REMOVED — data now comes from agentData, not hardcoded');
// Remove MODEL_BENCHMARKS line ~1021 (will be embedded in JSON)
const bmPattern = /const MODEL_BENCHMARKS = \{[\s\S]*?\n\};/;
html = html.replace(bmPattern, '/* MODEL_BENCHMARKS removed — data now in EMBEDDED_DATA.model_benchmarks */');
// ---------- Replace EMBEDDED_DATA section ----------
const startMarker = '// Default embedded data (minimal - updated by sync script)';
const endMarker = '};';
const startIdx = html.indexOf(startMarker);
if (startIdx === -1) throw new Error('Start marker not found');
// Find the start of the EMBEDDED_DATA object
const dataStartIdx = html.indexOf('const EMBEDDED_DATA = {', startIdx);
if (dataStartIdx === -1) throw new Error('EMBEDDED_DATA start not found');
// Find the end of the EMBEDDED_DATA object (the closing brace followed by semicolon)
const dataEndIdx = html.indexOf(endMarker, dataStartIdx) + endMarker.length;
if (dataEndIdx === -1) throw new Error('EMBEDDED_DATA end not found');
// Create properly formatted JSON without HTML escaping
const jsonStr = JSON.stringify(jsonData, null, 2);
// Ensure HTML characters are not escaped in string literals
// This is a workaround for JSON.stringify escaping < and > in some environments
const safeJsonStr = jsonStr
.replace(/\\u003c/g, '<')
.replace(/\\u003e/g, '>');
const embeddedData = `// Unified data from REAL sources (${new Date().toISOString()})
// Sources: .kilo/agents/*.md + kilo-meta.json + model-benchmarks-verified.json
const EMBEDDED_DATA = ${safeJsonStr};`;
html = html.substring(0, dataStartIdx) + embeddedData + html.substring(dataEndIdx);
// ---------- Replace init function ----------
const initStartPattern = /\/\/ Initialize\s*\n\s*async function init\(\)\s*\{/;
const initStart = html.match(initStartPattern);
if (initStart) {
let brace = 0, inFn = false, endIdx = initStart.index;
for (let i = initStart.index; i < html.length; i++) {
if (html[i] === '{') { brace++; inFn = true; }
else if (html[i] === '}') { brace--; if (inFn && brace === 0) { endIdx = i + 1; break; } }
}
const newInit = `// Initialize
async function init() {
agentData = EMBEDDED_DATA;
try {
document.getElementById('lastSync').textContent = formatDate(agentData.lastUpdated);
document.getElementById('agentCount').textContent = agentData.evolution_metrics.total_agents + ' agents';
document.getElementById('historyCount').textContent = agentData.evolution_metrics.agents_with_history + ' with history';
if (agentData.evolution_metrics.total_agents === 0) {
document.getElementById('lastSync').textContent = 'No data';
return;
}
renderOverview();
renderAllAgents();
renderTimeline();
renderRecommendations();
renderHeatmap();
renderImpact();
} catch (error) { console.error('Render error:', error); }
}`;
html = html.substring(0, initStart.index) + newInit + html.substring(endIdx);
}
// ---------- Replace renderHeatmap function ----------
const heatmapStartPattern = /function renderHeatmap\(\)\s*\{/;
const heatmapStart = html.match(heatmapStartPattern);
if (heatmapStart) {
let brace = 0, inFn = false, endIdx = heatmapStart.index;
for (let i = heatmapStart.index; i < html.length; i++) {
if (html[i] === '{') { brace++; inFn = true; }
else if (html[i] === '}') { brace--; if (inFn && brace === 0) { endIdx = i + 1; break; } }
}
const newHeatmap = `// Render Heatmap (read from agentData.model_benchmarks)
function renderHeatmap() {
const agents = Object.entries(agentData.agents);
if (agents.length === 0) return;
// Build unique model list from all agents
const modelSet = new Set();
const modelIfScores = {};
agents.forEach(([_, a]) => {
const model = a.current.model;
if (model) {
modelSet.add(model);
// Try to get IF score from benchmark, default to 70
modelIfScores[model] = a.current.benchmark?.instruction_following || 70;
}
});
// Build hmModels array
const hmModels = [...modelSet].map(m => {
// Extract short name from full model ID
let shortName = m;
if (m.includes('qwen3-coder')) shortName = 'Qwen3-Coder';
else if (m.includes('glm-')) shortName = m.includes('5.1') ? 'GLM-5.1' : 'GLM-5';
else if (m.includes('nemotron')) shortName = m.includes('nano') ? 'Nem. Nano' : 'Nem. Super';
else if (m.includes('minimax')) shortName = 'MiniMax M2.5';
else if (m.includes('kimi')) shortName = 'Kimi K2.6';
else if (m.includes('deepseek')) shortName = 'DeepSeek V3';
else if (m.includes('qwen3.5')) shortName = 'Qwen3.5';
else if (m.includes('gemma4')) shortName = 'Gemma4';
// Provider
let provider = 'Ollama';
if (m.includes('cloud') || m.includes('ollama-cloud')) provider = 'Ollama Cloud';
else if (m.includes('openrouter')) provider = 'OpenRouter';
else if (m.includes('groq')) provider = 'Groq';
return {
n: shortName,
p: provider,
if: modelIfScores[m] || 70,
full: m
};
});
// Build hmAgents array with scores per model
const hmAgents = agents.map(([name, agent]) => {
const currentModel = agent.current.model;
const currentIdx = hmModels.findIndex(m => m.full === currentModel);
const fitScore = agent.current.benchmark?.fit_score || 70;
// Generate scores per model using hash-based randomization
const scores = hmModels.map((m, idx) => {
if (m.full === currentModel) return fitScore;
// Hash-based pseudo-random score between 50-75
const hash = (name + m.full).split('').reduce((a, c) => a + c.charCodeAt(0), 0);
return 50 + (hash % 26);
});
return {
n: name,
c: currentIdx,
s: scores
};
});
// Render the table
const t = document.getElementById('hmTable');
let h = 'Agent ';
hmModels.forEach(m => {
const ifColor = m.if >= 85 ? '#00ff94' : m.if >= 75 ? '#facc15' : '#ff6b81';
h += '' +
m.n + ' ';
});
h += '
' +
'' + m.p + '
' +
'IF:' + m.if + '' +
'
No recommendations available
'; return; } document.getElementById('allRecommendations').innerHTML = recs.map((r, idx) => renderRecCard(r, idx)).join(''); }`; html = html.substring(0, recStart.index) + newRec + html.substring(endIdx); } // ---------- Write ---------- fs.writeFileSync(OUTPUT_FILE, html); fs.writeFileSync(path.join(__dirname, '../data/index.html'), html); console.log('\nBuilt standalone dashboard'); console.log(' Output:', OUTPUT_FILE); console.log(' Size:', (fs.statSync(OUTPUT_FILE).size / 1024).toFixed(1), 'KB'); } catch (error) { console.error('Error:', error.message); console.error(error.stack); process.exit(1); }