Bundle
@local/token-usage
๐ Token usage dashboard for DeepSeek Harness Settings โ daily/weekly/monthly per-model token stats with line & bar charts. Local-first, zero deps, pure SVG. DSH ่ฎพ็ฝฎ้ขๆฟ่ฏๅ ็จ้็ป่ฎกๆไปถ
- Source
- GIN0076
- License
- MIT
- Updated
- Updated 22 hours ago
Readme
<div align="center">
# ๐ Token Usage for DSH
**Bolt a fuel gauge onto your [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Settings panel โ**
**see exactly how many tokens every model burns, every day / week / month.**
[](LICENSE)
[](#-install)
[](CHANGELOG.md)
[](#-how-it-works)
[](#-privacy)
[English](README.md) ยท [็ฎไฝไธญๆ](README.zh.md)
</div>
---
## โจ Why you need this
DSH works hard for you โ but **do you know what it costs you?** The account page shows a
balance, and raw logs are a pile of JSONL...
Now just open **Settings โ ๐ Token Usage** and the answer draws itself:
```text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ Token Usage ( Day | Week | Month ) [Last 30d โพ] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โ
โ โ Total ๐งฎ โ โ Input โฌ๏ธ โ โ Output โฌ๏ธ โ โ Calls ๐ โ โ
โ โ 986.2M โ โ 610.0M โ โ 8.4M โ โ 947 โ โ
โ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โ
โ โ
โ ๐ Trend (line chart) โฆ hover any day โ per-model details โ
โ 80M โค โญโโฎ โ
โ 40M โค โญโโโฎ โญโโโฏ โฐโโโฎ โโโ total โ
โ โผโโโโดโโโดโโโดโโโโโโโโดโโโ โโโ alpha-chat โ
โ 06-02 06-06 06-10 โโโ beta-reason โ
โ โ
โ ๐ Model ranking (bar chart) โ
โ alpha-chat โโโโโโโโโโโโโโโโโโโโ 62.4% โ
โ beta-reason โโโโโโโโโโโโโโโโโโโโ 31.8% โ
โ gamma-mini โ 5.8% โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
> ๐ผ๏ธ Schematic of the UI. The real thing follows DSH's theme tokens and looks great in
> **both light and dark mode**.
## ๐ฏ Features
| Area | What you get |
|---|---|
| ๐
**Granularity** | Flip between **Day / Week / Month** โ Monday-start weeks, calendar months; only day buckets are stored, so switching is **instant and free** |
| ๐๏ธ **Custom range** | Presets (last 7 / 30 days, 12 weeks, 12 months, all time) + **pick your own start & end dates** โ chart any window you like |
| ๐ **Line chart** | Bold total line + thin per-model lines, **crosshair hover** breaks down every day, click legend chips to toggle models |
| ๐ **Bar chart** | Horizontal model ranking with share % โ spot your biggest token sink at a glance ๐ธ |
| ๐ **Rebuild** | One-click full re-scan โ idempotent, watermarks guard against double-counting and gaps |
| ๐ก **Accounting** | Dirty counters fail closed, **retried attempts are billed too**, fork inheritance never double-counts, compaction is included |
## ๐ Install
**Option one ยท straight from GitHub (recommended, DSH's standard channel)**
```powershell
dsh plugin --profile web add github:GIN0076/dsh-token-usage
```
**Option two ยท clone & install locally (works offline; the channel this repo was built on)**
```powershell
git clone https://github.com/GIN0076/dsh-token-usage.git
```
Then run `plugin_manager install_bundle` in DSH with the clone directory as target, or use
**Plugins page โ Install Bundle** in the GUI.
(Got `ambiguous-install`? `remove_bundle` first, then install โ a known leftover-link quirk.)
**Hard-refresh the page** (`Ctrl+Shift+R`) โ open **Settings โ ๐ Token Usage** ๐
**Uninstall**: `plugin_manager remove_bundle` โ `@local/token-usage` โ zero host residue;
the data folder is yours to keep or delete.
## ๐ค How it works
```text
~/.dsh/sessions session logs (the single source of truth, read-only)
โ โ live fold of session/event โก startup backfill (watermark-idempotent,
โผ skips sessions whose bytes never changed)
Host half โโโถ day ร provider/model ร six buckets โโโถ storage-domain (persistent)
โ (corrupt? backup-and-skip + rebuild)
โผ
/token-usage-rpc ๐ connection auth + loopback Host + same-origin Origin
โผ same-origin fetch (data never leaves your machine)
Client half โโโถ Settings section + pure-SVG charts (no chart lib, no build, zero deps)
```
**Accounting details** (`stats.js` pure functions, guarded by 46 fixtures):
- โ
Counted: `assistant/message` (including stream usage), **`assistant/attempt` โ retries
cost money too!**, `compaction/summary`
- ๐ท๏ธ Attribution: messages carry their own provider/model; attempts & compaction fall back to
the latest request header
- ๐ซ Skipped: unsafe integers, negatives, reasoning > output, totals that contradict buckets
- ๐ Buckets use the **host's local timezone**; fork-inherited prefixes are cut by
`inheritedEventCount` โ your ancestors' tokens are never counted twice
## ๐ Privacy
- **Local-first**: statistics come only from `~/.dsh/sessions` on this machine โ
**no network calls, no uploads, ever**
- Triple RPC fence: connection auth (cookie) + loopback Host + same-origin Origin
- MIT licensed. No telemetry, no accounts, no backdoors.
## ๐งฉ Architecture (for the tinkerer)
```text
~/.dsh/sessions session logs (the single source of truth, read-only)
โ โ live: ctx.on('session/event') folds post-commit events
โ โก backfill: sessionQuery.listSessions + readSession (watermark-idempotent;
โ skip sessions whose file bytes are unchanged)
โผ
Host half host.js
ยท storage-domain `token_usage` (per-record + backup-and-skip; path-safe base64url keys)
- daily: day ร provider/model ร six counters
- watermark: per-session { seq, route, bytes }
ยท /token-usage-rpc exact route (connection auth + loopback Host + same-origin Origin)
- stats {granularity, fromDay, toDay} โ aggregation (stats.js pure functions)
- status โ { backfill, rebuilding, storageOk, dataSpan }
- rebuild โ full re-scan (pauses live folding + buffered replay to avoid races)
โผ same-origin POST fetch
Client half client.js (static bundle, __ModuleLoader__)
ยท settings.section entry (id: token-usage, order: 50)
ยท Day/Week/Month segmented control + presets (7d/30d/12w/12m/all/custom dates) + rebuild
ยท summary cards โ trend line chart (bold total + per-model lines + crosshair + legend)
โ model ranking bar chart
```
| File | Responsibility |
|---|---|
| `stats.js` | Pure aggregation; `node stats.fixtures.mjs` runs **46 fixtures** (accounting / dedup / week-month buckets / custom ranges / rollup consistency) |
| `host.js` | Host half: folding, backfill, rebuild, storage, RPC |
| `client.js` | Client half: section, controls, two SVG charts |
| `cordis.patch.yml` | Bundle patch row (relative specifier `./host.js`) |
| `locale/{zh,en}.json` | Plugin Manager display metadata; section copy lives inline in client.js |
## ๐ ๏ธ Developer cheat sheet
| Want toโฆ | Do this |
|---|---|
| Change the **Client half** (UI / charts) | Edit `client.js` โ **hard-refresh the page** (client-hmr swaps the rev) |
| Change the **Host half** (stats / RPC) | Edit `host.js` โ **restart DSH**; hot-editing a running host is limited by Node's per-URL ESM cache, so swap the filename to force a new generation (see below) |
| Run tests | `node stats.fixtures.mjs` (46 fixtures) |
| Syntax check | `node --check host.js && node --check client.js && node --check stats.js` |
**Host hot-reload recipe** (running, no restart): edit `host.js` โ `Copy-Item host.js host2.js`
โ point the `cordis.patch.yml` row name at `'./host2.js'` โ `remove_bundle` + `install_bundle`.
Why: Node caches ESM per URL in-process; a new filename = a fresh URL = freshly loaded code.
**Fresh installs and restarts are not affected by this at all.**
### โ ๏ธ Pitfalls we hit (all fixed; kept here as a field manual)
| Pitfall | Symptom | Root cause & fix |
|---|---|---|
| `connection` not injected | Every RPC returns an empty 400 | Cordis Context is a strict proxy: touching a non-injected service throws, and the webserver's catch-all turns it into 400. Fix: add `'connection'` to `inject` (same as open-in-app) |
| Broken disposer | Domain stuck `already-open` after every remove | `ctx.inject()` returns a **fiber, not a function** โ calling it threw a TypeError and aborted `dom.close()`, leaking the reservation. Fix: try/catch per step, close first, let the parent ctx cascade child fibers |
| Inconsistent error check | Retry gave up after one attempt | `DomainError.code='already-open'` (hyphen) vs `message="โฆ is already open"` (**space**) โ check both |
| One-shot storage failure | `storageOk:false` forever | Added lazy recovery: retry `attachStorage` on later requests; when memory already holds data, skip hydrate (double-count guard) and overwrite disk instead |
| Ghost domain | A dead generation holds the reservation | `storageDomain.get(name)` returns the leaked handle โ close it directly (the facility is a singleton, so the holder must be a dead fiber); now built into the retry path |
## ๐ฆ Restoring after a DSH update
**Yes โ one command.** The plugin source lives in **your workspace**, not in `~/.dsh`, so an
update never touches it; only the profile registration is cleared. Re-run the install command
(`remove` first if you hit `ambiguous-install`). No peer constraints: the bundle declares no
`@deepseek-ai/dsh-*` peers, so compatibility gates never block it, and every API it uses
(`settings.section` / `sessionQuery` / `storageDomain` / `webServer` / `connection` /
`session/event`) is stable upstream surface. After an update, run the four-step check:
1. `list_plugins` โ `include:token-usage` should be `fiberPhase: active`
2. RPC returns **401** unauthenticated / **200** authenticated
3. Hard-refresh โ both charts render in Settings
4. If you edited the Host half, confirm the filename in `cordis.patch.yml` still exists
**Data**: statistics are derived. Even if `~/.dsh` is wiped, restoring the session logs makes
the startup backfill **rebuild everything** โ or hit "Rebuild" for a full re-scan. The source
of truth can't be lost, so the aggregates can always grow back.
## ๐ License
[MIT](LICENSE) ยฉ 2026 GIN0076 โ issues and PRs welcome.
*Inspired by the usage panel in [ZCode Usage Stats](https://zcode.z.ai/en/docs/usage-stats)
and the accounting design of local-first trackers like ccusage / tokscale / token-history.*
Install
dsh plugin --profile web add github:GIN0076/dsh-token-usage
Profile: web
With the hub plugin installed, ask your agent to install it by name โ it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install local-token-usage from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.