← Back to home@lurejewel

dsh-usage-plugin

Lightweight, native sidebar usage panel for DeepSeek Harness: official balance + token usage history from session logs.

Stars
0
Language
JavaScript
Created
Aug 25, 2026
Updated
Sep 28, 2026
GitHub repo

Introduction

dsh-usage-plugin

npm version npm downloads License: MIT

A native sidebar usage panel for DeepSeek Harness Web: shows your official DeepSeek balance and token usage history (today / last 7 days) right in the sidebar, with no separate process and no cross-origin calls.

A trigger button appears at the bottom of the left sidebar, above Settings — full-width with a label when the sidebar is expanded, a compact icon when collapsed. Click it to open the panel: live balance, today's input / output / cache tokens, and a 7-day trend computed from your own session logs.

Features

  • Official balance — live query of api.deepseek.com/user/balance using the API key DSH already has (DEEPSEEK_API_KEY from your credentials), never stored in the browser.
  • Token usage history — reads your $DSH_HOME/sessions logs and aggregates input / output / cache tokens per day (today, totals, daily average, cache hit rate).
  • Sidebar integration — registers a sidebar.footer.action trigger and a shell.overlay modal; layout follows the DSH design tokens (light/dark themes included).
  • Top up in one click — a 充值 / Top up button beside Refresh opens the official billing page (https://platform.deepseek.com/top_up) in a new tab.
  • Zero footprint — no daemon, no config, no database; data comes from the same DSH installation you already run.

Requirements

RequirementVersion
DeepSeek Harness>= 0.1.0-rc.7 (verified on 0.1.5-rc.2)
Node.js>= 20
pnpm>= 10 (for dsh plugin installation)
API keyan existing DEEPSEEK_API_KEY credential (the one DSH already uses)

Installation

From anywhere, run:

dsh plugin --profile web add dsh-usage-plugin

That's it — the package declares a dsh.bundle patch, so dsh plugin automatically mounts it into the profile layer stack. Then:

  1. Restart dsh web (stop and start the process). The loader tree is composed once at boot, so a plugin installed into a running server is not picked up until then.
  2. Hard refresh the browser (Cmd/Ctrl+Shift+R).
  3. Look for the usage icon at the bottom of the left sidebar.

Verifying the install

GET /api/dsh-usage/balance and GET /api/dsh-usage/stats?days=N sit behind the same process-token fence as the rest of DSH's /api (dsh >= 0.1.5), so a bare curl gets 401 unauthorized. Use the browser page, or pass the token that dsh web prints at startup:

# dsh web: http://127.0.0.1:3080/?token=XXXXXXXX
node scripts/verify-install.mjs 3080 XXXXXXXX

The verifier ships with the package, so an npm install can run it from ~/.dsh/profiles/web/node_modules/dsh-usage-plugin.

Alternatives

  • From GitHub: dsh plugin --profile web add github:lurejewel/dsh-usage-plugin
  • From a release tarball: dsh plugin --profile web add https://github.com/lurejewel/dsh-usage-plugin/archive/refs/tags/v0.1.6.tar.gz
  • From a local checkout (development): run the same command from inside this repo. Quote the path: dsh plugin forwards its arguments to pnpm through a shell without quoting them, so an unquoted path containing spaces is split into several package specs — D:\Software\DeepSeek Harness\dsh-usage-plugin becomes link:D:/Software/DeepSeek plus a phantom Harness\dsh-usage-plugin dependency, and the plugin is dropped from dsh.profile.bundles:
dsh plugin --profile web add '"D:\Software\DeepSeek Harness\dsh-usage-plugin"'

scripts/install-local.ps1 does exactly this.

  • Manual mount on older setups: add the row below to ~/.dsh/profiles/web/cordis.patch.yml, then restart:
- insert:
    - id: dsh-usage-plugin
      name: dsh-usage-plugin

Uninstall

dsh plugin --profile web remove dsh-usage-plugin

Then restart dsh web.

How it works

One package, two halves, mounted as a normal Cordis plugin:

lib/index.js          Host half: same-origin HTTP routes on the DSH web server
lib/client.js         Browser half: __ModuleLoader__ bundle served to the web GUI
lib/usage-history.js  Session-log reader (zstd frame scan + per-step dedup + daily aggregation)
  • GET /api/dsh-usage/balance — live official balance (server-side call, key never leaves the server).
  • GET /api/dsh-usage/stats?days=N&fresh=1 — balance + usage history; N defaults to 7, clamped to 1–90. fresh=1 bypasses the short result cache (the panel's refresh button sends it).

Implementation notes worth knowing:

  • The session scan runs in a worker thread (usage-service.js), never on the server's event loop. It reads and inflates every artifact — seconds of synchronous work on a large history — and running that inline stalls the entire harness web server. That is not merely slow: anything that health-checks the server over HTTP reads the stall as "server down". (dsh-remote's bridge watchdog probes http://127.0.0.1:3080/ with a 2s timeout and kills the tunnel when it fails, so an inline scan visibly kills and restarts the remote bridge on every panel click.) Results are cached for 15s and concurrent requests share one scan.
  • DSH session logs (session.<generation>.jsonl.zstd) are multiple concatenated zstd frames (one frame per persistence batch); the reader scans frame-by-frame instead of assuming a single frame. One damaged frame costs only its own rows.
  • Session artifacts are versioned format generations — session.jsonl.zstd is the released v0 root, session.vN.jsonl.zstd a later one, and DSH always writes and reads the highest generation present in a session directory. The reader therefore selects the highest canonical generation per session (never the plain v0 file alone, and never more than one file per session): a lower generation stops at that session's migration point, while reading two of them would double-count.
  • assistant/message and assistant/chunk events report the same usage numbers for the same (turn, step); the reader de-duplicates by (turn, step) so totals are not double-counted.
  • The API key is resolved through DSH's credentials service — the same source the DeepSeek provider uses. Nothing is stored in the browser; all calls are same-origin.
  • Both routes inherit DSH's browser-trust fence, and on dsh >= 0.1.5 the client bundle is delivered as part of the shell's single combined /plugins/??… request rather than per-package URLs. Neither changes the browser half: it runs inside the authenticated page.

Privacy & security

  • The balance call goes from your server to api.deepseek.com; the API key never enters the browser.
  • The panel only reads your own session logs under $DSH_HOME/sessions.
  • No telemetry, no third-party network calls.

Development

npm test                       # unit tests (frame scan / generation pick / dedup /
                               #   worker service / cache / non-blocking scan)
npm run test:client-boot       # boot the real client bundle in Node against mock slots
npm run test:standalone        # in-process E2E: real cordis + real API + real logs
                              #   (needs a local DSH install with @deepseek-ai packages
                              #    reachable at $DSH_HOME/profiles/node_modules, a
                              #    DEEPSEEK_API_KEY credential, and session logs)
npm run verify -- 3080 <token> # post-restart check against a running dsh web

lib/ is the shipped artifact and doubles as readable source (plain ESM, documented).

Windows helper scripts (optional)

  • scripts/install-local.ps1 — one-click local install as a link: to this repo (quotes the path for the dsh plugin shell hop).
  • scripts/restart-web.ps1 — restart dsh web, read the tokenized URL from the server log, then run the verification.
  • scripts/verify-install.mjs — post-restart check: token exchange, stats/balance routes, materialized client row, combined bundle contents. On dsh >= 0.1.5 it needs <port> <token>.

The two .ps1 helpers live in the git checkout only; verify-install.mjs is published in the npm package as well.

Changelog

0.1.6

  • The panel footer now has a Top up button (充值) to the left of Refresh, opening the official billing page https://platform.deepseek.com/top_up in a new tab. It is a plain anchor, so middle-click and copy-link work normally, and its label goes through the locale service.
  • peerDependencies now also declares the client-side packages this plugin consumes (dsh-client-ui-primitives, dsh-client-ui-slots, dsh-client-locale), so a future host version the plugin was not validated against is flagged at install time.

0.1.5

  • Fixed the sidebar entry disappearing on dsh 0.1.7: the host renamed its icon exports from size suffixes to stroke-weight suffixes (IconDataOutline16 → IconDataOutlineRegular). createElement(undefined) throws while the slot renders, so the whole button vanished while the routes, the boot manifest and the served bundle all still looked healthy. Icons now resolve through a candidate list with a built-in fallback glyph, so the next rename degrades instead of blanking the entry.
  • dsh.client.inject no longer names @deepseek-ai/dsh-client-runtime (which 0.1.7 does not ship) and now declares dsh-client-ui-sidebar, the owner of the slot this plugin renders into.
  • test/client-boot.test.mjs now renders the components against the real primitives export list read from the installed host, instead of a hand-written mock. The mock listed the old icon name, which is exactly why the rename slipped through.

0.1.4

  • The session scan now runs in a worker thread (with a 15 s result cache and de-duplication of concurrent scans), so the panel no longer blocks the harness event loop. Previously every panel open stalled the whole web server for seconds — long enough that dsh-remote's 2 s bridge watchdog declared the harness offline and restarted the remote tunnel on each click.
  • /api/dsh-usage/stats?fresh=1 bypasses the cache; the panel's refresh button sends it, while simply reopening the panel reuses a recent scan.
  • Balance and usage history are now fetched in parallel.

0.1.3

  • Read the highest session format generation (session.vN.jsonl.zstd), not only the released v0 root. A migrated session's v0 file stops at its migration point, so today's usage read as 0 and history under-counted by roughly 11%.
  • A damaged zstd frame now costs only its own rows instead of the whole session's history.

0.1.2

  • Adapt to dsh 0.1.5: process-token fence, combined client-bundle request, install paths containing spaces.
  • Route registrations are wrapped in ctx.effect, so unload/reload removes them instead of leaving zombie handlers.

0.1.1

  • Restore the /api/dsh-usage/stats client request (the path had been over-renamed).
  • Full-width footer trigger while the sidebar is expanded.

0.1.0

  • First release: official balance + token usage history in the sidebar.

License

MIT