At-a-glance Claude Code usage on a tiny screen. Pulls live 5-hour and weekly percentages from Anthropic's OAuth usage endpoint and pushes them to a GeeKmagic SmallTV clock over your LAN.
Numbers match the Claude app's Settings β Usage exactly β both
come from the same /api/oauth/usage endpoint the desktop app uses.
Supported on macOS and Linux. Runs as a user-level background service (launchd on macOS, systemd user unit on Linux).
ββββββββββββββββββββββ
β Claude Code CLI β (you're already signed in)
β tokens in Keychainβ macOS: Keychain "Claude Code-credentials"
β or ~/.claude/β¦ β Linux: ~/.claude/.credentials.json
βββββββββββ¬βββββββββββ
β reused (no separate login)
βΌ
ββββββββββββββββββββββ βββββββββββββββββββββββββββ
β claude-meter βββGETβββββββΆ /api/oauth/usage β
β push loop βββββββββββββ {five_hour, seven_day} β
βββββββββββ¬βββββββββββ βββββββββββββββββββββββββββ
β render to JPEG
βΌ
ββββββββββββββββββββββ
β Renderer β gif80 (80Γ80) or photo240 (240Γ240)
βββββββββββ¬βββββββββββ
β POST /upload
βΌ
ββββββββββββββββββββββ
β GeeKmagic clock β on your Wi-Fi
ββββββββββββββββββββββ
Token refresh is automatic. When the access token is within 60 seconds
of expiry, claude-meter exchanges the refresh token against
/v1/oauth/token and writes the new pair back to the same store
(Keychain on macOS, JSON file on Linux), so the claude CLI stays in
sync.
- macOS or Linux. Windows is not supported.
- Python 3.9+ available as
python3on your PATH. - Claude Code CLI installed and signed in. claude-meter reuses its OAuth tokens β there's no separate login.
- A GeeKmagic SmallTV clock on the same Wi-Fi network. Tested against v2 firmware.
Python runtime dependencies (pillow, requests) install
automatically.
claude-meter is not published on PyPI yet β install it from source.
git clone https://github.com/shavindraSN/claude-meter.git
cd claude-meter
./install.shinstall.sh is the end-to-end setup:
- Removes any previous install (old venv in the repo, stale service, old symlink).
- Creates a clean venv at
~/.venvs/claude-meterβ outside macOS's TCC-protected folders, so the background service can actually read it. - Installs claude-meter into that venv (non-editable) and symlinks
~/.local/bin/claude-meterto it. - Adds
~/.local/binto your PATH in~/.zshrcif it isn't already. - Prompts for the clock's IP address and writes it to the config.
- Runs
claude-meter checkto verify auth and the API. - Registers the launchd / systemd service and prints its status.
Non-interactive form β pass the clock's IP as the first argument:
./install.sh 192.168.1.50Override the install location or Python version with env vars:
CLAUDE_METER_VENV=~/apps/claude-meter PYTHON=python3.12 ./install.shOn macOS, launchd agents cannot read files under ~/Documents,
~/Desktop, or ~/Downloads without Full Disk Access. An editable
install leaves the package in the source tree, so the service fails
to start with PermissionError: β¦ pyvenv.cfg in the error log.
install.sh sidesteps this by installing into a venv
under ~/.venvs/.
If you're doing active development and your repo lives outside those
protected folders, a standard pip install -e . inside a venv works
fine.
The installer will prompt for the clock's IP, but you can change any value later:
claude-meter configure \
--device-host 192.168.1.50 \
--mode gif80 \
--push-interval 60 \
--force-push 600| Flag | Default | Meaning |
|---|---|---|
--device-host |
(required) | IP or hostname of the clock. |
--mode |
gif80 |
gif80 or photo240. See "Display modes" below. |
--transport |
geekmagic |
Only geekmagic is implemented today. |
--push-interval |
60 |
Seconds between fetches. Below ~30s tends to trip Anthropic's rate limiter; claude-meter honors Retry-After automatically but lighter polling is cleaner. |
--force-push |
600 |
Re-push even when numbers are unchanged after this many seconds (keeps the display from looking stuck). |
Config is stored as JSON. Discovery order:
$CLAUDE_METER_CONFIG$XDG_CONFIG_HOME/claude-meter/config.json~/.config/claude-meter/config.json(macOS and Linux default)
Inspect the current values:
claude-meter showgif80β 80Γ80 JPEG that lives in the device's Customization-GIF slot. Shown alongside the stock clock + weather. Good for "ambient" display.photo240β full-screen 240Γ240 usage card with reset countdowns. Requires Photo mode enabled on the clock: Settings β Photo,photo-switchON,file1-switchON,file2-switchβ¦file5-switchOFF.
Both modes push a single JPEG; gif80 wraps it in the firmware's
custom 33-frame container so it survives the Customization-GIF
validator.
claude-meter checkPrints three lines:
auth: ok (org=β¦)β Claude Code token works.usage: ok (5h=N%, 7d=N%)β API responded.config: β¦ device=β¦ mode=β¦ interval=β¦sβ loaded config.
Exit code is non-zero if any step fails.
As a background service (recommended, installed by install.sh):
claude-meter install-service # launchd on macOS, systemd user unit on Linux
claude-meter service-status
claude-meter uninstall-serviceIn the foreground (for debugging):
claude-meter runService layout:
| Platform | Unit file | Logs |
|---|---|---|
| macOS | ~/Library/LaunchAgents/com.claude-meter.plist |
~/Library/Logs/claude-meter/claude-meter.{out,err}.log |
| Linux | ~/.config/systemd/user/claude-meter.service |
journalctl --user -u claude-meter -f |
On macOS the agent restarts automatically (KeepAlive=true,
throttled to 10s); on Linux systemd is Restart=on-failure with a
10s backoff.
If the service gets stuck (e.g. extended upstream errors) and you want to kick it without re-registering the unit, restart it in place:
macOS (launchd):
launchctl kickstart -k gui/$(id -u)/com.claude-meter-k sends SIGTERM to the running process first, then launchd
respawns it. Confirm a fresh PID with:
launchctl list | grep claude-meterLinux (systemd user unit):
systemctl --user restart claude-meter
systemctl --user status claude-meterHeavier alternative on either platform β fully re-register the unit file (use this after editing the plist/service template):
claude-meter uninstall-service && claude-meter install-service
claude-meter service-statusmacOS:
tail -f ~/Library/Logs/claude-meter/claude-meter.out.log
tail -f ~/Library/Logs/claude-meter/claude-meter.err.logLinux:
journalctl --user -u claude-meter -fclaude-meter [-h] [--version]
{run,check,show,configure,install-service,uninstall-service,service-status}
| Command | Purpose |
|---|---|
run |
Run the push loop in the foreground. |
check |
Verify auth + API + config (one-shot). |
show |
Print the config file path and contents. |
configure |
Update config values (see flags above). |
install-service |
Install as a launchd / systemd user service. |
uninstall-service |
Remove the installed service. |
service-status |
Print launchctl list / systemctl status output. |
--version |
Print version. |
Runs inside a container if you'd rather not install on the host. You must mount your host's Claude Code credentials in, since claude-meter has no login of its own.
Linux host β credentials live in a file:
docker run --rm \
-v ~/.claude:/root/.claude:rw \
-v ~/.config/claude-meter:/root/.config/claude-meter \
-w /src -v "$PWD":/src \
python:3.12-slim bash -c "pip install . && claude-meter run"The -rw mount is required: claude-meter writes refreshed tokens back
so the claude CLI on the host stays signed in.
macOS host β credentials live in the Keychain, which isn't
mountable into a container. Run claude-meter directly on the host
(via install.sh), not in Docker.
| Device | Mode(s) | Status |
|---|---|---|
| GeeKmagic SmallTV (v2 firmware) | gif80, photo240 |
supported |
Adding another display is two files and a registration line:
- A transport under
src/claude_meter/transports/that implementspush(payload: bytes) -> int. - Optionally a new renderer under
src/claude_meter/renderers/if the existing JPEG sizes don't fit. - Register the names in the
get(...)factory of the respective__init__.py.
claude-meter talks to exactly two places:
api.anthropic.comβ for the usage endpoint and token refresh, using your Claude Code OAuth tokens.- The clock's IP on your LAN β for the JPEG upload.
No telemetry, no analytics, no third-party services, no phone-home. Tokens never leave your machine except to Anthropic.
auth: FAIL β Claude Code credentials not found
Run claude and sign in, then retry claude-meter check.
[warn] 429 rate limited, sleeping Ns in logs
Anthropic rate-limited the usage endpoint. claude-meter honors the
Retry-After header automatically, so occasional 429s are harmless.
If they're frequent, raise --push-interval (default is 60s).
Service fails to start on macOS with PermissionError: β¦ pyvenv.cfg
Your install lives under ~/Documents, ~/Desktop, or ~/Downloads.
macOS TCC blocks launchd agents from those folders. Re-run
install.sh β it creates the venv under ~/.venvs/
and symlinks the CLI into ~/.local/bin, both outside TCC control.
LastExitStatus = 256 in service-status
The service crashed and launchd is waiting to restart it. Check the
error log:
tail -n 50 ~/Library/Logs/claude-meter/claude-meter.err.logMost common cause is device_host is not set β run
claude-meter configure --device-host <IP> and reinstall the
service.
Clock shows the old image, byte count looks right
GeeKmagic's firmware silently rejects malformed uploads with HTTP 200
but keeps the previous content on screen. Make sure Photo mode is
configured correctly for photo240 (see Display modes above).
Clock IP changed / I moved networks
claude-meter configure --device-host <new IP>
claude-meter uninstall-service && claude-meter install-serviceThe restart is needed because the loop loads config once at startup.
The source tree:
src/claude_meter/
βββ __main__.py # `python -m claude_meter`
βββ cli.py # argparse + subcommand dispatch
βββ config.py # Config dataclass + JSON load/save
βββ auth.py # Keychain/file read, OAuth refresh
βββ usage.py # /api/oauth/usage + RateLimited handling
βββ loop.py # fetch β render β push β dedup β sleep
βββ service.py # install/uninstall/status for launchd & systemd
βββ renderers/
β βββ gif80.py # 80Γ80 JPEG with firmware-specific qtables
β βββ photo240.py # 240Γ240 JPEG
βββ transports/
β βββ geekmagic.py # multipart POST /upload, gif-container wrap
βββ services/
βββ launchd.plist.template
βββ systemd.service.template
For local iteration (repo checked out somewhere not under
~/Documents on macOS):
python3 -m venv .venv && . .venv/bin/activate
pip install -e .
claude-meter checkAfter code changes, re-run ./install.sh to reinstall the
background service from the updated source.
MIT. See LICENSE.