Metadata-Version: 2.4
Name: burnclock
Version: 0.1.0
Summary: Burnclock: see your AI budget runway at a glance — balances, burn, days-left. Estimates, not billing truth.
Author: Jeremy Silbernagel
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13
Requires-Dist: httpx>=0.27
Requires-Dist: tomli-w>=1.0
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Provides-Extra: tui
Requires-Dist: textual>=0.80; extra == "tui"

# 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):

```text
┌────────────┬───────┬───────────┬──────────┬───────────────┐
│ 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_REJECTED` vs `EMPTY` vs `PERMISSION_DENIED` are 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:

```sh
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

```sh
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 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.

## 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

```sh
.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](BENCH.md); release history in [CHANGELOG.md](CHANGELOG.md).

MIT licensed. Built by Jeremy Silbernagel.
