claude-token-saver 3.9.0 โ†’ 3.9.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rootstudioyaml
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md CHANGED
@@ -17,6 +17,23 @@ npm i -g claude-token-saver # postinstall auto-registers the statusline + Skil
17
17
 
18
18
  ![statusline example](./docs/statusline.png)
19
19
 
20
+ ## ๐Ÿ“บ Came here from the video? โ€” 60 seconds
21
+
22
+ This is not a router. It never intercepts a request in realtime.
23
+ **After a session ends** it reads your local logs, finds the easy patterns your expensive model
24
+ kept handling, and promotes them into rules so a cheaper model takes them **from the next session
25
+ onward**. Rules are scoped global or per-project.
26
+
27
+ ```bash
28
+ npm i -g claude-token-saver@latest
29
+ claude-token-saver route-scan # find delegation candidates in your own history (0 LLM calls)
30
+ claude-token-saver route-scan rules # list promoted rules ยท rm <N> to remove
31
+ ```
32
+
33
+ Thresholds come from **your own last-14-day distribution (p25/p75)**, not someone else's benchmark.
34
+ Measured rule-health โ€” whether a delegated run actually succeeded โ€” landed in [v3.9.0](#v390-2026-08-01).
35
+
36
+
20
37
  ## โšก Why โ€” the 30-second pitch
21
38
 
22
39
  | | |
@@ -144,19 +161,21 @@ An auto `.bak` is kept, but **the session context that earned the rule its place
144
161
 
145
162
  ## ๐Ÿ“ฆ compact-window โ€” pin where a 1M session compacts
146
163
 
147
- Claude Code compacts when usage approaches `min(autoCompactWindow, model max context)`. On a 1M window, with that value unset, compaction only fires near 800k โ€” and until then every request re-bills the whole context. Pinning 400k keeps twice a 200k session's headroom for the genuinely large pastes while cutting off the runaway tail.
164
+ Claude Code compacts when usage approaches `min(autoCompactWindow, model max context)`. On a 1M window, with that value unset, compaction only fires near 800k โ€” and until then every request re-bills the whole context. **1M is too large; the recommendation is a 400kโ€“700k band** โ€” 2โ€“3.5x a 200k session's headroom for the genuinely large pastes, with the runaway tail cut off.
165
+
166
+ **Anything inside the band is left alone.** 400k is the floor where the saving beats the extra compactions, and long sessions often want more room than that. Only an unset window, or one above 700k, is warned about (a smaller one is a deliberate, more aggressive choice).
148
167
 
149
168
  **200k sessions are never warned** โ€” their window is already at or below 200k, so the setting cannot change anything.
150
169
 
151
170
  ```bash
152
171
  claude-token-saver compact-window # status (model, window, value, source)
153
- claude-token-saver compact-window set --global # pin 400k in ~/.claude/settings.json
172
+ claude-token-saver compact-window set --global # pin 500k (mid-band) in ~/.claude/settings.json
154
173
  claude-token-saver compact-window set --project # pin it in <root>/.claude/settings.json
155
- claude-token-saver compact-window set --global --value 250k # explicit value (100kโ€“1M)
174
+ claude-token-saver compact-window set --global --value 600k # explicit value (100kโ€“1M)
156
175
  claude-token-saver compact-window off | on # toggle the warning
157
176
  ```
158
177
 
159
- - On a 1M model with the value unset or above 400k, the statusline shows `๐Ÿ…ทโš  compact-window?` and the session briefing hands the model the exact registration command.
178
+ - On a 1M model with the value unset or above 700k, the statusline shows `๐Ÿ…ทโš  compact-window?` and the session briefing hands the model the exact registration command.
160
179
  - Scope (`--global`/`--project`) is **required** for `set` โ€” a global settings file is never edited on a guess.
161
180
  - Every other key in `settings.json` is preserved and a `.bak` is written first. Malformed JSON aborts the write untouched.
162
181
  - An exported `CLAUDE_CODE_AUTO_COMPACT_WINDOW` beats settings.json; `set` detects that and says so.
@@ -263,6 +282,15 @@ Also update `statusLine.command` in `~/.claude/settings.json` to `claude-token-s
263
282
 
264
283
  ## Release notes
265
284
 
285
+ ### v3.9.2 (2026-08-01)
286
+ - **Added a LICENSE file (MIT)** โ€” the field existed in `package.json` but the file did not, which blocked license review for company adoption. It ships in the npm tarball now via `files`.
287
+ - **Package description and keywords rewritten for what this actually does** โ€” leftover cache-monitoring copy meant it never surfaced for `model-routing` / `delegation` / `subagent`.
288
+ - **A 60-second on-ramp at the top of the README** โ€” that this is post-hoc analysis rather than a router, plus the three commands from install to seeing your own numbers.
289
+ - **One-time note in `route-scan`** โ€” prints the explainer link exactly once. Disable with `CTS_NO_NOTE=1`.
290
+
291
+ ### v3.9.1 (2026-08-01)
292
+ - **compact-window now recommends a 400kโ€“700k band instead of a single 400k** โ€” 400k proved too tight in practice and compacted too often. The advice is a range now, and **a window inside it (or below it) is never warned about**; only an unset value or one above 700k raises `๐Ÿ…ทโš  compact-window?` and the briefing. `set` defaults to 500k (mid-band); pick your own with `--value 600k`.
293
+
266
294
  ### v3.9.0 (2026-08-01)
267
295
 
268
296
  Compared this tool's design against manifest.build's "Everyone is building LLM routers, we deprecated ours" (a retrospective on 4 months across 7,000 users) and closed the four of their failure modes that were not already ruled out here. Full comparison in [TIER_CRITERIA.md ยง3.9](./docs/TIER_CRITERIA.md).
package/README.md CHANGED
@@ -17,6 +17,22 @@ npm i -g claude-token-saver # postinstall์ด statusline + Skill ์ž๋™ ๋“ฑ๋ก
17
17
 
18
18
  ![statusline ์˜ˆ์‹œ](./docs/statusline.png)
19
19
 
20
+ ## ๐Ÿ“บ ์˜์ƒ ๋ณด๊ณ  ์˜ค์…จ๋‹ค๋ฉด โ€” 60์ดˆ
21
+
22
+ ๋ผ์šฐํ„ฐ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์š”์ฒญ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ€๋กœ์ฑ„์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
23
+ **์„ธ์…˜์ด ๋๋‚œ ๋’ค** ๋กœ์ปฌ ๊ธฐ๋ก์„ ์ฝ์–ด์„œ, ๋น„์‹ผ ๋ชจ๋ธ์ด ๋ฐ˜๋ณตํ•ด์„œ ์ฒ˜๋ฆฌํ•ด ์˜จ ์‰ฌ์šด ์œ ํ˜•์„ ๋ฝ‘๊ณ ,
24
+ ๊ทธ ์œ ํ˜•์€ **๋‹ค์Œ ์„ธ์…˜๋ถ€ํ„ฐ** ์‹ผ ๋ชจ๋ธ์ด ๋งก๋„๋ก ๋ฃฐ๋กœ ๊ฒ๋‹ˆ๋‹ค. ๋ฃฐ์€ ๊ธ€๋กœ๋ฒŒยทํ”„๋กœ์ ํŠธ๋กœ ๋ฒ”์œ„๊ฐ€ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค.
25
+
26
+ ```bash
27
+ npm i -g claude-token-saver@latest
28
+ claude-token-saver route-scan # ๋‚ด ์ง€๋‚œ ์„ธ์…˜์—์„œ ์œ„์ž„ ํ›„๋ณด ๋ฝ‘๊ธฐ (LLM ํ˜ธ์ถœ 0)
29
+ claude-token-saver route-scan rules # ์Šน๊ฒฉ๋œ ๋ฃฐ ํ™•์ธ ยท rm <N> ์œผ๋กœ ์‚ญ์ œ
30
+ ```
31
+
32
+ ๊ธฐ์ค€์„ ์€ ๋‚จ์˜ ๋ฒค์น˜๋งˆํฌ๊ฐ€ ์•„๋‹ˆ๋ผ **๋‚ด ์ตœ๊ทผ 14์ผ ๋ถ„ํฌ(p25/p75)** ๋กœ ์žก์Šต๋‹ˆ๋‹ค.
33
+ ๋„˜๊ธด ๋’ค ์‹ค์ œ๋กœ ์ž˜ ๋๋Š”์ง€๊นŒ์ง€ ์žฌ๋Š” ์‹ค์ธก rule-health๋Š” [v3.9.0](#v390-2026-08-01)์— ๋“ค์–ด๊ฐ”์Šต๋‹ˆ๋‹ค.
34
+
35
+
20
36
  ## โšก ์™œ ์“ฐ๋‚˜ โ€” 30์ดˆ ์š”์•ฝ
21
37
 
22
38
  | | |
@@ -125,15 +141,17 @@ ratchet์˜ ๊ฐ€์น˜๋Š” **ํ•œ ๋ฐฉํ–ฅ ๋ˆ„์ **์— ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฃฐ์„ ๊ฐ€๋ณ๊ฒŒ ์ง€
125
141
 
126
142
  ## ๐Ÿ“ฆ compact-window โ€” 1M ์ปจํ…์ŠคํŠธ์˜ ์ž๋™ ์••์ถ• ์ง€์  ๊ณ ์ •
127
143
 
128
- Claude Code๋Š” `min(autoCompactWindow, ๋ชจ๋ธ ์ตœ๋Œ€ ์ฐฝ)`์— ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ๋Œ€ํ™”๋ฅผ ์ž๋™ ์••์ถ•ํ•ฉ๋‹ˆ๋‹ค. 1M ์ฐฝ์„ ์“ฐ๋ฉด ์ด ๊ฐ’์ด ์žกํ˜€ ์žˆ์ง€ ์•Š์€ ํ•œ 80๋งŒ ํ† ํฐ ๊ทผ์ฒ˜๊นŒ์ง€ ๊ฐ€์„œ์•ผ ์••์ถ•์ด ๊ฑธ๋ฆฌ๊ณ , ๊ทธ์ „๊นŒ์ง€ ๋ชจ๋“  ์š”์ฒญ์ด ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ํ†ต์งธ๋กœ ์žฌ๊ณผ๊ธˆํ•ฉ๋‹ˆ๋‹ค. 40๋งŒ์œผ๋กœ ๊ณ ์ •ํ•˜๋ฉด ํฐ ๋ถ™์—ฌ๋„ฃ๊ธฐ์šฉ ์—ฌ์œ ๋Š” 200k ์„ธ์…˜์˜ ๋‘ ๋ฐฐ๋กœ ๋‚จ๊ธฐ๋ฉด์„œ ๊ผฌ๋ฆฌ๋งŒ ์ž˜๋ผ๋ƒ…๋‹ˆ๋‹ค.
144
+ Claude Code๋Š” `min(autoCompactWindow, ๋ชจ๋ธ ์ตœ๋Œ€ ์ฐฝ)`์— ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ๋Œ€ํ™”๋ฅผ ์ž๋™ ์••์ถ•ํ•ฉ๋‹ˆ๋‹ค. 1M ์ฐฝ์„ ์“ฐ๋ฉด ์ด ๊ฐ’์ด ์žกํ˜€ ์žˆ์ง€ ์•Š์€ ํ•œ 80๋งŒ ํ† ํฐ ๊ทผ์ฒ˜๊นŒ์ง€ ๊ฐ€์„œ์•ผ ์••์ถ•์ด ๊ฑธ๋ฆฌ๊ณ , ๊ทธ์ „๊นŒ์ง€ ๋ชจ๋“  ์š”์ฒญ์ด ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ํ†ต์งธ๋กœ ์žฌ๊ณผ๊ธˆํ•ฉ๋‹ˆ๋‹ค. **1M์€ ๋„ˆ๋ฌด ํฌ๋‹ˆ 40๋งŒ~70๋งŒ ๋ฒ”์œ„๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค** โ€” ํฐ ๋ถ™์—ฌ๋„ฃ๊ธฐ์šฉ ์—ฌ์œ ๋Š” 200k ์„ธ์…˜์˜ 2~3.5๋ฐฐ๋กœ ๋‚จ๊ธฐ๋ฉด์„œ ๊ผฌ๋ฆฌ๋งŒ ์ž˜๋ผ๋ƒ…๋‹ˆ๋‹ค.
145
+
146
+ **๊ถŒ์žฅ ๋ฒ”์œ„ ์•ˆ์ด๋ฉด ๊ฒฝ๊ณ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.** 40๋งŒ์€ ์ ˆ๊ฐ์ด ์••์ถ• ํšŸ์ˆ˜๋ฅผ ์ด๊ธฐ๋Š” ํ•˜ํ•œ์ด๊ณ , ๊ธด ์„ธ์…˜์€ ๊ทธ๋ณด๋‹ค ์—ฌ์œ ๊ฐ€ ๋” ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ๋ฏธ์„ค์ •์ด๊ฑฐ๋‚˜ 70๋งŒ์„ ๋„˜์„ ๋•Œ๋งŒ ์•Œ๋ฆฝ๋‹ˆ๋‹ค(๊ทธ๋ณด๋‹ค ๋‚ฎ๊ฒŒ ์žก์€ ๊ฑด ๋” ๊ณต๊ฒฉ์ ์œผ๋กœ ์•„๋ผ๊ฒ ๋‹ค๋Š” ์„ ํƒ์ด๋ผ ๊ทธ๋ƒฅ ๋‘ก๋‹ˆ๋‹ค).
129
147
 
130
148
  **200k ์ปจํ…์ŠคํŠธ๋Š” ๊ฒฝ๊ณ  ๋Œ€์ƒ์ด ์•„๋‹™๋‹ˆ๋‹ค** โ€” ์ฐฝ์ด ์ด๋ฏธ 200k ์ดํ•˜๋ผ ์ด ์„ค์ •์ด ๋ฐ”๊ฟ€ ๊ฒŒ ์—†์Šต๋‹ˆ๋‹ค.
131
149
 
132
150
  ```bash
133
151
  claude-token-saver compact-window # ํ˜„์žฌ ์ƒํƒœ (๋ชจ๋ธยท์ฐฝยท์„ค์ •๊ฐ’ยท์ถœ์ฒ˜)
134
- claude-token-saver compact-window set --global # ~/.claude/settings.json ์— 40๋งŒ ๊ณ ์ •
152
+ claude-token-saver compact-window set --global # ~/.claude/settings.json ์— 50๋งŒ ๊ณ ์ • (๋ฒ”์œ„ ์ค‘๊ฐ„)
135
153
  claude-token-saver compact-window set --project # <root>/.claude/settings.json ์— ๊ณ ์ •
136
- claude-token-saver compact-window set --global --value 250k # ๊ฐ’ ์ง์ ‘ ์ง€์ • (10๋งŒ~1M)
154
+ claude-token-saver compact-window set --global --value 600k # ๊ฐ’ ์ง์ ‘ ์ง€์ • (10๋งŒ~1M)
137
155
  claude-token-saver compact-window off | on # ๊ฒฝ๊ณ  ํ‘œ์‹œ ํ† ๊ธ€
138
156
  ```
139
157
 
@@ -220,6 +238,15 @@ npm uninstall -g claude-cache-monitor && npm i -g claude-token-saver
220
238
 
221
239
  ## ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ
222
240
 
241
+ ### v3.9.2 (2026-08-01)
242
+ - **LICENSE ํŒŒ์ผ ์ถ”๊ฐ€ (MIT)** โ€” `package.json`์—๋งŒ ์žˆ๊ณ  ํŒŒ์ผ์ด ์—†์–ด์„œ, ์‚ฌ๋‚ด ๋„์ž… ๊ฒ€ํ†  ์‹œ ๋ผ์ด์„ ์Šค ํ™•์ธ์ด ๋ง‰ํžˆ๋˜ ๋ฌธ์ œ. npm ํŒจํ‚ค์ง€์—๋„ ํฌํ•จ๋˜๋„๋ก `files`์— ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค.
243
+ - **ํŒจํ‚ค์ง€ ์„ค๋ช…ยทํ‚ค์›Œ๋“œ๋ฅผ ํ˜„์žฌ ๊ธฐ๋Šฅ์— ๋งž๊ฒŒ ๊ต์ฒด** โ€” ์บ์‹œ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์ ˆ ๋ฌธ๊ตฌ๊ฐ€ ๋‚จ์•„ ์žˆ์–ด ๋ชจ๋ธ ์œ„์ž„(`model-routing`ยท`delegation`ยท`subagent`)์œผ๋กœ ๊ฒ€์ƒ‰๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
244
+ - **README ์ƒ๋‹จ์— 60์ดˆ ์ง„์ž…๋กœ** โ€” ๋ผ์šฐํ„ฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์‚ฌํ›„ ๋ถ„์„์ด๋ผ๋Š” ์ ๊ณผ, ์„ค์น˜๋ถ€ํ„ฐ ๋‚ด ์ˆซ์ž ํ™•์ธ๊นŒ์ง€์˜ ๋ช…๋ น 3์ค„.
245
+ - **`route-scan` ์ตœ์ดˆ 1ํšŒ ์•ˆ๋‚ด** โ€” ๊ธฐ๋Šฅ ์„ค๋ช… ์˜์ƒ ๋งํฌ๋ฅผ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. `CTS_NO_NOTE=1`๋กœ ๋•๋‹ˆ๋‹ค.
246
+
247
+ ### v3.9.1 (2026-08-01)
248
+ - **compact-window ๊ถŒ์žฅ๊ฐ’์ด ๋‹จ์ผ 40๋งŒ์—์„œ 40๋งŒ~70๋งŒ ๋ฒ”์œ„๋กœ** โ€” 40๋งŒ์€ ์‹ค์‚ฌ์šฉ์—์„œ ๋„ˆ๋ฌด ๋นก๋นกํ•ด ์••์ถ•์ด ์žฆ์•˜์Šต๋‹ˆ๋‹ค. ์ด์ œ ๋ฒ”์œ„๋ฅผ ์ œ์•ˆํ•˜๊ณ , **๊ทธ ์•ˆ(๋˜๋Š” ๊ทธ๋ณด๋‹ค ๋‚ฎ๊ฒŒ) ์žก์•„๋‘” ์„ธ์…˜์€ ๊ฒฝ๊ณ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.** ๋ฏธ์„ค์ •์ด๊ฑฐ๋‚˜ 70๋งŒ ์ดˆ๊ณผ์ผ ๋•Œ๋งŒ `๐Ÿ…ทโš  compact-window?`์™€ ๋ธŒ๋ฆฌํ•‘์ด ๋œน๋‹ˆ๋‹ค. `set`์˜ ๊ธฐ๋ณธ๊ฐ’๋„ ๋ฒ”์œ„ ์ค‘๊ฐ„์ธ 50๋งŒ์œผ๋กœ ์˜ฌ๋ ธ๊ณ , ์›ํ•˜๋Š” ๊ฐ’์€ `--value 600k`๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
249
+
223
250
  ### v3.9.0 (2026-08-01)
224
251
 
225
252
  manifest.build์˜ "๋‹ค๋“ค LLM ๋ผ์šฐํ„ฐ ๋งŒ๋“œ๋Š”๋ฐ ์šฐ๋ฆฌ๋Š” ํ๊ธฐํ–ˆ๋‹ค"(7์ฒœ ์‚ฌ์šฉ์žยท4๊ฐœ์›” ์‹ค์‚ฌ์šฉ ํšŒ๊ณ )์™€ ์ด ๋„๊ตฌ์˜ ์„ค๊ณ„๋ฅผ ๋Œ€์กฐํ•ด, ๊ทธ์ชฝ ์‹คํŒจ ์š”์ธ ์ค‘ ์•„์ง ์•ˆ ๋ง‰ํ˜€ ์žˆ๋˜ ๊ฒƒ 4๊ฐœ๋ฅผ ๋ฉ”์› ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋Œ€์กฐ๋Š” [TIER_CRITERIA.md ยง3.9](./docs/TIER_CRITERIA.md).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.9.0",
4
- "description": "Save tokens on Claude Code โ€” spike diagnosis, 1M-context detection, TTL countdown, statusline. (formerly claude-cache-monitor)",
3
+ "version": "3.9.2",
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": {
7
7
  "claude-token-saver": "bin/cli.js"
@@ -16,22 +16,27 @@
16
16
  "presets/",
17
17
  "examples/",
18
18
  "README.md",
19
- "README.en.md"
19
+ "README.en.md",
20
+ "LICENSE"
20
21
  ],
21
22
  "engines": {
22
23
  "node": ">=18"
23
24
  },
24
25
  "keywords": [
25
26
  "claude",
27
+ "claude-code",
26
28
  "anthropic",
27
- "cache",
28
- "monitoring",
29
+ "model-routing",
30
+ "llm-router",
31
+ "delegation",
32
+ "subagent",
33
+ "haiku",
34
+ "cost-savings",
35
+ "token-usage",
29
36
  "prompt-caching",
30
- "cli",
37
+ "cache",
31
38
  "statusline",
32
- "claude-code",
33
- "token-usage",
34
- "cost-savings",
39
+ "cli",
35
40
  "1m-context"
36
41
  ],
37
42
  "license": "MIT",
package/src/brief.js CHANGED
@@ -246,7 +246,7 @@ export async function runBrief({ sessionId, transcriptPath, now = Date.now() })
246
246
  briefed.add(sig);
247
247
  s.briefed = [...briefed];
248
248
  const now = st.window ? `ํ˜„์žฌ ${fmtK(st.window)}` : 'ํ˜„์žฌ ๋ฏธ์„ค์ •';
249
- items.push(`1M ์ปจํ…์ŠคํŠธ ๋ชจ๋ธ(${st.model})์ธ๋ฐ autoCompactWindow๊ฐ€ ${now}์ž…๋‹ˆ๋‹ค โ€” ์ž๋™ ์••์ถ•์ด 80๋งŒ ํ† ํฐ ๊ทผ์ฒ˜์—์„œ์•ผ ๊ฑธ๋ ค ๊ทธ์ „๊นŒ์ง€ ๋ชจ๋“  ์š”์ฒญ์ด ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ์žฌ๊ณผ๊ธˆํ•ฉ๋‹ˆ๋‹ค. 40๋งŒ์œผ๋กœ ๊ณ ์ •ํ•˜๋ฉด 1M ์ฐฝ์€ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ์••์ถ• ์‹œ์ ๋งŒ ์•ž๋‹น๊น๋‹ˆ๋‹ค. ๋“ฑ๋ก: claude-token-saver compact-window set --global|--project (์ ์šฉ ๋ฒ”์œ„๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ํ™•์ธ) / ๋„๊ธฐ: compact-window off`);
249
+ items.push(`1M ์ปจํ…์ŠคํŠธ ๋ชจ๋ธ(${st.model})์ธ๋ฐ autoCompactWindow๊ฐ€ ${now}์ž…๋‹ˆ๋‹ค โ€” ์ž๋™ ์••์ถ•์ด 80๋งŒ ํ† ํฐ ๊ทผ์ฒ˜์—์„œ์•ผ ๊ฑธ๋ ค ๊ทธ์ „๊นŒ์ง€ ๋ชจ๋“  ์š”์ฒญ์ด ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ์žฌ๊ณผ๊ธˆํ•ฉ๋‹ˆ๋‹ค. 1M์€ ๋„ˆ๋ฌด ํฌ๋‹ˆ ${fmtK(st.recommendedMin)}~${fmtK(st.recommendedMax)} ๋ฒ”์œ„๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค(๊ทธ ๋ฒ”์œ„ ์•ˆ์ด๋ฉด ๊ฒฝ๊ณ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค). 1M ์ฐฝ ์ž์ฒด๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ์••์ถ• ์‹œ์ ๋งŒ ์•ž๋‹น๊น๋‹ˆ๋‹ค. ๋“ฑ๋ก: claude-token-saver compact-window set --global|--project [--value ${fmtK(st.recommendedMax)}] (๊ธฐ๋ณธ ${fmtK(st.recommended)}, ์ ์šฉ ๋ฒ”์œ„๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ํ™•์ธ) / ๋„๊ธฐ: compact-window off`);
250
250
  }
251
251
  }
252
252
  } catch { /* settings unreadable โ€” other briefings above still apply */ }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Subcommand: compact-window โ€” audit / pin Claude Code's `autoCompactWindow`.
3
3
  * claude-token-saver compact-window # status
4
- * claude-token-saver compact-window set --global # pin 400k in ~/.claude/settings.json
5
- * claude-token-saver compact-window set --project # pin 400k in <root>/.claude/settings.json
6
- * claude-token-saver compact-window set --global --value 250k
4
+ * claude-token-saver compact-window set --global # pin 500k in ~/.claude/settings.json
5
+ * claude-token-saver compact-window set --project # pin 500k in <root>/.claude/settings.json
6
+ * claude-token-saver compact-window set --global --value 600k
7
7
  * claude-token-saver compact-window off | on # toggle the statusline warning
8
8
  *
9
9
  * Scope is deliberately explicit for `set`: writing a global settings.json is
@@ -69,22 +69,25 @@ export async function run({ args, hasFlag }) {
69
69
  return;
70
70
  }
71
71
  if (s.ok) {
72
- console.log(ko ? '\nโœ… ์••์ถ• ์ฐฝ์ด 400k ์ดํ•˜๋กœ ๊ณ ์ •๋ผ ์žˆ์Šต๋‹ˆ๋‹ค.' : '\nโœ… Compaction window is pinned at or below 400k.');
72
+ console.log(ko
73
+ ? `\nโœ… ์••์ถ• ์ฐฝ์ด ๊ถŒ์žฅ ๋ฒ”์œ„(${fmt(s.recommendedMin)}~${fmt(s.recommendedMax)}) ์ƒํ•œ ์ดํ•˜์ž…๋‹ˆ๋‹ค.`
74
+ : `\nโœ… Compaction window is at or below the top of the recommended ${fmt(s.recommendedMin)}โ€“${fmt(s.recommendedMax)} band.`);
73
75
  return;
74
76
  }
75
77
  console.log(ko
76
78
  ? `\nโš  1M ์ปจํ…์ŠคํŠธ์ธ๋ฐ autoCompactWindow๊ฐ€ ${s.reason === 'unset' ? '์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค' : `${fmt(s.window)}๋กœ ๋„ˆ๋ฌด ํฝ๋‹ˆ๋‹ค`} โ€” ์ž๋™ ์••์ถ•์ด 80๋งŒ ํ† ํฐ ๊ทผ์ฒ˜์—์„œ์•ผ ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค.`
77
79
  : `\nโš  1M context with autoCompactWindow ${s.reason === 'unset' ? 'unset' : `at ${fmt(s.window)}`} โ€” compaction only fires near 800k.`);
78
80
  console.log(ko
79
- ? ' ๊ทธ ์ „๊นŒ์ง€ ๋ชจ๋“  ์š”์ฒญ์ด ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ์žฌ๊ณผ๊ธˆํ•ฉ๋‹ˆ๋‹ค. 40๋งŒ์œผ๋กœ ๊ณ ์ •ํ•˜๋ฉด 1M ์ฐฝ์€ ์œ ์ง€ํ•˜๋ฉด์„œ ์••์ถ• ์‹œ์ ๋งŒ ์•ž๋‹น๊น๋‹ˆ๋‹ค.'
80
- : ' Until then every request re-bills the whole context. Pinning 400k keeps the 1M window while capping the runaway tail.');
81
- console.log('\n claude-token-saver compact-window set --global (~/.claude/settings.json)');
82
- console.log(' claude-token-saver compact-window set --project (<root>/.claude/settings.json)');
81
+ ? ` ๊ทธ ์ „๊นŒ์ง€ ๋ชจ๋“  ์š”์ฒญ์ด ์ „์ฒด ์ปจํ…์ŠคํŠธ๋ฅผ ์žฌ๊ณผ๊ธˆํ•ฉ๋‹ˆ๋‹ค. 1M์€ ๋„ˆ๋ฌด ํฌ๋‹ˆ ${fmt(s.recommendedMin)}~${fmt(s.recommendedMax)} ๋ฒ”์œ„๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ๊ฐ’ ${fmt(s.recommended)}, --value๋กœ ์กฐ์ ˆ). 1M ์ฐฝ ์ž์ฒด๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ์••์ถ• ์‹œ์ ๋งŒ ์•ž๋‹น๊น๋‹ˆ๋‹ค.`
82
+ : ` Until then every request re-bills the whole context. 1M is too large โ€” pick something in ${fmt(s.recommendedMin)}โ€“${fmt(s.recommendedMax)} (default ${fmt(s.recommended)}, override with --value). The 1M window itself stays.`);
83
+ console.log(`\n claude-token-saver compact-window set --global (~/.claude/settings.json, ${fmt(s.recommended)})`);
84
+ console.log(` claude-token-saver compact-window set --project (<root>/.claude/settings.json, ${fmt(s.recommended)})`);
85
+ console.log(` claude-token-saver compact-window set --global --value ${fmt(s.recommendedMax)}`);
83
86
  console.log(ko ? ' (์ ์šฉ ๋ฒ”์œ„๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ๋จผ์ € ํ™•์ธํ•  ๊ฒƒ)' : ' (confirm the scope with the user first)');
84
87
  return;
85
88
  }
86
89
 
87
90
  console.error(`Unknown compact-window subcommand: ${sub}`);
88
- console.error('Usage: claude-token-saver compact-window [status|set --global|--project [--value 400k]|off|on]');
91
+ console.error('Usage: claude-token-saver compact-window [status|set --global|--project [--value 500k]|off|on]');
89
92
  process.exit(1);
90
93
  }
@@ -202,6 +202,7 @@ export async function run({ args, hasFlag, numArg }) {
202
202
  console.log(lang === 'ko'
203
203
  ? '์œ„์ž„ ํ›„๋ณด ์—†์Œ (๋ฐ˜๋ณต 3ํšŒ ๋ฏธ๋งŒ์ด๊ฑฐ๋‚˜ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋จ).'
204
204
  : 'No delegation candidates (below recurrence threshold or already resolved).');
205
+ (await import('../first-run-note.js')).printOnce('route-scan', lang);
205
206
  return;
206
207
  }
207
208
  console.log(lang === 'ko' ? '\n์œ„์ž„ ํ›„๋ณด (R<N>=ํ›„๋ณด ๋ฒˆํ˜ธ, T2/T1=๋‚œ์ด๋„ ๋“ฑ๊ธ‰):' : '\nDelegation candidates (R<N> = candidate id, T2/T1 = difficulty tier):');
@@ -223,5 +224,7 @@ export async function run({ args, hasFlag, numArg }) {
223
224
  console.log(lang === 'ko' ? '๋“ฑ๋ก / ๋ฌด์‹œ:' : 'Promote / dismiss:');
224
225
  console.log(' claude-token-saver harness promote R<N> --project|--global');
225
226
  console.log(' claude-token-saver route-scan dismiss <N>');
227
+ // ์ตœ์ดˆ 1ํšŒ๋งŒ โ€” ๋งค๋ฒˆ ์ฐ์œผ๋ฉด ๋„๊ตฌ๊ฐ€ ๊ด‘๊ณ ํŒ์ด ๋œ๋‹ค (CTS_NO_NOTE=1 ๋กœ ๋”)
228
+ (await import('../first-run-note.js')).printOnce('route-scan', lang);
226
229
  return;
227
230
  }
@@ -5,8 +5,13 @@
5
5
  * `min(settings.autoCompactWindow, model max context)`. On a 1M window the
6
6
  * default lets a session grow past 800k before compaction ever fires, so every
7
7
  * later request re-bills a context most sessions never needed. Pinning
8
- * `autoCompactWindow: 400000` keeps twice the headroom of a 200k session for
9
- * the genuinely large ones while capping the runaway tail.
8
+ * `autoCompactWindow` somewhere in 400kโ€“700k keeps 2โ€“3.5x the headroom of a
9
+ * 200k session for the genuinely large ones while capping the runaway tail.
10
+ *
11
+ * The recommendation is a range, not a number: 400k is the floor where the
12
+ * saving is worth the extra compactions, and in practice long sessions often
13
+ * want more room than that. Anything at or below 700k is left alone โ€” only an
14
+ * unset window, or one above 700k, is a real config defect.
10
15
  *
11
16
  * 200k sessions are exempt by design: their window is already <= 200k, so the
12
17
  * setting changes nothing and a warning would be pure noise.
@@ -28,7 +33,13 @@ import { debug } from './debug.js';
28
33
  // the app would honor.
29
34
  export const MIN_WINDOW = 100_000;
30
35
  export const MAX_WINDOW = 1_000_000;
31
- export const RECOMMENDED_WINDOW = 400_000;
36
+ // Recommended band. Warn only outside it on the high side (or when unset) โ€”
37
+ // a window below RECOMMENDED_MIN is a deliberate, more aggressive choice and
38
+ // costs nothing, so it stays silent.
39
+ export const RECOMMENDED_MIN = 400_000;
40
+ export const RECOMMENDED_MAX = 700_000;
41
+ // What `compact-window set` writes when no --value is given: middle of the band.
42
+ export const RECOMMENDED_WINDOW = 500_000;
32
43
 
33
44
  // A model id whose context is the 1M variant. Claude Code spells it as a
34
45
  // suffix on the model id (`claude-opus-5[1m]`); the beta header form
@@ -116,10 +127,10 @@ export function effectiveWindow(root) {
116
127
  /**
117
128
  * Full audit result. `ok` is true when there is nothing to warn about โ€” either
118
129
  * the session is not on a 1M model (exempt), or the window is already pinned at
119
- * or below the recommended 400k.
130
+ * or below the top of the recommended band (700k).
120
131
  *
121
132
  * `reason` names why it is not ok: 'unset' (no autoCompactWindow anywhere) or
122
- * 'too-large' (set, but above 400k โ€” still lets context run away).
133
+ * 'too-large' (set, but above 700k โ€” still lets context run away).
123
134
  */
124
135
  export function compactWindowStatus({ root = process.cwd() } = {}) {
125
136
  const { model, source: modelSource } = resolveModelId(root);
@@ -133,10 +144,12 @@ export function compactWindowStatus({ root = process.cwd() } = {}) {
133
144
  windowSource: win.source,
134
145
  windowPath: win.path || null,
135
146
  recommended: RECOMMENDED_WINDOW,
147
+ recommendedMin: RECOMMENDED_MIN,
148
+ recommendedMax: RECOMMENDED_MAX,
136
149
  };
137
150
  if (!is1m) return { ...base, ok: true, reason: 'not-1m' };
138
151
  if (win.value === null) return { ...base, ok: false, reason: 'unset' };
139
- if (win.value > RECOMMENDED_WINDOW) return { ...base, ok: false, reason: 'too-large' };
152
+ if (win.value > RECOMMENDED_MAX) return { ...base, ok: false, reason: 'too-large' };
140
153
  return { ...base, ok: true, reason: 'configured' };
141
154
  }
142
155
 
@@ -0,0 +1,63 @@
1
+ /**
2
+ * first-run-note โ€” a one-time pointer to where a feature is explained.
3
+ *
4
+ * A CLI that advertises on every invocation stops being a tool, so this fires
5
+ * ONCE per note key and then never again: the shown-at timestamp is persisted
6
+ * next to the other state files and checked before anything is printed.
7
+ *
8
+ * Opt out entirely with CTS_NO_NOTE=1 (also honoured by anything that pipes
9
+ * our output somewhere it does not belong).
10
+ */
11
+
12
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { userDataDir } from './paths.js';
15
+
16
+ export function firstRunStatePath() {
17
+ return join(userDataDir(), 'first-run.json');
18
+ }
19
+
20
+ function load() {
21
+ try {
22
+ const s = JSON.parse(readFileSync(firstRunStatePath(), 'utf8'));
23
+ return s && typeof s === 'object' ? s : {};
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ function markShown(key, now) {
30
+ const state = load();
31
+ state[key] = now;
32
+ const dir = userDataDir();
33
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
34
+ writeFileSync(firstRunStatePath(), JSON.stringify(state) + '\n');
35
+ }
36
+
37
+ /** True only the first time this key is asked about. Best-effort โ€” a
38
+ * read-only state dir just means the note repeats, never that we crash. */
39
+ export function shouldShowOnce(key, { now = Date.now() } = {}) {
40
+ if (process.env.CTS_NO_NOTE === '1') return false;
41
+ if (load()[key]) return false;
42
+ try {
43
+ markShown(key, now);
44
+ } catch {
45
+ /* state dir unwritable โ€” show it, do not fail the command */
46
+ }
47
+ return true;
48
+ }
49
+
50
+ const NOTES = {
51
+ 'route-scan': {
52
+ ko: '๐Ÿ“บ ์ด ๊ธฐ๋Šฅ์„ ์„ค๋ช…ํ•œ ์˜์ƒ: https://www.youtube.com/@DeepPulseKR (์ด ์•ˆ๋‚ด๋Š” ํ•œ ๋ฒˆ๋งŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค)',
53
+ en: '๐Ÿ“บ How this works, in 3 minutes: https://www.youtube.com/@DeepPulseEN (shown once)',
54
+ },
55
+ };
56
+
57
+ /** Print the one-time note for `key`, or nothing. */
58
+ export function printOnce(key, lang = 'en') {
59
+ const note = NOTES[key];
60
+ if (!note || !shouldShowOnce(key)) return;
61
+ console.log('');
62
+ console.log(lang === 'ko' ? note.ko : note.en);
63
+ }