dsh-ds-balance 2.3.1 → 3.0.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.
Files changed (42) hide show
  1. package/README.md +84 -178
  2. package/README_zh.md +140 -0
  3. package/lib/adapters/host-account.d.ts +22 -1
  4. package/lib/adapters/host-account.js +13 -0
  5. package/lib/client.js +619 -461
  6. package/lib/config.d.ts +28 -19
  7. package/lib/config.js +27 -19
  8. package/lib/domain/credential.d.ts +41 -0
  9. package/lib/domain/credential.js +52 -0
  10. package/lib/http/handlers.d.ts +33 -2
  11. package/lib/http/handlers.js +124 -31
  12. package/lib/http/routes.d.ts +5 -1
  13. package/lib/http/routes.js +2 -1
  14. package/lib/http/wire.d.ts +58 -1
  15. package/lib/http/wire.js +3 -1
  16. package/lib/index.d.ts +1 -1
  17. package/lib/index.js +35 -6
  18. package/lib/ports/account.d.ts +25 -0
  19. package/lib/services/key-resolver.d.ts +33 -3
  20. package/lib/services/key-resolver.js +41 -12
  21. package/lib/services/source-readers.js +3 -3
  22. package/lib/services/source-selector.d.ts +10 -0
  23. package/lib/services/source-selector.js +14 -4
  24. package/lib/services/sources-service.d.ts +148 -0
  25. package/lib/services/sources-service.js +209 -0
  26. package/lib/types/client/api-types.d.ts +8 -0
  27. package/lib/types/client/data.d.ts +72 -0
  28. package/lib/types/client/icons/currency.d.ts +26 -0
  29. package/lib/types/client/icons.d.ts +92 -0
  30. package/lib/types/client/locales.d.ts +15 -13
  31. package/lib/types/client/model.d.ts +10 -0
  32. package/lib/types/client/settings/AccountAvatar.d.ts +27 -0
  33. package/lib/types/client/settings/BalanceSettingsCard.d.ts +8 -1
  34. package/lib/types/client/settings/fields.d.ts +70 -39
  35. package/lib/types/client/settings/source-status.d.ts +43 -0
  36. package/lib/types/client/settings/use-config-form.d.ts +1 -27
  37. package/lib/types/client/settings/use-sources.d.ts +38 -0
  38. package/lib/version.d.ts +1 -1
  39. package/lib/version.js +1 -1
  40. package/package.json +32 -32
  41. package/README_en.md +0 -237
  42. package/lib/types/client/settings/use-credential-state.d.ts +0 -36
