Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

omarchy-opencode-usage

Adds an OpenCode tab to the Agents panel in Omarchy's top bar, next to the Claude, Codex and Fireworks tabs it ships with.

Omarchy's Agents panel renders one tab per usage record it finds in ~/.local/state/omarchy/agents/usage/. Omarchy ships collectors that write claude.json, codex.json and fireworks.json. This repo adds one that writes opencode.json, by reading OpenCode's own local SQLite database.

No Omarchy files are modified, forked or vendored. It is one Python script, two systemd user units, and an installer.

the OpenCode tab in the Agents panel

What you get

Prompts and sessions for today, tokens by model, a 7-day activity sparkline, lifetime totals — and the current session's context occupancy in the tab's subtitle, e.g. Local gateway - session 25.1k.

Requirements

  • Omarchy, with the stock agents shell plugin
  • OpenCode, having been run at least once
  • Python 3.11+ (uses X | Y type syntax), stdlib only — no pip install

Install

git clone https://github.com/<you>/omarchy-opencode-usage
cd omarchy-opencode-usage
./install.sh

The installer copies the collector to ~/.local/bin/, the units to ~/.config/systemd/user/, then enables and starts the timer. It prints what it is about to do and asks before doing it.

Open the Agents panel. If the tab is not there yet, give the timer one cycle (2 minutes) or run systemctl --user start opencode-usage.service.

Uninstall

./install.sh --uninstall

Configuration

Everything is optional.

Environment variable Default Meaning
OMARCHY_OPENCODE_PROVIDER (empty) Count only this OpenCode provider. Empty counts all of them.

Set it in the unit, not your shell — the timer does not read ~/.bashrc:

systemctl --user edit opencode-usage.service
[Service]
Environment=OMARCHY_OPENCODE_PROVIDER=myprovider

The filter is read from the environment and never from opencode.json. That is deliberate: a per-project opencode.json can move the provider block, so reading the filter out of the same file it is meant to filter would let one edit move the check and the target together — and the collector would confidently report on the wrong thing. Config drift is reported in the tab's status line; it is never authoritative.

What it reads, and what it refuses to read

The collector opens OpenCode's database read-only (file:...?mode=ro, plus PRAGMA query_only) and installs a sqlite3 authorizer that is deny-by-default against a two-entry allowlist: message.session_id and message.data. Everything else is denied, including a column added to an already-allowed table by a future OpenCode release.

That matters because the same database holds things a status widget has no business touching: account and control_account (OAuth tokens), credential (integration secrets), session_share (share secrets and URLs), and session_input, part and todo (your prompts and tool output, verbatim). An allowlist by column is the only shape that expresses "and nothing that gets added later, either".

It makes no network call, ever — and the systemd unit sets PrivateNetwork=yes, which turns that from "true because no import does it today" into a property the kernel enforces against any future edit.

Before the record is written it is shape-asserted: exact key set, every string value constrained to a tight charset, every number checked to be a number. Model ids are sanitized and the number of distinct model keys is capped, so an unexpected id can never become an unbounded new key in a file that may sync to other machines.

Two things it deliberately does not do

It does not report cost. Against a local or self-hosted gateway, session.cost is 0.0 for every row because local inference has no price attached. Rendering that as "$0.00 spent" would be a confident lie rather than a measurement, so the field is not read at all.

It does not invent a context ceiling. The tab shows how large the current session's prompt is, not how large it may get. OpenCode only knows a model's context limit if the client config declares one — for a custom provider with no declared limit, OpenCode's own overflow check is disabled and the ceiling genuinely is not knowable from the database. Showing a percentage against a guessed maximum would manufacture a number, so the tab shows the occupancy and stops there.

A note on how it is scheduled

Omarchy's omarchy-agent-usage-update only globs "$OMARCHY_PATH"/bin/ for collectors, so a collector in ~/.local/bin is never discovered by it. The panel watches the records directory and renders any valid record it finds; the refresh path runs first-party collectors only. So this ships its own timer rather than relying on an extension point that does not exist for a user.

The timer runs every 2 minutes, not every 15, because the panel's per-record file watcher applies a rewrite immediately rather than waiting for its own refresh — so the session-context number is only useful at a cadence close to live. Measured cost is ~0.6–0.8 s per run, roughly a 0.5% duty cycle.

Known rough edge

If a scan fails on a machine that has no previous record, the collector writes an honest not-ready record with zero counters — and Omarchy's providerHasData() gate hides a zero-counter tab. So the tab disappears and the stated reason is never displayed: could not answer renders identically to never asked.

Where a previous good record exists, the collector carries those totals forward (marked ready: false, with the reason and a stale-totals note) specifically so the tab survives and can tell you something is wrong. Nothing is ever manufactured on a fresh machine.

Licence

MIT — see LICENSE.

Not affiliated with the Omarchy or OpenCode projects.

About

Adds an OpenCode tab to Omarchy's Agents panel. Reads OpenCode's local database read-only; no network calls.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages