sprag-cli 3.56.0 → 3.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +60 -40
- package/README.md +63 -41
- package/bin/cli.js +65 -19
- package/bin/postinstall.js +22 -0
- package/docs/CODEX.ko.md +5 -1
- package/docs/CODEX.md +5 -0
- package/package.json +14 -26
- package/src/agents.js +16 -2
- package/src/brief.js +17 -8
- package/src/claude-price.js +23 -2
- package/src/codex-brief.js +35 -2
- package/src/codex-delegation.js +12 -4
- package/src/codex-harness.js +43 -9
- package/src/codex-hooks.js +24 -3
- package/src/codex-ledger.js +61 -5
- package/src/codex-panel.js +42 -22
- package/src/codex-rule-health.js +24 -1
- package/src/commands/brief.js +14 -3
- package/src/commands/codex-delegate.js +7 -4
- package/src/commands/codex.js +2 -0
- package/src/commands/harness.js +6 -0
- package/src/commands/install.js +24 -13
- package/src/compact-window.js +12 -4
- package/src/config.js +3 -3
- package/src/cost.js +5 -2
- package/src/ctx-window-cache.js +61 -0
- package/src/delegation-guard.js +15 -9
- package/src/doc2md.cjs +22 -8
- package/src/fig2md.cjs +7 -1
- package/src/formatters/statusline.js +4 -4
- package/src/formatters/table.js +18 -10
- package/src/gateway-auth.js +31 -3
- package/src/harness.js +43 -12
- package/src/history.js +6 -2
- package/src/hook-manager.js +54 -32
- package/src/installer.js +315 -158
- package/src/korean-lint.cjs +40 -7
- package/src/litellm-budget.js +10 -1
- package/src/model-rules.js +88 -41
- package/src/month-spend.js +17 -11
- package/src/parser.js +232 -56
- package/src/postinstall.js +32 -0
- package/src/prompt.js +28 -0
- package/src/route-caps.js +74 -0
- package/src/route-inject.js +31 -7
- package/src/route-scan.js +78 -26
- package/src/savings-ledger.js +33 -5
- package/src/seed-rules.js +3 -4
- package/src/session-cache.js +35 -1
- package/src/state-file.js +43 -0
- package/src/stats.js +20 -5
- package/src/statusline-base.js +37 -0
- package/src/stdin-payload.js +36 -5
package/README.ko.md
CHANGED
|
@@ -36,50 +36,25 @@ Sprag는 Claude Code와 Codex가 남기는 세션 기록을 읽어 반복 실패
|
|
|
36
36
|
npm i -g sprag-cli # 예전 이름 claude-token-saver 와 같은 패키지입니다
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## 설치하면 바로 달라지는 것
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
1. `npm i -g sprag-cli` 를 실행합니다. 훅과 상태줄, 추천 룰이 모든 프로젝트에
|
|
42
|
+
설정되므로 프로젝트마다 따로 설정할 것은 없습니다. 이미 쓰던 상태줄이 있으면
|
|
43
|
+
둘 다 표시할지, 교체할지, 그대로 둘지 설치 중에 묻습니다.
|
|
44
|
+
2. 아무 프로젝트에서나 Claude Code를 열고 평소처럼 일합니다.
|
|
45
|
+
3. 상태줄을 봅니다. 세션에서 쓴 금액과 아낀 금액이 나오고, 캐시나 컨텍스트,
|
|
46
|
+
사용량 한도에 문제가 생기면 경고 칩이 줄 맨 앞에 나타납니다.
|
|
46
47
|
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
sprag --agent codex --days 7
|
|
51
|
-
sprag panel --agent codex # 별도 터미널에서 실시간 패널 실행
|
|
52
|
-
sprag --statusline --agent codex --text
|
|
53
|
-
sprag doctor --agent codex # 문제가 생겼을 때만
|
|
54
|
-
sprag route-scan --agent codex
|
|
55
|
-
sprag seed --agent codex
|
|
48
|
+
```
|
|
49
|
+
🔀 Routing saved $44.67 | opus→sonnet 59× $39.75 · opus→haiku 10× $4.92
|
|
50
|
+
🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 🅷 5/5 · 📦 Ctx 47% of 1M
|
|
56
51
|
```
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
지침 전달을 켤 수 있습니다. 상태 표시는 Sprag 터미널 상태줄과 패널을 사용하십시오.
|
|
62
|
-
Codex 기본 상태줄은 셸 명령을 실행하지 않습니다. 반복된 단순 작업은 위임 후보로,
|
|
63
|
-
반복된 도구 실패는 래칫 후보로 제안합니다. 규칙을 등록하려면 사용자의 승인이 필요하며,
|
|
64
|
-
`seed`로 호환되는 기본 규칙을 선택할 수도 있습니다.
|
|
65
|
-
`sprag route-scan savings --agent codex`는 Sprag가 위임한 실행만 집계하고,
|
|
66
|
-
일치하는 LiteLLM 단가가 있을 때 예상 차액을 계산합니다. 청구 금액을 측정한 값은 아니며,
|
|
67
|
-
단가를 확인하지 못하면 `n/a`로 표시합니다. Codex 문서 누계는 Claude 추정치와 분리합니다.
|
|
68
|
-
아래의 기존 절감 수치는 Claude Code 기록이며 Codex 벤치마크가 아닙니다.
|
|
69
|
-
[Codex 지원 범위와 제한](https://sprag.io/docs/ko/codex/)이나
|
|
70
|
-
`sprag capabilities --agent codex`에서 기능별 지원 여부를 확인하십시오.
|
|
71
|
-
|
|
72
|
-
| 기능 | Claude Code | Codex |
|
|
73
|
-
|---|---|---|
|
|
74
|
-
| 세션 지침 | `CLAUDE.md`와 훅 | `AGENTS.md`와 기본 훅 |
|
|
75
|
-
| 실시간 상태 | 기본 상태줄에 명령 등록 | 별도 패널 또는 tmux 구성 |
|
|
76
|
-
| 규칙 후보 | Claude 기록과 모델 등급 프리셋 | Codex 기록과 호환 래칫 프리셋 |
|
|
77
|
-
| 모델 위임 | Haiku·Sonnet 등급 규칙 | 공급자 모델 ID와 범위별 규칙 |
|
|
78
|
-
| 위임 원장 | Claude 실행별 예상 차액 | LiteLLM 단가와 하위 실행별 예상 차액 |
|
|
79
|
-
|
|
80
|
-
두 연동을 함께 설치할 수 있습니다. `--agent codex`를 붙이지 않으면 기존 Claude 동작을 유지합니다.
|
|
53
|
+
Sprag는 사용자가 고른 모델을 바꾸지 않습니다. 분석은 로컬 세션 기록만 읽으며,
|
|
54
|
+
추가 모델 호출이나 API 키가 필요하지 않습니다. `sprag uninstall` 은 설치 때
|
|
55
|
+
추가한 것을 모두 지우고, 함께 표시하던 기존 상태줄을 되돌려 놓습니다.
|
|
81
56
|
|
|
82
|
-
|
|
57
|
+
Codex를 쓴다면 아래 [Codex](#codex) 절을 보십시오.
|
|
83
58
|
|
|
84
59
|
## 작동 방식
|
|
85
60
|
|
|
@@ -181,11 +156,56 @@ JetBrains IDE 내장 터미널에서는 이모지 대신 한 칸 글리프로
|
|
|
181
156
|
|
|
182
157
|
래칫 규칙과 위임 규칙은 늘어나는 방식이 다릅니다. 래칫 규칙은 사람이 저지른 실수에서 나오므로 계속 늘어납니다. 위임 규칙은 멈춥니다. 기준이 알아보는 작업 유형이 정해져 있어서, 유형마다 규칙이 하나 생기면 더 승격할 것이 없습니다.
|
|
183
158
|
|
|
159
|
+
## Codex
|
|
160
|
+
|
|
161
|
+
Codex 기본 훅과 `AGENTS.md` 하네스, 범위별 래칫 규칙,
|
|
162
|
+
한국어 검사, 프롬프트의 문서 경로 자동 변환, 로컬 토큰 보고서를 사용할 수 있습니다.
|
|
163
|
+
Codex가 설치되어 있으면 `npm i -g sprag-cli` 하나로 Codex도 설정하고, 추천 룰을 묻지 않고
|
|
164
|
+
전역으로 등록합니다(단계마다 확인하려면 `sprag install --manual`). Claude Code를 건드리지 않고
|
|
165
|
+
Codex만 쓴다면 다음과 같이 설치하십시오.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npm i -g sprag-cli --ignore-scripts
|
|
169
|
+
sprag install --agent codex
|
|
170
|
+
sprag --agent codex --days 7
|
|
171
|
+
sprag panel --agent codex # 별도 터미널에서 실시간 패널 실행
|
|
172
|
+
sprag --statusline --agent codex --text
|
|
173
|
+
sprag doctor --agent codex # 문제가 생겼을 때만
|
|
174
|
+
sprag route-scan --agent codex
|
|
175
|
+
sprag seed --agent codex
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
설치 후 Codex의 `/hooks`에서 새 훅을 검토하고 신뢰해야 실행됩니다. Codex가 요구하는
|
|
179
|
+
절차입니다. 명령의 기본 대상은 여전히 Claude Code이며, `--agent codex`를 붙이면 Codex를 고릅니다. Codex에서도 세션 브리핑, 경고 이력, 인수인계
|
|
180
|
+
파일을 지원합니다. `sprag delegate on --agent codex`로 모델 위임과 하위 에이전트
|
|
181
|
+
지침 전달을 켤 수 있습니다. 상태 표시는 Sprag 터미널 상태줄과 패널을 사용하십시오.
|
|
182
|
+
Codex 기본 상태줄은 셸 명령을 실행하지 않습니다. 반복된 단순 작업은 위임 후보로,
|
|
183
|
+
반복된 도구 실패는 래칫 후보로 제안합니다. 규칙을 등록하려면 사용자의 승인이 필요하며,
|
|
184
|
+
`seed`로 호환되는 기본 규칙을 선택할 수도 있습니다.
|
|
185
|
+
`sprag route-scan savings --agent codex`는 Sprag가 위임한 실행만 집계하고,
|
|
186
|
+
일치하는 LiteLLM 단가가 있을 때 예상 차액을 계산합니다. 청구 금액을 측정한 값은 아니며,
|
|
187
|
+
단가를 확인하지 못하면 `n/a`로 표시합니다. Codex 문서 누계는 Claude 추정치와 분리합니다.
|
|
188
|
+
아래의 기존 절감 수치는 Claude Code 기록이며 Codex 벤치마크가 아닙니다.
|
|
189
|
+
[Codex 지원 범위와 제한](https://sprag.io/docs/ko/codex/)이나
|
|
190
|
+
`sprag capabilities --agent codex`에서 기능별 지원 여부를 확인하십시오.
|
|
191
|
+
|
|
192
|
+
| 기능 | Claude Code | Codex |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| 세션 지침 | `CLAUDE.md`와 훅 | `AGENTS.md`와 기본 훅 |
|
|
195
|
+
| 실시간 상태 | 기본 상태줄에 명령 등록 | 별도 패널 또는 tmux 구성 |
|
|
196
|
+
| 규칙 후보 | Claude 기록과 모델 등급 프리셋 | Codex 기록과 호환 래칫 프리셋 |
|
|
197
|
+
| 모델 위임 | Haiku·Sonnet 등급 규칙 | 공급자 모델 ID와 범위별 규칙 |
|
|
198
|
+
| 위임 원장 | Claude 실행별 예상 차액 | LiteLLM 단가와 하위 실행별 예상 차액 |
|
|
199
|
+
|
|
200
|
+
두 연동을 함께 설치할 수 있습니다. `--agent codex`를 붙이지 않으면 기존 Claude 동작을 유지합니다.
|
|
201
|
+
|
|
202
|
+

|
|
203
|
+
|
|
184
204
|
## 자주 쓰는 명령
|
|
185
205
|
|
|
186
206
|
| 명령 | 하는 일 |
|
|
187
207
|
|---|---|
|
|
188
|
-
| `sprag` | 최근
|
|
208
|
+
| `sprag` | 최근 30일 진단 리포트 |
|
|
189
209
|
| `sprag handoff` | 한도에 막히기 전에 작업 상태를 백업 |
|
|
190
210
|
| `sprag route-scan` | 내 기록에서 위임 후보 찾기 (LLM 호출 0회) |
|
|
191
211
|
| `sprag route-scan savings` | 라우팅 절감 원장 조회 |
|
package/README.md
CHANGED
|
@@ -48,51 +48,27 @@ existing credentials. The CLI requires Node.js 18+ and one TOML parser dependenc
|
|
|
48
48
|
npm i -g sprag-cli # Claude Code, plus Codex when it is installed
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
##
|
|
51
|
+
## After you install
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
1. Run `npm i -g sprag-cli`. Hooks, the statusline and the starter rules are set
|
|
54
|
+
up for every project; there is nothing to configure per project. If you
|
|
55
|
+
already have a statusline, the install asks whether to show both, replace
|
|
56
|
+
yours, or keep yours.
|
|
57
|
+
2. Open Claude Code in any project and work as usual.
|
|
58
|
+
3. Watch the statusline. It shows what the session spent and saved, and a
|
|
59
|
+
warning chip leads the line when the cache, the context or a rate limit
|
|
60
|
+
needs attention:
|
|
58
61
|
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
sprag --agent codex --days 7
|
|
63
|
-
sprag panel --agent codex # live companion in a separate terminal
|
|
64
|
-
sprag --statusline --agent codex --text
|
|
65
|
-
sprag doctor --agent codex # troubleshooting only
|
|
66
|
-
sprag route-scan --agent codex
|
|
67
|
-
sprag seed --agent codex
|
|
62
|
+
```
|
|
63
|
+
🔀 Routing saved $44.67 | opus→sonnet 59× $39.75 · opus→haiku 10× $4.92
|
|
64
|
+
🧠 Cache hit 98.8% · ⏳ Cache expires 59:46 · 🅷 5/5 · 📦 Ctx 47% of 1M
|
|
68
65
|
```
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
model routing and subagent guidance (`sprag delegate on --agent codex`). The native Codex footer
|
|
74
|
-
does not accept shell commands; Sprag provides a terminal statusline and panel.
|
|
75
|
-
Recurring simple turns become route candidates; repeated tool failures become
|
|
76
|
-
ratchet candidates. Both require your approval before a rule is registered.
|
|
77
|
-
`seed` offers compatible starter rules with separate Codex decisions.
|
|
78
|
-
`sprag route-scan savings --agent codex` records only Sprag-routed child runs
|
|
79
|
-
and estimates their price difference when matching LiteLLM prices are available.
|
|
80
|
-
It is not a billing measurement. Unknown prices remain `n/a`, and Codex document
|
|
81
|
-
counts stay separate from Claude estimates. The historical dollar figures below
|
|
82
|
-
are from Claude Code, not a Codex benchmark. See [Codex support and limits](https://sprag.io/docs/codex/), or run
|
|
83
|
-
`sprag capabilities --agent codex` for the support matrix.
|
|
84
|
-
|
|
85
|
-
| Workflow | Claude Code | Codex |
|
|
86
|
-
|---|---|---|
|
|
87
|
-
| Session instructions | `CLAUDE.md` and hooks | `AGENTS.md` and native hooks |
|
|
88
|
-
| Live telemetry | Native statusline command | Companion panel or tmux layout |
|
|
89
|
-
| Rule candidates | Claude transcripts and tier presets | Codex rollouts and compatible ratchet presets |
|
|
90
|
-
| Model routing | Haiku/Sonnet tier rules | Explicit provider model IDs and scoped rules |
|
|
91
|
-
| Routing ledger | Claude run estimates | Attributed child estimates with LiteLLM prices |
|
|
92
|
-
|
|
93
|
-
Both integrations coexist. Commands without `--agent codex` retain Claude behavior.
|
|
67
|
+
Sprag never swaps the model you picked. Analysis reads your local session
|
|
68
|
+
logs only, with no extra model calls and no API key. `sprag uninstall` removes
|
|
69
|
+
everything it added and restores a statusline it merged with.
|
|
94
70
|
|
|
95
|
-
|
|
71
|
+
Using Codex? See [Codex](#codex) below.
|
|
96
72
|
|
|
97
73
|
## How it works
|
|
98
74
|
|
|
@@ -225,11 +201,57 @@ The two counts grow differently. Ratchet rules come from mistakes a person made,
|
|
|
225
201
|
so they keep coming. Delegation rules stop: the criteria recognize a fixed set of
|
|
226
202
|
work types, and once each has a rule there is nothing left to promote.
|
|
227
203
|
|
|
204
|
+
## Codex
|
|
205
|
+
|
|
206
|
+
Native hooks, an `AGENTS.md` harness, scoped ratchet rules, Korean lint,
|
|
207
|
+
document conversion, token reports, and a session-bound companion panel.
|
|
208
|
+
`npm i -g sprag-cli` sets Codex up too when it is installed, with the rule
|
|
209
|
+
presets registered globally and nothing asked (`sprag install --manual` asks at
|
|
210
|
+
each step). For a Codex-only install that leaves Claude Code alone:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npm i -g sprag-cli --ignore-scripts
|
|
214
|
+
sprag install --agent codex
|
|
215
|
+
sprag --agent codex --days 7
|
|
216
|
+
sprag panel --agent codex # live companion in a separate terminal
|
|
217
|
+
sprag --statusline --agent codex --text
|
|
218
|
+
sprag doctor --agent codex # troubleshooting only
|
|
219
|
+
sprag route-scan --agent codex
|
|
220
|
+
sprag seed --agent codex
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Review and trust the new hooks in Codex's `/hooks` menu; Codex requires that
|
|
224
|
+
step. Commands still default to Claude Code; add `--agent codex` to pick Codex.
|
|
225
|
+
Codex also supports session briefings, warning history, handoffs, and opt-in
|
|
226
|
+
model routing and subagent guidance (`sprag delegate on --agent codex`). The native Codex footer
|
|
227
|
+
does not accept shell commands; Sprag provides a terminal statusline and panel.
|
|
228
|
+
Recurring simple turns become route candidates; repeated tool failures become
|
|
229
|
+
ratchet candidates. Both require your approval before a rule is registered.
|
|
230
|
+
`seed` offers compatible starter rules with separate Codex decisions.
|
|
231
|
+
`sprag route-scan savings --agent codex` records only Sprag-routed child runs
|
|
232
|
+
and estimates their price difference when matching LiteLLM prices are available.
|
|
233
|
+
It is not a billing measurement. Unknown prices remain `n/a`, and Codex document
|
|
234
|
+
counts stay separate from Claude estimates. The historical dollar figures below
|
|
235
|
+
are from Claude Code, not a Codex benchmark. See [Codex support and limits](https://sprag.io/docs/codex/), or run
|
|
236
|
+
`sprag capabilities --agent codex` for the support matrix.
|
|
237
|
+
|
|
238
|
+
| Workflow | Claude Code | Codex |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| Session instructions | `CLAUDE.md` and hooks | `AGENTS.md` and native hooks |
|
|
241
|
+
| Live telemetry | Native statusline command | Companion panel or tmux layout |
|
|
242
|
+
| Rule candidates | Claude transcripts and tier presets | Codex rollouts and compatible ratchet presets |
|
|
243
|
+
| Model routing | Haiku/Sonnet tier rules | Explicit provider model IDs and scoped rules |
|
|
244
|
+
| Routing ledger | Claude run estimates | Attributed child estimates with LiteLLM prices |
|
|
245
|
+
|
|
246
|
+
Both integrations coexist. Commands without `--agent codex` retain Claude behavior.
|
|
247
|
+
|
|
248
|
+

|
|
249
|
+
|
|
228
250
|
## Day-to-day commands
|
|
229
251
|
|
|
230
252
|
| Command | What it does |
|
|
231
253
|
|---|---|
|
|
232
|
-
| `sprag` | Last-
|
|
254
|
+
| `sprag` | Last-30-day diagnostic report |
|
|
233
255
|
| `sprag handoff` | Back work up to markdown before a cap blocks you |
|
|
234
256
|
| `sprag route-scan` | Find delegation candidates in your own history (0 LLM calls) |
|
|
235
257
|
| `sprag route-scan savings` | The routing-savings ledger |
|
package/bin/cli.js
CHANGED
|
@@ -36,12 +36,13 @@ import {
|
|
|
36
36
|
extractContextUsage,
|
|
37
37
|
extractEffort,
|
|
38
38
|
extractModel,
|
|
39
|
+
extractSessionId,
|
|
39
40
|
extractTranscriptPath,
|
|
40
41
|
} from '../src/stdin-payload.js';
|
|
41
42
|
|
|
42
43
|
import { effortChipLevel } from '../src/ultracode.js';
|
|
43
44
|
|
|
44
|
-
import { parseAllSessions, getLastUserMessageTime } from '../src/parser.js';
|
|
45
|
+
import { parseAllSessions, sessionsSince, getLastUserMessageTime } from '../src/parser.js';
|
|
45
46
|
import {
|
|
46
47
|
dailyTrend,
|
|
47
48
|
ttlBreakdown,
|
|
@@ -92,6 +93,15 @@ function readUpdateChip() {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Whether the table report may use ANSI codes. The formatter looked at NO_COLOR
|
|
98
|
+
* alone, so `--no-color` did nothing there and a piped report (`sprag | less`,
|
|
99
|
+
* a redirect to a file) carried escape codes into its output.
|
|
100
|
+
*/
|
|
101
|
+
function tableColor() {
|
|
102
|
+
return !hasFlag('--no-color') && !process.env.NO_COLOR && !!process.stdout.isTTY;
|
|
103
|
+
}
|
|
104
|
+
|
|
95
105
|
// Subcommands this build knows how to run. Used only by the guard below.
|
|
96
106
|
const KNOWN_SUBCOMMANDS = new Set([
|
|
97
107
|
'last', 'brief', 'history', 'handoff', 'install', 'uninstall', 'mode', 'korean', 'cohesion',
|
|
@@ -131,6 +141,16 @@ Usage:
|
|
|
131
141
|
sprag last most recent warning + how to handle it
|
|
132
142
|
sprag history recent warning transitions
|
|
133
143
|
sprag handoff write a session handoff file
|
|
144
|
+
sprag mode [keywords] statusline preferences (icon/text, en/ko, window, ttl)
|
|
145
|
+
sprag seed register the bundled starter rules, one answer at a time
|
|
146
|
+
sprag korean on|off|status Korean writing guidance + write-time check
|
|
147
|
+
sprag cohesion on|off|status English sentence-connection guidance
|
|
148
|
+
sprag doc2md on|off|<file> convert documents to Markdown before the model reads them
|
|
149
|
+
sprag delegate on|off|status add bounds and context to Task/Agent delegation prompts
|
|
150
|
+
sprag compact-window audit or pin autoCompactWindow for 1M sessions
|
|
151
|
+
sprag litellm-budget LiteLLM gateway key budget and spend
|
|
152
|
+
sprag update-check is a newer version out?
|
|
153
|
+
sprag brief --hook internal: the UserPromptSubmit hook entry point
|
|
134
154
|
sprag feedback "<msg>" file a bug report / feature request (no browser needed)
|
|
135
155
|
sprag upgrade install the latest release
|
|
136
156
|
sprag --install-hook install cache-monitor PostToolUse hook
|
|
@@ -434,7 +454,7 @@ async function main() {
|
|
|
434
454
|
const { buildTableDemoData } = await import('../src/demo.js');
|
|
435
455
|
const { formatReport } = await import('../src/formatters/table.js');
|
|
436
456
|
const data = buildTableDemoData({ version: PKG_VERSION });
|
|
437
|
-
console.log(formatReport(data));
|
|
457
|
+
console.log(formatReport(data, { color: tableColor() }));
|
|
438
458
|
return;
|
|
439
459
|
}
|
|
440
460
|
|
|
@@ -529,7 +549,10 @@ async function main() {
|
|
|
529
549
|
const excludeSessionPath =
|
|
530
550
|
getArg('--exclude-session') || process.env.CACHE_MONITOR_EXCLUDE_SESSION || undefined;
|
|
531
551
|
|
|
532
|
-
|
|
552
|
+
// The statusline also reports this month's spend. Naming the month start here
|
|
553
|
+
// lets the one pass over the transcripts trim boundary sessions to it too.
|
|
554
|
+
const monthSince = isStatusline ? (await import('../src/month-spend.js')).monthStartMs() : undefined;
|
|
555
|
+
const sessions = await parseAllSessions({ days, projectFilter, sinceMs: monthSince });
|
|
533
556
|
|
|
534
557
|
if (sessions.length === 0) {
|
|
535
558
|
// Statusline must always emit a single line (no multi-line help spam every
|
|
@@ -552,16 +575,26 @@ async function main() {
|
|
|
552
575
|
// Resolved the same way as on the populated line below, so the chip does
|
|
553
576
|
// not change its mind about the level when the analysis window empties.
|
|
554
577
|
const effort = effortChipLevel(extractEffort(stdinJson), extractTranscriptPath(stdinJson));
|
|
578
|
+
// Recorded here too: a fresh session can render before it has any request
|
|
579
|
+
// in the analysis window, and its first prompt is briefed from this.
|
|
580
|
+
const ctxLive = extractContextUsage(stdinJson);
|
|
581
|
+
if (ctxLive?.size) {
|
|
582
|
+
try {
|
|
583
|
+
const { recordLiveWindow } = await import('../src/ctx-window-cache.js');
|
|
584
|
+
recordLiveWindow(extractSessionId(stdinJson), ctxLive.size);
|
|
585
|
+
} catch (e) { debug('ctx-window-cache:record', e); }
|
|
586
|
+
}
|
|
555
587
|
if (caps || model) {
|
|
556
588
|
try {
|
|
557
589
|
const { persistSnapshot } = await import('../src/caps-cache.js');
|
|
558
590
|
persistSnapshot({ caps, model });
|
|
559
591
|
} catch (e) { debug('caps-cache:persist', e); }
|
|
560
592
|
}
|
|
561
|
-
|
|
593
|
+
const { withBaseStatusline } = await import('../src/statusline-base.js');
|
|
594
|
+
console.log(withBaseStatusline(formatNoSession(
|
|
562
595
|
{ caps, model, effort, windowLabel, version: PKG_VERSION, update: readUpdateChip() },
|
|
563
596
|
{ color: colorOk, mode: labelMode },
|
|
564
|
-
));
|
|
597
|
+
)));
|
|
565
598
|
return;
|
|
566
599
|
}
|
|
567
600
|
// JSON mode stays JSON. A caller that asked for machine-readable output
|
|
@@ -618,6 +651,16 @@ async function main() {
|
|
|
618
651
|
? effortChipLevel(extractEffort(stdinJson), extractTranscriptPath(stdinJson))
|
|
619
652
|
: extractEffort(stdinJson);
|
|
620
653
|
const ctxLive = extractContextUsage(stdinJson);
|
|
654
|
+
// The prompt-submit briefing has no payload field for the window, so it reads
|
|
655
|
+
// back what this render was told (see src/ctx-window-cache.js).
|
|
656
|
+
if (isStatusline && ctxLive?.size) {
|
|
657
|
+
try {
|
|
658
|
+
const { recordLiveWindow } = await import('../src/ctx-window-cache.js');
|
|
659
|
+
recordLiveWindow(extractSessionId(stdinJson), ctxLive.size);
|
|
660
|
+
} catch (e) {
|
|
661
|
+
debug('ctx-window-cache:record', e);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
621
664
|
if (isStatusline && (caps || model)) {
|
|
622
665
|
try {
|
|
623
666
|
const { persistSnapshot } = await import('../src/caps-cache.js');
|
|
@@ -730,28 +773,23 @@ async function main() {
|
|
|
730
773
|
if (isStatusline) {
|
|
731
774
|
try {
|
|
732
775
|
const { monthSpend, monthStartMs } = await import('../src/month-spend.js');
|
|
733
|
-
const
|
|
776
|
+
const since = monthStartMs();
|
|
777
|
+
const daysNeeded = (Date.now() - since) / 86400000;
|
|
734
778
|
const monthSessions = days >= daysNeeded
|
|
735
779
|
? sessions
|
|
736
|
-
: await parseAllSessions({ days: Math.max(1, Math.ceil(daysNeeded)), projectFilter });
|
|
737
|
-
|
|
780
|
+
: await parseAllSessions({ days: Math.max(1, Math.ceil(daysNeeded)), projectFilter, sinceMs: since });
|
|
781
|
+
// 월 경계에 걸친 세션은 월초 이후 요청만 남긴다. 그러지 않으면 1일 자정을
|
|
782
|
+
// 넘긴 세션의 전월분이 이번 달 지출에 그대로 들어온다.
|
|
783
|
+
monthSpendInfo = monthSpend(await sessionsSince(monthSessions, since));
|
|
738
784
|
} catch (e) {
|
|
739
785
|
debug('month-spend', e);
|
|
740
786
|
}
|
|
741
787
|
}
|
|
742
788
|
|
|
743
|
-
// What delegation has measurably saved, read straight from the registry
|
|
744
|
-
// route-scan maintains. A lookup, never a scan: the statusline re-renders
|
|
745
|
-
// every few seconds and a scan parses tens of MB of transcripts.
|
|
746
|
-
let delegationSaved = 0;
|
|
747
|
-
try {
|
|
748
|
-
const { delegationSavedUsd } = await import('../src/model-rules.js');
|
|
749
|
-
delegationSaved = delegationSavedUsd();
|
|
750
|
-
} catch (e) {
|
|
751
|
-
debug('model-rules:saved', e); // an unreadable registry just hides the chip
|
|
752
|
-
}
|
|
753
789
|
// Rolling week/month/lifetime totals from the delegation ledger — the
|
|
754
790
|
// statusline's headline line. Falls back to null (chip hidden) on any error.
|
|
791
|
+
// A lookup, never a scan: the statusline re-renders every few seconds and a
|
|
792
|
+
// scan parses tens of MB of transcripts.
|
|
755
793
|
let delegationTotals = null;
|
|
756
794
|
try {
|
|
757
795
|
const { delegationSavedTotals } = await import('../src/savings-ledger.js');
|
|
@@ -759,6 +797,10 @@ async function main() {
|
|
|
759
797
|
} catch (e) {
|
|
760
798
|
debug('savings-ledger:totals', e);
|
|
761
799
|
}
|
|
800
|
+
// The single-line layout shows the same lifetime figure as the headline
|
|
801
|
+
// line. It used to sum the rule registry instead, a scan-window snapshot
|
|
802
|
+
// priced against another baseline, so one label carried two amounts.
|
|
803
|
+
const delegationSaved = Number(delegationTotals?.total) || 0;
|
|
762
804
|
// Document conversions, same shape as the delegation totals: a lifetime sum
|
|
763
805
|
// plus a document count. A lookup of a small JSON file, never a scan.
|
|
764
806
|
let doc2mdTotals = null;
|
|
@@ -857,9 +899,13 @@ async function main() {
|
|
|
857
899
|
});
|
|
858
900
|
} else {
|
|
859
901
|
const { formatReport } = await import('../src/formatters/table.js');
|
|
860
|
-
output = formatReport(data);
|
|
902
|
+
output = formatReport(data, { color: tableColor() });
|
|
861
903
|
}
|
|
862
904
|
|
|
905
|
+
if (format === 'statusline') {
|
|
906
|
+
const { withBaseStatusline } = await import('../src/statusline-base.js');
|
|
907
|
+
output = withBaseStatusline(output);
|
|
908
|
+
}
|
|
863
909
|
console.log(output);
|
|
864
910
|
}
|
|
865
911
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* npm `postinstall` entry point: runs `sprag install` for a global install and
|
|
5
|
+
* says why it did not for anything else. See src/postinstall.js.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { postinstallDecision } from '../src/postinstall.js';
|
|
12
|
+
|
|
13
|
+
const decision = postinstallDecision(process.env);
|
|
14
|
+
if (!decision.run) {
|
|
15
|
+
console.log(`sprag: setup skipped (${decision.reason}). Run \`sprag install\` to register the hooks and the statusline.`);
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
const cli = join(dirname(fileURLToPath(import.meta.url)), 'cli.js');
|
|
19
|
+
spawnSync(process.execPath, [cli, 'install'], { stdio: 'inherit' });
|
|
20
|
+
// A failed setup must not fail the package install: the CLI is still usable
|
|
21
|
+
// and `sprag install` can be run again by hand.
|
|
22
|
+
process.exit(0);
|
package/docs/CODEX.ko.md
CHANGED
|
@@ -114,7 +114,11 @@ Codex 0.159.2에서는 `spawn_agent`가 `PreToolUse`를 거치지 않습니다(
|
|
|
114
114
|
"사용자가 위임을 요청했다"는 뜻으로 읽게 합니다. 자식은 `fork_turns "none"`으로 뜨고, 부모는
|
|
115
115
|
같은 일을 직접 하지 않고 `wait_agent`(제한 시간 120초 이상)로 자식이 끝나기를 기다립니다. 귀속은
|
|
116
116
|
자식이 route id를 되받아 적는 것에 기대지 않습니다. 대신 `SubagentStart`가 대기 중인 기록(같은
|
|
117
|
-
부모 세션, 같은 목표 모델, 30분 이내)에 새로 뜬 자식을 매칭해 묶습니다.
|
|
117
|
+
부모 세션, 같은 목표 모델, 30분 이내)에 새로 뜬 자식을 매칭해 묶습니다. 어떤 자식도 가져가지
|
|
118
|
+
않은 안내는 부모 세션에 다음 프롬프트가 들어올 때 닫히므로, 나중에 다른 이유로 뜬 자식이 그
|
|
119
|
+
안내의 절감으로 잡히지 않습니다. 같은 프롬프트 시점에 아직 장부에 반영되지 않은 위임 실행도
|
|
120
|
+
별도 프로세스로 계산하므로, `Routing saved`는 다음 route-scan을 기다리지 않고 위임 뒤 첫
|
|
121
|
+
프롬프트에서 갱신됩니다.
|
|
118
122
|
|
|
119
123
|
이 안내는 부모 세션의 컨텍스트가 `codex.delegateMinContext`(기본 60000 토큰) 아래면 붙지
|
|
120
124
|
않습니다. 2026-09-30 실측에서 작은 세션은 스폰·대기·검증이라는 조율 비용이 자식이 아끼는
|
package/docs/CODEX.md
CHANGED
|
@@ -166,6 +166,11 @@ with `fork_turns "none"`, then wait for it (`wait_agent` with a timeout of at le
|
|
|
166
166
|
route id back: `SubagentStart` binds the id to whichever spawned child matches
|
|
167
167
|
the pending route (same parent thread, same target model, within 30 minutes),
|
|
168
168
|
so a route is credited even when the child's own first message never repeats it.
|
|
169
|
+
A hint that no child claimed is closed when the parent's next prompt arrives, so
|
|
170
|
+
a child spawned later for some other reason is not credited to it. That same
|
|
171
|
+
prompt also prices any delegated run the ledger has not joined yet, in a
|
|
172
|
+
detached process, so `Routing saved` follows a delegation by one prompt instead
|
|
173
|
+
of waiting for the next route scan.
|
|
169
174
|
|
|
170
175
|
The hint is withheld below `codex.delegateMinContext` (default 60000 input
|
|
171
176
|
tokens, read from the parent's own rollout). Measured 2026-09-30: a small
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprag-cli",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.58.0",
|
|
4
|
+
"description": "Lower your Claude Code bill without changing how you work: simple tasks go to cheaper models, documents go in as compact Markdown, and the statusline shows spend and savings live. Works with Codex too.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"sprag": "bin/cli.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"deploy": "node scripts/deploy.mjs",
|
|
12
12
|
"docs:statusline": "node scripts/docs-statusline.mjs",
|
|
13
|
-
"postinstall": "node bin/
|
|
13
|
+
"postinstall": "node bin/postinstall.js || true",
|
|
14
14
|
"publish:legacy": "node scripts/publish-legacy.mjs",
|
|
15
15
|
"release:notes": "node scripts/release-notes.mjs",
|
|
16
16
|
"site:docs": "node scripts/build-site-docs.mjs",
|
|
@@ -33,34 +33,22 @@
|
|
|
33
33
|
"node": ">=18"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
|
-
"sprag",
|
|
37
|
-
"ratchet",
|
|
38
|
-
"harness",
|
|
39
|
-
"ai-agent",
|
|
40
|
-
"claude",
|
|
41
36
|
"claude-code",
|
|
42
|
-
"
|
|
43
|
-
"openai-codex",
|
|
44
|
-
"agents-md",
|
|
37
|
+
"claude",
|
|
45
38
|
"anthropic",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"delegation",
|
|
49
|
-
"subagent",
|
|
50
|
-
"haiku",
|
|
51
|
-
"cost-savings",
|
|
39
|
+
"codex",
|
|
40
|
+
"token-usage",
|
|
52
41
|
"token-saver",
|
|
53
42
|
"reduce-tokens",
|
|
54
|
-
"
|
|
55
|
-
"prompt-caching",
|
|
56
|
-
"cache",
|
|
57
|
-
"statusline",
|
|
58
|
-
"cli",
|
|
59
|
-
"1m-context",
|
|
60
|
-
"claude-code-statusline",
|
|
43
|
+
"cost-savings",
|
|
61
44
|
"claude-code-cost",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
45
|
+
"claude-code-statusline",
|
|
46
|
+
"statusline",
|
|
47
|
+
"model-routing",
|
|
48
|
+
"subagent",
|
|
49
|
+
"prompt-caching",
|
|
50
|
+
"haiku",
|
|
51
|
+
"cli"
|
|
64
52
|
],
|
|
65
53
|
"license": "Apache-2.0",
|
|
66
54
|
"repository": {
|
package/src/agents.js
CHANGED
|
@@ -20,12 +20,26 @@ const AGENT_MODEL = {
|
|
|
20
20
|
'haiku-runner': 'haiku',
|
|
21
21
|
'haiku-translate': 'haiku',
|
|
22
22
|
'sonnet-worker': 'sonnet',
|
|
23
|
+
// A T1 rule names its target by tier alone (`agent: 'sonnet'`), with no
|
|
24
|
+
// preset agent behind it. Without these the phrase fell through to the
|
|
25
|
+
// haiku default, and a sonnet rule told the session to delegate to haiku.
|
|
26
|
+
sonnet: 'sonnet',
|
|
27
|
+
haiku: 'haiku',
|
|
23
28
|
};
|
|
24
29
|
|
|
25
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* True when `<name>.md` exists in the project or user agents directory.
|
|
32
|
+
*
|
|
33
|
+
* `root: null` asks about the user directory alone. Text that every project
|
|
34
|
+
* reads (the global ratchet-model.md) has to be worded from what every project
|
|
35
|
+
* can see: checked against the current directory, the same rule read
|
|
36
|
+
* `haiku-explore(model: haiku)` when the scan ran in a project that defines
|
|
37
|
+
* the agent and `model: haiku` when it ran anywhere else, and each flip
|
|
38
|
+
* rewrote a file sitting in the prompt-cache prefix of every session.
|
|
39
|
+
*/
|
|
26
40
|
export function agentExists(name, root = process.cwd()) {
|
|
27
41
|
if (!name) return false;
|
|
28
|
-
return existsSync(join(root, '.claude', 'agents', `${name}.md`)) ||
|
|
42
|
+
return (root !== null && existsSync(join(root, '.claude', 'agents', `${name}.md`))) ||
|
|
29
43
|
existsSync(join(claudeUserDir(), 'agents', `${name}.md`));
|
|
30
44
|
}
|
|
31
45
|
|