tledger 0.3.1 → 0.4.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 +283 -157
- package/bin/token-ledger-cache-data.mjs +492 -0
- package/bin/token-ledger-cache-image.mjs +7 -1147
- package/bin/token-ledger-cache-sections.mjs +848 -0
- package/bin/token-ledger-cost-terminal.mjs +234 -0
- package/bin/token-ledger-image-layout.mjs +20 -0
- package/bin/token-ledger-image-primitives.mjs +192 -0
- package/bin/token-ledger-report-data.mjs +1159 -0
- package/bin/token-ledger-source-status.mjs +31 -0
- package/bin/token-ledger-terminal.mjs +245 -69
- package/bin/token-ledger-trend-image.mjs +2025 -1724
- package/bin/token-ledger-trend-terminal.mjs +273 -150
- package/bin/token-ledger-trend.mjs +204 -198
- package/bin/token-ledger-tui.mjs +180 -51
- package/bin/token-ledger.mjs +735 -208
- package/docs/durable-ledger-operations.md +198 -0
- package/docs/release-notes-0.4.0.md +41 -0
- package/docs/token-ledger-report-7-day.png +0 -0
- package/lib/token-ledger-calendar.mjs +225 -0
- package/lib/token-ledger-collection.mjs +100 -0
- package/lib/token-ledger-importer.mjs +3329 -488
- package/lib/token-ledger-labels.mjs +66 -0
- package/lib/token-ledger-ledger.mjs +6056 -0
- package/lib/token-ledger-quota-contract.mjs +38 -0
- package/lib/token-ledger-range-analysis.mjs +120 -0
- package/lib/token-ledger-rates.mjs +330 -0
- package/lib/token-ledger-snapshot.mjs +336 -35
- package/lib/token-ledger-terminal-text.mjs +11 -0
- package/lib/token-ledger-usage.mjs +339 -33
- package/package.json +13 -10
- package/bin/token-ledger-rates.mjs +0 -65
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const SOURCE_STATUSES = Object.freeze([
|
|
2
|
+
"verified-current",
|
|
3
|
+
"explicit-snapshot",
|
|
4
|
+
"unchecked-cache",
|
|
5
|
+
"stale-fallback",
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
const SOURCE_STATUS_LABELS = Object.freeze({
|
|
9
|
+
"verified-current": "VERIFIED CURRENT",
|
|
10
|
+
"stale-fallback": "STALE FALLBACK",
|
|
11
|
+
"unchecked-cache": "UNCHECKED CACHE",
|
|
12
|
+
"explicit-snapshot": "EXPLICIT SNAPSHOT",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export function sourceStatusLabel(sourceStatus = "unchecked-cache") {
|
|
16
|
+
const label = SOURCE_STATUS_LABELS[sourceStatus];
|
|
17
|
+
if (!label) {
|
|
18
|
+
throw new Error(`Unknown report source status: ${String(sourceStatus)}`);
|
|
19
|
+
}
|
|
20
|
+
return label;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function sourceStatusLine(sourceStatus = "unchecked-cache") {
|
|
24
|
+
return `PROVENANCE · ${sourceStatusLabel(sourceStatus)}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function snapshotFreshnessDetail(freshness) {
|
|
28
|
+
return freshness?.status === "fresh" || freshness?.status === "stale"
|
|
29
|
+
? `${freshness.status} · ${freshness.ageLabel}`
|
|
30
|
+
: "age unknown";
|
|
31
|
+
}
|
|
@@ -8,10 +8,22 @@ import {
|
|
|
8
8
|
INTERACTIVE_HELP,
|
|
9
9
|
} from "./token-ledger-controls.mjs";
|
|
10
10
|
import {
|
|
11
|
+
MAX_SAFE_TOKEN_COUNT,
|
|
12
|
+
checkedFiniteAdd,
|
|
13
|
+
checkedTokenAdd,
|
|
14
|
+
scaledOutputTokens,
|
|
15
|
+
tokenValue,
|
|
11
16
|
usageBucketsInRange,
|
|
12
17
|
usageCallCount,
|
|
13
18
|
usageThreadIds,
|
|
14
19
|
} from "../lib/token-ledger-usage.mjs";
|
|
20
|
+
import { calendarDateParts } from "../lib/token-ledger-calendar.mjs";
|
|
21
|
+
import { sanitizeTerminalText } from "../lib/token-ledger-terminal-text.mjs";
|
|
22
|
+
import {
|
|
23
|
+
snapshotFreshnessDetail,
|
|
24
|
+
sourceStatusLine,
|
|
25
|
+
} from "./token-ledger-source-status.mjs";
|
|
26
|
+
import { historyScopeLabel } from "../lib/token-ledger-collection.mjs";
|
|
15
27
|
|
|
16
28
|
const RESET = "\u001b[0m";
|
|
17
29
|
const PRIMARY_STYLE = [38, 2, 255, 255, 255];
|
|
@@ -20,6 +32,7 @@ const ACCENT_STYLE = [38, 2, 51, 156, 255];
|
|
|
20
32
|
const BORDER_STYLE = [38, 2, 88, 88, 88];
|
|
21
33
|
const TRACK_STYLE = [38, 2, 59, 59, 59];
|
|
22
34
|
export const MODEL_COLORS = {
|
|
35
|
+
astra: [38, 2, 232, 121, 249],
|
|
23
36
|
sol: [38, 2, 120, 185, 242],
|
|
24
37
|
luna: ACCENT_STYLE,
|
|
25
38
|
terra: [38, 2, 214, 168, 95],
|
|
@@ -41,7 +54,7 @@ function colorize(value, code, enabled) {
|
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
function stripAnsi(value) {
|
|
44
|
-
return
|
|
57
|
+
return sanitizeTerminalText(value);
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
function visibleLength(value) {
|
|
@@ -111,6 +124,7 @@ function plural(value, singular, pluralForm = `${singular}s`) {
|
|
|
111
124
|
function modelLabel(value) {
|
|
112
125
|
const model = String(value || "Unknown model");
|
|
113
126
|
const lower = model.toLowerCase();
|
|
127
|
+
if (lower.includes("astra")) return "Astra";
|
|
114
128
|
if (lower.includes("sol")) return "Sol";
|
|
115
129
|
if (lower.includes("luna")) return "Luna";
|
|
116
130
|
if (lower.includes("terra")) return "Terra";
|
|
@@ -124,7 +138,7 @@ function modelColor(model) {
|
|
|
124
138
|
}
|
|
125
139
|
|
|
126
140
|
function usageTypeLabel(value) {
|
|
127
|
-
const words =
|
|
141
|
+
const words = sanitizeTerminalText(value || "unknown")
|
|
128
142
|
.trim()
|
|
129
143
|
.replace(/[_-]+/g, " ")
|
|
130
144
|
.split(/\s+/)
|
|
@@ -140,7 +154,12 @@ function usageTypeLabel(value) {
|
|
|
140
154
|
}
|
|
141
155
|
|
|
142
156
|
function displayProject(row) {
|
|
143
|
-
|
|
157
|
+
const label = sanitizeTerminalText(
|
|
158
|
+
row.displayProject || row.project || "Unlabelled activity",
|
|
159
|
+
)
|
|
160
|
+
.replace(/\s+/g, " ")
|
|
161
|
+
.trim();
|
|
162
|
+
return label || "Unlabelled activity";
|
|
144
163
|
}
|
|
145
164
|
|
|
146
165
|
function isRollingRange(range) {
|
|
@@ -161,26 +180,37 @@ function dateLabel(bounds, range = "day", rollingLabel = "1 day") {
|
|
|
161
180
|
const end = `${monthNames[endParts[1] - 1]} ${String(endParts[2]).padStart(2, "0")}`;
|
|
162
181
|
return `${start} – ${end} ${endParts[0]}`;
|
|
163
182
|
}
|
|
164
|
-
const
|
|
165
|
-
timeZone: bounds.timeZone,
|
|
183
|
+
const values = calendarDateParts(bounds.dateString, {
|
|
166
184
|
weekday: "short",
|
|
167
185
|
day: "2-digit",
|
|
168
186
|
month: "short",
|
|
169
187
|
year: "numeric",
|
|
170
|
-
})
|
|
171
|
-
const values = Object.fromEntries(
|
|
172
|
-
parts
|
|
173
|
-
.filter((part) => part.type !== "literal")
|
|
174
|
-
.map((part) => [part.type, part.value]),
|
|
175
|
-
);
|
|
188
|
+
});
|
|
176
189
|
return `${values.weekday} ${values.day} ${values.month} ${values.year}`.toUpperCase();
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
function modelTotals(events) {
|
|
180
193
|
const totals = new Map();
|
|
194
|
+
let scale = 1;
|
|
195
|
+
let totalTokens = 0;
|
|
181
196
|
for (const event of events) {
|
|
197
|
+
if (event?.invalidTokenRecord === true) continue;
|
|
182
198
|
const model = modelLabel(event.model);
|
|
183
|
-
|
|
199
|
+
const allowFractional = event.rangeAllocationEstimated === true;
|
|
200
|
+
const tokens = tokenValue(event.totalTokens, { allowFractional });
|
|
201
|
+
const scaledTokens = tokens / scale;
|
|
202
|
+
totalTokens += scaledTokens;
|
|
203
|
+
totals.set(
|
|
204
|
+
model,
|
|
205
|
+
(totals.get(model) ?? 0) + scaledTokens,
|
|
206
|
+
);
|
|
207
|
+
const scaleFactor = Math.max(1, totalTokens / MAX_SAFE_TOKEN_COUNT);
|
|
208
|
+
if (scaleFactor === 1) continue;
|
|
209
|
+
for (const [modelName, value] of totals) {
|
|
210
|
+
totals.set(modelName, value / scaleFactor);
|
|
211
|
+
}
|
|
212
|
+
totalTokens = MAX_SAFE_TOKEN_COUNT;
|
|
213
|
+
scale *= scaleFactor;
|
|
184
214
|
}
|
|
185
215
|
return [...totals.entries()]
|
|
186
216
|
.map(([model, totalTokens]) => ({ model, totalTokens }))
|
|
@@ -189,9 +219,28 @@ function modelTotals(events) {
|
|
|
189
219
|
|
|
190
220
|
function usageTypeTotals(events) {
|
|
191
221
|
const totals = new Map();
|
|
222
|
+
let scale = 1;
|
|
223
|
+
let totalTokens = 0;
|
|
192
224
|
for (const event of events) {
|
|
193
|
-
|
|
194
|
-
|
|
225
|
+
if (event?.invalidTokenRecord === true) continue;
|
|
226
|
+
const key = sanitizeTerminalText(event.useType || "unknown")
|
|
227
|
+
.trim()
|
|
228
|
+
.toLowerCase() || "unknown";
|
|
229
|
+
const allowFractional = event.rangeAllocationEstimated === true;
|
|
230
|
+
const tokens = tokenValue(event.totalTokens, { allowFractional });
|
|
231
|
+
const scaledTokens = tokens / scale;
|
|
232
|
+
totalTokens += scaledTokens;
|
|
233
|
+
totals.set(
|
|
234
|
+
key,
|
|
235
|
+
(totals.get(key) ?? 0) + scaledTokens,
|
|
236
|
+
);
|
|
237
|
+
const scaleFactor = Math.max(1, totalTokens / MAX_SAFE_TOKEN_COUNT);
|
|
238
|
+
if (scaleFactor === 1) continue;
|
|
239
|
+
for (const [usageType, value] of totals) {
|
|
240
|
+
totals.set(usageType, value / scaleFactor);
|
|
241
|
+
}
|
|
242
|
+
totalTokens = MAX_SAFE_TOKEN_COUNT;
|
|
243
|
+
scale *= scaleFactor;
|
|
195
244
|
}
|
|
196
245
|
return [...totals.entries()]
|
|
197
246
|
.map(([key, totalTokens]) => ({
|
|
@@ -202,6 +251,19 @@ function usageTypeTotals(events) {
|
|
|
202
251
|
.sort((left, right) => right.totalTokens - left.totalTokens);
|
|
203
252
|
}
|
|
204
253
|
|
|
254
|
+
function addCacheTotals(state, inputTokens, cachedInputTokens) {
|
|
255
|
+
const nextInput = state.inputTokens + inputTokens / state.scale;
|
|
256
|
+
const nextCached = state.cachedInputTokens + cachedInputTokens / state.scale;
|
|
257
|
+
const scaleFactor = Math.max(
|
|
258
|
+
1,
|
|
259
|
+
nextInput / MAX_SAFE_TOKEN_COUNT,
|
|
260
|
+
nextCached / MAX_SAFE_TOKEN_COUNT,
|
|
261
|
+
);
|
|
262
|
+
state.inputTokens = nextInput / scaleFactor;
|
|
263
|
+
state.cachedInputTokens = nextCached / scaleFactor;
|
|
264
|
+
state.scale *= scaleFactor;
|
|
265
|
+
}
|
|
266
|
+
|
|
205
267
|
function latestWeeklyQuotaObservation(snapshot) {
|
|
206
268
|
// The epoch-keyed timeline drops stale readings from superseded windows,
|
|
207
269
|
// so the last entry is the newest reading of the currently-live window.
|
|
@@ -228,9 +290,12 @@ export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
|
228
290
|
const windowStartMs =
|
|
229
291
|
(Number(observation.resetsAt) - Number(observation.windowMinutes) * 60) * 1_000;
|
|
230
292
|
const resetAtMs = Number(observation.resetsAt) * 1_000;
|
|
293
|
+
const observationThroughMs = Number.isFinite(observation.observedThroughMs)
|
|
294
|
+
? observation.observedThroughMs
|
|
295
|
+
: new Date(observation.eventCutoffAt ?? observation.timestamp).getTime();
|
|
231
296
|
const observedThroughMs = Math.min(
|
|
232
297
|
resetAtMs,
|
|
233
|
-
|
|
298
|
+
observationThroughMs,
|
|
234
299
|
);
|
|
235
300
|
if (
|
|
236
301
|
!Number.isFinite(windowStartMs) ||
|
|
@@ -249,20 +314,43 @@ export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
|
249
314
|
};
|
|
250
315
|
}
|
|
251
316
|
|
|
252
|
-
const sumUsage = (events) =>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
317
|
+
const sumUsage = (events, initialScale = 1) => {
|
|
318
|
+
const acc = {
|
|
319
|
+
tokens: 0,
|
|
320
|
+
credits: 0,
|
|
321
|
+
ratedTokens: 0,
|
|
322
|
+
hasUnrated: false,
|
|
323
|
+
scale: Number.isFinite(initialScale) && initialScale >= 1
|
|
324
|
+
? initialScale
|
|
325
|
+
: 1,
|
|
326
|
+
};
|
|
327
|
+
for (const event of events) {
|
|
328
|
+
if (event?.invalidTokenRecord === true) continue;
|
|
329
|
+
const allowFractional = event.rangeAllocationEstimated === true;
|
|
330
|
+
const tokens = tokenValue(event.totalTokens, { allowFractional });
|
|
331
|
+
const contribution = tokens / acc.scale;
|
|
332
|
+
const nextTokens = acc.tokens + contribution;
|
|
333
|
+
const scaleFactor = Math.max(
|
|
334
|
+
1,
|
|
335
|
+
nextTokens / MAX_SAFE_TOKEN_COUNT,
|
|
336
|
+
);
|
|
337
|
+
if (scaleFactor > 1) {
|
|
338
|
+
acc.tokens /= scaleFactor;
|
|
339
|
+
acc.ratedTokens /= scaleFactor;
|
|
340
|
+
acc.scale *= scaleFactor;
|
|
341
|
+
}
|
|
342
|
+
const scaledContribution = contribution / scaleFactor;
|
|
343
|
+
acc.tokens += scaledContribution;
|
|
344
|
+
const credits = eventCredits(event);
|
|
345
|
+
if (Number.isFinite(credits) && credits >= 0) {
|
|
346
|
+
acc.credits = checkedFiniteAdd(acc.credits, credits);
|
|
347
|
+
acc.ratedTokens += scaledContribution;
|
|
348
|
+
} else if (tokens > 0) {
|
|
349
|
+
acc.hasUnrated = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return acc;
|
|
353
|
+
};
|
|
266
354
|
const cycleEndMs = observedThroughMs + 1;
|
|
267
355
|
const cycle = sumUsage(
|
|
268
356
|
usageBucketsInRange(snapshot, windowStartMs, cycleEndMs),
|
|
@@ -273,6 +361,7 @@ export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
|
273
361
|
windowStartMs,
|
|
274
362
|
cycleEndMs,
|
|
275
363
|
),
|
|
364
|
+
cycle.scale,
|
|
276
365
|
);
|
|
277
366
|
const usedPercent = Math.min(100, Math.max(0, Number(observation.usedPercent) || 0));
|
|
278
367
|
// The weekly meter weights usage by model, token type, and fast mode;
|
|
@@ -280,7 +369,10 @@ export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
|
280
369
|
// when every event in the cycle is rated, and fall back to raw token
|
|
281
370
|
// share otherwise.
|
|
282
371
|
const creditsUsable =
|
|
283
|
-
cycle.
|
|
372
|
+
!cycle.hasUnrated &&
|
|
373
|
+
cycle.tokens > 0 &&
|
|
374
|
+
cycle.ratedTokens === cycle.tokens &&
|
|
375
|
+
cycle.credits > 0;
|
|
284
376
|
const displayedSharePercent = creditsUsable
|
|
285
377
|
? (displayed.credits / cycle.credits) * 100
|
|
286
378
|
: cycle.tokens
|
|
@@ -302,39 +394,44 @@ export function quotaCycleSummary(snapshot = {}, displayedEvents = []) {
|
|
|
302
394
|
};
|
|
303
395
|
}
|
|
304
396
|
|
|
305
|
-
function summary(events) {
|
|
306
|
-
const totalTokens =
|
|
307
|
-
(sum,
|
|
397
|
+
function summary(events, projectRows) {
|
|
398
|
+
const totalTokens = projectRows.reduce(
|
|
399
|
+
(sum, row) => sum + row.totalTokens,
|
|
308
400
|
0,
|
|
309
401
|
);
|
|
310
402
|
const calls = events.reduce(
|
|
311
|
-
(sum, event) =>
|
|
403
|
+
(sum, event) => {
|
|
404
|
+
const allowFractional = event?.rangeAllocationEstimated === true;
|
|
405
|
+
return checkedTokenAdd(sum, usageCallCount(event), { allowFractional });
|
|
406
|
+
},
|
|
312
407
|
0,
|
|
313
408
|
);
|
|
314
409
|
const threadIds = new Set(
|
|
315
410
|
events.flatMap((event) => usageThreadIds(event)),
|
|
316
411
|
);
|
|
317
|
-
const outputTokens = events
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}, 0);
|
|
412
|
+
const outputTokens = scaledOutputTokens(events, totalTokens);
|
|
413
|
+
const cacheTotals = { inputTokens: 0, cachedInputTokens: 0, scale: 1 };
|
|
414
|
+
for (const event of events) {
|
|
415
|
+
if (event?.invalidTokenRecord === true) continue;
|
|
416
|
+
const allowFractional = event.rangeAllocationEstimated === true;
|
|
417
|
+
const input = tokenValue(event.inputTokens, { allowFractional });
|
|
418
|
+
const cached = Math.min(
|
|
419
|
+
input,
|
|
420
|
+
tokenValue(event.cachedInputTokens, { allowFractional }),
|
|
421
|
+
);
|
|
422
|
+
addCacheTotals(cacheTotals, input, cached);
|
|
423
|
+
}
|
|
330
424
|
return {
|
|
331
425
|
totalTokens,
|
|
332
426
|
calls,
|
|
333
427
|
threads: threadIds.size,
|
|
334
428
|
outputTokens,
|
|
335
|
-
inputTokens,
|
|
336
|
-
cachedInputTokens,
|
|
337
|
-
uncachedInputTokens: Math.max(
|
|
429
|
+
inputTokens: cacheTotals.inputTokens,
|
|
430
|
+
cachedInputTokens: cacheTotals.cachedInputTokens,
|
|
431
|
+
uncachedInputTokens: Math.max(
|
|
432
|
+
0,
|
|
433
|
+
cacheTotals.inputTokens - cacheTotals.cachedInputTokens,
|
|
434
|
+
),
|
|
338
435
|
models: modelTotals(events),
|
|
339
436
|
usageTypes: usageTypeTotals(events),
|
|
340
437
|
};
|
|
@@ -343,12 +440,12 @@ function summary(events) {
|
|
|
343
440
|
function modelLegendItems(models, totalTokens) {
|
|
344
441
|
const known = new Map();
|
|
345
442
|
for (const model of models) {
|
|
346
|
-
const key = ["Sol", "Luna", "Terra", "GPT"].includes(model.model)
|
|
443
|
+
const key = ["Astra", "Sol", "Luna", "Terra", "GPT"].includes(model.model)
|
|
347
444
|
? model.model
|
|
348
445
|
: "Other";
|
|
349
446
|
known.set(key, (known.get(key) ?? 0) + model.totalTokens);
|
|
350
447
|
}
|
|
351
|
-
return ["Luna", "Sol", "Terra", "GPT", "Other"]
|
|
448
|
+
return ["Astra", "Luna", "Sol", "Terra", "GPT", "Other"]
|
|
352
449
|
.map((model) => ({
|
|
353
450
|
model,
|
|
354
451
|
totalTokens: known.get(model) ?? 0,
|
|
@@ -407,7 +504,10 @@ function panelLines(rows, allRows, totalTokens, panelWidth, options, enabled) {
|
|
|
407
504
|
panelWidth - labelWidth - shareWidth - totalWidth - 1 - rightPadding,
|
|
408
505
|
);
|
|
409
506
|
const maxTokens = allRows[0]?.totalTokens ?? 0;
|
|
410
|
-
const totalCredits = allRows.reduce(
|
|
507
|
+
const totalCredits = allRows.reduce(
|
|
508
|
+
(sum, item) => checkedFiniteAdd(sum, item.rateCardCredits),
|
|
509
|
+
0,
|
|
510
|
+
) || 1;
|
|
411
511
|
const selectedIndex = Math.min(
|
|
412
512
|
Math.max(0, Math.trunc(Number(options.selectedIndex) || 0)),
|
|
413
513
|
Math.max(0, rows.length - 1),
|
|
@@ -486,7 +586,12 @@ function sidebarLines(stats, panelWidth, enabled, options = {}, quota = null) {
|
|
|
486
586
|
label: "Other",
|
|
487
587
|
totalTokens: stats.usageTypes
|
|
488
588
|
.slice(4)
|
|
489
|
-
.reduce(
|
|
589
|
+
.reduce(
|
|
590
|
+
(sum, item) => checkedTokenAdd(sum, item.totalTokens, {
|
|
591
|
+
allowFractional: true,
|
|
592
|
+
}),
|
|
593
|
+
0,
|
|
594
|
+
),
|
|
490
595
|
},
|
|
491
596
|
]
|
|
492
597
|
: stats.usageTypes;
|
|
@@ -546,13 +651,61 @@ function panel(leftLines, rightLines, leftWidth, rightWidth, enabled, ascii) {
|
|
|
546
651
|
}
|
|
547
652
|
|
|
548
653
|
function snapshotLine(freshness, enabled) {
|
|
549
|
-
const detail = freshness
|
|
550
|
-
? `${freshness.status} · ${freshness.ageLabel}`
|
|
551
|
-
: "age unknown";
|
|
654
|
+
const detail = snapshotFreshnessDetail(freshness);
|
|
552
655
|
return `${colorize("SNAPSHOT", ACCENT_STYLE, enabled)} ${colorize("·", SECONDARY_STYLE, enabled)} ${colorize(detail, SECONDARY_STYLE, enabled)}`;
|
|
553
656
|
}
|
|
554
657
|
|
|
555
|
-
function
|
|
658
|
+
function provenanceLine(sourceStatus, enabled) {
|
|
659
|
+
return colorize(sourceStatusLine(sourceStatus), SECONDARY_STYLE, enabled);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function positiveCoverageCount(value) {
|
|
663
|
+
try {
|
|
664
|
+
const count = Number(value);
|
|
665
|
+
return Number.isFinite(count) && count > 0 ? count : 0;
|
|
666
|
+
} catch {
|
|
667
|
+
return 0;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export function incompleteSourceWarning(snapshot = {}) {
|
|
672
|
+
const coverage = snapshot?.coverage ?? {};
|
|
673
|
+
const parseErrors = positiveCoverageCount(coverage.parseErrors);
|
|
674
|
+
const invalidTokenRecords = positiveCoverageCount(coverage.invalidTokenRecords);
|
|
675
|
+
if (
|
|
676
|
+
parseErrors <= 0 &&
|
|
677
|
+
invalidTokenRecords <= 0 &&
|
|
678
|
+
coverage.sourceIncomplete !== true
|
|
679
|
+
) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
const details = [];
|
|
683
|
+
if (parseErrors > 0) {
|
|
684
|
+
details.push(
|
|
685
|
+
`${parseErrors.toLocaleString("en-US")} PARSE ERROR${parseErrors === 1 ? "" : "S"}`,
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (invalidTokenRecords > 0) {
|
|
689
|
+
details.push(
|
|
690
|
+
`${invalidTokenRecords.toLocaleString("en-US")} INVALID TOKEN RECORD${invalidTokenRecords === 1 ? "" : "S"}`,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
if (coverage.sourceIncomplete === true) {
|
|
694
|
+
details.push("INCOMPLETE SOURCE PROVENANCE");
|
|
695
|
+
}
|
|
696
|
+
return ["SOURCES INCOMPLETE", ...details].join(" · ");
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function headerLines(
|
|
700
|
+
stats,
|
|
701
|
+
bounds,
|
|
702
|
+
frameWidth,
|
|
703
|
+
options,
|
|
704
|
+
enabled,
|
|
705
|
+
freshness,
|
|
706
|
+
sourceStatus,
|
|
707
|
+
snapshot,
|
|
708
|
+
) {
|
|
556
709
|
const left = colorize("TOKEN LEDGER", TITLE_STYLE, enabled);
|
|
557
710
|
const date = colorize(
|
|
558
711
|
dateLabel(bounds, options.range, options.rollingLabel),
|
|
@@ -572,6 +725,23 @@ function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
|
572
725
|
const separator = colorize("·", SECONDARY_STYLE, enabled);
|
|
573
726
|
const join = ` ${separator} `;
|
|
574
727
|
const alignHeader = (line) => fit(` ${line}`, frameWidth);
|
|
728
|
+
const history = historyScopeLabel(snapshot);
|
|
729
|
+
const appendHistory = (lines) => history
|
|
730
|
+
? [...lines, alignHeader(colorize(history, SECONDARY_STYLE, enabled))]
|
|
731
|
+
: lines;
|
|
732
|
+
const appendSnapshotMetadata = (lines) => {
|
|
733
|
+
const metadata = appendHistory([
|
|
734
|
+
...lines,
|
|
735
|
+
...(isRollingRange(options.range)
|
|
736
|
+
? [alignHeader(snapshotLine(freshness, enabled))]
|
|
737
|
+
: []),
|
|
738
|
+
alignHeader(provenanceLine(sourceStatus, enabled)),
|
|
739
|
+
]);
|
|
740
|
+
const warning = incompleteSourceWarning(snapshot);
|
|
741
|
+
return warning
|
|
742
|
+
? [...metadata, alignHeader(colorize(warning, SECONDARY_STYLE, enabled))]
|
|
743
|
+
: metadata;
|
|
744
|
+
};
|
|
575
745
|
const fullLine = [
|
|
576
746
|
left,
|
|
577
747
|
date,
|
|
@@ -582,9 +752,7 @@ function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
|
582
752
|
metric(stats.projectCount.toLocaleString("en-US"), "PROJECTS"),
|
|
583
753
|
].join(join);
|
|
584
754
|
if (visibleLength(fullLine) < frameWidth) {
|
|
585
|
-
|
|
586
|
-
if (isRollingRange(options.range)) lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
587
|
-
return lines;
|
|
755
|
+
return appendSnapshotMetadata([alignHeader(fullLine)]);
|
|
588
756
|
}
|
|
589
757
|
|
|
590
758
|
const compactDate = dateLabel(bounds, options.range, options.rollingLabel)
|
|
@@ -607,9 +775,7 @@ function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
|
607
775
|
metric(stats.projectCount.toLocaleString("en-US"), "P"),
|
|
608
776
|
].join(join);
|
|
609
777
|
if (visibleLength(compactLine) < frameWidth) {
|
|
610
|
-
|
|
611
|
-
if (isRollingRange(options.range)) lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
612
|
-
return lines;
|
|
778
|
+
return appendSnapshotMetadata([alignHeader(compactLine)]);
|
|
613
779
|
}
|
|
614
780
|
|
|
615
781
|
const minimalTitle = colorize(frameWidth >= 45 ? "LEDGER" : "L", TITLE_STYLE, enabled);
|
|
@@ -622,22 +788,21 @@ function headerLines(stats, bounds, frameWidth, options, enabled, freshness) {
|
|
|
622
788
|
compact(stats.threads),
|
|
623
789
|
compact(stats.projectCount),
|
|
624
790
|
].join(" ");
|
|
625
|
-
|
|
626
|
-
if (isRollingRange(options.range)) lines.push(alignHeader(snapshotLine(freshness, enabled)));
|
|
627
|
-
return lines;
|
|
791
|
+
return appendSnapshotMetadata([alignHeader(minimalLine)]);
|
|
628
792
|
}
|
|
629
793
|
|
|
630
794
|
export function renderTerminal({
|
|
631
795
|
options,
|
|
632
796
|
snapshot,
|
|
633
797
|
snapshotFreshness,
|
|
798
|
+
sourceStatus = "unchecked-cache",
|
|
634
799
|
bounds,
|
|
635
800
|
events,
|
|
636
801
|
rows,
|
|
637
802
|
allRows,
|
|
638
803
|
}) {
|
|
639
804
|
const enabled = colorsEnabled(options);
|
|
640
|
-
const stats = summary(events);
|
|
805
|
+
const stats = summary(events, allRows);
|
|
641
806
|
const quota = quotaCycleSummary(snapshot, events);
|
|
642
807
|
stats.projectCount = allRows.length;
|
|
643
808
|
const columns = options.width ?? (Number(process.stdout.columns) || 120);
|
|
@@ -648,7 +813,16 @@ export function renderTerminal({
|
|
|
648
813
|
const left = panelLines(rows, allRows, stats.totalTokens, leftWidth, options, enabled);
|
|
649
814
|
const right = sideBySide ? sidebarLines(stats, sideWidth, enabled, options, quota) : null;
|
|
650
815
|
const lines = [
|
|
651
|
-
...headerLines(
|
|
816
|
+
...headerLines(
|
|
817
|
+
stats,
|
|
818
|
+
bounds,
|
|
819
|
+
frameWidth,
|
|
820
|
+
options,
|
|
821
|
+
enabled,
|
|
822
|
+
snapshotFreshness,
|
|
823
|
+
sourceStatus,
|
|
824
|
+
snapshot,
|
|
825
|
+
),
|
|
652
826
|
...panel(left, right, leftWidth, sideWidth, enabled, options.ascii),
|
|
653
827
|
];
|
|
654
828
|
if (!sideBySide) {
|
|
@@ -688,6 +862,7 @@ export function renderFullscreen({
|
|
|
688
862
|
options,
|
|
689
863
|
snapshot,
|
|
690
864
|
snapshotFreshness,
|
|
865
|
+
sourceStatus = "unchecked-cache",
|
|
691
866
|
bounds,
|
|
692
867
|
events,
|
|
693
868
|
rows,
|
|
@@ -710,6 +885,7 @@ export function renderFullscreen({
|
|
|
710
885
|
},
|
|
711
886
|
snapshot,
|
|
712
887
|
snapshotFreshness,
|
|
888
|
+
sourceStatus,
|
|
713
889
|
bounds,
|
|
714
890
|
events,
|
|
715
891
|
rows,
|