tledger 0.1.3 → 0.2.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 +121 -67
- package/bin/token-ledger-rates.mjs +62 -0
- package/bin/token-ledger-terminal.mjs +97 -154
- package/bin/token-ledger-trend-image.mjs +945 -0
- package/bin/token-ledger-trend-terminal.mjs +609 -0
- package/bin/token-ledger-trend.mjs +745 -0
- package/bin/token-ledger-tui.mjs +15 -21
- package/bin/token-ledger.mjs +390 -102
- package/lib/{token-ledger-collector.mjs → token-ledger-importer.mjs} +244 -407
- package/package.json +17 -10
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeQuotaTimeline,
|
|
3
|
+
weeklyQuotaObservations,
|
|
4
|
+
} from "./token-ledger-trend.mjs";
|
|
5
|
+
|
|
1
6
|
const RESET = "\u001b[0m";
|
|
2
|
-
const
|
|
3
|
-
const
|
|
7
|
+
const PRIMARY_STYLE = [38, 2, 255, 255, 255];
|
|
8
|
+
const SECONDARY_STYLE = [38, 2, 155, 155, 155];
|
|
9
|
+
const ACCENT_STYLE = [38, 2, 51, 156, 255];
|
|
10
|
+
const BORDER_STYLE = [38, 2, 88, 88, 88];
|
|
11
|
+
const TRACK_STYLE = [38, 2, 59, 59, 59];
|
|
4
12
|
export const MODEL_COLORS = {
|
|
5
|
-
sol: [38,
|
|
6
|
-
luna:
|
|
7
|
-
terra: [38,
|
|
8
|
-
gpt: [38,
|
|
9
|
-
|
|
10
|
-
other: [38, 5, 60],
|
|
13
|
+
sol: [38, 2, 120, 185, 242],
|
|
14
|
+
luna: ACCENT_STYLE,
|
|
15
|
+
terra: [38, 2, 214, 168, 95],
|
|
16
|
+
gpt: [38, 2, 174, 139, 219],
|
|
17
|
+
other: [38, 2, 116, 125, 144],
|
|
11
18
|
};
|
|
12
|
-
const TEXT_STYLE =
|
|
13
|
-
const TITLE_STYLE = [1,
|
|
14
|
-
const SUBTITLE_STYLE =
|
|
19
|
+
const TEXT_STYLE = PRIMARY_STYLE;
|
|
20
|
+
const TITLE_STYLE = [1, ...PRIMARY_STYLE];
|
|
21
|
+
const SUBTITLE_STYLE = SECONDARY_STYLE;
|
|
22
|
+
const SELECTED_BACKGROUND = "\u001b[48;2;42;42;42m";
|
|
15
23
|
|
|
16
24
|
function colorsEnabled(options) {
|
|
17
25
|
return options.forceColor ?? (!options.plain && !process.env.NO_COLOR && Boolean(process.stdout.isTTY));
|
|
@@ -23,16 +31,7 @@ function colorize(value, code, enabled) {
|
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
function stripAnsi(value) {
|
|
26
|
-
return String(value)
|
|
27
|
-
.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, "")
|
|
28
|
-
.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function sanitizeText(value) {
|
|
32
|
-
return stripAnsi(value)
|
|
33
|
-
.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ")
|
|
34
|
-
.replace(/\s+/g, " ")
|
|
35
|
-
.trim();
|
|
34
|
+
return String(value).replace(/\u001b\[[0-9;]*m/g, "");
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
function visibleLength(value) {
|
|
@@ -94,11 +93,8 @@ function plural(value, singular, pluralForm = `${singular}s`) {
|
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
function modelLabel(value) {
|
|
97
|
-
const model =
|
|
98
|
-
const lower = model.toLowerCase()
|
|
99
|
-
if (lower === "codex-auto-review" || lower.startsWith("codex-auto-review-")) {
|
|
100
|
-
return "Auto Review";
|
|
101
|
-
}
|
|
96
|
+
const model = String(value || "Unknown model");
|
|
97
|
+
const lower = model.toLowerCase();
|
|
102
98
|
if (lower.includes("sol")) return "Sol";
|
|
103
99
|
if (lower.includes("luna")) return "Luna";
|
|
104
100
|
if (lower.includes("terra")) return "Terra";
|
|
@@ -107,15 +103,13 @@ function modelLabel(value) {
|
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
function modelColor(model) {
|
|
110
|
-
const key = String(model || "")
|
|
111
|
-
.toLowerCase()
|
|
112
|
-
.replace(/[\s_-]+/g, "-");
|
|
113
|
-
if (key === "auto-review") return MODEL_COLORS.autoReview;
|
|
106
|
+
const key = String(model || "").toLowerCase();
|
|
114
107
|
return MODEL_COLORS[key] ?? MODEL_COLORS.other;
|
|
115
108
|
}
|
|
116
109
|
|
|
117
110
|
function usageTypeLabel(value) {
|
|
118
|
-
const words = (
|
|
111
|
+
const words = String(value || "unknown")
|
|
112
|
+
.trim()
|
|
119
113
|
.replace(/[_-]+/g, " ")
|
|
120
114
|
.split(/\s+/)
|
|
121
115
|
.filter(Boolean);
|
|
@@ -130,7 +124,7 @@ function usageTypeLabel(value) {
|
|
|
130
124
|
}
|
|
131
125
|
|
|
132
126
|
function displayProject(row) {
|
|
133
|
-
return
|
|
127
|
+
return row.displayProject || row.project || "Unlabelled activity";
|
|
134
128
|
}
|
|
135
129
|
|
|
136
130
|
function dateLabel(bounds, range = "day") {
|
|
@@ -174,9 +168,7 @@ function modelTotals(events) {
|
|
|
174
168
|
function usageTypeTotals(events) {
|
|
175
169
|
const totals = new Map();
|
|
176
170
|
for (const event of events) {
|
|
177
|
-
const key =
|
|
178
|
-
? "auto-review"
|
|
179
|
-
: String(event.useType || "unknown").trim().toLowerCase() || "unknown";
|
|
171
|
+
const key = String(event.useType || "unknown").trim().toLowerCase() || "unknown";
|
|
180
172
|
totals.set(key, (totals.get(key) ?? 0) + (Number(event.totalTokens) || 0));
|
|
181
173
|
}
|
|
182
174
|
return [...totals.entries()]
|
|
@@ -188,22 +180,17 @@ function usageTypeTotals(events) {
|
|
|
188
180
|
.sort((left, right) => right.totalTokens - left.totalTokens);
|
|
189
181
|
}
|
|
190
182
|
|
|
191
|
-
function latestWeeklyQuotaObservation(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
.sort(
|
|
199
|
-
(left, right) =>
|
|
200
|
-
new Date(left.timestamp).getTime() - new Date(right.timestamp).getTime(),
|
|
201
|
-
)
|
|
202
|
-
.at(-1) ?? null;
|
|
183
|
+
function latestWeeklyQuotaObservation(snapshot) {
|
|
184
|
+
// The epoch-keyed timeline drops stale readings from superseded windows,
|
|
185
|
+
// so the last entry is the newest reading of the currently-live window.
|
|
186
|
+
const normalized = normalizeQuotaTimeline(weeklyQuotaObservations(snapshot));
|
|
187
|
+
const latest = normalized.at(-1);
|
|
188
|
+
if (!latest) return null;
|
|
189
|
+
return { ...latest, usedPercent: latest.normalizedUsedPercent };
|
|
203
190
|
}
|
|
204
191
|
|
|
205
192
|
export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
206
|
-
const observation = latestWeeklyQuotaObservation(snapshot
|
|
193
|
+
const observation = latestWeeklyQuotaObservation(snapshot);
|
|
207
194
|
if (!observation) {
|
|
208
195
|
return {
|
|
209
196
|
available: false,
|
|
@@ -291,32 +278,6 @@ function summary(events) {
|
|
|
291
278
|
const cached = Math.max(0, Number(event.cachedInputTokens) || 0);
|
|
292
279
|
return sum + Math.min(input, cached);
|
|
293
280
|
}, 0);
|
|
294
|
-
const turnCount = (rows) => {
|
|
295
|
-
const keys = new Set();
|
|
296
|
-
for (const [index, event] of rows.entries()) {
|
|
297
|
-
const turnId = String(event.turnId || "").trim();
|
|
298
|
-
keys.add(turnId ? `turn:${turnId}` : `event:${event.id || index}`);
|
|
299
|
-
}
|
|
300
|
-
return keys.size;
|
|
301
|
-
};
|
|
302
|
-
const totalTurns = turnCount(events);
|
|
303
|
-
const autoReviewEvents = events.filter(
|
|
304
|
-
(event) => modelLabel(event.model) === "Auto Review",
|
|
305
|
-
);
|
|
306
|
-
const autoReviewTokens = autoReviewEvents.reduce(
|
|
307
|
-
(sum, event) => sum + (Number(event.totalTokens) || 0),
|
|
308
|
-
0,
|
|
309
|
-
);
|
|
310
|
-
const autoReviewInputTokens = autoReviewEvents.reduce(
|
|
311
|
-
(sum, event) => sum + Math.max(0, Number(event.inputTokens) || 0),
|
|
312
|
-
0,
|
|
313
|
-
);
|
|
314
|
-
const autoReviewCachedInputTokens = autoReviewEvents.reduce((sum, event) => {
|
|
315
|
-
const input = Math.max(0, Number(event.inputTokens) || 0);
|
|
316
|
-
const cached = Math.max(0, Number(event.cachedInputTokens) || 0);
|
|
317
|
-
return sum + Math.min(input, cached);
|
|
318
|
-
}, 0);
|
|
319
|
-
const autoReviewTurns = turnCount(autoReviewEvents);
|
|
320
281
|
return {
|
|
321
282
|
totalTokens,
|
|
322
283
|
calls,
|
|
@@ -327,30 +288,18 @@ function summary(events) {
|
|
|
327
288
|
uncachedInputTokens: Math.max(0, inputTokens - cachedInputTokens),
|
|
328
289
|
models: modelTotals(events),
|
|
329
290
|
usageTypes: usageTypeTotals(events),
|
|
330
|
-
autoReview: {
|
|
331
|
-
present: autoReviewEvents.length > 0,
|
|
332
|
-
totalTokens: autoReviewTokens,
|
|
333
|
-
turns: autoReviewTurns,
|
|
334
|
-
turnShare: totalTurns > 0 ? (autoReviewTurns / totalTurns) * 100 : 0,
|
|
335
|
-
cachedInputShare: autoReviewInputTokens > 0
|
|
336
|
-
? (autoReviewCachedInputTokens / autoReviewInputTokens) * 100
|
|
337
|
-
: 0,
|
|
338
|
-
},
|
|
339
291
|
};
|
|
340
292
|
}
|
|
341
293
|
|
|
342
294
|
function modelLegendItems(models, totalTokens) {
|
|
343
295
|
const known = new Map();
|
|
344
296
|
for (const model of models) {
|
|
345
|
-
const key = ["Sol", "Luna", "Terra", "GPT"
|
|
297
|
+
const key = ["Sol", "Luna", "Terra", "GPT"].includes(model.model)
|
|
346
298
|
? model.model
|
|
347
299
|
: "Other";
|
|
348
300
|
known.set(key, (known.get(key) ?? 0) + model.totalTokens);
|
|
349
301
|
}
|
|
350
|
-
|
|
351
|
-
if ((known.get("Auto Review") ?? 0) > 0) order.push("Auto Review");
|
|
352
|
-
order.push("Other");
|
|
353
|
-
return order
|
|
302
|
+
return ["Luna", "Sol", "Terra", "GPT", "Other"]
|
|
354
303
|
.map((model) => ({
|
|
355
304
|
model,
|
|
356
305
|
totalTokens: known.get(model) ?? 0,
|
|
@@ -358,27 +307,6 @@ function modelLegendItems(models, totalTokens) {
|
|
|
358
307
|
}));
|
|
359
308
|
}
|
|
360
309
|
|
|
361
|
-
function visibleUsageTypeItems(items, limit = 5) {
|
|
362
|
-
if (items.length <= limit) return items;
|
|
363
|
-
const visible = items.slice(0, limit - 1);
|
|
364
|
-
const autoReview = items.find((item) => item.key === "auto-review");
|
|
365
|
-
if (autoReview && !visible.includes(autoReview)) {
|
|
366
|
-
visible[visible.length - 1] = autoReview;
|
|
367
|
-
visible.sort((left, right) => right.totalTokens - left.totalTokens);
|
|
368
|
-
}
|
|
369
|
-
const visibleKeys = new Set(visible.map((item) => item.key));
|
|
370
|
-
return [
|
|
371
|
-
...visible,
|
|
372
|
-
{
|
|
373
|
-
key: "other",
|
|
374
|
-
label: "Other",
|
|
375
|
-
totalTokens: items
|
|
376
|
-
.filter((item) => !visibleKeys.has(item.key))
|
|
377
|
-
.reduce((sum, item) => sum + item.totalTokens, 0),
|
|
378
|
-
},
|
|
379
|
-
];
|
|
380
|
-
}
|
|
381
|
-
|
|
382
310
|
function stackedBar(row, width, maximumTokens, options, enabled) {
|
|
383
311
|
const symbol = options.ascii ? "#" : "█";
|
|
384
312
|
const trackSymbol = options.ascii ? "." : "░";
|
|
@@ -408,7 +336,7 @@ function stackedBar(row, width, maximumTokens, options, enabled) {
|
|
|
408
336
|
.join("");
|
|
409
337
|
const blank = colorize(
|
|
410
338
|
trackSymbol.repeat(Math.max(0, width - visibleLength(filled))),
|
|
411
|
-
|
|
339
|
+
TRACK_STYLE,
|
|
412
340
|
enabled,
|
|
413
341
|
);
|
|
414
342
|
return `${filled}${blank}`;
|
|
@@ -430,8 +358,13 @@ function panelLines(rows, allRows, totalTokens, panelWidth, options, enabled) {
|
|
|
430
358
|
panelWidth - labelWidth - shareWidth - totalWidth - 1 - rightPadding,
|
|
431
359
|
);
|
|
432
360
|
const maxTokens = allRows[0]?.totalTokens ?? 0;
|
|
361
|
+
const totalCredits = allRows.reduce((sum, item) => sum + item.rateCardCredits, 0) || 1;
|
|
362
|
+
const selectedIndex = Math.min(
|
|
363
|
+
Math.max(0, Math.trunc(Number(options.selectedIndex) || 0)),
|
|
364
|
+
Math.max(0, rows.length - 1),
|
|
365
|
+
);
|
|
433
366
|
const lines = [];
|
|
434
|
-
const heading = colorize("TOKENS BY PROJECT",
|
|
367
|
+
const heading = colorize("TOKENS BY PROJECT", ACCENT_STYLE, enabled);
|
|
435
368
|
const barHeaderWidth = barWidth;
|
|
436
369
|
const maximumLabel = compactMode ? `${compact(maxTokens)} max` : `${compact(maxTokens)} (max)`;
|
|
437
370
|
const axisLabel = barHeaderWidth >= maximumLabel.length + 2 ? maximumLabel : compact(maxTokens);
|
|
@@ -441,28 +374,33 @@ function panelLines(rows, allRows, totalTokens, panelWidth, options, enabled) {
|
|
|
441
374
|
lines.push(
|
|
442
375
|
`${fit(heading, labelWidth)}${fit(axisText, barHeaderWidth)}${fit("TOKENS", totalWidth, "right")}${fit("SHARE", shareWidth, "right")}${" ".repeat(rightPadding)}`,
|
|
443
376
|
);
|
|
444
|
-
lines.push(colorize("─".repeat(panelWidth),
|
|
377
|
+
lines.push(colorize("─".repeat(panelWidth), BORDER_STYLE, enabled));
|
|
445
378
|
|
|
446
379
|
for (const [index, row] of rows.entries()) {
|
|
447
380
|
const share = totalTokens > 0 ? (row.totalTokens / totalTokens) * 100 : 0;
|
|
448
|
-
const selected =
|
|
381
|
+
const selected = index === selectedIndex;
|
|
449
382
|
const caretValue = selected ? (options.ascii ? ">" : "▶") : " ";
|
|
450
383
|
const prefix = `${caretValue} ${index + 1}. `;
|
|
451
384
|
const projectText = truncateText(displayProject(row), labelWidth - prefix.length);
|
|
452
|
-
const caret = selected ? colorize(caretValue, [1, ...
|
|
385
|
+
const caret = selected ? colorize(caretValue, [1, ...ACCENT_STYLE], enabled) : caretValue;
|
|
453
386
|
const rank = colorize(`${index + 1}.`, TITLE_STYLE, enabled);
|
|
454
387
|
const title = `${caret} ${rank} ${colorize(projectText, TITLE_STYLE, enabled)}`;
|
|
455
388
|
const label = fit(title, labelWidth);
|
|
456
389
|
const metrics = `${fit(compact(row.totalTokens), totalWidth, "right")}${fit(percent(share), shareWidth, "right")}${" ".repeat(rightPadding)}`;
|
|
457
390
|
const bar = stackedBar(row, barWidth, maxTokens, options, enabled);
|
|
458
391
|
const rowLine = `${label}${bar} ${metrics}`;
|
|
459
|
-
const
|
|
392
|
+
const creditShare = row.rateCardCredits > 0 && row.rateCardCredits <= Number.MAX_SAFE_INTEGER
|
|
393
|
+
? row.rateCardCredits
|
|
394
|
+
: 0;
|
|
395
|
+
const detail = `${plural(row.threads, "thread")} · ${percent(
|
|
396
|
+
creditShare > 0 ? (creditShare / totalCredits) * 100 : 0,
|
|
397
|
+
)}${labelWidth >= 35 ? " credits" : ""}`;
|
|
460
398
|
const detailText = truncateText(detail, labelWidth - 4);
|
|
461
399
|
const subtitle = colorize(detailText, SUBTITLE_STYLE, enabled);
|
|
462
400
|
const detailLine = `${fit(` ${subtitle}`, labelWidth)}${" ".repeat(barWidth + 1 + totalWidth + shareWidth + rightPadding)}`;
|
|
463
401
|
if (selected && enabled && options.highlight !== false) {
|
|
464
|
-
lines.push(
|
|
465
|
-
lines.push(
|
|
402
|
+
lines.push(`${SELECTED_BACKGROUND}${fit(rowLine, panelWidth)}${RESET}`);
|
|
403
|
+
lines.push(`${SELECTED_BACKGROUND}${fit(detailLine, panelWidth)}${RESET}`);
|
|
466
404
|
} else {
|
|
467
405
|
lines.push(rowLine);
|
|
468
406
|
lines.push(detailLine);
|
|
@@ -479,30 +417,36 @@ function sidebarLines(stats, panelWidth, enabled, options = {}, quota = null) {
|
|
|
479
417
|
const push = (line = "") => {
|
|
480
418
|
lines.push(`${" ".repeat(inset)}${fit(line, contentWidth)}${" ".repeat(inset)}`);
|
|
481
419
|
};
|
|
482
|
-
const divider = () => push(colorize("─".repeat(contentWidth),
|
|
483
|
-
const heading = (value) => colorize(value,
|
|
420
|
+
const divider = () => push(colorize("─".repeat(contentWidth), BORDER_STYLE, enabled));
|
|
421
|
+
const heading = (value) => colorize(value, ACCENT_STYLE, enabled);
|
|
484
422
|
push(heading("MODEL MIX"));
|
|
485
423
|
if (!compactSidebar) push();
|
|
486
424
|
for (const item of modelLegendItems(stats.models, stats.totalTokens)) {
|
|
487
425
|
const swatch = colorize("■", modelColor(item.model), enabled);
|
|
488
426
|
push(`${swatch} ${fit(item.model, Math.max(1, contentWidth - 10))}${fit(percent(item.share), 8, "right")}`);
|
|
489
|
-
if (item.model === "Auto Review" && stats.autoReview.present) {
|
|
490
|
-
const turnLabel = stats.autoReview.turns === 1 ? "turn" : "turns";
|
|
491
|
-
push(` ${compact(stats.autoReview.turns)} ${turnLabel} · ${percent(stats.autoReview.turnShare)}`);
|
|
492
|
-
push(` ${compact(stats.autoReview.totalTokens)} · ${percent(stats.autoReview.cachedInputShare)} cached`);
|
|
493
|
-
}
|
|
494
427
|
}
|
|
495
|
-
|
|
428
|
+
push();
|
|
496
429
|
divider();
|
|
497
430
|
push(heading("USAGE TYPE · TOKENS"));
|
|
498
431
|
if (!compactSidebar) push();
|
|
499
|
-
const usageItems =
|
|
432
|
+
const usageItems = stats.usageTypes.length > 5
|
|
433
|
+
? [
|
|
434
|
+
...stats.usageTypes.slice(0, 4),
|
|
435
|
+
{
|
|
436
|
+
key: "other",
|
|
437
|
+
label: "Other",
|
|
438
|
+
totalTokens: stats.usageTypes
|
|
439
|
+
.slice(4)
|
|
440
|
+
.reduce((sum, item) => sum + item.totalTokens, 0),
|
|
441
|
+
},
|
|
442
|
+
]
|
|
443
|
+
: stats.usageTypes;
|
|
500
444
|
for (const item of usageItems) {
|
|
501
445
|
const swatch = "■";
|
|
502
446
|
const share = stats.totalTokens > 0 ? (item.totalTokens / stats.totalTokens) * 100 : 0;
|
|
503
447
|
push(`${swatch} ${fit(item.label, Math.max(1, contentWidth - 10))}${fit(percent(share), 8, "right")}`);
|
|
504
448
|
}
|
|
505
|
-
|
|
449
|
+
push();
|
|
506
450
|
divider();
|
|
507
451
|
push(heading("CACHE · INPUT"));
|
|
508
452
|
if (!compactSidebar) push();
|
|
@@ -516,7 +460,7 @@ function sidebarLines(stats, panelWidth, enabled, options = {}, quota = null) {
|
|
|
516
460
|
push(`${swatch} ${fit(item.label, Math.max(1, contentWidth - 10))}${fit(percent(share), 8, "right")}`);
|
|
517
461
|
}
|
|
518
462
|
if (quota?.available) {
|
|
519
|
-
|
|
463
|
+
push();
|
|
520
464
|
divider();
|
|
521
465
|
push(heading("RESET CYCLE"));
|
|
522
466
|
if (!compactSidebar) push();
|
|
@@ -536,28 +480,29 @@ function panel(leftLines, rightLines, leftWidth, rightWidth, enabled, ascii) {
|
|
|
536
480
|
? { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|", tm: "+", bm: "+" }
|
|
537
481
|
: { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", tm: "┬", bm: "┴" };
|
|
538
482
|
const rows = Math.max(leftLines.length, rightLines?.length ?? 0);
|
|
539
|
-
const
|
|
483
|
+
const border = (value) => colorize(value, BORDER_STYLE, enabled);
|
|
484
|
+
const top = border(`${glyphs.tl}${glyphs.h.repeat(leftWidth)}${rightLines ? glyphs.tm : glyphs.tr}${rightLines ? glyphs.h.repeat(rightWidth) + glyphs.tr : ""}`);
|
|
540
485
|
const body = [];
|
|
541
486
|
for (let index = 0; index < rows; index += 1) {
|
|
542
487
|
const left = fit(leftLines[index] ?? "", leftWidth);
|
|
543
488
|
if (rightLines) {
|
|
544
489
|
const right = fit(rightLines[index] ?? "", rightWidth);
|
|
545
|
-
body.push(`${glyphs.v}${left}${glyphs.v}${right}${glyphs.v}`);
|
|
490
|
+
body.push(`${border(glyphs.v)}${left}${border(glyphs.v)}${right}${border(glyphs.v)}`);
|
|
546
491
|
} else {
|
|
547
|
-
body.push(`${glyphs.v}${left}${glyphs.v}`);
|
|
492
|
+
body.push(`${border(glyphs.v)}${left}${border(glyphs.v)}`);
|
|
548
493
|
}
|
|
549
494
|
}
|
|
550
|
-
const bottom = `${glyphs.bl}${glyphs.h.repeat(leftWidth)}${rightLines ? glyphs.bm : glyphs.br}${rightLines ? glyphs.h.repeat(rightWidth) + glyphs.br : ""}
|
|
495
|
+
const bottom = border(`${glyphs.bl}${glyphs.h.repeat(leftWidth)}${rightLines ? glyphs.bm : glyphs.br}${rightLines ? glyphs.h.repeat(rightWidth) + glyphs.br : ""}`);
|
|
551
496
|
return [top, ...body, bottom];
|
|
552
497
|
}
|
|
553
498
|
|
|
554
499
|
function headerLines(stats, bounds, frameWidth, options, enabled) {
|
|
555
500
|
const left = colorize("TOKEN LEDGER", TITLE_STYLE, enabled);
|
|
556
501
|
const date = colorize(dateLabel(bounds, options.range), TEXT_STYLE, enabled);
|
|
557
|
-
const mode = colorize(options.range === "week" ? "7 DAYS" : "DAY", [1, ...
|
|
502
|
+
const mode = colorize(options.range === "week" ? "7 DAYS" : "DAY", [1, ...ACCENT_STYLE], enabled);
|
|
558
503
|
const metric = (value, label) =>
|
|
559
|
-
`${colorize(String(value), TITLE_STYLE, enabled)} ${colorize(label,
|
|
560
|
-
const separator = colorize("·",
|
|
504
|
+
`${colorize(String(value), TITLE_STYLE, enabled)} ${colorize(label, SECONDARY_STYLE, enabled)}`;
|
|
505
|
+
const separator = colorize("·", SECONDARY_STYLE, enabled);
|
|
561
506
|
const join = ` ${separator} `;
|
|
562
507
|
const alignHeader = (line) => fit(` ${line}`, frameWidth);
|
|
563
508
|
const fullLine = [
|
|
@@ -580,7 +525,7 @@ function headerLines(stats, bounds, frameWidth, options, enabled) {
|
|
|
580
525
|
const compactLine = [
|
|
581
526
|
left,
|
|
582
527
|
colorize(compactDate, TEXT_STYLE, enabled),
|
|
583
|
-
colorize(compactMode, [1, ...
|
|
528
|
+
colorize(compactMode, [1, ...ACCENT_STYLE], enabled),
|
|
584
529
|
metric(`${compact(stats.totalTokens)}`, "T"),
|
|
585
530
|
metric(stats.calls.toLocaleString("en-US"), "C"),
|
|
586
531
|
metric(stats.threads.toLocaleString("en-US"), "TH"),
|
|
@@ -594,7 +539,7 @@ function headerLines(stats, bounds, frameWidth, options, enabled) {
|
|
|
594
539
|
const minimalLine = [
|
|
595
540
|
minimalTitle,
|
|
596
541
|
colorize(compactDate.replaceAll(" ", ""), TEXT_STYLE, enabled),
|
|
597
|
-
colorize(compactMode, [1, ...
|
|
542
|
+
colorize(compactMode, [1, ...ACCENT_STYLE], enabled),
|
|
598
543
|
compact(stats.totalTokens),
|
|
599
544
|
compact(stats.calls),
|
|
600
545
|
compact(stats.threads),
|
|
@@ -611,7 +556,7 @@ export function renderTerminal({ options, snapshot, bounds, events, rows, allRow
|
|
|
611
556
|
const columns = options.width ?? (Number(process.stdout.columns) || 120);
|
|
612
557
|
const frameWidth = Math.max(38, Math.min(158, columns - 2));
|
|
613
558
|
const sideBySide = options.forceSideBySide ?? frameWidth >= 100;
|
|
614
|
-
const sideWidth = sideBySide ?
|
|
559
|
+
const sideWidth = sideBySide ? 26 : 0;
|
|
615
560
|
const leftWidth = sideBySide ? frameWidth - sideWidth - 1 : frameWidth;
|
|
616
561
|
const left = panelLines(rows, allRows, stats.totalTokens, leftWidth, options, enabled);
|
|
617
562
|
const right = sideBySide ? sidebarLines(stats, sideWidth, enabled, options, quota) : null;
|
|
@@ -623,23 +568,21 @@ export function renderTerminal({ options, snapshot, bounds, events, rows, allRow
|
|
|
623
568
|
lines.push("");
|
|
624
569
|
lines.push(...sidebarLines(stats, frameWidth, enabled, options, quota));
|
|
625
570
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
);
|
|
637
|
-
}
|
|
571
|
+
lines.push("");
|
|
572
|
+
lines.push(
|
|
573
|
+
colorize(
|
|
574
|
+
options.ascii
|
|
575
|
+
? "[j/k] select [enter] inspect [d/w/m] range [q] quit"
|
|
576
|
+
: "[↑↓] select [enter] inspect [d/w/m] range [q] quit",
|
|
577
|
+
SECONDARY_STYLE,
|
|
578
|
+
enabled,
|
|
579
|
+
),
|
|
580
|
+
);
|
|
638
581
|
return lines.join("\n");
|
|
639
582
|
}
|
|
640
583
|
|
|
641
|
-
export const SCREEN_BASE = "\u001b[38;
|
|
642
|
-
const PANEL_BASE = "\u001b[38;
|
|
584
|
+
export const SCREEN_BASE = "\u001b[38;2;255;255;255m\u001b[48;2;30;30;30m";
|
|
585
|
+
const PANEL_BASE = "\u001b[38;2;255;255;255m\u001b[48;2;24;24;24m";
|
|
643
586
|
|
|
644
587
|
function paintFullscreenLine(line, width, background, enabled) {
|
|
645
588
|
const fitted = fit(line, width);
|
|
@@ -660,7 +603,6 @@ export function renderFullscreen({ options, snapshot, bounds, events, rows, allR
|
|
|
660
603
|
forceSideBySide: frameWidth >= 84,
|
|
661
604
|
highlight: false,
|
|
662
605
|
compactSidebar: true,
|
|
663
|
-
hideHelp: true,
|
|
664
606
|
width: frameWidth + 2,
|
|
665
607
|
},
|
|
666
608
|
snapshot,
|
|
@@ -670,9 +612,10 @@ export function renderFullscreen({ options, snapshot, bounds, events, rows, allR
|
|
|
670
612
|
allRows,
|
|
671
613
|
});
|
|
672
614
|
const staticLines = staticOutput.split("\n");
|
|
615
|
+
staticLines.pop();
|
|
673
616
|
if (staticLines.at(-1) === "") staticLines.pop();
|
|
674
617
|
const summaryLine = staticLines.shift() ?? "";
|
|
675
|
-
const help = colorize("↑/↓ move • j/k move • q/esc quit",
|
|
618
|
+
const help = colorize("↑/↓ move • j/k move • q/esc quit", SECONDARY_STYLE, enabled);
|
|
676
619
|
const content = [
|
|
677
620
|
{ line: summaryLine, background: SCREEN_BASE },
|
|
678
621
|
...staticLines.map((line) => ({ line, background: PANEL_BASE })),
|