UNPKG

automagik-genie

Version:

Self-evolving AI agent orchestration framework with Model Context Protocol support

273 lines (272 loc) 12.7 kB
"use strict"; /** * Live Dashboard - Real-time terminal stats display * * Shows ALL 3 core cards from the wish: * 1. Current Session Card (live tokens, duration, tasks, project) * 2. This Month Overview Card (tokens, time, tasks, wishes, comparison) * 3. Streak & Records Card (current streak, longest, peak session, peak day) * * Features: * - Gradient headers and metrics * - Smart diff rendering (no flicker) * - Smooth token counter * - Live session timer * - Color-coded metrics * - Clean layout without broken ASCII boxes */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.runDashboardLive = runDashboardLive; const service_config_js_1 = require("../lib/service-config.js"); const stats_tracker_1 = require("../lib/stats-tracker"); const forge_manager_1 = require("../lib/forge-manager"); const forge_stats_1 = require("../lib/forge-stats"); const gradient_string_1 = __importDefault(require("gradient-string")); // Year 3025 color palettes const genie = (0, gradient_string_1.default)(['#00ff88', '#00ccff', '#0099ff']); // Genie brand const fire = (0, gradient_string_1.default)(['#ff6b35', '#f7931e', '#fdc830']); // Streak/energy const success = (0, gradient_string_1.default)(['#56ab2f', '#a8e063']); // Positive metrics const warning = (0, gradient_string_1.default)(['#ffa500', '#ff6347']); // Alerts const info = (0, gradient_string_1.default)(['#667eea', '#764ba2']); // Secondary info async function runDashboardLive(parsed, _config, _paths) { const baseUrl = (0, service_config_js_1.getForgeConfig)().baseUrl; const live = parsed.options.live; const tracker = new stats_tracker_1.StatsTracker(process.cwd()); const dashboardStartTime = Date.now(); // Track when dashboard started // Check if Forge is running const forgeRunning = await (0, forge_manager_1.isForgeRunning)(baseUrl); if (!forgeRunning) { console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log('🧞 GENIE DASHBOARD'); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); console.log('🔴 Forge backend is offline'); console.log(''); console.log('💡 Start Forge: npx automagik-genie'); console.log(''); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); return; } if (!live) { // One-time snapshot const state = await fetchDashboardState(tracker, dashboardStartTime); renderDashboard(state); return; } // Live mode - updates every second console.clear(); let state = await fetchDashboardState(tracker, dashboardStartTime); let sessionStartTime = state.session ? new Date(state.session.startTime).getTime() : null; const renderLoop = setInterval(async () => { console.clear(); // Update state state = await fetchDashboardState(tracker, dashboardStartTime); // Update session timer if (state.session && sessionStartTime) { const elapsed = Date.now() - sessionStartTime; state.session = { ...state.session, startTime: new Date(sessionStartTime).toISOString() }; } else if (state.session) { sessionStartTime = new Date(state.session.startTime).getTime(); } renderDashboard(state, true); }, 1000); // Update every second // Handle Ctrl+C gracefully process.on('SIGINT', () => { clearInterval(renderLoop); console.log('\n\n👋 Dashboard closed'); process.exit(0); }); } async function fetchDashboardState(tracker, dashboardStartTime) { const baseUrl = (0, service_config_js_1.getForgeConfig)().baseUrl; const task = tracker.getCurrentSession(); const now = new Date(); const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; const comparison = tracker.getMonthlyComparison(currentMonth); const allTime = tracker.getAllTimeStats(); const streak = tracker.calculateStreak(); // Fetch Forge stats let forgeStats = null; try { forgeStats = await (0, forge_stats_1.collectForgeStats)(baseUrl); } catch (error) { // Forge might be unavailable, continue without stats } return { session: task, monthly: comparison.current, previousMonth: comparison.previous, allTime, streak, lastMilestone: null, // TODO: Fetch from tracker lastTask: null, forgeStats, uptime: Date.now() - dashboardStartTime, startTime: dashboardStartTime }; } function renderDashboard(state, isLive = false) { const lines = []; // Year 3025 Header - Gradient banner const bannerText = isLive ? '🧞 GENIE DASHBOARD • LIVE' : '🧞 GENIE DASHBOARD'; lines.push(''); lines.push(genie('═'.repeat(80))); lines.push(genie(` ${bannerText.padEnd(76)} `)); if (isLive) { lines.push(info(' Press Ctrl+C to exit'.padEnd(80))); } lines.push(genie('═'.repeat(80))); lines.push(''); // ============================================================================ // CARD 1: Current Session (Live) // ============================================================================ lines.push(success(' 📊 CURRENT SESSION')); lines.push(' ' + '─'.repeat(78)); if (state.session) { const elapsed = Date.now() - new Date(state.session.startTime).getTime(); const duration = formatDuration(elapsed); lines.push(` ⏱️ Duration ${genie(duration)}`); lines.push(` 💬 Tokens ${fire(formatNumber(state.session.tokenCount.total))}`); lines.push(` • Input ${info(formatNumber(state.session.tokenCount.input))}`); lines.push(` • Output ${info(formatNumber(state.session.tokenCount.output))}`); lines.push(` ✅ Tasks Today ${success(state.session.tasksCompleted.length.toString())}`); lines.push(` 📂 Project ${state.session.projectName}`); if (state.session.agentsInvoked.length > 0) { const agents = state.session.agentsInvoked.join(', ').slice(0, 60); lines.push(` 🤖 Agents ${info(agents)}`); } } else { lines.push(warning(' No active session')); lines.push(' Start a Genie task to begin tracking'); } lines.push(''); // ============================================================================ // CARD 2: This Month Overview // ============================================================================ lines.push(info(' 📅 THIS MONTH')); lines.push(' ' + '─'.repeat(78)); const tokenK = (state.monthly.tokenTotal / 1000).toFixed(1); const timeFormatted = formatDuration(state.monthly.timeTotal); lines.push(` 💰 Tokens ${fire(tokenK + 'k')}${renderComparison(state, 'tokens')}`); lines.push(` ⏱️ Time ${genie(timeFormatted)}${renderComparison(state, 'time')}`); lines.push(` ✅ Tasks ${success(state.monthly.taskCount.toString())}${renderComparison(state, 'tasks')}`); lines.push(` 🎯 Wishes ${info(state.monthly.wishCount.toString())}${renderComparison(state, 'wishes')}`); lines.push(''); // ============================================================================ // CARD 3: Streak & Records // ============================================================================ lines.push(fire(' 🏆 STREAK & RECORDS')); lines.push(' ' + '─'.repeat(78)); const currentStreak = state.streak.current.days; const longestStreak = state.streak.longest.days; const peakTokens = (state.monthly.peakSession.tokens / 1000).toFixed(1); lines.push(` 🔥 Current ${fire(currentStreak + ' day' + (currentStreak === 1 ? '' : 's'))}`); lines.push(` 🏆 Longest ${success(longestStreak + ' day' + (longestStreak === 1 ? '' : 's'))}`); if (state.monthly.peakSession.tokens > 0) { lines.push(` 💪 Peak Session ${genie(peakTokens + 'k')} tokens • ${state.monthly.peakSession.date}`); } if (state.monthly.peakDay.tasks > 0) { lines.push(` 📅 Peak Day ${success(state.monthly.peakDay.tasks.toString())} tasks • ${state.monthly.peakDay.date}`); } lines.push(''); // ============================================================================ // All-Time Summary // ============================================================================ lines.push(genie(' 🌟 ALL TIME')); lines.push(' ' + '─'.repeat(78)); const allTimeTokensK = (state.allTime.totalTokens / 1000).toFixed(1); const allTimeTime = formatDuration(state.allTime.totalTime); lines.push(` 💬 Tokens ${fire(allTimeTokensK + 'k')}`); lines.push(` ⏱️ Time ${genie(allTimeTime)}`); lines.push(` ✅ Tasks ${success(state.allTime.totalTasks.toString())}`); lines.push(` 📊 Sessions ${info(state.allTime.totalSessions.toString())}`); lines.push(''); // ============================================================================ // System Health Card // ============================================================================ const uptime = formatDuration(state.uptime); lines.push(info(' 🩺 SYSTEM HEALTH')); lines.push(' ' + '─'.repeat(78)); const forgeStatus = state.forgeStats ? success('🟢 Online') : warning('🔴 Offline'); lines.push(` 📦 Forge ${forgeStatus}`); if (state.forgeStats) { lines.push(` 📊 Projects ${info((state.forgeStats.projects?.total || 0).toString())}`); lines.push(` 📝 Tasks ${info((state.forgeStats.tasks?.total || 0).toString())}`); const completed = state.forgeStats.attempts?.completed || 0; const failed = state.forgeStats.attempts?.failed || 0; const total = state.forgeStats.attempts?.total || 0; lines.push(` 🔄 Attempts ${total}${success('✅' + completed)} ${warning('❌' + failed)}`); } lines.push(` ⏱️ Uptime ${genie(uptime)}`); lines.push(''); // Footer lines.push(genie('═'.repeat(80))); lines.push(info(' 💡 Commands')); lines.push(' genie dashboard Quick snapshot'); lines.push(' genie dashboard --live Live updating dashboard'); lines.push(genie('═'.repeat(80))); console.log(lines.join('\n')); } function renderComparison(state, metric) { if (!state.previousMonth) return ''; const current = state.monthly; const previous = state.previousMonth; let currentValue = 0; let previousValue = 0; switch (metric) { case 'tokens': currentValue = current.tokenTotal; previousValue = previous.tokenTotal; break; case 'time': currentValue = current.timeTotal; previousValue = previous.timeTotal; break; case 'tasks': currentValue = current.taskCount; previousValue = previous.taskCount; break; case 'wishes': currentValue = current.wishCount; previousValue = previous.wishCount; break; } if (previousValue === 0) return currentValue > 0 ? ' (new!)' : ''; const change = ((currentValue - previousValue) / previousValue) * 100; const sign = change >= 0 ? '+' : ''; const arrow = change >= 0 ? '📈' : '📉'; return ` ${arrow} ${sign}${change.toFixed(1)}%`; } function formatDuration(ms) { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m ${seconds % 60}s`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } function formatNumber(num) { if (num >= 1000000) { return `${(num / 1000000).toFixed(2)}M`; } else if (num >= 1000) { return `${(num / 1000).toFixed(1)}k`; } else { return num.toString(); } }