claude-token-saver 3.41.0 → 3.42.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/bin/cli.js CHANGED
@@ -9,9 +9,9 @@
9
9
  * npx claude-token-saver --format json # JSON output
10
10
  * npx claude-token-saver --format csv # CSV output
11
11
  * npx claude-token-saver --project myproj # filter by project
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)
12
+ * npx sprag route-scan # detect recurring easy work → haiku-delegation candidates
13
+ * npx sprag install # set up skill/hooks/statusline; asks about harness + Korean guidance
14
+ * npx sprag install --yes # take the defaults without asking (same as --no-input)
15
15
  * npx claude-token-saver --install-hook # install PostToolUse hook
16
16
  * npx claude-token-saver --uninstall-hook # remove hook
17
17
  * npx claude-token-saver --hook-run # internal: called by hook
@@ -98,16 +98,16 @@ Usage:
98
98
  claude-token-saver --format json JSON output
99
99
  claude-token-saver --format csv CSV output
100
100
  claude-token-saver --project myproj filter by project
101
- claude-token-saver route-scan detect recurring easy work → delegation candidates
102
- claude-token-saver install set up skill/hooks/statusline
103
- claude-token-saver install --yes take the defaults without asking
101
+ sprag route-scan detect recurring easy work → delegation candidates
102
+ sprag install set up skill/hooks/statusline
103
+ sprag install --yes take the defaults without asking
104
104
  claude-token-saver uninstall remove everything install added
105
- claude-token-saver harness check score the harness setup in CLAUDE.md
106
- claude-token-saver harness analyze run the harness transcript analysis manually
105
+ sprag harness check score the harness setup in CLAUDE.md
106
+ sprag harness analyze run the harness transcript analysis manually
107
107
  claude-token-saver last most recent warning + how to handle it
108
108
  claude-token-saver history recent warning transitions
109
109
  claude-token-saver handoff write a session handoff file
110
- claude-token-saver feedback "<msg>" file a bug report / feature request (no browser needed)
110
+ sprag feedback "<msg>" file a bug report / feature request (no browser needed)
111
111
  claude-token-saver upgrade install the latest release
112
112
  claude-token-saver --install-hook install cache-monitor PostToolUse hook
113
113
  claude-token-saver --uninstall-hook remove that hook
