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 +21 -0
- package/README.en.md +32 -4
- package/README.md +30 -3
- package/package.json +14 -9
- package/src/brief.js +1 -1
- package/src/commands/compact-window.js +12 -9
- package/src/commands/route-scan.js +3 -0
- package/src/compact-window.js +19 -6
- package/src/first-run-note.js +63 -0
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
|

|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|

|
|
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
|
|
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 ์
|
|
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
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
28
|
-
"
|
|
29
|
+
"model-routing",
|
|
30
|
+
"llm-router",
|
|
31
|
+
"delegation",
|
|
32
|
+
"subagent",
|
|
33
|
+
"haiku",
|
|
34
|
+
"cost-savings",
|
|
35
|
+
"token-usage",
|
|
29
36
|
"prompt-caching",
|
|
30
|
-
"
|
|
37
|
+
"cache",
|
|
31
38
|
"statusline",
|
|
32
|
-
"
|
|
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๋ง ํ ํฐ ๊ทผ์ฒ์์์ผ ๊ฑธ๋ ค ๊ทธ์ ๊น์ง ๋ชจ๋ ์์ฒญ์ด ์ ์ฒด ์ปจํ
์คํธ๋ฅผ ์ฌ๊ณผ๊ธํฉ๋๋ค.
|
|
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
|
|
5
|
-
* claude-token-saver compact-window set --project # pin
|
|
6
|
-
* claude-token-saver compact-window set --global --value
|
|
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
|
|
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
|
-
?
|
|
80
|
-
:
|
|
81
|
-
console.log(
|
|
82
|
-
console.log(
|
|
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
|
|
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
|
}
|
package/src/compact-window.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 >
|
|
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
|
+
}
|