# cmduse
Live Command Code usage dashboard for your terminal.
[](https://github.com/JeffreyJYZ/cmdcode-tools/actions/workflows/ci.yml) [](https://crates.io/crates/cmd-usage) [](LICENSE-MIT)
## What it is
Plan dashboard, account-wide usage reports (all harnesses), offline local reports, and a customisable statusline.
## Install
```sh
brew install JeffreyJYZ/tap/cmduse # macOS (Homebrew)
cargo install cmd-usage # any platform with Rust
```
## Quick start
```text
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
```sh
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:
```json
{
"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):
```sh
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 |
```sh
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:
```sh
# .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`:
```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:
```sh
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](https://commandcode.ai) — 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).
```sh
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 `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). 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 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
- [Source (GitHub)](https://github.com/JeffreyJYZ/cmdcode-tools/tree/main/cmduse)
- [crates.io: cmd-usage](https://crates.io/crates/cmd-usage)
- [MIT license](LICENSE-MIT)