@@ -179,8 +179,8 @@ async function main() {
179
179
  // user can just mention chip wording and Claude responds. v2.6.0 dropped
180
180
  // the redundant /token-monitor slash command in favor of the skill alone;
181
181
  // a legacy command file is removed automatically. Cross-platform.
182
- // claude-token-saver install # install/update the skill
183
- // claude-token-saver install --force # overwrite existing skill file
182
+ // sprag install # install/update the skill
183
+ // sprag install --force # overwrite existing skill file
184
184
  if (args[0] === 'install') {
185
185
  return (await import('../src/commands/install.js')).run({ hasFlag });
186
186
  }
@@ -203,13 +203,13 @@ async function main() {
203
203
 
204
204
  // Subcommand: route-scan — detect recurring easy work on expensive models
205
205
  // and propose model-delegation ratchet rules. Zero token cost, fully local.
206
- // claude-token-saver route-scan # scan (24h cache) + print candidates
207
- // claude-token-saver route-scan --refresh # force rescan
208
- // claude-token-saver route-scan --days 30 # wider lookback
209
- // claude-token-saver route-scan --hook # SessionStart hook mode (context injection)
210
- // claude-token-saver route-scan dismiss <N> # mute candidate R<N>
206
+ // sprag route-scan # scan (24h cache) + print candidates
207
+ // sprag route-scan --refresh # force rescan
208
+ // sprag route-scan --days 30 # wider lookback
209
+ // sprag route-scan --hook # SessionStart hook mode (context injection)
210
+ // sprag route-scan dismiss <N> # mute candidate R<N>
211
211
  // Promote a candidate to a ratchet rule (scope is always explicit):
212
- // claude-token-saver harness promote R<N> --project|--global
212
+ // sprag harness promote R<N> --project|--global
213
213
  // brief --hook — UserPromptSubmit hook mode: per-session, change-triggered
214
214
  // briefing of state the statusline can only chip (ctx tier crossings,
215
215
  // mid-session route/rule-health changes). Silent when nothing changed.
@@ -223,21 +223,21 @@ async function main() {
223
223
 
224
224
  // Subcommand: korean — Korean writing guidance injected at session start,
225
225
  // so the rules apply in every project without an output-style switch.
226
- // claude-token-saver korean on | off | status | show
226
+ // sprag korean on | off | status | show
227
227
  if (args[0] === 'korean') {
228
228
  return (await import('../src/commands/korean.js')).run({ args, hasFlag });
229
229
  }
230
230
 
231
231
  // Subcommand: cohesion — English sentence-connection guidance injected at
232
232
  // session start; the language-neutral half of the Korean supplement.
233
- // claude-token-saver cohesion on | off | status | show
233
+ // sprag cohesion on | off | status | show
234
234
  if (args[0] === 'cohesion') {
235
235
  return (await import('../src/commands/cohesion.js')).run({ args, hasFlag });
236
236
  }
237
237
 
238
238
  // Subcommand: doc2md — convert pptx/xlsx/pdf/docx to Markdown before the
239
239
  // model reads them, so an unreadable binary never enters the context window.
240
- // claude-token-saver doc2md on | off | <file> | --clean
240
+ // sprag doc2md on | off | <file> | --clean
241
241
  if (args[0] === 'doc2md') {
242
242
  return (await import('../src/commands/doc2md.js')).run({ args, hasFlag });
243
243
  }
@@ -250,12 +250,12 @@ async function main() {
250
250
  }
251
251
 
252
252
  // Subcommand: harness — manage the project's CLAUDE.md harness rules.
253
- // claude-token-saver harness init # write CLAUDE.md (5 sections) + ratchet.md
254
- // claude-token-saver harness uninit # remove harness block from CLAUDE.md (backup kept)
255
- // claude-token-saver harness check # show 🅷 N/5 + which sections are missing
256
- // claude-token-saver harness promote "<rule>" # append a rule to ratchet.md
257
- // claude-token-saver harness pull [--global|--project] # register the package's curated preset rules (default global)
258
- // claude-token-saver harness off | on # toggle the statusline 🅷 segment
253
+ // sprag harness init # write CLAUDE.md (5 sections) + ratchet.md
254
+ // sprag harness uninit # remove harness block from CLAUDE.md (backup kept)
255
+ // sprag harness check # show 🅷 N/5 + which sections are missing
256
+ // sprag harness promote "<rule>" # append a rule to ratchet.md
257
+ // sprag harness pull [--global|--project] # register the package's curated preset rules (default global)
258
+ // sprag harness off | on # toggle the statusline 🅷 segment
259
259
  if (args[0] === 'harness') {
260
260
  return (await import('../src/commands/harness.js')).run({ args, hasFlag });
261
261
  }
@@ -281,9 +281,9 @@ async function main() {
281
281
 
282
282
  // Subcommand: update-check — the registry lookup behind the ⬆ statusline
283
283
  // chip and the session-start upgrade offer.
284
- // claude-token-saver update-check # print cached status
285
- // claude-token-saver update-check --refresh # hit the registry now (detached child uses this)
286
- // claude-token-saver update-check --dismiss # stop offering THIS version at session start
284
+ // sprag update-check # print cached status
285
+ // sprag update-check --refresh # hit the registry now (detached child uses this)
286
+ // sprag update-check --dismiss # stop offering THIS version at session start
287
287
  if (args[0] === 'update-check') {
288
288
  return (await import('../src/commands/update-check.js')).run({
289
289
  hasFlag,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.41.0",
3
+ "version": "3.42.0",
4
4
  "description": "Legacy name of sprag-cli (Sprag) - same tool, kept publishing so existing installs stay current. Prefer: npm i -g sprag-cli",
5
5
  "type": "module",
6
6
  "bin": {
package/src/brief.js CHANGED
@@ -226,8 +226,8 @@ export async function runBrief({ sessionId, transcriptPath, now = Date.now() })
226
226
  }
227
227
  for (const [kind, x] of fresh) {
228
228
  items.push(kind === 'route'
229
- ? `새 위임 후보가 감지되었습니다 — "${x.label}" 유형 ${x.count}회 반복(statusline의 route? R${x.id} 칩). 등록: claude-token-saver harness promote R${x.id} --project|--global (적용 범위는 사용자에게 확인) / 무시: route-scan dismiss ${x.id}`
230
- : `승인된 위임 룰의 최근 에러율이 기준(20%)을 넘었습니다 — "${x.label}" (${x.healthSource === 'delegated' ? `위임 실행 ${x.delegatedRuns}건 중 에러율 ${Math.round((x.delegatedErrRate || 0) * 100)}%` : `유사 에피소드 기준 에러율 ${Math.round((x.errRate || 0) * 100)}%`}, statusline의 rule-health 칩). 조건 좁히기/제거를 사용자와 상의하세요: claude-token-saver route-scan rules`);
229
+ ? `새 위임 후보가 감지되었습니다 — "${x.label}" 유형 ${x.count}회 반복(statusline의 route? R${x.id} 칩). 등록: sprag harness promote R${x.id} --project|--global (적용 범위는 사용자에게 확인) / 무시: route-scan dismiss ${x.id}`
230
+ : `승인된 위임 룰의 최근 에러율이 기준(20%)을 넘었습니다 — "${x.label}" (${x.healthSource === 'delegated' ? `위임 실행 ${x.delegatedRuns}건 중 에러율 ${Math.round((x.delegatedErrRate || 0) * 100)}%` : `유사 에피소드 기준 에러율 ${Math.round((x.errRate || 0) * 100)}%`}, statusline의 rule-health 칩). 조건 좁히기/제거를 사용자와 상의하세요: sprag route-scan rules`);
231
231
  }
232
232
  s.briefed = [...briefed];
233
233
  } catch { /* caches unreadable — ctx briefing above still applies */ }
@@ -257,7 +257,7 @@ export async function runBrief({ sessionId, transcriptPath, now = Date.now() })
257
257
 
258
258
  if (items.length === 0) return null;
259
259
  const lines = [
260
- '[claude-token-saver 브리핑] 아래 상태 변화를 사용자에게 알려주세요. 진행 중인 답변 흐름을 끊지 말고, 답변 말미에 `※ [claude-token-saver]` 라벨을 달아 각 항목을 1~2줄로 요약해 전달하면 됩니다 (이 브리핑은 항목당 한 번만 주입됩니다):',
260
+ '[claude-token-saver 브리핑] 아래 상태 변화를 사용자에게 알려주세요. 진행 중인 답변 흐름을 끊지 말고, 답변 말미에 `※ [sprag]` 라벨을 달아 각 항목을 1~2줄로 요약해 전달하면 됩니다 (이 브리핑은 항목당 한 번만 주입됩니다):',
261
261
  ];
262
262
  for (const it of items) lines.push(`- ${it}`);
263
263
  return lines.join('\n');
package/src/cohesion.js CHANGED
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Off by default for the same reason the Korean guidance is opt-in: a
11
11
  * token-saving tool has no business silently billing ~0.5k tokens a session.
12
- * Enable with `claude-token-saver cohesion on`.
12
+ * Enable with `sprag cohesion on`.
13
13
  *
14
14
  * When the Korean guidance is enabled, this block is NOT injected even if
15
15
  * enabled: the Korean supplement already carries the cohesion rules, and
@@ -62,7 +62,7 @@ export async function cohesionInjection({ cfg = loadConfig() } = {}) {
62
62
  const text = cohesionText();
63
63
  if (!text) return null;
64
64
  return [
65
- '[claude-token-saver cohesion] Follow this guidance for English prose in this session.',
65
+ '[sprag cohesion] Follow this guidance for English prose in this session.',
66
66
  'The user enabled it in claude-token-saver.',
67
67
  '',
68
68
  text,
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Subcommand: route-scan — detect recurring easy work on expensive models
3
3
  * and propose model-delegation ratchet rules. Zero token cost, fully local.
4
- * claude-token-saver route-scan # scan (24h cache) + print candidates
5
- * claude-token-saver route-scan --refresh # force rescan
6
- * claude-token-saver route-scan --days 30 # wider lookback
7
- * claude-token-saver route-scan --hook # SessionStart hook mode (context injection)
8
- * claude-token-saver route-scan dismiss <N> # mute candidate R<N>
4
+ * sprag route-scan # scan (24h cache) + print candidates
5
+ * sprag route-scan --refresh # force rescan
6
+ * sprag route-scan --days 30 # wider lookback
7
+ * sprag route-scan --hook # SessionStart hook mode (context injection)
8
+ * sprag route-scan dismiss <N> # mute candidate R<N>
9
9
  * Promote a candidate to a ratchet rule (scope is always explicit):
10
- * claude-token-saver harness promote R<N> --project|--global
10
+ * sprag harness promote R<N> --project|--global
11
11
  * brief --hook — UserPromptSubmit hook mode: per-session, change-triggered
12
12
  * briefing of state the statusline can only chip (ctx tier crossings,
13
13
  * mid-session route/rule-health changes). Silent when nothing changed.
@@ -18,7 +18,7 @@ import { debug } from '../debug.js';
18
18
 
19
19
  export async function run({ hasFlag }) {
20
20
  if (!hasFlag('--hook')) {
21
- console.error('Usage: claude-token-saver brief --hook (UserPromptSubmit hook mode)');
21
+ console.error('Usage: sprag brief --hook (UserPromptSubmit hook mode)');
22
22
  process.exit(1);
23
23
  }
24
24
  const ctx = readStdinJson() || {};
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Subcommand: cohesion — English sentence-connection guidance for every session.
3
- * claude-token-saver cohesion on # inject at session start, all projects
4
- * claude-token-saver cohesion off # stop injecting
5
- * claude-token-saver cohesion status # current state and cost
6
- * claude-token-saver cohesion show # print the guidance itself
3
+ * sprag cohesion on # inject at session start, all projects
4
+ * sprag cohesion off # stop injecting
5
+ * sprag cohesion status # current state and cost
6
+ * sprag cohesion show # print the guidance itself
7
7
  *
8
8
  * The English sibling of `korean on`, carrying only the language-neutral
9
9
  * cohesion rules (given-before-new, one referent per pronoun, subject
@@ -51,9 +51,9 @@ export async function run({ args }) {
51
51
  const suppressed = enabled && koreanStyleEnabled();
52
52
  if (lang === 'ko') {
53
53
  console.log(`cohesion: ${enabled ? 'on' : 'off'}${suppressed ? ' (korean 지침이 켜져 있어 주입은 생략됨)' : ''}`);
54
- console.log('영어 산문의 문장 연결 지침을 세션 시작에 주입합니다. 켜기: claude-token-saver cohesion on');
54
+ console.log('영어 산문의 문장 연결 지침을 세션 시작에 주입합니다. 켜기: sprag cohesion on');
55
55
  } else {
56
56
  console.log(`cohesion: ${enabled ? 'on' : 'off'}${suppressed ? ' (suppressed while the Korean guidance is on — it already carries these rules)' : ''}`);
57
- console.log('Injects English cohesion guidance at session start. Enable with: claude-token-saver cohesion on');
57
+ console.log('Injects English cohesion guidance at session start. Enable with: sprag cohesion on');
58
58
  }
59
59
  }
@@ -2,11 +2,11 @@
2
2
  * Subcommand: doc2md — convert attached documents to Markdown before the
3
3
  * model reads them.
4
4
  *
5
- * claude-token-saver doc2md # status
6
- * claude-token-saver doc2md on|off # register / remove the Read hook
7
- * claude-token-saver doc2md <file> # convert one file by hand
8
- * claude-token-saver doc2md --clean # drop every cached conversion
9
- * claude-token-saver doc2md --hook # PreToolUse entry point
5
+ * sprag doc2md # status
6
+ * sprag doc2md on|off # register / remove the Read hook
7
+ * sprag doc2md <file> # convert one file by hand
8
+ * sprag doc2md --clean # drop every cached conversion
9
+ * sprag doc2md --hook # PreToolUse entry point
10
10
  */
11
11
 
12
12
  import { createRequire } from 'node:module';
@@ -162,5 +162,5 @@ export async function run({ args, hasFlag }) {
162
162
  console.log(` hook: ${hookRegistered() ? 'registered on Read' : 'not registered'}`);
163
163
  console.log('');
164
164
  if (!python) console.log(`Install the converter: ${doc2md.INSTALL_HINT}`);
165
- console.log('Enable with: claude-token-saver doc2md on');
165
+ console.log('Enable with: sprag doc2md on');
166
166
  }
@@ -2,8 +2,8 @@
2
2
  * Subcommand: feedback — submit a bug report or feature request from the
3
3
  * terminal (or from a Claude Code session) without opening a browser.
4
4
  *
5
- * claude-token-saver feedback "statusline이 IntelliJ에서 깨져요"
6
- * claude-token-saver feedback --title "cache chip" "5m TTL 칩이 안 사라짐"
5
+ * sprag feedback "statusline이 IntelliJ에서 깨져요"
6
+ * sprag feedback --title "cache chip" "5m TTL 칩이 안 사라짐"
7
7
  *
8
8
  * Why this exists: GitHub issues require a logged-in browser session, and
9
9
  * corporate networks often block github.com entirely. This command tries
@@ -102,7 +102,7 @@ export async function run({ args, getArg, version, dataDir }) {
102
102
  const explicitTitle = getArg('--title');
103
103
 
104
104
  if (!message) {
105
- console.log('usage: claude-token-saver feedback [--title "<제목>"] "<내용>"');
105
+ console.log('usage: sprag feedback [--title "<제목>"] "<내용>"');
106
106
  console.log(` (GitHub에서 직접 제보: ${ISSUES_URL})`);
107
107
  process.exitCode = 1;
108
108
  return;
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Subcommand: harness — manage the project's CLAUDE.md harness rules.
3
- * claude-token-saver harness init # write CLAUDE.md (5 sections) + ratchet.md
4
- * claude-token-saver harness uninit # remove harness block from CLAUDE.md (backup kept)
5
- * claude-token-saver harness check # show 🅷 N/5 + which sections are missing
6
- * claude-token-saver harness promote "<rule>" # append a rule to ratchet.md
7
- * claude-token-saver harness pull [--global|--project] # register the package's curated preset rules (default global)
8
- * claude-token-saver harness off | on # toggle the statusline 🅷 segment
3
+ * sprag harness init # write CLAUDE.md (5 sections) + ratchet.md
4
+ * sprag harness uninit # remove harness block from CLAUDE.md (backup kept)
5
+ * sprag harness check # show 🅷 N/5 + which sections are missing
6
+ * sprag harness promote "<rule>" # append a rule to ratchet.md
7
+ * sprag harness pull [--global|--project] # register the package's curated preset rules (default global)
8
+ * sprag harness off | on # toggle the statusline 🅷 segment
9
9
  */
10
10
 
11
11
  import { debug } from '../debug.js';
@@ -49,8 +49,8 @@ export async function run({ args, hasFlag }) {
49
49
  const sec = HARNESS_SECTIONS.find((x) => x.id === id);
50
50
  console.log(` - ${id}: ${sec ? sec.heading.replace(/^#+\s*/, '') : ''}`);
51
51
  }
52
- console.log('\nRun: claude-token-saver harness init (this project)');
53
- console.log(' or: claude-token-saver harness init --global (all projects, ~/.claude/CLAUDE.md)');
52
+ console.log('\nRun: sprag harness init (this project)');
53
+ console.log(' or: sprag harness init --global (all projects, ~/.claude/CLAUDE.md)');
54
54
  } else {
55
55
  console.log('All 5 harness sections present. ✅');
56
56
  }
@@ -60,7 +60,7 @@ export async function run({ args, hasFlag }) {
60
60
  const dead = [!s.hasRatchetImport && 'ratchet.md', !s.hasModelRatchetImport && 'ratchet-model.md'].filter(Boolean).join(' + ');
61
61
  console.log(`\n⚠ ${dead} is NOT loaded into sessions — the harness block has no \`@\` import line for it.`);
62
62
  console.log(' Those rules are being written to a file nothing reads.');
63
- console.log(` Fix: claude-token-saver harness init${s.source === 'global' ? ' --global' : ''} (updates the block in place)`);
63
+ console.log(` Fix: sprag harness init${s.source === 'global' ? ' --global' : ''} (updates the block in place)`);
64
64
  }
65
65
  // Imported ratchets are charged on every request, so their size matters.
66
66
  // Static `@` imports cannot be filtered at load time — the only lever is
@@ -71,7 +71,7 @@ export async function run({ args, hasFlag }) {
71
71
  const line = `ratchet.md [${sc}]: ${size.count} rules, ~${size.tokens} tok/request`;
72
72
  if (size.overBudget) {
73
73
  console.log(`\n⚠ ${line} — over the ~${RATCHET_TOKEN_BUDGET} token budget.`);
74
- console.log(` Trim: claude-token-saver harness prune${sc === 'global' ? ' --global' : ''} --older-than 6 --dry-run`);
74
+ console.log(` Trim: sprag harness prune${sc === 'global' ? ' --global' : ''} --older-than 6 --dry-run`);
75
75
  console.log(' (project-specific rules belong in --project scope, not global.)');
76
76
  } else {
77
77
  console.log(`${line}`);
@@ -138,8 +138,8 @@ export async function run({ args, hasFlag }) {
138
138
  }
139
139
  const raw = promoteArgs.filter((_, i) => !scopeFlags.has(i)).join(' ').trim();
140
140
  if (!raw) {
141
- console.error('Usage: claude-token-saver harness promote [--global|--project] <N> # from statusline 🅷⚠ ratchet? #N');
142
- console.error(' or: claude-token-saver harness promote [--global|--project] "<rule text>"');
141
+ console.error('Usage: sprag harness promote [--global|--project] <N> # from statusline 🅷⚠ ratchet? #N');
142
+ console.error(' or: sprag harness promote [--global|--project] "<rule text>"');
143
143
  process.exit(1);
144
144
  }
145
145
  let rule = raw;
@@ -173,7 +173,7 @@ export async function run({ args, hasFlag }) {
173
173
  const rs = await import('../route-scan.js');
174
174
  const cand = (rs.openCandidates(rs.readRouteScan()) || []).find((c) => c.id === n);
175
175
  if (!cand) {
176
- console.error(`No open route candidate R${n}. Run: claude-token-saver route-scan`);
176
+ console.error(`No open route candidate R${n}. Run: sprag route-scan`);
177
177
  process.exit(1);
178
178
  }
179
179
  rule = lang === 'ko' ? cand.rule : (cand.ruleEn || cand.rule);
@@ -229,7 +229,7 @@ export async function run({ args, hasFlag }) {
229
229
  console.error(`Route candidate R${routeCandidateId} was detected in another project (${routeCandidate.project}),`);
230
230
  console.error('but this cached scan predates project-path tracking.');
231
231
  console.error('Re-scan to capture it, then promote again:');
232
- console.error(' claude-token-saver route-scan --refresh');
232
+ console.error(' sprag route-scan --refresh');
233
233
  process.exit(1);
234
234
  }
235
235
  }
@@ -261,8 +261,8 @@ export async function run({ args, hasFlag }) {
261
261
  ? `(route candidate R${routeCandidateId} resolved — 다음 세션부터 자동 위임, 이후 스캔마다 로그 기반 갱신됩니다)`
262
262
  : `(route candidate R${routeCandidateId} resolved — delegation applies from the next session, refreshed from logs on every rescan)`);
263
263
  console.log(lang === 'ko'
264
- ? '룰 목록/제거: claude-token-saver route-scan rules [rm <N>]'
265
- : 'List / remove: claude-token-saver route-scan rules [rm <N>]');
264
+ ? '룰 목록/제거: sprag route-scan rules [rm <N>]'
265
+ : 'List / remove: sprag route-scan rules [rm <N>]');
266
266
  // Event-triggered refresh: establish the new rule's stat baseline
267
267
  // right away instead of waiting for the next data-gated rescan.
268
268
  try {
@@ -345,8 +345,8 @@ export async function run({ args, hasFlag }) {
345
345
  }
346
346
  if (r.skippedRules && r.added.length) console.log(` (${r.skippedRules} already present — skipped)`);
347
347
  console.log(lang === 'ko'
348
- ? '\n필요 없는 룰은 언제든: claude-token-saver harness list / rm <N>'
349
- : '\nDrop any rule you do not want: claude-token-saver harness list / rm <N>');
348
+ ? '\n필요 없는 룰은 언제든: sprag harness list / rm <N>'
349
+ : '\nDrop any rule you do not want: sprag harness list / rm <N>');
350
350
  return;
351
351
  }
352
352
 
@@ -365,8 +365,8 @@ export async function run({ args, hasFlag }) {
365
365
  };
366
366
  if (wantProject) print('project');
367
367
  if (wantGlobal) print('global');
368
- console.log('Remove with: claude-token-saver harness rm [--global|--project] <N>');
369
- console.log('Archive in bulk: claude-token-saver harness prune [--global] [--tag <t>] [--older-than <months>] [--dry-run]');
368
+ console.log('Remove with: sprag harness rm [--global|--project] <N>');
369
+ console.log('Archive in bulk: sprag harness prune [--global] [--tag <t>] [--older-than <months>] [--dry-run]');
370
370
  return;
371
371
  }
372
372
 
@@ -406,7 +406,7 @@ export async function run({ args, hasFlag }) {
406
406
  const rmArgs = args.slice(2).filter((a) => a !== '--global' && a !== '--project');
407
407
  const raw = (rmArgs[0] || '').trim();
408
408
  if (!/^\d+$/.test(raw)) {
409
- console.error('Usage: claude-token-saver harness rm [--global|--project] <N> # N from `harness list`');
409
+ console.error('Usage: sprag harness rm [--global|--project] <N> # N from `harness list`');
410
410
  process.exit(1);
411
411
  }
412
412
  const n = parseInt(raw, 10);
@@ -454,6 +454,6 @@ export async function run({ args, hasFlag }) {
454
454
  }
455
455
 
456
456
  console.error(`Unknown harness subcommand: ${sub}`);
457
- console.error('Usage: claude-token-saver harness [check|init|uninit [--purge-ratchet]|promote "<rule>"|pull [--global|--project]|list|rm <N>|prune [--tag <t>] [--older-than <months>] [--dry-run]|off|on]');
457
+ console.error('Usage: sprag harness [check|init|uninit [--purge-ratchet]|promote "<rule>"|pull [--global|--project]|list|rm <N>|prune [--tag <t>] [--older-than <months>] [--dry-run]|off|on]');
458
458
  process.exit(1);
459
459
  }
@@ -3,10 +3,10 @@
3
3
  * user can just mention chip wording and Claude responds. v2.6.0 dropped
4
4
  * the redundant /token-monitor slash command in favor of the skill alone;
5
5
  * a legacy command file is removed automatically. Cross-platform.
6
- * claude-token-saver install # install/update the skill
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
6
+ * sprag install # install/update the skill
7
+ * sprag install --force # overwrite existing skill file
8
+ * sprag install --yes # take the defaults instead of asking
9
+ * sprag install --no-input # same as --yes; also implied by CTS_NO_INPUT=1
10
10
  */
11
11
 
12
12
  import { debug } from '../debug.js';
@@ -94,8 +94,8 @@ export async function run({ hasFlag }) {
94
94
  s = installStatusline({ force: true });
95
95
  } else {
96
96
  s = { ...s, reason: lang === 'ko'
97
- ? '기존 statusline을 유지했습니다. 교체하려면 `claude-token-saver install --force`'
98
- : 'kept your statusline — replace later with `claude-token-saver install --force`' };
97
+ ? '기존 statusline을 유지했습니다. 교체하려면 `sprag install --force`'
98
+ : 'kept your statusline — replace later with `sprag install --force`' };
99
99
  }
100
100
  }
101
101
  const verb = s.action === 'exists' ? 'already configured (refreshInterval=5)'
@@ -178,8 +178,8 @@ export async function run({ hasFlag }) {
178
178
  }
179
179
  if (!proceed) {
180
180
  console.log(lang === 'ko'
181
- ? ' harness: 건너뛰었습니다 — 나중에 `claude-token-saver harness init --global` 로 설정할 수 있습니다.'
182
- : ' harness: skipped — set it up later with `claude-token-saver harness init --global`.');
181
+ ? ' harness: 건너뛰었습니다 — 나중에 `sprag harness init --global` 로 설정할 수 있습니다.'
182
+ : ' harness: skipped — set it up later with `sprag harness init --global`.');
183
183
  throw new SkipStep();
184
184
  }
185
185
  const h = harnessInit({ scope: 'global' });
@@ -194,8 +194,8 @@ export async function run({ hasFlag }) {
194
194
  ? ' harness: 5원칙을 ~/.claude/CLAUDE.md 에 설정했습니다 — statusline에 🅷 5/5 가 표시됩니다.'
195
195
  : ' harness: 5 principles installed in ~/.claude/CLAUDE.md — the statusline now shows 🅷 5/5.');
196
196
  console.log(lang === 'ko'
197
- ? ' 되돌리려면: claude-token-saver harness uninit --global'
198
- : ' to undo: claude-token-saver harness uninit --global');
197
+ ? ' 되돌리려면: sprag harness uninit --global'
198
+ : ' to undo: sprag harness uninit --global');
199
199
  }
200
200
  } catch (e) {
201
201
  // A declined prompt already printed its own line; only real failures fall
@@ -206,8 +206,8 @@ export async function run({ hasFlag }) {
206
206
  // in place, and `harness init` remains available as a manual step.
207
207
  debug('install:harness-init', e);
208
208
  console.log(lang === 'ko'
209
- ? ' harness: 자동 설정을 건너뛰었습니다 — `claude-token-saver harness init --global` 로 직접 설정하세요.'
210
- : ' harness: auto-setup skipped — run `claude-token-saver harness init --global` yourself.');
209
+ ? ' harness: 자동 설정을 건너뛰었습니다 — `sprag harness init --global` 로 직접 설정하세요.'
210
+ : ' harness: auto-setup skipped — run `sprag harness init --global` yourself.');
211
211
  }
212
212
  }
213
213
 
@@ -264,12 +264,12 @@ export async function run({ hasFlag }) {
264
264
  if (interactive || enable) ks.setKoreanStyleEnabled(enable);
265
265
  if (enable) {
266
266
  console.log(lang === 'ko'
267
- ? ' korean: 켰습니다 — 모든 프로젝트의 세션 시작 시 주입됩니다. 끄려면 `claude-token-saver korean off`.'
268
- : ' korean: enabled — injected at session start in every project. Turn off with `claude-token-saver korean off`.');
267
+ ? ' korean: 켰습니다 — 모든 프로젝트의 세션 시작 시 주입됩니다. 끄려면 `sprag korean off`.'
268
+ : ' korean: enabled — injected at session start in every project. Turn off with `sprag korean off`.');
269
269
  } else {
270
270
  console.log(lang === 'ko'
271
- ? ' korean: 꺼 두었습니다 — 나중에 `claude-token-saver korean on` 으로 켤 수 있습니다.'
272
- : ' korean: left off — enable later with `claude-token-saver korean on`.');
271
+ ? ' korean: 꺼 두었습니다 — 나중에 `sprag korean on` 으로 켤 수 있습니다.'
272
+ : ' korean: left off — enable later with `sprag korean on`.');
273
273
  }
274
274
  }
