omp packages

pi-sub2api-usage0.4.0

Show the current sub2api site's balance and today's actual spend in the Pi footer (read-only)

GitHub Actions Published 7 days ago MIT ~209 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-sub2api-usage

Shows the balance and usage of the current sub2api site, permanently, in Pi's footer. Read-only, zero config, zero timers.

Sub2 $1.83 · Today $0.1866

The balance is coloured by severity: healthy is green, below $2 turns yellow, below $0.5 turns red. One glance tells you whether to top up.

How it differs from pi-sub2api

Extensions like pi-sub2api exist to register a provider and discover models: they fetch /v1/models on startup, decide on a wire protocol and write auth.json. Balance display is only a side effect of that.

This extension does balance and nothing else, so it stays completely out of the traffic path — no provider registration, no model list, no change to any inference request.

Install

pi install npm:pi-sub2api-usage

Try it for a single invocation without declaring anything:

pi -e npm:pi-sub2api-usage

From a checkout, load the extension file directly:

pi --extension ./src/index.ts

The package manifest declares pi.extensions, so nothing else is needed.

Note that loading a checkout with --extension ./src/index.ts while the package is also installed from npm loads the extension twice. Both copies write the same status key, so every refresh arrives in pairs and /sub2-usage is registered twice.

What it looks like

The footer follows whichever billing mode the site reports. Each of the four has its own reading:

Mode Footer Meaning
Wallet Sub2 $1.83 · Today $0.1866 Available balance and today's actual spend
Quota Sub2 62% Percentage of the quota used
Subscription Sub2 1d 20% · 7d 30% · 1m 18% Used percentage of each daily / weekly / monthly window
Rate limit Sub2 5h 82% [⟳ 42m] Window used percentage and the reset countdown

Non-wallet modes omit the Today segment: their headline is the window cap, and every extension's status shares one footer line, so brevity wins. Today's spend is always available in /sub2-usage.

Colours

The balance is judged by absolute thresholds, and only when the site reports USD — for any other unit there is nothing to judge against, so the default colour is used:

Balance Colour
≥ $2 green (healthy)
< $2 yellow (time to top up)
< $0.5 red

The thresholds are deliberately smaller than the $10/$5 used by large-account balance displays: a typical sub2api balance sits at a few dollars, so copying those numbers would keep the footer permanently red and drain it of signal.

Window percentages are coloured by used ratio: under 70% green, 70% and above yellow, 90% and above red.

Precision: balance 2 decimals, spend 4. A single turn often costs $0.001–$0.01, so 2 decimals give the balance an at-a-glance overview while 4 keep small spend movements visible.

State markers

Display Meaning
Sub2 $1.83 · Today $0.1866 Normal
Sub2 ~$1.83 · Today $0.1866 Refresh failed; ~ marks the last successful value
Sub2 ! The API key is invalid or has expired
Sub2 ? The request failed and there is no cached value
(empty) The current model is not a sub2api site

The entire status text is an OSC 8 hyperlink pointing at the site root; it degrades to plain text on terminals that do not support hyperlinks, and the sequence is only applied in TUI mode.

The footer is published through ctx.ui.setStatus, so it appears under whichever status-bar composer is installed. It also works in RPC mode, where Pi supports setStatus but reports no theme support — so the text travels uncoloured there.

/sub2-usage

Prints the full detail (balance, today, total, last 3 days, by model), leading with the actual spend and showing the list price in parentheses. When the current model is not a sub2api site, it lists every candidate site it probed together with the outcome, so you can see why nothing is being displayed:

sub2api · site-a
https://sub2.example.com
Wallet balance (shared across keys) $1.83
Today 198 req · 28.4M tok · spend $0.1866 (list $0.7464)
Total 1385 req · 136.2M tok · spend $0.9672 (list $3.8688)
Last 3d 10-09 $0.1994 · 10-10 $0.5812 · 10-11 $0.1866
By model m-1 $0.9672 · 136.2M tok · 1385 req

One important semantic

Balance is shared per user; usage is per API key.

On the same sub2api site, several keys that point at one account all return exactly the same balance, while their usage and model_stats are tracked separately. So "another key reports the same balance but zero spend" is not a fault.

That is why the detail line in wallet mode is labelled "shared across keys".

Requirements

  • A sub2api site already configured in Pi's own config — a provider in models.json with credentials that can be resolved. The extension does not read CC Switch, Claude Code or Codex config files.
  • Node ≥ 22.19 — this is Pi's own floor, so the extension cannot ask for less.
  • Pi ≥ 0.83.0 — the release that introduced the agent_settled event used for the end-of-run refresh. Below that the footer would still render, but no run would ever refresh it.
  • The site must expose GET {base}/v1/usage with Bearer API key auth. An older sub2api without that endpoint is silently classified as "not a sub2api site".
  • baseUrl may be written as https://host or https://host/v1; either is normalised.

When it refreshes

Trigger Fires when Throttled?
session_start a session opens or resumes no
input you send a message yes
turn_end one LLM turn finishes, after its tool results land yes
agent_settled the whole run finishes no, forced
model_select the provider changes no
/sub2-usage you run the command no

turn_end is what keeps a long run moving. A single prompt can run for many minutes and execute dozens of tool calls, while agent_settled fires only once, at the very end — so refreshing on that alone would freeze the footer for the whole run, which is exactly when the spend climbs fastest. turn_end already follows its turn's tool results, so it also covers "after a tool call".

Turn-by-turn refreshes are throttled to one request per 30 seconds, and only one request is ever in flight. A ten-minute run therefore issues roughly twenty requests instead of one per turn. The agent_settled refresh bypasses the throttle, so the number you get at the end of a run is never swallowed by the window the last turn_end opened.

There are no timers and no background polling: the extension acts only on the events above and issues nothing at all while a session sits idle. Each request has a 5-second timeout, the second-tier candidate probe is concurrent, and every request is fire-and-forget and never blocks the conversation. The candidate probe's negative verdicts are cached for the session, so reopening /sub2-usage does not re-hit dead ends. A candidate that answers is always probed again (its balance moves), and a new session probes everything from scratch — so if you fix a key at the gateway mid-session, start a new session to see that candidate clear.

Security

  • Credentials are read only from Pi's own config and sent only to the baseUrl that the config entry declares.
  • The API key travels in the Authorization header only, never in a URL.
  • Logs and error messages carry only the provider name and the first 8 hex characters of the key's sha256 fingerprint; the full key is never printed.
  • Nothing is written to disk and no config file is ever modified.

Development

npm test          # vitest, fully offline
npm run typecheck # tsc --noEmit
npm run check     # both of the above — what CI gates on

The extension has no runtime dependencies. @earendil-works/pi-coding-agent and @earendil-works/pi-tui are supplied by Pi at runtime, so they are declared as peerDependencies and deliberately not bundled — a physical copy can bypass Pi's extension module mapping and produce duplicate module instances.