Skip to content
dsh.fish
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: MIT](https://img.shields.io/badge/License-MIT-ffd93d?style=flat-square&labelColor=2b2b2b)](LICENSE)
[![DSH Plugin](https://img.shields.io/badge/DSH-Plugin-4f8ef7?style=flat-square&labelColor=2b2b2b)](#-install)
[![Version](https://img.shields.io/badge/version-1.0.0-22b07d?style=flat-square&labelColor=2b2b2b)](CHANGELOG.md)
[![Zero Deps](https://img.shields.io/badge/zero--deps-๐ŸŸฉ_pure_SVG-e0566b?style=flat-square&labelColor=2b2b2b)](#-how-it-works)
[![Local Only](https://img.shields.io/badge/local--first-๐Ÿ”’_nothing_leaves_your_machine-9b6bff?style=flat-square&labelColor=2b2b2b)](#-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

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source