GIN0076/dsh-token-usage โ†—โ˜… 0

@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 ่ฎพ็ฝฎ้ขๆฟ่ฏๅ…ƒ็”จ้‡็ปŸ่ฎกๆ’ไปถ ้€‚ๅˆ้œ€่ฆ็›ด่ง‚็›‘ๆŽงๆฏๆ—ฅใ€ๆฏๅ‘จใ€ๆฏๆœˆๆจกๅž‹Tokenๆถˆ่€—็š„็”จๆˆทใ€‚

Package
@local/token-usage
Compatibility
Unverified
Version
1.0.0
License
MIT
Last updated
Sep 26, 2026

Install

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:GIN0076/dsh-token-usage

๐Ÿ“Š Token Usage for DSH

Bolt a fuel gauge onto your DeepSeek Harness Settings panel โ€” see exactly how many tokens every model burns, every day / week / month.

License: MIT DSH Plugin Version Zero Deps Local Only

English ยท ็ฎ€ไฝ“ไธญๆ–‡


โœจ 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:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿ“Š 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

AreaWhat you get
๐Ÿ“… GranularityFlip between Day / Week / Month โ€” Monday-start weeks, calendar months; only day buckets are stored, so switching is instant and free
๐Ÿ—“๏ธ Custom rangePresets (last 7 / 30 days, 12 weeks, 12 months, all time) + pick your own start & end dates โ€” chart any window you like
๐Ÿ“ˆ Line chartBold total line + thin per-model lines, crosshair hover breaks down every day, click legend chips to toggle models
๐Ÿ† Bar chartHorizontal model ranking with share % โ€” spot your biggest token sink at a glance ๐Ÿ’ธ
๐Ÿ” RebuildOne-click full re-scan โ€” idempotent, watermarks guard against double-counting and gaps
๐Ÿ›ก AccountingDirty 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)

dsh plugin --profile web add github:GIN0076/dsh-token-usage

Option two ยท clone & install locally (works offline; the channel this repo was built on)

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

 ~/.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)

 ~/.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
FileResponsibility
stats.jsPure aggregation; node stats.fixtures.mjs runs 46 fixtures (accounting / dedup / week-month buckets / custom ranges / rollup consistency)
host.jsHost half: folding, backfill, rebuild, storage, RPC
client.jsClient half: section, controls, two SVG charts
cordis.patch.ymlBundle patch row (relative specifier ./host.js)
locale/{zh,en}.jsonPlugin 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 testsnode stats.fixtures.mjs (46 fixtures)
Syntax checknode --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)

PitfallSymptomRoot cause & fix
connection not injectedEvery RPC returns an empty 400Cordis 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 disposerDomain stuck already-open after every removectx.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 checkRetry gave up after one attemptDomainError.code='already-open' (hyphen) vs message="โ€ฆ is already open" (space) โ€” check both
One-shot storage failurestorageOk:false foreverAdded lazy recovery: retry attachStorage on later requests; when memory already holds data, skip hydrate (double-count guard) and overwrite disk instead
Ghost domainA dead generation holds the reservationstorageDomain.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 ยฉ 2026 GIN0076 โ€” issues and PRs welcome.

Inspired by the usage panel in ZCode Usage Stats and the accounting design of local-first trackers like ccusage / tokscale / token-history.