中文 · English

dsh-token-data: a token-usage dashboard for the DeepSeek Harness web GUI - per-day usage heatmap, provider balances and subscription quotas in one floating panel

MIT License npm version DSH 0.2.0-rc.2 no build step read-only API

**dsh-token-data** is a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that puts a live token-usage dashboard inside the `dsh web` GUI. Click the sidebar footer badge to open a floating panel with your per-day token burn, provider balances and subscription quota windows - no tab-switching between provider dashboards, no spreadsheet bookkeeping. ## What you get | You want to know | The panel shows | | --- | --- | | How many tokens did I burn today / this month? | Hero summary cards + a Codex-style **daily heatmap** | | Where are the tokens going? | **Per-day, per-model** breakdown with a 7-day stacked trend chart | | Is my DeepSeek balance about to run out? | **Provider balances** (DeepSeek, OpenRouter, Moonshot/Kimi, Z.ai ...) | | How much of my subscription window is left? | **Quota windows** for OpenCode Go, Z.ai, Kimi and MiniMax token plans | | Is my prompt cache actually helping? | **Cache hit rate** per day, right next to the totals | ## Why it is different - **Tracks DSH 0.2** - usage is read from `sessionPersistence` (`list` / `open` / `read`) instead of the deprecated synchronous `Session` event readers, provider connection facts come from the live `settings.describe()` projection, and the browser bundle imports no harness Client package (icons are drawn inline), so a renamed primitives icon can no longer crash the panel. - **Zero build step** - the browser bundle is a hand-written `__ModuleLoader__` module (no bundler, no npm install for consumers); the server is plain ESM. Clone, add, restart. - **Read-only, loopback-only API** - five `GET` endpoints behind a peer-socket loopback fence; nothing is ever written to your sessions. - **Keys stay in the credentials seam** - provider API keys are resolved through the harness credentials service at request time. The plugin stores nothing. - **Incremental aggregation** - per-session fold state is cached (`/storages/usage-stats-cache.json`) behind a stable artifact key (size + event count); steady-state cost stays O(new events) no matter how large the logs grow (measured: ~0.2 s for 121 local sessions). - **Resilient panel** - network/timeout/HTTP errors get friendly localized messages, transient failures retry with backoff, and the last good payload is kept so a hiccup never blanks the panel. - **Locale-aware formatting** - 亿/万 for zh/ja, K/M/B for en, and every color comes from a light/dark theme token. ## Supported providers | Provider | Balance | Subscription / quota | | --- | --- | --- | | DeepSeek | `GET /user/balance` (CNY) | - | | OpenRouter | `GET /api/v1/credits` (management key) | - | | Moonshot / Kimi | `GET /v1/users/me/balance` | Kimi coding usage | | Z.ai (GLM) | `GET /api/paas/v4/balance` | Coding Plan quota windows | | OpenCode Go | - | Undocumented usage endpoint + workspace-dashboard fallback | | MiniMax | - | Token-plan remains (global / CN) | | Anything else | Generic `new-api` / `sub2api` / `general` adapters, plus declarative JSON-Pointer config | | Providers without a public balance API show an explicit **no public balance interface** state instead of a guessed number. ## Install **Prerequisites:** a working DSH (`dsh web` runs), Node.js >= 20. ### Option A - install from npm (recommended) ```bash dsh plugin --profile web add dsh-token-data ``` ### Option B - clone and add (review the source / contribute) ```bash git clone https://github.com/dawsondx/dsh-token-data.git cd dsh-token-data dsh plugin --profile web add \ --ignore-scripts --config.auto-install-peers=false \ . ``` ### Option C - add directly from GitHub (no npm needed) ```bash dsh plugin --profile web add \ --ignore-scripts --config.auto-install-peers=false \ github:dawsondx/dsh-token-data ``` ### Option D - local development ```bash dsh plugin --profile web add link:C:/path/to/dsh-token-data ``` Restart `dsh web` after installing. The panel opens from the footer badge in the sidebar; use the profile's `cordis.patch.yml` to configure monitors (example below). ## Configuration The plugin is a [DSH profile bundle](https://github.com/deepseek-ai/deepseek-harness) (`dsh.bundle.patch`), mounting the internal plugin id `usage-stats`. Attach monitors and per-provider options in your web profile's `cordis.patch.yml`: ```yaml - id: usage-stats config: monitors: deepseek-official: adapter: deepseek-balance allowPrivateNetwork: true # allow local proxy (TUN/fake-ip) resolution minimax-cn: adapter: minimax-token-plan region: cn # MiniMax CN account ``` ## How it works ```mermaid flowchart LR S[session event logs] --> F[incremental fold
per-day / per-model] F --> C[(usage-stats cache
DSH_HOME/storages)] C --> E[5 read-only loopback endpoints] E --> P[web panel
heatmap - balances - quotas] K[credentials seam] -. resolve keys at request time .-> E ``` The server registers five exact routes under `/api`, so they take precedence over the connection plugin's `/api` prefix handler. Aggregation is anchored on `sessionPersistence`: `list()` reports every stored session's log artifact and `open(id, "read")` + `read(offset)` returns only the events after the folded prefix. A session whose artifact did not change (same size and event count) is skipped for that whole round, so steady-state cost is independent of the total log volume. Folding matches the harness's own `token-meter` projection: a sample rides `assistant/message`'s `data.usage` or, when absent, the last `usage` chunk of that event's `stream`, and `assistant/attempt` (a failed or retried attempt) counts too. A repeated `(turn, step)` **replaces** the earlier value instead of adding to it, and `llm/retry-started` clears the slot so the retried attempt accumulates. A forked session counts only its own events (after `inheritedEventCount`), so an ancestor's usage is never counted twice. Legacy artifacts the backend refuses to open are remembered and excluded from the totals behind a single aggregated warning, so they never slow every refresh down. | Endpoint | Purpose | | --- | --- | | `GET /api/usage-stats/usage` | Per-day token usage across every session | | `GET /api/usage-stats/providers` | Configured providers + balance schemes | | `GET /api/usage-stats/balance?provider=` | Balance for one provider | | `GET /api/usage-stats/subscriptions` | OpenCode Go + Z.ai quota windows | | `GET /api/usage-stats/account` | Unified account snapshot for one provider | ## Security - **Read-only** - every endpoint is `GET`; nothing is written to your sessions or logs. - **Loopback-only** - the peer-socket address (not the client-controlled `Host` header) must be a loopback interface; the `Host` header is only a secondary check. - **No key storage** - credentials are resolved through the harness credentials seam at request time; account snapshots redact sensitive headers (`authorization`, `cookie`, `api-key`, ...). - **Bounded upstream calls** - 15 s timeouts and 1 MB response caps on provider requests. ## Development ```bash npm run check # node --check on every lib file npm test # pure-function assertions for the fold (replace / retry / attribution) ``` `lib/usage.js` and `lib/balance.js` are pure modules (no cordis imports) - the folding and balance parsing can be unit-tested and validated against real logs outside the harness. The browser half has no build step: edit `lib/client.js` and refresh the page (the harness client-hmr re-serves the bundle when the file changes). The Host half (`lib/index.js`) belongs to the Node module graph, so changing it requires a `dsh web` restart. ## Acknowledgments - [dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats) (MIT, © 2026 dsh-usage-stats contributors) - the base this project grew out of: the internal plugin id `usage-stats` and the API routes are kept for compatibility, while the package name and the front-end bundle are new. - [cc-switch](https://github.com/farion1231/cc-switch) (MIT) - inspiration for the stats-panel UI (hero summary cards, SVG stacked trend chart, tabular numerals) and the `formatTokensShort` locale-aware formatting logic (亿/万 for zh/ja, K/M/B for en). ## License [MIT](./LICENSE) © dawsondx