claude-token-saver 3.23.3 β 3.25.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 +46 -1
- package/README.md +46 -1
- package/bin/cli.js +51 -1
- package/package.json +1 -1
- package/src/commands/korean.js +145 -2
- package/src/commands/route-scan.js +48 -0
- package/src/commands/update-check.js +77 -0
- package/src/commands/upgrade.js +68 -0
- package/src/formatters/statusline.js +42 -2
- package/src/installer.js +62 -0
- package/src/korean-lint.cjs +244 -0
- package/src/korean-style.js +22 -2
- package/src/update-check.js +198 -0
package/README.en.md
CHANGED
|
@@ -130,6 +130,8 @@ With an empty ledger (no measured delegation yet) row 1 is not drawn and the lay
|
|
|
130
130
|
| `β¦ current` / `π
weekly` | 5-hour / 7-day rate-limit window usage + reset time |
|
|
131
131
|
| `π¦` | Context usage (e.g. `Ctx 68% of 1M`) β colored by fill. Current models default to 1M with no premium, but token volume itself drives per-turn cost and 5H/7D burn |
|
|
132
132
|
| `π°` | Cumulative savings from prompt caching β a **different** number from row 1's `π` (model routing) |
|
|
133
|
+
| `v3.24.0` | The version you are running. Gray, at the tail, when it is the latest one |
|
|
134
|
+
| `β¬ v3.24.0 β 3.25.0` | A newer release exists. Actionable, so it moves to the front of the line ([Update notifications](#-update-notifications)) |
|
|
133
135
|
|
|
134
136
|
When something is wrong, a **warning chip leads the line**:
|
|
135
137
|
|
|
@@ -158,7 +160,12 @@ Run these in your shell (inside Claude Code, the `/claude-token-saver` Skill is
|
|
|
158
160
|
| `claude-token-saver route-scan` | Detect recurring easy work on expensive models β propose haiku-delegation ratchet rules (below) |
|
|
159
161
|
| `claude-token-saver route-scan savings` | The routing-savings ledger β per-model-change rollup + per-run log (the evidence behind the figure) |
|
|
160
162
|
| `claude-token-saver compact-window` | Warn when a 1M-context session has no auto-compact cap β pin 400k with `set` (below) |
|
|
161
|
-
| `claude-token-saver korean on\|off\|status` | Inject Korean writing guidance at session start (below) |
|
|
163
|
+
| `claude-token-saver korean on\|off\|status` | Inject Korean writing guidance at session start and install the write-time check (below) |
|
|
164
|
+
| `claude-token-saver korean lint block\|warn\|off` | How the write-time check handles findings |
|
|
165
|
+
| `claude-token-saver korean lint scope all\|prose` | Check every text file, or documents only |
|
|
166
|
+
| `claude-token-saver --version` | Print the installed version |
|
|
167
|
+
| `claude-token-saver update-check` | Is a newer version out? (`--refresh` to ask now, `--dismiss` to mute this version's offer) |
|
|
168
|
+
| `claude-token-saver upgrade` | Install the latest release with the package manager that installed this copy (`--print` shows the command only) |
|
|
162
169
|
| `claude-token-saver install` | Manually register Skill + statusline |
|
|
163
170
|
|
|
164
171
|
Switch output language with `mode ko` / `mode en` (English default; statusline chips stay symbolic).
|
|
@@ -182,6 +189,16 @@ Switch output language with `mode ko` / `mode en` (English default; statusline c
|
|
|
182
189
|
| `--install-hook` / `--uninstall-hook` | Manage the PostToolUse hook | β |
|
|
183
190
|
</details>
|
|
184
191
|
|
|
192
|
+
## β¬ Update notifications
|
|
193
|
+
|
|
194
|
+
A statusline cannot open a dialog, and it re-renders every ~300ms, so it can never touch the network while drawing. The notification is therefore split in two:
|
|
195
|
+
|
|
196
|
+
- **The statusline tells you.** Up to date: a quiet gray `v3.24.0` at the tail. Newer release out: `β¬ v3.24.0 β 3.25.0` in yellow, moved to the front. Never red β nothing is broken.
|
|
197
|
+
- **Session start asks you.** On a new session or `/clear`, the SessionStart hook injects one line telling the model a newer version exists and to ask before installing anything. Only after you agree does it run `claude-token-saver upgrade`.
|
|
198
|
+
- **Declining sticks.** `claude-token-saver update-check --dismiss` mutes the offer for that version; the next release asks again. The statusline chip stays β you declined the question, not the fact.
|
|
199
|
+
|
|
200
|
+
The registry lookup runs at most once every 24h in a detached background process and only ever writes a cache file (`update-check.json`) β the same shape npm's `update-notifier` uses. A failed check still stamps its timestamp, so an offline machine backs off instead of retrying on every render. Turn checks off entirely with `CTS_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER`.
|
|
201
|
+
|
|
185
202
|
## π
· Harness mode
|
|
186
203
|
|
|
187
204
|
Bootstrap five engineering principles (Ratchet Β· Evidence Β· PEV Β· Structured Task Β· Default Safe Path) into `CLAUDE.md` with one command; the statusline scores it as `π
· 5/5`. When the same error keeps recurring, a `π
·β ratchet?` nudge appears so you can promote it to a rule.
|
|
@@ -313,6 +330,29 @@ Three things change. Clauses chained with em dashes become separate sentences, s
|
|
|
313
330
|
|
|
314
331
|
The technical content is identical in both. The guidance touches sentence construction only, not judgement or accuracy: the answer does not change, it just stops needing a second read. In a channel people scroll through, that difference cuts follow-up questions β and the tokens those follow-ups would have cost.
|
|
315
332
|
|
|
333
|
+
### The write-time check (v3.24.0)
|
|
334
|
+
|
|
335
|
+
Injecting the guidance once at session start turned out to be half the job. The model reads it, then writes dozens of files over the next hours with nothing re-reading the output. Sessions with the guidance active still shipped violations into documents, and it surfaced only when a human read the finished artifact. An August 2026 fix reworded the scope sentence to address this; it recurred, because rewording an instruction does not add a checkpoint.
|
|
336
|
+
|
|
337
|
+
From v3.24.0 `korean on` also installs a PostToolUse hook. It opens the file the model just wrote, runs the clauses a machine can decide, and hands any findings back. The file is already saved, so nothing is lost β the model fixes it on the spot.
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
claude-token-saver korean lint block # default: findings are handed back as blocking feedback
|
|
341
|
+
claude-token-saver korean lint warn # print findings, do not block
|
|
342
|
+
claude-token-saver korean lint off # disable the check
|
|
343
|
+
|
|
344
|
+
claude-token-saver korean lint scope all # default: every text file the session writes
|
|
345
|
+
claude-token-saver korean lint scope prose # documents only
|
|
346
|
+
|
|
347
|
+
claude-token-saver korean lint docs/*.md # check files already on disk
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Checked: 15 figurative phrases, translationese markers, separators (`β`Β·`γ
‘`Β·`|`), three or more `μ` particles in one phrase, and a period after a nominal ending. Clauses that need judgement stay with the guidance text.
|
|
351
|
+
|
|
352
|
+
The default `all` scope covers code comments, UI strings, subtitles, templates, and build output, not just documents. The vendored guidance exempts comments, but comments are read by people and generated artifacts (PDF, HTML) are assembled from those strings, so exempting them reopens the exact gap that was reported. Only installed dependencies, VCS internals, lockfiles, and binary or image files are skipped; `dist/` and `build/` are checked. `korean lint scope prose` restores the narrow reading.
|
|
353
|
+
|
|
354
|
+
The scope sentence in the injected guidance is generated from the same setting, so the model is never told one rule while being corrected against another.
|
|
355
|
+
|
|
316
356
|
### The encoding rule that ships with it (v3.23.2)
|
|
317
357
|
|
|
318
358
|
Alongside the writing guidance, one more line is injected: **non-ASCII strings in tool-call parameters must be written as literal UTF-8, never as `\uXXXX` unicode escapes.**
|
|
@@ -414,6 +454,11 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
|
|
|
414
454
|
|
|
415
455
|
## Release notes
|
|
416
456
|
|
|
457
|
+
### v3.25.0 (2026-09-04)
|
|
458
|
+
- **The statusline now shows which version is running** β until now the version lived only in the table report's footer, so "which version am I on" meant running a full report. A `--version` flag was added alongside it.
|
|
459
|
+
- **A new release asks at session start** β a statusline cannot open a dialog, so telling and asking are split. The statusline only reports (`β¬ v3.24.0 β 3.25.0`); the SessionStart hook injects a line telling the model to ask the user whether to upgrade. On yes it runs `claude-token-saver upgrade`, which uses the package manager that installed this copy; on no, `update-check --dismiss` mutes that version until a newer one ships.
|
|
460
|
+
- **The check never blocks a render** β the registry lookup runs at most once every 24h in a detached background process, and the render path only reads the cache file. A failed check still stamps its timestamp, so an offline machine backs off instead of retrying every render. Disable with `CTS_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER`.
|
|
461
|
+
|
|
417
462
|
### v3.21.0 (2026-08-22)
|
|
418
463
|
- **The install shows what it is about to enable, then asks** β the harness 5 principles and the Korean writing guidance used to be switched on by the installer, leaving the user with the result rather than the choice. The install now prints the five principle headings, and for the Korean guidance what it changes plus its per-session cost and source, before asking. Locale detection is demoted from an answer to the question's default, so an English-locale machine used for Korean work can enable it on the spot.
|
|
419
464
|
- **Unattended installs behave exactly as before** β npm `postinstall`, CI, piped stdin and `CTS_NO_INPUT=1` skip the question and apply the old defaults, because a blocked prompt hangs an install. `--yes` and `--no-input` force that path explicitly. A non-interactive install on a non-Korean machine now leaves the setting undecided instead of recording an answer nobody gave, so a later run at a terminal still asks.
|
package/README.md
CHANGED
|
@@ -129,6 +129,8 @@ npm i -g claude-token-saver
|
|
|
129
129
|
| `β¦ current` / `π
weekly` | 5μκ° / 7μΌ rate-limit μλ μ¬μ©λ₯ + 리μ
μκ° |
|
|
130
130
|
| `π¦` | 컨ν
μ€νΈ μ¬μ©λ₯ μ
λλ€(μ: `Ctx 68% of 1M`). μ¬μ©λ₯ μ λ°λΌ λ
ΉμΒ·λ
ΈλμΒ·λΉ¨κ°μμΌλ‘ νμν©λλ€. μ΅μ λͺ¨λΈμ 1M 컨ν
μ€νΈκ° κΈ°λ³Έμ΄κ³ λ³λ μκΈμ΄ λΆμ§ μμ§λ§, ν ν°λ μμ²΄κ° ν΄λΉ λΉμ©κ³Ό 5μκ°Β·7μΌ νλλ₯Ό λΉ λ₯΄κ² μλͺ¨μν΅λλ€ |
|
|
131
131
|
| `π°` | ν둬ννΈ μΊμκ° μ μ½ν΄ μ€ λμ κΈμ‘μ
λλ€. 첫째 μ€μ `π`(λͺ¨λΈ λΌμ°ν
μ κ°μ‘)μλ **μλ‘ λ€λ₯Έ μμΉμ
λλ€** |
|
|
132
|
+
| `v3.24.0` | μ§κΈ μ€ν μ€μΈ claude-token-saverμ λ²μ μ
λλ€. μ΅μ μ΄λ©΄ νμμΌλ‘ μ€ λμ μ‘°μ©ν λμ
λλ€ |
|
|
133
|
+
| `β¬ v3.24.0 β 3.25.0` | μ λ²μ μ΄ λ°°ν¬λμ΄ μλ€λ νμμ
λλ€. μ‘°μΉκ° νμν μΉ©μ΄λ―λ‘ μ€ μμͺ½μΌλ‘ μ¬λΌμ΅λλ€ ([μ
λ°μ΄νΈ μλ΄](#-μ
λ°μ΄νΈ-μλ΄)) |
|
|
132
134
|
|
|
133
135
|
λ¬Έμ κ° κ°μ§λλ©΄ **κ²½κ³ μΉ©μ μ€ λ§¨ μμ** λΆμ
λλ€.
|
|
134
136
|
|
|
@@ -157,11 +159,26 @@ Claude Code μμμ `/claude-token-saver` Skillμ μ€ννκ±°λ, μΉ©μ μ
|
|
|
157
159
|
| `claude-token-saver route-scan` | μμ λͺ¨λΈμ΄ λ°λ³΅ μ²λ¦¬ν μ¬μ΄ μμ
μ κ°μ§ν΄ haiku μμ λ«μ³ λ£°μ μ μν©λλ€ (μλ μ°Έκ³ ) |
|
|
158
160
|
| `claude-token-saver route-scan savings` | λΌμ°ν
μ κ° μμ₯μ
λλ€. λͺ¨λΈ μ΄λλ³ ν©κ³μ μ€νλ³ λ΄μμ ν¨κ» λ³΄μ¬ μ£Όλ©°, νμλλ κΈμ‘μ κ·Όκ±°κ° λ©λλ€ |
|
|
159
161
|
| `claude-token-saver compact-window` | 1M 컨ν
μ€νΈλ₯Ό μ°λ©΄μ μλ μμΆ μ°½μ΄ μ€μ λμ§ μμμΌλ©΄ κ²½κ³ νκ³ , `set`μΌλ‘ 40λ§μ κ³ μ ν©λλ€ (μλ μ°Έκ³ ) |
|
|
160
|
-
| `claude-token-saver korean on\|off\|status` | νκ΅μ΄ 문체 μ§μΉ¨μ μΈμ
μμ μ
|
|
162
|
+
| `claude-token-saver korean on\|off\|status` | νκ΅μ΄ 문체 μ§μΉ¨μ μΈμ
μμ μ μ£Όμ
νκ³ , μ°κΈ° μμ κ²μ¬λ₯Ό ν¨κ» μ€μΉν©λλ€ (μλ μ°Έκ³ ) |
|
|
163
|
+
| `claude-token-saver korean lint block\|warn\|off` | μ°κΈ° μμ κ²μ¬κ° μλ°μ μ΄λ»κ² μ²λ¦¬ν μ§ μ ν©λλ€ |
|
|
164
|
+
| `claude-token-saver korean lint scope all\|prose` | κ²μ¬ λ²μλ₯Ό λͺ¨λ ν
μ€νΈ νμΌκ³Ό λ¬Έμ μ μ© μ¬μ΄μμ κ³ λ¦
λλ€ |
|
|
165
|
+
| `claude-token-saver --version` | μ€μΉλ λ²μ μ μΆλ ₯ν©λλ€ |
|
|
166
|
+
| `claude-token-saver update-check` | μ λ²μ μ΄ μλμ§ νμΈν©λλ€ (`--refresh`λ‘ μ¦μ μ‘°ν, `--dismiss`λ‘ κ·Έ λ²μ μλ΄ λκΈ°) |
|
|
167
|
+
| `claude-token-saver upgrade` | μ€μΉ κ²½λ‘μ λ§λ λͺ
λ ΉμΌλ‘ μ΅μ λ²μ μ μ€μΉν©λλ€ (`--print`λ‘ μ€ν μμ΄ λͺ
λ Ήλ§ νμΈ) |
|
|
161
168
|
| `claude-token-saver install` | SkillΒ·statusline μλ λ±λ‘ |
|
|
162
169
|
|
|
163
170
|
μΆλ ₯ μΈμ΄λ `mode ko`μ `mode en`μΌλ‘ μ νν©λλ€. κΈ°λ³Έκ°μ μμ΄μ΄λ©°, statuslineμ μΉ©μ μΈμ λ κΈ°νΈλ‘ νμν©λλ€. μ 체 μ΅μ
μ [μλ¬Έ README](./README.en.md#options)λ₯Ό μ°Έκ³ νμμμ€.
|
|
164
171
|
|
|
172
|
+
## β¬ μ
λ°μ΄νΈ μλ΄
|
|
173
|
+
|
|
174
|
+
statuslineμ λν μμλ₯Ό λμΈ μ μκ³ , 300λ°λ¦¬μ΄λ§λ€ λ€μ κ·Έλ €μ§κΈ° λλ¬Έμ 그리λ μμ μ λ€νΈμν¬λ₯Ό μΈ μλ μμ΅λλ€. κ·Έλμ μλ΄λ₯Ό λ μ§μ μΌλ‘ λλμμ΅λλ€.
|
|
175
|
+
|
|
176
|
+
- **statuslineμ μλ¦¬κΈ°λ§ ν©λλ€.** μ΅μ λ²μ μ΄λ©΄ μ€ λμ `v3.24.0`μ νμμΌλ‘ μ‘°μ©ν νμνκ³ , μ λ²μ μ΄ μμΌλ©΄ `β¬ v3.24.0 β 3.25.0`μ μ€ μμͺ½μ λ
ΈλμμΌλ‘ μ¬λ¦½λλ€. λΉ¨κ°μμ μ°μ§ μμ΅λλ€. 무μλ κ³ μ₯ λ μνκ° μλκΈ° λλ¬Έμ
λλ€.
|
|
177
|
+
- **묻λ μΌμ μΈμ
μμμμ ν©λλ€.** μ μΈμ
μ΄λ `/clear` μμ μ SessionStart ν
μ΄ "μ λ²μ μ΄ μμΌλ μ¬μ©μμκ² μ
κ·Έλ μ΄λν μ§ λ¬Όμ΄λ³΄λΌ"λ ν μ€μ λͺ¨λΈμκ² μ£Όμ
ν©λλ€. λͺ¨λΈμ μ¬μ©μμκ² νμΈν λ€μλ§ `claude-token-saver upgrade`λ₯Ό μ€νν©λλ€. λ¬»μ§ μκ³ μ€μΉνμ§ μμ΅λλ€.
|
|
178
|
+
- **κ±°μ μ κΈ°μ΅ν©λλ€.** μ¬μ©μκ° μμΉ μμΌλ©΄ `claude-token-saver update-check --dismiss`λ‘ κ·Έ λ²μ μ λ¬»μ§ μλλ‘ μ€μ ν©λλ€. λ μλ‘μ΄ λ²μ μ΄ λ°°ν¬λλ©΄ λ€μ 묻μ΅λλ€. statusline μΉ©μ κ·Έλλ‘ λ¨μ΅λλ€. κ±°μ ν κ²μ μ§λ¬Έμ΄μ§, μ λ²μ μ΄ μλ€λ μ¬μ€μ΄ μλκΈ° λλ¬Έμ
λλ€.
|
|
179
|
+
|
|
180
|
+
λ²μ μ‘°νλ 24μκ°μ ν λ², λΆλ¦¬λ λ°±κ·ΈλΌμ΄λ νλ‘μΈμ€κ° μννκ³ κ²°κ³Όλ§ νμΌμ λ¨κΉλλ€(`update-check.json`). μ΄ λ°©μμ npmμ `update-notifier`κ° μ°λ κ²κ³Ό κ°μ΅λλ€. λ€νΈμν¬κ° λ겨 μμ΄λ μ€ν¨ μκ°μ κΈ°λ‘ν΄ λλ―λ‘ λ§€ λ λλ§λ€ μ¬μλνμ§ μμ΅λλ€. νμΈ μ체λ₯Ό λλ €λ©΄ νκ²½ λ³μ `CTS_NO_UPDATE_CHECK=1` λλ `NO_UPDATE_NOTIFIER`λ₯Ό μ€μ νμμμ€.
|
|
181
|
+
|
|
165
182
|
## π
· Harness λͺ¨λ
|
|
166
183
|
|
|
167
184
|
λ€μ― μμΉ(Ratchet Β· Evidence Β· PEV Β· Structured Task Β· Default Safe Path)μ ν μ€ λͺ
λ ΉμΌλ‘ `CLAUDE.md`μ μ
μ
νκ³ statuslineμ΄ `π
· 5/5`λ‘ μ μνν©λλ€. κ°μ μλ¬κ° λ°λ³΅λλ©΄ `π
·β ratchet?` μλ¦Όμ΄ λ μ λ£°λ‘ μΉκ²©ν μ μμ΅λλ€.
|
|
@@ -303,6 +320,29 @@ Claude Codeμ output styleλ‘λ κ°μ μΌμ ν μ μμ§λ§, output style
|
|
|
303
320
|
|
|
304
321
|
κΈ°μ μ λ΄μ©μ μμͺ½μ΄ λμΌν©λλ€. μ§μΉ¨μ νλ¨μ΄λ μ νλκ° μλλΌ λ¬Έμ₯μ μμ±λμλ§ κ΄μ¬νλ―λ‘, λ΅μ΄ λ¬λΌμ§λ κ²μ΄ μλλΌ κ°μ λ΅μ λ€μ μ½μ§ μμλ λλ ννλ‘ λ§λ€μ΄ μ€λλ€. μ¬λμ²λΌ μ¬λμ΄ μ€ν¬λ‘€νλ©° μ½λ μ±λμμλ μ΄ μ°¨μ΄κ° λ묻λ νμλ₯Ό μ€μ΄κ³ , λλ¬»μ§ μλ λ§νΌ ν ν°λ μλλλ€.
|
|
305
322
|
|
|
323
|
+
### μ°κΈ° μμ κ²μ¬ (v3.24.0)
|
|
324
|
+
|
|
325
|
+
μ§μΉ¨μ μΈμ
μμμ ν λ² λ£λ κ²λ§μΌλ‘λ λΆμ‘±νμ΅λλ€. λͺ¨λΈμ μ§μΉ¨μ ν λ² μ½κ³ κ·Έ λ€λ‘ νμΌ μμ κ°λ₯Ό μ°λλ°, κ·Έλμ κ²°κ³Όλ¬Όμ λ€μ μ½μ΄ 보λ λ¨κ³κ° μμμ΅λλ€. κ·Έλμ μ§μΉ¨μ΄ μΌμ§ μΈμ
μ΄ κ·μ½μ μ΄κΈλλ λ¬Έμ₯μ λ¬Έμμ κ·Έλλ‘ μ€μ΄ 보λκ³ , μ¬λμ΄ μμ±λ³Έμ μ½μ λμμΌ λλ¬λ¬μ΅λλ€. 2026λ
8μμ μ μ© λ²μ λ¬Έμ₯μ κ³ μ³μ κ°μ λ¬Έμ λ₯Ό μ‘μΌλ € νμ§λ§, λ¬Έμ₯μ κ³ μ³λ κ²μ¬ λ¨κ³κ° μλ€λ 쑰건μ κ·Έλλ‘μκΈ° λλ¬Έμ μ¬λ°νμ΅λλ€.
|
|
326
|
+
|
|
327
|
+
v3.24.0λΆν° `korean on`μ΄ PostToolUse ν
μ ν¨κ» μ€μΉν©λλ€. λͺ¨λΈμ΄ λ°©κΈ μ΄ νμΌμ μ΄μ΄μ κΈ°κ³λ‘ νμ ν μ μλ μ‘°νμ κ²μ¬νκ³ , μλ°μ΄ μμΌλ©΄ λͺ¨λΈμκ² λλλ € 보λ
λλ€. νμΌμ μ΄λ―Έ μ μ₯λ λ€μ΄λ―λ‘ μλ κ²μ μκ³ , λͺ¨λΈμ΄ κ·Έ μ리μμ κ³ μΉ©λλ€.
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
claude-token-saver korean lint block # κΈ°λ³Έκ°. μλ°μ λλλ € λ³΄λ΄ κ³ μΉκ² ν©λλ€
|
|
331
|
+
claude-token-saver korean lint warn # μλ¦¬κΈ°λ§ νκ³ μ§νμ λ§μ§ μμ΅λλ€
|
|
332
|
+
claude-token-saver korean lint off # κ²μ¬νμ§ μμ΅λλ€
|
|
333
|
+
|
|
334
|
+
claude-token-saver korean lint scope all # κΈ°λ³Έκ°. μΈμ
μ΄ μ΄ λͺ¨λ ν
μ€νΈ νμΌ
|
|
335
|
+
claude-token-saver korean lint scope prose # λ§ν¬λ€μ΄Β·ν
μ€νΈ λ¬Έμλ§
|
|
336
|
+
|
|
337
|
+
claude-token-saver korean lint docs/*.md # μ΄λ―Έ μ μ₯λ νμΌμ μ§μ κ²μ¬
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
κ²μ¬ νλͺ©μ μ¬λμ΄ νμ ν νμκ° μλ κ²λ€μ
λλ€. λΉμ μ΄ν 15μ’
(`~λ μ리`, `~μ νλ¦`, `λΏλλ€`, `κ±·μ΄λ΄λ€`, `λ°λͺ©μ μ‘λ€` λ±), λ²μ체 νμ§(`~μ λν`, `~λ₯Ό μν`, `~λμ΄μ§`), ꡬλΆμ(`β`Β·`γ
‘`Β·`|`), ν ꡬμ μΈ λ² μ΄μ μ΄μ΄μ§λ μ‘°μ¬ `μ`, λͺ
μ¬ν μ’
κ²° λ€μ λ§μΉ¨νμ
λλ€. νλ¨μ΄ νμν μ‘°ν(μ±λΆ μλ΅, νμμ΄ μ ν)μ κ·Έλλ‘ μ§μΉ¨μ΄ λ΄λΉν©λλ€.
|
|
341
|
+
|
|
342
|
+
κΈ°λ³Έ λ²μ `all`μ λ¬ΈμλΏ μλλΌ **μ½λ μ£Όμκ³Ό νλ©΄μ λκ°λ λ¬Έμμ΄, μλ§Β·ν
νλ¦Ώ, μμ± κ²°κ³Όλ¬ΌκΉμ§** κ²μ¬ν©λλ€. λ²€λλ§ν μ§μΉ¨ μλ¬Έμ μ½λ μ£Όμμ μμΈλ‘ λμ§λ§, μ£Όμλ μ¬λμ΄ μ½κ³ PDFΒ·HTML κ°μ μ°μΆλ¬Όμ κ·Έ λ¬Έμμ΄λ€λ‘ 쑰립λκΈ° λλ¬Έμ μμΈλ‘ λλ©΄ μ νν λ¬Έμ κ° λλ κ²½λ‘κ° λ€μ μ΄λ¦½λλ€. κ²μ¬μμ λΉ μ§λ κ²μ μ€μΉλ μμ‘΄μ±(`node_modules`), VCS λ΄λΆ, λ½ νμΌ, κ·Έλ¦¬κ³ μ΄μ§Β·μ΄λ―Έμ§ νμΌλΏμ
λλ€. `dist`λ `build` κ°μ μ°μΆλ¬Ό λλ ν°λ¦¬λ κ²μ¬ν©λλ€. μλ¬Έ κ·μ½λλ‘ λ¬Έμλ§ λ³΄κ³ μΆμΌλ©΄ `korean lint scope prose`λ‘ λλ립λλ€.
|
|
343
|
+
|
|
344
|
+
μ£Όμ
λ¬Έμ μ μ© λ²μ μλ΄λ μ΄ μ€μ μμ μμ±ν©λλ€. λͺ¨λΈμκ² μ리λ λ²μμ κ²μ¬νλ λ²μκ° κ°λΌμ§λ©΄ 8μκ³Ό κ°μ μνλ‘ λμκ°κΈ° λλ¬Έμ
λλ€.
|
|
345
|
+
|
|
306
346
|
### ν¨κ» μ£Όμ
λλ μΈμ½λ© κ·μΉ (v3.23.2)
|
|
307
347
|
|
|
308
348
|
문체 μ§μΉ¨κ³Ό λ³λλ‘, **λꡬ νΈμΆ μΈμμ λ΄λ λΉASCII λ¬Έμμ΄μ 리ν°λ΄ UTF-8λ‘ μ°κ³ `\uXXXX` μ λμ½λ μ΄μ€μΌμ΄νλ‘λ μ°μ§ λ§λΌ**λ ν μ€μ΄ κ°μ΄ μ£Όμ
λ©λλ€.
|
|
@@ -380,6 +420,11 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
|
|
|
380
420
|
|
|
381
421
|
## λ¦΄λ¦¬μ€ λ
ΈνΈ
|
|
382
422
|
|
|
423
|
+
### v3.25.0 (2026-09-04)
|
|
424
|
+
- **statuslineμ μ€ν μ€μΈ λ²μ μ νμν©λλ€.** μ§κΈκΉμ§λ ν νμ 리ν¬νΈμ κ°μ£Όμλ§ λ²μ μ΄ μμκΈ° λλ¬Έμ, μ΄λ€ λ²μ μ΄ λλμ§ νμΈνλ €λ©΄ μ 체 리ν¬νΈλ₯Ό μ€νν΄μΌ νμ΅λλ€. `--version` νλκ·Έλ ν¨κ» μΆκ°νμ΅λλ€.
|
|
425
|
+
- **μ λ²μ μ΄ λμ€λ©΄ μΈμ
μμμ λ¬Όμ΄λ΄
λλ€.** statuslineμ λν μμλ₯Ό λμΈ μ μμΌλ―λ‘, μλ¦Όκ³Ό μ§λ¬Έμ λλμμ΅λλ€. statuslineμ `β¬ v3.24.0 β 3.25.0`μΌλ‘ μλ¦¬κΈ°λ§ νκ³ , μ€μ μ§λ¬Έμ SessionStart ν
μ΄ λͺ¨λΈμκ² "μ¬μ©μμκ² μ
κ·Έλ μ΄λ μ¬λΆλ₯Ό νμΈνλΌ"κ³ μ£Όμ
ν΄μ μ΄λ£¨μ΄μ§λλ€. μΉλνλ©΄ `claude-token-saver upgrade`κ° μ€μΉ κ²½λ‘μ λ§λ λͺ
λ Ήμ μ€ννκ³ , κ±°μ νλ©΄ `update-check --dismiss`κ° κ·Έ λ²μ μ λ λ¬»μ§ μλλ‘ κΈ°λ‘ν©λλ€.
|
|
426
|
+
- **λ²μ νμΈμ΄ λ λλ₯Ό λΆμ‘μ§ μμ΅λλ€.** μ‘°νλ 24μκ°μ ν λ² λΆλ¦¬λ λ°±κ·ΈλΌμ΄λ νλ‘μΈμ€κ° μννκ³ , λ λ κ²½λ‘λ μΊμ νμΌλ§ μ½μ΅λλ€. νμΈμ μ€ν¨ν΄λ μκ°μ κΈ°λ‘νλ―λ‘ μ€νλΌμΈμμ λ§€ λ λλ§λ€ μ¬μλνμ§ μμ΅λλ€. `CTS_NO_UPDATE_CHECK=1` λλ `NO_UPDATE_NOTIFIER`λ‘ λ μ μμ΅λλ€.
|
|
427
|
+
|
|
383
428
|
### v3.21.0 (2026-08-22)
|
|
384
429
|
- **μ€μΉκ° 무μμ μΌλμ§ λ³΄μ¬ μ€ λ€ λ¬Όμ΄λ΄
λλ€** β μ§κΈκΉμ§λ π
· Harness 5μμΉκ³Ό νκ΅μ΄ 문체 μ§μΉ¨μ μ€μΉκ° μ€μ€λ‘ μΌ°κ³ , μ¬μ©μλ κ²°κ³Όλ§ λ³Ό μ μμμ΅λλ€. μ΄μ harnessλ λ€μ― μμΉμ μ λͺ©μ, νκ΅μ΄ μ§μΉ¨μ λ°κΎΈλ λ΄μ©κ³Ό μΈμ
λΉ λΉμ©κ³Ό μΆμ²λ₯Ό λ¨Όμ μΆλ ₯ν λ€μ μΌ€μ§ λ¬»μ΅λλ€. λ‘μΊ κ°μ§λ λ΅μ΄ μλλΌ μ§λ¬Έμ κΈ°λ³Έκ°μΌλ‘ λ΄λ €μμΌλ―λ‘, μμ΄ λ‘μΊμμ νκ΅μ΄λ‘ μμ
νλ κ²½μ°μλ κ·Έ μ리μμ μΌ€ μ μμ΅λλ€.
|
|
385
430
|
- **μ¬λμ΄ λΆμ΄ μμ§ μμ μ€μΉλ μμ λλ‘ λμν©λλ€** β npmμ `postinstall`, CI, νμ΄ν μ
λ ₯, `CTS_NO_INPUT=1`μμλ μ§λ¬Έμ 건λλ°κ³ κΈ°μ‘΄ κΈ°λ³Έκ°μ κ·Έλλ‘ μ μ©ν©λλ€. ν둬ννΈκ° λ©μΆ° μλ©΄ μ€μΉκ° 걸리기 λλ¬Έμ
λλ€. `--yes`μ `--no-input`μΌλ‘ μ§μ κ°μ ν μλ μμ΅λλ€. λΉλννμ΄λ©΄μ λ‘μΊμ΄ νκ΅μ΄κ° μλ κ²½μ°μλ μ€μ μ μ μ₯νμ§ μκ³ λ―Έκ²°μ μΌλ‘ λ¨κ²¨ λλ―λ‘, λμ€μ ν°λ―Έλμμ μ€μΉνλ©΄ κ·Έλ λ¬Όμ΄λ΄
λλ€.
|
package/bin/cli.js
CHANGED
|
@@ -52,6 +52,7 @@ import { estimateCost } from '../src/cost.js';
|
|
|
52
52
|
import { chipForIssues } from '../src/advice.js';
|
|
53
53
|
import { debug } from '../src/debug.js';
|
|
54
54
|
import { createArgs } from '../src/cli-args.js';
|
|
55
|
+
import { updateStatus, maybeSpawnUpdateCheck } from '../src/update-check.js';
|
|
55
56
|
|
|
56
57
|
const args = process.argv.slice(2);
|
|
57
58
|
|
|
@@ -66,6 +67,22 @@ const PKG_VERSION = (() => {
|
|
|
66
67
|
|
|
67
68
|
const { getArg, hasFlag, numArg } = createArgs(args);
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Version/update state for the statusline chip. Reads a cache file and, when
|
|
72
|
+
* that cache has aged past the check interval, kicks a detached child to
|
|
73
|
+
* refresh it for a later render. Never awaits the network, never throws into
|
|
74
|
+
* the render: a registry outage must not cost the statusline its other chips.
|
|
75
|
+
*/
|
|
76
|
+
function readUpdateChip() {
|
|
77
|
+
try {
|
|
78
|
+
maybeSpawnUpdateCheck(PKG_VERSION);
|
|
79
|
+
return updateStatus(PKG_VERSION);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
debug('update-check:chip', e);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
async function main() {
|
|
70
87
|
// Subcommand: last β print the most recent warning + how to handle it.
|
|
71
88
|
// Designed for the auto-trigger skill so the user immediately sees
|
|
@@ -166,6 +183,37 @@ async function main() {
|
|
|
166
183
|
return (await import('../src/commands/compact-window.js')).run({ args, hasFlag });
|
|
167
184
|
}
|
|
168
185
|
|
|
186
|
+
// `--version` / `-v` β the flag every CLI is expected to answer. Until now
|
|
187
|
+
// the version was only visible in the table view's footer, which meant
|
|
188
|
+
// "which version am I on" required running a full report.
|
|
189
|
+
if (hasFlag('--version') || hasFlag('-v')) {
|
|
190
|
+
console.log(PKG_VERSION);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Subcommand: update-check β the registry lookup behind the β¬ statusline
|
|
195
|
+
// chip and the session-start upgrade offer.
|
|
196
|
+
// claude-token-saver update-check # print cached status
|
|
197
|
+
// claude-token-saver update-check --refresh # hit the registry now (detached child uses this)
|
|
198
|
+
// claude-token-saver update-check --dismiss # stop offering THIS version at session start
|
|
199
|
+
if (args[0] === 'update-check') {
|
|
200
|
+
return (await import('../src/commands/update-check.js')).run({
|
|
201
|
+
hasFlag,
|
|
202
|
+
version: PKG_VERSION,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Subcommand: upgrade β run the install command that matches how this copy
|
|
207
|
+
// got here, then confirm the new version.
|
|
208
|
+
// claude-token-saver upgrade # install the latest release
|
|
209
|
+
// claude-token-saver upgrade --print # just show the command, run nothing
|
|
210
|
+
if (args[0] === 'upgrade') {
|
|
211
|
+
return (await import('../src/commands/upgrade.js')).run({
|
|
212
|
+
hasFlag,
|
|
213
|
+
version: PKG_VERSION,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
169
217
|
// Hook management
|
|
170
218
|
if (hasFlag('--install-hook')) {
|
|
171
219
|
const { installHook } = await import('../src/hook-manager.js');
|
|
@@ -319,7 +367,7 @@ async function main() {
|
|
|
319
367
|
} catch (e) { debug('caps-cache:persist', e); }
|
|
320
368
|
}
|
|
321
369
|
console.log(formatNoSession(
|
|
322
|
-
{ caps, model, windowLabel },
|
|
370
|
+
{ caps, model, windowLabel, version: PKG_VERSION, update: readUpdateChip() },
|
|
323
371
|
{ color: colorOk, mode: isIcon ? 'icon' : 'text' },
|
|
324
372
|
));
|
|
325
373
|
return;
|
|
@@ -471,6 +519,8 @@ async function main() {
|
|
|
471
519
|
anomalies,
|
|
472
520
|
cost,
|
|
473
521
|
options: { days, windowHours, windowLabel, version: PKG_VERSION },
|
|
522
|
+
// Cached-only; the background refresh it may trigger lands on a later render.
|
|
523
|
+
update: format === 'statusline' ? readUpdateChip() : null,
|
|
474
524
|
lastActivity,
|
|
475
525
|
spikeReport,
|
|
476
526
|
contextWindow,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.25.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": {
|
package/src/commands/korean.js
CHANGED
|
@@ -11,14 +11,144 @@
|
|
|
11
11
|
* machine. This ships the guidance with the package and delivers it through
|
|
12
12
|
* the SessionStart hook that is already installed, so it applies everywhere
|
|
13
13
|
* the CLI is installed and leaves the output-style slot free.
|
|
14
|
+
*
|
|
15
|
+
* Injection alone turned out to be half the job. The guidance is read once at
|
|
16
|
+
* session start and never again, so a long session writes documents that drift
|
|
17
|
+
* back to the patterns it forbids, and the drift is caught only when a human
|
|
18
|
+
* reads the finished file. `korean on` therefore also installs a PostToolUse
|
|
19
|
+
* hook that runs the machine-checkable clauses over the prose the model just
|
|
20
|
+
* wrote:
|
|
21
|
+
* claude-token-saver korean lint block|warn|off # how findings are handled
|
|
22
|
+
* claude-token-saver korean lint <file...> # check files on disk
|
|
14
23
|
*/
|
|
15
24
|
|
|
16
|
-
|
|
25
|
+
// Enforcement is only useful if it is on by default β a check the user has to
|
|
26
|
+
// discover is the same hole in a different shape.
|
|
27
|
+
function koreanLintMode(cfg) {
|
|
28
|
+
const mode = cfg?.koreanStyle?.lint;
|
|
29
|
+
return mode === 'off' || mode === 'warn' ? mode : 'block';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `all` checks every text file the session writes, including Korean sitting in
|
|
33
|
+
// comments and UI strings. That is wider than the vendored guidance's own
|
|
34
|
+
// exemption list, and deliberately so: a comment is read by a person, and
|
|
35
|
+
// generated artifacts (PDF, HTML, captions) are assembled from those strings,
|
|
36
|
+
// so exempting them reopens the gap for exactly the outputs users complained
|
|
37
|
+
// about. `prose` restores the narrow reading.
|
|
38
|
+
function koreanLintScope(cfg) {
|
|
39
|
+
return cfg?.koreanStyle?.lintScope === 'prose' ? 'prose' : 'all';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function run({ args, hasFlag }) {
|
|
17
43
|
const sub = args[1] || 'status';
|
|
18
44
|
const ks = await import('../korean-style.js');
|
|
19
|
-
const { userLanguage } = await import('../config.js');
|
|
45
|
+
const { userLanguage, loadConfig, saveConfig } = await import('../config.js');
|
|
20
46
|
const lang = userLanguage();
|
|
21
47
|
|
|
48
|
+
// PostToolUse hook. Claude Code feeds the tool-call payload on stdin; we
|
|
49
|
+
// check the prose the model just wrote and hand any findings straight back
|
|
50
|
+
// to it. Silent and cheap when the file is clean, which is the common case.
|
|
51
|
+
if (hasFlag?.('--hook') || sub === '--hook') {
|
|
52
|
+
const { readStdinJson } = await import('../stdin-payload.js');
|
|
53
|
+
const payload = readStdinJson();
|
|
54
|
+
if (!payload || !ks.koreanStyleEnabled()) return;
|
|
55
|
+
const mode = koreanLintMode(loadConfig());
|
|
56
|
+
if (mode === 'off') return;
|
|
57
|
+
const { createRequire } = await import('node:module');
|
|
58
|
+
const req = createRequire(import.meta.url);
|
|
59
|
+
const lint = req('../korean-lint.cjs');
|
|
60
|
+
const result = lint.lintToolUse(payload, { scope: koreanLintScope(loadConfig()) });
|
|
61
|
+
if (!result) return;
|
|
62
|
+
const message = lint.formatFindings(result.filePath, result.findings);
|
|
63
|
+
if (mode === 'block') {
|
|
64
|
+
// Exit 2 is Claude Code's blocking-feedback channel: the file is already
|
|
65
|
+
// written, so nothing is lost, but the model must address this before it
|
|
66
|
+
// moves on. That is the whole point β a warning it can scroll past is
|
|
67
|
+
// what failed in the first place.
|
|
68
|
+
process.stderr.write(message + '\n');
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
console.log(message);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Manual run over files already on disk: `korean lint docs/*.md`.
|
|
76
|
+
if (sub === 'lint' && args.length > 2 && !['on', 'off', 'warn', 'block', 'scope'].includes(args[2])) {
|
|
77
|
+
const { readFileSync } = await import('node:fs');
|
|
78
|
+
const { createRequire } = await import('node:module');
|
|
79
|
+
const lint = createRequire(import.meta.url)('../korean-lint.cjs');
|
|
80
|
+
let total = 0;
|
|
81
|
+
for (const file of args.slice(2)) {
|
|
82
|
+
let text;
|
|
83
|
+
try {
|
|
84
|
+
text = readFileSync(file, 'utf8');
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(`${file}: ${e.message}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const findings = lint.lintKoreanText(text, { code: !lint.isProseFile(file) });
|
|
90
|
+
total += findings.length;
|
|
91
|
+
if (findings.length) console.log(lint.formatFindings(file, findings));
|
|
92
|
+
}
|
|
93
|
+
if (total === 0) {
|
|
94
|
+
console.log(lang === 'ko' ? '문체 κ·μ½ μλ°μ΄ μμ΅λλ€.' : 'No findings.');
|
|
95
|
+
}
|
|
96
|
+
process.exit(total > 0 ? 1 : 0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Scope switch: `korean lint scope all|prose`.
|
|
100
|
+
if (sub === 'lint' && args[2] === 'scope') {
|
|
101
|
+
const cfg = loadConfig();
|
|
102
|
+
const next = args[3];
|
|
103
|
+
if (!next) {
|
|
104
|
+
console.log(lang === 'ko'
|
|
105
|
+
? `κ²μ¬ λ²μ: ${koreanLintScope(cfg)}`
|
|
106
|
+
: `Check scope: ${koreanLintScope(cfg)}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!['all', 'prose'].includes(next)) {
|
|
110
|
+
console.error('Usage: claude-token-saver korean lint scope [all|prose]');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
cfg.koreanStyle = cfg.koreanStyle || {};
|
|
114
|
+
cfg.koreanStyle.lintScope = next;
|
|
115
|
+
saveConfig(cfg);
|
|
116
|
+
console.log(lang === 'ko'
|
|
117
|
+
? (next === 'all'
|
|
118
|
+
? 'κ²μ¬ λ²μ: all β λ¬Έμμ μ½λ μ£Όμ, UI λ¬Έμμ΄κΉμ§ μΈμ
μ΄ μ΄ λͺ¨λ ν
μ€νΈ νμΌμ κ²μ¬ν©λλ€.'
|
|
119
|
+
: 'κ²μ¬ λ²μ: prose β λ§ν¬λ€μ΄κ³Ό ν
μ€νΈ λ¬Έμλ§ κ²μ¬ν©λλ€.')
|
|
120
|
+
: (next === 'all'
|
|
121
|
+
? 'Check scope: all β every text file the session writes, comments and UI strings included.'
|
|
122
|
+
: 'Check scope: prose β documents only.'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Enforcement mode: `korean lint block|warn|off`.
|
|
127
|
+
if (sub === 'lint') {
|
|
128
|
+
const mode = args[2];
|
|
129
|
+
const cfg = loadConfig();
|
|
130
|
+
if (!mode) {
|
|
131
|
+
console.log(lang === 'ko'
|
|
132
|
+
? `μ°κΈ° μμ κ²μ¬: ${koreanLintMode(cfg)} (λ²μ ${koreanLintScope(cfg)})`
|
|
133
|
+
: `Write-time check: ${koreanLintMode(cfg)} (scope ${koreanLintScope(cfg)})`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!['block', 'warn', 'off'].includes(mode)) {
|
|
137
|
+
console.error('Usage: claude-token-saver korean lint [block|warn|off] | korean lint <file...>');
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
cfg.koreanStyle = cfg.koreanStyle || {};
|
|
141
|
+
cfg.koreanStyle.lint = mode;
|
|
142
|
+
saveConfig(cfg);
|
|
143
|
+
const note = {
|
|
144
|
+
block: lang === 'ko' ? 'μλ°μ λ°κ²¬νλ©΄ λͺ¨λΈμκ² λλλ € λ³΄λ΄ κ³ μΉκ² ν©λλ€.' : 'Findings are handed back to the model as blocking feedback.',
|
|
145
|
+
warn: lang === 'ko' ? 'μλ°μ μλ¦¬κΈ°λ§ νκ³ μ§νμ λ§μ§ μμ΅λλ€.' : 'Findings are printed as a note and do not block.',
|
|
146
|
+
off: lang === 'ko' ? 'μ°κΈ° μμ κ²μ¬λ₯Ό νμ§ μμ΅λλ€.' : 'The write-time check is disabled.',
|
|
147
|
+
}[mode];
|
|
148
|
+
console.log(`${lang === 'ko' ? 'μ°κΈ° μμ κ²μ¬' : 'Write-time check'}: ${mode} β ${note}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
22
152
|
if (sub === 'show') {
|
|
23
153
|
const text = ks.koreanStyleText();
|
|
24
154
|
if (!text) {
|
|
@@ -32,6 +162,8 @@ export async function run({ args }) {
|
|
|
32
162
|
if (sub === 'on' || sub === 'off') {
|
|
33
163
|
const enabled = sub === 'on';
|
|
34
164
|
ks.setKoreanStyleEnabled(enabled);
|
|
165
|
+
const { installKoreanLintHook, removeKoreanLintHook } = await import('../installer.js');
|
|
166
|
+
const hook = enabled ? installKoreanLintHook() : removeKoreanLintHook();
|
|
35
167
|
if (enabled) {
|
|
36
168
|
console.log(lang === 'ko'
|
|
37
169
|
? 'νκ΅μ΄ 문체 μ§μΉ¨μ μΌ°μ΅λλ€. λ€μ μΈμ
λΆν° λͺ¨λ νλ‘μ νΈμ μ μ©λ©λλ€.'
|
|
@@ -42,6 +174,15 @@ export async function run({ args }) {
|
|
|
42
174
|
console.log(lang === 'ko'
|
|
43
175
|
? ` μΆμ²: ${ks.KOREAN_STYLE_SOURCE}`
|
|
44
176
|
: ` Source: ${ks.KOREAN_STYLE_SOURCE}`);
|
|
177
|
+
if (hook.action === 'skipped') {
|
|
178
|
+
console.log(lang === 'ko'
|
|
179
|
+
? ` β μ°κΈ° μμ κ²μ¬ ν
μ λͺ» κ±Έμμ΅λλ€: ${hook.reason}`
|
|
180
|
+
: ` β Could not register the write-time hook: ${hook.reason}`);
|
|
181
|
+
} else {
|
|
182
|
+
console.log(lang === 'ko'
|
|
183
|
+
? ` μ°κΈ° μμ κ²μ¬: ${koreanLintMode(loadConfig())}, λ²μ ${koreanLintScope(loadConfig())} (WriteΒ·Edit λ‘ μ΄ νκ΅μ΄λ₯Ό κ²μ¬ν©λλ€)`
|
|
184
|
+
: ` Write-time check: ${koreanLintMode(loadConfig())}, scope ${koreanLintScope(loadConfig())} (runs on Korean written via Write/Edit)`);
|
|
185
|
+
}
|
|
45
186
|
} else {
|
|
46
187
|
console.log(lang === 'ko'
|
|
47
188
|
? 'νκ΅μ΄ 문체 μ§μΉ¨μ κ»μ΅λλ€. λ€μ μΈμ
λΆν° μ£Όμ
νμ§ μμ΅λλ€.'
|
|
@@ -59,6 +200,7 @@ export async function run({ args }) {
|
|
|
59
200
|
if (lang === 'ko') {
|
|
60
201
|
console.log(`νκ΅μ΄ 문체 μ§μΉ¨: ${on ? 'μΌμ§' : 'κΊΌμ§'}`);
|
|
61
202
|
console.log(` λΉμ©: μΈμ
λΉ μ½ ${tokens} ν ν° (μΈμ
μμ 1ν μ£Όμ
)`);
|
|
203
|
+
console.log(` μ°κΈ° μμ κ²μ¬: ${koreanLintMode(loadConfig())}, λ²μ ${koreanLintScope(loadConfig())} (λ°κΎΈλ €λ©΄ claude-token-saver korean lint block|warn|off, korean lint scope all|prose)`);
|
|
62
204
|
console.log(` μΆμ²: ${ks.KOREAN_STYLE_SOURCE}`);
|
|
63
205
|
console.log(` λΌμ΄μ μ€ μ λ¬Έ: ${ks.KOREAN_STYLE_LICENSE_PATH}`);
|
|
64
206
|
console.log(on
|
|
@@ -67,6 +209,7 @@ export async function run({ args }) {
|
|
|
67
209
|
} else {
|
|
68
210
|
console.log(`Korean writing guidance: ${on ? 'on' : 'off'}`);
|
|
69
211
|
console.log(` Cost: ~${tokens} tokens per session (injected once at session start)`);
|
|
212
|
+
console.log(` Write-time check: ${koreanLintMode(loadConfig())}, scope ${koreanLintScope(loadConfig())} (change with: claude-token-saver korean lint block|warn|off, korean lint scope all|prose)`);
|
|
70
213
|
console.log(` Source: ${ks.KOREAN_STYLE_SOURCE}`);
|
|
71
214
|
console.log(` License text: ${ks.KOREAN_STYLE_LICENSE_PATH}`);
|
|
72
215
|
console.log(on
|
|
@@ -2,9 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
5
8
|
import { readStdinJson } from '../stdin-payload.js';
|
|
6
9
|
import { debug } from '../debug.js';
|
|
7
10
|
|
|
11
|
+
/** This package's own version β the baseline the upgrade offer compares against. */
|
|
12
|
+
function readPackageVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
|
|
15
|
+
return JSON.parse(readFileSync(pkg, 'utf8')).version || null;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
debug('route-scan:pkg-version', e);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
export async function run({ args, hasFlag, numArg }) {
|
|
9
23
|
const rs = await import('../route-scan.js');
|
|
10
24
|
const { userLanguage } = await import('../config.js');
|
|
@@ -190,10 +204,44 @@ export async function run({ args, hasFlag, numArg }) {
|
|
|
190
204
|
koreanBlock = koreanStyleInjection();
|
|
191
205
|
} catch (e) { debug('route-scan:korean-style', e); /* style is optional */ }
|
|
192
206
|
|
|
207
|
+
// Upgrade offer. A statusline cannot open a dialog, so session start is
|
|
208
|
+
// where the *asking* happens: the model gets one line telling it a newer
|
|
209
|
+
// version exists and to ask before installing anything. Cached read only
|
|
210
|
+
// β the refresh runs detached off the statusline path, never here.
|
|
211
|
+
let updateBlock = null;
|
|
212
|
+
try {
|
|
213
|
+
const { updateStatus, maybeSpawnUpdateCheck } = await import('../update-check.js');
|
|
214
|
+
const pkgVersion = readPackageVersion();
|
|
215
|
+
// Session start is the one moment where a stale cache matters most, so
|
|
216
|
+
// kick the detached refresh here too. It lands in time for the
|
|
217
|
+
// statusline and for the next session, not for this line.
|
|
218
|
+
if (pkgVersion) maybeSpawnUpdateCheck(pkgVersion);
|
|
219
|
+
const u = pkgVersion ? updateStatus(pkgVersion) : null;
|
|
220
|
+
// `dismissed` is the whole point of asking once: a user who said no to
|
|
221
|
+
// this version must not be asked again every `/clear`.
|
|
222
|
+
if (u && u.available && !u.dismissed) {
|
|
223
|
+
updateBlock = lang === 'ko'
|
|
224
|
+
? [
|
|
225
|
+
`[claude-token-saver update] μ λ²μ μ΄ λμ μμ΅λλ€: v${u.current} β ${u.latest}.`,
|
|
226
|
+
'μ¬μ©μμκ² μ§κΈ μ
κ·Έλ μ΄λν μ§ λ¬Όμ΄λ³΄κ³ , μΉλνλ©΄ μλ λͺ
λ Ήμ μ€ννμμμ€. λ¬»μ§ μκ³ μ€μΉνμ§λ λ§μμμ€.',
|
|
227
|
+
' claude-token-saver upgrade # μ
κ·Έλ μ΄λ μ€ν',
|
|
228
|
+
' claude-token-saver update-check --dismiss # μ¬μ©μκ° μμΉ μμΌλ©΄ (λ€μ λ²μ μ΄ λμ¬ λκΉμ§ λ€μ λ¬»μ§ μμ΅λλ€)',
|
|
229
|
+
].join('\n')
|
|
230
|
+
: [
|
|
231
|
+
`[claude-token-saver update] A newer version is available: v${u.current} β ${u.latest}.`,
|
|
232
|
+
'Ask the user whether to upgrade now, and run the command below only if they agree. Do not install without asking.',
|
|
233
|
+
' claude-token-saver upgrade # perform the upgrade',
|
|
234
|
+
' claude-token-saver update-check --dismiss # if they decline (stays quiet until a newer release)',
|
|
235
|
+
].join('\n');
|
|
236
|
+
}
|
|
237
|
+
} catch (e) { debug('route-scan:update-check', e); /* the offer is optional */ }
|
|
238
|
+
|
|
193
239
|
if (open.length === 0 && reviewRules.length === 0) {
|
|
240
|
+
if (updateBlock) console.log(updateBlock);
|
|
194
241
|
if (koreanBlock) console.log(koreanBlock);
|
|
195
242
|
return; // nothing else to inject
|
|
196
243
|
}
|
|
244
|
+
if (updateBlock) console.log(updateBlock);
|
|
197
245
|
// This text is injected straight into the model's context, so it must
|
|
198
246
|
// follow the user's configured language β a Korean-only briefing in an
|
|
199
247
|
// English session steers the whole first response into Korean.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subcommand: update-check β everything around "is a newer version out".
|
|
3
|
+
*
|
|
4
|
+
* claude-token-saver update-check # print the cached answer
|
|
5
|
+
* claude-token-saver update-check --refresh # ask the registry now
|
|
6
|
+
* claude-token-saver update-check --refresh --quiet
|
|
7
|
+
* # what the detached background child runs
|
|
8
|
+
* claude-token-saver update-check --dismiss # stop offering this version at session start
|
|
9
|
+
*
|
|
10
|
+
* The statusline never calls the --refresh path directly; it spawns this
|
|
11
|
+
* command detached so the render itself stays offline and instant.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export async function run({ hasFlag, version }) {
|
|
15
|
+
const {
|
|
16
|
+
updateStatus,
|
|
17
|
+
refreshUpdateState,
|
|
18
|
+
dismissUpdate,
|
|
19
|
+
upgradeCommand,
|
|
20
|
+
updateCheckDisabled,
|
|
21
|
+
updateStatePath,
|
|
22
|
+
} = await import('../update-check.js');
|
|
23
|
+
const { userLanguage } = await import('../config.js');
|
|
24
|
+
const lang = userLanguage();
|
|
25
|
+
const quiet = hasFlag('--quiet');
|
|
26
|
+
|
|
27
|
+
if (hasFlag('--refresh')) {
|
|
28
|
+
const res = await refreshUpdateState(version);
|
|
29
|
+
if (quiet) return;
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
console.error(lang === 'ko'
|
|
32
|
+
? `μ΅μ λ²μ μ νμΈνμ§ λͺ»νμ΅λλ€: ${res.error}`
|
|
33
|
+
: `Could not reach the registry: ${res.error}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (hasFlag('--dismiss')) {
|
|
39
|
+
const s = updateStatus(version);
|
|
40
|
+
if (s.latest) dismissUpdate(s.latest);
|
|
41
|
+
console.log(lang === 'ko'
|
|
42
|
+
? `${s.latest || 'μ΅μ '} λ²μ μλ΄λ₯Ό λλλ€. κ·Έ λ€μ λ²μ μ΄ λμ€λ©΄ λ€μ μλ΄ν©λλ€.`
|
|
43
|
+
: `Muted the ${s.latest || 'latest'} notice. The next release after it will ask again.`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (updateCheckDisabled()) {
|
|
48
|
+
console.log(lang === 'ko'
|
|
49
|
+
? `νμ¬ λ²μ v${version} β μ
λ°μ΄νΈ νμΈμ΄ νκ²½ λ³μλ‘ κΊΌμ Έ μμ΅λλ€ (CTS_NO_UPDATE_CHECK / NO_UPDATE_NOTIFIER).`
|
|
50
|
+
: `v${version} β update checks are disabled by env var (CTS_NO_UPDATE_CHECK / NO_UPDATE_NOTIFIER).`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const s = updateStatus(version);
|
|
55
|
+
if (s.available) {
|
|
56
|
+
console.log(lang === 'ko'
|
|
57
|
+
? `μ λ²μ μ΄ μμ΅λλ€: v${version} β ${s.latest}`
|
|
58
|
+
: `Update available: v${version} β ${s.latest}`);
|
|
59
|
+
console.log(lang === 'ko'
|
|
60
|
+
? `μ
κ·Έλ μ΄λ: ${upgradeCommand()} (λλ claude-token-saver upgrade)`
|
|
61
|
+
: `Upgrade with: ${upgradeCommand()} (or: claude-token-saver upgrade)`);
|
|
62
|
+
if (s.dismissed) {
|
|
63
|
+
console.log(lang === 'ko'
|
|
64
|
+
? 'μ΄ λ²μ μ μ¬μ©μκ° ν λ² λκΈ΄ μνλΌ, μΈμ
μμ μμλ λ€μ λ¬»μ§ μμ΅λλ€.'
|
|
65
|
+
: 'This version was dismissed, so the session-start offer stays quiet for it.');
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
console.log(lang === 'ko'
|
|
70
|
+
? `μ΅μ λ²μ μ μ°κ³ μμ΅λλ€ (v${version}).`
|
|
71
|
+
: `Up to date (v${version}).`);
|
|
72
|
+
if (!s.latest) {
|
|
73
|
+
console.log(lang === 'ko'
|
|
74
|
+
? `μμ§ νμΈλ κΈ°λ‘μ΄ μμ΅λλ€. μ§κΈ νμΈνλ €λ©΄: claude-token-saver update-check --refresh (κΈ°λ‘ νμΌ: ${updateStatePath()})`
|
|
75
|
+
: `No check has completed yet. Run: claude-token-saver update-check --refresh (state: ${updateStatePath()})`);
|
|
76
|
+
}
|
|
77
|
+
}
|