claude-token-saver 3.18.0 → 3.20.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 +63 -21
- package/README.md +63 -21
- package/bin/cli.js +7 -0
- package/package.json +1 -1
- package/presets/korean-style/LICENSE-fluent-korean +21 -0
- package/presets/korean-style/fluent-korean.md +52 -0
- package/src/commands/install.js +39 -0
- package/src/commands/korean.js +76 -0
- package/src/commands/route-scan.js +18 -1
- package/src/formatters/statusline.js +24 -1
- package/src/korean-style.js +131 -0
package/README.en.md
CHANGED
|
@@ -4,52 +4,64 @@
|
|
|
4
4
|
|
|
5
5
|
# claude-token-saver
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Moves the easy work your expensive model keeps repeating onto cheaper ones, and shows what that actually saved — measured, in your statusline.** Zero dependencies, one-line install.
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g claude-token-saver
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Three parts, working together
|
|
16
|
+
|
|
17
|
+
| | What it does | Effect |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 🔀 **Routing** | Delegates recurring easy work to cheaper models | Savings recorded per run in a ledger |
|
|
20
|
+
| 🅷 **Harness** | Blocks the token-burning habits: unevidenced "done", skipped verification (5 principles) | **−18.6% cost** ([measured](#real-world-impact--beforeafter-report)) |
|
|
21
|
+
| ⚙️ **Ratchet** | Freezes each error you hit into a rule | Same mistake stops recurring |
|
|
12
22
|
|
|
13
|
-
|
|
23
|
+
One install sets up all three. The measured −18.6% comes from the harness and ratchet; routing savings sit on top of it.
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
- **after** — the model that actually ran it
|
|
17
|
-
- **the gap** — the same token counts priced against both
|
|
25
|
+
## 🔀 The savings figure is a ledger entry, not an estimate
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
Every delegated run is recorded like this:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
before after gap
|
|
31
|
+
claude-opus-5 → haiku-4-5 = $0.57
|
|
32
|
+
(the model (what (same token counts,
|
|
33
|
+
handling this actually priced against
|
|
34
|
+
before the rule) ran it) both models)
|
|
35
|
+
```
|
|
20
36
|
|
|
21
37
|
```bash
|
|
22
|
-
$ claude-token-saver route-scan savings
|
|
38
|
+
$ claude-token-saver route-scan savings # trace every dollar back to its rule
|
|
23
39
|
|
|
24
40
|
🔀 Routing saved, lifetime $2.09 (last 7d $1.40 · 30d $2.09)
|
|
25
41
|
|
|
26
42
|
By model change:
|
|
27
|
-
claude-fable-5 → claude-sonnet-5
|
|
28
|
-
claude-opus-5 → claude-haiku-4-5
|
|
43
|
+
claude-fable-5 → claude-sonnet-5 — 1 run, $0.72
|
|
44
|
+
claude-opus-5 → claude-haiku-4-5 — 1 run, $0.57
|
|
29
45
|
|
|
30
46
|
By run (newest first):
|
|
31
47
|
2026-08-22 $0.51 claude-fable-5 → claude-haiku-4-5
|
|
32
48
|
rule: T2|paste|-Users-me-projects-my-app
|
|
33
49
|
```
|
|
34
50
|
|
|
35
|
-
**
|
|
51
|
+
**What is excluded** — an honest number beats a big one:
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
53
|
+
- Delegations no registered rule covers (`Explore`, your own agents, plugin subagents): this tool did not route them.
|
|
54
|
+
- Model ids the pricing table cannot recognize: the run is dropped rather than priced wrong.
|
|
40
55
|
|
|
41
56
|
---
|
|
42
57
|
|
|
43
|
-
## ⚡
|
|
58
|
+
## ⚡ What else the statusline catches
|
|
44
59
|
|
|
45
60
|
| | |
|
|
46
61
|
|---|---|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
| 🚨 **No surprise rate limits** | Instant warning when the 5H/7D window hits 90% + `handoff` to back up your work |
|
|
51
|
-
| 🧠 **Cache waste detection** | Hit rate, TTL countdown, 1M-context detection — token spikes diagnosed with issue codes |
|
|
52
|
-
| 🅷 **Stop repeating mistakes** | Recurring errors get promoted to ratchet rules — auto-applied from the next session |
|
|
62
|
+
| 🚨 **No surprise rate limits** | Warns when the 5H/7D window hits 90%; `handoff` backs up your work |
|
|
63
|
+
| 🧠 **Cache waste detection** | Hit rate, TTL, 1M-context detection — spikes diagnosed with issue codes |
|
|
64
|
+
| 🇰🇷 **Korean writing guidance** | Enabled automatically on a Korean locale ([below](#-korean-writing-guidance)) |
|
|
53
65
|
|
|
54
66
|
## Not a router — 60 seconds
|
|
55
67
|
|
|
@@ -144,6 +156,7 @@ Run these in your shell (inside Claude Code, the `/claude-token-saver` Skill is
|
|
|
144
156
|
| `claude-token-saver route-scan` | Detect recurring easy work on expensive models → propose haiku-delegation ratchet rules (below) |
|
|
145
157
|
| `claude-token-saver route-scan savings` | The routing-savings ledger — per-model-change rollup + per-run log (the evidence behind the figure) |
|
|
146
158
|
| `claude-token-saver compact-window` | Warn when a 1M-context session has no auto-compact cap → pin 400k with `set` (below) |
|
|
159
|
+
| `claude-token-saver korean on\|off\|status` | Inject Korean writing guidance at session start (below) |
|
|
147
160
|
| `claude-token-saver install` | Manually register Skill + statusline |
|
|
148
161
|
|
|
149
162
|
Switch output language with `mode ko` / `mode en` (English default; statusline chips stay symbolic).
|
|
@@ -265,6 +278,27 @@ For environments the learner cannot reach, write the mapping yourself in `<userD
|
|
|
265
278
|
|
|
266
279
|
That file holds internal identifiers in plain text — do not commit it. On a direct-API machine it is never created and behaviour is unchanged.
|
|
267
280
|
|
|
281
|
+
## 🇰🇷 Korean writing guidance
|
|
282
|
+
|
|
283
|
+
Injects guidance that corrects how Claude writes Korean (dropped sentence parts, noun-stopped sentences, translationese, em-dash overuse) **once per session.**
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
claude-token-saver korean on # on, for every project
|
|
287
|
+
claude-token-saver korean status # state, cost, provenance
|
|
288
|
+
claude-token-saver korean show # print the guidance itself
|
|
289
|
+
claude-token-saver korean off # off
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Claude Code's output styles can do the same thing, but an output style is **a single global slot**: turning it on takes that slot away from anything else and has to be configured per machine. This ships the guidance inside the package and delivers it through the SessionStart hook that is already installed, so it **applies wherever the CLI is installed and leaves the output-style slot free.** It survives `/clear`, because the hook fires again.
|
|
293
|
+
|
|
294
|
+
Cost is **~1.5k tokens per session, injected once at session start rather than per turn**, and covered by the prompt cache from the second request on. When it is on, a `가` chip appears in the statusline.
|
|
295
|
+
|
|
296
|
+
**The default is decided at install time.** A Korean system locale (`ko_KR` and friends; on macOS the system setting is checked too) turns it on; anything else leaves it off, so users who never write Korean are not billed 1.5k tokens a session. **Once you have turned it on or off yourself, that choice sticks — an upgrade never overrides it.** Install with `CTS_NO_KOREAN=1` to skip the automatic decision.
|
|
297
|
+
|
|
298
|
+
> **Source and license**
|
|
299
|
+
> The guidance text comes from [fluent-korean](https://github.com/snflkd/fluent-korean). Copyright (c) 2026 snflkd, MIT License.
|
|
300
|
+
> The wording is unmodified; only the output-style frontmatter was removed. The full license ships with the package at `presets/korean-style/LICENSE-fluent-korean`.
|
|
301
|
+
|
|
268
302
|
## Spike issue codes
|
|
269
303
|
|
|
270
304
|
| Code | Meaning |
|
|
@@ -345,6 +379,14 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
|
|
|
345
379
|
|
|
346
380
|
## Release notes
|
|
347
381
|
|
|
382
|
+
### v3.20.0 (2026-08-22)
|
|
383
|
+
- **The README opening is now scannable** — prose replaced by a one-line summary, the screenshot, and the install command up top. The three parts (routing, harness, ratchet) are a table; how a saving is computed (before → after → gap) is a diagram. The old 30-second pitch table, which repeated all of it, now lists only what the statusline additionally catches.
|
|
384
|
+
|
|
385
|
+
### v3.19.0 (2026-08-22)
|
|
386
|
+
- **Korean writing guidance** — corrects how Claude writes Korean (dropped sentence parts, noun-stopped sentences, translationese, em-dash overuse), injected once per session. Claude Code's output styles occupy a single global slot and must be configured per machine; this ships the guidance in the package and delivers it through the SessionStart hook already installed, so it **applies in every project and leaves the output-style slot free.** Text vendored from [fluent-korean](https://github.com/snflkd/fluent-korean) (Copyright (c) 2026 snflkd, MIT), license included.
|
|
387
|
+
- **Decided at install time** — a Korean system locale turns it on; anything else leaves it off. Your own on/off choice is preserved, so upgrades never override it. Skip with `CTS_NO_KOREAN=1`; a `가` chip shows in the statusline when active.
|
|
388
|
+
- **Corrected an overstated README claim** — routing savings were described as "the whole product", but the measured −18.6% comes from the harness and ratchet. The relationship between the three is now stated accurately.
|
|
389
|
+
|
|
348
390
|
### v3.18.0 (2026-08-22)
|
|
349
391
|
- **Korean documentation rewritten for clarity** — full sentences with explicit predicates, and em dashes replaced by colons and conjunctions where they were compressing too much meaning.
|
|
350
392
|
- **Added why realtime model routing can cost more** — prompt caches are per-model, so a mid-session switch cancels the savings via cache loss; this is why the tool delegates to subagents only.
|
package/README.md
CHANGED
|
@@ -4,52 +4,64 @@
|
|
|
4
4
|
|
|
5
5
|
# claude-token-saver
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**비싼 모델이 반복하던 쉬운 작업을 싼 모델로 내려보내고, 그렇게 아낀 금액을 statusline에 실측으로 보여 줍니다.** 의존성 0, 설치 한 줄.
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g claude-token-saver
|
|
13
|
+
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## 세 가지가 함께 돌아갑니다
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
| | 하는 일 | 효과 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 🔀 **라우팅** | 반복되는 쉬운 작업을 더 싼 모델에 위임 | 절감액을 원장에 실측 기록 |
|
|
20
|
+
| 🅷 **Harness** | 증거 없는 완료 보고·검증 생략 차단 (5원칙) | **비용 −18.6%** ([실측](#실제-효과-도입-전후-리포트)) |
|
|
21
|
+
| ⚙️ **Ratchet** | 한 번 겪은 에러를 룰로 고정 | 같은 실수 재발 차단 |
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
설치 한 번이면 셋 다 적용됩니다. 실측 −18.6%는 Harness와 ratchet의 몫이고, 라우팅 절감액은 그 위에 얹힙니다.
|
|
24
|
+
|
|
25
|
+
## 🔀 절감액은 추정이 아니라 원장 기록입니다
|
|
26
|
+
|
|
27
|
+
위임된 실행 하나하나를 이렇게 기록합니다.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
기준 모델 실행 모델 차액
|
|
31
|
+
claude-opus-5 → haiku-4-5 = $0.57
|
|
32
|
+
(룰 승격 전 (실제로 (같은 토큰량에
|
|
33
|
+
이 유형을 처리한 두 모델 가격표를
|
|
34
|
+
처리하던 모델) 모델) 각각 적용)
|
|
35
|
+
```
|
|
20
36
|
|
|
21
37
|
```bash
|
|
22
|
-
$ claude-token-saver route-scan savings
|
|
38
|
+
$ claude-token-saver route-scan savings # 모든 금액을 룰 단위까지 역추적
|
|
23
39
|
|
|
24
40
|
🔀 라우팅 절감 누적 $2.09 (최근 7일 $1.40 · 30일 $2.09)
|
|
25
41
|
|
|
26
42
|
모델 이동별:
|
|
27
|
-
claude-fable-5 → claude-sonnet-5
|
|
28
|
-
claude-opus-5 → claude-haiku-4-5
|
|
43
|
+
claude-fable-5 → claude-sonnet-5 — 1회, $0.72
|
|
44
|
+
claude-opus-5 → claude-haiku-4-5 — 1회, $0.57
|
|
29
45
|
|
|
30
46
|
실행별 (최근순):
|
|
31
47
|
2026-08-22 $0.51 claude-fable-5 → claude-haiku-4-5
|
|
32
48
|
룰: T2|paste|-Users-me-projects-my-app
|
|
33
49
|
```
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
**집계에서 빼는 것들** — 정직한 숫자가 작은 숫자보다 낫기 때문입니다.
|
|
36
52
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
53
|
+
- 등록된 룰이 담당하지 않는 위임(`Explore`, 직접 만든 에이전트, 플러그인 에이전트): 이 도구가 라우팅한 결과가 아닙니다.
|
|
54
|
+
- 가격표가 인식하지 못하는 모델명: 틀린 금액을 쓰느니 그 실행을 뺍니다.
|
|
40
55
|
|
|
41
56
|
---
|
|
42
57
|
|
|
43
|
-
## ⚡
|
|
58
|
+
## ⚡ 그 밖에 statusline이 잡아 주는 것
|
|
44
59
|
|
|
45
60
|
| | |
|
|
46
61
|
|---|---|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
| 🚨 **한도 초과 예방** | 5시간·7일 rate-limit 윈도가 90%에 도달하면 즉시 경고하고, `handoff`로 진행 중인 작업을 백업합니다 |
|
|
51
|
-
| 🧠 **캐시 낭비 감지** | 히트율과 TTL 카운트다운, 1M 컨텍스트 사용 여부를 자동으로 감지해 토큰이 급증한 원인을 코드로 진단합니다 |
|
|
52
|
-
| 🅷 **같은 실수 차단** | 반복되는 에러를 감지해 ratchet 룰로 승격하고, 다음 세션부터 자동으로 적용합니다 |
|
|
62
|
+
| 🚨 **한도 초과 예방** | 5시간·7일 rate-limit 윈도가 90%에 닿으면 경고하고, `handoff`로 작업을 백업합니다 |
|
|
63
|
+
| 🧠 **캐시 낭비 감지** | 히트율·TTL·1M 컨텍스트를 감지해 토큰 급증 원인을 코드로 진단합니다 |
|
|
64
|
+
| 🇰🇷 **한국어 문체 교정** | 한국어 환경이면 자동으로 켜집니다 ([아래](#-한국어-문체-지침)) |
|
|
53
65
|
|
|
54
66
|
## 라우터가 아닙니다: 60초 설명
|
|
55
67
|
|
|
@@ -143,6 +155,7 @@ Claude Code 안에서 `/claude-token-saver` Skill을 실행하거나, 칩에 적
|
|
|
143
155
|
| `claude-token-saver route-scan` | 상위 모델이 반복 처리한 쉬운 작업을 감지해 haiku 위임 랫쳇 룰을 제안합니다 (아래 참고) |
|
|
144
156
|
| `claude-token-saver route-scan savings` | 라우팅 절감 원장입니다. 모델 이동별 합계와 실행별 내역을 함께 보여 주며, 표시되는 금액의 근거가 됩니다 |
|
|
145
157
|
| `claude-token-saver compact-window` | 1M 컨텍스트를 쓰면서 자동 압축 창이 설정되지 않았으면 경고하고, `set`으로 40만에 고정합니다 (아래 참고) |
|
|
158
|
+
| `claude-token-saver korean on\|off\|status` | 한국어 문체 지침을 세션 시작 시 주입합니다 (아래 참고) |
|
|
146
159
|
| `claude-token-saver install` | Skill·statusline 수동 등록 |
|
|
147
160
|
|
|
148
161
|
출력 언어는 `mode ko`와 `mode en`으로 전환합니다. 기본값은 영어이며, statusline의 칩은 언제나 기호로 표시합니다. 전체 옵션은 [영문 README](./README.en.md#options)를 참고하십시오.
|
|
@@ -255,6 +268,27 @@ v3.10.0부터는 프로파일 ID를 역할(main·opus·sonnet·haiku)로 되돌
|
|
|
255
268
|
|
|
256
269
|
이 파일에는 사내 식별자가 평문으로 남으므로 저장소에 커밋하지 마십시오. 게이트웨이를 쓰지 않는 환경에서는 파일이 아예 만들어지지 않고 기존 동작이 그대로 유지됩니다.
|
|
257
270
|
|
|
271
|
+
## 🇰🇷 한국어 문체 지침
|
|
272
|
+
|
|
273
|
+
Claude가 한국어로 쓸 때 나타나는 문체 결함(문장 성분 생략, 명사형 종결, 번역체, 엠대시 남용)을 교정하는 지침을 **세션 시작 시 한 번 주입합니다.**
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
claude-token-saver korean on # 켜기 (모든 프로젝트에 적용)
|
|
277
|
+
claude-token-saver korean status # 상태·비용·출처 확인
|
|
278
|
+
claude-token-saver korean show # 지침 원문 출력
|
|
279
|
+
claude-token-saver korean off # 끄기
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Claude Code의 output style로도 같은 일을 할 수 있지만, output style은 **전역 슬롯 하나**라서 켜는 순간 다른 스타일을 못 쓰게 되고 머신마다 따로 설정해야 합니다. 이 기능은 지침을 패키지에 담고 이미 설치된 SessionStart 훅으로 전달하므로, **CLI가 설치된 모든 프로젝트에 적용되며 output style 슬롯은 비워 둡니다.** `/clear` 이후에도 훅이 다시 실행되어 유지됩니다.
|
|
283
|
+
|
|
284
|
+
비용은 **세션당 약 1,500 토큰이며 매 턴이 아니라 세션 시작에 한 번만** 주입됩니다. 두 번째 요청부터는 프롬프트 캐시에 올라가므로 추가 부담이 거의 없습니다. 켜져 있으면 statusline에 `가` 칩이 표시됩니다.
|
|
285
|
+
|
|
286
|
+
**설치할 때 자동으로 결정됩니다.** 시스템 로캘이 한국어이면(`ko_KR` 등, macOS는 시스템 설정까지 확인) 설치와 동시에 켜지고, 한국어 환경이 아니면 꺼진 채로 둡니다. 한국어를 쓰지 않는 사용자에게 세션마다 1,500 토큰을 청구하지 않기 위한 판단입니다. **한 번이라도 직접 켜거나 끈 뒤에는 그 선택을 유지하므로, 업데이트 설치가 사용자의 결정을 되돌리지 않습니다.** 자동 설정을 원하지 않으면 `CTS_NO_KOREAN=1`을 붙여 설치하십시오.
|
|
287
|
+
|
|
288
|
+
> **출처와 라이선스**
|
|
289
|
+
> 지침 원문은 [fluent-korean](https://github.com/snflkd/fluent-korean)에서 가져왔습니다. Copyright (c) 2026 snflkd, MIT License.
|
|
290
|
+
> 원문은 수정하지 않았고 output style 프런트매터만 제거했습니다. 라이선스 전문은 패키지의 `presets/korean-style/LICENSE-fluent-korean`에 함께 배포합니다.
|
|
291
|
+
|
|
258
292
|
## 토큰 급증 원인 코드
|
|
259
293
|
|
|
260
294
|
| 코드 | 의미 |
|
|
@@ -311,6 +345,14 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
|
|
|
311
345
|
|
|
312
346
|
## 릴리스 노트
|
|
313
347
|
|
|
348
|
+
### v3.20.0 (2026-08-22)
|
|
349
|
+
- **README 첫 화면을 훑어 읽을 수 있게 다시 짰습니다** — 설명 문단을 걷어내고 한 줄 요약, 스크린샷, 설치 명령을 앞에 두었습니다. 라우팅·Harness·ratchet 세 기능은 표로, 절감액 계산 방식(기준 모델 → 실행 모델 → 차액)은 도식으로 바꿨습니다. 뒤에서 같은 내용을 반복하던 "30초 요약" 표는 statusline이 추가로 잡아 주는 항목만 남겨 정리했습니다.
|
|
350
|
+
|
|
351
|
+
### v3.19.0 (2026-08-22)
|
|
352
|
+
- **한국어 문체 지침 기능을 추가했습니다** — Claude가 한국어를 쓸 때 나타나는 문체 결함(문장 성분 생략, 명사형 종결, 번역체, 엠대시 남용)을 교정하는 지침을 세션 시작 시 한 번 주입합니다. Claude Code의 output style은 전역 슬롯 하나를 차지하고 머신마다 설정해야 하지만, 이 기능은 지침을 패키지에 담고 이미 설치된 SessionStart 훅으로 전달하므로 **CLI가 설치된 모든 프로젝트에 적용되며 output style 슬롯은 비워 둡니다.** 지침 원문은 [fluent-korean](https://github.com/snflkd/fluent-korean)(Copyright (c) 2026 snflkd, MIT)에서 가져왔고 라이선스 전문을 함께 배포합니다.
|
|
353
|
+
- **설치와 동시에 결정됩니다** — 시스템 로캘이 한국어이면 설치 시 자동으로 켜지고, 아니면 꺼 둡니다. 직접 켜거나 끈 뒤에는 그 선택을 유지하므로 업데이트가 사용자의 결정을 되돌리지 않습니다. `CTS_NO_KOREAN=1`로 건너뛸 수 있고, 켜져 있으면 statusline에 `가` 칩이 표시됩니다.
|
|
354
|
+
- **README의 과장된 설명을 바로잡았습니다** — 라우팅 절감액을 "이 도구의 전부"라고 적었으나, 실측 −18.6%는 Harness와 ratchet을 도입한 효과입니다. 세 기능의 관계를 정확히 다시 썼습니다.
|
|
355
|
+
|
|
314
356
|
### v3.18.0 (2026-08-22)
|
|
315
357
|
- **한국어 문서를 다시 다듬었습니다** — 문장 성분을 생략하지 않고 서술어로 끝맺는 형태로 본문을 고쳐 썼습니다. 의미를 지나치게 함축하던 엠대시는 콜론과 접속사로 바꾸었습니다.
|
|
316
358
|
- **실시간 모델 라우팅이 비용을 키우는 이유를 설명에 추가했습니다** — 프롬프트 캐시가 모델별로 유지되기 때문에 세션 중간에 모델을 바꾸면 절감액이 캐시 손실로 상쇄된다는 점, 그래서 이 도구가 서브에이전트 위임만 사용한다는 점을 명시했습니다.
|
package/bin/cli.js
CHANGED
|
@@ -135,6 +135,13 @@ async function main() {
|
|
|
135
135
|
return (await import('../src/commands/route-scan.js')).run({ args, hasFlag, numArg });
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// Subcommand: korean — Korean writing guidance injected at session start,
|
|
139
|
+
// so the rules apply in every project without an output-style switch.
|
|
140
|
+
// claude-token-saver korean on | off | status | show
|
|
141
|
+
if (args[0] === 'korean') {
|
|
142
|
+
return (await import('../src/commands/korean.js')).run({ args, hasFlag });
|
|
143
|
+
}
|
|
144
|
+
|
|
138
145
|
// Subcommand: harness — manage the project's CLAUDE.md harness rules.
|
|
139
146
|
// claude-token-saver harness init # write CLAUDE.md (5 sections) + ratchet.md
|
|
140
147
|
// claude-token-saver harness uninit # remove harness block from CLAUDE.md (backup kept)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"description": "Route the easy work your expensive Claude model keeps repeating down to haiku/sonnet — post-hoc session analysis, no realtime router, no extra LLM calls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 snflkd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR IN CONNECTION WITH
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Vendored from fluent-korean (https://github.com/snflkd/fluent-korean)
|
|
3
|
+
Copyright (c) 2026 snflkd — MIT License. Full text: ./LICENSE-fluent-korean
|
|
4
|
+
|
|
5
|
+
Only the output-style frontmatter was removed; the guidance below is
|
|
6
|
+
unmodified. claude-token-saver injects it at session start so the rules
|
|
7
|
+
apply in every project without installing the plugin or switching the
|
|
8
|
+
Claude Code output style.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
당신은 한국어를 활용해야 하는 상황이라면 본 문서에 제시된 지침들을 준수해야 합니다. 그럼으로써 의사 소통의 효율성을 높일 수 있습니다. 이 지침들은, 의미가 명확하며 비교적 가독성이 높고 안정적인 구조를 지닌 한국어 문장을 출력하는 방법을 자세히 설명합니다. 인용, 코드, 코드 주석에는 이 지침들을 적용하지 않습니다.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## 상황과 목표
|
|
15
|
+
|
|
16
|
+
- LLM은 한국어를 구사할 때 몇 가지 특징을 보이는데, 일부 특징은 결과물의 완성도를 낮추거나, 사용자가 소통에 더 많은 노력을 들이게 만듭니다. 이 문서에 작성된 사항들을 준수하면 이런 현상을 개선할 수 있습니다.
|
|
17
|
+
|
|
18
|
+
- 이 문서에서 제시하는 지침들을 요약하는 것은 일반적으로 권장되지 않습니다. 그렇게 한다면 조항마다 첨부된 예시를 확인할 수 없으므로 조항의 문구가 구체적으로 어떤 동작을 의도했는지 파악하기 어렵습니다. 또한 요약에 포함된 몇 가지 지침을 제외한 나머지 지침들은 잘 준수되지 않는 방향으로 서술 압력이 작동하게 될 수도 있습니다. 그리고 목적과 의도를 생략하고 제한 사항만 요약한다면 목적에 부합하지 않게 기계적으로 지침을 준수했는지 확인하게 될 수도 있습니다.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## 동작 범위
|
|
22
|
+
|
|
23
|
+
1. 본문의 지침들은 한국어를 활용하는 상황에서 그 한국어를 명확하게 출력하라는 지시입니다. 외국어 문장이나 어휘를 출력해야 하는 상황에서, 그것을 한국어로 번역하거나 대체하라는 지시가 아닙니다.
|
|
24
|
+
|
|
25
|
+
2. 변수명과 주석, 커밋 메시지, 로그 문자열처럼 코드에 속하는 텍스트는 프로젝트의 기존 관례를 준수해야 합니다. 이러한 텍스트는 지침을 적용하면 안 되기 때문에 이 조항에서 한 번 더 강조하고 있습니다.
|
|
26
|
+
|
|
27
|
+
3. 고유 명사와 기술 용어 등은, 통상적인 용례로 정착된 번역어 혹은 음차가 있다면 우선적으로 사용하고, 그렇지 않다면 원어를 유지함으로써, 한국어 사용자가 이해하기 편하고 의미를 잘 이해할 수 있도록 합니다.
|
|
28
|
+
|
|
29
|
+
4. 사용자가 어떤 어조나 어휘를 사용하든지, 사용자 메시지의 어조를 모방하지 않고, 본문에서 제시하는 지침들을 일관되게 유지합니다.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## 문장 단위
|
|
33
|
+
|
|
34
|
+
1. 읽는 이가 문장의 의미를 충분히 이해할 수 있어야 하므로, 의미가 있는 문장 성분을 생략하지 않습니다. [그러면 경고가 붙습니다.→ ('그러면 이미 작업중인 파일에도 경고 표지가 추가됩니다.'와 같이, 맥락과 정보를 충분히 제공하도록 수정) ] 특히 보조사 '의'를 필요 이상으로 사용한다면, 의미를 담고 있는 문장 성분을 생략하기 쉬우므로 유의해야 합니다. [사본의 문구는 작업의 상황을 → 사본에 기재된 문구는 작업이 진행되는 상황을]
|
|
35
|
+
|
|
36
|
+
2. (이 2번 조항은 헤더와 목록에는 강제로 적용되는 사항이 아닙니다.) 명사구나 부사구, 연결어미로 문장을 끝내지 말고, 서술어와 종결어미를 사용하여 완성된 형태의 문장으로 끝을 맺어야 합니다.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## 구 단위
|
|
40
|
+
|
|
41
|
+
1. 필수적인 경우가 아니라면 조사와 어미를 생략하지 말아야 합니다. 또한 부사, 보조사와 선어말어미, 보조 용언을 적극적으로 활용하면, 의미가 명확한 한국어 문장을 완성할 수 있습니다. [이 결정은 이후 중요 정책이 갈리는 자리. 컨텍스트 압축 전 신중 반영한다. → 이 결정은 이후 중요한 정책에 지속적으로 영향을 주기 때문에, 컨텍스트가 압축되기 전에 신중히 반영합니다. → 지금 답변해주신 결정 사항은 이후 중요한 정책에도 지속적으로 영향을 미치기 때문에, 컨텍스트가 압축되기 전에 미리 신중하게 반영해 놓겠습니다.]
|
|
42
|
+
|
|
43
|
+
2. 구체적인 의미를 담고 있는 한자어와 자연스러운 통사 구조를 결합하면, 풍부하고 명확한 의미를 전달할 수 있습니다. 따라서 맥락에 적합한 한자어를 적극적으로 활용하고, 그 한자어에 조사와 어미를 붙여서 어휘 사이의 관계를 확실하게 나타내야 합니다. [<쓴 비용을 구하는 토큰 카운트 함수에 문제가 생기면 (상황에 적합한 어휘가 사용되지 않아 의미가 불충분함) /지출 비용 추론 용도의 토큰 카운트 함수의 오류 상황에서 (조사와 어미가 없어 가독성이 낮고 의미 관계가 불분명함)> → 지출한 비용을 추론하는 토큰 카운트 함수에 오류가 발생하면 (이 지침의 목표 예시)]
|
|
44
|
+
|
|
45
|
+
3. 일반적인 어휘를 사용해야 하는 자리에 비유적 어휘를 사용하면 가독성이 낮고, 의미가 변질되기 쉽습니다. 따라서 꼭 필요한 경우가 아니라면 비유적 어휘로 일반적인 명사나 동사를 대체하지 않습니다. 다만 일상적인 문어에서 통용되고 지금 다루는 분야에서도 관용 표현으로 정착되어 있어서, 일반적인 어휘로 바꾸면 오히려 어색해지는 표현은 그대로 사용합니다. [<분석의 흐름 → 분석의 방향성>, <코드로 박는 자리 → 코드에 명시하는 상황 (혹은 코드에 명시하는 작업)>, <요청을 받습니다 -> 요청을 확인했습니다 (혹은 요청대로 수행하겠습니다)>]
|
|
46
|
+
|
|
47
|
+
4. 엠대시(—)는 앞뒤 문장의 관계를 지나치게 함축하기 때문에 자제하고, 문맥과 형식에 따라 콜론이나 접속사로 대체합니다.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## 추가 사항
|
|
51
|
+
|
|
52
|
+
- 서브에이전트를 호출할 때, 한국어로 프롬프트를 작성했다면 실제로 서브에이전트 호출 도구를 사용하기 전에 이 본문의 지침들이 준수되어 있는지 점검합니다. 서브에이전트가 산출한 결과를 사용자에게 전달할 때에도 본문의 지침들이 그대로 적용됩니다.
|
package/src/commands/install.js
CHANGED
|
@@ -108,6 +108,45 @@ export async function run({ hasFlag }) {
|
|
|
108
108
|
: ' harness: auto-setup skipped — run `claude-token-saver harness init --global` yourself.');
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// Korean writing guidance. Decided here rather than left to a command the
|
|
112
|
+
// user has to find, because the people who need it are exactly the ones
|
|
113
|
+
// who would not know to look for it. Enabled when the machine's locale
|
|
114
|
+
// says Korean; left alone once the user has answered either way, so an
|
|
115
|
+
// upgrade never re-enables something they turned off.
|
|
116
|
+
try {
|
|
117
|
+
const ks = await import('../korean-style.js');
|
|
118
|
+
if (process.env.CTS_NO_KOREAN === '1') {
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(lang === 'ko'
|
|
121
|
+
? ' korean: CTS_NO_KOREAN=1 이므로 건너뜁니다 (나중에 `korean on`).'
|
|
122
|
+
: ' korean: skipped (CTS_NO_KOREAN=1) — run `korean on` later.');
|
|
123
|
+
} else if (ks.koreanStyleDecided()) {
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(lang === 'ko'
|
|
126
|
+
? ` korean: 기존 설정 유지 — 한국어 문체 지침 ${ks.koreanStyleEnabled() ? '켜짐' : '꺼짐'}`
|
|
127
|
+
: ` korean: keeping your setting — Korean writing guidance is ${ks.koreanStyleEnabled() ? 'on' : 'off'}`);
|
|
128
|
+
} else if (ks.koreanLocaleDetected()) {
|
|
129
|
+
ks.setKoreanStyleEnabled(true);
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(lang === 'ko'
|
|
132
|
+
? ' korean: 한국어 환경이 감지되어 문체 지침을 켰습니다 — 모든 프로젝트의 세션 시작 시 주입됩니다.'
|
|
133
|
+
: ' korean: Korean locale detected — writing guidance enabled, injected at session start in every project.');
|
|
134
|
+
console.log(lang === 'ko'
|
|
135
|
+
? ` 출처: ${ks.KOREAN_STYLE_SOURCE}`
|
|
136
|
+
: ` source: ${ks.KOREAN_STYLE_SOURCE}`);
|
|
137
|
+
console.log(lang === 'ko'
|
|
138
|
+
? ' 끄려면: claude-token-saver korean off'
|
|
139
|
+
: ' turn off with: claude-token-saver korean off');
|
|
140
|
+
} else {
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(lang === 'ko'
|
|
143
|
+
? ' korean: 한국어 환경이 아니어서 꺼 두었습니다 — 필요하면 `claude-token-saver korean on`.'
|
|
144
|
+
: ' korean: left off (no Korean locale detected) — enable with `claude-token-saver korean on`.');
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
debug('install:korean-style', e); // optional feature; never fail install
|
|
148
|
+
}
|
|
149
|
+
|
|
111
150
|
console.log('');
|
|
112
151
|
console.log('Open Claude Code in any directory and just mention:');
|
|
113
152
|
console.log(' "cache hit rate" / "1M context" / "5H cap" — the skill auto-activates.');
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subcommand: korean — Korean writing guidance for every session.
|
|
3
|
+
* claude-token-saver korean on # inject at session start, all projects
|
|
4
|
+
* claude-token-saver korean off # stop injecting
|
|
5
|
+
* claude-token-saver korean status # current state, cost, and provenance
|
|
6
|
+
* claude-token-saver korean show # print the guidance itself
|
|
7
|
+
*
|
|
8
|
+
* Why this exists rather than pointing users at Claude Code's output styles:
|
|
9
|
+
* an output style is one global slot, so turning it on takes the slot away
|
|
10
|
+
* from whatever else the user had there, and it has to be configured on every
|
|
11
|
+
* machine. This ships the guidance with the package and delivers it through
|
|
12
|
+
* the SessionStart hook that is already installed, so it applies everywhere
|
|
13
|
+
* the CLI is installed and leaves the output-style slot free.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export async function run({ args }) {
|
|
17
|
+
const sub = args[1] || 'status';
|
|
18
|
+
const ks = await import('../korean-style.js');
|
|
19
|
+
const { userLanguage } = await import('../config.js');
|
|
20
|
+
const lang = userLanguage();
|
|
21
|
+
|
|
22
|
+
if (sub === 'show') {
|
|
23
|
+
const text = ks.koreanStyleText();
|
|
24
|
+
if (!text) {
|
|
25
|
+
console.error('Korean style guidance file is missing from the package.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
console.log(text);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (sub === 'on' || sub === 'off') {
|
|
33
|
+
const enabled = sub === 'on';
|
|
34
|
+
ks.setKoreanStyleEnabled(enabled);
|
|
35
|
+
if (enabled) {
|
|
36
|
+
console.log(lang === 'ko'
|
|
37
|
+
? '한국어 문체 지침을 켰습니다. 다음 세션부터 모든 프로젝트에 적용됩니다.'
|
|
38
|
+
: 'Korean writing guidance is on. It applies in every project from the next session.');
|
|
39
|
+
console.log(lang === 'ko'
|
|
40
|
+
? ' 주입 시점: 세션 시작 1회 (매 턴이 아니므로 두 번째 요청부터는 캐시에 올라갑니다)'
|
|
41
|
+
: ' Injected once per session (not per turn), so it rides the prompt cache from the second request on.');
|
|
42
|
+
console.log(lang === 'ko'
|
|
43
|
+
? ` 출처: ${ks.KOREAN_STYLE_SOURCE}`
|
|
44
|
+
: ` Source: ${ks.KOREAN_STYLE_SOURCE}`);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(lang === 'ko'
|
|
47
|
+
? '한국어 문체 지침을 껐습니다. 다음 세션부터 주입하지 않습니다.'
|
|
48
|
+
: 'Korean writing guidance is off. Nothing is injected from the next session.');
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// status (default)
|
|
54
|
+
const on = ks.koreanStyleEnabled();
|
|
55
|
+
const text = ks.koreanStyleText();
|
|
56
|
+
// 4 bytes/token is the usual mixed ko/en approximation, same as the ratchet
|
|
57
|
+
// size report — this is the number the user is trading for the style.
|
|
58
|
+
const tokens = text ? Math.round(Buffer.byteLength(text, 'utf8') / 4) : 0;
|
|
59
|
+
if (lang === 'ko') {
|
|
60
|
+
console.log(`한국어 문체 지침: ${on ? '켜짐' : '꺼짐'}`);
|
|
61
|
+
console.log(` 비용: 세션당 약 ${tokens} 토큰 (세션 시작 1회 주입)`);
|
|
62
|
+
console.log(` 출처: ${ks.KOREAN_STYLE_SOURCE}`);
|
|
63
|
+
console.log(` 라이선스 전문: ${ks.KOREAN_STYLE_LICENSE_PATH}`);
|
|
64
|
+
console.log(on
|
|
65
|
+
? ' 끄려면: claude-token-saver korean off'
|
|
66
|
+
: ' 켜려면: claude-token-saver korean on');
|
|
67
|
+
} else {
|
|
68
|
+
console.log(`Korean writing guidance: ${on ? 'on' : 'off'}`);
|
|
69
|
+
console.log(` Cost: ~${tokens} tokens per session (injected once at session start)`);
|
|
70
|
+
console.log(` Source: ${ks.KOREAN_STYLE_SOURCE}`);
|
|
71
|
+
console.log(` License text: ${ks.KOREAN_STYLE_LICENSE_PATH}`);
|
|
72
|
+
console.log(on
|
|
73
|
+
? ' Turn off with: claude-token-saver korean off'
|
|
74
|
+
: ' Turn on with: claude-token-saver korean on');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -159,7 +159,21 @@ export async function run({ args, hasFlag, numArg }) {
|
|
|
159
159
|
.map((r, i) => ({ ...r, n: i + 1 }))
|
|
160
160
|
.filter((r) => r.status === 'review');
|
|
161
161
|
} catch (e) { debug('route-scan:load-rules', e); /* candidate briefing still goes out */ }
|
|
162
|
-
|
|
162
|
+
// Korean writing guidance, when the user enabled it. Printed before the
|
|
163
|
+
// route-scan briefing and independently of it: the style has to reach a
|
|
164
|
+
// session even when there is no candidate to report, which is the usual
|
|
165
|
+
// case. Injecting here rather than through a separate hook keeps it on
|
|
166
|
+
// one SessionStart round-trip and one cached prefix.
|
|
167
|
+
let koreanBlock = null;
|
|
168
|
+
try {
|
|
169
|
+
const { koreanStyleInjection } = await import('../korean-style.js');
|
|
170
|
+
koreanBlock = koreanStyleInjection();
|
|
171
|
+
} catch (e) { debug('route-scan:korean-style', e); /* style is optional */ }
|
|
172
|
+
|
|
173
|
+
if (open.length === 0 && reviewRules.length === 0) {
|
|
174
|
+
if (koreanBlock) console.log(koreanBlock);
|
|
175
|
+
return; // nothing else to inject
|
|
176
|
+
}
|
|
163
177
|
// This text is injected straight into the model's context, so it must
|
|
164
178
|
// follow the user's configured language — a Korean-only briefing in an
|
|
165
179
|
// English session steers the whole first response into Korean.
|
|
@@ -223,6 +237,9 @@ export async function run({ args, hasFlag, numArg }) {
|
|
|
223
237
|
lines.push(lang === 'ko'
|
|
224
238
|
? '위 내용은 사용자의 첫 메시지 주제와 무관하더라도(단순 인사여도) 첫 응답 말미에 `※ [claude-token-saver]` 라벨을 달아 요약 브리핑하세요 — 사용자가 물어볼 때까지 기다리지 말 것.'
|
|
225
239
|
: 'Summarize the above at the end of your first response under a `※ [claude-token-saver]` label, even if the user\'s first message is unrelated (a bare greeting counts) — do not wait to be asked.');
|
|
240
|
+
// Style first, briefing second: the briefing is Korean prose too, so the
|
|
241
|
+
// guidance has to be in context before the model reads it.
|
|
242
|
+
if (koreanBlock) console.log(koreanBlock + '\n');
|
|
226
243
|
console.log(lines.join('\n'));
|
|
227
244
|
// Record what was actually briefed so the UserPromptSubmit brief hook
|
|
228
245
|
// suppresses exactly these — a candidate landing after this read (e.g.
|
|
@@ -21,6 +21,7 @@ import { formatResetClock } from '../format-time.js';
|
|
|
21
21
|
import { labelForKey } from '../window-labels.js';
|
|
22
22
|
import { harnessStatusForStatusline } from '../harness.js';
|
|
23
23
|
import { loadConfig } from '../config.js';
|
|
24
|
+
import { koreanStyleEnabled } from '../korean-style.js';
|
|
24
25
|
|
|
25
26
|
// The 8-color ANSI defaults (RED=31, GREEN=32, YELLOW=33…) read as garish
|
|
26
27
|
// next to each other — terminal palettes set them with unbalanced perceptual
|
|
@@ -137,6 +138,24 @@ export function pickCapWarn(caps) {
|
|
|
137
138
|
return candidates[0];
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Korean-style chip builder. Renders only when the session-start injection is
|
|
143
|
+
* enabled, so nothing changes for anyone who never asked for it.
|
|
144
|
+
*/
|
|
145
|
+
function buildKoreanSeg(c, isIcon, verbose) {
|
|
146
|
+
try {
|
|
147
|
+
if (!koreanStyleEnabled()) return null;
|
|
148
|
+
// Deliberately quiet (gray, one syllable): this is a "yes, it is on"
|
|
149
|
+
// confirmation, not a warning. Without it a silently-failed hook looks
|
|
150
|
+
// exactly like a working one, because the style only shows up when the
|
|
151
|
+
// model happens to write Korean.
|
|
152
|
+
if (isIcon) return `${c(GRAY)}${verbose ? '가 Korean style' : '가'}${c(RESET)}`;
|
|
153
|
+
return `${c(GRAY)}Korean style${c(RESET)}`;
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
/**
|
|
141
160
|
* Harness 🅷 segment builder — shared by the full report and the no-session
|
|
142
161
|
* fallback line. Best-effort: never throws into the statusline (corrupted
|
|
@@ -211,7 +230,7 @@ export function formatNoSession({ caps = null, model = null, windowLabel = '' }
|
|
|
211
230
|
* @param {boolean} [opts.verbose=false] - longer layout with labels
|
|
212
231
|
* @param {boolean} [opts.timer=true] - show TTL countdown segment
|
|
213
232
|
* @param {'text'|'icon'} [opts.mode='text'] - label style. 'icon' uses 🧠 ⏳ 💰 instead of word labels.
|
|
214
|
-
* @param {string[]|null} [opts.segments] - whitelist of segments to render. Names: cap-warn, spike, harness, model, hit, ttl, saved, delegated, ctx, period, plus per-window keys (`five_hour`, `seven_day`, …). `5h`/`7d` are kept as aliases for back-compat. Null/undefined = all.
|
|
233
|
+
* @param {string[]|null} [opts.segments] - whitelist of segments to render. Names: cap-warn, spike, harness, korean, model, hit, ttl, saved, delegated, ctx, period, plus per-window keys (`five_hour`, `seven_day`, …). `5h`/`7d` are kept as aliases for back-compat. Null/undefined = all.
|
|
215
234
|
* @param {boolean} [opts.singleLine=false] - force the legacy one-line layout. By default, when the delegation ledger has lifetime savings, the routing totals lead on their own first line and everything else moves to line 2 (Claude Code renders multi-line statuslines; `--single-line` is the escape hatch for terminals that only show the first line).
|
|
216
235
|
*/
|
|
217
236
|
export function formatReport(data, { color = true, verbose = false, timer = true, mode = 'text', segments = null, singleLine = false } = {}) {
|
|
@@ -426,6 +445,9 @@ export function formatReport(data, { color = true, verbose = false, timer = true
|
|
|
426
445
|
// a missing section at a glance and know to run `harness init`.
|
|
427
446
|
const harnessSeg = buildHarnessSeg(c, isIcon);
|
|
428
447
|
|
|
448
|
+
// Korean-style chip — rendered only when the session-start injection is on.
|
|
449
|
+
const koreanSeg = buildKoreanSeg(c, isIcon, verbose);
|
|
450
|
+
|
|
429
451
|
// Model chip — pulled from Claude Code's stdin payload (`model.display_name`).
|
|
430
452
|
// Cheap identity context: useful when the user toggles between Sonnet/Opus
|
|
431
453
|
// mid-session and wants to confirm at a glance which one is answering.
|
|
@@ -528,6 +550,7 @@ export function formatReport(data, { color = true, verbose = false, timer = true
|
|
|
528
550
|
if (capWarnSeg && want('cap-warn')) segs.push(capWarnSeg);
|
|
529
551
|
if (spikeSeg && want('spike')) segs.push(spikeSeg);
|
|
530
552
|
if (harnessSeg && want('harness')) segs.push(harnessSeg);
|
|
553
|
+
if (koreanSeg && want('korean')) segs.push(koreanSeg);
|
|
531
554
|
if (modelSeg && want('model')) segs.push(modelSeg);
|
|
532
555
|
// Delegation savings ride up front, next to the model that would otherwise
|
|
533
556
|
// have done the work. "Cache saved" stays at the tail: it is a lifetime brag
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* korean-style — inject Korean writing guidance into every session.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code's own mechanism for this is an output style, which is a global
|
|
5
|
+
* switch: turning it on replaces whatever style the user had, and it only
|
|
6
|
+
* applies where the user remembered to configure it. Projects opened on a
|
|
7
|
+
* different machine, or by a teammate, get nothing.
|
|
8
|
+
*
|
|
9
|
+
* This module carries the guidance inside the package instead and hands it to
|
|
10
|
+
* the model through the SessionStart hook claude-token-saver already installs.
|
|
11
|
+
* The rules then apply in every project on the machine, with no output-style
|
|
12
|
+
* change and no plugin to install, and they survive `/clear` because the hook
|
|
13
|
+
* fires again.
|
|
14
|
+
*
|
|
15
|
+
* Cost: the text is ~1.3k tokens, injected once per session (not per turn) and
|
|
16
|
+
* covered by the prompt cache from the second request on. A token-saving tool
|
|
17
|
+
* has no business spending that silently, so the feature is opt-in via
|
|
18
|
+
* `claude-token-saver korean on`.
|
|
19
|
+
*
|
|
20
|
+
* The guidance itself is vendored from fluent-korean
|
|
21
|
+
* (https://github.com/snflkd/fluent-korean), Copyright (c) 2026 snflkd, MIT
|
|
22
|
+
* License — see presets/korean-style/LICENSE-fluent-korean. Only the
|
|
23
|
+
* output-style frontmatter was stripped; the wording is unmodified.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
27
|
+
import { join, dirname } from 'node:path';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
import { createRequire } from 'node:module';
|
|
30
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
31
|
+
|
|
32
|
+
const require = createRequire(import.meta.url);
|
|
33
|
+
|
|
34
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
35
|
+
|
|
36
|
+
export const KOREAN_STYLE_PATH = join(packageRoot, 'presets', 'korean-style', 'fluent-korean.md');
|
|
37
|
+
export const KOREAN_STYLE_LICENSE_PATH = join(packageRoot, 'presets', 'korean-style', 'LICENSE-fluent-korean');
|
|
38
|
+
export const KOREAN_STYLE_SOURCE = 'fluent-korean by snflkd (MIT) — https://github.com/snflkd/fluent-korean';
|
|
39
|
+
|
|
40
|
+
/** Whether session-start injection is enabled. Off unless the user asked. */
|
|
41
|
+
export function koreanStyleEnabled(cfg = loadConfig()) {
|
|
42
|
+
return cfg?.koreanStyle?.enabled === true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** True once the user has turned the feature on or off explicitly. */
|
|
46
|
+
export function koreanStyleDecided(cfg = loadConfig()) {
|
|
47
|
+
return typeof cfg?.koreanStyle?.enabled === 'boolean';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether this machine looks like it writes Korean.
|
|
52
|
+
*
|
|
53
|
+
* Used only to decide the DEFAULT at install time. Turning the guidance on for
|
|
54
|
+
* everyone would bill ~1.5k tokens per session to users who never write a
|
|
55
|
+
* Korean sentence; leaving it off for everyone means the people who need it
|
|
56
|
+
* have to discover a command that exists for exactly them. Locale answers the
|
|
57
|
+
* question well enough, and the user can override either way afterwards.
|
|
58
|
+
*
|
|
59
|
+
* Signals, cheapest first: the tool's own language setting, then the POSIX
|
|
60
|
+
* locale variables, then (macOS only, where those are routinely unset) the
|
|
61
|
+
* system locale.
|
|
62
|
+
*/
|
|
63
|
+
export function koreanLocaleDetected({ env = process.env, platform = process.platform } = {}) {
|
|
64
|
+
try {
|
|
65
|
+
if (loadConfig().language === 'ko') return true;
|
|
66
|
+
} catch { /* unreadable config falls through to the env checks */ }
|
|
67
|
+
for (const v of [env.LC_ALL, env.LC_MESSAGES, env.LANG, env.LANGUAGE]) {
|
|
68
|
+
// `ko` must be a whole subtag: `ko`, `ko_KR.UTF-8`, `ko-KR`, and the
|
|
69
|
+
// colon-separated `LANGUAGE=ko:en` all count, while `kok` (Konkani) and
|
|
70
|
+
// `tok` do not.
|
|
71
|
+
if (typeof v === 'string' && /(^|[:._-])ko([:._-]|$)/i.test(v)) return true;
|
|
72
|
+
}
|
|
73
|
+
if (platform === 'darwin') {
|
|
74
|
+
try {
|
|
75
|
+
// `LANG` is commonly unset in macOS GUI-launched shells, so the system
|
|
76
|
+
// locale is the only reliable signal there.
|
|
77
|
+
const { execFileSync } = require('node:child_process');
|
|
78
|
+
const out = execFileSync('defaults', ['read', '-g', 'AppleLocale'], {
|
|
79
|
+
encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
80
|
+
}).trim();
|
|
81
|
+
if (/^ko(_|-|$)/i.test(out)) return true;
|
|
82
|
+
} catch { /* `defaults` missing or slow — treat as "not detected" */ }
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function setKoreanStyleEnabled(enabled) {
|
|
88
|
+
const cfg = loadConfig();
|
|
89
|
+
cfg.koreanStyle = { ...(cfg.koreanStyle || {}), enabled: !!enabled };
|
|
90
|
+
saveConfig(cfg);
|
|
91
|
+
return cfg.koreanStyle;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The guidance text, with the vendoring comment stripped (it is provenance for
|
|
96
|
+
* readers of the repo, not instruction for the model — and every token of it
|
|
97
|
+
* would be charged on each session).
|
|
98
|
+
*
|
|
99
|
+
* Returns null when the file is missing, which the hook reads as "inject
|
|
100
|
+
* nothing" rather than failing a session start.
|
|
101
|
+
*/
|
|
102
|
+
export function koreanStyleText() {
|
|
103
|
+
try {
|
|
104
|
+
if (!existsSync(KOREAN_STYLE_PATH)) return null;
|
|
105
|
+
const raw = readFileSync(KOREAN_STYLE_PATH, 'utf8');
|
|
106
|
+
const body = raw.replace(/^<!--[\s\S]*?-->\s*/, '').trim();
|
|
107
|
+
return body || null;
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Block to inject at session start, or null when disabled/unavailable.
|
|
115
|
+
*
|
|
116
|
+
* The framing line matters: without it the model can read the guidance as
|
|
117
|
+
* "the user is asking about Korean writing rules" instead of "these rules
|
|
118
|
+
* govern how I write from now on".
|
|
119
|
+
*/
|
|
120
|
+
export function koreanStyleInjection({ cfg = loadConfig() } = {}) {
|
|
121
|
+
if (!koreanStyleEnabled(cfg)) return null;
|
|
122
|
+
const text = koreanStyleText();
|
|
123
|
+
if (!text) return null;
|
|
124
|
+
return [
|
|
125
|
+
'[claude-token-saver korean-style] 이 세션에서 한국어를 출력할 때는 아래 지침을 따르십시오.',
|
|
126
|
+
'이 지침은 사용자가 claude-token-saver에 설정한 것이며, 답변·문서·주석이 아닌 산문 전반에 적용됩니다.',
|
|
127
|
+
`(출처: ${KOREAN_STYLE_SOURCE})`,
|
|
128
|
+
'',
|
|
129
|
+
text,
|
|
130
|
+
].join('\n');
|
|
131
|
+
}
|