claude-token-saver 2.8.5 → 2.9.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.en.md +67 -23
- package/README.md +75 -31
- package/bin/cli.js +26 -22
- package/package.json +4 -1
- package/src/config.js +22 -0
- package/src/history.js +55 -2
package/README.en.md
CHANGED
|
@@ -13,40 +13,38 @@ 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
|
|
|
28
|
-
|
|
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
|
+
|
|
31
|
+
## Claude Code statusline
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
After install, Claude Code's bottom statusline updates every 5 seconds with cache state (postinstall registers it in `~/.claude/settings.json` automatically).
|
|
31
34
|
|
|
32
35
|
```
|
|
33
|
-
|
|
34
|
-
Context window: 200k ✓ standard
|
|
35
|
-
Cache hit rate: 98.2% | Total input: 1957.94M tokens
|
|
36
|
-
TTL Breakdown / Cost Impact / Daily Trend ...
|
|
36
|
+
🤖 Opus 4.7 · 🧠 Cache hit 98.0% · ⏳ Cache expires 59:51 · 📦 Ctx 200k · 💰 Cache saved $221 · last 1d
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
**(B) Statusline chip** — once wired into `~/.claude/settings.json`:
|
|
39
|
+
Risk chips lead when something's wrong:
|
|
42
40
|
|
|
43
41
|
```
|
|
44
|
-
|
|
42
|
+
🚨 5H 94% (resets in 12m) · 🤖 Opus 4.7 · 🧠 Cache hit 72.1% · ⚠ Cache miss · 📦 Ctx 200k · last 1d
|
|
45
43
|
```
|
|
46
44
|
|
|
47
|
-
Risk chips
|
|
45
|
+
Risk chips: `🚨 5H/7D NN%`, `⚠ 1M ON`, `⚠ Input spike`, `⚠ Cache miss`, `⚠ 5m TTL`, `⚠ Rebuild churn`, `⚠ Output heavy`, `⚠ Call surge`.
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
If postinstall was skipped (you already use a different statusline, etc.), wire it manually:
|
|
50
48
|
|
|
51
49
|
```json
|
|
52
50
|
{
|
|
@@ -58,19 +56,50 @@ Risk chips lead when something's wrong: `🚨 5H 94%`, `⚠ 1M ON`, `⚠ Cache m
|
|
|
58
56
|
}
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
`refreshInterval: 5` keeps the TTL countdown ticking while idle
|
|
59
|
+
`refreshInterval: 5` keeps the TTL countdown ticking while idle. For Windows PowerShell see `examples/statusline-command.ps1`.
|
|
60
|
+
|
|
61
|
+
## Skill — mention a chip, it activates
|
|
62
|
+
|
|
63
|
+
The Claude Code Skill registered at install time auto-activates whenever you mention chip wording in chat ("cache hit rate", "1M context", "5H cap"). It runs `claude-token-saver last` to surface the most recent warning + remediation, then points you at `history` / `handoff` for follow-up. (v2.6.0 folded the legacy `/token-monitor` slash command into this Skill — older installs are cleaned up automatically the next time you run `claude-token-saver install`.)
|
|
64
|
+
|
|
65
|
+
## One-shot report
|
|
66
|
+
|
|
67
|
+
Run `claude-token-saver` for the last-day diagnostic table:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Claude Token Saver — Last 1 day
|
|
71
|
+
(claude-token-saver v2.9.0)
|
|
72
|
+
══════════════════════════════════════════════════
|
|
73
|
+
|
|
74
|
+
Context window: 200k ✓ 200k context (standard)
|
|
75
|
+
Sessions: 11 | API calls: 578 | Cache hit rate: 98.0%
|
|
76
|
+
TTL Breakdown / Cost Impact / Daily Trend …
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If a session spiked, a `⚠ Spike detected` block leads the report with the root-cause code (table below) and an OS-aware remediation command.
|
|
80
|
+
|
|
81
|
+
## Output language
|
|
82
|
+
|
|
83
|
+
`last` / `history` / advice messages render in one language at a time (statusline chips stay symbolic). English is the default — switch via:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
claude-token-saver mode ko # or: claude-token-saver mode lang=ko
|
|
87
|
+
claude-token-saver mode en # back to English
|
|
88
|
+
claude-token-saver mode # show current settings
|
|
89
|
+
```
|
|
62
90
|
|
|
63
91
|
## Commands
|
|
64
92
|
|
|
65
93
|
| Command | What it does |
|
|
66
94
|
|---|---|
|
|
67
|
-
| `claude-token-saver` | Last
|
|
68
|
-
| `claude-token-saver
|
|
69
|
-
| `claude-token-saver --statusline --icon` | One-line statusline output |
|
|
70
|
-
| `claude-token-saver install` | Register Claude Code Skill (auto-activates on chip wording) |
|
|
95
|
+
| `claude-token-saver` | Last-1-day diagnostic report (`--days N` to change window) |
|
|
96
|
+
| `claude-token-saver last` | Most recent warning + remediation (the command the Skill invokes) |
|
|
71
97
|
| `claude-token-saver history` | Last 7 days of chip transitions (1M ON, Cache miss, cap, …) |
|
|
72
98
|
| `claude-token-saver handoff` | Back current work up to `HANDOFF-YYYY-MM-DD-HHMM.md` before a cap blocks you |
|
|
73
|
-
| `claude-token-saver
|
|
99
|
+
| `claude-token-saver mode [keywords...]` | Configure output (`icon`/`text`, `en`/`ko`, `verbose`, `1d`/`7d`, …) |
|
|
100
|
+
| `claude-token-saver --statusline --icon` | One-line statusline output (called by Claude Code) |
|
|
101
|
+
| `claude-token-saver install` | Manually register Skill + statusline (postinstall fallback) |
|
|
102
|
+
| `claude-token-saver --install-hook` | Optionally auto-log cache stats on every tool call |
|
|
74
103
|
|
|
75
104
|
## Options
|
|
76
105
|
|
|
@@ -147,6 +176,21 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
|
|
|
147
176
|
|
|
148
177
|
**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
178
|
|
|
179
|
+
## Release notes
|
|
180
|
+
|
|
181
|
+
### v2.9.0 (2026-04-27)
|
|
182
|
+
- **Output language is now configurable.** `last` / `history` / advice render in a single language at a time. English by default; switch with `claude-token-saver mode ko`. Statusline chips remain symbolic.
|
|
183
|
+
- History files stay bilingual on disk; the language toggle is applied at display time.
|
|
184
|
+
|
|
185
|
+
### v2.8.6 (2026-04-27)
|
|
186
|
+
- **Skill auto-registers on install.** A `postinstall` hook wires the Claude Code Skill and statusline into `~/.claude` automatically — no second command. `claude-token-saver install` still works as a fallback for `--ignore-scripts` / sudo / sandboxed environments.
|
|
187
|
+
- README polish in both languages; corrected the `claude-cache-monitor` alias-removal note (timing was reversed).
|
|
188
|
+
|
|
189
|
+
### v2.8.5
|
|
190
|
+
- IntelliJ Claude Code plugin: auto-fall back to text mode when `TERMINAL_EMULATOR=JetBrains-JediTerm` to avoid frame-fusion artefacts.
|
|
191
|
+
|
|
192
|
+
Older versions: see `git log`.
|
|
193
|
+
|
|
150
194
|
## License
|
|
151
195
|
|
|
152
196
|
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,40 +13,38 @@ 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
|
|
|
28
|
-
|
|
25
|
+
`--ignore-scripts`나 sudo 등으로 postinstall이 실행되지 않은 환경에서는 다음 명령으로 수동 등록할 수 있습니다.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude-token-saver install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Claude Code statusline
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
설치 후 Claude Code 하단 statusline에 캐시 상태가 5초마다 갱신됩니다 (postinstall이 `~/.claude/settings.json`에 자동 등록).
|
|
31
34
|
|
|
32
35
|
```
|
|
33
|
-
|
|
34
|
-
Context window: 200k ✓ 표준
|
|
35
|
-
Cache hit rate: 98.2% | Total input: 1957.94M tokens
|
|
36
|
-
TTL Breakdown / Cost Impact / Daily Trend ...
|
|
36
|
+
🤖 Opus 4.7 · 🧠 Cache hit 98.0% · ⏳ Cache expires 59:51 · 📦 Ctx 200k · 💰 Cache saved $221 · last 1d
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
**(B) statusline 한 줄** — `~/.claude/settings.json`에 등록 시:
|
|
39
|
+
위험 상황에서는 경고 칩이 맨 앞으로 나옵니다.
|
|
42
40
|
|
|
43
41
|
```
|
|
44
|
-
|
|
42
|
+
🚨 5H 94% (resets in 12m) · 🤖 Opus 4.7 · 🧠 Cache hit 72.1% · ⚠ Cache miss · 📦 Ctx 200k · last 1d
|
|
45
43
|
```
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
표시되는 경고 칩 — `🚨 5H/7D NN%`, `⚠ 1M ON`, `⚠ Input spike`, `⚠ Cache miss`, `⚠ 5m TTL`, `⚠ Rebuild churn`, `⚠ Output heavy`, `⚠ Call surge`.
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
수동 등록이 필요한 경우(다른 statusline을 이미 쓰고 있어 postinstall이 건너뛴 경우 등):
|
|
50
48
|
|
|
51
49
|
```json
|
|
52
50
|
{
|
|
@@ -58,30 +56,61 @@ TTL Breakdown / Cost Impact / Daily Trend ...
|
|
|
58
56
|
}
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
`refreshInterval: 5`는
|
|
59
|
+
`refreshInterval: 5`는 idle 상태에서도 TTL 카운트다운을 5초마다 갱신합니다. Windows(PowerShell)는 `examples/statusline-command.ps1` 참고.
|
|
60
|
+
|
|
61
|
+
## Skill — 칩을 언급하면 자동 활성화
|
|
62
|
+
|
|
63
|
+
설치 시 함께 등록되는 Claude Code Skill 덕분에 대화 중 "cache hit rate", "1M context", "5H cap" 같은 칩 단어를 언급하면 Claude가 자동으로 `claude-token-saver last`를 실행해 가장 최근 경고와 처방을 보여주고, 필요 시 `history` / `handoff`로 후속 조치를 안내합니다. (v2.6.0에서 레거시 `/token-monitor` 슬래시 커맨드는 Skill로 흡수됐습니다 — 이전 버전 사용자는 `claude-token-saver install`을 한 번 다시 실행하면 자동 정리됩니다.)
|
|
64
|
+
|
|
65
|
+
## 단발 리포트
|
|
66
|
+
|
|
67
|
+
`claude-token-saver`를 실행하면 최근 1일 진단 표가 출력됩니다.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Claude Token Saver — Last 1 day
|
|
71
|
+
(claude-token-saver v2.9.0)
|
|
72
|
+
══════════════════════════════════════════════════
|
|
73
|
+
|
|
74
|
+
Context window: 200k ✓ 200k context (standard)
|
|
75
|
+
Sessions: 11 | API calls: 578 | Cache hit rate: 98.0%
|
|
76
|
+
TTL Breakdown / Cost Impact / Daily Trend …
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
급증 세션이 있으면 상단에 `⚠ Spike detected` 블록과 원인 코드(아래 표) · OS별 해결 명령이 함께 출력됩니다.
|
|
80
|
+
|
|
81
|
+
## 출력 언어 전환
|
|
82
|
+
|
|
83
|
+
`last` / `history` / 처방 메시지는 영어가 기본값이며 한 번에 한 언어만 출력합니다 (statusline 칩은 항상 동일한 기호 형식). 한국어로 바꾸려면:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
claude-token-saver mode ko # 또는: claude-token-saver mode lang=ko
|
|
87
|
+
claude-token-saver mode en # 영어로 복귀
|
|
88
|
+
claude-token-saver mode # 현재 설정 확인
|
|
89
|
+
```
|
|
62
90
|
|
|
63
91
|
## 주요 명령
|
|
64
92
|
|
|
65
93
|
| 명령 | 설명 |
|
|
66
94
|
|---|---|
|
|
67
|
-
| `claude-token-saver` | 최근
|
|
68
|
-
| `claude-token-saver
|
|
69
|
-
| `claude-token-saver --statusline --icon` | statusline용 한 줄 |
|
|
70
|
-
| `claude-token-saver install` | Claude Code Skill 자동 등록 (칩 단어 언급 시 자동 활성) |
|
|
95
|
+
| `claude-token-saver` | 최근 1일 진단 리포트 (`--days N`로 기간 변경) |
|
|
96
|
+
| `claude-token-saver last` | 가장 최근 경고 1건 + 처방 (Skill이 호출하는 명령) |
|
|
71
97
|
| `claude-token-saver history` | 최근 7일간 칩 전이 로그 (1M ON, Cache miss, cap 등) |
|
|
72
98
|
| `claude-token-saver handoff` | 현재 작업을 `HANDOFF-YYYY-MM-DD-HHMM.md`로 백업 (cap 임박 시) |
|
|
73
|
-
| `claude-token-saver
|
|
99
|
+
| `claude-token-saver mode [keywords...]` | 출력 모드 설정 (`icon`/`text`, `ko`/`en`, `verbose`, `1d`/`7d` 등) |
|
|
100
|
+
| `claude-token-saver --statusline --icon` | statusline용 한 줄 출력 (Claude Code가 호출) |
|
|
101
|
+
| `claude-token-saver install` | Skill·statusline 수동 등록 (postinstall이 막힌 환경) |
|
|
102
|
+
| `claude-token-saver --install-hook` | 매 도구 호출마다 캐시 통계 자동 로깅 (선택) |
|
|
74
103
|
|
|
75
104
|
전체 옵션은 `--help` 또는 [영문 README](./README.en.md#options).
|
|
76
105
|
|
|
77
|
-
##
|
|
106
|
+
## 토큰 급증 원인 코드
|
|
78
107
|
|
|
79
108
|
| 코드 | 의미 |
|
|
80
109
|
|---|---|
|
|
81
110
|
| `LARGE_INPUT_PER_REQUEST` | 단일 요청 250k+ → 1M 컨텍스트 의심 |
|
|
82
|
-
| `LOW_HIT_RATE` | 캐시 히트율
|
|
83
|
-
| `BUCKET_5M_DOMINANT` | 캐시 쓰기의 70
|
|
84
|
-
| `HIGH_OUTPUT_RATIO` | 출력/입력
|
|
111
|
+
| `LOW_HIT_RATE` | 캐시 히트율 50% 미만 |
|
|
112
|
+
| `BUCKET_5M_DOMINANT` | 캐시 쓰기의 70% 이상이 5분 버킷에 집중 (Pro 플랜 또는 Max 다운그레이드) |
|
|
113
|
+
| `HIGH_OUTPUT_RATIO` | 출력/입력 비율 0.15 초과 (출력 단가가 입력의 5배) |
|
|
85
114
|
| `FREQUENT_CACHE_REBUILD` | 캐시 재작성이 읽기보다 많음 |
|
|
86
115
|
|
|
87
116
|
각 코드마다 OS별 해결 명령(`~/.zshrc` / `setx`)이 함께 출력됩니다.
|
|
@@ -93,11 +122,11 @@ npm uninstall -g claude-cache-monitor
|
|
|
93
122
|
npm i -g claude-token-saver
|
|
94
123
|
```
|
|
95
124
|
|
|
96
|
-
`~/.claude/settings.json`의 `statusLine.command`를 `claude-cache-monitor …` → `claude-token-saver …`로 교체하세요. v2.0
|
|
125
|
+
`~/.claude/settings.json`의 `statusLine.command`를 `claude-cache-monitor …` → `claude-token-saver …`로 교체하세요. v2.0에 잠시 제공됐던 `claude-cache-monitor` 바이너리 별칭은 글로벌 설치 시 npm 충돌(EEXIST)을 일으켜 이후 버전에서 제거됐습니다.
|
|
97
126
|
|
|
98
127
|
## 동작 원리
|
|
99
128
|
|
|
100
|
-
Claude Code는 모든 API 응답을 `~/.claude/projects/<dir>/<session>.jsonl`에 기록합니다. 이 도구는 `cache_read_input_tokens`, `cache_creation.ephemeral_5m/1h_input_tokens`
|
|
129
|
+
Claude Code는 모든 API 응답을 `~/.claude/projects/<dir>/<session>.jsonl`에 기록합니다. 이 도구는 `cache_read_input_tokens`, `cache_creation.ephemeral_5m/1h_input_tokens` 같은 필드를 `requestId` 기준으로 중복 제거한 뒤 일·세션 단위로 집계합니다.
|
|
101
130
|
|
|
102
131
|
## 환경
|
|
103
132
|
|
|
@@ -105,7 +134,22 @@ Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
|
|
|
105
134
|
|
|
106
135
|
## 알려진 환경 이슈
|
|
107
136
|
|
|
108
|
-
**IntelliJ Claude Code plugin** — statusline 위젯이 이전 프레임과 새 프레임을 글자 단위로 잘못 합쳐 `Cache expires 59:548` 같은
|
|
137
|
+
**IntelliJ Claude Code plugin** — statusline 위젯이 이전 프레임과 새 프레임을 글자 단위로 잘못 합쳐 `Cache expires 59:548` 같은 잔재 문자열이 보이는 버그가 있습니다 (이모지가 포함된 출력에서만 재현). v2.8.5+는 `TERMINAL_EMULATOR=JetBrains-JediTerm`을 감지하면 자동으로 text 모드로 폴백해 이모지 없이 출력합니다 (`--icon` 플래그도 IntelliJ에서는 무시됩니다). 다른 터미널(iTerm, Terminal, WSL 등)에는 영향 없습니다.
|
|
138
|
+
|
|
139
|
+
## 릴리스 노트
|
|
140
|
+
|
|
141
|
+
### v2.9.0 (2026-04-27)
|
|
142
|
+
- **출력 언어 전환 추가** — `last` / `history` / 처방 메시지가 한 번에 한 언어만 출력합니다. 기본은 영어, `claude-token-saver mode ko`로 한국어 전환 (statusline 칩은 영향 없음).
|
|
143
|
+
- 기존 history 파일은 이중언어로 보관되며, 표시할 때 선택한 언어만 필터링됩니다.
|
|
144
|
+
|
|
145
|
+
### v2.8.6 (2026-04-27)
|
|
146
|
+
- **Skill 자동 등록** — `npm i -g claude-token-saver` 시 postinstall 훅이 Skill과 statusline을 자동으로 `~/.claude`에 등록. 수동 `claude-token-saver install`은 `--ignore-scripts` / sudo 환경용 폴백으로 유지.
|
|
147
|
+
- 한·영 README 문장 다듬기, `claude-cache-monitor` alias 제거 시점 설명 정정.
|
|
148
|
+
|
|
149
|
+
### v2.8.5
|
|
150
|
+
- IntelliJ Claude Code plugin에서 statusline 프레임 합성 버그 회피 — `TERMINAL_EMULATOR=JetBrains-JediTerm` 감지 시 자동 text 모드.
|
|
151
|
+
|
|
152
|
+
이전 버전은 `git log`를 참고하세요.
|
|
109
153
|
|
|
110
154
|
## 라이선스
|
|
111
155
|
|
package/bin/cli.js
CHANGED
|
@@ -230,6 +230,8 @@ async function main() {
|
|
|
230
230
|
if (args[0] === 'last') {
|
|
231
231
|
const { readRecent, historyDir } = await import('../src/history.js');
|
|
232
232
|
const { ISSUE_MESSAGES, CHIP_TO_CODES, CAP_TIPS } = await import('../src/advice.js');
|
|
233
|
+
const { userLanguage } = await import('../src/config.js');
|
|
234
|
+
const lang = userLanguage();
|
|
233
235
|
const days = parseFloat(getArg('--days') || '1');
|
|
234
236
|
const recent = readRecent(days);
|
|
235
237
|
const latest = findLatestWarning(recent, CHIP_TO_CODES);
|
|
@@ -247,10 +249,9 @@ async function main() {
|
|
|
247
249
|
if (latest.isCap) {
|
|
248
250
|
if (latest.capReset) console.log(` Cap window: ${latest.capReset}`);
|
|
249
251
|
console.log('');
|
|
250
|
-
console.log('💡 ' + CAP_TIPS.en);
|
|
251
|
-
console.log('💡 ' + CAP_TIPS.ko);
|
|
252
|
+
console.log('💡 ' + (lang === 'ko' ? CAP_TIPS.ko : CAP_TIPS.en));
|
|
252
253
|
console.log('');
|
|
253
|
-
console.log('Run:');
|
|
254
|
+
console.log(lang === 'ko' ? '실행:' : 'Run:');
|
|
254
255
|
console.log(' claude-token-saver handoff');
|
|
255
256
|
return;
|
|
256
257
|
}
|
|
@@ -258,31 +259,29 @@ async function main() {
|
|
|
258
259
|
// bilingual (English first, `└ Korean` continuation per line — matches
|
|
259
260
|
// the history.md format).
|
|
260
261
|
if (latest.codes.length === 0) {
|
|
261
|
-
console.log(
|
|
262
|
-
|
|
262
|
+
console.log(lang === 'ko'
|
|
263
|
+
? '(진단 코드 없음 — 표 뷰를 열어보세요: `claude-token-saver --days 1`)'
|
|
264
|
+
: '(No diagnostic code attached — open the table view: `claude-token-saver --days 1`)');
|
|
263
265
|
return;
|
|
264
266
|
}
|
|
267
|
+
// Pick a single language per field; fall back to EN when KO is missing.
|
|
268
|
+
const pick = (en, ko) => (lang === 'ko' && ko ? ko : en);
|
|
265
269
|
for (const code of latest.codes) {
|
|
266
270
|
const msg = ISSUE_MESSAGES[code];
|
|
267
271
|
if (!msg) {
|
|
268
272
|
console.log(`Code: ${code} (no advice registered)`);
|
|
269
273
|
continue;
|
|
270
274
|
}
|
|
271
|
-
console.log(`▎ ${msg.title}`);
|
|
272
|
-
|
|
273
|
-
console.log(` ${msg.explain}`);
|
|
274
|
-
if (msg.explainKo && msg.explainKo !== msg.explain) console.log(` └ ${msg.explainKo}`);
|
|
275
|
+
console.log(`▎ ${pick(msg.title, msg.titleKo)}`);
|
|
276
|
+
console.log(` ${pick(msg.explain, msg.explainKo)}`);
|
|
275
277
|
const actions = typeof msg.actions === 'function' ? msg.actions() : msg.actions || [];
|
|
276
278
|
for (const a of actions) {
|
|
277
279
|
console.log('');
|
|
278
|
-
console.log(` ${a.label}:`);
|
|
279
|
-
if (a.labelKo && a.labelKo !== a.label) console.log(` └ ${a.labelKo}:`);
|
|
280
|
+
console.log(` ${pick(a.label, a.labelKo)}:`);
|
|
280
281
|
const cmds = a.commands || [];
|
|
281
282
|
const cmdsKo = a.commandsKo || [];
|
|
282
283
|
for (let i = 0; i < cmds.length; i++) {
|
|
283
|
-
console.log(` - ${cmds[i]}`);
|
|
284
|
-
const ko = cmdsKo[i];
|
|
285
|
-
if (ko && ko !== cmds[i]) console.log(` └ ${ko}`);
|
|
284
|
+
console.log(` - ${pick(cmds[i], cmdsKo[i])}`);
|
|
286
285
|
}
|
|
287
286
|
}
|
|
288
287
|
console.log('');
|
|
@@ -297,7 +296,10 @@ async function main() {
|
|
|
297
296
|
// claude-token-saver history --days 30 # custom window
|
|
298
297
|
// claude-token-saver history --list # just list available dates
|
|
299
298
|
if (args[0] === 'history') {
|
|
300
|
-
const { readRecent, listDates, historyDir } =
|
|
299
|
+
const { readRecent, listDates, historyDir, formatHistoryForLanguage } =
|
|
300
|
+
await import('../src/history.js');
|
|
301
|
+
const { userLanguage } = await import('../src/config.js');
|
|
302
|
+
const lang = userLanguage();
|
|
301
303
|
if (hasFlag('--list')) {
|
|
302
304
|
const dates = listDates();
|
|
303
305
|
if (dates.length === 0) {
|
|
@@ -316,7 +318,8 @@ async function main() {
|
|
|
316
318
|
return;
|
|
317
319
|
}
|
|
318
320
|
for (const { content } of recent) {
|
|
319
|
-
|
|
321
|
+
const filtered = formatHistoryForLanguage(content, lang);
|
|
322
|
+
console.log(filtered.replace(/\n+$/, ''));
|
|
320
323
|
console.log('');
|
|
321
324
|
}
|
|
322
325
|
return;
|
|
@@ -400,11 +403,12 @@ async function main() {
|
|
|
400
403
|
const eff = statuslineDefaults();
|
|
401
404
|
const raw = loadConfig().statusline || {};
|
|
402
405
|
console.log('Statusline mode (effective):');
|
|
403
|
-
console.log(` icon:
|
|
404
|
-
console.log(` verbose:
|
|
405
|
-
console.log(` timer:
|
|
406
|
-
console.log(` color:
|
|
407
|
-
console.log(`
|
|
406
|
+
console.log(` icon: ${eff.icon}`);
|
|
407
|
+
console.log(` verbose: ${eff.verbose}`);
|
|
408
|
+
console.log(` timer: ${eff.timer}`);
|
|
409
|
+
console.log(` color: ${eff.color}`);
|
|
410
|
+
console.log(` language: ${eff.language} (advice / history / last)`);
|
|
411
|
+
console.log(` window: ${eff.windowLabel} (${eff.windowHours}h)`);
|
|
408
412
|
console.log('');
|
|
409
413
|
console.log(`Stored config (${configPath()}):`);
|
|
410
414
|
console.log(` ${Object.keys(raw).length === 0 ? '(none — using defaults)' : JSON.stringify(raw)}`);
|
|
@@ -421,7 +425,7 @@ async function main() {
|
|
|
421
425
|
}
|
|
422
426
|
const eff = statuslineDefaults();
|
|
423
427
|
console.log(`Updated: ${applied.join(', ')}`);
|
|
424
|
-
console.log(`Now: icon=${eff.icon} verbose=${eff.verbose} timer=${eff.timer} color=${eff.color} window=${eff.windowLabel}`);
|
|
428
|
+
console.log(`Now: icon=${eff.icon} verbose=${eff.verbose} timer=${eff.timer} color=${eff.color} language=${eff.language} window=${eff.windowLabel}`);
|
|
425
429
|
console.log('Statusline picks up the change on the next refresh (~1s).');
|
|
426
430
|
return;
|
|
427
431
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
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/",
|
package/src/config.js
CHANGED
|
@@ -45,6 +45,12 @@ const KEYWORDS = {
|
|
|
45
45
|
'no-timer': { key: 'timer', value: false },
|
|
46
46
|
color: { key: 'color', value: true },
|
|
47
47
|
'no-color': { key: 'color', value: false },
|
|
48
|
+
// Language for advice / history / `last` output. Default is `en`.
|
|
49
|
+
// Statusline chips themselves stay symbolic (e.g. `🚨 5H 94%`) regardless.
|
|
50
|
+
en: { key: 'language', value: 'en' },
|
|
51
|
+
english: { key: 'language', value: 'en' },
|
|
52
|
+
ko: { key: 'language', value: 'ko' },
|
|
53
|
+
korean: { key: 'language', value: 'ko' },
|
|
48
54
|
};
|
|
49
55
|
|
|
50
56
|
// Window preset accepts forms like:
|
|
@@ -68,6 +74,7 @@ function parseWindow(word) {
|
|
|
68
74
|
export const VALID_KEYWORDS = Object.keys(KEYWORDS).concat([
|
|
69
75
|
'<N>h (e.g. 1h, 6h, 24h)',
|
|
70
76
|
'<N>d (e.g. 1d, 7d, 30d)',
|
|
77
|
+
'lang=en | lang=ko',
|
|
71
78
|
'reset',
|
|
72
79
|
'default',
|
|
73
80
|
]);
|
|
@@ -90,6 +97,13 @@ export function applyMode(words) {
|
|
|
90
97
|
applied.push(lower);
|
|
91
98
|
continue;
|
|
92
99
|
}
|
|
100
|
+
const langMatch = lower.match(/^lang(?:uage)?=(en|english|ko|korean)$/);
|
|
101
|
+
if (langMatch) {
|
|
102
|
+
const v = langMatch[1].startsWith('k') ? 'ko' : 'en';
|
|
103
|
+
cfg.statusline.language = v;
|
|
104
|
+
applied.push(`lang=${v}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
93
107
|
const hours = parseWindow(lower);
|
|
94
108
|
if (hours !== null && hours > 0) {
|
|
95
109
|
cfg.statusline.windowHours = hours;
|
|
@@ -136,11 +150,19 @@ export function statuslineDefaults() {
|
|
|
136
150
|
verbose: s.verbose !== false,
|
|
137
151
|
timer: s.timer !== false,
|
|
138
152
|
color: s.color !== false,
|
|
153
|
+
language: s.language === 'ko' ? 'ko' : 'en',
|
|
139
154
|
windowHours,
|
|
140
155
|
windowLabel: formatWindow(windowHours),
|
|
141
156
|
};
|
|
142
157
|
}
|
|
143
158
|
|
|
159
|
+
// Resolve the user's preferred output language for advice/history/last.
|
|
160
|
+
// Returns 'en' or 'ko'; defaults to 'en' for first-time users.
|
|
161
|
+
export function userLanguage() {
|
|
162
|
+
const s = loadConfig().statusline || {};
|
|
163
|
+
return s.language === 'ko' ? 'ko' : 'en';
|
|
164
|
+
}
|
|
165
|
+
|
|
144
166
|
/**
|
|
145
167
|
* Render hours as the most natural unit:
|
|
146
168
|
* 24h → "1d", 168h → "7d", 6h → "6h", 36h → "36h" (not whole days).
|
package/src/history.js
CHANGED
|
@@ -151,7 +151,9 @@ function tipsForEvent(chip, detail) {
|
|
|
151
151
|
const tip = ISSUE_TIPS[code];
|
|
152
152
|
if (!tip) continue;
|
|
153
153
|
enLines.push(` 💡 ${tip.en}`);
|
|
154
|
-
|
|
154
|
+
// KR tip carries the `└` marker so language-filtered renderers can
|
|
155
|
+
// pair it with its EN counterpart (mirrors the event-line continuation).
|
|
156
|
+
koLines.push(` └ 💡 ${tip.ko}`);
|
|
155
157
|
}
|
|
156
158
|
return { en: enLines.join('\n'), ko: koLines.join('\n') };
|
|
157
159
|
}
|
|
@@ -288,7 +290,7 @@ export function recordCapTransition(window) {
|
|
|
288
290
|
ko = `✓ ${labelKo} 캡 경고 해소`;
|
|
289
291
|
}
|
|
290
292
|
// Cap-warn entry → handoff tip; resolution → no tip (just the ✓ line).
|
|
291
|
-
const capTips = isWarn ? { en: ` 💡 ${CAP_TIPS.en}`, ko: ` 💡 ${CAP_TIPS.ko}` } : null;
|
|
293
|
+
const capTips = isWarn ? { en: ` 💡 ${CAP_TIPS.en}`, ko: ` └ 💡 ${CAP_TIPS.ko}` } : null;
|
|
292
294
|
appendDayLine(en, ko, now, capTips);
|
|
293
295
|
state[slotKey] = isWarn;
|
|
294
296
|
saveState(state);
|
|
@@ -310,6 +312,57 @@ export function recordHandoff(filePath) {
|
|
|
310
312
|
return true;
|
|
311
313
|
}
|
|
312
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Filter a daily-history file's content down to a single language.
|
|
317
|
+
*
|
|
318
|
+
* History files are written bilingual (EN line + ` └ KO` continuation +
|
|
319
|
+
* ` 💡 EN tip` + ` └ 💡 KO tip`). This helper renders just the chosen side
|
|
320
|
+
* for display, while the on-disk file stays bilingual for archival.
|
|
321
|
+
*
|
|
322
|
+
* en: drop every ` └ ...` line (KR continuations + KR tips).
|
|
323
|
+
* ko: replace each EN line with the immediately following ` └ KO` line
|
|
324
|
+
* (preserving the leading `- HH:MM:SS` from the EN line so timestamps
|
|
325
|
+
* still appear); drop unpaired EN-only events.
|
|
326
|
+
*
|
|
327
|
+
* Lines that don't match the bilingual pattern (headers, blank lines,
|
|
328
|
+
* legacy entries from older versions without the `└ 💡` marker) pass through
|
|
329
|
+
* unchanged.
|
|
330
|
+
*/
|
|
331
|
+
export function formatHistoryForLanguage(content, lang) {
|
|
332
|
+
if (lang !== 'ko') {
|
|
333
|
+
// English: simply strip the `└` continuations.
|
|
334
|
+
return content
|
|
335
|
+
.split('\n')
|
|
336
|
+
.filter((line) => !/^\s*└\s/.test(line))
|
|
337
|
+
.join('\n');
|
|
338
|
+
}
|
|
339
|
+
// Korean: pair each line with its `└` continuation when present.
|
|
340
|
+
const lines = content.split('\n');
|
|
341
|
+
const out = [];
|
|
342
|
+
for (let i = 0; i < lines.length; i++) {
|
|
343
|
+
const line = lines[i];
|
|
344
|
+
const next = lines[i + 1] || '';
|
|
345
|
+
const cont = next.match(/^(\s*)└\s+(.*)$/);
|
|
346
|
+
// Event line: `- HH:MM:SS <english>` → keep the timestamp prefix, swap text.
|
|
347
|
+
const evt = line.match(/^(- \d{2}:\d{2}:\d{2}\s+)(.*)$/);
|
|
348
|
+
if (evt && cont) {
|
|
349
|
+
out.push(`${evt[1]}${cont[2]}`);
|
|
350
|
+
i++;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
// Tip line: ` 💡 <english>` paired with ` └ 💡 <korean>`.
|
|
354
|
+
const tip = line.match(/^(\s*)💡\s+(.*)$/);
|
|
355
|
+
if (tip && cont && /^💡\s/.test(cont[2])) {
|
|
356
|
+
out.push(`${tip[1]}${cont[2]}`);
|
|
357
|
+
i++;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
// Header / blank / unpaired line — pass through.
|
|
361
|
+
out.push(line);
|
|
362
|
+
}
|
|
363
|
+
return out.join('\n');
|
|
364
|
+
}
|
|
365
|
+
|
|
313
366
|
/**
|
|
314
367
|
* List all available history file dates (sorted newest first).
|
|
315
368
|
*/
|