@astrosheep/pi-quota

Composable quota bar with Codex, Kimi, OpenCode Go, new-api, DeepSeek, Sub2API and YesCode adapters

Packages

Package details

extension

Install @astrosheep/pi-quota from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@astrosheep/pi-quota
Package
@astrosheep/pi-quota
Version
0.12.2
Published
Oct 13, 2026
Downloads
1,998/mo · 775/wk
Author
astrosheep
License
unknown
Types
extension
Size
183.4 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@astrosheep/pi-quota

Compact quota status for Pi: Codex, Kimi, OpenCode Go and DeepSeek work out of the box; new-api, Sub2API and YesCode use explicit provider bindings.

pi install npm:@astrosheep/pi-quota

Reload Pi after installing or changing settings. Use /usage for a persistent, context-free snapshot of providers with available quota data, including the current one. Use /usage --all to inspect provider errors. Automatic footer polling never adds chat items.

What it shows

Kind Footer /usage
Renewable window 5h [▆] 72% ↺ 2h15m Horizontal bar, remaining percentage, reset, exact amounts if available
Wallet Bal $12.34 Balance; optional Today/Lifetime spend
Fixed quota Quota [▇] $85.00 Remaining bar plus used and limit

A percentage always means remaining, not used. Unknown is [?] ?; an expired reset is ↺ due with the stale percentage hidden until the next successful query. A wallet balance never becomes a percentage. Historical spend is not a quota.

The footer shows at most two windows — set quotaUsage.footerWindows to a positive integer or "all" to show more — and +N for additional ones, for the current provider only. /usage queries all configured supported providers and shows successful results with all windows, grouped by provider; it does not open a modal or send messages to the model. Snapshots remain frozen at capture time. Query failures replace stale values in the footer and appear in /usage --all.

Configure providers

Add bindings in global ~/.pi/agent/settings.json under quotaUsage (not project settings):

{
  "quotaUsage": {
    "providers": {
      "my-sub2api": { "adapter": "sub2api" },
      "my-new-api": { "adapter": "new-api", "quotaPerUnit": 500000, "currency": "USD" },
      "deepseek": { "adapter": "deepseek" },
      "opencode-go-2": { "adapter": "opencode-go" },
      "yes-vg": { "adapter": "yescode", "username": "you@example.com", "password": "..." },
      "my-script": { "adapter": "exec", "command": ["my-quota-cli", "--json"], "label": "My Script" },
      "my-alias": { "adapter": "alias", "of": "my-sub2api" }
    }
  }
}

Use the exact Pi provider ID. Built-in providers and explicitly bound providers are queried; explicit bindings replace a built-in adapter with the same ID. Unknown keys or adapter options are rejected. The API key comes from Pi's provider auth, and the endpoint comes from the provider's registered model; do not put model API keys in quotaUsage.

Adapter Endpoint Result
sub2api {baseUrl}/usage Wallet, key quota, rate windows or subscription day/week/month windows; API-key Today/Lifetime actual spend
new-api Account PAT /api/user/self, falling back to key-native billing/token endpoints Account balance + lifetime spend; finite key quota when explicitly granted; not a renewable window
deepseek Official /user/balance Balance only; no invented spend
opencode-go Official https://opencode.ai/zen/go/v1/usage 5h/1w/30d windows, same as the built-in opencode-go adapter
yescode co.yes.vg login → session → /api/v1/auth/profile + /api/v1/user/balance Wallet (subscription + pay-as-you-go) plus daily/weekly subscription windows
exec Runs command as argv (no shell), parses stdout as a QuotaSnapshot JSON Whatever the command reports; fetchedAt is stamped by the adapter, never by the command
alias No endpoint — delegates to the of binding (built-in or explicit) Shares the target account's quota; one folded row in /usage

Bind opencode-go to a second OpenCode Go account: register it as its own Pi provider in models.json with its own apiKey (e.g. opencode-go-2), then bind that ID here. The built-in opencode-go binding is implicit and needs no entry; the reserved IDs openai-codex, kimi-coding and opencode-go cannot be rebound.

Sub2API accepts no extra settings; it preserves deployment subpaths. Rate-only keys need not expose a top-level currency (rates are USD). Unknown rate-window durations and invalid limits fail rather than inventing a 1d window. Subscription periods without a cap are omitted. Its dashboard /api/v1/usage is a separate, JWT-authenticated paginated request log; the adapter uses the API-key /v1/usage instead.

