cmduse
Live Command Code usage dashboard for your terminal.
What it is
Plan dashboard, account-wide usage reports (all harnesses), offline local reports, and a customisable statusline.
Install
Quick start
Command Code Usage · GOAT · active
Period ends 2026-09-27
Credits $59.12 / $70.00 monthly · $0.00 purchased · $0.00 free
Usage windows
Monthly 15.1% ━━╱╱╱╱╱╱╱╱ $10.56 / $70.00 · resets in 22d 9h · window 28% elapsed · $19.60 by now
5-hour 1.5% ╱╱╱╱╱╱╱╱╱╱ $0.21 / $14.00 · resets in 4h 58m · window 0% elapsed
Weekly 15.8% ━╱╱╱╱╱╱╱╱╱ $5.54 / $35.00 · resets in 6d 5h · window 11% elapsed · on pace to hit cap in 4d 3h
This billing period
Requests 856 · Cost $10.69 · Tokens 89.5M in / 265.7K out · Success 100%
Usage
# same fields as the colored dashboard: period end,
# monthly quota, Monthly row (+ $X by now), window % elapsed
# (also what the MCP `usage` tool and the opencode
# /cmd-usage dialog render)
# --days max 365, fetched 8-at-a-time
Color: SGR escapes are suppressed when stdout is not a terminal or when NO_COLOR is set non-empty. The live dashboard's spinner goes straight to /dev/tty, so it only runs when stdout is a terminal — --plain, or any piped or redirected run, never writes to the terminal at all. --plain forces plain text; reports have no --plain — pipe them or use --json/--csv.
MCP server
cmduse mcp runs an MCP stdio server (newline-delimited JSON-RPC, hand-rolled — no extra dependencies) so any MCP-capable host (Zed, and others) can call Command Code usage as tools:
| Tool | Body |
|---|---|
usage |
one-shot dashboard (same as cmduse -1) |
plans |
plan comparison table (same as cmduse plans) |
models |
live model list, filtered to the current plan (same as cmduse models --gated) |
daily |
account daily report; args days, tz (±HH:MM), local |
hourly |
account hourly report; args hours, tz, local |
session |
local usage by project/session; args since (ISO-8601), days, tz |
model |
local usage by model; args since (ISO-8601), days, tz |
Auth is the same as the CLI (CMD_API_KEY, then ~/.commandcode/auth.json). Tool errors come back as isError: true results; stdout carries protocol traffic only. Zed config:
-1 --json emits a single dashboard object (plan, credits, both windows, billing summary). --gated --json on models emits every model with allowed and reason instead of filtering.
GNU forms are accepted: --interval=5m, --days=14, and attached short values like -i30 / -w40. --last/-l are aliases for --days.
Cap alerts: in watch mode a desktop notification fires once when a window crosses into overflow (macOS osascript, Linux notify-send). Disable with cmduse config set notify=false.
Burn-rate: windows show on pace to hit cap in … when the current spend rate projects hitting the cap before the window resets, and only once the window is ≥10% elapsed (flat-rate projection is unreliable early). ponytail: assumes flat spend rate; bursty sessions shift the ETA.
Watch mode: a spend bursts (N samples) sparkline shows $ spent per refresh. It is on by default, hidden while idle (all-zero deltas), and its sample count is configurable. Session-only: a fresh run starts a fresh trend (no stale data from earlier runs):
Statusline
Template-driven via config. Placeholders:
| Placeholder | Shows |
|---|---|
{plan} |
plan name (GOAT, Pro, …) |
{credits} |
remaining monthly credits |
{cap} |
monthly cap |
{credits_bar} |
monthly usage bar |
{5h_bar} {5h_pct} {5h_used} {5h_cap} {5h_eta} |
5-hour window (eta = pace text, empty when none) |
{wk_bar} {wk_pct} {wk_used} {wk_cap} {wk_eta} |
weekly window |
Multi-line templates work (newlines allowed). Unknown placeholders are dropped. Wire into your shell prompt or tmux status:
# .zshrc / tmux status-right
CMD_API_KEY env var overrides the stored key — point cmduse at any account without touching ~/.commandcode/auth.json.
Config
~/.config/cmd-usage/config.json:
CLI flags override config. cmduse config set interval=<s> width=<n> burst_on=<bool> burst=<n> notify=<bool> sl=<tpl> sl_colors=<bool> sl_ascii=<bool> update_check=<bool> dismissed_update=<ver>.
Updates
Once per day (cache at ~/.cache/cmd-usage/last-check) the dashboard checks crates.io; when a newer cmd-usage exists it shows a boxed notice inside the watch frame (stderr for -1). The cached version is replayed every run, so the reminder persists until you upgrade. Dismiss or disable:
Data sources
- Dashboard / daily / hourly / statusline: Command Code API with your account key. Daily and hourly cover every harness that used the key (CLI, Provider API, other agents).
- model / session / daily --local: local session logs at
~/.commandcode/projects— offline, but only what the CLI recorded.model/sessionare all-time unless given--days Nor--since <ISO>; the usage API has no per-model split, so local logs are the only per-model source.
Requirements
Logged-in Command Code CLI — reads your API key from ~/.commandcode/auth.json (run cmd login if missing), or set CMD_API_KEY.
How it works
ocuse — OpenCode Go/Zen (same crate, second binary)
The same crate ships ocuse, which tracks OpenCode Go (the Go subscription) and OpenCode Zen (pay-as-you-go) at full command/flag parity with cmduse — same subcommands, same --json/--csv, same config keys, same update check and cap notification. The two share one front-end (cli.rs parses both binaries' argv, config.rs owns both config files) and cmduse's render/watch/sparkline helpers; only the data source and the flags it forces differ (--plan/--window/--period-start/--infer-anniversary/--db/--no-api are ocuse-only, --local is cmduse-only).
Two upstream facts shape it: OpenCode Go does have a usage API (the console's own source), and Go's allowance is per model — each model has a monthly dollar limit with windows of 5h = 20%, weekly = 50%, monthly = 100%. So ocuse reads GET https://opencode.ai/zen/go/v1/usage with the Go key (OCUSE_API_KEY, OPENCODE_API_KEY, else the opencode-go entry of opencode's auth.json) for the three account meters — rolling (5-hour), weekly, monthly, each a percent and a reset instant, exactly what opencode.ai/console prints — and reports its own local usage from opencode.db (providers opencode-go and opencode, which record real cost) against the limits in core/zen.json (regenerated with bun scripts/extract-zen.ts) for everything the endpoint cannot give: the per-model table, daily/hourly/session, tokens. The endpoint's figures win wherever both exist, because the server's windows are not reproducible locally: its 5-hour window is a block that resets at its own instant, its week is anchored to Monday 00:00 UTC, its month is the subscription anniversary, and it meters with its own price basis (fitted from real rows, opencode's recorded cost sits below it). No key, no network, or --no-api leaves the windows as a local estimate, and the dashboard labels them that way. Go comes in two plans — Go ($10/mo) and Go Plus ($40/mo) — that share the token prices but grant different per-model limits; neither the store nor the endpoint names your plan, so ocuse selects it with --plan go|go-plus (default go). ocuse models lists that catalogue for the plan (--gated keeps only the models the plan grants); ocuse models --json is one object per line, like cmduse models --json.
The billing period comes from the endpoint's own monthly reset instant (the anniversary — hence the dashboard's Go billing month label); without it the default is the calendar month, --period-start YYYY-MM-DD pins a real one, --window all|<n>d reports other ranges, --since <ISO-8601> pins an exact instant for model/session, and --tz shifts day/hour buckets. --days (default 120) bounds the read window; hourly uses --hours (default 24, max 168). ocuse -1 --json carries the period bounds in epoch milliseconds — periodStart (with the alias periodStartAt) and periodEndAt, the resolved end of the period (null for --window all, whose range has no boundary) — plus an account object with the three meters ({status, percent, resetsAt} each, null when the endpoint did not answer) and an accountError string naming why it is null when that is worth saying (HTTP 401 api_key_expired, no Go key, a transport fault — null when the endpoint answered, and for --no-api, where the missing meters are a deliberate choice). The dashboard prints that reason under the GO heading, because "estimated locally" on its own cannot tell a missing key from an expired one, and only the expired one is something to act on (re-login does not renew it: create a new key in the OpenCode console). The dashboard names that end date on its period: line and splits period tokens in / out, mirroring cmduse. cmduse's Success N% is absent: opencode's own store records no per-request success/error signal.
ocuse renders with cmduse's own presentation: the same palette, the same severity-coloured gauges (green <70%, yellow 70–90%, red ≥90%), the same in-place watch redraw (cmduse's redraw_frame, so a shrinking frame never scrolls), and the same spend-burst sparkline. Both watch loop and gauges reuse cmduse/src/render.rs rather than a second copy. Every subcommand takes the colour flag — daily, hourly, session, model, models, plans and statusline included (bold headings, dim secondary text, cyan figures, an over-cap window or model flagged LIMIT EXCEEDED, and a per-model share of period spend). With the account meters in play each window row shows this store's money with the server's share labelled beside it ($2.29 (6.0% of limit) — the two need not divide to each other, the server meters higher), keeping the elapsed share measured against the server's own resets; there is no money pair, pace ETA or pro-rata figure, because Go grants limits per model and no account-wide pool exists to divide by — a summed ceiling would read as a budget the console does not have. The per-model 5H/WK columns in the table stay local rolling windows and the dashboard says so. statusline's {five_hour_pct}/{weekly_pct}/{month_pct} (and their _bar forms) resolve to the account's shares when it answers and to a dash without it, while money placeholders stay local. --plain, NO_COLOR and a piped stdout drop colour and gauges; scripted output stays escape-free, and --json/--csv are never coloured.
Config lives at ~/.config/ocuse/config.json with the same keys as cmduse's: interval_secs, bar_width, burst_enabled, burst_samples, notify_on_cap, statusline_template, statusline_colors, statusline_ascii, check_updates, dismissed_update. Set with ocuse config set interval=<s> width=<n> burst_on=<bool> burst=<n> notify=<bool> sl=<tpl> sl_colors=<bool> sl_ascii=<bool> update_check=<bool> dismissed_update=<ver>. The statusline template placeholders: {plan} {period} {month} {month_pct} {month_bar} {five_hour} {five_hour_pct} {five_hour_bar} (aliases {5h} {5h_pct} {5h_bar}), {weekly} {weekly_pct} {weekly_bar} (aliases {wk} {wk_pct} {wk_bar}), and {models} (top model by period spend).
Notes
- Window bars: green <70%, yellow 70–90%, red ≥90%, plus
LIMIT EXCEEDEDflag. - Report tables show cache read and cache write separately;
dailyis bucketed in UTC unless--tz, and--localhonors--tz; token totals in the model/sessionTokenscolumn include both cache columns.ocuse's--csvcolumns areday|hour|model|session,requests,tokens_in,tokens_out,cache_read,cost_usd— its aggregates carry no cache-write, so that column is deliberately absent. - Spend-burst sparkline appears in watch mode after 2 refreshes (bars = $ spent between refreshes, ~3 min of history at 5s interval, capped at 40 samples; tall = burst, flat = idle).
ocuse's watch frame uses the same three pieces:redraw_frameparks the cursor on the frame's last line (its status/countdown line), the sparkline tracks 5-hour spend deltas at 10s samples (capped at 60), andInkcarriescmduse's palette as empty strings when colour is off.- On Monthly caps: monthly pool is the plan total (e.g. $70 on GOAT). Docs describe per-model allowances, but the CLI and API meter one shared pool — verified empirically.