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 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 window7d 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.
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. |
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.
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.shInstalling 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.pyIt 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). AFont=(none)value renders the label invisibly while still drawing the widget.
To remove the widget later: right-click it → Remove.
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.
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
claudeCLI, 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_POLLseconds (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.
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.
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).
- The widget reads your Claude OAuth token from
~/.claude/.credentials.jsonand sends it only tohttps://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
sudois just to install the GenMon package).
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.shIt 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 rcWhen 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.
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 -vCI runs the suite on every push and pull request (see
.github/workflows/ci.yml).
Contributions are welcome — please open a pull request:
- Fork the repo and create a branch.
- Make your change. Keep it dependency-free (standard library only) and match the existing style.
- Add or update tests, and make sure
python3 -m unittestpasses. - 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.
"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.
GNU Lesser General Public License v3.0 or later (LGPL-3.0-or-later).
See COPYING.LESSER and COPYING for the full
text.
.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 .githooksBypass 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-verifyMissing 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).
