Skip to content
dsh.fish
Bundle

dsh-usage-unified

Unified DeepSeek Harness usage statistics — machine-wide token accounting across every dsh home, with trends, a custom date range, per-model bucket splits, call details and CSV/JSON export.

Source
satan9394
License
MIT
Updated
Updated 12 hours ago

Readme

# dsh-usage-unified

English | [中文](./README.zh.md)

One DeepSeek Harness (`dsh`) plugin that is the **union** of two upstream plugins,
so no feature lives in one and not the other:

| Upstream | What it contributed |
| --- | --- |
| [`lanlandeli/dsh-usage-stats`](https://github.com/lanlandeli/dsh-usage-stats) | The rich dashboard: sidebar + full-screen overlay, per-day token trend, model donut, token-composition card, per-call detail table with filters and pagination, CSV/JSON export, workspace/task-scope filters, zh/en i18n, theme-adaptive styling. |
| [`zoyluoblue/deepseek-harness-token`](https://github.com/zoyluoblue/deepseek-harness-token) | The machine-wide core: discovery of **every** dsh home (`~/.dsh`, `~/.dsh_desktop/<version>`, `$DSH_HOME`), raw session-log reading (zstd container frames), the pure resumable fold, an incremental durable index, all-time / 7d / 30d ranges, current + longest streaks, peak hour, per-model disjoint-bucket split, coverage disclosure, and a Settings section. |

The two were **merged at the data layer**, not bolted together: one machine-wide
index now feeds one dashboard, so the numbers agree everywhere.

## Feature matrix (after the merge)

| Feature | Upstream #1 | Upstream #2 | Unified |
| --- | :---: | :---: | :---: |
| Machine-wide (all dsh homes / old Desktop versions) | ✗ | ✓ | ✓ |
| Both session-log formats (`session.jsonl.zstd`, `session.v3.jsonl.zstd`) | n/a | ✗ (v0 only) | ✓ |
| Sidebar entry + full-screen overlay | ✓ | ✗ | ✓ |
| Settings → Usage section | ✗ | ✓ | ✓ |
| Token totals incl. cache reads | ✓ | ✓ | ✓ |
| 7d / 30d / all-time trend | ✓ | ✓ | ✓ |
| Custom date range | ✗ | ✗ | ✓ |
| Smooth multi-series trend (total / input / output / cache read) | ✗ | ✗ | ✓ |
| Current streak | ✓ | ✓ | ✓ |
| Longest streak | ✗ | ✓ | ✓ |
| Peak hour | ✗ | ✓ | ✓ |
| Cache-hit-rate + model-call cards | ✗ | ✗ | ✓ |
| Per-model donut + share | ✓ | ✗ | ✓ |
| Per-model input/cacheRead/cacheWrite/output split | ✗ | ✓ | ✓ |
| Per-call detail table (time, duration, tokens, cache %, model, effort) | ✓ | ✗ | ✓ |
| Workspace + main/subtask scope filters | ✓ | ✗ | ✓ |
| CSV / JSON export | ✓ | ✗ | ✓ |
| Coverage + homes + skipped-log disclosure | ✗ | ✓ | ✓ |
| zh/en, light/dark | ✓ | ✓ | ✓ |

> The activity heatmap both upstreams shipped was **dropped** here as redundant
> with the trend chart.

## Install

```powershell
# straight from GitHub (runtime files are committed — no install-time build):
dsh plugin --profile web add github:satan9394/dsh-usage-unified

# from an npm release, once published:
dsh plugin --profile web add dsh-usage-unified

# from a local checkout:
dsh plugin --profile web add E:\path\to\dsh-usage-unified
```

Restart the Web profile. The dashboard appears as a **使用统计 / Usage Stats**
item in the sidebar footer, and as a section under **Settings**. All data stays
local; the routes answer loopback callers only.

See [docs/COMPATIBILITY.md](./docs/COMPATIBILITY.md) for the declared Node/DSH
ranges and [docs/SECURITY.md](./docs/SECURITY.md) for permissions, dependencies,
and failure bounds.

## Architecture

```
src/
  index.ts          host entry: Config, mount index + routes + refresh loop
  homes.ts          discover every dsh home (deduped by sessions realpath)
  reader.ts         walk session logs; decode both on-disk formats (no private API)
  zstd-frames.ts    scan concatenated zstd frames (resume cursor is frame-aligned)
  fold.ts           pure resumable fold: session → totals, day/hour slices, calls
  aggregate.ts      snapshot + call rows + streaks/peak hour + CSV
  index-store.ts    incremental index, file-backed cache under DSH_HOME
  transport.ts      /snapshot, /calls, /export.csv, /export.json (loopback only)
  types.ts          the wire contract shared by both halves
  client/
    index.tsx       sidebar + overlay + settings registrations, dashboard
    i18n.ts         merged zh/en dictionaries
    source.ts       the one transport seam
    styles.ts       dashboard stylesheet
```

Key design decisions (full rationale in [PLAN.md](./PLAN.md)):

- **The reader is harness-version-agnostic.** Legacy logs are JSONL *storage
  records* where streaming content is packed (`text-chunks`, …) with no `seq`;
  versioned logs are plain events. Token accounting only needs the sequenced
  events (`assistant/message` carries the final usage), so the reader keeps any
  line with a string `type` and numeric `seq` and skips the packed runs — no
  dependency on the harness's private `decodeStorageRecord`, which has moved
  between releases.
- **One row per `(turn, step)`.** A usage report arrives twice per step (a
  streaming `assistant/chunk` and the final `assistant/message`); the fold
  *replaces* rather than accumulates, and stores one call row per step for the
  detail table. Compaction summaries are their own rows.
- **All-time vs bounded range.** All-time reads authoritative per-session
  counters (so tokens with rejected timestamps are still counted); a bounded
  range is summed from day slices (so it can be smaller — the honest behaviour).
- **File-backed cache.** The index persists to `$DSH_HOME/usage-unified/index-v1.json`
  atomically, rather than depending on `ctx.storageDomain`, so the panel always
  loads.

## Development

```powershell
npm install          # .npmrc sets legacy-peer-deps for the dsh peer tree
npm run typecheck    # tsc --noEmit
npm run test         # vitest (45 tests)
npm run build        # tsdown → lib/index.js + lib/client.js
npm run check        # all three
npm run verify:realdata   # read-only pass over this machine's real dsh homes
npm run smoke:local       # local HTTP self-test against real ~/.dsh (no install)
npm run smoke:serve       # keep the local viewer up (prints the URL)
npm run report            # static self-contained report (30d) → opens in browser
npm run report:all        # same, all-time range
```

`smoke:local` is the "run it in the workspace, point at the real data" path: it
does **not** touch the DSH profile. It mounts the same host routes over a plain
Node server on a free loopback port, points the index at the real `~/.dsh`,
runs seven HTTP assertions (snapshot 7d/all, main scope, calls, CSV, JSON,
404), and — with `smoke:serve` — serves a small built-in viewer so the real
numbers are visible without the DSH UI. The first cold scan takes minutes; the
index is cached in `.smoke-cache/` (gitignored), so later runs are seconds.

`verify:realdata` walks every real session log, folds it, and cross-checks the
folded tokens against an independently coded reconstruction of provider usage;
it prints the discovery, both formats, totals, models, workspaces, coverage,
the call endpoint and a persistence round-trip.

## Compatibility

- Targets the `dsh` 0.1.5-rc.1 plugin API (`peerDependencies`), and reads both
  the legacy and the current session-log layouts, so it is not limited to one
  harness generation.
- The browser half declares its own minimal structural types for the injected
  runtime seam (`src/client/runtime.d.ts`) instead of importing
  `@deepseek-ai/dsh-client-runtime`, whose published line has diverged from the
  one the harness ships. The `@deepseek-ai/*` client modules remain
  `peerDependencies`, provided by the harness at load time.

## Cross-agent leaderboard (optional, opt-in)

The dashboard here is **DSH-only** and works entirely offline — installing the
plugin never contacts the network and never uploads anything. Aggregating every
agent on the machine (DSH, Claude Code, Codex, OpenCode, …) into one number and
posting it to the [tokscale](https://github.com/junhoyeo/tokscale) leaderboard
is **opt-in** and only happens if you run the guided setup **from a clone** of
this repo (the plugin itself never needs it):

```powershell
git clone https://github.com/satan9394/dsh-usage-unified && cd dsh-usage-unified
npm install
npm run leaderboard:setup     # guided: install/login tokscale, export, first submit
npm run leaderboard:off       # undo: remove the daily task, stop submitting
```

`leaderboard:setup` follows [docs/LEADERBOARD.md](docs/LEADERBOARD.md), which
states exactly what leaves the machine and asks before anything is uploaded.
That document also covers the exporters (`scripts/tokscale-export.mjs` for DSH's
versioned logs, `scripts/ccswitch-export.mjs` for CC Switch's proxy-side Claude
usage), the pricing table (`scripts/custom-pricing.mjs`), and the daily refresh
task.

### Privacy at a glance

A submit carries **aggregates only** — token buckets per day, estimated cost,
message counts, client and model names, MCP server names, session timing
metrics, a random `dev_…` device key, and the CLI version. It never carries
prompts, responses, source code, or workspace/file paths. Two items cannot be
redacted at upload: model/provider names and MCP server names (rename your MCP
servers if those are sensitive). Set `TOKSCALE_USERNAME` before `npm run report`
to embed your own profile card; leave it unset and the report stays anonymous.

## License

MIT — see [LICENSE](./LICENSE). Derived from two MIT-licensed plugins; see
`_upstream/` for their sources.

Install

dsh plugin --profile web add github:satan9394/dsh-usage-unified

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source