pi-usage-hub
v1.0.9
Published
Usage hub for pi — provider quota/balance registry with cache, /usage-hub panel, and pull API
Maintainers
Readme
pi-usage-hub
Track provider quotas and balances in pi — /usage-hub panel, session stats, and a pull API for footer integration.
Why
Checking separate dashboards for DeepSeek, NewAPI relays, xAI, Kiro, OpenCode Go, and ARK breaks flow. pi-usage-hub brings their quotas and balances into one TUI panel while keeping footer integration optional and pull-based.
Install
pi install npm:pi-usage-hubCommands
| Command | Description |
|---------|-------------|
| /usage-hub | Show quotas and balances for detected providers |
| /usage-hub session | Show local session token and cost stats |
| /usage-hub login <name> | Open browser login for a cookie-based provider |
Inside the panel, Tab switches between Quota and Session. In the Quota view, ↑↓ scrolls when content overflows.


Configuration
Create ~/.pi/agent/pi-usage-hub.json. Provider order is preserved in the panel.
{
"providers": [
{
"type": "deepseek",
"apiKey": "sk-..."
},
{
"name": "xh",
"type": "newapi",
"matchProviders": ["xh-cc", "xh-glm"],
"host": "https://example.com",
"token": "...",
"userId": "1"
},
{
"name": "ocg",
"type": "opencode-go",
"workspaceId": "wrk_...",
"matchProviders": ["opencode-go"]
},
{ "type": "ark" },
{
"type": "xai",
"matchProviders": ["xai-auth", "xai", "grok-cli"]
},
{ "type": "kiro" }
]
}Common fields
| Field | Required | Description |
|-------|----------|-------------|
| type | yes | Built-in provider factory |
| name | no | Instance key; defaults to type, then type-2, type-3, and so on |
| matchProviders | no | Additional model.provider values mapped to this entry |
| shortLabel / label | no | Override the footer label or panel title |
| hidden | no | Exclude from panel and footer; /usage-hub login still works |
Provider types
| Type | Credentials | Notes | Website |
|------|-------------|-------|---------|
| deepseek | apiKey | Account balance | deepseek |
| newapi | host, token, userId | Balance and today's spend; supports multiple instances | newapi |
| opencode-go | workspaceId; optional auth | Uses the configured auth cookie or macOS Chrome | opencode |
| ark | optional cookie, csrfToken | Uses the configured Cookie header or macOS Chrome | 火山引擎 |
| xai | — | Reads auth.json or ~/.grok/auth.json | x.ai/grok |
| kiro | — | Reads the Kiro OAuth entry from auth.json | kiro.dev |
For NewAPI, token is the system access token (not a chat sk- token), and userId is sent as New-Api-User. See Authentication and Generate access token.
For ARK and OpenCode Go, configured cookie / auth values take priority over Chrome. Manual credentials disable /usage-hub login for that entry; replace them when they expire. Automatic Chrome cookie reading is macOS only.
Related auth packages
These are companion packages, not npm peer dependencies:
| Type | Companion | Purpose |
|------|-----------|---------|
| xai | pi-xai-oauth | /login xai-auth writes the OAuth entry to auth.json |
| kiro | pi-provider-kiro-dev | Provides /login kiro, models, and the auth.json entry |
pi-usage-hub only reads those credentials; it does not run their OAuth flows.
Built-in providers cover the integrations used and smoke-tested by the author. To support another provider, register a custom provider from an extension, or fork the package and submit a PR.
Add a custom provider
See examples/custom-provider.ts for a self-contained extension that implements and registers a provider. Copy it into ~/.pi/agent/extensions/, then adapt the endpoint, credentials, response shape, and labels.
pi.events.on("pi-usage-hub:ready", (hub) => hub.register(myProvider));
pi.events.emit("pi-usage-hub:register", myProvider);
pi.events.emit("pi-usage-hub:unregister", { key: "my-relay" });Pull API
The hub caches results for 60 seconds and deduplicates concurrent requests. It never pushes footer text: consumers start refreshes without blocking lifecycle events, read the cached summary, and re-render when notified.
| API | Role |
|-----|------|
| pi-usage-hub:ready | Provides the hub; also emitted on session_start |
| hub.refresh({ model?, force? }) | Refreshes the matching provider and returns its summary |
| hub.getSummary(model?) | Synchronously reads the cached one-line summary |
| pi-usage-hub:updated | Signals { key, summary } after a cache update |
Footer example

type UsageHub = {
getSummary(model?: { provider?: string }): string | null;
refresh(opts?: {
model?: { provider?: string };
force?: boolean;
}): Promise<string | null>;
};
let usageHub: UsageHub | null = null;
let requestRender: (() => void) | null = null;
const offReady = pi.events.on("pi-usage-hub:ready", (hub: UsageHub) => {
usageHub = hub;
requestRender?.();
});
const offUpdated = pi.events.on("pi-usage-hub:updated", () => {
requestRender?.();
});
// Pi awaits lifecycle handlers; refresh in the background and re-render on pi-usage-hub:updated.
pi.on("session_start", (_event, ctx) => {
void usageHub?.refresh({ model: ctx.model, force: true });
});
pi.on("model_select", (event) => {
void usageHub?.refresh({ model: event.model, force: true });
});
pi.on("agent_end", (_event, ctx) => {
void usageHub?.refresh({ model: ctx.model, force: true });
});
// Inside footer render():
// const usageText = usageHub?.getSummary(ctx.model);
// "XAI 87% · ↻ 3d 16h"
pi.on("session_shutdown", async () => {
offReady();
offUpdated();
usageHub = null;
});License
MIT
