sprag-cli 3.67.0 → 3.68.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.
Files changed (48) hide show
  1. package/README.ko.md +29 -62
  2. package/README.md +26 -61
  3. package/bin/cli.js +122 -61
  4. package/docs/CODEX.ko.md +32 -0
  5. package/docs/CODEX.md +34 -0
  6. package/package.json +11 -4
  7. package/src/{codex-brief.js → codex/brief.js} +7 -7
  8. package/src/{codex-budget.js → codex/budget.js} +2 -2
  9. package/src/{codex-cache-policy.js → codex/cache-policy.js} +9 -9
  10. package/src/{codex-delegation-diagnostics.js → codex/delegation-diagnostics.js} +2 -2
  11. package/src/{codex-delegation.js → codex/delegation.js} +11 -11
  12. package/src/{codex-doc2md-ledger.js → codex/doc2md-ledger.js} +5 -5
  13. package/src/{codex-doc2md.js → codex/doc2md.js} +4 -4
  14. package/src/{codex-doctor.js → codex/doctor.js} +4 -4
  15. package/src/{codex-harness.js → codex/harness.js} +5 -5
  16. package/src/{codex-hooks.js → codex/hooks.js} +20 -20
  17. package/src/{codex-installer.js → codex/installer.js} +2 -2
  18. package/src/{codex-ledger.js → codex/ledger.js} +6 -6
  19. package/src/{codex-panel-launcher.js → codex/panel-launcher.js} +2 -2
  20. package/src/{codex-panel-runner.js → codex/panel-runner.js} +2 -2
  21. package/src/{codex-panel-session.js → codex/panel-session.js} +1 -1
  22. package/src/{codex-panel-setup.js → codex/panel-setup.js} +1 -1
  23. package/src/{codex-panel-state.js → codex/panel-state.js} +3 -3
  24. package/src/{codex-panel.js → codex/panel.js} +43 -34
  25. package/src/{codex-parser.js → codex/parser.js} +5 -9
  26. package/src/{codex-reply-language.js → codex/reply-language.js} +2 -2
  27. package/src/{codex-route-scan.js → codex/route-scan.js} +10 -10
  28. package/src/{codex-rule-health.js → codex/rule-health.js} +4 -4
  29. package/src/commands/codex-delegate.js +6 -6
  30. package/src/commands/codex.js +44 -44
  31. package/src/commands/install.js +10 -7
  32. package/src/commands/korean.js +1 -1
  33. package/src/commands/litellm-budget.js +56 -0
  34. package/src/commands/saved.js +60 -5
  35. package/src/commands/uninstall.js +3 -3
  36. package/src/config.js +4 -0
  37. package/src/formatters/statusline.js +713 -504
  38. package/src/glyphs.js +2 -0
  39. package/src/installer.js +77 -1
  40. package/src/model-alias.js +45 -60
  41. package/src/model-share.js +136 -0
  42. package/src/parser.js +4 -13
  43. package/src/session-records.js +2 -8
  44. package/src/statusline-base.js +9 -3
  45. package/src/stdin-payload.js +1 -1
  46. package/src/transcript-lines.js +40 -0
  47. /package/src/{codex-cache.js → codex/cache.js} +0 -0
  48. /package/src/{codex-panel-shell.js → codex/panel-shell.js} +0 -0
package/README.ko.md CHANGED
@@ -9,7 +9,9 @@
9
9
 
