claude-token-saver 2.8.2 → 2.8.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
4
4
  "description": "Save tokens on Claude Code — spike diagnosis, 1M-context detection, TTL countdown, statusline. (formerly claude-cache-monitor)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -103,7 +103,7 @@ function formatTimer(remainingSec) {
103
103
  // Defensive: non-finite/NaN inputs (e.g. clock skew, stringified Date) used
104
104
  // to slip through and render as "NaN:NaN" or stretched seconds. Treat any
105
105
  // weird input as expired rather than rendering garbage in the statusline.
106
- if (!Number.isFinite(remainingSec) || remainingSec <= 0) return 'EXPIRED';
106
+ if (!Number.isFinite(remainingSec) || remainingSec <= 0) return padTimer('EXPIRED');
107
107
  const totalSec = Math.max(0, Math.floor(remainingSec));
108
108
  const h = Math.floor(totalSec / 3600);
109
109
  const mRaw = Math.floor((totalSec % 3600) / 60);
@@ -112,8 +112,22 @@ function formatTimer(remainingSec) {
112
112
  // truncation) can never produce m:sss like "4:547".
113
113
  const m = Math.min(59, Math.max(0, mRaw));
114
114
  const s = Math.min(59, Math.max(0, sRaw));
115
- if (h > 0) return `${h}:${String(m).padStart(2, '0')}`;
116
- return `${m}:${String(s).padStart(2, '0')}`;
115
+ if (h > 0) return padTimer(`${h}:${String(m).padStart(2, '0')}`);
116
+ return padTimer(`${m}:${String(s).padStart(2, '0')}`);
117
+ }
118
+
119
+ // Pad timer text to a fixed width so transitions never shrink the visible
120
+ // length. Otherwise `42:38` (5 chars) → `1:00` (4 chars) on activity reset
121
+ // leaves the trailing `8` on screen → fused garbage like `1:008`. Some
122
+ // terminals (JetBrains JediTerm, certain Claude Code render paths) don't
123
+ // fully clear the statusline region between frames, so column-stable output
124
+ // is the only reliable defense — `\x1b[K` and trailing-space padding only
125
+ // help if the cursor lands at the right spot to begin with.
126
+ // "EXPIRED" → "EXPIRED" (7 — already widest, used as the target width)
127
+ // "59:59" → " 59:59 " no — left-pad only so the digits stay right-aligned
128
+ const TIMER_WIDTH = 7;
129
+ function padTimer(s) {
130
+ return s.padStart(TIMER_WIDTH, ' ');
117
131
  }
118
132
 
119
133
  /**
@@ -401,5 +415,13 @@ export function formatReport(data, { color = true, verbose = false, timer = true
401
415
  // digits from a prior frame leaking past the new shorter timer text.
402
416
  // \x1b[K is the standard "erase from cursor to EOL" CSI; safe on any
403
417
  // ANSI-compatible terminal and a no-op when stdout isn't a TTY.
404
- return segs.join(' · ') + '\x1b[K';
418
+ // Defensive overwrite terminals that miscount emoji width (JetBrains JediTerm
419
+ // is the known case, but others surface periodically) leave the cursor at the
420
+ // wrong column, which makes trailing `\x1b[K` erase the wrong region and
421
+ // leftover bytes from the previous frame fuse with the new one ("4:54" + "8"
422
+ // → "4:548"). Appending a run of spaces overwrites those leftover bytes
423
+ // positionally without any clear-then-redraw step (so no flicker), and is
424
+ // invisible on terminals that already redraw cleanly. `\x1b[K` still mops up
425
+ // anything beyond the padding.
426
+ return segs.join(' · ') + ' '.repeat(40) + '\x1b[K';
405
427
  }