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.
@@ -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 DIM = [38, 5, 245];
3
- const TEAL = [38, 5, 80];
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, 5, 67],
6
- luna: [38, 5, 80],
7
- terra: [38, 5, 179],
8
- gpt: [38, 5, 176],
9
- autoReview: [38, 5, 153],
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 = [38, 5, 255];
13
- const TITLE_STYLE = [1, 38, 5, 255];
14
- const SUBTITLE_STYLE = [38, 5, 245];
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 = sanitizeText(value) || "Unknown model";
98
- const lower = model.toLowerCase().replaceAll("_", "-");
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 = (sanitizeText(value) || "unknown")
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 sanitizeText(row.displayProject || row.project) || "Unlabelled activity";
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 = modelLabel(event.model) === "Auto Review"
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(observations) {
192
- const weekly = (observations ?? []).filter(
193
- (item) => Number(item.windowMinutes) === 10_080,
194
- );
195
- const accountWide = weekly.filter((item) => item.scope !== "named");
196
- const candidates = accountWide.length ? accountWide : weekly;
197
- return [...candidates]
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.quotaObservations);
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", "Auto Review"].includes(model.model)
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
- const order = ["Luna", "Sol", "Terra", "GPT"];
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
- DIM,
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", TEAL, enabled);
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), DIM, enabled));
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 = !options.static && index === (options.selectedIndex ?? 0);
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, ...TEAL], enabled) : caretValue;
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 detail = `${plural(row.threads, "thread")} · ${percent(share)} of tokens`;
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(`\u001b[48;5;236m${fit(rowLine, panelWidth)}${RESET}`);
465
- lines.push(`\u001b[48;5;236m${fit(detailLine, panelWidth)}${RESET}`);
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), DIM, enabled));
483
- const heading = (value) => colorize(value, TEAL, enabled);
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
- if (!compactSidebar) push();
428
+ push();
496
429
  divider();
497
430
  push(heading("USAGE TYPE · TOKENS"));
498
431
  if (!compactSidebar) push();
499
- const usageItems = visibleUsageTypeItems(stats.usageTypes);
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
- if (!compactSidebar) push();
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
- if (!compactSidebar) push();
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 top = `${glyphs.tl}${glyphs.h.repeat(leftWidth)}${rightLines ? glyphs.tm : glyphs.tr}${rightLines ? glyphs.h.repeat(rightWidth) + glyphs.tr : ""}`;
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, ...TEAL], enabled);
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, DIM, enabled)}`;
560
- const separator = colorize("·", DIM, enabled);
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, ...TEAL], enabled),
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, ...TEAL], enabled),
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 ? (stats.autoReview.present ? 28 : 26) : 0;
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
- if (!options.static && !options.hideHelp) {
627
- lines.push("");
628
- lines.push(
629
- colorize(
630
- options.ascii
631
- ? "[j/k] select [q/esc] quit"
632
- : "[↑↓ or j/k] select [q/esc] quit",
633
- DIM,
634
- enabled,
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;5;255m\u001b[48;2;16;16;18m";
642
- const PANEL_BASE = "\u001b[38;5;255m\u001b[48;2;5;5;6m";
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", DIM, enabled);
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 })),