Burnclock
How much AI budget you have left, and how long it lasts — in one glance.
Burnclock reads each AI provider's own balance/usage endpoint (or your local Hermes usage records), then computes what most consoles don't show you: burn per day, days of paid runway left, and the date your monthly budget runs out — per provider, in one table or one JSON envelope.
Estimates, not billing truth. Every dollar is computed locally from provider responses and usage records. It is not a bill, not a guarantee, and not affiliated with any provider.
Example output (synthesized, not a live account):
┌────────────┬───────┬───────────┬──────────┬───────────────┐
│ Provider │ State │ Balance │ Burn/day │ Runway (paid) │
├────────────┼───────┼───────────┼──────────┼───────────────┤
│ DeepSeek │ OK │ 20.23 USD │ 2.69 │ 6.0 d │
│ OpenRouter │ EMPTY │ -0.09 USD │ 8.08 │ — │
└────────────┴───────┴───────────┴──────────┴───────────────┘
What it answers
- Do I need to top up today? Balance + days-left at your current burn, per provider.
- Where is my money going? Burn/day from your actual usage (Hermes
state.db, deduped logs), not a guess. - When does the month's budget run out? Budget bars and a projected crossing date.
- Is this key dead or just empty?
KEY_REJECTEDvsEMPTYvsPERMISSION_DENIEDare distinct states.
Providers
| Provider | Balance source | Verification |
|---|---|---|
| DeepSeek | GET /user/balance |
live-verified |
| OpenRouter | GET /api/v1/key + /credits (remaining = credits − usage) |
live-verified |
| Moonshot | GET /v1/users/me/balance |
doc-unverified, disabled by default |
| xAI | management API prepaid balance (needs team_id) |
doc-unverified, disabled by default |
| MiniMax | account/query_balance or token_plan/remains (region auto) |
doc-unverified, disabled by default |
| fal | account/billing (prepaid/postpaid discriminated) |
doc-unverified, disabled by default |
Providers without a balance API still get an honest row: NO_API (spend-only, e.g. priced usage records)
or NOT_QUERYABLE (console-only). No state ever fabricates a number.
Install
Not on PyPI yet — the first release is pending. From source:
git clone <repo-url> && cd burnclock
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/burnclock --version
Once released: pipx install burnclock or uv tool install burnclock.
Requires Python ≥ 3.12. The watch TUI needs the optional extra: pip install 'burnclock[tui]'.
Use
burnclock # human status table
burnclock status --json # envelope v1 + script-safe exit codes
burnclock forecast # paid runway, burn/day, budget crossing date
burnclock dashboard # every known provider (disabled rows included)
burnclock budget show # resolved monthly budgets
burnclock budget set deepseek 50 --dry-run # offline preview, never a surprise write
burnclock watch # live TUI on a TTY; plain table when piped
burnclock badge # prompt-safe one-line balance (cached; never blocks)
burnclock completion bash|zsh|fish # print a static shell completion script
burnclock config show | path # effective config / config file location
Config lives at ~/.config/burnclock/config.toml on macOS/Linux and %APPDATA%\burnclock\config.toml on
Windows (the pre-rename location is still read, with a one-time warning). Keys are never stored in config: a provider entry names an environment
variable (e.g. key_env = "DEEPSEEK_API_KEY"); a secret-shaped value in config refuses to load.
Exit codes on machine paths: 0 ok · 1 internal · 2 usage · 3 config · 4 auth
(KEY_REJECTED/PERMISSION_DENIED) · 5 threshold crossed · 6 unreachable — worst wins, in the order
1 > 2 > 3 > 4 > 5 > 6 > 0. Human views exit 0 whenever output was produced.
status --min <usd> and status --min-days <n> turn it into an alert (exit 5) for cron and agents.
On an interactive terminal only (never for --json or piped output), burnclock checks PyPI at most once a
day and prints a one-line upgrade hint to stderr; set BURNCLOCK_NO_UPGRADE_CHECK=1 to opt out.
Badge
burnclock badge prints one line for a shell prompt, tmux bar, or agent surface:
DeepSeek $12.68 · 4.3d. It reads a small cache and never blocks on the network — a stale or missing
cache prints the last known line (else burnclock —) and refreshes itself in a detached background
process.
burnclock badge # cache hit: instant, no network, no config
burnclock badge --refresh # collect now (same exit codes as status), write the cache
burnclock badge --print-path # print the cat-able badge.txt path and exit
burnclock badge --provider xai --format plain
--refresh writes badge.json plus a plain-text sibling badge.txt (exactly one line, no ANSI) for
prompts that just cat it. Both live in ~/.cache/burnclock/ on macOS/Linux (%LOCALAPPDATA%\burnclock\
on Windows); $BURNCLOCK_BADGE_CACHE moves the cache file. The cache is fresh for 300 s
($BURNCLOCK_BADGE_TTL). --provider picks the row; otherwise [badge].provider from config, then the
first provider with a numeric balance. Color is TTY-only and disabled by NO_COLOR or --format plain;
--json is always ANSI-free.
In a prompt or status bar
badge.txt is one plain line, so a prompt only has to read the file — no Python startup, no network:
# ~/.zshrc: right prompt, refreshed in the background at most every 4 minutes
_burnclock_precmd() {
[[ -r ~/.cache/burnclock/badge.txt ]] &&
RPROMPT="%F{244}$(<~/.cache/burnclock/badge.txt)%f"
(( EPOCHSECONDS - ${_bc_last:-0} < 240 )) && return
_bc_last=$EPOCHSECONDS
~/.hermes/skills/burnclock/scripts/badge-chip.sh --refresh >/dev/null 2>&1 &!
}
autoload -Uz add-zsh-hook && add-zsh-hook precmd _burnclock_precmd
# ~/.tmux.conf: burnclock paints its own line, tmux refreshes it every 60 s
set -g status-interval 60
set -g status-right '#[fg=colour244]#(~/.hermes/skills/burnclock/scripts/badge-chip.sh)#[default]'
badge-chip.sh is the integration entry point: it routes through the skill shim, so a gateway- or
launchd-spawned shell (which never inherited your keys) can still refresh the cache.
The repo also ships a Hermes desktop status-bar chip (desktop-plugin/);
bash scripts/install-badge.sh installs it into ~/.hermes/desktop-plugins/burnclock-badge/, where the
app hot-loads it. It polls the same cache every 60 s and tones the dot green → amber (<5 days runway) →
red (<2 days or a failed provider).
States
OK · LOW · CRITICAL · EMPTY · KEY_REJECTED · PERMISSION_DENIED · NO_API · NOT_QUERYABLE · ERROR · UNREACHABLE
KEY_REJECTED distinguishes a dead/rotated key from a $0 balance; PERMISSION_DENIED means the key is
valid but under-scoped. EMPTY means unknown or zero — never "free".
Hermes skill
The repo ships a Hermes skill (hermes-skill/, packaged as dist/burnclock-skill.zip) so an agent session
can answer "how many tokens do I have left" with real numbers. Its shim reads only the six provider key
variable names from ~/.hermes/.env — it never sources the file and never prints a value. A daily cron
suggestion for a runway alert lives in hermes-skill/CRON.md.
Design rules
- Read-only. No top-ups, no spending, no provider mutations. Spending endpoints are never called.
- Fail-closed. Missing keys are reported as such; malformed responses are
ERROR, never$0. - No secrets in files. Config holds env-var names only; the packaged skill is secret-scanned.
- Offline floor. With zero keys it still reports what your local Hermes usage records know.
- Estimates, not billing truth. Runway is a range: bursty usage moves it, and providers change prices.
Development
.venv/bin/pytest -q # full suite (synthetic fixtures only, no network)
bash hermes-skill/scripts/build-skill-zip.sh # package + secret-scan the skill
Tests never touch a real provider: transports are faked and every fixture is synthetic. Timing protocol and recorded numbers live in BENCH.md; release history in CHANGELOG.md.
MIT licensed. Built by Jeremy Silbernagel.
Metadata
Release files for burnclock 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| burnclock-0.1.1.tar.gz | 140.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| burnclock-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 230.0 kB
Release files / burnclock-0.1.1.tar.gz
| Download URL | burnclock-0.1.1.tar.gz |
|---|---|
| Size | 140.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0e5cc0d90ccb1d7bab9282ebb2b517e35b7355e97bf7d62e4b77528dc25ffaac
|
|
BLAKE2b-256 checksum How to use checksums |
97b5444f5e354d6789d425e6b65c05c4231cf027bd7cf0ed51c1884cf4c4039d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logRelease files / burnclock-0.1.1-py3-none-any.whl
| Download URL | burnclock-0.1.1-py3-none-any.whl |
|---|---|
| Size | 89.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
524be71af724ca5e9bbc7afc772a5fcf530ddb8f0c2eb4a005b9135cd1722e4d
|
|
BLAKE2b-256 checksum How to use checksums |
f79096fc56137bf68bb081b0fee11d5b6dc25e60b2f15979e4ec29347c901ebf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency log