claude-token-saver 3.20.1 โ†’ 3.21.1

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
@@ -61,7 +61,7 @@ By run (newest first):
61
61
  |---|---|
62
62
  | ๐Ÿšจ **No surprise rate limits** | Warns when the 5H/7D window hits 90%; `handoff` backs up your work |
63
63
  | ๐Ÿง  **Cache waste detection** | Hit rate, TTL, 1M-context detection โ€” spikes diagnosed with issue codes |
64
- | ๐Ÿ‡ฐ๐Ÿ‡ท **Korean writing guidance** | Enabled automatically on a Korean locale ([below](#-korean-writing-guidance)) |
64
+ | ๐Ÿ‡ฐ๐Ÿ‡ท **Korean writing guidance** | Offered at install time, defaulting to your locale ([below](#-korean-writing-guidance)) |
65
65
 
66
66
  ## Not a router โ€” 60 seconds
67
67
 
@@ -103,7 +103,9 @@ npm i -g claude-token-saver
103
103
 
104
104
  The statusline appears at the bottom of Claude Code right away. If auto-registration was skipped (`--ignore-scripts`, sudo, sandboxed installs), run `claude-token-saver install`.
105
105
 
106
- One install sets up everything: **statusline, Skill, SessionStart hook, the ๐Ÿ…ท Harness (5 principles), and a first route-scan.** The harness is **appended** to `~/.claude/CLAUDE.md` as a marked block (your existing content is backed up and preserved) and is left alone if one is already there. Skip it with `CTS_NO_HARNESS=1 npm i -g claude-token-saver`; undo it with `claude-token-saver harness uninit --global`.
106
+ One install sets up everything: **statusline, Skill, SessionStart hook, the ๐Ÿ…ท Harness (5 principles), and a first route-scan.** The harness and the Korean writing guidance **show what they add and ask before enabling it.** The harness is **appended** to `~/.claude/CLAUDE.md` as a marked block (your existing content is backed up and preserved) and is left alone if one is already there.
107
+
108
+ Outside a terminal โ€” npm `postinstall`, CI, piped stdin โ€” the question is skipped and the old defaults apply. Use `--yes` or `--no-input` to skip it deliberately, `CTS_NO_HARNESS=1 npm i -g claude-token-saver` to skip the harness entirely, and `claude-token-saver harness uninit --global` to undo it.
107
109
 
108
110
  > โš ๏ธ 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`.
109
111
 
@@ -291,9 +293,31 @@ claude-token-saver korean off # off
291
293
 
292
294
  Claude Code's output styles can do the same thing, but an output style is **a single global slot**: turning it on takes that slot away from anything else and has to be configured per machine. This ships the guidance inside the package and delivers it through the SessionStart hook that is already installed, so it **applies wherever the CLI is installed and leaves the output-style slot free.** It survives `/clear`, because the hook fires again.
293
295
 
294
- Cost is **~1.5k tokens per session, injected once at session start rather than per turn**, and covered by the prompt cache from the second request on. When it is on, a `๊ฐ€` chip appears in the statusline.
296
+ Cost is **~1.5k tokens per session, injected once at session start rather than per turn**, and covered by the prompt cache from the second request on. When it is on, a `โœ๏ธ` chip appears in the statusline.
297
+
298
+ ### Before and after
299
+
300
+ From a real pipeline (yaml-sns-agent), where a Slack bot shells out to `claude -p` to investigate the repository and answers in a thread. The guidance was toggled off and then on, with the same question both times: "explain in three Korean sentences how to narrow down a render failure."
301
+
302
+ **Before** (guidance off)
303
+
304
+ > ๋ Œ๋” ์‹คํŒจ ์›์ธ ์ขํžˆ๊ธฐ 3๋‹จ๊ณ„: ๋จผ์ € `hn:still --check`๋กœ ์Šคํ‹ธ์ด ๋ช…์„ธ๋ณด๋‹ค ์ตœ์‹ ์ธ์ง€(FRESH/STALE) ํ™•์ธ โ€” STALE์ด๋ฉด ์žฌ์ƒ์„ฑ ์•ˆ ํ•ด์„œ ์˜› ์ž์‚ฐ์œผ๋กœ ๋ Œ๋” ์‹œ๋„ํ•œ ๊ฒŒ ์›์ธ. ๋‹ค์Œ caption-blocks๊ฐ€ ์ตœ์‹  ๋‚˜๋ ˆ์ด์…˜๊ณผ ๋‹จ์–ด์—ด ์ผ์น˜ํ•˜๋Š”์ง€ ์ง์ ‘ ๋Œ€์กฐ โ€” TTS ์žฌ์ƒ์„ฑ ํ›„ blocks ์žฌ์ƒ์„ฑ ๋น ์ง€๋ฉด ์ž๋ง‰ยท์Œ์„ฑ ์–ด๊ธ‹๋‚˜ ๋ Œ๋” ๊นจ์ง.
305
+
306
+ **After** (guidance on)
295
307
 
296
- **The default is decided at install time.** A Korean system locale (`ko_KR` and friends; on macOS the system setting is checked too) turns it on; anything else leaves it off, so users who never write Korean are not billed 1.5k tokens a session. **Once you have turned it on or off yourself, that choice sticks โ€” an upgrade never overrides it.** Install with `CTS_NO_KOREAN=1` to skip the automatic decision.
308
+ > ๋จผ์ € `hn:still --check` ๋Œ๋ ค ์Šคํ‹ธ์ด ๋ช…์„ธ๋ณด๋‹ค ์ตœ์‹ ์ธ์ง€ ํ™•์ธํ•œ๋‹ค. STALE์ด๋ฉด ์žฌ์ƒ์„ฑ ์•ˆ ํ•ด์„œ ์ƒ๊ธด ๋ฌธ์ œ.
309
+ >
310
+ > ๋‹ค์Œ caption-blocks๊ฐ€ captions.json ๋‹จ์–ด์—ด๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๋ณธ๋‹ค. ๋‚ด๋ ˆ์ด์…˜ ์žฌTTS ํ›„ blocks ์žฌ์ƒ์„ฑ ๋น ์ง€๋ฉด ์˜› ์ž๋ง‰์ด ์ƒˆ ์Œ์„ฑ ์œ„์— ๋œฌ๋‹ค.
311
+
312
+ Three things change. Clauses chained with em dashes become separate sentences, so one sentence carries one fact. Noun-stopped phrases (ํ™•์ธ, ๋Œ€์กฐ, ๋ Œ๋” ๊นจ์ง โ€” "check", "compare", "render breaks") become predicates (ํ™•์ธํ•œ๋‹ค, ๋ณธ๋‹ค, ๋œฌ๋‹ค), which makes it explicit that these are steps to take. And the particles come back where they had been dropped, so subject and object are legible on the first read.
313
+
314
+ 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
+
316
+ ### Asked at install time
317
+
318
+ The install **prints what the guidance changes, its per-session cost and its source, then asks.** A Korean system locale (`ko_KR` and friends; on macOS the system setting is checked too) makes the question default to yes; anything else defaults to no, so users who never write Korean are not billed 1.5k tokens a session. The locale is only a default, so an English-locale machine used for Korean work can still turn it on right there.
319
+
320
+ Installs with nobody attached โ€” npm `postinstall`, CI, piped stdin โ€” skip the question and apply the locale default, because a blocked prompt hangs the install. In that case, if the locale is not Korean the setting is **left undecided rather than recorded**, so a later run at a terminal still gets to ask. Use `--yes` or `--no-input` to force the non-interactive path, or `CTS_NO_KOREAN=1` to skip the feature entirely. **Once you have turned it on or off yourself, that choice sticks โ€” an upgrade never overrides it.**
297
321
 
298
322
  > **Source and license**
299
323
  > The guidance text comes from [fluent-korean](https://github.com/snflkd/fluent-korean). Copyright (c) 2026 snflkd, MIT License.
@@ -379,12 +403,17 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
379
403
 
380
404
  ## Release notes
381
405
 
406
+ ### v3.21.0 (2026-08-22)
407
+ - **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.
408
+ - **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.
409
+ - **The Korean-guidance statusline chip is now `โœ๏ธ`, not `๊ฐ€`** โ€” every other chip is an emoji, so a bare syllable read as a stray character rather than a status indicator.
410
+
382
411
  ### v3.20.0 (2026-08-22)
383
412
  - **The README opening is now scannable** โ€” prose replaced by a one-line summary, the screenshot, and the install command up top. The three parts (routing, harness, ratchet) are a table; how a saving is computed (before โ†’ after โ†’ gap) is a diagram. The old 30-second pitch table, which repeated all of it, now lists only what the statusline additionally catches.
384
413
 
385
414
  ### v3.19.0 (2026-08-22)
386
415
  - **Korean writing guidance** โ€” corrects how Claude writes Korean (dropped sentence parts, noun-stopped sentences, translationese, em-dash overuse), injected once per session. Claude Code's output styles occupy a single global slot and must be configured per machine; this ships the guidance in the package and delivers it through the SessionStart hook already installed, so it **applies in every project and leaves the output-style slot free.** Text vendored from [fluent-korean](https://github.com/snflkd/fluent-korean) (Copyright (c) 2026 snflkd, MIT), license included.
387
- - **Decided at install time** โ€” a Korean system locale turns it on; anything else leaves it off. Your own on/off choice is preserved, so upgrades never override it. Skip with `CTS_NO_KOREAN=1`; a `๊ฐ€` chip shows in the statusline when active.
416
+ - **Decided at install time** โ€” a Korean system locale turns it on; anything else leaves it off. Your own on/off choice is preserved, so upgrades never override it. Skip with `CTS_NO_KOREAN=1`. (From v3.21.0 the install asks instead of deciding for you.)
388
417
  - **Corrected an overstated README claim** โ€” routing savings were described as "the whole product", but the measured โˆ’18.6% comes from the harness and ratchet. The relationship between the three is now stated accurately.
389
418
 
390
419
  ### v3.18.0 (2026-08-22)
package/README.md CHANGED
@@ -102,7 +102,9 @@ npm i -g claude-token-saver
102
102
 
103
103
  ์„ค์น˜ํ•˜๋ฉด Claude Code ํ™”๋ฉด ํ•˜๋‹จ์— statusline์ด ๊ณง๋ฐ”๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. `--ignore-scripts` ์˜ต์…˜์ด๋‚˜ sudo ์‚ฌ์šฉ ๋“ฑ์œผ๋กœ ์ž๋™ ๋“ฑ๋ก์ด ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด `claude-token-saver install`์„ ์‹คํ–‰ํ•ด ์ง์ ‘ ๋“ฑ๋กํ•˜์‹ญ์‹œ์˜ค.
104
104
 
105
- ์„ค์น˜ ํ•œ ๋ฒˆ์œผ๋กœ **statusline๊ณผ Skill, SessionStart ํ›…, ๐Ÿ…ท Harness(5์›์น™), ์ตœ์ดˆ route-scan์ด** ๋ชจ๋‘ ์ค€๋น„๋ฉ๋‹ˆ๋‹ค. Harness๋Š” `~/.claude/CLAUDE.md`์— ํ‘œ์‹œ๊ฐ€ ๋ถ™์€ ๋ธ”๋ก์œผ๋กœ **์ถ”๊ฐ€๋˜๋ฉฐ**, ๊ธฐ์กด์— ์ž‘์„ฑํ•ด ๋‘” ๋‚ด์šฉ์€ ๋ฐฑ์—…ํ•œ ๋’ค ๊ทธ๋Œ€๋กœ ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ ์„ค์ •๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ๋ฐ”๊พธ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž๋™ ์„ค์ •์„ ์›ํ•˜์ง€ ์•Š์œผ๋ฉด `CTS_NO_HARNESS=1 npm i -g claude-token-saver`๋กœ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๊ณ , ์ด๋ฏธ ์ ์šฉํ•œ ์„ค์ •์„ ๋˜๋Œ๋ฆฌ๋ ค๋ฉด `claude-token-saver harness uninit --global`์„ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค.
105
+ ์„ค์น˜ ํ•œ ๋ฒˆ์œผ๋กœ **statusline๊ณผ Skill, SessionStart ํ›…, ๐Ÿ…ท Harness(5์›์น™), ์ตœ์ดˆ route-scan์ด** ๋ชจ๋‘ ์ค€๋น„๋ฉ๋‹ˆ๋‹ค. Harness์™€ ํ•œ๊ตญ์–ด ๋ฌธ์ฒด ์ง€์นจ์€ **๋ฌด์—‡์ด ์ถ”๊ฐ€๋˜๋Š”์ง€ ๋ณด์—ฌ ์ค€ ๋’ค ์ผค์ง€ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค.** Harness๋Š” `~/.claude/CLAUDE.md`์— ํ‘œ์‹œ๊ฐ€ ๋ถ™์€ ๋ธ”๋ก์œผ๋กœ **์ถ”๊ฐ€๋˜๋ฉฐ**, ๊ธฐ์กด์— ์ž‘์„ฑํ•ด ๋‘” ๋‚ด์šฉ์€ ๋ฐฑ์—…ํ•œ ๋’ค ๊ทธ๋Œ€๋กœ ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ ์„ค์ •๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ๋ฐ”๊พธ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
106
+
107
+ ํ„ฐ๋ฏธ๋„์ด ์•„๋‹Œ ํ™˜๊ฒฝ(npm์˜ `postinstall`, CI, ํŒŒ์ดํ”„ ์ž…๋ ฅ)์—์„œ๋Š” ์งˆ๋ฌธ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๊ธฐ์กด ๊ธฐ๋ณธ๊ฐ’์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์งˆ๋ฌธ ์—†์ด ์ง„ํ–‰ํ•˜๋ ค๋ฉด `--yes`๋‚˜ `--no-input`, ์•„์˜ˆ ๊ฑด๋„ˆ๋›ฐ๋ ค๋ฉด `CTS_NO_HARNESS=1 npm i -g claude-token-saver`๋ฅผ ์“ฐ์‹ญ์‹œ์˜ค. ์ด๋ฏธ ์ ์šฉํ•œ ์„ค์ •์„ ๋˜๋Œ๋ฆฌ๋ ค๋ฉด `claude-token-saver harness uninit --global`์„ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค.
106
108
 
107
109
  > โš ๏ธ sudo๋กœ ๊ธ€๋กœ๋ฒŒ ์„ค์น˜๋ฅผ ํ•˜๋ฉด Skill์ด ์‚ฌ์šฉ์ž ๊ณ„์ •์ด ์•„๋‹ˆ๋ผ root์˜ `~/.claude`์— ๋“ฑ๋ก๋˜๋Š” ํ•จ์ •์ด ์žˆ์Šต๋‹ˆ๋‹ค. nvm์ด๋‚˜ fnm, Volta๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž ์˜์—ญ์— ์„ค์น˜ํ•˜๊ธฐ๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
108
110
 
@@ -281,9 +283,31 @@ claude-token-saver korean off # ๋„๊ธฐ
281
283
 
282
284
  Claude Code์˜ output style๋กœ๋„ ๊ฐ™์€ ์ผ์„ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, output style์€ **์ „์—ญ ์Šฌ๋กฏ ํ•˜๋‚˜**๋ผ์„œ ์ผœ๋Š” ์ˆœ๊ฐ„ ๋‹ค๋ฅธ ์Šคํƒ€์ผ์„ ๋ชป ์“ฐ๊ฒŒ ๋˜๊ณ  ๋จธ์‹ ๋งˆ๋‹ค ๋”ฐ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์€ ์ง€์นจ์„ ํŒจํ‚ค์ง€์— ๋‹ด๊ณ  ์ด๋ฏธ ์„ค์น˜๋œ SessionStart ํ›…์œผ๋กœ ์ „๋‹ฌํ•˜๋ฏ€๋กœ, **CLI๊ฐ€ ์„ค์น˜๋œ ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์— ์ ์šฉ๋˜๋ฉฐ output style ์Šฌ๋กฏ์€ ๋น„์›Œ ๋‘ก๋‹ˆ๋‹ค.** `/clear` ์ดํ›„์—๋„ ํ›…์ด ๋‹ค์‹œ ์‹คํ–‰๋˜์–ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
283
285
 
284
- ๋น„์šฉ์€ **์„ธ์…˜๋‹น ์•ฝ 1,500 ํ† ํฐ์ด๋ฉฐ ๋งค ํ„ด์ด ์•„๋‹ˆ๋ผ ์„ธ์…˜ ์‹œ์ž‘์— ํ•œ ๋ฒˆ๋งŒ** ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ์š”์ฒญ๋ถ€ํ„ฐ๋Š” ํ”„๋กฌํ”„ํŠธ ์บ์‹œ์— ์˜ฌ๋ผ๊ฐ€๋ฏ€๋กœ ์ถ”๊ฐ€ ๋ถ€๋‹ด์ด ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ์ผœ์ ธ ์žˆ์œผ๋ฉด statusline์— `๊ฐ€` ์นฉ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
286
+ ๋น„์šฉ์€ **์„ธ์…˜๋‹น ์•ฝ 1,500 ํ† ํฐ์ด๋ฉฐ ๋งค ํ„ด์ด ์•„๋‹ˆ๋ผ ์„ธ์…˜ ์‹œ์ž‘์— ํ•œ ๋ฒˆ๋งŒ** ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ์š”์ฒญ๋ถ€ํ„ฐ๋Š” ํ”„๋กฌํ”„ํŠธ ์บ์‹œ์— ์˜ฌ๋ผ๊ฐ€๋ฏ€๋กœ ์ถ”๊ฐ€ ๋ถ€๋‹ด์ด ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ์ผœ์ ธ ์žˆ์œผ๋ฉด statusline์— `โœ๏ธ` ์นฉ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
287
+
288
+ ### ์ ์šฉ ์ „ํ›„ ๋น„๊ต
289
+
290
+ ์Šฌ๋ž™ ๋ด‡์ด `claude -p`๋กœ ์ €์žฅ์†Œ๋ฅผ ์กฐ์‚ฌํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์Šค๋ ˆ๋“œ์— ๋‹ตํ•˜๋Š” ์‹ค์ œ ํŒŒ์ดํ”„๋ผ์ธ(yaml-sns-agent)์—์„œ, ์ง€์นจ๋งŒ ๊ป๋‹ค ์ผœ๊ณ  ๊ฐ™์€ ์งˆ๋ฌธ์„ ๋˜์ง„ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์งˆ๋ฌธ์€ "๋ Œ๋”๊ฐ€ ์‹คํŒจํ–ˆ์„ ๋•Œ ์›์ธ์„ ์–ด๋–ป๊ฒŒ ์ขํžˆ๋Š”์ง€ ํ•œ๊ตญ์–ด 3๋ฌธ์žฅ์œผ๋กœ ์„ค๋ช…ํ•ด๋ผ"์˜€์Šต๋‹ˆ๋‹ค.
291
+
292
+ **์ด์ „** (์ง€์นจ ๊บผ์ง)
293
+
294
+ > ๋ Œ๋” ์‹คํŒจ ์›์ธ ์ขํžˆ๊ธฐ 3๋‹จ๊ณ„: ๋จผ์ € `hn:still --check`๋กœ ์Šคํ‹ธ์ด ๋ช…์„ธ๋ณด๋‹ค ์ตœ์‹ ์ธ์ง€(FRESH/STALE) ํ™•์ธ โ€” STALE์ด๋ฉด ์žฌ์ƒ์„ฑ ์•ˆ ํ•ด์„œ ์˜› ์ž์‚ฐ์œผ๋กœ ๋ Œ๋” ์‹œ๋„ํ•œ ๊ฒŒ ์›์ธ. ๋‹ค์Œ caption-blocks๊ฐ€ ์ตœ์‹  ๋‚˜๋ ˆ์ด์…˜๊ณผ ๋‹จ์–ด์—ด ์ผ์น˜ํ•˜๋Š”์ง€ ์ง์ ‘ ๋Œ€์กฐ โ€” TTS ์žฌ์ƒ์„ฑ ํ›„ blocks ์žฌ์ƒ์„ฑ ๋น ์ง€๋ฉด ์ž๋ง‰ยท์Œ์„ฑ ์–ด๊ธ‹๋‚˜ ๋ Œ๋” ๊นจ์ง.
295
+
296
+ **์ดํ›„** (์ง€์นจ ์ผœ์ง)
285
297
 
286
- **์„ค์น˜ํ•  ๋•Œ ์ž๋™์œผ๋กœ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.** ์‹œ์Šคํ…œ ๋กœ์บ˜์ด ํ•œ๊ตญ์–ด์ด๋ฉด(`ko_KR` ๋“ฑ, macOS๋Š” ์‹œ์Šคํ…œ ์„ค์ •๊นŒ์ง€ ํ™•์ธ) ์„ค์น˜์™€ ๋™์‹œ์— ์ผœ์ง€๊ณ , ํ•œ๊ตญ์–ด ํ™˜๊ฒฝ์ด ์•„๋‹ˆ๋ฉด ๊บผ์ง„ ์ฑ„๋กœ ๋‘ก๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด๋ฅผ ์“ฐ์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ์„ธ์…˜๋งˆ๋‹ค 1,500 ํ† ํฐ์„ ์ฒญ๊ตฌํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•œ ํŒ๋‹จ์ž…๋‹ˆ๋‹ค. **ํ•œ ๋ฒˆ์ด๋ผ๋„ ์ง์ ‘ ์ผœ๊ฑฐ๋‚˜ ๋ˆ ๋’ค์—๋Š” ๊ทธ ์„ ํƒ์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ, ์—…๋ฐ์ดํŠธ ์„ค์น˜๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๊ฒฐ์ •์„ ๋˜๋Œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.** ์ž๋™ ์„ค์ •์„ ์›ํ•˜์ง€ ์•Š์œผ๋ฉด `CTS_NO_KOREAN=1`์„ ๋ถ™์—ฌ ์„ค์น˜ํ•˜์‹ญ์‹œ์˜ค.
298
+ > ๋จผ์ € `hn:still --check` ๋Œ๋ ค ์Šคํ‹ธ์ด ๋ช…์„ธ๋ณด๋‹ค ์ตœ์‹ ์ธ์ง€ ํ™•์ธํ•œ๋‹ค. STALE์ด๋ฉด ์žฌ์ƒ์„ฑ ์•ˆ ํ•ด์„œ ์ƒ๊ธด ๋ฌธ์ œ.
299
+ >
300
+ > ๋‹ค์Œ caption-blocks๊ฐ€ captions.json ๋‹จ์–ด์—ด๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๋ณธ๋‹ค. ๋‚ด๋ ˆ์ด์…˜ ์žฌTTS ํ›„ blocks ์žฌ์ƒ์„ฑ ๋น ์ง€๋ฉด ์˜› ์ž๋ง‰์ด ์ƒˆ ์Œ์„ฑ ์œ„์— ๋œฌ๋‹ค.
301
+
302
+ ๋ฐ”๋€ ์ง€์ ์€ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ์ฒซ์งธ, ์— ๋Œ€์‹œ๋กœ ์ด์–ด ๋ถ™์ด๋˜ ์ ˆ์ด ๋งˆ์นจํ‘œ๋กœ ๋Š์–ด์ ธ ํ•œ ๋ฌธ์žฅ์ด ํ•œ ๊ฐ€์ง€ ์‚ฌ์‹ค๋งŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋‘˜์งธ, "ํ™•์ธ", "๋Œ€์กฐ", "๋ Œ๋” ๊นจ์ง" ๊ฐ™์€ ๋ช…์‚ฌํ˜• ์ข…๊ฒฐ์ด "ํ™•์ธํ•œ๋‹ค", "๋ณธ๋‹ค", "๋œฌ๋‹ค"์ฒ˜๋Ÿผ ์„œ์ˆ ์–ด๋กœ ๋ฐ”๋€Œ์–ด ๋ฌด์—‡์„ ํ•˜๋ผ๋Š” ๊ฒƒ์ธ์ง€๊ฐ€ ๋ถ„๋ช…ํ•ด์ง‘๋‹ˆ๋‹ค. ์…‹์งธ, "์ž๋ง‰ยท์Œ์„ฑ ์–ด๊ธ‹๋‚˜"์ฒ˜๋Ÿผ ์กฐ์‚ฌ๊ฐ€ ๋น ์ง„ ์ž๋ฆฌ์— ์กฐ์‚ฌ๊ฐ€ ๋Œ์•„์™€ ์–ด๋–ค ์„ฑ๋ถ„์ด ์ฃผ์–ด์ด๊ณ  ๋ชฉ์ ์–ด์ธ์ง€ ์ฝ๋Š” ์ฆ‰์‹œ ์žกํž™๋‹ˆ๋‹ค.
303
+
304
+ ๊ธฐ์ˆ ์  ๋‚ด์šฉ์€ ์–‘์ชฝ์ด ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ง€์นจ์€ ํŒ๋‹จ์ด๋‚˜ ์ •ํ™•๋„๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฌธ์žฅ์˜ ์™„์„ฑ๋„์—๋งŒ ๊ด€์—ฌํ•˜๋ฏ€๋กœ, ๋‹ต์ด ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๊ฐ™์€ ๋‹ต์„ ๋‹ค์‹œ ์ฝ์ง€ ์•Š์•„๋„ ๋˜๋Š” ํ˜•ํƒœ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ์Šฌ๋ž™์ฒ˜๋Ÿผ ์‚ฌ๋žŒ์ด ์Šคํฌ๋กคํ•˜๋ฉฐ ์ฝ๋Š” ์ฑ„๋„์—์„œ๋Š” ์ด ์ฐจ์ด๊ฐ€ ๋˜๋ฌป๋Š” ํšŸ์ˆ˜๋ฅผ ์ค„์ด๊ณ , ๋˜๋ฌป์ง€ ์•Š๋Š” ๋งŒํผ ํ† ํฐ๋„ ์•„๋‚๋‹ˆ๋‹ค.
305
+
306
+ ### ์„ค์น˜ํ•  ๋•Œ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค
307
+
308
+ ์„ค์น˜ ๊ณผ์ •์—์„œ **์ง€์นจ์˜ ๋‚ด์šฉ๊ณผ ์„ธ์…˜๋‹น ๋น„์šฉ, ์ถœ์ฒ˜๋ฅผ ๋จผ์ € ๋ณด์—ฌ ์ค€ ๋‹ค์Œ ์ผค์ง€ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค.** ์‹œ์Šคํ…œ ๋กœ์บ˜์ด ํ•œ๊ตญ์–ด์ด๋ฉด(`ko_KR` ๋“ฑ, macOS๋Š” ์‹œ์Šคํ…œ ์„ค์ •๊นŒ์ง€ ํ™•์ธ) ์งˆ๋ฌธ์˜ ๊ธฐ๋ณธ๊ฐ’์ด "์ผฌ"์ด ๋˜๊ณ , ํ•œ๊ตญ์–ด ํ™˜๊ฒฝ์ด ์•„๋‹ˆ๋ฉด ๊ธฐ๋ณธ๊ฐ’์ด "๋”"์ž…๋‹ˆ๋‹ค. ๋กœ์บ˜์€ ๋‹ต์ด ์•„๋‹ˆ๋ผ ๊ธฐ๋ณธ๊ฐ’์ผ ๋ฟ์ด๋ฏ€๋กœ ์˜์–ด ๋กœ์บ˜์—์„œ ํ•œ๊ตญ์–ด๋กœ ์ž‘์—…ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ๊ทธ ์ž๋ฆฌ์—์„œ ์ผค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
309
+
310
+ npm์˜ `postinstall`์ด๋‚˜ CI์ฒ˜๋Ÿผ ์‚ฌ๋žŒ์ด ๋ถ™์–ด ์žˆ์ง€ ์•Š์€ ์„ค์น˜์—์„œ๋Š” ์งˆ๋ฌธ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋กœ์บ˜ ๊ธฐ๋ณธ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋ฉˆ์ถฐ ์„œ๋ฉด ์„ค์น˜ ์ž์ฒด๊ฐ€ ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋•Œ ๋กœ์บ˜์ด ํ•œ๊ตญ์–ด๊ฐ€ ์•„๋‹ˆ๋ฉด **์„ค์ •์„ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๋ฏธ๊ฒฐ์ •์œผ๋กœ ๋‚จ๊ฒจ ๋‘๋ฏ€๋กœ**, ๋‚˜์ค‘์— ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์‹œ ์„ค์น˜ํ•˜๋ฉด ๊ทธ๋•Œ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค. ์งˆ๋ฌธ ์—†์ด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋„˜๊ธฐ๋ ค๋ฉด `--yes`๋‚˜ `--no-input`์„, ๊ธฐ๋Šฅ ์ž์ฒด๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋ ค๋ฉด `CTS_NO_KOREAN=1`์„ ์“ฐ์‹ญ์‹œ์˜ค. **ํ•œ ๋ฒˆ์ด๋ผ๋„ ์ง์ ‘ ์ผœ๊ฑฐ๋‚˜ ๋ˆ ๋’ค์—๋Š” ๊ทธ ์„ ํƒ์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ, ์—…๋ฐ์ดํŠธ ์„ค์น˜๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๊ฒฐ์ •์„ ๋˜๋Œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.**
287
311
 
288
312
  > **์ถœ์ฒ˜์™€ ๋ผ์ด์„ ์Šค**
289
313
  > ์ง€์นจ ์›๋ฌธ์€ [fluent-korean](https://github.com/snflkd/fluent-korean)์—์„œ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค. Copyright (c) 2026 snflkd, MIT License.
@@ -345,12 +369,17 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
345
369
 
346
370
  ## ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ
347
371
 
372
+ ### v3.21.0 (2026-08-22)
373
+ - **์„ค์น˜๊ฐ€ ๋ฌด์—‡์„ ์ผœ๋Š”์ง€ ๋ณด์—ฌ ์ค€ ๋’ค ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค** โ€” ์ง€๊ธˆ๊นŒ์ง€๋Š” ๐Ÿ…ท Harness 5์›์น™๊ณผ ํ•œ๊ตญ์–ด ๋ฌธ์ฒด ์ง€์นจ์„ ์„ค์น˜๊ฐ€ ์Šค์Šค๋กœ ์ผฐ๊ณ , ์‚ฌ์šฉ์ž๋Š” ๊ฒฐ๊ณผ๋งŒ ๋ณผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ harness๋Š” ๋‹ค์„ฏ ์›์น™์˜ ์ œ๋ชฉ์„, ํ•œ๊ตญ์–ด ์ง€์นจ์€ ๋ฐ”๊พธ๋Š” ๋‚ด์šฉ๊ณผ ์„ธ์…˜๋‹น ๋น„์šฉ๊ณผ ์ถœ์ฒ˜๋ฅผ ๋จผ์ € ์ถœ๋ ฅํ•œ ๋‹ค์Œ ์ผค์ง€ ๋ฌป์Šต๋‹ˆ๋‹ค. ๋กœ์บ˜ ๊ฐ์ง€๋Š” ๋‹ต์ด ์•„๋‹ˆ๋ผ ์งˆ๋ฌธ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋‚ด๋ ค์™”์œผ๋ฏ€๋กœ, ์˜์–ด ๋กœ์บ˜์—์„œ ํ•œ๊ตญ์–ด๋กœ ์ž‘์—…ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ๊ทธ ์ž๋ฆฌ์—์„œ ์ผค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
374
+ - **์‚ฌ๋žŒ์ด ๋ถ™์–ด ์žˆ์ง€ ์•Š์€ ์„ค์น˜๋Š” ์˜ˆ์ „๋Œ€๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค** โ€” npm์˜ `postinstall`, CI, ํŒŒ์ดํ”„ ์ž…๋ ฅ, `CTS_NO_INPUT=1`์—์„œ๋Š” ์งˆ๋ฌธ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๊ธฐ์กด ๊ธฐ๋ณธ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋ฉˆ์ถฐ ์„œ๋ฉด ์„ค์น˜๊ฐ€ ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. `--yes`์™€ `--no-input`์œผ๋กœ ์ง์ ‘ ๊ฐ•์ œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋น„๋Œ€ํ™”ํ˜•์ด๋ฉด์„œ ๋กœ์บ˜์ด ํ•œ๊ตญ์–ด๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋Š” ์„ค์ •์„ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๋ฏธ๊ฒฐ์ •์œผ๋กœ ๋‚จ๊ฒจ ๋‘๋ฏ€๋กœ, ๋‚˜์ค‘์— ํ„ฐ๋ฏธ๋„์—์„œ ์„ค์น˜ํ•˜๋ฉด ๊ทธ๋•Œ ๋ฌผ์–ด๋ด…๋‹ˆ๋‹ค.
375
+ - **statusline์˜ ํ•œ๊ตญ์–ด ์ง€์นจ ์นฉ์„ `๊ฐ€`์—์„œ `โœ๏ธ`๋กœ ๋ฐ”๊ฟจ์Šต๋‹ˆ๋‹ค** โ€” ๋‹ค๋ฅธ ์นฉ์ด ๋ชจ๋‘ ์ด๋ชจ์ง€๋ผ์„œ ์Œ์ ˆ ํ•˜๋‚˜๊ฐ€ ์ƒํƒœ ํ‘œ์‹œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ž˜๋ชป ์„ž์—ฌ ๋“ค์–ด๊ฐ„ ๊ธ€์ž๋กœ ๋ณด์˜€์Šต๋‹ˆ๋‹ค.
376
+
348
377
  ### v3.20.0 (2026-08-22)
349
378
  - **README ์ฒซ ํ™”๋ฉด์„ ํ›‘์–ด ์ฝ์„ ์ˆ˜ ์žˆ๊ฒŒ ๋‹ค์‹œ ์งฐ์Šต๋‹ˆ๋‹ค** โ€” ์„ค๋ช… ๋ฌธ๋‹จ์„ ๊ฑท์–ด๋‚ด๊ณ  ํ•œ ์ค„ ์š”์•ฝ, ์Šคํฌ๋ฆฐ์ƒท, ์„ค์น˜ ๋ช…๋ น์„ ์•ž์— ๋‘์—ˆ์Šต๋‹ˆ๋‹ค. ๋ผ์šฐํŒ…ยทHarnessยทratchet ์„ธ ๊ธฐ๋Šฅ์€ ํ‘œ๋กœ, ์ ˆ๊ฐ์•ก ๊ณ„์‚ฐ ๋ฐฉ์‹(๊ธฐ์ค€ ๋ชจ๋ธ โ†’ ์‹คํ–‰ ๋ชจ๋ธ โ†’ ์ฐจ์•ก)์€ ๋„์‹์œผ๋กœ ๋ฐ”๊ฟจ์Šต๋‹ˆ๋‹ค. ๋’ค์—์„œ ๊ฐ™์€ ๋‚ด์šฉ์„ ๋ฐ˜๋ณตํ•˜๋˜ "30์ดˆ ์š”์•ฝ" ํ‘œ๋Š” statusline์ด ์ถ”๊ฐ€๋กœ ์žก์•„ ์ฃผ๋Š” ํ•ญ๋ชฉ๋งŒ ๋‚จ๊ฒจ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
350
379
 
351
380
  ### v3.19.0 (2026-08-22)
352
381
  - **ํ•œ๊ตญ์–ด ๋ฌธ์ฒด ์ง€์นจ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค** โ€” Claude๊ฐ€ ํ•œ๊ตญ์–ด๋ฅผ ์“ธ ๋•Œ ๋‚˜ํƒ€๋‚˜๋Š” ๋ฌธ์ฒด ๊ฒฐํ•จ(๋ฌธ์žฅ ์„ฑ๋ถ„ ์ƒ๋žต, ๋ช…์‚ฌํ˜• ์ข…๊ฒฐ, ๋ฒˆ์—ญ์ฒด, ์— ๋Œ€์‹œ ๋‚จ์šฉ)์„ ๊ต์ •ํ•˜๋Š” ์ง€์นจ์„ ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค. Claude Code์˜ output style์€ ์ „์—ญ ์Šฌ๋กฏ ํ•˜๋‚˜๋ฅผ ์ฐจ์ง€ํ•˜๊ณ  ๋จธ์‹ ๋งˆ๋‹ค ์„ค์ •ํ•ด์•ผ ํ•˜์ง€๋งŒ, ์ด ๊ธฐ๋Šฅ์€ ์ง€์นจ์„ ํŒจํ‚ค์ง€์— ๋‹ด๊ณ  ์ด๋ฏธ ์„ค์น˜๋œ SessionStart ํ›…์œผ๋กœ ์ „๋‹ฌํ•˜๋ฏ€๋กœ **CLI๊ฐ€ ์„ค์น˜๋œ ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์— ์ ์šฉ๋˜๋ฉฐ output style ์Šฌ๋กฏ์€ ๋น„์›Œ ๋‘ก๋‹ˆ๋‹ค.** ์ง€์นจ ์›๋ฌธ์€ [fluent-korean](https://github.com/snflkd/fluent-korean)(Copyright (c) 2026 snflkd, MIT)์—์„œ ๊ฐ€์ ธ์™”๊ณ  ๋ผ์ด์„ ์Šค ์ „๋ฌธ์„ ํ•จ๊ป˜ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค.
353
- - **์„ค์น˜์™€ ๋™์‹œ์— ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค** โ€” ์‹œ์Šคํ…œ ๋กœ์บ˜์ด ํ•œ๊ตญ์–ด์ด๋ฉด ์„ค์น˜ ์‹œ ์ž๋™์œผ๋กœ ์ผœ์ง€๊ณ , ์•„๋‹ˆ๋ฉด ๊บผ ๋‘ก๋‹ˆ๋‹ค. ์ง์ ‘ ์ผœ๊ฑฐ๋‚˜ ๋ˆ ๋’ค์—๋Š” ๊ทธ ์„ ํƒ์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ ์—…๋ฐ์ดํŠธ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๊ฒฐ์ •์„ ๋˜๋Œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. `CTS_NO_KOREAN=1`๋กœ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๊ณ , ์ผœ์ ธ ์žˆ์œผ๋ฉด statusline์— `๊ฐ€` ์นฉ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
382
+ - **์„ค์น˜์™€ ๋™์‹œ์— ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค** โ€” ์‹œ์Šคํ…œ ๋กœ์บ˜์ด ํ•œ๊ตญ์–ด์ด๋ฉด ์„ค์น˜ ์‹œ ์ž๋™์œผ๋กœ ์ผœ์ง€๊ณ , ์•„๋‹ˆ๋ฉด ๊บผ ๋‘ก๋‹ˆ๋‹ค. ์ง์ ‘ ์ผœ๊ฑฐ๋‚˜ ๋ˆ ๋’ค์—๋Š” ๊ทธ ์„ ํƒ์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ ์—…๋ฐ์ดํŠธ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๊ฒฐ์ •์„ ๋˜๋Œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. `CTS_NO_KOREAN=1`๋กœ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(v3.21.0๋ถ€ํ„ฐ๋Š” ์„ค์น˜๊ฐ€ ๋ฌป์Šต๋‹ˆ๋‹ค).
354
383
  - **README์˜ ๊ณผ์žฅ๋œ ์„ค๋ช…์„ ๋ฐ”๋กœ์žก์•˜์Šต๋‹ˆ๋‹ค** โ€” ๋ผ์šฐํŒ… ์ ˆ๊ฐ์•ก์„ "์ด ๋„๊ตฌ์˜ ์ „๋ถ€"๋ผ๊ณ  ์ ์—ˆ์œผ๋‚˜, ์‹ค์ธก โˆ’18.6%๋Š” Harness์™€ ratchet์„ ๋„์ž…ํ•œ ํšจ๊ณผ์ž…๋‹ˆ๋‹ค. ์„ธ ๊ธฐ๋Šฅ์˜ ๊ด€๊ณ„๋ฅผ ์ •ํ™•ํžˆ ๋‹ค์‹œ ์ผ์Šต๋‹ˆ๋‹ค.
355
384
 
356
385
  ### v3.18.0 (2026-08-22)
package/bin/cli.js CHANGED
@@ -10,6 +10,8 @@
10
10
  * npx claude-token-saver --format csv # CSV output
11
11
  * npx claude-token-saver --project myproj # filter by project
12
12
  * npx claude-token-saver route-scan # detect recurring easy work โ†’ haiku-delegation candidates
13
+ * npx claude-token-saver install # set up skill/hooks/statusline; asks about harness + Korean guidance
14
+ * npx claude-token-saver install --yes # take the defaults without asking (same as --no-input)
13
15
  * npx claude-token-saver --install-hook # install PostToolUse hook
14
16
  * npx claude-token-saver --uninstall-hook # remove hook
15
17
  * npx claude-token-saver --hook-run # internal: called by hook
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.20.1",
3
+ "version": "3.21.1",
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": {
@@ -5,15 +5,27 @@
5
5
  * a legacy command file is removed automatically. Cross-platform.
6
6
  * claude-token-saver install # install/update the skill
7
7
  * claude-token-saver install --force # overwrite existing skill file
8
+ * claude-token-saver install --yes # take the defaults instead of asking
9
+ * claude-token-saver install --no-input # same as --yes; also implied by CTS_NO_INPUT=1
8
10
  */
9
11
 
10
12
  import { debug } from '../debug.js';
11
13
 
14
+ /** Thrown when the user declines an optional step, so it is not reported as a failure. */
15
+ class SkipStep extends Error {}
16
+
12
17
  export async function run({ hasFlag }) {
13
18
  const { installAll } = await import('../installer.js');
14
19
  const { userLanguage } = await import('../config.js');
20
+ const { canPrompt, confirm } = await import('../prompt.js');
15
21
  const lang = userLanguage();
16
22
  const force = hasFlag('--force');
23
+ // Two features below write to ~/.claude and cost tokens in every session,
24
+ // so the install shows what they contain and asks before turning them on.
25
+ // Asking is only possible with a human attached: postinstall, CI and pipes
26
+ // fall through to the previous automatic defaults so an unattended upgrade
27
+ // behaves exactly as it did before.
28
+ const interactive = canPrompt() && !hasFlag('--yes') && !hasFlag('--no-input');
17
29
  const print = (kind, r) => {
18
30
  const verb = r.action === 'exists' ? 'already exists' : r.action;
19
31
  console.log(` ${kind}: ${r.path} (${verb})`);
@@ -84,6 +96,30 @@ export async function run({ hasFlag }) {
84
96
  ? ` harness: ์ด๋ฏธ ์„ค์ •๋จ โ€” ๐Ÿ…ท ${before.configured}/${before.total} (${before.file})`
85
97
  : ` harness: already set up โ€” ๐Ÿ…ท ${before.configured}/${before.total} (${before.file})`);
86
98
  } else {
99
+ // Show the five principle headings before writing them. The block goes
100
+ // into the file the model reads at the start of every session, so the
101
+ // user should see its contents before agreeing, not after.
102
+ const { HARNESS_SECTIONS } = await import('../harness-templates.js');
103
+ console.log('');
104
+ console.log(lang === 'ko'
105
+ ? ' harness: ๋‹ค์Œ 5์›์น™์„ ~/.claude/CLAUDE.md ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค (๋ชจ๋“  ํ”„๋กœ์ ํŠธ์— ์ ์šฉ).'
106
+ : ' harness: the following 5 principles would be added to ~/.claude/CLAUDE.md (all projects).');
107
+ for (const s of HARNESS_SECTIONS) {
108
+ console.log(` ${s.heading.replace(/^###\s*/, '')}`);
109
+ }
110
+ console.log(lang === 'ko'
111
+ ? ' ๊ธฐ์กด ๋‚ด์šฉ์€ ์ง€์šฐ์ง€ ์•Š๊ณ  ๋’ค์— ๋ง๋ถ™์ด๋ฉฐ, ์›๋ณธ์€ .bak ์œผ๋กœ ๋ฐฑ์—…๋ฉ๋‹ˆ๋‹ค.'
112
+ : ' existing content is kept โ€” the block is appended and the original is backed up as .bak.');
113
+ let proceed = true;
114
+ if (interactive) {
115
+ proceed = await confirm(lang === 'ko' ? ' ์ง€๊ธˆ ์„ค์ •ํ• ๊นŒ์š”?' : ' Set this up now?', { defaultValue: true });
116
+ }
117
+ if (!proceed) {
118
+ console.log(lang === 'ko'
119
+ ? ' harness: ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค โ€” ๋‚˜์ค‘์— `claude-token-saver harness init --global` ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'
120
+ : ' harness: skipped โ€” set it up later with `claude-token-saver harness init --global`.');
121
+ throw new SkipStep();
122
+ }
87
123
  const h = harnessInit({ scope: 'global' });
88
124
  console.log('');
89
125
  for (const p of h.backedUp) {
@@ -100,12 +136,17 @@ export async function run({ hasFlag }) {
100
136
  : ' to undo: claude-token-saver harness uninit --global');
101
137
  }
102
138
  } catch (e) {
139
+ // A declined prompt already printed its own line; only real failures fall
140
+ // through to the advice below.
141
+ if (e instanceof SkipStep) { /* user said no โ€” nothing to report */ }
142
+ else {
103
143
  // Never fail an install over this โ€” the statusline and Skill are already
104
144
  // in place, and `harness init` remains available as a manual step.
105
145
  debug('install:harness-init', e);
106
146
  console.log(lang === 'ko'
107
147
  ? ' harness: ์ž๋™ ์„ค์ •์„ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค โ€” `claude-token-saver harness init --global` ๋กœ ์ง์ ‘ ์„ค์ •ํ•˜์„ธ์š”.'
108
148
  : ' harness: auto-setup skipped โ€” run `claude-token-saver harness init --global` yourself.');
149
+ }
109
150
  }
110
151
 
111
152
  // Korean writing guidance. Decided here rather than left to a command the
@@ -125,23 +166,49 @@ export async function run({ hasFlag }) {
125
166
  console.log(lang === 'ko'
126
167
  ? ` korean: ๊ธฐ์กด ์„ค์ • ์œ ์ง€ โ€” ํ•œ๊ตญ์–ด ๋ฌธ์ฒด ์ง€์นจ ${ks.koreanStyleEnabled() ? '์ผœ์ง' : '๊บผ์ง'}`
127
168
  : ` korean: keeping your setting โ€” Korean writing guidance is ${ks.koreanStyleEnabled() ? 'on' : 'off'}`);
128
- } else if (ks.koreanLocaleDetected()) {
129
- ks.setKoreanStyleEnabled(true);
169
+ } else {
170
+ // The locale only decides what the question defaults to. It is a good
171
+ // guess, not an answer, so a user at a terminal gets to overrule it
172
+ // either way โ€” including turning the guidance on where the locale is
173
+ // English but the person writing is not.
174
+ const detected = ks.koreanLocaleDetected();
130
175
  console.log('');
131
176
  console.log(lang === 'ko'
132
- ? ' korean: ํ•œ๊ตญ์–ด ํ™˜๊ฒฝ์ด ๊ฐ์ง€๋˜์–ด ๋ฌธ์ฒด ์ง€์นจ์„ ์ผฐ์Šต๋‹ˆ๋‹ค โ€” ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์˜ ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค.'
133
- : ' korean: Korean locale detected โ€” writing guidance enabled, injected at session start in every project.');
177
+ ? ' korean: ํ•œ๊ตญ์–ด ๋ฌธ์ฒด ์ง€์นจ(fluent-korean)์„ ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'
178
+ : ' korean: Korean writing guidance (fluent-korean) can be injected at session start.');
179
+ console.log(lang === 'ko'
180
+ ? ' ๋‚ด์šฉ: ์กฐ์‚ฌยท์–ด๋ฏธ๋ฅผ ์ƒ๋žตํ•˜์ง€ ์•Š๊ณ , ๋ช…์‚ฌ๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ ์„œ์ˆ ์–ด๋กœ ๋ฌธ์žฅ์„ ๋๋งบ์œผ๋ฉฐ,'
181
+ : ' what it does: keeps particles and endings, ends sentences with a predicate,');
182
+ console.log(lang === 'ko'
183
+ ? ' ๋ฒˆ์—ญ์ฒด ๋Œ€์‹  ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋ฅผ ์“ฐ๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ์™€ ์ฃผ์„์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.'
184
+ : ' and asks for idiomatic Korean over translationese. Code and comments are exempt.');
185
+ console.log(lang === 'ko'
186
+ ? ' ๋น„์šฉ: ์„ธ์…˜๋‹น ์•ฝ 1.5k ํ† ํฐ(ํ„ด๋งˆ๋‹ค๊ฐ€ ์•„๋‹ˆ๋ผ ์„ธ์…˜ ์‹œ์ž‘ ์‹œ 1ํšŒ, ์ดํ›„ ํ”„๋กฌํ”„ํŠธ ์บ์‹œ์— ํฌํ•จ).'
187
+ : ' cost: ~1.5k tokens per session (once at session start, cached from the second request on).');
134
188
  console.log(lang === 'ko'
135
189
  ? ` ์ถœ์ฒ˜: ${ks.KOREAN_STYLE_SOURCE}`
136
190
  : ` source: ${ks.KOREAN_STYLE_SOURCE}`);
137
191
  console.log(lang === 'ko'
138
- ? ' ๋„๋ ค๋ฉด: claude-token-saver korean off'
139
- : ' turn off with: claude-token-saver korean off');
140
- } else {
141
- console.log('');
142
- console.log(lang === 'ko'
143
- ? ' korean: ํ•œ๊ตญ์–ด ํ™˜๊ฒฝ์ด ์•„๋‹ˆ์–ด์„œ ๊บผ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค โ€” ํ•„์š”ํ•˜๋ฉด `claude-token-saver korean on`.'
144
- : ' korean: left off (no Korean locale detected) โ€” enable with `claude-token-saver korean on`.');
192
+ ? ` ๊ฐ์ง€๋œ ํ™˜๊ฒฝ: ${detected ? 'ํ•œ๊ตญ์–ด (๊ธฐ๋ณธ๊ฐ’ ์ผฌ)' : 'ํ•œ๊ตญ์–ด ์•„๋‹˜ (๊ธฐ๋ณธ๊ฐ’ ๋”)'}`
193
+ : ` detected locale: ${detected ? 'Korean (default on)' : 'not Korean (default off)'}`);
194
+ let enable = detected;
195
+ if (interactive) {
196
+ enable = await confirm(lang === 'ko' ? ' ์ผค๊นŒ์š”?' : ' Enable it?', { defaultValue: detected });
197
+ }
198
+ // Record the choice only when it is really a choice. An unattended
199
+ // install on a non-Korean machine leaves the setting undecided so that
200
+ // a later run at a terminal still gets to ask, instead of silently
201
+ // inheriting a default the user never saw.
202
+ if (interactive || enable) ks.setKoreanStyleEnabled(enable);
203
+ if (enable) {
204
+ console.log(lang === 'ko'
205
+ ? ' korean: ์ผฐ์Šต๋‹ˆ๋‹ค โ€” ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์˜ ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ์ฃผ์ž…๋ฉ๋‹ˆ๋‹ค. ๋„๋ ค๋ฉด `claude-token-saver korean off`.'
206
+ : ' korean: enabled โ€” injected at session start in every project. Turn off with `claude-token-saver korean off`.');
207
+ } else {
208
+ console.log(lang === 'ko'
209
+ ? ' korean: ๊บผ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค โ€” ๋‚˜์ค‘์— `claude-token-saver korean on` ์œผ๋กœ ์ผค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'
210
+ : ' korean: left off โ€” enable later with `claude-token-saver korean on`.');
211
+ }
145
212
  }
146
213
  } catch (e) {
147
214
  debug('install:korean-style', e); // optional feature; never fail install
package/src/prompt.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * prompt โ€” minimal yes/no prompting for the install flow.
3
+ *
4
+ * Everything here exists to answer one question: may we stop and ask, or must
5
+ * we fall back to a default? Most installs of this package run as npm's
6
+ * postinstall, where stdin is not a terminal and a readline prompt would either
7
+ * hang the install or read garbage. So the rule is: ask only when a human is
8
+ * demonstrably on the other end, and otherwise keep the previous
9
+ * decide-it-for-them behavior untouched.
10
+ */
11
+
12
+ import { createInterface } from 'node:readline';
13
+
14
+ /**
15
+ * Whether it is safe to block on a question.
16
+ *
17
+ * `npm_lifecycle_event` is checked in addition to the TTY test because npm can
18
+ * leave a TTY attached while still running the script unattended; CI is
19
+ * checked because build agents deadlock rather than answer.
20
+ */
21
+ export function canPrompt({ env = process.env, stdin = process.stdin, stdout = process.stdout } = {}) {
22
+ if (env.CTS_NO_INPUT === '1') return false;
23
+ if (env.CI && env.CI !== 'false') return false;
24
+ if (env.npm_lifecycle_event === 'postinstall') return false;
25
+ return Boolean(stdin.isTTY && stdout.isTTY);
26
+ }
27
+
28
+ /**
29
+ * Ask a yes/no question and resolve to a boolean.
30
+ *
31
+ * An empty answer takes `defaultValue`, which is also what a closed stream
32
+ * resolves to, so a prompt that somehow runs unattended still terminates with
33
+ * the same choice the non-interactive path would have made.
34
+ */
35
+ export function confirm(question, { defaultValue = true, input = process.stdin, output = process.stdout } = {}) {
36
+ const hint = defaultValue ? '[Y/n]' : '[y/N]';
37
+ return new Promise((resolve) => {
38
+ const rl = createInterface({ input, output });
39
+ let answered = false;
40
+ rl.question(`${question} ${hint} `, (answer) => {
41
+ answered = true;
42
+ rl.close();
43
+ const a = String(answer).trim().toLowerCase();
44
+ if (a === 'y' || a === 'yes') return resolve(true);
45
+ if (a === 'n' || a === 'no') return resolve(false);
46
+ resolve(defaultValue); // empty line, or anything we do not recognize
47
+ });
48
+ // A stream that ends without a line โ€” a closed pipe, Ctrl-D โ€” must still
49
+ // settle the promise, or the install would wait forever.
50
+ rl.on('close', () => { if (!answered) resolve(defaultValue); });
51
+ });
52
+ }