@konnga/dsh-hud 0.9.1 → 0.10.0

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.en.md CHANGED
@@ -8,7 +8,7 @@ A read-only DeepSeek Harness status panel: branch, ahead/behind, changed files a
8
8
 
9
9
  > Screenshots in this file were taken in the **Chinese UI**; the plugin's own labels follow your UI language, and every quoted label below is the English one you would actually see.
10
10
 
11
- ![The three capsules above the composer: Git status on the left, tools & skills in the middle, this session's running time on the right](docs/images/capsules.webp)
11
+ ![The capsule row above the composer: Git status on the left, tools & skills in the middle, this session's running time on the right (the balance capsule came later; the screenshot predates it)](docs/images/capsules.webp)
12
12
 
13
13
  ## Install
14
14
 
@@ -26,7 +26,8 @@ For local development, point the profile at the checkout (host changes apply imm
26
26
  | --- | --- | --- |
27
27
  | **Git capsule** | Workspace name, branch, ahead/behind, change counts, files this session touched | Leftmost of the row above the composer (click to open the panel) |
28
28
  | **Tools capsule** | What is running now (live seconds), per-tool call counts, skills used | Same row, middle |
29
- | **Session timer capsule** | How long this session has been running; hover for model/tool wall time and turns | Same row, rightmost |
29
+ | **Session timer capsule** | How long this session has been running; hover for model/tool wall time and turns | Same row, rightmost (after the balance) |
30
+ | **Balance capsule** | **Follows the current session's model provider**: the official account for the platform ledger, that relay's own balance for a relay; `⚠` low-balance warning, **click to paste a dashboard token** (that is how the relay's current balance becomes readable), hover shows balance / API requests / account usage / key usage | Same row, before the timer |
30
31
  | **Panel** | Change overview, change list, recent commits, tags; click any row for its diff | Click the Git capsule |
31
32
  | **Diffs, commit detail, images** | Unstaged / staged, word wrap, copy diff; image side-by-side + zoom | Right of the panel, draggable |
32
33
  | **Per-turn image changes** | Images touched this turn: click to view one, `⇄` for before/after | Next to the built-in "edited N files" card |
@@ -39,10 +40,10 @@ For local development, point the profile at the checkout (host changes apply imm
39
40
  ### Capsules (the row above the composer)
40
41
 
41
42
  ```
42
- dsh-hud · git:(main*) ↑1 ↓2 M4 A1 ?1 │ ●3 │ ✓ bash ×16 │ … │ ⏱ 2h 14m
43
+ dsh-hud · git:(main*) ↑1 ↓2 M4 A1 ?1 │ ●3 │ ✓ bash ×16 │ … │ ⏱ 2h 14m │ ¥128.40+
43
44
  ```
44
45
 
45
- > Hover tips on all three capsules are **bubbles the plugin draws itself** — not the browser's native tooltip, which never showed up in the real app and whose delay and styling we cannot control anyway.
46
+ > Hover tips on the capsules are **bubbles the plugin draws itself** — not the browser's native tooltip, which never showed up in the real app and whose delay and styling we cannot control anyway.
46
47
 
47
48
  **Git** (leftmost)
48
49
 
@@ -63,7 +64,7 @@ dsh-hud · git:(main*) ↑1 ↓2 M4 A1 ?1 │ ●3 │ ✓ bash ×16 │ …
63
64
  - After `skills:` come skill *names* (so you can tell which skill, not just how many calls).
64
65
  - At most 3 tools + 2 skills are laid out; the rest collapse into `+k`. Hover for the full list and the latest results.
65
66
 
66
- **Session timer** (rightmost)
67
+ **Session timer** (rightmost, after the balance capsule)
67
68
 
68
69
  ```
69
70
  ⏱ 2h 14m
@@ -85,6 +86,201 @@ Here it is on a real machine:
85
86
 
86
87
  *The same bubble is used by the Git and tools capsules (the Git one is multi-line: full path / upstream / per-type counts).*
87
88
 
89
+ **Account balance** (before the timer)
90
+
91
+ ```
92
+ ¥128.40+
93
+ ```
94
+
95
+ **Balance** (before the timer) — it shows the ledger the current session is actually spending from:
96
+
97
+ | The session uses | Capsule shows | Example |
98
+ | --- | --- | --- |
99
+ | DeepSeek Platform account (`deepseek-account`) | the platform wallet balance | `¥128.40+` |
100
+ | Official API key (`deepseek-official`) | the wallet from `GET /user/balance` | `¥128.60+` |
101
+ | Third-party relay readable with an API key | whatever remaining quota that relay reports | `$2,265.33` |
102
+ | Relay that exposes the remainder only to a web session (web token configured) | the account balance from the site's own console | `$137.48` |
103
+ | Relay reports "unlimited" / no web token configured | **used** (remaining unknown, never invented) | `used $1.24` |
104
+ | Unrecognised relay (neither protocol answers) | the site id plus `--` (unreadable, said plainly) | `api404 --` |
105
+ | Not resolved yet (new session / just switched / host not ready) | `--` (**never** the platform account balance as a stand-in) | `--` |
106
+
107
+ With a dashboard credential configured, a relay capsule's hover looks like this — four readings coloured by meaning
108
+ (see below for why):
109
+
110
+ ![Hovering a relay balance capsule: the bubble reads the site name happycoding, then "balance $137.48" in green, "API requests 3,514" in blue, "$7.51 used on this account" in amber, "$1.24 used on this key" in amber, and "updated 2026-10-27 16:45" in grey; below it the capsule itself "$137.48" and the timer capsule "⏱ 7d 4h 4m"](docs/images/balance-hover.webp)
111
+
112
+ **DeepSeek account balance** (hover)
113
+
114
+ ```
115
+ DeepSeek account balance
116
+ ¥8.60
117
+ Bonus balance ¥2.00
118
+ -¥0.30 since last refresh (2026-10-27 08:45)
119
+ updated 2026-10-27 08:50
120
+ ```
121
+
122
+ The tip holds **readings only** — no usage instructions, no warning prose: the old "click to refresh / right-click for
123
+ the usage page / balance below … topping up" lines were removed (they pushed the block to seven lines and squeezed out
124
+ the numbers that actually move). Both actions remain: **click = refresh now**, **right-click = open the Platform usage
125
+ page** in a new tab. A low balance is signalled by the `⚠` and warning colour on the capsule alone, never restated in the tip.
126
+
127
+ - The number comes from DSH's own account API (`remote.account.getBalance` — the same data as Settings → Account). Read-only, never persisted.
128
+ - Formatting matches DSH's account card rule for rule: `¥0.00` / `<¥0.01` (less than a cent) / `¥1,234.56` (thousands separators, truncated toward zero at two decimals) / a leading `-` for negatives. The currency symbol comes from the amount itself (`¥` / `$`) and is never drawn twice.
129
+ - A trailing `+` means there is also bonus balance (the amount is in the hover tip). A trailing `?` in the warning colour means the last refresh failed and what you see is the previous number (the tip says from when).
130
+ - **Low-balance warning** — below the threshold (¥10 by default) the capsule gains a `⚠` and the amount turns warning-coloured (**on the capsule only — no text**).
131
+ The threshold can be overridden with the localStorage key `dsh-hud.balanceLow` (write a decimal number, e.g. `localStorage.setItem('dsh-hud.balanceLow', '50')`); reload the page to apply.
132
+ - **Change since last refresh** — the hover tip shows something like "-¥0.30 since last refresh (22:37)". This is the difference between **our own two samples**, not Platform's "today's spend";
133
+ it does not accumulate across restarts (we do not know what happened in between), which is why the wording says "since last refresh".
134
+ The subtraction aligns decimals and works on integers, so `8.6 - 8.3` is exactly `0.30` — never `0.2999999999999998`.
135
+ - It refreshes every 5 minutes (it costs a real Platform request, and the balance only moves by the minute); **click** to refresh immediately.
136
+
137
+ ### Relay account balance: why some sites can only report usage
138
+
139
+ The new-api family (`ur` / `muyuan` / `happycoding` / `ccwu` all are) splits its data behind two different auth schemes:
140
+
141
+ | Endpoint | Accepts | Returns | Measured (`ur`) |
142
+ | --- | --- | --- | --- |
143
+ | `GET /dashboard/billing/subscription` | ✅ API key | the quota ceiling | `hard_limit_usd: 100000000` (a **placeholder**) |
144
+ | `GET /dashboard/billing/usage` | ✅ API key | cumulative usage of **this key** | `$479.44` |
145
+ | `GET /api/user/self` | ❌ **dashboard credential only** (PAT / login session; behind `UserAuth()`) | **account** balance + account cumulative usage + request count | `$855.69` / `$526.66` / `3685` |
146
+ | `GET /api/usage/token/` | ✅ API key | this key's own quota view (`unlimited_quota: true` here) | `total_available: -199583909` |
147
+
148
+ So the **account balance** (the console's "current balance") has exactly one readable path, and it rejects `sk` keys.
149
+ To see it, configure a **dashboard credential** for that site — **click the balance capsule** and a small panel appears:
150
+
151
+ ```
152
+ ┌─ Add a dashboard access token ────── ur ─┐
153
+ │ This site only exposes its current │
154
+ │ balance to a dashboard session (API │
155
+ │ keys cannot read it). Create an access │
156
+ │ token with the profile:read scope, then │
157
+ │ paste it here. │
158
+ │ [••••••••••••••••••••] │
159
+ │ Will be written to UR_ACCESS_TOKEN │
160
+ │ [Save] [Cancel] │
161
+ └──────────────────────────────────────────┘
162
+ ```
163
+
164
+ - Where to get it: the site's "personal settings → access tokens", with the **`profile:read`** scope (long-lived).
165
+ Older deployments without that page also accept a login session token copied from the browser's `user.token` (expires).
166
+ - **No editing of `~/.dsh/.credentials.yaml`**: the reference name (`UR_ACCESS_TOKEN`) is derived from `UR_API_KEY`,
167
+ and the write goes through DSH's credentials service. Making users hand-edit that file turns "configure a token" into
168
+ "edit a config file" — one bad indent and the whole credential store stops parsing.
169
+ - The token is **never echoed**: a `password` input, never in localStorage, never in logs, never in the snapshot,
170
+ and the host never sends it back to the browser (the response carries only the reference name).
171
+ - Saving refetches immediately (bypassing the host's 60-second cache) and the panel closes itself.
172
+ - When the token expires (new device, sign-out, site reset) it **falls back to the usage path automatically**.
173
+ Sites without a credential keep reporting usage; a `ur —` capsule is the "no credential yet" state and opens the same panel.
174
+
175
+ - **No hardcoded conversion rate**: `quota` is an integer; dividing by the `quota_per_unit` the site publishes in `/api/status`
176
+ (measured `500000` on `ur`) gives the amount, and non-USD sites are converted with their own published `usd_exchange_rate`.
177
+ Only when the scale is unreadable does it fall back to new-api's default, and that fallback is logged.
178
+ (All four sites report `quota_display_type: USD`, so the non-USD branch is defensive code for them.)
179
+ - **Cross-checked digit for digit** (two independent passes, hours apart, each of the four numbers matching its own scale):
180
+ - `ur`: `427845000 / 500000 = $855.69`, `263330000 / 500000 = $526.66`, `request_count = 3685`;
181
+ - `happycoding` (the site in the screenshot above): `68725535 / 500000 = $137.45`, `3774465 / 500000 = $7.55`,
182
+ `request_count = 3525`, `total_usage = 127.0872 cents = $1.27` — the screenshot read `$137.48 / $7.51 / 3,514 / $1.24`,
183
+ and the difference is the usage that actually happened in those minutes (balance down, the other three up),
184
+ **not a conversion error**.
185
+
186
+ ### When the capsule shows `--`
187
+
188
+ The capsule only draws a reading it actually has. When it has none — not yet, or not readable —
189
+ it draws `--` and **never** substitutes the platform account balance: that number may belong to a
190
+ different ledger than the one this session spends, so filling it in would report an unrelated figure.
191
+
192
+ | State | Capsule |
193
+ | --- | --- |
194
+ | Reading available | `$742.23` |
195
+ | Site known, unreadable (e.g. `api404`) | `api404 --` |
196
+ | Site not even known yet (new session, just switched, host warming up) | `--` |
197
+ | Host says the provider's ledger *is* the platform account | platform balance `⚠ ¥8.60` |
198
+
199
+ Switching sessions clears the ledger **synchronously**: the previous ledger is gone the instant the new
200
+ session binds (`unknown` → `--`), then the new session's model provider is queried. So a switch never
201
+ leaves the previous session's site and amount on screen — that is another ledger's money.
202
+
203
+ ### The two "used" figures are two different ledgers
204
+
205
+ Once a dashboard credential is configured, the hover lists **account** and **this key** usage side by side — they differ:
206
+
207
+ ```
208
+ happycoding
209
+ balance $137.48 ← quota of /api/user/self ÷ quota_per_unit
210
+ API requests 3,514 ← request_count from the same response (right under the balance:
211
+ it is the only count on this screen, not money)
212
+ $7.51 used on this account ← used_quota from the same response (whole account)
213
+ $1.24 used on this key ← total_usage of /dashboard/billing/usage (this key only)
214
+ updated 2026-10-27 16:45
215
+ ```
216
+
217
+ On one site the two can differ substantially (here: `$7.51` on the account vs `$1.24` on this key — the gap is other keys
218
+ on the same account; on `ur` it was `$526.66` vs `$479.44`), so they are **two fields and two lines**, not one field behind
219
+ a flag — collapsing them would throw away a real reading.
220
+
221
+ **Why three colours**: this screen holds three amounts and one count, so they are separated by meaning —
222
+
223
+ | Colour | What uses it | Why |
224
+ | --- | --- | --- |
225
+ | green | balance | the common reading of "how much is left" |
226
+ | blue | API requests | it is a **count**, not money; a third amount in the same colour reads as one |
227
+ | amber | both "used" lines | a category colour ("this is spending"), **not** a warning |
228
+ | grey | labels / site name / updated-at | timestamps and labels are not readings; colouring them means everything is coloured |
229
+
230
+ All colours come from DSH's own theme tokens (`state-success-primary` / `state-business-primary` / `state-warn-primary`),
231
+ which carry separate values for light and dark themes — no hardcoded hex.
232
+
233
+ The line order is `balance → API requests → account usage → this key's usage`: the request count sits right under the
234
+ balance because it is the only **count** on the screen rather than an amount, and sandwiched between two amounts it reads
235
+ like a third one.
236
+
237
+ ### "Used" is not "this month"
238
+
239
+ These sites **ignore `start_date` / `end_date`** (measured across four sites and four windows — identical answers),
240
+ so the figure is cumulative. Since the range has no effect, the request asks for the full history and the label says cumulative —
241
+ never a monthly label hinting at monthly arithmetic.
242
+
243
+ ### Balance for the current session's model provider
244
+
245
+ Switch models inside one session and you switch ledgers — so the capsule follows **the provider the current model belongs to**:
246
+
247
+ - **Which provider**: the host reads the session's `modelSelection` projection (`next ?? lastUsed`, so a model just picked in the menu counts before the first message is sent).
248
+ - **Where to query**: the `baseURL` and credential reference are read live from the **Settings → Models** projection — no second copy that could drift from the user's edits.
249
+ - **Credential**: resolved once from DSH's `credentials` service and sent **only to that provider's own baseURL**; never logged, never returned to the browser, never persisted.
250
+ - **Three protocols** (measured 2026-10-27 against a real local configuration):
251
+
252
+ | Source | Endpoint | Fields | Measured |
253
+ | --- | --- | --- | --- |
254
+ | Platform account | DSH `remote.account.getBalance` | wallet array | already in use; the host does not re-fetch |
255
+ | Official API key | `GET {origin}/user/balance` | `balance_infos[].total_balance` (+ `granted_balance`) | `200`, `-0.19 CNY` |
256
+ | Relay (one-api / new-api family) | `GET {base}/dashboard/billing/subscription` + `/usage` | `hard_limit_usd` (**remaining**) + `total_usage` (**cents**) | `muyuan` → remaining `2265.33`, used `212.74` |
257
+
258
+ Three things the implementation states plainly rather than papering over:
259
+
260
+ 1. **`hard_limit_usd` is remaining quota, not a total** — so used is **not** subtracted again. Writing `limit - used` would understate the balance.
261
+ 2. **`total_usage` is in cents** — measured `21274.26` matches the site's `$212.74`.
262
+ 3. **`1e8` is an "unlimited" sentinel** — measured on `ur` / `ccwu` / `happycoding`: two probes both returned `100000000` while real usage was climbing. Remaining quota is then **not computable**, so the capsule reports "used" rather than passing off `1e8 - used` as a balance.
263
+
264
+ Boundaries:
265
+
266
+ - An unrecognised relay (measured `api404`: both paths 404/502) reports `unsupported` and the capsule renders `api404 --`.
267
+ **No number is drawn** (it never falls back to the platform account's balance — that is a different ledger), but the capsule does not vanish either:
268
+ a `—` is a reading ("this ledger can't be read"), while a blank is just a doubt that sends people off to check logs and reinstall.
269
+ - No currency conversion: relay fields are named `*_usd` but some sites account in CNY 1:1 internally; only the site's own stated figures are repeated.
270
+ - **Some sites expose remaining quota only to a web session** (measured on `ur`: `/api/user/self` answers `401 AUTH_UNAUTHORIZED`
271
+ while `/dashboard/billing/*` is readable with an API key). The capsule then reports usage only instead of pretending to know the remainder —
272
+ it neither guesses nor rides your web session (that would mean private requests with your account credentials, broken by any site change).
273
+ - Caching: 60 s client TTL, 60 s per-provider host cache, 8 s request timeout. Relays rate-limit aggressive polling, and that is how "can't read the balance" actually happens.
274
+ - The client re-asks every 30 s (actual requests are gated by the TTL: 60 s on success, 30 s on failure): the first read often lands before the session projection is ready, and waiting five minutes would leave the capsule blank for that long.
275
+ - A failed read keeps the previous numbers (a network blip must not make the capsule vanish); switching sessions re-reads immediately instead of reusing the previous session's ledger.
276
+ - **Nothing to show, nothing drawn**: signed out, no account plugin, or no wallet at all — the capsule simply does not appear. It never fakes `¥0.00`.
277
+
278
+ > Why spend totals are not in the capsule: DSH's account API returns **wallet balances only** (`getBalance`) plus the
279
+ > account projection — it carries no spending numbers at all; that data exists only on DeepSeek's own usage page
280
+ > (**right-click the capsule** to go straight there). The plugin does not scrape Platform-internal endpoints
281
+ > (which needs the account credential and breaks whenever Platform changes), nor does it multiply tokens by a
282
+ > self-invented price to produce a number that looks precise but is not.
283
+
88
284
  ### Panel (click the Git capsule)
89
285
 
90
286
  ![The panel: change overview and this session's changes, change list, recent commits and tags on the left; the selected commit's files in the middle; the diff on the right](docs/images/panel.webp)
@@ -165,11 +361,14 @@ With the composer empty, `↑` brings back your previous prompt; keep pressing t
165
361
 
166
362
  ## Development
167
363
 
168
- - `lib/index.js` — the host half (runs git, serves `/dsh-hud-rpc`).
169
- - `lib/client.js` — the browser half (**no build step**, hand-written JS, depends only on the seed module `react`).
170
- - `npm test` — the whole suite (`node --test test/*.test.mjs`). No dependencies to install.
364
+ - `src/host/**` — the host half's source (TypeScript; runs git, serves `/dsh-hud-rpc`), compiled file-by-file by `tsc` into `lib/host/**`.
365
+ - `src/client/**` — the browser half's source (TypeScript; depends only on the seed module `react`); `lib/client.js` is a **build artifact** (rolldown), because DSH loads exactly one classic script.
366
+ - Convention: `src/` is all source, `lib/` is all artifacts — `pnpm run build` rebuilds both halves (`tsc -p tsconfig.host.json` + `rolldown`), and artifacts are committed together with the source (DSH and the local profile read `lib/` directly).
367
+ - `pnpm test` — typecheck (both halves are under **full `strict`**) + the whole suite.
368
+ - `pnpm run format` — Prettier formats the sources (`.prettierrc.json`; `lib/` is artifacts and is not formatted); `pnpm run format:check` only checks.
369
+ - The package manager is pnpm: `pnpm-lock.yaml` is committed, and `pnpm-workspace.yaml` disables `autoInstallPeers` because DSH provides those client services at runtime.
171
370
  - `docs/dock-slot-host.md` — a proposal for turning the row above the composer into a registrable inline host.
172
- - **Releasing**: `npm run tag && npm publish`. `prepublishOnly` runs the tests and then verifies version ↔ git tag (clean tree, `v<version>` exists and points at HEAD); a mismatch blocks the publish.
371
+ - **Releasing**: `pnpm run tag && pnpm publish`. `prepublishOnly` runs the tests and then verifies version ↔ git tag (clean tree, `v<version>` exists and points at HEAD); a mismatch blocks the publish.
173
372
 
174
373
  ## License
175
374
 
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # dsh-hud
2
2
 
3
- [![release](https://img.shields.io/npm/v/%40konnga%2Fdsh-hud?label=release&color=fb8c00)](https://www.npmjs.com/package/@konnga/dsh-hud) [![node](https://img.shields.io/badge/node-%3E%3D20-43853d?logo=nodedotjs&logoColor=white)](#安装) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
3
+ [![release](https://img.shields.io/npm/v/%40konnga%2Fdsh-hud?label=release&color=fb8c00)](https://www.npmjs.com/package/@konnga/dsh-hud) [![node](<https://img.shields.io/badge/node-%3E%3D20-43853d?logo=nodedotjs&logoColor=white>)](#安装) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
4
 
5
5
  [English](./README.en.md) | **中文**
6
6
 
7
7
  DeepSeek HUD 状态面板:分支、ahead/behind、改动清单与差异,外加「这个会话改过哪些文件、审到哪了、跑了什么工具」。
8
8
 
9
- ![输入框上方的三枚胶囊:左边是 Git 状态,中间是工具与技能,最右边是本次会话运行时长](docs/images/capsules.webp)
9
+ ![输入框上方的胶囊行:左边是 Git 状态,中间是工具与技能,右边是本次会话运行时长(图中的余额胶囊是后加的,截图尚未更新)](docs/images/capsules.webp)
10
10
 
11
11
  ## 安装
12
12
 
@@ -20,27 +20,28 @@ DeepSeek HUD 状态面板:分支、ahead/behind、改动清单与差异,外
20
20
 
21
21
  ## 功能总览
22
22
 
23
- | 功能 | 一句话 | 在哪儿 |
24
- | ------------------------------ | ----------------------------------------------------------------------- | ------------------------------------ |
25
- | **Git 胶囊** | 工作区名、分支、ahead/behind、改动计数、本会话改过几个文件 | 输入框上方那一行,最左(点它开面板) |
26
- | **工具胶囊** | 正在跑什么(实时秒数)、各工具调用次数、用过的技能 | 同一行,中间 |
27
- | **会话计时胶囊** | 本次会话已运行多久;悬停给模型 / 工具墙钟与轮次 | 同一行,最右 |
28
- | **面板** | 改动概览、改动清单、最近提交、标签;点任意一行看差异 | 点 Git 胶囊 |
29
- | **差异与提交明细** | 未暂存 / 已暂存、自动换行、复制差异;图片并排对比 + 缩放 | 面板右侧,可拖动 |
30
- | **轮尾「本轮图片改动」** | 这一轮动过的图:点开看单张,`⇄` 看前后对比 | 内置「已编辑 N 个文件」卡片旁 |
31
- | **侧栏会话行** | 悬停或点 `⋯` 展开:git 摘要 + 本会话归因 | 侧栏每个会话行 |
32
- | **键盘** | 面板内 `↑↓` `Enter` `[ ]` `n p` `Space` `v` `r` `Esc` | 面板激活后 |
33
- | **输入框 `↑` 召回** | 空输入框按 `↑` 带回上一条 prompt | 输入框里 |
23
+ | 功能 | 一句话 | 在哪儿 |
24
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ |
25
+ | **Git 胶囊** | 工作区名、分支、ahead/behind、改动计数、本会话改过几个文件 | 输入框上方那一行,最左(点它开面板) |
26
+ | **工具胶囊** | 正在跑什么(实时秒数)、各工具调用次数、用过的技能 | 同一行,中间 |
27
+ | **会话计时胶囊** | 本次会话已运行多久;悬停给模型 / 工具墙钟与轮次 | 同一行,最右(余额之后) |
28
+ | **余额胶囊** | **跟着当前会话的模型来源走**:用官方账号就查账号余额,用中转站就查那家的余额;低余额 `⚠` 提醒,**点一下可填后台令牌**(拿得到"当前余额"),悬停给余额 / API 请求 / 账号已用 / 本 Key 已用 | 同一行,计时之前 |
29
+ | **面板** | 改动概览、改动清单、最近提交、标签;点任意一行看差异 | 点 Git 胶囊 |
30
+ | **差异与提交明细** | 未暂存 / 已暂存、自动换行、复制差异;图片并排对比 + 缩放 | 面板右侧,可拖动 |
31
+ | **轮尾「本轮图片改动」** | 这一轮动过的图:点开看单张,`⇄` 看前后对比 | 内置「已编辑 N 个文件」卡片旁 |
32
+ | **侧栏会话行** | 悬停或点 `⋯` 展开:git 摘要 + 本会话归因 | 侧栏每个会话行 |
33
+ | **键盘** | 面板内 `↑↓` `Enter` `[ ]` `n p` `Space` `v` `r` `Esc` | 面板激活后 |
34
+ | **输入框 `↑` 召回** | 空输入框按 `↑` 带回上一条 prompt | 输入框里 |
34
35
 
35
36
  ## 你会看到什么
36
37
 
37
38
  ### 胶囊(输入框上方那一行)
38
39
 
39
40
  ```
40
- dsh-hud · git:(main*) ↑1 ↓2 M4 A1 ?1 │ ●3 │ ✓ bash ×16 │ … │ ⏱ 2h 14m
41
+ dsh-hud · git:(main*) ↑1 ↓2 M4 A1 ?1 │ ●3 │ ✓ bash ×16 │ … │ ⏱ 2h 14m │ ¥128.40+
41
42
  ```
42
43
 
43
- > 三枚胶囊的**悬停提示是插件自己画的气泡**(不用浏览器原生 tooltip —— 它在真机上不出现,延迟与样式也控制不了)。
44
+ > 几枚胶囊的**悬停提示是插件自己画的气泡**(不用浏览器原生 tooltip —— 它在真机上不出现,延迟与样式也控制不了)。
44
45
 
45
46
  **Git**(最左)
46
47
 
@@ -61,7 +62,7 @@ dsh-hud · git:(main*) ↑1 ↓2 M4 A1 ?1 │ ●3 │ ✓ bash ×16 │ …
61
62
  - `skills:` 后面是技能名(看得出是哪一个,不是"技能调了几次")。
62
63
  - 最多铺 3 个工具 + 2 个技能,其余收成 `+k`;悬停里有完整清单与最近几条结果。
63
64
 
64
- **会话计时**(最右)
65
+ **会话计时**(最右,余额胶囊之后)
65
66
 
66
67
  ```
67
68
  ⏱ 2h 14m
@@ -83,92 +84,141 @@ dsh-hud · git:(main*) ↑1 ↓2 M4 A1 ?1 │ ●3 │ ✓ bash ×16 │ …
83
84
 
84
85
  *同一套气泡也用在 Git 与工具胶囊上(Git 那颗是多行的:完整路径 / 上游 / 各类型计数)。*
85
86
 
86
- ### 面板(点 Git 胶囊打开)
87
+ **余额**(计时之前)—— 它画的是**当前会话真正在用的那个账本**:
87
88
 
88
- ![面板:左边是改动概览与本会话改了哪些、改动清单、最近提交、标签;中间是这次提交的明细;右边是差异](docs/images/panel.webp)
89
+ | 会话用的是 | 胶囊显示 | 例 |
90
+ | -------------------------------------------------- | -------------------------------- | ---------------- |
91
+ | DeepSeek 平台账号(`deepseek-account`) | 平台账号钱包余额 | `¥128.40+` |
92
+ | 官方 API Key(`deepseek-official`) | `GET /user/balance` 的钱包 | `¥128.60+` |
93
+ | 第三方中转站,API Key 就能读到剩余 | 那家自己报的剩余额度 | `$2,265.33` |
94
+ | 第三方中转站,剩余额只给网页登录态(配了网页令牌) | 账号余额(站点后台那个数) | `$137.48` |
95
+ | 中转站把额度写成"无限"占位 / 没配网页令牌 | **已用**(剩余未知,不编) | `已用 $1.24` |
96
+ | 认不出的站(两套协议都不通) | 站名 + `--`(读不到就直说) | `api404 --` |
97
+ | 还没查出来(新会话 / 刚换会话 / 宿主还没就绪) | `--`(**不拿官方账号余额顶上**) | `--` |
89
98
 
90
- *上图是「看某次提交」时的样子。标题栏可拖动,`⟳` 刷新 · `⌖` 复位位置 · `✕` 收起。*
99
+ 配好后台凭据之后,中转站那一枚的悬停长这样 —— 四个读数按语义上色(下面说为什么):
91
100
 
92
- 1. **仓库路径 · 上游 · 短 oid**
93
- 2. **改动概览** —— 按类型分组的彩色计数;干净时写「工作区干净」。
94
- 3. **本会话改了哪些** —— 一行计数加开关:`只看本会话改的`、`只看最近一轮`;有审阅标记时还有 `已审 3/8` 与 `清空已审`。
95
- 4. **改动清单** —— 每行 `标记 状态字母 路径`,重命名读作 `旧 → 新`。
96
- - 行首 `●` 确定改过 · `◐` 可能相关(从命令里看出来,悬停给出那条命令)· `○` 与本会话无关。
97
- - 行尾 `○` / `✓` 是审阅标记,点一下或按 `v` 切换;`✓` 变黄说明改动又变了,需要重看。
98
- - 点任意一行看这个文件的差异。
99
- 5. **最近提交** —— `短sha 标题 [HEAD] 2026-10-16 14:18`;点一行看这次提交,再点一次收起。
100
- 6. **标签** —— 新的在前;点一行看它指向的提交。没有标签时写一行「这个仓库还没有标签」。
101
+ ![悬停中转站余额胶囊:气泡里依次是站名 happycoding、「余额 $137.48」(绿色)、「API 请求 3,514」(蓝色)、「账号已用 $7.51」(琥珀色)、「本 Key 已用 $1.24」(琥珀色)、「更新于 2026-10-27 16:45」(灰色);下方是胶囊本身「$137.48」与旁边的计时胶囊「⏱ 7d 4h 4m」](docs/images/balance-hover.webp)
101
102
 
102
- 时间一律是**完整年月日 + 时分**(本地时区)。
103
+ **DeepSeek 平台账号**的悬停(中转站那一路换成上面那五行,没有赠送余额那两行):
103
104
 
104
- ### 差异、提交明细与图片
105
-
106
- - **看差异**:两栏,工具栏是 `未暂存` / `已暂存` / `自动换行` / `复制差异`;点 hunk 头可折叠那一段。
107
- - **看提交**:三栏 —— 面板 │ 这次提交 │ 差异,并自动打开这次提交的第一个文件。
108
- - **两条抓握都能拖**:右边缘那条调差异栏宽度(默认 470px),提交明细栏右边那条调它(默认 240px)。两条宽度都会记住。
109
- - **看图片改动**:认得出是图片的文件(PNG / JPEG / GIF / WebP / BMP,**按文件头认,不看扩展名**)会画成两张并排,并给出尺寸与字节变化(`1024×768 → 1024×512`、`2.0 KB → 1.0 KB`)。画不出来时说明原因:超过单侧 4 MB、不是可预览的图片、或缺这一侧(新增 / 删除)。
110
- - **图片缩放**:常显的 `−` / 档位 / `+`,默认「适应宽度」,档位 25% / 50% / 100% / 150% / 200%(按图片固有像素算宽度),点档位本身回到「适应宽度」。档位会记住。
111
-
112
- ![差异栏里的图片改动:工具栏是未暂存/已暂存/自动换行/复制差异,下面是「并排」与 − 100% + 缩放按钮,橙色高亮写着「尺寸 190×140 → 230×160」和「8.8 KB → 11.4 KB」,两张图左右并排,左标「索引」右标「工作区」](docs/images/image-diff.webp)
105
+ ```
106
+ DeepSeek 账号余额
107
+ ¥8.60
108
+ 赠送余额 ¥2.00
109
+ 较上次刷新 -¥0.30(2026-10-27 08:45)
110
+ 更新于 2026-10-27 08:50
111
+ ```
113
112
 
114
- ### 轮尾的「本轮图片改动」
113
+ 气泡里只有**读数**,没有操作说明、也没有提醒话术:那几句"点一下刷新 / 右键开用量页 / 余额低于 …… 记得充值"
114
+ 都已删掉(它们会把整块明细撑成七行、还挤掉真正会变的那几个数)。两个动作仍然在:**左键点击 = 立即刷新**,
115
+ **右键 = 在新标签打开平台用量页**。低余额也只由胶囊上的 `⚠` + 警示色表达,不在气泡里复述。
115
116
 
116
- ![轮尾的「本轮图片改动」:三张缩略图,命令行听来的两张带 ◐ 标记,每张后面跟着一个 ⇄,末尾写「还有 3 个」](docs/images/turn-images.webp)
117
+ - 余额来自 DSH 自己的账号接口(`remote.account.getBalance`,与「设置 → 账号」是同一份数),**只读**、不落盘。
118
+ - 显示规则与 DSH 账号卡片逐条一致:`¥0.00` / `<¥0.01`(不足一分)/ `¥1,234.56`(千分位、向零截断两位)/ 负数带 `-`。币种符号由金额本身带(`¥` / `$`),不会再额外多画一个。
119
+ - 末尾 `+` —— 还有赠送余额;具体多少在悬停里。末尾 `?` 颜色发黄 —— 最近一次刷新失败,屏幕上是上一次的数(悬停里会说是什么时候的)。
120
+ - **低余额提醒** —— 低于阈值(默认 ¥10)时胶囊前面加 `⚠`、金额变成警示色(**只在胶囊上,不写文案**)。
121
+ 阈值可用 localStorage 键 `dsh-hud.balanceLow` 覆盖(写十进制数,例如 `localStorage.setItem('dsh-hud.balanceLow', '50')`),刷新页面生效。
122
+ - **变化量** —— 悬停里给「较上次刷新 -¥0.30(22:37)」。这是**我们自己两次采样之间**的差,不是平台的"今日消耗";
123
+ 跨重启不累计(中间发生过什么我们并不知道),所以措辞写的是"较上次刷新"。
124
+ 减法按十进制对齐做整数运算,`8.6 - 8.3` 就是 `0.30`,不会出现 `0.2999999999999998`。
125
+ - 平时 5 分钟刷一次(它背后是平台的一次真实网络请求,余额也按分钟变);**左键点击**可立即刷新。
126
+ - **读不到就不画**:没登录、没有账号插件、一个钱包都没有时这枚胶囊不出现,也不会画 `¥0.00` 冒充"没钱"。
117
127
 
118
- 内置那张「已编辑 N 个文件」的卡片里看不到图片,所以本插件在同一处轮尾放了自己的门:
128
+ ### 中转站的「账号余额」:为什么有的站只给得出用量
119
129
 
120
- - 点缩略图 → 在右侧内置文档预览里打开这张图;
121
- - 点 `⇄` → 打开本插件的面板,看这张图的**前后对比**(内置预览只有当前这一版);
122
- - 只列**这一轮**改过的图片,最近改的在前,最多 4 张 + `还有 N 个`;这一轮没动过图片就整条不渲染;
123
- - 归因两档都收:工具改的算确定,从 shell 命令里认出来的标 `◐`(悬停给出那条命令)—— 图基本是脚本产出的,只收确定档等于收不到。
130
+ new-api 家族(`ur` / `muyuan` / `happycoding` / `ccwu` 都是)把数据分在两套鉴权后面:
124
131
 
125
- ### 侧栏会话行
132
+ | 接口 | 认什么 | 给什么 | 实测(`ur`) |
133
+ | --------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------- |
134
+ | `GET /dashboard/billing/subscription` | ✅ API Key | 额度上限 | `hard_limit_usd: 100000000`(**占位值**) |
135
+ | `GET /dashboard/billing/usage` | ✅ API Key | **这枚 Key** 的累计用量 | `$479.44` |
136
+ | `GET /api/user/self` | ❌ **只认后台凭据**(PAT / 登录会话;挂在 `UserAuth()` 下) | **账号**余额 + 账号累计用量 + 请求数 | `$855.69` / `$526.66` / `3685` |
137
+ | `GET /api/usage/token/` | ✅ API Key | 这枚 Key 的额度口径(若为"无限"则 `unlimited_quota: true`) | `total_available: -199583909` |
126
138
 
127
- 侧栏那一行本身没有标记,信息在悬停或点行尾 `⋯` 展开处:
139
+ 所以**账号余额**(后台「当前余额」那个数)只有一条路能读,而那条路不认 `sk` 开头的 Key。
140
+ 要看到它,给某个站配一枚**后台凭据** —— **点一下余额胶囊就会弹出设置面板**,粘进去即可:
128
141
 
129
142
  ```
130
- main* ↑0 ↓0 · 已修改 7 未跟踪 2
131
- 本会话改过 3 个文件 · 命令旁听到 4 个可能改动的路径(1 个现在有差异)
143
+ ┌─ 天机访问令牌 ────────────── ur ─┐
144
+ │ 这个站的「当前余额」只对后台登录态可见 │
145
+ │ (API Key 读不到)。去站点「个人设置 → │
146
+ │ 访问令牌」新建一枚,权限勾 profile:read, │
147
+ │ 粘进来就行。 │
148
+ │ [••••••••••••••••••••] │
149
+ │ 将写入 UR_ACCESS_TOKEN │
150
+ │ [保存] [取消] │
151
+ └──────────────────────────────────────────┘
132
152
  ```
133
153
 
134
- 干净时写「工作区干净」「本会话没有改动记录」。
135
-
136
- ### 键盘
154
+ - 令牌去哪拿:站点「个人设置 → 访问令牌」新建一枚,scope 勾 **`profile:read`**(长期有效)。
155
+ 老版本站点没有访问令牌页面时,也可以从浏览器 localStorage 的 `user.token` 复制一枚登录令牌(会过期)。
156
+ - **不用碰 `~/.dsh/.credentials.yaml`**:引用名(`UR_ACCESS_TOKEN`)由插件从 `UR_API_KEY` 推导,
157
+ 写入走 DSH 的凭据服务。让用户手改那个文件等于把"配一个令牌"变成"改一个配置文件"——
158
+ 手滑一个缩进整份凭据就读不出来。
159
+ - 令牌**不回显**:输入框是 `password`,不进 localStorage、不进日志、不进快照,
160
+ 宿主也从不把它回传给浏览器(答复里只有引用名)。
161
+ - 存好立刻重取一次(跳过宿主 60 秒缓存),面板自动收起 —— 粘完就能看见数字变出来。
162
+ - 令牌过期(换设备 / 退出登录 / 站点重置)时**自动退回用量那一路**,不会整枚消失。
163
+ 没配凭据的站照旧只报用量;`ur —` 也是"没配凭据"的样子,点它同样弹面板。
164
+ - **兑换率不写死**:`quota` 是整数,除以站点公开在 `/api/status` 的 `quota_per_unit`(实测 `ur` = `500000`)才是金额;
165
+ 站点用非美元显示时按它自己公布的 `usd_exchange_rate` 换算。拿不到口径时才退回 new-api 的默认值,并在日志里留痕。
166
+ (这四个站实测 `quota_display_type` 全是 `USD`,非美元那条换算分支对它们用不到,属防御性代码。)
167
+ - **逐位对过账**(两次独立核对,间隔数小时,四个数各自按自己的口径对得上):
168
+ - `ur`:`427845000 / 500000 = $855.69`、`263330000 / 500000 = $526.66`、`request_count = 3685`;
169
+ - `happycoding`(就是上面那张效果图那台):`68725535 / 500000 = $137.45`、`3774465 / 500000 = $7.55`、
170
+ `request_count = 3525`、`total_usage = 127.0872 分 = $1.27` —— 截图时是 `$137.48 / $7.51 / 3,514 / $1.24`,
171
+ 相差的就是这十几分钟里真实用掉的量(余额降、其余三个升),**不是换算误差**。
172
+
173
+ ### 什么时候显示 `--`
174
+
175
+ 胶囊只画**读到的读数**。还没读到、或读不到的时候画 `--`,**绝不拿平台账号的余额顶上** ——
176
+ 那个数未必是这次会话要花的钱,填进来等于报一个与本次会话无关的数。
177
+
178
+ | 状态 | 胶囊 |
179
+ | --- | --- |
180
+ | 读到余额 | `$742.23` |
181
+ | 只知道站、读不到(如 `api404`) | `api404 --` |
182
+ | 连站都还不知道(新会话、刚换会话、宿主还没就绪) | `--` |
183
+ | 宿主明确说"这个提供方的账本就是平台账号" | 平台账号余额 `⚠ ¥8.60` |
184
+
185
+ 换会话是**同步清空**的:绑定新会话那一瞬旧账本就没了(`unknown` → `--`),紧接着按新会话的
186
+ 模型来源查一次。所以切换时不会出现"屏幕上还写着上一个会话的站名和金额"——那是另一个账本的钱。
187
+
188
+ ### 悬停里那两个「已用」是两本账
189
+
190
+ 配好后台凭据之后,悬停里同时列出**账号**与**本 Key** 两个已用 —— 它们是两个不同的数(上面那张效果图就是这一屏):
137
191
 
138
- **先在面板里点一下**(或 Tab 进去)才接管按键,点到面板外就交还。Cmd/Ctrl/Alt 组合键与输入框里的按键一律不碰。
139
-
140
- | 键 | 作用 |
141
- | ------------- | ------------------------------------------------- |
142
- | `↑` `↓` | 上下移动选中行(只移动,不打开) |
143
- | `Enter` | 打开选中的那条 |
144
- | `[` `]` | 上一个 / 下一个文件,并直接打开 |
145
- | `n` `p` | 下一个 / 上一个 hunk(折叠着的会展开) |
146
- | `Space` | 折叠 / 展开当前 hunk |
147
- | `v` | 把当前行标成「这一版我审过了」(再按取消) |
148
- | `r` | 重新读取 |
149
- | `Esc` | 退一层:先收右栏(差异 / 提交明细),再按才收面板 |
150
-
151
- 看提交明细时,`↑ ↓ Enter [ ]` 作用于明细那一栏的清单。
152
-
153
- ### 输入框里按 `↑` 召回上一条 prompt
192
+ ```
193
+ happycoding
194
+ 余额 $137.48 ← /api/user/self 的 quota ÷ quota_per_unit
195
+ API 请求 3,514 ← 同一个响应的 request_count(紧跟余额:它是唯一一个次数,不是钱)
196
+ 账号已用 $7.51 ← 同一个响应的 used_quota(整个账号)
197
+ 本 Key 已用 $1.24 ← /dashboard/billing/usage 的 total_usage(只有这枚 Key)
198
+ 更新于 2026-10-27 16:45
199
+ ```
154
200
 
155
- 输入框为空时按 `↑`,带上一条 prompt;继续按往更早翻,`↓` 往回翻,翻过最新一条就回到你原来的草稿。
201
+ 同一个站上这两个数能差出一截(这台上:账号 `$7.51` vs 本 Key `$1.24`,差的那部分是账号下另一个 Key 的用量;
202
+ `ur` 上则是 `$526.66` vs `$479.44`),所以它们是**两个字段、两行**,不是靠一个标记二选一 ——
203
+ 塞进一个字段就必须丢掉一个真实读数。
156
204
 
157
- - **只处理纯文本**:`/命令`、`@文件` 本来就是文本,照原样带回来仍然有效;附件、图片不会跟着回来。
158
- - **数据来自会话日志**(内置 `turnOutline` 投影),不是插件自己记一份 —— 重启、分页、压实都不影响,**在别的客户端发过的话也能召回**。宿主的投影没有时,整个功能静默不启用。
159
- - **输入框非空时完全不接管**,多行草稿里的 `↑` 永远只是移动光标,也不会跟 `/` 命令、`@` 提及的弹窗打架。带修饰键、输入法组合中、有选区时同样让路。
160
- - 你一打字就退出历史模式,草稿不会被错还原。
205
+ **数字为什么分三种颜色**:这一屏里三个是钱、一个是次数,所以按语义分开 ——
161
206
 
162
- > 实现说明:DSH 的输入框是 Lexical 的 `contenteditable`,插件碰不到它的内部模型,只能模拟一次粘贴(失败退到 `execCommand`、再退到直接改文本),每层都等落地后回读确认。这是全插件唯一贴着 DOM 走的地方,DSH 改结构时它可能失效 —— 失效即静默不启用。
207
+ | 颜色 | 谁用它 | 为什么 |
208
+ | --- | --- | --- |
209
+ | 绿 | 余额 | "还剩多少"的通行读法 |
210
+ | 蓝 | API 请求 | 它是**次数**不是金额;同一个颜色会被读成第三个金额 |
211
+ | 琥珀 | 两个「已用」 | 分类色("这是消耗"),**不是在报警** |
212
+ | 灰 | 标签 / 站名 / 更新时间 | 时间戳和标签不是读数,染上就等于满屏是色、没有重点 |
163
213
 
164
- ## 开发
214
+ 颜色全部取自 DSH 自己的主题令牌(`state-success-primary` / `state-business-primary` / `state-warn-primary`),
215
+ 深浅色主题各有一套取值,不写死十六进制。
165
216
 
166
- - `lib/index.js`:宿主半(跑 git、提供 `/dsh-hud-rpc`)。
167
- - `lib/client.js`:浏览器半(**无构建步骤**,手写 JS,只依赖种子模块 `react`)。
168
- - `npm test`:全部用例(`node --test test/*.test.mjs`)。零依赖、不用装东西。
169
- - `docs/dock-slot-host.md`:把输入框上方那一行做成「可注册行内宿主」的提案,给上游看的。
170
- - **发版**:`npm run tag && npm publish`。`prepublishOnly` 先跑测试,再校验「版本号 ↔ git tag」(工作区要干净、`v<版本>` 要存在且指向 HEAD),对不上就发不出去。
217
+ 行序是 `余额 → API 请求 → 账号已用 → 本 Key 已用`:请求数紧跟余额,因为它是这一屏里唯一一个**次数**而不是金额,
218
+ 夹在两个金额中间容易被读成第三个金额。
171
219
 
172
- ## 许可
220
+ ### 「已用」不是「本月」
173
221
 
174
- MIT
222
+ 这些站**都忽略 `start_date` / `end_date`**(实测四个站、四个区间回的完全一样),
223
+ 所以那个数不是「本月」而是**累计**。既然区间不生效,请求就发全历史区间、标签写「累计」——
224
+ 不写一个按月的标签去暗示它按月算。
package/cordis.patch.yml CHANGED
@@ -3,7 +3,7 @@
3
3
  # `name` 必须是**真实的包名**(改名时这里漏改过一次,结果整个插件加载不起来:插入找不到模块,
4
4
  # 组合树里就没有这条,界面上一片空白)。
5
5
  #
6
- # 宿主半(lib/index.js)在自己的 `ctx.webServer` 上注册 `/dsh-hud-rpc` 前缀路由,
6
+ # 宿主半(源码 src/host/index.ts,产物 lib/host/index.js)在自己的 `ctx.webServer` 上注册 `/dsh-hud-rpc` 前缀路由,
7
7
  # 收发与 `/api` 相同的 Connection RPC 信封,并用 `connection.requestRejection`
8
8
  # 复用 Host/Origin + 浏览器会话围栏;浏览器半(lib/client.js,一个 dsh.client 行)
9
9
  # 通过 `ctx.connection.rpc.call("/dsh-hud-rpc", endpoint, { args })` 取数。
Binary file