claude-token-saver 2.14.2 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +10 -1
- package/README.md +7 -0
- package/bin/cli.js +30 -8
- package/package.json +2 -2
- package/src/harness.js +50 -16
package/README.en.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
[한국어](./README.md) · **English**
|
|
2
2
|
|
|
3
3
|
[](https://www.youtube.com/@DeepPulseKR)
|
|
4
|
+
[](https://www.youtube.com/@DeepPulseEN)
|
|
5
|
+
[](https://rootstudioyaml.github.io/)
|
|
4
6
|
[](https://www.npmjs.com/package/claude-token-saver)
|
|
5
7
|
|
|
6
8
|
# claude-token-saver
|
|
@@ -256,7 +258,9 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
|
|
|
256
258
|
|
|
257
259
|
- [GitHub Issue #46829](https://github.com/anthropics/claude-code/issues/46829) — cache TTL regression
|
|
258
260
|
- [HN discussion](https://news.ycombinator.com/item?id=47736476) — 168 points, 142 comments
|
|
259
|
-
- [
|
|
261
|
+
- [DeepPulse KR](https://www.youtube.com/@DeepPulseKR) — Korean HN tech deep-dives
|
|
262
|
+
- [DeepPulse EN](https://www.youtube.com/@DeepPulseEN) — English HN tech deep-dives
|
|
263
|
+
- [Homepage](https://rootstudioyaml.github.io/) — project site
|
|
260
264
|
|
|
261
265
|
## Known environment quirks
|
|
262
266
|
|
|
@@ -264,6 +268,11 @@ Node.js ≥ 18 · macOS / Windows / Linux / WSL · zero dependencies.
|
|
|
264
268
|
|
|
265
269
|
## Release notes
|
|
266
270
|
|
|
271
|
+
### v2.15.0 (2026-06-13)
|
|
272
|
+
- **Global harness init** — `harness init`/`uninit`/`check` gain the same scope concept ratchet already had. `harness init --global` installs the 5 sections into `~/.claude/CLAUDE.md` (+ `~/.claude/ratchet.md`) once, so they **apply to every project**. The no-flag default stays `project` (back-compat).
|
|
273
|
+
- `harness check` now treats global as a **fallback** — a project with no local block but a global harness installed reports `🅷 5/5 (covered by global)`, matching how Claude Code loads the global `CLAUDE.md` for every project. Use `--project`/`--global` to inspect a single scope.
|
|
274
|
+
- npm package homepage changed to `https://rootstudioyaml.github.io/`; README adds the **@DeepPulseEN** channel and a homepage badge.
|
|
275
|
+
|
|
267
276
|
### v2.13.3 (2026-05-04)
|
|
268
277
|
- "Real-world impact" section restructured as a **harness + ratchet adoption cost-savings report**. Added Max(1h)/Pro(5m) cache TTL distinction (different hit-rate headroom), the handoff-before-expiry workflow, and the 2026-05-09 refresh promise. Chart title updated to match.
|
|
269
278
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
**한국어** · [English](./README.en.md)
|
|
2
2
|
|
|
3
3
|
[](https://www.youtube.com/@DeepPulseKR)
|
|
4
|
+
[](https://www.youtube.com/@DeepPulseEN)
|
|
5
|
+
[](https://rootstudioyaml.github.io/)
|
|
4
6
|
[](https://www.npmjs.com/package/claude-token-saver)
|
|
5
7
|
|
|
6
8
|
# claude-token-saver
|
|
@@ -224,6 +226,11 @@ Node.js ≥ 18 · macOS / Linux / Windows / WSL · 의존성 0.
|
|
|
224
226
|
|
|
225
227
|
## 릴리스 노트
|
|
226
228
|
|
|
229
|
+
### v2.15.0 (2026-06-13)
|
|
230
|
+
- **글로벌 harness init** — `harness init`/`uninit`/`check`에 ratchet과 동일한 스코프 개념 도입. `harness init --global`이 `~/.claude/CLAUDE.md`(+ `~/.claude/ratchet.md`)에 5개 섹션을 한 번에 깔아 **모든 프로젝트에 적용**됩니다. 무플래그 기본값은 종전대로 `project`(하위호환).
|
|
231
|
+
- `harness check`는 이제 글로벌을 **fallback**으로 인정 — 로컬 블록이 없어도 글로벌 harness가 깔려 있으면 `🅷 5/5 (covered by global)`로 표시(Claude Code가 전역 `CLAUDE.md`를 모든 프로젝트에 로드하는 실제 동작과 일치). `--project`/`--global`로 특정 스코프만 조회 가능.
|
|
232
|
+
- npm 패키지 homepage를 `https://rootstudioyaml.github.io/`로 변경, README에 **@DeepPulseEN** 채널·홈페이지 배지 추가.
|
|
233
|
+
|
|
227
234
|
### v2.13.3 (2026-05-04)
|
|
228
235
|
- "실제 효과" 섹션을 **harness + ratchet 도입 전후 비용 절감 리포트** 형태로 재구성. Max(1h)/Pro(5m) 캐시 TTL 차이에 따른 히트율 개선 여지 차이 설명, 만료 직전 handoff 워크플로 안내, 7일치 누적 시점(2026-05-09) 갱신 예고 추가. 차트 제목도 동일하게 갱신.
|
|
229
236
|
|
package/bin/cli.js
CHANGED
|
@@ -452,24 +452,44 @@ async function main() {
|
|
|
452
452
|
// claude-token-saver harness off | on # toggle the statusline 🅷 segment
|
|
453
453
|
if (args[0] === 'harness') {
|
|
454
454
|
const sub = args[1];
|
|
455
|
+
// Scope flags for init/uninit/check (same convention as promote/list/rm):
|
|
456
|
+
// --global | --project | --scope=global|project | --scope global|project
|
|
457
|
+
const parseHarnessScope = (argv, dflt) => {
|
|
458
|
+
for (let i = 0; i < argv.length; i++) {
|
|
459
|
+
const a = argv[i];
|
|
460
|
+
if (a === '--global') return 'global';
|
|
461
|
+
if (a === '--project') return 'project';
|
|
462
|
+
if (a === '--scope' && (argv[i + 1] === 'global' || argv[i + 1] === 'project')) return argv[i + 1];
|
|
463
|
+
if (a.startsWith('--scope=')) {
|
|
464
|
+
const v = a.slice('--scope='.length);
|
|
465
|
+
if (v === 'global' || v === 'project') return v;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return dflt;
|
|
469
|
+
};
|
|
455
470
|
const { harnessInit, harnessUninit, harnessStatus, harnessPromote, harnessListRules, harnessRmRule, findProjectRoot } =
|
|
456
471
|
await import('../src/harness.js');
|
|
457
472
|
const { HARNESS_SECTIONS } = await import('../src/harness-templates.js');
|
|
458
473
|
const { loadConfig, saveConfig } = await import('../src/config.js');
|
|
459
474
|
|
|
460
475
|
if (!sub || sub === 'check') {
|
|
476
|
+
const scope = parseHarnessScope(args.slice(2), 'auto'); // auto = project, else global fallback
|
|
461
477
|
const root = findProjectRoot();
|
|
462
|
-
const s = harnessStatus(root);
|
|
463
|
-
|
|
478
|
+
const s = harnessStatus(root, { scope });
|
|
479
|
+
// Only call out "covered by global" when we *fell back* to it (auto), not
|
|
480
|
+
// when the user explicitly asked for the global scope.
|
|
481
|
+
const via = (scope === 'auto' && s.source === 'global') ? ' (covered by global ~/.claude/CLAUDE.md)' : '';
|
|
482
|
+
console.log(`🅷 ${s.configured}/${s.total} — ${s.file}${via}`);
|
|
464
483
|
console.log(`CLAUDE.md: ${s.hasFile ? 'present' : 'missing'}` +
|
|
465
|
-
(s.hasFile ? `, harness block: ${s.hasBlock ? 'yes' : 'no'}` : ''));
|
|
484
|
+
(s.hasFile ? `, harness block: ${s.hasBlock ? 'yes' : 'no'}` : '') + ` [${s.source}]`);
|
|
466
485
|
if (s.missing.length) {
|
|
467
486
|
console.log('Missing sections:');
|
|
468
487
|
for (const id of s.missing) {
|
|
469
488
|
const sec = HARNESS_SECTIONS.find((x) => x.id === id);
|
|
470
489
|
console.log(` - ${id}: ${sec ? sec.heading.replace(/^#+\s*/, '') : ''}`);
|
|
471
490
|
}
|
|
472
|
-
console.log('\nRun: claude-token-saver harness init');
|
|
491
|
+
console.log('\nRun: claude-token-saver harness init (this project)');
|
|
492
|
+
console.log(' or: claude-token-saver harness init --global (all projects, ~/.claude/CLAUDE.md)');
|
|
473
493
|
} else {
|
|
474
494
|
console.log('All 5 harness sections present. ✅');
|
|
475
495
|
}
|
|
@@ -477,9 +497,10 @@ async function main() {
|
|
|
477
497
|
}
|
|
478
498
|
|
|
479
499
|
if (sub === 'init') {
|
|
500
|
+
const scope = parseHarnessScope(args.slice(2), 'project'); // default project (back-compat)
|
|
480
501
|
const force = hasFlag('--force');
|
|
481
|
-
const r = harnessInit({ force });
|
|
482
|
-
console.log(`
|
|
502
|
+
const r = harnessInit({ force, scope });
|
|
503
|
+
console.log(`Scope: ${scope}${scope === 'global' ? ' (~/.claude/CLAUDE.md — applies to all projects)' : ` (${r.root})`}`);
|
|
483
504
|
for (const p of r.backedUp) console.log(`Backed up: ${p}`);
|
|
484
505
|
for (const p of r.wrote) console.log(`Wrote: ${p}`);
|
|
485
506
|
for (const p of r.skipped) console.log(`Skipped: ${p}`);
|
|
@@ -609,9 +630,10 @@ async function main() {
|
|
|
609
630
|
}
|
|
610
631
|
|
|
611
632
|
if (sub === 'uninit' || sub === 'remove') {
|
|
633
|
+
const scope = parseHarnessScope(args.slice(2), 'project');
|
|
612
634
|
const purgeRatchet = args.includes('--purge-ratchet');
|
|
613
|
-
const r = harnessUninit({ purgeRatchet });
|
|
614
|
-
console.log(`
|
|
635
|
+
const r = harnessUninit({ purgeRatchet, scope });
|
|
636
|
+
console.log(`Scope: ${scope}${scope === 'global' ? ' (~/.claude/CLAUDE.md)' : ` (${r.root})`}`);
|
|
615
637
|
r.removed.forEach((f) => console.log(` removed: ${f}`));
|
|
616
638
|
r.backedUp.forEach((f) => console.log(` backup: ${f}`));
|
|
617
639
|
r.skipped.forEach((f) => console.log(` skip: ${f}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-token-saver",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"description": "Save tokens on Claude Code — spike diagnosis, 1M-context detection, TTL countdown, statusline. (formerly claude-cache-monitor)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"type": "git",
|
|
38
38
|
"url": "git+https://github.com/rootstudioyaml/claude-token-saver.git"
|
|
39
39
|
},
|
|
40
|
-
"homepage": "https://
|
|
40
|
+
"homepage": "https://rootstudioyaml.github.io/",
|
|
41
41
|
"bugs": {
|
|
42
42
|
"url": "https://github.com/rootstudioyaml/claude-token-saver/issues"
|
|
43
43
|
},
|
package/src/harness.js
CHANGED
|
@@ -67,28 +67,41 @@ function resolveRatchetPath(scope, root) {
|
|
|
67
67
|
return scope === 'global' ? globalRatchetMdPath() : ratchetMdPath(root);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// Global harness lives in ~/.claude/CLAUDE.md — Claude Code loads this for every
|
|
71
|
+
// project, so a global init makes the 5 harness sections apply everywhere
|
|
72
|
+
// (mirrors the project/global split that ratchet.md already has).
|
|
73
|
+
function globalClaudeMdPath() {
|
|
74
|
+
return join(homedir(), '.claude', 'CLAUDE.md');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveClaudeMdPath(scope, root) {
|
|
78
|
+
return scope === 'global' ? globalClaudeMdPath() : claudeMdPath(root);
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
/**
|
|
71
82
|
* Count how many of the 5 harness sections appear in the project's CLAUDE.md.
|
|
72
83
|
* Returns { configured, total, missing, hasBlock }. Cheap enough to call from
|
|
73
84
|
* statusline — single file read + regex.
|
|
74
85
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!existsSync(
|
|
86
|
+
// Count harness sections in a single CLAUDE.md file. Shared by both scopes.
|
|
87
|
+
function statusForFile(filePath) {
|
|
88
|
+
if (!existsSync(filePath)) {
|
|
78
89
|
return {
|
|
79
90
|
configured: 0,
|
|
80
91
|
total: HARNESS_SECTIONS.length,
|
|
81
92
|
missing: HARNESS_SECTIONS.map((s) => s.id),
|
|
82
93
|
hasBlock: false,
|
|
83
94
|
hasFile: false,
|
|
84
|
-
|
|
95
|
+
optOut: false,
|
|
96
|
+
custom: false,
|
|
97
|
+
file: filePath,
|
|
85
98
|
};
|
|
86
99
|
}
|
|
87
100
|
let content = '';
|
|
88
101
|
try {
|
|
89
|
-
content = readFileSync(
|
|
102
|
+
content = readFileSync(filePath, 'utf8');
|
|
90
103
|
} catch {
|
|
91
|
-
return { configured: 0, total: HARNESS_SECTIONS.length, missing: [], hasBlock: false, hasFile: true,
|
|
104
|
+
return { configured: 0, total: HARNESS_SECTIONS.length, missing: [], hasBlock: false, hasFile: true, optOut: false, custom: false, file: filePath };
|
|
92
105
|
}
|
|
93
106
|
const hasBlock = content.includes(HARNESS_BLOCK_BEGIN);
|
|
94
107
|
// Opt-out marker — when the user intentionally customizes the harness block
|
|
@@ -114,10 +127,30 @@ export function harnessStatus(root = findProjectRoot()) {
|
|
|
114
127
|
hasFile: true,
|
|
115
128
|
optOut,
|
|
116
129
|
custom,
|
|
117
|
-
|
|
130
|
+
file: filePath,
|
|
118
131
|
};
|
|
119
132
|
}
|
|
120
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Harness status for a project, with scope control:
|
|
136
|
+
* scope 'project' — count only <root>/CLAUDE.md
|
|
137
|
+
* scope 'global' — count only ~/.claude/CLAUDE.md
|
|
138
|
+
* scope 'auto' (default) — use the project file if it carries the harness
|
|
139
|
+
* block, otherwise fall back to the global file. This makes a project that
|
|
140
|
+
* relies on a globally-installed harness report 🅷 5/5 (covered by global),
|
|
141
|
+
* matching reality: Claude Code loads ~/.claude/CLAUDE.md for every project.
|
|
142
|
+
* The returned `source` ('project'|'global') tells callers which file was used.
|
|
143
|
+
*/
|
|
144
|
+
export function harnessStatus(root = findProjectRoot(), { scope = 'auto' } = {}) {
|
|
145
|
+
if (scope === 'project') return { ...statusForFile(claudeMdPath(root)), root, source: 'project' };
|
|
146
|
+
if (scope === 'global') return { ...statusForFile(globalClaudeMdPath()), root, source: 'global' };
|
|
147
|
+
const project = statusForFile(claudeMdPath(root));
|
|
148
|
+
if (project.hasBlock) return { ...project, root, source: 'project' };
|
|
149
|
+
const global = statusForFile(globalClaudeMdPath());
|
|
150
|
+
if (global.hasBlock) return { ...global, root, source: 'global' };
|
|
151
|
+
return { ...project, root, source: 'project' };
|
|
152
|
+
}
|
|
153
|
+
|
|
121
154
|
/**
|
|
122
155
|
* harness init — write CLAUDE.md (single file, 5 sections) + .claude/ratchet.md.
|
|
123
156
|
* If CLAUDE.md exists, back it up to CLAUDE.md.bak-YYYYMMDD-HHMMSS first
|
|
@@ -125,10 +158,10 @@ export function harnessStatus(root = findProjectRoot()) {
|
|
|
125
158
|
*
|
|
126
159
|
* Returns { wrote: [], backedUp: [], skipped: [] } so the CLI can report.
|
|
127
160
|
*/
|
|
128
|
-
export function harnessInit({ root = findProjectRoot(), force = false } = {}) {
|
|
129
|
-
const cmPath =
|
|
130
|
-
const rmPath =
|
|
131
|
-
const result = { wrote: [], backedUp: [], skipped: [], root };
|
|
161
|
+
export function harnessInit({ root = findProjectRoot(), force = false, scope = 'project' } = {}) {
|
|
162
|
+
const cmPath = resolveClaudeMdPath(scope, root); // global → ~/.claude/CLAUDE.md
|
|
163
|
+
const rmPath = resolveRatchetPath(scope, root); // global → ~/.claude/ratchet.md
|
|
164
|
+
const result = { wrote: [], backedUp: [], skipped: [], root, scope };
|
|
132
165
|
|
|
133
166
|
// CLAUDE.md
|
|
134
167
|
const block = harnessClaudeMdBlock();
|
|
@@ -158,11 +191,12 @@ export function harnessInit({ root = findProjectRoot(), force = false } = {}) {
|
|
|
158
191
|
result.wrote.push(cmPath + ' (harness block appended)');
|
|
159
192
|
}
|
|
160
193
|
} else {
|
|
194
|
+
mkdirSync(dirname(cmPath), { recursive: true }); // global: ensure ~/.claude exists
|
|
161
195
|
writeFileSync(cmPath, block);
|
|
162
196
|
result.wrote.push(cmPath);
|
|
163
197
|
}
|
|
164
198
|
|
|
165
|
-
//
|
|
199
|
+
// ratchet.md (only if missing — don't clobber user-grown rules)
|
|
166
200
|
if (!existsSync(rmPath)) {
|
|
167
201
|
mkdirSync(dirname(rmPath), { recursive: true });
|
|
168
202
|
writeFileSync(rmPath, harnessRatchetMdInitial());
|
|
@@ -181,10 +215,10 @@ export function harnessInit({ root = findProjectRoot(), force = false } = {}) {
|
|
|
181
215
|
*
|
|
182
216
|
* Returns { removed: [], backedUp: [], skipped: [] }.
|
|
183
217
|
*/
|
|
184
|
-
export function harnessUninit({ root = findProjectRoot(), purgeRatchet = false } = {}) {
|
|
185
|
-
const cmPath =
|
|
186
|
-
const rmPath =
|
|
187
|
-
const result = { removed: [], backedUp: [], skipped: [], root };
|
|
218
|
+
export function harnessUninit({ root = findProjectRoot(), purgeRatchet = false, scope = 'project' } = {}) {
|
|
219
|
+
const cmPath = resolveClaudeMdPath(scope, root);
|
|
220
|
+
const rmPath = resolveRatchetPath(scope, root);
|
|
221
|
+
const result = { removed: [], backedUp: [], skipped: [], root, scope };
|
|
188
222
|
|
|
189
223
|
if (existsSync(cmPath)) {
|
|
190
224
|
const existing = readFileSync(cmPath, 'utf8');
|