Usage & Billing

dsh-quota-panel

brittanistrehlowll-oss/dsh-quota-panel · ★ 3

brittanistrehlowll-oss

Provider quota/balance corner panel for the dsh web surface (DeepSeek Harness plugin): server-side credential

License
MIT
Added
2026-08-23
Maintenance
active
Install kind
github

No manifest check is available; listing does not imply compatibility or security review.

安装

dsh plugin --profile web add github:brittanistrehlowll-oss/dsh-quota-panel

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. Interface
  2. Features
  3. When a provider cannot be refreshed
  4. Install
  5. Web (dsh web)
  6. Desktop (Electron DeepSeek Harness.exe)
  7. Configuration
  8. DeepSeek balance levels
  9. OpenCode usage states
  10. Command Code cost states
  11. Position and dragging
  12. Security
  13. Changelog
  14. Local development
  15. License

dsh-quota-panel

简体中文 · English

version license DSH plugin platform dependencies providers stars

Provider quota and balance status capsule for the dsh web surface (DeepSeek Harness). One glanceable line in the bottom-left corner tells you how much money and how much quota is left; click it for the full card, click again to fold it back into one line.

A zero-dependency host plugin: for every configured provider it registers one server-side proxy route /api/quota/<id> — the API key is resolved through the credentials seam and never reaches the browser — then injects this native widget into the page. The same implementation serves dsh web and the official Electron Desktop app, with no dependency on any other plugin.

Interface

Collapsed: one independent "status dot + value" pair per account (e.g. ● ¥58 · ● 86% · ● 23%), no text labels — only the affected account's own dot changes color. Money is rounded for the glance; the exact reading is in the expanded card and the tooltip.

Light Dark
Collapsed (light) Collapsed (dark)

Expanded: balances and usage stacked per account, with an individual five-hour / weekly / monthly progress bar and reset time; CommandCode adds period spend and remaining credits. The refresh and lock buttons sit in the top-right corner.

Light Dark
Expanded (light) Expanded (dark)

