Meter your Z.ai GLM Coding Plan usage in pi, without guessing when the window resets.
@danielgap/pi-zai-usage adds a usage meter for the Z.ai GLM Coding Plan to pi, designed for Gentle Shell (npm: gentle-pi). With Gentle Shell installed, it registers Z.ai through the shell's official third-party usage-source event and feeds its native usage surfaces, just like built-in Codex/Claude sources. Without Gentle Shell, it still provides a standalone status-bar segment, a /zai:usage subscriptions panel, and automatic refresh while a Z.ai provider is active.
Z.ai meters the GLM Coding Plan through an undocumented quota endpoint. This package turns that endpoint into visible windows — plan level, used percentage per window, and when each one resets — and stays out of your way the moment you switch to another provider.
Coding against a metered plan fails quietly, not loudly:
- the 5-hour window empties mid-session and you only learn it from failed calls;
- the weekly ceiling is invisible, so a productive Friday sabotages Monday;
- the quota endpoint is undocumented, so every meter is a reverse-engineered guess;
- nothing in pi shows subscription usage for Z.ai providers.
This extension fixes the visibility. You bring the API key you already configured for the provider; it brings the parsing, the meter, and the discipline.
| Capability | What it does |
|---|---|
| Native Gentle Shell usage | Registers both zai and zai-glm as usage sources through Gentle Shell's official gentle-pi:usage-source/v1 event at session_start, so the shell's native usage store meters Z.ai like its own Codex/Claude sources — its refresh cadence (5 minutes, after each response, forced once on registration when a Z.ai model is active) and its /gentle:usage panel |
| Status-bar segment (standalone fallback) | zai 5h ▰▰▱▱▱▱▱▱ 42% · week 71% — first window gauged, the rest compact — delivered through pi's public ctx.ui.setStatus contract: trailing segment of Gentle Shell's narrow footer, a line in its sidebar Integrations group, or pi's native footer without the shell |
/zai:usage command |
Opens the ✿ Subscriptions panel Gentle Shell's /gentle:usage opens: 16-cell meters, reset countdowns, plan, updated Xm ago; r refetches, esc closes |
/zai:usage off / on |
Hides or restores the standalone status segment — never the native usage Gentle Shell already recorded from the event |
| Automatic refresh | Polls every 5 minutes and after every response while a zai / zai-glm model is active |
| Provider-aware lifecycle | The status appears on Z.ai providers and clears itself when you switch away |
| Defensive parsing | Unknown payload shapes degrade to empty limits; percentages clamp to 0-100; a hung request times out in 10s |
| Zero runtime dependencies | Type-only imports; the extension runs entirely on pi's extension API |
| Provenance-locked parser | lib/zai-usage.ts is lifted verbatim from Gentle Shell's shell-usage, so the standalone meter and the official integration share one battle-tested contract |
pi install npm:@danielgap/pi-zai-usageFrom a local checkout (without npm):
pi install /path/to/pi-zai-usageThen restart pi, pick a Z.ai model (zai / zai-glm provider), and the meter arrives with the first fetch. /zai:usage forces one immediately.
-
Have a Z.ai API key configured — the extension resolves it through pi's model registry (
getApiKeyForProvider) and falls back toZAI_GLM_API_KEYorZAI_API_KEYin the environment. -
Switch to a Z.ai provider model (
/model, or Ctrl+P cycling). -
The meter appears:
- Gentle Shell installed: Z.ai rides the shell's native usage surfaces — the same store, header, and
/gentle:usagepanel its built-in Codex/Claude sources feed. Registration is load-order independent (the shell subscribes before anysession_startfires) and forces one refresh when a Z.ai model is already active, so the first windows show up without waiting for the shell's 5-minute cycle. - No Gentle Shell: the compact segment
zai 5h ▰▰▰▱▱▱▱▱ 34% · week 11%rides pi's native footer as the trailing status segment. - Both worlds: event delivery has no acknowledgement, so the extension cannot tell whether the shell consumed the registration — the standalone segment keeps rendering (in Gentle Shell's footer or its sidebar Integrations group).
/zai:usage offhides that segment only.
- Gentle Shell installed: Z.ai rides the shell's native usage surfaces — the same store, header, and
-
/zai:usageopens the subscriptions panel (same frame and keys as Gentle Shell's/gentle:usage):
╭─ ✿ Subscriptions ──────────────────────────────────────╮
│ ✿ zai · max · updated just now │
│ zai │
│ 5h ▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱ 34% resets in 3h 12m │
│ week ▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 11% resets in 5d 9h │
│ r refresh esc close │
╰─────────────────────────────────────────────────────────╯
| Command | Effect |
|---|---|
/zai:usage |
Force-refresh and open the ✿ Subscriptions panel (a failed quota request is announced; in RPC mode the windows are notified as plain lines) |
/zai:usage off |
Hide the standalone status segment only — the native usage Gentle Shell already recorded from the event is untouched |
/zai:usage on |
Restore the status segment and refresh |
The quota endpoint (https://api.z.ai/api/monitor/usage/quota/limit) is undocumented; the contract below is what the parser enforces, and what the tests pin:
- The payload carries a
data.limitsarray. One entry becomes one window when it names a known token-window unit —3maps to the 5-hour rolling window (18,000s),6to the weekly one (604,800s) — and aTOKENS_LIMITorCREDIT_LIMITtype, and a finitepercentage. - Everything else is skipped: the web-search counter (
TIME_LIMIT) rides the same array and intentionally stays out of the subscription view; unknown units, mystery types, non-numeric percentages, andnullentries degrade to empty limits instead of errors. - Percentages outside 0-100 are clamped, so a stray server value can never render a broken bar. A window at 100% marks the limit reached.
- Resets arrive as epoch milliseconds; the panel renders them as
resets in 2h 13mstyle notes, quiet when unknown. - The key is sent as a bearer token, requests time out in 10 seconds, and any failure — missing key, non-OK response, parse miss — is silent on background refreshes and announced on user-triggered ones.
Gentle Shell's official third-party usage-source event is the native path, and this package registers on it while keeping a standalone meter that works with or without the shell.
lib/zai-usage.tsis lifted verbatim from Gentle Shell'slib/shell-usage.ts(branchfeat/zai-usage-meter), including its parser tests. The standalone parser and the shell's own stay in sync by shared provenance.- The rendering is a port, not a lookalike: the status paints
renderUsageBar's exact bar segment (8-cell gauge with accent/warning/error tones and border-dimmed empty cells) andlib/zai-usage-view.tsports Gentle Shell'sUsageViewframe — same✿ Subscriptionstitle, same 16-cell panel meters, samer refresh · esc closekeys — through pi's publicsetStatus/ui.customcontracts only. No pi-tui dependency, no forked UI. - The native integration is Gentle Shell's documented door: the shell emits nothing, it listens. It subscribes to
gentle-pi:usage-source/v1(payload schemagentle-pi.usage-source/v1) when its extension factory runs — before anysession_startfires, so registration from this side is load-order independent. The shell validates the payload field by field, replaces the previous source per provider instead of accumulating, resolves the provider-specific API key from pi's model registry and supplies it to the source, and falls back to nothing: this package's own environment fallback (ZAI_GLM_API_KEY/ZAI_API_KEY) covers consumers that pass no key. No gentle-shell file is read, imported, or patched. - The earlier experimental sidebar decoration is gone on purpose: it wrapped an undocumented shared-state symbol (
gentle-pi.experimental-sidebar.state), exactly the kind of surface a shell update could silently break. The usage-source event is the supported contract for the same job.
This extension was built with Gentle AI.
Releases publish automatically from version tags through .github/workflows/publish.yml:
-
Bump
package.jsonto the next version, commit, and push tomain. The old unscoped namepi-zai-usagebelongs to another npm maintainer, and@danielgap/gentle-shell-zai-usage(v0.4.1–v0.4.3) is deprecated in favor of this name; publish only@danielgap/pi-zai-usage. -
Tag the release on the freshly fetched
origin/maincommit — the workflow verifies the tag is annotated, matchespackage.json's version, and points to a commit reachable frommain:git fetch origin main --tags git tag -a vX.Y.Z "$(git rev-parse 'origin/main^{commit}')" -m "@danielgap/pi-zai-usage vX.Y.Z" git push origin refs/tags/vX.Y.Z
-
CI installs, tests, typechecks, packs, publishes to npm with provenance, creates the GitHub Release if it is missing, and verifies the registry. A brand-new package version can take a few minutes to appear in the registry after a successful publish, so the Verify npm step retries for up to 3 minutes before failing.
First-time setup: add an NPM_TOKEN secret with publish rights for @danielgap/pi-zai-usage under Settings → Secrets and variables → Actions. A token restricted to the deprecated @danielgap/gentle-shell-zai-usage name will not work for this name; update its permissions or replace the secret if needed. A run that failed for publication-only reasons can be retried without moving its tag (gh workflow run publish.yml -f tag=vX.Y.Z).
pnpm install
pnpm test # node --test over the parser, fetch, and rendering (no network)
pnpm typecheck # tsc --noEmit against @earendil-works/pi-coding-agent typesThe parser and renderer are pure; tests cover the documented payload shape, the legacy CREDIT_LIMIT plans, hostile inputs (wrong units, string percentages, over-range values, null entries), the fetch contract (bearer key, timeout, no key → no request), the gauge tones, the bar segment, the panel rows, the overlay view's r/esc handling, and the usage-source registration (both providers on the shell's event channel, consumer-supplied key preferred with environment fallback, nothing sent without a key). No test touches the network.
- Same parser, one truth. The parsing contract is Gentle Shell's; this package carries it verbatim rather than improvising a second one.
- Degrade, never break. An undocumented endpoint gets defensive parsing: unknown shapes cost the meter, not your session.
- Quiet when background, loud when asked. Automatic refreshes never interrupt; user-triggered commands always answer, including with bad news.
- Zero footprint. No runtime dependencies, no configuration surface, no data leaving the machine except the quota request itself.
MIT — see LICENSE.
