| 290 | }) |
| 291 | |
| 292 | export function displayStats(stats: SessionStats, toolLimit?: number, modelLimit?: number) { |
| 293 | const width = 56 |
| 294 | |
| 295 | function renderRow(label: string, value: string): string { |
| 296 | const availableWidth = width - 1 |
| 297 | const paddingNeeded = availableWidth - label.length - value.length |
| 298 | const padding = Math.max(0, paddingNeeded) |
| 299 | return `│${label}${" ".repeat(padding)}${value} │` |
| 300 | } |
| 301 | |
| 302 | // Overview section |
| 303 | console.log("┌────────────────────────────────────────────────────────┐") |
| 304 | console.log("│ OVERVIEW │") |
| 305 | console.log("├────────────────────────────────────────────────────────┤") |
| 306 | console.log(renderRow("Sessions", stats.totalSessions.toLocaleString())) |
| 307 | console.log(renderRow("Messages", stats.totalMessages.toLocaleString())) |
| 308 | console.log(renderRow("Days", stats.days.toString())) |
| 309 | console.log("└────────────────────────────────────────────────────────┘") |
| 310 | console.log() |
| 311 | |
| 312 | // Cost & Tokens section |
| 313 | console.log("┌────────────────────────────────────────────────────────┐") |
| 314 | console.log("│ COST & TOKENS │") |
| 315 | console.log("├────────────────────────────────────────────────────────┤") |
| 316 | const cost = isNaN(stats.totalCost) ? 0 : stats.totalCost |
| 317 | const costPerDay = isNaN(stats.costPerDay) ? 0 : stats.costPerDay |
| 318 | const tokensPerSession = isNaN(stats.tokensPerSession) ? 0 : stats.tokensPerSession |
| 319 | console.log(renderRow("Total Cost", `$${cost.toFixed(2)}`)) |
| 320 | console.log(renderRow("Avg Cost/Day", `$${costPerDay.toFixed(2)}`)) |
| 321 | console.log(renderRow("Avg Tokens/Session", formatNumber(Math.round(tokensPerSession)))) |
| 322 | const medianTokensPerSession = isNaN(stats.medianTokensPerSession) ? 0 : stats.medianTokensPerSession |
| 323 | console.log(renderRow("Median Tokens/Session", formatNumber(Math.round(medianTokensPerSession)))) |
| 324 | console.log(renderRow("Input", formatNumber(stats.totalTokens.input))) |
| 325 | console.log(renderRow("Output", formatNumber(stats.totalTokens.output))) |
| 326 | console.log(renderRow("Cache Read", formatNumber(stats.totalTokens.cache.read))) |
| 327 | console.log(renderRow("Cache Write", formatNumber(stats.totalTokens.cache.write))) |
| 328 | console.log("└────────────────────────────────────────────────────────┘") |
| 329 | console.log() |
| 330 | |
| 331 | // Model Usage section |
| 332 | if (modelLimit !== undefined && Object.keys(stats.modelUsage).length > 0) { |
| 333 | const sortedModels = Object.entries(stats.modelUsage).sort(([, a], [, b]) => b.messages - a.messages) |
| 334 | const modelsToDisplay = modelLimit === Infinity ? sortedModels : sortedModels.slice(0, modelLimit) |
| 335 | |
| 336 | console.log("┌────────────────────────────────────────────────────────┐") |
| 337 | console.log("│ MODEL USAGE │") |
| 338 | console.log("├────────────────────────────────────────────────────────┤") |
| 339 | |
| 340 | for (const [model, usage] of modelsToDisplay) { |
| 341 | console.log(`│ ${model.padEnd(54)} │`) |
| 342 | console.log(renderRow(" Messages", usage.messages.toLocaleString())) |
| 343 | console.log(renderRow(" Input Tokens", formatNumber(usage.tokens.input))) |
| 344 | console.log(renderRow(" Output Tokens", formatNumber(usage.tokens.output))) |
| 345 | console.log(renderRow(" Cache Read", formatNumber(usage.tokens.cache.read))) |
| 346 | console.log(renderRow(" Cache Write", formatNumber(usage.tokens.cache.write))) |
| 347 | console.log(renderRow(" Cost", `$${usage.cost.toFixed(4)}`)) |
| 348 | console.log("├────────────────────────────────────────────────────────┤") |
| 349 | } |