claude-finops 0.7.2 → 0.9.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.md +12 -47
- package/config/pricing.json +166 -24
- package/config/settings.json +11 -5
- package/finops/actions.py +1 -1
- package/finops/agents.py +2 -1
- package/finops/analytics.py +721 -611
- package/finops/api.py +179 -55
- package/finops/diagnose.py +17 -20
- package/finops/etl.py +169 -57
- package/finops/integrate.py +22 -57
- package/finops/paths.py +12 -0
- package/finops/plan_history.py +80 -0
- package/finops/pricing.py +57 -6
- package/finops/procs.py +7 -5
- package/finops/report.py +29 -23
- package/finops/segments.py +149 -0
- package/package.json +1 -1
- package/run.cmd +3 -2
- package/run.py +26 -59
- package/web/app.js +297 -333
- package/web/charts.js +60 -16
- package/finops/advisor.py +0 -188
- package/finops/trial.py +0 -169
package/README.md
CHANGED
|
@@ -8,8 +8,10 @@ It answers, in a few clicks:
|
|
|
8
8
|
> **What I used → what it cost → why it cost that much → whether it was efficient →
|
|
9
9
|
> what is likely to happen next → and what I should change.**
|
|
10
10
|
|
|
11
|
-
Everything runs on `127.0.0.1` with the Python standard library. No dependencies,
|
|
12
|
-
|
|
11
|
+
Everything runs on `127.0.0.1` with the Python standard library. No dependencies, and
|
|
12
|
+
no data leaves the machine. No outbound calls except a once-a-day version check
|
|
13
|
+
against the npm registry (disable with `--no-update-check`), and provider APIs only
|
|
14
|
+
if you add a key.
|
|
13
15
|
|
|
14
16
|
```bash
|
|
15
17
|
npx claude-finops
|
|
@@ -97,9 +99,10 @@ set.
|
|
|
97
99
|
- **Nothing to learn.** Every screen states its own conclusion in plain English before
|
|
98
100
|
it shows you a chart, and every number carries a badge saying whether it is measured
|
|
99
101
|
or estimated.
|
|
100
|
-
- **Your data stays put.** It binds to `127.0.0.1
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
- **Your data stays put.** It binds to `127.0.0.1`. No outbound calls except a
|
|
103
|
+
once-a-day version check against the npm registry (disable with
|
|
104
|
+
`--no-update-check`), and provider APIs only if you add a key. The warehouse lives
|
|
105
|
+
in `~/.claude-finops`, so upgrading or deleting the package never touches it.
|
|
103
106
|
- **It tells you what to change**, not just what happened — usually with a prompt you
|
|
104
107
|
can paste into Claude Code.
|
|
105
108
|
|
|
@@ -115,7 +118,7 @@ carries one of four badges, and nothing is invented.
|
|
|
115
118
|
| **Actual** | Read straight out of your transcripts: token counts, timestamps, models, effort, tool calls, file paths, session and project identity. |
|
|
116
119
|
| **Estimated** | Derived. **Every dollar figure is estimated**, because Claude Code transcripts contain token counts but no billed amount. Cost = tokens × the price table in `config/pricing.json`. |
|
|
117
120
|
| **Forecast** | Projected from your history. Assumes the recent pattern continues. |
|
|
118
|
-
| **Recommendation** | A
|
|
121
|
+
| **Recommendation** | A recommendation grounded in an observed share of spend. No saving is estimated. |
|
|
119
122
|
|
|
120
123
|
### Deliberately not fabricated
|
|
121
124
|
|
|
@@ -173,8 +176,9 @@ Two files, both editable without touching code. The server picks up changes to
|
|
|
173
176
|
### `config/pricing.json`
|
|
174
177
|
|
|
175
178
|
Model prices per million tokens, kept strictly separate from usage data so the table
|
|
176
|
-
can be updated as prices change. A model with no entry
|
|
177
|
-
`
|
|
179
|
+
can be updated as prices change. A `claude-*` model with no entry is priced as
|
|
180
|
+
`unpriced` ($0, flagged as such in the Model analysis view) rather than silently
|
|
181
|
+
billed at another model's rate.
|
|
178
182
|
|
|
179
183
|
### `config/settings.json`
|
|
180
184
|
|
|
@@ -287,45 +291,6 @@ place for you to delete once you are happy.
|
|
|
287
291
|
|
|
288
292
|
---
|
|
289
293
|
|
|
290
|
-
## Running the trial, not just recommending one
|
|
291
|
-
|
|
292
|
-
The evidence table ends by admitting its own limit: your prompts were never
|
|
293
|
-
randomly assigned to models, so a category can simply have been easier on one of
|
|
294
|
-
them. **Try it →** on any row closes that gap. It pulls prompts you actually
|
|
295
|
-
typed in that category, re-runs them headlessly on the candidate model, and
|
|
296
|
-
prices the result against what they cost the first time.
|
|
297
|
-
|
|
298
|
-
Nothing runs on its own: a trial spends real money and drives a real agent, so
|
|
299
|
-
it takes two clicks and shows the first-time bill before you commit. Runs happen
|
|
300
|
-
in a scratch directory, and headless Claude cannot ask for permission — so tools
|
|
301
|
-
that need it are denied and counted, and a task that needs your repo will look
|
|
302
|
-
smaller than it is. The verdict says which way it went: *confirmed*, *smaller
|
|
303
|
-
than advertised*, or *history overstated it*.
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## Live model advice
|
|
308
|
-
|
|
309
|
-
The back-test tells you what to use next time. These tell you mid-session, while
|
|
310
|
-
you can still act on it:
|
|
311
|
-
|
|
312
|
-
```
|
|
313
|
-
claude-finops --advise what every running session should switch to
|
|
314
|
-
claude-finops --install-hook suggest a cheaper model as you send each prompt
|
|
315
|
-
claude-finops --install-statusline model, context pressure and advice in your statusline
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
The hook and statusline read the same evidence as the dashboard, cached for an
|
|
319
|
-
hour, and stay quiet unless your own history shows a cheaper model doing that
|
|
320
|
-
category of work without taking more turns. Neither can block or slow a prompt:
|
|
321
|
-
they fail silent and always exit 0. Undo with `--uninstall-hook` /
|
|
322
|
-
`--uninstall-statusline`; your `~/.claude/settings.json` is backed up first.
|
|
323
|
-
|
|
324
|
-
The Running sessions view shows the same line per live session, with the
|
|
325
|
-
`/model` command ready to copy.
|
|
326
|
-
|
|
327
|
-
---
|
|
328
|
-
|
|
329
294
|
## Privacy
|
|
330
295
|
|
|
331
296
|
`~/.claude-finops/data/finops.db` and the prompt/CSV exports contain **your full prompt text**. The
|
package/config/pricing.json
CHANGED
|
@@ -2,19 +2,147 @@
|
|
|
2
2
|
"_comment": "Model pricing in USD per 1,000,000 tokens. Kept SEPARATE from usage data so it can be updated without touching the dashboard. Any cost derived from these numbers is ESTIMATED, never actual billed cost.",
|
|
3
3
|
"currency": "USD",
|
|
4
4
|
"unit": "per_million_tokens",
|
|
5
|
-
"updated": "2026-09-
|
|
6
|
-
"source": "
|
|
7
|
-
"default_model_pricing": {
|
|
8
|
-
"input": 3.0,
|
|
9
|
-
"output": 15.0,
|
|
10
|
-
"cache_write_5m": 3.75,
|
|
11
|
-
"cache_write_1h": 6.0,
|
|
12
|
-
"cache_read": 0.3
|
|
13
|
-
},
|
|
5
|
+
"updated": "2026-09-28",
|
|
6
|
+
"source": "Anthropic list prices (platform.claude.com/docs/en/about-claude/pricing, fetched 2026-09-28)",
|
|
14
7
|
"models": {
|
|
15
8
|
"claude-opus-5": {
|
|
16
9
|
"display_name": "Claude Opus 5",
|
|
17
10
|
"tier": "frontier",
|
|
11
|
+
"input": 5.0,
|
|
12
|
+
"output": 25.0,
|
|
13
|
+
"cache_write_5m": 6.25,
|
|
14
|
+
"cache_write_1h": 10.0,
|
|
15
|
+
"cache_read": 0.5,
|
|
16
|
+
"context_window": 1000000,
|
|
17
|
+
"provider": "anthropic"
|
|
18
|
+
},
|
|
19
|
+
"claude-opus-5[fast]": {
|
|
20
|
+
"display_name": "Claude Opus 5 (fast mode)",
|
|
21
|
+
"tier": "frontier",
|
|
22
|
+
"input": 10.0,
|
|
23
|
+
"output": 50.0,
|
|
24
|
+
"cache_write_5m": 12.5,
|
|
25
|
+
"cache_write_1h": 20.0,
|
|
26
|
+
"cache_read": 1.0,
|
|
27
|
+
"context_window": 1000000,
|
|
28
|
+
"provider": "anthropic",
|
|
29
|
+
"_note": "Research-preview fast mode, 2x standard. Selected when usage.speed == 'fast'."
|
|
30
|
+
},
|
|
31
|
+
"claude-opus-5-5": {
|
|
32
|
+
"display_name": "Claude Opus 5.5",
|
|
33
|
+
"tier": "frontier",
|
|
34
|
+
"input": 4.0,
|
|
35
|
+
"output": 20.0,
|
|
36
|
+
"cache_write_5m": 5.0,
|
|
37
|
+
"cache_write_1h": 8.0,
|
|
38
|
+
"cache_read": 0.20,
|
|
39
|
+
"context_window": 1000000,
|
|
40
|
+
"provider": "anthropic"
|
|
41
|
+
},
|
|
42
|
+
"claude-opus-5-5[fast]": {
|
|
43
|
+
"display_name": "Claude Opus 5.5 (fast mode)",
|
|
44
|
+
"tier": "frontier",
|
|
45
|
+
"input": 8.0,
|
|
46
|
+
"output": 40.0,
|
|
47
|
+
"cache_write_5m": 10.0,
|
|
48
|
+
"cache_write_1h": 16.0,
|
|
49
|
+
"cache_read": 0.40,
|
|
50
|
+
"context_window": 1000000,
|
|
51
|
+
"provider": "anthropic",
|
|
52
|
+
"_note": "Research-preview fast mode, 2x standard. Selected when usage.speed == 'fast'."
|
|
53
|
+
},
|
|
54
|
+
"claude-sonnet-5": {
|
|
55
|
+
"display_name": "Claude Sonnet 5",
|
|
56
|
+
"tier": "balanced",
|
|
57
|
+
"input": 2.0,
|
|
58
|
+
"output": 10.0,
|
|
59
|
+
"cache_write_5m": 2.5,
|
|
60
|
+
"cache_write_1h": 4.0,
|
|
61
|
+
"cache_read": 0.2,
|
|
62
|
+
"context_window": 1000000,
|
|
63
|
+
"provider": "anthropic"
|
|
64
|
+
},
|
|
65
|
+
"claude-fable-5": {
|
|
66
|
+
"display_name": "Claude Fable 5",
|
|
67
|
+
"tier": "frontier",
|
|
68
|
+
"input": 10.0,
|
|
69
|
+
"output": 50.0,
|
|
70
|
+
"cache_write_5m": 12.5,
|
|
71
|
+
"cache_write_1h": 20.0,
|
|
72
|
+
"cache_read": 1.0,
|
|
73
|
+
"context_window": 1000000,
|
|
74
|
+
"provider": "anthropic"
|
|
75
|
+
},
|
|
76
|
+
"claude-fable-5-1": {
|
|
77
|
+
"display_name": "Claude Fable 5.1",
|
|
78
|
+
"tier": "frontier",
|
|
79
|
+
"input": 10.0,
|
|
80
|
+
"output": 50.0,
|
|
81
|
+
"cache_write_5m": 12.5,
|
|
82
|
+
"cache_write_1h": 20.0,
|
|
83
|
+
"cache_read": 0.25,
|
|
84
|
+
"context_window": 1000000,
|
|
85
|
+
"provider": "anthropic"
|
|
86
|
+
},
|
|
87
|
+
"claude-opus-4-8": {
|
|
88
|
+
"display_name": "Claude Opus 4.8",
|
|
89
|
+
"tier": "frontier",
|
|
90
|
+
"input": 5.0,
|
|
91
|
+
"output": 25.0,
|
|
92
|
+
"cache_write_5m": 6.25,
|
|
93
|
+
"cache_write_1h": 10.0,
|
|
94
|
+
"cache_read": 0.5,
|
|
95
|
+
"context_window": 1000000,
|
|
96
|
+
"provider": "anthropic"
|
|
97
|
+
},
|
|
98
|
+
"claude-opus-4-8[fast]": {
|
|
99
|
+
"display_name": "Claude Opus 4.8 (fast mode)",
|
|
100
|
+
"tier": "frontier",
|
|
101
|
+
"input": 10.0,
|
|
102
|
+
"output": 50.0,
|
|
103
|
+
"cache_write_5m": 12.5,
|
|
104
|
+
"cache_write_1h": 20.0,
|
|
105
|
+
"cache_read": 1.0,
|
|
106
|
+
"context_window": 1000000,
|
|
107
|
+
"provider": "anthropic",
|
|
108
|
+
"_note": "Research-preview fast mode, 2x standard. Selected when usage.speed == 'fast'."
|
|
109
|
+
},
|
|
110
|
+
"claude-opus-4-7": {
|
|
111
|
+
"display_name": "Claude Opus 4.7",
|
|
112
|
+
"tier": "frontier",
|
|
113
|
+
"input": 5.0,
|
|
114
|
+
"output": 25.0,
|
|
115
|
+
"cache_write_5m": 6.25,
|
|
116
|
+
"cache_write_1h": 10.0,
|
|
117
|
+
"cache_read": 0.5,
|
|
118
|
+
"context_window": 1000000,
|
|
119
|
+
"provider": "anthropic"
|
|
120
|
+
},
|
|
121
|
+
"claude-opus-4-6": {
|
|
122
|
+
"display_name": "Claude Opus 4.6",
|
|
123
|
+
"tier": "frontier",
|
|
124
|
+
"input": 5.0,
|
|
125
|
+
"output": 25.0,
|
|
126
|
+
"cache_write_5m": 6.25,
|
|
127
|
+
"cache_write_1h": 10.0,
|
|
128
|
+
"cache_read": 0.5,
|
|
129
|
+
"context_window": 1000000,
|
|
130
|
+
"provider": "anthropic"
|
|
131
|
+
},
|
|
132
|
+
"claude-opus-4-5": {
|
|
133
|
+
"display_name": "Claude Opus 4.5",
|
|
134
|
+
"tier": "frontier",
|
|
135
|
+
"input": 5.0,
|
|
136
|
+
"output": 25.0,
|
|
137
|
+
"cache_write_5m": 6.25,
|
|
138
|
+
"cache_write_1h": 10.0,
|
|
139
|
+
"cache_read": 0.5,
|
|
140
|
+
"context_window": 200000,
|
|
141
|
+
"provider": "anthropic"
|
|
142
|
+
},
|
|
143
|
+
"claude-opus-4-1": {
|
|
144
|
+
"display_name": "Claude Opus 4.1",
|
|
145
|
+
"tier": "frontier",
|
|
18
146
|
"input": 15.0,
|
|
19
147
|
"output": 75.0,
|
|
20
148
|
"cache_write_5m": 18.75,
|
|
@@ -23,30 +151,30 @@
|
|
|
23
151
|
"context_window": 200000,
|
|
24
152
|
"provider": "anthropic"
|
|
25
153
|
},
|
|
26
|
-
"claude-opus-
|
|
27
|
-
"display_name": "Claude Opus
|
|
154
|
+
"claude-opus-4": {
|
|
155
|
+
"display_name": "Claude Opus 4",
|
|
28
156
|
"tier": "frontier",
|
|
29
|
-
"input":
|
|
30
|
-
"output":
|
|
31
|
-
"cache_write_5m":
|
|
32
|
-
"cache_write_1h":
|
|
33
|
-
"cache_read":
|
|
34
|
-
"context_window":
|
|
157
|
+
"input": 15.0,
|
|
158
|
+
"output": 75.0,
|
|
159
|
+
"cache_write_5m": 18.75,
|
|
160
|
+
"cache_write_1h": 30.0,
|
|
161
|
+
"cache_read": 1.5,
|
|
162
|
+
"context_window": 200000,
|
|
35
163
|
"provider": "anthropic"
|
|
36
164
|
},
|
|
37
|
-
"claude-sonnet-
|
|
38
|
-
"display_name": "Claude Sonnet
|
|
165
|
+
"claude-sonnet-4-6": {
|
|
166
|
+
"display_name": "Claude Sonnet 4.6",
|
|
39
167
|
"tier": "balanced",
|
|
40
168
|
"input": 3.0,
|
|
41
169
|
"output": 15.0,
|
|
42
170
|
"cache_write_5m": 3.75,
|
|
43
171
|
"cache_write_1h": 6.0,
|
|
44
172
|
"cache_read": 0.3,
|
|
45
|
-
"context_window":
|
|
173
|
+
"context_window": 1000000,
|
|
46
174
|
"provider": "anthropic"
|
|
47
175
|
},
|
|
48
|
-
"claude-
|
|
49
|
-
"display_name": "Claude
|
|
176
|
+
"claude-sonnet-4-5": {
|
|
177
|
+
"display_name": "Claude Sonnet 4.5",
|
|
50
178
|
"tier": "balanced",
|
|
51
179
|
"input": 3.0,
|
|
52
180
|
"output": 15.0,
|
|
@@ -56,8 +184,8 @@
|
|
|
56
184
|
"context_window": 200000,
|
|
57
185
|
"provider": "anthropic"
|
|
58
186
|
},
|
|
59
|
-
"claude-
|
|
60
|
-
"display_name": "Claude
|
|
187
|
+
"claude-sonnet-4": {
|
|
188
|
+
"display_name": "Claude Sonnet 4",
|
|
61
189
|
"tier": "balanced",
|
|
62
190
|
"input": 3.0,
|
|
63
191
|
"output": 15.0,
|
|
@@ -67,6 +195,17 @@
|
|
|
67
195
|
"context_window": 200000,
|
|
68
196
|
"provider": "anthropic"
|
|
69
197
|
},
|
|
198
|
+
"claude-3-5-haiku": {
|
|
199
|
+
"display_name": "Claude Haiku 3.5",
|
|
200
|
+
"tier": "economy",
|
|
201
|
+
"input": 0.8,
|
|
202
|
+
"output": 4.0,
|
|
203
|
+
"cache_write_5m": 1.0,
|
|
204
|
+
"cache_write_1h": 1.6,
|
|
205
|
+
"cache_read": 0.08,
|
|
206
|
+
"context_window": 200000,
|
|
207
|
+
"provider": "anthropic"
|
|
208
|
+
},
|
|
70
209
|
"claude-haiku-4-5-20251001": {
|
|
71
210
|
"display_name": "Claude Haiku 4.5",
|
|
72
211
|
"tier": "economy",
|
|
@@ -225,6 +364,9 @@
|
|
|
225
364
|
"_note": "Cursor bills by subscription and doesn't record the model locally: no cost is estimated"
|
|
226
365
|
}
|
|
227
366
|
},
|
|
367
|
+
"aliases": {
|
|
368
|
+
"claude-haiku-4-5": "claude-haiku-4-5-20251001"
|
|
369
|
+
},
|
|
228
370
|
"_sources": {
|
|
229
371
|
"openai": "https://developers.openai.com/api/docs/pricing (standard tier, 2026-09-18)",
|
|
230
372
|
"google": "https://ai.google.dev/gemini-api/docs/pricing (paid tier, 2026-09-18)"
|
package/config/settings.json
CHANGED
|
@@ -41,17 +41,23 @@
|
|
|
41
41
|
"frontier"
|
|
42
42
|
],
|
|
43
43
|
"low_output_ratio_vs_median": 0.4,
|
|
44
|
+
"tool_loop_calls": 40,
|
|
45
|
+
"poor_cache_min_writes": 500000,
|
|
44
46
|
"_low_output_note": "Sessions are flagged when their output/billable ratio falls below this fraction of YOUR median session ratio, rather than an absolute number, because a healthy ratio depends on how agentic your workload is."
|
|
45
47
|
},
|
|
48
|
+
"hygiene": {
|
|
49
|
+
"_comment": "Context sizes (prompt-side tokens) the hygiene view reports spend above. Observed shares only; no saving is estimated from them.",
|
|
50
|
+
"context_thresholds": [100000, 150000]
|
|
51
|
+
},
|
|
46
52
|
"anomaly": {
|
|
53
|
+
"_comment": "session_ratio is measured against the MEDIAN session, not the mean: session sizes are right-skewed enough that a mean lets the outliers inflate their own baseline. Session token counts are heavy-tailed, so max_session_outliers, not the ratio, is what keeps the list short and leaves room for the other anomaly types. daily_robust_z is a threshold on a median/MAD robust z-score (MAD scaled by 1.4826 to estimate a stdev), not the plain daily_zscore: unpriced $0 days from agents with no pricing data would otherwise pollute a mean/stdev baseline.",
|
|
47
54
|
"daily_zscore": 2.0,
|
|
48
55
|
"daily_ratio": 2.0,
|
|
49
|
-
"
|
|
56
|
+
"daily_robust_z": 3.5,
|
|
57
|
+
"session_ratio": 20.0,
|
|
58
|
+
"max_session_outliers": 5
|
|
50
59
|
},
|
|
51
60
|
"scorecard": {
|
|
52
|
-
"_comment": "
|
|
53
|
-
"target_output_ratio": 0.0088,
|
|
54
|
-
"target_cost_per_1k_output_usd": 0.3,
|
|
55
|
-
"frontier_cost_share_allowance_pct": 40
|
|
61
|
+
"_comment": "The scorecard has no targets to configure: its three dimensions (Context share, Cache break-even, Budget adherence) are each measured directly from your transcripts, except budget adherence which needs a budget configured above."
|
|
56
62
|
}
|
|
57
63
|
}
|
package/finops/actions.py
CHANGED
package/finops/agents.py
CHANGED
|
@@ -33,7 +33,8 @@ def _cursor_state_db():
|
|
|
33
33
|
|
|
34
34
|
AGENTS = {
|
|
35
35
|
"claude": {"name": "Claude Code", "data": "full",
|
|
36
|
-
"note": "Tokens, model, cost, prompts and tool calls per request."
|
|
36
|
+
"note": "Tokens, model, cost, prompts and tool calls per request. "
|
|
37
|
+
"Includes Claude desktop app (Cowork) sessions when present."},
|
|
37
38
|
"codex": {"name": "Codex", "data": "tokens",
|
|
38
39
|
"paths": [os.path.join(HOME, ".codex", "sessions")],
|
|
39
40
|
"note": "Tokens and model per turn. Cost estimated at OpenAI API list prices; "
|