guanhaisen/dsh-token-dashboard ↗★ 0
dsh-token-dashboard
Dashboard for daily token usage in DeepSeek Harness: records every model call from the moment it mounts, aggregates by day, model, purpose and project, and shows it at the sidebar foot 适合需要通过热力图和柱状图直观分析每日Token消耗趋势的用户。
같은 패키지 이름의 다른 저장소
설치
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:guanhaisen/dsh-token-dashboardThe usage calendar
Below the bar chart is a calendar heatmap: one square per day, one column per week, Monday to Sunday, shaded by that day's total. It answers a different question from the chart above it — when was I busy, rather than how much in the range I picked — so it deliberately ignores the range control and always shows the trailing year ending today. The card says so in its own sub-line, and the number of days that actually have records is printed beside the legend. A range-driven calendar would be a single square under "Today" and a year under "All", which is why it is pinned.
- Shades are the window's own quartiles, not fixed token rates. A fixed scale would paint every day of a light user at the palest shade and every day of a heavy one at the darkest, which says nothing about which days differed. Days with zero tokens are excluded from the quartiles and keep their own level (the empty cell), so an idle day cannot drag the low boundary to zero and flatten every real day into the darkest shade.
- A flat week reads as typical, not as maximal. When every recorded day in the window has the same total, the quartiles collapse; rather than painting all of them at level 4, they land on the middle shade.
- The square is the same total the bar chart draws — cache reads included — so the two charts cannot disagree about what a day cost in tokens.
- Every day in the window is drawn, recorded or not. Gaps are information; skipping absent days would compress the year and misstate them.
- Hovering a square shows the full date, its weekday, the exact total and the request count. The hovered square keeps its own shade and gains an outline, so the shade being explained is never hidden by the highlight.
- The window ends on the day the Host calls today, cut at the same configurable offset as the rest of the ledger — not at UTC, which would drop today's records for the first eight hours of a UTC+8 day.
The grid is sized by the pane, not by the window: the cell stays 11–13px while there is room and the gap shrinks first, so a narrow main pane still shows a year of squares rather than dissolving into hairlines.
Beside the grid is a rail of four readings derived from the same series, so it can never disagree with the squares it annotates: the twelve-month total, the per-active-day average, the busiest day, and the current streak. They exist because a nearly empty year of squares is a poor answer on its own — five busy days look like an empty page. The rail drops below the grid on a narrow pane and is omitted entirely when there is nothing in the window.
Other figures:
- Cache hit rate =
cacheReadTokens / (inputTokens + cacheReadTokens). When the denominator is 0 the dashboard shows—, not0%: "nothing was sent" and "nothing was cached" are different facts. - Average first token is the mean over calls that actually produced a first token. A call that failed before emitting anything contributes no sample rather than a 0 ms one.
- A call that reported no usage is still counted as a request (and as an error if it failed), but contributes no tokens — so a failing route cannot quietly dilute your averages toward zero.
- The recent-calls table lists exactly the day range you selected. Nothing outside the range is shown, so the table can never disagree with the cards and the chart above it — including when the range is empty, which shows an empty table rather than the last thing that happened to be recorded.
- The chart buckets by week once a range is long enough that one bar per day would be a hairline. The bucket's label is its first day. Only days that have records are drawn, so the axis is a sequence of recorded days rather than a strictly linear calendar.
Configuration
Edit the row in cordis.patch.yml (or override it from your profile's
cordis.patch.yml):
| Field | Default | Meaning |
|---|---|---|
retentionDays | 730 | Days of daily aggregation kept; older days are dropped whole |
recentLimit | 300 | Detail records kept, newest first |
utcOffsetMinutes | 480 | Fixed day-boundary offset; 480 = UTC+8 |
trackProjects | true | Record the cwd dimension |
flushDebounceMs | 2000 | Debounce before a dirty ledger reaches disk |
A value that cannot be used is rejected, not clamped. Each numeric field must
be a finite number in a sane range (retentionDays 1–36500, recentLimit
0–10000, utcOffsetMinutes ±720/840, flushDebounceMs 0–60000) and
trackProjects must be a real boolean; anything else falls back to the default
and logs one warning naming the key. This matters more than it sounds: a
retentionDays of null or a typo like "730" used to be coerced to 0, which
made the retention cutoff tomorrow and deleted every recorded day on the next
flush. A configuration typo must not be able to destroy your history, so an
unusable value is ignored rather than reinterpreted.
The day boundary also drives what the page says: the dashboard's footnote names
the offset actually in use (UTC+8, UTC±0, UTC+5:30, …) rather than assuming
the default.