UNPKG

claude-monitor

Version:

Real-time terminal monitoring tool for Claude AI token usage

191 lines 6.88 kB
import blessed from 'blessed'; import { calculateSessionMetrics, formatTimeUntil, formatPredictedTime, getBurnRateStatus, formatBurnRateStatus } from '../core/session-metrics.js'; import { formatLargeNumber } from '../core/calculations.js'; import { formatTime } from '../utils/time.js'; export class TerminalDisplay { screen; container; mainBox; footerBox; lastData = null; updateTimer = null; constructor() { this.screen = blessed.screen({ smartCSR: true, title: 'Claude Monitor', fullUnicode: true, }); this.container = blessed.box({ parent: this.screen, top: 0, left: 0, width: '100%', height: '100%', style: { bg: '#1a1a2e', }, }); blessed.box({ parent: this.container, top: 0, left: 0, width: '100%', height: 3, content: this.createHeader(), style: { fg: 'cyan', bg: '#1a1a2e', }, }); this.mainBox = blessed.box({ parent: this.container, top: 3, left: 0, width: '100%', height: '100%-6', style: { fg: 'white', bg: '#1a1a2e', }, }); this.footerBox = blessed.box({ parent: this.container, bottom: 0, left: 0, width: '100%', height: 3, style: { fg: 'gray', bg: '#1a1a2e', }, }); this.screen.key(['q', 'C-c'], () => { this.cleanup(); process.exit(0); }); this.screen.render(); } createHeader() { const sparkles = '{cyan-fg}✦ ✧ ✦ ✧{/}'; const title = '{bold}{cyan-fg}CLAUDE TOKEN MONITOR{/}'; const line = '{cyan-fg}' + '═'.repeat(50) + '{/}'; return `${sparkles} ${title} ${sparkles}\n${line}`; } display(data) { this.lastData = data; if (data.activeSession) { this.displayActiveSession(data.activeSession, data.tokenLimit, data.settings); } else { this.displayNoSession(); } this.updateFooter(data); this.screen.render(); } displayActiveSession(session, tokenLimit, settings) { const metrics = calculateSessionMetrics(session, tokenLimit); const content = []; content.push('{bold}{white-fg}📊 Token Usage:{/}'); content.push(this.createProgressBar(metrics.usagePercentage, 'tokens')); content.push(''); content.push('{bold}{white-fg}⏰ Time to Reset:{/}'); content.push(this.createProgressBar(metrics.timePercentage, 'time')); content.push(''); const tokensFormatted = formatLargeNumber(metrics.tokensUsed); const limitFormatted = formatLargeNumber(tokenLimit); const remainingFormatted = formatLargeNumber(metrics.tokensRemaining); content.push(`{white-fg}🎯 Tokens:{/} ${tokensFormatted} / ${limitFormatted} ({green-fg}${remainingFormatted} left{/})`); if (session.burnRate) { const burnRateFormatted = Math.round(session.burnRate.tokensPerMinute).toLocaleString(); content.push(`{white-fg}🔥 Burn Rate:{/} ${burnRateFormatted} tokens/min`); } content.push(''); content.push(`{white-fg}🏁 Predicted End:{/} ${formatPredictedTime(metrics.predictedEndTime, settings.timeFormat)}`); content.push(`{white-fg}🔄 Token Reset:{/} ${formatTime(metrics.resetTime, settings.timeFormat)}`); this.mainBox.setContent(content.join('\n')); } displayNoSession() { const content = [ '', '{center}{gray-fg}No active session{/center}', '', '{center}{gray-fg}Waiting for Claude activity...{/center}', ]; this.mainBox.setContent(content.join('\n')); } createProgressBar(percentage, type) { const width = 40; const filled = Math.round((percentage / 100) * width); const empty = Math.max(0, width - filled); let color = 'green'; let emoji = '🟢'; if (percentage >= 90) { color = 'red'; emoji = '🔴'; } else if (percentage >= 70) { color = 'yellow'; emoji = '🟡'; } const filledChar = '█'; const emptyChar = '░'; const bar = `{${color}-fg}${filledChar.repeat(filled)}{/}{red-fg}${emptyChar.repeat(empty)}{/}`; const percentText = `${percentage}%`; if (type === 'time' && this.lastData?.activeSession) { const metrics = calculateSessionMetrics(this.lastData.activeSession, this.lastData.tokenLimit); const timeLeft = formatTimeUntil(metrics.remainingMinutes); return ` ${emoji} [${bar}] ${timeLeft}`; } return ` ${emoji} [${bar}] ${percentText}`; } updateFooter(data) { if (!data.activeSession || !data.activeSession.burnRate) { this.footerBox.setContent(`{gray-fg}⏱ ${formatTime(new Date(), data.settings.timeFormat)} | Ctrl+C to exit{/}`); return; } const metrics = calculateSessionMetrics(data.activeSession, data.tokenLimit); const uptime = Math.round(metrics.elapsedMinutes); const status = getBurnRateStatus(data.activeSession.burnRate, metrics.tokensRemaining, metrics.remainingMinutes); const statusMsg = formatBurnRateStatus(status); const content = `{gray-fg}🐶 ${uptime.toString().padStart(2, '0')}:00:00 {/}${statusMsg} {gray-fg}| Ctrl+C to exit 📦{/}`; this.footerBox.setContent(content); } displayError(error) { const errorBox = blessed.message({ parent: this.screen, top: 'center', left: 'center', width: '50%', height: 'shrink', label: ' Error ', content: error.message, border: { type: 'line', }, style: { fg: 'red', bg: 'black', border: { fg: 'red', }, }, }); errorBox.display(error.message, 0, () => { errorBox.destroy(); this.screen.render(); }); } displayWarning(message) { console.warn('Warning:', message); } displayInfo(message) { console.info('Info:', message); } cleanup() { if (this.updateTimer) { clearTimeout(this.updateTimer); } this.screen.destroy(); } } //# sourceMappingURL=terminal-display.js.map