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 +72 -4
- package/README.md +72 -4
- package/bin/cli.js +12 -4
- package/package.json +1 -1
- package/presets/model-rules.json +115 -0
- package/presets/ratchet-rules.json +38 -0
- package/src/advice.js +8 -5
- package/src/commands/install.js +107 -2
- package/src/commands/route-scan.js +25 -2
- package/src/commands/seed.js +105 -0
- package/src/config.js +25 -0
- package/src/demo.js +8 -7
- package/src/formatters/statusline.js +3 -1
- package/src/formatters/table.js +7 -5
- package/src/harness.js +21 -8
- package/src/history.js +6 -3
- package/src/installer.js +5 -4
- package/src/korean-style.js +11 -3
- package/src/model-rules.js +34 -5
- package/src/route-scan.js +6 -7
- package/src/seed-rules.js +280 -0
- package/src/stats.js +17 -4
- package/presets/ratchet-rules.md +0 -16
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
503
|
-
// if the most recent session
|
|
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.
|
|
515
|
+
if (contextWindow.overWarn) {
|
|
508
516
|
spikeChip = chipForIssues([], contextWindow);
|
|
509
|
-
chipDetail = `Single-request context exceeded
|
|
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.
|
|
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
|
|
549
|
-
// on the model merely supporting 1M
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
|
|
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';
|
package/src/commands/install.js
CHANGED
|
@@ -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.');
|