claude-monitor
Version:
Real-time terminal monitoring tool for Claude AI token usage
145 lines • 5.96 kB
JavaScript
import chalk from 'chalk';
import { calculateSessionMetrics, formatTimeUntil, formatPredictedTime, getBurnRateStatus, formatBurnRateStatus } from '../core/session-metrics.js';
import { formatLargeNumber } from '../core/calculations.js';
import { formatTime } from '../utils/time.js';
import { getSessionBillableTokens } from '../types/session-tokens.js';
const ANSI = {
clear: '\x1b[2J\x1b[H',
hide: '\x1b[?25l',
show: '\x1b[?25h',
save: '\x1b[s',
restore: '\x1b[u',
};
export class RichDisplay {
lastUpdate = 0;
minUpdateInterval = 100;
display(data) {
const now = Date.now();
if (now - this.lastUpdate < this.minUpdateInterval) {
return;
}
this.lastUpdate = now;
process.stdout.write(ANSI.clear + ANSI.hide);
this.displayHeader();
if (data.activeSession) {
this.displayActiveSession(data.activeSession, data.tokenLimit, data.settings);
}
else {
this.displayNoSession();
}
this.displayFooter(data.settings.timeFormat);
process.stdout.write(ANSI.show);
}
displayHeader() {
const sparkles = chalk.cyan('✦ ✧ ✦ ✧');
const title = chalk.bold.cyan('CLAUDE TOKEN MONITOR');
const line = chalk.cyan('═'.repeat(60));
console.log(`${sparkles} ${title} ${sparkles}`);
console.log(line);
console.log();
}
displayActiveSession(session, tokenLimit, settings) {
const metrics = calculateSessionMetrics(session, tokenLimit);
const billableTokens = getSessionBillableTokens(session);
console.log(chalk.bold('📊 Token Usage:') + ' ' + this.createProgressBar(metrics.usagePercentage));
console.log(chalk.bold('⏰ Time to Reset:') + ' ' + this.createTimeProgressBar(metrics.timePercentage, metrics.remainingMinutes));
console.log();
const usedFormatted = formatLargeNumber(billableTokens);
const limitFormatted = formatLargeNumber(tokenLimit);
const remainingFormatted = formatLargeNumber(metrics.tokensRemaining);
console.log(chalk.bold('🎯 Tokens:') + ' ' +
`${usedFormatted} / ~${limitFormatted} ` +
chalk.cyan(`(${remainingFormatted} left)`));
if (session.burnRate) {
const burnRateFormatted = Math.round(session.burnRate.tokensPerMinute).toLocaleString();
console.log(chalk.bold('🔥 Burn Rate:') + ' ' + `${burnRateFormatted} tokens/min`);
}
console.log();
console.log(chalk.bold('🏁 Predicted End:') + ' ' + formatPredictedTime(metrics.predictedEndTime, settings.timeFormat));
console.log(chalk.bold('🔄 Token Reset:') + ' ' + formatTime(metrics.resetTime, settings.timeFormat));
console.log();
const uptime = this.formatUptime(metrics.elapsedMinutes);
const status = getBurnRateStatus(session.burnRate, metrics.tokensRemaining, metrics.remainingMinutes);
const statusMsg = formatBurnRateStatus(status);
console.log(chalk.gray(`🐶 ${uptime} ${statusMsg} | Ctrl+C to exit 📦`));
}
createProgressBar(percentage) {
const width = 50;
const displayPercentage = Math.min(percentage, 100);
const filled = Math.round((displayPercentage / 100) * width);
const empty = Math.max(0, width - filled);
let color;
let emoji;
if (percentage >= 90) {
color = chalk.red;
emoji = '🔴';
}
else if (percentage >= 70) {
color = chalk.yellow;
emoji = '🟡';
}
else {
color = chalk.green;
emoji = '🟢';
}
const filledBar = color('█'.repeat(filled));
const emptyBar = chalk.red('░'.repeat(empty));
return `${emoji} [${filledBar}${emptyBar}] ${percentage.toFixed(1)}%`;
}
createTimeProgressBar(percentage, remainingMinutes) {
const width = 50;
const displayPercentage = Math.min(percentage, 100);
const filled = Math.round((displayPercentage / 100) * width);
const empty = Math.max(0, width - filled);
let color;
let emoji;
if (percentage >= 90) {
color = chalk.red;
emoji = '🔴';
}
else if (percentage >= 70) {
color = chalk.yellow;
emoji = '🟡';
}
else {
color = chalk.green;
emoji = '🟢';
}
const filledBar = color('█'.repeat(filled));
const emptyBar = chalk.red('░'.repeat(empty));
const timeLeft = formatTimeUntil(remainingMinutes);
return `${emoji} [${filledBar}${emptyBar}] ${timeLeft}`;
}
displayNoSession() {
console.log();
console.log(chalk.gray.dim('No active session'));
console.log();
console.log(chalk.gray.dim('Waiting for Claude activity...'));
console.log();
}
displayFooter(timeFormat) {
console.log();
console.log(chalk.cyan('─'.repeat(60)));
console.log(chalk.gray(`Last updated: ${formatTime(new Date(), timeFormat)}`));
console.log(chalk.gray('Press Ctrl+C to exit'));
}
formatUptime(minutes) {
const hours = Math.floor(minutes / 60);
const mins = Math.floor(minutes % 60);
const secs = Math.floor((minutes * 60) % 60);
return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
displayError(error) {
console.error(chalk.red('❌ Error:'), error.message);
}
displayWarning(message) {
console.warn(chalk.yellow('⚠️ Warning:'), message);
}
displayInfo(message) {
console.info(chalk.blue('ℹ️ Info:'), message);
}
cleanup() {
process.stdout.write(ANSI.show + '\x1b[0m\n');
}
}
//# sourceMappingURL=rich-display.js.map