Waybar widget and tabbed TUI for AI plan usage across Anthropic Claude, OpenAI Codex/ChatGPT, Z.AI (GLM), OpenRouter, and DeepSeek.
This started as a Rust port of claudebar and stays drop-in compatible with it. It keeps the minimalist Pango-bordered tooltip, Omarchy theme auto-detection, and flock-protected OAuth refresh, then adds three more vendors and a proper testable codebase instead of one long shell script.
- Per-vendor Waybar modules with the same JSON shape as claudebar.
- Tabbed TUI (
ai-usagebar-tui) with Tab/h/l switching, per-tab refresh, and 60-second auto-refresh. Native ratatui widgets fill the available terminal width and keep the vendor tabs visually consistent. - Scroll-to-cycle on the bar: wire
on-scroll-up/on-scroll-down, and one bar item cycles through your enabled vendors. - Config-driven primary vendor: set
[ui] primaryonce; the widget shows that vendor by default and the TUI opens on its tab. - Local testing tools:
--prettyrenders ANSI-colored terminal output (auto-detects TTY), and--watch Nre-renders every N seconds. - Drop-in claudebar compatibility with the same flags (
--icon,--format,--tooltip-format,--pace-tolerance,--format-pace-color,--tooltip-pace-pts,--color-*) and{placeholders}. - Always exits 0, because Waybar hides modules that don't.
- Atomic cache writes + flock, so multi-monitor Waybar instances can coexist without API stampedes.
- Separate transient and hard errors: DNS/timeout failures show a quiet
Loading…; HTTP 4xx/5xx errors put the code in the tooltip. - Live API smoke tests:
make smokehits the real undocumented endpoints and catches schema drift early.
Two packages. Pick one:
yay -S ai-usagebar-bin # prebuilt binary from GitHub Releases (fast, ~5s install)
yay -S ai-usagebar # compiles from source (~30-60s, hermetic)The -bin variant downloads the same x86_64 ELF that CI built and tested. The source variant compiles locally with your toolchain. Both install identical binaries to /usr/bin/. If you already have one installed, switch with yay -S the other package; pacman handles the swap through conflicts/provides.
cargo install ai-usagebar # compile from source (needs rustup)
cargo binstall ai-usagebar # download prebuilt binary (needs cargo-binstall, no rustup)cargo binstall fetches the same x86_64 / aarch64 Linux tarball the AUR -bin package uses. Both install ai-usagebar + ai-usagebar-tui to ~/.cargo/bin/.
cargo build --release
sudo make install # → /usr/local/bin
# or
make install PREFIX=$HOME/.local # → ~/.local/binThe Waybar widget is Wayland-only and does not apply to Windows. The
ai-usagebar-tui binary, however, runs natively, and ai-usagebar --json
/ --pretty work too (handy for feeding a custom tray/widget). Build with a
standard Rust toolchain:
cargo build --release
# binaries land in target\release\ai-usagebar.exe and ai-usagebar-tui.exeCredentials are read from the Windows user profile rather than $HOME:
%USERPROFILE%\.claude\.credentials.json (Anthropic) and
%USERPROFILE%\.codex\auth.json (OpenAI Codex). Run the official claude /
codex CLI once on Windows to populate them, exactly as on Linux/macOS.
API-key vendors (Z.AI, OpenRouter, DeepSeek) work unchanged via environment
variables or config.toml.
Each vendor authenticates a little differently. Anthropic and OpenAI use OAuth credentials that their official CLIs already wrote to disk, so no env vars are needed. Z.AI and OpenRouter use API keys. You can pass those through env vars or, if you don't source secrets in your shell, put them inline in config.toml.
| Vendor | Method | Action required |
|---|---|---|
| Anthropic | OAuth, read from ~/.claude/.credentials.json (or the macOS login Keychain — see below) |
Run claude once to log in. Token auto-refreshes. |
| OpenAI | OAuth, read from ~/.codex/auth.json |
Run codex login once. Token auto-refreshes. |
| Z.AI | API key (ZAI_API_KEY env or [zai] api_key in config) |
Set either. |
| OpenRouter | API key (OPENROUTER_API_KEY env or [openrouter] api_key in config) |
Set either. |
| DeepSeek | API key (DEEPSEEK_API_KEY env or [deepseek] api_key in config) |
Set either. Disabled by default — add [deepseek] enabled = true to config. |
For each API-key vendor, ai-usagebar checks in this order:
- Env var named by
api_key_envin config (defaults:ZAI_API_KEY,OPENROUTER_API_KEY). If set + non-empty, used. - Inline
api_keyin the same config section. - Otherwise, error with a message naming both options.
- If you put inline
api_keyvalues in config,chmod 600 ~/.config/ai-usagebar/config.toml. The default behavior reads only env vars, which is safer when your config might be world-readable. - Don't commit your config dir if you check it into dotfiles unless you've redacted
api_keylines. - OAuth credential files (
~/.claude/.credentials.json,~/.codex/auth.json) are managed by their respective CLIs and already chmod-protected.
On macOS, recent Claude Code builds don't write ~/.claude/.credentials.json — they keep the same OAuth JSON in the login Keychain under the generic-password service Claude Code-credentials. ai-usagebar detects the missing file and transparently reads (and writes refreshed tokens back to) that Keychain item via the built-in security tool, so no manual step is needed. If the file does exist it still takes precedence, matching Linux.
~/.config/ai-usagebar/config.toml (optional — defaults enable Anthropic, OpenAI, Z.AI, and OpenRouter; DeepSeek is opt-in). Full example:
[ui]
# Which vendor the widget shows when --vendor is omitted, AND which tab
# is selected when the TUI opens. Defaults to anthropic when not set.
# primary = "anthropic" # anthropic | openai | zai | openrouter | deepseek
[anthropic]
enabled = true
# credentials_path = "/home/you/.claude/.credentials.json"
[openai]
enabled = true
# codex_auth_path = "/home/you/.codex/auth.json"
[zai]
enabled = true
api_key_env = "ZAI_API_KEY"
# api_key = "..." # used if ZAI_API_KEY is unset; chmod 600 the file!
# plan_tier = "lite" # lite | pro | max — display-only
[openrouter]
enabled = true
api_key_env = "OPENROUTER_API_KEY"
# api_key = "sk-or-v1-..."
[deepseek]
enabled = true # disabled by default; enable once you add an API key
api_key_env = "DEEPSEEK_API_KEY"
# api_key = "sk-..." # used if DEEPSEEK_API_KEY is unset; chmod 600 the file!# Local testing — auto-detects TTY and renders human-readable output.
ai-usagebar # uses [ui] primary (defaults to anthropic)
ai-usagebar --vendor openai
ai-usagebar --vendor zai
ai-usagebar --vendor openrouter
ai-usagebar --vendor deepseek
# Force Waybar JSON (e.g. piping into jq).
ai-usagebar --json
# Live preview while iterating on --format / --tooltip-format.
ai-usagebar --vendor openrouter --watch 5
# Interactive TUI with tabs.
ai-usagebar-tuiThe two binaries are independent. If you don't run Waybar (or just want to check usage occasionally rather than have it on your bar permanently), ai-usagebar-tui works as a fully standalone terminal app:
ai-usagebar-tui # opens in your current terminalIt runs in any terminal emulator (Kitty, Alacritty, Foot, Ghostty, etc.), works in plain SSH sessions, and doesn't need a compositor or window manager integration. All controls and the Settings overlay work the same way. Use it as:
- An ad-hoc check ("am I close to my Claude weekly limit before I start a long session?")
- A foreground monitor on a secondary screen or tmux pane while you code
- A shell-only tool on remote machines (just install the binary; no Waybar/Hyprland dependencies)
The Waybar widget is optional. The TUI is the best way to see every enabled vendor at once, even if you never set up the widget.
Use one bar item and scroll through your vendors. The TUI on-click still shows them all:
The {vendor_short} placeholder always expands to a 3-letter vendor ID (cld / gpt / zai / opr / dsk), so the bar text tells you which vendor is active. The other usage placeholders ({session_pct} for Anthropic, {oai_session_pct} for OpenAI, etc.) are vendor-specific. If you want one format string for every cycled vendor, prefer the generic placeholders where available. For now, {session_pct} works for Anthropic only; the other vendors expose their own {oai_*} / {zai_*} / {or_*} / {ds_*} families, which expand to empty strings for vendors that don't define them.
signal: 13 lets the scroll-cycle commands refresh the bar instantly (via SIGRTMIN+13) instead of waiting for the next 300s interval.
If your Waybar theme puts a tray expander immediately after custom/aibar, such as Omarchy's group/tray-expander with custom/expand-icon, the usage text can sit very close to the expand icon. Add right padding for the module in your Waybar CSS if you want extra spacing:
#custom-aibar {
padding-right: 18px;
}If you'd rather see them all at once:
"modules-right": ["custom/claude", "custom/openai", "custom/openrouter", "custom/zai", "custom/deepseek"],
"custom/claude": {
"exec": "ai-usagebar --vendor anthropic --icon ''",
"return-type": "json",
"interval": 300,
"tooltip": true,
"on-click": "ai-usagebar-tui"
},
"custom/openai": {
"exec": "ai-usagebar --vendor openai --icon ''",
"return-type": "json",
"interval": 300,
"tooltip": true
},
"custom/openrouter": {
"exec": "ai-usagebar --vendor openrouter --icon '' --format '{or_balance} · {or_used_today}'",
"return-type": "json",
"interval": 600,
"tooltip": true
},
"custom/zai": {
"exec": "ai-usagebar --vendor zai --icon ''",
"return-type": "json",
"interval": 300,
"tooltip": true
},
"custom/deepseek": {
"exec": "ai-usagebar --vendor deepseek --icon ''",
"return-type": "json",
"interval": 600,
"tooltip": true
}Why 300s? The Anthropic and OpenAI Codex endpoints are undocumented and rate-limit aggressively below ~300s. The cache TTL is 60s so multi-monitor instances coexist, but Waybar's polling interval should stay at 300s.
By default Hyprland tiles the TUI. To make ai-usagebar-tui open as a centered floating window, the same way Omarchy floats its own settings TUIs (Wi-Fi/impala, audio/wiremix, Bluetooth/bluetui), add this to ~/.config/hypr/hyprland.conf or any sourced .conf, such as looknfeel.conf:
# ai-usagebar TUI — float + center + fixed size. omarchy-launch-tui sets the
# app-id from the binary basename, so the class is org.omarchy.ai-usagebar-tui.
# 875x600 matches the size Omarchy gives its own `floating-window`-tagged TUIs.
windowrule = float on, match:class ^(org\.omarchy\.ai-usagebar-tui)$
windowrule = center on, match:class ^(org\.omarchy\.ai-usagebar-tui)$
windowrule = size 875 600, match:class ^(org\.omarchy\.ai-usagebar-tui)$Then hyprctl reload (no logout needed).
Omarchy tags a hardcoded list of TUI app-ids with
floating-windowin~/.local/share/omarchy/default/hypr/apps/system.conf, which then appliesfloat + center + size 875 600. The rules above set those values directly, so the size is deterministic regardless of which config is sourced first. If you launch the TUI differently (e.g.kitty -e ai-usagebar-tui), replace the class regex with whateverhyprctl clientsreports for your terminal.
Hyprland 0.46+ uses the unified
windowrulekeyword withmatch:…filters. The olderwindowrulev2 = …, class:…syntax still works on legacy Hyprland but is deprecated — use the form above on current Omarchy / Hyprland releases.
| Vendor | Endpoint | What you see |
|---|---|---|
| Anthropic | api.anthropic.com/api/oauth/usage (undocumented) |
Session (5h), Weekly (7d), Sonnet (7d), Extra usage $ |
| OpenAI | chatgpt.com/backend-api/wham/usage (undocumented; used by official codex CLI) |
Codex 5h, Codex weekly, Code-review weekly, Credits |
| Z.AI | api.z.ai/api/monitor/usage/quota/limit (undocumented) |
Session 5h, Weekly 7d, MCP tools monthly |
| OpenRouter | openrouter.ai/api/v1/{credits,key} (documented) |
Balance, today/week/month spend, free vs paid tier |
Three of the four endpoints are undocumented. The Anthropic and OpenAI endpoints are used by their official CLIs (claude and codex), so removing them would break those tools too. That makes them less shaky than scraped web endpoints. Z.AI's monitor endpoint is reverse-engineered from a third-party plugin; treat it as the most fragile one.
When an endpoint drifts, run make smoke. The live API tests check the exact fields this project depends on and produce a precise failure pointing at what changed. Paste the failure back into Claude Code and the affected types.rs can usually be updated mechanically.
| Placeholder | Example |
|---|---|
{plan} |
Max 5x |
{session_pct}, {session_reset}, {session_bar}, {session_elapsed} |
62, 1h 30m, █████████████░░░░░░░, 58 |
{session_pace}, {session_pace_indicator}, {session_pace_pct}, {session_pace_pts}, {session_pace_delta}, {session_pace_abs_delta} |
↑, ↑, 12% ahead, 4pts ahead, 4, 4 |
{weekly_*} |
same family for the 7d window |
{sonnet_*} |
same family for the 7d Sonnet window (empty when absent) |
{extra_spent}, {extra_limit}, {extra_pct}, {extra_bar} |
$2.50, $50.00, 5, █░░░░░░░░░░░░░░░░░░░ |
{oai_plan}, {oai_session_pct}, {oai_session_reset}, {oai_session_elapsed}, {oai_session_pace}, {oai_session_pace_indicator}, {oai_weekly_*} (same family), {oai_code_review_pct}, {oai_credit_balance}, {oai_local_msgs}, {oai_cloud_msgs}
{zai_plan}, {zai_session_pct}, {zai_session_reset}, {zai_weekly_pct}, {zai_weekly_reset}, {zai_mcp_pct}, {zai_mcp_reset}
{or_label}, {or_balance}, {or_total}, {or_used}, {or_used_today}, {or_used_week}, {or_used_month}, {or_consumed_pct}, {or_free_tier}, {or_limit}, {or_limit_remaining}, {or_balance_bar}
{ds_balance}, {ds_granted}, {ds_topped_up}, {ds_available} — credit balance from /user/balance. USD is preferred when both currencies are present; falls back to CNY otherwise.
ai-usagebar --watch 5 # iterate on --format live
ai-usagebar --vendor openrouter --format '{or_balance} · today {or_used_today}'
make test # unit + integration
source ~/.config/zsh/secrets # only needed for live smoke
make smoke # live API drift detection
make clippy # cargo clippy -D warningsTab/l/→— next tabShift+Tab/h/←— previous tabr— refresh active tabR— refresh all tabss— open Settings overlay (primary vendor + API keys)q/Esc/Ctrl-C— quit
Auto-refresh runs every 60 seconds in the background. Vendors use the same layout. Here's OpenRouter showing the credit balance gauge (red because 98% is consumed), usage-by-period totals, and tier:
Press s while the TUI is open. The overlay lets you:
- Pick the primary vendor that the widget defaults to and that the TUI selects on startup. Use
←/→to cycle. - Enter your Z.AI API key and OpenRouter API key inline. Keys are masked as you type; press
Ctrl-Vto reveal or hide them. Env vars (ZAI_API_KEY,OPENROUTER_API_KEY) still win at runtime if they're set; the inline key is the fallback.
Key bindings inside the overlay:
Tab/↑↓— move between fields←/→— cycle primary-vendor selection (only on the vendor field)Ctrl-V— toggle key visibility on the focused key fieldCtrl-S— save and closeEsc— discard and close
Save writes to ~/.config/ai-usagebar/config.toml via toml_edit so your existing comments and unrelated fields are preserved. The file is automatically chmod 600ed on save, so inline keys aren't world-readable.
After save, the Settings overlay fires SIGRTMIN+13 so any Waybar module configured with signal: 13 refreshes immediately. You don't need to wait for the next 300-second interval or kick the bar by hand. The TUI's own tabs also re-fetch right away, so a freshly set API key takes effect on the spot.
If your module doesn't use signal: 13, the signal is a no-op and the bar will refresh on its next normal tick (up to interval seconds away). To force-refresh manually: pkill -SIGUSR2 waybar (full reload).
- One Dark palette by default.
- Auto-merges with the active Omarchy theme at
~/.config/omarchy/current/theme/colors.toml. - Per-color overrides:
--color-low,--color-mid,--color-high,--color-critical(claudebar-compatible).
See CHANGELOG.md for the release history. Each release also has its own page at https://github.com/akitaonrails/ai-usagebar/releases with the auto-generated install snippet and checksum.
The OpenAI and Anthropic OAuth endpoint references came from claudebar and codexbar, both by mryll. The visual design, including the bordered Pango tooltip, severity colors, and pacing math, is theirs. This project is a Rust port with multi-vendor support.
MIT.



