UNPKG

claude-usage-tracker

Version:

Advanced analytics for Claude Code usage with cost optimization, conversation length analysis, and rate limit tracking

163 lines 6.24 kB
import chalk from "chalk"; export class TerminalCharts { /** * Create a sparkline chart for array of numbers */ static sparkline(data, width, options = {}) { if (data.length === 0) return ""; const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]; const min = options.min ?? Math.min(...data); const max = options.max ?? Math.max(...data); const range = max - min || 1; // Resample data to fit width const resampled = this.resampleData(data, width); return resampled .map((value) => { const normalized = (value - min) / range; const index = Math.floor(normalized * (chars.length - 1)); const char = chars[Math.max(0, Math.min(chars.length - 1, index))]; if (options.color) { return options.color(value, max)(char); } return char; }) .join(""); } /** * Create a horizontal bar chart */ static barChart(data, maxWidth, options = {}) { if (data.length === 0) return []; const { showPercentageOfTotal = true } = options; const maxValue = Math.max(...data.map((d) => d.value)); const totalValue = data.reduce((sum, d) => sum + d.value, 0); const maxLabelLength = Math.max(...data.map((d) => d.label.length)); const barWidth = maxWidth - maxLabelLength - 10; // Leave space for label and value return data.map(({ label, value, color = chalk.blue }) => { const barLength = Math.floor((value / maxValue) * barWidth); const bar = color("█".repeat(barLength)); const padding = " ".repeat(maxLabelLength - label.length); const percentage = showPercentageOfTotal ? ((value / totalValue) * 100).toFixed(0) : ((value / maxValue) * 100).toFixed(0); return `${label}${padding} ${bar} ${percentage}%`; }); } /** * Create a simple line chart using ASCII */ static lineChart(data, width, height, options = {}) { if (data.length === 0) return []; const { showAxes = true, color = chalk.blue } = options; const resampled = this.resampleData(data, width - (showAxes ? 2 : 0)); const min = Math.min(...resampled); const max = Math.max(...resampled); const range = max - min || 1; // Create empty canvas const canvas = Array(height) .fill(null) .map(() => Array(width).fill(" ")); // Draw axes if requested if (showAxes) { // Y-axis for (let y = 0; y < height; y++) { canvas[y][0] = "│"; } // X-axis for (let x = 0; x < width; x++) { canvas[height - 1][x] = "─"; } canvas[height - 1][0] = "└"; } // Plot data points const xOffset = showAxes ? 2 : 0; resampled.forEach((value, index) => { const x = index + xOffset; const y = height - 1 - Math.floor(((value - min) / range) * (height - 1)); if (x < width && y >= 0 && y < height) { canvas[y][x] = color("●"); // Draw vertical line to x-axis for (let yi = y + 1; yi < height - 1; yi++) { if (canvas[yi][x] === " ") { canvas[yi][x] = color("│"); } } } }); return canvas.map((row) => row.join("")); } /** * Create a heat map for hourly usage */ static heatMap(hourlyData, options = {}) { const { width = 24, showLabels = true } = options; const chars = [" ", "░", "▒", "▓", "█"]; const colors = [ chalk.gray, chalk.blue, chalk.cyan, chalk.yellow, chalk.red, ]; const max = Math.max(...hourlyData, 1); const result = []; if (showLabels) { // Hour labels const labels = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, "0")); result.push("Hour: " + labels .map((l, i) => (i % 3 === 0 ? l : " ")) .join("") .substring(0, width * 2)); } // Heat map row const heatRow = hourlyData .slice(0, width) .map((value) => { const normalized = value / max; const index = Math.floor(normalized * (chars.length - 1)); const char = chars[Math.max(0, Math.min(chars.length - 1, index))]; const color = colors[index]; return color(char.repeat(2)); // Double width for better visibility }) .join(""); result.push("Usage: " + heatRow); return result; } /** * Create a progress bar */ static progressBar(current, total, width, options = {}) { const { showPercentage = true, color = chalk.green, bgColor = chalk.gray, } = options; const percentage = Math.min(100, (current / total) * 100); const filled = Math.floor((percentage / 100) * width); const empty = width - filled; const bar = color("█".repeat(filled)) + bgColor("░".repeat(empty)); if (showPercentage) { return `${bar} ${percentage.toFixed(1)}%`; } return bar; } /** * Resample data to fit a specific width */ static resampleData(data, targetWidth) { if (data.length <= targetWidth) return data; const result = []; const bucketSize = data.length / targetWidth; for (let i = 0; i < targetWidth; i++) { const start = Math.floor(i * bucketSize); const end = Math.floor((i + 1) * bucketSize); const bucket = data.slice(start, end); // Average the bucket const avg = bucket.reduce((a, b) => a + b, 0) / bucket.length; result.push(avg); } return result; } } //# sourceMappingURL=terminal-charts.js.map