Default position in a page (bottom-left, keeping clear space above the sidebar's settings row — never over the content and never over a host control):

Light Dark
In page (light) In page (dark)

Interactive offline examples: light / dark — self-contained pages written by npm run demo; open them in a browser. The data is a local mock and never touches a real account. The pictures above are re-captured from that same injected script by npm run showcase; docs/verification/ holds regression artifacts.

Features

  • Two sizes, one component — collapsed 128×34px (about 3.76:1); expanded 168px wide (--dsh-quota-width) with content-driven height (about 382px for the three demo accounts), rounded ends, no shadow, growing upward from the bottom edge.
  • Independent per account — one account going critical only repaints its own dot.
  • "The endpoint failed" is not "the quota is dangerous" — they do not share a color (see the table below).
  • No invented readings — a missing or non-finite number is an explicit data error and is never folded to 0%: "I could not read the number" must not render as "plenty left".
  • Follows the product theme — driven entirely by the Harness design tokens (--dsw-alias-*, --dsw-static-*, --dsw-font-*) with sensible fallbacks, so it carries no palette of its own.
  • Draggable, remembered, lockable — the position persists per browser, is re-clamped on resize, and stops moving once locked.
  • One registration, two carriers — the host half registers on the connection exact Fetch registry, which the Web server and the Desktop host both dispatch into.
  • Zero dependencies, zero browser-side secrets — no npm dependency at all; the browser only talks to /api/quota/<id> while the credential stays host-side.
  • Keyboard and accessibility — Enter/Space toggles, Tab reaches the lock, Space toggles it, Escape collapses and restores focus; the DOM is built with createElement/textContent only.

When a provider cannot be refreshed

situation dot value tooltip
healthy filled green/amber/red as reported the reading
refresh failed, we have an earlier reading hollow ring last known value, muted grey 数据更新于 15:32 + the failure reason
never read since load neutral grey — the failure reason

So a momentary network failure no longer repaints a healthy ¥58.36 as a red —. The default refresh interval is 5 seconds and pauses while the page is hidden; the expanded state has a manual refresh button that preserves the open state, and re-entrant requests are merged.

Install

Web (dsh web)

dsh plugin --profile web add "github:brittanistrehlowll-oss/dsh-quota-panel"
# restart `dsh web` (bundle layers apply at boot)

The package declares dsh.bundle.patch, so dsh plugin add activates it as a profile layer automatically.

Desktop (Electron DeepSeek Harness.exe)

The package path above does not work on the Desktop app: dsh-desktop-host ships config/desktop.cordis.patch.yml, which disables the web-startup, webserver and web-runtime rows outright (the renderer runs on dsh-app:// over the IPC pipe, with no listening port). A plugin that injects webServer therefore waits forever and never loads.

Since v0.8.0 the host half is carrier-neutral, so the Desktop install is a profile-local relative-path row:

# 1) link the source into the desktop profile (junction → edit the repo, restart to apply)
New-Item -ItemType Junction `
  -Path "$env:USERPROFILE\.dsh\profiles\desktop\plugins\quota-panel" `
  -Target "D:\deepseek\dsh-quota-panel"

# 2) add the insert row to $DSH_HOME\profiles\desktop\cordis.patch.yml
#    (name is relative to the profile; inject keeps only credentials)
#    - insert:
#        - id: quota-panel
#          name: './plugins/quota-panel/lib/index.js'
#          inject: [credentials]
#          config: { refreshMs: 60000, providers: [ … ] }

Then quit and reopen the app — a window reload is not enough: the Desktop host reads the profile layer once at boot (there is no CLI live-patch watcher). npm run verify:desktop <profileDir> boots that composition with the real runDesktopHost, no Electron required, and asserts both the injection and the live readings from all three routes (most recently on DSH 0.1.5-rc.2, see docs/verification/desktop-carrier-20260914.log).

Why it works: /api/* rides the connection exact Fetch registry — the Web server mounts the registry under its HTTP /api prefix, and the Desktop host dispatches its piped /api/* requests into the same registry. The page script enters index.html as a structured webserver/index-inject row, an event both carriers emit. One registration serves both; keys stay host-side.

Configuration

Each provider is one entry under providers. Three renderers ship:

format endpoint shape row
deepseek-balance { "balance_infos": [{ "currency", "total_balance" }] } balance ¥58.36
opencode-usage { "usage": { "rolling"|"weekly"|"monthly": { "percent", "resetsAt" } } } highest usage 86%
command-cost API base; the host merges identity, credits and usage period spend $16.24

Override the shipped defaults in your profile's cordis.patch.yml:

- id: quota-panel
  config:
    refreshMs: 30000
    providers:
      - id: deepseek
        label: DeepSeek
        credential: DEEPSEEK_API_KEY
        endpoint: https://api.deepseek.com/user/balance
        format: deepseek-balance
        balanceTiers: { critical: 10, warn: 20, healthy: 50 }
      - id: opencode-go
        label: OpenCode Go
        credential: OPENCODE_GO_API_KEY
        endpoint: https://opencode.ai/zen/go/v1/usage
        format: opencode-usage
        windowLabels: { rolling: 五, weekly: 周, monthly: 月 }
        warnPercent: 70
        errorPercent: 90
      - id: command
        label: Command Code
        credential: COMMAND_API_KEY
        endpoint: https://api.commandcode.ai
        format: command-cost
        windowLabels: { fiveHour: 五, weekly: 周 }
        warnPercent: 70
        errorPercent: 90
        currency: USD

Fields:

field meaning default
id route id (/api/quota/<id>), ^[a-z0-9-]+$ required
label provider name on the card required
credential credential reference ($DSH_HOME/.credentials.yaml or env) required
endpoint quota JSON endpoint (API base for command-cost), GET with Authorization: Bearer <key> required
format row renderer deepseek-balance
balanceTiers (deepseek-balance) {critical, warn, healthy} levels {10, 20, 50}
lowBalance legacy alias for balanceTiers.warn —
windowLabels (opencode-usage) {rolling, weekly, monthly}; (command-cost) {fiveHour, weekly} UI language
warnPercent / errorPercent (opencode-usage, command-cost) thresholds 70 / 90
currency (command-cost) money currency, 3-letter ISO code USD
refreshMs auto-refresh interval, 5000 <= refreshMs <= 86400000 5000

Validation is strict on purpose: a present-but-invalid number (NaN, Infinity, a string) is an error rather than a silent fallback to the default, warnPercent < errorPercent must hold inside 0..100, balanceTiers must be ordered, and endpoint must be https — the proxy attaches your API key as a Bearer token, so plain http is accepted for loopback hosts only (localhost / 127.0.0.1 / ::1).

DeepSeek balance levels

With the default balanceTiers {critical: 10, warn: 20, healthy: 50}:

balance state secondary line
<= 10 error (red dot + value) 建议充值
10 < x <= 20 warn (amber) 余额紧张
20 < x <= 50 ok 余额正常
> 50 ok 余额充足

OpenCode usage states

high = max(rolling, weekly, monthly):

usage state
< warnPercent ok (green dot, DeepSeek-blue progress)
>= warnPercent warn (amber dot + progress)
>= errorPercent error (red dot + progress)

A window whose percent is missing or not a finite number makes the row an explicit data error — it is never folded to 0%.

Command Code cost states

high = max(period spend %, five-hour %, weekly %):

reading state
< warnPercent ok (green)
>= warnPercent warn (amber)
>= errorPercent error (red)

The monthly ratio comes from totalMonthlyCredits in /alpha/usage/summary and credits.monthlyCredits in /alpha/billing/credits, computed as used / (used + remaining) when periodBasis=billing-period. Purchased and free credits are excluded from the ratio, and a missing field reads as —. The five-hour and weekly ratios come from windowLimits.used/cap. The period allowance is not reported by the API; it is reconstructed as spend + remaining credits — on a GOAT plan that reads as $16.24 + $53.77 = $70.01. A reset date the API does not return is never invented.

Position and dragging

Both states can be dragged when unlocked:

  • Drag it anywhere — the panel leaves the default bottom-left position and follows the pointer, clamped so the whole capsule always stays on screen.
  • The default position sits in the bottom-left with clearance above the sidebar's settings row: it first looks for the host's [data-slot="settings.trigger"], which measures 0×0 on some host builds, and in that case walks up from the visible 设置/Settings label to the first ancestor that does have a box. Fonts and the capsule's real height only land after the first layout, so the default is re-anchored across the next two frames; a capsule with a saved position is never moved.
  • The position is remembered per browser (localStorage, key dsh.quota.pos) and restored on the next load; resizing the window re-clamps it into view.
  • Click toggles the capsule: a press that travels less than 4px is a click, anything further is a drag (the click that ends a drag is swallowed).
  • The lock appears only when expanded; it disables dragging, not toggling, and persists under dsh.quota.locked.
  • Right-click while unlocked restores the default position (24px left, and the settings-row clearance described above).
  • Optional mountCompact moves the whole shell; manually saved positions take precedence. An unlocked hosted capsule can be dragged out.

Security

  • API keys are resolved server-side via ctx.credentials and only used in the server-to-provider request; the browser only talks to /api/quota/<id>.
  • Every route answers a normalized envelope — { ok: true, data } or { ok: false, error: { code, status, message } } — with code one of credentials / upstream / timeout / network / invalid-body. An upstream HTML error page is turned into invalid-body host-side, so the page script never parses a non-JSON body.
  • The route is the trust boundary: it never echoes the credential, and verification asserts the key is absent from the response.
  • endpoint must be https (loopback http excepted), since the proxy attaches the key as a bearer token.
  • The injected card builds DOM with createElement/textContent only; API response values never pass through innerHTML. Refresh failures are reported in title hover text (and as a hollow status dot), not as card body copy.

Changelog

  • v0.8.0-p1 — Two defect fixes plus a decoupling cleanup. (1) The version marker no longer drifts: #dsh-quota-panel hardcoded data-version = '0.7.0' and was never updated for the 0.8.0 release, so the page advertised 0.7.0 while package.json said 0.8.0 — anything gating on that marker (an acceptance harness, a host, a support screenshot) was told the wrong version. It is now read from package.json. (2) The initial position lands where it was designed to: the author's "clearance above the settings row" rule had a selector that resolves to a zero-size wrapper on DSH 0.1.7-rc.1 (measured 0×0), so footerRect.width > 0 was never true and the rule never ran, leaving the capsule in the innerHeight - 92 fallback corner. It now walks up from the visible 设置/Settings label to the first ancestor with a real box, computes the clearance from the panel constant instead of a not-yet-laid-out capsuleEl.offsetHeight, and re-anchors across two frames after the first paint. Measured at 1483×707: y 615 → 619, gap above the settings row 22px (incidental) → 18px (by design), and no overlap with a host control before or after. (3) Decoupling cleanup: dropped the [aria-label="鲸息入口"] probe, rewrote the module header and the seam comments in neutral terms, removed a foreign product name from the package.json description, and replaced the always-true if (setExpanded) guard in openDetail with a direct call. A scan of the shipped package contents (lib/index.js and the rest of the files whitelist) for 鲸息|Whale|watchdog|dsh-lifecycle|restart-button|dsh-controller returns zero hits, and that assertion now lives in verification/verify-packed-tarball.mjs; this README and the historical acceptance records under docs/verification/ are outside that scan (the former has to name the defect it fixed, the latter are point-in-time snapshots) and are left as they are. The DSH_PLUGIN_INTEGRATION_V1 seam itself is kept on purpose — it is the only stable interface for a host page that wants to embed the compact capsule, and it is silent when the runtime is absent.
  • v0.8.0 — Carrier-neutral host half: routes register on the connection exact Fetch registry (the shared /api table the Web server and the Desktop host both dispatch into), and the page script ships as a structured webserver/index-inject row — no webServer dependency left. Hosts without that registry still fall back to webServer.register + tapIndex. The plugin now works on the official Electron Desktop app (no HTTP server, dsh-app:// + IPC). Adds npm run verify:desktop.
  • v0.7.0 — Compact capsule with full period details: one 128×34px line collapsed, 168px wide vertical details expanded; period progress and reset times restored; the initial position follows the asynchronously loading sidebar and avoids the bottom entry.
  • v0.6.0 — 176px compact two-state capsule with 28/56px heights; independent bottom-left position, dragging and a persistent lock button; the separate card and toolbar were removed. A valid critical reading is no longer overwritten by an older one, an anomalous number is refused instead of becoming 0, and upstream error bodies are no longer echoed. Adds real mouse/keyboard, two-state mutual-exclusion and text-geometry checks. The CSS ships with the package.
  • v0.5.0 — Command Code (command-cost) row: billing-period spend, request / token counts, remaining credits and the five-hour + weekly windows. One horizontal size and one 16px radius for both states (no edge jump). Failed refreshes keep the last good reading as stale instead of turning red. Normalized { ok, data | error } route envelope; money formatted from the payload currency via Intl.NumberFormat; Esc collapses; aria-controls and a focus ring; strict config validation; shared Chromium resolution in scripts; CI on every push. Fixes an i18n bug where {h}/{n} placeholders were never substituted (captions rendered as 当前最高占用 {h}%).
  • v0.4.0 — Free positioning: the capsule is a drag handle (pointer events, touch-friendly), position persisted per browser and restored on load, viewport clamping, card flip/anchor when placed, right-click reset.
  • v0.3.0 — Two sizes: collapsed capsule (independent per-account dot + battery-colored value) expands into the full card.
  • v0.2.0 — Harness-native card: design tokens, balance tiers, progress bar.
  • v0.1.0 — Initial floating panel: server-side quota proxies + page badge.

Local development

npm test              # model, configuration, emitted-script and security tests
npm run demo          # regenerate docs/demo.html + docs/demo-dark.html
npm run verify        # headless-browser DOM/geometry/stale assertions
npm run verify:dark   # same in the dark theme
npm run verify:drag   # synthesize real drags; assert persistence/clamping
npm run verify:route  # offline host-route contract (no real credentials)
npm run verify:desktop -- <profileDir>  # real runDesktopHost composition probe (no Electron)
npm run verify:package # pack, then load the plugin FROM the tarball
npm run screenshot    # verify both themes, write docs/verification/*.png
npm run showcase      # regenerate the project-page art in assets/ and docs/images/
npm run pack          # write dist/dsh-quota-panel-<version>.tgz
npm run check         # test + demo + verify

verify:package is the release gate: it packs, extracts, and imports the plugin from the extracted copy, asserting that files ships everything the plugin reads at runtime (notably lib/plugin-integration-v1.js, which is read from disk at import time — a working-tree test would never catch it missing).

verify:route runs offline and never reads real credentials by default. An explicit --live opts in to the live check. Browser verification covers both themes, real lock/unlock and drag input, keyboard navigation and layout; results are in docs/verification/.

showcase is an authoring tool, not a gate: it captures the pictures from the script actually injected into docs/demo.html, so the README cannot drift from the implementation. The hero banner is rendered with the local desktop font stack, so run it on a machine that has one.

The browser scripts find Chrome, Chromium or Edge automatically; override with CHROME_PATH / CHROME_BIN.

License

MIT