#!/usr/bin/env bun // CRITICAL import { hideCursor, showCursor } from './ansi'; import { setupInput } from './input'; import { render } from './render'; import * as api from './api'; import type { AppState, View } from './types'; // Route to headless mode if CLI args provided if (process.argv.length > 2) { const { runHeadless } = await import('./headless'); await runHeadless(); process.exit(0); } const state: AppState = { view: 'dashboard', selectedIndex: 0, gpus: [], recipes: [], status: { running: false, launching: false }, config: null, lifetime: { total_tokens: 0, total_requests: 0, total_energy_kwh: 0 }, error: null, }; async function refresh(): Promise { state.error = null; try { const [gpus, recipes, status, config, lifetime] = await Promise.all([ api.fetchGPUs(), api.fetchRecipes(), api.fetchStatus(), api.fetchConfig(), api.fetchLifetimeMetrics(), ]); Object.assign(state, { gpus, recipes, status, config, lifetime }); } catch (e) { state.error = e instanceof Error ? e.message : 'Unknown error'; } render(state); } const VIEWS: View[] = ['dashboard', 'recipes', 'status', 'config']; let cleanupInput: () => void = (): void => { /* no-op */ }; function cleanup(): void { cleanupInput?.(); showCursor(); process.exit(0); } function handleKey(key: string): void { if (key === 'q' || key === 'ctrl-c') return cleanup(); if (key === 'r') return void refresh(); if (key >= '1' && key <= '4') { state.view = VIEWS[parseInt(key) - 1]; state.selectedIndex = 0; } if (key === 'up') state.selectedIndex = Math.max(0, state.selectedIndex - 1); if (key === 'down') state.selectedIndex = Math.min(state.recipes.length - 1, state.selectedIndex + 1); if (key === 'enter' && state.view === 'recipes' && state.recipes[state.selectedIndex]) { api.launchRecipe(state.recipes[state.selectedIndex].id).then(refresh); } if (key === 'e' && state.status.running) api.evictModel().then(refresh); render(state); } hideCursor(); cleanupInput = setupInput(handleKey); process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); await refresh(); setInterval(refresh, 2000);