claude-token-saver 2.8.4 → 2.8.6
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 +13 -6
- package/README.md +23 -16
- package/bin/cli.js +14 -3
- package/package.json +4 -1
- package/src/formatters/statusline.js +7 -32
package/README.en.md
CHANGED
|
@@ -13,18 +13,21 @@ A CLI to **diagnose and save tokens on Claude Code**. Cache hit rate, TTL countd
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
#
|
|
16
|
+
# (existing users) remove the old package
|
|
17
17
|
npm uninstall -g claude-cache-monitor
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# install — the postinstall hook auto-registers the Skill and statusline
|
|
20
20
|
npm i -g claude-token-saver
|
|
21
|
-
|
|
22
|
-
# 3. wire into Claude Code (Skill + statusline guidance)
|
|
23
|
-
claude-token-saver install
|
|
24
21
|
```
|
|
25
22
|
|
|
26
23
|
Or run once with no install: `npx claude-token-saver`.
|
|
27
24
|
|
|
25
|
+
If postinstall was skipped (e.g. `--ignore-scripts`, sudo, or sandboxed installs), register manually:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude-token-saver install
|
|
29
|
+
```
|
|
30
|
+
|
|
28
31
|
## What you see after install
|
|
29
32
|
|
|
30
33
|
**(A) One-shot report** — `claude-token-saver`:
|
|
@@ -67,7 +70,7 @@ Risk chips lead when something's wrong: `🚨 5H 94%`, `⚠ 1M ON`, `⚠ Cache m
|
|
|
67
70
|
| `claude-token-saver` | Last 30 days diagnostic report |
|
|
68
71
|
| `claude-token-saver --days 7` | Change window |
|
|
69
72
|
| `claude-token-saver --statusline --icon` | One-line statusline output |
|
|
70
|
-
| `claude-token-saver install` |
|
|
73
|
+
| `claude-token-saver install` | Manually register the Claude Code Skill (fallback when postinstall is skipped) |
|
|
71
74
|
| `claude-token-saver history` | Last 7 days of chip transitions (1M ON, Cache miss, cap, …) |
|
|
72
75
|
| `claude-token-saver handoff` | Back current work up to `HANDOFF-YYYY-MM-DD-HHMM.md` before a cap blocks you |
|
|
73
76
|
| `claude-token-saver --install-hook` | Auto-log cache stats on every tool call |
|
|
@@ -143,6 +146,10 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
|
|
|
143
146
|
- [HN discussion](https://news.ycombinator.com/item?id=47736476) — 168 points, 142 comments
|
|
144
147
|
- [HNPulse KR](https://www.youtube.com/@HNPulseKR) — Korean HN tech deep-dives
|
|
145
148
|
|
|
149
|
+
## Known environment quirks
|
|
150
|
+
|
|
151
|
+
**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.
|
|
152
|
+
|
|
146
153
|
## License
|
|
147
154
|
|
|
148
155
|
MIT
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# claude-token-saver
|
|
4
4
|
|
|
5
|
-
> v2.0에서 `claude-cache-monitor` → `claude-token-saver`로 이름이 바뀌었습니다. 기존 사용자는 [마이그레이션](#마이그레이션-claude-cache-monitor에서)
|
|
5
|
+
> v2.0에서 `claude-cache-monitor` → `claude-token-saver`로 이름이 바뀌었습니다. 기존 사용자는 아래 [마이그레이션](#마이그레이션-claude-cache-monitor에서) 항목을 참고하세요.
|
|
6
6
|
|
|
7
7
|
Claude Code의 **토큰 사용량을 진단·절약**하는 CLI. 캐시 히트율, TTL 카운트다운, 1M 컨텍스트 감지, 5h/7d 한도 경고를 statusline 한 줄로 보여줍니다.
|
|
8
8
|
|
|
@@ -13,18 +13,21 @@ Claude Code의 **토큰 사용량을 진단·절약**하는 CLI. 캐시 히트
|
|
|
13
13
|
## 설치
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
#
|
|
16
|
+
# (기존 사용자) 구 패키지 제거
|
|
17
17
|
npm uninstall -g claude-cache-monitor
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# 설치 — postinstall 훅이 Skill과 statusline을 자동 등록합니다
|
|
20
20
|
npm i -g claude-token-saver
|
|
21
|
-
|
|
22
|
-
# 3. Claude Code 통합 (Skill + statusline 안내)
|
|
23
|
-
claude-token-saver install
|
|
24
21
|
```
|
|
25
22
|
|
|
26
23
|
설치 없이 한 번만 실행하려면 `npx claude-token-saver`.
|
|
27
24
|
|
|
25
|
+
`--ignore-scripts`나 sudo 등으로 postinstall이 실행되지 않은 환경에서는 다음 명령으로 수동 등록할 수 있습니다.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude-token-saver install
|
|
29
|
+
```
|
|
30
|
+
|
|
28
31
|
## 설치 후 보이는 것
|
|
29
32
|
|
|
30
33
|
**(A) 단발 리포트** — `claude-token-saver` 실행 시:
|
|
@@ -44,7 +47,7 @@ TTL Breakdown / Cost Impact / Daily Trend ...
|
|
|
44
47
|
🧠 97.5% · ⏳ 1h 42:15 · 💰 $4.8K · 🤖 Opus 4.7 · ✦ 5H 47% · 📅 7D 9%
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
위험 상황에서는 칩이
|
|
50
|
+
위험 상황에서는 경고 칩이 맨 앞에 표시됩니다 — `🚨 5H 94%`, `⚠ 1M ON`, `⚠ Cache miss`, `⚠ 5m TTL` 등.
|
|
48
51
|
|
|
49
52
|
## statusline 등록
|
|
50
53
|
|
|
@@ -58,7 +61,7 @@ TTL Breakdown / Cost Impact / Daily Trend ...
|
|
|
58
61
|
}
|
|
59
62
|
```
|
|
60
63
|
|
|
61
|
-
`refreshInterval: 5`는
|
|
64
|
+
`refreshInterval: 5`는 idle 상태에서도 TTL 카운트다운을 5초마다 갱신합니다 (1초도 가능하지만 상시 I/O 부담을 줄이기 위해 5초를 기본값으로 권장합니다). Windows(PowerShell) 환경은 `examples/statusline-command.ps1`을 참고하세요.
|
|
62
65
|
|
|
63
66
|
## 주요 명령
|
|
64
67
|
|
|
@@ -66,22 +69,22 @@ TTL Breakdown / Cost Impact / Daily Trend ...
|
|
|
66
69
|
|---|---|
|
|
67
70
|
| `claude-token-saver` | 최근 30일 진단 리포트 |
|
|
68
71
|
| `claude-token-saver --days 7` | 기간 변경 |
|
|
69
|
-
| `claude-token-saver --statusline --icon` | statusline용 한 줄 |
|
|
70
|
-
| `claude-token-saver install` | Claude Code Skill
|
|
72
|
+
| `claude-token-saver --statusline --icon` | statusline용 한 줄 출력 |
|
|
73
|
+
| `claude-token-saver install` | Claude Code Skill 수동 등록 (postinstall이 막힌 환경 대비) |
|
|
71
74
|
| `claude-token-saver history` | 최근 7일간 칩 전이 로그 (1M ON, Cache miss, cap 등) |
|
|
72
75
|
| `claude-token-saver handoff` | 현재 작업을 `HANDOFF-YYYY-MM-DD-HHMM.md`로 백업 (cap 임박 시) |
|
|
73
76
|
| `claude-token-saver --install-hook` | 매 도구 호출마다 캐시 통계 자동 로깅 |
|
|
74
77
|
|
|
75
78
|
전체 옵션은 `--help` 또는 [영문 README](./README.en.md#options).
|
|
76
79
|
|
|
77
|
-
##
|
|
80
|
+
## 토큰 급증 원인 코드
|
|
78
81
|
|
|
79
82
|
| 코드 | 의미 |
|
|
80
83
|
|---|---|
|
|
81
84
|
| `LARGE_INPUT_PER_REQUEST` | 단일 요청 250k+ → 1M 컨텍스트 의심 |
|
|
82
|
-
| `LOW_HIT_RATE` | 캐시 히트율
|
|
83
|
-
| `BUCKET_5M_DOMINANT` | 캐시 쓰기의 70
|
|
84
|
-
| `HIGH_OUTPUT_RATIO` | 출력/입력
|
|
85
|
+
| `LOW_HIT_RATE` | 캐시 히트율 50% 미만 |
|
|
86
|
+
| `BUCKET_5M_DOMINANT` | 캐시 쓰기의 70% 이상이 5분 버킷에 집중 (Pro 플랜 또는 Max 다운그레이드) |
|
|
87
|
+
| `HIGH_OUTPUT_RATIO` | 출력/입력 비율 0.15 초과 (출력 단가가 입력의 5배) |
|
|
85
88
|
| `FREQUENT_CACHE_REBUILD` | 캐시 재작성이 읽기보다 많음 |
|
|
86
89
|
|
|
87
90
|
각 코드마다 OS별 해결 명령(`~/.zshrc` / `setx`)이 함께 출력됩니다.
|
|
@@ -93,16 +96,20 @@ npm uninstall -g claude-cache-monitor
|
|
|
93
96
|
npm i -g claude-token-saver
|
|
94
97
|
```
|
|
95
98
|
|
|
96
|
-
`~/.claude/settings.json`의 `statusLine.command`를 `claude-cache-monitor …` → `claude-token-saver …`로 교체하세요. v2.0
|
|
99
|
+
`~/.claude/settings.json`의 `statusLine.command`를 `claude-cache-monitor …` → `claude-token-saver …`로 교체하세요. v2.0에 잠시 제공됐던 `claude-cache-monitor` 바이너리 별칭은 글로벌 설치 시 npm 충돌(EEXIST)을 일으켜 이후 버전에서 제거됐습니다.
|
|
97
100
|
|
|
98
101
|
## 동작 원리
|
|
99
102
|
|
|
100
|
-
Claude Code는 모든 API 응답을 `~/.claude/projects/<dir>/<session>.jsonl`에 기록합니다. 이 도구는 `cache_read_input_tokens`, `cache_creation.ephemeral_5m/1h_input_tokens`
|
|
103
|
+
Claude Code는 모든 API 응답을 `~/.claude/projects/<dir>/<session>.jsonl`에 기록합니다. 이 도구는 `cache_read_input_tokens`, `cache_creation.ephemeral_5m/1h_input_tokens` 같은 필드를 `requestId` 기준으로 중복 제거한 뒤 일·세션 단위로 집계합니다.
|
|
101
104
|
|
|
102
105
|
## 환경
|
|
103
106
|
|
|
104
107
|
Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
|
|
105
108
|
|
|
109
|
+
## 알려진 환경 이슈
|
|
110
|
+
|
|
111
|
+
**IntelliJ Claude Code plugin** — statusline 위젯이 이전 프레임과 새 프레임을 글자 단위로 잘못 합쳐 `Cache expires 59:548` 같은 잔재 문자열이 보이는 버그가 있습니다 (이모지가 포함된 출력에서만 재현). v2.8.5+는 `TERMINAL_EMULATOR=JetBrains-JediTerm`을 감지하면 자동으로 text 모드로 폴백해 이모지 없이 출력합니다 (`--icon` 플래그도 IntelliJ에서는 무시됩니다). 다른 터미널(iTerm, Terminal, WSL 등)에는 영향 없습니다.
|
|
112
|
+
|
|
106
113
|
## 라이선스
|
|
107
114
|
|
|
108
115
|
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 =
|
|
705
|
-
?
|
|
706
|
-
: (hasFlag('--
|
|
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,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.6",
|
|
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": {
|
|
7
7
|
"claude-token-saver": "bin/cli.js"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node bin/cli.js install || true"
|
|
11
|
+
},
|
|
9
12
|
"files": [
|
|
10
13
|
"bin/",
|
|
11
14
|
"src/",
|
|
@@ -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
|
|
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
|
|
116
|
-
return
|
|
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.
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
|
|
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
|
}
|