claude-token-saver 2.8.4 → 2.8.5

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.en.md CHANGED
@@ -143,6 +143,10 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
143
143
  - [HN discussion](https://news.ycombinator.com/item?id=47736476) — 168 points, 142 comments
144
144
  - [HNPulse KR](https://www.youtube.com/@HNPulseKR) — Korean HN tech deep-dives
145
145
 
146
+ ## Known environment quirks
147
+
148
+ **IntelliJ Claude Code plugin** — the statusline widget fuses prior and current frames at the character level when emoji are in the output, producing artifacts like `Cache expires 59:548`. v2.8.5+ detects `TERMINAL_EMULATOR=JetBrains-JediTerm` and falls back to text mode automatically (`--icon` is also ignored under IntelliJ). Other terminals (iTerm, Terminal, WSL, etc.) are unaffected.
149
+
146
150
  ## License
147
151
 
148
152
  MIT
package/README.md CHANGED
@@ -103,6 +103,10 @@ Claude Code는 모든 API 응답을 `~/.claude/projects/<dir>/<session>.jsonl`
103
103
 
104
104
  Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
105
105
 
106
+ ## 알려진 환경 이슈
107
+
108
+ **IntelliJ Claude Code plugin** — statusline 위젯이 이전 프레임과 새 프레임을 글자 단위로 잘못 합쳐 `Cache expires 59:548` 같은 합성 잔재가 보이는 버그가 있습니다 (이모지가 들어간 텍스트일 때만). v2.8.5+에서는 `TERMINAL_EMULATOR=JetBrains-JediTerm`을 감지하면 자동으로 text 모드로 폴백해 이모지 없이 출력합니다 (`--icon` 플래그도 IntelliJ에서는 무시). 다른 터미널(iTerm, Terminal, WSL 등)에는 영향 없습니다.
109
+
106
110
  ## 라이선스
107
111
 
108
112
  MIT
package/bin/cli.js CHANGED
@@ -700,10 +700,21 @@ async function main() {
700
700
  const { statuslineDefaults } = await import('../src/config.js');
701
701
  const cfg = statuslineDefaults();
702
702
 
703
+ // IntelliJ's Claude Code plugin renders the statusline through a custom
704
+ // widget that fuses prior frames with the new one when emoji are present,
705
+ // producing garbage like "59:548" that no ANSI escape can clean up
706
+ // (verified: emitting the same output directly into JediTerm renders
707
+ // cleanly, so the bug is in the plugin's render path, not the terminal).
708
+ // Force text mode unconditionally inside IntelliJ — even past an explicit
709
+ // `--icon` flag, since wrappers commonly hardcode `--icon` and the user
710
+ // can't easily edit them; icon mode is just broken there.
711
+ const isIntelliJ = process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
703
712
  // CLI flags take precedence; otherwise fall back to persisted config.
704
- const isIcon = hasFlag('--icon')
705
- ? true
706
- : (hasFlag('--no-icon') || hasFlag('--text') ? false : cfg.icon);
713
+ const isIcon = isIntelliJ
714
+ ? false
715
+ : (hasFlag('--icon')
716
+ ? true
717
+ : (hasFlag('--no-icon') || hasFlag('--text') ? false : cfg.icon));
707
718
  const isVerbose = hasFlag('--verbose')
708
719
  ? true
709
720
  : (hasFlag('--no-verbose') || hasFlag('--compact') ? false : cfg.verbose);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "2.8.4",
3
+ "version": "2.8.5",
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 padTimer('EXPIRED');
106
+ if (!Number.isFinite(remainingSec) || remainingSec <= 0) return '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,22 +112,8 @@ 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 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, ' ');
115
+ if (h > 0) return `${h}:${String(m).padStart(2, '0')}`;
116
+ return `${m}:${String(s).padStart(2, '0')}`;
131
117
  }
132
118
 
133
119
  /**
@@ -409,19 +395,8 @@ export function formatReport(data, { color = true, verbose = false, timer = true
409
395
  if (want('saved')) segs.push(saveSeg);
410
396
  if (want('period')) segs.push(periodSeg);
411
397
  // Trailing erase-to-end-of-line so any leftover characters from a previous
412
- // (longer) statusline render don't bleed into ours. Some terminals + the
413
- // Claude Code statusline integration don't fully clear the line on rewrite,
414
- // which surfaced as "Cache expires 4:574" or "Cache expires 43550" — old
415
- // digits from a prior frame leaking past the new shorter timer text.
416
- // \x1b[K is the standard "erase from cursor to EOL" CSI; safe on any
417
- // ANSI-compatible terminal and a no-op when stdout isn't a TTY.
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';
398
+ // (longer) statusline render don't bleed into ours. \x1b[K is the standard
399
+ // "erase from cursor to EOL" CSI; safe on any ANSI-compatible terminal and
400
+ // a no-op when stdout isn't a TTY.
401
+ return segs.join(' · ') + '\x1b[K';
427
402
  }