Skip to content

Releases: moutazideal/opencode-vitals

OpenCode Vitals 0.1.7

Choose a tag to compare

@github-actions github-actions released this 28 Sep 09:45

[0.1.7] - 2026-09-28

The idle window is ten seconds, and it no longer mistakes a slow reply for an
idle machine.

Fixed

  • A long reply took the bar down with it. The idle window was measured from
    the last finished response, so a reply that streamed for a minute — before
    any record existed — put the bar away at the exact moment its numbers were
    worth reading. Liveness is now stamped by any event for a session, so work in
    progress keeps the bar up.
  • A replayed batch of events could pass for a live session. The stamp used
    the event's own created, and a replayed event carries the time it was
    recorded, not the time it arrived. It is the arrival time now.
  • The companion tick is 1s, down from 5s, so the bar leaves inside the ten
    seconds it promises instead of at the next five-second boundary.

Changed

  • IDLE_HIDE_MS is 10 seconds, down from fifteen minutes, and COMPANION_TICK_MS
    is 1s. Both are exported. A session that has never sent an event still keeps
    the bar, so a first run is not a bar that never appears.

Measured

  • 250 plugin checks and 142 bar checks, unchanged from 0.1.6 in count and
    different in content: the idle cases now pin that activity beats a finished
    record, that a replayed event counts as activity now, that the tick fits
    inside the window, and that the same runtime stands the bar down once the
    events stop.

Upgrade: npx opencode-vitals install

OpenCode Vitals 0.1.6

Choose a tag to compare

@github-actions github-actions released this 28 Sep 09:38

[0.1.6] - 2026-09-28

The bar stopped showing you somebody else's numbers, and stopped ignoring the
work you delegated.

Fixed

  • A session with no measurements displayed another session's totals. The bar
    resolved no session for a freshly opened tab and fell back to the last session
    that published a record, so a new session showed another one's turns and
    tok/s with nothing on screen to say so. It now says it is waiting for a
    response, and names no number it cannot attribute.
  • Every OpenCode instance on the machine overwrote the current session. The
    status directory is one temporary path shared by all of them, and the current
    session was a single slot, so the last instance to publish won it. The file is
    now a map keyed by project, and the plugin merges the other entries instead of
    replacing them. A v1 file is still read, so a bar and a plugin from different
    versions can overlap during an update.
  • The bar is told which project spawned it. It is one window for one screen
    and every project shares the files it reads; it now answers only with sessions
    belonging to its own project, and falls back to a guess only when there is
    exactly one candidate and so nothing to choose between.
  • Subagent work was never counted. A subagent runs as a session of its own,
    so its steps and tokens landed in a bucket nobody was looking at: measured on
    one machine, 22 of 50 tracked sessions were subagents holding 3267 of 5044
    steps. The parent link is now asked of the session API once per session, and
    the delegated steps and tokens are credited to the session that asked for the
    work.
  • A bar outlived every session it was measuring. Whether a bar belongs on the
    screen was decided from the OpenCode process being alive, and a service stays
    alive with nothing open, so the bar stayed up for a program nobody was using.
    It now stands down after fifteen minutes with no measured response. A session
    that has never been measured keeps the bar, so a first run still shows it.
  • The last10 label now reads last10 resp. It is the mean rate of the last ten
    responses and always was; the steps inside one reply are not ten separate
    answers. The card is 450 pixels wide to fit the clearer label.

Changed

  • A subagent's streaming time is deliberately not added to its parent's. The
    session rate is generated tokens over stream time, and a subagent's tokens
    without its seconds would print a speed the parent never ran at. The parent's
    rate stays its own; the subagent's rate stays on its own session, where its own
    time is known. Delegated work is counted apart in subagentTurns and
    subagentSteps so the split is visible in the record.
  • Sessions carry their project, and every totals snapshot carries it too, so a
    bar can tell "not measured yet" from "another project's measurement".
  • barExpectedFrom takes an openSessionAt; SUBAGENT_FIELDS,
    SESSION_LOOKUP_TIMEOUT_MS, IDLE_HIDE_MS and COMPANION_TICK_MS are
    exported for the tests.

Measured

  • 246 plugin checks and 142 bar checks, up from 223 and 135. The bar suite grew
    by seven checks that pin the new attribution rules: no borrowing another
    session's numbers, per-project isolation, a v1 file still readable, and the
    delegated work counted in the parent without touching its rate. The plugin
    suite grew by nine: the subagent credit and every way it must not leak into
    the parent's rate, a root session, a missing session API, the project on the
    record, the per-project current-session map, and the idle stand-down.