For new-api, quotaPerUnit defaults to 500000, currency to USD. Optionally add dashboardAccessToken (console system access token) and dashboardUserId (positive numeric ID required by some old forks) to that provider. This PAT is the only optional secret kept in settings. The account endpoint takes precedence; failures fall back to the key-native billing and token usage paths. Without a PAT, an API key cannot read the dashboard account quota. A finite key reports a quota bar only when total_granted is present and consistent with used plus remaining; otherwise it shows a balance and lifetime spend, without inventing a limit. Values in settings and server error bodies are never logged.

For YesCode, username is the account email and password the account password; both stay in global settings.json, which is the trade-off of this adapter. YesCode rejects API keys on account endpoints ("requires a user session or JWT"), so the adapter logs in via /api/v1/auth/login, caches the issued cookie/JWT in memory for ~23 hours, and re-logs in once on an auth failure. The model cr_ key is never sent to account endpoints; it only pins the provider to the official co.yes.vg origin. Balances are summed from subscription and pay-as-you-go; daily/weekly windows appear only when the account actually has those caps.

Exec runs the configured argv without a shell and parses stdout as a QuotaSnapshot JSON object: windows, optional balance, allowance and spend — the same schema every adapter produces, enforced by the controller. fetchedAt is stamped by the adapter; a value from the command is ignored. The controller bounds every query (10s default) and kills the child on timeout; non-zero exits, unreadable JSON and output over 256KB become errors, and command output is never logged. The child inherits Pi's environment, so keep secrets in env vars, not in quotaUsage.

Alias binds a second provider ID to the same account: "of" names the real binding, which may be a built-in or an explicit adapter. The alias never queries on its own — a session on the alias shows the target's quota in the footer, queried under the target's identity and credentials, and /usage folds the alias into the target's single row with the session pin (and ★) following the fold. Aliases are one hop: of must not itself be an alias, and an alias whose target is not configured fails visibly instead of vanishing.

Authenticated quota URLs must use HTTPS, except HTTP loopback for local deployments. URL userinfo, query strings, fragments and HTTP redirects are rejected; credentials never follow redirects. Fixed official endpoints reject custom origins. No browser cookie scraping or credential discovery.

Architecture

Pi model/auth → adapter → QuotaSnapshot → controller → footer / /usage / state event
  • src/query/: protocol-specific parsing, credential resolution, safe URLs, bounded GET client, state validation and controller. No Pi/TUI imports.
  • src/config.ts: strict global provider bindings.
  • src/bar/: pure compact rendering and persistent multi-provider chat card.
  • src/extension.ts and src/index.ts: Pi lifecycle and auth bridge.

QuotaSnapshot separates balance (wallet), allowance (finite key quota), windows (renewable limits), and optional spend (Today/Lifetime actual charge). This prevents unrelated amounts from sharing an ambiguous Used label. Custom adapters can be passed to createQuotaExtension({ adapters: [...] }); see examples/custom-provider.ts. Provider aliases are never inferred.

The extension publishes structured state on quota-bar:state:v1. Use createQuotaExtension({ footer: false }) to consume it without this extension's footer. Normal queries are throttled, cancellable, timeout-bounded and backed off after errors. /usage forces a fresh query for every configured supported provider, then omits failed results from its default view. Switching provider discards old footer values immediately.

Standalone query layer

Non-Pi consumers (bridges, plugins, scripts) can import the query layer without pulling in the Pi extension or its peer dependencies:

import {
  AdapterRegistry, codexAdapter, createJsonClient, createPostJsonClient,
} from '@astrosheep/pi-quota/query';

const registry = new AdapterRegistry([codexAdapter]);
const snapshot = await registry.get('openai-codex')!.query({
  provider: 'openai-codex',
  signal: AbortSignal.timeout(10_000),
  now: () => Date.now(),
  getAuth: myAuthResolver,      // you supply credentials
  getJson: createJsonClient(),
  postJson: createPostJsonClient(),
});

The subpath exports every adapter, the registry/controller, option validators, HTTP/exec plumbing, and all query types. Peer dependencies are marked optional, so installing for the query layer alone stays light.

Development

Requires Node ≥22.18 and a compatible Pi installation.

npm install --ignore-scripts
npm test
npm run typecheck
npm run demo