275
275
  } catch (e) {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Subcommand: korean — Korean writing guidance for every session.
3
- * claude-token-saver korean on # inject at session start, all projects
4
- * claude-token-saver korean off # stop injecting
5
- * claude-token-saver korean status # current state, cost, and provenance
6
- * claude-token-saver korean show # print the guidance itself
3
+ * sprag korean on # inject at session start, all projects
4
+ * sprag korean off # stop injecting
5
+ * sprag korean status # current state, cost, and provenance
6
+ * sprag korean show # print the guidance itself
7
7
  *
8
8
  * Why this exists rather than pointing users at Claude Code's output styles:
9
9
  * an output style is one global slot, so turning it on takes the slot away
@@ -18,8 +18,8 @@
18
18
  * reads the finished file. `korean on` therefore also installs a PostToolUse
19
19
  * hook that runs the machine-checkable clauses over the prose the model just
20
20
  * wrote:
21
- * claude-token-saver korean lint block|warn|off # how findings are handled
22
- * claude-token-saver korean lint <file...> # check files on disk
21
+ * sprag korean lint block|warn|off # how findings are handled
22
+ * sprag korean lint <file...> # check files on disk
23
23
  */
24
24
 
25
25
  // Enforcement is only useful if it is on by default — a check the user has to
@@ -108,7 +108,7 @@ export async function run({ args, hasFlag }) {
108
108
  return;
109
109
  }
110
110
  if (!['all', 'prose'].includes(next)) {
111
- console.error('Usage: claude-token-saver korean lint scope [all|prose]');
111
+ console.error('Usage: sprag korean lint scope [all|prose]');
112
112
  process.exit(1);
113
113
  }
114
114
  cfg.koreanStyle = cfg.koreanStyle || {};
@@ -135,7 +135,7 @@ export async function run({ args, hasFlag }) {
135
135
  return;
136
136
  }
137
137
  if (!['block', 'warn', 'off'].includes(mode)) {
138
- console.error('Usage: claude-token-saver korean lint [block|warn|off] | korean lint <file...>');
138
+ console.error('Usage: sprag korean lint [block|warn|off] | korean lint <file...>');
139
139
  process.exit(1);
140
140
  }
141
141
  cfg.koreanStyle = cfg.koreanStyle || {};
@@ -201,20 +201,20 @@ export async function run({ args, hasFlag }) {
201
201
  if (lang === 'ko') {
202
202
  console.log(`한국어 문체 지침: ${on ? '켜짐' : '꺼짐'}`);
203
203
  console.log(` 비용: 세션당 약 ${tokens} 토큰 (세션 시작 1회 주입)`);
204
- console.log(` 쓰기 시점 검사: ${koreanLintMode(loadConfig())}, 범위 ${koreanLintScope(loadConfig())} (바꾸려면 claude-token-saver korean lint block|warn|off, korean lint scope all|prose)`);
204
+ console.log(` 쓰기 시점 검사: ${koreanLintMode(loadConfig())}, 범위 ${koreanLintScope(loadConfig())} (바꾸려면 sprag korean lint block|warn|off, korean lint scope all|prose)`);
205
205
  console.log(` 출처: ${ks.KOREAN_STYLE_SOURCE}`);
206
206
  console.log(` 라이선스 전문: ${ks.KOREAN_STYLE_LICENSE_PATH}`);
207
207
  console.log(on
208
- ? ' 끄려면: claude-token-saver korean off'
209
- : ' 켜려면: claude-token-saver korean on');
208
+ ? ' 끄려면: sprag korean off'
209
+ : ' 켜려면: sprag korean on');
210
210
  } else {
211
211
  console.log(`Korean writing guidance: ${on ? 'on' : 'off'}`);
212
212
  console.log(` Cost: ~${tokens} tokens per session (injected once at session start)`);
213
- console.log(` Write-time check: ${koreanLintMode(loadConfig())}, scope ${koreanLintScope(loadConfig())} (change with: claude-token-saver korean lint block|warn|off, korean lint scope all|prose)`);
213
+ console.log(` Write-time check: ${koreanLintMode(loadConfig())}, scope ${koreanLintScope(loadConfig())} (change with: sprag korean lint block|warn|off, korean lint scope all|prose)`);
214
214
  console.log(` Source: ${ks.KOREAN_STYLE_SOURCE}`);
215
215
  console.log(` License text: ${ks.KOREAN_STYLE_LICENSE_PATH}`);
216
216
  console.log(on
217
- ? ' Turn off with: claude-token-saver korean off'
218
- : ' Turn on with: claude-token-saver korean on');
217
+ ? ' Turn off with: sprag korean off'
218
+ : ' Turn on with: sprag korean on');
219
219
  }
