claude-token-saver 3.31.0 → 3.34.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 CHANGED
@@ -114,13 +114,36 @@ Outside a terminal — npm `postinstall`, CI, piped stdin — the question is sk
114
114
 
115
115
  > ⚠️ Avoid `sudo` global installs — the Skill lands in root's `~/.claude` instead of yours. Use nvm/fnm/Volta or `npm config set prefix ~/.npm-global`.
116
116
 
117
+ ### What the install turns on, and what stays manual
118
+
119
+ Everything that costs nothing until it is needed is on after a plain install. The only manual items are the ones that change Claude Code's own settings or need a human to pick a scope.
120
+
121
+ | Feature | After install | How to turn it off |
122
+ |---|---|---|
123
+ | statusline (diagnostic chips, savings ledger) | on | `claude-token-saver uninstall` |
124
+ | `/claude-token-saver` Skill | on | same |
125
+ | SessionStart hook (route-scan refresh) | on | same |
126
+ | UserPromptSubmit hook (brief injection) | on | same |
127
+ | First route-scan (last 14 days of logs) | runs once during the install | n/a |
128
+ | 🅷 Harness 5 principles (`~/.claude/CLAUDE.md`) | on (shown and confirmed once at a terminal) | `harness uninit --global`, `CTS_NO_HARNESS=1` |
129
+ | doc2md hooks (Read, Edit/Write, prompt) | on | `doc2md off`, `CTS_NO_DOC2MD=1` |
130
+ | doc2md converter (markitdown venv) | offered at a terminal; an unattended install prints the command | install later with `doc2md install-converter` |
131
+ | Korean writing guidance | on when the locale is Korean (asked at a terminal) | `korean off`, `CTS_NO_KOREAN=1` |
132
+ | compact-window warning chip | on | `compact-window off` |
133
+ | Update-available chip | on | `CTS_NO_UPDATE_CHECK=1` |
134
+ | **Pinning compact-window** (`autoCompactWindow` 500k) | **off — run it yourself** | `compact-window set --global` or `--project` |
135
+ | **Model-fitting rules** (`ratchet-model.md` delegations) | **candidates are proposed only** | review and approve with `route-scan rules` |
136
+ | `handoff` (back up work before a cap) | an on-demand command | n/a |
137
+
138
+ `compact-window set` writes into Claude Code's `settings.json` and a human has to choose global or project scope, so it is never run for you. Model-fitting rules keep an approval step for the same reason: which work belongs on a cheaper tier is your call.
139
+
117
140
  ## Reading the statusline
118
141
 
119
142
  Once the savings ledger has entries it renders as **two rows** — routing savings on row 1, diagnostics on row 2.
120
143
 
121
144
  ```
122
145
  🔀 Routing saved $2.09 | fable→sonnet 1× $0.72 · opus→haiku 1× $0.57
123
- ⚠ Ctx 200k+ · 🅷 5/5 · 🤖 Opus 5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · ✦ current ███▓░░ 62% 🔄 21:33 · 📅 weekly ██▒░░░ 38% 🔄 Tue 19:33 · 📦 Ctx 47% of 1M · 💰 Cache saved $1.0K · last 1d
146
+ ⚠ Ctx 500k+ · 🅷 5/5 · 🤖 Opus 5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · ✦ current ███▓░░ 62% 🔄 21:33 · 📅 weekly ██▒░░░ 38% 🔄 Tue 19:33 · 📦 Ctx 47% of 1M · 💰 Cache saved $1.0K · last 1d
124
147
  ```
125
148
 
126
149
  With an empty ledger (no measured delegation yet) row 1 is not drawn and the layout stays single-line. If your build renders only the first row (some macOS Claude Code versions), pass `--single-line`.
@@ -144,7 +167,7 @@ When something is wrong, a **warning chip leads the line**:
144
167
  🚨 5H █████▓ 94% 🔄 12:36 · 🅷 5/5 · 🤖 Opus 4.8 · 🧠 Cache hit 72.1% · ⚠ Cache miss · 📅 weekly ▓░░░░░ 12% 🔄 Sun 14:26 · 📦 Ctx 200k · last 1d
145
168
  ```
146
169
 
147
- Chips — `🚨 5H/7D NN%` (cap imminent) · `⚠ Ctx 200k+` (a single request actually exceeded 200k) · `⚠ Cache miss` · `⚠ Input spike` · `⚠ Output heavy` · `⚠ Call surge` · `⚠ Rebuild churn` · `⚠ 5m TTL`. When both windows cross 90% at once, the sooner-resetting one is promoted to 🚨 and the other stays visible as a red segment (v2.16.0+).
170
+ Chips — `🚨 5H/7D NN%` (cap imminent) · `⚠ Ctx 500k+` (a single request actually exceeded 500k) · `⚠ Cache miss` · `⚠ Input spike` · `⚠ Output heavy` · `⚠ Call surge` · `⚠ Rebuild churn` · `⚠ 5m TTL`. When both windows cross 90% at once, the sooner-resetting one is promoted to 🚨 and the other stays visible as a red segment (v2.16.0+).
148
171
 
149
172
  ### When a chip appears
150
173
 
@@ -177,7 +200,7 @@ Run these in your shell (inside Claude Code, the `/claude-token-saver` Skill is
177
200
  | `claude-token-saver install` | Manually register Skill + statusline |
178
201
  | `claude-token-saver uninstall [--purge]` | Remove the hooks, statusline and skill it registered. Recorded savings are kept unless `--purge` is given |
179
202
 
180
- Switch output language with `mode ko` / `mode en` (English default; statusline chips stay symbolic).
203
+ The output language is decided once, at install time: a terminal install proposes the system locale and asks whether to use Korean, while an unattended install records what the locale says. Once recorded it is never asked again, not even on an upgrade. Change it later with `mode ko` / `mode en`, or pin it for a scripted install with `CTS_LANG=ko` / `CTS_LANG=en`. Statusline chips stay symbolic either way.
181
204
 
182
205
  <details>
183
206
  <summary>All CLI options</summary>
@@ -225,7 +248,8 @@ claude-token-saver harness off | on # toggle the 🅷 chip
225
248
  ```
226
249
 
227
250
  - `promote` **requires** `--project`/`--global` in non-TTY contexts (scripts, LLM calls) — a scope choice is never silently made for the caller.
