ai-agent-status-bar
Universal tmux quota/usage status bar for AI coding CLIs, compatible with Codex, Claude Code, OpenCode, Cline and more.
Links
README
From the repo.
ai-agent-status-bar
A single tmux status bar that shows the quota of whichever AI coding CLI you happen to be running — OpenCode Go or its Z.AI GLM Coding Plan (toggleable with F12), Cline/ClinePass, Command Code, Codex, or Claude Code — next to a clock.
Cline ClinePass 5h 100% (3h02m) · W 50% (19/08) · M 43% (30/08) | 16/08/2026 13:44
OpenCode Go 5h 99% (4h58m) · W 100% (16/08) · M 100% (16/09) | 16/08/2026 18:51
Lite (Z.AI) 5h 95% (4h31m) · W 99% (16/08) | (after F12)
Command Code Goat 5h 99% (4h58m) · W 100% (24/08) · M 86% (17/09) | 17/08/2026 18:51
Codex Plus W 11% (20/08) | 16/08/2026 13:44
Claude Code Team 5h 100% (29m) · W 95% (22/08) | 16/08/2026 13:44
This is the unified successor to three sibling projects
(opencode-go-tmux-status-bar, cline-cli-clinepass-tmux-status-bar,
command-code-tmux-status-bar). They shared ~80% of their code — caching,
colour thresholds, countdown rendering, the tmux launcher flow, the installer —
and differed only in where the numbers come from. That difference is now the
only thing a new harness needs to contribute; everything else lives in a
shared core.
Percentages are quota remaining, coloured by how much is left (green >
50%, amber > 20%, red below). The bracketed figure is when that window resets:
a live countdown for the fast-moving 5-hour window, a date for the slower
weekly and monthly ones. A trailing ~ means the refresh failed and the last
known values are being shown.
The bar appears only when you launch a harness through it, and disappears with it. Nothing starts a multiplexer behind your back, and no AI CLI is ever modified — the providers read credentials the CLIs already write and call the corresponding billing/usage APIs directly.
Architecture
ai-agent-status-bar/
├── README.md
├── LICENSE
├── install.sh # one installer for all providers
├── lib/
│ ├── core.sh # cache TTL, fallback, colours, countdown,
│ │ # rendering, plan toggle, tmux session setup
│ └── providers/
│ ├── opencodego.sh # first-party Go usage API (curl)
│ ├── opencodezai.sh # Z.AI GLM Coding Plan monitor API (curl)
│ ├── clinepass.sh # ClinePass usage-limits API (curl)
│ ├── commandcode.sh # billing API (curl) + plan-cap table
│ ├── codex.sh # chatgpt.com wham/usage (curl) — Codex
│ └── claudecode.sh # api.anthropic.com oauth/usage (curl) — Claude
├── bin/
│ ├── ai-bar-quota # dispatcher: auto-detect or AI_BAR_PROVIDER
│ ├── ai-bar-toggle # F12 helper: swap a bar between sibling plans
│ ├── ai-bar-tmux # generic launcher (--tool opencode|cline|command-code|<bin>)
│ └── wrappers/ # compatibility shims for the old command names
│ ├── _template.sh # one shim, symlinked under each legacy name
│ ├── opencodego-quota, opencodego-tmux
│ ├── opencodezai-quota, opencodezai-tmux
│ ├── clinepass-quota, clinepass-tmux
│ ├── commandcode-quota, commandcode-tmux
│ ├── codex-quota, codex-tmux
│ └── claudecode-quota, claudecode-tmux
├── tmux/
│ └── bar.conf # the bar as a global config (optional)
└── shell/
└── bashrc-snippet.sh # harness aliases (opencode/cline/command-code)
Core / provider split. lib/core.sh is harness-agnostic: it owns the
cache (TTL, stale fallback, field-count guard), the colour thresholds, the
countdown and date rendering, the tmux session-setup, and the launcher flow.
Each file under lib/providers/ implements a small adapter contract — only
the fetching and normalisation of that harness's numbers — and nothing else.
Adding a new AI coding CLI is, in the common case, a single new file under
lib/providers/. See Adding a new provider below.
How the bar is applied
status-right is a session option in tmux, not a server-wide one:
ai-bar-tmux creates its session and then sets that session's status line,
palette included, so the bar shows only while you are in a session launched
through it. Whatever ~/.tmux.conf defines globally — your own dotfiles, or
another quota bar — is never touched and keeps governing every other session.
Run opencode and you see the Go bar; run cline and you see the ClinePass
bar; run command-code and you see the Goat/Pro/Max bar; run codex and you
see the Plus bar; run claude and you see the Team bar — even concurrently
on the same tmux server. Install order
does not matter, and nothing is ever written to ~/.tmux.conf.
tmux/bar.conf is kept for anyone who wants a bar globally; wiring it up
by hand is ln -s tmux/bar.conf ~/.tmux.conf. On a machine with no tmux
config at all there is nothing to do: the session options carry the whole bar.
Requirements
- Linux (tested on Ubuntu 26.04 under WSL2, x86_64)
- At least one of:
- OpenCode with the OpenCode Go provider connected
(
/connectin the TUI), and optionally the Z.AI GLM Coding Plan too (zai-coding-plan) to alternate between them with F12 - Cline CLI signed in to ClinePass, or a Cline API key
- Command Code (
npm i -g command-code, thencommand-code login) - Codex (
npm i -g @openai/codexorbrew install codex, thencodex login) - Claude Code (
npm i -g @anthropic-ai/claude-code, thenclaude login)
- OpenCode with the OpenCode Go provider connected
(
jq,curl,tmux- A terminal about 110 columns wide, or the bar gets clipped from the left
- No root: everything lands under
~/.local
Install
git clone <this repo> ai-agent-status-bar
cd ai-agent-status-bar
./install.sh
Then open a new shell and run a harness as usual (opencode, cline,
command-code, codex, or claude). The installer is safe to re-run,
backs up anything it would
overwrite, and never touches ~/.tmux.conf.
By default every provider found under lib/providers/ is installed. To
install only specific ones:
./install.sh --provider opencodego --provider clinepass
| Flag | Effect |
|---|---|
--provider X | install only provider X (repeatable) |
--all | install all providers (default) |
--help | show usage |
--no-codexbar | no-op (kept for backwards compat) |
What the installer does
- Prerequisites — checks
jq,curl,tmux. - Core scripts — symlinks
bin/ai-bar-quota,bin/ai-bar-toggle,bin/ai-bar-tmuxinto~/.local/bin(shared by all providers). - Legacy wrappers — for each selected provider, symlinks the old command
names (
opencodego-quota,clinepass-quota,commandcode-quota,codex-quota,claudecode-quota, …) so existing tmux configs and muscle memory keep working. - tmux — nothing: the bar is applied per-session by
ai-bar-tmux. - Shell aliases — adds one block to
~/.bashrcthat sourcesshell/bashrc-snippet.sh, definingopencode/cline/command-code/codex/claudealiases (each only when its launcher is installed).
Symlinks rather than copies: the clone stays the single source of truth, git pull updates every machine at once, and git status surfaces local drift
instead of hiding it.
Usage
Run a harness with the bar
opencode # alias -> ai-bar-tmux --tool opencode
cline # alias -> ai-bar-tmux --tool cline
command-code # alias -> ai-bar-tmux --tool command-code
codex # alias -> ai-bar-tmux --tool codex
claude # alias -> ai-bar-tmux --tool claude
Or explicitly, pinning a provider so the right bar shows regardless of auto-detection:
ai-bar-tmux --tool opencode --provider opencodego
Inside an existing tmux or when tmux is absent, the harness runs directly (no nesting, no second status bar).
Render the block standalone
The helper script prints plain text by default and tmux colour markup with
--tmux, so it is equally usable from a shell:
$ ai-bar-quota
Go 5h 99% (4h58m) · W 100% (16/08) · M 100% (16/09)
$ AI_BAR_PROVIDER=opencodezai ai-bar-quota
Lite 5h 95% (4h31m) · W 99% (16/08) # Z.AI GLM Coding Plan
$ ai-bar-quota --tmux
#[fg=colour245]Go #[fg=colour114]5h 99% (4h58m) · W 100% (16/08) · M 100% (16/09)#[fg=colour238] | #[default]
Alternate between plans (F12)
When both OpenCode plan keys exist (opencode-go and zai-coding-plan in
~/.local/share/opencode/auth.json), the Go-pinned bar becomes
multi-plan: press F12 inside the session to swap the slot between
Go … and Lite/Pro … (the Z.AI plan's level). The choice lives in a small
per-session state
file under ~/.cache/ai-bar/sessions/, so it survives relaunches, and every
plan keeps its own quota cache either way. Only plans whose credentials are
actually present take part in the cycle — with one key there is nothing to
swap and the bar stays static.
Change the key with AI_BAR_TOGGLE_KEY; disable by unsetting the declaring
provider's PROVIDER_TOGGLE_LIST. Two caveats worth knowing:
- The
F12binding lives in tmux's root (global) key table, so with several live multi-plan sessions the most recently launched one owns the shared key; pressing it anywhere else just no-ops. - Bare
F12stops reaching the terminal application while such a session is attached. Pick a different key viaAI_BAR_TOGGLE_KEYif yours uses it.
Pin / override the provider
Auto-detection tries, in order: $AI_BAR_PROVIDER, the first provider whose
gate_check passes, then the first whose HARNESS_BIN is on PATH. To force
a specific provider:
AI_BAR_PROVIDER=opencodego ai-bar-quota
Legacy command names
The old per-tool commands still work, pinning the matching provider:
opencodego-quota # -> ai-bar-quota with AI_BAR_PROVIDER=opencodego
clinepass-quota --tmux # -> ai-bar-quota --tmux with AI_BAR_PROVIDER=clinepass
Configuration
Per-provider TTL env vars control how often the network is hit; the bar
redraws on tmux's status-interval (1s in the optional global config, 5s for
multi-plan sessions so F12 feels instant, otherwise tmux's default) and only
one real API request goes out per TTL window.
| Variable | Default | Provider | Effect |
|---|---|---|---|
OPENCODEGO_QUOTA_TTL | 30 | opencodego | Seconds between Go usage API calls |
OPENCODEGO_API_BASE | https://opencode.ai | opencodego | Override the first-party API base URL |
OPENCODEZAI_QUOTA_TTL | 30 | opencodezai | Seconds between Z.AI quota API calls |
OPENCODEZAI_API_BASE | https://api.z.ai | opencodezai | Override the Z.AI monitor API base URL |
OPENCODEZAI_QUOTA_ALWAYS | — | opencodezai | Show the Z.AI block even without a plan key on disk |
CLINEPASS_QUOTA_TTL | 30 | clinepass | Seconds between usage calls |
CLINEPASS_QUOTA_ALWAYS | 0 | clinepass | 1 shows the quota even when ClinePass is not the active provider |
CLINE_API_KEY | — | clinepass | Preferred Cline API key; otherwise a valid local OAuth token is tried |
CLINEPASS_API_KEY | — | clinepass | Compatibility alias for CLINE_API_KEY |
CLINEPASS_API_BASE | https://api.cline.bot | clinepass | Override the usage-limits API base URL |
COMMANDCODE_QUOTA_TTL | 30 | commandcode | Seconds between billing API calls |
COMMAND_CODE_API_KEY | — | commandcode | Override the API key (otherwise read from ~/.commandcode/auth.json) |
COMMANDCODE_API_BASE | https://api.commandcode.ai | commandcode | Override the billing API base URL |
CODEX_QUOTA_TTL | 30 | codex | Seconds between wham/usage calls |
CODEX_API_BASE | https://chatgpt.com | codex | Override the wham/usage API base URL |
CLAUDECODE_QUOTA_TTL | 30 | claudecode | Seconds between oauth/usage calls |
CLAUDE_API_BASE | https://api.anthropic.com | claudecode | Override the oauth/usage API base URL |
AI_BAR_PROVIDER | — | all | Pin the provider, skipping auto-detection |
AI_BAR_TMUX_RIGHT_LEN | 110 | all | status-right-length for launched sessions |
AI_BAR_TOGGLE_KEY | F12 | multi-plan bars | Key that swaps the bar between sibling plans |
AI_BAR_STATUS_INTERVAL | 5 | multi-plan bars | Session status-interval while a toggleable bar is applied |
To change a refresh rate permanently, edit the default in the provider file
under lib/providers/.
How it works
ai-bar-quota runs this flow for the detected provider:
- Serve fresh cache — if the cached values are younger than the TTL and
the field count matches
CACHE_FIELDS, render them and exit. No network. - Gate check — the provider may hide the block entirely (no key configured, a different provider active, …). Silent for status bars, explanatory when a human runs the script by hand.
- Fetch — call
fetch_quota, which returns one line of normalised values:label fp fr wp wr mp mr. Percentages are remaining (0-100, or -1 to omit a window); resets are epochs (0 = unknown). The cache is written and the values rendered. - Degrade — on any failure, render the stale cache (with a trailing
~) if available, elsen/d. A stale number beats a blank bar.
The block carries its own trailing separator, so it disappears cleanly
instead of leaving an orphan | behind when there is nothing to show.
Per-provider data sources
| Provider | Quota source | Gate |
|---|---|---|
| opencodego | GET https://opencode.ai/zen/go/v1/usage with the Go key from ~/.local/share/opencode/auth.json | OpenCode Go key present |
| opencodezai | GET https://api.z.ai/api/monitor/usage/quota/limit with the zai-coding-plan key from the same auth.json; 5-hour + weekly credit windows, label from the subscribed level (Lite, Pro, …, bare ZAI when unknown). Quirk: this endpoint wants the raw key (Authorization: <key>, no Bearer) | OpenCode Z.AI coding-plan key present (or OPENCODEZAI_QUOTA_ALWAYS=1) |
| clinepass | GET https://api.cline.bot/api/v1/users/me/plan/usage-limits; CLINE_API_KEY first, then unexpired OAuth tokens from ~/.cline/data/settings/providers.json | lastUsedProvider == cline-pass or cline (or CLINEPASS_QUOTA_ALWAYS=1) |
| commandcode | curl to /alpha/billing/credits + /alpha/billing/subscriptions (best-effort /alpha/whoami for orgId) with the key from ~/.commandcode/auth.json or COMMAND_CODE_API_KEY | API key present |
| codex | curl to /backend-api/wham/usage with the ChatGPT access_token from ~/.codex/auth.json | ~/.codex/auth.json with tokens.access_token |
| claudecode | curl to /api/oauth/usage with the OAuth accessToken from ~/.claude/.credentials.json (field claudeAiOauth.accessToken) | ~/.claude/.credentials.json with claudeAiOauth.accessToken |
For commandcode, the 5-hour and weekly windows come straight from the
credits payload's windowLimits; the monthly window is derived — plan cap
(looked up from the same per-plan table the TUI's /usage overlay uses) minus
remaining monthly credits. Purchased and free credits are excluded: they do
not reset at period end and would mask exhaustion. The block label is the
plan's short name (Go, Goat, Pro, Max, Ultra, Provider, TeamsPro), resolved from planId and cached with the figures so it survives
failed refreshes.
Adding a new provider
A new AI coding CLI needs one file: lib/providers/<name>.sh. It is
sourced by core.sh and implements the adapter contract. Nothing else in the
project needs to change — install.sh auto-discovers providers via glob,
the dispatchers auto-detect via gate_check / HARNESS_BIN, and the launcher
accepts any binary via --tool <bin>.
Minimal provider
# lib/providers/myagent.sh
if [ -n "${_AI_BAR_PROVIDER_MYAGENT:-}" ]; then
unset -f gate_check plan_label 2>/dev/null
unset _AI_BAR_PROVIDER_MYAGENT
fi
_AI_BAR_PROVIDER_MYAGENT=1
PROVIDER_ID="myagent"
PROVIDER_LABEL="MyAgent" # default block label
CACHE_FIELDS=7 # label + 6 (3 windows × pct,reset)
QUOTA_TTL_ENV="MYAGENT_QUOTA_TTL"
HARNESS_BIN="myagent" # for auto-detection + --tool alias
# (no external binary beyond jq/curl; providers are fully autonomous)
# fetch_quota: stdout = "label fp fr wp wr mp mr"
# fp/wp/mp: percent REMAINING (0-100), or -1 to omit that window
# fr/wr/mr: reset epoch seconds (0 = unknown)
# return non-zero on hard failure; the core handles stale-cache / n/d.
fetch_quota() {
local json
json=$(myagent-usage --json 2>/dev/null) || return 1
printf '%s' "$json" | jq -er '
def clamp: round | if . < 0 then 0 elif . > 100 then 100 else . end;
def epoch:
if type == "number" then .
elif type == "string" and . != ""
then (sub("\\.[0-9]+"; "") | try fromdate catch 0)
else 0 end;
[ "MyAgent",
(100 - .fiveHour.usedPercent | clamp), (.fiveHour.resetsAt | epoch),
(100 - .weekly.usedPercent | clamp), (.weekly.resetsAt | epoch),
(100 - .monthly.usedPercent | clamp), (.monthly.resetsAt | epoch) ]
| map(tostring) | join(" ")
'
}
Optional hooks
| Function | Default | When to override |
|---|---|---|
gate_check | always pass | hide the block when the provider is not active (no key, wrong provider, …). Print an explanation only when ai_bar_mode = plain. |
plan_label | pass through | when the block label is dynamic (e.g. commandcode resolves it from planId). |
Variables
| Variable | Required | Meaning |
|---|---|---|
PROVIDER_ID | yes (defaults to filename) | cache directory name |
PROVIDER_LABEL | yes | default block label / fallback |
CACHE_FIELDS | yes (default 6) | field count the cache must have to be valid |
QUOTA_TTL_ENV | yes (default AI_BAR_QUOTA_TTL) | env var overriding the 30s TTL |
HARNESS_BIN | recommended | harness binary name, for auto-detection and --tool |
PROVIDER_TOGGLE_LIST | no | space-separated sibling ids AI_BAR_TOGGLE_KEY (F12) may cycle through while this provider owns the bar; declare it in every participating provider so a pinned sibling stays toggleable too |
Install / wrapper
./install.sh picks up the new provider automatically. To add a legacy
wrapper name, drop a symlink in bin/wrappers/ following the existing
convention (<name>-quota, <name>-tmux → _template.sh) and add a case to
the case "$p" in block in install.sh if the legacy names differ from the
provider id.
Things that are not obvious
These cost real debugging time, inherited from the three original projects.
Fully autonomous for all six providers — every adapter uses curl +
jq; CodexBar is not installed or invoked. --no-codexbar remains accepted
as a deprecated no-op so existing installation scripts do not break.
OpenCode Go's usage endpoint is first-party but new.
/zen/go/v1/usage returns the server-side rolling, weekly, and monthly
windows and accepts the same key stored after /connect. It is deployed but
not yet listed with the documented model endpoints, so failures degrade to
the stale cache; the adapter does not fabricate a local estimate.
CodexBar has a commandcode provider but it does not work on Linux. Its
usage source is web-only and the CLI answers "only supported on macOS". The
commandcode provider here calls the billing API with curl instead, exactly
like the TUI mod.
resetAt (commandcode) arrives in three shapes. Epoch milliseconds, epoch
seconds, or an ISO string depending on the endpoint — and the ISO strings
carry fractional seconds and non-Z offsets, which jq's fromdate rejects
as-is. The parser normalises all of them; anything before ~1973 in
milliseconds is implausible for a reset date, so that is how seconds and
milliseconds are told apart.
The plan-cap table is duplicated from the mod, longest-prefix-first. The
product's own order matches individual-go before individual-goat after a
plan upgrade; the copy here keeps the corrected order. If Command Code adds
plans, update the table in lib/providers/commandcode.sh.
expiresAt in Cline's providers.json is in milliseconds. Comparing it
against date +%s makes valid tokens look long expired.
A Cline API key is the reliable ClinePass credential. Create one under
Settings → API Keys and export it as CLINE_API_KEY. As a zero-config
fallback the adapter tries unexpired workos: account tokens from
providers.json, but deliberately does not refresh or rewrite them. Cline
uses a five-minute refresh margin, and the adapter excludes tokens inside
that margin rather than racing their expiry.
OpenCode has no readable "active provider" flag on disk. Nothing as cheap as a JSON field says whether Go is the provider in use right now, so the opencodego block shows whenever a Go key is configured, in any tmux session. The F12 toggle exists precisely because of this: instead of guessing which plan is hot, the two plans share one slot and you pick with a keystroke.
The Z.AI coding-plan quota endpoint is official but undocumented.
/api/monitor/usage/quota/limit is what Z.AI's own
@z_ai/coding-helper
plugin queries; it wants the raw key, not Bearer, and reports percent used
with epoch-millisecond resets. Schema drift degrades to the stale cache like
any other provider.
tmux runs #() through a non-interactive sh whose PATH has no
~/.local/bin. The scripts prepend it themselves rather than trusting the
caller. $HOME is expanded inside #(), which is what keeps tmux/bar.conf
free of absolute paths.
tmux only runs status jobs for an attached client. A detached session
never draws its status bar, so #() never fires — worth knowing before
concluding that a config is broken. tmux display-message -p '#{T:status-right}'
does not run jobs either, so it is useless for testing this.
tmux loads ~/.tmux.conf once per server. Only relevant if you wired
tmux/bar.conf up globally by hand: a server that was already running keeps
serving the old bar until it exits (tmux kill-server when nothing valuable
is open). Session options like the ones ai-bar-tmux applies are immune to
this, which is one more reason the bar is applied per session.
Uninstall
./uninstall.sh # remove symlinks + bashrc block
./uninstall.sh --purge # also remove cache, legacy CodexBar, and warn about sources
Safe to re-run, backs up ~/.bashrc before editing. The script only removes
symlinks that point back into this repo, so a hand-placed file with the same
name is left alone. --purge does not rm -rf the repo itself (the script is
running from inside it); it prints the path and the command to run.
Manual uninstall (equivalent):
rm ~/.local/bin/{ai-bar-quota,ai-bar-toggle,ai-bar-tmux}
rm ~/.local/bin/{opencodego,opencodezai,clinepass,commandcode,codex,claudecode}-{quota,tmux}
# Legacy CodexBar installed by ai-agent-status-bar <= 0.2 (if unused elsewhere)
rm -rf ~/.local/lib/codexbar ~/.local/bin/codexbar
# Cache
rm -rf ~/.cache/ai-bar
Then delete the block marked ai-agent-status-bar from ~/.bashrc. Backups
made by install.sh / uninstall.sh are named *.bak-<timestamp> next to
each original.
Migration from the per-tool repos
The unified project is a drop-in replacement. The old per-tool command names keep working through wrappers, and the old tmux configs that reference them need no edits. To migrate:
./install.sh(installs all providers and the legacy wrappers).- Remove the old per-tool checkout and its symlinks (the installer's
backup()will have preserved any pre-existing files it overwrote). - The old
clinepass-quota-bar/opencodego-quota-bar/commandcode-quota-barblocks in~/.bashrcare superseded by the singleai-agent-status-barblock; remove the old ones to avoid double-aliasing.
License
MIT — see LICENSE.
Tests
./tests/run.sh
The suite uses an isolated temporary HOME and a deterministic curl mock;
it performs no network requests and never reads real credentials.
Credits
All six providers are fetched directly with curl + jq; CodexBar is not a
runtime dependency. The ClinePass endpoint/schema and the historical
OpenCode local-estimation behaviour were cross-checked against
CodexBar.
The Z.AI GLM Coding Plan adapter mirrors the query performed by Z.AI's own
@z_ai/coding-helper plugin (api.z.ai/api/monitor/usage/quota/limit, raw-key
auth), keyed by the zai-coding-plan entry OpenCode writes at /connect.
Command Code quota semantics (derived monthly window, plan-cap table,
defensive parsing) mirror the
command-code-quota-status-bar
TUI mod. Codex quota comes from chatgpt.com/backend-api/wham/usage with the
ChatGPT login token that Codex stores at ~/.codex/auth.json (same pattern as
Command Code: read at call time, Bearer header only). Claude Code quota comes
from api.anthropic.com/api/oauth/usage with the OAuth token at
~/.claude/.credentials.json (claudeAiOauth.accessToken, 5h + weekly
windows, plan-aware label). This unified project
supersedes the three sibling repos
opencode-go-tmux-status-bar,
cline-cli-clinepass-tmux-status-bar,
and
command-code-tmux-status-bar.
Collected info
- ★ 0 stars
- Language: Shell
- Source updated: 8/27/2026
Config for your environment
Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.
Tool
OS
Config file: ~/.cursor/mcp.json
{
"mcpServers": {
"mcp-server": {
"url": "{MCP_ENDPOINT_URL}"
}
}
}Paste into mcpServers in the config file. Restart Cursor after saving.
If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.