cmd-usage 0.7.22

Live Command Code (commandcode.ai) usage dashboard: credits, 5-hour and weekly windows, plan limits. Watch mode or one-shot.
Documentation

cmduse

Live Command Code usage dashboard for your terminal.

CI crates.io license

What it is

Plan dashboard, account-wide usage reports (all harnesses), offline local reports, and a customisable statusline.

Install

brew install JeffreyJYZ/tap/cmduse   # macOS (Homebrew)
cargo install cmd-usage              # any platform with Rust

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

cmduse                       # live dashboard, redraws in place (default every 5s)
cmduse -1                    # one-shot fetch, print, exit
cmduse -V                    # print version
cmduse -p -1                 # plain output, no ANSI (for scripts/pipes)
                             # 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)
cmduse -i 30                 # refresh every 30s
cmduse -i 5m                 # duration suffixes: s, m, h, d
cmduse -w 40                 # 40-char progress bars
cmduse watch                 # explicit watch mode (same as bare cmduse)
man cmduse                   # full behavior spec (brew installs the man page)

cmduse daily --days 14       # account usage by day (all harnesses, from usage API)
                             # --days max 365, fetched 8-at-a-time
cmduse daily --local         # CLI-logs-only (offline, misses other harnesses)
cmduse daily --tz +05:30     # bucket by a fixed UTC offset instead of UTC
cmduse daily --local --tz +05:30  # ...the local-log path honors --tz too
cmduse hourly --hours 6      # account usage by hour (default 24, max 168)
cmduse hourly --local        # hourly from local CLI logs (offline)
cmduse model                 # local usage by model (all time)
cmduse model --days 30       # ...only the last 30 days
cmduse model --since 2026-08-27T00:00:00Z   # ...or since a billing-period start
cmduse session --days 30     # local usage by project, same window flags
cmduse models                # live model list from the Command Code API
cmduse models --gated        # ...only models the current plan allows
cmduse models --gated --json # every model annotated with allowed + reason
cmduse plans                 # plan comparison table (marks your plan)
cmduse statusline            # compact one-liner for prompts/tmux
cmduse mcp                   # MCP stdio server (usage/plans/models/daily/hourly/session/model tools)
cmduse daily --json          # JSON output (daily, hourly, model, session, models, statusline, plans, -1)
cmduse daily --csv           # CSV output for daily/hourly/model/session

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:

{
  "context_servers": {
    "cmduse": { "source": "custom", "command": "cmduse", "args": ["mcp"] }
  }
}

-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):

cmduse config set burst_on=false      # turn the sparkline off
cmduse config set burst=80            # 80 samples (5–240)
cmduse -b 120                         # same for one run

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
cmduse config set sl="{plan} {credits}/{cap} 5h:{5h_pct} wk:{wk_pct}"
cmduse config set sl="{credits_bar}"           # just one bar
cmduse config set sl_colors=false              # strip ANSI
cmduse config set sl_ascii=true                # #--- bars instead of ━╱╱

Multi-line templates work (newlines allowed). Unknown placeholders are dropped. Wire into your shell prompt or tmux status:

# .zshrc / tmux status-right
status() { cmduse statusline 2>/dev/null; }

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:

{
  "interval_secs": 5,
  "bar_width": 20,
  "burst_enabled": true,
  "burst_samples": 40,
  "notify_on_cap": true,
  "statusline_template": "{plan} {credits}/{cap} · 5h {5h_bar} · wk {wk_bar}",
  "statusline_colors": true,
  "statusline_ascii": false,
  "check_updates": true,
  "dismissed_update": null
}

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:

cmduse --dismiss-update              # hide this version until a newer one
cmduse config set update_check=false # never check
cmduse config set dismissed_update=<ver>

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/session are all-time unless given --days N or --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).

ocuse                        # watch: live frame (colour, gauges, spend-burst sparkline)
ocuse -1                     # one-shot dashboard
ocuse -1 --json              # one-shot, machine-readable
ocuse daily [--days N] [--json|--csv] [--tz ±HH:MM]
ocuse hourly [--hours N] [--json|--csv] [--tz ±HH:MM]
ocuse model [id] [--since ISO] [--json|--csv]
ocuse session [--since ISO] [--json|--csv]
ocuse models [--gated] [--json]   # the Go catalogue for the selected plan
ocuse plans [--json]              # both plans' limits, rates and window shares
ocuse statusline [--json]         # compact one-liner, or its numbers as JSON
ocuse mcp                    # MCP stdio server (usage/plans/models/daily/hourly/session/model)

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. 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).

arkuse                       # watch: live frame (meters, gauges, model block)
arkuse -1                    # one-shot dashboard
arkuse -1 --json             # one-shot, machine-readable
arkuse daily [--days N] [--json|--csv] [--tz ±HH:MM]
arkuse hourly [--hours N] [--json|--csv] [--tz ±HH:MM]
arkuse model [id] [--since ISO] [--json|--csv]
arkuse session [--since ISO] [--json|--csv]
arkuse models [--gated] [--json]   # the ARK catalogue, coefficient in force today
arkuse plans [--json]              # personal tiers, AFP quotas, window shares
arkuse statusline [--json]
arkuse mcp                   # MCP stdio server (usage/plans/models/daily/hourly/session/model)

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). 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 EXCEEDED flag.
  • Report tables show cache read and cache write separately; daily is bucketed in UTC unless --tz, and --local honors --tz; token totals in the model/session Tokens column include both cache columns. ocuse's --csv columns are day|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_frame parks 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), and Ink carries cmduse'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.

Links