ultracost 0.2.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/CHANGELOG.md +81 -0
- package/LICENSE +21 -0
- package/NOTICE +18 -0
- package/README.md +306 -0
- package/bin/cli.js +264 -0
- package/docs/ESTIMATES.md +191 -0
- package/docs/PUBLISHING.md +164 -0
- package/docs/TESTING.md +260 -0
- package/docs/architecture.md +166 -0
- package/docs/policy.md +42 -0
- package/docs/ultracode.md +37 -0
- package/package.json +52 -0
- package/src/estimate.js +101 -0
- package/src/guard.js +300 -0
- package/src/index.js +7 -0
- package/src/install.js +113 -0
- package/src/log.js +18 -0
- package/src/paths.js +27 -0
- package/src/policy.js +80 -0
- package/src/pricing.js +82 -0
- package/src/rules.js +84 -0
- package/templates/hooks/reinject.mjs +41 -0
- package/templates/hooks/workflow-gate.mjs +126 -0
- package/templates/policy.default.json +49 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **The cost gate is now mode-aware and hard in every permission mode.** It reads
|
|
11
|
+
`permission_mode` from the `PreToolUse` event: it asks (with the estimate) in
|
|
12
|
+
`default`/`acceptEdits`/`auto`, and **auto-denies** an unpinned/banned/`inherit` workflow
|
|
13
|
+
in `bypassPermissions`/`dontAsk` — where an `ask` is auto-approved and wouldn't pause. A
|
|
14
|
+
`PreToolUse` `deny` is honored in every mode, so this closes the bypass gap without an env
|
|
15
|
+
var. `ULTRACOST_GATE=strict` denies on any problem in all modes; new `=ask` opts out of the
|
|
16
|
+
escalation (always ask); `=off` disables. Documented the residual upstream limitation that
|
|
17
|
+
Claude Code skips `PreToolUse` hooks for subagents dispatched under `bypassPermissions`
|
|
18
|
+
([#43772](https://github.com/anthropics/claude-code/issues/43772)).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **`pipeline(items, ...stages)` fan-out detection.** The guard and estimate now recognize
|
|
22
|
+
the Workflow API's `pipeline()` primitive: every stage's `agent()` runs once per item, so
|
|
23
|
+
those stages are counted as fan-out (like `.map`). A live test exposed that an `ultracode`
|
|
24
|
+
build/verify/fix workflow uses `pipeline()`, which the old detector counted as a few fixed
|
|
25
|
+
agents — badly under-reporting both the agent count and the cost.
|
|
26
|
+
- **The cost gate now enforces, not just estimates.** `workflow-gate.mjs` runs the static
|
|
27
|
+
guard and leads the prompt with `⚠ N/M stage(s) NOT pinned -> will inherit <session model>`
|
|
28
|
+
when any stage is unpinned/banned/`inherit`. New `ULTRACOST_GATE=strict` mode **denies**
|
|
29
|
+
such launches outright (the model must pin every stage and relaunch).
|
|
30
|
+
- **The estimate is surfaced via `systemMessage`** so it's actually visible to the user.
|
|
31
|
+
Claude Code does not render `permissionDecisionReason` for `"ask"` decisions in the TUI
|
|
32
|
+
([#24059](https://github.com/anthropics/claude-code/issues/24059)); the gate now sends the
|
|
33
|
+
numbers through the documented `systemMessage` channel (and still sets the reason).
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- **The pre-flight cost gate is now ON by default and deterministic.** The plugin
|
|
37
|
+
registers the `PreToolUse` hook on the `Workflow` tool (`hooks/hooks.json` →
|
|
38
|
+
`templates/hooks/workflow-gate.mjs`), so every dynamic-workflow launch hard-stops with a
|
|
39
|
+
cost estimate and an approve/deny prompt — no longer reliant on the model invoking
|
|
40
|
+
`AskUserQuestion`. Set `ULTRACOST_GATE=off` to disable for non-interactive runs
|
|
41
|
+
(`claude -p`, Auto Mode, CI); bypass-permissions mode auto-approves it. The gate now
|
|
42
|
+
always pauses on a `Workflow` launch (even if the script can't be read for an estimate)
|
|
43
|
+
and fails closed rather than letting an unpriced fan-out through.
|
|
44
|
+
|
|
45
|
+
## [0.2.0] - 2026-06-14
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- **`ultracost estimate <script>`** — static cost estimate for a workflow: agent count
|
|
49
|
+
(fan-outs as `N x`), model mix, and tiered-vs-all-opus cost with savings. `--json` supported.
|
|
50
|
+
- **Dynamic per-stage effort.** The policy now has Claude pick an `effort` per stage
|
|
51
|
+
(`low`..`xhigh`, bounded by model) instead of a fixed per-tier effort, and the estimate
|
|
52
|
+
factors effort into output-token cost. New `effort` block in `policy.json`.
|
|
53
|
+
- **Official-sourced pricing.** `pricing` block in `policy.json` carries `_source`/`_asOf`
|
|
54
|
+
provenance; `ultracost pricing` shows it and `ultracost pricing refresh` re-fetches
|
|
55
|
+
Anthropic's official pricing page and rewrites it. The estimate stays offline.
|
|
56
|
+
- **Pre-flight cost gate.** The injected policy + skill have Claude estimate a workflow and
|
|
57
|
+
offer Approve / Cancel / Modify via `AskUserQuestion` before launching. Ships an opt-in
|
|
58
|
+
deterministic `PreToolUse` gate (`templates/hooks/workflow-gate.mjs`) on the `Workflow` tool.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
- The `SessionStart` hook now injects the routing policy as `additionalContext` on every
|
|
62
|
+
source (`startup|resume|clear|compact`), not just `compact`. A live end-to-end test showed
|
|
63
|
+
the model-invoked skill alone was only *offered* and never opened during workflow authoring,
|
|
64
|
+
so stages stayed unpinned; injecting the policy at session start makes the same prompt pin
|
|
65
|
+
every stage with correct tiers. The skill now plays a secondary, explicit-reference role.
|
|
66
|
+
|
|
67
|
+
## [0.1.0] - 2026-06-14
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- `ultracost init` — install a quality-first routing policy, compile it into
|
|
71
|
+
`~/.claude/CLAUDE.md`, and register a `SessionStart` policy-injection hook.
|
|
72
|
+
- **Workflow Guard** (`ultracost check`) — static analysis that flags `agent()` stages
|
|
73
|
+
missing a model pin, with codes `UC001`–`UC005`, `--json`, and `--fix`.
|
|
74
|
+
- Data-driven `policy.json` with load-time validation (rejects undefined default tiers
|
|
75
|
+
and tiers whose model is in `neverUse`).
|
|
76
|
+
- `ultracost status`, `ultracost doctor`, `ultracost uninstall`.
|
|
77
|
+
- Docs: architecture, policy reference, and the ultracode rationale.
|
|
78
|
+
|
|
79
|
+
[Unreleased]: https://github.com/danielkremen818/ultracost/compare/v0.2.0...HEAD
|
|
80
|
+
[0.2.0]: https://github.com/danielkremen818/ultracost/compare/v0.1.0...v0.2.0
|
|
81
|
+
[0.1.0]: https://github.com/danielkremen818/ultracost/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Kremen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
ultracost
|
|
2
|
+
Copyright (c) 2026 Daniel Kremen
|
|
3
|
+
|
|
4
|
+
This project is an original, clean-room implementation. It was informed by
|
|
5
|
+
prior art in the Claude Code model-routing ecosystem. None of the projects
|
|
6
|
+
below contributed code to ultracost; they are credited as inspiration for the
|
|
7
|
+
problem space (routing Claude Code subagents to cost-appropriate models).
|
|
8
|
+
|
|
9
|
+
Prior art / inspiration:
|
|
10
|
+
- JacobiusMakes/claude-budget — static model-routed agents + CLAUDE.md rules + post-compact reminder
|
|
11
|
+
- 0xrdan/claude-router — automatic per-prompt routing via hooks
|
|
12
|
+
- gacabartosz/claude-smart-router — research-inspired complexity scoring
|
|
13
|
+
- R4CK/claude-model-changer — quota-aware complexity routing
|
|
14
|
+
|
|
15
|
+
ultracost's distinct contribution is per-stage routing for dynamic workflows
|
|
16
|
+
(ultracode): a quality-first policy plus a static-analysis guard that inspects
|
|
17
|
+
the workflow scripts Claude Code authors and flags subagent stages that would
|
|
18
|
+
silently inherit the session model.
|
package/README.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ultracost
|
|
4
|
+
|
|
5
|
+
**Per-stage model routing for Claude Code dynamic workflows.**
|
|
6
|
+
|
|
7
|
+
Stop a single `ultracode` fan-out from running 40 subagents on Opus 4.8 by accident.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/ultracost)
|
|
10
|
+
[](https://github.com/danielkremen818/ultracost/actions/workflows/ci.yml)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## About
|
|
19
|
+
|
|
20
|
+
**ultracost keeps Claude Code's `ultracode` mode from silently running every
|
|
21
|
+
subagent on Opus 4.8.** When `ultracode` is on, the session is pinned to Opus 4.8
|
|
22
|
+
@ `xhigh` and a single dynamic workflow fans out to dozens of subagents that
|
|
23
|
+
*inherit that model* unless every stage is pinned. ultracost makes the per-stage
|
|
24
|
+
routing explicit, injects the policy at the start of every session, and ships a
|
|
25
|
+
guard that fails any unpinned stage.
|
|
26
|
+
|
|
27
|
+
> Built for `ultracode` (Opus 4.8 @ `xhigh` dynamic workflows) — that is the only
|
|
28
|
+
> place the multi-agent fan-out it guards against happens.
|
|
29
|
+
|
|
30
|
+
No telemetry. No network on the hot path. MIT.
|
|
31
|
+
|
|
32
|
+
**Setup (plugin):**
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
/plugin marketplace add danielkremen818/ultracost
|
|
36
|
+
/plugin install ultracost@ultracost
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Built-in command:** `/ultracost:check [script]` — flag any `agent()` stage missing a model pin.
|
|
40
|
+
|
|
41
|
+
**CLI verbs:** `init · check · audit · estimate · pricing · status · doctor · uninstall`.
|
|
42
|
+
|
|
43
|
+
<div align="center">
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
## The problem
|
|
50
|
+
|
|
51
|
+
When `ultracode` is on, Claude Code runs the session on **Opus 4.8 @ xhigh** (the only model that supports `xhigh`) and auto-orchestrates **dynamic workflows** that fan out to dozens — up to 1,000 — subagents. Two defaults compound:
|
|
52
|
+
|
|
53
|
+
1. **Subagents inherit the session model.** No per-stage override → every stage is Opus 4.8.
|
|
54
|
+
2. **The built-in workflow guidance tells Claude to _omit_ the per-agent model.** So inheritance wins.
|
|
55
|
+
|
|
56
|
+
The documented result: [one prompt spawning 46 Opus subagents and ~3M tokens with no warning](https://github.com/anthropics/claude-code/issues/66023). A grep sweep and a per-file verifier do not need Opus.
|
|
57
|
+
|
|
58
|
+
## The evidence: nobody pins a stage
|
|
59
|
+
|
|
60
|
+
This is the default behavior, not user error. In a scan of ~22 real `ultracode` workflow scripts authored across `~/.claude/projects/**/workflows/scripts/`, **almost none pinned `model:` on any stage** — every stage inherited the session model (Opus 4.8 @ xhigh). Even Anthropic's own bundled `deep-research` workflow pins **zero** stages. Left to its defaults, Claude Code writes fan-outs that silently run everything on the most expensive model.
|
|
61
|
+
|
|
62
|
+
You can reproduce this on your own history in one command:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx ultracost audit ~/.claude/projects
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## What ultracost does
|
|
69
|
+
|
|
70
|
+
ultracost makes the routing **explicit, policy-driven, and verifiable** — without giving up quality on the work that matters.
|
|
71
|
+
|
|
72
|
+
- **A quality-first policy.** Coding and reasoning stay on **Opus @ xhigh**. Pre-planned mechanical work and search/collection drop to **Sonnet**. **Haiku is never used.** You own the policy in one JSON file.
|
|
73
|
+
- **Always-on routing guidance.** As a plugin, a `SessionStart` hook injects the policy as context at the start of every session (and re-injects it after compaction) — so it is present when Claude authors a workflow, without relying on the model choosing to open a skill. As the npm CLI, the same policy compiles into your `~/.claude/CLAUDE.md`. A routing skill ships alongside for explicit `/`-reference.
|
|
74
|
+
- **The Workflow Guard.** A static analyzer that scans the workflow scripts Claude authors and flags any `agent()` stage missing a `model:` pin — so a fan-out can't silently inherit Opus. Run it by hand, via `/ultracost:check`, or in CI. **No other tool does this.**
|
|
75
|
+
|
|
76
|
+
## Architecture
|
|
77
|
+
|
|
78
|
+
One shared core in `src/`, two delivery surfaces: a Claude Code **plugin** (primary) and an **npm CLI** (secondary). Both compile from the same `policy.json`.
|
|
79
|
+
|
|
80
|
+
```mermaid
|
|
81
|
+
flowchart TD
|
|
82
|
+
subgraph core["src/ — shared core"]
|
|
83
|
+
POL["policy.js<br/>(policy.json — you own it)"]
|
|
84
|
+
RUL["rules.js<br/>(rule compiler)"]
|
|
85
|
+
GRD["guard.js<br/>(static analysis)"]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
subgraph plugin["Claude Code plugin — PRIMARY"]
|
|
89
|
+
SK["skills/ultracost<br/>routing policy (always-relevant)"]
|
|
90
|
+
CMD["/ultracost:check command"]
|
|
91
|
+
HK["hooks.json<br/>SessionStart (all sources)"]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
subgraph cli["npm CLI — secondary"]
|
|
95
|
+
BIN["bin/cli.js<br/>init · check · audit · doctor · status · uninstall"]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
POL --> RUL
|
|
99
|
+
RUL --> SK
|
|
100
|
+
RUL --> BIN
|
|
101
|
+
GRD --> CMD
|
|
102
|
+
GRD --> BIN
|
|
103
|
+
HK --> RE["reinject.mjs<br/>(node, no bash/jq)"]
|
|
104
|
+
BIN --> RE
|
|
105
|
+
|
|
106
|
+
classDef ft fill:#1f6feb,stroke:#0b3d91,color:#fff;
|
|
107
|
+
class POL,RUL,GRD ft;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The plan lives in **data** (`policy.json`), not in prose buried in a prompt. The guard is the enforcement layer the model can't talk its way out of. See [`docs/architecture.md`](./docs/architecture.md) for the full picture.
|
|
111
|
+
|
|
112
|
+
## Install
|
|
113
|
+
|
|
114
|
+
### Plugin (recommended)
|
|
115
|
+
|
|
116
|
+
Inside Claude Code, add the marketplace and install the plugin:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
/plugin marketplace add danielkremen818/ultracost
|
|
120
|
+
/plugin install ultracost@ultracost
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Then verify a workflow script at any time:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
/ultracost:check ./path/to/workflow.js
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The plugin bundles four things and **touches none of your existing files**:
|
|
130
|
+
|
|
131
|
+
- a **`SessionStart`** hook that injects the routing policy as context at session start and after compaction (the always-on guidance),
|
|
132
|
+
- a **`PreToolUse` cost gate** on the `Workflow` tool that hard-stops every dynamic-workflow launch with an estimate (set `ULTRACOST_GATE=off` to disable; see [`docs/ESTIMATES.md`](./docs/ESTIMATES.md)),
|
|
133
|
+
- the **`/ultracost:check`** command (the Workflow Guard), and
|
|
134
|
+
- a **routing-policy skill** for explicit reference when Claude authors workflow/ultracode/subagent scripts.
|
|
135
|
+
|
|
136
|
+
> Requires Claude Code with the `/plugin` command (run `/help` to confirm) and dynamic workflows enabled.
|
|
137
|
+
|
|
138
|
+
### npm CLI (professional secondary)
|
|
139
|
+
|
|
140
|
+
For CI, scripting, or if you prefer the `~/.claude/CLAUDE.md` injection path:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx ultracost init
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This writes `~/.claude/ultracost/policy.json`, injects the routing block into `~/.claude/CLAUDE.md`, installs the re-inject hook (`~/.claude/ultracost/reinject.mjs`), and registers it in `~/.claude/settings.json`. New sessions pick it up immediately. Paths honor `CLAUDE_CONFIG_DIR` if you've relocated your config.
|
|
147
|
+
|
|
148
|
+
> Requires Node ≥ 24.
|
|
149
|
+
|
|
150
|
+
## Uninstall
|
|
151
|
+
|
|
152
|
+
### Plugin
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
/plugin uninstall ultracost@ultracost
|
|
156
|
+
/plugin marketplace remove ultracost
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The plugin touches none of your own files — its hook, command, and skill live inside the plugin package, so removing it removes everything; nothing is left in your `~/.claude/CLAUDE.md` or `settings.json`. If Claude Code offers to "disable in `settings.local.json`" instead (because the plugin was enabled in a shared `settings.json`), that has the same effect — accept it, or remove the marketplace as above.
|
|
160
|
+
|
|
161
|
+
### npm CLI
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
ultracost uninstall
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Reverses everything `init` did: removes the routing block from `~/.claude/CLAUDE.md`, deletes `~/.claude/ultracost/`, and unregisters the hook from `~/.claude/settings.json` (an invalid `settings.json` is reported, never overwritten).
|
|
168
|
+
|
|
169
|
+
## Quickstart (CLI)
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
ultracost init # install policy + rules + hook
|
|
173
|
+
ultracost status # see the active policy and install state
|
|
174
|
+
ultracost audit ~/.claude/projects # pin stats across your real workflow scripts
|
|
175
|
+
ultracost check ./path/to/workflow # scan a workflow script (or a directory)
|
|
176
|
+
ultracost check . --fix # auto-pin the default model on unpinned stages
|
|
177
|
+
ultracost estimate ./workflow.js # agents, model mix, and cost vs all-opus baseline
|
|
178
|
+
ultracost pricing refresh # update prices from Anthropic's official page
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Point `check` at the script Claude wrote (its path is printed when a run starts, under `~/.claude/projects/`), or wire it into CI.
|
|
182
|
+
|
|
183
|
+
## Cost estimate + dynamic effort + pre-flight gate
|
|
184
|
+
|
|
185
|
+
Beyond routing, ultracost estimates a workflow's cost before it runs, has Claude pick a per-stage **effort** level (low to xhigh), and gates the launch so you can approve, cancel, or restructure it.
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
$ ultracost estimate ./workflow.js
|
|
189
|
+
|
|
190
|
+
agents 4 fixed + 1 fan-out group(s) x ~5 = ~9
|
|
191
|
+
model mix 3x opus, 6x sonnet
|
|
192
|
+
|
|
193
|
+
baseline (all opus) $0.9000
|
|
194
|
+
tiered (ultracost) $0.5304
|
|
195
|
+
savings $0.3696 (41%)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- **Pricing is official-sourced.** Prices live in `policy.json` with a `_source` URL and `_asOf` date; `ultracost pricing refresh` re-fetches Anthropic's official pricing page and updates them. The estimate itself runs offline (no network on the hot path).
|
|
199
|
+
- **Dynamic effort.** Each stage gets the lowest effort that fits (`low`/`medium`/`high`/`xhigh`), bounded by model (`sonnet` up to `high`, `opus` up to `xhigh`). Effort feeds the estimate.
|
|
200
|
+
- **Pre-flight gate (on by default, hard in every mode).** The plugin ships a deterministic `PreToolUse` hook on the `Workflow` tool that **hard-stops every dynamic-workflow launch** — it runs the guard + estimate and leads with `⚠ N/M stage(s) NOT pinned -> will inherit Opus` when stages are unpinned, so an accidental all-Opus fan-out can't slip by. It is **mode-aware**: it asks (with the estimate) in `default`/`acceptEdits`/`auto`, and **auto-denies** an unpinned workflow in `bypassPermissions`/`dontAsk` where an ask wouldn't pause — so it holds in every permission mode, not just when the model chooses to ask. `ULTRACOST_GATE=strict` denies on any problem everywhere; `=ask` never escalates; `=off` disables it (headless/CI). On top of that, the policy has Claude run `ultracost estimate` and offer the **Approve / Cancel / Modify** menu via `AskUserQuestion`.
|
|
201
|
+
|
|
202
|
+
Estimates are relative (tiered vs all-opus), not a bill; fan-outs are ranges; the interactive 3-option menu needs a TUI. Full detail, assumptions, and the gate's [#52343](https://github.com/anthropics/claude-code/issues/52343) limitation are in [`docs/ESTIMATES.md`](./docs/ESTIMATES.md).
|
|
203
|
+
|
|
204
|
+
## How routing is decided
|
|
205
|
+
|
|
206
|
+
| Tier | Model | Use for |
|
|
207
|
+
|------|-------|---------|
|
|
208
|
+
| **opus** | `claude-opus-4-8` @ `xhigh` | writing/refactoring/debugging code, design & architecture, security/perf, tests that need judgment, planning, synthesis |
|
|
209
|
+
| **sonnet** | `claude-sonnet-4-6` @ `high` | applying a *decided* edit across files, search/grep, running tests, git ops, docs, gathering context |
|
|
210
|
+
|
|
211
|
+
**Decision rule:** if the stage must *decide how* to change code → opus. If the *how* is already planned and it just executes → sonnet. When in doubt → opus. **Never haiku.**
|
|
212
|
+
|
|
213
|
+
This is opinionated and quality-first by design. If you want a cost-first split, edit the policy (below).
|
|
214
|
+
|
|
215
|
+
## The Workflow Guard
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
$ ultracost check ./wf.js
|
|
219
|
+
wf.js:2:15 UC001 stage has no options object — add { model: ... } so it does not inherit the session model
|
|
220
|
+
wf.js:3:14 UC002 stage options object has no model — will inherit the session model
|
|
221
|
+
wf.js:4:13 UC003 stage pins banned model "haiku" (policy.neverUse)
|
|
222
|
+
|
|
223
|
+
3 error(s), 0 warning(s) in 1 file(s).
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
| Code | Meaning |
|
|
227
|
+
|------|---------|
|
|
228
|
+
| `UC001` | `agent(x)` with no options object |
|
|
229
|
+
| `UC002` | options object present, no `model` |
|
|
230
|
+
| `UC003` | model resolves to a banned model (e.g. haiku) |
|
|
231
|
+
| `UC004` | `model: 'inherit'` while `allowInherit` is false |
|
|
232
|
+
| `UC005` | options passed as a variable — can't verify (warning) |
|
|
233
|
+
|
|
234
|
+
The scanner is string- and comment-aware: an `agent(` that appears inside a prompt string or a comment is prose, not a call, and is never flagged. `--json` for CI, `--fix` to auto-insert the default model on the unambiguous cases (`UC001`/`UC002`), `--quiet` to print only the problems. Exit code is non-zero when errors are found.
|
|
235
|
+
|
|
236
|
+
## Audit your history
|
|
237
|
+
|
|
238
|
+
`ultracost audit [dir]` scans `<dir>/**/workflows/scripts/*.js` (default `~/.claude/projects`) and reports the totals — how many stages exist and how many would silently inherit the session model:
|
|
239
|
+
|
|
240
|
+
```text
|
|
241
|
+
$ ultracost audit ~/.claude/projects
|
|
242
|
+
ultracost audit
|
|
243
|
+
|
|
244
|
+
scanned 22 script(s) under ~/.claude/projects
|
|
245
|
+
|
|
246
|
+
agent() stages 137
|
|
247
|
+
pinned 4
|
|
248
|
+
unpinned 128 (UC001/UC002 — inherit the session model)
|
|
249
|
+
banned 0 (UC003)
|
|
250
|
+
inherit 1 (UC004)
|
|
251
|
+
dynamic 4 (UC005 — options is a variable)
|
|
252
|
+
|
|
253
|
+
unpinned ratio 93.4%
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
> Numbers above are illustrative; run it to see your own. `--json` emits the totals for dashboards or CI.
|
|
257
|
+
|
|
258
|
+
## Customizing the policy
|
|
259
|
+
|
|
260
|
+
Edit `~/.claude/ultracost/policy.json`, then re-run `ultracost init` to recompile the rules:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"neverUse": ["haiku"],
|
|
265
|
+
"allowInherit": false,
|
|
266
|
+
"default": "opus",
|
|
267
|
+
"tieBreaker": "opus",
|
|
268
|
+
"tiers": {
|
|
269
|
+
"opus": { "model": "opus", "effort": "xhigh" },
|
|
270
|
+
"sonnet": { "model": "sonnet", "effort": "high" }
|
|
271
|
+
},
|
|
272
|
+
"alwaysOpus": ["orchestrator", "planner", "final-synthesis"]
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
See [`docs/policy.md`](./docs/policy.md) for the full reference.
|
|
277
|
+
|
|
278
|
+
## Use in CI
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
- run: npx ultracost check . --json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Fails the build if any committed workflow script has a stage that would inherit the session model.
|
|
285
|
+
|
|
286
|
+
## How it compares
|
|
287
|
+
|
|
288
|
+
ultracost is intentionally narrow. General-purpose routers ([claude-router](https://github.com/0xrdan/claude-router), [claude-smart-router](https://github.com/gacabartosz/claude-smart-router), [claude-model-changer](https://github.com/R4CK/claude-model-changer)) score every prompt and route the *main loop*. ultracost targets the **dynamic-workflow / ultracode** path and adds a **guard** that statically verifies stage-level pins — which none of them do. See [`NOTICE`](./NOTICE) for prior-art credits.
|
|
289
|
+
|
|
290
|
+
## Documentation
|
|
291
|
+
|
|
292
|
+
- [Architecture](./docs/architecture.md)
|
|
293
|
+
- [Policy reference](./docs/policy.md)
|
|
294
|
+
- [Why ultracode needs this](./docs/ultracode.md)
|
|
295
|
+
- [Testing guide](./docs/TESTING.md) — sandbox, plugin, npm, and live Claude Code CLI checks
|
|
296
|
+
- [Publishing & recognition](./docs/PUBLISHING.md) — marketplaces, awesome lists, launch
|
|
297
|
+
|
|
298
|
+
## Versioning & releases
|
|
299
|
+
|
|
300
|
+
Semantic versioning. See [`CHANGELOG.md`](./CHANGELOG.md). Tagged releases (`vX.Y.Z`) publish to npm and GitHub Releases via CI.
|
|
301
|
+
|
|
302
|
+
> Configured for GitHub `danielkremen818/ultracost`. If you fork it, update the handle in the install commands and badges, `package.json`, `CHANGELOG.md`, and `.claude-plugin/plugin.json`. See [`docs/PUBLISHING.md`](./docs/PUBLISHING.md) for the full pre-publish checklist.
|
|
303
|
+
|
|
304
|
+
## License
|
|
305
|
+
|
|
306
|
+
MIT © Daniel Kremen. Clean-room implementation; prior art credited in [`NOTICE`](./NOTICE).
|