Upgrade: npx opencode-vitals install

OpenCode Vitals 0.1.5

Choose a tag to compare

@moutazideal moutazideal released this 26 Sep 15:57

Honest rates, a second reading, and a bar you can size

tok/s was sometimes wrong, and in one measured case wrong by two orders of magnitude. A step's token count includes the tokens spent writing its tool call, and those arguments stream as session.tool.input.* rather than text — they were counted in the numerator with no time in the denominator. On this machine that printed 4686 tok/s for a turn whose visible output was 80 characters. Tool-call arguments are measured as model time now, and the record carries toolArgCharacters and toolArgDeltaCount so the arithmetic can be checked. A four digit rate prints short (4.7k) so the line always fits.

Totals are merged as whole snapshots too. The plugin and the bar each took the maximum of every field separately, so a record could hold one turn's token count next to another turn's stream time — and every reader divides one by the other. Totals are ranked and replaced as a whole now, so an older snapshot can no longer walk the numbers backwards.

New in the bar

  • The average of the last ten responses, next to the session average. · N last10 is the mean of the rates of the last ten completed responses on screen. The session average is the whole session divided as one sum and barely moves once a session is long; the last-ten reading reacts to the reply you just watched. The rates live in the session totals, so they survive a plugin reload.
  • The bar is yours to size. Drag the bottom-right grip, or hold Ctrl and use the wheel; everything scales together and the size is remembered. Right-click resets it to 100%, OPENCODE_LATENCY_SCALE pins it, the range is 0.6×–2.5×, and the collapsed bar scales with the same number.
  • The bar steps aside when your attention moves. Minimized, another program focused, or another window covering OpenCode by 60% — each one fail-open, and each switchable with OPENCODE_LATENCY_HIDE_UNFOCUSED=0 or OPENCODE_LATENCY_HIDE_OCCLUDED=0.
  • New screenshots at 2× from the running program, and a real desktop capture sent in by the project's author, reading 7 turns · 66 steps · 243 tok/s · 311 last10.

Also fixed

  • Clicking the bar made it flicker. Tk's winfo_id() names the child window it draws in, while the window manager tracks the parent, and the parent is what _NET_ACTIVE_WINDOW reports the moment the bar is clicked. The manager's window is matched once by title now, and a click on the bar counts as being in the app.
  • A restart could freeze a session's totals, last-ten list included. The totals file is part of the seed now — history first, then the file, newest snapshot per session — and the merged view is published at startup instead of waiting for the next response.
  • Publishing totals erased the other projects' sessions. Several instances of this plugin share one totals file, one per project directory, and each wrote only what it knew. The on-disk sessions are merged in before writing now.
  • A reconnect inflated the denominator. A stream that stops for longer than 30 seconds and continues under the same message id restarts the span there instead of counting the silence (STREAM_GAP_LIMIT_MS).
  • A reply that arrived in one piece showed no rate at all. The rate falls back to first-to-last, then to that message's own wall time, and the record carries rateSource so the fallback is visible.
  • Two executions running at once were reported as one turn. A second session.execution.started closes the open turn first, so parallel work and subagents no longer merge into a single inflated turn.
  • Unknown event types were dropped in silence. Anything outside the known lists is counted, reported on the record as unknownEventTypes, and logged at most once every few minutes.
  • A bar that correctly stood down was counted as a crash. It exits with code 6 when another instance owns the lock, and the plugin treats that as the wanted state: no backoff, no failure line in the log.
  • The bar polled far harder than it needed to. Records are read through a stat-based cache, the poll beat went from 200ms to 500ms, the window search runs on its own slower beat with rejected window ids remembered, and the announcement write moved out of the render path.

Changed

  • log now defaults to false: one line per completed turn in somebody else's log file is noise, and the numbers are on the bar. Errors and version changes go through a new warn() path.
  • The selftest reports whether xprop and xwininfo are present, as notes.

Measured

  • 223 plugin checks and 135 bar checks, up from 166 and 66.

Upgrade

npx opencode-vitals install     # updates in place

The bar process is replaced within about five seconds of the new script landing; a restart of OpenCode is only needed for the version recorded by the plugin to read 0.1.5.

OpenCode Vitals 0.1.4

Choose a tag to compare