220
220
  }
@@ -111,7 +111,7 @@ export async function run({ args, hasFlag, numArg }) {
111
111
  const n = parseInt(args[3], 10);
112
112
  const removed = Number.isFinite(n) ? mr.removeModelRule(n) : null;
113
113
  if (!removed) {
114
- console.error('Usage: claude-token-saver route-scan rules rm <N> # N from `route-scan rules`');
114
+ console.error('Usage: sprag route-scan rules rm <N> # N from `route-scan rules`');
115
115
  process.exit(1);
116
116
  }
117
117
  // A target whose last rule was removed gets its (tool-owned) file deleted.
@@ -181,20 +181,20 @@ export async function run({ args, hasFlag, numArg }) {
181
181
  + '\n Map it under modelAliases in profile-map.json, then run route-scan --refresh.');
182
182
  }
183
183
  console.log(lang === 'ko'
184
- ? '\n제거: claude-token-saver route-scan rules rm <N>'
185
- : '\nRemove with: claude-token-saver route-scan rules rm <N>');
184
+ ? '\n제거: sprag route-scan rules rm <N>'
185
+ : '\nRemove with: sprag route-scan rules rm <N>');
186
186
  return;
187
187
  }
188
188
 
189
189
  if (args[1] === 'dismiss') {
190
190
  const n = parseInt(args[2], 10);
191
191
  if (!Number.isFinite(n)) {
192
- console.error('Usage: claude-token-saver route-scan dismiss <N> # N from `route? R<N>`');
192
+ console.error('Usage: sprag route-scan dismiss <N> # N from `route? R<N>`');
193
193
  process.exit(1);
194
194
  }
195
195
  const cand = rs.resolveCandidate(n);
196
196
  if (!cand) {
197
- console.error(`No route candidate R${n}. Run: claude-token-saver route-scan`);
197
+ console.error(`No route candidate R${n}. Run: sprag route-scan`);
198
198
  process.exit(1);
199
199
  }
200
200
  console.log(lang === 'ko'
@@ -301,16 +301,16 @@ export async function run({ args, hasFlag, numArg }) {
301
301
  if (u && u.available && !u.dismissed) {
302
302
  updateBlock = lang === 'ko'
303
303
  ? [
304
- `[claude-token-saver update] 새 버전이 나와 있습니다: v${u.current} → ${u.latest}.`,
304
+ `[sprag update] 새 버전이 나와 있습니다: v${u.current} → ${u.latest}.`,
305
305
  '사용자에게 지금 업그레이드할지 물어보고, 승낙하면 아래 명령을 실행하십시오. 묻지 않고 설치하지는 마십시오.',
306
306
  ' claude-token-saver upgrade # 업그레이드 실행',
307
- ' claude-token-saver update-check --dismiss # 사용자가 원치 않으면 (다음 버전이 나올 때까지 다시 묻지 않습니다)',
307
+ ' sprag update-check --dismiss # 사용자가 원치 않으면 (다음 버전이 나올 때까지 다시 묻지 않습니다)',
308
308
  ].join('\n')
309
309
  : [
310
- `[claude-token-saver update] A newer version is available: v${u.current} → ${u.latest}.`,
310
+ `[sprag update] A newer version is available: v${u.current} → ${u.latest}.`,
311
311
  'Ask the user whether to upgrade now, and run the command below only if they agree. Do not install without asking.',
312
312
  ' claude-token-saver upgrade # perform the upgrade',
313
- ' claude-token-saver update-check --dismiss # if they decline (stays quiet until a newer release)',
313
+ ' sprag update-check --dismiss # if they decline (stays quiet until a newer release)',
314
314
  ].join('\n');
315
315
  }
316
316
  } catch (e) { debug('route-scan:update-check', e); /* the offer is optional */ }
@@ -332,10 +332,10 @@ export async function run({ args, hasFlag, numArg }) {
332
332
  const lines = [];
333
333
  if (open.length > 0) {
334
334
  if (lang === 'ko') {
335
- lines.push(`[claude-token-saver route-scan] 최근 ${cache.days}일 세션에서 비싼 모델(opus/fable)이 반복 처리해 온, 더 싼 모델로 넘겨도 되는 작업이 감지되었습니다.`);
335
+ lines.push(`[sprag route-scan] 최근 ${cache.days}일 세션에서 비싼 모델(opus/fable)이 반복 처리해 온, 더 싼 모델로 넘겨도 되는 작업이 감지되었습니다.`);
336
336
  lines.push('(R<N>은 후보 번호, T2/T1은 난이도 등급입니다 — 사용자에게 전달할 때는 코드가 아니라 아래 풀어쓴 설명으로 브리핑하세요)');
337
337
  } else {
338
- lines.push(`[claude-token-saver route-scan] Over the last ${cache.days} days, expensive models (opus/fable) repeatedly handled work that a cheaper model could take.`);
338
+ lines.push(`[sprag route-scan] Over the last ${cache.days} days, expensive models (opus/fable) repeatedly handled work that a cheaper model could take.`);
339
339
  lines.push('(R<N> is the candidate id, T2/T1 the difficulty tier — brief the user with the spelled-out wording below, not the codes.)');
340
340
  }
341
341
  for (const c of open) {
@@ -356,12 +356,12 @@ export async function run({ args, hasFlag, numArg }) {
356
356
  }
357
357
  if (lang === 'ko') {
358
358
  lines.push('등록하면 다음 세션부터 자동 위임됩니다. 사용자에게 등록 여부를 물을 때 위 룰 원문을 그대로 보여주고, 적용 범위까지 확인한 뒤 실행하세요:');
359
- lines.push(' claude-token-saver harness promote R<N> --project|--global # 적용 범위는 반드시 사용자에게 확인');
360
- lines.push(' claude-token-saver route-scan dismiss <N> # 사용자가 원치 않으면');
359
+ lines.push(' sprag harness promote R<N> --project|--global # 적용 범위는 반드시 사용자에게 확인');
360
+ lines.push(' sprag route-scan dismiss <N> # 사용자가 원치 않으면');
361
361
  } else {
362
362
  lines.push('Once registered, delegation happens automatically from the next session. Show the user the rule text verbatim, confirm the scope with them, then run:');
363
- lines.push(' claude-token-saver harness promote R<N> --project|--global # ALWAYS confirm the scope with the user first');
364
- lines.push(' claude-token-saver route-scan dismiss <N> # if they do not want it');
363
+ lines.push(' sprag harness promote R<N> --project|--global # ALWAYS confirm the scope with the user first');
364
+ lines.push(' sprag route-scan dismiss <N> # if they do not want it');
365
365
  }
366
366
  }
367
367
  if (reviewRules.length > 0) {
@@ -383,12 +383,12 @@ export async function run({ args, hasFlag, numArg }) {
383
383
  lines.push(` "${r.rule}"`);
384
384
  }
385
385
  lines.push(lang === 'ko'
386
- ? ' 제거: claude-token-saver route-scan rules rm <N>'
387
- : ' remove with: claude-token-saver route-scan rules rm <N>');
386
+ ? ' 제거: sprag route-scan rules rm <N>'
387
+ : ' remove with: sprag route-scan rules rm <N>');
388
388
  }
389
389
  lines.push(lang === 'ko'
390
- ? '위 내용은 사용자의 첫 메시지 주제와 무관하더라도(단순 인사여도) 첫 응답 말미에 `※ [claude-token-saver]` 라벨을 달아 요약 브리핑하세요 — 사용자가 물어볼 때까지 기다리지 말 것.'
391
- : 'Summarize the above at the end of your first response under a `※ [claude-token-saver]` label, even if the user\'s first message is unrelated (a bare greeting counts) — do not wait to be asked.');
390
+ ? '위 내용은 사용자의 첫 메시지 주제와 무관하더라도(단순 인사여도) 첫 응답 말미에 `※ [sprag]` 라벨을 달아 요약 브리핑하세요 — 사용자가 물어볼 때까지 기다리지 말 것.'
391
+ : 'Summarize the above at the end of your first response under a `※ [sprag]` label, even if the user\'s first message is unrelated (a bare greeting counts) — do not wait to be asked.');
392
392
  // Style first, briefing second: the briefing is Korean prose too, so the
393
393
  // guidance has to be in context before the model reads it.
394
394
  if (koreanBlock) console.log(koreanBlock + '\n');
@@ -446,8 +446,8 @@ export async function run({ args, hasFlag, numArg }) {
446
446
  }
447
447
  console.log('');
448
448
  console.log(lang === 'ko' ? '등록 / 무시:' : 'Promote / dismiss:');
449
- console.log(' claude-token-saver harness promote R<N> --project|--global');
450
- console.log(' claude-token-saver route-scan dismiss <N>');
449
+ console.log(' sprag harness promote R<N> --project|--global');
450
+ console.log(' sprag route-scan dismiss <N>');
451
451
  // 최초 1회만 — 매번 찍으면 도구가 광고판이 된다 (CTS_NO_NOTE=1 로 끔)
452
452
  (await import('../first-run-note.js')).printOnce('route-scan', lang);
453
453
  return;
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Subcommand: update-check — everything around "is a newer version out".
3
3
  *
4
- * claude-token-saver update-check # print the cached answer
5
- * claude-token-saver update-check --refresh # ask the registry now
6
- * claude-token-saver update-check --refresh --quiet
4
+ * sprag update-check # print the cached answer
5
+ * sprag update-check --refresh # ask the registry now
6
+ * sprag update-check --refresh --quiet
7
7
  * # what the detached background child runs
8
- * claude-token-saver update-check --dismiss # stop offering this version at session start
8
+ * sprag update-check --dismiss # stop offering this version at session start
9
9
  *
10
10
  * The statusline never calls the --refresh path directly; it spawns this
11
11
  * command detached so the render itself stays offline and instant.
@@ -71,7 +71,7 @@ export async function run({ hasFlag, version }) {
71
71
  : `Up to date (v${version}).`);
72
72
  if (!s.latest) {
73
73
  console.log(lang === 'ko'
74
- ? `아직 확인된 기록이 없습니다. 지금 확인하려면: claude-token-saver update-check --refresh (기록 파일: ${updateStatePath()})`
75
- : `No check has completed yet. Run: claude-token-saver update-check --refresh (state: ${updateStatePath()})`);
74
+ ? `아직 확인된 기록이 없습니다. 지금 확인하려면: sprag update-check --refresh (기록 파일: ${updateStatePath()})`
75
+ : `No check has completed yet. Run: sprag update-check --refresh (state: ${updateStatePath()})`);
76
76
  }
77
77
  }
