tledger 0.1.4 → 0.2.1
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 +142 -78
- package/bin/token-ledger-controls.mjs +24 -0
- package/bin/token-ledger-rates.mjs +62 -0
- package/bin/token-ledger-terminal.mjs +184 -264
- 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 +26 -30
- package/bin/token-ledger.mjs +601 -253
- package/lib/{token-ledger-collector.mjs → token-ledger-importer.mjs} +256 -409
- package/package.json +19 -14
- package/lib/token-ledger-models.mjs +0 -113
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "
|
|
2
|
+
normalizeQuotaTimeline,
|
|
3
|
+
weeklyQuotaObservations,
|
|
4
|
+
} from "./token-ledger-trend.mjs";
|
|
5
|
+
import {
|
|
6
|
+
INTERACTIVE_FOOTER,
|
|
7
|
+
INTERACTIVE_HELP,
|
|
8
|
+
} from "./token-ledger-controls.mjs";
|
|
5
9
|
|
|
6
10
|
const RESET = "\u001b[0m";
|
|
7
|
-
const
|
|
8
|
-
const
|
|
11
|
+
const PRIMARY_STYLE = [38, 2, 255, 255, 255];
|
|
12
|
+
const SECONDARY_STYLE = [38, 2, 155, 155, 155];
|
|
13
|
+
const ACCENT_STYLE = [38, 2, 51, 156, 255];
|
|
14
|
+
const BORDER_STYLE = [38, 2, 88, 88, 88];
|
|
15
|
+
const TRACK_STYLE = [38, 2, 59, 59, 59];
|
|
9
16
|
export const MODEL_COLORS = {
|
|
10
|
-
sol: [38,
|
|
11
|
-
luna:
|
|
12
|
-
terra: [38,
|
|
13
|
-
gpt: [38,
|
|
14
|
-
|
|
15
|
-
autoReview: [38, 5, 153],
|
|
16
|
-
other: [38, 5, 60],
|
|
17
|
+
sol: [38, 2, 120, 185, 242],
|
|
18
|
+
luna: ACCENT_STYLE,
|
|
19
|
+
terra: [38, 2, 214, 168, 95],
|
|
20
|
+
gpt: [38, 2, 174, 139, 219],
|
|
21
|
+
other: [38, 2, 116, 125, 144],
|
|
17
22
|
};
|
|
18
|
-
const TEXT_STYLE =
|
|
19
|
-
const TITLE_STYLE = [1,
|
|
20
|
-
const SUBTITLE_STYLE =
|
|
23
|
+
const TEXT_STYLE = PRIMARY_STYLE;
|
|
24
|
+
const TITLE_STYLE = [1, ...PRIMARY_STYLE];
|
|
25
|
+
const SUBTITLE_STYLE = SECONDARY_STYLE;
|
|
26
|
+
const SELECTED_BACKGROUND = "\u001b[48;2;42;42;42m";
|
|
21
27
|
|
|
22
28
|
function colorsEnabled(options) {
|
|
23
29
|
return options.forceColor ?? (!options.plain && !process.env.NO_COLOR && Boolean(process.stdout.isTTY));
|
|
@@ -29,16 +35,7 @@ function colorize(value, code, enabled) {
|
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
function stripAnsi(value) {
|
|
32
|
-
return String(value)
|
|
33
|
-
.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, "")
|
|
34
|
-
.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function sanitizeText(value) {
|
|
38
|
-
return stripAnsi(value)
|
|
39
|
-
.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ")
|
|
40
|
-
.replace(/\s+/g, " ")
|
|
41
|
-
.trim();
|
|
38
|
+
return String(value).replace(/\u001b\[[0-9;]*m/g, "");
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
function visibleLength(value) {
|
|
@@ -100,16 +97,23 @@ function plural(value, singular, pluralForm = `${singular}s`) {
|
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
function modelLabel(value) {
|
|
103
|
-
const model =
|
|
104
|
-
|
|
100
|
+
const model = String(value || "Unknown model");
|
|
101
|
+
const lower = model.toLowerCase();
|
|
102
|
+
if (lower.includes("sol")) return "Sol";
|
|
103
|
+
if (lower.includes("luna")) return "Luna";
|
|
104
|
+
if (lower.includes("terra")) return "Terra";
|
|
105
|
+
if (lower.includes("gpt-5.5") || lower.includes("gpt-5.4")) return "GPT";
|
|
106
|
+
return "Other";
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
function modelColor(model) {
|
|
108
|
-
|
|
110
|
+
const key = String(model || "").toLowerCase();
|
|
111
|
+
return MODEL_COLORS[key] ?? MODEL_COLORS.other;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
function usageTypeLabel(value) {
|
|
112
|
-
const words = (
|
|
115
|
+
const words = String(value || "unknown")
|
|
116
|
+
.trim()
|
|
113
117
|
.replace(/[_-]+/g, " ")
|
|
114
118
|
.split(/\s+/)
|
|
115
119
|
.filter(Boolean);
|
|
@@ -124,11 +128,12 @@ function usageTypeLabel(value) {
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
function displayProject(row) {
|
|
127
|
-
return
|
|
131
|
+
return row.displayProject || row.project || "Unlabelled activity";
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
function dateLabel(bounds, range = "day") {
|
|
131
|
-
if (range
|
|
135
|
+
if (range === "rolling24h") return "LAST 24 HOURS";
|
|
136
|
+
if (range === "week" && bounds.startDateString && bounds.endDateString) {
|
|
132
137
|
const startParts = bounds.startDateString.split("-").map(Number);
|
|
133
138
|
const endParts = bounds.endDateString.split("-").map(Number);
|
|
134
139
|
const monthNames = [
|
|
@@ -137,12 +142,7 @@ function dateLabel(bounds, range = "day") {
|
|
|
137
142
|
];
|
|
138
143
|
const start = `${monthNames[startParts[1] - 1]} ${String(startParts[2]).padStart(2, "0")}`;
|
|
139
144
|
const end = `${monthNames[endParts[1] - 1]} ${String(endParts[2]).padStart(2, "0")}`;
|
|
140
|
-
|
|
141
|
-
return `${end} ${endParts[0]}`;
|
|
142
|
-
}
|
|
143
|
-
return startParts[0] === endParts[0]
|
|
144
|
-
? `${start} – ${end} ${endParts[0]}`
|
|
145
|
-
: `${start} ${startParts[0]} – ${end} ${endParts[0]}`;
|
|
145
|
+
return `${start} – ${end} ${endParts[0]}`;
|
|
146
146
|
}
|
|
147
147
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
148
148
|
timeZone: bounds.timeZone,
|
|
@@ -159,13 +159,6 @@ function dateLabel(bounds, range = "day") {
|
|
|
159
159
|
return `${values.weekday} ${values.day} ${values.month} ${values.year}`.toUpperCase();
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
function rangeModeLabel(bounds, range = "day", compact = false) {
|
|
163
|
-
if (range === "all") return "ALL";
|
|
164
|
-
if (range === "day") return "DAY";
|
|
165
|
-
const days = bounds.rangeDays ?? 1;
|
|
166
|
-
return compact ? `${days}D` : `${days} ${days === 1 ? "DAY" : "DAYS"}`;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
162
|
function modelTotals(events) {
|
|
170
163
|
const totals = new Map();
|
|
171
164
|
for (const event of events) {
|
|
@@ -180,9 +173,7 @@ function modelTotals(events) {
|
|
|
180
173
|
function usageTypeTotals(events) {
|
|
181
174
|
const totals = new Map();
|
|
182
175
|
for (const event of events) {
|
|
183
|
-
const key =
|
|
184
|
-
? "auto-review"
|
|
185
|
-
: String(event.useType || "unknown").trim().toLowerCase() || "unknown";
|
|
176
|
+
const key = String(event.useType || "unknown").trim().toLowerCase() || "unknown";
|
|
186
177
|
totals.set(key, (totals.get(key) ?? 0) + (Number(event.totalTokens) || 0));
|
|
187
178
|
}
|
|
188
179
|
return [...totals.entries()]
|
|
@@ -194,22 +185,17 @@ function usageTypeTotals(events) {
|
|
|
194
185
|
.sort((left, right) => right.totalTokens - left.totalTokens);
|
|
195
186
|
}
|
|
196
187
|
|
|
197
|
-
function latestWeeklyQuotaObservation(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
return
|
|
204
|
-
.sort(
|
|
205
|
-
(left, right) =>
|
|
206
|
-
new Date(left.timestamp).getTime() - new Date(right.timestamp).getTime(),
|
|
207
|
-
)
|
|
208
|
-
.at(-1) ?? null;
|
|
188
|
+
function latestWeeklyQuotaObservation(snapshot) {
|
|
189
|
+
// The epoch-keyed timeline drops stale readings from superseded windows,
|
|
190
|
+
// so the last entry is the newest reading of the currently-live window.
|
|
191
|
+
const normalized = normalizeQuotaTimeline(weeklyQuotaObservations(snapshot));
|
|
192
|
+
const latest = normalized.at(-1);
|
|
193
|
+
if (!latest) return null;
|
|
194
|
+
return { ...latest, usedPercent: latest.normalizedUsedPercent };
|
|
209
195
|
}
|
|
210
196
|
|
|
211
197
|
export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
212
|
-
const observation = latestWeeklyQuotaObservation(snapshot
|
|
198
|
+
const observation = latestWeeklyQuotaObservation(snapshot);
|
|
213
199
|
if (!observation) {
|
|
214
200
|
return {
|
|
215
201
|
available: false,
|
|
@@ -297,32 +283,6 @@ function summary(events) {
|
|
|
297
283
|
const cached = Math.max(0, Number(event.cachedInputTokens) || 0);
|
|
298
284
|
return sum + Math.min(input, cached);
|
|
299
285
|
}, 0);
|
|
300
|
-
const turnCount = (rows) => {
|
|
301
|
-
const keys = new Set();
|
|
302
|
-
for (const [index, event] of rows.entries()) {
|
|
303
|
-
const turnId = String(event.turnId || "").trim();
|
|
304
|
-
keys.add(turnId ? `turn:${turnId}` : `event:${event.id || index}`);
|
|
305
|
-
}
|
|
306
|
-
return keys.size;
|
|
307
|
-
};
|
|
308
|
-
const totalTurns = turnCount(events);
|
|
309
|
-
const autoReviewEvents = events.filter(
|
|
310
|
-
(event) => modelLabel(event.model) === "Auto Review",
|
|
311
|
-
);
|
|
312
|
-
const autoReviewTokens = autoReviewEvents.reduce(
|
|
313
|
-
(sum, event) => sum + (Number(event.totalTokens) || 0),
|
|
314
|
-
0,
|
|
315
|
-
);
|
|
316
|
-
const autoReviewInputTokens = autoReviewEvents.reduce(
|
|
317
|
-
(sum, event) => sum + Math.max(0, Number(event.inputTokens) || 0),
|
|
318
|
-
0,
|
|
319
|
-
);
|
|
320
|
-
const autoReviewCachedInputTokens = autoReviewEvents.reduce((sum, event) => {
|
|
321
|
-
const input = Math.max(0, Number(event.inputTokens) || 0);
|
|
322
|
-
const cached = Math.max(0, Number(event.cachedInputTokens) || 0);
|
|
323
|
-
return sum + Math.min(input, cached);
|
|
324
|
-
}, 0);
|
|
325
|
-
const autoReviewTurns = turnCount(autoReviewEvents);
|
|
326
286
|
return {
|
|
327
287
|
totalTokens,
|
|
328
288
|
calls,
|
|
@@ -333,48 +293,25 @@ function summary(events) {
|
|
|
333
293
|
uncachedInputTokens: Math.max(0, inputTokens - cachedInputTokens),
|
|
334
294
|
models: modelTotals(events),
|
|
335
295
|
usageTypes: usageTypeTotals(events),
|
|
336
|
-
autoReview: {
|
|
337
|
-
present: autoReviewEvents.length > 0,
|
|
338
|
-
totalTokens: autoReviewTokens,
|
|
339
|
-
turns: autoReviewTurns,
|
|
340
|
-
turnShare: totalTurns > 0 ? (autoReviewTurns / totalTurns) * 100 : 0,
|
|
341
|
-
cachedInputShare: autoReviewInputTokens > 0
|
|
342
|
-
? (autoReviewCachedInputTokens / autoReviewInputTokens) * 100
|
|
343
|
-
: 0,
|
|
344
|
-
},
|
|
345
296
|
};
|
|
346
297
|
}
|
|
347
298
|
|
|
348
299
|
function modelLegendItems(models, totalTokens) {
|
|
349
|
-
|
|
350
|
-
|
|
300
|
+
const known = new Map();
|
|
301
|
+
for (const model of models) {
|
|
302
|
+
const key = ["Sol", "Luna", "Terra", "GPT"].includes(model.model)
|
|
303
|
+
? model.model
|
|
304
|
+
: "Other";
|
|
305
|
+
known.set(key, (known.get(key) ?? 0) + model.totalTokens);
|
|
306
|
+
}
|
|
307
|
+
return ["Luna", "Sol", "Terra", "GPT", "Other"]
|
|
351
308
|
.map((model) => ({
|
|
352
|
-
|
|
353
|
-
|
|
309
|
+
model,
|
|
310
|
+
totalTokens: known.get(model) ?? 0,
|
|
311
|
+
share: totalTokens > 0 ? ((known.get(model) ?? 0) / totalTokens) * 100 : 0,
|
|
354
312
|
}));
|
|
355
313
|
}
|
|
356
314
|
|
|
357
|
-
function visibleUsageTypeItems(items, limit = 5) {
|
|
358
|
-
if (items.length <= limit) return items;
|
|
359
|
-
const visible = items.slice(0, limit - 1);
|
|
360
|
-
const autoReview = items.find((item) => item.key === "auto-review");
|
|
361
|
-
if (autoReview && !visible.includes(autoReview)) {
|
|
362
|
-
visible[visible.length - 1] = autoReview;
|
|
363
|
-
visible.sort((left, right) => right.totalTokens - left.totalTokens);
|
|
364
|
-
}
|
|
365
|
-
const visibleKeys = new Set(visible.map((item) => item.key));
|
|
366
|
-
return [
|
|
367
|
-
...visible,
|
|
368
|
-
{
|
|
369
|
-
key: "other",
|
|
370
|
-
label: "Other",
|
|
371
|
-
totalTokens: items
|
|
372
|
-
.filter((item) => !visibleKeys.has(item.key))
|
|
373
|
-
.reduce((sum, item) => sum + item.totalTokens, 0),
|
|
374
|
-
},
|
|
375
|
-
];
|
|
376
|
-
}
|
|
377
|
-
|
|
378
315
|
function stackedBar(row, width, maximumTokens, options, enabled) {
|
|
379
316
|
const symbol = options.ascii ? "#" : "█";
|
|
380
317
|
const trackSymbol = options.ascii ? "." : "░";
|
|
@@ -404,7 +341,7 @@ function stackedBar(row, width, maximumTokens, options, enabled) {
|
|
|
404
341
|
.join("");
|
|
405
342
|
const blank = colorize(
|
|
406
343
|
trackSymbol.repeat(Math.max(0, width - visibleLength(filled))),
|
|
407
|
-
|
|
344
|
+
TRACK_STYLE,
|
|
408
345
|
enabled,
|
|
409
346
|
);
|
|
410
347
|
return `${filled}${blank}`;
|
|
@@ -426,9 +363,13 @@ function panelLines(rows, allRows, totalTokens, panelWidth, options, enabled) {
|
|
|
426
363
|
panelWidth - labelWidth - shareWidth - totalWidth - 1 - rightPadding,
|
|
427
364
|
);
|
|
428
365
|
const maxTokens = allRows[0]?.totalTokens ?? 0;
|
|
429
|
-
const
|
|
366
|
+
const totalCredits = allRows.reduce((sum, item) => sum + item.rateCardCredits, 0) || 1;
|
|
367
|
+
const selectedIndex = Math.min(
|
|
368
|
+
Math.max(0, Math.trunc(Number(options.selectedIndex) || 0)),
|
|
369
|
+
Math.max(0, rows.length - 1),
|
|
370
|
+
);
|
|
430
371
|
const lines = [];
|
|
431
|
-
const heading = colorize("TOKENS BY PROJECT",
|
|
372
|
+
const heading = colorize("TOKENS BY PROJECT", ACCENT_STYLE, enabled);
|
|
432
373
|
const barHeaderWidth = barWidth;
|
|
433
374
|
const maximumLabel = compactMode ? `${compact(maxTokens)} max` : `${compact(maxTokens)} (max)`;
|
|
434
375
|
const axisLabel = barHeaderWidth >= maximumLabel.length + 2 ? maximumLabel : compact(maxTokens);
|
|
@@ -438,29 +379,33 @@ function panelLines(rows, allRows, totalTokens, panelWidth, options, enabled) {
|
|
|
438
379
|
lines.push(
|
|
439
380
|
`${fit(heading, labelWidth)}${fit(axisText, barHeaderWidth)}${fit("TOKENS", totalWidth, "right")}${fit("SHARE", shareWidth, "right")}${" ".repeat(rightPadding)}`,
|
|
440
381
|
);
|
|
441
|
-
lines.push(colorize("─".repeat(panelWidth),
|
|
382
|
+
lines.push(colorize("─".repeat(panelWidth), BORDER_STYLE, enabled));
|
|
442
383
|
|
|
443
384
|
for (const [index, row] of rows.entries()) {
|
|
444
|
-
const rankValue = rowOffset + index + 1;
|
|
445
385
|
const share = totalTokens > 0 ? (row.totalTokens / totalTokens) * 100 : 0;
|
|
446
|
-
const selected =
|
|
386
|
+
const selected = index === selectedIndex;
|
|
447
387
|
const caretValue = selected ? (options.ascii ? ">" : "▶") : " ";
|
|
448
|
-
const prefix = `${caretValue} ${
|
|
388
|
+
const prefix = `${caretValue} ${index + 1}. `;
|
|
449
389
|
const projectText = truncateText(displayProject(row), labelWidth - prefix.length);
|
|
450
|
-
const caret = selected ? colorize(caretValue, [1, ...
|
|
451
|
-
const rank = colorize(`${
|
|
390
|
+
const caret = selected ? colorize(caretValue, [1, ...ACCENT_STYLE], enabled) : caretValue;
|
|
391
|
+
const rank = colorize(`${index + 1}.`, TITLE_STYLE, enabled);
|
|
452
392
|
const title = `${caret} ${rank} ${colorize(projectText, TITLE_STYLE, enabled)}`;
|
|
453
393
|
const label = fit(title, labelWidth);
|
|
454
394
|
const metrics = `${fit(compact(row.totalTokens), totalWidth, "right")}${fit(percent(share), shareWidth, "right")}${" ".repeat(rightPadding)}`;
|
|
455
395
|
const bar = stackedBar(row, barWidth, maxTokens, options, enabled);
|
|
456
396
|
const rowLine = `${label}${bar} ${metrics}`;
|
|
457
|
-
const
|
|
397
|
+
const creditShare = row.rateCardCredits > 0 && row.rateCardCredits <= Number.MAX_SAFE_INTEGER
|
|
398
|
+
? row.rateCardCredits
|
|
399
|
+
: 0;
|
|
400
|
+
const detail = `${plural(row.threads, "thread")} · ${percent(
|
|
401
|
+
creditShare > 0 ? (creditShare / totalCredits) * 100 : 0,
|
|
402
|
+
)}${labelWidth >= 35 ? " credits" : ""}`;
|
|
458
403
|
const detailText = truncateText(detail, labelWidth - 4);
|
|
459
404
|
const subtitle = colorize(detailText, SUBTITLE_STYLE, enabled);
|
|
460
405
|
const detailLine = `${fit(` ${subtitle}`, labelWidth)}${" ".repeat(barWidth + 1 + totalWidth + shareWidth + rightPadding)}`;
|
|
461
406
|
if (selected && enabled && options.highlight !== false) {
|
|
462
|
-
lines.push(
|
|
463
|
-
lines.push(
|
|
407
|
+
lines.push(`${SELECTED_BACKGROUND}${fit(rowLine, panelWidth)}${RESET}`);
|
|
408
|
+
lines.push(`${SELECTED_BACKGROUND}${fit(detailLine, panelWidth)}${RESET}`);
|
|
464
409
|
} else {
|
|
465
410
|
lines.push(rowLine);
|
|
466
411
|
lines.push(detailLine);
|
|
@@ -477,41 +422,36 @@ function sidebarLines(stats, panelWidth, enabled, options = {}, quota = null) {
|
|
|
477
422
|
const push = (line = "") => {
|
|
478
423
|
lines.push(`${" ".repeat(inset)}${fit(line, contentWidth)}${" ".repeat(inset)}`);
|
|
479
424
|
};
|
|
480
|
-
const divider = () => push(colorize("─".repeat(contentWidth),
|
|
481
|
-
const heading = (value) => colorize(value,
|
|
425
|
+
const divider = () => push(colorize("─".repeat(contentWidth), BORDER_STYLE, enabled));
|
|
426
|
+
const heading = (value) => colorize(value, ACCENT_STYLE, enabled);
|
|
482
427
|
push(heading("MODEL MIX"));
|
|
483
428
|
if (!compactSidebar) push();
|
|
484
|
-
const
|
|
485
|
-
const modelNameWidth = Math.max(1, contentWidth - modelShareWidth - 2);
|
|
486
|
-
const modelItems = modelLegendItems(stats.models, stats.totalTokens);
|
|
487
|
-
const modelLimit = Number.isInteger(options.modelLimit)
|
|
488
|
-
? Math.max(0, options.modelLimit)
|
|
489
|
-
: modelItems.length;
|
|
490
|
-
const visibleModelItems = modelItems.slice(0, modelLimit);
|
|
491
|
-
for (const item of visibleModelItems) {
|
|
429
|
+
for (const item of modelLegendItems(stats.models, stats.totalTokens)) {
|
|
492
430
|
const swatch = colorize("■", modelColor(item.model), enabled);
|
|
493
|
-
push(`${swatch} ${fit(item.model,
|
|
494
|
-
if (item.model === "Auto Review" && stats.autoReview.present) {
|
|
495
|
-
const turnLabel = stats.autoReview.turns === 1 ? "turn" : "turns";
|
|
496
|
-
push(` ${compact(stats.autoReview.turns)} ${turnLabel} · ${percent(stats.autoReview.turnShare)}`);
|
|
497
|
-
push(` ${compact(stats.autoReview.totalTokens)} · ${percent(stats.autoReview.cachedInputShare)} cached`);
|
|
498
|
-
}
|
|
431
|
+
push(`${swatch} ${fit(item.model, Math.max(1, contentWidth - 10))}${fit(percent(item.share), 8, "right")}`);
|
|
499
432
|
}
|
|
500
|
-
|
|
501
|
-
if (hiddenModelCount > 0) {
|
|
502
|
-
push(colorize(`… ${plural(hiddenModelCount, "more model")}`, DIM, enabled));
|
|
503
|
-
}
|
|
504
|
-
if (!compactSidebar) push();
|
|
433
|
+
push();
|
|
505
434
|
divider();
|
|
506
435
|
push(heading("USAGE TYPE · TOKENS"));
|
|
507
436
|
if (!compactSidebar) push();
|
|
508
|
-
const usageItems =
|
|
437
|
+
const usageItems = stats.usageTypes.length > 5
|
|
438
|
+
? [
|
|
439
|
+
...stats.usageTypes.slice(0, 4),
|
|
440
|
+
{
|
|
441
|
+
key: "other",
|
|
442
|
+
label: "Other",
|
|
443
|
+
totalTokens: stats.usageTypes
|
|
444
|
+
.slice(4)
|
|
445
|
+
.reduce((sum, item) => sum + item.totalTokens, 0),
|
|
446
|
+
},
|
|
447
|
+
]
|
|
448
|
+
: stats.usageTypes;
|
|
509
449
|
for (const item of usageItems) {
|
|
510
450
|
const swatch = "■";
|
|
511
451
|
const share = stats.totalTokens > 0 ? (item.totalTokens / stats.totalTokens) * 100 : 0;
|
|
512
452
|
push(`${swatch} ${fit(item.label, Math.max(1, contentWidth - 10))}${fit(percent(share), 8, "right")}`);
|
|
513
453
|
}
|
|
514
|
-
|
|
454
|
+
push();
|
|
515
455
|
divider();
|
|
516
456
|
push(heading("CACHE · INPUT"));
|
|
517
457
|
if (!compactSidebar) push();
|
|
@@ -525,7 +465,7 @@ function sidebarLines(stats, panelWidth, enabled, options = {}, quota = null) {
|
|
|
525
465
|
push(`${swatch} ${fit(item.label, Math.max(1, contentWidth - 10))}${fit(percent(share), 8, "right")}`);
|
|
526
466
|
}
|
|
527
467
|
if (quota?.available) {
|
|
528
|
-
|
|
468
|
+
push();
|
|
529
469
|
divider();
|
|
530
470
|
push(heading("RESET CYCLE"));
|
|
531
471
|
if (!compactSidebar) push();
|
|
@@ -545,28 +485,41 @@ function panel(leftLines, rightLines, leftWidth, rightWidth, enabled, ascii) {
|
|
|
545
485
|
? { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|", tm: "+", bm: "+" }
|
|
546
486
|
: { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", tm: "┬", bm: "┴" };
|
|
547
487
|
const rows = Math.max(leftLines.length, rightLines?.length ?? 0);
|
|
548
|
-
const
|
|
488
|
+
const border = (value) => colorize(value, BORDER_STYLE, enabled);
|
|
489
|
+
const top = border(`${glyphs.tl}${glyphs.h.repeat(leftWidth)}${rightLines ? glyphs.tm : glyphs.tr}${rightLines ? glyphs.h.repeat(rightWidth) + glyphs.tr : ""}`);
|
|
549
490
|
const body = [];
|
|
550
491
|
for (let index = 0; index < rows; index += 1) {
|
|
551
492
|
const left = fit(leftLines[index] ?? "", leftWidth);
|
|
552
493
|
if (rightLines) {
|
|
553
494
|
const right = fit(rightLines[index] ?? "", rightWidth);
|
|
554
|
-
body.push(`${glyphs.v}${left}${glyphs.v}${right}${glyphs.v}`);
|
|
495
|
+
body.push(`${border(glyphs.v)}${left}${border(glyphs.v)}${right}${border(glyphs.v)}`);
|
|
555
496
|
} else {
|
|
556
|
-
body.push(`${glyphs.v}${left}${glyphs.v}`);
|
|
497
|
+
body.push(`${border(glyphs.v)}${left}${border(glyphs.v)}`);
|
|
557
498
|
}
|
|
558
499
|
}
|
|
559
|
-
const bottom = `${glyphs.bl}${glyphs.h.repeat(leftWidth)}${rightLines ? glyphs.bm : glyphs.br}${rightLines ? glyphs.h.repeat(rightWidth) + glyphs.br : ""}
|
|
500
|
+
const bottom = border(`${glyphs.bl}${glyphs.h.repeat(leftWidth)}${rightLines ? glyphs.bm : glyphs.br}${rightLines ? glyphs.h.repeat(rightWidth) + glyphs.br : ""}`);
|
|
560
501
|
return [top, ...body, bottom];
|
|
561
502
|
}
|
|
562
503
|
|
|
563
|
-
function
|
|
504
|
+
function snapshotLine(freshness, enabled) {
|
|
505
|
+
const detail = freshness?.status === "fresh" || freshness?.status === "stale"
|
|
506
|
+
? `${freshness.status} · ${freshness.ageLabel}`
|
|
507
|
+
: "age unknown";
|
|
508
|
+
return `${colorize("SNAPSHOT", ACCENT_STYLE, enabled)} ${colorize("·", SECONDARY_STYLE, enabled)} ${colorize(detail, SECONDARY_STYLE, enabled)}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
564
512
|
const left = colorize("TOKEN LEDGER", TITLE_STYLE, enabled);
|
|
565
513
|
const date = colorize(dateLabel(bounds, options.range), TEXT_STYLE, enabled);
|
|
566
|
-
const
|
|
514
|
+
const modeLabel = options.range === "rolling24h"
|
|
515
|
+
? "24 HOURS"
|
|
516
|
+
: options.range === "week"
|
|
517
|
+
? "7 DAYS"
|
|
518
|
+
: "DAY";
|
|
519
|
+
const mode = colorize(modeLabel, [1, ...ACCENT_STYLE], enabled);
|
|
567
520
|
const metric = (value, label) =>
|
|
568
|
-
`${colorize(String(value), TITLE_STYLE, enabled)} ${colorize(label,
|
|
569
|
-
const separator = colorize("·",
|
|
521
|
+
`${colorize(String(value), TITLE_STYLE, enabled)} ${colorize(label, SECONDARY_STYLE, enabled)}`;
|
|
522
|
+
const separator = colorize("·", SECONDARY_STYLE, enabled);
|
|
570
523
|
const join = ` ${separator} `;
|
|
571
524
|
const alignHeader = (line) => fit(` ${line}`, frameWidth);
|
|
572
525
|
const fullLine = [
|
|
@@ -579,44 +532,58 @@ function headerLines(stats, bounds, frameWidth, options, enabled) {
|
|
|
579
532
|
metric(stats.projectCount.toLocaleString("en-US"), "PROJECTS"),
|
|
580
533
|
].join(join);
|
|
581
534
|
if (visibleLength(fullLine) < frameWidth) {
|
|
582
|
-
|
|
535
|
+
const lines = [alignHeader(fullLine)];
|
|
536
|
+
if (options.range === "rolling24h") lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
537
|
+
return lines;
|
|
583
538
|
}
|
|
584
539
|
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
540
|
+
const compactDate = dateLabel(bounds, options.range)
|
|
541
|
+
.replace(/ 20\d{2}$/, "")
|
|
542
|
+
.replace(" – ", "–");
|
|
543
|
+
const compactMode = options.range === "rolling24h"
|
|
544
|
+
? "24H"
|
|
545
|
+
: options.range === "week"
|
|
546
|
+
? "7D"
|
|
547
|
+
: "DAY";
|
|
593
548
|
const compactLine = [
|
|
594
549
|
left,
|
|
595
550
|
colorize(compactDate, TEXT_STYLE, enabled),
|
|
596
|
-
colorize(compactMode, [1, ...
|
|
551
|
+
colorize(compactMode, [1, ...ACCENT_STYLE], enabled),
|
|
597
552
|
metric(`${compact(stats.totalTokens)}`, "T"),
|
|
598
553
|
metric(stats.calls.toLocaleString("en-US"), "C"),
|
|
599
554
|
metric(stats.threads.toLocaleString("en-US"), "TH"),
|
|
600
555
|
metric(stats.projectCount.toLocaleString("en-US"), "P"),
|
|
601
556
|
].join(join);
|
|
602
557
|
if (visibleLength(compactLine) < frameWidth) {
|
|
603
|
-
|
|
558
|
+
const lines = [alignHeader(compactLine)];
|
|
559
|
+
if (options.range === "rolling24h") lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
560
|
+
return lines;
|
|
604
561
|
}
|
|
605
562
|
|
|
606
563
|
const minimalTitle = colorize(frameWidth >= 45 ? "LEDGER" : "L", TITLE_STYLE, enabled);
|
|
607
564
|
const minimalLine = [
|
|
608
565
|
minimalTitle,
|
|
609
566
|
colorize(compactDate.replaceAll(" ", ""), TEXT_STYLE, enabled),
|
|
610
|
-
colorize(compactMode, [1, ...
|
|
567
|
+
colorize(compactMode, [1, ...ACCENT_STYLE], enabled),
|
|
611
568
|
compact(stats.totalTokens),
|
|
612
569
|
compact(stats.calls),
|
|
613
570
|
compact(stats.threads),
|
|
614
571
|
compact(stats.projectCount),
|
|
615
572
|
].join(" ");
|
|
616
|
-
|
|
573
|
+
const lines = [alignHeader(minimalLine)];
|
|
574
|
+
if (options.range === "rolling24h") lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
575
|
+
return lines;
|
|
617
576
|
}
|
|
618
577
|
|
|
619
|
-
export function renderTerminal({
|
|
578
|
+
export function renderTerminal({
|
|
579
|
+
options,
|
|
580
|
+
snapshot,
|
|
581
|
+
snapshotFreshness,
|
|
582
|
+
bounds,
|
|
583
|
+
events,
|
|
584
|
+
rows,
|
|
585
|
+
allRows,
|
|
586
|
+
}) {
|
|
620
587
|
const enabled = colorsEnabled(options);
|
|
621
588
|
const stats = summary(events);
|
|
622
589
|
const quota = quotaCycleSummary(snapshot, events);
|
|
@@ -624,35 +591,31 @@ export function renderTerminal({ options, snapshot, bounds, events, rows, allRow
|
|
|
624
591
|
const columns = options.width ?? (Number(process.stdout.columns) || 120);
|
|
625
592
|
const frameWidth = Math.max(38, Math.min(158, columns - 2));
|
|
626
593
|
const sideBySide = options.forceSideBySide ?? frameWidth >= 100;
|
|
627
|
-
const sideWidth = sideBySide ?
|
|
594
|
+
const sideWidth = sideBySide ? 26 : 0;
|
|
628
595
|
const leftWidth = sideBySide ? frameWidth - sideWidth - 1 : frameWidth;
|
|
629
596
|
const left = panelLines(rows, allRows, stats.totalTokens, leftWidth, options, enabled);
|
|
630
597
|
const right = sideBySide ? sidebarLines(stats, sideWidth, enabled, options, quota) : null;
|
|
631
598
|
const lines = [
|
|
632
|
-
...headerLines(stats, bounds, frameWidth, options, enabled),
|
|
599
|
+
...headerLines(stats, bounds, frameWidth, options, enabled, snapshotFreshness),
|
|
633
600
|
...panel(left, right, leftWidth, sideWidth, enabled, options.ascii),
|
|
634
601
|
];
|
|
635
602
|
if (!sideBySide) {
|
|
636
603
|
lines.push("");
|
|
637
604
|
lines.push(...sidebarLines(stats, frameWidth, enabled, options, quota));
|
|
638
605
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
enabled,
|
|
648
|
-
),
|
|
649
|
-
);
|
|
650
|
-
}
|
|
606
|
+
lines.push("");
|
|
607
|
+
lines.push(
|
|
608
|
+
colorize(
|
|
609
|
+
options.ascii ? INTERACTIVE_FOOTER.ascii : INTERACTIVE_FOOTER.unicode,
|
|
610
|
+
SECONDARY_STYLE,
|
|
611
|
+
enabled,
|
|
612
|
+
),
|
|
613
|
+
);
|
|
651
614
|
return lines.join("\n");
|
|
652
615
|
}
|
|
653
616
|
|
|
654
|
-
export const SCREEN_BASE = "\u001b[38;
|
|
655
|
-
const PANEL_BASE = "\u001b[38;
|
|
617
|
+
export const SCREEN_BASE = "\u001b[38;2;255;255;255m\u001b[48;2;30;30;30m";
|
|
618
|
+
const PANEL_BASE = "\u001b[38;2;255;255;255m\u001b[48;2;24;24;24m";
|
|
656
619
|
|
|
657
620
|
function paintFullscreenLine(line, width, background, enabled) {
|
|
658
621
|
const fitted = fit(line, width);
|
|
@@ -661,89 +624,46 @@ function paintFullscreenLine(line, width, background, enabled) {
|
|
|
661
624
|
return `${background}${restored}${RESET}`;
|
|
662
625
|
}
|
|
663
626
|
|
|
664
|
-
export function renderFullscreen({
|
|
627
|
+
export function renderFullscreen({
|
|
628
|
+
options,
|
|
629
|
+
snapshot,
|
|
630
|
+
snapshotFreshness,
|
|
631
|
+
bounds,
|
|
632
|
+
events,
|
|
633
|
+
rows,
|
|
634
|
+
allRows,
|
|
635
|
+
width,
|
|
636
|
+
height,
|
|
637
|
+
}) {
|
|
665
638
|
const enabled = options.forceColor ?? colorsEnabled(options);
|
|
666
639
|
const columns = Math.max(40, Number(width) || Number(process.stdout.columns) || 120);
|
|
667
640
|
const screenHeight = Math.max(1, Number(height) || Number(process.stdout.rows) || 32);
|
|
668
641
|
const frameWidth = Math.max(38, Math.min(158, columns - 4));
|
|
669
|
-
const forceSideBySide = frameWidth >= 84;
|
|
670
|
-
const staticBudget = Math.max(0, screenHeight - 1);
|
|
671
|
-
let visibleRows = rows;
|
|
672
|
-
let fullscreenOptions = {
|
|
673
|
-
...options,
|
|
674
|
-
forceColor: enabled,
|
|
675
|
-
forceSideBySide,
|
|
676
|
-
highlight: false,
|
|
677
|
-
compactSidebar: true,
|
|
678
|
-
hideHelp: true,
|
|
679
|
-
width: frameWidth + 2,
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
if (forceSideBySide && staticBudget >= 3) {
|
|
683
|
-
const stats = summary(events);
|
|
684
|
-
const quota = quotaCycleSummary(snapshot, events);
|
|
685
|
-
const panelContentBudget = Math.max(1, staticBudget - 3);
|
|
686
|
-
const visibleRowCount = Math.min(
|
|
687
|
-
rows.length,
|
|
688
|
-
Math.max(1, Math.floor((panelContentBudget - 2) / 2)),
|
|
689
|
-
);
|
|
690
|
-
const selectedIndex = Math.max(
|
|
691
|
-
0,
|
|
692
|
-
Math.min(rows.length - 1, Number(options.selectedIndex) || 0),
|
|
693
|
-
);
|
|
694
|
-
const rowOffset = Math.min(
|
|
695
|
-
Math.max(0, selectedIndex - visibleRowCount + 1),
|
|
696
|
-
Math.max(0, rows.length - visibleRowCount),
|
|
697
|
-
);
|
|
698
|
-
visibleRows = rows.slice(rowOffset, rowOffset + visibleRowCount);
|
|
699
|
-
|
|
700
|
-
const sideWidth = stats.autoReview.present ? 28 : 26;
|
|
701
|
-
let modelLimit = modelLegendItems(stats.models, stats.totalTokens).length;
|
|
702
|
-
while (
|
|
703
|
-
modelLimit > 0 &&
|
|
704
|
-
sidebarLines(
|
|
705
|
-
stats,
|
|
706
|
-
sideWidth,
|
|
707
|
-
enabled,
|
|
708
|
-
{ ...fullscreenOptions, modelLimit },
|
|
709
|
-
quota,
|
|
710
|
-
).length > panelContentBudget
|
|
711
|
-
) {
|
|
712
|
-
modelLimit -= 1;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
fullscreenOptions = {
|
|
716
|
-
...fullscreenOptions,
|
|
717
|
-
modelLimit,
|
|
718
|
-
rowOffset,
|
|
719
|
-
selectedIndex: selectedIndex - rowOffset,
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
|
|
723
642
|
const staticOutput = renderTerminal({
|
|
724
|
-
options:
|
|
643
|
+
options: {
|
|
644
|
+
...options,
|
|
645
|
+
forceColor: enabled,
|
|
646
|
+
forceSideBySide: frameWidth >= 84,
|
|
647
|
+
highlight: false,
|
|
648
|
+
compactSidebar: true,
|
|
649
|
+
width: frameWidth + 2,
|
|
650
|
+
},
|
|
725
651
|
snapshot,
|
|
652
|
+
snapshotFreshness,
|
|
726
653
|
bounds,
|
|
727
654
|
events,
|
|
728
|
-
rows
|
|
655
|
+
rows,
|
|
729
656
|
allRows,
|
|
730
657
|
});
|
|
731
|
-
|
|
658
|
+
const staticLines = staticOutput.split("\n");
|
|
659
|
+
staticLines.pop();
|
|
732
660
|
if (staticLines.at(-1) === "") staticLines.pop();
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
const availableStaticLines = Math.max(
|
|
736
|
-
0,
|
|
737
|
-
screenHeight - 1 - (includeSpacer ? 1 : 0),
|
|
738
|
-
);
|
|
739
|
-
staticLines = staticLines.slice(0, availableStaticLines);
|
|
740
|
-
const summaryLine = staticLines.shift();
|
|
661
|
+
const summaryLine = staticLines.shift() ?? "";
|
|
662
|
+
const help = colorize(INTERACTIVE_HELP, SECONDARY_STYLE, enabled);
|
|
741
663
|
const content = [
|
|
742
|
-
|
|
743
|
-
? []
|
|
744
|
-
: [{ line: summaryLine, background: SCREEN_BASE }]),
|
|
664
|
+
{ line: summaryLine, background: SCREEN_BASE },
|
|
745
665
|
...staticLines.map((line) => ({ line, background: PANEL_BASE })),
|
|
746
|
-
|
|
666
|
+
{ line: "", background: SCREEN_BASE },
|
|
747
667
|
{ line: help, background: SCREEN_BASE },
|
|
748
668
|
];
|
|
749
669
|
const topPadding = Math.max(0, Math.floor((screenHeight - content.length) / 2));
|