dsh-header-widgets
August 19, 2026 · View on GitHub
Header widgets for the DSH (DeepSeek Harness) web GUI: a peak-pricing light and a live DeepSeek API balance badge.

Unofficial third-party plugin — not affiliated with or endorsed by DeepSeek.
Features
- Peak-pricing light — a small status dot in the conversation header that shows the current DeepSeek billing tier. Pure client-side: it computes the tier from the Beijing-time clock (peak windows 09:00–12:00 and 14:00–18:00 daily, half-open intervals
[start, end); peak rate 2.0×, off-peak rate 0.5×; there is no "shoulder" tier, and weekdays vs. holidays make no difference). The window table can be overridden at runtime via thedsh-peak.tablelocalStorage key. - Live balance badge — a compact badge showing your DeepSeek API balance. It fetches once on mount, then refreshes on a wall-clock-aligned 60 s interval (aligned to minute boundaries), plus on
visibilitychange/focuswake-ups. Fetches are single-flight and stale responses are dropped via a request sequence counter. Hovering shows the granted/topped-up breakdown; the badge opens the top-up page (https://platform.deepseek.com/top_up) in a new tab on ok/warn, and clicking an error badge retries. - Bilingual — zh/en dictionaries for both widgets, following the DSH locale system.
- No build step, zero runtime dependencies, and the API key never leaves the server.
Installation
Prerequisites
- DSH ≥
0.1.0-rc(requires thewebServerservice and the web client modules, see Compatibility) - A web profile (
dsh --profile web)
Steps
-
Get the plugin onto disk, either way:
Option A — npm (once published): install it into your web profile, where DSH's module loader resolves plugin names (
cordis.patch.ymlregisters the plugin by package name):npm install --prefix "$DSH_HOME/profiles/web" dsh-header-widgets$DSH_HOMEdefaults to~/.dshwhen unset; replacewebwith your profile name. (Installing globally withnpm i -galone is not enough — the globalnode_modulesis not on the profile's resolution path.)Option B — git clone / symlink: clone (or symlink) this repository into the plugins directory:
git clone https://github.com/Beijongggg/dsh-header-widgets.git \ "$DSH_HOME/plugins/dsh-header-widgets"$DSH_HOMEdefaults to~/.dshwhen unset. -
Register the plugin in
cordis.patch.yml— either$DSH_HOME/cordis.patch.yml(all profiles) or<profile>/cordis.patch.yml(single profile):- insert: - id: widgets name: 'dsh-header-widgets'(The exact snippet ships in
examples/cordis.patch.yml.) -
Restart the web profile:
dsh --profile web -
Verify the plugin (and the balance route) are wired up:
dsh --profile web --dump-config
Configuration
The balance widget needs a DeepSeek API key. It is resolved lazily on every request by the server-side route, so you can change the key without restarting DSH.
| Priority | Source | Notes |
|---|---|---|
| 1 | DSH credentials service — ctx.get("credentials").resolve("DEEPSEEK_API_KEY") | Preferred when the credentials service is active |
| 2 | Environment variable DEEPSEEK_API_KEY | Plain string |
| 3 | $DSH_HOME/.credentials.yaml (default ~/.dsh/.credentials.yaml) | Flat line DEEPSEEK_API_KEY: sk-...; no quotes needed, no YAML dependency |
The optional dsh-peak.table localStorage key overrides the peak-window table at runtime (values are validated; on any invalid entry the built-in table is used):
// Override the peak windows, e.g. single peak window 10:00–11:00 at 3.0×
localStorage.setItem("dsh-peak.table", JSON.stringify([
{ start: 10 * 60, end: 11 * 60, rate: 3 }
]));
Usage
Peak-pricing light
- Windows (Beijing time, half-open
[start, end)):09:00–12:00and14:00–18:00, rate 2.0×; everything else is off-peak at 0.5×. - Weekdays and holidays make no difference; there is no shoulder tier.
- Policy source: https://api-docs.deepseek.com/zh-cn/quick_start/pricing (policy verified as of 2026-08-17).
- The light re-evaluates on a minute-aligned timer and on visibility/focus changes, so it flips automatically at window boundaries.
- Accessible: exposes an
aria-labellikeCurrent billing period: Peak (2.0x).
Balance badge
- On mount it fetches once; afterwards it refreshes on a wall-clock-aligned 60 s interval plus
visibilitychange/focuswake-ups. Requests are single-flight and stale responses are dropped. - Hover shows the full title: total balance with the granted / topped-up breakdown (plus an "unavailable" note when
is_availableis false). - Click behavior: ok/warn → open the top-up page in a new tab (
https://platform.deepseek.com/top_up,noopener); error → retry immediately; loading shows—. - Accessible: exposes an
aria-labellikeDeepSeek API balance: ¥123.45.
Security design
- The API key is resolved only on the server; the browser only ever sees same-origin JSON from the proxy route.
- The proxy route enforces GET-only (non-GET →
405withAllow: GET). - Upstream calls use an 8 s timeout (
AbortSignal.timeout); network failures and timeouts map to502, upstream401/403to502with codeUPSTREAM_401. - Only successful results are cached (30 s TTL); failures are never cached, so a recovered key/network is picked up on the next request. Concurrent requests are deduplicated (single-flight).
- Errors are sanitized before being sent to the client: newlines are folded and messages truncated to 300 characters.
- Nothing is ever logged with the key or the
Authorizationheader — the module logs nothing at all. - Note: the balance route has no separate authentication beyond the DSH web session. Do not expose your DSH web instance naked to the public internet.
Architecture
┌───────────────────────── DSH host (Node.js ≥ 18) ─────────────────────────┐
│ │
│ cordis plugin "dsh-header-widgets" (name = "widgets", inject webServer) │
│ apply(ctx) │
│ └─ ctx.effect(...) │
│ └─ ws.register({ kind: "exact", │
│ path: "/plugins/dsh-header-widgets/balance", │
│ handler: createBalanceHandler({ ctx }) }) │
│ │
│ createBalanceHandler({ ctx, resolveKey, fetchImpl, upstreamUrl, now }) │
│ · resolveKey: credentials service → env → ~/.dsh/.credentials.yaml │
│ · GET https://api.deepseek.com/user/balance (8 s AbortSignal.timeout) │
│ · 30 s success cache + single-flight dedupe, failures never cached │
│ · node: builtins only (fs/os/path) — zero new dependencies │
│ │
└───────────────────────────────────────────────────────────────────────────┘
▲
│ same-origin JSON (GET only)
│ /plugins/dsh-header-widgets/balance
▼
┌─────────────────────────── Browser (DSH web GUI) ─────────────────────────┐
│ │
│ client.js — hand-written window.__ModuleLoader__.load bundle (no build) │
│ slots.inject("conversation.session.header.actions") │
│ · "peak-indicator" order 80 (locale dict "peak") │
│ · "balance-indicator" order 81 (locale dict "balance") │
│ zh/en dictionaries · minute-aligned refresh · localStorage overrides │
│ │
└───────────────────────────────────────────────────────────────────────────┘
The host side is a tiny cordis plugin: apply() registers the exact balance route through ctx.effect, delegating to createBalanceHandler, a pure, dependency-injectable function (node builtins only). The client side is a hand-written module in the window.__ModuleLoader__.load format served verbatim by the host — deliberately no build step.
Compatibility
- DSH ≥
0.1.0-rc, web profile — requires thewebServerservice and the client modules@deepseek-ai/dsh-client-locale,@deepseek-ai/dsh-client-runtime,@deepseek-ai/dsh-client-ui-layout,@deepseek-ai/dsh-client-ui-sidebar,@deepseek-ai/dsh-client-ui-conversation. - Node.js ≥ 18 (
AbortSignal.timeout). - Browsers: modern evergreen browsers (ES2019+).
- No build step, zero runtime dependencies.
Development / Testing
-
Local development: clone the repo, add the patch insert (see Installation), restart
dsh --profile web, and confirm wiring withdsh --profile web --dump-config. -
Testing the balance handler:
createBalanceHandlertakes injectable seams —fetchImpl,upstreamUrl,now, andresolveKey— so you can feed mocks without a live DeepSeek key or network:import { createBalanceHandler } from "./lib/balance.js"; const handler = createBalanceHandler({ resolveKey: async () => "sk-test", fetchImpl: async () => new Response(JSON.stringify({ balance_infos: [] })), now: () => Date.now() }); -
Contributors: the hand-written
__ModuleLoader__.loadformat is a deliberate design decision — please do not introduce a build step.
License
MIT © 2026 Beijongggg — see LICENSE.
Third-party notices: this plugin is an independent, unofficial project. "DeepSeek" and related marks are property of their respective owners; this plugin is not affiliated with or endorsed by DeepSeek.