sandbase-harness
sandbaseai
Local-first, self-hosted AI agent runtime and MCP bridge with sandboxed sessions, memory, credentials, audit/replay, and a local Console.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:wenzetan/dsh-quota-panel
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
English | 中文
dsh-quota-panel is a provider quota / balance status widget for the
DeepSeek Harness (DSH) web surface (dsh web). It sits in the
bottom-right corner of the product UI, watches every AI provider whose API
key you have configured, and tells you at a glance how much balance /
quota is left — DeepSeek, OpenRouter, SiliconFlow, Moonshot, StepFun,
xAI, Zhipu GLM, OpenCode Go, Volcengine Ark (Agent/Coding Plan), plus one-api / new-api style aggregators,
and the coding plans (智谱 GLM Coding, Z.AI, Kimi Coding, MiniMax
Coding global/CN) with 5-hour / weekly usage windows and MCP monthly quota.
xAI, Zhipu GLM, OpenCode Go, ChatGPT subscription (Plus/Pro via Codex
login), plus one-api / new-api style aggregators, and the coding
plans (智谱 GLM Coding, Z.AI, Kimi Coding, MiniMax Coding global/CN)
with 5-hour / weekly usage windows and MCP monthly quota.
Since v0.5 it is a dual-face plugin with a built-in provider catalog
and auto discovery: install it, restart dsh web, and every provider
whose key resolves automatically appears on the panel — zero configuration.
It needs no npm dependencies and asks for no allowBuilds authorization.
Collapsed capsule, light theme:

Expanded card, light theme:

Settings panel (⚙), light theme:

Collapsed capsule, dark theme:

Expanded card, dark theme:

Settings panel (⚙), dark theme:

