Releases: satan9394/dsh-usage-unified
Release list
v0.5.0 — today and 14-day windows, start-to-now spans, an honest custom range
- Ranges: added Today (
range=1d) and Last 14 days (range=14d) beside the existing
7d/30d/all, so the window set is now 1/7/14/30/all. - Custom spans gained a second mode. Alongside start–end, a
fromwith notomeans
start → now: the host resolves the upper bound against today, so the window keeps extending as
the calendar moves instead of freezing on the day it was chosen.towithout afromis
rejected — an upper bound alone has no sane default — as isrange=custom, which is a reporting
id rather than a request value. - The panel no longer calls a hand-picked span "last 30 days". A bounded span reports
range.id = "custom"instead of the placeholder it used to claim. - Today plots by hour. One day bucket says nothing a daily curve can show, so
range=1ddraws
the host's 24 hourly buckets. Those carry totals only (TimeBucketis{tokens, messages}), so
the hourly view deliberately offers the total series alone rather than drawing three empty lines. - The open panel refreshes itself every 60s; the host re-scans its index every 30s. Both the
snapshot and the call table re-read through their existing code paths, so nothing new can drift. - Tests: 77, nine of them new (window bounds,
1daggregation, the range/custom wire rules, hour
ticks). The local smoke run now covers1d,14dand start-to-now end to end.
v0.4.2 — how to update, per install kind
Both READMEs gained an Updating subsection, because the question that actually comes up — "how do I move to a newer build?" — was never answered.
The runtime is loaded when the harness starts, so a new build needs a restart of the Web profile, and what "update" means depends on how the plugin was installed:
- From a checkout or a
link:— pull or edit the checkout, rebuild if the source changed, restart. Alink:dependency is a directory junction, so there is no copy to refresh: the profile reads the checkout itself. - From GitHub or npm — re-run
dsh plugin --profile web add <same target>. That install is pinned to a commit, so re-adding is what moves it.
Documentation and scripts/ changes need no restart at all. npm run check: 68 tests pass.
v0.4.1 — standard READMEs in both languages, plus a changelog
Documentation rewritten in both languages to a conventional shape, plus a changelog.
The READMEs had grown essay-first: the feature matrix preceded any statement of what the plugin is, there was no table of contents, no requirements, no configuration reference, and the facts a reader most needs — what the runtime reads and writes, what it never sends, what it cannot know — were split between an architecture section and a separate document. Both languages now follow the same outline: pitch, contents, features, requirements, install, usage, configuration table with every key and default, data and privacy, cost estimate, known limitations, architecture, development, compatibility, opt-in leaderboard, credits.
Two things are now stated plainly instead of left to be discovered:
- DSH STORE status. The marketplace marks this entry
blocked: its automatic admission policy refuses any plugin whose runtime touches the filesystem or the network, and this one must — the host half reads session logs from disk, the browser half fetches its own loopback route. The manual install path is kept, anddocs/SECURITY.mdstates exactly what those capabilities are used for. - The limitations that make the numbers what they are. Retried steps keep only the final usage (both attempts were billed); a bounded range can total less than all-time because clock-skewed tokens belong in a total but not on a calendar; days are keyed in the host process's timezone; a cold build takes minutes on a large history — ~1,380 log files / ~790 MB compressed here, measured.
CHANGELOG.md collects all nine releases in order and ships with the package. npm run check: 68 tests pass.
v0.4.0 — verified DSH compatibility declaration; the year range retires quietly
Two things in this release.
dsh.compatibility now exists. DSH STORE will not evaluate a listing without an explicit declaration, so package.json carries:
"compatibility": {
"dsh": ">=0.1.5-rc.2 <0.1.6",
"dshReleases": { "0.1.5-rc.2": "compatible" }
}A caret range is deliberately avoided — ^0.1.5-rc.2 would also claim rc.3+, and only rc.2 has been exercised on this machine. Later stable rc releases get their own precise entry once verified.
The retired year range is gone. It existed for the activity heatmap, which both halves dropped; no test mentioned it and the static report no longer asked for it. range=year now degrades to all-time instead of failing, because a browser holding a cached client bundle would otherwise receive a 400 that breaks the whole panel. Other unknown values still return 400, and a test pins both behaviours.
npm run check: 68 tests pass. smoke:local: 9/9.
v0.3.3 — vitest 5, and a store-watch that keeps its polls
Two follow-ups found while checking the GitHub side.
Dependabot #2 could not be merged as-is. It branched from before the 0.3.x work, so its package.json still carried 0.2.0, the old description and the already-deleted tokscale:export script — merging it would have reverted all of that. The upgrade is redone on current main instead, plus the explicit vite devDependency vitest 5 now needs (without it, every run dies with ERR_MODULE_NOT_FOUND before collecting a single test). #2 is closed with that explanation.
scripts/store-watch.ps1 no longer loses a poll. It folded gh's stderr into the JSON stream and then failed with an opaque parse error, which is what left the 2026-09-17 17:13 gap in logs/dsh-store-806.log. It now checks the exit code, parses from the first brace to the last, and records the raw output when it cannot parse.
npm run check: 67 tests pass on vitest 5.0.1; smoke:local 9/9 against real data.
v0.3.2 — retire the DSH exporter (tokscale reads versioned logs itself)
Removes the DSH supplement that tokscale no longer needs.
tokscale 4.17.0 ships junhoyeo/tokscale#1328, so its own dsh scanner reads session.v<N>.jsonl.zstd. Measured on this machine, keeping the converted copies and their scanner.extraScanPaths.dsh entry reports DSH at 24.80B against the 15.38B tokscale reads by itself — the extra root is not deduped against the default scan.
- Deleted
scripts/tokscale-export.mjsand its.tokscale-home/output (both to the recycle bin), thetokscale:exportnpm script, the daily refresh step, and thedshextra-scan-path entry. setup-leaderboard.ps1now warns when the installed tokscale is older than 4.17.0, since below that DSH is under-counted.- The CC Switch supplement stays: tokscale still cannot see proxy-side Claude usage by itself.
Why the leaderboard number moved. Four states, same files: 4.16.0 legacy-only 9.30B; the old 4.16.0 + exporter 18.73B (+7.1% over truth); 4.17.0 native 15.38B (−12.1%); 4.17.0 + exporter 24.80B. Truth is 17.49B, corroborated by DSH's own projection cache (17.56B, −0.42%). So the previous submission was inflated and the new one is low; the user chose the honest figure, resubmitted after delete-submitted-data (server 366.82亿 → 334.27亿 / $4,212.39, rank #425 → #464), and the remaining 12% gap is filed upstream as junhoyeo/tokscale#1348.
v0.3.1 — the static report now reads the same as the panel
The static report had the same defect the panel just fixed: its headline cards read snapshot.allTime.totals, so --range 7d still showed the all-time total and tucked the real window into a second card row underneath.
- The cards now read the selected window; the two streaks stay all-time and say so; the reconciliation line is labelled all-time.
- Mirrored from the panel: one merged model panel (share + four-bucket stack + calls + cost, tail behind a toggle), a session ranking, a cost card with the peak-blend disclosure, and
pricingPathwired up so the numbers match the plugin exactly. - The activity heatmap is gone for the same reason the panel dropped it — it duplicated the per-day bars. Sections collapse with native
<details>.
Verified on this machine: report-all hero 174.91亿 / cost $154.55; report-30d 160.89亿; report-7d 40.33亿 / cost $34.95 — identical to the panel's routes, with per-model cost ($33.69 for deepseek-v4.1-flash) and the all-time reconciliation unchanged across all three.
v0.3.0 — the range governs every card; DeepSeek off-peak/peak pricing
Two reported problems.
1. The time-range control looked inert. The host was right — range=7d returns 7 days and range=all returns every day — but eight of the eleven headline cards read snapshot.allTime.*, which is deliberately range-independent, so the numbers never moved. They now read snapshot.totals.* and snapshot.mostUsedModel; the two streak cards stay all-time (the host always computes them all-time) and are tagged all-time instead of silently ignoring the filter. A caption under the range control and in the trend panel's header spells out the resolved window, and smoke:local now asserts range isolation so this cannot regress.
2. Some models had no price. Cross-checking DeepSeek's pricing page against OpenCode Go's and Command Code's per-model tables showed only deepseek-v4.1-flash (the busiest model here, 21.7% of all tokens) and a zero-usage muse-spark-1.2-contributor were missing — and that DeepSeek's whole Flash/Pro line is priced by time of day.
- A price entry may now carry a
peaktier, folded into the effective rate bypeakShare(default 0.2083 = the documented 35/168h peak window) and disclosed on the cost card. Neither the optimistic off-peak bound nor the peak one. scripts/pricing.override.json(in git) holds the hand-checked entries and is merged over CC Switch's table on everypricing:setup, so a models.dev re-sync cannot drop them.- CC Switch's own table was updated too, so tokscale's custom pricing agrees (backed up first).
Also: zero-token model rows are filtered out of the panel — they leaked display names and failed requests into the unpriced list — and the pricing module is re-exported from the host entry.
Measured on this machine: cost .61 -> .55, unpriced models 3 -> 0, headlines 7d/30d/all = 40.3亿 / 160.9亿 / 174.9亿, tests 62 -> 67, smoke assertions 8 -> 9.
v0.2.1 — cost estimate correctness (no double count, nested model ids)
Patch on top of v0.2.0, found by verifying the live plugin against this machine's real data.
Cost estimate was roughly 2x too high. The summary summed the per-model rows and the session rows, which describe the same tokens. Only the per-model rows partition a window's tokens, so they now alone feed the total; session rows and the most-used-model card are decorated with a per-row cost for display only. The smoke test asserts priced + unpriced == window tokens so this cannot regress silently.
Nested model ids never matched the price table. Ids whose own name contains a slash (command/deepseek/deepseek-v4.1-flash — the bulk of this machine's usage) were looked up with only the first segment treated as the provider. lookupPrice now also tries the segment after the last slash.
Measured on the author's machine: cost .79 -> .61, and priced model rows went from effectively 0 to 27 of 30.
npm run check: 62 tests pass. npm run smoke:local: 8/8.
v0.2.0 — merged model panel, session ranking, optional cost, faster index
Dashboard
- One model panel replaces the two duplicate ones: each row carries the share, the stacked input/cache-read/cache-write/output bar, the call count and (when priced) the cost. Only the first six models show until you ask for the rest.
- Every large panel is now collapsible (trend, models, breakdown, session ranking, call details); the flag survives the overlay closing.
- New session ranking — sessions ranked by tokens for the selected window, with one click drilling through to that session's calls.
- New cost estimate card, shown only when a local pricing table exists; models missing a price are disclosed as unpriced, never counted as free.
Cost table (opt-in)
npm run pricing:setupwrites\/usage-unified/pricing.jsonfrom CC Switch's~/.cc-switch/model-pricing.json(or any file in this project's own shape). Edit prices without reinstalling: the table is re-read on each index scan.
Performance (measured, not guessed)
- Bounded-concurrency decoding (
indexConcurrency, default 4): cold-cache 250 files went 37.5s -> 23.1s; 2-4 lanes is the sweet spot, 8 regresses. - A resume read now seeks to the appended tail instead of re-reading the whole log.
- The index persists only when it changed — it used to rewrite a ~20 MB cache every refresh interval.
- Async
zlib.zstdDecompresswas benchmarked and rejected (per-frame await made it ~2x slower than the sync path).
Cleanup
- Removed the dead heatmap/bar-chart CSS (-61 lines) and 7 unused dictionary keys.
scripts/tokscale-export.mjsis deprecated: junhoyeo/tokscale#1328 shipped in tokscale v4.17.0, which reads versioned DSH logs natively. Migration steps are in the script header; keeping it past the upgrade would double-count.
Verification
npm run check: typecheck + 62 tests + build.npm run verify:realdata: 1,369 sessions, both log formats, folded totals match an independent reconstruction with a zero-token delta.npm run smoke:local: 8/8 HTTP assertions, including session ranking + drill-down.