Skip to content

Repository files navigation

claude-token-tray

CI License: LGPL v3

An Xfce panel (GenMon) widget that shows your Claude Code subscription usage across the 5-hour and weekly windows, matching what claude /usage and claude.ai report.

The widget on an Xfce panel

  • the leading mark is a small Anthropic-style logo (assets/anthropic.png), with a text fallback when no icon image is available
  • 5h 36% — usage of the current 5-hour window
  • 7d 8% — usage of the rolling 7-day window (turns amber ≥70%, red ≥90%)
  • ↻02:40 — when the current 5-hour window resets

The whole label dims to a darker grey when no Claude Code session is running — with no session, usage isn't moving, so the widget stops polling entirely until you start working again (see Polling and rate limits). Hover the widget for both percentages, reset times, and any extra-usage (overage) status.

Requirements

This is a single Python script plus an installer; there are no third-party Python packages to install (standard library only).

Requirement Notes
Linux + Xfce desktop Uses the Xfce panel and xfconf.
xfce4-genmon-plugin The GenMon panel plugin. sudo apt install xfce4-genmon-plugin (or your distro's equivalent).
Python ≥ 3.8 Standard library only — no pip install needed.
Claude Code, signed in Provides the OAuth token at ~/.claude/.credentials.json and the transcripts under ~/.claude/projects/.
A Claude subscription (Pro/Max) The usage windows are a subscription feature.
Network access to api.anthropic.com For the live percentages. Without it the widget falls back to an offline estimate.

Xfce and GenMon versions

Developed and tested on Xfce 4.20 with xfce4-genmon-plugin 4.3, but it isn't tied to a specific version. The only version-dependent detail is where GenMon keeps its configuration, and the tooling handles both styles:

GenMon Toolkit Config stored in
4.x (current) GTK 3 xfconf/plugins/plugin-<id>/…
3.x (older) GTK 2 the ~/.config/xfce4/panel/genmon-<id>.rc file

setup-panel.sh writes both the .rc file and the xfconf properties, so a fresh install works on either series without you needing to know which one you have. On the xfconf series, an unclean shutdown can blank the stored command (see After a reboot the widget shows only (genmon) under Troubleshooting); fix-tray.sh restores it from the .rc. The script itself is plain Python and GenMon-version-agnostic — it only reads/writes stdout, so any GenMon that runs a command works.

Quick start

sudo apt install xfce4-genmon-plugin     # Debian/Ubuntu — see below for others
git clone https://github.com/quiandy/claude-token-tray.git
cd claude-token-tray
./setup-panel.sh

Installing the GenMon plugin on other distros:

Distro Command
Debian / Ubuntu sudo apt install xfce4-genmon-plugin
Fedora sudo dnf install xfce4-genmon-plugin
Arch sudo pacman -S xfce4-genmon-plugin
openSUSE sudo zypper install xfce4-genmon-plugin

setup-panel.sh registers a new GenMon instance on the first panel (next to the status icons) refreshing every 5 seconds. To check the script alone without the panel, just run it:

./claude-token-genmon.py

It prints the GenMon XML (<txt>…</txt><tool>…</tool>) to stdout.

Font note: the GenMon font must be a real font (the installer sets Sans 11). A Font=(none) value renders the label invisibly while still drawing the widget.

To remove the widget later: right-click it → Remove.

How it works

Anthropic meters subscription usage as a server-side utilization percentage over a 5-hour window (anchored to your first message) and a 7-day window. There is no fixed local "token budget" for a plan — the effective allowance is dynamic (it varies with model, caching, and demand), so only the percentage is meaningful, and that percentage is retrievable.

claude-token-genmon.py reads the real numbers directly from the same source Claude Code's own /usage command uses:

GET https://api.anthropic.com/api/oauth/usage
Authorization: Bearer <token from ~/.claude/.credentials.json>
anthropic-beta: oauth-2025-04-20

which returns, e.g.:

{"five_hour": {"utilization": 14.0, "resets_at": "...Z"},
 "seven_day": {"utilization": 6.0, "resets_at": "...Z"},
 "extra_usage": {"is_enabled": false, ...}}

So the panel matches claude.ai exactly — no token-summing, no budgets, no calibration. (The same utilization/reset values are also broadcast on every API response as anthropic-ratelimit-unified-{five_hour,seven_day}-* headers; the endpoint is just easier to poll.) The last good response is cached in ~/.cache/claude-token-tray/usage.json.

Polling and rate limits

The panel refreshes every ~5 seconds, but /api/oauth/usage is itself rate-limited (HTTP 429 with a Retry-After of a few minutes), and utilization moves slowly. The widget keeps API traffic minimal in two ways:

  • Session-gated. On every ~5 s tick it checks whether a Claude Code process is actually running (the claude CLI, detected via /proc). With no session running it does not poll at all — it shows the last cached numbers, dimmed to the darker idle grey. Polling resumes on the very next tick once you start a session again.
  • Throttled while active. With a session running it polls at most once per CLAUDE_LIVE_MIN_POLL seconds (default 180) and serves the cache in between.

If a poll does return a 429, it honours Retry-After — capped at CLAUDE_LIVE_BACKOFF_SECONDS (default 300) so an unusually long value can't freeze the panel — and keeps showing the last good numbers, marked as cached, until the window clears. The last good response is cached in ~/.cache/claude-token-tray/usage.json.

Fallback estimate

If the API is unreachable (offline, or an expired OAuth token it can't refresh), the widget falls back to a legacy estimate: it sums per-message usage from ~/.claude/projects/*/*.jsonl over each window, weights the tokens (output/cache-write up, cache-read down), and divides by a configurable budget. This path is approximate — it is labelled (est) in the panel — and exists only so the widget shows something offline. A cached live result younger than CLAUDE_LIVE_STALE_SECONDS (default 15 min) is preferred over the estimate.

Configuration (environment variables)

The live path needs no configuration. These tune behaviour and the offline fallback:

Variable Default Purpose
CLAUDE_TRAY_ICON assets/anthropic.png Image shown before the text. Set to a different path to use your own, or to empty to fall back to the glyph.
CLAUDE_IDLE_COLOUR #666666 Colour the whole label dims to when no Claude session is running.
CLAUDE_HTTP_TIMEOUT 4 Seconds to wait on the usage API.
CLAUDE_LIVE_MIN_POLL 180 Minimum seconds between API polls while a session is running; the cache is reused in between. (With no session, polling is suspended entirely.)
CLAUDE_LIVE_BACKOFF_SECONDS 300 Cap on how long a 429 Retry-After may suppress polling, so an over-long value can't freeze the panel.
CLAUDE_LIVE_STALE_SECONDS 900 How long a cached live result is shown when the API is unreachable before falling back to the estimate.
CLAUDE_5H_BUDGET / CLAUDE_WEEKLY_BUDGET 6000000 / 60000000 Estimate only: weighted-token budgets.
CLAUDE_W_INPUT / CLAUDE_W_OUTPUT / CLAUDE_W_CACHE_READ / CLAUDE_W_CACHE_WRITE 1 / 5 / 0.1 / 1.25 Estimate only: per-token weights.

Set these in the GenMon command line, e.g. edit the rc Command= to:

Command=env CLAUDE_HTTP_TIMEOUT=6 /path/to/claude-token-genmon.py

The estimate's budgets are crude guesses, only used offline. If you want the fallback to be less wrong, calibrate once: budget = weighted_sum / (claude.ai_percent / 100).

Security & privacy

  • The widget reads your Claude OAuth token from ~/.claude/.credentials.json and sends it only to https://api.anthropic.com — the same endpoint Claude Code itself calls. Nothing is sent to any third party.
  • The token never leaves your machine except in that request, and is not logged or written anywhere by this tool. The only thing cached on disk is the usage response (percentages and reset times) in ~/.cache/claude-token-tray/.
  • It runs entirely as your user; no elevated privileges (the one sudo is just to install the GenMon package).

Troubleshooting

The label is invisible / blank. The GenMon font is unset. Set it to a real font (see the font note above).

Shows (est) instead of live numbers. The API call isn't succeeding — you're offline, the OAuth token expired (re-run Claude Code to refresh it), or you've been rate-limited (it recovers automatically within a few minutes).

The label is dimmed and the numbers aren't updating. That's the idle state: no claude process is running, so the widget intentionally stops polling and shows the last known numbers. Start a Claude Code session and it refreshes on the next tick.

After a reboot the widget shows only (genmon). Newer xfce4-genmon-plugin versions store their settings in xfconf (/plugins/plugin-<id>/command), not the .rc file. An unclean shutdown can make the panel save an empty command back to xfconf, so on next login the plugin has nothing to run and falls back to its (genmon) placeholder. A plain xfce4-panel -r won't fix it — the restarting panel just re-saves the empty value over anything you write. The command has to be restored while the panel is stopped. Run the bundled repair script:

./fix-tray.sh

It finds the widget's plugin id, stops the panel, rewrites the command (reusing your genmon-<id>.rc Command= line, env vars and all), and starts a fresh panel that reads the restored value back in.

My panel edits keep getting reverted. A running xfce4-panel owns its plugin config and rewrites it from memory on restart, so editing under a live panel gets clobbered. Stop the panel first, then write — which is exactly what fix-tray.sh does for the GenMon command. To hand-edit a .rc-based GenMon, write the rc first, then hard-kill the panel so the session respawns it from your file:

pkill -9 -x xfce4-panel        # the session respawns it, reading your new rc

When driving any of this from a non-login shell, the panel's session vars (DISPLAY, DBUS_SESSION_BUS_ADDRESS) must be set — read them from /proc/$(pgrep -x xfce4-panel)/environ. fix-tray.sh does this for you.

Development

Tests are pure standard-library unittest (no dependencies). They sandbox all filesystem state into a temp dir and stub the network, so they never touch your real ~/.claude or contact the API:

python3 -m unittest -v        # or: pytest -v

CI runs the suite on every push and pull request (see .github/workflows/ci.yml).

Contributing

Contributions are welcome — please open a pull request:

  1. Fork the repo and create a branch.
  2. Make your change. Keep it dependency-free (standard library only) and match the existing style.
  3. Add or update tests, and make sure python3 -m unittest passes.
  4. Open a PR describing the change. For larger changes, opening an issue first to discuss is appreciated.

Bug reports and feature ideas are also welcome via GitHub issues.

Trademarks

"Anthropic" and "Claude" are trademarks of Anthropic. This is an unofficial, community project and is not affiliated with or endorsed by Anthropic. The bundled assets/anthropic.png is a simple stylised mark (regenerate it with assets/make-icon.py), not the official logo asset — set CLAUDE_TRAY_ICON to swap it for your own.

License

GNU Lesser General Public License v3.0 or later (LGPL-3.0-or-later). See COPYING.LESSER and COPYING for the full text.

Git hooks (local CI mirror)

.githooks/ mirrors .github/workflows/ci.yml so a red CI run is caught before it reaches GitHub, split by cost:

Hook Runs Why there
pre-commit lint only sub-second; it's the step CI runs first, and a lint failure blocks every later step
pre-push lint + tests + coverage gate the full mirror, at the last point a red run is still preventable

The hooks are version-controlled, but core.hooksPath is local git config — a fresh clone must opt in once:

git config core.hooksPath .githooks

Bypass a hook when you genuinely need to (--no-verify on either command):

git commit --no-verify    # e.g. committing a known-broken WIP on a branch
git push --no-verify

Missing tooling never blocks a commit: the hooks resolve ruff from .venv/, then PATH, then uvx, and skip with a warning if none is available (CI still enforces it).

About

Xfce panel widget showing live Claude Code 5h/weekly usage

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages