A Waybar module that shows your Claude Code rate limits, today's tokens and spend, and live session stats — on the same bar you already have open.

Hover — compact Pango tooltip anchored to the bar icon

Click — GTK4 + libadwaita popover with live session stats
Claude Code's /usage is in-terminal only; close the shell and the numbers are gone. ccusage is a good CLI but it doesn't live on your panel. The nice menu-bar app for this on macOS is CCSeva, which has no Linux build. So: a waybar module that reads the rate_limits block from Claude Code's official statusline stdin (v2.1.80+, shipped 2026-03-19) and pairs it with ccusage daily --json for today's tokens and dollar cost.
Built on Arch Linux / Hyprland / Omarchy; nothing is Omarchy-specific except the optional theme colour harvest.
The bar label is 41%·11% — five-hour on the left, seven-day on the right, green while you're fine, amber near 80%, rust past 100%.
Hover for a Pango panel: progress bars, reset countdowns, today's tokens and dollar cost, and the active session's cost / context % / lines added or removed. Click for a GTK4 + libadwaita popover that anchors under the icon, refreshes every second, and has a Settings link that opens config.toml in $EDITOR.
The status chip at the top (LIVE, IDLE, CRIT, OVER) comes from real state — no guessed plan tier, no synthesised "Pro" badge.
Chrome colours follow your Omarchy theme: the popover reads ~/.config/omarchy/current/theme/waybar.css and matches whatever you have active. Accent colours stay baked because harvesting them from alacritty's ANSI slots painted healthy usage red on themes that don't invert the semantics. That bug was real; this is the fix.
git clone https://github.com/infiniV/claude-usage-waybar.git
cd claude-usage-waybar
./install.shOr the one-liner once the repo is public:
curl -fsSL https://raw.githubusercontent.com/infiniV/claude-usage-waybar/main/install.sh | bashThe installer is idempotent. All edits to your existing waybar/config.jsonc, waybar/style.css, and ~/.claude/settings.json are guarded by >>> claude-usage-waybar >>> markers so ./uninstall.sh can back them out cleanly. If you already had a statusLine.command, it's chained (our emitter pipes the same stdin to it) and restored on uninstall.
| Tool | Required | Why |
|---|---|---|
waybar |
yes | the host |
python3 ≥ 3.11 |
yes | emitter + popup |
jq |
yes | config patching |
| Claude Code ≥ 2.1.80 | yes | earlier builds don't emit rate_limits |
| A Nerd Font | yes | for the glyph |
python-gobject + gtk4 + libadwaita |
optional | only for the click popover |
gtk4-layer-shell |
optional | anchors the popover under the bar icon instead of centring it |
ccusage (or bunx / pnpm / npx) |
optional | fills in today's tokens + cost; without it the Today row stays empty |
On fresh Arch: sudo pacman -S waybar jq python python-gobject gtk4 libadwaita gtk4-layer-shell ttf-jetbrains-mono-nerd.
If you fork: waybar, waybar-module, claude-code, anthropic, omarchy, hyprland, arch-linux, ccusage.
~/.config/claude-usage/config.toml. Every key is optional; defaults live in bin/claude_usage.py's DEFAULT_CONFIG.
[display]
format = " {five_hour}%·{seven_day}%" # waybar label; placeholders: {icon}, {five_hour}, {seven_day}
icon = ""
critical_threshold = 80 # % that flips the bar to `.critical`
exhausted_threshold = 100 # % that flips the bar to `.exhausted`
[behavior]
stale_after_seconds = 300 # after this long with no statusline turn, grey out + "idle"
[tooltip]
show_progress_bars = true
show_today = true
show_updated_ago = true
bar_width = 10
[statusline]
# Recorded automatically if you had a pre-existing statusLine.command.
chained_command = ""Nothing in that file is decorative; every key is read by at least one script. tests/test_no_fluff.py::test_config_keys_are_all_consumed fails CI if an orphan key sneaks back in.
Claude Code turn systemd --user timer
│ (every 5 min)
│ stdin JSON │
▼ ▼
claude-usage-statusline claude-usage-today
(writes rate_limits + (runs ccusage daily --json
session snapshot) and writes today.tokens + cost)
│ │
└───────────┬───────────────────────────┘
▼
~/.cache/claude-usage/state.json (atomic writes, single source of truth)
│
┌───────────┴────────────┐
▼ ▼
claude-usage-bar claude-usage-popup
(waybar interval=15s) (waybar on-click)
Two data sources, one state file. The statusline emitter fires every Claude Code turn (so the percentages are always fresh while you're using it); a systemd user timer runs ccusage every 5 minutes (so Today's cost doesn't only update when you open Claude Code). The bar and popup both read state.json — no timers of their own, no API calls, no network.
~/.cache/claude-usage/state.json:
{
"schema": 1,
"updated_at": 1776214800,
"source": "statusline",
"five_hour": { "pct": 41, "resets_at": 1776225600 },
"seven_day": { "pct": 11, "resets_at": 1776517200 },
"today": {
"tokens": 13583603,
"cost_usd": 13.92,
"models": ["opus-4", "haiku-4-5"]
},
"session": {
"id": "…",
"model_id": "claude-opus-4-6[1m]",
"model_name": "Opus 4.6 (1M context)",
"cost_usd": 14.49,
"lines_added": 2913,
"lines_removed": 272,
"input_tokens": 94467,
"output_tokens": 134075,
"context_pct": 23
}
}Every field here is pulled verbatim from either Claude Code's statusline stdin (see bin/claude-usage-statusline:extract_session) or ccusage daily --json (bin/claude-usage-today). Fields you might expect but won't find — messages, plan_tier, cwd, session_duration — are absent because there's no honest source for them, not because we forgot.
./uninstall.shReverts every marker-guarded edit, disables the systemd timer, and removes the scripts and cache. Your ~/.config/claude-usage/config.toml is left alone in case you're coming back; delete it by hand if you want to start fresh.
python3 -m pytest tests -q # 34 tests, zero network, pytest required
./install.sh --dry-run # prints every action without writing anything
./bin/claude-usage-bar # dumps waybar JSON against your current state
./bin/claude-usage-popup # launches the popover out-of-bandFixtures in tests/fixtures/ are verbatim captures of real Claude Code statusline stdin (v2.1.108) and real ccusage daily --json output (v18). If Anthropic changes the statusline schema or ccusage renames a field, the tests fail before your users do.
Two tests worth knowing about:
test_no_fluff.py— walksDEFAULT_CONFIGandEMPTY_STATEand fails if any key is documented but unread, or stored but unrendered. Also scans every user-facing file for a blocklist of words from previous-life features (messages,PRO plan, dead config knobs) so they can't creep back in. Backticked mentions like these are exempt; prose resurrection is not.test_palette.py— feeds adversarial theme files toload_palette()and asserts the accent/warn/crit/danger colours never come from ANSI slots, no matter what the theme does. A past version harvested accents from alacritty and painted healthy usage red on non-IBM themes; this test exists so that never happens again.
PRs welcome. Anything that adds a state field should also add a consumer; test_no_fluff will fail otherwise.
MIT — see LICENSE.