(stats: ClaudeCodeStats)
| 1093 | return lines.join('\n'); |
| 1094 | } |
| 1095 | function renderOverviewToAnsi(stats: ClaudeCodeStats): string[] { |
| 1096 | const lines: string[] = []; |
| 1097 | const theme = getTheme(resolveThemeSetting(getGlobalConfig().theme)); |
| 1098 | const h = (text: string) => applyColor(text, theme.claude as Color); |
| 1099 | |
| 1100 | // Two-column helper with fixed spacing |
| 1101 | // Column 1: label (18 chars) + value + padding to reach col 2 |
| 1102 | // Column 2 starts at character position 40 |
| 1103 | const COL1_LABEL_WIDTH = 18; |
| 1104 | const COL2_START = 40; |
| 1105 | const COL2_LABEL_WIDTH = 18; |
| 1106 | const row = (l1: string, v1: string, l2: string, v2: string): string => { |
| 1107 | // Build column 1: label + value |
| 1108 | const label1 = (l1 + ':').padEnd(COL1_LABEL_WIDTH); |
| 1109 | const col1PlainLen = label1.length + v1.length; |
| 1110 | |
| 1111 | // Calculate spaces needed between col1 value and col2 label |
| 1112 | const spaceBetween = Math.max(2, COL2_START - col1PlainLen); |
| 1113 | |
| 1114 | // Build column 2: label + value |
| 1115 | const label2 = (l2 + ':').padEnd(COL2_LABEL_WIDTH); |
| 1116 | |
| 1117 | // Assemble with colors applied to values only |
| 1118 | return label1 + h(v1) + ' '.repeat(spaceBetween) + label2 + h(v2); |
| 1119 | }; |
| 1120 | |
| 1121 | // Heatmap - use fixed width for screenshot (56 = 52 weeks + 4 for day labels) |
| 1122 | if (stats.dailyActivity.length > 0) { |
| 1123 | lines.push(generateHeatmap(stats.dailyActivity, { |
| 1124 | terminalWidth: 56 |
| 1125 | })); |
| 1126 | lines.push(''); |
| 1127 | } |
| 1128 | |
| 1129 | // Calculate values |
| 1130 | const modelEntries = Object.entries(stats.modelUsage).sort(([, a], [, b]) => b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens)); |
| 1131 | const favoriteModel = modelEntries[0]; |
| 1132 | const totalTokens = modelEntries.reduce((sum, [, usage]) => sum + usage.inputTokens + usage.outputTokens, 0); |
| 1133 | |
| 1134 | // Row 1: Favorite model | Total tokens |
| 1135 | if (favoriteModel) { |
| 1136 | lines.push(row('Favorite model', renderModelName(favoriteModel[0]), 'Total tokens', formatNumber(totalTokens))); |
| 1137 | } |
| 1138 | lines.push(''); |
| 1139 | |
| 1140 | // Row 2: Sessions | Longest session |
| 1141 | lines.push(row('Sessions', formatNumber(stats.totalSessions), 'Longest session', stats.longestSession ? formatDuration(stats.longestSession.duration) : 'N/A')); |
| 1142 | |
| 1143 | // Row 3: Current streak | Longest streak |
| 1144 | const currentStreakVal = `${stats.streaks.currentStreak} ${stats.streaks.currentStreak === 1 ? 'day' : 'days'}`; |
| 1145 | const longestStreakVal = `${stats.streaks.longestStreak} ${stats.streaks.longestStreak === 1 ? 'day' : 'days'}`; |
| 1146 | lines.push(row('Current streak', currentStreakVal, 'Longest streak', longestStreakVal)); |
| 1147 | |
| 1148 | // Row 4: Active days | Peak hour |
| 1149 | const activeDaysVal = `${stats.activeDays}/${stats.totalDays}`; |
| 1150 | const peakHourVal = stats.peakActivityHour !== null ? `${stats.peakActivityHour}:00-${stats.peakActivityHour + 1}:00` : 'N/A'; |
| 1151 | lines.push(row('Active days', activeDaysVal, 'Peak hour', peakHourVal)); |
| 1152 |
no test coverage detected