import color from 'kleur' import { get } from '../http' import { resolveAppName } from '../name' interface AppStats { name: string state: string port?: number pid?: number cpu?: number memory?: number rss?: number tool?: boolean } function formatRss(kb?: number): string { if (kb === undefined) return '-' if (kb < 1024) return `${kb} KB` if (kb < 1024 * 1024) return `${(kb / 1024).toFixed(1)} MB` return `${(kb / 1024 / 1024).toFixed(2)} GB` } function formatPercent(value?: number): string { if (value === undefined) return '-' return `${value.toFixed(1)}%` } function pad(str: string, len: number, right = false): string { if (right) return str.padStart(len) return str.padEnd(len) } export async function statsApp(arg?: string) { // If arg is provided, show stats for that app only if (arg) { const name = resolveAppName(arg) if (!name) return const stats: AppStats | undefined = await get(`/api/tools/stats/api/stats/${name}`) if (!stats) { console.error(`App not found: ${name}`) return } console.log(`${color.bold(stats.name)} ${stats.tool ? color.gray('[tool]') : ''}`) console.log(` State: ${stats.state}`) if (stats.pid) console.log(` PID: ${stats.pid}`) if (stats.port) console.log(` Port: ${stats.port}`) if (stats.cpu !== undefined) console.log(` CPU: ${formatPercent(stats.cpu)}`) if (stats.memory !== undefined) console.log(` Memory: ${formatPercent(stats.memory)}`) if (stats.rss !== undefined) console.log(` RSS: ${formatRss(stats.rss)}`) return } // Show stats for all apps const stats: AppStats[] | undefined = await get('/api/tools/stats/api/stats') if (!stats || stats.length === 0) { console.log('No apps found') return } // Sort: running first, then by name stats.sort((a, b) => { if (a.state === 'running' && b.state !== 'running') return -1 if (a.state !== 'running' && b.state === 'running') return 1 return a.name.localeCompare(b.name) }) // Calculate column widths const nameWidth = Math.max(4, ...stats.map(s => s.name.length + (s.tool ? 7 : 0))) const stateWidth = Math.max(5, ...stats.map(s => s.state.length)) // Header console.log( color.gray( `${pad('NAME', nameWidth)} ${pad('STATE', stateWidth)} ${pad('PID', 7, true)} ${pad('CPU', 7, true)} ${pad('MEM', 7, true)} ${pad('RSS', 10, true)}` ) ) // Rows for (const s of stats) { const name = s.tool ? `${s.name} ${color.gray('[tool]')}` : s.name const stateColor = s.state === 'running' ? color.green : s.state === 'invalid' ? color.red : color.gray const state = stateColor(s.state) const pid = s.pid ? String(s.pid) : '-' const cpu = formatPercent(s.cpu) const mem = formatPercent(s.memory) const rss = formatRss(s.rss) console.log( `${pad(name, nameWidth)} ${pad(state, stateWidth)} ${pad(pid, 7, true)} ${pad(cpu, 7, true)} ${pad(mem, 7, true)} ${pad(rss, 10, true)}` ) } // Summary const running = stats.filter(s => s.state === 'running') const totalCpu = running.reduce((sum, s) => sum + (s.cpu ?? 0), 0) const totalRss = running.reduce((sum, s) => sum + (s.rss ?? 0), 0) console.log() console.log(color.gray(`${running.length} running, ${formatPercent(totalCpu)} CPU, ${formatRss(totalRss)} total`)) }