# ai-usagebar
Waybar widget and tabbed TUI for AI plan usage across **Anthropic Claude**, **OpenAI Codex/ChatGPT**, **Z.AI (GLM)**, **OpenRouter**, **DeepSeek**, and **Kimi**.
This started as a Rust port of [`claudebar`](https://github.com/mryll/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 five more vendors and a proper testable codebase instead of one long shell script.

## Features
- **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.
- **Optional local Claude Code context monitor** in the TUI, with a bounded,
compaction-aware view of recent session input-context usage.
- **Native desktop integrations** for GNOME Shell and the macOS menu bar, with selectors for Anthropic, OpenAI, Z.AI, OpenRouter, and DeepSeek.
- **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] primary` once; the widget shows that vendor by default and the TUI opens on its tab.
- **Local testing tools**: `--pretty` renders ANSI-colored terminal output (auto-detects TTY), and `--watch N` re-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 smoke` hits the real undocumented endpoints and catches schema drift early.
## Install
### Arch (AUR)
Two packages. Pick one:
```bash
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`.
### Other Linux / macOS (crates.io)
```bash
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/`.
### From source
```bash
cargo build --release
sudo make install # → /usr/local/bin
# or
make install PREFIX=$HOME/.local # → ~/.local/bin
```
### Windows
The **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:
```powershell
cargo build --release
# binaries land in target\release\ai-usagebar.exe and ai-usagebar-tui.exe
```
Credentials 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 work unchanged via environment variables or `config.toml`.
## Authentication
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.** Every other vendor uses an API key. 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. |
| Anthropic (API) | Console Admin key (`ANTHROPIC_ADMIN_KEY` env or `[anthropic_api] api_key` in config) | Set either. Opt-in. This is an organization Admin key (`sk-ant-admin01-…`), not an inference key or Claude Code OAuth credential. |
| 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. Opt-in — see below. |
| Kimi | API key (`KIMI_API_KEY` env or `[kimi] api_key` in config) | Set either. Opt-in — see below. |
| Kilo | API key (`KILO_API_KEY` env or `[kilo] api_key` in config) | Set either. Opt-in. For a team balance, also set `[kilo] organization_id`; omit it for the personal balance. |
| Novita | API key (`NOVITA_API_KEY` env or `[novita] api_key` in config) | Set either. Opt-in. |
| Moonshot | API key (`MOONSHOT_API_KEY` env or `[moonshot] api_key` in config) | Set either. Opt-in. Set `[moonshot] region = "cn"` for `api.moonshot.cn` (balance in CNY); the default `"global"` uses `api.moonshot.ai` (USD). |
| Grok (xAI) | **Management** key (`XAI_MANAGEMENT_KEY` env or `[grok] api_key` in config) | Set either. Opt-in. This is **not** the inference key — create it under xAI Console → Management keys. See the team note below. |
#### Grok: team-scoped vs organization-scoped keys
The balance lives at `/v1/billing/teams/{team}/prepaid/balance`, so a team has to
be identified. With a **team-scoped** management key the team is read
automatically from the key. With an **organization-scoped** key it cannot be —
that key's `scopeId` is an *organization* id, not a team — so set the team
explicitly:
```toml
[grok]
team_id = "your-team-id"
```
Without it, an organization-scoped key reports an error saying exactly this
rather than silently querying the wrong URL.
### Enabling a vendor
`enabled = true` is what makes a vendor fetch. Anthropic (API), DeepSeek, Kimi,
Kilo, Novita, Moonshot, and Grok all default to **disabled** so that existing
installs are unaffected until you opt in. Two ways to do it:
- **Via the TUI Settings overlay** (`ai-usagebar-tui`, then `s`): saving a
non-empty API key sets that vendor's `enabled = true` for you. Clearing the
field again removes the inline key from `config.toml`.
- **By hand**: add `enabled = true` to the vendor's section alongside the key.
The primary-vendor selector only offers vendors that are currently enabled, so a
vendor you haven't opted into cannot be set as primary.
### Credential resolution order (for API-key vendors)
For each API-key vendor, ai-usagebar checks in this order:
1. **Env var named by `api_key_env`** in config (defaults: `ANTHROPIC_ADMIN_KEY`, `ZAI_API_KEY`, `OPENROUTER_API_KEY`, `DEEPSEEK_API_KEY`, `KIMI_API_KEY`, `KILO_API_KEY`, `NOVITA_API_KEY`, `MOONSHOT_API_KEY`, `XAI_MANAGEMENT_KEY`). If set + non-empty, used.
2. **Inline `api_key`** in the same config section.
3. Otherwise, **error** with a message naming both options.
### Security
- If you put inline `api_key` values 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_key` lines.
- OAuth credential files (`~/.claude/.credentials.json`, `~/.codex/auth.json`) are managed by their respective CLIs and already chmod-protected.
#### macOS: Anthropic credentials in the Keychain
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.
## Configuration
`~/.config/ai-usagebar/config.toml` (optional — defaults enable Anthropic, OpenAI, Z.AI, and OpenRouter; all other vendors are opt-in). Full example:
```toml
[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.
# Only a vendor that is enabled can be primary.
# primary = "anthropic" # anthropic | anthropic_api | openai | zai
# # | openrouter | deepseek | kimi | kilo | novita
# # | moonshot | grok
[context]
enabled = false # opt in, then press c in ai-usagebar-tui
# projects_path = "~/.claude/projects"
# context_window_tokens = 200000 # optional fallback denominator
# [context.model_context_window_tokens]
# "claude-opus-4-6" = 1000000 # exact model id overrides the fallback
[anthropic]
enabled = true
# credentials_path = "/home/you/.claude/.credentials.json"
[anthropic_api]
enabled = true # disabled by default; requires an organization Admin key
api_key_env = "ANTHROPIC_ADMIN_KEY"
# api_key = "sk-ant-admin01-..." # not an inference key; chmod 600 if inline
# monthly_limit = 1000 # optional positive, finite USD display limit
[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!
[kimi]
enabled = true # disabled by default; enable once you add an API key
api_key_env = "KIMI_API_KEY"
# api_key = "sk-..." # used if KIMI_API_KEY is unset; chmod 600 the file!
# --- Account-balance vendors (all opt-in) ---
[kilo]
enabled = true # disabled by default; enable once you add an API key
api_key_env = "KILO_API_KEY"
# api_key = "..." # used if KILO_API_KEY is unset; chmod 600 the file!
# organization_id = "org_..." # team balance; omit for the personal balance
[novita]
enabled = true # disabled by default; enable once you add an API key
api_key_env = "NOVITA_API_KEY"
# api_key = "..." # used if NOVITA_API_KEY is unset; chmod 600 the file!
[moonshot]
enabled = true # disabled by default; enable once you add an API key
api_key_env = "MOONSHOT_API_KEY"
# api_key = "sk-..." # used if MOONSHOT_API_KEY is unset; chmod 600 the file!
# region = "global" # global → api.moonshot.ai (USD) | cn → api.moonshot.cn (CNY)
[grok]
enabled = true # disabled by default; enable once you add an API key
# The xAI *Management* key, NOT the inference key.
api_key_env = "XAI_MANAGEMENT_KEY"
# api_key = "..." # used if XAI_MANAGEMENT_KEY is unset; chmod 600 the file!
# Required for organization-scoped keys; auto-resolved for team-scoped ones.
# team_id = "..."
```
## Quick start
```bash
# Local testing — auto-detects TTY and renders human-readable output.
ai-usagebar # uses [ui] primary (defaults to anthropic)
ai-usagebar --vendor anthropic_api
ai-usagebar --vendor openai
ai-usagebar --vendor zai
ai-usagebar --vendor openrouter
ai-usagebar --vendor deepseek
ai-usagebar --vendor kimi
# 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-tui
```
## Standalone TUI — no Waybar required
The 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:
```bash
ai-usagebar-tui # opens in your current terminal
```
It 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.
## Native desktop integrations (v0.13)
The [GNOME Shell extension](gnome-extension/README.md) and [macOS menu bar app](macos/README.md) support selectors for **Anthropic, OpenAI, Z.AI, OpenRouter, and DeepSeek**. **Kimi is widget/TUI-only in v0.13**; do not select it in either native desktop integration yet. Desktop protocol and marker parity for Kimi is dedicated future work.
## Waybar config
### Single module, scroll-to-cycle (recommended)
Use one bar item and scroll through your vendors. The TUI on-click still shows them all:
```jsonc
"modules-right": ["custom/aibar", ...],
"custom/aibar": {
"exec": "ai-usagebar --format '{vendor_short} {session_pct}% · {session_reset}'",
"return-type": "json",
"interval": 300,
"signal": 13,
"tooltip": true,
"on-click": "ai-usagebar-tui",
"on-scroll-up": "ai-usagebar --cycle-next",
"on-scroll-down": "ai-usagebar --cycle-prev"
}
```
The `{vendor_short}` placeholder always expands to a 3-letter vendor ID (`cld` / `gpt` / `zai` / `opr` / `dsk` / `kmi`), 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 aliases: `{session_pct}`, `{session_reset}`, `{weekly_pct}`, and `{weekly_reset}` are implemented by all six vendors (Anthropic, OpenAI, Z.AI, OpenRouter, DeepSeek, and Kimi; OpenRouter and DeepSeek use `0` / `—` for the windows they don't expose). Anthropic and OpenAI add `*_elapsed`, `*_pace`, and `*_bar` families; each vendor also exposes its own `{oai_*}` / `{zai_*}` / `{or_*}` / `{ds_*}` / `{kimi_*}` 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:
```css
#custom-aibar {
padding-right: 18px;
}
```
### Per-vendor modules
If you'd rather see them all at once:
```jsonc
"modules-right": ["custom/claude", "custom/openai", "custom/openrouter", "custom/zai", "custom/deepseek", "custom/kimi"],
"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
},
"custom/kimi": {
"exec": "ai-usagebar --vendor kimi --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.
### Multiple accounts (advanced)
To watch **more than one account of the same vendor** — say a personal and a
work Claude subscription — run one module per account, giving each its own
credentials file and its own cache directory:
```jsonc
"modules-right": ["custom/claude-personal", "custom/claude-work", ...],
"custom/claude-personal": {
"exec": "ai-usagebar --vendor anthropic --icon '' --format 'p {session_pct}% · {session_reset}'",
"return-type": "json",
"interval": 300,
"tooltip": true
},
"custom/claude-work": {
"exec": "ai-usagebar --vendor anthropic --icon '' --format 'w {session_pct}% · {session_reset}' --creds-path ~/.config/ai-usagebar/accounts/work.credentials.json --cache-dir ~/.cache/ai-usagebar/anthropic-work",
"return-type": "json",
"interval": 300,
"tooltip": true
}
```
- `--creds-path` points the module at a different OAuth credentials file
(same JSON shape Claude Code writes). To capture a second account's file,
log in with `claude` under that account and copy
`~/.claude/.credentials.json` somewhere stable — token refreshes are
written back to whatever file the flag names, so each account keeps
itself alive independently. `chmod 600` the copies.
- `--cache-dir` gives the module a private cache so the two accounts don't
overwrite each other's 60-second cache window. Any directory works; the
per-vendor default is `~/.cache/ai-usagebar/<vendor>`.
- `--creds-path` currently applies to the **Anthropic vendor only**. For
API-key vendors (Z.AI, OpenRouter, DeepSeek, Kimi) point each module at a
different key via a wrapper script that sets the env var, plus its own
`--cache-dir`.
- The TUI shows the default Claude tab plus one tab per configured
`[[anthropic.accounts]]` entry (see the config example below); Tab / `h` / `l`
cycle through them like any other tab.
- On macOS, the login Keychain can hold only one Claude credential per OS
user, so additional accounts must be file-based as shown above.
#### Config-driven accounts (`--account`)
Instead of repeating `--creds-path`/`--cache-dir` on every module, name your
extra Anthropic accounts once in config and select them with `--account
<label>`:
```toml
[anthropic]
# The default account. `--vendor anthropic` with no `--account` uses this,
# exactly as before. Optional — falls back to ~/.claude/.credentials.json.
# credentials_path = "~/.claude/.credentials.json"
[[anthropic.accounts]]
label = "work"
credentials_path = "~/.config/ai-usagebar/accounts/work.json"
[[anthropic.accounts]]
label = "personal"
credentials_path = "~/.config/ai-usagebar/accounts/personal.json"
```
```jsonc
"custom/claude-work": {
"exec": "ai-usagebar --vendor anthropic --account work --format 'w {session_pct}% · {session_reset}'",
"return-type": "json",
"interval": 300,
"tooltip": true
}
```
- The **default account** is the singular `[anthropic] credentials_path` (or the
platform default file). `--vendor anthropic` without `--account` uses it, with
the same output and the same `~/.cache/ai-usagebar/anthropic/` cache as today.
- Each `--account <label>` gets an isolated cache at
`~/.cache/ai-usagebar/anthropic/<label>/` automatically — no `--cache-dir`
needed. Only *extra* accounts get a subdir; the default never moves.
- `--account` is Anthropic-only and can't be combined with `--creds-path` (both
name a credentials file). A typo'd label fails loudly, listing the known ones.
- **`ai-usagebar-tui`** reads the same `[[anthropic.accounts]]` and shows one
tab per account (after the default Claude tab), so the config above wires up
the widget and the TUI at once.
## Hyprland: float the TUI window
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`:
```ini
# 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-window` in `~/.local/share/omarchy/default/hypr/apps/system.conf`, which then applies `float + 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 whatever `hyprctl clients` reports for your terminal.
> Hyprland 0.46+ uses the unified `windowrule` keyword with `match:…` filters. The older `windowrulev2 = …, class:…` syntax still works on legacy Hyprland but is deprecated — use the form above on current Omarchy / Hyprland releases.
## Vendor support matrix
| Vendor | Endpoint | What you see | Native desktop selector (v0.13) |
|---|---|---|---|
| **Anthropic** | `api.anthropic.com/api/oauth/usage` (undocumented) | Session (5h), Weekly (7d), model-scoped weekly (e.g. Fable), Extra usage $ | Yes |
| **OpenAI** | `chatgpt.com/backend-api/wham/usage` (undocumented; used by official `codex` CLI) | Codex 5h, Codex weekly, Code-review weekly, Credits | Yes |
| **Z.AI** | `api.z.ai/api/monitor/usage/quota/limit` (undocumented) | Session 5h, Weekly 7d, MCP tools monthly | Yes |
| **OpenRouter** | `openrouter.ai/api/v1/{credits,key}` (documented) | Balance, today/week/month spend, free vs paid tier | Yes |
| **DeepSeek** | `api.deepseek.com/user/balance` (documented) | Balance, granted, topped-up credits | Yes |
| **Kimi** | `api.kimi.com/coding/v1/usages` (undocumented; community-confirmed) | Weekly subscription quota + 5h rolling rate-limit window | No — widget/TUI only; desktop protocol and marker parity are future work |
| **Kilo** | `api.kilo.ai/api/profile/balance` (undocumented; extension-internal) | Remaining credit balance ($) | No — widget/TUI only |
| **Novita** | `api.novita.ai/openapi/v1/billing/balance/detail` (documented) | Remaining credit balance ($) | No — widget/TUI only |
| **Moonshot** | `api.moonshot.ai\|.cn/v1/users/me/balance` (documented) | Account balance ($ on `.ai`, ¥ on `.cn`) | No — widget/TUI only |
| **Grok (xAI)** | `management-api.x.ai/v1/billing/teams/{team}/prepaid/balance` (Management API; documented) | Prepaid credit balance ($) | No — widget/TUI only |
| **Anthropic (API)** | `api.anthropic.com/v1/organizations/cost_report` (Admin API; documented) | Month-to-date spend ($, excludes Priority Tier), optional spend-vs-limit % | No — widget/TUI only |
### Endpoint stability
Four of the six 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. Kimi's `/coding/v1/usages` is community-confirmed and used by third-party quota tools; treat it as drift-prone.
When an endpoint drifts, **run `make smoke`**. It runs all ignored vendor tests, so the existing Anthropic, OpenAI, Z.AI, and OpenRouter smoke tests still require their respective OAuth credentials or API keys. Kimi alone is optional: its test skips with a diagnostic when `KIMI_API_KEY` is unset, or run it alone with `cargo test --test live kimi_live -- --ignored --nocapture`. The live API tests check the exact fields this project depends on and produce a precise failure pointing at what changed. Paste a failure back into Claude Code and the affected `types.rs` can usually be updated mechanically.
## Format placeholders
### Shared / Anthropic (claudebar-compatible)
| 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) |
| `{scoped_model}`, `{scoped_pct}`, `{scoped_reset}`, `{scoped_elapsed}`, `{scoped_bar}` | `Fable`, `84`, `5d 2h`, `27`, `█████████████████░░░` — first model-scoped weekly window (neutral empty/`0`/`—` when absent) |
| `{extra_spent}`, `{extra_limit}`, `{extra_pct}`, `{extra_bar}` | `$2.50`, `$50.00`, `5`, `█░░░░░░░░░░░░░░░░░░░` |
### OpenAI (Codex OAuth)
`{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}`
### Z.AI
`{zai_plan}`, `{zai_session_pct}`, `{zai_session_reset}`, `{zai_weekly_pct}`, `{zai_weekly_reset}`, `{zai_mcp_pct}`, `{zai_mcp_reset}`
### OpenRouter
`{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}`
### DeepSeek
`{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.
### Kimi
`{kimi_plan}`, `{kimi_weekly_pct}`, `{kimi_weekly_used}`, `{kimi_weekly_limit}`, `{kimi_weekly_remaining}`, `{kimi_weekly_reset}`, `{kimi_window_pct}`, `{kimi_window_used}`, `{kimi_window_limit}`, `{kimi_window_remaining}`, `{kimi_window_reset}` — subscription quota + rolling rate-limit window from `api.kimi.com/coding/v1/usages`. Generic aliases `{plan}` (plan), `{weekly_pct}` (weekly usage), and `{session_pct}` (5h window usage) are also available.
### Kilo
`{kilo_balance}` — remaining credit balance (USD) from `api.kilo.ai/api/profile/balance`.
### Novita
`{nv_balance}`, `{nv_cash}`, `{nv_credit_limit}`, `{nv_owed}` — account balance and breakdown (USD) from `api.novita.ai/openapi/v1/billing/balance/detail`.
### Moonshot
`{km_balance}`, `{km_voucher}`, `{km_cash}`, `{currency}` — account balance from `api.moonshot.ai|.cn/v1/users/me/balance` (USD on `.ai`, CNY on `.cn`).
### Grok
`{grok_balance}` — prepaid credit balance (USD) from the xAI Management API (`management-api.x.ai`).
### Anthropic (API)
`{aapi_headline}`, `{aapi_spent}`, `{aapi_limit}`, `{aapi_pct}` — month-to-date spend for the API/Console account from the Admin API `cost_report`. The headline is `$1.34 / $1000 · 0%` when a positive, finite `monthly_limit` is set in config, `$1.34/mo` otherwise. Generic aliases `{plan}`, `{session_pct}`, and `{weekly_pct}` are also available (the last two both map to the spend-vs-limit %).
> **Two things this figure is not.** It is **spend**, not remaining credit — Anthropic exposes no API for the prepaid balance, which is visible only on the Console dashboard. And per the [Cost API docs](https://platform.claude.com/docs/en/manage-claude/usage-cost-api) it **omits Priority Tier costs**, so an organization on Priority Tier is seeing less than its true total spend.
## Local development
```bash
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 # required for existing vendor smoke tests
make smoke # runs all ignored tests; only Kimi skips without its key
make clippy # cargo clippy -D warnings
```
## TUI controls

- `Tab` / `l` / `→` — next tab
- `Shift+Tab` / `h` / `←` — previous tab
- `r` — refresh active tab
- `R` — refresh all tabs
- `s` — open Settings overlay (primary vendor + API keys)
- `c` — open local Claude context sessions (only when `[context] enabled = true`)
- `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:

