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 +34 -5
- package/README.md +33 -4
- package/bin/cli.js +2 -0
- package/package.json +1 -1
- package/src/commands/install.js +78 -11
- package/src/prompt.js +52 -0
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** |
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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`์ ํ์๊ฐ ๋ถ์ ๋ธ๋ก์ผ๋ก **์ถ๊ฐ๋๋ฉฐ**, ๊ธฐ์กด์ ์์ฑํด ๋ ๋ด์ฉ์ ๋ฐฑ์
ํ ๋ค ๊ทธ๋๋ก ๋ณด์กดํฉ๋๋ค. ์ด๋ฏธ ์ค์ ๋์ด ์๋ ๊ฒฝ์ฐ์๋ ์๋ฌด๊ฒ๋ ๋ฐ๊พธ์ง ์์ต๋๋ค.
|
|
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
|
-
|
|
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`๋ก ๊ฑด๋๋ธ ์
|
|
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.
|
|
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": {
|
package/src/commands/install.js
CHANGED
|
@@ -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
|
|
129
|
-
|
|
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
|
|
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
|
-
?
|
|
139
|
-
:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|