tokenmaxing 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/bin/ai-wrapped.js +8 -0
- package/package.json +29 -0
- package/src/cli.js +91 -0
- package/src/metrics.js +75 -0
- package/src/pricing.js +68 -0
- package/src/report.js +974 -0
- package/src/scanners/claude.js +198 -0
- package/src/scanners/codex.js +103 -0
- package/src/utils.js +281 -0
package/src/report.js
ADDED
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compactNumber,
|
|
3
|
+
dollars,
|
|
4
|
+
formatInteger,
|
|
5
|
+
formatPercent,
|
|
6
|
+
sortedEntries,
|
|
7
|
+
tokenTotal,
|
|
8
|
+
} from "./utils.js";
|
|
9
|
+
import { estimateModelCosts } from "./pricing.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// palette — ember on black, edex-terminal style
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
const RESET = "\x1b[0m";
|
|
16
|
+
const BOLD = "\x1b[1m";
|
|
17
|
+
|
|
18
|
+
const RED = [255, 59, 48];
|
|
19
|
+
const EMBER = [255, 94, 0];
|
|
20
|
+
const ORANGE = [255, 138, 0];
|
|
21
|
+
const AMBER = [255, 184, 48];
|
|
22
|
+
const FG = [235, 226, 210];
|
|
23
|
+
const GRAY = [128, 119, 104];
|
|
24
|
+
const SHADOW = [82, 74, 64];
|
|
25
|
+
const COAL = [54, 49, 43];
|
|
26
|
+
const HEAT_STOPS = [
|
|
27
|
+
[255, 59, 48],
|
|
28
|
+
[255, 94, 0],
|
|
29
|
+
[255, 184, 48],
|
|
30
|
+
];
|
|
31
|
+
const GRID_LEVELS = [
|
|
32
|
+
[92, 36, 26],
|
|
33
|
+
[178, 60, 30],
|
|
34
|
+
[255, 94, 0],
|
|
35
|
+
[255, 184, 48],
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// categorical provider hues — claude warm orange, codex cool cyan, combined amber
|
|
39
|
+
const CYAN = [0, 209, 255];
|
|
40
|
+
const PROVIDER_COLORS = {
|
|
41
|
+
claude: ORANGE,
|
|
42
|
+
codex: CYAN,
|
|
43
|
+
all: AMBER,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function providerKey(name) {
|
|
47
|
+
const value = String(name || "").toLowerCase();
|
|
48
|
+
if (value.includes("codex") || /^(gpt|o3|o4|codex)/.test(value)) return "codex";
|
|
49
|
+
if (value.includes("claude")) return "claude";
|
|
50
|
+
return "all";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function providerColor(name) {
|
|
54
|
+
return PROVIDER_COLORS[providerKey(name)] || ORANGE;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const colorEnabled =
|
|
58
|
+
process.env.NO_COLOR === undefined &&
|
|
59
|
+
(process.env.FORCE_COLOR !== undefined || process.stdout.isTTY === true);
|
|
60
|
+
|
|
61
|
+
export function isColorEnabled() {
|
|
62
|
+
return colorEnabled;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function fg([r, g, b]) {
|
|
66
|
+
return colorEnabled ? `\x1b[38;2;${r};${g};${b}m` : "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function bold(text) {
|
|
70
|
+
return colorEnabled ? `${BOLD}${text}${RESET}` : text;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function reset() {
|
|
74
|
+
return colorEnabled ? RESET : "";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function paint(rgb, text) {
|
|
78
|
+
return `${fg(rgb)}${text}${reset()}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function lerp(a, b, t) {
|
|
82
|
+
return [
|
|
83
|
+
Math.round(a[0] + (b[0] - a[0]) * t),
|
|
84
|
+
Math.round(a[1] + (b[1] - a[1]) * t),
|
|
85
|
+
Math.round(a[2] + (b[2] - a[2]) * t),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function heat(t) {
|
|
90
|
+
const clamped = Math.min(1, Math.max(0, t));
|
|
91
|
+
const [low, mid, high] = HEAT_STOPS;
|
|
92
|
+
return clamped < 0.5 ? lerp(low, mid, clamped * 2) : lerp(mid, high, (clamped - 0.5) * 2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function scale(rgb, k) {
|
|
96
|
+
return [Math.round(rgb[0] * k), Math.round(rgb[1] * k), Math.round(rgb[2] * k)];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// single-hue ramp: dim at the base, full saturation toward the bar tip
|
|
100
|
+
function shadeRamp(color) {
|
|
101
|
+
const base = scale(color, 0.42);
|
|
102
|
+
const tip = lerp(color, [255, 255, 255], 0.12);
|
|
103
|
+
return (t) => (t < 0.5 ? lerp(base, color, t * 2) : lerp(color, tip, (t - 0.5) * 2));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// ansi-aware layout helpers
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
111
|
+
|
|
112
|
+
function visibleLength(text) {
|
|
113
|
+
return text.replace(ANSI_PATTERN, "").length;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function padEndVisible(text, width) {
|
|
117
|
+
return text + " ".repeat(Math.max(0, width - visibleLength(text)));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function padStartVisible(text, width) {
|
|
121
|
+
return " ".repeat(Math.max(0, width - visibleLength(text))) + text;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function truncate(text, width) {
|
|
125
|
+
return text.length > width ? `${text.slice(0, width - 1)}…` : text;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function wrapText(text, width) {
|
|
129
|
+
const words = String(text).split(/\s+/);
|
|
130
|
+
const wrapped = [];
|
|
131
|
+
let current = "";
|
|
132
|
+
for (const word of words) {
|
|
133
|
+
if (current && current.length + word.length + 1 > width) {
|
|
134
|
+
wrapped.push(current);
|
|
135
|
+
current = word;
|
|
136
|
+
} else {
|
|
137
|
+
current = current ? `${current} ${word}` : word;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (current) wrapped.push(current);
|
|
141
|
+
return wrapped;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function reportWidth() {
|
|
145
|
+
const columns = process.stdout.columns || Number(process.env.COLUMNS) || 96;
|
|
146
|
+
return Math.min(Math.max(columns - 2, 80), 104);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function fmtUSD(value) {
|
|
150
|
+
return `$${(Number(value) || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// banner — 5x5 pixel font folded into half-blocks
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
const GLYPHS = {
|
|
158
|
+
A: [".###.", "#...#", "#####", "#...#", "#...#"],
|
|
159
|
+
I: ["#####", "..#..", "..#..", "..#..", "#####"],
|
|
160
|
+
W: ["#...#", "#...#", "#.#.#", "##.##", "#...#"],
|
|
161
|
+
R: ["####.", "#...#", "####.", "#..#.", "#...#"],
|
|
162
|
+
P: ["####.", "#...#", "####.", "#....", "#...."],
|
|
163
|
+
E: ["#####", "#....", "####.", "#....", "#####"],
|
|
164
|
+
D: ["####.", "#...#", "#...#", "#...#", "####."],
|
|
165
|
+
" ": ["..", "..", "..", "..", ".."],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
function banner(text, indent, colorFn = heat) {
|
|
169
|
+
const grid = ["", "", "", "", ""];
|
|
170
|
+
for (const letter of text) {
|
|
171
|
+
const glyph = GLYPHS[letter] || GLYPHS[" "];
|
|
172
|
+
for (let row = 0; row < 5; row += 1) grid[row] += `${glyph[row]}.`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const totalCols = grid[0].length;
|
|
176
|
+
const lines = [];
|
|
177
|
+
for (let pair = 0; pair < 3; pair += 1) {
|
|
178
|
+
const top = grid[pair * 2];
|
|
179
|
+
const bottom = grid[pair * 2 + 1] || ".".repeat(totalCols);
|
|
180
|
+
let line = " ".repeat(indent);
|
|
181
|
+
for (let col = 0; col < totalCols; col += 1) {
|
|
182
|
+
const upper = top[col] === "#";
|
|
183
|
+
const lower = bottom[col] === "#";
|
|
184
|
+
const glyphChar = upper && lower ? "█" : upper ? "▀" : lower ? "▄" : " ";
|
|
185
|
+
if (glyphChar === " ") {
|
|
186
|
+
line += " ";
|
|
187
|
+
} else {
|
|
188
|
+
line += fg(colorFn(totalCols <= 1 ? 1 : col / (totalCols - 1))) + glyphChar;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
lines.push(line + reset());
|
|
192
|
+
}
|
|
193
|
+
return lines;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// panel frame — edex-style boxed sections
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
function panel(title, contentLines, width) {
|
|
201
|
+
const interior = width - 4;
|
|
202
|
+
const lines = [];
|
|
203
|
+
|
|
204
|
+
const head =
|
|
205
|
+
paint(COAL, "┌── ") +
|
|
206
|
+
bold(paint(ORANGE, title.toUpperCase())) +
|
|
207
|
+
paint(SHADOW, " ");
|
|
208
|
+
lines.push(head + paint(COAL, `${"─".repeat(Math.max(0, width - visibleLength(head) - 1))}┐`));
|
|
209
|
+
lines.push(paint(COAL, "│") + " ".repeat(width - 2) + paint(COAL, "│"));
|
|
210
|
+
|
|
211
|
+
for (const content of contentLines) {
|
|
212
|
+
lines.push(paint(COAL, "│ ") + padEndVisible(content, interior) + paint(COAL, " │"));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
lines.push(paint(COAL, "│") + " ".repeat(width - 2) + paint(COAL, "│"));
|
|
216
|
+
lines.push(paint(COAL, `└${"─".repeat(width - 2)}┘`));
|
|
217
|
+
return lines;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// widgets — every widget takes the panel interior width
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
const PARTIALS = "▏▎▍▌▋▊▉█";
|
|
225
|
+
|
|
226
|
+
function gradientBar(ratio, width, colorFn = heat) {
|
|
227
|
+
const cells = Math.min(1, Math.max(0, ratio)) * width;
|
|
228
|
+
const full = Math.floor(cells);
|
|
229
|
+
const fracIndex = Math.round((cells - full) * 8);
|
|
230
|
+
|
|
231
|
+
let out = "";
|
|
232
|
+
for (let cell = 0; cell < full; cell += 1) {
|
|
233
|
+
out += fg(colorFn(width <= 1 ? 1 : cell / (width - 1))) + "█";
|
|
234
|
+
}
|
|
235
|
+
let used = full;
|
|
236
|
+
if (fracIndex > 0 && full < width) {
|
|
237
|
+
out += fg(colorFn(width <= 1 ? 1 : full / (width - 1))) + PARTIALS[fracIndex - 1];
|
|
238
|
+
used += 1;
|
|
239
|
+
}
|
|
240
|
+
out += fg(COAL) + "·".repeat(Math.max(0, width - used)) + reset();
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function barChart(items, width, options = {}) {
|
|
245
|
+
const clean = items.filter((item) => Number.isFinite(item.value) && item.value >= 0);
|
|
246
|
+
if (!clean.length) return [paint(GRAY, "no data")];
|
|
247
|
+
|
|
248
|
+
const labelWidth = options.labelWidth || 18;
|
|
249
|
+
const valueWidth = options.valueWidth || 9;
|
|
250
|
+
const prefixWidth = options.ranked ? 3 : 0;
|
|
251
|
+
const barWidth = Math.max(14, width - labelWidth - valueWidth - prefixWidth - 3);
|
|
252
|
+
const max = Math.max(...clean.map((item) => item.value), Number.EPSILON);
|
|
253
|
+
const valueFormatter = options.valueFormatter || compactNumber;
|
|
254
|
+
|
|
255
|
+
return clean.map((item, index) => {
|
|
256
|
+
const hue = item.color || null;
|
|
257
|
+
const colorFn = hue ? shadeRamp(hue) : heat;
|
|
258
|
+
const isTop = options.rankTop && item.value === max;
|
|
259
|
+
const labelColor = hue || (isTop ? AMBER : GRAY);
|
|
260
|
+
const label = padEndVisible(paint(labelColor, truncate(item.label, labelWidth)), labelWidth);
|
|
261
|
+
const value = padStartVisible(
|
|
262
|
+
isTop ? bold(paint(AMBER, valueFormatter(item.value))) : paint(hue || FG, valueFormatter(item.value)),
|
|
263
|
+
valueWidth,
|
|
264
|
+
);
|
|
265
|
+
const prefix = options.ranked
|
|
266
|
+
? paint(index === 0 ? RED : SHADOW, `${String(index + 1).padStart(2, "0")} `)
|
|
267
|
+
: "";
|
|
268
|
+
return `${prefix}${label} ${paint(SHADOW, "▕")}${gradientBar(item.value / max, barWidth, colorFn)} ${value}`;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function statTiles(stats, width) {
|
|
273
|
+
const columns = 3;
|
|
274
|
+
const gap = 2;
|
|
275
|
+
const tileWidth = Math.floor((width - gap * (columns - 1)) / columns);
|
|
276
|
+
const inner = tileWidth - 4;
|
|
277
|
+
const lines = [];
|
|
278
|
+
|
|
279
|
+
for (let start = 0; start < stats.length; start += columns) {
|
|
280
|
+
const row = stats.slice(start, start + columns);
|
|
281
|
+
const top = [];
|
|
282
|
+
const labelLine = [];
|
|
283
|
+
const valueLine = [];
|
|
284
|
+
const bottom = [];
|
|
285
|
+
|
|
286
|
+
for (const stat of row) {
|
|
287
|
+
top.push(paint(COAL, `┌${"─".repeat(tileWidth - 2)}┐`));
|
|
288
|
+
labelLine.push(
|
|
289
|
+
paint(COAL, "│ ") +
|
|
290
|
+
padEndVisible(paint(GRAY, truncate(stat.label.toUpperCase(), inner)), inner) +
|
|
291
|
+
paint(COAL, " │"),
|
|
292
|
+
);
|
|
293
|
+
const accent = stat.accent || ORANGE;
|
|
294
|
+
valueLine.push(
|
|
295
|
+
paint(COAL, "│ ") +
|
|
296
|
+
padEndVisible(bold(paint(accent, truncate(String(stat.value), inner))), inner) +
|
|
297
|
+
paint(COAL, " │"),
|
|
298
|
+
);
|
|
299
|
+
bottom.push(paint(COAL, `└${"─".repeat(tileWidth - 2)}┘`));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const joiner = " ".repeat(gap);
|
|
303
|
+
lines.push(top.join(joiner), labelLine.join(joiner), valueLine.join(joiner), bottom.join(joiner));
|
|
304
|
+
}
|
|
305
|
+
return lines;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const BLOCKS_VERTICAL = "▁▂▃▄▅▆▇█";
|
|
309
|
+
|
|
310
|
+
function hourHistogram(hours) {
|
|
311
|
+
const total = hours.reduce((sum, value) => sum + (Number(value) || 0), 0);
|
|
312
|
+
if (!total) return [paint(GRAY, "no data")];
|
|
313
|
+
|
|
314
|
+
const max = Math.max(...hours, Number.EPSILON);
|
|
315
|
+
let peakHour = 0;
|
|
316
|
+
for (let hour = 0; hour < 24; hour += 1) {
|
|
317
|
+
if ((hours[hour] || 0) > (hours[peakHour] || 0)) peakHour = hour;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const ROWS = 6;
|
|
321
|
+
const lines = [];
|
|
322
|
+
for (let row = ROWS; row >= 1; row -= 1) {
|
|
323
|
+
let line = "";
|
|
324
|
+
for (let hour = 0; hour < 24; hour += 1) {
|
|
325
|
+
const level = ((hours[hour] || 0) / max) * ROWS;
|
|
326
|
+
const fill = Math.min(1, Math.max(0, level - (row - 1)));
|
|
327
|
+
const index = Math.round(fill * 8);
|
|
328
|
+
if (index === 0) {
|
|
329
|
+
line += " ";
|
|
330
|
+
} else {
|
|
331
|
+
const color = hour === peakHour ? RED : heat((hours[hour] || 0) / max);
|
|
332
|
+
line += fg(color) + BLOCKS_VERTICAL[index - 1].repeat(2) + reset() + " ";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
lines.push(line);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
lines.push(paint(COAL, "▔".repeat(24 * 3 - 1)));
|
|
339
|
+
|
|
340
|
+
let axis = "";
|
|
341
|
+
for (let hour = 0; hour < 24; hour += 1) {
|
|
342
|
+
if (hour % 3 === 0 || hour === peakHour) {
|
|
343
|
+
const color = hour === peakHour ? RED : SHADOW;
|
|
344
|
+
axis += paint(color, String(hour).padStart(2, "0")) + " ";
|
|
345
|
+
} else {
|
|
346
|
+
axis += " ";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
lines.push(axis);
|
|
350
|
+
lines.push("");
|
|
351
|
+
lines.push(
|
|
352
|
+
paint(GRAY, "peak ") +
|
|
353
|
+
bold(paint(RED, `${String(peakHour).padStart(2, "0")}:00`)) +
|
|
354
|
+
paint(GRAY, ` — ${formatInteger(hours[peakHour])} messages (${formatPercent(hours[peakHour] / total)} of all activity)`),
|
|
355
|
+
);
|
|
356
|
+
return lines;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// github-contribution-style daily activity matrix, edex memory-grid flavored
|
|
360
|
+
function activityMatrix(dailyActivity, width) {
|
|
361
|
+
const days = Object.keys(dailyActivity || {}).sort();
|
|
362
|
+
if (!days.length) return [paint(GRAY, "no data")];
|
|
363
|
+
|
|
364
|
+
const LABEL_WIDTH = 4;
|
|
365
|
+
const first = new Date(`${days[0]}T00:00:00`);
|
|
366
|
+
const last = new Date(`${days[days.length - 1]}T00:00:00`);
|
|
367
|
+
|
|
368
|
+
// grid columns are weeks starting monday; drop to single-char cells when a
|
|
369
|
+
// full history (e.g. a whole year) would not fit, trim oldest weeks last
|
|
370
|
+
const gridStart = new Date(first);
|
|
371
|
+
gridStart.setDate(gridStart.getDate() - ((gridStart.getDay() + 6) % 7));
|
|
372
|
+
const totalWeeks = Math.floor((last - gridStart) / (7 * 86400000)) + 1;
|
|
373
|
+
const cellWidth = LABEL_WIDTH + totalWeeks * 2 <= width ? 2 : 1;
|
|
374
|
+
const maxWeeks = Math.floor((width - LABEL_WIDTH) / cellWidth);
|
|
375
|
+
const weeks = Math.min(totalWeeks, maxWeeks);
|
|
376
|
+
const start = new Date(gridStart);
|
|
377
|
+
start.setDate(start.getDate() + (totalWeeks - weeks) * 7);
|
|
378
|
+
|
|
379
|
+
const max = Math.max(...Object.values(dailyActivity), 1);
|
|
380
|
+
const dayKey = (date) =>
|
|
381
|
+
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
382
|
+
|
|
383
|
+
// month labels across the top
|
|
384
|
+
const monthCells = Array.from({ length: LABEL_WIDTH + weeks * cellWidth }, () => " ");
|
|
385
|
+
let lastMonth = -1;
|
|
386
|
+
let lastLabelEnd = -1;
|
|
387
|
+
for (let week = 0; week < weeks; week += 1) {
|
|
388
|
+
const weekDate = new Date(start);
|
|
389
|
+
weekDate.setDate(weekDate.getDate() + week * 7);
|
|
390
|
+
const month = weekDate.getMonth();
|
|
391
|
+
const at = LABEL_WIDTH + week * cellWidth;
|
|
392
|
+
if (month !== lastMonth && at > lastLabelEnd && at + 3 <= monthCells.length) {
|
|
393
|
+
for (let offset = 0; offset < 3; offset += 1) monthCells[at + offset] = MONTH_NAMES[month][offset];
|
|
394
|
+
lastLabelEnd = at + 3;
|
|
395
|
+
lastMonth = month;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const monthRow = paint(GRAY, monthCells.join("").trimEnd());
|
|
399
|
+
|
|
400
|
+
const DAY_LABELS = ["MON", "", "WED", "", "FRI", "", "SUN"];
|
|
401
|
+
const gap = cellWidth === 2 ? " " : "";
|
|
402
|
+
const rows = [monthRow];
|
|
403
|
+
for (let weekday = 0; weekday < 7; weekday += 1) {
|
|
404
|
+
let row = padEndVisible(paint(GRAY, DAY_LABELS[weekday]), LABEL_WIDTH);
|
|
405
|
+
for (let week = 0; week < weeks; week += 1) {
|
|
406
|
+
const date = new Date(start);
|
|
407
|
+
date.setDate(date.getDate() + week * 7 + weekday);
|
|
408
|
+
if (date < first || date > last) {
|
|
409
|
+
row += " " + gap;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const value = dailyActivity[dayKey(date)] || 0;
|
|
413
|
+
if (value <= 0) {
|
|
414
|
+
row += paint(GRAY, "·") + gap;
|
|
415
|
+
} else {
|
|
416
|
+
const level = Math.min(GRID_LEVELS.length - 1, Math.floor((value / max) * GRID_LEVELS.length));
|
|
417
|
+
row += paint(GRID_LEVELS[level], "■") + gap;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
rows.push(row);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// streaks + busiest day, edex "using x of y" style header
|
|
424
|
+
const activeDays = days.filter((day) => dailyActivity[day] > 0);
|
|
425
|
+
const spanDays = Math.round((last - first) / 86400000) + 1;
|
|
426
|
+
let longestStreak = 0;
|
|
427
|
+
let streak = 0;
|
|
428
|
+
let previousTime = null;
|
|
429
|
+
for (const day of activeDays) {
|
|
430
|
+
const time = new Date(`${day}T00:00:00`).getTime();
|
|
431
|
+
streak = previousTime !== null && time - previousTime === 86400000 ? streak + 1 : 1;
|
|
432
|
+
longestStreak = Math.max(longestStreak, streak);
|
|
433
|
+
previousTime = time;
|
|
434
|
+
}
|
|
435
|
+
const busiest = activeDays.reduce((best, day) => (dailyActivity[day] > dailyActivity[best] ? day : best), activeDays[0]);
|
|
436
|
+
|
|
437
|
+
const header =
|
|
438
|
+
paint(GRAY, "ACTIVE ") +
|
|
439
|
+
bold(paint(AMBER, `${activeDays.length}`)) +
|
|
440
|
+
paint(GRAY, ` OUT OF ${spanDays} DAYS`) +
|
|
441
|
+
paint(SHADOW, totalWeeks > weeks ? ` · showing last ${weeks} weeks` : "");
|
|
442
|
+
|
|
443
|
+
let legend = paint(SHADOW, "LESS ") + paint(GRAY, "· ");
|
|
444
|
+
for (const level of GRID_LEVELS) legend += paint(level, "■ ");
|
|
445
|
+
legend += paint(SHADOW, "MORE");
|
|
446
|
+
const facts =
|
|
447
|
+
paint(GRAY, "longest streak ") +
|
|
448
|
+
bold(paint(RED, `${longestStreak}d`)) +
|
|
449
|
+
paint(GRAY, " · busiest ") +
|
|
450
|
+
paint(AMBER, `${monthLabel(busiest.slice(0, 7)).split(" ")[0]} ${Number(busiest.slice(8))}`) +
|
|
451
|
+
paint(GRAY, ` — ${formatInteger(dailyActivity[busiest])} messages`);
|
|
452
|
+
|
|
453
|
+
return [header, "", ...rows, "", legend, facts];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function stackedShareBar(segments, width) {
|
|
457
|
+
const barWidth = width - 2;
|
|
458
|
+
const total = segments.reduce((sum, segment) => sum + segment.value, 0);
|
|
459
|
+
if (!total) return [paint(GRAY, "no data")];
|
|
460
|
+
|
|
461
|
+
let bar = "";
|
|
462
|
+
let used = 0;
|
|
463
|
+
const visible = segments.filter((segment) => segment.value > 0);
|
|
464
|
+
for (let index = 0; index < visible.length; index += 1) {
|
|
465
|
+
const segment = visible[index];
|
|
466
|
+
const isLast = index === visible.length - 1;
|
|
467
|
+
const cells = isLast
|
|
468
|
+
? Math.max(0, barWidth - used)
|
|
469
|
+
: Math.max(1, Math.round((segment.value / total) * barWidth));
|
|
470
|
+
bar += fg(segment.color) + "█".repeat(Math.max(0, cells));
|
|
471
|
+
used += cells;
|
|
472
|
+
}
|
|
473
|
+
bar += reset();
|
|
474
|
+
|
|
475
|
+
const lines = [bar, ""];
|
|
476
|
+
const labelWidth = Math.max(...segments.map((segment) => segment.label.length)) + 2;
|
|
477
|
+
for (const segment of segments) {
|
|
478
|
+
const share = segment.value / total;
|
|
479
|
+
lines.push(
|
|
480
|
+
paint(segment.color, "■ ") +
|
|
481
|
+
padEndVisible(paint(GRAY, segment.label.toUpperCase()), labelWidth) +
|
|
482
|
+
padStartVisible(paint(FG, compactNumber(segment.value)), 9) +
|
|
483
|
+
padStartVisible(paint(segment.value > 0 ? AMBER : SHADOW, formatPercent(share)), 8),
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return lines;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function tokenSummaryTable(reports) {
|
|
490
|
+
const columns = [
|
|
491
|
+
{ key: "name", label: "SOURCE", width: 12, left: true },
|
|
492
|
+
{ key: "input", label: "INPUT", width: 8 },
|
|
493
|
+
{ key: "output", label: "OUTPUT", width: 8 },
|
|
494
|
+
{ key: "cacheRead", label: "CACHE R", width: 8 },
|
|
495
|
+
{ key: "cacheCreation", label: "CACHE W", width: 8 },
|
|
496
|
+
{ key: "reasoning", label: "REASON", width: 8 },
|
|
497
|
+
{ key: "total", label: "TOTAL", width: 9 },
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
const rows = reports.map((report) => ({
|
|
501
|
+
name: report.provider,
|
|
502
|
+
hue: providerColor(report.key),
|
|
503
|
+
input: report.tokens.input || 0,
|
|
504
|
+
output: report.tokens.output || 0,
|
|
505
|
+
cacheRead: report.tokens.cacheRead || 0,
|
|
506
|
+
cacheCreation: report.tokens.cacheCreation || 0,
|
|
507
|
+
reasoning: report.tokens.reasoning || 0,
|
|
508
|
+
total: tokenTotal(report.tokens),
|
|
509
|
+
combined: report.key === "all",
|
|
510
|
+
}));
|
|
511
|
+
|
|
512
|
+
const header = columns
|
|
513
|
+
.map((column) =>
|
|
514
|
+
column.left
|
|
515
|
+
? padEndVisible(paint(SHADOW, column.label), column.width)
|
|
516
|
+
: padStartVisible(paint(SHADOW, column.label), column.width),
|
|
517
|
+
)
|
|
518
|
+
.join(" ");
|
|
519
|
+
|
|
520
|
+
const tableWidth = columns.reduce((sum, column) => sum + column.width + 1, -1);
|
|
521
|
+
const lines = [header, paint(COAL, "─".repeat(tableWidth))];
|
|
522
|
+
|
|
523
|
+
for (const row of rows) {
|
|
524
|
+
const cells = columns.map((column) => {
|
|
525
|
+
if (column.left) {
|
|
526
|
+
const name = truncate(row.name, column.width);
|
|
527
|
+
return padEndVisible(row.combined ? bold(paint(row.hue, name)) : paint(row.hue, name), column.width);
|
|
528
|
+
}
|
|
529
|
+
const value = compactNumber(row[column.key]);
|
|
530
|
+
const color = column.key === "total" ? AMBER : row[column.key] > 0 ? FG : SHADOW;
|
|
531
|
+
return padStartVisible(row.combined ? bold(paint(color, value)) : paint(color, value), column.width);
|
|
532
|
+
});
|
|
533
|
+
lines.push(cells.join(" "));
|
|
534
|
+
}
|
|
535
|
+
return lines;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// month / model labels
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
|
|
542
|
+
const MONTH_NAMES = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
|
543
|
+
|
|
544
|
+
function monthLabel(month) {
|
|
545
|
+
const [year, monthNumber] = String(month).split("-");
|
|
546
|
+
const name = MONTH_NAMES[Number(monthNumber) - 1];
|
|
547
|
+
return name ? `${name} ${year}` : month;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function shortDate(iso) {
|
|
551
|
+
const date = new Date(iso);
|
|
552
|
+
if (!Number.isFinite(date.getTime())) return iso;
|
|
553
|
+
return `${MONTH_NAMES[date.getMonth()]} ${String(date.getDate()).padStart(2, "0")}`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function modelLabel(usage) {
|
|
557
|
+
return `${usage.provider || ""} ${usage.model || "unknown"}`.trim();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
// burn estimation — list prices applied to per-model token splits
|
|
562
|
+
// ---------------------------------------------------------------------------
|
|
563
|
+
|
|
564
|
+
function buildBurnLines(combined, width) {
|
|
565
|
+
const lines = [];
|
|
566
|
+
const hasLocalSpend = Object.keys(combined.monthlySpendUSD).length > 0;
|
|
567
|
+
|
|
568
|
+
if (hasLocalSpend) {
|
|
569
|
+
lines.push(
|
|
570
|
+
...barChart(
|
|
571
|
+
sortedEntries(combined.monthlySpendUSD).map(([month, spendUSD]) => ({
|
|
572
|
+
label: monthLabel(month),
|
|
573
|
+
value: spendUSD,
|
|
574
|
+
})),
|
|
575
|
+
width,
|
|
576
|
+
{ valueFormatter: fmtUSD, rankTop: true, valueWidth: 11 },
|
|
577
|
+
),
|
|
578
|
+
);
|
|
579
|
+
return lines;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const { priced, unpriced, totalUSD } = estimateModelCosts(combined.modelUsage);
|
|
583
|
+
if (!priced.length) {
|
|
584
|
+
lines.push(paint(GRAY, "no local spend cache and no models matched the pricing table"));
|
|
585
|
+
return lines;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
lines.push(paint(GRAY, "EST. ALL-TIME BURN ") + bold(paint(RED, `≈ ${fmtUSD(totalUSD)}`)));
|
|
589
|
+
lines.push("");
|
|
590
|
+
|
|
591
|
+
// allocate the estimated total across months in proportion to token volume
|
|
592
|
+
const monthTotals = Object.entries(combined.monthlyTokens || {}).map(([month, tokens]) => ({
|
|
593
|
+
month,
|
|
594
|
+
tokens: tokenTotal(tokens),
|
|
595
|
+
}));
|
|
596
|
+
const trackedTokens = monthTotals.reduce((sum, entry) => sum + entry.tokens, 0);
|
|
597
|
+
if (trackedTokens > 0) {
|
|
598
|
+
lines.push(
|
|
599
|
+
...barChart(
|
|
600
|
+
monthTotals
|
|
601
|
+
.sort((a, b) => a.month.localeCompare(b.month))
|
|
602
|
+
.map((entry) => ({
|
|
603
|
+
label: monthLabel(entry.month),
|
|
604
|
+
value: (totalUSD * entry.tokens) / trackedTokens,
|
|
605
|
+
})),
|
|
606
|
+
width,
|
|
607
|
+
{ valueFormatter: (value) => `≈ ${fmtUSD(value)}`, rankTop: true, valueWidth: 12 },
|
|
608
|
+
),
|
|
609
|
+
);
|
|
610
|
+
lines.push("");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
lines.push(paint(SHADOW, "BY MODEL"));
|
|
614
|
+
lines.push(
|
|
615
|
+
...barChart(
|
|
616
|
+
priced.slice(0, 8).map((entry) => ({
|
|
617
|
+
label: modelLabel(entry.usage),
|
|
618
|
+
value: entry.costUSD,
|
|
619
|
+
})),
|
|
620
|
+
width,
|
|
621
|
+
{ valueFormatter: (value) => `≈ ${fmtUSD(value)}`, labelWidth: 32, valueWidth: 12, ranked: true },
|
|
622
|
+
),
|
|
623
|
+
);
|
|
624
|
+
lines.push("");
|
|
625
|
+
const footnotes = [
|
|
626
|
+
"estimated from public API list prices per 1M tokens — subscription plans not reflected",
|
|
627
|
+
"reasoning tokens billed within output",
|
|
628
|
+
];
|
|
629
|
+
if (unpriced.length) footnotes.push(`unpriced models excluded: ${unpriced.join(", ")}`);
|
|
630
|
+
for (const footnote of footnotes) {
|
|
631
|
+
for (const wrapped of wrapText(footnote, width)) lines.push(paint(SHADOW, wrapped));
|
|
632
|
+
}
|
|
633
|
+
return lines;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ---------------------------------------------------------------------------
|
|
637
|
+
// hero — the one big screenshot stat
|
|
638
|
+
// ---------------------------------------------------------------------------
|
|
639
|
+
|
|
640
|
+
const WAR_AND_PEACE_WORDS = 587287;
|
|
641
|
+
|
|
642
|
+
// full-height 5x5 digit font — rendered upright (no half-block folding) so the
|
|
643
|
+
// numbers stay unambiguous; magnitude (BILLION/MILLION/…) goes in the label
|
|
644
|
+
const HERO_DIGITS = {
|
|
645
|
+
"0": ["█████", "█ █", "█ █", "█ █", "█████"],
|
|
646
|
+
"1": [" █ ", " ██ ", " █ ", " █ ", "█████"],
|
|
647
|
+
"2": ["█████", " █", "█████", "█ ", "█████"],
|
|
648
|
+
"3": ["█████", " █", " ████", " █", "█████"],
|
|
649
|
+
"4": ["█ █", "█ █", "█████", " █", " █"],
|
|
650
|
+
"5": ["█████", "█ ", "█████", " █", "█████"],
|
|
651
|
+
"6": ["█████", "█ ", "█████", "█ █", "█████"],
|
|
652
|
+
"7": ["█████", " █", " █ ", " █ ", " █ "],
|
|
653
|
+
"8": ["█████", "█ █", "█████", "█ █", "█████"],
|
|
654
|
+
"9": ["█████", "█ █", "█████", " █", "█████"],
|
|
655
|
+
".": [" ", " ", " ", " ", " ██ "],
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const MAGNITUDE_WORDS = { K: "THOUSAND", M: "MILLION", B: "BILLION", T: "TRILLION" };
|
|
659
|
+
|
|
660
|
+
function centerVisible(line, width) {
|
|
661
|
+
const pad = Math.max(0, Math.floor((width - visibleLength(line)) / 2));
|
|
662
|
+
return " ".repeat(pad) + line;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function heroColor(t) {
|
|
666
|
+
return t < 0.5 ? lerp(EMBER, AMBER, t * 2) : lerp(AMBER, [255, 240, 205], (t - 0.5) * 2);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// big upright number, each pixel doubled horizontally for weight
|
|
670
|
+
function heroNumber(text) {
|
|
671
|
+
const rows = ["", "", "", "", ""];
|
|
672
|
+
for (const char of text) {
|
|
673
|
+
const glyph = HERO_DIGITS[char] || HERO_DIGITS["."];
|
|
674
|
+
for (let row = 0; row < 5; row += 1) {
|
|
675
|
+
rows[row] += glyph[row].replace(/./g, (cell) => (cell === "█" ? "██" : " ")) + " ";
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const totalCols = Math.max(...rows.map((row) => row.length));
|
|
680
|
+
return rows.map((row) => {
|
|
681
|
+
let out = "";
|
|
682
|
+
for (let col = 0; col < row.length; col += 1) {
|
|
683
|
+
out += row[col] === " " ? " " : fg(heroColor(totalCols <= 1 ? 1 : col / (totalCols - 1))) + row[col];
|
|
684
|
+
}
|
|
685
|
+
return out + reset();
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function heroBlock(combined, width) {
|
|
690
|
+
const tokens = tokenTotal(combined.tokens);
|
|
691
|
+
const useTokens = tokens > 0;
|
|
692
|
+
const compact = compactNumber(useTokens ? tokens : combined.totalMessages);
|
|
693
|
+
const match = compact.match(/^([\d.,]+)([A-Z]?)$/) || [compact, compact, ""];
|
|
694
|
+
const digits = match[1].replace(/,/g, "");
|
|
695
|
+
const magnitude = MAGNITUDE_WORDS[match[2]] || "";
|
|
696
|
+
const noun = useTokens ? "TOKENS PROCESSED" : "MESSAGES SENT";
|
|
697
|
+
const label = `${magnitude ? `${magnitude} ` : ""}${noun}`;
|
|
698
|
+
|
|
699
|
+
const big = heroNumber(digits);
|
|
700
|
+
const lines = ["", ...big.map((line) => centerVisible(line, width)), ""];
|
|
701
|
+
lines.push(centerVisible(bold(paint(AMBER, label.split("").join(" "))), width));
|
|
702
|
+
|
|
703
|
+
if (useTokens) {
|
|
704
|
+
const words = Math.round(tokens * 0.75);
|
|
705
|
+
const novels = words / WAR_AND_PEACE_WORDS;
|
|
706
|
+
const burn = estimateModelCosts(combined.modelUsage).totalUSD;
|
|
707
|
+
const novelText =
|
|
708
|
+
novels >= 1 ? `${novels < 10 ? novels.toFixed(1) : compactNumber(Math.round(novels))}× War & Peace` : null;
|
|
709
|
+
const parts = [`≈ ${compactNumber(words)} words`];
|
|
710
|
+
if (novelText) parts.push(novelText);
|
|
711
|
+
if (burn > 0) parts.push(`≈ ${fmtUSD(burn)} at list price`);
|
|
712
|
+
lines.push("");
|
|
713
|
+
lines.push(centerVisible(paint(SHADOW, parts.join(" · ")), width));
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return lines;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// ---------------------------------------------------------------------------
|
|
720
|
+
// report
|
|
721
|
+
// ---------------------------------------------------------------------------
|
|
722
|
+
|
|
723
|
+
export function renderReportBlocks(payload) {
|
|
724
|
+
const { reports, combined, generatedAt } = payload;
|
|
725
|
+
const width = reportWidth();
|
|
726
|
+
const interior = width - 4;
|
|
727
|
+
const blocks = [];
|
|
728
|
+
let lines = [];
|
|
729
|
+
const endBlock = () => {
|
|
730
|
+
blocks.push(lines.join("\n"));
|
|
731
|
+
lines = [];
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
// banner + status header
|
|
735
|
+
lines.push("");
|
|
736
|
+
lines.push(...banner("AI WRAPPED", 2));
|
|
737
|
+
lines.push("");
|
|
738
|
+
const range =
|
|
739
|
+
combined.firstSeen && combined.lastSeen
|
|
740
|
+
? `${shortDate(combined.firstSeen)} → ${shortDate(combined.lastSeen)}`
|
|
741
|
+
: "no history";
|
|
742
|
+
const subtitle =
|
|
743
|
+
" " +
|
|
744
|
+
paint(GRAY, "TERMINAL USAGE REPORT ") +
|
|
745
|
+
paint(SHADOW, "· ") +
|
|
746
|
+
paint(AMBER, range) +
|
|
747
|
+
paint(SHADOW, " · ") +
|
|
748
|
+
paint(GRAY, "LOCAL ONLY");
|
|
749
|
+
const generated = paint(SHADOW, `generated ${new Date(generatedAt).toLocaleString()}`);
|
|
750
|
+
if (visibleLength(subtitle) + visibleLength(generated) + 3 <= width) {
|
|
751
|
+
lines.push(`${subtitle}${paint(SHADOW, " · ")}${generated}`);
|
|
752
|
+
} else {
|
|
753
|
+
lines.push(subtitle);
|
|
754
|
+
lines.push(` ${generated}`);
|
|
755
|
+
}
|
|
756
|
+
for (const report of reports) {
|
|
757
|
+
const hue = providerColor(report.key);
|
|
758
|
+
const dot = report.available ? paint(hue, "●") : paint(RED, "○");
|
|
759
|
+
const status = report.available ? paint(GRAY, report.root || "n/a") : paint(RED, "missing");
|
|
760
|
+
const extra =
|
|
761
|
+
report.key === "claude" && report.available && report.projectConfigDirs
|
|
762
|
+
? paint(SHADOW, ` +${report.projectConfigDirs} project .claude dirs`)
|
|
763
|
+
: "";
|
|
764
|
+
lines.push(` ${dot} ${padEndVisible(paint(hue, report.provider), 13)} ${status}${extra}`);
|
|
765
|
+
}
|
|
766
|
+
lines.push("");
|
|
767
|
+
endBlock();
|
|
768
|
+
|
|
769
|
+
// hero — the screenshot stat
|
|
770
|
+
lines.push(...heroBlock(combined, width));
|
|
771
|
+
lines.push("");
|
|
772
|
+
endBlock();
|
|
773
|
+
|
|
774
|
+
// overview tiles
|
|
775
|
+
const burn = estimateModelCosts(combined.modelUsage);
|
|
776
|
+
const spendTile = combined.knownSpendUSD
|
|
777
|
+
? { label: "known spend", value: fmtUSD(combined.knownSpendUSD), accent: AMBER }
|
|
778
|
+
: burn.totalUSD > 0
|
|
779
|
+
? { label: "est. burn (list price)", value: `≈ ${fmtUSD(burn.totalUSD)}`, accent: AMBER }
|
|
780
|
+
: { label: "known spend", value: dollars(0), accent: AMBER };
|
|
781
|
+
lines.push(
|
|
782
|
+
...statTiles(
|
|
783
|
+
[
|
|
784
|
+
{ label: "messages", value: formatInteger(combined.totalMessages), accent: RED },
|
|
785
|
+
{ label: "all-time tokens", value: compactNumber(tokenTotal(combined.tokens)) },
|
|
786
|
+
{ label: "sessions", value: `${formatInteger(combined.totalSessions)} · ${formatInteger(combined.sessionFiles)} files` },
|
|
787
|
+
spendTile,
|
|
788
|
+
{
|
|
789
|
+
label: "favorite model",
|
|
790
|
+
value: combined.favoriteModel ? combined.favoriteModel.model : "unknown",
|
|
791
|
+
accent: combined.favoriteModel ? providerColor(combined.favoriteModel.model) : RED,
|
|
792
|
+
},
|
|
793
|
+
{ label: "history range", value: range, accent: AMBER },
|
|
794
|
+
],
|
|
795
|
+
width,
|
|
796
|
+
),
|
|
797
|
+
);
|
|
798
|
+
lines.push("");
|
|
799
|
+
endBlock();
|
|
800
|
+
|
|
801
|
+
// 01 messages by source
|
|
802
|
+
lines.push(
|
|
803
|
+
...panel(
|
|
804
|
+
"01 · messages",
|
|
805
|
+
barChart(
|
|
806
|
+
[combined, ...reports].map((report) => ({
|
|
807
|
+
label: report.provider,
|
|
808
|
+
value: report.totalMessages,
|
|
809
|
+
color: providerColor(report.key),
|
|
810
|
+
})),
|
|
811
|
+
interior,
|
|
812
|
+
{ valueFormatter: formatInteger, valueWidth: 11 },
|
|
813
|
+
),
|
|
814
|
+
width,
|
|
815
|
+
),
|
|
816
|
+
);
|
|
817
|
+
endBlock();
|
|
818
|
+
|
|
819
|
+
// 02 monthly tokens
|
|
820
|
+
lines.push(
|
|
821
|
+
...panel(
|
|
822
|
+
"02 · monthly tokens",
|
|
823
|
+
barChart(
|
|
824
|
+
sortedEntries(combined.monthlyTokens).map(([month, tokens]) => ({
|
|
825
|
+
label: monthLabel(month),
|
|
826
|
+
value: tokenTotal(tokens),
|
|
827
|
+
})),
|
|
828
|
+
interior,
|
|
829
|
+
{ valueFormatter: compactNumber, rankTop: true },
|
|
830
|
+
),
|
|
831
|
+
width,
|
|
832
|
+
),
|
|
833
|
+
);
|
|
834
|
+
endBlock();
|
|
835
|
+
|
|
836
|
+
// 03 burn — local spend cache when present, list-price estimate otherwise
|
|
837
|
+
lines.push(...panel("03 · burn — list-price estimate", buildBurnLines(combined, interior), width));
|
|
838
|
+
endBlock();
|
|
839
|
+
|
|
840
|
+
// 04 all-time tokens by source
|
|
841
|
+
lines.push(
|
|
842
|
+
...panel(
|
|
843
|
+
"04 · all-time tokens",
|
|
844
|
+
barChart(
|
|
845
|
+
[combined, ...reports].map((report) => ({
|
|
846
|
+
label: report.provider,
|
|
847
|
+
value: tokenTotal(report.tokens),
|
|
848
|
+
color: providerColor(report.key),
|
|
849
|
+
})),
|
|
850
|
+
interior,
|
|
851
|
+
{ valueFormatter: compactNumber },
|
|
852
|
+
),
|
|
853
|
+
width,
|
|
854
|
+
),
|
|
855
|
+
);
|
|
856
|
+
endBlock();
|
|
857
|
+
|
|
858
|
+
// 05 token ledger
|
|
859
|
+
lines.push(...panel("05 · token ledger", tokenSummaryTable([combined, ...reports]), width));
|
|
860
|
+
endBlock();
|
|
861
|
+
|
|
862
|
+
// 06 token mix
|
|
863
|
+
lines.push(
|
|
864
|
+
...panel(
|
|
865
|
+
"06 · token mix",
|
|
866
|
+
stackedShareBar(
|
|
867
|
+
[
|
|
868
|
+
{ label: "input", value: combined.tokens.input || 0, color: AMBER },
|
|
869
|
+
{ label: "output", value: combined.tokens.output || 0, color: RED },
|
|
870
|
+
{ label: "cache read", value: combined.tokens.cacheRead || 0, color: EMBER },
|
|
871
|
+
{ label: "cache write", value: combined.tokens.cacheCreation || 0, color: ORANGE },
|
|
872
|
+
{ label: "reasoning", value: combined.tokens.reasoning || 0, color: GRAY },
|
|
873
|
+
],
|
|
874
|
+
interior,
|
|
875
|
+
),
|
|
876
|
+
width,
|
|
877
|
+
),
|
|
878
|
+
);
|
|
879
|
+
endBlock();
|
|
880
|
+
|
|
881
|
+
// 07 model leaderboard (total tokens)
|
|
882
|
+
lines.push(
|
|
883
|
+
...panel(
|
|
884
|
+
"07 · model leaderboard — total tokens",
|
|
885
|
+
barChart(
|
|
886
|
+
Object.values(combined.modelUsage)
|
|
887
|
+
.map((usage) => ({ label: modelLabel(usage), value: tokenTotal(usage), color: providerColor(usage.provider) }))
|
|
888
|
+
.sort((a, b) => b.value - a.value)
|
|
889
|
+
.slice(0, 16),
|
|
890
|
+
interior,
|
|
891
|
+
{ valueFormatter: compactNumber, labelWidth: 32, ranked: true },
|
|
892
|
+
),
|
|
893
|
+
width,
|
|
894
|
+
),
|
|
895
|
+
);
|
|
896
|
+
endBlock();
|
|
897
|
+
|
|
898
|
+
// 08 model standing (input + output)
|
|
899
|
+
lines.push(
|
|
900
|
+
...panel(
|
|
901
|
+
"08 · model standing — input + output",
|
|
902
|
+
[
|
|
903
|
+
...barChart(
|
|
904
|
+
Object.values(combined.modelUsage)
|
|
905
|
+
.map((usage) => ({
|
|
906
|
+
label: modelLabel(usage),
|
|
907
|
+
value: (usage.input || 0) + (usage.output || 0),
|
|
908
|
+
color: providerColor(usage.provider),
|
|
909
|
+
}))
|
|
910
|
+
.sort((a, b) => b.value - a.value)
|
|
911
|
+
.slice(0, 12),
|
|
912
|
+
interior,
|
|
913
|
+
{ valueFormatter: compactNumber, labelWidth: 32, ranked: true },
|
|
914
|
+
),
|
|
915
|
+
"",
|
|
916
|
+
paint(SHADOW, "favorite = highest input + output token count"),
|
|
917
|
+
],
|
|
918
|
+
width,
|
|
919
|
+
),
|
|
920
|
+
);
|
|
921
|
+
endBlock();
|
|
922
|
+
|
|
923
|
+
// 09 time of day
|
|
924
|
+
lines.push(...panel("09 · active hours", hourHistogram(combined.hourlyActivity), width));
|
|
925
|
+
endBlock();
|
|
926
|
+
|
|
927
|
+
// 10 daily activity matrix
|
|
928
|
+
lines.push(...panel("10 · activity matrix", activityMatrix(combined.dailyActivity, interior), width));
|
|
929
|
+
endBlock();
|
|
930
|
+
|
|
931
|
+
return blocks;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// ---------------------------------------------------------------------------
|
|
935
|
+
// decode reveal — scramble cipher glyphs, then resolve left-to-right
|
|
936
|
+
// ---------------------------------------------------------------------------
|
|
937
|
+
|
|
938
|
+
const SCRAMBLE_CHARS = "01<>/\\|+=*#%&@?!;:^~";
|
|
939
|
+
const SCRAMBLE_TARGET = /[A-Za-z0-9]/;
|
|
940
|
+
|
|
941
|
+
function sleep(ms) {
|
|
942
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
export async function animateReveal(text) {
|
|
946
|
+
const lines = text.split("\n");
|
|
947
|
+
process.stdout.write("\x1b[?25l");
|
|
948
|
+
try {
|
|
949
|
+
for (const line of lines) {
|
|
950
|
+
const plain = line.replace(ANSI_PATTERN, "");
|
|
951
|
+
if (!plain.trim()) {
|
|
952
|
+
process.stdout.write(`${line}\n`);
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
const FRAMES = 2;
|
|
956
|
+
for (let frame = 0; frame < FRAMES; frame += 1) {
|
|
957
|
+
const resolved = Math.floor(plain.length * (frame / FRAMES));
|
|
958
|
+
let scrambled = "";
|
|
959
|
+
for (let index = 0; index < plain.length; index += 1) {
|
|
960
|
+
const char = plain[index];
|
|
961
|
+
scrambled +=
|
|
962
|
+
index < resolved || !SCRAMBLE_TARGET.test(char)
|
|
963
|
+
? char
|
|
964
|
+
: SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)];
|
|
965
|
+
}
|
|
966
|
+
process.stdout.write(`\r\x1b[2K${fg(SHADOW)}${scrambled}${reset()}`);
|
|
967
|
+
await sleep(5);
|
|
968
|
+
}
|
|
969
|
+
process.stdout.write(`\r\x1b[2K${line}\n`);
|
|
970
|
+
}
|
|
971
|
+
} finally {
|
|
972
|
+
process.stdout.write("\x1b[?25h");
|
|
973
|
+
}
|
|
974
|
+
}
|