LinHyu-2006/dsh-balance-hud ↗★ 0
dsh-balance-hud
在Web界面右下角显示余额与会话计费HUD。 适合在Web端使用并需要常驻预算警报和费用提示的用户。
同名套件的其他儲存庫
安裝
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:LinHyu-2006/dsh-balance-hud說明文件
閱讀完整 README ↗English | 简体中文
dsh-balance-hud
An always-on HUD in the bottom-right corner of the DSH (DeepSeek Harness) Web GUI that shows your DeepSeek account balance and the cost of the current session.
简体中文说明 · Design notes · 设计说明 · LICENSE

About these images: they are not photographs of a live DSH window. They are renders of the plugin's own layout and colours, driven by its real style values and real figures — a capture tool for the running GUI was not available when they were made. Treat them as a faithful layout reference, not as evidence of a running install. Pull requests replacing them with genuine screenshots are welcome.
- Account balance — recharge balance + bonus balance, read through the same service the
Settings → Account & balance page uses (
ctx.deepseekAccount.getBalance()). - Two cost figures — a token-based estimate from the official price table, and an actual figure accumulated from real balance decreases (external reductions excluded).
- External balance changes, both ways — a top-up raises the balance, and a lapsed bonus grant is reclaimed by the platform; money moving in or out externally never touches the spending counter.
- Per session — the tracker is keyed by session id; refreshing the page or switching sessions never damages or clobbers another session's record, because the read → modify → write round trip is lossless.
- No credentials, and no outbound network unless you opt in — the plugin never touches your API key. It reaches the network only after you turn price syncing on (see Prices and syncing); until then it makes zero external requests.
- Two halves, one relay — a browser half renders the HUD; a Host half exposes read-only loopback HTTP routes (balance, bonus signal, price sync, model list), so the browser itself never talks to anything but loopback.
- Configurable, and budgeted — a preference row in Settings → General sets the model, the poll intervals, the budgets and the warnings; the card gains a coloured edge as spending approaches a budget, and the panel counts down to the next peak/off-peak switch.
node tools/install.mjs # install into ~/.dsh/profiles/desktop, then restart DSH
node tools/smoke.mjs # offline smoke suite (83 assertions), no DSH install needed
The HUD
┌──────────────────────────────┐
│ Balance ¥43.42 │
│ Session est. ¥0.59 │
└──────────────────────────────┘
Hovering the card expands a detail panel: balance rows (recharge / bonus / total), the three priced
components of the estimate, the actual figure, token counts, and — with parallel sessions — the
apportionment rows (Session share, Other sessions, Tracked sessions, Account total). The
panel uses 4 decimals; the card uses 2. The shipped labels are currently Chinese; the English names
here are what each row means.
| Field | Meaning |
|---|---|
| Balance | Total account balance (recharge + bonus), 2 decimals |
| Session est. | Estimate for the current session; 2 decimals on the card, 4 in the panel; — when the session has no token usage yet |
| Actual charged | Accumulated real spending for this session (single session; becomes Session share when parallel) |
Amounts below the smallest displayed unit render as ` total token estimate + 0.02 → externally added money (topup) top-up, bonus grant, billing correction balance drop > total token estimate + 0.02 → externally removed money (expire) bonus expiry, refund reversal, billing correction everything else → ordinary spending, accumulate as usual
The platform can never have charged more than the tokens consumed are worth, so a bigger rise must
have come from outside. Symmetrically, it cannot charge for tokens that were not consumed: a drop
larger than the worth of every token spent in the window cannot have come from consumption either.
The *same* bound therefore catches a bonus expiry as well, survives a "top-up and spending at the
same time" event, and cannot be triggered by normal spending noise.
**Why the reduction direction exists: bonus expiry.** A bonus grant that lapses is reclaimed by the
platform, so the balance drops with no token consumption behind it. The previous implementation
classified *every* decrease as spending (`if (delta < 0) return { kind: 'spend', amount: -delta }`),
so a ¥5 bonus expiring was recorded as ¥5 of session spending — and because the counter only grows,
the error stayed in the record until the entry was pruned.
Both directions take the **same code path** (`applyExternalChange`): only the account-level bookmark
moves — every session's `baseline` and `lastBalance` — while `spent` is left untouched, and the
event is appended to the `topups[]` history carrying its `kind` (`'topup'` or `'expire'`). Because
the balance is account-wide, one event re-anchors **every** session. The function deliberately never
touches `lastRead` — "the balance this session actually observed last" is session-private — so a
session that has not yet observed the change still sees the full drop on its next read and
classifies it as an external change instead of charging it to its own spending. Each session
self-heals, and the event is not double-counted because the session that triggered it already
advanced its own `lastRead`.
Three layers, in order of authority:
1. **The token-bound rule** — pure client side, always active: the authority in both directions.
2. **Official bonus-grant confirmation** — `GET /api/balance-hud/bonuses` reads
`ctx.deepseekAccount.getUnnotifiedBonuses()`. It is **read-only and deliberately does not call
`ackBonusNotified`**, so it never consumes DSH's own bonus notifications; the data stays
readable and the client de-duplicates by `orderId`. This layer corroborates **grants** only:
there is **no official "expired" feed**, which is exactly why the token bound has to be the
authority in the reduction direction. If the route is unreachable a top-up is still detected —
this layer is only corroboration. `normalizeBonuses` also passes `expiresAt` through (it is
**not** what the accounting decision uses) so the notice can name the earliest lapse date.
3. **A manual fallback** — the *I just topped up* button takes the same re-anchor path and shows a
"querying…" state while it re-anchors and queries the bonuses route.
**Notices.** An external change in either direction shows a one-line notice in the panel for about
12 seconds:
- a top-up: `已识别充值 ¥50.00,花费继续累计(官方赠金 ¥5.00 印证),最早到期 2025-12-31。` —
"recognised a ¥50.00 top-up; spending keeps accumulating (corroborated by an official ¥5.00
bonus); earliest expiry 2025-12-31".