pi-usage-hub
Usage hub for pi — provider quota/balance registry with cache, /usage-hub panel, and pull API
Package details
Install pi-usage-hub from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-usage-hub- Package
pi-usage-hub- Version
1.1.1- Published
- Sep 2, 2026
- Downloads
- 415/mo · 207/wk
- Author
- ouzhenkun
- License
- MIT
- Types
- extension
- Size
- 587.9 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
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, OpenAI Codex, 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-hub
Commands
| 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" },
{ "type": "kimi-coding" },
{ "type": "zai" },
{ "type": "codex" }
]
}
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 |
disabled |
no | Do not register at all; on untyped entries, refuses that provider's registration |
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 |
kimi-coding |
optional apiKey |
5h rolling window + weekly quota; falls back to the kimi-coding entry in auth.json (Kimi Code key, not the open-platform key) |
kimi.com/code |
zai |
optional apiKey |
5h window, weekly, and monthly search quota; falls back to the zai entry in auth.json |
z.ai |
codex |
— | Codex rolling and 7-day usage windows; reads the openai-codex OAuth entry in auth.json |
OpenAI Codex |
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 |
|---|---|---|
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" });
Re-registering the same key overwrites (registration is idempotent), so the ready-listener + emit combination above never duplicates a provider.
Configure a custom provider from JSON
providers entries without type target an externally-registered provider by name. Meta fields override the provider's own values at register time; disabled: true refuses registration; all other fields pass through untouched to hub.getProviderConfig(name), so the extension can read its credentials from JSON instead of env vars:
{
"providers": [
{ "type": "kimi-coding" },
{
"name": "qianwen",
"shortLabel": "QW",
"hidden": false,
"cookie": "login_qianwenai_ticket=..."
}
]
}
pi.events.on("pi-usage-hub:ready", (hub) => {
const cfg = hub.getProviderConfig("qianwen"); // { shortLabel, hidden, cookie, ... }
const cookie = typeof cfg?.cookie === "string" ? cfg.cookie : undefined;
});
Untyped entries do not affect built-in name allocation; two untyped entries with the same name resolve last-write-wins. An untyped entry whose name matches a built-in entry also applies to it, so { "name": "kimi-coding", "hidden": true } hides the built-in without editing its own entry.
Register a built-in from an extension
hub.registerBuiltin(type, cfg?) instantiates any built-in factory with your own parameters (e.g. credentials obtained at runtime) and registers it, allocating type, type-2, … on key clash. Returns the registered key, or null for unknown types or disabled: true.
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