OpenCode Usage
One bar icon and one panel that shows the tokens you have spent in OpenCode, read from OpenCode's local database — plus the subscription meters of your OpenCode Go plan when a key is available.
Panel.qml owns the bar button and the popup; Main.qml owns the scanner
fan-out and the optional cross-device aggregation; providers/ holds the
provider adapter; scripts/opencode_usage_scanner.py reads the SQLite
database and probes the usage endpoint with nothing but the Python standard
library.
What you see
- Hero — the OpenCode mark and where the numbers come from ("Go" when plan meters are live, "Local stats" otherwise, or a status such as "Waiting for auth").
- Limits — one percent meter per Go plan window (Session, Weekly, Monthly) with a reset countdown. When meters are unavailable the panel shows the status and the help text instead of empty meters.
- Tokens by day — one row per day for the last week: day, bar, tokens, with today bolded at the bottom. Hover today for its prompt and session count.
- Tokens by model — tokens per model with the bar behind each row scaled to the heaviest model. Hover for the input / output / cache split.
The widget appears in the bar once a scan finds usage (or limits arrive), and leaves the bar entirely on a machine that has never run OpenCode.
Go plan meters
Meters come from OpenCode's usage endpoint
(https://opencode.ai/zen/go/v1/usage). The key is resolved in this order:
OPENCODE_GO_API_KEYenvironment variable, otherwise- the OpenCode CLI login stored in
~/.local/share/opencode/auth.json(create it withopencode auth login).
The endpoint base can be overridden with OPENCODE_API_BASE_URL; it must be
https:// — the scanner refuses to send the key over plain HTTP. Without a
key the panel reports "Waiting for auth" and keeps showing local stats.
Data source
The scanner reads assistant-message metadata from OpenCode's SQLite database. The database resolution order:
- the widget setting
providers.opencode.dbPath, otherwise OPENCODE_DB/OPENCODE_DATABASEwhen set, otherwise- the freshest channel database (
opencode-<channel>.db— the one whose WAL was written most recently, since channel databases all carry real subscription traffic), otherwise $XDG_DATA_HOME/opencode/opencode.db(usually~/.local/share/opencode/opencode.db).
Set an explicit override in the widget settings with:
omarchy bar set markbusai.opencode-usage providers '{"opencode": {"enabled": true, "dbPath": "/custom/path/opencode.db"}}' --json
How scanning works
The scan is SQL-side: a json_extract filter (guarded by json_valid so a
malformed row can never abort the scan, with LIKE gates in front as pure
acceleration) streams only opencode-go assistant rows' token splits out of
SQLite — Python never holds the raw message JSON, so huge databases stay
cheap.
Results are cached in ~/.cache/omarchy/agent-usage/ and refreshed
incrementally. A watermark envelope records where the last scan stopped; when
the database changes, only rows newer than the watermark are re-read and
merged into the cached totals, with a 10-minute overlap band so rows written
asynchronously are neither lost nor double-counted. Rows are also re-read when
their time_updated moved past the previous scan (opencode finalizes a
message's tokens in place after creation); an edited row forces one exact
full rescan, since the merge cannot correct a stale contribution. A day
rollover, a shrunken table, a band-width change, or a corrupt envelope all
fall back to a full scan too. The cache files are locked down (0600 files,
0700 directory).
Install
omarchy plugin add https://github.com/markbus-ai/omarchy-opencode-usage --enable
The widget joins the bar's default layout at the next shell reload
(omarchy-restart-shell). Remove it with:
omarchy plugin remove markbusai.opencode-usage
Settings
Settings live in the widget's entry in ~/.config/omarchy/shell.json. Set
them with omarchy bar set markbusai.opencode-usage <key> <value>:
| Key | Default | What it does |
|---|---|---|
refreshIntervalSec |
900 |
How often local scans and snapshots refresh |
syncMode |
"Off" |
"On" writes this machine's snapshot and merges the others |
syncDir |
"" |
A folder synced by Syncthing, Dropbox, rsync, … |
syncFileName |
<hostname>.json |
This machine's snapshot file |
syncDeviceId |
hostname | Stable device name inside the snapshot |
Numbers need --json, or they land in shell.json as strings:
omarchy bar set markbusai.opencode-usage refreshIntervalSec 300 --json
omarchy bar set markbusai.opencode-usage syncDir '~/Sync/opencode-usage'
With syncMode on, every *.json snapshot in syncDir is merged, so today,
the last 7 days, and the all-time totals cover every machine you code on —
active days are unioned by date rather than summed. The snapshot contains
counts only, never conversation content.
Interactions
- Bar icon: left = panel, right = refresh, middle = next provider.
- Panel:
j/kscroll,ror Enter refresh, Tab moves to the neighboring bar panel, Esc closes. - IPC:
omarchy-shell markbusai.opencode-usage <open|close|toggle|refresh|next>.
Troubleshooting
- "Waiting for auth" — no key found. Run
opencode auth loginor exportOPENCODE_GO_API_KEY(restart the shell after either). - "OpenCode's usage endpoint rejected the key (status 401/403)" — the key is invalid, expired, or the account has no Go plan. Local stats still show.
- Meters missing after a 429 or 5xx — the endpoint is rate limiting or having an outage; the panel keeps local stats and retries on the next refresh.
- "OpenCode data not found" — run
opencodeonce to create its usage database. - "OpenCode scan failed" — the database is unreadable or its schema is unsupported (for example an unrelated SQLite file at the resolved path).
- Stale totals — the cache lives in
~/.cache/omarchy/agent-usage/; deleting it forces the next scan to rebuild everything from scratch.
License
MIT. The OpenCode mark is a trademark of its respective owner and is used here to identify the tool this widget reports on.