package/src/doc2md.cjs CHANGED
@@ -158,7 +158,7 @@ function banner(sourcePath, saving) {
158
158
  const src = fs.statSync(sourcePath);
159
159
  const lines = [
160
160
  '<!--',
161
- 'claude-token-saver doc2md 가 변환한 파일입니다. 직접 편집하지 마십시오.',
161
+ 'sprag doc2md 가 변환한 파일입니다. 직접 편집하지 마십시오.',
162
162
  `원본: ${path.resolve(sourcePath)} (${humanBytes(src.size)})`,
163
163
  `변환: ${new Date().toISOString()} · Markdown ${humanBytes(Buffer.byteLength(saving.markdown, 'utf8'))} · 약 ${saving.tokens.toLocaleString('en-US')} 토큰`,
164
164
  ];
@@ -642,7 +642,7 @@ function markNoticeShown() {
642
642
  } catch { /* an unwritable state dir just means the notice repeats */ }
643
643
  }
644
644
 
645
- const INSTALL_HINT = 'claude-token-saver doc2md install-converter';
645
+ const INSTALL_HINT = 'sprag doc2md install-converter';
646
646
 
647
647
  /**
648
648
  * Decide what to tell Claude Code about one PreToolUse(Read) payload.
@@ -847,8 +847,8 @@ function contextForPrompt(payload, opts = {}) {
847
847
  : ` ${name}: the converter needs Python 3.10+, and none is on PATH (macOS ships 3.9). Tell the user to install a newer Python, e.g. \`brew install python\`.`);
848
848
  } else if (result.reason === 'no-figparser') {
849
849
  lines.push(lang === 'ko'
850
- ? ` ${name}: .fig 파서가 없어 변환하지 못했습니다. 설치: claude-token-saver doc2md install-converter`
851
- : ` ${name}: no .fig parser installed. Install it with: claude-token-saver doc2md install-converter`);
850
+ ? ` ${name}: .fig 파서가 없어 변환하지 못했습니다. 설치: sprag doc2md install-converter`
851
+ : ` ${name}: no .fig parser installed. Install it with: sprag doc2md install-converter`);
852
852
  } else if (result.reason === 'sensitive') {
853
853
  lines.push(lang === 'ko'
854
854
  ? ` ${name}: 파일명이 민감 문서 패턴에 걸려 변환하지 않았습니다.`
@@ -861,8 +861,8 @@ function contextForPrompt(payload, opts = {}) {
861
861
  }
862
862
  if (paths.length > targets.length) {
863
863
  lines.push(lang === 'ko'
864
- ? ` (문서 ${paths.length}개 가운데 앞 ${targets.length}개만 변환했습니다. 나머지는 \`claude-token-saver doc2md <경로>\` 로 변환하십시오.)`
865
- : ` (converted the first ${targets.length} of ${paths.length} documents; run \`claude-token-saver doc2md <path>\` for the rest.)`);
864
+ ? ` (문서 ${paths.length}개 가운데 앞 ${targets.length}개만 변환했습니다. 나머지는 \`sprag doc2md <경로>\` 로 변환하십시오.)`
865
+ : ` (converted the first ${targets.length} of ${paths.length} documents; run \`sprag doc2md <path>\` for the rest.)`);
866
866
  }
867
867
 
868
868
  return lang === 'ko'
@@ -870,15 +870,15 @@ function contextForPrompt(payload, opts = {}) {
870
870
  '[doc2md] 이 프롬프트에 문서 경로가 있어 Markdown 으로 변환해 두었습니다.',
871
871
  ...lines,
872
872
  '원본 대신 위 변환본을 Read 하십시오. pptx·xlsx·docx 는 Read 도구가 이진 파일이라며 거부하므로 원본을 직접 열 수 없습니다.',
873
- '변환이 일어났다는 사실은 사용자에게도 보여야 하므로, 답변 첫 줄에 변환한 문서마다 다음 형식으로 한 줄씩 출력하십시오: `📄 [claude-token-saver] doc2md: <파일명> (<원본 크기> → <Markdown 크기>, 약 <토큰> 토큰)`. 절감액이 함께 안내됐다면 그 금액도 같은 줄 끝에 덧붙이십시오.',
874
- '경로가 주어지지 않은 문서를 열어야 할 때는 `claude-token-saver doc2md <경로>` 를 실행해 변환본 경로를 얻으십시오.',
873
+ '변환이 일어났다는 사실은 사용자에게도 보여야 하므로, 답변 첫 줄에 변환한 문서마다 다음 형식으로 한 줄씩 출력하십시오: `📄 [sprag] doc2md: <파일명> (<원본 크기> → <Markdown 크기>, 약 <토큰> 토큰)`. 절감액이 함께 안내됐다면 그 금액도 같은 줄 끝에 덧붙이십시오.',
874
+ '경로가 주어지지 않은 문서를 열어야 할 때는 `sprag doc2md <경로>` 를 실행해 변환본 경로를 얻으십시오.',
875
875
  ].join('\n')
876
876
  : [
877
877
  '[doc2md] This prompt names document paths, which have been converted to Markdown:',
878
878
  ...lines,
879
879
  'Read the conversions above rather than the originals: Read refuses pptx/xlsx/docx as binary files, so the originals cannot be opened directly.',
880
- 'The user cannot see that any of this happened, so open your reply with one line per converted document: `📄 [claude-token-saver] doc2md: <name> (<source size> → <markdown size>, ~<tokens> tokens)`, adding the saving above if one was reported.',
881
- 'For a document whose path was not given here, run `claude-token-saver doc2md <path>` to get its conversion.',
880
+ 'The user cannot see that any of this happened, so open your reply with one line per converted document: `📄 [sprag] doc2md: <name> (<source size> → <markdown size>, ~<tokens> tokens)`, adding the saving above if one was reported.',
881
+ 'For a document whose path was not given here, run `sprag doc2md <path>` to get its conversion.',
882
882
  ].join('\n');
883
883
  }
884
884
 
@@ -893,14 +893,14 @@ function sessionNote(lang = 'en') {
893
893
  if (lang === 'ko') {
894
894
  return [
895
895
  '[doc2md] 문서(pptx·xlsx·pdf·docx)를 다룰 때의 규칙입니다.',
896
- ' 문서를 열어야 하는데 Read 가 이진 파일이라며 거부하면, `claude-token-saver doc2md <경로>` 를 실행하고 그 결과로 나온 .md 를 Read 하십시오.',
896
+ ' 문서를 열어야 하는데 Read 가 이진 파일이라며 거부하면, `sprag doc2md <경로>` 를 실행하고 그 결과로 나온 .md 를 Read 하십시오.',
897
897
  ' 사용자가 문서를 메시지에 직접 첨부했다면, 첨부는 내용 전체가 컨텍스트에 실려 토큰을 크게 씁니다. 다음부터는 첨부 대신 파일 경로를 적어 달라고 안내하십시오. 경로로 주면 변환본만 읽습니다.',
898
- ' 변환은 단방향입니다. 변환본 .md 를 고쳐도 원본에는 반영되지 않습니다. 문서 수정 요청을 받으면: ① 원본을 복사하고 ② 복사본을 스크립트로 수정하십시오. pptx·docx·xlsx 는 도구 venv 파이썬에 python-pptx·python-docx·openpyxl 이 준비되어 있고(`claude-token-saver doc2md` 상태 출력에 경로가 나옵니다), .fig 는 openfig-core 로 편집·재인코드합니다. ③ 수정한 복사본을 doc2md 로 재변환해 변경이 들어갔는지 검증하십시오. 차트·이미지 같은 시각 요소는 변환본에 안 잡히므로 텍스트 검증만으로 완료를 단정하지 마십시오.',
898
+ ' 변환은 단방향입니다. 변환본 .md 를 고쳐도 원본에는 반영되지 않습니다. 문서 수정 요청을 받으면: ① 원본을 복사하고 ② 복사본을 스크립트로 수정하십시오. pptx·docx·xlsx 는 도구 venv 파이썬에 python-pptx·python-docx·openpyxl 이 준비되어 있고(`sprag doc2md` 상태 출력에 경로가 나옵니다), .fig 는 openfig-core 로 편집·재인코드합니다. ③ 수정한 복사본을 doc2md 로 재변환해 변경이 들어갔는지 검증하십시오. 차트·이미지 같은 시각 요소는 변환본에 안 잡히므로 텍스트 검증만으로 완료를 단정하지 마십시오.',
899
899
  ].join('\n');
900
900
  }
901
901
  return [
902
902
  '[doc2md] Handling documents (pptx/xlsx/pdf/docx):',
903
- ' If Read refuses a document as a binary file, run `claude-token-saver doc2md <path>` and Read the .md it prints.',
903
+ ' If Read refuses a document as a binary file, run `sprag doc2md <path>` and Read the .md it prints.',
904
904
  ' If the user attached a document to their message, its full contents were billed into the context. Tell them that naming the file path instead is far cheaper, since only the converted Markdown gets read.',
905
905
  ' Conversions are one-way: editing the cached .md changes nothing in the source. When asked to modify a document: ① copy the original, ② edit the COPY with a script — the managed venv python has python-pptx/python-docx/openpyxl, and .fig edits go through openfig-core — then ③ re-convert the copy with doc2md to verify the change landed. Charts and images do not appear in conversions, so text verification alone does not prove visual edits.',
906
906
  ].join('\n');
@@ -939,7 +939,7 @@ function decideForWrite(context) {
939
939
  + ' 문서 수정이 목적이라면: 원본을 복사한 뒤(cp) 복사본을 스크립트로 수정하십시오. '
940
940
  + `pptx·docx·xlsx 는 ${managedPython()} 에 python-pptx·python-docx·openpyxl 이 설치되어 있고, `
941
941
  + '.fig 는 doc2md-fig 의 openfig-core 로 편집·재인코드할 수 있습니다. '
942
- + '수정 후 복사본을 `claude-token-saver doc2md <복사본>` 으로 재변환해 의도한 변경이 들어갔는지 확인하십시오. 원본은 절대 직접 수정하지 마십시오.',
942
+ + '수정 후 복사본을 `sprag doc2md <복사본>` 으로 재변환해 의도한 변경이 들어갔는지 확인하십시오. 원본은 절대 직접 수정하지 마십시오.',
943
943
  };
944
944
  }
945
945
 
@@ -949,7 +949,7 @@ function decideForWrite(context) {
949
949
  reason: `[doc2md] ${path.basename(abs)} 는 이진 문서입니다. Edit/Write 는 텍스트를 쓰므로 이 파일을 파괴합니다. `
950
950
  + '수정하려면 원본을 복사한 뒤(cp) 복사본을 스크립트로 고치십시오. '
951
951
  + `pptx·docx·xlsx 는 ${managedPython()} 의 python-pptx·python-docx·openpyxl, .fig 는 openfig-core 를 쓰고, `
952
- + '수정 후 `claude-token-saver doc2md <복사본>` 재변환으로 결과를 검증하십시오.',
952
+ + '수정 후 `sprag doc2md <복사본>` 재변환으로 결과를 검증하십시오.',
953
953
  };
954
954
  }
955
955
  return null;
@@ -51,7 +51,7 @@ export function harnessClaudeMdBlock(scope = 'project') {
51
51
  return `${HARNESS_BLOCK_BEGIN}
52
52
  ## 🅷 Harness Rules (claude-token-saver)
53
53
 
54
- 이 섹션은 \`claude-token-saver harness init\`이 생성합니다. 5가지 원칙 모두를
54
+ 이 섹션은 \`sprag harness init\`이 생성합니다. 5가지 원칙 모두를
55
55
  지키면 statusline에 \`🅷 5/5\`로 표시되고, 빠진 게 있으면 \`🅷 3/5\` 식으로
56
56
  경고합니다. 수정해도 무방하지만, 섹션 헤더(### 1. ~ ### 5.)는 검출용이므로
57
57
  지우지 마세요.
@@ -60,7 +60,7 @@ ${HARNESS_SECTIONS[0].heading}
60
60
  - 같은 에러·오해·반복 작업이 한 번 더 발생하면 즉시 \`.claude/ratchet.md\`에
61
61
  "조건 → 행동" 한 줄로 룰 추가.
62
62
  - claude-token-saver가 후보를 감지하면 statusline에 \`🅷⚠ ratchet?\`로 알림.
63
- \`claude-token-saver harness promote "<rule>" --project|--global\`로 승인.
63
+ \`sprag harness promote "<rule>" --project|--global\`로 승인.
64
64
  - **scope는 항상 사용자에게 먼저 물어볼 것** — 프로젝트 한정이면 \`--project\`,
65
65
  도구·환경 일반 룰이면 \`--global\`(\`~/.claude/ratchet.md\`). Bash 환경은
66
66
  non-TTY라 CLI의 readline 프롬프트가 안 뜨므로, 호출자(LLM)가 직접 묻고
@@ -108,9 +108,9 @@ ${HARNESS_SECTIONS[4].heading}
108
108
  ---
109
109
 
110
110
  📌 운영:
111
- - \`claude-token-saver harness check\` — 현재 셋업 점수
112
- - \`claude-token-saver harness promote "<룰>" --project|--global\` — ratchet에 룰 추가 (scope는 사용자에게 먼저 물어볼 것)
113
- - \`claude-token-saver harness off\` — statusline 표시 끄기
111
+ - \`sprag harness check\` — 현재 셋업 점수
112
+ - \`sprag harness promote "<룰>" --project|--global\` — ratchet에 룰 추가 (scope는 사용자에게 먼저 물어볼 것)
113
+ - \`sprag harness off\` — statusline 표시 끄기
114
114
 
115
115
  ---
116
116
 
@@ -127,13 +127,13 @@ export function harnessRatchetMdInitial() {
127
127
 
128
128
  같은 실수가 두 번 발생하면 여기에 한 줄 추가됩니다. 형식: "YYYY-MM-DD: <조건> → <행동>".
129
129
 
130
- \`claude-token-saver harness promote "<rule>"\`로 룰을 추가하면 자동으로
130
+ \`sprag harness promote "<rule>"\`로 룰을 추가하면 자동으로
131
131
  이 파일에 append 됩니다.
132
132
 
133
133
  이 파일은 CLAUDE.md의 \`@\` import로 매 세션 로드됩니다 — 즉 여기 있는 모든 줄이
134
134
  매 요청마다 토큰을 씁니다. 날짜 뒤에 \`[태그]\`를 붙여두면 나중에 묶어서 정리할 수
135
135
  있습니다: \`- 2026-05-08: [video,tts] ...\` →
136
- \`claude-token-saver harness prune --tag video\` (삭제 아니라 ratchet-archive.md로 이동).
136
+ \`sprag harness prune --tag video\` (삭제 아니라 ratchet-archive.md로 이동).
137
137
 
138
138
  ## Rules
139
139
 
@@ -15,7 +15,7 @@
15
15
  * Cost: the text is ~1.3k tokens, injected once per session (not per turn) and
16
16
  * covered by the prompt cache from the second request on. A token-saving tool
17
17
  * has no business spending that silently, so the feature is opt-in via
18
- * `claude-token-saver korean on`.
18
+ * `sprag korean on`.
19
19
  *
20
20
  * The guidance itself is vendored from fluent-korean
21
21
  * (https://github.com/snflkd/fluent-korean), Copyright (c) 2026 snflkd, MIT
@@ -176,7 +176,7 @@ export function koreanStyleInjection({ cfg = loadConfig() } = {}) {
176
176
  const text = koreanStyleText();
177
177
  if (!text) return null;
178
178
  return [
179
- '[claude-token-saver korean-style] 이 세션에서 한국어를 출력할 때는 아래 지침을 따르십시오.',
179
+ '[sprag korean-style] 이 세션에서 한국어를 출력할 때는 아래 지침을 따르십시오.',
180
180
  '이 지침은 사용자가 claude-token-saver에 설정한 것입니다.',
181
181
  ...koreanScopeLines(cfg),
182
182
  '표기 규칙(예외 없이 적용): 도구 호출 인자에 한국어를 비롯한 비ASCII 문자열을 담을 때에는 반드시 리터럴 UTF-8로 작성하고, \\uXXXX 유니코드 이스케이프로는 절대 작성하지 마십시오. 이스케이프로 작성하면 글자가 깨진 채 파일에 기록되는 사례가 자주 발생합니다.',
@@ -201,7 +201,7 @@ export function renderModelRatchet(rules, lang = userLanguage()) {
201
201
  '# Model-Fitting Ratchet (claude-token-saver 자동 관리)',
202
202
  '',
203
203
  '로그 기반 티어 위임 룰. 이 파일은 route-scan이 매 스캔마다 통째로 재생성하므로',
204
- '직접 수정하지 마세요 — 목록/제거: `claude-token-saver route-scan rules [rm <N>]`.',
204
+ '직접 수정하지 마세요 — 목록/제거: `sprag route-scan rules [rm <N>]`.',
205
205
  '',
206
206
  '**사용자는 아래 룰에 해당하는 서브에이전트 위임을 이미 승인했습니다.**',
207
207
  '룰에 들어맞는 요청이면 되묻지 말고 바로 위임하십시오. "사용자가 명시적으로',
@@ -212,7 +212,7 @@ export function renderModelRatchet(rules, lang = userLanguage()) {
212
212
  '',
213
213
  '위임을 실행할 때는 사용자에게 다음 한 줄을 먼저 표시할 것',
214
214
  '(어떤 도구가 토큰을 아끼고 있는지 가시화):',
215
- '`🔀 [claude-token-saver] 모델 피팅: "<유형>" → <agent> 위임`',
215
+ '`🔀 [sprag] 모델 피팅: "<유형>" → <agent> 위임`',
216
216
  '',
217
217
  '## 티어 판별 기준 (모든 룰에 공통 적용)',
218
218
  '',
@@ -249,7 +249,7 @@ export function renderModelRatchet(rules, lang = userLanguage()) {
249
249
  '',
250
250
  'Log-derived tier delegation rules. route-scan regenerates this file in full',
251
251
  'on every scan — do not edit it by hand. List / remove with:',
252
- '`claude-token-saver route-scan rules [rm <N>]`.',
252
+ '`sprag route-scan rules [rm <N>]`.',
253
253
  '',
254
254
  '**The user has already approved the subagent delegations described below.**',
255
255
  'When a request matches a rule, delegate right away instead of asking. If a',
@@ -260,7 +260,7 @@ export function renderModelRatchet(rules, lang = userLanguage()) {
260
260
  '',
261
261
  'When delegating under a rule below, show the user this line first so it is',
262
262
  'visible which tool is saving tokens:',
263
- '`🔀 [claude-token-saver] model fitting: "<category>" → delegated to <agent>`',
263
+ '`🔀 [sprag] model fitting: "<category>" → delegated to <agent>`',
264
264
  '',
265
265
  '## Tier criteria (apply to every rule below)',
266
266
  '',