dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Machine-wide DeepSeek Harness (dsh) token usage statistics — trends, a custom date range, per-model bucket splits, call details, CSV/JSON export. 全机 DSH 用量统计:趋势 / 自定义区间 / 模型四桶 / 调用明细 / 导出,中英双语、浅深色。
Machine-wide token accounting for DeepSeek Harness (dsh).
One dashboard over every session log on the machine: trends, per-model breakdown, a session
ranking, call details, exports, and an optional local cost estimate. Everything runs locally —
the plugin never contacts the network.
English | 中文
dsh writes a session log per conversation. Reading those logs is the only way to answer
"what did this machine actually spend, and on what" — the harness's own projection only covers
the home it is running against.
This plugin merges two earlier plugins into one, at the data layer rather than side by side, so one index feeds one dashboard and the numbers agree everywhere:
| Upstream | What it contributed |
|---|---|
lanlandeli/dsh-usage-stats | The dashboard: sidebar entry and overlay, per-day trend, per-call detail table with filters and pagination, CSV/JSON export, workspace and task-scope filters, zh/en, theme-adaptive styling. |
zoyluoblue/deepseek-harness-token | The machine-wide core: discovery of every dsh home, raw zstd log reading, a pure resumable fold, an incremental durable index, streaks, peak hour, per-model disjoint-bucket split, coverage disclosure, settings section. |
~/.dsh, ~/.dsh_desktop/<version>, $DSH_HOME,
plus any extra roots you list. Reads legacy session.jsonl[.zstd] and versioned
session.v<N>.jsonl[.zstd], and never depends on the harness's private decoder.dsh 0.1.5-rc.1 or newer on the plugin API (see Compatibility).^22.19.0 || >=24.0.0 — the runtime the harness itself uses.react and the @deepseek-ai/* modules are peers, provided by the
harness.# from GitHub
dsh plugin --profile web add github:satan9394/dsh-usage-unified
# from a local checkout
dsh plugin --profile web add E:\path\to\dsh-usage-unified
Then restart the Web profile. The dashboard appears as 使用统计 / Usage Stats in the sidebar footer, and as a section under Settings.
DSH STORE status. The marketplace lists this plugin as
blocked. Its automatic admission policy refuses any plugin whose runtime source touches the filesystem, the network, commands or credentials, and this one must: the host half reads session logs from disk and the browser half fetches its own loopback route. The catalog keeps the manual install path above, and docs/SECURITY.md states exactly what the runtime does with those capabilities. Nothing about the plugin's behaviour differs from the description here.
The runtime (lib/) is loaded when the harness starts, so a new build only takes effect after a
restart of the Web profile.
link: — git pull in that directory, rebuild if the source changed
(npm run build), then restart. A link: dependency is a directory junction, so there is no
copy to refresh: the profile reads the checkout itself.dsh plugin --profile web add <same target> again. That install is
pinned to a commit, so re-adding is what moves it.Only a change to the runtime needs the restart; documentation and scripts/ changes do not.
Open the sidebar footer entry (or Settings → Usage Stats). Pick a range; the caption under the range control and the trend panel's header both spell out the exact window being reported.
The host serves four same-origin, loopback-only routes under /usage-unified/v1:
/snapshot, /calls, /export.csv, /export.json. Non-loopback callers get 403.
Set these in the profile's plugin row (the bundle patch in
cordis.patch.yml shows the shape).
| Key | Type | Default | Meaning |
|---|---|---|---|
extraSessionRoots | string[] | [] | Extra dsh home directories to scan. Home discovery is a heuristic, so a home only reachable through an unset $DSH_HOME has to be listed here. |
includeCompaction | boolean | true | Count the tokens spent generating compaction summaries. Set false to reconcile 1:1 with the harness's own projection. |
refreshIntervalMs | number | 30000 | How often to re-scan for appended sessions. |
indexChunkYieldMs | number | 16 | Cooperative yield interval during a scan, so a cold build stays responsive. |
indexConcurrency | number | 4 | Sessions decoded in parallel. Measured: 2–4 is the sweet spot; 8+ regresses. |
apiPath | string | /usage-unified/v1 | Same-origin read-only API prefix. |
cachePath | string | $DSH_HOME/usage-unified/index-v1.json | Index cache location. |
pricingPath | string | $DSH_HOME/usage-unified/pricing.json | Cost table. Absent file means no cost is shown. |
cacheWriteDelayMs | number | 1000 | Debounce before the index is written. It is written only when it actually changed. |
This is the whole picture; docs/SECURITY.md has the details and the failure bounds.
$DSH_HOME/usage-unified/index-v1.json,
atomically (temp + rename) with mode 0600, and only when the index changed.403). Its imports are node:fs/promises, node:path, node:os,
node:zlib and @deepseek-ai/*.child_process, no shell, no eval, and no secret is
read, written or logged.dsh ships no price list, so the plugin shows no cost by default. When a pricing table
exists, an "Est. cost" card and a per-model cost appear; a model with no entry is counted as
unpriced, never as free, and the card plus the footer disclose the priced share.
npm run pricing:setup # writes $DSH_HOME/usage-unified/pricing.json
Two sources are merged, the second winning:
~/.cc-switch/model-pricing.json (--source can point at any file
in this project's own { models: { id: {...} } } shape).scripts/pricing.override.json (in git): the models the primary table lacks,
plus two-tier rates. It lives here rather than in CC Switch because a models.dev re-sync can
drop hand-added rows; every pricing:setup re-applies it.Time-of-day tiers. DeepSeek prices its whole Flash/Pro line by clock: peak is UTC Mon–Fri
01:00–04:00 and 06:00–10:00 (35 of the week's 168 hours). An entry may declare both an off-peak
table and a peak tier; the plugin folds them into the effective rate as
offPeak × (1 − peakShare) + peak × peakShare, with peakShare defaulting to 0.2083, and
discloses that share on the card — neither the optimistic off-peak bound nor the peak one. The
values shipped match DeepSeek's pricing page,
OpenCode Go and
Command Code.
Disclosed rather than hidden — the footer repeats the ones that apply to the current data.
retried in the
footer.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 + session ranking + call rows + streaks/peak hour + CSV
pricing.ts optional pricing table: load, match by model id, cost + coverage
index-store.ts incremental index (parallel decode), file-backed cache under DSH_HOME
transport.ts /snapshot, /calls (incl. ?session= drill-down), /export.* (loopback only)
types.ts the wire contract shared by both halves
client/
index.tsx sidebar + overlay + settings registrations, dashboard
i18n.ts zh/en dictionaries
source.ts the one transport seam
styles.ts dashboard stylesheet
Design decisions worth knowing (full rationale in PLAN.md):
seq; versioned logs are plain events. Accounting only
needs the sequenced events, so the reader keeps any line with a string type and numeric
seq — no dependency on the harness's private decodeStorageRecord, which has moved between
releases.(turn, step). A usage report arrives twice per step (a streaming chunk and
the final message); the fold replaces rather than accumulates, and keeps one call row per
step. Compaction summaries are their own rows.$DSH_HOME rather than ctx.storageDomain, so a missing optional service can never stop the
panel from loading.npm install # .npmrc sets legacy-peer-deps for the @deepseek-ai peer tree
npm run typecheck # tsc --noEmit
npm run test # vitest (68 tests)
npm run build # tsdown → lib/index.js + lib/client.js
npm run check # typecheck + test + build
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 pricing:setup # build the cost table from this machine's CC Switch
npm run report # static self-contained report (all-time) → opens in browser
npm run report:30d # same, last 30 days
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, and runs nine HTTP assertions. verify:realdata
walks every real session log and cross-checks the folded tokens against an independently coded
reconstruction (exact match, zero-token delta on this machine).
lib/ is committed, because DSH STORE requires the runtime artifacts to exist inside the fixed
commit. CI therefore asserts that the committed build output matches the source, so a source
change that was never rebuilt cannot be installed as the old code.
The static report is generated from the same snapshot and reads the same way: --range drives
the headline cards, it carries the same merged model panel, session ranking and cost card, and it
drops the heatmap for the same reason the panel did.
The scripts under
scripts/are only available from a source checkout; they are not part of the published package.
@deepseek-ai/* peerDependencies (currently ^0.1.5-rc.1); Node
^22.19.0 || >=24.0.0.session.jsonl[.zstd] and versioned session.v<N>.jsonl[.zstd].dsh.compatibility in package.json declares only what has actually been verified:
>=0.1.5-rc.2 <0.1.6 with dshReleases["0.1.5-rc.2"] = "compatible". A caret range is
deliberately not used — it would claim rc.3+ as well, which nobody has tested.Details and the evidence behind each claim: docs/COMPATIBILITY.md.
The dashboard is DSH-only and works entirely offline. Aggregating every agent on the machine (DSH, Claude Code, Codex, OpenCode, …) into one number and posting it to the tokscale leaderboard is opt-in, and only happens if you run the guided setup from a clone:
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, which states exactly
what leaves the machine and asks before anything is uploaded. 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 device key and the CLI version. It never carries
prompts, responses, source code or workspace paths. Two items cannot be redacted at upload:
model/provider names and MCP server names (rename your MCP servers if those are sensitive).
MIT — see LICENSE and NOTICE. Derived from two MIT-licensed plugins,
lanlandeli/dsh-usage-stats and
zoyluoblue/deepseek-harness-token;
their sources are kept under _upstream/ for comparison.
Change history: CHANGELOG.md.