dsh-token-stats
Token usage statistics plugin for DeepSeek Harness (DSH). It parses real provider usage data from DSH session logs (session.jsonl.zstd) and provides complete usage statistics inside the Web GUI: cumulative / today / peak tokens, longest chat duration, streak days, and a 12-month activity heatmap (daily / weekly / cumulative), broken down by model and workspace.

Features
Stat cards (6 cards, 3×2 grid)
The page opens with six key figures in a symmetric 3-column × 2-row grid (2 columns on narrow screens):
| Card | Meaning |
|---|---|
| Cumulative tokens | Full total of input + output + cache read + cache write |
| Today's tokens | Usage so far today, with the call count and its share of the cumulative total |
| Peak tokens | Highest single-session consumption, with the date it happened |
| Longest chat | Longest continuous chat session (sessions are split after 30 minutes of inactivity) |
| Current streak | Consecutive active days ending today (or yesterday) |
| Longest streak | Best consecutive-active-days run on record |
An "active day" is any day with at least one token-consuming call.
Activity heatmap
A GitHub-style 7-row calendar grid (Mon–Sun) over a rolling 12-month window, with two freely combinable dimensions:
- Three views:
- Daily: each cell is that day's usage - Weekly: each cell is its week's total (the whole 7-cell column lights up, so an active week reads at a glance) - Cumulative: each cell is the running total up to that day (a left-to-right gradient ramp)
- Time range: 12 months / 3 months / 30 days, 3 months by default (so sparse data does not leave the calendar mostly empty)
- Hover preview: a bubble shows "8月13日 使用了 6391.7万个 Token(58 次调用)"; empty days show "无使用"
- Click to drill down: in the daily view, clicking a day cell opens that day's detail dialog — every session's tokens and calls for the day, labeled with the session title (so you can tell which session it is), repeated titles auto-merged with a ×N count, paginated 10 rows per page; with the dialog open you can click other dates to switch directly
- CSV export: one-click export of the current range as per-day rows (date / tokens / calls), with a UTF-8 BOM so Excel opens it without garbled text
Consumption breakdown
Input / output / cache read / cache write / reasoning with full-precision numbers (thousand separators, no 万/亿 abbreviation) — exact figures when you need them. (Cache read is DeepSeek's prefix-cache hits; it usually dominates the total but bills far below input.)
By model / workspace
Per-model and per-workspace consumption rankings with full-precision numbers, so you can see at a glance which model and which project directory consume the most.
Live session badge
Two pills sit in the conversation header, always visible:
- 本会话 X: the current session's token usage, polled every 10 seconds; click for the session detail dialog (total / input / output / cache / reasoning, a per-model breakdown, and the session title), with a one-click shortcut to the full statistics
- ¥ balance: the DeepSeek account balance, polled every 15 minutes; clicking force-refreshes the balance and opens the dedicated balance dialog
DeepSeek account balance
The balance appears in three places, each with its own purpose:
1. Header balance pill: next to the session badge, visible at all times 2. Dedicated balance dialog: opened by clicking the pill — a large amount with availability status, topped-up / granted breakdown, a refresh button, and the data source / last-updated time 3. Balance panel on the full stats page: a compact full-width strip below the heatmap, with its own refresh button
Refresh behavior: clicking refresh forces a live query to the DeepSeek open platform (/user/balance) via ?force=1, with a "刷新中…" state on the button; the balance is near-realtime — whenever new tokens are burned (session usage advances or the cumulative total moves) it auto-refreshes (5s throttle), with a 60-second poll as the idle baseline; on failure the last cached value is kept (no flicker). The API key is resolved through the DSH credentials service and stays in the host process — it is never sent to the browser.
Standalone stats page
http://127.0.0.1:3080/token-stats: the same dark-themed full statistics page as the GUI, directly accessible without opening the GUI, auto-refreshing every 15 seconds, with heatmap / balance / breakdowns / export / drill-down all working.
Balance warning: when the balance drops below the configured threshold (¥5 by default, balanceWarnThreshold, 0 disables it), the header pill dot turns red and the balance dialog / stats-page panel show a red "余额不足" state with a top-up reminder, so calls never fail silently on an exhausted balance.
Data source
All figures come from real provider usage recorded in DSH session logs (inputTokens / outputTokens / cacheReadTokens / cacheWriteTokens / reasoningTokens) — no estimation, no traffic interception. On startup the plugin replays historical session logs, captures new usage live via session/event, and reconciles incrementally every 30 seconds.
Data is stored at $DSH_HOME/token-stats/usage.jsonl ($DSH_HOME is the DSH data root; it defaults to ~/.dsh or the DSH_HOME environment variable).
Configuration
Tunables are overridden through $DSH_HOME/token-stats/config.json (defaults apply when absent):
{
"gapMs": 1800000,
"rescanIntervalMs": 30000,
"balanceCacheMs": 15000,
"windowMonths": 12,
"defaultRange": "3m",
"balanceWarnThreshold": 5
}| Key | Default | Meaning |
|---|---|---|
gapMs | 1800000 (30 min) | Inactivity gap that splits a session into separate chats |
rescanIntervalMs | 30000 (30 s) | Session-log reconciliation interval |
balanceCacheMs | 15000 (15 s) | Balance query cache TTL (for the near-realtime, usage-driven refresh) |
windowMonths | 12 | Full heatmap window in months |
defaultRange | 3m | Default heatmap range (12m/3m/30d) |
balanceWarnThreshold | 5 (¥) | Red balance warning below this amount (0 disables) |
Restart DSH after editing; the effective config is exposed at GET /token-stats/api/config.
Tests
pnpm testVitest suite covering zstd multi-frame decoding, log parsing, aggregation, store dedupe/merge, config merging and the balance service (24 cases, fixture data, no live logs).
Installation
The plugin mounts through DSH's profile plugin mechanism (managed by dsh plugin, the same mechanism as @liustack/modlens):
1. Add the dependency and bundle entry in the web profile's package.json:
// data/profiles/web/package.json
{
"dependencies": {
"dsh-token-stats": "link:../../path/to/dsh-token-stats" // or file:, or a version once published to npm
},
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-token-stats"]
}
}
}2. Run pnpm install in the profile directory 3. Restart DSH (exit and relaunch)
HTTP API
| Endpoint | Description |
|---|---|
GET /token-stats/api/stats | Full stats snapshot (everything the cards / heatmap / breakdowns need) |
GET /token-stats/api/balance | DeepSeek account balance (15-minute cache; ?force=1 to bypass) |
GET /token-stats/api/day?date=YYYY-MM-DD | Per-session usage for one date (heatmap drill-down) |
GET /token-stats/api/session/<sessionId> | Single-session usage summary (used by the badge / dialog) |
GET /token-stats | Standalone stats page (HTML) |
Package structure
dsh-token-stats/
├── package.json # dsh.bundle.patch (host mount) + dsh.client (browser half)
├── cordis.patch.yml # plugin loading config
├── dsh/ # host half: log scanning, aggregation, HTTP routes
│ ├── index.js # apply(ctx): boot backfill + live capture + routes
│ ├── scan.js # session-log scanning and usage extraction
│ ├── stats.js # aggregation (cumulative / peak / heatmap / streaks / today / sessions)
│ ├── zstd.js # multi-frame zstd decoding (session logs are frame-appended)
│ ├── store.js # usage.jsonl persistence and migration
│ ├── home.js # DSH data root resolution
│ └── page.js # standalone stats page
└── client/ # browser half: settings page + session badge + dialogs
└── bundle.js # web shell loader format, hand-written, no build stepLicense
[MIT](LICENSE)