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.--rows Ntrims a table (0= every row); it never touches--json/--csv, which always carry every row.
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 live credential opencode itself is using — credential in opencode.db, which is where v2 and /connect write it, with the legacy ~/.local/share/opencode/auth.json as the fallback for a v1 install) 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 carries this store's own spend beside the server's share of the limit ($28.00 · 55% of limit), priced on the documented peak/off-peak schedule — opencode records the off-peak sheet for every turn, so its recorded cost reads ~40% under the console on peak-heavy days and is not what the rows use — keeping the elapsed share measured against the server's own resets, an on pace to hit cap in … ETA, and the monthly window's $X by now pro-rata figure. No dollar denominator is printed: the console meters at roughly twice the cost opencode records, so any ceiling derived from one side contradicts the other — spend / share offered $40 against a documented $80 monthly allowance, and the summed per-model limits were the old $215. Each model's own limit, and its spend against it, stay in the table below. 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. The live frame refreshes every 5s, like cmduse's — interval_secs (or -i) is the knob. 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).
arkuse — Volcengine ARK Agent Plan (same crate, third binary)
The same crate ships arkuse, which tracks a Volcengine ARK Agent Plan at the same command/flag parity as cmduse and ocuse — every subcommand, --json/--csv, the same watch frame, config keys, update check, cap notification and MCP server. ARK is metered in AFP, the plan's own unit, not in dollars: AFP = (input + cache_read + output) × coefficient / 10,000, one AFP is ¥0.002 at every tier. Cache reads are billed, and reasoning is not added again because it already sits inside output. Model coefficients, tier gates and the promo dates that halve them live in hand-maintained core/ark.json (the publisher documents them as prose, so there is no machine-readable source to extract from).
Two sources, and the split matters. The control plane (arkcli usage plan --profile <name>, which arkuse shells out to so arkcli keeps owning the credential chain) returns the plan's own 5-hour / daily / weekly / monthly meters with their reset instants — the only figures that see every machine and every harness on the subscription. This store (opencode.db, provider agent-plan only) supplies the per-model attribution and the local estimate. Where both exist the meter wins for the window rows and the local AFP is printed beside it, labelled local; with no profile, no arkcli, or --no-api, the windows fall back to a rolling cut and say estimated locally — still as used / quota against the plan's published split (5-hour 10%, daily 50%, weekly 35%, monthly 100% of its AFP), so a window never degrades to AFP alone. The server's windows are not reproducible locally: the 5-hour window is a block with its own reset, the week is anchored to Monday 00:00, and the month is the subscription anniversary — so arkuse cuts this store's rows at reset − window rather than a rolling now − window, and prints dates in the server's own offset. A window the control plane reports without figures (a just-reset block comes back as total + percent only) is derived rather than rejected, and its countdown falls back to the oldest row still inside the cut. A text-only workload has no daily window (only media/Harness items do), so that row simply does not appear.
--plan small|medium|large|max selects the ARK tier (default: the profile's own plan_tier, else medium); --profile <name> names the arkcli profile (default $ARKUSE_PROFILE, else the first agent-plan profile), and --db overrides the store. The billing period comes from the meter's monthly reset; without it the default is the calendar month, with --period-start, --window all|<n>d and --since as in ocuse. --no-api skips arkcli entirely, so the tier then comes from --plan (or the catalogue default) and no profile is resolved.
ARK limits the plan, not any one model, so notify_on_cap fires per window meter (5-hour / weekly / monthly), and the model block shows each model's share of period AFP instead of a per-model allowance ARK does not have. The plan's own semantics are surfaced where they matter: no downgrade, the monthly quota is pro-rated on upgrade while the 5-hour and weekly windows reset to the new tier immediately, and plain API calls against the plan base URL are forbidden (ban risk) — the catalogue's notes carry these in arkuse plans. arkuse plans also lists the enterprise seat prices; their AFP is metered per seat and the personal usage API does not expose it, so only prices are shown.
Config lives at ~/.config/arkuse/config.json with cmduse's keys (interval_secs, bar_width, burst_enabled, burst_samples, notify_on_cap, statusline_template, statusline_colors, statusline_ascii, check_updates, dismissed_update). The live frame refreshes every 5s like the other two — each refresh is an arkcli control-plane round trip, so raise interval_secs (or pass -i) if that feels heavy on your machine. arkuse daily|hourly|session|model --csv use the repo's shared columns with afp in place of cost_usd (afp counts every token the coefficient prices, cache reads included).
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.