@sunshaobei/dsh-balance-panel 0.0.0-stage → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +830 -2
- package/cordis.patch.yml +44 -0
- package/lib/client.d.ts +0 -0
- package/lib/client.js +1384 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +1980 -0
- package/lib/vendor/cosmokit.js +325 -0
- package/lib/vendor/schemastery.mjs +596 -0
- package/package.json +58 -4
- package/src/client.ts +1368 -0
- package/src/index.ts +1924 -0
- package/src/vendor/cosmokit.js +325 -0
- package/src/vendor/schemastery.d.mts +8 -0
- package/src/vendor/schemastery.mjs +596 -0
package/README.md
CHANGED
|
@@ -1,3 +1,831 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-quota-panel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
**dsh-quota-panel** is a **provider quota / balance status widget** for the
|
|
6
|
+
DeepSeek Harness (DSH) **web surface** (`dsh web`). It sits in the
|
|
7
|
+
bottom-right corner of the product UI, watches every AI provider whose API
|
|
8
|
+
key you have configured, and tells you at a glance how much balance /
|
|
9
|
+
quota is left — DeepSeek, OpenRouter, SiliconFlow, Moonshot, StepFun,
|
|
10
|
+
xAI, Zhipu GLM, OpenCode Go, Volcengine Ark (Agent/Coding Plan), plus one-api / new-api style aggregators,
|
|
11
|
+
and the **coding plans** (智谱 GLM Coding, Z.AI, Kimi Coding, MiniMax
|
|
12
|
+
Coding global/CN) with 5-hour / weekly usage windows and MCP monthly quota.
|
|
13
|
+
xAI, Zhipu GLM, OpenCode Go, **ChatGPT subscription (Plus/Pro via Codex
|
|
14
|
+
login)**, plus one-api / new-api style aggregators, and the **coding
|
|
15
|
+
plans** (智谱 GLM Coding, Z.AI, Kimi Coding, MiniMax Coding global/CN)
|
|
16
|
+
with 5-hour / weekly usage windows and MCP monthly quota.
|
|
17
|
+
|
|
18
|
+
Since v0.5 it is a **dual-face plugin** with a **built-in provider catalog
|
|
19
|
+
and auto discovery**: install it, restart `dsh web`, and every provider
|
|
20
|
+
whose key resolves automatically appears on the panel — zero configuration.
|
|
21
|
+
It needs **no npm dependencies** and asks for **no `allowBuilds` authorization**.
|
|
22
|
+
|
|
23
|
+
## Screenshots (real browser rendering)
|
|
24
|
+
|
|
25
|
+
Collapsed capsule, light theme:
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
Expanded card, light theme:
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
Settings panel (⚙), light theme:
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
Collapsed capsule, dark theme:
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
Expanded card, dark theme:
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
Settings panel (⚙), dark theme:
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
## Supported features
|
|
50
|
+
|
|
51
|
+
- **Auto discovery** — the host half ships a catalog of well-known
|
|
52
|
+
providers; each entry names the provider's standard credential
|
|
53
|
+
references, and **every provider whose key resolves
|
|
54
|
+
(`$DSH_HOME/.credentials.yaml` / `.env` / environment variables)
|
|
55
|
+
appears on the panel automatically**, with zero config. Remove the key
|
|
56
|
+
and the row disappears. No credential enumeration API exists in DSH, so
|
|
57
|
+
the catalog is probed each refresh cycle.
|
|
58
|
+
- **Coding-plan usage windows** — GLM / Z.AI / Kimi / MiniMax coding
|
|
59
|
+
plans render as usage rows: 5-hour window, weekly pool and (GLM/Z.AI)
|
|
60
|
+
the MCP monthly lane, each with its own reset countdown; windows the
|
|
61
|
+
plan does not carry show `—` instead of a fabricated 0%.
|
|
62
|
+
- **Two sizes** — collapsed: a minimal capsule with one independent
|
|
63
|
+
"status dot + value" pair per account (`● ¥58.36 · ● 45%`); expanded: a
|
|
64
|
+
full card with a row per provider (status dot, name, primary value,
|
|
65
|
+
secondary info, progress bar for usage-kind providers).
|
|
66
|
+
- **Auto refresh** — follows the configured interval (default 60 s), paused
|
|
67
|
+
while the page is hidden; the refresh button spins during a fetch and
|
|
68
|
+
repeated clicks never fire concurrent requests.
|
|
69
|
+
- **Per-account status** — balance rows are graded by tier
|
|
70
|
+
(`critical <= warn <= healthy`), usage rows by percent
|
|
71
|
+
(`error >= warn`); the offending dot/value alone recolors, others stay
|
|
72
|
+
calm. Usage percentages use battery-style three-color grading, independent
|
|
73
|
+
of the status dots.
|
|
74
|
+
- **Settings panel (⚙)** — per-provider visibility, refresh interval,
|
|
75
|
+
per-provider warning thresholds, per-provider **HTTP(S) proxy URL**, the
|
|
76
|
+
**capsule display mode** (auto = highest window — the default — / 5h
|
|
77
|
+
window / weekly window / highest), and "restore defaults". All local
|
|
78
|
+
settings apply immediately, persist to browser localStorage, and are
|
|
79
|
+
never written to the profile or uploaded.
|
|
80
|
+
- **Per-row HTTP(S) proxy** — configure a proxy for providers that cannot
|
|
81
|
+
be reached directly from your network (see below).
|
|
82
|
+
- **One-api / new-api aggregators** — the built-in `openai-billing`
|
|
83
|
+
format adapts aggregator dashboards.
|
|
84
|
+
- **Theming** — driven entirely by Harness design tokens
|
|
85
|
+
(`--dsw-alias-*`, `--dsw-static-*`, `--dsw-shadow-*`,
|
|
86
|
+
`--dsw-font-*`) with sensible fallbacks, so it follows the product
|
|
87
|
+
theme (light/dark) and ships no palette of its own.
|
|
88
|
+
- **Security by construction** — API keys never reach the browser; the
|
|
89
|
+
browser talks only to a loopback-only RPC channel and receives only
|
|
90
|
+
normalized views (see "How it works").
|
|
91
|
+
|
|
92
|
+
## Not supported (yet)
|
|
93
|
+
|
|
94
|
+
- **Usage-only providers** — OpenAI, Anthropic, Together, Groq, Mistral,
|
|
95
|
+
Cohere, DashScope, Baichuan expose no public "remaining balance"
|
|
96
|
+
endpoint, only usage/cost queries (usually admin keys + time windows,
|
|
97
|
+
"spent" semantics rather than "remaining"). Planned as a separate
|
|
98
|
+
usage row kind showing monthly spend (Anthropic Admin API and OpenAI
|
|
99
|
+
usage API first).
|
|
100
|
+
- **Cookie / CLI-only coding plans** — the quota pages of Qwen Token Plan
|
|
101
|
+
(Bailian console), Xiaomi MiMo Token Plan, and Qoder expose no API-key
|
|
102
|
+
quota endpoint: they require web cookies, the `arkcli` CLI, or chat-endpoint
|
|
103
|
+
rate-limit probes (per
|
|
104
|
+
[CodexBar](https://github.com/steipete/CodexBar/tree/main/docs) research).
|
|
105
|
+
Volcengine Ark / Doubao Agent Plan and Coding Plan are now supported via
|
|
106
|
+
the AK/SK-signed OpenAPI (see the catalog table above). Other providers on
|
|
107
|
+
this list cannot be wired in until an API-key endpoint appears.
|
|
108
|
+
- **socks5 proxies** — only HTTP/HTTPS proxies are accepted (a socks URL
|
|
109
|
+
is rejected with a clear per-row error).
|
|
110
|
+
- **Custom adapters** — new upstream formats cannot be plugged in from the
|
|
111
|
+
profile; a `format` value outside the built-in set fails loud at mount.
|
|
112
|
+
- **Multi-page placements** — the widget lives in the `shell.overlay`
|
|
113
|
+
slot only (bottom-right corner), not in sidebars, headers, or the status
|
|
114
|
+
bar.
|
|
115
|
+
|
|
116
|
+
### Requesting a new provider
|
|
117
|
+
|
|
118
|
+
Missing a provider? [Open an issue](https://github.com/wenzetan/dsh-quota-panel/issues/new)
|
|
119
|
+
with:
|
|
120
|
+
|
|
121
|
+
1. **the provider id** you want (`^[a-z0-9-]+$`, e.g. `together`), using a
|
|
122
|
+
`-cn` suffix for the China site of a dual-site provider
|
|
123
|
+
(cf. `siliconflow` / `siliconflow-cn`);
|
|
124
|
+
2. **the balance API URL** — a public endpoint that answers the provider's
|
|
125
|
+
standard API key with remaining balance/quota (e.g.
|
|
126
|
+
`GET https://api.provider.com/v1/user/info`, Bearer auth), plus the
|
|
127
|
+
response shape if you can paste it.
|
|
128
|
+
|
|
129
|
+
That is all the catalog needs: an id whose standard credential reference
|
|
130
|
+
resolves, an endpoint, and a format adapter for the response. Providers with
|
|
131
|
+
only cookie/CLI quota pages (see above) cannot be supported until they expose
|
|
132
|
+
an API-key endpoint.
|
|
133
|
+
|
|
134
|
+
## How it works
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
┌─────────────── browser (lib/client.js) ───────────────┐
|
|
138
|
+
│ shell.overlay slot → capsule / card / settings panel │
|
|
139
|
+
│ localStorage: visibility · interval · thresholds · │
|
|
140
|
+
│ proxy URLs (frontend settings) │
|
|
141
|
+
└──────────────┬─────────────────────────────────────────┘
|
|
142
|
+
│ loopback-only Connection RPC: /dsh-quota-panel
|
|
143
|
+
│ specs (render hints, no credentials)
|
|
144
|
+
│ fetch-all { proxy: {rowId: url} } → normalized views
|
|
145
|
+
┌──────────────▼────────────── host (lib/index.js) ──────┐
|
|
146
|
+
│ ctx.credentials → API keys (never leave the host) │
|
|
147
|
+
│ catalog probe → auto discovery (15 built-in providers) │
|
|
148
|
+
│ per-row fetch → proxy engine (CONNECT tunnel / │
|
|
149
|
+
│ absolute-URI) → upstream JSON │
|
|
150
|
+
│ normalization → {balance | usage | info} view models │
|
|
151
|
+
└─────────────────────────────────────────────────────────┘
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- **Host half** (`lib/index.js`) registers one loopback-only Connection
|
|
155
|
+
RPC channel `/dsh-quota-panel` with two endpoints:
|
|
156
|
+
- `specs` — the resolved rows with render hints only (id, label, row
|
|
157
|
+
kind, currency, threshold tiers, window labels, configured proxy name).
|
|
158
|
+
No credentials, no endpoints.
|
|
159
|
+
- `fetch-all` — fetches every visible row, normalizes each upstream
|
|
160
|
+
response into a **generic view model** (`balance` / `usage` /
|
|
161
|
+
`info`), and returns `{rows: [{id, view} | {id, error}], fetchedAt}`.
|
|
162
|
+
Raw upstream JSON stays host-side like the keys; one failing row never
|
|
163
|
+
affects the others.
|
|
164
|
+
- **Auto discovery** — because DSH's credential store has no enumeration
|
|
165
|
+
API, the host half probes the catalog entries' standard refs each fetch
|
|
166
|
+
cycle; every entry whose key resolves joins the panel, and entries with
|
|
167
|
+
unresolvable keys are skipped (a missing key yields a clear per-row error
|
|
168
|
+
only when it was explicitly configured via `providers`).
|
|
169
|
+
- **Proxy engine** — zero-dependency hand-rolled `proxiedGetJson`:
|
|
170
|
+
https targets go through an HTTP `CONNECT` tunnel (TLS over the
|
|
171
|
+
tunnel), http targets via absolute-URI forwarding. 15 s per-row timeout,
|
|
172
|
+
1 MB body cap. Proxy selection precedence:
|
|
173
|
+
**frontend settings panel > profile config > direct**.
|
|
174
|
+
- **Threshold judgement happens client-side** from the `specs` hints, so
|
|
175
|
+
local threshold overrides apply without refetching; profile thresholds
|
|
176
|
+
ship in `specs` and the frontend settings override them locally.
|
|
177
|
+
- **Config validation** — the exported `Config` schema (vendored
|
|
178
|
+
schemastery) declares structure and defaults; cross-field constraints (id
|
|
179
|
+
uniqueness, `critical <= warn <= healthy`, proxy references, catalog
|
|
180
|
+
override keys) are validated host-side at mount and fail loud.
|
|
181
|
+
- **DOM safety** — the card builds DOM exclusively with
|
|
182
|
+
`createElement`/`textContent`; API values never touch `innerHTML`;
|
|
183
|
+
technical errors (401, timeout, missing credential, refused proxy) surface
|
|
184
|
+
only in `title` tooltips or inline row text.
|
|
185
|
+
|
|
186
|
+
## Configuration
|
|
187
|
+
|
|
188
|
+
**Out of the box: nothing.** Install, restart, and any provider whose key
|
|
189
|
+
resolves appears automatically. The table below is only for tuning.
|
|
190
|
+
|
|
191
|
+
All keys are optional — the structure and defaults live in the exported
|
|
192
|
+
`Config` schema, so profile patches may omit every defaulted field.
|
|
193
|
+
|
|
194
|
+
| Key | Meaning | Default |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `auto` | probe the built-in catalog; providers with a resolvable key join the panel | `true` |
|
|
197
|
+
| `hide` | row ids to drop (catalog and explicit rows alike) | `[]` |
|
|
198
|
+
| `proxies` | named proxy definitions `{<name>: "http://host:port"}`, HTTP(S) only | `{}` |
|
|
199
|
+
| `catalog` | partial overrides for auto-discovered rows `{<catalog-id>: {...}}` | `{}` |
|
|
200
|
+
| `refreshMs` | auto-refresh interval | 60000 |
|
|
201
|
+
| `providers` | explicit rows; a same-id entry replaces the catalog row wholesale | `[]` |
|
|
202
|
+
|
|
203
|
+
Each `catalog` override may set: `label` / `endpoint` / `format` /
|
|
204
|
+
`proxy` / `refs` (credential references to probe, UPPER_SNAKE) /
|
|
205
|
+
`secretRefs` (a second credential reference — required for the Volcengine
|
|
206
|
+
AK/SK pair; the row is only discovered when BOTH `refs` and `secretRefs`
|
|
207
|
+
resolve) / `region` (Volcengine OpenAPI region; default `cn-beijing`) /
|
|
208
|
+
`currency` (balance rows: symbol like `$` or `US$`) / `balanceTiers` /
|
|
209
|
+
`warnPercent` / `errorPercent` / `windowLabels`.
|
|
210
|
+
|
|
211
|
+
Explicit `providers` fields:
|
|
212
|
+
|
|
213
|
+
| Field | Meaning | Default |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `id` | row id (RPC rows align by id), `^[a-z0-9-]+$` | required |
|
|
216
|
+
| `label` | provider name shown on the card | required |
|
|
217
|
+
| `credential` | credential reference (`$DSH_HOME/.credentials.yaml` or environment) | required |
|
|
218
|
+
| `secretCredential` | second credential reference (Volcengine `volcengine-agent-usage` / `volcengine-coding-usage`: the SK) | — |
|
|
219
|
+
| `endpoint` | quota JSON endpoint; base URL for `openai-billing` | required |
|
|
220
|
+
| `format` | row adapter (see table below) | `deepseek-balance` |
|
|
221
|
+
| `proxy` | a proxy name defined in `proxies`; absent = direct | — |
|
|
222
|
+
| `region` | (`volcengine-agent-usage` / `volcengine-coding-usage`) Volcengine OpenAPI region | `cn-beijing` |
|
|
223
|
+
| `currency` | (balance rows) currency symbol, overrides the format default | format default |
|
|
224
|
+
| `balanceTiers` | (balance rows) `{critical, warn, healthy}` | `{10, 20, 50}` |
|
|
225
|
+
| `lowBalance` | legacy alias for `balanceTiers.warn` | — |
|
|
226
|
+
| `windowLabels` | (usage-kind formats) labels for the usage windows | `{滚, 周, 月}` |
|
|
227
|
+
| `warnPercent` / `errorPercent` | (usage rows) thresholds | 70 / 90 |
|
|
228
|
+
|
|
229
|
+
### Built-in provider catalog (auto discovery)
|
|
230
|
+
|
|
231
|
+
| Provider | Credential refs probed | Endpoint | Row kind |
|
|
232
|
+
|---|---|---|---|
|
|
233
|
+
| DeepSeek | `DEEPSEEK_API_KEY` | `api.deepseek.com/user/balance` | ¥ balance |
|
|
234
|
+
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter.ai/api/v1/credits` | $ balance (purchased − used) |
|
|
235
|
+
| SiliconFlow (global) | `SILICONFLOW_API_KEY` | `api.siliconflow.com/v1/user/info` | $ balance |
|
|
236
|
+
| SiliconFlow (CN) | `SILICONFLOW_CN_API_KEY` | `api.siliconflow.cn/v1/user/info` | ¥ balance |
|
|
237
|
+
| Moonshot / Kimi | `MOONSHOT_API_KEY` | `api.moonshot.cn/v1/users/me/balance` | ¥ balance |
|
|
238
|
+
| MiniMax Coding (global) | `MINIMAX_API_KEY` | `www.minimax.io/v1/token_plan/remains` | 5h prompt usage % |
|
|
239
|
+
| MiniMax Coding (CN) | `MINIMAX_CN_API_KEY` | `api.minimaxi.com/v1/token_plan/remains` | 5h prompt usage % |
|
|
240
|
+
| StepFun | `STEP_API_KEY` / `STEPFUN_API_KEY` | `api.stepfun.com/v1/accounts` | ¥ balance (hover: cash/voucher) |
|
|
241
|
+
| xAI | `XAI_API_KEY` | `api.x.ai/v1/billing/credits` | $ balance |
|
|
242
|
+
| 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) |
|
|
243
|
+
| 智谱 GLM Coding | `ZAI_CODING_CN_API_KEY` | `open.bigmodel.cn/api/monitor/usage/quota/limit` | coding-plan windows (5h tokens / weekly / searches) |
|
|
244
|
+
| Z.AI GLM Coding | `ZAI_API_KEY` | `api.z.ai/api/monitor/usage/quota/limit` | coding-plan windows (5h tokens / weekly / searches) |
|
|
245
|
+
| Kimi Coding | `KIMI_API_KEY` | `api.kimi.com/coding/v1/usages` | usage % (5h rate limit + weekly request pool) |
|
|
246
|
+
| OpenCode Go | `OPENCODE_GO_API_KEY` | `opencode.ai/zen/go/v1/usage` | three-window usage % |
|
|
247
|
+
| Volcengine Ark Agent Plan | `VOLC_ACCESS_KEY` + `VOLC_SECRET_KEY` | `open.volcengineapi.com` (OpenAPI, signed) | usage % (5h / weekly / monthly, `GetAFPUsage`) |
|
|
248
|
+
| Volcengine Ark Coding Plan | `VOLC_ACCESS_KEY` + `VOLC_SECRET_KEY` | `open.volcengineapi.com` (OpenAPI, signed) | usage % (session / weekly / monthly, `GetCodingPlanUsage`) |
|
|
249
|
+
|
|
250
|
+
> Volcengine Ark has **two separate subscriptions — Agent Plan and Coding Plan** —
|
|
251
|
+
> shown as **two independent rows** (like two providers) that share the same
|
|
252
|
+
> AK/SK pair. Each row queries only its own plan's API and never falls back to
|
|
253
|
+
> the other, so a plan the account has not subscribed to shows a "not
|
|
254
|
+
> subscribed" message on that row instead of the other plan's numbers.
|
|
255
|
+
> Volcengine authenticates with an **AccessKey ID / SecretAccessKey pair** using
|
|
256
|
+
> HMAC-SHA256 request signing — not a Bearer token. The inference `ARK_API_KEY`
|
|
257
|
+
> (shaped `ark-...`) cannot query usage; only the AK/SK pair has OpenAPI
|
|
258
|
+
> permission. Full setup is in the next section.
|
|
259
|
+
|
|
260
|
+
#### Volcengine Ark setup
|
|
261
|
+
|
|
262
|
+
Ark Agent Plan / Coding Plan usage comes from the **control-plane OpenAPI**,
|
|
263
|
+
which requires an AK/SK pair with read-only access. Three steps:
|
|
264
|
+
|
|
265
|
+
**1. Create an AccessKey**
|
|
266
|
+
|
|
267
|
+
Open [https://console.volcengine.com/iam/keymanage](https://console.volcengine.com/iam/keymanage)
|
|
268
|
+
(Volcengine console → Identity and Access Management → Access Keys) and click
|
|
269
|
+
*New access key*. Prefer creating a **sub-user key** dedicated to this plugin
|
|
270
|
+
rather than using the primary account key. Save the AccessKey ID and
|
|
271
|
+
SecretAccessKey when shown — the SecretAccessKey is displayed once.
|
|
272
|
+
|
|
273
|
+
**2. Grant Ark read-only permission**
|
|
274
|
+
|
|
275
|
+
Attach the `ArkReadOnlyAccess` policy to the sub-user (or role) that owns the
|
|
276
|
+
key:
|
|
277
|
+
|
|
278
|
+
- Open [IAM → Users](https://console.volcengine.com/iam/user/list), pick the
|
|
279
|
+
sub-user, choose *Permissions* → *Add permissions*;
|
|
280
|
+
- In the *Search policy name and remarks* box, type `ArkReadOnlyAccess`;
|
|
281
|
+
- The result whose **service source is "Volcengine Ark"** is the one you need — check it. There are only two same-named policies in the search results, and checking both is harmless too.
|
|
282
|
+
|
|
283
|
+
`ArkReadOnlyAccess` alone is sufficient — `GetAFPUsage` and
|
|
284
|
+
`GetCodingPlanUsage` are read-only actions; `ArkFullAccess` or account-level
|
|
285
|
+
billing permissions are not required.
|
|
286
|
+
|
|
287
|
+
**3. Save the credentials**
|
|
288
|
+
|
|
289
|
+
Add two lines to `$DSH_HOME/.credentials.yaml` (by default
|
|
290
|
+
`C:\Users\<you>\.dsh\.credentials.yaml` on Windows or `~/.dsh/.credentials.yaml`
|
|
291
|
+
on Linux/macOS):
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
VOLC_ACCESS_KEY: AKLTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
295
|
+
VOLC_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
You can also use the `VOLC_ACCESS_KEY` / `VOLC_SECRET_KEY` environment
|
|
299
|
+
variables (DSH's credential resolver falls back to the environment). Restart
|
|
300
|
+
`dsh web`; the "Volcengine Agent" and "Volcengine Coding" rows appear in the
|
|
301
|
+
bottom-right panel automatically (whichever plan the account subscribes to
|
|
302
|
+
shows data; both rows share the same AK/SK pair) — no `providers:` block required.
|
|
303
|
+
|
|
304
|
+
**Verifying permissions**
|
|
305
|
+
|
|
306
|
+
After restart, check the panel:
|
|
307
|
+
|
|
308
|
+
- The Agent row shows three percentages (5h / weekly / monthly) and the Coding
|
|
309
|
+
row shows three (session / weekly / monthly) → AK/SK and `ArkReadOnlyAccess`
|
|
310
|
+
are both working (if only one plan is subscribed, the other row shows a
|
|
311
|
+
"not subscribed" message);
|
|
312
|
+
- `volcengine SignatureDoesNotMatch: ...` → the SK was copied wrong (watch the
|
|
313
|
+
trailing `=`);
|
|
314
|
+
- `volcengine AccessDenied: ...` → the policy is not attached or the wrong
|
|
315
|
+
source policy was selected;
|
|
316
|
+
- `No active Volcengine Ark Agent/Coding Plan subscription` → the signature
|
|
317
|
+
worked but the account has no subscription to that plan (typical for
|
|
318
|
+
pay-as-you-go accounts; the two rows report independently — hide the
|
|
319
|
+
unsubscribed one from the ⚙ settings panel).
|
|
320
|
+
|
|
321
|
+
> **Migration (from ≤ 0.9.1, if you pinned the old row):** the single
|
|
322
|
+
> catalog row id `volcengine` and the format id `volcengine-usage` were
|
|
323
|
+
> replaced by `volcengine-agent` (`volcengine-agent-usage`) and
|
|
324
|
+
> `volcengine-coding` (`volcengine-coding-usage`). Auto-discovered setups need
|
|
325
|
+
> no changes; a hand-written `catalog:` override or `providers:` entry that
|
|
326
|
+
> still references the old ids makes the plugin refuse to load with a
|
|
327
|
+
> validation error listing the valid ids — update it to the two new ids.
|
|
328
|
+
|
|
329
|
+
> Security note: an AK/SK pair can read all Ark usage data for the account.
|
|
330
|
+
> Redact it before pasting into chats, tickets, or screenshots, and rotate it
|
|
331
|
+
> from the [key management page](https://console.volcengine.com/iam/keymanage)
|
|
332
|
+
> when no longer needed.
|
|
333
|
+
| 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) |
|
|
334
|
+
|
|
335
|
+
An additional **`openai-billing`** format adapts one-api / new-api style
|
|
336
|
+
aggregators: set `endpoint` to the aggregator base URL and the host half
|
|
337
|
+
requests `{base}/v1/dashboard/billing/subscription`
|
|
338
|
+
(`hard_limit_usd`) plus `{base}/v1/dashboard/billing/usage`
|
|
339
|
+
(`total_usage`); remaining = limit − used ($). Aggregator domains differ
|
|
340
|
+
per deployment, so this format is explicit-config only.
|
|
341
|
+
|
|
342
|
+
### Dual-site provider ids (custom id → site mapping)
|
|
343
|
+
|
|
344
|
+
Some providers run separate international and China sites with different
|
|
345
|
+
endpoints, credential references and currencies. The catalog models each
|
|
346
|
+
site as its own **provider id**, so configuring the matching key is all it
|
|
347
|
+
takes — and an explicit `providers:` entry reusing one of these ids replaces
|
|
348
|
+
the catalog row wholesale (same fields, your endpoint/label/currency):
|
|
349
|
+
|
|
350
|
+
| provider id | Site | Endpoint | Credential ref | Currency |
|
|
351
|
+
|---|---|---|---|---|
|
|
352
|
+
| `siliconflow` | SiliconFlow global | `api.siliconflow.com/v1/user/info` | `SILICONFLOW_API_KEY` | `$` |
|
|
353
|
+
| `siliconflow-cn` | SiliconFlow China | `api.siliconflow.cn/v1/user/info` | `SILICONFLOW_CN_API_KEY` | `¥` |
|
|
354
|
+
| `minimax` | MiniMax Coding global | `www.minimax.io/v1/token_plan/remains` | `MINIMAX_API_KEY` | — (usage %) |
|
|
355
|
+
| `minimax-cn` | MiniMax Coding China | `api.minimaxi.com/v1/token_plan/remains` | `MINIMAX_CN_API_KEY` | — (usage %) |
|
|
356
|
+
| `zai` | Z.AI GLM Coding global | `api.z.ai/api/monitor/usage/quota/limit` | `ZAI_API_KEY` | — (usage %) |
|
|
357
|
+
| `zai-coding-cn` | 智谱 GLM Coding China | `open.bigmodel.cn/api/monitor/usage/quota/limit` | `ZAI_CODING_CN_API_KEY` | — (usage %) |
|
|
358
|
+
|
|
359
|
+
Both sites of one provider can be on the panel at the same time (configure
|
|
360
|
+
both keys); `hide: ["siliconflow"]` drops either row individually.
|
|
361
|
+
|
|
362
|
+
The currency symbol for balance-kind rows comes from the format by default
|
|
363
|
+
(`siliconflow-balance` renders ¥) and can be overridden per row: catalog
|
|
364
|
+
rows carry `currency` (the global SiliconFlow row sets `$`), a `catalog:`
|
|
365
|
+
override may set it, and explicit `providers:` entries accept a `currency`
|
|
366
|
+
field (e.g. `"US$"`).
|
|
367
|
+
|
|
368
|
+
### ChatGPT subscription (Plus/Pro)
|
|
369
|
+
|
|
370
|
+
ChatGPT subscription usage is **not** API billing — there is no public
|
|
371
|
+
balance/usage API. This plugin calls the same internal usage endpoint Codex
|
|
372
|
+
uses, with a ChatGPT OAuth token, and shows the **weekly window** (and the
|
|
373
|
+
**5-hour window** on Pro) as a used-percentage row. **Two login methods are
|
|
374
|
+
supported**, pick whichever you prefer:
|
|
375
|
+
|
|
376
|
+
> ⚠️ **Experimental.** The endpoint (`chatgpt.com/backend-api/wham/usage`) is
|
|
377
|
+
> an undocumented internal API used by the Codex CLI; its response shape may
|
|
378
|
+
> change. The plugin only performs read-only queries.
|
|
379
|
+
|
|
380
|
+
#### Option A: in-plugin login (recommended — no Codex CLI install required)
|
|
381
|
+
|
|
382
|
+
1. Restart `dsh web` and open the panel **settings** (gear icon) in the
|
|
383
|
+
bottom-right corner;
|
|
384
|
+
2. In the "ChatGPT account" section at the top, click **"Log in to ChatGPT"**;
|
|
385
|
+
3. The plugin starts an OAuth device-code flow and displays a **one-time code**
|
|
386
|
+
plus the sign-in URL `https://auth.openai.com/codex/device`;
|
|
387
|
+
4. Open the URL in your browser, sign in with your ChatGPT Plus/Pro account,
|
|
388
|
+
and enter the code;
|
|
389
|
+
5. Once authorized, a "ChatGPT" row appears automatically (no restart). Hover
|
|
390
|
+
to see `plan: plus/pro` and `weekly: N%` (Pro also shows a 5h window).
|
|
391
|
+
|
|
392
|
+
Tokens are stored in `$DSH_HOME/dsh-quota-panel/chatgpt-auth.json`
|
|
393
|
+
(`C:\Users\<you>\.dsh\dsh-quota-panel\` on Windows, file mode 0600). When the
|
|
394
|
+
access token expires the plugin refreshes it with the refresh token and writes
|
|
395
|
+
it back. Click "Log out" in the same section to delete the local token.
|
|
396
|
+
|
|
397
|
+
#### Option B: reuse a Codex CLI login
|
|
398
|
+
|
|
399
|
+
If you have already logged in via the [Codex CLI](https://developers.openai.com/codex/)
|
|
400
|
+
(ran `codex` and completed the browser sign-in), the plugin automatically reads
|
|
401
|
+
`~/.codex/auth.json` (or `$CODEX_HOME/auth.json`) — **no extra configuration
|
|
402
|
+
needed**. With this method, refreshed tokens stay in the plugin process memory
|
|
403
|
+
only and are **never written back** to `auth.json` (that file is owned by the
|
|
404
|
+
Codex CLI).
|
|
405
|
+
|
|
406
|
+
When both are present, the **in-plugin login takes precedence**. When neither
|
|
407
|
+
exists the ChatGPT row is hidden. If the token is invalidated by a login
|
|
408
|
+
elsewhere, the row shows an error — run Option A again, or `codex login`, to
|
|
409
|
+
recover.
|
|
410
|
+
|
|
411
|
+
### Built-in formats
|
|
412
|
+
|
|
413
|
+
| format | Row kind | Upstream response shape |
|
|
414
|
+
|---|---|---|
|
|
415
|
+
| `deepseek-balance` | ¥ balance | `{ balance_infos: [{ currency, total_balance, granted_balance, topped_up_balance }] }` |
|
|
416
|
+
| `openrouter-credits` | $ balance | `{ data: { total_credits, total_usage } }` |
|
|
417
|
+
| `siliconflow-balance` | balance (¥ by default, per-row currency override) | `{ data: { balance, chargeBalance, totalUsage } }` |
|
|
418
|
+
| `moonshot-balance` | ¥ balance | `{ data: { total_balance } }` |
|
|
419
|
+
| `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` |
|
|
420
|
+
| `stepfun-accounts` | ¥ balance | `{ balance, total_cash_balance, total_voucher_balance }` |
|
|
421
|
+
| `xai-credits` | $ balance | `{ total: { val } }` (cents → dollars) |
|
|
422
|
+
| `openai-billing` | $ balance | aggregator `dashboard/billing` endpoints |
|
|
423
|
+
| `zhipu-quota` | text | `{ code: 200, data: { limits: [{ remaining, number }] } }` (limits without `remaining` fall back to `percentage`) |
|
|
424
|
+
| `opencode-usage` | usage % | `{ usage: { rolling|weekly|monthly: { percent, resetsAt } } }` |
|
|
425
|
+
| `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/resource-package plans (issue #7) return CREDIT_LIMIT rows instead of token windows: they map onto rolling/weekly by `nextResetTime` order, and the hover title tags them `[CREDIT_LIMIT left remaining/number]` |
|
|
426
|
+
| `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 |
|
|
427
|
+
| `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). |
|
|
428
|
+
| `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. |
|
|
429
|
+
| `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 |
|
|
430
|
+
|
|
431
|
+
### Proxy (providers that cannot be reached directly)
|
|
432
|
+
|
|
433
|
+
Configure per-provider proxies in the **frontend settings panel (⚙ →
|
|
434
|
+
代理)**: fill an HTTP(S) proxy URL (e.g. `http://127.0.0.1:7890`,
|
|
435
|
+
user:pass allowed), saved to browser localStorage, effective immediately —
|
|
436
|
+
leave it empty to fall back to the profile config or a direct connection.
|
|
437
|
+
Requests still run host-side: the browser sends each row's proxy URL in the
|
|
438
|
+
`fetch-all` payload, the host validates it (http/https only, socks
|
|
439
|
+
rejected) and fetches through it — keys never reach the browser, but the
|
|
440
|
+
proxy itself can observe them (see [Known issues & risks](#known-issues--risks-proxy-path)
|
|
441
|
+
under Security).
|
|
442
|
+
|
|
443
|
+
The profile `proxies` map + row-level `proxy` /
|
|
444
|
+
`catalog.<id>.proxy` remain available as **default proxies** (used when
|
|
445
|
+
the frontend field is empty). Precedence:
|
|
446
|
+
**frontend settings > profile config > direct**.
|
|
447
|
+
|
|
448
|
+
```yaml
|
|
449
|
+
# example profile-level default proxy (the ⚙ panel can override per row)
|
|
450
|
+
- id: quota-panel
|
|
451
|
+
name: 'dsh-quota-panel'
|
|
452
|
+
config:
|
|
453
|
+
proxies:
|
|
454
|
+
home: http://127.0.0.1:7890 # local proxy http port (clash / v2rayN …)
|
|
455
|
+
catalog:
|
|
456
|
+
openrouter:
|
|
457
|
+
proxy: home # OpenRouter via proxy by default
|
|
458
|
+
providers:
|
|
459
|
+
- id: my-agg
|
|
460
|
+
label: My aggregator
|
|
461
|
+
credential: AGG_API_KEY
|
|
462
|
+
endpoint: https://agg.example # base URL for openai-billing
|
|
463
|
+
format: openai-billing
|
|
464
|
+
proxy: home
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Threshold defaults
|
|
468
|
+
|
|
469
|
+
DeepSeek balance (`balanceTiers {critical: 10, warn: 20, healthy: 50}`):
|
|
470
|
+
|
|
471
|
+
| Balance | Status | Secondary info |
|
|
472
|
+
|---|---|---|
|
|
473
|
+
| `<= 10` | error (red dot + red value) | 建议充值 |
|
|
474
|
+
| `10 < x <= 20` | warn (amber) | 余额紧张 |
|
|
475
|
+
| `20 < x <= 50` | ok | 余额正常 |
|
|
476
|
+
| `> 50` | ok | 余额充足 |
|
|
477
|
+
|
|
478
|
+
OpenCode usage (`high = max(rolling, weekly, monthly)`):
|
|
479
|
+
|
|
480
|
+
| Usage | Status |
|
|
481
|
+
|---|---|
|
|
482
|
+
| `< warnPercent` | ok (green dot, DeepSeek-blue bar) |
|
|
483
|
+
| `>= warnPercent` | warn (amber dot + bar) |
|
|
484
|
+
| `>= errorPercent` | error (red dot + bar) |
|
|
485
|
+
|
|
486
|
+
## Install
|
|
487
|
+
|
|
488
|
+
**Install a released version — not the `main` branch.** `main` receives
|
|
489
|
+
unverified work-in-progress; only tagged releases have passed the CI gates
|
|
490
|
+
(check + boot) and — for stable versions — the human approval gate.
|
|
491
|
+
|
|
492
|
+
**Recommended — the latest stable release (or the latest pre-release for the
|
|
493
|
+
current iteration cycle):**
|
|
494
|
+
|
|
495
|
+
```sh
|
|
496
|
+
# Pin the latest stable release tag (checked on the Releases page)
|
|
497
|
+
dsh plugin --profile web add "github:wenzetan/dsh-quota-panel#v0.8.0"
|
|
498
|
+
|
|
499
|
+
# Or, once the repo secret NPM_TOKEN is configured (see below), by name —
|
|
500
|
+
# npm `latest` always resolves to the last human-approved stable:
|
|
501
|
+
dsh plugin --profile web add dsh-quota-panel
|
|
502
|
+
# Restart `dsh web` (bundle layer and client module graph apply at boot)
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**When you want the latest pre-release** (e.g. testing the current
|
|
506
|
+
`0.8.0-rc.N` iteration):
|
|
507
|
+
|
|
508
|
+
```sh
|
|
509
|
+
# Pin the pre-release tag
|
|
510
|
+
dsh plugin --profile web add "github:wenzetan/dsh-quota-panel#v0.8.0-rc.1"
|
|
511
|
+
# Or from npm under the `next` dist-tag:
|
|
512
|
+
dsh plugin --profile web add dsh-quota-panel@next
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
> **Avoid bare `github:wenzetan/dsh-quota-panel`** (no `#tag`) — it tracks
|
|
516
|
+
> `main` HEAD, which is the testing branch: it may carry unreleased work,
|
|
517
|
+
> fail CI, or break. Only developers iterating on the plugin itself should
|
|
518
|
+
> install from `main`.
|
|
519
|
+
|
|
520
|
+
Refresh the browser page once after installing. Zero npm dependencies (the
|
|
521
|
+
schema library — schemastery + cosmokit, both MIT — is vendored under
|
|
522
|
+
`src/vendor/` with relative-path imports), no `allowBuilds` authorization
|
|
523
|
+
needed.
|
|
524
|
+
|
|
525
|
+
### Release channels & npm publishing (maintainer)
|
|
526
|
+
|
|
527
|
+
Versioning policy — the version string picks the channel:
|
|
528
|
+
|
|
529
|
+
| package.json version | Channel | Gate | GitHub Release | npm dist-tag |
|
|
530
|
+
|---|---|---|---|---|
|
|
531
|
+
| `0.8.0-rc.1` (any `-suffix`) | pre-release | CI only (check + boot) | flagged **pre-release** | `next` |
|
|
532
|
+
| `0.8.0` (plain `X.Y.Z`) | stable | CI **+ human approval** | normal release | `latest` |
|
|
533
|
+
|
|
534
|
+
Workflow:
|
|
535
|
+
|
|
536
|
+
1. **Iterate (automatic)** — bump to `0.8.0-rc.1` and push main. CI runs
|
|
537
|
+
the full gates, **auto-tags** `v0.8.0-rc.1` and publishes the
|
|
538
|
+
pre-release (fast lane, no approval). A pre-release is published under
|
|
539
|
+
the npm `next` dist-tag and can never own `latest` — a reclaim step
|
|
540
|
+
re-claims `latest` to the newest stable if npm ever pointed it at a
|
|
541
|
+
prerelease — so `dsh plugin add dsh-quota-panel` keeps resolving to the
|
|
542
|
+
last verified stable.
|
|
543
|
+
2. **Verify (human)** — install the rc (`dsh plugin --profile web add
|
|
544
|
+
"github:wenzetan/dsh-quota-panel#v0.8.0-rc.1"`, or
|
|
545
|
+
`dsh-quota-panel@0.8.0-rc.1` from npm) and test it for real.
|
|
546
|
+
3. **Promote (manual, required for stable)** — stable versions are NEVER
|
|
547
|
+
auto-tagged. On the Actions page, run the CI workflow with the
|
|
548
|
+
**`rc_tag`** input set to the green pre-release tag (e.g.
|
|
549
|
+
`v0.8.0-rc.1`). The `promote` job verifies that tag's CI run passed on
|
|
550
|
+
exactly that commit, creates the stable twin `v0.8.0` on the same
|
|
551
|
+
commit and dispatches the release run. The stable release job then
|
|
552
|
+
**waits in the `production` environment for a human approval** before
|
|
553
|
+
creating the GitHub Release and publishing to npm `latest`.
|
|
554
|
+
|
|
555
|
+
One-time setup:
|
|
556
|
+
|
|
557
|
+
- **npm token** — create an Automation (or granular) token with publish
|
|
558
|
+
rights to `dsh-quota-panel` (name free as of this writing) and add it as
|
|
559
|
+
the repository secret **`NPM_TOKEN`** (Settings → Secrets and variables →
|
|
560
|
+
Actions). Without it, GitHub Releases still ship; npm steps are skipped.
|
|
561
|
+
- **stable gate** — Settings → Environments → New environment →
|
|
562
|
+
`production` → Required reviewers → add yourself. This is what makes
|
|
563
|
+
"no stable release without human confirmation" enforced rather than
|
|
564
|
+
conventional. (Without the reviewer configured, the stable channel
|
|
565
|
+
publishes without pausing — same as before.)
|
|
566
|
+
|
|
567
|
+
The package declares `dsh.bundle.patch` (host half auto-activates as a
|
|
568
|
+
profile layer) and the `dsh.client` manifest (browser half auto-joins the
|
|
569
|
+
`__DSH_BOOT__` module graph, `immediately: true` prefetched with the shell).
|
|
570
|
+
|
|
571
|
+
## Acknowledgments
|
|
572
|
+
|
|
573
|
+
This plugin builds on community work — thanks to:
|
|
574
|
+
|
|
575
|
+
- [yingjunnan/dsh-deepseek-quota](https://github.com/yingjunnan/dsh-deepseek-quota)
|
|
576
|
+
— the original bottom-right DeepSeek balance card for the DSH Web page
|
|
577
|
+
(auto-refresh + manual refresh); the capsule/card interaction model is
|
|
578
|
+
directly inspired by it.
|
|
579
|
+
- [Ghost011118/dsh-balance-meter](https://github.com/Ghost011118/dsh-balance-meter)
|
|
580
|
+
— DeepSeek account balance and session cost readout for the DSH Web GUI;
|
|
581
|
+
its panel design informed the expanded card layout.
|
|
582
|
+
- [0xsline/awesome-deepseek-harness](https://github.com/0xsline/awesome-deepseek-harness)
|
|
583
|
+
— the community plugin catalog that surfaced the projects above and the
|
|
584
|
+
broader DSH plugin ecosystem.
|
|
585
|
+
- [hanmumuHL/check_balance](https://github.com/hanmumuHL/check_balance) —
|
|
586
|
+
endpoint research (DeepSeek balance API) that informed the catalog.
|
|
587
|
+
- [steipete/CodexBar](https://github.com/steipete/CodexBar) — its provider
|
|
588
|
+
docs (z.ai/GLM coding-plan windows, Kimi Code usage API, MiMo / Qwen /
|
|
589
|
+
Qoder / Doubao auth research) shaped the coding-plan adapters and the
|
|
590
|
+
not-supported list.
|
|
591
|
+
- [zwen64657/glm-plan-usage2](https://github.com/zwen64657/glm-plan-usage2)
|
|
592
|
+
— Rust GLM usage tracker whose monitor-API research (`docs/api-research.md`
|
|
593
|
+
real-world samples) pinned the semantic window mapping: TOKENS_LIMIT
|
|
594
|
+
`unit=3` → 5h, `unit=6` → weekly, TIME_LIMIT → MCP monthly, `percentage`
|
|
595
|
+
as the authoritative field; its Kimi (`window.duration` 300/10080,
|
|
596
|
+
`limit − remaining`) and MiniMax (coding-model row, weekly lane) clients
|
|
597
|
+
informed the matching adapters here (issue #2).
|
|
598
|
+
- [PowerUserZ/OpenTokenUsage](https://github.com/PowerUserZ/OpenTokenUsage)
|
|
599
|
+
— documented the MiniMax `token_plan/remains` response quirks and the
|
|
600
|
+
Kimi Code usage endpoint.
|
|
601
|
+
- [schemastery](https://github.com/shigma/schemastery) and
|
|
602
|
+
[cosmokit](https://github.com/cosmokit/cosmokit) (both MIT) — the schema
|
|
603
|
+
library vendored under `src/vendor/`.
|
|
604
|
+
|
|
605
|
+
## Changelog
|
|
606
|
+
|
|
607
|
+
- **v0.9.2-rc.1** — Volcengine Ark Agent Plan and Coding Plan are now **shown as
|
|
608
|
+
two rows at once** (previously a single row queried Agent Plan first and fell
|
|
609
|
+
back to Coding Plan). The two subscriptions render as two independent
|
|
610
|
+
providers sharing the same AK/SK pair: the Agent row (`volcengine-agent`,
|
|
611
|
+
`GetAFPUsage`, 5h/weekly/monthly) and the Coding row (`volcengine-coding`,
|
|
612
|
+
`GetCodingPlanUsage`, session/weekly/monthly) each query only their own API
|
|
613
|
+
with no cross-fallback; a plan the account has not subscribed to reports its
|
|
614
|
+
own "not subscribed" message on that row. The Coding row's hover title labels
|
|
615
|
+
its rolling window `session:` (it is a session limit, not a 5h window), and a
|
|
616
|
+
migration note for the replaced row/format ids (`volcengine` /
|
|
617
|
+
`volcengine-usage`) was added to the Volcengine troubleshooting section.
|
|
618
|
+
- **v0.8.1-rc.6** — layout fixes from issue #1, reworked: the panel is now
|
|
619
|
+
**draggable** — grab the collapsed capsule or the expanded card header and
|
|
620
|
+
move it anywhere (pointer capture, 5px move threshold so click-to-expand
|
|
621
|
+
still works, viewport-clamped so it stays grabbable, position persisted to
|
|
622
|
+
localStorage with the other settings, restored positions re-clamped on
|
|
623
|
+
resize, "restore defaults" clears it). The default anchor stays
|
|
624
|
+
right/bottom 18px until the first drag. Also injects
|
|
625
|
+
`[class*="overlayLayer"]{z-index:1150 !important}` so the shell overlay
|
|
626
|
+
layer (z-index 20) is no longer covered whole-layer by body-mounted
|
|
627
|
+
third-party fixed panels (z-index 1000+) — the widget stays inside the
|
|
628
|
+
React tree, keeping event delegation intact. Both style tags are removed
|
|
629
|
+
on plugin unload. (rc.5 was briefly auto-released with a fixed 60px bottom
|
|
630
|
+
offset instead of dragging; its tag/release were rolled back — the npm
|
|
631
|
+
version is an orphan pre-release.)
|
|
632
|
+
- **v0.8.1-rc.4** — capsule display mode (issue #2 follow-up): the settings
|
|
633
|
+
panel gains 胶囊显示 / "capsule display" (auto = highest window — the
|
|
634
|
+
default, unchanged behavior / 5h window / weekly window / highest). In
|
|
635
|
+
rolling/weekly modes the collapsed capsule's value, status dot, progress
|
|
636
|
+
bar and 100% caption all follow the chosen window instead of the highest
|
|
637
|
+
one, so a 5h capsule no longer glows warn because the weekly pool sits at
|
|
638
|
+
40%; a plan without the chosen window falls back to the highest. The
|
|
639
|
+
expanded card always shows every window.
|
|
640
|
+
- **v0.8.1-rc.3** — coding-plan adapter fixes (issue #2, cross-checked with
|
|
641
|
+
[glm-plan-usage2](https://github.com/zwen64657/glm-plan-usage2)):
|
|
642
|
+
`zai-coding-quota` maps windows semantically (TOKENS_LIMIT `unit=3` → 5h,
|
|
643
|
+
`unit=6` → weekly, TIME_LIMIT → the MCP monthly lane; unknown units fall
|
|
644
|
+
back to `nextResetTime` ordering) instead of the size heuristic that
|
|
645
|
+
swapped 5h/weekly on plans returning both rows, prefers the `percentage`
|
|
646
|
+
field for every window, and relabels the third slot 搜索 → 月;
|
|
647
|
+
`kimi-coding-usage` matches windows by `window.duration` (300 = 5h,
|
|
648
|
+
10080 = weekly) instead of blind `limits[0]` and computes used as
|
|
649
|
+
`limit − remaining` (the old code read a nonexistent `detail.used`, so
|
|
650
|
+
the 5h window silently dropped); `minimax-remains` prefers the
|
|
651
|
+
`MiniMax-M*` coding model row over whatever comes first and adds the
|
|
652
|
+
weekly window (`current_weekly_total_count > 0`, remaining-side counts).
|
|
653
|
+
- **v0.8.1-rc.1** — first pre-release on the automatic rc pipeline: 100% usage
|
|
654
|
+
caption appends the reset time (当前已使用 100% 等待重置 …); CI reworked
|
|
655
|
+
(reference dsh-llm-newapi): pre-releases auto-tag + publish to npm `next`
|
|
656
|
+
with a `latest` reclaim guard; stable versions require the manual
|
|
657
|
+
`rc_tag` promote workflow.
|
|
658
|
+
- **v0.8.0** — first stable release on the dual-channel pipeline: same
|
|
659
|
+
code as v0.7.3 (which already passed check + boot) plus the install
|
|
660
|
+
guidance rework (released tags / npm latest + next instead of bare main).
|
|
661
|
+
- **v0.7.3** — no unconfigured provider rows: `cordis.patch.yml` no longer
|
|
662
|
+
ships explicit example rows (deepseek / opencode-go), so the settings
|
|
663
|
+
panel lists exactly the providers whose credential resolves (auto
|
|
664
|
+
discovery). The CI boot gate now asserts both directions: the seeded key
|
|
665
|
+
appears, and unconfigured providers do not.
|
|
666
|
+
- **v0.7.2** — web i18n: the panel follows the shell's language setting
|
|
667
|
+
(通用设置 → 语言, `locale.preference`; zh / en) through the
|
|
668
|
+
`ctx.locale` service — all copy (capsule, card, settings panel, errors,
|
|
669
|
+
aria labels, usage windows) ships as zh/en dictionaries registered under
|
|
670
|
+
the `quota-panel` namespace; provider labels are proper nouns kept as-is
|
|
671
|
+
(GLM, MiniMax, Kimi Coding…) with Chinese brand names romanized
|
|
672
|
+
(智谱 → ZhiPu); host catalog labels normalized accordingly
|
|
673
|
+
(SiliconFlow CN, MiniMax Coding CN, ZhiPu GLM). Also: usage reset times
|
|
674
|
+
now show absolute 24h timestamps (下次重置 2026-08-15 14:00, dictionary
|
|
675
|
+
key `nextReset`); usage rows drop the weekly segment when the plan has no
|
|
676
|
+
weekly window and render the search/MCP lane as `-%` when it is unknown
|
|
677
|
+
(no fabricated 0%); the usage caption reads 当前已使用 X%.
|
|
678
|
+
- **v0.7.1** — dual-site SiliconFlow: catalog id `siliconflow` now maps to the
|
|
679
|
+
global site (`api.siliconflow.com`, `$`), new id `siliconflow-cn` maps to
|
|
680
|
+
the China site (`api.siliconflow.cn`, `¥`, ref `SILICONFLOW_CN_API_KEY`);
|
|
681
|
+
balance rows gained a per-row `currency` override (catalog rows, `catalog:`
|
|
682
|
+
overrides, and explicit `providers:` entries); README documents the
|
|
683
|
+
dual-site provider id → endpoint/currency mapping.
|
|
684
|
+
- **v0.7.0** — adopted the org TypeScript tool-bundle template (dsh-plugin-check
|
|
685
|
+
compliant, zero waivers): sources moved to `src/*.ts` compiled into `lib/`
|
|
686
|
+
by `npm run build` (tsc + vendored runtime copy), committed artifacts
|
|
687
|
+
verified current by CI; new `dsh-plugin-check` CI gate (any error or warning
|
|
688
|
+
fails — currently verdict=pass, 0 error / 0 warning); CI check job now
|
|
689
|
+
installs dev dependencies and builds before testing.
|
|
690
|
+
- **v0.6.0** — coding-plan support: new catalog rows 智谱 GLM Coding
|
|
691
|
+
(`ZAI_CODING_CN_API_KEY`), Z.AI GLM Coding (`ZAI_API_KEY`), Kimi Coding
|
|
692
|
+
(`KIMI_API_KEY`), MiniMax Coding global/CN (`MINIMAX_API_KEY` /
|
|
693
|
+
`MINIMAX_CN_API_KEY`); new `zai-coding-quota` (5h/weekly token windows +
|
|
694
|
+
search lane) and `kimi-coding-usage` (5h + weekly request pool)
|
|
695
|
+
adapters; `minimax-remains` rewritten for the real `model_remains`
|
|
696
|
+
response (now a usage row); `zhipu-quota` shows `percentage` when a
|
|
697
|
+
limit carries no `remaining`; usage rows render missing windows as
|
|
698
|
+
`—` (labels from `windowLabels`, no longer hardcoded rolling/weekly/
|
|
699
|
+
monthly).
|
|
700
|
+
- **v0.5.0** — built-in provider catalog + auto discovery (probes
|
|
701
|
+
credential refs; 9 providers on board with zero config); 8 new format
|
|
702
|
+
adapters (incl. one-api/new-api `openai-billing`); `fetch-all`
|
|
703
|
+
contract switched to host-side normalized views (balance / usage / info),
|
|
704
|
+
upstream JSON no longer shipped; per-row HTTP(S) proxy (CONNECT tunnel /
|
|
705
|
+
absolute URI, zero-dependency), **configured in the ⚙ settings panel**
|
|
706
|
+
(localStorage, takes precedence over profile `proxies` / `proxy`);
|
|
707
|
+
new `auto` / `hide` / `proxies` / `catalog` config keys.
|
|
708
|
+
- **v0.4.0** — dual-face refactor: host half moved to a loopback Connection
|
|
709
|
+
RPC channel (`specs` / `fetch-all`) + `Config` schema; browser half
|
|
710
|
+
moved into the `dsh.client` manifest + `shell.overlay` slot (React);
|
|
711
|
+
added the ⚙ settings panel (visibility / refresh interval / thresholds,
|
|
712
|
+
localStorage-persisted).
|
|
713
|
+
- **v0.3.0** — two sizes: collapsed minimal capsule (independent status
|
|
714
|
+
dot + battery-style value per account), click to expand the full card.
|
|
715
|
+
- **v0.2.0** — Harness-native card: design tokens, balance tier
|
|
716
|
+
thresholds, usage progress bar.
|
|
717
|
+
- **v0.1.0** — initial floating panel: server-side quota proxy + page badge.
|
|
718
|
+
|
|
719
|
+
## Security
|
|
720
|
+
|
|
721
|
+
- API keys are resolved host-side via `ctx.credentials` and used only for
|
|
722
|
+
host-side requests to providers; the browser talks exclusively over the
|
|
723
|
+
loopback RPC channel `/dsh-quota-panel`, the `specs` endpoint ships
|
|
724
|
+
render hints only (labels/kinds/thresholds) with no credential or
|
|
725
|
+
endpoint; since v0.5 `fetch-all` ships normalized views only — raw
|
|
726
|
+
upstream JSON stays host-side too.
|
|
727
|
+
- The card builds DOM exclusively with `createElement`/`textContent`;
|
|
728
|
+
API values never touch `innerHTML`; technical errors (401, timeout,
|
|
729
|
+
missing credential, refused proxy) surface only in `title` tooltips or
|
|
730
|
+
inline row text, and one failing row never affects the others.
|
|
731
|
+
|
|
732
|
+
### Known issues & risks (proxy path)
|
|
733
|
+
|
|
734
|
+
The per-row proxy feature has two known risk points you should weigh before
|
|
735
|
+
routing a provider through a proxy:
|
|
736
|
+
|
|
737
|
+
1. **The upstream `Authorization` header is forwarded to the proxy.** When a
|
|
738
|
+
row goes through a proxy, the host sends the request headers — including
|
|
739
|
+
`Authorization: Bearer <key>` — to the proxy server itself: for https
|
|
740
|
+
targets the key rides in the CONNECT request (outside the TLS tunnel), for
|
|
741
|
+
http targets in the absolute-URI request. The proxy operator can therefore
|
|
742
|
+
read every API key routed through it.
|
|
743
|
+
2. **The loopback RPC channel accepts an arbitrary proxy URL per row.** The
|
|
744
|
+
browser-side proxy field is sent to the host in the `fetch-all` payload
|
|
745
|
+
and validated only as http/https. Per the platform contract the channel is
|
|
746
|
+
loopback-only and unauthenticated, so any process that can reach
|
|
747
|
+
`http://127.0.0.1:3080` can POST a proxy override pointing at an
|
|
748
|
+
arbitrary server and have the host send your provider keys to it.
|
|
749
|
+
|
|
750
|
+
**Stay safe:**
|
|
751
|
+
|
|
752
|
+
- **Use a proxy you fully trust — ideally on your own machine**
|
|
753
|
+
(`http://127.0.0.1:7890`, e.g. clash / v2rayN). Never point a row at a
|
|
754
|
+
third-party or public proxy you do not operate: its operator can read your
|
|
755
|
+
keys (see 1).
|
|
756
|
+
- **Use dedicated API keys** for providers queried through a proxy — separate
|
|
757
|
+
from keys used anywhere else, with the least privilege your provider offers
|
|
758
|
+
(billing/balance-only scopes where available), and rotate them if a proxy
|
|
759
|
+
was ever shared or is suspected compromised.
|
|
760
|
+
- **Run `dsh web` only on machines you trust.** The loopback RPC surface is
|
|
761
|
+
unauthenticated by design (see 2); don't expose the port to other users or
|
|
762
|
+
the network.
|
|
763
|
+
- Proxy URLs are stored in browser localStorage as entered — prefer proxies
|
|
764
|
+
without embedded credentials, or a dedicated local proxy that needs none.
|
|
765
|
+
|
|
766
|
+
## TODO
|
|
767
|
+
|
|
768
|
+
- **Usage-only providers** (OpenAI / Anthropic / Together / Groq / Mistral /
|
|
769
|
+
Cohere / DashScope / Baichuan) as a separate usage row kind showing
|
|
770
|
+
monthly spend instead of balance (Anthropic Admin API and OpenAI usage
|
|
771
|
+
API first).
|
|
772
|
+
- socks5 proxy support (HTTP/HTTPS only today).
|
|
773
|
+
|
|
774
|
+
## Local development
|
|
775
|
+
|
|
776
|
+
```sh
|
|
777
|
+
# Sources live in src/*.ts (org tool-bundle template): tsc compiles them
|
|
778
|
+
# into lib/ (declarations included) and scripts/build.mjs copies the
|
|
779
|
+
# vendored schema runtime into lib/vendor/. devDependencies are build-only
|
|
780
|
+
# — runtime stays zero-dependency.
|
|
781
|
+
npm install
|
|
782
|
+
npm run build
|
|
783
|
+
|
|
784
|
+
# After editing src/, rebuild and COMMIT lib/ — github: installs run from
|
|
785
|
+
# the committed artifacts (CI's "Committed artifacts are current" step
|
|
786
|
+
# rejects a stale lib/).
|
|
787
|
+
|
|
788
|
+
# Dual-face check: host RPC contract + catalog discovery/proxy engine
|
|
789
|
+
# (exercised against real local servers) + client slot/settings surfaces
|
|
790
|
+
node scripts/test-page-script.mjs
|
|
791
|
+
|
|
792
|
+
# Health check with @deepseek-ai/dsh-plugin-check (same gate as CI; fails
|
|
793
|
+
# on any error or warning). One-off deps dir, then the gate script:
|
|
794
|
+
mkdir -p /tmp/pc-deps && cd /tmp/pc-deps && npm init -y >/dev/null
|
|
795
|
+
npm install --no-audit --no-fund --ignore-scripts \
|
|
796
|
+
github:omdsh-dev/dsh-plugin-check \
|
|
797
|
+
@deepseek-ai/dsh-tools @deepseek-ai/dsh-invariants @deepseek-ai/cordis
|
|
798
|
+
cd /path/to/dsh-quota-panel
|
|
799
|
+
PLUGIN_CHECK_DEPS=/tmp/pc-deps node scripts/plugin-check.mjs .
|
|
800
|
+
|
|
801
|
+
# To upgrade the vendored schema library: replace the two runtime files
|
|
802
|
+
# under src/vendor/ and rewrite the cosmokit import on line 1 of
|
|
803
|
+
# schemastery.mjs to "./cosmokit.js", then rebuild.
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
## Contributing
|
|
807
|
+
|
|
808
|
+
**Pull requests are welcome!** 🎉 Whether it is a new provider adapter
|
|
809
|
+
(the catalog + auto-discovery pipeline makes adding one mostly declarative
|
|
810
|
+
— see the [Volcengine Ark PR][pr4] for a complete reference
|
|
811
|
+
implementation, including the first AK/SK-signed provider), a bug fix, a
|
|
812
|
+
UI tweak, or docs improvements.
|
|
813
|
+
|
|
814
|
+
Before opening a PR:
|
|
815
|
+
|
|
816
|
+
1. Follow [Local development](#local-development) to build and run the
|
|
817
|
+
dual-face checks (`npm run build && npm test`) — CI rejects a stale
|
|
818
|
+
`lib/`, so rebuild and commit the artifacts after editing `src/`;
|
|
819
|
+
2. Add test assertions in `scripts/test-page-script.mjs` covering the new
|
|
820
|
+
behavior (mocked upstream + normalized row view);
|
|
821
|
+
3. Update both `README.md` and `README.zh.md` if the change is
|
|
822
|
+
user-facing.
|
|
823
|
+
|
|
824
|
+
For provider requests without an OpenAPI endpoint (Cookie / CLI-only
|
|
825
|
+
plans), open an issue first so we can discuss feasibility.
|
|
826
|
+
|
|
827
|
+
[pr4]: https://github.com/wenzetan/dsh-quota-panel/pull/4
|
|
828
|
+
|
|
829
|
+
## License
|
|
830
|
+
|
|
831
|
+
MIT
|