# dsh-token-panel [![License: MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE) [![Version](https://img.shields.io/badge/version-0.4.7-blue?style=flat-square)](https://github.com/juhe291/dsh-token-panel/releases) [![Platform](https://img.shields.io/badge/platform-web-cyan?style=flat-square)](https://github.com/juhe291/dsh-token-panel) [![Topic: dsh-plugin](https://img.shields.io/badge/topic-dsh--plugin-8A2BE2?style=flat-square)](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 |

dsh-token-panel cover

> πŸ“· 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.*