claude-token-saver 2.8.6 → 2.9.1
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 +76 -17
- package/README.md +76 -17
- package/bin/cli.js +27 -21
- package/package.json +1 -1
- package/src/config.js +47 -6
- package/src/history.js +55 -2
package/README.en.md
CHANGED
|
@@ -28,28 +28,27 @@ If postinstall was skipped (e.g. `--ignore-scripts`, sudo, or sandboxed installs
|
|
|
28
28
|
claude-token-saver install
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Claude Code statusline
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
After install, Claude Code's bottom statusline updates every 5 seconds with cache state (postinstall registers it in `~/.claude/settings.json` automatically).
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
|
|
37
|
-
Context window: 200k ✓ standard
|
|
38
|
-
Cache hit rate: 98.2% | Total input: 1957.94M tokens
|
|
39
|
-
TTL Breakdown / Cost Impact / Daily Trend ...
|
|
36
|
+
🤖 Opus 4.7 · 🧠 Cache hit 98.0% · ⏳ Cache expires 58:38 · ✦ current █░░░░░ 15% 🔄 08:50 · 📅 weekly █▒░░░░ 24% 🔄 Thu 13:00 · 📦 Ctx 200k · 💰 Cache saved $205 · last 1d
|
|
40
37
|
```
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
Segments — `🤖 model` · `🧠 cache hit rate` · `⏳ TTL countdown` · `✦ current` (5-hour window) · `📅 weekly` (7-day window) · `📦 context` · `💰 cumulative savings` · `last <window>`.
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
When excessive token usage is detected, a warning chip is prepended at the front of the statusline:
|
|
45
42
|
|
|
46
43
|
```
|
|
47
|
-
|
|
44
|
+
🚨 5H 94% (resets in 12m) · 🤖 Opus 4.7 · 🧠 Cache hit 72.1% · ⚠ Cache miss · ✦ current ██████ 94% · 📦 Ctx 200k · last 1d
|
|
48
45
|
```
|
|
49
46
|
|
|
50
|
-
Risk chips
|
|
47
|
+
Risk chips: `🚨 5H/7D NN%`, `⚠ 1M ON`, `⚠ Input spike`, `⚠ Cache miss`, `⚠ 5m TTL`, `⚠ Rebuild churn`, `⚠ Output heavy`, `⚠ Call surge`.
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
**What to do** — run the `/claude-token-saver` Skill in Claude. It calls `claude-token-saver last` and surfaces the root cause + step-by-step fix. Saying the chip wording out loud (e.g. "5H cap is up", "cache miss") also auto-activates the same Skill. See the [Skill workflow](#when-a-warning-chip-appears--skill-workflow) section below for the full flow.
|
|
50
|
+
|
|
51
|
+
If postinstall was skipped (you already use a different statusline, etc.), wire it manually:
|
|
53
52
|
|
|
54
53
|
```json
|
|
55
54
|
{
|
|
@@ -61,19 +60,59 @@ Risk chips lead when something's wrong: `🚨 5H 94%`, `⚠ 1M ON`, `⚠ Cache m
|
|
|
61
60
|
}
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
`refreshInterval: 5` keeps the TTL countdown ticking while idle
|
|
63
|
+
`refreshInterval: 5` keeps the TTL countdown ticking while idle. For Windows PowerShell see `examples/statusline-command.ps1`.
|
|
64
|
+
|
|
65
|
+
## When a warning chip appears — Skill workflow
|
|
66
|
+
|
|
67
|
+
The Claude Code Skill registered at install time bridges "warning chip → remediation":
|
|
68
|
+
|
|
69
|
+
1. **A risk chip appears in the statusline** — e.g. `🚨 5H 94%`, `⚠ Cache miss`, `⚠ 1M ON`.
|
|
70
|
+
2. **Run `/claude-token-saver`** — invoking the Skill via slash is the simplest path. Mentioning the chip wording to Claude ("5H cap is up", "cache miss showing", "why is 1M context on?") auto-activates the same Skill.
|
|
71
|
+
3. **The Skill fetches the remediation.** Internally it runs `claude-token-saver last` to surface the most recent warning + root-cause code + step-by-step fix, and recommends `claude-token-saver handoff` when a cap is imminent.
|
|
72
|
+
4. **Run manually any time.** `claude-token-saver last` (latest event), `claude-token-saver history` (last 7 days of transitions), `claude-token-saver handoff` (back up before a cap blocks you) — same information, on demand.
|
|
73
|
+
|
|
74
|
+
> v2.6.0 folded the legacy `/token-monitor` slash command into this Skill. On older installs, run `claude-token-saver install` once and the legacy file is cleaned up automatically.
|
|
75
|
+
|
|
76
|
+
## One-shot report
|
|
77
|
+
|
|
78
|
+
Run `claude-token-saver` for the last-day diagnostic table:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Claude Token Saver — Last 1 day
|
|
82
|
+
(claude-token-saver v2.9.0)
|
|
83
|
+
══════════════════════════════════════════════════
|
|
84
|
+
|
|
85
|
+
Context window: 200k ✓ 200k context (standard)
|
|
86
|
+
Sessions: 11 | API calls: 578 | Cache hit rate: 98.0%
|
|
87
|
+
TTL Breakdown / Cost Impact / Daily Trend …
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If a session spiked, a `⚠ Spike detected` block leads the report with the root-cause code (table below) and an OS-aware remediation command.
|
|
91
|
+
|
|
92
|
+
## Output language
|
|
93
|
+
|
|
94
|
+
`last` / `history` / advice messages render in one language at a time (statusline chips stay symbolic). English is the default — switch via:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
claude-token-saver mode ko # or: claude-token-saver mode lang=ko
|
|
98
|
+
claude-token-saver mode en # back to English
|
|
99
|
+
claude-token-saver mode # show current settings
|
|
100
|
+
```
|
|
65
101
|
|
|
66
102
|
## Commands
|
|
67
103
|
|
|
104
|
+
All of the commands below run in your **shell (terminal)**. Inside a Claude Code session, the only entry point is the `/claude-token-saver` Skill, which calls these commands for you. The `--statusline` form is invoked automatically by Claude Code on each statusline refresh — you never type it yourself.
|
|
105
|
+
|
|
68
106
|
| Command | What it does |
|
|
69
107
|
|---|---|
|
|
70
|
-
| `claude-token-saver` | Last
|
|
71
|
-
| `claude-token-saver
|
|
72
|
-
| `claude-token-saver --statusline --icon` | One-line statusline output |
|
|
73
|
-
| `claude-token-saver install` | Manually register the Claude Code Skill (fallback when postinstall is skipped) |
|
|
108
|
+
| `claude-token-saver` | Last-1-day diagnostic report (`--days N` to change window) |
|
|
109
|
+
| `claude-token-saver last` | Most recent warning + remediation (the command the Skill invokes) |
|
|
74
110
|
| `claude-token-saver history` | Last 7 days of chip transitions (1M ON, Cache miss, cap, …) |
|
|
75
111
|
| `claude-token-saver handoff` | Back current work up to `HANDOFF-YYYY-MM-DD-HHMM.md` before a cap blocks you |
|
|
76
|
-
| `claude-token-saver
|
|
112
|
+
| `claude-token-saver mode [keywords...]` | Configure output (`icon`/`text`, `en`/`ko`, `verbose`, `1d`/`7d`, …) |
|
|
113
|
+
| `claude-token-saver --statusline --icon` | One-line statusline output (called by Claude Code) |
|
|
114
|
+
| `claude-token-saver install` | Manually register Skill + statusline (postinstall fallback) |
|
|
115
|
+
| `claude-token-saver --install-hook` | Optionally auto-log cache stats on every tool call |
|
|
77
116
|
|
|
78
117
|
## Options
|
|
79
118
|
|
|
@@ -150,6 +189,26 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
|
|
|
150
189
|
|
|
151
190
|
**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
191
|
|
|
192
|
+
## Release notes
|
|
193
|
+
|
|
194
|
+
### v2.9.1 (2026-04-27)
|
|
195
|
+
- Fix the README statusline sample so it matches actual output (includes the `✦ current` / `📅 weekly` window segments that were missing).
|
|
196
|
+
- Add a 4-step "When a warning chip appears" Skill workflow — spot the chip → mention its wording to Claude → Skill runs `last` → apply remediation.
|
|
197
|
+
- Move `language` from `cfg.statusline.language` to top-level `cfg.language` (it doesn't belong with statusline toggles). The legacy location is still read as a fallback so existing configs migrate transparently. `mode` output also splits the statusline section from the output-language section.
|
|
198
|
+
|
|
199
|
+
### v2.9.0 (2026-04-27)
|
|
200
|
+
- **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.
|
|
201
|
+
- History files stay bilingual on disk; the language toggle is applied at display time.
|
|
202
|
+
|
|
203
|
+
### v2.8.6 (2026-04-27)
|
|
204
|
+
- **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.
|
|
205
|
+
- README polish in both languages; corrected the `claude-cache-monitor` alias-removal note (timing was reversed).
|
|
206
|
+
|
|
207
|
+
### v2.8.5
|
|
208
|
+
- IntelliJ Claude Code plugin: auto-fall back to text mode when `TERMINAL_EMULATOR=JetBrains-JediTerm` to avoid frame-fusion artefacts.
|
|
209
|
+
|
|
210
|
+
Older versions: see `git log`.
|
|
211
|
+
|
|
153
212
|
## License
|
|
154
213
|
|
|
155
214
|
MIT
|
package/README.md
CHANGED
|
@@ -28,28 +28,27 @@ npm i -g claude-token-saver
|
|
|
28
28
|
claude-token-saver install
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Claude Code statusline
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
설치 후 Claude Code 하단 statusline에 캐시 상태가 5초마다 갱신됩니다 (postinstall이 `~/.claude/settings.json`에 자동 등록).
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
|
|
37
|
-
Context window: 200k ✓ 표준
|
|
38
|
-
Cache hit rate: 98.2% | Total input: 1957.94M tokens
|
|
39
|
-
TTL Breakdown / Cost Impact / Daily Trend ...
|
|
36
|
+
🤖 Opus 4.7 · 🧠 Cache hit 98.0% · ⏳ Cache expires 58:38 · ✦ current █░░░░░ 15% 🔄 08:50 · 📅 weekly █▒░░░░ 24% 🔄 Thu 13:00 · 📦 Ctx 200k · 💰 Cache saved $205 · last 1d
|
|
40
37
|
```
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
세그먼트 — `🤖 모델` · `🧠 캐시 히트율` · `⏳ TTL 카운트다운` · `✦ current` (5시간 윈도) · `📅 weekly` (7일 윈도) · `📦 컨텍스트` · `💰 누적 절감액` · `last <기간>`.
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
토큰이 과도하게 사용되는 상황이 감지되면 경고 칩이 맨 앞에 노출됩니다.
|
|
45
42
|
|
|
46
43
|
```
|
|
47
|
-
|
|
44
|
+
🚨 5H 94% (resets in 12m) · 🤖 Opus 4.7 · 🧠 Cache hit 72.1% · ⚠ Cache miss · ✦ current ██████ 94% · 📦 Ctx 200k · last 1d
|
|
48
45
|
```
|
|
49
46
|
|
|
50
|
-
|
|
47
|
+
경고 칩 종류 — `🚨 5H/7D NN%`, `⚠ 1M ON`, `⚠ Input spike`, `⚠ Cache miss`, `⚠ 5m TTL`, `⚠ Rebuild churn`, `⚠ Output heavy`, `⚠ Call surge`.
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
**해야 할 일** — Claude에서 `/claude-token-saver` Skill을 실행하면 됩니다. Skill이 `claude-token-saver last`를 호출해 원인 코드와 단계별 해결 명령을 자동으로 보여줍니다. 칩 문구(예: "5H cap 떴어", "cache miss")만 말해도 동일한 Skill이 자동 활성화됩니다. 자세한 흐름은 아래 [Skill 워크플로](#경고-칩이-떴을-때--skill-워크플로) 참고.
|
|
50
|
+
|
|
51
|
+
수동 등록이 필요한 경우(다른 statusline을 이미 쓰고 있어 postinstall이 건너뛴 경우 등):
|
|
53
52
|
|
|
54
53
|
```json
|
|
55
54
|
{
|
|
@@ -61,19 +60,59 @@ TTL Breakdown / Cost Impact / Daily Trend ...
|
|
|
61
60
|
}
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
`refreshInterval: 5`는 idle 상태에서도 TTL 카운트다운을 5초마다
|
|
63
|
+
`refreshInterval: 5`는 idle 상태에서도 TTL 카운트다운을 5초마다 갱신합니다. Windows(PowerShell)는 `examples/statusline-command.ps1` 참고.
|
|
64
|
+
|
|
65
|
+
## 경고 칩이 떴을 때 — Skill 워크플로
|
|
66
|
+
|
|
67
|
+
설치 시 함께 등록되는 Claude Code Skill이 "경고 칩 → 처방"의 다리 역할을 합니다.
|
|
68
|
+
|
|
69
|
+
1. **statusline에 경고 칩이 뜬다** — 예: `🚨 5H 94%`, `⚠ Cache miss`, `⚠ 1M ON`.
|
|
70
|
+
2. **`/claude-token-saver` Skill을 실행한다** — Claude에서 슬래시로 Skill을 직접 호출하는 게 가장 간단합니다. 또는 칩 문구를 그대로 말해도 동일한 Skill이 자동 활성화됩니다 ("5H cap 떴어", "cache miss 떴어", "1M context 왜 켜졌지?" 등 — 칩 텍스트가 트리거 단어로 등록돼 있음).
|
|
71
|
+
3. **Skill이 처방을 가져온다** — 내부적으로 `claude-token-saver last`를 실행해 가장 최근 경고 + 원인 코드 + 단계별 해결 명령을 한 번에 보여주고, 캡 임박 시에는 `claude-token-saver handoff`로 현재 작업 백업을 권합니다.
|
|
72
|
+
4. **수동 확인이 필요하면** — `claude-token-saver last` (최근 1건), `claude-token-saver history` (최근 7일 전이 로그), `claude-token-saver handoff` (cap 직전 백업)를 직접 실행해도 같은 정보를 얻을 수 있습니다.
|
|
73
|
+
|
|
74
|
+
> v2.6.0에서 레거시 `/token-monitor` 슬래시 커맨드는 이 Skill로 흡수됐습니다. 이전 버전 사용자는 `claude-token-saver install`을 한 번 더 실행하면 자동 정리됩니다.
|
|
75
|
+
|
|
76
|
+
## 단발 리포트
|
|
77
|
+
|
|
78
|
+
`claude-token-saver`를 실행하면 최근 1일 진단 표가 출력됩니다.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Claude Token Saver — Last 1 day
|
|
82
|
+
(claude-token-saver v2.9.0)
|
|
83
|
+
══════════════════════════════════════════════════
|
|
84
|
+
|
|
85
|
+
Context window: 200k ✓ 200k context (standard)
|
|
86
|
+
Sessions: 11 | API calls: 578 | Cache hit rate: 98.0%
|
|
87
|
+
TTL Breakdown / Cost Impact / Daily Trend …
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
급증 세션이 있으면 상단에 `⚠ Spike detected` 블록과 원인 코드(아래 표) · OS별 해결 명령이 함께 출력됩니다.
|
|
91
|
+
|
|
92
|
+
## 출력 언어 전환
|
|
93
|
+
|
|
94
|
+
`last` / `history` / 처방 메시지는 영어가 기본값이며 한 번에 한 언어만 출력합니다 (statusline 칩은 항상 동일한 기호 형식). 한국어로 바꾸려면:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
claude-token-saver mode ko # 또는: claude-token-saver mode lang=ko
|
|
98
|
+
claude-token-saver mode en # 영어로 복귀
|
|
99
|
+
claude-token-saver mode # 현재 설정 확인
|
|
100
|
+
```
|
|
65
101
|
|
|
66
102
|
## 주요 명령
|
|
67
103
|
|
|
104
|
+
아래 명령은 모두 **셸(터미널)에서 직접 실행**합니다. Claude Code 세션 안에서는 `/claude-token-saver` Skill 하나만 쓰며, Skill이 내부적으로 이 명령들을 호출합니다. `--statusline` 형식은 Claude Code가 statusline 갱신마다 자동으로 호출하므로 사용자가 직접 입력하지 않습니다.
|
|
105
|
+
|
|
68
106
|
| 명령 | 설명 |
|
|
69
107
|
|---|---|
|
|
70
|
-
| `claude-token-saver` | 최근
|
|
71
|
-
| `claude-token-saver
|
|
72
|
-
| `claude-token-saver --statusline --icon` | statusline용 한 줄 출력 |
|
|
73
|
-
| `claude-token-saver install` | Claude Code Skill 수동 등록 (postinstall이 막힌 환경 대비) |
|
|
108
|
+
| `claude-token-saver` | 최근 1일 진단 리포트 (`--days N`로 기간 변경) |
|
|
109
|
+
| `claude-token-saver last` | 가장 최근 경고 1건 + 처방 (Skill이 호출하는 명령) |
|
|
74
110
|
| `claude-token-saver history` | 최근 7일간 칩 전이 로그 (1M ON, Cache miss, cap 등) |
|
|
75
111
|
| `claude-token-saver handoff` | 현재 작업을 `HANDOFF-YYYY-MM-DD-HHMM.md`로 백업 (cap 임박 시) |
|
|
76
|
-
| `claude-token-saver
|
|
112
|
+
| `claude-token-saver mode [keywords...]` | 출력 모드 설정 (`icon`/`text`, `ko`/`en`, `verbose`, `1d`/`7d` 등) |
|
|
113
|
+
| `claude-token-saver --statusline --icon` | statusline용 한 줄 출력 (Claude Code가 호출) |
|
|
114
|
+
| `claude-token-saver install` | Skill·statusline 수동 등록 (postinstall이 막힌 환경) |
|
|
115
|
+
| `claude-token-saver --install-hook` | 매 도구 호출마다 캐시 통계 자동 로깅 (선택) |
|
|
77
116
|
|
|
78
117
|
전체 옵션은 `--help` 또는 [영문 README](./README.en.md#options).
|
|
79
118
|
|
|
@@ -110,6 +149,26 @@ Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
|
|
|
110
149
|
|
|
111
150
|
**IntelliJ Claude Code plugin** — statusline 위젯이 이전 프레임과 새 프레임을 글자 단위로 잘못 합쳐 `Cache expires 59:548` 같은 잔재 문자열이 보이는 버그가 있습니다 (이모지가 포함된 출력에서만 재현). v2.8.5+는 `TERMINAL_EMULATOR=JetBrains-JediTerm`을 감지하면 자동으로 text 모드로 폴백해 이모지 없이 출력합니다 (`--icon` 플래그도 IntelliJ에서는 무시됩니다). 다른 터미널(iTerm, Terminal, WSL 등)에는 영향 없습니다.
|
|
112
151
|
|
|
152
|
+
## 릴리스 노트
|
|
153
|
+
|
|
154
|
+
### v2.9.1 (2026-04-27)
|
|
155
|
+
- README의 statusline 예시를 실제 출력(`✦ current` / `📅 weekly` 윈도 세그먼트 포함)으로 정정.
|
|
156
|
+
- "경고 칩이 떴을 때" Skill 워크플로 4단계 가이드 추가 — 칩 발견 → Claude에게 칩 문구 그대로 말하기 → Skill이 `last` 실행 → 처방 적용.
|
|
157
|
+
- `language` 설정 위치를 `cfg.statusline` 하위에서 top-level `cfg.language`로 이동(statusline 토글이 아니므로). 구버전 위치도 fallback으로 계속 읽어 마이그레이션은 자동. `mode` 출력도 statusline / output language를 분리해서 표시.
|
|
158
|
+
|
|
159
|
+
### v2.9.0 (2026-04-27)
|
|
160
|
+
- **출력 언어 전환 추가** — `last` / `history` / 처방 메시지가 한 번에 한 언어만 출력합니다. 기본은 영어, `claude-token-saver mode ko`로 한국어 전환 (statusline 칩은 영향 없음).
|
|
161
|
+
- 기존 history 파일은 이중언어로 보관되며, 표시할 때 선택한 언어만 필터링됩니다.
|
|
162
|
+
|
|
163
|
+
### v2.8.6 (2026-04-27)
|
|
164
|
+
- **Skill 자동 등록** — `npm i -g claude-token-saver` 시 postinstall 훅이 Skill과 statusline을 자동으로 `~/.claude`에 등록. 수동 `claude-token-saver install`은 `--ignore-scripts` / sudo 환경용 폴백으로 유지.
|
|
165
|
+
- 한·영 README 문장 다듬기, `claude-cache-monitor` alias 제거 시점 설명 정정.
|
|
166
|
+
|
|
167
|
+
### v2.8.5
|
|
168
|
+
- IntelliJ Claude Code plugin에서 statusline 프레임 합성 버그 회피 — `TERMINAL_EMULATOR=JetBrains-JediTerm` 감지 시 자동 text 모드.
|
|
169
|
+
|
|
170
|
+
이전 버전은 `git log`를 참고하세요.
|
|
171
|
+
|
|
113
172
|
## 라이선스
|
|
114
173
|
|
|
115
174
|
MIT
|
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;
|
|
@@ -393,20 +396,23 @@ async function main() {
|
|
|
393
396
|
// claude-token-saver mode icon verbose # set icon + verbose
|
|
394
397
|
// claude-token-saver mode reset # clear back to defaults
|
|
395
398
|
if (args[0] === 'mode') {
|
|
396
|
-
const { applyMode, loadConfig, configPath, statuslineDefaults, VALID_KEYWORDS } =
|
|
399
|
+
const { applyMode, loadConfig, configPath, statuslineDefaults, userLanguage, VALID_KEYWORDS } =
|
|
397
400
|
await import('../src/config.js');
|
|
398
401
|
const words = args.slice(1);
|
|
399
402
|
if (words.length === 0) {
|
|
400
403
|
const eff = statuslineDefaults();
|
|
401
|
-
const raw = loadConfig()
|
|
402
|
-
console.log('Statusline
|
|
404
|
+
const raw = loadConfig();
|
|
405
|
+
console.log('Statusline (effective):');
|
|
403
406
|
console.log(` icon: ${eff.icon}`);
|
|
404
407
|
console.log(` verbose: ${eff.verbose}`);
|
|
405
408
|
console.log(` timer: ${eff.timer}`);
|
|
406
409
|
console.log(` color: ${eff.color}`);
|
|
407
410
|
console.log(` window: ${eff.windowLabel} (${eff.windowHours}h)`);
|
|
408
411
|
console.log('');
|
|
409
|
-
console.log(
|
|
412
|
+
console.log('Output language (advice / history / last):');
|
|
413
|
+
console.log(` language: ${userLanguage()}`);
|
|
414
|
+
console.log('');
|
|
415
|
+
console.log(`Stored config file (${configPath()}):`);
|
|
410
416
|
console.log(` ${Object.keys(raw).length === 0 ? '(none — using defaults)' : JSON.stringify(raw)}`);
|
|
411
417
|
console.log('');
|
|
412
418
|
console.log('Change with: claude-token-saver mode <keywords...>');
|
|
@@ -421,7 +427,7 @@ async function main() {
|
|
|
421
427
|
}
|
|
422
428
|
const eff = statuslineDefaults();
|
|
423
429
|
console.log(`Updated: ${applied.join(', ')}`);
|
|
424
|
-
console.log(`Now: icon=${eff.icon} verbose=${eff.verbose} timer=${eff.timer} color=${eff.color} window=${eff.windowLabel}`);
|
|
430
|
+
console.log(`Now: icon=${eff.icon} verbose=${eff.verbose} timer=${eff.timer} color=${eff.color} window=${eff.windowLabel} language=${userLanguage()}`);
|
|
425
431
|
console.log('Statusline picks up the change on the next refresh (~1s).');
|
|
426
432
|
return;
|
|
427
433
|
}
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -36,6 +36,7 @@ export function saveConfig(cfg) {
|
|
|
36
36
|
|
|
37
37
|
// Each keyword maps to a single statusline option toggle.
|
|
38
38
|
// Orthogonal — `mode icon verbose` flips both without resetting the rest.
|
|
39
|
+
// Statusline-only toggles. Stored under `cfg.statusline`.
|
|
39
40
|
const KEYWORDS = {
|
|
40
41
|
icon: { key: 'icon', value: true },
|
|
41
42
|
text: { key: 'icon', value: false },
|
|
@@ -47,6 +48,16 @@ const KEYWORDS = {
|
|
|
47
48
|
'no-color': { key: 'color', value: false },
|
|
48
49
|
};
|
|
49
50
|
|
|
51
|
+
// Output language for `last`/`history`/advice. Stored at top level
|
|
52
|
+
// (`cfg.language`) because it has nothing to do with statusline rendering —
|
|
53
|
+
// the chips themselves stay symbolic regardless.
|
|
54
|
+
const LANG_KEYWORDS = {
|
|
55
|
+
en: 'en',
|
|
56
|
+
english: 'en',
|
|
57
|
+
ko: 'ko',
|
|
58
|
+
korean: 'ko',
|
|
59
|
+
};
|
|
60
|
+
|
|
50
61
|
// Window preset accepts forms like:
|
|
51
62
|
// `1h`, `6h`, `24h` — hours
|
|
52
63
|
// `1d`, `7d`, `30d` — days (× 24h)
|
|
@@ -65,12 +76,15 @@ function parseWindow(word) {
|
|
|
65
76
|
return null;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
export const VALID_KEYWORDS = Object.keys(KEYWORDS)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
export const VALID_KEYWORDS = Object.keys(KEYWORDS)
|
|
80
|
+
.concat(Object.keys(LANG_KEYWORDS))
|
|
81
|
+
.concat([
|
|
82
|
+
'<N>h (e.g. 1h, 6h, 24h)',
|
|
83
|
+
'<N>d (e.g. 1d, 7d, 30d)',
|
|
84
|
+
'lang=en | lang=ko',
|
|
85
|
+
'reset',
|
|
86
|
+
'default',
|
|
87
|
+
]);
|
|
74
88
|
|
|
75
89
|
/**
|
|
76
90
|
* Apply user-supplied mode keywords to the persisted config.
|
|
@@ -87,9 +101,25 @@ export function applyMode(words) {
|
|
|
87
101
|
const lower = String(w).toLowerCase();
|
|
88
102
|
if (lower === 'reset' || lower === 'default') {
|
|
89
103
|
cfg.statusline = {};
|
|
104
|
+
delete cfg.language;
|
|
90
105
|
applied.push(lower);
|
|
91
106
|
continue;
|
|
92
107
|
}
|
|
108
|
+
const langMatch = lower.match(/^lang(?:uage)?=(en|english|ko|korean)$/);
|
|
109
|
+
if (langMatch) {
|
|
110
|
+
const v = langMatch[1].startsWith('k') ? 'ko' : 'en';
|
|
111
|
+
cfg.language = v;
|
|
112
|
+
// Migrate any legacy value that lived under cfg.statusline.language.
|
|
113
|
+
if (cfg.statusline) delete cfg.statusline.language;
|
|
114
|
+
applied.push(`lang=${v}`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (LANG_KEYWORDS[lower]) {
|
|
118
|
+
cfg.language = LANG_KEYWORDS[lower];
|
|
119
|
+
if (cfg.statusline) delete cfg.statusline.language;
|
|
120
|
+
applied.push(`lang=${cfg.language}`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
93
123
|
const hours = parseWindow(lower);
|
|
94
124
|
if (hours !== null && hours > 0) {
|
|
95
125
|
cfg.statusline.windowHours = hours;
|
|
@@ -141,6 +171,17 @@ export function statuslineDefaults() {
|
|
|
141
171
|
};
|
|
142
172
|
}
|
|
143
173
|
|
|
174
|
+
// Resolve the user's preferred output language for advice/history/last.
|
|
175
|
+
// Returns 'en' or 'ko'; defaults to 'en' for first-time users.
|
|
176
|
+
// Reads `cfg.language` (current location) with a fallback to the legacy
|
|
177
|
+
// `cfg.statusline.language` slot so configs from earlier 2.9.x installs
|
|
178
|
+
// keep working.
|
|
179
|
+
export function userLanguage() {
|
|
180
|
+
const cfg = loadConfig();
|
|
181
|
+
const v = cfg.language || (cfg.statusline && cfg.statusline.language);
|
|
182
|
+
return v === 'ko' ? 'ko' : 'en';
|
|
183
|
+
}
|
|
184
|
+
|
|
144
185
|
/**
|
|
145
186
|
* Render hours as the most natural unit:
|
|
146
187
|
* 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
|
*/
|