One small daemon that shows your Claude Code usage on a desk device. It reads the Claude usage endpoint (using the OAuth token Claude Code already stores on your machine) and delivers your 5-hour and 7-day usage to the device by whichever transport fits your setup:
| Transport | How | For |
|---|---|---|
| serial | writes JSON lines over USB CDC | the original Clawdmeter (ESP32‑S3, USB‑attached) |
| push | HTTP POST to the device |
a SmallTV behind Wi‑Fi client isolation (device can't reach the PC) |
| serve | HTTP server the device polls | a SmallTV in pull mode, n8n, anything |
Pick one or several — they share the same poller, token handling and tray icon.
This merges the two device-specific daemons into one:
- Clawdmeter (ESP32‑S3, serial): https://github.com/giovi321/clawdmeter-win
- SmallTV (ESP8266, HTTP): https://github.com/giovi321/smalltv-mod
Not affiliated with Anthropic. It reads
GET /api/oauth/usage, a plain status query, so a poll spends no inference request and does not consume the quota it reports. Short intervals are safe.
Needs Python 3.10+. Works on Windows, macOS and Linux.
pip install -r requirements.txtRecommended: use the wrapper that installs deps and registers login autostart in one
step: install.bat (Windows) / ./install.sh (macOS/Linux). Both create a
self-contained .venv beside the script and register autostart to use it, so the
daemon always starts with its dependencies. This matters because the interpreter a
launcher resolves at start time is not always the one you installed deps into — on
Windows the Microsoft Store Python installs packages to a sandboxed location another
launch can't import, which silently drops the tray to headless; on modern
Homebrew/Debian the system Python refuses a plain pip install (PEP 668). The
macOS/Linux venv uses --system-site-packages so a Linux tray still sees the system
GTK/AppIndicator bindings.
A manual pip install -r requirements.txt into your own environment works too, as
long as you launch the daemon with that same interpreter.
httpx is required; pyserial is only needed for --serial, pystray +
Pillow only for the tray icon, and zeroconf only for mDNS auto-discovery on
--push (without it, push still works via explicit --push-to hosts). On
macOS the tray also needs pyobjc-framework-Cocoa (auto-installed by the
requirements marker). On Linux the tray needs the AppIndicator + GTK system
packages and python3-tk for the push-targets dialog — install.sh prints the
exact command for your distro; without them the daemon runs headless.
python clawdmeter_daemon.py --serial # USB Clawdmeter (auto-detect COM)
python clawdmeter_daemon.py --serial COM5 # ...or a specific port
python clawdmeter_daemon.py --push # push to every SmallTV it finds (mDNS)
python clawdmeter_daemon.py --push-to 192.168.1.50 # push to a specific SmallTV (or smalltv.local)
python clawdmeter_daemon.py --serve --port 8787 # serve for the device to pull
python clawdmeter_daemon.py --serial --serve # several at once
python clawdmeter_daemon.py --no-tray --serve # headless consoleWith no transport flag it defaults to --serve on :8787.
The daemon needs a Claude token. In order it tries:
CLAUDE_CODE_OAUTH_TOKENenv var — a long-lived token fromclaude setup-token. This is the robust choice for an always-on daemon: it doesn't expire, so there's nothing to refresh.- macOS Keychain /
~/.claude/.credentials.json, refreshing via the OAuth refresh grant or by briefly spawningclaude(same autonomous mechanisms the original daemon used).
The on-disk session credentials expire (often every few hours) and, for some subscription logins, carry no refresh token — then nothing can renew them headlessly. So for a set-and-forget daemon:
claude setup-token # subscription required; prints a token (sk-ant-oat…)
# Windows: setx CLAUDE_CODE_OAUTH_TOKEN "sk-ant-oat...your-token..."
# macOS/Linux: export CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat..." in your shell profileThen restart the daemon from a new shell so it inherits the variable.
By default the daemon shows a tray / menu-bar icon (the mascot): grey while
waiting, red if you're not logged in, full colour once it's serving data. Hover for
live 5h % / 7d %. Right-click (macOS: click) to pick the transport — Serial
(USB) / HTTP push to device / HTTP serve — which switches live and is
remembered (in ~/.clawdmeter-daemon.json), plus Configure push targets…,
Refresh now and Quit. So you don't need flags after the first run; the tray
is the switch. Configure push targets… opens a box to type one or more device
IPs/hostnames (comma-separated, e.g. 192.168.1.44, 192.168.1.45); it applies
immediately and is remembered. Leave it blank to rely on mDNS auto-discovery only.
(HTTP push also seeds its targets from CLAWDMETER_PUSH_URL / SMALLTV_PUSH_URL
/ --push-to.)
--install registers the tray daemon to start at login, per-user and without admin,
using each OS's native mechanism — no hardcoded Python path (it registers the
interpreter you run it with):
| OS | Mechanism | Where |
|---|---|---|
| Windows | HKCU\…\Run value (windowless pythonw) |
Task Manager → Startup |
| macOS | LaunchAgent | ~/Library/LaunchAgents/com.giovi321.clawdmeter.plist |
| Linux | XDG autostart .desktop (GUI session) |
~/.config/autostart/clawdmeter-daemon.desktop |
python clawdmeter_daemon.py --install # register autostart at login
python clawdmeter_daemon.py --uninstall # remove it
python clawdmeter_daemon.py --autostart-status # show what's registeredThe autostart command is just --tray; the transport comes from the remembered
config and the env vars above, so autostart needs no edits — set them once (e.g.
CLAWDMETER_PUSH_URL=smalltv.local for push, otherwise it serves on :8787).
Convenience scripts wrap dependency install + --install:
- Windows —
install.bat(creates.venv, installs deps, registers autostart),start-daemon.bat [flags](start now, silent, using the.venvinterpreter),uninstall.bat(remove autostart, stop the process, and clear the legacySmallTVUsageDaemon/ClaudeUsageDaemonshortcuts this merged daemon replaced). - macOS / Linux —
./install.sh,./start-daemon.sh [flags],./uninstall.sh(setPYTHON=/path/to/python3to force an interpreter).
Windows Microsoft-Store
pythonwstub, or a non-default interpreter? SetCLAWDMETER_PYTHONWbeforestart-daemon.bat, e.g.set CLAWDMETER_PYTHONW=C:\Python314\pythonw.exe.
The tray icon starts in the Windows 11
⌃overflow area — drag it onto the taskbar to pin it. On GNOME/Wayland there is no tray at all without the AppIndicator and KStatusNotifier Support extension; without a usable tray backend the daemon logs a note and runs headless (it keeps working, just no icon).
Anything the daemon logs also goes to ~/.clawdmeter-daemon.log — the place to
look if a windowless/headless launch seems to do nothing.
Every transport delivers the same object:
{ "s": 29, "sr": 142, "w": 4, "wr": 9876, "st": "normal", "ok": true }| field | meaning |
|---|---|
s / w |
5‑hour / 7‑day window utilization (%) |
sr / wr |
minutes until each window resets |
st |
session limit severity (normal, warning, rejected, …) |
ok |
false when there's no data (e.g. not logged in) |
- serial: one JSON line per update; reads
{"ready"}/{"refresh"}back from the device to re-poll.--no-hidsends{"hid":false}on connect. - push:
POSTtohttp://<device>/api/usage. - serve:
GET http://host:port/returns the latest object (/healthztoo).
stchanged vocabulary in v1.1.0, when the daemon moved to the usage endpoint. It used to carry the rate-limit header status (allowed,allowed_warning,rejected). Firmware that branches on the exact string wants smalltv-mod 2.13.1+; older builds readnormalas a warning and light the accent dot.
--serial [PORT] USB serial; optional COM port, else auto-detect (VID 0x303A)
--no-hid tell the serial device to disable its HID keys
--push HTTP-push with mDNS auto-discovery of every SmallTV on the LAN
(mDNS is link-local: won't cross subnets/VLANs, see Troubleshooting)
--push-to DEVICE HTTP-push to a device (IP or hostname). Repeatable
(--push-to A --push-to B) and/or comma-separated
(--push-to "A,B"); env CLAWDMETER_PUSH_URL accepts the same list
--no-discover disable mDNS discovery for push (only push to --push-to hosts)
--push-interval N seconds between pushes (default 20)
--serve run the HTTP server (default when no transport is chosen)
--host / --port bind address for --serve (default 0.0.0.0:8787)
--interval N seconds between Claude API refreshes (default 60)
--no-tray run headless in the console
--install register autostart at login (per-user) and exit
--uninstall remove the autostart entry and exit
--autostart-status print whether autostart is registered and exit
- No tray icon appears. First check
~/.clawdmeter-daemon.log. If it sayspystray/Pillow not installed - running headless, the daemon is running under a Python that lacks the tray deps (a launcher resolved a different interpreter than you installed into — common with the Microsoft Store Python). Fix: runinstall.bat/./install.sh, which pin a.venv, or setCLAWDMETER_PYTHONWto a Python that haspystray+Pillow. If the log instead shows the daemon polling, it's running and the icon is just hidden or unsupported. Windows 11: the icon starts in the⌃overflow flyout; drag it onto the taskbar. Linux (GNOME/Wayland): there is no tray without the AppIndicator and KStatusNotifier Support extension, and the icon needs the AppIndicator/GTK packages (see Install); without a backend the daemon logs a note and runs headless. macOS: it's a menu-bar icon (no Dock icon by design). - Tray says "Token expired - run: claude setup-token". Your on-disk credentials expired and can't be renewed headlessly. Use a long-lived token (see Authentication).
- Device never shows data (push/serve). The device must be able to reach the PC
(serve) or the PC the device (push). On Wi‑Fi with client/AP isolation the
device can't open a connection back — use push mode. Also open the PC's
firewall for
--serve(New-NetFirewallRule -DisplayName clawdmeter -Direction Inbound -Protocol TCP -LocalPort 8787 -Action Allow). - Device IP keeps changing. Push to its mDNS name (e.g.
smalltv.local) or set a DHCP reservation. - Several SmallTVs on one network. With firmware 2.8.0+ just run
--push: each device advertises itself over mDNS (_clawdmeter._tcp) and the daemon discovers them all and pushes the same usage to every one, no per-device address. Devices that join or drop off are picked up on the next push. Needszeroconf(inrequirements.txt). - Auto-discovery finds nothing (but the devices are reachable). mDNS is
link-local — it does not cross routers/VLANs. If the daemon PC and the SmallTVs
are on different subnets (e.g. PC on
192.168.2.x, devices on192.168.10.x), discovery sees nothing and.localnames won't resolve, even though direct IP still routes. Fixes: run the daemon on a machine on the same subnet as the devices (then--pushjust works), or enable an mDNS reflector/repeater on your router between the VLANs, or skip discovery and list the device IPs explicitly — via the tray's Configure push targets…, or--push-to 192.168.10.44 --push-to 192.168.10.45(or--push-to "192.168.10.44,192.168.10.45"). For a fixed IP list, add DHCP reservations so the addresses don't drift. - Only some devices update. You listed one host but have several — add the rest
(tray Configure push targets… or repeated/comma-separated
--push-to), or use--pushif they're all on the daemon's subnet. On older firmware, push to each device's unique hostname (smalltv-3fa2.local) by hand. - Serial device not found. Check the cable/driver; pass the port explicitly
(
--serial COM5). Find it in Device Manager.
- Original Clawdmeter (ESP32‑S3 desk dashboard): HermannBjorgvin/Clawdmeter.
- USB/Windows fork: clawdmeter-win.
- SmallTV firmware: smalltv-mod.
WTFPL — Do What The F*ck You Want To Public License.