token-finops-cli 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- token_finops_cli-0.3.0/PKG-INFO +267 -0
- token_finops_cli-0.3.0/README.md +244 -0
- token_finops_cli-0.3.0/examples/generate_synthetic_db.py +40 -0
- token_finops_cli-0.3.0/pyproject.toml +53 -0
- token_finops_cli-0.3.0/src/token_finops_cli/__init__.py +7 -0
- token_finops_cli-0.3.0/src/token_finops_cli/__main__.py +3 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/__init__.py +22 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/aider.py +338 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/base.py +53 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/claude_code.py +412 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/cline.py +416 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/codex.py +423 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/continue_dev.py +290 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/copilot.py +212 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/gemini_cli.py +368 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/hermes.py +520 -0
- token_finops_cli-0.3.0/src/token_finops_cli/adapters/opencode.py +445 -0
- token_finops_cli-0.3.0/src/token_finops_cli/burn/__init__.py +440 -0
- token_finops_cli-0.3.0/src/token_finops_cli/burn/history.py +168 -0
- token_finops_cli-0.3.0/src/token_finops_cli/burn/plans.json +31 -0
- token_finops_cli-0.3.0/src/token_finops_cli/cli.py +527 -0
- token_finops_cli-0.3.0/src/token_finops_cli/core/__init__.py +1 -0
- token_finops_cli-0.3.0/src/token_finops_cli/core/model.py +142 -0
- token_finops_cli-0.3.0/src/token_finops_cli/core/pricing.py +167 -0
- token_finops_cli-0.3.0/src/token_finops_cli/core/runway.py +262 -0
- token_finops_cli-0.3.0/src/token_finops_cli/cost_per_token.py +172 -0
- token_finops_cli-0.3.0/src/token_finops_cli/report.py +184 -0
- token_finops_cli-0.3.0/src/token_finops_cli/savings/__init__.py +258 -0
- token_finops_cli-0.3.0/src/token_finops_cli/savings/energy.json +21 -0
- token_finops_cli-0.3.0/src/token_finops_cli/savings/hardware_profiles.json +67 -0
- token_finops_cli-0.3.0/src/token_finops_cli/status.py +243 -0
- token_finops_cli-0.3.0/src/token_finops_cli/synth/__init__.py +161 -0
- token_finops_cli-0.3.0/src/token_finops_cli/synth/env.py +52 -0
- token_finops_cli-0.3.0/src/token_finops_cli/synth/scenarios.py +109 -0
- token_finops_cli-0.3.0/tests/conftest.py +181 -0
- token_finops_cli-0.3.0/tests/test_aider.py +75 -0
- token_finops_cli-0.3.0/tests/test_burn.py +350 -0
- token_finops_cli-0.3.0/tests/test_burn_history.py +338 -0
- token_finops_cli-0.3.0/tests/test_claude_code.py +147 -0
- {token_finops_cli-0.2.0 → token_finops_cli-0.3.0}/tests/test_cli.py +11 -0
- token_finops_cli-0.3.0/tests/test_cli_and_savings.py +642 -0
- token_finops_cli-0.3.0/tests/test_cli_smoke_workflow.py +153 -0
- token_finops_cli-0.3.0/tests/test_cline.py +89 -0
- token_finops_cli-0.3.0/tests/test_codex.py +111 -0
- token_finops_cli-0.3.0/tests/test_continue_dev.py +60 -0
- token_finops_cli-0.3.0/tests/test_copilot.py +122 -0
- token_finops_cli-0.3.0/tests/test_core.py +92 -0
- token_finops_cli-0.3.0/tests/test_cost_per_token.py +129 -0
- token_finops_cli-0.3.0/tests/test_e2e_matrix.py +338 -0
- token_finops_cli-0.3.0/tests/test_gemini_cli.py +92 -0
- token_finops_cli-0.3.0/tests/test_hermes.py +52 -0
- token_finops_cli-0.3.0/tests/test_opencode.py +73 -0
- token_finops_cli-0.3.0/tests/test_pricing.py +224 -0
- token_finops_cli-0.3.0/tests/test_report.py +284 -0
- token_finops_cli-0.3.0/tests/test_robustness.py +673 -0
- token_finops_cli-0.3.0/tests/test_runway_windows.py +485 -0
- token_finops_cli-0.3.0/tests/test_self_audit.py +245 -0
- token_finops_cli-0.3.0/tests/test_status.py +107 -0
- token_finops_cli-0.3.0/tests/test_statusline.py +264 -0
- token_finops_cli-0.3.0/tests/test_synth.py +143 -0
- token_finops_cli-0.3.0/tests/test_usecases.py +1051 -0
- token_finops_cli-0.2.0/PKG-INFO +0 -166
- token_finops_cli-0.2.0/README.md +0 -144
- token_finops_cli-0.2.0/examples/generate_synthetic_db.py +0 -24
- token_finops_cli-0.2.0/pyproject.toml +0 -44
- token_finops_cli-0.2.0/src/token_finops_cli/__init__.py +0 -6
- {token_finops_cli-0.2.0 → token_finops_cli-0.3.0}/.github/workflows/ci.yml +0 -0
- {token_finops_cli-0.2.0 → token_finops_cli-0.3.0}/.github/workflows/release.yml +0 -0
- {token_finops_cli-0.2.0 → token_finops_cli-0.3.0}/.gitignore +0 -0
- {token_finops_cli-0.2.0 → token_finops_cli-0.3.0}/LICENSE +0 -0
- /token_finops_cli-0.2.0/src/token_finops_cli/cli.py → /token_finops_cli-0.3.0/src/token_finops_cli/copilot_legacy.py +0 -0
- {token_finops_cli-0.2.0 → token_finops_cli-0.3.0}/src/token_finops_cli/examples_helper.py +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: token-finops-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Read-only, local-first token usage and budget-runway tracker for AI coding agents (Copilot CLI, Claude Code, Codex CLI, Gemini CLI, Hermes Agent, ...) plus a local-vs-cloud savings estimator
|
|
5
|
+
Project-URL: Homepage, https://github.com/tronicum/burn-token-burn
|
|
6
|
+
Project-URL: Origin, https://github.com/oh-my-agent-code/token-finops-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/tronicum/burn-token-burn/issues
|
|
8
|
+
Author: tronicum
|
|
9
|
+
License-Expression: AGPL-3.0-or-later
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: claude-code,cli,codex,copilot-cli,finops,gemini-cli,github-copilot,hermes-agent,local-llm,token-usage
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# token-finops-cli
|
|
25
|
+
|
|
26
|
+
Read-only, local-first token usage and budget-runway tracker for AI coding
|
|
27
|
+
agents. **v0.3.0** grew this out of the original 0.2.x line — a single
|
|
28
|
+
600-line script that read GitHub Copilot CLI's local telemetry — into a
|
|
29
|
+
modular tool with **9 adapters** (Copilot, Claude Code, Codex CLI, Gemini
|
|
30
|
+
CLI, Hermes Agent, OpenCode/Kilo, Cline/Roo/Kilo, Aider, Continue.dev), a
|
|
31
|
+
shared runway engine, a self-audit for Claude Code sessions, and a
|
|
32
|
+
local-vs-cloud savings estimator. The philosophy hasn't changed: standard
|
|
33
|
+
library only, no runtime dependencies, and it never writes to any tool's
|
|
34
|
+
own data store — SQLite is opened read-only/immutable where applicable.
|
|
35
|
+
|
|
36
|
+
Everything below that talks about the original Copilot budget/cycle/session
|
|
37
|
+
reports still works exactly as it did in 0.2.x.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv tool install token-finops-cli # recommended
|
|
43
|
+
pipx install token-finops-cli
|
|
44
|
+
pip install token-finops-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 60-second tour
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
$ token-finops adapters
|
|
51
|
+
tool found root
|
|
52
|
+
copilot yes ~/.copilot
|
|
53
|
+
claude_code yes ~/.claude
|
|
54
|
+
codex no ~/.codex
|
|
55
|
+
gemini_cli no ~/.gemini
|
|
56
|
+
hermes no ~/.hermes
|
|
57
|
+
opencode no ~/.local/share/opencode
|
|
58
|
+
cline no ~/.config/Code/User/globalStorage
|
|
59
|
+
aider no ~
|
|
60
|
+
continue no ~/.continue
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
$ token-finops report --compact
|
|
65
|
+
GitHub Copilot CLI [######--------------] 29.4% runway 41.2d OK
|
|
66
|
+
Claude Code [############--------] 61.0% runway 1.9h WARN
|
|
67
|
+
|
|
68
|
+
binding constraint: Claude Code (5h) — runway 1.9h -> WARN
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
$ token-finops sessions --tool claude_code --since 30d
|
|
73
|
+
Claude Code sessions (last 30d):
|
|
74
|
+
session_id started calls tokens API-eq $
|
|
75
|
+
0bbc8283-… 2026-09-05 09:12 428 73.1M 87.28
|
|
76
|
+
…
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
$ token-finops self-audit
|
|
81
|
+
Self-audit: Claude Code session 0bbc8283-…
|
|
82
|
+
API calls (deduplicated): 428 = main loop 211 + sub-agents 217
|
|
83
|
+
total tokens: 73.1M (95 % of them cache reads)
|
|
84
|
+
API-equivalent: $87.28
|
|
85
|
+
|
|
86
|
+
By model:
|
|
87
|
+
claude-fable-5-1 255 calls $73.25 84%
|
|
88
|
+
claude-sonnet-5 172 calls $14.03 16%
|
|
89
|
+
|
|
90
|
+
Sub-agents = 30 % of API-equivalent cost
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
$ token-finops savings --hardware mac-studio-m4-max-128gb --model qwen3-32b --utilization 0.2
|
|
95
|
+
local total: $8.86 / 1M tok (capex-dominated at 20 % utilisation)
|
|
96
|
+
cloud (sonnet): $3.20 / 1M tok -> CLOUD CHEAPER
|
|
97
|
+
break-even utilisation: 60 % of 24/7
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
$ token-finops break-even --hardware mac-studio-m4-max-128gb
|
|
102
|
+
replaceable cloud usage (haiku/sonnet-class): 35.5M tokens = $14.94 API-equivalent
|
|
103
|
+
local alternative: energy $14.29 + capex $116.87 = $131.15
|
|
104
|
+
-> cloud still cheaper by $116.21; at the current pace the box never pays off
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
$ token-finops synth --out /tmp/demo-home --print-env && \
|
|
109
|
+
eval "$(token-finops synth --out /tmp/demo-home --print-env)" && \
|
|
110
|
+
token-finops report --compact
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
runs the whole CLI against a fully synthetic, throwaway fake-home tree — no
|
|
114
|
+
real telemetry anywhere. See `docs/SYNTH.md` in the repo root for scenarios
|
|
115
|
+
(`steady`, `burst`, `exhausted`, `weekend`, `fresh`, `quiet`,
|
|
116
|
+
`subagent-heavy`).
|
|
117
|
+
|
|
118
|
+
## GitHub Copilot CLI (the original tool)
|
|
119
|
+
|
|
120
|
+
`token-finops-cli` still reads `~/.copilot/session-store.db`
|
|
121
|
+
(`assistant_usage_events`) directly — no external API calls, nothing
|
|
122
|
+
leaves your machine, the DB is opened read-only.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
token-finops report --tool copilot # 7-day summary + runway (default window)
|
|
126
|
+
token-finops report --tool copilot --budget 1500 --cycle-day 1 # your plan's monthly AI units
|
|
127
|
+
token-finops report --tool copilot --compact # 2-line minimal output
|
|
128
|
+
token-finops report --tool copilot --watch 5 # live-refreshing view every 5s
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Session history and break/gap reports (per-session archival view, distinct
|
|
132
|
+
from the runway report):
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
token-finops sessions # list all past Copilot sessions
|
|
136
|
+
token-finops sessions --since 30d --limit 10
|
|
137
|
+
token-finops sessions --session <id> # detailed break/gap report
|
|
138
|
+
token-finops sessions --session <id> --gap-minutes 60
|
|
139
|
+
token-finops sessions --totals # ONE combined report across ALL sessions
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The detailed per-session view reports "breaks" — gaps between requests
|
|
143
|
+
longer than `--gap-minutes` (default 30) — and active vs. idle wall-clock
|
|
144
|
+
time. The "AI unit" is GitHub's own cost/usage unit (`total_nano_aiu / 1e9`)
|
|
145
|
+
— the same number shown on the Copilot billing page, not raw token counts.
|
|
146
|
+
|
|
147
|
+
## Claude Code: status-line hook
|
|
148
|
+
|
|
149
|
+
Anthropic subscriptions expose only a percentage of a rolling 5h/7d window,
|
|
150
|
+
and only through the status line. Wire the collector as your status line
|
|
151
|
+
command; every redraw stores a snapshot, and two snapshots in the same
|
|
152
|
+
window are enough for a burn estimate:
|
|
153
|
+
|
|
154
|
+
```jsonc
|
|
155
|
+
// ~/.claude/settings.json
|
|
156
|
+
{ "statusLine": { "type": "command", "command": "token-finops collect-statusline" } }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
token-finops report --tool claude_code --json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
Claude Code — window: 5h (resets 2026-09-05 12:29 UTC)
|
|
165
|
+
budget: [############------------------] 40.0%
|
|
166
|
+
window: [############------------------] 40.0% elapsed
|
|
167
|
+
pace: 1.00 (on pace)
|
|
168
|
+
burn: 20.0% per hour (from snapshots)
|
|
169
|
+
runway: 3.0h vs 3.0h left -> OK
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Status bars: tmux, starship, waybar, SwiftBar
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
token-finops status # CC 61% 2h! | CX 17% 3h | CP 29% 41d | binds: CC
|
|
176
|
+
token-finops status --format tmux # for status-right (contrib/tmux/token-finops.tmux)
|
|
177
|
+
token-finops status --format starship # binding constraint only
|
|
178
|
+
token-finops status --format waybar # JSON; also polybar, i3, xbar, json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`status` serves a cache (`~/.token-finops/last.json`) and rescans only with `--fresh` or when
|
|
182
|
+
the cache is older than `--max-age` seconds. Refresh it from one timer (`contrib/refresh/`) and let
|
|
183
|
+
every widget poll freely. Details: `../docs/TMUX.md`.
|
|
184
|
+
|
|
185
|
+
## Self-audit
|
|
186
|
+
|
|
187
|
+
Claude Code writes one line per content block, so naive counting
|
|
188
|
+
over-reports by roughly 1.9x. `self-audit` deduplicates on
|
|
189
|
+
`(message.id, requestId)`, includes sub-agent transcripts, and breaks the
|
|
190
|
+
bill down by model — the model, not the raw token count, is what decides
|
|
191
|
+
the cost.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
token-finops self-audit # latest session
|
|
195
|
+
token-finops self-audit --session <id-prefix>
|
|
196
|
+
token-finops self-audit --json # machine-readable, for CI/PR checklists
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Local vs. cloud: savings and break-even
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
token-finops savings --list # available hardware/model/tariff keys
|
|
203
|
+
token-finops savings --hardware rtx-4090-workstation --model llama-3.3-70b
|
|
204
|
+
token-finops savings --power solar-de-feed-in --utilization 0.8
|
|
205
|
+
token-finops break-even --since 90d --replaceable-tiers haiku,sonnet
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Solar is priced at the feed-in tariff you forgo, not at zero. Capex is
|
|
209
|
+
amortised per hour of *actual* inference. Only Haiku/Sonnet-class work
|
|
210
|
+
counts as replaceable by a local model — a 32B model does not do
|
|
211
|
+
Opus/Fable-class work. Hardware and energy numbers live in editable JSON
|
|
212
|
+
(`savings/hardware_profiles.json`, `savings/energy.json`) with sources and
|
|
213
|
+
review dates — measure your own box and overwrite them.
|
|
214
|
+
|
|
215
|
+
## Environment variables
|
|
216
|
+
|
|
217
|
+
Every adapter reads its data root from an override variable before falling
|
|
218
|
+
back to the tool's real default location. None of these are required —
|
|
219
|
+
they exist so you can point the CLI at a different install, a synthetic
|
|
220
|
+
fixture tree, or a non-default `$HOME`.
|
|
221
|
+
|
|
222
|
+
| Variable | Overrides | Default |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| `TOKEN_FINOPS_COPILOT_DB` | Copilot session-store DB path | `~/.copilot/session-store.db` |
|
|
225
|
+
| `TOKEN_FINOPS_DB` | (legacy alias, `sessions`/original CLI) Copilot DB path | `~/.copilot/session-store.db` |
|
|
226
|
+
| `CLAUDE_CONFIG_DIR` | Claude Code config root (`self-audit --config-dir` also sets this) | `~/.claude` |
|
|
227
|
+
| `CODEX_HOME` | OpenAI Codex CLI home | `~/.codex` |
|
|
228
|
+
| `GEMINI_CLI_HOME` | Gemini CLI home | `~/.gemini` |
|
|
229
|
+
| `HERMES_HOME` | Hermes Agent home | `~/.hermes` |
|
|
230
|
+
| `CLINE_CLI_HOME` | Cline CLI (non-VS Code) home | `~/.cline` |
|
|
231
|
+
| `TOKEN_FINOPS_CLINE_DIRS` | Cline/Roo/Kilo VS Code `globalStorage` roots | per-OS VS Code data dir |
|
|
232
|
+
| `TOKEN_FINOPS_OPENCODE_DB` | OpenCode/Kilo CLI sqlite DB path | `$XDG_DATA_HOME/opencode/opencode.db` |
|
|
233
|
+
| `XDG_DATA_HOME` | Base for the OpenCode DB default above | `~/.local/share` |
|
|
234
|
+
| `TOKEN_FINOPS_AIDER_DIRS` | Aider chat-history search roots | `~` |
|
|
235
|
+
| `TOKEN_FINOPS_AIDER_ANALYTICS` | Aider opt-in analytics JSONL path | `~/.aider/analytics.jsonl` |
|
|
236
|
+
| `CONTINUE_GLOBAL_DIR` | Continue.dev sessions root | `~/.continue` |
|
|
237
|
+
| `TOKEN_FINOPS_HARDWARE_JSON` | Override the `savings` hardware-profiles JSON | packaged `hardware_profiles.json` |
|
|
238
|
+
| `TOKEN_FINOPS_ENERGY_JSON` | Override the `savings` energy/tariff JSON | packaged `energy.json` |
|
|
239
|
+
|
|
240
|
+
## Documentation and design rules
|
|
241
|
+
|
|
242
|
+
This package is one component of the `token-finops` repository. For the
|
|
243
|
+
full guide, the read-only/fractions-not-dollars design rules, the
|
|
244
|
+
per-assistant Architecture Decision Records, and the 50 executable use
|
|
245
|
+
cases, see the repository root:
|
|
246
|
+
|
|
247
|
+
- [`../README.md`](../README.md) — project overview, design rules, related tools
|
|
248
|
+
- [`../docs/PLAN.md`](../docs/PLAN.md) — structured plan and status
|
|
249
|
+
- [`../docs/ADAPTERS.md`](../docs/ADAPTERS.md) — how to add an adapter
|
|
250
|
+
- [`../docs/adr/`](../docs/adr/) — one ADR per coding assistant
|
|
251
|
+
- [`../docs/SYNTH.md`](../docs/SYNTH.md) — synthetic telemetry generator
|
|
252
|
+
- [`../docs/USECASES.md`](../docs/USECASES.md) — every executable use case
|
|
253
|
+
- [`../docs/TMUX.md`](../docs/TMUX.md) — `status --format …` for tmux, starship, waybar, polybar, i3, SwiftBar
|
|
254
|
+
- [`../docs/INTEGRATIONS.md`](../docs/INTEGRATIONS.md) — editor / agent-native / desktop integrations (design doc)
|
|
255
|
+
- [`../CHANGELOG.md`](../CHANGELOG.md), [`../CONTRIBUTING.md`](../CONTRIBUTING.md)
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
AGPL-3.0-or-later.
|
|
260
|
+
|
|
261
|
+
## Credit
|
|
262
|
+
|
|
263
|
+
The Copilot CLI budget/runway/session-break logic in this package is a
|
|
264
|
+
direct port of the original **token-finops-cli** by **tronicum**
|
|
265
|
+
([oh-my-agent-code/token-finops-cli](https://github.com/oh-my-agent-code/token-finops-cli)),
|
|
266
|
+
kept verbatim where possible. Everything else in this repository builds on
|
|
267
|
+
that foundation.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# token-finops-cli
|
|
2
|
+
|
|
3
|
+
Read-only, local-first token usage and budget-runway tracker for AI coding
|
|
4
|
+
agents. **v0.3.0** grew this out of the original 0.2.x line — a single
|
|
5
|
+
600-line script that read GitHub Copilot CLI's local telemetry — into a
|
|
6
|
+
modular tool with **9 adapters** (Copilot, Claude Code, Codex CLI, Gemini
|
|
7
|
+
CLI, Hermes Agent, OpenCode/Kilo, Cline/Roo/Kilo, Aider, Continue.dev), a
|
|
8
|
+
shared runway engine, a self-audit for Claude Code sessions, and a
|
|
9
|
+
local-vs-cloud savings estimator. The philosophy hasn't changed: standard
|
|
10
|
+
library only, no runtime dependencies, and it never writes to any tool's
|
|
11
|
+
own data store — SQLite is opened read-only/immutable where applicable.
|
|
12
|
+
|
|
13
|
+
Everything below that talks about the original Copilot budget/cycle/session
|
|
14
|
+
reports still works exactly as it did in 0.2.x.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install token-finops-cli # recommended
|
|
20
|
+
pipx install token-finops-cli
|
|
21
|
+
pip install token-finops-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 60-second tour
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
$ token-finops adapters
|
|
28
|
+
tool found root
|
|
29
|
+
copilot yes ~/.copilot
|
|
30
|
+
claude_code yes ~/.claude
|
|
31
|
+
codex no ~/.codex
|
|
32
|
+
gemini_cli no ~/.gemini
|
|
33
|
+
hermes no ~/.hermes
|
|
34
|
+
opencode no ~/.local/share/opencode
|
|
35
|
+
cline no ~/.config/Code/User/globalStorage
|
|
36
|
+
aider no ~
|
|
37
|
+
continue no ~/.continue
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
$ token-finops report --compact
|
|
42
|
+
GitHub Copilot CLI [######--------------] 29.4% runway 41.2d OK
|
|
43
|
+
Claude Code [############--------] 61.0% runway 1.9h WARN
|
|
44
|
+
|
|
45
|
+
binding constraint: Claude Code (5h) — runway 1.9h -> WARN
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
$ token-finops sessions --tool claude_code --since 30d
|
|
50
|
+
Claude Code sessions (last 30d):
|
|
51
|
+
session_id started calls tokens API-eq $
|
|
52
|
+
0bbc8283-… 2026-09-05 09:12 428 73.1M 87.28
|
|
53
|
+
…
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
$ token-finops self-audit
|
|
58
|
+
Self-audit: Claude Code session 0bbc8283-…
|
|
59
|
+
API calls (deduplicated): 428 = main loop 211 + sub-agents 217
|
|
60
|
+
total tokens: 73.1M (95 % of them cache reads)
|
|
61
|
+
API-equivalent: $87.28
|
|
62
|
+
|
|
63
|
+
By model:
|
|
64
|
+
claude-fable-5-1 255 calls $73.25 84%
|
|
65
|
+
claude-sonnet-5 172 calls $14.03 16%
|
|
66
|
+
|
|
67
|
+
Sub-agents = 30 % of API-equivalent cost
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
$ token-finops savings --hardware mac-studio-m4-max-128gb --model qwen3-32b --utilization 0.2
|
|
72
|
+
local total: $8.86 / 1M tok (capex-dominated at 20 % utilisation)
|
|
73
|
+
cloud (sonnet): $3.20 / 1M tok -> CLOUD CHEAPER
|
|
74
|
+
break-even utilisation: 60 % of 24/7
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
$ token-finops break-even --hardware mac-studio-m4-max-128gb
|
|
79
|
+
replaceable cloud usage (haiku/sonnet-class): 35.5M tokens = $14.94 API-equivalent
|
|
80
|
+
local alternative: energy $14.29 + capex $116.87 = $131.15
|
|
81
|
+
-> cloud still cheaper by $116.21; at the current pace the box never pays off
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
$ token-finops synth --out /tmp/demo-home --print-env && \
|
|
86
|
+
eval "$(token-finops synth --out /tmp/demo-home --print-env)" && \
|
|
87
|
+
token-finops report --compact
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
runs the whole CLI against a fully synthetic, throwaway fake-home tree — no
|
|
91
|
+
real telemetry anywhere. See `docs/SYNTH.md` in the repo root for scenarios
|
|
92
|
+
(`steady`, `burst`, `exhausted`, `weekend`, `fresh`, `quiet`,
|
|
93
|
+
`subagent-heavy`).
|
|
94
|
+
|
|
95
|
+
## GitHub Copilot CLI (the original tool)
|
|
96
|
+
|
|
97
|
+
`token-finops-cli` still reads `~/.copilot/session-store.db`
|
|
98
|
+
(`assistant_usage_events`) directly — no external API calls, nothing
|
|
99
|
+
leaves your machine, the DB is opened read-only.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
token-finops report --tool copilot # 7-day summary + runway (default window)
|
|
103
|
+
token-finops report --tool copilot --budget 1500 --cycle-day 1 # your plan's monthly AI units
|
|
104
|
+
token-finops report --tool copilot --compact # 2-line minimal output
|
|
105
|
+
token-finops report --tool copilot --watch 5 # live-refreshing view every 5s
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Session history and break/gap reports (per-session archival view, distinct
|
|
109
|
+
from the runway report):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
token-finops sessions # list all past Copilot sessions
|
|
113
|
+
token-finops sessions --since 30d --limit 10
|
|
114
|
+
token-finops sessions --session <id> # detailed break/gap report
|
|
115
|
+
token-finops sessions --session <id> --gap-minutes 60
|
|
116
|
+
token-finops sessions --totals # ONE combined report across ALL sessions
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The detailed per-session view reports "breaks" — gaps between requests
|
|
120
|
+
longer than `--gap-minutes` (default 30) — and active vs. idle wall-clock
|
|
121
|
+
time. The "AI unit" is GitHub's own cost/usage unit (`total_nano_aiu / 1e9`)
|
|
122
|
+
— the same number shown on the Copilot billing page, not raw token counts.
|
|
123
|
+
|
|
124
|
+
## Claude Code: status-line hook
|
|
125
|
+
|
|
126
|
+
Anthropic subscriptions expose only a percentage of a rolling 5h/7d window,
|
|
127
|
+
and only through the status line. Wire the collector as your status line
|
|
128
|
+
command; every redraw stores a snapshot, and two snapshots in the same
|
|
129
|
+
window are enough for a burn estimate:
|
|
130
|
+
|
|
131
|
+
```jsonc
|
|
132
|
+
// ~/.claude/settings.json
|
|
133
|
+
{ "statusLine": { "type": "command", "command": "token-finops collect-statusline" } }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
token-finops report --tool claude_code --json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Claude Code — window: 5h (resets 2026-09-05 12:29 UTC)
|
|
142
|
+
budget: [############------------------] 40.0%
|
|
143
|
+
window: [############------------------] 40.0% elapsed
|
|
144
|
+
pace: 1.00 (on pace)
|
|
145
|
+
burn: 20.0% per hour (from snapshots)
|
|
146
|
+
runway: 3.0h vs 3.0h left -> OK
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Status bars: tmux, starship, waybar, SwiftBar
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
token-finops status # CC 61% 2h! | CX 17% 3h | CP 29% 41d | binds: CC
|
|
153
|
+
token-finops status --format tmux # for status-right (contrib/tmux/token-finops.tmux)
|
|
154
|
+
token-finops status --format starship # binding constraint only
|
|
155
|
+
token-finops status --format waybar # JSON; also polybar, i3, xbar, json
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`status` serves a cache (`~/.token-finops/last.json`) and rescans only with `--fresh` or when
|
|
159
|
+
the cache is older than `--max-age` seconds. Refresh it from one timer (`contrib/refresh/`) and let
|
|
160
|
+
every widget poll freely. Details: `../docs/TMUX.md`.
|
|
161
|
+
|
|
162
|
+
## Self-audit
|
|
163
|
+
|
|
164
|
+
Claude Code writes one line per content block, so naive counting
|
|
165
|
+
over-reports by roughly 1.9x. `self-audit` deduplicates on
|
|
166
|
+
`(message.id, requestId)`, includes sub-agent transcripts, and breaks the
|
|
167
|
+
bill down by model — the model, not the raw token count, is what decides
|
|
168
|
+
the cost.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
token-finops self-audit # latest session
|
|
172
|
+
token-finops self-audit --session <id-prefix>
|
|
173
|
+
token-finops self-audit --json # machine-readable, for CI/PR checklists
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Local vs. cloud: savings and break-even
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
token-finops savings --list # available hardware/model/tariff keys
|
|
180
|
+
token-finops savings --hardware rtx-4090-workstation --model llama-3.3-70b
|
|
181
|
+
token-finops savings --power solar-de-feed-in --utilization 0.8
|
|
182
|
+
token-finops break-even --since 90d --replaceable-tiers haiku,sonnet
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Solar is priced at the feed-in tariff you forgo, not at zero. Capex is
|
|
186
|
+
amortised per hour of *actual* inference. Only Haiku/Sonnet-class work
|
|
187
|
+
counts as replaceable by a local model — a 32B model does not do
|
|
188
|
+
Opus/Fable-class work. Hardware and energy numbers live in editable JSON
|
|
189
|
+
(`savings/hardware_profiles.json`, `savings/energy.json`) with sources and
|
|
190
|
+
review dates — measure your own box and overwrite them.
|
|
191
|
+
|
|
192
|
+
## Environment variables
|
|
193
|
+
|
|
194
|
+
Every adapter reads its data root from an override variable before falling
|
|
195
|
+
back to the tool's real default location. None of these are required —
|
|
196
|
+
they exist so you can point the CLI at a different install, a synthetic
|
|
197
|
+
fixture tree, or a non-default `$HOME`.
|
|
198
|
+
|
|
199
|
+
| Variable | Overrides | Default |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `TOKEN_FINOPS_COPILOT_DB` | Copilot session-store DB path | `~/.copilot/session-store.db` |
|
|
202
|
+
| `TOKEN_FINOPS_DB` | (legacy alias, `sessions`/original CLI) Copilot DB path | `~/.copilot/session-store.db` |
|
|
203
|
+
| `CLAUDE_CONFIG_DIR` | Claude Code config root (`self-audit --config-dir` also sets this) | `~/.claude` |
|
|
204
|
+
| `CODEX_HOME` | OpenAI Codex CLI home | `~/.codex` |
|
|
205
|
+
| `GEMINI_CLI_HOME` | Gemini CLI home | `~/.gemini` |
|
|
206
|
+
| `HERMES_HOME` | Hermes Agent home | `~/.hermes` |
|
|
207
|
+
| `CLINE_CLI_HOME` | Cline CLI (non-VS Code) home | `~/.cline` |
|
|
208
|
+
| `TOKEN_FINOPS_CLINE_DIRS` | Cline/Roo/Kilo VS Code `globalStorage` roots | per-OS VS Code data dir |
|
|
209
|
+
| `TOKEN_FINOPS_OPENCODE_DB` | OpenCode/Kilo CLI sqlite DB path | `$XDG_DATA_HOME/opencode/opencode.db` |
|
|
210
|
+
| `XDG_DATA_HOME` | Base for the OpenCode DB default above | `~/.local/share` |
|
|
211
|
+
| `TOKEN_FINOPS_AIDER_DIRS` | Aider chat-history search roots | `~` |
|
|
212
|
+
| `TOKEN_FINOPS_AIDER_ANALYTICS` | Aider opt-in analytics JSONL path | `~/.aider/analytics.jsonl` |
|
|
213
|
+
| `CONTINUE_GLOBAL_DIR` | Continue.dev sessions root | `~/.continue` |
|
|
214
|
+
| `TOKEN_FINOPS_HARDWARE_JSON` | Override the `savings` hardware-profiles JSON | packaged `hardware_profiles.json` |
|
|
215
|
+
| `TOKEN_FINOPS_ENERGY_JSON` | Override the `savings` energy/tariff JSON | packaged `energy.json` |
|
|
216
|
+
|
|
217
|
+
## Documentation and design rules
|
|
218
|
+
|
|
219
|
+
This package is one component of the `token-finops` repository. For the
|
|
220
|
+
full guide, the read-only/fractions-not-dollars design rules, the
|
|
221
|
+
per-assistant Architecture Decision Records, and the 50 executable use
|
|
222
|
+
cases, see the repository root:
|
|
223
|
+
|
|
224
|
+
- [`../README.md`](../README.md) — project overview, design rules, related tools
|
|
225
|
+
- [`../docs/PLAN.md`](../docs/PLAN.md) — structured plan and status
|
|
226
|
+
- [`../docs/ADAPTERS.md`](../docs/ADAPTERS.md) — how to add an adapter
|
|
227
|
+
- [`../docs/adr/`](../docs/adr/) — one ADR per coding assistant
|
|
228
|
+
- [`../docs/SYNTH.md`](../docs/SYNTH.md) — synthetic telemetry generator
|
|
229
|
+
- [`../docs/USECASES.md`](../docs/USECASES.md) — every executable use case
|
|
230
|
+
- [`../docs/TMUX.md`](../docs/TMUX.md) — `status --format …` for tmux, starship, waybar, polybar, i3, SwiftBar
|
|
231
|
+
- [`../docs/INTEGRATIONS.md`](../docs/INTEGRATIONS.md) — editor / agent-native / desktop integrations (design doc)
|
|
232
|
+
- [`../CHANGELOG.md`](../CHANGELOG.md), [`../CONTRIBUTING.md`](../CONTRIBUTING.md)
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
AGPL-3.0-or-later.
|
|
237
|
+
|
|
238
|
+
## Credit
|
|
239
|
+
|
|
240
|
+
The Copilot CLI budget/runway/session-break logic in this package is a
|
|
241
|
+
direct port of the original **token-finops-cli** by **tronicum**
|
|
242
|
+
([oh-my-agent-code/token-finops-cli](https://github.com/oh-my-agent-code/token-finops-cli)),
|
|
243
|
+
kept verbatim where possible. Everything else in this repository builds on
|
|
244
|
+
that foundation.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Generate a synthetic fake-home tree for demos/tests.
|
|
2
|
+
|
|
3
|
+
Thin CLI wrapper around `token_finops_cli.synth.generate` -- see that module
|
|
4
|
+
(and `docs/SYNTH.md`) for what gets written per tool and what the scenarios
|
|
5
|
+
mean. Equivalent to `token-finops synth`.
|
|
6
|
+
"""
|
|
7
|
+
import argparse
|
|
8
|
+
|
|
9
|
+
from token_finops_cli.synth import ALL_TOOLS, generate
|
|
10
|
+
from token_finops_cli.synth.env import env_for
|
|
11
|
+
from token_finops_cli.synth.scenarios import KNOWN as SCENARIOS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
parser = argparse.ArgumentParser(description=__doc__,
|
|
16
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
17
|
+
parser.add_argument("out", help="Output directory for the synthetic fake-home tree")
|
|
18
|
+
parser.add_argument("--tools", action="append", default=None,
|
|
19
|
+
help=f"comma-separated tool(s) to generate (default: all). known: {', '.join(ALL_TOOLS)}")
|
|
20
|
+
parser.add_argument("--days", type=int, default=14)
|
|
21
|
+
parser.add_argument("--scenario", choices=SCENARIOS, default="steady")
|
|
22
|
+
parser.add_argument("--seed", type=int, default=42)
|
|
23
|
+
parser.add_argument("--print-env", action="store_true",
|
|
24
|
+
help="also print `export VAR=...` lines")
|
|
25
|
+
args = parser.parse_args()
|
|
26
|
+
|
|
27
|
+
tools = None
|
|
28
|
+
if args.tools:
|
|
29
|
+
tools = [t.strip() for chunk in args.tools for t in chunk.split(",") if t.strip()]
|
|
30
|
+
|
|
31
|
+
manifest = generate(args.out, tools=tools, days=args.days, scenario=args.scenario, seed=args.seed)
|
|
32
|
+
for tool, info in manifest.items():
|
|
33
|
+
print(f"{tool}: {info['events']} events -> {info['path']}")
|
|
34
|
+
if args.print_env:
|
|
35
|
+
for var, val in env_for(args.out).items():
|
|
36
|
+
print(f"export {var}={val}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
main()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "token-finops-cli"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Read-only, local-first token usage and budget-runway tracker for AI coding agents (Copilot CLI, Claude Code, Codex CLI, Gemini CLI, Hermes Agent, ...) plus a local-vs-cloud savings estimator"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "AGPL-3.0-or-later"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "tronicum" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["github-copilot", "copilot-cli", "claude-code", "codex", "gemini-cli", "hermes-agent", "finops", "token-usage", "cli", "local-llm"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Topic :: Utilities",
|
|
23
|
+
]
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest>=8.0",
|
|
29
|
+
"ruff>=0.6",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/tronicum/burn-token-burn"
|
|
34
|
+
Origin = "https://github.com/oh-my-agent-code/token-finops-cli"
|
|
35
|
+
Issues = "https://github.com/tronicum/burn-token-burn/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
token-finops = "token_finops_cli:main"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/token_finops_cli"]
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
line-length = 110
|
|
45
|
+
target-version = "py310"
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
# pin the rule set explicitly: ruff 0.16 widened its defaults and broke CI
|
|
49
|
+
select = ["E4", "E7", "E9", "F"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
pythonpath = ["src"]
|
|
53
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Per-tool adapters. Each adapter is read-only and exposes:
|
|
2
|
+
|
|
3
|
+
class XAdapter(BaseAdapter):
|
|
4
|
+
tool = "x"
|
|
5
|
+
def available(self) -> bool
|
|
6
|
+
def scan(self, since=None) -> Iterable[UsageEvent]
|
|
7
|
+
def quota(self) -> Optional[QuotaSnapshot]
|
|
8
|
+
def default_policy(self, allowance=None) -> BudgetPolicy
|
|
9
|
+
|
|
10
|
+
Adapters must never write to the tool's own data store. SQLite sources are
|
|
11
|
+
opened with `immutable=1` (no WAL/lock interaction with the live app).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .base import BaseAdapter, registry, sqlite_readonly # noqa: F401
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def all_adapters() -> list[BaseAdapter]:
|
|
19
|
+
# Import side effects register the adapters.
|
|
20
|
+
from . import aider, claude_code, cline, codex, continue_dev, copilot, gemini_cli, hermes, opencode # noqa: F401
|
|
21
|
+
|
|
22
|
+
return [cls() for cls in registry.values()]
|