10
10
  [![npm](https://img.shields.io/npm/v/sprag-cli.svg?label=sprag-cli)](https://www.npmjs.com/package/sprag-cli)
11
11
  [![downloads](https://img.shields.io/npm/dm/sprag-cli.svg)](https://www.npmjs.com/package/sprag-cli)
12
- [![license](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](./LICENSE)
12
+ [![test](https://github.com/rootstudioyaml/sprag/actions/workflows/test.yml/badge.svg)](https://github.com/rootstudioyaml/sprag/actions/workflows/test.yml)
13
+ ![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)
14
+ [![license](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](https://github.com/rootstudioyaml/sprag/blob/main/LICENSE)
13
15
 
14
16
  [English](https://github.com/rootstudioyaml/sprag/blob/main/README.md) · [한국어](https://github.com/rootstudioyaml/sprag/blob/main/README.ko.md)
15
17
 
@@ -30,12 +32,14 @@
30
32
 
31
33
  Sprag는 Claude Code와 Codex가 남기는 세션 기록을 읽어 반복 실패와 위임 후보를 찾습니다. 사용자가 승인한 규칙은 다음 세션에도 적용하며, 위임 기록과 모델 단가를 확인할 수 있으면 예상 차액을 원장에 적습니다. 캐시와 사용량 한도 문제는 상태줄이나 패널에서 확인할 수 있습니다.
32
34
 
33
- 세션 분석은 로컬에서 실행하므로 추가 모델 호출이나 API 키가 필요하지 않습니다. Codex의 선택 기능인 예산·단가 조회는 기존 인증 정보로 사용자가 설정한 LiteLLM에 접속합니다. CLI는 Node.js 18 이상과 TOML 파서 하나를 사용합니다.
35
+ 세션 분석은 로컬에서 실행하므로 추가 모델 호출이나 API 키가 필요하지 않습니다. Codex의 선택 기능인 예산·단가 조회는 기존 인증 정보로 사용자가 설정한 LiteLLM에 접속합니다. CLI는 Node.js 20 이상과 TOML 파서 하나를 사용합니다.
34
36
 
35
37
  ```bash
36
- npm i -g sprag-cli # 예전 이름 claude-token-saver 와 같은 패키지입니다
38
+ npm i -g sprag-cli # Claude Code용이며, Codex가 설치되어 있으면 Codex도 함께 설정합니다
37
39
  ```
38
40
 
41
+ pnpm이나 bun처럼 패키지 관리자가 설치 스크립트를 건너뛰면, 설치한 뒤 `sprag install` 을 한 번 실행하십시오. 예전 패키지 `claude-token-saver` 는 3.55.0 에서 갱신이 멈췄으니 `sprag-cli` 로 옮기십시오.
42
+
39
43
  ## 설치하면 바로 달라지는 것
40
44
 
41
45
  1. `npm i -g sprag-cli` 를 실행합니다. 훅과 상태줄, 추천 룰이 모든 프로젝트에
@@ -46,10 +50,12 @@ npm i -g sprag-cli # 예전 이름 claude-token-saver 와 같은 패키지입
46
50
  사용량 한도에 문제가 생기면 경고 칩이 줄 맨 앞에 나타납니다.
47
51
 
48
52
  ```
49
- 🔀 Routing saved $188 | opus→sonnet 67× $93.4 · fable→sonnet 26× $83.7 · opus→haiku 25× $7.41 · fable→haiku 6× $3.73
50
- 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 🅷 5/5 · 📦 Ctx 47% of 1M
53
+ 🔀 Routing saved $188 | opus→sonnet ×67 $93.4 · fable→sonnet ×26 $83.7 · opus→haiku ×25 $7.41 · fable→haiku ×6 $3.73
54
+ 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 📦 Ctx 47% of 1M
51
55
  ```
52
56
 
57
+ ![statusline 예시. 첫 줄은 라우팅 절감액, 둘째 줄은 문서 변환 절감액, 셋째 줄은 진단 칩입니다](https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/statusline.png)
58
+
53
59
  Sprag는 사용자가 고른 모델을 바꾸지 않습니다. 분석은 로컬 세션 기록만 읽으며,
54
60
  추가 모델 호출이나 API 키가 필요하지 않습니다. `sprag uninstall` 은 설치 때
55
61
  추가한 것을 모두 지우고, 함께 표시하던 기존 상태줄을 되돌려 놓습니다.
@@ -73,7 +79,7 @@ Sprag 는 사용자가 지정한 모델을 바꾸지 않습니다. 이미 실행
73
79
  |---|---|---|---|
74
80
  | 공개 벤치마크 정확도 | **59.1%** · 단일 최고 모델 57.9% | `▰▰▰▰▰▰▰▰▰▰▰▰` | [벤치마크](https://github.com/rootstudioyaml/sprag/blob/main/docs/BENCHMARK.md) |
75
81
  | 같은 문항 총비용 | **$268** · gpt-5 $388 · gemini-2.5-pro $734 | `▰▰▰▰▰▱▱▱▱▱▱▱` | [벤치마크](https://github.com/rootstudioyaml/sprag/blob/main/docs/BENCHMARK.md) |
76
- | 같은 문서들의 토큰 | **−95.9%** · 2,011,178 → 82,209 (12건) | `▰▱▱▱▱▱▱▱▱▱▱▱` | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.md) |
82
+ | 같은 문서들의 토큰 | **−95.9%** · 2,011,178 → 82,209 (12건) | `▰▱▱▱▱▱▱▱▱▱▱▱` | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.ko.md) |
77
83
  | 위임 절감액 (원장) | **$188.27** · 위임 124건 | `▰▰▰▰▰▰▰▰▰▰▰▱` | `sprag route-scan savings` |
78
84
  | 같은 프롬프트를 저가 등급에 | **비용 24%** · 여덟 건 중 한 건이 더 틀림 | `▰▰▰▱▱▱▱▱▱▱▱▱` | [A/B 실측](#같은-프롬프트를-저가-등급에-돌리면) |
79
85
 
@@ -131,10 +137,10 @@ LLMRouterBench(Findings of ACL 2026)에서 오프라인으로 측정했습니다
131
137
  | | 하는 일 | 더 보기 |
132
138
  |---|---|---|
133
139
  | ⚙️ **래칫 규칙** | 되풀이된 실패가 한 줄짜리 규칙이 되어 매 세션에 올라옵니다. 후보는 기록에서 자동으로 찾아 줍니다. | [Harness](https://github.com/rootstudioyaml/sprag/blob/main/docs/HARNESS.ko.md) |
134
- | 🔀 **모델 피팅** | 기록에서 뽑은 위임 규칙에 측정한 오류율과 절감액을 붙여 `ratchet-model.md`에 적습니다. | [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.md) |
140
+ | 🔀 **모델 피팅** | 기록에서 뽑은 위임 규칙에 측정한 오류율과 절감액을 붙여 `ratchet-model.md`에 적습니다. | [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.ko.md) |
135
141
  | 🅷 **하네스 점수** | 다섯 가지 운영 원칙을 실시간으로 점검합니다. 검증 단계를 건너뛰면 `🅷 4/5`가 먼저 알려 줍니다. | [Harness](https://github.com/rootstudioyaml/sprag/blob/main/docs/HARNESS.ko.md) |
136
142
  | 📊 **토큰 텔레메트리** | 캐시 적중률과 TTL, 컨텍스트 크기, 출력 급증, 두 가지 사용량 한도를 매 턴 표시합니다. | [statusline](https://github.com/rootstudioyaml/sprag/blob/main/docs/STATUSLINE.ko.md) |
137
- | 📄 **doc2md** | pptx와 xlsx, pdf, docx, fig를 필요할 때 Markdown 으로 바꿔 읽습니다. | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.md) |
143
+ | 📄 **doc2md** | pptx와 xlsx, pdf, docx, fig를 필요할 때 Markdown 으로 바꿔 읽습니다. | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.ko.md) |
138
144
  | 🇰🇷 **문체 게이트** | 쓰는 시점에 hook으로 검사합니다. 이중 피동과 번역투, 문장 연결을 살핍니다. | [문체 지침](https://github.com/rootstudioyaml/sprag/blob/main/docs/KOREAN-STYLE.ko.md) |
139
145
 
140
146
  두 절감액은 한 숫자로 합치지 않습니다. 재는 대상이 다르기 때문입니다. 라우팅 원장은 위임된 실행의 단가 차액을 적고, 변환 원장은 문서가 쓰지 않게 된 토큰을 적습니다.
@@ -145,7 +151,7 @@ LLMRouterBench(Findings of ACL 2026)에서 오프라인으로 측정했습니다
145
151
 
146
152
  ```
147
153
  🔀 라우팅 절감 $188 | opus→sonnet 67회 $93.4 · fable→sonnet 26회 $83.7 · opus→haiku 25회 $7.41 · fable→haiku 6회 $3.73
148
- 🚨 5H ▰▰▰▰▰▰▰▰▰▰▰▱ 94% 🔄 12:36 · 🅷 5/5 · 🤖 Opus 5.5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 📅 weekly ▰▰▰▰▰▱▱▱▱▱▱▱ 38% · 💵 Sep $42 · 📦 Ctx 47% of 1M
154
+ 🚨 5H ▰▰▰▰▰▰▰▰▰▰▰▱ 94% 🔄 12:36 · 🤖 Opus 5.5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 📅 weekly ▰▰▰▰▰▱▱▱▱▱▱▱ 38% · 💵 Sep $42 · 📦 Ctx 47% of 1M
149
155
  ```
150
156
 
151
157
  문제가 생기면 경고 칩이 줄 맨 앞으로 옵니다. `🚨 5H/7D NN%`, `⚠ Ctx 500k+`, `⚠ Cache miss`, `⚠ Input spike`, `⚠ Output heavy`, `⚠ Call surge`, `⚠ Rebuild churn`, `⚠ 5m TTL` 여덟 가지입니다. 칩 문구를 Claude 에게 그대로 붙여 넣으면 Skill 이 원인 코드와 조치 순서를 꺼내 줍니다. 세그먼트마다 무엇을 뜻하는지는 [statusline 문서](https://github.com/rootstudioyaml/sprag/blob/main/docs/STATUSLINE.ko.md)에 정리했습니다.
@@ -180,48 +186,11 @@ JetBrains IDE 내장 터미널에서는 이모지 대신 한 칸 글리프로
180
186
 
181
187
  ## Codex
182
188
 
183
- Codex 기본 훅과 `AGENTS.md` 하네스, 범위별 래칫 규칙,
184
- 한국어 검사, 프롬프트의 문서 경로 자동 변환, 로컬 토큰 보고서를 사용할 수 있습니다.
185
- Codex가 설치되어 있으면 `npm i -g sprag-cli` 하나로 Codex도 설정하고, 추천 룰을 묻지 않고
186
- 전역으로 등록합니다(단계마다 확인하려면 `sprag install --manual`). Claude Code를 건드리지 않고
187
- Codex만 쓴다면 다음과 같이 설치하십시오.
189
+ Codex에는 기본 훅과 `AGENTS.md` 하네스, 범위별 래칫 규칙, 한국어 검사, 문서 변환, 토큰 보고서, 세션에 연결된 동반 패널이 들어갑니다. Codex가 설치되어 있으면 `npm i -g sprag-cli` 하나로 Codex도 설정하고, 추천 룰을 묻지 않고 전역으로 등록합니다(단계마다 확인하려면 `sprag install --manual`). Claude Code를 건드리지 않고 Codex만 쓰려면 `npm i -g sprag-cli --ignore-scripts` 를 실행한 뒤 `sprag install --agent codex` 를 실행하십시오.
188
190
 
189
- ```bash
190
- npm i -g sprag-cli --ignore-scripts
191
- sprag install --agent codex
192
- sprag --agent codex --days 7
193
- sprag panel --agent codex # 별도 터미널에서 실시간 패널 실행
194
- sprag --statusline --agent codex --text
195
- sprag doctor --agent codex # 문제가 생겼을 때만
196
- sprag route-scan --agent codex
197
- sprag seed --agent codex
198
- ```
191
+ 명령의 기본 대상은 Claude Code이며, `--agent codex`를 붙이면 Codex를 고릅니다. 예를 들면 `sprag --agent codex --days 7` 이나 `sprag panel --agent codex` 입니다. 설치 후에는 Codex의 `/hooks`에서 새 훅을 검토하고 신뢰해야 하며, Codex가 요구하는 절차입니다. 이 README의 금액 수치는 Claude Code 세션 기록에서 나온 값이고 Codex 벤치마크가 아닙니다.
199
192
 
200
- 설치 후 Codex의 `/hooks`에서 새 훅을 검토하고 신뢰해야 실행됩니다. Codex가 요구하는
201
- 절차입니다. 명령의 기본 대상은 여전히 Claude Code이며, `--agent codex`를 붙이면 Codex를 고릅니다. Codex에서도 세션 브리핑, 경고 이력, 인수인계
202
- 파일을 지원합니다. `sprag delegate on --agent codex`로 모델 위임과 하위 에이전트
203
- 지침 전달을 켤 수 있습니다. 상태 표시는 Sprag 터미널 상태줄과 패널을 사용하십시오.
204
- Codex 기본 상태줄은 셸 명령을 실행하지 않습니다. 반복된 단순 작업은 위임 후보로,
205
- 반복된 도구 실패는 래칫 후보로 제안합니다. 규칙을 등록하려면 사용자의 승인이 필요하며,
206
- `seed`로 호환되는 기본 규칙을 선택할 수도 있습니다.
207
- `sprag route-scan savings --agent codex`는 Sprag가 위임한 실행만 집계하고,
208
- 일치하는 LiteLLM 단가가 있을 때 예상 차액을 계산합니다. 청구 금액을 측정한 값은 아니며,
209
- 단가를 확인하지 못하면 `n/a`로 표시합니다. Codex 문서 누계는 Claude 추정치와 분리합니다.
210
- 아래의 기존 절감 수치는 Claude Code 기록이며 Codex 벤치마크가 아닙니다.
211
- [Codex 지원 범위와 제한](https://sprag.io/docs/ko/codex/)이나
212
- `sprag capabilities --agent codex`에서 기능별 지원 여부를 확인하십시오.
213
-
214
- | 기능 | Claude Code | Codex |
215
- |---|---|---|
216
- | 세션 지침 | `CLAUDE.md`와 훅 | `AGENTS.md`와 기본 훅 |
217
- | 실시간 상태 | 기본 상태줄에 명령 등록 | 별도 패널 또는 tmux 구성 |
218
- | 규칙 후보 | Claude 기록과 모델 등급 프리셋 | Codex 기록과 호환 래칫 프리셋 |
219
- | 모델 위임 | Haiku·Sonnet 등급 규칙 | 공급자 모델 ID와 범위별 규칙 |
220
- | 위임 원장 | Claude 실행별 예상 차액 | LiteLLM 단가와 하위 실행별 예상 차액 |
221
-
222
- 두 연동을 함께 설치할 수 있습니다. `--agent codex`를 붙이지 않으면 기존 Claude 동작을 유지합니다.
223
-
224
- ![statusline 예시. 첫 줄은 라우팅 절감액, 둘째 줄은 문서 변환 절감액, 셋째 줄은 진단 칩입니다](https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/statusline.png)
193
+ 전체 명령 목록과 Claude Code·Codex 비교, 집계 한계, 제거 방법은 [Codex 지원 범위와 제한](https://sprag.io/docs/ko/codex/)([원문](https://github.com/rootstudioyaml/sprag/blob/main/docs/CODEX.ko.md))에 있습니다. `sprag capabilities --agent codex`로 기능별 지원 여부를 확인할 수도 있습니다.
225
194
 
226
195
  ## 자주 쓰는 명령
227
196
 
@@ -240,14 +209,15 @@ Codex 기본 상태줄은 셸 명령을 실행하지 않습니다. 반복된 단
240
209
 
241
210
  | 문서 | 다루는 내용 |
242
211
  |---|---|
212
+ | [Codex](https://sprag.io/docs/ko/codex/) | 설치, 패널, 기본 훅, 자동 후보, 프리셋, 집계 한계 |
243
213
  | [설치와 기본 설정](https://github.com/rootstudioyaml/sprag/blob/main/docs/INSTALL.ko.md) | 설치 직후 켜지는 기능, 직접 켜야 하는 기능, 끄는 방법 |
244
214
  | [statusline](https://github.com/rootstudioyaml/sprag/blob/main/docs/STATUSLINE.ko.md) | 세그먼트 의미, 경고 칩, 토큰 급증 원인 코드, 업데이트 안내 |
245
215
  | [명령](https://github.com/rootstudioyaml/sprag/blob/main/docs/COMMANDS.ko.md) | 전체 서브커맨드와 CLI 옵션 |
246
216
  | [Harness](https://github.com/rootstudioyaml/sprag/blob/main/docs/HARNESS.ko.md) | 5원칙, 래칫 승격, compact-window 고정, seed 프리셋 |
247
- | [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.md) | 티어 판정, 룰 파일 구조, 스캔 시점, 서브에이전트 설정 |
217
+ | [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.ko.md) | 티어 판정, 룰 파일 구조, 스캔 시점, 서브에이전트 설정 |
248
218
  | [티어 판별 기준](https://github.com/rootstudioyaml/sprag/blob/main/docs/TIER_CRITERIA.md) | T0·T1·T2 를 가르는 근거와 연구 출처 |
249
219
  | [벤치마크](https://github.com/rootstudioyaml/sprag/blob/main/docs/BENCHMARK.md) | LLMRouterBench 11,696문항 재현 결과 |
250
- | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.md) | 변환 대상 포맷, 절감 근거, 문서 수정 절차, DRM 문서 |
220
+ | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.ko.md) | 변환 대상 포맷, 절감 근거, 문서 수정 절차, DRM 문서 |
251
221
  | [한국어 문체 지침](https://github.com/rootstudioyaml/sprag/blob/main/docs/KOREAN-STYLE.ko.md) | 주입 내용, 쓰기 시점 검사, 적용 전후 비교 |
252
222
  | [라우터가 아닌 이유](https://github.com/rootstudioyaml/sprag/blob/main/docs/NOT-A-ROUTER.ko.md) | 실시간 라우팅이 캐시를 깨서 비용을 키우는 구조 |
253
223
  | [게이트웨이와 환경](https://github.com/rootstudioyaml/sprag/blob/main/docs/GATEWAYS.ko.md) | Bedrock·Vertex·LiteLLM, 가격표, FAQ, 동작 원리 |
@@ -256,18 +226,15 @@ Codex 기본 상태줄은 셸 명령을 실행하지 않습니다. 반복된 단
256
226
  | [코덱스 토큰 사용량](https://sprag.io/docs/ko/codex-token-usage/) | 세션 범위 나누기, 네이티브 서브에이전트 위임, 코덱스 문서 변환 |
257
227
  | [도구 비교](https://sprag.io/docs/ko/compare/) | ccusage·claude-usage·CCometixLine 같은 스테이터스라인 도구와 Sprag의 차이 |
258
228
 
259
- LiteLLM 게이트웨이 환경에서는 `sprag profile-map --refresh` 가 게이트웨이의 `GET /model/info` 에서 모델 별칭을 바로 읽어 오므로, route-scan 이 위임 투표가 쌓이길 기다리거나 `profile-map.json` 을 손으로 편집할 필요가 없어졌습니다. 이 갱신은 LiteLLM 예산 게이지와 같은 5분 점검·24시간 캐시 주기를 씁니다. 인증 토큰은 저장하지 않으며, 호출 시점마다 `apiKeyHelper` 에서 읽어 그 헬퍼의 수명 그대로 만료되게 둡니다. 자세한 내용은 [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.md) 문서에 있습니다.
260
-
261
229
  ## 릴리스 노트
262
230
 
263
- 전체 내역은 [CHANGELOG.md](./CHANGELOG.md) 로 옮겼습니다. 최근 변경은 다음과 같습니다.
231
+ 최근 릴리스는 다음과 같습니다.
232
+
233
+ - **v3.67.0**: 도구를 제한한 위임 전용 서브에이전트를 설치해 게이트웨이 뒤에서도 서브에이전트 요청이 가벼워지고(LiteLLM 프록시에서 첫 요청이 46,618 토큰에서 약 20,000 토큰으로 줄었습니다), Claude 앱의 Cowork 탭에도 ratchet 룰이 적용되며, 설계와 구현을 나누는 룰을 추천 프리셋으로 제공합니다.
234
+ - **v3.66.0**: Claude 데스크톱 앱에도 sprag 상태 줄이 나오고, 암호가 걸린 문서와 DRM 문서는 읽지 않고 읽을 수 없다고 알리며, 서브에이전트가 실제로 실행된 모델을 기준으로 모델 프로필을 학습합니다.
235
+ - **v3.65.0**: 라우팅 절감액 옆에 그 위임에 든 비용이 함께 나오고(`Routing saved $15.1 · spent $9.78 since 10/9`), 이번 세션의 서브에이전트 칩에도 쓴 비용이 붙습니다.
264
236
 
265
- - **v3.54.0**: Codex용 네이티브 훅, `AGENTS.md` 하네스, 세션에 연결된 동반 패널, 문서 변환, 진단과 선택적 위임 기능을 추가했습니다. Codex 사용량은 Claude Code와 분리해 집계합니다. 자세한 내용은 [Codex 가이드](./docs/CODEX.ko.md)에 있습니다.
266
- - **v3.39.0**: `feedback` 서브커맨드가 터미널이나 Claude 세션에서 버그 제보·기능 제안을 바로 제출합니다. gh CLI가 있으면 이슈 직접 등록, 없으면 로그인 없는 익명 제출(릴레이가 이슈로 자동 등록), 오프라인이면 로컬 저장. `install`은 기존 statusline이 있으면 조용히 건너뛰지 않고 교체 여부를 묻습니다.
267
- - **v3.38.0**: `cohesion on`이 응집성 다섯 원칙을 영어 블록으로 주입합니다. 옵트인이고, korean 지침이 켜져 있으면 중복 주입을 막습니다.
268
- - **v3.37.0**: 한국어 지침에 보강 지침(supplement)이 붙습니다. 번역투·상투 패턴·응집성(문장 이어짐) 조항이며, 쓰기 시점 검사에도 번역투 5종이 추가됐습니다. 실파일 255개 실측에서 오탐 1건으로 검증했습니다.
269
- - **v3.35.0**: 이번 달 1일 00시 이후 지출 추정치를 `💵 Sep $42` 세그먼트로 상시 표시합니다. LiteLLM 게이트웨이 사용자는 키의 max_budget/spend 를 `🔑 budget` 게이지로 봅니다 (5h/7d cap 이 없는 Bedrock·LiteLLM 환경 대응).
270
- - **v3.34.0**: seed 프리셋 제안, 설치 시 출력 언어 선택, 컨텍스트 경고 500k 상향. 상세는 CHANGELOG 참고.
237
+ 모든 릴리스는 [GitHub Releases](https://github.com/rootstudioyaml/sprag/releases)에 올라와 있고, 전체 내역은 [CHANGELOG.md](https://github.com/rootstudioyaml/sprag/blob/main/CHANGELOG.md)에 있습니다.
271
238
 
272
239
  ## 피드백
273
240
 
@@ -285,7 +252,7 @@ sprag feedback "Bedrock에서 5m TTL 칩이 사라지지 않아요"
285
252
 
286
253
  ## 라이선스
287
254
 
288
- Apache License 2.0 입니다. 원문은 [LICENSE](./LICENSE) 에 있고, 그 라이선스가 요구하는 출처 표기는 [NOTICE](./NOTICE) 에 정리했습니다.
255
+ Apache License 2.0 입니다. 원문은 [LICENSE](https://github.com/rootstudioyaml/sprag/blob/main/LICENSE) 에 있고, 그 라이선스가 요구하는 출처 표기는 [NOTICE](https://github.com/rootstudioyaml/sprag/blob/main/NOTICE) 에 정리했습니다.
289
256
 
290
257
  v3.47.0 까지는 MIT 로 배포했습니다. 그 조건으로 받은 사본은 계속 MIT 를 따르고, Apache-2.0 은 그 이후 릴리스부터 적용됩니다.
291
258
 
@@ -297,6 +264,6 @@ v3.47.0 까지는 MIT 로 배포했습니다. 그 조건으로 받은 사본은
297
264
 
298
265
  [![DeepPulse YouTube](https://img.shields.io/badge/YouTube-@DeepPulseKR-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseKR)
299
266
  [![DeepPulseEN YouTube](https://img.shields.io/badge/YouTube-@DeepPulseEN-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseEN)
300
- [![Homepage](https://img.shields.io/badge/Homepage-rootstudioyaml.github.io-2ea44f)](https://rootstudioyaml.github.io/)
267
+ [![Homepage](https://img.shields.io/badge/Homepage-sprag.io-2ea44f)](https://sprag.io)
301
268
 
302
269
  AI 개발 도구를 다루는 채널 **DeepPulse**에서 만들고 씁니다. 이 도구의 배경과 사용법은 [출시 영상(60초)](https://www.youtube.com/shorts/RaD8qMsPTnA)에서 볼 수 있습니다.
package/README.md CHANGED
@@ -9,7 +9,9 @@
9
9
 
10
10
  [![npm](https://img.shields.io/npm/v/sprag-cli.svg?label=sprag-cli)](https://www.npmjs.com/package/sprag-cli)
11
11
  [![downloads](https://img.shields.io/npm/dm/sprag-cli.svg)](https://www.npmjs.com/package/sprag-cli)
12
- [![license](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](./LICENSE)
12
+ [![test](https://github.com/rootstudioyaml/sprag/actions/workflows/test.yml/badge.svg)](https://github.com/rootstudioyaml/sprag/actions/workflows/test.yml)
13
+ ![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)
14
+ [![license](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](https://github.com/rootstudioyaml/sprag/blob/main/LICENSE)
13
15
 
14
16
  [English](https://github.com/rootstudioyaml/sprag/blob/main/README.md) · [한국어](https://github.com/rootstudioyaml/sprag/blob/main/README.ko.md)
15
17
 
@@ -42,12 +44,14 @@ trouble reaches your statusline while you can still act on it.
42
44
  Session analysis runs locally on the logs Claude Code and Codex already write.
43
45
  No extra model calls or API key are needed for analysis. Optional Codex gateway
44
46
  budget and price lookups contact your configured LiteLLM endpoint using its
45
- existing credentials. The CLI requires Node.js 18+ and one TOML parser dependency.
47
+ existing credentials. The CLI requires Node.js 20+ and one TOML parser dependency.
46
48
 
47
49
  ```bash
48
50
  npm i -g sprag-cli # Claude Code, plus Codex when it is installed
49
51
  ```
50
52
 
53
+ If your package manager skips install scripts (pnpm, bun), run `sprag install` once after installing. The old `claude-token-saver` package stopped updating at 3.55.0, so move to `sprag-cli`.
54
+
51
55
  ## After you install
52
56
 
53
57
  1. Run `npm i -g sprag-cli`. Hooks, the statusline and the starter rules are set
@@ -60,10 +64,12 @@ npm i -g sprag-cli # Claude Code, plus Codex when it is installed
60
64
  needs attention:
61
65
 
62
66
  ```
63
- 🔀 Routing saved $188 | opus→sonnet 67× $93.4 · fable→sonnet 26× $83.7 · opus→haiku 25× $7.41 · fable→haiku 6× $3.73
64
- 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 🅷 5/5 · 📦 Ctx 47% of 1M
67
+ 🔀 Routing saved $188 | opus→sonnet ×67 $93.4 · fable→sonnet ×26 $83.7 · opus→haiku ×25 $7.41 · fable→haiku ×6 $3.73
68
+ 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 📦 Ctx 47% of 1M
65
69
  ```
66
70
 
71
+ ![statusline example: routing savings on row 1, document conversion savings on row 2, diagnostics on row 3](https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/statusline.png)
72
+
67
73
  Sprag never swaps the model you picked. Analysis reads your local session
68
74
  logs only, with no extra model calls and no API key. `sprag uninstall` removes
69
75
  everything it added and restores a statusline it merged with.
@@ -184,8 +190,8 @@ arrives as fewer round-trips rather than as cheaper ones.
184
190
  ## The statusline in one line
185
191
 
186
192
  ```
187
- 🔀 Routing saved $188 | opus→sonnet 67× $93.4 · fable→sonnet 26× $83.7 · opus→haiku 25× $7.41 · fable→haiku 6× $3.73
188
- 🚨 5H ▰▰▰▰▰▰▰▰▰▰▰▱ 94% 🔄 12:36 · 🅷 5/5 · 🤖 Opus 5.5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 📅 weekly ▰▰▰▰▰▱▱▱▱▱▱▱ 38% · 💵 Sep $42 · 📦 Ctx 47% of 1M
193
+ 🔀 Routing saved $188 | opus→sonnet ×67 $93.4 · fable→sonnet ×26 $83.7 · opus→haiku ×25 $7.41 · fable→haiku ×6 $3.73
194
+ 🚨 5H ▰▰▰▰▰▰▰▰▰▰▰▱ 94% 🔄 12:36 · 🤖 Opus 5.5 · 🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 📅 weekly ▰▰▰▰▰▱▱▱▱▱▱▱ 38% · 💵 Sep $42 · 📦 Ctx 47% of 1M
189
195
  ```
190
196
 
191
197
  When something is wrong the warning chip leads the line: `🚨 5H/7D NN%`, `⚠ Ctx 500k+`, `⚠ Cache miss`, `⚠ Input spike`, `⚠ Output heavy`, `⚠ Call surge`, `⚠ Rebuild churn`, `⚠ 5m TTL`. Paste the chip text into Claude and the Skill names the root-cause code and the fix. Every segment is explained in [the statusline reference](https://github.com/rootstudioyaml/sprag/blob/main/docs/STATUSLINE.md).
@@ -232,49 +238,11 @@ work types, and once each has a rule there is nothing left to promote.
232
238
 
233
239
  ## Codex
234
240
 
235
- Native hooks, an `AGENTS.md` harness, scoped ratchet rules, Korean lint,
236
- document conversion, token reports, and a session-bound companion panel.
237
- `npm i -g sprag-cli` sets Codex up too when it is installed, with the rule
238
- presets registered globally and nothing asked (`sprag install --manual` asks at
239
- each step). For a Codex-only install that leaves Claude Code alone:
241
+ Codex gets native hooks, an `AGENTS.md` harness, scoped ratchet rules, Korean lint, document conversion, token reports and a session-bound companion panel. `npm i -g sprag-cli` sets Codex up too when it is installed, with the starter rules registered globally and nothing asked (`sprag install --manual` asks at each step). For a Codex-only install that leaves Claude Code alone, run `npm i -g sprag-cli --ignore-scripts` and then `sprag install --agent codex`.
240
242
 
241
- ```bash
242
- npm i -g sprag-cli --ignore-scripts
243
- sprag install --agent codex
244
- sprag --agent codex --days 7
245
- sprag panel --agent codex # live companion in a separate terminal
246
- sprag --statusline --agent codex --text
247
- sprag doctor --agent codex # troubleshooting only
248
- sprag route-scan --agent codex
249
- sprag seed --agent codex
250
- ```
243
+ Commands default to Claude Code; add `--agent codex` to pick Codex, for example `sprag --agent codex --days 7` or `sprag panel --agent codex`. Review and trust the new hooks in Codex's `/hooks` menu, because Codex requires that step. The dollar figures in this README come from Claude Code sessions, not from a Codex benchmark.
251
244
 
252
- Review and trust the new hooks in Codex's `/hooks` menu; Codex requires that
253
- step. Commands still default to Claude Code; add `--agent codex` to pick Codex.
254
- Codex also supports session briefings, warning history, handoffs, and opt-in
255
- model routing and subagent guidance (`sprag delegate on --agent codex`). The native Codex footer
256
- does not accept shell commands; Sprag provides a terminal statusline and panel.
257
- Recurring simple turns become route candidates; repeated tool failures become
258
- ratchet candidates. Both require your approval before a rule is registered.
259
- `seed` offers compatible starter rules with separate Codex decisions.
260
- `sprag route-scan savings --agent codex` records only Sprag-routed child runs
261
- and estimates their price difference when matching LiteLLM prices are available.
262
- It is not a billing measurement. Unknown prices remain `n/a`, and Codex document
263
- counts stay separate from Claude estimates. The historical dollar figures below
264
- are from Claude Code, not a Codex benchmark. See [Codex support and limits](https://sprag.io/docs/codex/), or run
265
- `sprag capabilities --agent codex` for the support matrix.
266
-
267
- | Workflow | Claude Code | Codex |
268
- |---|---|---|
269
- | Session instructions | `CLAUDE.md` and hooks | `AGENTS.md` and native hooks |
270
- | Live telemetry | Native statusline command | Companion panel or tmux layout |
271
- | Rule candidates | Claude transcripts and tier presets | Codex rollouts and compatible ratchet presets |
272
- | Model routing | Haiku/Sonnet tier rules | Explicit provider model IDs and scoped rules |
273
- | Routing ledger | Claude run estimates | Attributed child estimates with LiteLLM prices |
274
-
275
- Both integrations coexist. Commands without `--agent codex` retain Claude behavior.
276
-
277
- ![statusline example: routing savings on row 1, document conversion savings on row 2, diagnostics on row 3](https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/statusline.png)
245
+ The full command list, the Claude Code and Codex comparison, accounting limits and removal are in [Codex support and limits](https://sprag.io/docs/codex/) ([source](https://github.com/rootstudioyaml/sprag/blob/main/docs/CODEX.md)), or run `sprag capabilities --agent codex` for the support matrix.
278
246
 
279
247
  ## Day-to-day commands
280
248
 
@@ -298,10 +266,10 @@ Every subcommand, flag and the output-language setting: [command reference](http
298
266
  | [Statusline](https://github.com/rootstudioyaml/sprag/blob/main/docs/STATUSLINE.md) | Segments, warning chips, spike issue codes, update notifications |
299
267
  | [Commands](https://github.com/rootstudioyaml/sprag/blob/main/docs/COMMANDS.md) | Full CLI surface |
300
268
  | [Harness](https://github.com/rootstudioyaml/sprag/blob/main/docs/HARNESS.md) | The five principles, rule promotion, compact-window pinning, seed presets |
301
- | [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.md) | Tier verdicts, rule-file mechanics, scan triggers, subagent setup |
269
+ | [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.md) ([한국어](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.ko.md)) | Tier verdicts, rule-file mechanics, scan triggers, subagent setup |
302
270
  | [Tier criteria](https://github.com/rootstudioyaml/sprag/blob/main/docs/TIER_CRITERIA.md) | Why T0/T1/T2 split where they do, with sources (Korean) |
303
271
  | [Benchmark](https://github.com/rootstudioyaml/sprag/blob/main/docs/BENCHMARK.md) | The 11,696-instance LLMRouterBench run |
304
- | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.md) | Supported formats, savings evidence, editing documents, DRM cases |
272
+ | [doc2md](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.md) ([한국어](https://github.com/rootstudioyaml/sprag/blob/main/docs/DOC2MD.ko.md)) | Supported formats, savings evidence, editing documents, DRM cases |
305
273
  | [Korean style](https://github.com/rootstudioyaml/sprag/blob/main/docs/KOREAN-STYLE.md) | What gets injected, the write-time check, before/after |
306
274
  | [Not a router](https://github.com/rootstudioyaml/sprag/blob/main/docs/NOT-A-ROUTER.md) | Why realtime routing breaks the cache and costs more |
307
275
  | [Gateways & environment](https://github.com/rootstudioyaml/sprag/blob/main/docs/GATEWAYS.md) | Bedrock, Vertex, LiteLLM budgets, pricing table, FAQ, how it works |
@@ -310,18 +278,15 @@ Every subcommand, flag and the output-language setting: [command reference](http
310
278
  | [Codex token usage](https://sprag.io/docs/codex-token-usage/) | Session scoping, native subagent delegation, document conversion for Codex |
311
279
  | [Compare](https://sprag.io/docs/compare/) | Sprag vs ccusage, claude-usage, and statusline tools like CCometixLine |
312
280
 
313
- Behind a LiteLLM gateway, `sprag profile-map --refresh` now reads model aliases straight from the gateway's own `GET /model/info`, so route-scan no longer needs to wait on learned votes or a hand-edited `profile-map.json`. The refresh uses the same 5-minute check and 24-hour cache as the LiteLLM budget gauge. It never stores your auth token: each refresh reads it from `apiKeyHelper` at call time and lets it expire on the helper's own schedule. Details: [route-scan](https://github.com/rootstudioyaml/sprag/blob/main/docs/ROUTE_SCAN.md).
314
-
315
281
  ## Release notes
316
282
 
317
- The full history moved to [CHANGELOG.md](./CHANGELOG.md) (Korean; version headings and command names are language-neutral). Recent changes:
283
+ Recent releases:
284
+
285
+ - **v3.67.0**: Tool-restricted delegation agents keep subagent requests light behind a gateway (the first request drops from 46,618 to about 20,000 tokens on a LiteLLM proxy), ratchet rules reach the Claude app's Cowork tab, and the design/implementation split ships as a bundled preset.
286
+ - **v3.66.0**: The Claude desktop app shows the sprag statusline, password-protected and DRM documents are refused with a clear notice, and model profiles are learned from the model a subagent actually ran on.
287
+ - **v3.65.0**: The routing headline shows what delegation cost next to what it saved (`Routing saved $15.1 · spent $9.78 since 10/9`), and the session subagent chip shows what those runs spent.
318
288
 
319
- - **v3.54.0**: Codex integration adds native hooks, `AGENTS.md` harness rules, a session-bound companion panel, document conversion, diagnostics, and opt-in delegation. Codex usage accounting stays separate from Claude Code. See the [Codex guide](./docs/CODEX.md).
320
- - **v3.39.0**: `feedback` subcommand for filing bug reports and feature requests straight from the terminal or a Claude session, via the gh CLI, an anonymous no-login form (auto-filed as a GitHub issue by an Apps Script relay), or a local fallback. `install` now asks before replacing an existing statusline instead of silently skipping.
321
- - **v3.38.0**: `cohesion on` turns the language-neutral cohesion rules from the Korean supplement into a standalone English injection (given-before-new, one referent per pronoun, subject consistency, bridging, merging choppy sentences). Opt-in, ~0.5k tokens per session, suppressed while `korean on` already carries them.
322
- - **v3.37.0**: Korean guidance grows a conservative supplement (translationese, AI-writing tics, a research-backed cohesion section whose principles apply to English prose too) and the write-time lint gains 5 translationese patterns, validated at 1 false positive across 255 real files.
323
- - **v3.35.0**: A `💵 Sep $42` segment now shows estimated spend since 00:00 on the 1st of the current month, always on, including gateway setups with no 5h/7d caps. LiteLLM gateway users get a `🔑 budget ▰▱ 34% $34/$100` gauge built from the key's budget (`GET /key/info` + `GET /user/info`, team-membership budget first, then key, then internal user, verified against a Dockerized LiteLLM).
324
- - **v3.34.0**: seed presets offered one at a time, output-language choice at install, context warning raised to 500k.
289
+ Every release is on [GitHub Releases](https://github.com/rootstudioyaml/sprag/releases), and the full history is in [CHANGELOG.md](https://github.com/rootstudioyaml/sprag/blob/main/CHANGELOG.md) (Korean; version headings and command names are language-neutral).
325
290
 
326
291
  ## Feedback
327
292
 
@@ -339,8 +304,8 @@ For a bug, please include the tool version (`sprag --version`) and your OS. For
339
304
 
340
305
  ## License
341
306
 
342
- Apache License 2.0. The full text is in [LICENSE](./LICENSE), and the attributions
343
- it requires are in [NOTICE](./NOTICE).
307
+ Apache License 2.0. The full text is in [LICENSE](https://github.com/rootstudioyaml/sprag/blob/main/LICENSE), and the attributions
308
+ it requires are in [NOTICE](https://github.com/rootstudioyaml/sprag/blob/main/NOTICE).
344
309
 
345
310
  Releases up to and including v3.47.0 went out under the MIT License. A copy
346
311
  received under those terms stays under them; the new license applies to
@@ -360,6 +325,6 @@ channels below.
360
325
 
361
326
  [![DeepPulse YouTube](https://img.shields.io/badge/YouTube-@DeepPulseKR-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseKR)
362
327
  [![DeepPulseEN YouTube](https://img.shields.io/badge/YouTube-@DeepPulseEN-FF0000?logo=youtube&logoColor=white)](https://www.youtube.com/@DeepPulseEN)
363
- [![Homepage](https://img.shields.io/badge/Homepage-rootstudioyaml.github.io-2ea44f)](https://rootstudioyaml.github.io/)
328
+ [![Homepage](https://img.shields.io/badge/Homepage-sprag.io-2ea44f)](https://sprag.io)
364
329
 
365
330
  Built at **DeepPulse**, a channel about AI developer tooling, and used there every day. The [launch Short (60s)](https://www.youtube.com/shorts/RaD8qMsPTnA) covers where it came from and how it gets used.
package/bin/cli.js CHANGED
@@ -32,6 +32,7 @@ import { dirname, isAbsolute, join } from 'node:path';
32
32
 
33
33
  import {
34
34
  readStdinJson,
35
+ readStdinRaw,
35
36
  extractCaps,
36
37
  extractContextUsage,
37
38
  extractEffort,
@@ -87,12 +88,78 @@ function tableColor() {
87
88
  }
88
89
 
89
90
  // Subcommands this build knows how to run. Used only by the guard below.
91
+ /**
92
+ * `--segments a,b,c` as a list, or null for the default order. One parser for
93
+ * the demo, the populated line and the no-session line, so `--segments harness`
94
+ * means the same chip set on each of them.
95
+ */
96
+ function segmentsFromArgs() {
97
+ const raw = getArg('--segments');
98
+ return raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : null;
99
+ }
100
+
90
101
  const KNOWN_SUBCOMMANDS = new Set([
91
102
  'last', 'brief', 'history', 'handoff', 'install', 'uninstall', 'mode', 'korean', 'cohesion',
92
103
  'doc2md', 'delegate', 'harness', 'route-scan', 'compact-window', 'update-check', 'upgrade',
93
104
  'seed', 'litellm-budget', 'profile-map', 'feedback', 'saved', 'cowork',
94
105
  ]);
95
106
 
107
+ // Flags each entry path reads, collected from the hasFlag/getArg/numArg calls in
108
+ // this file, src/statusline-mode.js, src/hook.cjs and the install/uninstall
109
+ // commands. `value` flags take an argument (`--days 3` or `--days=3`); `bool`
110
+ // flags do not. --agent is not listed: selectAgent() removes it before `args`
111
+ // exists. Only the default path (report, statusline, hook management) and
112
+ // install/uninstall are checked; every other subcommand parses its own arguments.
113
+ // Keep these in step with the code that reads the flag: a flag read but not
114
+ // listed is rejected, and a flag listed but never read is silently ignored again.
115
+ const FLAG_SPECS = {
116
+ report: {
117
+ value: ['--format', '-f', '--project', '-p', '--days', '-d', '--hours', '--demo',
118
+ '--demo-cycle-sec', '--segments', '--exclude-session', '--threshold'],
119
+ bool: ['--statusline', '--verbose', '--no-verbose', '--compact', '--no-color', '--icon',
120
+ '--no-icon', '--text', '--narrow', '--no-timer', '--single-line', '--demo-label',
121
+ '--version', '-v', '--help', '-h', '--hook', '--hook-run', '--install-hook', '--uninstall-hook'],
122
+ },
123
+ install: { value: [], bool: ['--force', '--manual', '--yes', '--no-input', '--help', '-h'] },
124
+ // --yes and --no-input are the explicit spelling of "do not ask", and
125
+ // uninstall never asks, so they are accepted as no-ops.
126
+ uninstall: { value: [], bool: ['--purge', '--yes', '--no-input', '--help', '-h'] },
127
+ };
128
+
129
+ /** Which spec applies to this invocation, or null for a subcommand that parses its own arguments. */
130
+ function flagSpecFor(first) {
131
+ if (!first || first.startsWith('-')) return FLAG_SPECS.report;
132
+ return FLAG_SPECS[first] || null;
133
+ }
134
+
135
+ /**
136
+ * The flags in `argv` that `spec` does not list. A value flag swallows the next
137
+ * token the way getArg() does, so `--exclude-session --x` reads `--x` as a path,
138
+ * not as a flag, and both `--days 3` and `--days=3` are understood. A bool flag
139
+ * written `--no-color=1` is reported too: hasFlag() compares whole tokens, so
140
+ * that form was never honoured. Everything after a bare `--` is not a flag.
141
+ *
142
+ * @returns {string[]} the offending tokens as typed
143
+ */
144
+ function findUnknownFlags(argv, spec) {
145
+ const value = new Set(spec.value);
146
+ const bool = new Set(spec.bool);
147
+ const unknown = [];
148
+ for (let i = 0; i < argv.length; i++) {
149
+ const tok = argv[i];
150
+ if (tok === '--') break;
151
+ if (tok.length < 2 || !tok.startsWith('-')) continue;
152
+ const eq = tok.indexOf('=');
153
+ const name = eq === -1 ? tok : tok.slice(0, eq);
154
+ if (value.has(name)) {
155
+ if (eq === -1) i++;
156
+ } else if (!bool.has(name) || eq !== -1) {
157
+ unknown.push(tok);
158
+ }
159
+ }
160
+ return unknown;
161
+ }
162
+
96
163
  const USAGE = `sprag — Claude Code and Codex agent harness
97
164
 
98
165
  Usage:
@@ -171,6 +238,34 @@ async function main() {
171
238
  return;
172
239
  }
173
240
 
241
+ // Refuse what this build cannot honour, before anything is scanned or written.
242
+ // An unknown first word used to fall into the 30-day report, and an unknown
243
+ // flag was dropped without a word: `sprag install --dry-run` ran the real
244
+ // install and rewrote settings.json. Exit 2 is the usage-error code.
245
+ if (args[0] && !args[0].startsWith('-') && !KNOWN_SUBCOMMANDS.has(args[0])) {
246
+ console.error(`Unknown command: ${args[0]}`);
247
+ console.error('Run `sprag --help` for the list of commands.');
248
+ process.exitCode = 2;
249
+ return;
250
+ }
251
+ const flagSpec = flagSpecFor(args[0]);
252
+ const unknownFlags = flagSpec ? findUnknownFlags(args, flagSpec) : [];
253
+ if (unknownFlags.length > 0) {
254
+ // The statusline and the PostToolUse hook run from a fixed command in
255
+ // settings.json, and a typo or a flag from a newer release there must not
256
+ // blank the line or fail the hook. Warn and carry on; every other caller is
257
+ // a person at a prompt who is better told.
258
+ const lenient = hasFlag('--statusline') || getArg('--format') === 'statusline' || hasFlag('--hook-run');
259
+ if (lenient) {
260
+ for (const f of unknownFlags) console.error(`Warning: Unknown option: ${f} (ignored)`);
261
+ } else {
262
+ for (const f of unknownFlags) console.error(`Unknown option: ${f}`);
263
+ console.error('Run `sprag --help` for usage.');
264
+ process.exitCode = 2;
265
+ return;
266
+ }
267
+ }
268
+
174
269
  // Subcommand: last — print the most recent warning + how to handle it.
175
270
  // Designed for the auto-trigger skill so the user immediately sees
176
271
  // "what just fired and how to fix it" without having to read the whole
@@ -346,56 +441,9 @@ async function main() {
346
441
  }
347
442
 
348
443
  // Subcommand: litellm-budget · LiteLLM 게이트웨이 키의 max_budget/spend 조회.
349
- // sprag litellm-budget # 캐시된 예산을 게이지·금액으로 출력
350
- // sprag litellm-budget --json # 캐시 원본 JSON 출력
351
- // sprag litellm-budget --refresh # 지금 프록시에 물어봄 (detached 자식이 사용)
444
+ // sprag litellm-budget [--json | --refresh]
352
445
  if (args[0] === 'litellm-budget') {
353
- const { gatewayBase, readBudgetState, refreshBudgetState, formatBudgetReport, resolveKey } =
354
- await import('../src/litellm-budget.js');
355
- const quiet = hasFlag('--quiet');
356
- if (hasFlag('--refresh')) {
357
- try {
358
- // apiKeyHelper 가 토큰 수명(TTL)을 소유합니다. 헬퍼를 한 번만 불러 그
359
- // 키를 예산 갱신과 게이트웨이 모델맵 갱신 양쪽에 재사용합니다. 갱신
360
- // 경로마다 resolveKey() 를 다시 부르면 사본을 만드는 셈이 되고, 헬퍼
361
- // 호출 횟수도 그만큼 늘어납니다.
362
- const key = resolveKey();
363
- let next = null;
364
- if (key) {
365
- next = await refreshBudgetState(process.env, fetch, key);
366
- const { maybeRefreshGatewayModelMap } = await import('../src/litellm-models.js');
367
- await maybeRefreshGatewayModelMap({ base: gatewayBase(), key });
368
- }
369
- if (!quiet) console.log(JSON.stringify(next, null, 2));
370
- } catch (e) {
371
- debug('litellm-budget:refresh', e);
372
- if (!quiet) console.error(`litellm-budget refresh failed: ${e.message}`);
373
- process.exitCode = 1;
374
- }
375
- return;
376
- }
377
- // 읽기 경로는 캐시만 보므로 키를 요구하지 않습니다 (apiKeyHelper 환경 배려).
378
- const base = gatewayBase();
379
- if (!base) {
380
- console.log('게이트웨이가 감지되지 않았습니다 (ANTHROPIC_BASE_URL 필요).');
381
- return;
382
- }
383
- const state = readBudgetState();
384
- if (hasFlag('--json')) {
385
- console.log(JSON.stringify(state, null, 2));
386
- return;
387
- }
388
- if (state.base !== base || state.maxBudget === undefined) {
389
- console.log('예산 캐시가 비어 있습니다. `sprag litellm-budget --refresh` 로 먼저 조회하십시오.');
390
- return;
391
- }
392
- // Same resolver the statusline uses: this report draws a gauge too, and a
393
- // glyph the terminal's font lacks garbles here exactly as it does there.
394
- const { resolveLabelMode } = await import('../src/statusline-mode.js');
395
- const { statuslineDefaults } = await import('../src/config.js');
396
- const { mode: labelMode } = resolveLabelMode({ hasFlag, cfg: statuslineDefaults() });
397
- for (const line of formatBudgetReport(state, new Date(), labelMode)) console.log(line);
398
- return;
446
+ return (await import('../src/commands/litellm-budget.js')).run({ hasFlag });
399
447
  }
400
448
 
401
449
  // Subcommand: profile-map · LiteLLM 게이트웨이의 프로파일 ID → 모델 별칭
@@ -484,10 +532,7 @@ async function main() {
484
532
  : (hasFlag('--no-verbose') || hasFlag('--compact') ? false : cfg.verbose);
485
533
  const showTimer = hasFlag('--no-timer') ? false : cfg.timer;
486
534
  const colorOk = !hasFlag('--no-color') && !process.env.NO_COLOR && cfg.color;
487
- const segmentsArg = getArg('--segments');
488
- const segments = segmentsArg
489
- ? segmentsArg.split(',').map((s) => s.trim()).filter(Boolean)
490
- : null;
535
+ const segments = segmentsFromArgs();
491
536
  const out = formatReport(data, {
492
537
  color: colorOk,
493
538
  verbose: isVerbose,
@@ -591,8 +636,8 @@ async function main() {
591
636
  const { withBaseStatusline } = await import('../src/statusline-base.js');
592
637
  console.log(withBaseStatusline(formatNoSession(
593
638
  { caps, model, effort, windowLabel, version: PKG_VERSION, update: readUpdateChip(PKG_VERSION) },
594
- { color: colorOk, mode: labelMode },
595
- )));
639
+ { color: colorOk, mode: labelMode, segments: segmentsFromArgs() },
640
+ ), { input: readStdinRaw() }));
596
641
  return;
597
642
  }
598
643
  // JSON mode stays JSON. A caller that asked for machine-readable output
@@ -768,12 +813,13 @@ async function main() {
768
813
  // 이미 파싱한 세션을 재사용하고, 사용자가 창을 줄여 둔 경우(mode 1d 등)에만
769
814
  // 월초까지 다시 파싱한다. 파싱 결과는 세션 캐시가 받아 주므로 싸다.
770
815
  let monthSpendInfo = null;
816
+ let monthSessions = null;
771
817
  if (isStatusline) {
772
818
  try {
773
819
  const { monthSpend, monthStartMs } = await import('../src/month-spend.js');
774
820
  const since = monthStartMs();
775
821
  const daysNeeded = (Date.now() - since) / 86400000;
776
- const monthSessions = days >= daysNeeded
822
+ monthSessions = days >= daysNeeded
777
823
  ? sessions
778
824
  : await parseAllSessions({ days: Math.max(1, Math.ceil(daysNeeded)), projectFilter, sinceMs: since });
779
825
  // 월 경계에 걸친 세션은 월초 이후 요청만 남긴다. 그러지 않으면 1일 자정을
@@ -784,6 +830,22 @@ async function main() {
784
830
  }
785
831
  }
786
832
 
833
+ // 모델별 사용 비중: `sprag saved` 와 같은 기간(라우팅 초기화 기록, 없으면 월초)의
834
+ // 비용 기준 분포. 이미 파싱한 월간 세션을 재사용한다. 실패하면 칩만 빠진다.
835
+ let modelShare = null;
836
+ if (isStatusline) {
837
+ try {
838
+ const { statuslineDefaults } = await import('../src/config.js');
839
+ if (statuslineDefaults().modelShare) {
840
+ const { loadModelShare } = await import('../src/model-share.js');
841
+ const { userDataDir } = await import('../src/paths.js');
842
+ modelShare = await loadModelShare({ dir: userDataDir(), env: process.env, projectFilter, monthSessions: monthSessions || undefined });
843
+ }
844
+ } catch (e) {
845
+ debug('model-share', e);
846
+ }
847
+ }
848
+
787
849
  // Rolling week/month/lifetime totals from the delegation ledger — the
788
850
  // statusline's headline line. Falls back to null (chip hidden) on any error.
789
851
  // A lookup, never a scan: the statusline re-renders every few seconds and a
@@ -880,6 +942,7 @@ async function main() {
880
942
  update: format === 'statusline' ? readUpdateChip(PKG_VERSION) : null,
881
943
  lastActivity,
882
944
  monthSpend: monthSpendInfo,
945
+ modelShare,
883
946
  spikeReport,
884
947
  contextWindow,
885
948
  ctxLive,
@@ -922,10 +985,7 @@ async function main() {
922
985
  const colorOk =
923
986
  !hasFlag('--no-color') && !process.env.NO_COLOR && cfg.color;
924
987
 
925
- const segmentsArg = getArg('--segments');
926
- const segments = segmentsArg
927
- ? segmentsArg.split(',').map((s) => s.trim()).filter(Boolean)
928
- : null;
988
+ const segments = segmentsFromArgs();
929
989
  output = formatReport(data, {
930
990
  color: colorOk,
931
991
  verbose: isVerbose,
@@ -944,7 +1004,8 @@ async function main() {
944
1004
 
945
1005
  if (format === 'statusline') {
946
1006
  const { withBaseStatusline } = await import('../src/statusline-base.js');
947
- output = withBaseStatusline(output);
1007
+ // The payload was read above; the base gets the same text, not a second read.
1008
+ output = withBaseStatusline(output, { input: readStdinRaw() });
948
1009
  }
949
1010
  console.log(output);
950
1011
  }