tledger 0.2.1 → 0.3.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 +150 -136
- package/bin/token-ledger-cache-image.mjs +1150 -0
- package/bin/token-ledger-rates.mjs +4 -1
- package/bin/token-ledger-terminal.mjs +89 -29
- package/bin/token-ledger-trend-image.mjs +1527 -584
- package/bin/token-ledger-trend-terminal.mjs +73 -28
- package/bin/token-ledger-trend.mjs +25 -11
- package/bin/token-ledger-tui.mjs +7 -3
- package/bin/token-ledger.mjs +303 -92
- package/docs/token-ledger-cli-week.png +0 -0
- package/docs/token-ledger-report-7-day.png +0 -0
- package/lib/token-ledger-importer.mjs +589 -279
- package/lib/token-ledger-snapshot.mjs +267 -0
- package/lib/token-ledger-usage.mjs +524 -0
- package/package.json +10 -5
|
@@ -18,10 +18,13 @@ export const RATE_CARD = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export function normalizeModel(model) {
|
|
21
|
+
// Collapse underscore and whitespace separators to dashes so variants like
|
|
22
|
+
// "gpt-5.4 mini" resolve to their own rate-card entry. Keep in lockstep
|
|
23
|
+
// with normalizeModel in lib/token-ledger-importer.mjs.
|
|
21
24
|
const value = String(model || "unknown")
|
|
22
25
|
.trim()
|
|
23
26
|
.toLowerCase()
|
|
24
|
-
.
|
|
27
|
+
.replace(/[\s_]+/g, "-");
|
|
25
28
|
if (RATE_CARD[value]) return value;
|
|
26
29
|
if (value.startsWith("gpt-5.6-sol")) return "gpt-5.6-sol";
|
|
27
30
|
if (value.startsWith("gpt-5.6-terra")) return "gpt-5.6-terra";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
eventCredits,
|
|
2
3
|
normalizeQuotaTimeline,
|
|
3
4
|
weeklyQuotaObservations,
|
|
4
5
|
} from "./token-ledger-trend.mjs";
|
|
@@ -6,6 +7,11 @@ import {
|
|
|
6
7
|
INTERACTIVE_FOOTER,
|
|
7
8
|
INTERACTIVE_HELP,
|
|
8
9
|
} from "./token-ledger-controls.mjs";
|
|
10
|
+
import {
|
|
11
|
+
usageBucketsInRange,
|
|
12
|
+
usageCallCount,
|
|
13
|
+
usageThreadIds,
|
|
14
|
+
} from "../lib/token-ledger-usage.mjs";
|
|
9
15
|
|
|
10
16
|
const RESET = "\u001b[0m";
|
|
11
17
|
const PRIMARY_STYLE = [38, 2, 255, 255, 255];
|
|
@@ -78,12 +84,18 @@ function compact(value) {
|
|
|
78
84
|
[1_000_000, "M"],
|
|
79
85
|
[1_000, "K"],
|
|
80
86
|
];
|
|
81
|
-
for (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
for (let index = 0; index < units.length; index += 1) {
|
|
88
|
+
const [divisor, suffix] = units[index];
|
|
89
|
+
if (absolute < divisor) continue;
|
|
90
|
+
const scaled = value / divisor;
|
|
91
|
+
const magnitude = Math.abs(scaled);
|
|
92
|
+
const precision = magnitude >= 100 ? 0 : magnitude >= 10 ? 1 : 2;
|
|
93
|
+
// Values that round to 1000 of a unit belong to the next unit up
|
|
94
|
+
// (999,999 → 1.00M, not 1000K).
|
|
95
|
+
if (index > 0 && Number(magnitude.toFixed(precision)) >= 1_000) {
|
|
96
|
+
return compact(Math.sign(value) * divisor * 1_000);
|
|
86
97
|
}
|
|
98
|
+
return `${scaled.toFixed(precision)}${suffix}`;
|
|
87
99
|
}
|
|
88
100
|
return Math.round(value).toLocaleString("en-US");
|
|
89
101
|
}
|
|
@@ -131,8 +143,13 @@ function displayProject(row) {
|
|
|
131
143
|
return row.displayProject || row.project || "Unlabelled activity";
|
|
132
144
|
}
|
|
133
145
|
|
|
134
|
-
function
|
|
146
|
+
function isRollingRange(range) {
|
|
147
|
+
return range === "rolling24h" || range === "rolling";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function dateLabel(bounds, range = "day", rollingLabel = "1 day") {
|
|
135
151
|
if (range === "rolling24h") return "LAST 24 HOURS";
|
|
152
|
+
if (range === "rolling") return `LAST ${rollingLabel.toUpperCase()}`;
|
|
136
153
|
if (range === "week" && bounds.startDateString && bounds.endDateString) {
|
|
137
154
|
const startParts = bounds.startDateString.split("-").map(Number);
|
|
138
155
|
const endParts = bounds.endDateString.split("-").map(Number);
|
|
@@ -232,30 +249,52 @@ export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
|
232
249
|
};
|
|
233
250
|
}
|
|
234
251
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
252
|
+
const sumUsage = (events) =>
|
|
253
|
+
events.reduce(
|
|
254
|
+
(acc, event) => {
|
|
255
|
+
const tokens = Number(event.totalTokens) || 0;
|
|
256
|
+
acc.tokens += tokens;
|
|
257
|
+
const credits = eventCredits(event);
|
|
258
|
+
if (Number.isFinite(credits) && credits >= 0) {
|
|
259
|
+
acc.credits += credits;
|
|
260
|
+
acc.ratedTokens += tokens;
|
|
261
|
+
}
|
|
262
|
+
return acc;
|
|
263
|
+
},
|
|
264
|
+
{ tokens: 0, credits: 0, ratedTokens: 0 },
|
|
241
265
|
);
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
266
|
+
const cycleEndMs = observedThroughMs + 1;
|
|
267
|
+
const cycle = sumUsage(
|
|
268
|
+
usageBucketsInRange(snapshot, windowStartMs, cycleEndMs),
|
|
269
|
+
);
|
|
270
|
+
const displayed = sumUsage(
|
|
271
|
+
usageBucketsInRange(
|
|
272
|
+
{ events: displayedEvents },
|
|
273
|
+
windowStartMs,
|
|
274
|
+
cycleEndMs,
|
|
275
|
+
),
|
|
276
|
+
);
|
|
247
277
|
const usedPercent = Math.min(100, Math.max(0, Number(observation.usedPercent) || 0));
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
278
|
+
// The weekly meter weights usage by model, token type, and fast mode;
|
|
279
|
+
// rate-card credits carry those weights. Allocate burn by credit share
|
|
280
|
+
// when every event in the cycle is rated, and fall back to raw token
|
|
281
|
+
// share otherwise.
|
|
282
|
+
const creditsUsable =
|
|
283
|
+
cycle.tokens > 0 && cycle.ratedTokens === cycle.tokens && cycle.credits > 0;
|
|
284
|
+
const displayedSharePercent = creditsUsable
|
|
285
|
+
? (displayed.credits / cycle.credits) * 100
|
|
286
|
+
: cycle.tokens
|
|
287
|
+
? (displayed.tokens / cycle.tokens) * 100
|
|
288
|
+
: null;
|
|
251
289
|
|
|
252
290
|
return {
|
|
253
291
|
available: true,
|
|
254
292
|
usedPercent,
|
|
255
293
|
remainingPercent: 100 - usedPercent,
|
|
256
|
-
cycleTokens,
|
|
257
|
-
displayedTokens,
|
|
294
|
+
cycleTokens: cycle.tokens,
|
|
295
|
+
displayedTokens: displayed.tokens,
|
|
258
296
|
displayedSharePercent,
|
|
297
|
+
shareBasis: creditsUsable ? "credits" : "tokens",
|
|
259
298
|
estimatedDisplayedBurnPercent:
|
|
260
299
|
displayedSharePercent === null
|
|
261
300
|
? null
|
|
@@ -268,8 +307,13 @@ function summary(events) {
|
|
|
268
307
|
(sum, event) => sum + (Number(event.totalTokens) || 0),
|
|
269
308
|
0,
|
|
270
309
|
);
|
|
271
|
-
const calls = events.
|
|
272
|
-
|
|
310
|
+
const calls = events.reduce(
|
|
311
|
+
(sum, event) => sum + usageCallCount(event),
|
|
312
|
+
0,
|
|
313
|
+
);
|
|
314
|
+
const threadIds = new Set(
|
|
315
|
+
events.flatMap((event) => usageThreadIds(event)),
|
|
316
|
+
);
|
|
273
317
|
const outputTokens = events.reduce(
|
|
274
318
|
(sum, event) => sum + (Number(event.outputTokens) || 0),
|
|
275
319
|
0,
|
|
@@ -510,9 +554,15 @@ function snapshotLine(freshness, enabled) {
|
|
|
510
554
|
|
|
511
555
|
function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
512
556
|
const left = colorize("TOKEN LEDGER", TITLE_STYLE, enabled);
|
|
513
|
-
const date = colorize(
|
|
557
|
+
const date = colorize(
|
|
558
|
+
dateLabel(bounds, options.range, options.rollingLabel),
|
|
559
|
+
TEXT_STYLE,
|
|
560
|
+
enabled,
|
|
561
|
+
);
|
|
514
562
|
const modeLabel = options.range === "rolling24h"
|
|
515
563
|
? "24 HOURS"
|
|
564
|
+
: options.range === "rolling"
|
|
565
|
+
? options.rollingLabel.toUpperCase()
|
|
516
566
|
: options.range === "week"
|
|
517
567
|
? "7 DAYS"
|
|
518
568
|
: "DAY";
|
|
@@ -533,15 +583,17 @@ function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
|
533
583
|
].join(join);
|
|
534
584
|
if (visibleLength(fullLine) < frameWidth) {
|
|
535
585
|
const lines = [alignHeader(fullLine)];
|
|
536
|
-
if (options.range
|
|
586
|
+
if (isRollingRange(options.range)) lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
537
587
|
return lines;
|
|
538
588
|
}
|
|
539
589
|
|
|
540
|
-
const compactDate = dateLabel(bounds, options.range)
|
|
590
|
+
const compactDate = dateLabel(bounds, options.range, options.rollingLabel)
|
|
541
591
|
.replace(/ 20\d{2}$/, "")
|
|
542
592
|
.replace(" – ", "–");
|
|
543
593
|
const compactMode = options.range === "rolling24h"
|
|
544
594
|
? "24H"
|
|
595
|
+
: options.range === "rolling"
|
|
596
|
+
? `${options.rollingAmount}${options.rollingUnit.toUpperCase()}`
|
|
545
597
|
: options.range === "week"
|
|
546
598
|
? "7D"
|
|
547
599
|
: "DAY";
|
|
@@ -556,7 +608,7 @@ function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
|
556
608
|
].join(join);
|
|
557
609
|
if (visibleLength(compactLine) < frameWidth) {
|
|
558
610
|
const lines = [alignHeader(compactLine)];
|
|
559
|
-
if (options.range
|
|
611
|
+
if (isRollingRange(options.range)) lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
560
612
|
return lines;
|
|
561
613
|
}
|
|
562
614
|
|
|
@@ -571,7 +623,7 @@ function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
|
571
623
|
compact(stats.projectCount),
|
|
572
624
|
].join(" ");
|
|
573
625
|
const lines = [alignHeader(minimalLine)];
|
|
574
|
-
if (options.range
|
|
626
|
+
if (isRollingRange(options.range)) lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
575
627
|
return lines;
|
|
576
628
|
}
|
|
577
629
|
|
|
@@ -603,6 +655,14 @@ export function renderTerminal({
|
|
|
603
655
|
lines.push("");
|
|
604
656
|
lines.push(...sidebarLines(stats, frameWidth, enabled, options, quota));
|
|
605
657
|
}
|
|
658
|
+
if (events.some((event) => event.rangeAllocationEstimated === true)) {
|
|
659
|
+
lines.push("");
|
|
660
|
+
lines.push(colorize(
|
|
661
|
+
"≈ Boundary-spanning compact history is allocated proportionally.",
|
|
662
|
+
SECONDARY_STYLE,
|
|
663
|
+
enabled,
|
|
664
|
+
));
|
|
665
|
+
}
|
|
606
666
|
lines.push("");
|
|
607
667
|
lines.push(
|
|
608
668
|
colorize(
|