| 314 | |
| 315 | /** Render a TokenReport as a human-readable string for terminal output. */ |
| 316 | export function formatReport(r: TokenReport): string { |
| 317 | const lines: string[] = []; |
| 318 | lines.push(`Token analysis — session ${r.sessionId.slice(0, 8)} (${r.turnCount} turn${r.turnCount === 1 ? '' : 's'})`); |
| 319 | lines.push(''); |
| 320 | lines.push('Per-turn breakdown (estimated tokens):'); |
| 321 | lines.push(''); |
| 322 | lines.push(' Turn System Schemas User Assist Results Total'); |
| 323 | lines.push(' ──── ─────── ─────── ────── ────── ─────── ───────'); |
| 324 | for (const t of r.turns) { |
| 325 | lines.push( |
| 326 | ` ${pad(String(t.turnNumber), 4)}` + |
| 327 | ` ${pad(t.system.toLocaleString(), 7)}` + |
| 328 | ` ${pad(t.toolSchemas.toLocaleString(), 7)}` + |
| 329 | ` ${pad(t.user.toLocaleString(), 6)}` + |
| 330 | ` ${pad(t.assistant.toLocaleString(), 6)}` + |
| 331 | ` ${pad(t.toolResults.toLocaleString(), 7)}` + |
| 332 | ` ${pad(t.total.toLocaleString(), 7)}`, |
| 333 | ); |
| 334 | } |
| 335 | lines.push(' ──── ─────── ─────── ────── ────── ─────── ───────'); |
| 336 | lines.push( |
| 337 | ' TOT ' + |
| 338 | ` ${pad(r.totals.system.toLocaleString(), 7)}` + |
| 339 | ` ${pad(r.totals.toolSchemas.toLocaleString(), 7)}` + |
| 340 | ` ${pad(r.totals.user.toLocaleString(), 6)}` + |
| 341 | ` ${pad(r.totals.assistant.toLocaleString(), 6)}` + |
| 342 | ` ${pad(r.totals.toolResults.toLocaleString(), 7)}` + |
| 343 | ` ${pad(r.totals.grandTotal.toLocaleString(), 7)}`, |
| 344 | ); |
| 345 | |
| 346 | if (r.toolHotspots.length > 0) { |
| 347 | lines.push(''); |
| 348 | lines.push('Tool hotspots (by output tokens consumed):'); |
| 349 | for (const h of r.toolHotspots.slice(0, 8)) { |
| 350 | lines.push(` ${pad(h.tool, 32)} calls=${pad(String(h.calls), 3)} tokens=${pad(h.outputTokens.toLocaleString(), 8)} avg=${h.avgPerCall.toLocaleString()}`); |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | if (r.fileHotspots.length > 0) { |
| 355 | const top = r.fileHotspots.slice(0, 5); |
| 356 | lines.push(''); |
| 357 | lines.push('File hotspots (most-accessed paths):'); |
| 358 | for (const h of top) { |
| 359 | lines.push(` ${pad(h.path, 50)} reads=${h.reads}`); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | lines.push(''); |
| 364 | lines.push('Recommendations:'); |
| 365 | for (const rec of r.recommendations) { |
| 366 | // Wrap recommendation text to ~78 chars for readability |
| 367 | const wrapped = wrapAt(rec, 76); |
| 368 | lines.push(' • ' + wrapped.replace(/\n/g, '\n ')); |
| 369 | } |
| 370 | |
| 371 | return lines.join('\n'); |
| 372 | } |
| 373 | |