package/README_en.md DELETED
@@ -1,237 +0,0 @@
1
- <p align="center">
2
- <img src="icon.svg" alt="dsh-ds-balance" width="80" height="80">
3
- </p>
4
-
5
- <p align="center">
6
- <h1 align="center">dsh-ds-balance</h1>
7
- </p>
8
-
9
- <div align="center">
10
- <p><strong>DeepSeek account balance in the DSH sidebar</strong></p>
11
- <p><em>把 DeepSeek 账户余额放进 DSH 的左边栏</em></p>
12
-
13
- <p>
14
- <a href="https://api-docs.deepseek.com/"><img src="https://img.shields.io/badge/DeepSeek%20API-Official-4D6BFE?style=flat" alt="DeepSeek official API"></a>
15
- <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/DeepSeek%20Harness-Plugin-4176E6?style=flat" alt="DeepSeek Harness Plugin"></a>
16
- </p>
17
-
18
- <p>
19
- <a href="https://github.com/zlZayn/dsh-ds-balance/actions/workflows/ci.yml"><img src="https://github.com/zlZayn/dsh-ds-balance/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
20
- <a href="https://www.npmjs.com/package/dsh-ds-balance"><img src="https://img.shields.io/npm/v/dsh-ds-balance.svg" alt="npm"></a>
21
- <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a>
22
- </p>
23
-
24
- <p>
25
- <a href="README.md">简体中文</a> · <strong><a href="README_en.md">English</a></strong>
26
- </p>
27
- </div>
28
-
29
- ---
30
-
31
- > [!NOTE]
32
- > **The balance is read from the official DeepSeek API** — not estimated. The credential is resolved only through DSH's credential channel: the API key never lands in a settings file and is never returned to the UI.
33
-
34
- The balance needs somewhere to live that does not take up room. A permanent status ring at the bottom of the sidebar; click it for the three amounts and the freshness of the data. Anything you want to configure lives on the plugin's details page under **Plugins → Installed**.
35
-
36
- <p align="center">
37
- <img src="assets/sidebar-popover_en.png" alt="The DeepSeek balance entry at the bottom of the sidebar, with its popover open" width="360">
38
- <br>
39
- <em>A permanent fixture at the <strong>bottom of the sidebar</strong>, alongside Usage statistics and Settings; click it for the balance, the granted / topped-up split, and how fresh the data is.</em>
40
- </p>
41
-
42
- ## The ring at a glance
43
-
44
- That ring is "what is going on right now" — five shapes, each with a different thing for you to do:
45
-
46
- | What you see | Meaning | What you do |
47
- |---|---|---|
48
- | Coloured ring + an arc | There is a balance; the colour is how much | **Read the number** |
49
- | Grey ring + a gap rotating | Fetching; no answer yet | **Wait** |
50
- | Grey ring + a **+** in the middle | Nothing connected | **Configure** (an API key, or sign in) |
51
- | Grey, empty ring | Connected, the account simply has no balance | **Nothing** |
52
- | Red ring + a **×** in the middle | No usable number here | **Wait, investigate, or check the account** |
53
-
54
- **Hover it and it tells you what this spot means right now** (the same sentence whether you hover the
55
- 56px rail ring or the expanded entry with its label):
56
-
57
- - **When the number is trustworthy** (normal / low / critical) — it shows the **amount**;
58
- - **otherwise** — it shows that situation's own line: "Loading", "Balance unavailable",
59
- "Service error", "No credential configured", "Account unavailable", "Data is stale", "No balance".
60
-
61
- **A suspended account and stale data hold a number too, and still do not show it**:
62
- a suspended account or an expired snapshot reporting a figure only misleads ("I have ¥0" /
63
- "I have ¥110"). The ring already draws the problem (red cross / old colour); hovering explains it.
64
-
65
- The two kinds of "nothing to show" are told apart as well: **not connected** asks you to configure
66
- a credential; **connected but unreadable** says "Balance unavailable".
67
- It deliberately does **not** say "temporarily" — some causes clear up on their own, others need you
68
- to fix a credential, and "temporarily" would send the second group away to wait for nothing.
69
- The **×** covers both "could not read it" and "read it, but this account cannot be used" —
70
- both mean "no usable number here", and both send you to investigate.
71
-
72
- ## Surface at a glance
73
-
74
- | Surface | One line | Use it for |
75
- |---|---|---|
76
- | Sidebar ring | A status ring plus label at the bottom of the sidebar; **hovering it (in either state) says what this spot means** — the amount when the number is trustworthy, otherwise the situation's own line; and **how full it is = how far the balance is from that currency's warning line** | Seeing at a glance how much is left and how close it is to the warning line |
77
- | Balance popover | Click the entry: total, granted / topped-up split, data freshness, manual refresh (with cooldown); **an icon at its top right opens this plugin's configuration page** (on a host that does not support it, it falls back to the Plugins list and its tooltip says so) | Checking the exact figures, and how many minutes old they are |
78
- | Settings card | Connection / Display / Thresholds / Refresh, **all collapsed by default**, expand a group from its header | Changing the endpoint, the currency, the warning lines, the cadence |
79
-
80
- The division of labour is fixed: **the ring answers "roughly how much is left", the popover answers "exactly how much", and the card answers "how is that computed".**
81
-
82
- <p align="center">
83
- <img src="assets/settings-card_en.png" alt="The DeepSeek balance card on the Plugins page" width="360">
84
- <br>
85
- <em>The card is those four groups: all collapsed by default, expanded one header at a time; a group holding a bad value opens itself.</em>
86
- </p>
87
-
88
- ## Capabilities
89
-
90
- - A permanent status ring plus label at the bottom of the sidebar; click it for the breakdown. The collapsed and expanded states share the same ring, in the same place.
91
- - The balance refreshes on its own schedule and the UI reads a cache — leaving the interface open does not hammer the upstream.
92
- - The popover shows the total, the granted / topped-up split, how old the data is, and a manual refresh with a cooldown; its title line (whale icon + "DeepSeek balance") is itself a link that opens the [official usage page](https://platform.deepseek.com/usage) in a new tab.
93
- - Multiple currencies: the account decides which currency is shown; when the one chosen in settings is absent, the popover explains and offers "switch to the shown currency" — **that writes straight into settings**, and the settings field follows; the button is disabled when the Host is not writable.
94
- - The settings card has four groups, **all collapsed by default**, expanded one header at a time; a group holding a bad value opens itself.
95
- - The credential is inherited from the official model settings by default, so there is nothing to re-enter; where it comes from is on a badge
96
- (a key is configured / no key is configured, exactly the official card's wording), and a read-only field explains itself instead of offering an edit.
97
- - Colour carries state only (normal / low / critical), never an amount; see "[Reading the ring](#reading-the-ring)".
98
- - It appears on the Plugins page under a **localized name**: "DeepSeek Balance" in English, "DeepSeek 余额" in Chinese —
99
- the card and the details page follow the interface language, and the icon is the same ring as the sidebar one
100
- (an orange arc reading "seven tenths used"); the details page still spells out the package name `dsh-ds-balance`.
101
-
102
- ## Installation
103
-
104
- ### Requirements
105
-
106
- - **DSH**: the range lives in `engines.dsh` in [package.json](package.json) — the single source of truth, character-for-character identical to every `@deepseek-ai/dsh-*` entry. This plugin follows the host's current **RC line** (the dist-tag is `next`). The declared surface commits to **two** prerelease families (the RC family and the still-usable older one, enumerated with `||` — npm only matches prereleases within the same `major.minor.patch`, so a single range cannot cover two families); the exact value drifts, so look it up rather than trusting this file.
107
- - **Node**: `>= 20` (declared in the same place).
108
-
109
- Install the host by **naming the version line explicitly**: neither `alpha` nor `latest` of `@deepseek-ai/dsh` is the line we declare — a default install lands outside the declared range.
110
-
111
- ```bash
112
- npm install -g @deepseek-ai/dsh@next # the line this plugin promises to support (the RC line; there is no `rc` tag on npm)
113
- ```
114
-
115
- Compatibility is measured, not inferred: every week [compat.yml](.github/workflows/compat.yml) swaps packages onto the `next` (RC) line and reruns the existing tests, and one separate job judges whether the declared ranges still cover the line; a red patrol opens or updates a tracking issue with a fixed title. The current verdict, and what to do when it goes red, are in [Compatibility](docs/PUBLISHING.md#兼容性); the Host-version watershed is in [Version compatibility](#version-compatibility).
116
-
117
- ### From npm
118
-
119
- ```bash
120
- dsh plugin --profile web add dsh-ds-balance
121
- ```
122
-
123
- **Restarting `dsh --profile web`** is what makes it take effect.
124
-
125
- ### From source
126
-
127
- ```bash
128
- git clone https://github.com/zlZayn/dsh-ds-balance.git
129
- cd dsh-ds-balance
130
- npm install && npm run build
131
-
132
- dsh plugin --profile web add "$PWD"
133
- ```
134
-
135
- Same as the npm route: it takes effect after a restart.
136
-
137
- ⚠️ The `npm install` above is not optional: under the `link:` form the repository supplies its
138
- own dependencies (see [AGENTS.md](AGENTS.md)) — skip it and the plugin shows as "not running"
139
- (`ERR_MODULE_NOT_FOUND`).
140
-
141
- ### Discovery
142
-
143
- - **npm**: [`dsh-ds-balance`](https://www.npmjs.com/package/dsh-ds-balance)
144
- - **GitHub**: [`zlZayn/dsh-ds-balance`](https://github.com/zlZayn/dsh-ds-balance)
145
-
146
- The repository carries the GitHub topic [`dsh-plugin`](https://github.com/topics/dsh-plugin), which is how the plugin marketplace discovers plugins.
147
-
148
- ## Version compatibility
149
-
150
- - **Which Host version is required**: whatever [package.json](package.json) declares; this plugin follows the host's current **RC line**.
151
- To get the configuration page, upgrade the Host to that version or higher: `npm install -g @deepseek-ai/dsh@next`.
152
- - **The declaration is narrow**: the floor is the version we actually tested, written as `>=` with **no ceiling** —
153
- it claims no "everything in the future counts".
154
- (It does cover later prereleases **within the same `major.minor.patch`**; once the host moves a patch position and
155
- ships another prerelease, this floor no longer covers it and has to be raised again. Why: [Compatibility](docs/PUBLISHING.md#兼容性).)
156
- - **Why the floor moved**: not a change of where the UI lives, but a change of the **settings seam**
157
- (without the two things this plugin relies on, the whole client half never renders — the ring and the popover go with it).
158
- The plugin never reads a Host version; it **probes that capability live**, and when it is absent the popover carries
159
- one extra English line saying why the configuration page is unavailable and where to upgrade.
160
- Technical detail: see [Architecture](docs/ARCHITECTURE.md).
161
- - **Compatibility is not inferred**: a weekly [compat.yml](.github/workflows/compat.yml) run swaps packages on both the
162
- `alpha` and `next` lines and re-runs the existing tests, plus a separate check that the declared range still covers the
163
- tracked line; a failure opens or updates a tracking issue with a fixed title. Current conclusions and what to do when
164
- it goes red: see [Compatibility](docs/PUBLISHING.md#兼容性).
165
-
166
- ## Configuration
167
-
168
- Open **Plugins → Installed** and step into the **DeepSeek Balance** (shown as **DeepSeek 余额** in Chinese) details page — **the configuration sits right below the description, ready to edit** (there is no second Configure step on that row). Four groups are **all collapsed by default** — expand them from their headers:
169
-
170
- <p align="center">
171
- <img src="assets/settings-cards-position_en.png" alt="Where the DeepSeek balance entry sits in the Plugins list" width="480">
172
- <br>
173
- <em>Where it sits: the Plugins page's list view, with "DeepSeek Balance" alongside the other installed plugins; open its details page and the form shown above sits right below the description.</em>
174
- </p>
175
-
176
- - **Connection**: the API base URL and the credential, both blank by default — a blank URL means the official DeepSeek endpoint, and the credential is inherited from the official model page and is read-only.
177
- The nested "Customised settings" holds only the credential reference name.
178
- - **Display**: which currency to use for amounts, or let it follow the account.
179
- - **Thresholds**: two alert lines per currency (warning / critical). **Within one currency the critical line must be strictly lower than the warning line** — equality is rejected too, and saving is blocked while it is.
180
- But a hand-edited config file with an illegal pair **no longer errors** — the plugin **falls that pair back to its defaults** the moment it reads it, and logs one line.
181
-
182
- > **Upgrade note**: the settings live in this plugin's own entry config and the key it is filed under changed once; **old values are not migrated** — please fill the table above in once after upgrading.
183
- - **Refresh**: the server refresh interval, the UI poll interval and the manual refresh cooldown. The cooldown counts from **the moment the refresh finishes**, so the seconds shown in the popover are the real ones left; if the backend says the refresh did not fire, the UI takes its remaining time instead of pretending otherwise.
184
-
185
- Saving applies immediately; there is no need to restart DSH.
186
-
187
- ### Reading the ring
188
-
189
- - How full the ring is = the current balance as a fraction of that currency's **warning line**, capped at 100%; the critical line takes no part in drawing it — it already decided the colour.
190
- - Colour carries state only, never an amount: normal, low and critical each get one hue; an account that cannot be read gets a ring with a cross instead.
191
- - A currency with no threshold configured falls back to state: **a full ring for normal, 3/4 for low, 1/4 for critical**.
192
- (Only the family with a balance to look at draws an arc — those three, plus the old number while data is stale;
193
- an unreadable account, a suspended account, nothing connected, an account with no balance: none of those draw an arc.)
194
- - Why colour is never computed from an amount, and why thresholds are only a scale → [Data flow](docs/ARCHITECTURE.md#数据流).
195
-
196
- ### Credentials
197
-
198
- The key is resolved only through DSH's credential channel; the card's "API key" inherits the one already configured on the official model settings page.
199
- When it comes from the launch environment (an environment variable) the field is read-only and the badge says where it came from.
200
-
201
- **The balance has two official routes and the plugin picks one itself**: the API key from the model settings page or the launch environment, and the **DSH account sign-in** (signed in without a key is enough). The order is:
202
-
203
- - **the route the current session is on** — switching sessions *or the model in this session* re-reads it at once;
204
- - with no session readable, the globally selected model's route;
205
- - with neither decidable, **key first, account as the fallback**;
206
- - only when both are missing does it read "no credential configured".
207
-
208
- The parenthesised label to the right of the popover title is where that number came from: `(API key)` / `(account sign-in)`.
209
- **Whichever is shown is also the one that gets refreshed and named** — display, refresh and label never disagree.
210
-
211
- **This plugin only tracks the official number**, so if the preferred route has nothing to show it falls back to the other official one: as long as either the account or an API key works, you still see a balance (even a slightly older snapshot). **You are only told about it when neither route can produce a number** — that is what "not connected to official" means. What the ring looks like at that moment: see [the ring at a glance](#the-ring-at-a-glance).
212
-
213
- **How long a key change takes to land follows DSH's own layers — this plugin deliberately adds nothing on top:**
214
-
215
- - **A key from the launch environment (an environment variable): restart DSH to change it.** A process's environment is fixed the moment it starts; a later change to that variable outside is invisible to the running plugin.
216
- - **A key changed on the official model settings page / in the credential store: no restart needed** — it lands by the next server refresh cycle at the latest (later while failure backoff is active); to see it at once, use the manual refresh in the popover (it is rate-limited).
217
- - Until then the UI still shows the **previous ledger**: the ledger identifier is derived from the key, so the two never mix, but the in-memory copy is only replaced once the next fetch succeeds.
218
-
219
- Why it is this way → [Key decisions](docs/ARCHITECTURE.md#关键决策).
220
-
221
- ## Security and boundaries
222
-
223
- - **The API key is never returned to the UI**: the config endpoint returns only a fixed-length mask, not even the last few characters.
224
- - The key is never logged and never written to a file owned by this plugin; the settings file holds only a reference name, so the card is safe to screenshot or share.
225
- - Balance snapshots live in DSH's own data directory, grouped by an identifier derived from the credential — **changing the key opens a new ledger** and old snapshots are never mixed in.
226
- - That identifier also involves a random salt file in DSH's home directory; **lose it and old snapshots become unreadable** (they can never resolve to someone else's).
227
- - Only `api.deepseek.com` is contacted; nothing is proxied or forwarded.
228
-
229
- ## License
230
-
231
- [MIT](LICENSE).
232
-
233
- ## Contributing
234
-
235
- Where to report bugs, what to check before proposing a feature, and what to do before sending a PR → [CONTRIBUTING_en.md](CONTRIBUTING_en.md).
236
-
237
- Design stance and implementation constraints → [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md); the release flow and the version-bump decision chain → [docs/PUBLISHING.md](docs/PUBLISHING.md); the maintainer's document map → [AGENTS.md](AGENTS.md).
@@ -1,36 +0,0 @@
1
- /**
2
- * 凭据状态:问后端要「配没配 / 可不可写」这三个事实。
3
- *
4
- * 官方的 provider 卡片用 `describeCredential()` 决定密钥字段是「可编辑」还是
5
- * 「由启动环境提供(只读)」;浏览器半边拿不到凭据服务,所以走插件自己的
6
- * `GET /api/v1/config`(**响应里没有密钥**,只有一个固定长度的掩码串)。
7
- *
8
- * 读不到就回 `null`,界面据此把字段当只读——宁可少给一个编辑入口,
9
- * 也不要在不知道的情况下让用户以为能改。
10
- * @module dsh-ds-balance/client/settings/use-credential-state
11
- */
12
- import { type CredentialInfo } from '../data.ts';
13
- /** 只读凭据行要显示的状态。 */
14
- export type CredentialView = 'env' | 'configured' | 'notConfigured' | 'overridden';
15
- /**
16
- * 决定只读凭据行显示哪一档。
17
- *
18
- * 它回答的是「**当前生效的值从哪来**」,不是「这个框能不能改」:
19
- * - `overridden` 优先:折叠里存过 apiKey 或引用名时,生效的就是折叠里那一份。
20
- * - 其次看宿主能不能写:写不了就是启动环境给的。
21
- * - 再退到「配没配」。
22
- * - 凭据信息读不到(`null`)一律当 `env`:这一行本来就是只读展示,编辑入口在下面的
23
- * 「自定义设置」,所以「环境提供」在任何未知情况下都不会说错话,也不会因为字段缺失把卡片打挂。
24
- *
25
- * 优先级本身有测试兜底(`test/use-credential-state.test.ts`)—— 它是最容易被改错的一处。
26
- * @param credential - 宿主的凭据描述;`null` 表示读不到。
27
- * @param overridden - 用户层是否存着覆盖值(`apiKey` 或引用名)。
28
- * @returns 四档之一。
29
- */
30
- export declare function credentialViewOf(credential: CredentialInfo | null, overridden: boolean): CredentialView;
31
- /**
32
- * 订阅凭据的只读描述。
33
- * @param ref - 当前生效的凭据引用名;它一变就重读一次。
34
- * @returns 三个事实,或 `null` 表示读不到。
35
- */
36
- export declare function useCredentialState(ref: string): CredentialInfo | null;