### Local context overlay
The optional context overlay answers a different local question from the
vendor tabs: how much input context was present in recent Claude Code sessions.
Enable it by hand, restart the TUI, and press `c`:
```toml
[context]
enabled = true
# projects_path = "~/.claude/projects" # this is the default
# context_window_tokens = 200000 # optional fallback
# Exact model ids override the fallback when 200K and 1M sessions coexist.
[context.model_context_window_tokens]
"claude-opus-4-6" = 1000000
```
Use `↑`/`↓` or `j`/`k` to select a session, `Enter` for its detail gauge,
`Esc` to return, and `r` to rescan. The percentage follows
[Claude Code's status-line definition](https://code.claude.com/docs/en/statusline):
`input_tokens + cache_creation_input_tokens + cache_read_input_tokens`. If no
trustworthy window size is configured for a model, the overlay shows the raw
token count rather than guessing a percentage. After compaction it shows a
waiting state until the next assistant response establishes the new context.
This is a best-effort reader for Claude Code's undocumented local JSONL format,
not an API. It reads only bounded tails from the 100 most recently modified
top-level sessions, ignores unknown or corrupt lines, skips `subagents`
sidechains, never follows discovered symlinks, and does the filesystem work on
the blocking pool so the TUI remains responsive. Nothing under
`~/.claude/projects` is read while the feature is disabled. Context controls
stay in TOML rather than expanding the already-full Settings modal.
### Settings overlay

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**, **OpenRouter API key**, **DeepSeek API key**, and **Kimi API key** inline. Keys are masked as you type; press `Ctrl-V` to reveal or hide them. Env vars (`ZAI_API_KEY`, `OPENROUTER_API_KEY`, `DEEPSEEK_API_KEY`, `KIMI_API_KEY`) still win at runtime if they're set; the inline key is the fallback. DeepSeek and Kimi remain disabled until their respective config sections set `enabled = true`.
Saving an API key in the overlay does not enable the vendor — you still need `enabled = true` in `[kimi]` or `[deepseek]` for the widget and TUI to include it.
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 field
- `Ctrl-S` — save and close
- `Esc` — 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 600`ed 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).
## Theming
- 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).
## Changelog
See [CHANGELOG.md](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.
## Acknowledgements
The OpenAI and Anthropic OAuth endpoint references came from [`claudebar`](https://github.com/mryll/claudebar) and [`codexbar`](https://github.com/mryll/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.
The Kimi `/coding/v1/usages` endpoint reference came from community quota tools: [`CodexBar`](https://github.com/steipete/CodexBar) (steipete), [`OpenUsage`](https://github.com/robinebers/openusage), and [`OmniRoute`](https://github.com/diegosouzapw/OmniRoute).
## License
MIT.