# dsh-token-panel
[](LICENSE)
[](https://github.com/juhe291/dsh-token-panel/releases)
[](https://github.com/juhe291/dsh-token-panel)
[](https://github.com/topics/dsh-plugin)
**Real-time token consumption HUD for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) β live session pressure, per-session cost, history curves, and per-day/per-month statistics, in a draggable corner dashboard that follows your current conversation.**
π [**δΈζ**](README.md) ο½ **English**
## β¨ Highlights
| π° **Per-model pricing** | flash / pro each billed at official rates β sessions that switch models stay accurate |
|---|---|
| β±οΈ **Auto peak/off-peak** | switches to peak/off-peak pricing **automatically** at the 2026-08-17 revision β no config change |
| π«§ **Hover to inspect values** | Steam-style follow bubble on live curves, per-point hover bubble on stats curves |
| π§ **Frosted glass panel** | translucent frosted glass, consistent across light/dark, pill most transparent |
| ποΈ **Time scrubber** | look back through live history, pan stats to earlier periods |
| βοΈ **Click-to-edit budget / balance** | inline editing in the stats view; balance decreases locally with token consumption |
| π±οΈ **Drag + long-press menu** | four corner presets + custom default position, remembered across reloads |
| π **Durable daily / monthly stats** | JSONL logs on disk, survive restarts, get richer over time |
> π· The two panels in the cover are **real UI screenshots** (left: live view; right: stats view).
---
## Installation
### From npm
```sh
dsh plugin --profile web add dsh-token-panel@0.4.7
```
The npm package ships the built `lib/` artifacts β **no local build needed**. Use the **current latest version** in the command (bump it on each new release, see [Publishing a release](#publishing-a-release)).
> β οΈ **Why an exact version?** A freshly published release (under the ~24h supply-chain minimum release age) can make pnpm's `@latest` resolution fall back to an older version; an explicit version always installs the newest fixed release. Once the package is older than the policy cutoff, `@latest` becomes reliable.
> β οΈ **Got an old version?** Reinstall with the exact version above and confirm `npm view dsh-token-panel dist-tags.latest` reports the newest release.
### From GitHub
```sh
dsh plugin --profile web add github:juhe291/dsh-token-panel
```
### From a local path
```sh
dsh plugin --profile web add C:\path\to\dsh-token-panel
```
**Restart the profile**, then refresh the browser β the TOKEN pill appears bottom-right.
> β οΈ **pnpm β₯ 10 blocks Git build scripts**: if the first install fails with `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`, add the `allowBuilds` entry printed in the error to the `pnpm-workspace.yaml` in your profile directory, then re-run the install command. This package ships a `prepare` build script **and** committed `lib/` artifacts, so it works with or without allowlisted builds.
> β οΈ **Git / local installs need Node β₯ 22.5**: source installs run the `prepare` build (pnpm 11.7 is pinned via the `packageManager` field and uses `node:sqlite`), so Node 20 fails to build. Prefer the npm install β no build required.
> β οΈ **`dsh` not recognized**: `dsh` is the DSH CLI. If your shell says the command is not found, install it globally first (`npm install -g @deepseek-ai/dsh`), then open a new terminal and re-run the commands above.
### Uninstall
```sh
dsh plugin --profile web remove dsh-token-panel
```
> π‘ **One-command fallback**: a freshly published plugin (under the ~24h supply-chain minimum release age) can make `dsh plugin remove` fail its lockfile verification. The package ships a one-command uninstaller that calls pnpm directly (bypassing that check) and cleans the bundle registration:
> ```sh
> node ~/.dsh/profiles/web/node_modules/dsh-token-panel/scripts/uninstall.mjs
> ```
> Restart the profile afterwards. Once the package is older than the policy cutoff (~24h), `dsh plugin remove` works normally again.
---
## Usage
### Panel interactions (three gestures, zero conflicts)
1. **Click the pill** to open the panel (pill shows pressure + βcumulative + TPS)
2. **Drag** the pill or the panel header to move β you may drag the panel **past the screen edges** (any side), but a grabbable header strip always stays visible; the position is remembered across reloads
3. **Long-press 0.6s** (pill or panel header) opens the position menu:
```
ββββββββββββββββββββββββ
β β Bottom-right β β icon/text follow the saved default
β β Position βΈ β
β Cancel β
ββββββββββββββββββββββββ
Position submenu:
β β Top-left / β Top-right / β Bottom-left / β Bottom-right β presets
β β Custom positionβ¦ β drag & release to save
```
- **Back to default**: the first item's icon and label follow the saved default ("Back to default Β· Top-right", "Back to custom position")
- **Corner presets**: click to move there AND save as the default (uses the actual panel size, so bottom corners truly touch the edge)
- **Custom position**: choose it, then drag the panel anywhere and release β saved as the new default, used on every page load
- **β** closes the panel without moving it (use the long-press menu to reset)
### Live view
- Session rows: **bold number = current context pressure** (k scale); grey `β` = cumulative usage (M scale, incl. cache reads); **green `Β₯` = that session's estimated cost** (priced by the model the session actually used)
- Click a row for details (input / output / cache read / cache write, pressure / projected / capacity, cost, context-usage bar) and its consumption curve
- Curves: **auto-scaling Y axis** (1/2/2.5/5Γ10βΏ rounding with hysteresis, zero when idle, unit label always visible) + gridlines + a dashed leader with a floating value pill at the latest point + time ticks on X; hover anywhere over the curve and a **follow bubble** shows the token value at that spot (Steam-download style)
- A **time scrubber** under the curve: drag left to look back through the history buffer at earlier windows (size follows 2m / 5m / 15m); peak rate and the latest value follow the panned window
- Peak consumption rate (t/s) above the curve; 2m / 5m / 15m window switching
- The panel follows the conversation you are viewing; "Show all" reveals historic sessions (survive restarts)
### Stats view
- **Cumulative consumption** headline in one line (total tokens + βΒ₯ cost)
- **Editable budget / balance**: click a value to edit inline (Enter to save / Esc to cancel); the balance decreases locally with token consumption (persists across reloads), falling back to the API-fetched official balance when unset
- **Daily / Monthly** switch: trend curve + detail list (collapsed by default, "Expand all" to see everything); hovering a plotted point pops a bubble with that day's / month's token value (the current day / month is skipped β its label is already shown); a **time scrubber** under the curve pans to earlier periods (defaults to the most recent 14 days / 6 months)
- Usage is persisted per day (JSONL) and survives restarts
---
## Overview
A compact pill in the bottom-right corner shows the total token pressure in real time. Click it to expand a dashboard with two switchable views β **Live** and **Stats** β styled with DSH design tokens (auto light/dark adaptation). The panel **follows your current conversation**: when you open a different chat, the panel shows that session; empty and historic sessions stay hidden behind a "Show all" toggle.
### π’ Live View
| Feature | Description |
|---|---|
| Session list | One row per session: **title + current context pressure + cumulative usage + session cost**; titles come from the DSH session-title service |
| Session details | Click a row: input / output / cache-read / cache-write, pressure / projected / capacity, estimated cost, context-usage progress bar (turns red above 85%) |
| Live curves | Per-session SVG area chart with auto-scaling Y axis (1/2/2.5/5Γ10βΏ rounding + hysteresis, zero when idle, unit label always visible), gridlines, dashed leader with floating value pill, and **2m / 5m / 15m** range switching; hovering anywhere over the curve shows a follow bubble with the token value (Steam style); a **time scrubber** below looks back through the history buffer |
| Peak rate | Peak consumption rate (t/s) shown above the curve |
| Follows current session | Only the open conversation is shown by default; historic sessions collapse behind "Show all" (survive restarts) |
| Empty-session filter | Fresh conversations with 0 tokens are hidden entirely |
| TPS | Generation speed (t/s) shown on the pill and the footer |
### π Stats View
| Feature | Description |
|---|---|
| Daily / Monthly | Independent switch: trend curve + detail list (collapsed by default, "Expand all" to see every day / month) |
| Trend curves | SVG curves over days / months with M/D date ticks and Y-axis labels; hovering a plotted point pops a bubble with that day's / month's token value; a **time scrubber** below pans to earlier periods (defaults to the most recent 14 days / 6 months) |
| Cumulative total | One-line headline: total tokens consumed plus the βΒ₯ estimated cost |
| Budget & balance | **Click values to edit inline** (Enter saves / Esc cancels); budget shows used-this-month / total with a progress bar (red over budget); balance decreases locally with token consumption, falling back to the API-fetched official balance when unset |
| Durable | Usage is written to per-day JSONL logs on disk β **survives restarts** |
> β οΈ **Number scale note**: the stats view's "Daily / Monthly" figures are **cumulative historical consumption** (input + output + **cache reads** summed); cache reads usually dominate, so a single day can reach hundreds of millions of tokens (displayed with the M suffix). The live view, by contrast, shows **current context pressure** (tokens in context right now, typically tens of thousands β k suffix). **These are two different quantities**; seeing "live 400k / stats 100M" is expected, not a bug. The `β` number on a session row is that session's cumulative consumption, and the `Β₯` figure is its estimated cost β both matching the stats-view scale.
### π° Cost Estimation
- **Per-model pricing**: built-in tables for `deepseek-v4-flash` and `deepseek-v4-pro` (cache hit / uncached input / output billed separately); sessions and stats are priced by the model each session actually used, so a session that switched models is never billed entirely at one rate
- **auto price mode** (default): uses the flat legacy rates until 2026-08-17 00:00 Beijing time, then automatically switches to DeepSeek's official peak/off-peak schedule (peak 9-12 & 14-18); the footer badge shows "flat rate / peak rate / off-peak rate" accordingly β no config change needed
- Display-only estimates β the provider dashboard is authoritative.
---
## Configuration
In your profile's `cordis.patch.yml` (or the plugin section of `settings.yaml`):
```yaml
- id: token-panel
name: dsh-token-panel
config:
pollInterval: 1500 # live poll interval (ms)
priceMode: auto # auto = flat until 2026-08-17, then peak-offpeak automatically; flat / peak-offpeak to pin
# Global fallback prices (models not listed in modelPrices; defaults = flash rates)
pricePerMInput: 1 # uncached input price (CNY / 1M tokens)
pricePerMCacheRead: 0.02 # cache-hit price (CNY / 1M tokens)
pricePerMOutput: 2 # output price (CNY / 1M tokens)
# Peak / off-peak fallback (when peak-offpeak is active)
pricePeakInput: 3 # peak uncached input
pricePeakCacheRead: 0.1 # peak cache-hit
pricePeakOutput: 9 # peak output
priceOffpeakInput: 1.5 # off-peak uncached input
priceOffpeakCacheRead: 0.05 # off-peak cache-hit
priceOffpeakOutput: 4.5 # off-peak output
# Per-model price tables (CNY / 1M tokens): sessions and stats bill by the model actually used
modelPrices:
deepseek-v4-flash:
flat: { hit: 0.02, miss: 1, output: 2 }
peak: { hit: 0.10, miss: 3, output: 9 }
offpeak: { hit: 0.05, miss: 1.5, output: 4.5 }
deepseek-v4-pro:
flat: { hit: 0.025, miss: 3, output: 6 }
peak: { hit: 0.30, miss: 9, output: 27 }
offpeak: { hit: 0.15, miss: 4.5, output: 13.5 }
budgetMonthly: 0 # monthly budget (CNY); 0 disables (or click the value in the stats view to set it)
# dataDir: ~/.dsh/cache/dsh-token-panel # durable log directory (optional)
```
| Key | Default | Description |
|---|---|---|
| `pollInterval` | `1500` | Browser live-poll interval (ms) |
| `priceMode` | `auto` | Pricing mode: `auto` switches from flat to peak/off-peak automatically at 2026-08-17 00:00 Beijing time; `flat` / `peak-offpeak` pin a mode |
| `pricePerM*` | `1 / 0.02 / 2` | Global fallback prices per 1M tokens (CNY, display only) |
| `pricePeak*` | `3 / 0.1 / 9` | Peak-period fallback prices (Beijing 9-12, 14-18) |
| `priceOffpeak*` | `1.5 / 0.05 / 4.5` | Off-peak fallback prices |
| `modelPrices` | built-in flash + pro | Per-model price tables (flat / peak / off-peak tiers); override or add models |
| `budgetMonthly` | `0` | Monthly budget (CNY); >0 shows a budget bar, or click the value in the stats view to set it directly |
| `hidden` | `false` | `true` hides the entire HUD (pill + panel); also reachable via the long-press menu "Hide HUD" and a visual switch in DSH settings β no config editing needed |
| `dataDir` | `~/.dsh/cache/dsh-token-panel` | Durable usage-log directory |
> Built-in defaults match the official DeepSeek tables (flat legacy rates before 2026-08-17, peak/off-peak after). Adjust `modelPrices` for other models/providers.
---
## Data Storage
Usage logs are appended per day (one JSON delta per line):
```
~/.dsh/cache/dsh-token-panel/
βββ usage-2026-08-14.jsonl # daily usage logs (deltas: input/output/cache read/cache write/model)
βββ state.json # last-seen baselines (resume across restarts)
βββ known-sessions.json # session registry ("Show all" survives restarts)
```
Tracked buckets: uncached input, output, cache read, cache write, **model** (deltas). The first observation of a session writes a full baseline, then deltas follow β totals start from the true baseline and never double-count after a restart.
---
## How It Works
- **Host side** (`src/index.ts`):
- Aggregates `ctx.tokenMeter.measure()` (pressure/surface), `ctx.sessionProjections.snapshot()` (provider usage/capacity/breakdown), `ctx.sessionTitle.get()` (titles) and `ctx.credentials.resolve('DEEPSEEK_API_KEY')` (official balance)
- Serves three HTTP routes: `/plugins/dsh-token-panel/snapshot` (live + per-model price tables), `/plugins/dsh-token-panel/stats` (durable stats), `/plugins/dsh-token-panel/balance` (official balance, 5-min cache)
- Persists usage deltas per day (crash-safe: tmp + atomic rename), accumulated per session Γ model for per-model cost pricing
- Filters out empty sessions (0 tokens)
- **Client side** (`src/client/`): body-portal corner panel, 1.5s live poll + 10s stats poll + 60s balance poll, SVG curves, DSH design-token theming, **en/zh locale** following the DSH language setting, current-session tracking via `ctx.sessions.list`; budget/balance stored in localStorage (click values to edit inline)
---
## Development
```sh
pnpm install
pnpm build # tsc host + tsc client + tsdown
pnpm verify # artifact consistency (exports/patch/client bundle)
```
### Publishing a release
```sh
pnpm build && pnpm verify
git add -A
git commit -m "feat: ..."
git push
```
---
## FAQ
**Q: Why do the live and stats numbers differ?**
A: Live shows **current context pressure** (tens of thousands β k units); stats show **cumulative historical usage** including cache reads (hundreds of millions β M units). Two different metrics; the panel shows both (pressure + βcumulative).
**Q: How accurate is the cost estimate?**
A: It applies official DeepSeek rates by bucket (cache hit / uncached input / output billed separately, with per-model tables for v4-flash and v4-pro). Display-only β always verify against the [DeepSeek platform](https://platform.deepseek.com).
**Q: Do I need to change config after the 2026-08-17 price revision?**
A: No. The default `priceMode: auto` switches to peak/off-peak pricing automatically at 2026-08-17 00:00 Beijing time, and the footer badge changes from "flat rate" to "peak / off-peak rate" accordingly.
**Q: Why doesn't the balance match the official one?**
A: Once you set a balance, it decreases locally by **estimated** cost (estimates may drift slightly from the official bill and exclude discounts/grants). To re-sync, click the balance value and re-enter the official balance; when unset, the panel shows the API-fetched official balance instead.
**Q: Why is the curve only the last few minutes?**
A: Live curves are an in-memory rolling window (600 points β 15 min) and reset on restart; the stats view's daily/monthly curves are backed by durable disk logs and persist.
**Q: Some sessions are missing from the panel.**
A: The panel follows your current conversation and hides empty (0-token) sessions. Historic sessions appear after clicking "Show all".
---
## License
[MIT](LICENSE) Β· Changelog: [CHANGELOG.en.md](CHANGELOG.en.md)
---
*Made with π for the DeepSeek Harness plugin ecosystem.*