@moutazideal moutazideal released this 26 Sep 07:21

The bar now follows the window

Minimize OpenCode and the bar goes away; restore the window and the bar comes back. On Linux/X11 the window manager is asked over _NET_WM_STATE with xprop, at most once a second, and the OpenCode window is found by WM_CLASS. Verified against the live display: the window is found and reported not hidden while maximized.

On other platforms, or when xprop is unavailable, the bar stays up rather than hiding a measurement it cannot be sure about.

Also in this release

  • The npm package page now renders its images. The README used relative paths (docs/bar.png), which GitHub resolves and npm cannot, so the package page showed broken images. Every image and the changelog link are absolute now, and the README test rejects a relative image source.
  • The platform table states the window-following behaviour per platform, and the tested OpenCode versions list now includes 2.0.18.
  • Built for OpenCode V2; the requirements table names the tested V2 releases.

Measured

  • 166 plugin checks and 66 bar checks. Three of the plugin checks enforce npm-safe image URLs; ten of the bar checks cover the new window-following behaviour (window-list parsing, the hidden state, the fail-open paths, and the bar withdrawing and returning).

Upgrade

npx opencode-vitals install     # updates in place

The bar process is replaced within about five seconds of the new script landing; a restart of OpenCode is only needed for the version recorded by the plugin to read 0.1.4.

OpenCode Vitals 0.1.3

Choose a tag to compare

@moutazideal moutazideal released this 26 Sep 06:57

The install command actually works now

0.1.2 shipped the audit fixes but not a working way to install itself from npm. Two things were wrong:

  • npx opencode-vitals-install asks npm for a package by that name, and opencode-vitals-install is a file inside this package, so npm answered 404. There is a bin called opencode-vitals now.
  • The installer decided whether it was started directly by comparing process.argv[1] with its own path as strings; npm runs bins through a symlink in node_modules/.bin, so the two never matched and the command exited silently, with no output and status zero. Both sides are resolved through symlinks now, with a regression test that runs the commands through a shim shaped exactly like npm's.

Install

npx opencode-vitals selftest     # can this machine draw the bar?
npx opencode-vitals install      # installs it into the directory OpenCode reads
# restart OpenCode

npx opencode-vitals status reports what is installed, npx opencode-vitals uninstall removes it, and --link, --dir and --force work as documented. The older names (opencode-vitals-install, opencode-vitals-selftest) still exist for scripts that add the package to a project.

Also in this release: a closed pipe (| head) no longer prints a node stack trace, and the README test that generates its checks from the README's npx commands now also proves that every subcommand it prints is one the dispatcher accepts.

Measured

  • 162 plugin checks and 56 bar checks, up from 156 and 56. The documented commands were run end to end from a packed tarball installed through npm: install copied 12 files at 0.1.3, and status, the --status alias and uninstall all behaved.

OpenCode Vitals 0.1.2

Choose a tag to compare

@moutazideal moutazideal released this 26 Sep 06:35

What this release contains

The audit fixes, published as they were. 0.1.1 went to npm before an audit of the shipped code, so it contains the two dangerous bugs below; 0.1.2 is 0.1.1 plus every fix, each with a test that fails on the old behaviour.

  • Windows: the liveness check killed the process it checked. Python hands any signal other than CTRL_C_EVENT/CTRL_BREAK_EVENT to TerminateProcess, so os.kill(pid, 0) would have terminated the OpenCode process the bar belongs to. Windows now asks OpenProcess/GetExitCodeProcess.
  • A bar that died at once was retried every five seconds forever — measured four spawns in sixteen seconds. Short-lived exits back off 15s, 30s, 60s … capped at five minutes, with the reason logged once. After the fix: attempts at 0s, 15s and 30s over fifty seconds.
  • A failed compaction swallowed the next response — measured zero records where one was expected.
  • Signalling trusted the pid alone, so a recycled pid in a stale lock could receive SIGTERM; nothing is signalled unless the command line says bar.py.
  • A malformed event no longer ends the subscription. The host call for missing token counts has a deadline. The storage lock waits before its fail-open path. Session totals are evicted by least recently updated.
  • The installer follows OpenCode's real plugin path (XDG_CONFIG_HOME || ~/.config) on every platform, --force over a file no longer crashes, and --uninstall refuses to remove a stranger.
  • The selftest no longer counts any plugin folder as an installed Vitals, and no longer calls a machine untrustworthy before the plugin is installed: runtime facts are notes, and the verdict names the real state.

