# ccusage-mqtt Publishes Claude Code usage telemetry to MQTT with Home Assistant auto-discovery. Mirrors the [Clawdmeter](https://github.com/HermannBjorgvin/Clawdmeter) ESP32 firmware's telemetry surface so the same `mood` / burn-rate thresholds apply. Once running, your Home Assistant gains a `Claude Code Usage` device with sensors for current 5h and 7d utilisation %, burn rate %/min, an enum `mood` sensor (idle / normal / active / heavy), time-to-limit, tokens & spend so far, hourly token and spend rates, and reset countdowns for both windows. See [`docs/design.md`](docs/design.md) for the full design. ## Prerequisites - A working Claude Code installation on the host (the publisher reads `~/.claude/.credentials.json` for the OAuth token — no separate Anthropic API key is required) - Node.js + npm if installing on-host (the publisher shells out to [`ccusage`](https://github.com/ryoppippi/ccusage) for token/$ totals). Not needed if you use the Docker path — the image installs it. - Python ≥ 3.12 if installing on-host - A Home Assistant instance with an MQTT broker reachable on your LAN. See **Home Assistant MQTT setup** below if you don't have one yet. ## Install ### Option A: From source (recommended) Runs as a regular Python process — manage it under `systemd`/`launchd`/etc. ```bash # 1. Install ccusage (Node) globally — the publisher shells out to it npm install -g ccusage # 2. Install the publisher git clone https://github.com/george-vice/ccusage-mqtt.git cd ccusage-mqtt pipx install . # (or `pip install .`) # 3. Configure ./setup.sh # writes ./.env in the repo root # (or skip setup.sh and set env vars by hand — MQTT_HOST is the only required one) # 4. Run ccusage-mqtt # picks up ./.env from the current directory # or: ccusage-mqtt --env-file /path/to/anywhere/.env ``` Defaults resolve to `~/.claude/.credentials.json` and `~/.claude/projects/` on disk automatically — no bind-mount to worry about. To run it as a service, drop something like this into `~/.config/systemd/user/ccusage-mqtt.service`: ```ini [Unit] Description=ccusage-mqtt — Claude Code → MQTT publisher After=network-online.target [Service] ExecStart=%h/.local/bin/ccusage-mqtt --env-file %h/.config/ccusage-mqtt/.env Restart=on-failure RestartSec=10 [Install] WantedBy=default.target ``` Then `systemctl --user enable --now ccusage-mqtt`. ### Option B: Docker Self-contained — bundles Node + `ccusage` + Python in one image. Use this if you'd rather not install Node/Python on the host. ```bash git clone https://github.com/george-vice/ccusage-mqtt.git cd ccusage-mqtt ./setup.sh # prompts for MQTT broker + per-instance identity docker compose up -d --build docker compose logs -f ``` ## Home Assistant MQTT setup `ccusage-mqtt` is a publisher. Home Assistant needs an MQTT broker to receive the messages and the MQTT integration to render them. If you don't already have those, set them up first: 1. **Install an MQTT broker.** The simplest path on Home Assistant OS is the **Mosquitto broker** add-on: - Settings → Add-ons → Add-on Store → "Mosquitto broker" → Install → Start. - Any other MQTT broker on your LAN (a standalone mosquitto, EMQX, HiveMQ, etc.) works equally well — skip this step and point at your existing broker in step 3. 2. **Create a Home Assistant user for MQTT** (recommended — keeps broker credentials separate from your login). - Settings → People → Users → Add User. Give it a username and password. "Can only log in from the local network" is fine. 3. **Add the MQTT integration to HA.** - Settings → Devices & Services → Add Integration → "MQTT". - Broker: the hostname/IP of your broker. If you're using the Mosquitto add-on, this is your HA host (e.g. `homeassistant.local`, or your server's LAN IP). With the add-on, `core-mosquitto` also works from other add-ons but not from external hosts. - Username / password: the user from step 2. - **MQTT auto-discovery should be enabled by default.** This is what makes the 15 sensors appear automatically when `ccusage-mqtt` starts publishing. That's the HA side. You'll re-enter the broker hostname and the same username/password into `./setup.sh` below. After either install path, Home Assistant auto-discovers a `Claude Code Usage` device with 15 entities within ~60s under Settings → Devices & Services → MQTT. Re-run `./setup.sh` any time to reconfigure, or hand-edit `.env` directly. ## Blueprints Drop-in automations you can import into Home Assistant with one click. ### Heavy-usage alert Fires a notification (or any action you pick) when `mood` stays in `heavy` for a configurable duration — catches runaway burn-rate sessions before you hit the 5h limit. [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgithub.com%2Fgeorge-vice%2Fccusage-mqtt%2Fblob%2Fmaster%2Fblueprints%2Fautomation%2Fccusage-mqtt%2Fheavy-usage-alert.yaml) Source: [`blueprints/automation/ccusage-mqtt/heavy-usage-alert.yaml`](blueprints/automation/ccusage-mqtt/heavy-usage-alert.yaml). ## Configuration `./setup.sh` only asks for the four values you almost always need to set (MQTT host / port / user / pass). Everything else takes sensible defaults from `.env.example`. Notable optional knobs: | Var | Default | Purpose | |---|---|---| | `CLAUDE_CREDENTIALS_PATH` | `~/.claude/.credentials.json` (source) / `/data/claude-projects/.credentials.json` (Docker) | Where the publisher finds Claude Code's OAuth token. Resolves automatically based on environment; only set this if your Claude Code config lives somewhere unusual. | | `OAUTH_REFRESH_DISABLED` | _unset_ | Set to `true` if the credentials dir is also used by Claude Code CLI on the same host. See [Sharing the credentials dir with Claude Code CLI](#sharing-the-credentials-dir-with-claude-code-cli) below. | | `PROBE_MODEL` | `claude-haiku-4-5-20251001` | Model used for the ratelimit-probe API call. Cheapest current Anthropic model. | | `HEADER_POLL_SEC` | `60` | How often to refresh the 5h / 7d utilisation from Anthropic | | `CCUSAGE_POLL_SEC` | `30` | How often to refresh token / cost data from `ccusage` | | `BURN_RATE_WINDOW_SEC` | `240` | Ring-buffer span for burn-rate smoothing. 240 matches the Clawdmeter firmware. | | `MOOD_*_BELOW` | `0.10 / 0.20 / 0.33` | Mood threshold %/min boundaries (Pro/Max only). Defaults match the Clawdmeter firmware. | | `MOOD_TOKENS_*_BELOW` | `500 / 2500 / 10000` | Mood threshold tokens/hour boundaries — used on Enterprise plans where session_pct is overage-utilization (stuck at 0 until you blow past base allocation). | ## How it works Two pollers run in a single Python process: - **Anthropic API headers** (every 60s). Sends a 1-token probe to `/v1/messages` using your Claude Code OAuth token and reads the `anthropic-ratelimit-unified-*` response headers — the authoritative source for current 5h / 7d window % used, reset times, and allowed/limited status. - **`ccusage` CLI** (every 30s). Reads your local Claude Code session JSONLs to get current-block token count and cost in USD. A ring buffer over the 5h % samples produces a smoothed burn-rate (%/min), which classifies into one of four moods using the same thresholds as the firmware. All 15 sensors publish to MQTT with the `retain` flag so values survive broker restarts. When an access token expires, the publisher uses the stored `refreshToken` to mint a fresh one at Anthropic's OAuth endpoint and writes it back to the credentials file in-place — no manual `claude login` needed. If the refresh itself fails (only happens if the refresh token has also expired), the container does **not** crash-loop: it marks `session_status: "unknown"` in HA and keeps publishing token/$ sensors from `ccusage`, self-healing on the next poll once you re-login. ### Sharing the credentials dir with Claude Code CLI If the publisher and Claude Code CLI both write to the same `~/.claude/.credentials.json` — the default single-account setup — they will race for the rotating refresh token. Anthropic issues refresh tokens single-use, so whichever process loses the race ends up with `invalid_grant: Refresh token not found or invalid` and is stuck until a manual `claude` re-login. Set `OAUTH_REFRESH_DISABLED=true` in `.env` on these hosts. The publisher will skip its own refresh entirely and just consume whatever `accessToken` the CLI has most recently written to the file; the CLI owns rotation. On a 401 the publisher marks `session_status: "unknown"` until the CLI's next use refreshes the file. Token/$ sensors from `ccusage` continue regardless. Leave the flag unset for dedicated config dirs that nothing else writes to (e.g. a second `~/.claude-work` for a separate account) — the publisher's self-refresh works fine there. Both Claude **Pro/Max** and **Enterprise** plans are supported. Enterprise returns a different ratelimit header schema (no 5h or 7d window — overage allowance instead); the parser handles both. Enterprise users get the `session_*` sensors populated from overage data; `weekly_*` sensors stay `null` (correct, not a bug). Because overage-utilization is 0% until you blow past your base allocation, burn-rate is a dead signal on Enterprise — so `mood` instead classifies off `tokens_per_hour` (from `ccusage`) using the `MOOD_TOKENS_*_BELOW` thresholds. On Pro/Max, mood continues to use the %/min burn rate exactly as the Clawdmeter firmware does. ## Troubleshooting - **All sensors stuck at `unknown`** — the OAuth probe is failing. Check `docker compose logs ` for the exact error. The publisher tries to auto-refresh the access token using the stored `refreshToken`; if you see `OAuth refresh failed: 400 invalid_grant`, the refresh token has also expired and a manual re-login is required: `rm ~/.claude/.credentials.json && claude` (i.e. re-run `claude` to log in again). The publisher self-heals within the next 60s poll cycle. - **`invalid_grant` returns roughly daily on a single-account host** — the publisher and Claude Code CLI are racing for the rotating refresh token. Set `OAUTH_REFRESH_DISABLED=true` in `.env` and let the CLI own token rotation. See [Sharing the credentials dir with Claude Code CLI](#sharing-the-credentials-dir-with-claude-code-cli). - **`mood` stuck at `idle` past 4 minutes** — your actual burn rate is below 0.10 %/min. This is normal — Claude Code isn't being used heavily right now. Lower `MOOD_IDLE_BELOW` if you want a more sensitive scale. - **No `tokens_used` value** — `ccusage` couldn't find your session JSONLs. Verify `~/.claude/projects/` exists on the host with at least one session in it. - **HA never sees the device** — check that auto-discovery is enabled on the MQTT integration (Settings → Devices & Services → MQTT → Configure → "Enable discovery"). With it enabled, the device should appear within 60s of the container starting. ## License MIT — see [LICENSE](LICENSE).