claude-token-saver 2.8.6 → 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 +59 -18
- package/README.md +59 -18
- package/bin/cli.js +26 -22
- package/package.json +1 -1
- package/src/config.js +22 -0
- package/src/history.js +55 -2
package/README.en.md
CHANGED
|
@@ -28,28 +28,23 @@ 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 59:51 · 📦 Ctx 200k · 💰 Cache saved $221 · last 1d
|
|
40
37
|
```
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
**(B) Statusline chip** — once wired into `~/.claude/settings.json`:
|
|
39
|
+
Risk chips lead when something's wrong:
|
|
45
40
|
|
|
46
41
|
```
|
|
47
|
-
|
|
42
|
+
🚨 5H 94% (resets in 12m) · 🤖 Opus 4.7 · 🧠 Cache hit 72.1% · ⚠ Cache miss · 📦 Ctx 200k · last 1d
|
|
48
43
|
```
|
|
49
44
|
|
|
50
|
-
Risk chips
|
|
45
|
+
Risk chips: `🚨 5H/7D NN%`, `⚠ 1M ON`, `⚠ Input spike`, `⚠ Cache miss`, `⚠ 5m TTL`, `⚠ Rebuild churn`, `⚠ Output heavy`, `⚠ Call surge`.
|
|
51
46
|
|
|
52
|
-
|
|
47
|
+
If postinstall was skipped (you already use a different statusline, etc.), wire it manually:
|
|
53
48
|
|
|
54
49
|
```json
|
|
55
50
|
{
|
|
@@ -61,19 +56,50 @@ Risk chips lead when something's wrong: `🚨 5H 94%`, `⚠ 1M ON`, `⚠ Cache m
|
|
|
61
56
|
}
|
|
62
57
|
```
|
|
63
58
|
|
|
64
|
-
`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
|
+
```
|
|
65
90
|
|
|
66
91
|
## Commands
|
|
67
92
|
|
|
68
93
|
| Command | What it does |
|
|
69
94
|
|---|---|
|
|
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) |
|
|
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) |
|
|
74
97
|
| `claude-token-saver history` | Last 7 days of chip transitions (1M ON, Cache miss, cap, …) |
|
|
75
98
|
| `claude-token-saver handoff` | Back current work up to `HANDOFF-YYYY-MM-DD-HHMM.md` before a cap blocks you |
|
|
76
|
-
| `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 |
|
|
77
103
|
|
|
78
104
|
## Options
|
|
79
105
|
|
|
@@ -150,6 +176,21 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
|
|
|
150
176
|
|
|
151
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.
|
|
152
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
|
+
|
|
153
194
|
## License
|
|
154
195
|
|
|
155
196
|
MIT
|
package/README.md
CHANGED
|
@@ -28,28 +28,23 @@ 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 59:51 · 📦 Ctx 200k · 💰 Cache saved $221 · last 1d
|
|
40
37
|
```
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
**(B) statusline 한 줄** — `~/.claude/settings.json`에 등록 시:
|
|
39
|
+
위험 상황에서는 경고 칩이 맨 앞으로 나옵니다.
|
|
45
40
|
|
|
46
41
|
```
|
|
47
|
-
|
|
42
|
+
🚨 5H 94% (resets in 12m) · 🤖 Opus 4.7 · 🧠 Cache hit 72.1% · ⚠ Cache miss · 📦 Ctx 200k · last 1d
|
|
48
43
|
```
|
|
49
44
|
|
|
50
|
-
|
|
45
|
+
표시되는 경고 칩 — `🚨 5H/7D NN%`, `⚠ 1M ON`, `⚠ Input spike`, `⚠ Cache miss`, `⚠ 5m TTL`, `⚠ Rebuild churn`, `⚠ Output heavy`, `⚠ Call surge`.
|
|
51
46
|
|
|
52
|
-
|
|
47
|
+
수동 등록이 필요한 경우(다른 statusline을 이미 쓰고 있어 postinstall이 건너뛴 경우 등):
|
|
53
48
|
|
|
54
49
|
```json
|
|
55
50
|
{
|
|
@@ -61,19 +56,50 @@ TTL Breakdown / Cost Impact / Daily Trend ...
|
|
|
61
56
|
}
|
|
62
57
|
```
|
|
63
58
|
|
|
64
|
-
`refreshInterval: 5`는 idle 상태에서도 TTL 카운트다운을 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
|
+
```
|
|
65
90
|
|
|
66
91
|
## 주요 명령
|
|
67
92
|
|
|
68
93
|
| 명령 | 설명 |
|
|
69
94
|
|---|---|
|
|
70
|
-
| `claude-token-saver` | 최근
|
|
71
|
-
| `claude-token-saver
|
|
72
|
-
| `claude-token-saver --statusline --icon` | statusline용 한 줄 출력 |
|
|
73
|
-
| `claude-token-saver install` | Claude Code Skill 수동 등록 (postinstall이 막힌 환경 대비) |
|
|
95
|
+
| `claude-token-saver` | 최근 1일 진단 리포트 (`--days N`로 기간 변경) |
|
|
96
|
+
| `claude-token-saver last` | 가장 최근 경고 1건 + 처방 (Skill이 호출하는 명령) |
|
|
74
97
|
| `claude-token-saver history` | 최근 7일간 칩 전이 로그 (1M ON, Cache miss, cap 등) |
|
|
75
98
|
| `claude-token-saver handoff` | 현재 작업을 `HANDOFF-YYYY-MM-DD-HHMM.md`로 백업 (cap 임박 시) |
|
|
76
|
-
| `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` | 매 도구 호출마다 캐시 통계 자동 로깅 (선택) |
|
|
77
103
|
|
|
78
104
|
전체 옵션은 `--help` 또는 [영문 README](./README.en.md#options).
|
|
79
105
|
|
|
@@ -110,6 +136,21 @@ Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
|
|
|
110
136
|
|
|
111
137
|
**IntelliJ Claude Code plugin** — statusline 위젯이 이전 프레임과 새 프레임을 글자 단위로 잘못 합쳐 `Cache expires 59:548` 같은 잔재 문자열이 보이는 버그가 있습니다 (이모지가 포함된 출력에서만 재현). v2.8.5+는 `TERMINAL_EMULATOR=JetBrains-JediTerm`을 감지하면 자동으로 text 모드로 폴백해 이모지 없이 출력합니다 (`--icon` 플래그도 IntelliJ에서는 무시됩니다). 다른 터미널(iTerm, Terminal, WSL 등)에는 영향 없습니다.
|
|
112
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`를 참고하세요.
|
|
153
|
+
|
|
113
154
|
## 라이선스
|
|
114
155
|
|
|
115
156
|
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;
|
|
@@ -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
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
|
*/
|