Unofficial continuation of
marckrenn/pi-sub. This fork keeps the upstream MIT license and attribution, but publishes packages under the@eiei114npm scope.
Monorepo for the sub-* extension ecosystem: a shared usage core (sub-core), UI clients (like sub-bar), and headless consumers that subscribe to usage updates.
This fork exists because the upstream repo has been quiet while a few maintenance issues and PRs were waiting. The goal is to keep pi-sub usable for current Pi users, not to present this fork as the upstream project.
Initial changes in this fork:
- Package scope changed from
@marckrenn/*to@eiei114/*for npm publishing. - Install docs updated for the
@eiei114packages. - Cache writes are more robust on Windows by retrying transient rename failures and cleaning up temp files.
- Cache lock ownership is safer so one process does not release another process's lock.
turn_endandtool_resultrefreshes now respect the cache TTL instead of forcing network requests every turn.
See docs/WINDOWS_CACHE_CONTRACT.md for the exact cache/lock/TTL contract and the source files that implement it.
- sub-core: fetches usage + status, manages cache/locks, owns provider selection, and emits updates via
pi.events. - sub-bar: UI widget that renders the current usage state above the editor.
- sub-status: compact status-line client that renders the current usage state via
ctx.ui.setStatus(...). - sub-shared: shared types + event contract (published to npm as
@eiei114/pi-sub-shared).
sub-core can power multiple sub-* extensions at once, including rich UI clients like sub-bar and compact/headless-friendly clients like sub-status.
| Package | Version | Downloads | Description |
|---|---|---|---|
@eiei114/pi-sub-core |
Shared fetch/cache core (pi extension). | ||
@eiei114/pi-sub-bar |
Rich widget display client (pi extension). | ||
@eiei114/pi-sub-status |
Compact status-line display client (pi extension). | ||
@eiei114/pi-sub-shared |
Shared types + event contract (npm package). |
| Package | Description |
|---|---|
pi-sub-compare |
Usage comparison chart across multiple providers. |
pi-sub-model-switcher |
Auto model/provider switching when reaching a usage threshold. |
pi-sub-account-switcher |
Cycle between multiple subscriptions at usage thresholds. |
If you’d like to work on these, PRs or standalone packages are welcome.
- Node.js >= 24 (see
.nvmrc) - npm (bundled with Node)
Install the rich widget via the pi package manager (it bundles sub-core):
pi install npm:@eiei114/pi-sub-barFor a compact status line, install the optional client too:
pi install npm:@eiei114/pi-sub-statusInstall @eiei114/pi-sub-core separately only when you want the headless core without a display client. Installing sub-core separately alongside sub-bar or sub-status can load duplicate core instances.
For local development, link the display client(s) you want; their package metadata loads sub-core automatically:
git clone https://github.com/eiei114/pi-sub.git
cd pi-sub
npm install
ln -s /path/to/pi-sub/packages/sub-bar ~/.pi/agent/extensions/sub-bar
ln -s /path/to/pi-sub/packages/sub-status ~/.pi/agent/extensions/sub-statusAlternative (no symlink): add only the client extensions you want to ~/.pi/agent/settings.json:
{
"extensions": [
"/path/to/pi-sub/packages/sub-bar/index.ts",
"/path/to/pi-sub/packages/sub-status/index.ts"
]
}
sub-sharedis an npm dependency and is pulled automatically. The client package metadata includessub-core; do not add a separatesub-coreextension unless you are using the headless core by itself.
sub-core is the source of truth. It emits updates and accepts requests/actions over pi.events.
To keep UI clients responsive (like sub-bar), prefer this sequence when a model or session changes:
- Render cached state immediately (even if stale).
- Fetch fresh usage in the background.
- Re-render when new data arrives.
Why: awaiting fetches inside pi.on("session_start") / pi.on("model_select") blocks other extension handlers, so UI renders can lag behind network calls. In sub-core we use a non-blocking refresh (void refresh(...)) and allow stale cache (allowStaleCache: true) so cached usage is emitted before the forced fetch finishes. UI clients should listen for sub-core:update-current and render whenever state changes.
Broadcasts
sub-core:ready→{ state, settings }(first load)sub-core:update-current→{ state }(cache hit or fresh fetch)sub-core:update-all→{ state }(cached entries + current provider)sub-core:settings:updated→{ settings }
Requests (pull)
sub-core:request→{ reply, includeSettings? }sub-core:request→{ type: "entries", reply, force? }
Actions (mutate core state)
sub-core:settings:patch→{ patch }(persists core settings)sub-core:action→{ type: "refresh" | "cycleProvider", force? }
UI extensions like sub-bar and compact clients like sub-status listen for updates and render the current provider state in their own format.
Settings live in the agent directory to survive updates (legacy extension settings.json files are migrated on first run when present, and removed after successful migration). Cache/lock files live under ~/.pi/agent/cache/sub-core; legacy cache/lock files next to the sub-core extension entry or in the agent root are migrated and removed on first run.
- sub-core settings:
~/.pi/agent/pi-sub-core-settings.json - sub-bar settings:
~/.pi/agent/pi-sub-bar-settings.json - cache:
~/.pi/agent/cache/sub-core/cache.json - lock:
~/.pi/agent/cache/sub-core/cache.lock
You must update sub-shared (provider id + metadata), sub-core (fetch layer), and sub-bar (display/UI).
Unofficial provider APIs: Cursor, OpenCode, Command Code, xAI (Grok), Devin, and Ollama Cloud usage endpoints are unofficial and may change without notice. Failures soft-error in the UI; disable the provider in settings if an API shape breaks.
xAI (Grok) limitations: the xAI provider reports the subscription quota (SuperGrok/Grok plan) of the single base
xaiOAuth credential in~/.pi/agent/auth.json. xAI API keys (XAI_API_KEY) are not accepted for this endpoint — they are valid credentials for the developer API, just not for subscription quota. Additional numbered accounts (xai-2, …) are not supported and intentionally show no usage instead of the base account's numbers.
Devin limitations: the Devin provider reports the organization quota behind the
devincredential in~/.pi/agent/auth.json, which is the samedevin-session-token$…thedevinmodel provider stores. Quota is scoped to an organization, so a token that belongs to several of them is tried primary-first and the first readable organization wins. The endpoint returns bare percentages with no documented direction; they are read as the used share, which is what a freshly allocated plan reporting0means.
Ollama Cloud limitations: the Ollama provider reads
ollama.com/api/usagewith an API key (OLLAMA_API_KEYor theollama-cloudcredential in~/.pi/agent/auth.json, i.e./login ollama-cloud); the Ed25519 CLI credential fromollama signinis not accepted by this endpoint. Windows appear only when the account has them — a Pro plan may reportmonthlyonly.usageis a 0..1 used fraction, andactivity.costis the period spend in USD.
- Add provider name to
PROVIDERS/PROVIDER_METADATAinpackages/sub-shared/index.ts.
- Implement fetcher in
packages/sub-core/src/providers/impl/<provider>.ts. - Register provider in
packages/sub-core/src/providers/registry.ts. - Add URL constants in
packages/sub-core/src/config.tswhen needed.
- Add display metadata in
packages/sub-bar/src/providers/metadata.ts. - Add settings UI + defaults in
packages/sub-bar/src/providers/settings.tsandpackages/sub-bar/src/settings-types.ts.
- sub-core owns fetching, caching, status lookup, provider detection, and emits update events.
- sub-bar owns display rules, formatting, per-provider UI settings, and visibility of windows/extras.
- If you add new shared types or provider metadata used by multiple packages, update sub-shared and re-export.
Use this rule of thumb when deciding where a feature lives:
Put it in sub-core when:
- It affects data fetching, provider detection/selection, or status polling.
- It changes event contracts (
sub-core:*events) or tools (sub_get_usage,sub_get_all_usage). - It introduces shared settings that should affect all clients.
- It requires cache/lock behavior or cross-window coordination.
Put it in sub- when:*
- It is presentation-only (formatting, layout, colors, widget behavior).
- It is UI-only settings (visibility toggles, label text, window ordering in display).
- It targets a single client (e.g. sub-bar specific display change).
If both layers need it:
- Add data and settings in sub-core (and
sub-sharedtypes), then consume in sub-bar. - Update docs and tests for the shared contract.
- New API field or rate window data → sub-core (fetch + cache), then surface in sub-bar.
- New bar style or status icon pack → sub-bar only.
- New provider enablement behavior → sub-core (and sub-bar UI can forward settings).
npm installCommon commands:
npm run check— typecheck all workspacesnpm run test— run workspace tests (sub-bar + sub-core + sub-shared + sub-status)npm run lint/npm run lint:fix— lint TypeScriptnpm run format— format with Prettiernpm run verify— run check + test + lint
Watch mode:
npm run check:watch -w @eiei114/pi-sub-core
npm run check:watch -w @eiei114/pi-sub-bar
npm run check:watch -w @eiei114/pi-sub-status
npm run check:watch -w @eiei114/pi-sub-shared
npm run test:watch -w @eiei114/pi-sub-bar
npm run test:watch -w @eiei114/pi-sub-statusWorkspace-specific commands:
npm run check -w @eiei114/pi-sub-core
npm run check -w @eiei114/pi-sub-bar
npm run check -w @eiei114/pi-sub-status
npm run check -w @eiei114/pi-sub-shared
npm run test -w @eiei114/pi-sub-core
npm run test -w @eiei114/pi-sub-bar
npm run test -w @eiei114/pi-sub-shared
npm run test -w @eiei114/pi-sub-status