One thing in this release does not work

The install command this release's README prints, npx opencode-vitals-install, is answered with 404 by npm — npx resolves package names, and that is the name of a file inside the package — and the installer exits silently with no output when npm runs it through its shim. Both are fixed in 0.1.3, which is otherwise the same code: npx opencode-vitals install.

Measured

  • 156 plugin checks and 56 bar checks, up from 132 and 44. Both headline fixes were re-measured with the same probes that found them.

Upgrading from 0.1.1: npx --package=opencode-vitals@0.1.2 -- opencode-vitals-install does not work, so either add the plugins entry and let OpenCode resolve this version, or use 0.1.3 and run npx opencode-vitals install.

OpenCode Vitals 0.1.1

Choose a tag to compare

@moutazideal moutazideal released this 26 Sep 06:11

Install with one command

Editing opencode.json to add a single line is a poor first step for anyone new, so the package installs itself into the directory OpenCode already reads, and never touches your configuration file.

npx opencode-vitals-selftest     # can this machine draw the bar?
npx opencode-vitals-install      # installs it
# restart OpenCode

The install command copies exactly the files npm ships, keeps the shell and Python files executable, prints the path it used, and makes no network calls of its own. Also available: --link (symlink while working on the source), --status, --uninstall, --dir PATH, and --force.

Running it twice is an update, not a second copy, and it removes files that a newer release no longer ships. It refuses to replace a directory that holds a different package, and it will not write outside the plugin directory.

Fixed

  • The install command's guard against writing outside the plugin directory compared path prefixes with the PATH separator (: on Linux) instead of the path separator, so every install was rejected. It could not have shipped working.
  • An install over an existing copy was reported as "a different package" because the guard looked for any manifest instead of comparing the package name, so an update could not run.
  • --status printed the internal word other-package about your own installation. It now says copy.

Measured

  • 132 plugin checks and 56 bar checks. The install command is covered for: copying the shipped list, keeping executable bits, creating nested directories, being idempotent, removing files a newer release drops, refusing to touch a different package, refusing to replace a link without --force, linking twice being a no-op, uninstalling, and plugin directory resolution.

Note

This release was published before an audit of the shipped code found two dangerous bugs (a Windows liveness check that terminated the process it checked, and a five second respawn loop for a bar that could not start). They are fixed in 0.1.2, which is 0.1.1 plus every audit fix.

Upgrading from 0.1.0: run npx opencode-vitals-install if you installed by folder, or restart OpenCode if you use the plugins config entry.

OpenCode Vitals 0.1.0

Choose a tag to compare

@moutazideal moutazideal released this 26 Sep 05:39

First public release of OpenCode Vitals: a small always-on-top bar that shows one honest line for the session you are working in.

◔  19 turns   65 steps   282 tok/s

What it measures

  • turns — responses completed in this session.
  • steps — model steps across those responses, so a turn that ran five tools is not mistaken for a fast one.
  • tok/s — generated tokens (output + reasoning) divided by active stream time. Tool executions stay out of the denominator, so the number is the model's speed and not your machine's.

Compaction executions and synthetic inbox items are excluded, and duplicate completions, late deltas and repeated plugin instances are deduplicated. When the provider reports no token counts, the bar shows – instead of inventing a number.

The bar

360×54 pixels, undecorated, always on top, no taskbar entry, no focus steal. Drag it anywhere and the position is remembered. The close button collapses it to a small square that keeps showing tok/s; clicking the square brings it back. It follows the Desktop tab you are looking at, and it leaves when you close OpenCode.

Install

{ "plugins": ["opencode-vitals"] }

Restart OpenCode. To check your machine first: npx opencode-vitals-selftest.

Honest platform note

Verified on Linux with GNOME/Mutter on X11. The macOS and Windows paths are ordinary platform code with a fail-open rule — when a check cannot run, the bar stays instead of disappearing — and their decision logic is covered by tests driven by a fake process list, but no macOS or Windows machine has run it yet. The selftest is the fastest way to find out on yours.

Privacy

No network code at all: no registry calls, no telemetry, no update pings. Prompts and responses are never stored. The one exception is a single read-only row from the Desktop app's own state database so the bar knows which tab you are looking at; no draft text is read.

Quality gates

103 plugin checks and 44 bar checks run before every publish, plus a selftest that reports the Tk version, the window type that stays undecorated and on top, the database path and the tab it read.