<p align="center">
  <a href="./README.md">中文</a> · <b>English</b>
</p>

<p align="center">
  <img src="./assets/readme/hero.svg" width="100%" alt="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">
</p>

<p align="center">
  <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a>
  <img src="https://img.shields.io/badge/DSH-0.1.0--rc.6-1f2937.svg" alt="DSH 0.1.0-rc.6">
  <img src="https://img.shields.io/badge/build-none-1f6feb.svg" alt="no build step">
  <img src="https://img.shields.io/badge/API-read--only-22c55e.svg" alt="read-only API">
</p>

**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

- **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 (`<DSH_HOME>/storages/usage-stats-cache.json`); steady-state cost stays O(new events) no matter how large the logs grow.
- **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.

## 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 - clone and add (recommended)

```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 B - add directly from GitHub

```bash
dsh plugin --profile web add \
  --ignore-scripts --config.auto-install-peers=false \
  github:dawsondx/dsh-token-data
```

### Option C - 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<br/>per-day / per-model]
  F --> C[(usage-stats cache<br/>DSH_HOME/storages)]
  C --> E[5 read-only loopback endpoints]
  E --> P[web panel<br/>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. Live sessions fold their in-memory tail; persisted sessions only reprocess events after the backend's opaque revision changes (with contiguity checks so a truncated log is refolded exactly).

| 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=<id>` | 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
```

`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.

## 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