228
- - `pull` registers the **author-curated ratchet rules** bundled with the package (`presets/ratchet-rules.md` — only general-purpose rules promoted from real recurring mistakes) into your global ratchet (`~/.claude/ratchet.md`). `install`/`init` never auto-inject anything; `pull` is always opt-in and idempotent. Drop any rule you dislike with `harness rm`.
251
+ - `pull` registers the **author-curated ratchet rules** bundled with the package (`presets/ratchet-rules.json` — only general-purpose rules promoted from real recurring mistakes) into your global ratchet (`~/.claude/ratchet.md`). `install`/`init` never auto-inject anything; `pull` is always opt-in and idempotent. Drop any rule you dislike with `harness rm`.
252
+ - `seed` offers the same presets **one at a time**. Where `pull` registers the whole ratchet set in one go, `seed` covers the model-fitting presets too and asks about each of them in the first session after an install or upgrade ([below](#-seed-delegation-that-works-from-the-first-session)).
229
253
  - 🅷⚠ runtime warnings (`ratchet?` `no-evidence` `PEV-skip`) expire after 30 minutes, subdirectory sessions match their project correctly, and PEV-skip counts only mutating tools (Edit/Write/Bash) so read-only research sessions don't trip it (v2.16.0+).
230
254
 
231
255
  <details>
@@ -307,6 +331,32 @@ For environments the learner cannot reach, write the mapping yourself in `<userD
307
331
 
308
332
  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.
309
333
 
334
+ ## 🌱 seed: delegation that works from the first session
335
+
336
+ The model-fitting ratchet (`ratchet-model.md`) **starts empty.** A rule exists only after route-scan has seen the same kind of work recur in your own logs and you have approved that candidate. So a fresh install delegates nothing, and keeps delegating nothing for days — precisely the stretch where the savings would matter most.
337
+
338
+ `seed` fills that gap from presets bundled with the package.
339
+
340
+ | Presets | What they cover | File |
341
+ |---|---|---|
342
+ | 9 model-fitting | running commands, lookup, status checks, questions about pasted logs, read-and-summarize — each with a T2 (haiku) and a T1 (sonnet) rule | `presets/model-rules.json` |
343
+ | 6 ratchet | general-purpose rules promoted from mistakes that actually recurred | `presets/ratchet-rules.json` |
344
+
345
+ **How they get registered:** in the first session after an install or upgrade, the SessionStart hook hands the pending presets to the model, which walks the user through them **one at a time**. Each answer runs one of these immediately:
346
+
347
+ ```bash
348
+ claude-token-saver seed # pending presets + recorded answers
349
+ claude-token-saver seed accept <id> --global|--project # register one (scope required)
350
+ claude-token-saver seed accept all --global # when the user says "register them all"
351
+ claude-token-saver seed skip <id> # decline — never offered again
352
+ claude-token-saver seed reset # clear the answers and offer everything again
353
+ ```
354
+
355
+ - **Nothing is written without a yes to that specific rule.** A declined rule stays declined across upgrades; a later release only surfaces the presets it actually added.
356
+ - A preset is withheld when you already approved a rule of the same shape (same tier and category).
357
+ - A seeded rule **does not pass someone else's statistics off as yours.** It is recorded as `preset (curated)` until a scan measures real firings and delegations, and then those numbers replace it. If its delegated error rate crosses the threshold it gets the same review flag as any other rule.
358
+ - The scope must be stated as `--global` or `--project`. The hook environment is non-TTY, so the CLI cannot ask — the model confirms with the user and passes the flag.
359
+
310
360
  ## 🇰🇷 Korean writing guidance
311
361
 
312
362
  Injects guidance that corrects how Claude writes Korean (dropped sentence parts, noun-stopped sentences, translationese, em-dash overuse) **once per session.**
@@ -616,6 +666,24 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
616
666
 
617
667
  ## Release notes
618
668
 
669
+ ### v3.34.0 (2026-09-10)
670
+ - **The output language is chosen at install time.** With nothing recorded it used to fall back to English in silence, so a Korean user read English reports until they happened to find `mode ko`. A terminal install now proposes the system locale and asks whether to use Korean; an unattended install records what the locale says. Once recorded it is never asked again, and a scripted install can pin it with `CTS_LANG=ko|en`.
671
+ - **Fixed a locale-detection defect.** An explicit `LANG=en_US.UTF-8` was overruled on macOS, where detection went on to read the system locale (`AppleLocale`) and answered "Korean". A POSIX locale variable that is set now counts as the answer; the system locale is consulted only when all of them are empty, which is the macOS GUI-shell case the fallback was for.
672
+ - Language is the first step of the install, so the harness, doc2md and seed notices below it all print in the chosen language.
673
+
674
+ ### v3.33.0 (2026-09-10)
675
+ - **`seed`: delegation works from the first session.** The model-fitting ratchet started empty, so a fresh install delegated nothing for days — until enough of the user's own history accumulated and a candidate was approved. The package now bundles **9 model-fitting presets** (`presets/model-rules.json`) and **6 ratchet presets** (`presets/ratchet-rules.json`), and the SessionStart hook hands the pending ones to the model in the first session after an install or upgrade so it can ask about them **one at a time**.
676
+ - **Answers are recorded for good.** A declined preset is not re-offered after an upgrade; only presets a later release actually added show up. A preset is withheld entirely when a rule of the same tier and category is already registered. Commands: `seed`, `seed accept <id> --global|--project`, `seed skip <id>`, `seed reset`.
677
+ - **A seeded rule does not report someone else's statistics as yours.** It reads `preset (curated)` until a scan measures real firings and delegations, then those numbers take over. Its delegated error rate gets the same review flag as any other rule.
678
+ - **Ratchet presets are bilingual now.** `presets/ratchet-rules.md` became `presets/ratchet-rules.json` with both languages per rule: the text is injected into the model's context, so a Korean-only rule pulled an English session's answers into Korean. Ids come from the Korean text, so revising an English wording keeps the user's answer.
679
+ - **One template for rule sentences.** route-scan and seed both compose through `modelRuleBaseText()`, so a reworded rule cannot mean two different things depending on which producer wrote it.
680
+
681
+ ### v3.32.0 (2026-09-10)
682
+ - **The context warning moved from 200k to 500k.** A brand-new session clears 200k from the system prompt plus a couple of file reads, so `⚠ Ctx 200k+` was on almost permanently and therefore said nothing. The chip is now `⚠ Ctx 500k+` and fires only when a single request really carried more than 500k input tokens. The `📦 Ctx` segment also waits for 500k before turning yellow — being on a 1M window is no longer a warning by itself.
683
+ - **Old history still resolves.** The legacy `⚠ Ctx 200k+` chip and the old detail string (`Single-request context exceeded 200k`) are still parsed.
684
+ - **doc2md is enabled by the install.** Registering the hooks is two entries in `settings.json` and costs nothing until a document appears, yet until now only people who found `doc2md on` themselves benefited. The converter (a markitdown venv) takes minutes to build, so it is offered at a terminal and merely printed as a command on an unattended install. Opt out with `doc2md off` or `CTS_NO_DOC2MD=1`.
685
+ - **Documented which features the install turns on and which stay manual.** The only manual ones left are `compact-window set` (writes Claude Code settings and needs an explicit scope) and approving model-fitting rules.
686
+
619
687
  ### v3.27.0 (2026-09-04)
620
688
  - **A document path in the prompt is finally caught.** The 3.26.x `PreToolUse(Read)` hook never reached the formats it was written for: Claude Code refuses pptx/xlsx/docx as binary *before* running any hook. Measured — a `.pdf` Read fires the hook, a `.pptx` Read in the same session leaves no hook log entry. Interception now also happens at `UserPromptSubmit`, which sees the raw prompt: paths there are converted and the conversion is handed back as context. `@path`, quoted and relative forms all count.
621
689
  - **A session-start note was added.** It tells the model two things it cannot work out alone: that a binary-file refusal is answered by `doc2md <path>`, and that if the user attached a document, they should be asked for a path next time. An attachment bills its entire contents into the context, and no hook event receives attachment content, so there is nothing the tool itself can do about it.
package/README.md CHANGED
@@ -113,13 +113,36 @@ npm i -g claude-token-saver
113
113
 
114
114
  > ⚠️ sudo로 글로벌 설치를 하면 Skill이 사용자 계정이 아니라 root의 `~/.claude`에 등록되는 함정이 있습니다. nvm이나 fnm, Volta를 사용해 사용자 영역에 설치하기를 권장합니다.
115
115
 
116
+ ### 설치하면 켜지는 기능과 직접 켜야 하는 기능
117
+
118
+ 설치 시점에 비용이 들지 않는 기능은 전부 자동으로 켜집니다. 수동으로 남겨 둔 항목은 Claude Code 자체의 설정을 바꾸거나, 적용 범위를 사람이 정해 주어야 하는 것들뿐입니다.
119
+
120
+ | 기능 | 설치 직후 상태 | 끄는 방법 |
121
+ |---|---|---|
122
+ | statusline (진단 칩·절감 원장) | 켜짐 | `claude-token-saver uninstall` |
123
+ | `/claude-token-saver` Skill | 켜짐 | 위와 같습니다 |
124
+ | SessionStart 훅 (route-scan 재분석) | 켜짐 | 위와 같습니다 |
125
+ | UserPromptSubmit 훅 (brief 주입) | 켜짐 | 위와 같습니다 |
126
+ | 최초 route-scan (최근 14일 로그 분석) | 설치 중 즉시 1회 실행 | 해당 없습니다 |
127
+ | 🅷 Harness 5원칙 (`~/.claude/CLAUDE.md`) | 켜짐 (터미널에서는 내용을 보여 주고 한 번 묻습니다) | `harness uninit --global` · `CTS_NO_HARNESS=1` |
128
+ | doc2md 훅 (Read·Edit·Write·프롬프트) | 켜짐 | `doc2md off` · `CTS_NO_DOC2MD=1` |
129
+ | doc2md 변환기(markitdown venv) | 터미널에서 설치 여부를 묻고, 비대화형 설치에서는 명령만 안내합니다 | `doc2md install-converter` 로 나중에 설치 |
130
+ | 한국어 문체 지침 | 시스템 로케일이 한국어면 켜짐 (터미널에서는 묻습니다) | `korean off` · `CTS_NO_KOREAN=1` |
131
+ | compact-window 경고 칩 | 켜짐 | `compact-window off` |
132
+ | 업데이트 안내 칩 | 켜짐 | `CTS_NO_UPDATE_CHECK=1` |
133
+ | **compact-window 값 고정** (`autoCompactWindow` 500k) | **꺼짐 — 직접 실행해야 합니다** | `compact-window set --global` 또는 `--project` |
134
+ | **모델 피팅 룰 승인** (`ratchet-model.md` 위임 룰) | **후보만 제안합니다** | `route-scan rules` 로 확인하고 승인·삭제 |
135
+ | `handoff` (한도 임박 시 작업 백업) | 필요할 때 직접 실행하는 명령입니다 | 해당 없습니다 |
136
+
137
+ `compact-window set` 은 Claude Code 의 `settings.json` 에 값을 적고, 전역과 프로젝트 중 어디에 적을지는 사람이 정해야 하므로 자동으로 실행하지 않습니다. 모델 피팅 룰도 같은 이유로 승인 단계를 남겨 두었습니다. 어떤 작업을 더 싼 티어에 맡길지는 사용자의 판단이 필요합니다.
138
+
116
139
  ## statusline 읽는 법
117
140
 
118
141
  절감 원장에 기록이 쌓이면 statusline이 **두 줄로** 출력됩니다. 첫째 줄에는 라우팅 절감액만 표시하고, 둘째 줄에는 진단 칩을 표시합니다.
119
142
 
120
143
  ```
121
144
  🔀 Routing saved $2.09 | fable→sonnet 1× $0.72 · opus→haiku 1× $0.57
122
- ⚠ Ctx 200k+ · 🅷 5/5 · 🤖 Opus 5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · ✦ current ███▓░░ 62% 🔄 21:33 · 📅 weekly ██▒░░░ 38% 🔄 Tue 19:33 · 📦 Ctx 47% of 1M · 💰 Cache saved $1.0K · last 1d
145
+ ⚠ Ctx 500k+ · 🅷 5/5 · 🤖 Opus 5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · ✦ current ███▓░░ 62% 🔄 21:33 · 📅 weekly ██▒░░░ 38% 🔄 Tue 19:33 · 📦 Ctx 47% of 1M · 💰 Cache saved $1.0K · last 1d
123
146
  ```
124
147
 
125
148
  원장이 비어 있으면, 다시 말해 아직 실측된 위임이 없으면 첫째 줄을 그리지 않고 종전처럼 한 줄로 출력합니다. 일부 환경(구버전 macOS Claude Code)에서 첫째 줄만 표시된다면 `--single-line` 옵션으로 한 줄 레이아웃을 유지하십시오.
@@ -143,7 +166,7 @@ npm i -g claude-token-saver
143
166
  🚨 5H █████▓ 94% 🔄 12:36 · 🅷 5/5 · 🤖 Opus 4.8 · 🧠 Cache hit 72.1% · ⚠ Cache miss · 📅 weekly ▓░░░░░ 12% 🔄 Sun 14:26 · 📦 Ctx 200k · last 1d
144
167
  ```
145
168
 
146
- 칩의 종류는 다음과 같습니다. `🚨 5H/7D NN%`(한도 임박) · `⚠ Ctx 200k+`(단일 요청이 실제로 200k를 초과) · `⚠ Cache miss` · `⚠ Input spike` · `⚠ Output heavy` · `⚠ Call surge` · `⚠ Rebuild churn` · `⚠ 5m TTL`. 두 윈도가 동시에 90%를 넘으면 리셋이 더 임박한 쪽을 🚨로 올리고, 나머지 하나는 빨간 세그먼트로 계속 표시합니다 (v2.16.0 이상).
169
+ 칩의 종류는 다음과 같습니다. `🚨 5H/7D NN%`(한도 임박) · `⚠ Ctx 500k+`(단일 요청이 실제로 500k를 초과) · `⚠ Cache miss` · `⚠ Input spike` · `⚠ Output heavy` · `⚠ Call surge` · `⚠ Rebuild churn` · `⚠ 5m TTL`. 두 윈도가 동시에 90%를 넘으면 리셋이 더 임박한 쪽을 🚨로 올리고, 나머지 하나는 빨간 세그먼트로 계속 표시합니다 (v2.16.0 이상).
147
170
 
148
171
  ### 경고 칩이 떴을 때
149
172
 
@@ -176,7 +199,7 @@ Claude Code 안에서 `/claude-token-saver` Skill을 실행하거나, 칩에 적
176
199
  | `claude-token-saver install` | Skill·statusline 수동 등록 |
177
200
  | `claude-token-saver uninstall [--purge]` | 등록한 훅·statusline·Skill 제거. 기록된 절감액은 남기며, `--purge` 를 붙이면 상태 디렉터리까지 지웁니다 |
178
201
 
179
- 출력 언어는 `mode ko`와 `mode en`으로 전환합니다. 기본값은 영어이며, statusline의 칩은 언제나 기호로 표시합니다. 전체 옵션은 [영문 README](./README.en.md#options)를 참고하십시오.
202
+ 출력 언어는 설치할 때 한 번 정합니다. 터미널에서 설치하면 시스템 로케일을 기본값으로 제시하고 한국어를 쓸지 물어보며, 비대화형 설치에서는 로케일 판정을 그대로 기록합니다. 한 번 기록되면 업그레이드해도 다시 묻지 않습니다. 나중에 바꿀 때는 `mode ko`나 `mode en`을 쓰고, 스크립트에서 설치할 때는 `CTS_LANG=ko` 또는 `CTS_LANG=en`으로 지정할 있습니다. statusline의 칩은 언제나 기호로 표시합니다. 전체 옵션은 [영문 README](./README.en.md#options)를 참고하십시오.
180
203
 
181
204
  ## ⬆ 업데이트 안내
182
205
 
@@ -205,7 +228,8 @@ claude-token-saver harness off | on # 🅷 표시 토글
205
228
  ```
206
229
 
207
230
  - `promote`는 non-TTY 환경(스크립트나 LLM 호출)에서 `--project` 또는 `--global` 플래그가 **반드시 필요합니다.** 적용 범위가 사용자에게 묻지 않은 채 결정되는 사고를 막기 위한 설계입니다.
208
- - `pull`은 패키지에 동봉된 **제작자 큐레이션 랫쳇 룰**(`presets/ratchet-rules.md` — 실제 반복 사고에서 승격된 범용 룰만)을 내 글로벌 랫쳇(`~/.claude/ratchet.md`)에 등록합니다. 설치(`install`)나 `init`은 아무것도 자동 주입하지 않으며, `pull`은 항상 opt-in이고 재실행해도 중복이 없습니다(멱등). 마음에 안 드는 룰은 `harness rm`으로 제거하면 됩니다.
231
+ - `pull`은 패키지에 동봉된 **제작자 큐레이션 랫쳇 룰**(`presets/ratchet-rules.json` — 실제 반복 사고에서 승격된 범용 룰만)을 내 글로벌 랫쳇(`~/.claude/ratchet.md`)에 등록합니다. 설치(`install`)나 `init`은 아무것도 자동 주입하지 않으며, `pull`은 항상 opt-in이고 재실행해도 중복이 없습니다(멱등). 마음에 안 드는 룰은 `harness rm`으로 제거하면 됩니다.
232
+ - `seed`는 같은 프리셋을 **한 건씩** 물어보는 경로입니다. `pull`이 랫쳇 룰 전체를 한 번에 등록하는 명령인 데 반해, `seed`는 모델 피팅 프리셋까지 포함해 설치·업그레이드 후 첫 세션에서 한 건씩 제안합니다 ([아래](#-seed-설치-직후부터-위임이-걸리게-하는-시작-룰)).
209
233
  - 🅷⚠ 런타임 경고(`ratchet?` `no-evidence` `PEV-skip`)는 30분 후 자동 만료되고, 하위 디렉터리 세션도 프로젝트에 올바르게 매칭됩니다. PEV-skip은 변경성 도구(Edit/Write/Bash)만 카운트해 읽기 위주 세션에서는 발동하지 않습니다 (v2.16.0+).
210
234
 
211
235
  <details>
@@ -297,6 +321,32 @@ v3.10.0부터는 프로파일 ID를 역할(main·opus·sonnet·haiku)로 되돌
297
321
 
298
322
  이 파일에는 사내 식별자가 평문으로 남으므로 저장소에 커밋하지 마십시오. 게이트웨이를 쓰지 않는 환경에서는 파일이 아예 만들어지지 않고 기존 동작이 그대로 유지됩니다.
299
323
 
324
+ ## 🌱 seed: 설치 직후부터 위임이 걸리게 하는 시작 룰
325
+
326
+ 모델 피팅 랫쳇(`ratchet-model.md`)은 **빈 파일로 시작합니다.** route-scan이 사용자의 로그에서 같은 유형의 작업을 여러 번 관측하고, 사용자가 그 후보를 승인해야 룰이 생깁니다. 즉 갓 설치한 상태에서는 위임이 한 건도 걸리지 않고, 그 상태가 며칠 이어집니다. 정작 절감 효과가 가장 클 시기입니다.
327
+
328
+ `seed`는 패키지에 동봉된 프리셋으로 그 공백을 메웁니다.
329
+
330
+ | 프리셋 | 내용 | 파일 |
331
+ |---|---|---|
332
+ | 모델 피팅 9건 | 명령 실행·탐색·상태 확인·붙여넣은 로그 질문·읽기 요약, 각 유형의 T2(haiku)와 T1(sonnet) 룰 | `presets/model-rules.json` |
333
+ | 랫쳇 6건 | 실제 반복 사고에서 승격된 범용 룰 | `presets/ratchet-rules.json` |
334
+
335
+ **등록 절차:** 설치나 업그레이드 후 첫 세션에서 SessionStart 훅이 대기 중인 프리셋을 모델에게 전달하고, 모델이 **한 건씩 순서대로** 등록 여부를 묻습니다. 사용자가 답하면 그 자리에서 아래 명령을 실행합니다.
336
+
337
+ ```bash
338
+ claude-token-saver seed # 대기 중인 프리셋과 응답 기록
339
+ claude-token-saver seed accept <id> --global|--project # 한 건 등록 (적용 범위 필수)
340
+ claude-token-saver seed accept all --global # 사용자가 "전부 등록"이라고 답한 경우
341
+ claude-token-saver seed skip <id> # 거절 — 다시 묻지 않습니다
342
+ claude-token-saver seed reset # 응답 기록을 지워 전체를 다시 제안 대상으로
343
+ ```
344
+
345
+ - **승인 없이는 아무것도 기록되지 않습니다.** 거절한 룰은 업그레이드 후에도 다시 묻지 않고, 새 릴리스에서 추가된 프리셋만 다음 세션에 제안됩니다.
346
+ - 사용자가 이미 같은 유형(같은 티어·카테고리)의 룰을 직접 승인해 두었다면 그 프리셋은 제안하지 않습니다.
347
+ - 프리셋으로 등록한 룰은 **남의 통계를 내 것처럼 표시하지 않습니다.** 등록 직후에는 `preset (curated)`로 적히고, 이후 스캔에서 실제 발화와 위임 결과가 측정되면 그 수치로 대체됩니다. 위임 에러율이 기준을 넘으면 다른 룰과 똑같이 재검토 플래그가 붙습니다.
348
+ - 적용 범위는 `--global`과 `--project` 중 반드시 명시해야 합니다. 훅 환경은 non-TTY라 CLI가 직접 물을 수 없으므로, 모델이 사용자에게 확인한 뒤 플래그를 붙여 실행합니다.
349
+
300
350
  ## 🇰🇷 한국어 문체 지침
301
351
 
302
352
  Claude가 한국어로 쓸 때 나타나는 문체 결함(문장 성분 생략, 명사형 종결, 번역체, 엠대시 남용)을 교정하는 지침을 **세션 시작 시 한 번 주입합니다.**
@@ -588,6 +638,24 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
588
638
 
589
639
  ## 릴리스 노트
590
640
 
641
+ ### v3.34.0 (2026-09-10)
642
+ - **출력 언어를 설치할 때 정합니다.** 지금까지는 기록이 없으면 조용히 영어로 떨어져서, 한국어 사용자는 `mode ko`를 우연히 발견할 때까지 영어 리포트를 읽었습니다. 이제 터미널 설치에서는 시스템 로케일을 기본값으로 제시하고 한국어를 쓸지 물어보며, 비대화형 설치에서는 로케일 판정을 기록합니다. 한 번 기록되면 업그레이드해도 다시 묻지 않고, 스크립트 설치는 `CTS_LANG=ko|en`으로 고정할 수 있습니다.
643
+ - **로케일 판정의 결함을 고쳤습니다.** `LANG=en_US.UTF-8`이 지정돼 있어도 macOS에서는 시스템 로케일(`AppleLocale`)까지 읽어 한국어로 판정했습니다. 이제 POSIX 로케일 변수가 값을 갖고 있으면 그 값을 답으로 받아들이고, 변수가 모두 비어 있을 때에만 시스템 로케일을 봅니다. 이 대비책은 `LANG`이 비어 있는 macOS GUI 셸을 위한 것이었습니다.
644
+ - 언어는 설치 절차의 첫 단계입니다. 그 아래의 harness·doc2md·seed 안내가 모두 선택한 언어로 출력됩니다.
645
+
646
+ ### v3.33.0 (2026-09-10)
647
+ - **`seed`: 설치 직후부터 위임이 걸립니다.** 모델 피팅 랫쳇은 빈 파일로 시작해서, 사용자 로그에 같은 유형의 작업이 쌓이고 후보를 승인할 때까지 며칠간 위임이 한 건도 걸리지 않았습니다. 이제 패키지에 **모델 피팅 프리셋 9건**(`presets/model-rules.json`)과 **랫쳇 프리셋 6건**(`presets/ratchet-rules.json`)을 동봉하고, 설치·업그레이드 후 첫 세션에서 SessionStart 훅이 이를 모델에게 전달해 **한 건씩** 등록 여부를 묻습니다.
648
+ - **응답은 영구 기록됩니다.** 거절한 프리셋은 업그레이드 후에도 다시 묻지 않고, 새 릴리스에서 추가된 항목만 제안됩니다. 이미 같은 티어·카테고리의 룰을 승인해 두었다면 그 프리셋은 제안 대상에서 빠집니다. 명령: `seed` · `seed accept <id> --global|--project` · `seed skip <id>` · `seed reset`.
649
+ - **프리셋 룰은 남의 통계를 내 것처럼 적지 않습니다.** 등록 직후에는 `preset (curated)`로 표기하고, 이후 스캔이 실제 발화·위임 결과를 측정하면 그 수치로 대체합니다. 위임 에러율이 기준을 넘으면 다른 룰과 동일하게 재검토 플래그가 붙습니다.
650
+ - **랫쳇 프리셋을 양국어로 제공합니다.** `presets/ratchet-rules.md`를 `presets/ratchet-rules.json`으로 바꾸고 룰마다 한국어와 영어를 함께 담았습니다. 룰 텍스트는 모델 컨텍스트에 주입되므로, 한국어만 있으면 영어 세션의 응답까지 한국어로 끌려갑니다. 식별자는 한국어 원문에서 뽑아 영어 문장을 다듬어도 사용자의 응답이 유지됩니다.
651
+ - **룰 문장 템플릿을 한 곳으로 합쳤습니다.** route-scan과 seed가 같은 `modelRuleBaseText()`를 쓰므로, 문구를 고칠 때 두 생산자의 표현이 갈라질 수 없습니다.
652
+
653
+ ### v3.32.0 (2026-09-10)
654
+ - **컨텍스트 경고 기준을 200k 에서 500k 로 올렸습니다.** 설치 직후의 새 세션도 시스템 프롬프트와 파일 몇 개만 읽으면 200k 를 넘기므로, `⚠ Ctx 200k+` 는 거의 항상 떠 있는 칩이었고 그만큼 아무 정보도 주지 못했습니다. 칩은 `⚠ Ctx 500k+` 로 바뀌었고, 단일 요청이 실제로 500k 를 넘길 때만 뜹니다. `📦 Ctx` 세그먼트도 500k 를 넘겨야 노란색으로 바뀝니다. 1M 윈도를 쓰고 있다는 사실 자체는 더 이상 경고가 아닙니다.
655
+ - **예전 히스토리는 그대로 읽힙니다.** `⚠ Ctx 200k+` 칩과 구 디테일 문구(`Single-request context exceeded 200k`)도 계속 해석합니다.
656
+ - **doc2md 를 설치 때 자동으로 켭니다.** 훅 등록은 `settings.json` 항목 두 개일 뿐이고 문서를 읽지 않으면 비용이 발생하지 않는데, 그동안은 `doc2md on` 을 직접 찾아낸 사람만 썼습니다. 변환기(markitdown venv)는 설치에 몇 분이 걸리므로 터미널에서만 물어보고, 비대화형 설치에서는 명령만 안내합니다. 끄려면 `doc2md off` 또는 `CTS_NO_DOC2MD=1` 을 쓰십시오.
657
+ - **설치 직후 켜지는 기능과 직접 켜야 하는 기능을 문서에 표로 정리했습니다.** 수동으로 남은 항목은 `compact-window set`(Claude Code 설정에 값을 적고 적용 범위를 사람이 정해야 함)과 모델 피팅 룰 승인뿐입니다.
658
+
591
659
  ### v3.27.0 (2026-09-04)
592
660
  - **문서 경로를 프롬프트에 적으면 이제 실제로 걸립니다.** 3.26.x 의 `PreToolUse(Read)` 훅은 정작 목표한 형식에 닿지 못했습니다. Claude Code 가 pptx·xlsx·docx 를 이진 파일이라며 훅보다 먼저 거부하기 때문입니다(실측: `.pdf` Read 는 훅이 실행되고 `.pptx` Read 는 훅 기록이 남지 않습니다). 개입 지점을 `UserPromptSubmit` 으로 넓혀, 프롬프트에 적힌 경로를 변환한 뒤 변환본 경로를 컨텍스트로 넣습니다. `@경로`·따옴표·상대 경로를 모두 인식합니다.
593
661
  - **세션 시작 안내문을 추가했습니다.** 두 가지를 모델에게 알립니다. `Read` 가 이진 파일이라며 거부하면 `doc2md <경로>` 를 실행할 것, 그리고 사용자가 문서를 직접 첨부했으면 다음부터 경로로 달라고 안내할 것입니다. 첨부는 내용 전체가 컨텍스트에 실려 토큰을 크게 쓰는데, 어떤 훅 이벤트도 첨부 내용을 받지 못해 도구가 개입할 방법이 없습니다.
package/bin/cli.js CHANGED
@@ -87,6 +87,7 @@ function readUpdateChip() {
87
87
  const KNOWN_SUBCOMMANDS = new Set([
88
88
  'last', 'brief', 'history', 'handoff', 'install', 'uninstall', 'mode', 'korean',
89
89
  'doc2md', 'harness', 'route-scan', 'compact-window', 'update-check', 'upgrade',
90
+ 'seed',
90
91
  ]);
91
92
 
92
93
  const USAGE = `claude-token-saver — Claude Code token usage, cache health, and model routing
@@ -224,6 +225,13 @@ async function main() {
224
225
  return (await import('../src/commands/doc2md.js')).run({ args, hasFlag });
225
226
  }
226
227
 
228
+ // Subcommand: seed — register the bundled starter rules (model-fitting
229
+ // presets + curated ratchet rules), one answer at a time.
230
+ // claude-token-saver seed | seed accept <id> --global|--project | seed skip <id>
231
+ if (args[0] === 'seed') {
232
+ return (await import('../src/commands/seed.js')).run({ args, hasFlag });
233
+ }
234
+
227
235
  // Subcommand: harness — manage the project's CLAUDE.md harness rules.
228
236
  // claude-token-saver harness init # write CLAUDE.md (5 sections) + ratchet.md
229
237
  // claude-token-saver harness uninit # remove harness block from CLAUDE.md (backup kept)
@@ -499,14 +507,14 @@ async function main() {
499
507
  }
500
508
 
501
509
  // For statusline: attach a single-word chip only when there's something
502
- // actionable right now. 1M context is always shown; otherwise only fire
503
- // if the most recent session actually appears in the spike list.
510
+ // actionable right now. A context over the 500k warn line is always shown;
511
+ // otherwise only fire if the most recent session appears in the spike list.
504
512
  let spikeChip = null;
505
513
  let chipDetail = null;
506
514
  if (format === 'statusline') {
507
- if (contextWindow.size === '1M') {
515
+ if (contextWindow.overWarn) {
508
516
  spikeChip = chipForIssues([], contextWindow);
509
- chipDetail = `Single-request context exceeded 200k (max ${Math.round(contextWindow.maxContext / 1000)}k tokens)`;
517
+ chipDetail = `Single-request context exceeded 500k (max ${Math.round(contextWindow.maxContext / 1000)}k tokens)`;
510
518
  } else {
511
519
  const recentSession = sessions
512
520
  .slice()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.31.0",
3
+ "version": "3.34.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,115 @@
1
+ {
2
+ "note": [
3
+ "Curated tier-delegation presets bundled with the package — the model-fitting",
4
+ "counterpart to presets/ratchet-rules.md. These were promoted from the author's",
5
+ "own route-scan candidates over months of real sessions, then stripped of the",
6
+ "originating examples, project paths and recurrence counts: what carries over is",
7
+ "the shape of the work (category, tier, agent, cap), not the author's transcripts.",
8
+ "Each entry is offered one at a time at session start; nothing is registered",
9
+ "without the user saying yes. Caps are the defaults a scan would calibrate to;",
10
+ "later scans refresh the stats on whatever the user accepts.",
11
+ "Rule sentences are composed at accept time from modelRuleBaseText() so that a",
12
+ "reworded template never leaves this file stale."
13
+ ],
14
+ "presets": [
15
+ {
16
+ "id": "run-t2",
17
+ "tier": "T2",
18
+ "category": "run",
19
+ "label": "명령 실행 (빌드·테스트·git)",
20
+ "labelEn": "running commands (build/test/git)",
21
+ "agent": "haiku-runner",
22
+ "example": "npm test 돌려봐",
23
+ "exampleEn": "run npm test",
24
+ "budget": { "calls": 8, "out": 1500 }
25
+ },
26
+ {
27
+ "id": "run-t1",
28
+ "tier": "T1",
29
+ "category": "run",
30
+ "label": "명령 실행 (빌드·테스트·git)",
31
+ "labelEn": "running commands (build/test/git)",
32
+ "agent": "sonnet",
33
+ "example": "빌드하고 실패한 테스트만 보여줘",
34
+ "exampleEn": "build it and show only the failing tests",
35
+ "budget": { "calls": null, "out": 6000 }
36
+ },
37
+ {
38
+ "id": "explore-t2",
39
+ "tier": "T2",
40
+ "category": "explore",
41
+ "label": "탐색·조회 (파일/값 찾기)",
42
+ "labelEn": "lookup (finding files/values)",
43
+ "agent": "haiku-explore",
44
+ "example": "이 설정값 어느 파일에 있어?",
45
+ "exampleEn": "which file holds this config value?",
46
+ "budget": { "calls": 8, "out": 1500 }
47
+ },
48
+ {
49
+ "id": "explore-t1",
50
+ "tier": "T1",
51
+ "category": "explore",
52
+ "label": "탐색·조회 (파일/값 찾기)",
53
+ "labelEn": "lookup (finding files/values)",
54
+ "agent": "sonnet",
55
+ "example": "이 함수를 호출하는 곳 전부 찾아줘",
56
+ "exampleEn": "find every caller of this function",
57
+ "budget": { "calls": null, "out": 6000 }
58
+ },
59
+ {
60
+ "id": "check-t2",
61
+ "tier": "T2",
62
+ "category": "check",
63
+ "label": "상태 확인·검증",
64
+ "labelEn": "status checks / verification",
65
+ "agent": "haiku-explore",
66
+ "example": "지금 배포된 버전이 뭔지 확인해줘",
67
+ "exampleEn": "check which version is deployed right now",
68
+ "budget": { "calls": 8, "out": 1500 }
69
+ },
70
+ {
71
+ "id": "check-t1",
72
+ "tier": "T1",
73
+ "category": "check",
74
+ "label": "상태 확인·검증",
75
+ "labelEn": "status checks / verification",
76
+ "agent": "sonnet",
77
+ "example": "마이그레이션이 실제로 적용됐는지 확인해줘",
78
+ "exampleEn": "verify the migration actually applied",
79
+ "budget": { "calls": null, "out": 6000 }
80
+ },
81
+ {
82
+ "id": "paste-t2",
83
+ "tier": "T2",
84
+ "category": "paste",
85
+ "label": "붙여넣은 화면·로그 질문",
86
+ "labelEn": "questions about pasted screens/logs",
87
+ "agent": "haiku-explore",
88
+ "example": "이 에러 로그는 무슨 뜻이야?",
89
+ "exampleEn": "what does this error log mean?",
90
+ "budget": { "calls": 8, "out": 1500 }
91
+ },
92
+ {
93
+ "id": "paste-t1",
94
+ "tier": "T1",
95
+ "category": "paste",
96
+ "label": "붙여넣은 화면·로그 질문",
97
+ "labelEn": "questions about pasted screens/logs",
98
+ "agent": "sonnet",
99
+ "example": "이 스택트레이스에서 어느 모듈이 죽은 건지 짚어줘",
100
+ "exampleEn": "point out which module failed in this stack trace",
101
+ "budget": { "calls": null, "out": 8000 }
102
+ },
103
+ {
104
+ "id": "read-t2",
105
+ "tier": "T2",
106
+ "category": "read",
107
+ "label": "읽기·요약·설명",
108
+ "labelEn": "reading / summarizing / explaining",
109
+ "agent": "haiku-explore",
110
+ "example": "이 README 요약해줘",
111
+ "exampleEn": "summarize this README",
112
+ "budget": { "calls": 8, "out": 1900 }
113
+ }
114
+ ]
115
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "note": [
3
+ "Curated ratchet rules bundled with the package — promoted from mistakes that",
4
+ "actually recurred in the author's sessions, not from theory. `harness pull`",
5
+ "registers the whole set at once; `seed` offers them one at a time and records",
6
+ "each answer. Both read this file, so there is one source for the wording.",
7
+ "Each rule carries both languages because the text is injected into the model's",
8
+ "context, where a Korean-only rule would steer an English session into Korean.",
9
+ "Ids are derived from the `ko` text (the canonical one), so a rule keeps its",
10
+ "identity — and a user's 'no' — when the English wording is revised."
11
+ ],
12
+ "rules": [
13
+ {
14
+ "ko": "웹 fetch/search에서 robots·UA 차단('unable to fetch' 등)이 발생하면 → curl에 브라우저 UA(Mozilla/5.0 ... Chrome/...) 헤더를 붙여 재시도. reddit 등은 .json 엔드포인트 + UA 조합으로 접근 가능",
15
+ "en": "When a web fetch/search is blocked by robots or user-agent checks ('unable to fetch' and similar) → retry with curl carrying a browser UA header (Mozilla/5.0 ... Chrome/...). Sites like reddit also answer on their .json endpoints with that UA"
16
+ },
17
+ {
18
+ "ko": "ratchet 룰 promote 호출 직전 사용자에게 scope(project/global)를 먼저 묻는다 — Bash 환경은 non-TTY라 CLI readline 프롬프트가 안 뜨므로, 호출자(LLM)가 대신 묻고 --project/--global 플래그를 명시해 실행",
19
+ "en": "Ask the user for the scope (project or global) before calling ratchet promote — the Bash environment is non-TTY, so the CLI's readline prompt never appears; the caller (the model) has to ask and pass --project/--global explicitly"
20
+ },
21
+ {
22
+ "ko": "자동화 도구가 grep·정규식으로 찾는 마커 텍스트(헤더·앵커·키워드 라인)는 리팩터링·압축·수정 금지 — 정확 매칭 검출은 한 글자만 바뀌어도 깨짐",
23
+ "en": "Never refactor, compress or reword marker text that an automated tool finds by grep or regex (headings, anchors, keyword lines) — exact-match detection breaks on a single changed character"
24
+ },
25
+ {
26
+ "ko": "자동화 설계에서 vision/screenshot 루프가 보이면 → 호출 가능한 함수·API·MCP tool부터 찾는다. 통제 가능한 내부 도구는 무조건 API, 통제 불가 외부 SaaS만 vision 차선책. 인터페이스를 바꾼 뒤에 모델을 내린다 — 거꾸로 가면 토큰 폭발",
27
+ "en": "When an automation design calls for a vision/screenshot loop → look for a callable function, API or MCP tool first. Anything you control gets an API; vision is the fallback only for external SaaS you cannot change. Fix the interface before lowering the model — doing it the other way round explodes the token cost"
28
+ },
29
+ {
30
+ "ko": "입력 데이터(ground-truth) 변경 시 그에 의존하는 모든 파생 자산을 끝까지 재생성한다 — 중간 단계를 생략하면 stale 산출물이 새 입력 위에 얹혀 결과가 깨짐",
31
+ "en": "When the ground-truth input changes, regenerate every derived asset all the way through — skipping an intermediate step leaves a stale artifact sitting on top of the new input and the result breaks"
32
+ },
33
+ {
34
+ "ko": "탐색·조회·상태 확인·명령 실행 같은 단순 요청은 `model: haiku` 서브에이전트로 위임해 상위 모델 토큰을 아낀다 (읽기 전용 preset 에이전트가 이미 있으면 그걸 쓰고, 없으면 티어만 내려서 위임)",
35
+ "en": "Delegate simple requests — lookup, status checks, running commands — to a `model: haiku` subagent to keep the expensive model's tokens for real work (use an existing read-only preset agent when there is one; otherwise delegate with the tier lowered)"
36
+ }
37
+ ]
38
+ }
package/src/advice.js CHANGED
@@ -514,6 +514,8 @@ export const ISSUE_TIPS = {
514
514
  * need a fallback so history.js can still surface the right tip.
515
515
  */
516
516
  export const CHIP_TO_CODES = {
517
+ '⚠ Ctx 500k+': ['LARGE_INPUT_PER_REQUEST'],
518
+ // Legacy chip name (pre-v3.32, when the threshold was 200k).
517
519
  '⚠ Ctx 200k+': ['LARGE_INPUT_PER_REQUEST'],
518
520
  // Legacy chip name (pre-v2.18) — kept so `last`/`history` can still resolve
519
521
  // codes from history files written by older versions.
@@ -545,11 +547,12 @@ export const CAP_TIPS = {
545
547
  * shown to a global audience.
546
548
  */
547
549
  export function chipForIssues(issues, contextWindow) {
548
- // Fires on *actual usage* (a real request carried >210k input tokens), not
549
- // on the model merely supporting 1M current models are all 1M by default
550
- // with no price premium, so "1M ON" stopped being a meaningful alarm. The
551
- // meaningful signal is "your context genuinely exceeded 200k".
552
- if (contextWindow?.size === '1M') return '⚠ Ctx 200k+';
550
+ // Fires on *actual usage* (a real request carried more than
551
+ // CONTEXT_WARN_TOKENS), not on the model merely supporting 1M. The old 200k
552
+ // line fired on a brand-new session system prompt plus a couple of file
553
+ // reads already clears it so it warned constantly and told nobody
554
+ // anything. 500k is where the per-turn re-billing is worth interrupting for.
555
+ if (contextWindow?.overWarn) return '⚠ Ctx 500k+';
553
556
  const codes = issues.map((i) => i.code);
554
557
  if (codes.includes('LARGE_INPUT_PER_REQUEST')) return '⚠ Input spike';
555
558
  if (codes.includes('BUCKET_5M_DOMINANT') || codes.includes('BUCKET_5M_DOMINANT_GATEWAY')) return '⚠ 5m TTL';
@@ -16,9 +16,8 @@ class SkipStep extends Error {}
16
16
 
17
17
  export async function run({ hasFlag }) {
18
18
  const { installAll } = await import('../installer.js');
19
- const { userLanguage } = await import('../config.js');
19
+ const { userLanguage, languageDecided, setUserLanguage } = await import('../config.js');
20
20
  const { canPrompt, confirm } = await import('../prompt.js');
21
- const lang = userLanguage();
22
21
  const force = hasFlag('--force');
23
22
  // Two features below write to ~/.claude and cost tokens in every session,
24
23
  // so the install shows what they contain and asks before turning them on.
@@ -26,6 +25,45 @@ export async function run({ hasFlag }) {
26
25
  // fall through to the previous automatic defaults so an unattended upgrade
27
26
  // behaves exactly as it did before.
28
27
  const interactive = canPrompt() && !hasFlag('--yes') && !hasFlag('--no-input');
28
+
29
+ // Output language, decided first because every line below it — and every
30
+ // briefing the hooks inject from here on — is written in it. Until now it
31
+ // fell back to English with no question asked, so a Korean user read English
32
+ // reports until they happened to find `mode ko`.
33
+ //
34
+ // The locale is the default, not the answer: a terminal user gets to
35
+ // overrule it either way. An unattended install records the detected locale
36
+ // rather than leaving the setting blank, because "blank" silently means
37
+ // English — the one outcome a Korean-locale machine should not get by
38
+ // default. Already decided means never asked again.
39
+ try {
40
+ if (!languageDecided() && !process.env.CTS_LANG) {
41
+ const { koreanLocaleDetected } = await import('../korean-style.js');
42
+ const detected = koreanLocaleDetected() ? 'ko' : 'en';
43
+ console.log('');
44
+ console.log(' language: reports, warnings and session briefings are written in this language.');
45
+ console.log(` detected locale: ${detected === 'ko' ? 'Korean' : 'not Korean (English)'}`);
46
+ let chosen = detected;
47
+ if (interactive) {
48
+ const ko = await confirm(' Use Korean? (no = English)', { defaultValue: detected === 'ko' });
49
+ chosen = ko ? 'ko' : 'en';
50
+ }
51
+ setUserLanguage(chosen);
52
+ console.log(` language: set to ${chosen === 'ko' ? '한국어' : 'English'} — change it any time with \`claude-token-saver mode lang=${chosen === 'ko' ? 'en' : 'ko'}\``);
53
+ } else if (process.env.CTS_LANG) {
54
+ // Escape hatch for scripted installs, which cannot answer a prompt but
55
+ // do know which language the machine's user reads.
56
+ const forced = setUserLanguage(process.env.CTS_LANG);
57
+ console.log('');
58
+ console.log(forced
59
+ ? ` language: set to ${forced} (CTS_LANG)`
60
+ : ` language: ignored CTS_LANG=${process.env.CTS_LANG} — use 'ko' or 'en'`);
61
+ }
62
+ } catch (e) {
63
+ debug('install:language', e); // the English fallback still works
64
+ }
65
+ const lang = userLanguage();
66
+
29
67
  const print = (kind, r) => {
30
68
  const verb = r.action === 'exists' ? 'already exists' : r.action;
31
69
  console.log(` ${kind}: ${r.path} (${verb})`);
@@ -214,6 +252,73 @@ export async function run({ hasFlag }) {
214
252
  debug('install:korean-style', e); // optional feature; never fail install
215
253
  }
216
254
 
255
+ // doc2md. The hook itself is two entries in settings.json and costs
256
+ // nothing until a document shows up, so it goes on with the rest of the
257
+ // install instead of waiting for the user to discover `doc2md on` — the
258
+ // people who would save the most tokens are the ones who never find it.
259
+ // The converter is the expensive half (a venv plus a pip install), so it
260
+ // is only built when a human is attached; an unattended postinstall just
261
+ // prints the command.
262
+ try {
263
+ const { createRequire } = await import('node:module');
264
+ const require = createRequire(import.meta.url);
265
+ const doc2md = require('../doc2md.cjs');
266
+ if (process.env.CTS_NO_DOC2MD === '1') {
267
+ console.log('');
268
+ console.log(lang === 'ko'
269
+ ? ' doc2md: CTS_NO_DOC2MD=1 이므로 건너뜁니다 (나중에 `doc2md on`).'
270
+ : ' doc2md: skipped (CTS_NO_DOC2MD=1) — run `doc2md on` later.');
271
+ } else {
272
+ const { installDoc2mdHook } = await import('../installer.js');
273
+ const res = installDoc2mdHook();
274
+ console.log('');
275
+ console.log(res.action === 'skipped'
276
+ ? ` doc2md: ${res.reason}`
277
+ : ` doc2md: Read/Write hook ${res.action} (${res.path})`);
278
+ console.log(lang === 'ko'
279
+ ? ` 대상 형식: ${doc2md.TARGET_EXTENSIONS.join(' ')} — 모델이 읽기 전에 Markdown으로 변환합니다.`
280
+ : ` formats: ${doc2md.TARGET_EXTENSIONS.join(' ')} — converted to Markdown before the model reads them.`);
281
+ let python = doc2md.findInterpreter();
282
+ if (!python && interactive) {
283
+ const build = await confirm(lang === 'ko'
284
+ ? ' 변환기(markitdown)를 지금 설치할까요? 몇 분 걸립니다.'
285
+ : ' Install the converter (markitdown) now? Takes a few minutes.', { defaultValue: true });
286
+ if (build) {
287
+ const cres = doc2md.installConverter({ onProgress: (m) => console.log(` ${m}`) });
288
+ if (cres.ok) python = cres.python;
289
+ else console.log(` ${cres.reason}: ${cres.detail || ''}`);
290
+ }
291
+ }
292
+ console.log(python
293
+ ? (lang === 'ko' ? ` 변환기: ${python}` : ` converter: ${python}`)
294
+ : (lang === 'ko'
295
+ ? ` 변환기가 없습니다 — \`${doc2md.INSTALL_HINT}\` 를 실행해야 훅이 동작합니다.`
296
+ : ` converter missing — run \`${doc2md.INSTALL_HINT}\` or the hook does nothing.`));
297
+ }
298
+ } catch (e) {
299
+ debug('install:doc2md', e); // optional feature; never fail install
300
+ }
301
+
302
+ // Starter rules. The asking itself belongs to the first session — a
303
+ // postinstall cannot hold a conversation, and a prompt here would be
304
+ // answered by whoever happens to be at the terminal for a set of rules
305
+ // they have not read. All the install does is say what is waiting.
306
+ try {
307
+ const { pendingSeeds } = await import('../seed-rules.js');
308
+ const pending = pendingSeeds();
309
+ if (pending.length > 0) {
310
+ console.log('');
311
+ console.log(lang === 'ko'
312
+ ? ` seed: 추천 룰 ${pending.length}건이 대기 중입니다 (모델 피팅 + 랫쳇 프리셋).`
313
+ : ` seed: ${pending.length} recommended rule(s) are waiting (model-fitting + ratchet presets).`);
314
+ console.log(lang === 'ko'
315
+ ? ' 다음 Claude Code 세션에서 한 건씩 등록할지 물어봅니다. 지금 보려면: claude-token-saver seed'
316
+ : ' the next Claude Code session asks about them one at a time. See them now: claude-token-saver seed');
317
+ }
318
+ } catch (e) {
319
+ debug('install:seed-offer', e); // optional feature; never fail install
320
+ }
321
+
217
322
  console.log('');
218
323
  console.log('Open Claude Code in any directory and just mention:');
219
324
  console.log(' "cache hit rate" / "1M context" / "5H cap" — the skill auto-activates.');