$DSH_HOME/.credentials.yaml / .env / environment variables)
appears on the panel automatically, with zero config. Remove the key
and the row disappears. No credential enumeration API exists in DSH, so
the catalog is probed each refresh cycle.— instead of a fabricated 0%.● ¥58.36 · ● 45%); expanded: a
full card with a row per provider (status dot, name, primary value,
secondary info, progress bar for usage-kind providers).critical <= warn <= healthy), usage rows by percent
(error >= warn); the offending dot/value alone recolors, others stay
calm. Usage percentages use battery-style three-color grading, independent
of the status dots.openai-billing
format adapts aggregator dashboards.--dsw-alias-*, --dsw-static-*, --dsw-shadow-*,
--dsw-font-*) with sensible fallbacks, so it follows the product
theme (light/dark) and ships no palette of its own./api channel and receives only normalized views (see
"How it works").arkcli CLI, or chat-endpoint
rate-limit probes (per
CodexBar research).
Volcengine Ark / Doubao Agent Plan and Coding Plan are now supported via
the AK/SK-signed OpenAPI (see the catalog table above). Other providers on
this list cannot be wired in until an API-key endpoint appears.format value outside the built-in set fails loud at mount.shell.overlay
slot only (bottom-right corner), not in sidebars, headers, or the status
bar.Missing a provider? Open an issue with:
^[a-z0-9-]+$, e.g. together), using a
-cn suffix for the China site of a dual-site provider
(cf. siliconflow / siliconflow-cn);GET https://api.provider.com/v1/user/info, Bearer auth), plus the
response shape if you can paste it.That is all the catalog needs: an id whose standard credential reference resolves, an endpoint, and a format adapter for the response. Providers with only cookie/CLI quota pages (see above) cannot be supported until they expose an API-key endpoint.
┌─────────────── browser (lib/client.js) ───────────────┐
│ shell.overlay slot → capsule / card / settings panel │
│ localStorage: visibility · interval · thresholds · │
│ proxy URLs (frontend settings) │
└──────────────┬─────────────────────────────────────────┘
│ Connection /api channel (browser-session fenced):
│ POST /api/dsh-quota-panel/specs (render hints)
│ POST /api/dsh-quota-panel/fetch-all { proxy: {...} }
┌──────────────▼────────────── host (lib/index.js) ──────┐
│ ctx.credentials → API keys (never leave the host) │
│ catalog probe → auto discovery (15 built-in providers) │
│ per-row fetch → proxy engine (CONNECT tunnel / │
│ absolute-URI) → upstream JSON │
│ normalization → {balance | usage | info} view models │
└─────────────────────────────────────────────────────────┘
lib/index.js) mounts one exact Fetch route per endpoint on
the Connection service's authenticated /api channel, under the
dsh-quota-panel method namespace:POST /api/dsh-quota-panel/specs — the resolved rows with render hints
only (id, label, row kind, currency, threshold tiers, window labels,
configured proxy name). No credentials, no endpoints.POST /api/dsh-quota-panel/fetch-all — fetches every visible row,
normalizes each upstream response into a generic view model
(balance / usage / info), and returns
{rows: [{id, view} | {id, error}], fetchedAt}.
Raw upstream JSON stays host-side like the keys; one failing row never
affects the others.POST /api/dsh-quota-panel/chatgpt-auth-status /
chatgpt-login-start / chatgpt-login-cancel / chatgpt-logout —
the optional ChatGPT subscription device-login flow.
Requests are fenced by DSH's own /api route (trusted host + browser
session), so the endpoints are not reachable from another machine or from
a client without the page session.providers).proxiedGetJson:
https targets go through an HTTP CONNECT tunnel (TLS over the
tunnel), http targets via absolute-URI forwarding. 15 s per-row timeout,
1 MB body cap. Proxy selection precedence:
frontend settings panel > profile config > direct.specs hints, so
local threshold overrides apply without refetching; profile thresholds
ship in specs and the frontend settings override them locally.Config schema (vendored
schemastery) declares structure and defaults; cross-field constraints (id
uniqueness, critical <= warn <= healthy, proxy references, catalog
override keys) are validated host-side at mount and fail loud.createElement/textContent; API values never touch innerHTML;
technical errors (401, timeout, missing credential, refused proxy) surface
only in title tooltips or inline row text.error event into the row's result, so an
unreachable proxy (e.g. clash stopped, ECONNREFUSED 127.0.0.1:7890) shows
as a per-row error instead of an unhandled EventEmitter error.Out of the box: nothing. Install, restart, and any provider whose key resolves appears automatically. The table below is only for tuning.
All keys are optional — the structure and defaults live in the exported
Config schema, so profile patches may omit every defaulted field.
| Key | Meaning | Default |
|---|---|---|
auto |
probe the built-in catalog; providers with a resolvable key join the panel | true |
hide |
row ids to drop (catalog and explicit rows alike) | [] |
proxies |
named proxy definitions {<name>: "http://host:port"}, HTTP(S) only |
{} |
catalog |
partial overrides for auto-discovered rows {<catalog-id>: {...}} |
{} |
refreshMs |
auto-refresh interval | 60000 |
providers |
explicit rows; a same-id entry replaces the catalog row wholesale | [] |
Each catalog override may set: label / endpoint / format /
proxy / refs (credential references to probe, UPPER_SNAKE) /
secretRefs (a second credential reference — required for the Volcengine
AK/SK pair; the row is only discovered when BOTH refs and secretRefs
resolve) / region (Volcengine OpenAPI region; default cn-beijing) /
currency (balance rows: symbol like $ or US$) / balanceTiers /
warnPercent / errorPercent / windowLabels.
Explicit providers fields:
| Field | Meaning | Default |
|---|---|---|
id |
row id (RPC rows align by id), ^[a-z0-9-]+$ |
required |
label |
provider name shown on the card | required |
credential |
credential reference ($DSH_HOME/.credentials.yaml or environment) |
required |
secretCredential |
second credential reference (Volcengine volcengine-agent-usage / volcengine-coding-usage: the SK) |
— |
endpoint |
quota JSON endpoint; base URL for openai-billing |
required |
format |
row adapter (see table below) | deepseek-balance |
proxy |
a proxy name defined in proxies; absent = direct |
— |
region |
(volcengine-agent-usage / volcengine-coding-usage) Volcengine OpenAPI region |
cn-beijing |
currency |
(balance rows) currency symbol, overrides the format default | format default |
balanceTiers |
(balance rows) {critical, warn, healthy} |
{10, 20, 50} |
lowBalance |
legacy alias for balanceTiers.warn |
— |
windowLabels |
(usage-kind formats) labels for the usage windows | {滚, 周, 月} |
warnPercent / errorPercent |
(usage rows) thresholds | 70 / 90 |
| Provider | Credential refs probed | Endpoint | Row kind |
|---|---|---|---|
| DeepSeek | DEEPSEEK_API_KEY |
api.deepseek.com/user/balance |
¥ balance |
| OpenRouter | OPENROUTER_API_KEY |
openrouter.ai/api/v1/credits |
$ balance (purchased − used) |
| SiliconFlow (global) | SILICONFLOW_API_KEY |
api.siliconflow.com/v1/user/info |
$ balance |
| SiliconFlow (CN) | SILICONFLOW_CN_API_KEY |
api.siliconflow.cn/v1/user/info |
¥ balance |
| Moonshot / Kimi | MOONSHOT_API_KEY |
api.moonshot.cn/v1/users/me/balance |
¥ balance |
| MiniMax Coding (global) | MINIMAX_API_KEY |
www.minimax.io/v1/token_plan/remains |
5h prompt usage % |
| MiniMax Coding (CN) | MINIMAX_CN_API_KEY |
api.minimaxi.com/v1/token_plan/remains |
5h prompt usage % |
| StepFun | STEP_API_KEY / STEPFUN_API_KEY |
api.stepfun.com/v1/accounts |
¥ balance (hover: cash/voucher) |
| xAI | XAI_API_KEY |
api.x.ai/v1/billing/credits |
$ balance |
| Zhipu GLM | ZHIPU_API_KEY / GLM_API_KEY |
open.bigmodel.cn/api/monitor/usage/quota/limit |
text row (quota remaining/total; no public balance API) |
| 智谱 GLM Coding | ZAI_CODING_CN_API_KEY |
open.bigmodel.cn/api/monitor/usage/quota/limit |
coding-plan windows (5h tokens / weekly / searches) |
| Z.AI GLM Coding | ZAI_API_KEY |
api.z.ai/api/monitor/usage/quota/limit |
coding-plan windows (5h tokens / weekly / searches) |
| Kimi Coding | KIMI_API_KEY |
api.kimi.com/coding/v1/usages |
usage % (5h rate limit + weekly request pool) |
| OpenCode Go | OPENCODE_GO_API_KEY |
opencode.ai/zen/go/v1/usage |
three-window usage % |
| Volcengine Ark Agent Plan | VOLC_ACCESS_KEY + VOLC_SECRET_KEY |
open.volcengineapi.com (OpenAPI, signed) |
usage % (5h / weekly / monthly, GetAFPUsage) |
| Volcengine Ark Coding Plan | VOLC_ACCESS_KEY + VOLC_SECRET_KEY |
open.volcengineapi.com (OpenAPI, signed) |
usage % (session / weekly / monthly, GetCodingPlanUsage) |
Volcengine Ark has two separate subscriptions — Agent Plan and Coding Plan — shown as two independent rows (like two providers) that share the same AK/SK pair. Each row queries only its own plan's API and never falls back to the other, so a plan the account has not subscribed to shows a "not subscribed" message on that row instead of the other plan's numbers. Volcengine authenticates with an AccessKey ID / SecretAccessKey pair using HMAC-SHA256 request signing — not a Bearer token. The inference
ARK_API_KEY(shapedark-...) cannot query usage; only the AK/SK pair has OpenAPI permission. Full setup is in the next section.
Ark Agent Plan / Coding Plan usage comes from the control-plane OpenAPI, which requires an AK/SK pair with read-only access. Three steps:
1. Create an AccessKey
Open https://console.volcengine.com/iam/keymanage (Volcengine console → Identity and Access Management → Access Keys) and click New access key. Prefer creating a sub-user key dedicated to this plugin rather than using the primary account key. Save the AccessKey ID and SecretAccessKey when shown — the SecretAccessKey is displayed once.
2. Grant Ark read-only permission
Attach the ArkReadOnlyAccess policy to the sub-user (or role) that owns the
key:
ArkReadOnlyAccess;ArkReadOnlyAccess alone is sufficient — GetAFPUsage and
GetCodingPlanUsage are read-only actions; ArkFullAccess or account-level
billing permissions are not required.
3. Save the credentials
Add two lines to $DSH_HOME/.credentials.yaml (by default
C:\Users\<you>\.dsh\.credentials.yaml on Windows or ~/.dsh/.credentials.yaml
on Linux/macOS):
VOLC_ACCESS_KEY: AKLTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
VOLC_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
You can also use the VOLC_ACCESS_KEY / VOLC_SECRET_KEY environment
variables (DSH's credential resolver falls back to the environment). Restart
dsh web; the "Volcengine Agent" and "Volcengine Coding" rows appear in the
bottom-right panel automatically (whichever plan the account subscribes to
shows data; both rows share the same AK/SK pair) — no providers: block required.
Verifying permissions
After restart, check the panel:
ArkReadOnlyAccess
are both working (if only one plan is subscribed, the other row shows a
"not subscribed" message);volcengine SignatureDoesNotMatch: ... → the SK was copied wrong (watch the
trailing =);volcengine AccessDenied: ... → the policy is not attached or the wrong
source policy was selected;No active Volcengine Ark Agent/Coding Plan subscription → the signature
worked but the account has no subscription to that plan (typical for
pay-as-you-go accounts; the two rows report independently — hide the
unsubscribed one from the ⚙ settings panel).Migration (from ≤ 0.9.1, if you pinned the old row): the single catalog row id
volcengineand the format idvolcengine-usagewere replaced byvolcengine-agent(volcengine-agent-usage) andvolcengine-coding(volcengine-coding-usage). Auto-discovered setups need no changes; a hand-writtencatalog:override orproviders:entry that still references the old ids makes the plugin refuse to load with a validation error listing the valid ids — update it to the two new ids.
Security note: an AK/SK pair can read all Ark usage data for the account. Redact it before pasting into chats, tickets, or screenshots, and rotate it from the key management page when no longer needed. | ChatGPT subscription (Plus/Pro) | in-plugin login or
~/.codex/auth.json(no API key) |chatgpt.com/backend-api/wham/usage| weekly usage % (Pro includes a 5h window) |
An additional openai-billing format adapts one-api / new-api style
aggregators: set endpoint to the aggregator base URL and the host half
requests {base}/v1/dashboard/billing/subscription
(hard_limit_usd) plus {base}/v1/dashboard/billing/usage
(total_usage); remaining = limit − used ($). Aggregator domains differ
per deployment, so this format is explicit-config only.
Some providers run separate international and China sites with different
endpoints, credential references and currencies. The catalog models each
site as its own provider id, so configuring the matching key is all it
takes — and an explicit providers: entry reusing one of these ids replaces
the catalog row wholesale (same fields, your endpoint/label/currency):
| provider id | Site | Endpoint | Credential ref | Currency |
|---|---|---|---|---|
siliconflow |
SiliconFlow global | api.siliconflow.com/v1/user/info |
SILICONFLOW_API_KEY |
$ |
siliconflow-cn |
SiliconFlow China | api.siliconflow.cn/v1/user/info |
SILICONFLOW_CN_API_KEY |
¥ |
minimax |
MiniMax Coding global | www.minimax.io/v1/token_plan/remains |
MINIMAX_API_KEY |
— (usage %) |
minimax-cn |
MiniMax Coding China | api.minimaxi.com/v1/token_plan/remains |
MINIMAX_CN_API_KEY |
— (usage %) |
zai |
Z.AI GLM Coding global | api.z.ai/api/monitor/usage/quota/limit |
ZAI_API_KEY |
— (usage %) |
zai-coding-cn |
智谱 GLM Coding China | open.bigmodel.cn/api/monitor/usage/quota/limit |
ZAI_CODING_CN_API_KEY |
— (usage %) |
Both sites of one provider can be on the panel at the same time (configure
both keys); hide: ["siliconflow"] drops either row individually.
The currency symbol for balance-kind rows comes from the format by default
(siliconflow-balance renders ¥) and can be overridden per row: catalog
rows carry currency (the global SiliconFlow row sets $), a catalog:
override may set it, and explicit providers: entries accept a currency
field (e.g. "US$").
ChatGPT subscription usage is not API billing — there is no public balance/usage API. This plugin calls the same internal usage endpoint Codex uses, with a ChatGPT OAuth token, and shows the weekly window (and the 5-hour window on Pro) as a used-percentage row. Two login methods are supported, pick whichever you prefer:
⚠️ Experimental. The endpoint (
chatgpt.com/backend-api/wham/usage) is an undocumented internal API used by the Codex CLI; its response shape may change. The plugin only performs read-only queries.
dsh web and open the panel settings (gear icon) in the
bottom-right corner;https://auth.openai.com/codex/device;plan: plus/pro and weekly: N% (Pro also shows a 5h window).Tokens are stored in $DSH_HOME/dsh-quota-panel/chatgpt-auth.json
(C:\Users\<you>\.dsh\dsh-quota-panel\ on Windows, file mode 0600). When the
access token expires the plugin refreshes it with the refresh token and writes
it back. Click "Log out" in the same section to delete the local token.
If you have already logged in via the Codex CLI
(ran codex and completed the browser sign-in), the plugin automatically reads
~/.codex/auth.json (or $CODEX_HOME/auth.json) — no extra configuration
needed. With this method, refreshed tokens stay in the plugin process memory
only and are never written back to auth.json (that file is owned by the
Codex CLI).
When both are present, the in-plugin login takes precedence. When neither
exists the ChatGPT row is hidden. If the token is invalidated by a login
elsewhere, the row shows an error — run Option A again, or codex login, to
recover.
| format | Row kind | Upstream response shape |
|---|---|---|
deepseek-balance |
¥ balance | { balance_infos: [{ currency, total_balance, granted_balance, topped_up_balance }] } |
openrouter-credits |
$ balance | { data: { total_credits, total_usage } } |
siliconflow-balance |
balance (¥ by default, per-row currency override) | { data: { balance, chargeBalance, totalUsage } } |
moonshot-balance |
¥ balance | { data: { total_balance } } |
minimax-remains |
usage % | { base_resp, model_remains: [{ model_name, current_interval_total_count, current_interval_usage_count, current_interval_remaining_percent, end_time, current_weekly_total_count, current_weekly_usage_count, weekly_end_time }] } — coding model row (MiniMax-M*) preferred; counts are remaining-side (used = total − count); weekly window only when current_weekly_total_count > 0 |
stepfun-accounts |
¥ balance | { balance, total_cash_balance, total_voucher_balance } |
xai-credits |
$ balance | { total: { val } } (cents → dollars) |
openai-billing |
$ balance | aggregator dashboard/billing endpoints |
zhipu-quota |
text | { code: 200, data: { limits: [{ remaining, number }] } } (limits without remaining fall back to percentage) |
opencode-usage |
usage % | { usage: { rolling|weekly|monthly: { percent, resetsAt } } } |
zai-coding-quota |
usage % | { code: 200, data: { limits: [{ type: TOKENS_LIMIT \| TIME_LIMIT \| CREDIT_LIMIT, unit, number, percentage, currentValue, usage, remaining, nextResetTime }] } } — semantic mapping (glm-plan-usage2, issue #2): TOKENS_LIMIT unit=3 → 5h window, unit=6 → weekly, TIME_LIMIT → MCP monthly lane; unknown units fall back to nextResetTime ordering; every window prefers the percentage field. Credit-package plans (issue #7) answer with CREDIT_LIMIT rows that carry the same unit/number declaration, so both row kinds share one unit match (unit=3 → the 5h credit window, e.g. 2000 credits; unit=6 → the weekly pool, e.g. 10000 credits) and either kind may fill a lane the other left empty; only rows that declare no unit fall back to nextResetTime ordering. Credit lanes are tagged in the hover title as [CREDIT_LIMIT u3n5 left remaining/usage], or no unit when the declaration is absent |
kimi-coding-usage |
usage % | { usage: { limit, used, remaining, resetTime }, limits: [{ window: { duration, timeUnit }, detail: { limit, used, remaining, resetTime } }] } — 5h = the duration=300 window, weekly = duration=10080 (fallback: top-level usage); used = limit − remaining |
volcengine-agent-usage |
usage % | Dispatched inline in fetchRow (not through adaptRow): signs and calls the Volcengine Ark OpenAPI GetAFPUsage, parsing Result.AFPFiveHour / AFPWeekly / AFPMonthly (Agent Plan 5h/weekly/monthly; AFPDaily skipped per the console). |
volcengine-coding-usage |
usage % | Dispatched inline in fetchRow (not through adaptRow): signs and calls the Volcengine Ark OpenAPI GetCodingPlanUsage, parsing Result.QuotaUsage[].Level ∈ {session,weekly,monthly} (Coding Plan session/weekly/monthly, percentages only). Independent from the Agent row — no fallback between them. |
chatgpt-subscription |
usage % | { plan_type, rate_limit: { primary_window: { used_percent, reset_at, limit_window_seconds }, secondary_window? } } — read via the OAuth token in ~/.codex/auth.json against Codex's internal usage endpoint; windows are classified by limit_window_seconds (18000s ≈ 5h → rolling, 604800s ≈ 7d → weekly), falling back to the typical positional layout (primary = 5h session, secondary = weekly pool). Experimental |
Configure per-provider proxies in the frontend settings panel (⚙ →
代理): fill an HTTP(S) proxy URL (e.g. http://127.0.0.1:7890,
user:pass allowed), saved to browser localStorage, effective immediately —
leave it empty to fall back to the profile config or a direct connection.
Requests still run host-side: the browser sends each row's proxy URL in the
fetch-all payload, the host validates it (http/https only, socks
rejected) and fetches through it — keys never reach the browser, but the
proxy itself can observe them (see Known issues & risks
under Security).
The profile proxies map + row-level proxy /
catalog.<id>.proxy remain available as default proxies (used when
the frontend field is empty). Precedence:
frontend settings > profile config > direct.
# example profile-level default proxy (the ⚙ panel can override per row)
- id: quota-panel
name: 'dsh-quota-panel'
config:
proxies:
home: http://127.0.0.1:7890 # local proxy http port (clash / v2rayN …)
catalog:
openrouter:
proxy: home # OpenRouter via proxy by default
providers:
- id: my-agg
label: My aggregator
credential: AGG_API_KEY
endpoint: https://agg.example # base URL for openai-billing
format: openai-billing
proxy: home
DeepSeek balance (balanceTiers {critical: 10, warn: 20, healthy: 50}):
| Balance | Status | Secondary info |
|---|---|---|
<= 10 |
error (red dot + red value) | 建议充值 |
10 < x <= 20 |
warn (amber) | 余额紧张 |
20 < x <= 50 |
ok | 余额正常 |
> 50 |
ok | 余额充足 |
OpenCode usage (high = max(rolling, weekly, monthly)):
| Usage | Status |
|---|---|
< warnPercent |
ok (green dot, DeepSeek-blue bar) |
>= warnPercent |
warn (amber dot + bar) |
>= errorPercent |
error (red dot + bar) |
One DSH host line at a time. This package supports exactly the host line
its peerDependencies pin — currently @deepseek-ai/dsh@0.1.7-rc.1
(npm next). The five seam packages it actually talks to are declared as
exact peers:
| Seam package (peer, exact) | Used for |
|---|---|
@deepseek-ai/dsh-client-connection |
host connection.fetch RPC routes + browser connection.rpc |
@deepseek-ai/dsh-credentials |
host-side credentials.resolve |
@deepseek-ai/dsh-client-ui-renderer |
browser slots service (shell.overlay registration) |
@deepseek-ai/dsh-cordis-client-runner |
browser timer service (ctx.interval) |
@deepseek-ai/dsh-client-locale |
browser locale service (dictionaries) |
Because those peers are exact versions, a DSH 0.1.7-or-newer host's plugin/host
compatibility gate (evaluatePluginCompatibility) refuses to install or boot
this package on any other host line. That is intentional: old host lines are
served by old plugin versions. Keep running the release that pinned your DSH
version — its tag records which one — and upgrade the plugin together with DSH.
(Host lines older than 0.1.7 have no such gate and simply ignore the
declaration; they are not tested or supported.)
Verification for the current line lives in the testbed and the CI boot gate;
docs/2026-09-24-dsh-0.1.7-rc.1-assessment.md
records the seam-by-seam diff against the previous line.
The version string carries the DSH host line plus a local revision,
0.1.7-rc.1-v0.1
└────┬────┘ └┬┘
DSH line └─ local revision (this repo's counter)
(peer pin)
package.json#version is <dsh-line>-v<local> (for example
0.1.7-rc.1-v0.1).v + that version: v0.1.7-rc.1-v0.1.npm view @deepseek-ai/dsh dist-tags and publishes the plugin under the tag
that currently names the declared host line: a release for dsh latest
(today 0.1.5-rc.3) goes to npm latest; a release for dsh next
(today 0.1.7-rc.1) goes to npm next. A line no dist-tag points at falls
back to next. GitHub releases follow the same split: latest → normal
release gated by the production environment's reviewer, everything else →
pre-release.release/0.1.5-rc.3) by pushing the tag by hand or dispatching the CI
workflow with release: true; main only ever auto-tags its own line.promote_tag (e.g. v0.1.7-rc.1-v0.1) to
move npm latest and the GitHub Latest flag to that release, even when its
host line is still dsh next.Because the host line is pinned in peerDependencies, one DSH line gets one
plugin line: old host lines keep their old plugin release, and the plugin is
upgraded together with DSH.
Install a released version — not the main branch. main receives
unverified work-in-progress; only tagged releases have passed the CI gates
(check + boot) and — for the shipped line — the human approval gate.
Recommended — the release that matches your DSH line:
# dsh npm `latest` (0.1.5-rc.3 today) → plugin release tagged
# v0.1.5-rc.3-v0.1, published as npm `latest`:
dsh plugin --profile web add dsh-quota-panel
# dsh npm `next` (0.1.7-rc.1 today) → plugin release tagged
# v0.1.7-rc.1-v0.1, published as npm `next`:
dsh plugin --profile web add dsh-quota-panel@next
# Pin the tag instead (checked on the Releases page):
dsh plugin --profile web add "github:wenzetan/dsh-quota-panel#v0.1.5-rc.3-v0.1"
# Restart `dsh web` (bundle layer and client module graph apply at boot)
Check npm view @deepseek-ai/dsh dist-tags to see which tag your DSH line
rides, then install the matching plugin channel — the version rule is in
Versioning and tags.
Avoid bare
github:wenzetan/dsh-quota-panel(no#tag) — it tracksmainHEAD, which is the testing branch: it may carry unreleased work, fail CI, or break. Only developers iterating on the plugin itself should install frommain.
Refresh the browser page once after installing. Zero npm dependencies (the
schema library — schemastery + cosmokit, both MIT — is vendored under
src/vendor/ with relative-path imports), no allowBuilds authorization
needed.
The release identity and channel rules live in
Versioning and tags. In short: package.json#version
is <dsh-line>-v<local>, the tag is v<that version>, and the publish
channel mirrors @deepseek-ai/dsh's own dist-tags — latest for the shipped
line (GitHub release, production environment reviewer), that line's dist-tag
for everything else (GitHub pre-release).
| package.json version | Host line it pins | Channel | Gate | GitHub Release | npm dist-tag |
|---|---|---|---|---|---|
0.1.7-rc.1-v0.1 |
dsh next |
that line's tag | CI only (check + boot) | flagged pre-release | next |
0.1.5-rc.3-v0.1 |
dsh latest |
that line's tag | CI + human approval | normal release | latest |
Workflow:
package.json#version
(e.g. 0.1.7-rc.1-v0.1) and push main. CI runs the full gates,
auto-tags v0.1.7-rc.1-v0.1 and publishes it under the dist-tag
that @deepseek-ai/dsh itself currently carries for 0.1.7-rc.1
(next today). The classify job re-derives that mapping at publish
time, so nothing is hardcoded.release/0.1.5-rc.3), bump the version there (0.1.5-rc.3-v0.1), and
either push the tag by hand or run the CI workflow with the release
input on that branch. The release job publishes under the tag that dsh
itself carries for that line — latest for 0.1.5-rc.3 — and the
release-latest job waits in the production environment for a human
approval before creating the GitHub Release and publishing.promote_tag set to its tag
(e.g. v0.1.7-rc.1-v0.1). The promote job moves npm latest and the
GitHub Latest flag there, and demotes every other normal release to a
pre-release — so exactly one release (the current latest) is a normal
release. It runs behind the production environment.dsh plugin --profile web add "github:wenzetan/dsh-quota-panel#v0.1.5-rc.3-v0.1", or
dsh-quota-panel@latest / @next from npm) and test it for real.One-time setup:
dsh-quota-panel (name free as of this writing) and add it as
the repository secret NPM_TOKEN (Settings → Secrets and variables →
Actions). Without it, GitHub Releases still ship; npm steps are skipped.production → Required reviewers → add yourself. This is what makes
"no stable release without human confirmation" enforced rather than
conventional. (Without the reviewer configured, the stable channel
publishes without pausing — same as before.)The package declares dsh.bundle.patch (host half auto-activates as a
profile layer) and the dsh.client manifest (browser half auto-joins the
__DSH_BOOT__ module graph, immediately: true prefetched with the shell).
This plugin builds on community work — thanks to:
docs/api-research.md
real-world samples) pinned the semantic window mapping: TOKENS_LIMIT
unit=3 → 5h, unit=6 → weekly, TIME_LIMIT → MCP monthly, percentage
as the authoritative field; its Kimi (window.duration 300/10080,
limit − remaining) and MiniMax (coding-model row, weekly lane) clients
informed the matching adapters here (issue #2).token_plan/remains response quirks and the
Kimi Code usage endpoint.src/vendor/.package.json#version is <dsh-line>-v<local>, the
git tag is v<that version>, and the npm channel mirrors
@deepseek-ai/dsh's own dist-tags (0.1.7-rc.1 → next, 0.1.5-rc.3 →
latest). The five seam packages are declared as exact-version peers, so a
DSH 0.1.7+ host's plugin compatibility gate only loads the release built for
its own host line (old lines keep their old plugin release). Client bundle
URLs in the boot graph are document-relative since 0.1.7 — the testbed
probes accept both forms. Every release before this entry was unpublished
from GitHub and deprecated on npm.CREDIT_LIMIT rows now share the TOKENS_LIMIT window
declaration (unit=3 → the 5h credit window, unit=6 → the weekly pool)
instead of being placed by nextResetTime order alone — the weekly pool
resetting before the 5h window inverted the two lanes every time. Either row
kind can now also fill a lane its sibling left empty (a lone TOKENS_LIMIT
row used to discard the CREDIT_LIMIT row and lose the 5h lane entirely).
Rows that declare no unit keep the reset-order fallback, and the hover title
spells out the declaration and quota ([CREDIT_LIMIT u3n5 left 1900/2000],
no unit when absent). Existing V1 (5h-only) and V2 (5h/weekly/MCP) shapes
are unchanged.error listener, so an unreachable proxy
(ECONNREFUSED 127.0.0.1:7890 — proxy not running) surfaced as Node's
Emitted 'error' event on ClientRequest instance and killed dsh web. Every
socket and request now funnels error into the row result, so a dead proxy
degrades to a per-row message.@deepseek-ai/dsh@0.1.5-rc.1 and
newer. connection.rpc.handle() no longer works for third-party plugins
there: its route disposer reads owner.webServer, and that owner resolves to
the Connection plugin's own fiber, which never holds webServer — the
registration throws inside a child fiber, so boot stays silent while the
channel disappears. The host half now mounts one exact Fetch route per
endpoint through connection.fetch.register() (which needs only
owner.effect), under DSH's own authenticated /api channel:
POST /api/dsh-quota-panel/<endpoint>. This supersedes rc.1, which remains
merged but must not be promoted because its RPC endpoints are missing on
current DSH.volcengine-agent,
GetAFPUsage, 5h/weekly/monthly) and the Coding row (volcengine-coding,
GetCodingPlanUsage, session/weekly/monthly) each query only their own API
with no cross-fallback; a plan the account has not subscribed to reports its
own "not subscribed" message on that row. The Coding row's hover title labels
its rolling window session: (it is a session limit, not a 5h window), and a
migration note for the replaced row/format ids (volcengine /
volcengine-usage) was added to the Volcengine troubleshooting section.[class*="overlayLayer"]{z-index:1150 !important} so the shell overlay
layer (z-index 20) is no longer covered whole-layer by body-mounted
third-party fixed panels (z-index 1000+) — the widget stays inside the
React tree, keeping event delegation intact. Both style tags are removed
on plugin unload. (rc.5 was briefly auto-released with a fixed 60px bottom
offset instead of dragging; its tag/release were rolled back — the npm
version is an orphan pre-release.)zai-coding-quota maps windows semantically (TOKENS_LIMIT unit=3 → 5h,
unit=6 → weekly, TIME_LIMIT → the MCP monthly lane; unknown units fall
back to nextResetTime ordering) instead of the size heuristic that
swapped 5h/weekly on plans returning both rows, prefers the percentage
field for every window, and relabels the third slot 搜索 → 月;
kimi-coding-usage matches windows by window.duration (300 = 5h,
10080 = weekly) instead of blind limits[0] and computes used as
limit − remaining (the old code read a nonexistent detail.used, so
the 5h window silently dropped); minimax-remains prefers the
MiniMax-M* coding model row over whatever comes first and adds the
weekly window (current_weekly_total_count > 0, remaining-side counts).next
with a latest reclaim guard; stable versions require the manual
rc_tag promote workflow.cordis.patch.yml no longer
ships explicit example rows (deepseek / opencode-go), so the settings
panel lists exactly the providers whose credential resolves (auto
discovery). The CI boot gate now asserts both directions: the seeded key
appears, and unconfigured providers do not.locale.preference; zh / en) through the
ctx.locale service — all copy (capsule, card, settings panel, errors,
aria labels, usage windows) ships as zh/en dictionaries registered under
the quota-panel namespace; provider labels are proper nouns kept as-is
(GLM, MiniMax, Kimi Coding…) with Chinese brand names romanized
(智谱 → ZhiPu); host catalog labels normalized accordingly
(SiliconFlow CN, MiniMax Coding CN, ZhiPu GLM). Also: usage reset times
now show absolute 24h timestamps (下次重置 2026-08-15 14:00, dictionary
key nextReset); usage rows drop the weekly segment when the plan has no
weekly window and render the search/MCP lane as -% when it is unknown
(no fabricated 0%); the usage caption reads 当前已使用 X%.siliconflow now maps to the
global site (api.siliconflow.com, $), new id siliconflow-cn maps to
the China site (api.siliconflow.cn, ¥, ref SILICONFLOW_CN_API_KEY);
balance rows gained a per-row currency override (catalog rows, catalog:
overrides, and explicit providers: entries); README documents the
dual-site provider id → endpoint/currency mapping.src/*.ts compiled into lib/
by npm run build (tsc + vendored runtime copy), committed artifacts
verified current by CI; new dsh-plugin-check CI gate (any error or warning
fails — currently verdict=pass, 0 error / 0 warning); CI check job now
installs dev dependencies and builds before testing.ZAI_CODING_CN_API_KEY), Z.AI GLM Coding (ZAI_API_KEY), Kimi Coding
(KIMI_API_KEY), MiniMax Coding global/CN (MINIMAX_API_KEY /
MINIMAX_CN_API_KEY); new zai-coding-quota (5h/weekly token windows +
search lane) and kimi-coding-usage (5h + weekly request pool)
adapters; minimax-remains rewritten for the real model_remains
response (now a usage row); zhipu-quota shows percentage when a
limit carries no remaining; usage rows render missing windows as
— (labels from windowLabels, no longer hardcoded rolling/weekly/
monthly).openai-billing); fetch-all
contract switched to host-side normalized views (balance / usage / info),
upstream JSON no longer shipped; per-row HTTP(S) proxy (CONNECT tunnel /
absolute URI, zero-dependency), configured in the ⚙ settings panel
(localStorage, takes precedence over profile proxies / proxy);
new auto / hide / proxies / catalog config keys.specs / fetch-all) + Config schema; browser half
moved into the dsh.client manifest + shell.overlay slot (React);
added the ⚙ settings panel (visibility / refresh interval / thresholds,
localStorage-persisted).ctx.credentials and used only for
host-side requests to providers; the browser talks exclusively to the
plugin's methods on Connection's authenticated /api channel
(/api/dsh-quota-panel/<method>), the specs endpoint ships
render hints only (labels/kinds/thresholds) with no credential or
endpoint; since v0.5 fetch-all ships normalized views only — raw
upstream JSON stays host-side too.createElement/textContent;
API values never touch innerHTML; technical errors (401, timeout,
missing credential, refused proxy) surface only in title tooltips or
inline row text, and one failing row never affects the others.The per-row proxy feature has two known risk points you should weigh before routing a provider through a proxy:
Authorization header is forwarded to the proxy. When a
row goes through a proxy, the host sends the request headers — including
Authorization: Bearer <key> — to the proxy server itself: for https
targets the key rides in the CONNECT request (outside the TLS tunnel), for
http targets in the absolute-URI request. The proxy operator can therefore
read every API key routed through it.fetch-all payload
and validated only as http/https. The endpoint itself sits behind DSH's
/api fence (trusted host + browser session), so a caller needs the page
session of the running dsh web — but anyone who does hold that session
(or a script driving the same browser profile) can POST a proxy override
pointing at an arbitrary server and have the host send your provider keys
to it.Stay safe:
http://127.0.0.1:7890, e.g. clash / v2rayN). Never point a row at a
third-party or public proxy you do not operate: its operator can read your
keys (see 1).dsh web only on machines you trust. The plugin's endpoints sit
behind DSH's /api fence (trusted host + browser session), so they are not
open to the network; keep it that way by not exposing the port to other
users or the network.# Sources live in src/*.ts (org tool-bundle template): tsc compiles them
# into lib/ (declarations included) and scripts/build.mjs copies the
# vendored schema runtime into lib/vendor/. devDependencies are build-only
# — runtime stays zero-dependency.
npm install
npm run build
# After editing src/, rebuild and COMMIT lib/ — github: installs run from
# the committed artifacts (CI's "Committed artifacts are current" step
# rejects a stale lib/).
# Dual-face check: host RPC contract + catalog discovery/proxy engine
# (exercised against real local servers) + client slot/settings surfaces
node scripts/test-page-script.mjs
# Health check with @deepseek-ai/dsh-plugin-check (same gate as CI; fails
# on any error or warning). One-off deps dir, then the gate script:
mkdir -p /tmp/pc-deps && cd /tmp/pc-deps && npm init -y >/dev/null
npm install --no-audit --no-fund --ignore-scripts \
github:omdsh-dev/dsh-plugin-check \
@deepseek-ai/dsh-tools @deepseek-ai/dsh-invariants @deepseek-ai/cordis
cd /path/to/dsh-quota-panel
PLUGIN_CHECK_DEPS=/tmp/pc-deps node scripts/plugin-check.mjs .
# To upgrade the vendored schema library: replace the two runtime files
# under src/vendor/ and rewrite the cosmokit import on line 1 of
# schemastery.mjs to "./cosmokit.js", then rebuild.
Pull requests are welcome! 🎉 Whether it is a new provider adapter (the catalog + auto-discovery pipeline makes adding one mostly declarative — see the Volcengine Ark PR for a complete reference implementation, including the first AK/SK-signed provider), a bug fix, a UI tweak, or docs improvements.
Before opening a PR:
npm run build && npm test) — CI rejects a stale
lib/, so rebuild and commit the artifacts after editing src/;scripts/test-page-script.mjs covering the new
behavior (mocked upstream + normalized row view);README.md and README.zh.md if the change is
user-facing.For provider requests without an OpenAPI endpoint (Cookie / CLI-only plans), open an issue first so we can discuss feasibility.
MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: quota。