|
4 | 4 |
|
5 | 5 | import { appendFileSync, existsSync, readdirSync, renameSync, statSync, unlinkSync } from "node:fs" |
6 | 6 | import { join } from "node:path" |
7 | | -import type { Paths } from "./types.ts" |
| 7 | +import type { Paths, SessionStats } from "./types.ts" |
8 | 8 |
|
9 | 9 | /** ANSI escape codes for terminal formatting */ |
10 | 10 | const ANSI = { |
@@ -338,6 +338,61 @@ export class Logger { |
338 | 338 | this.writeToBuffer(this.formatForFile(`[ACTIVITY] ${action} ${detail || ""}`)) |
339 | 339 | } |
340 | 340 |
|
| 341 | + /** |
| 342 | + * Log a session/operation summary (shows stats at phase end) |
| 343 | + */ |
| 344 | + summary(stats: SessionStats, phase?: string): void { |
| 345 | + this.stopSpinner() |
| 346 | + const duration = Date.now() - stats.startTime |
| 347 | + const durationStr = this.formatDurationMs(duration) |
| 348 | + |
| 349 | + // Format token counts |
| 350 | + const tokensStr = `${this.formatNum(stats.inputTokens)} in / ${this.formatNum(stats.outputTokens)} out` |
| 351 | + |
| 352 | + // Format cost |
| 353 | + const costStr = |
| 354 | + stats.costUsd < 0.01 ? `$${stats.costUsd.toFixed(4)}` : `$${stats.costUsd.toFixed(2)}` |
| 355 | + |
| 356 | + // Build summary line |
| 357 | + const phaseStr = phase ? `${phase} ` : "" |
| 358 | + const filesStr = stats.filesModified.length > 0 ? `, ${stats.filesModified.length} files` : "" |
| 359 | + |
| 360 | + console.log( |
| 361 | + `${ANSI.dim} ${phaseStr}Summary: ${stats.toolCalls} tools, ${tokensStr}, ${costStr}, ${durationStr}${filesStr}${ANSI.reset}`, |
| 362 | + ) |
| 363 | + this.writeToBuffer( |
| 364 | + this.formatForFile( |
| 365 | + `[SUMMARY] ${phaseStr}tools=${stats.toolCalls} tokens=${stats.inputTokens}/${stats.outputTokens} cost=${costStr} duration=${durationStr} files=${stats.filesModified.length}`, |
| 366 | + ), |
| 367 | + ) |
| 368 | + } |
| 369 | + |
| 370 | + /** |
| 371 | + * Format a number for display (e.g., 1234 -> "1.2K") |
| 372 | + */ |
| 373 | + private formatNum(n: number): string { |
| 374 | + if (n >= 1_000_000) { |
| 375 | + return `${(n / 1_000_000).toFixed(1)}M` |
| 376 | + } |
| 377 | + if (n >= 1_000) { |
| 378 | + return `${(n / 1_000).toFixed(1)}K` |
| 379 | + } |
| 380 | + return n.toLocaleString() |
| 381 | + } |
| 382 | + |
| 383 | + /** |
| 384 | + * Format duration in milliseconds |
| 385 | + */ |
| 386 | + private formatDurationMs(ms: number): string { |
| 387 | + const seconds = Math.floor(ms / 1000) |
| 388 | + const minutes = Math.floor(seconds / 60) |
| 389 | + if (minutes > 0) { |
| 390 | + const remainingSeconds = seconds % 60 |
| 391 | + return `${minutes}m ${remainingSeconds}s` |
| 392 | + } |
| 393 | + return `${seconds}s` |
| 394 | + } |
| 395 | + |
341 | 396 | /** |
342 | 397 | * Format message for file output with timestamp |
343 | 398 | */ |
|
0 commit comments