Skip to content

shavindraSN/claude-meter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

claude-meter

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).


How it works

       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚  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.


Requirements

  • macOS or Linux. Windows is not supported.
  • Python 3.9+ available as python3 on 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.


Install

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

install.sh is the end-to-end setup:

  1. Removes any previous install (old venv in the repo, stale service, old symlink).
  2. Creates a clean venv at ~/.venvs/claude-meter β€” outside macOS's TCC-protected folders, so the background service can actually read it.
  3. Installs claude-meter into that venv (non-editable) and symlinks ~/.local/bin/claude-meter to it.
  4. Adds ~/.local/bin to your PATH in ~/.zshrc if it isn't already.
  5. Prompts for the clock's IP address and writes it to the config.
  6. Runs claude-meter check to verify auth and the API.
  7. 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.50

Override the install location or Python version with env vars:

CLAUDE_METER_VENV=~/apps/claude-meter PYTHON=python3.12 ./install.sh

Why not pip install -e . in the source tree?

On 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.


Configure

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:

  1. $CLAUDE_METER_CONFIG
  2. $XDG_CONFIG_HOME/claude-meter/config.json
  3. ~/.config/claude-meter/config.json (macOS and Linux default)

Inspect the current values:

claude-meter show

Display modes

  • gif80 β€” 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-switch ON, file1-switch ON, file2-switch…file5-switch OFF.

Both modes push a single JPEG; gif80 wraps it in the firmware's custom 33-frame container so it survives the Customization-GIF validator.


Verify

claude-meter check

Prints 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.


Run

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

In the foreground (for debugging):

claude-meter run

Service 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.

Restart the service

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

Linux (systemd user unit):

systemctl --user restart claude-meter
systemctl --user status claude-meter

Heavier 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-status

Watch the logs

macOS:

tail -f ~/Library/Logs/claude-meter/claude-meter.out.log
tail -f ~/Library/Logs/claude-meter/claude-meter.err.log

Linux:

journalctl --user -u claude-meter -f

CLI reference

claude-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.

Docker

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.


Supported displays

Device Mode(s) Status
GeeKmagic SmallTV (v2 firmware) gif80, photo240 supported

Adding another display is two files and a registration line:


Privacy

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.


Troubleshooting

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.log

Most 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-service

The restart is needed because the loop loads config once at startup.


Development

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 check

After code changes, re-run ./install.sh to reinstall the background service from the updated source.


License

MIT. See LICENSE.

About

At-a-glance Claude Code usage on a tiny screen. Pulls live 5-hour and weekly percentages from Anthropic's OAuth API and pushes them to a GeeKmagic SmallTV clock over Wi-Fi.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors