dsh-bottom-info-bar 1.16.1 → 1.16.2

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 CHANGED
@@ -1,292 +1,180 @@
1
1
  # Bottom Info Bar
2
2
 
3
- <img src="assets/wechat-group.png" width="118" align="right" alt="WeChat group DeepThinking — scan to join">
4
-
5
3
  **English** | [**中文**](README.zh-CN.md)
6
4
 
5
+ [![npm version](https://img.shields.io/npm/v/dsh-bottom-info-bar)](https://www.npmjs.com/package/dsh-bottom-info-bar)
7
6
  [![License: MIT](https://img.shields.io/github/license/songoao25/dsh-bottom-info-bar)](https://github.com/songoao25/dsh-bottom-info-bar/blob/main/LICENSE)
8
- [![Release](https://img.shields.io/github/v/release/songoao25/dsh-bottom-info-bar)](https://github.com/songoao25/dsh-bottom-info-bar/releases)
9
- [![Last commit](https://img.shields.io/github/last-commit/songoao25/dsh-bottom-info-bar)](https://github.com/songoao25/dsh-bottom-info-bar)
10
- [![CI](https://img.shields.io/github/actions/workflow/status/songoao25/dsh-bottom-info-bar/ci.yml)](https://github.com/songoao25/dsh-bottom-info-bar/actions)
11
7
 
12
- **A drop-in replacement for the stats row under the DeepSeek Harness composer.** Everything that row showed stays — turns and steps, LLM and tool time, cache hit rate, input/output tokens — and it adds the **provider and exact model**, your **real balance** (or subscription quota, or this month's bill), **peak/off-peak pricing** with a countdown to the next switch, and **what this conversation has cost so far**.
8
+ A DeepSeek Harness plugin that replaces the stats row under the composer with one line: provider and model, real balance or subscription quota, peak/off-peak pricing, and what this session has cost.
13
9
 
14
- Install once, restart once, done. The billing mode is detected automatically, and every figure comes from a provider API — or is **explicitly labelled as an estimate**.
10
+ ![Bottom Info Bar in full mode](assets/bar-full.webp)
15
11
 
16
- ![Info bar in full view](assets/info-bar-full.webp)
17
- ![Info bar in compact view](assets/info-bar-compact.webp)
12
+ ## What it shows
18
13
 
19
- <sub>**Full** and **compact** view (English UI; labels follow DSH's language setting). Click the bar to switch.</sub>
14
+ | Group | Fields |
15
+ |---|---|
16
+ | Provider | the exact provider and model, mirroring DSH's model switcher |
17
+ | Native stats (kept) | turns and steps, model time, tool time, cache hit rate, input/output tokens, context usage |
18
+ | Money | real balance, subscription quota windows, or this month's cloud bill |
19
+ | Pricing | peak and off-peak prices, the current period, a countdown to the next price change |
20
+ | Spend | this session (including subagents), today, last 30 days, all time |
21
+ | Extras | main time, world time, custom text |
20
22
 
21
- > 💬 Questions or ideas? Scan the QR code to join our WeChat group **DeepThinking**. <sub>The code expires about 7 days after it is generated — if it has expired, open an issue and we will refresh it.</sub>
23
+ Click the bar to switch between **full** and **compact** mode; colors follow DSH's light or dark theme.
22
24
 
23
- ## At a glance
25
+ ![Bottom Info Bar in compact mode](assets/bar-compact.webp)
24
26
 
25
- - **Three billing modes, auto-detected** — balance, subscription quota, or cloud bill. They replace each other and never overlap.
26
- - **Real data, honestly labelled** — balances, quotas, plans and bills come from official provider APIs. The one figure that cannot (OpenAI has no public balance API) is derived from your spending rate and marked `(estimated)` in the bar.
27
- - **Exactly what DSH shows** — the provider and model match DSH's model switcher, and newly published models are picked up automatically.
28
- - **Peak / off-peak pricing** — both prices plus a countdown to the next switch (weekends count as off-peak all day).
29
- - **Honest spend tracking** — this conversation (including subagents), today, last 30 days, and all time — persisted to disk, nothing lost on restart.
30
- - **Native by design** — it replaces the native row instead of duplicating it; click to switch full/compact, and pick fields and colours on the plugin page.
27
+ ## Three billing modes
31
28
 
32
- ## Requirements
29
+ The bar follows the active session and picks the mode from the provider. The three modes are mutually exclusive — there is no manual switch.
33
30
 
34
- - **[DeepSeek Harness](https://github.com/deepseek-ai)** with the **web** interface (`dsh web`). This plugin is web-only — the bar is a web UI component.
35
- - **[pnpm](https://pnpm.io/)** — used by `dsh plugin` to manage profile packages.
36
- - An API key for whichever provider you use, set in DSH under **Settings → Models**.
31
+ ### Balance
37
32
 
38
- ## Quick start
33
+ Shows the real balance from the provider's own API. It refetches when the bar opens, the page refreshes or the provider changes, then polls every 60 seconds; a failed refresh keeps the last known figure on screen. Below 20 (in the account's currency) the amount and a **Low** label turn red.
39
34
 
40
- ```bash
41
- dsh plugin --profile web add dsh-bottom-info-bar
42
- ```
35
+ ![Balance mode](assets/bar-balance.webp)
43
36
 
44
- Or add it from DSH itself: **Plugins → Add plugin**, then enter the package name `dsh-bottom-info-bar`. The repository address works there too — the package lives at the repository root.
37
+ ### Subscription quota
45
38
 
46
- Then **restart `dsh web`** — plugins are composed when the host process starts, so a page refresh is not enough.
39
+ Shows the remaining quota per window (5-hour / weekly / monthly) and a countdown to the next reset — both always come from the same window, so they can never disagree. Windows show **remaining** percent by default and can be switched to **used** in settings; the low-quota warning always follows the remaining ≤ 20% rule. Compact mode prefers the shortest available window (5-hour → weekly → monthly).
47
40
 
48
- That is the whole setup: configure your provider's API key, restart, done. The plugin then shows up under **Plugins**, enabled:
41
+ ### Cloud billing
49
42
 
50
- ![Plugins list with bottom-info-bar installed and enabled](assets/plugins-installed.en.webp)
43
+ Shows this month's real spend from the provider's official billing API, for example `Together | This month $12.34` or `AWS Bedrock | This month $45.60 · Budget 46%`. Cloudflare also shows the daily free-quota remainder with a UTC-midnight reset countdown when the API actually reports a free allowance.
51
44
 
52
- <details>
53
- <summary>Other ways to install (and which one to pick)</summary>
45
+ ## Dark mode
54
46
 
55
- **Recommended: the npm command above.** It installs the released artifact, and the update reminder hands you one command — see [Updating](#updating).
47
+ Colors adapt to DSH's theme — there is no separate setting.
56
48
 
57
- **From the GitHub address** — the package is at the repository root, so DSH takes the repository address directly (this is the address the plugin page's guide asks for):
49
+ ![Dark mode](assets/bar-dark.webp)
58
50
 
59
- ```bash
60
- dsh plugin --profile web add https://github.com/songoao25/dsh-bottom-info-bar
61
- ```
51
+ ## Install
62
52
 
63
- It tracks the default branch rather than a release. No build runs on install: `lib/` is committed. Update by running the same command again.
53
+ Requires DeepSeek Harness with the web interface (`dsh web`) and pnpm.
64
54
 
65
- **From a local checkout** (for development, or to run unreleased code):
55
+ **From npm** (recommended — installs the released version):
66
56
 
67
57
  ```bash
68
- git clone https://github.com/songoao25/dsh-bottom-info-bar.git
69
- dsh plugin --profile web add /path/to/dsh-bottom-info-bar
58
+ dsh plugin --profile web add dsh-bottom-info-bar
70
59
  ```
71
60
 
72
- This creates a `link:` install. It tracks your checkout rather than npm, so updates are `git pull` instead of a package install — see [Updating](#updating).
61
+ **From the GitHub repository** — [github.com/songoao25/dsh-bottom-info-bar](https://github.com/songoao25/dsh-bottom-info-bar) (tracks the default branch; `lib/` is committed, so no build runs on install):
73
62
 
74
- If you installed from a checkout before the package moved to the repository root, the path ended in `/plugin`. That directory no longer exists — remove the plugin and run the command above again.
63
+ ```bash
64
+ dsh plugin --profile web add https://github.com/songoao25/dsh-bottom-info-bar
65
+ ```
75
66
 
76
- **One-command script** — clone and install in one step:
67
+ **With the local one-click script** (clone, build and install in one step):
77
68
 
78
69
  ```bash
79
70
  git clone https://github.com/songoao25/dsh-bottom-info-bar.git
80
71
  cd dsh-bottom-info-bar
81
- ./install.sh # installs into the "web" profile; --profile <name> to override
72
+ ./install.sh
82
73
  ```
83
74
 
84
- Like the local-checkout option, this produces a `link:` install.
85
-
86
- Detailed instructions and troubleshooting: [docs/INSTALL.md](docs/INSTALL.md).
87
- </details>
88
-
89
- ## Configuration
90
-
91
- Everything is on the **plugin page** — **Plugins → bottom-info-bar** — not in DSH's global settings. Field switches, colours, custom text and the billing ledger all live there, and changes save as you make them.
75
+ Then **restart `dsh web`** — plugins are composed when the host starts, so a page refresh is not enough. The plugin shows up in the Plugins list, enabled:
92
76
 
93
- ![Plugin page: Info Bar settings — search, the two field groups, restore defaults, custom text and billing data](assets/plugin-page.webp)
77
+ ![Plugins list with Bottom Info Bar installed](assets/plugins-list.webp)
94
78
 
95
- <sub>**Info Bar** holds the search box, the two field groups, **Restore defaults** and **Custom text**; typing in the search box expands both groups. **Billing data** exports CSV/JSON or clears the ledger.</sub>
79
+ More detail and troubleshooting: [docs/INSTALL.md](docs/INSTALL.md).
96
80
 
97
- ![Native information expanded: one switch and one colour per field](assets/field-config.webp)
81
+ ## Settings
98
82
 
99
- <sub>**Native information** — the six fields DSH's own stats row already showed. Each row keeps its own switch and colour; the defaults match DSH.</sub>
83
+ Everything lives on the plugin page — **Plugins → bottom-info-bar**. Changes save as you make them.
100
84
 
101
- ![Plugin information expanded: the fields this plugin adds](assets/field-config-plugin.webp)
85
+ ![Plugin settings overview](assets/settings-overview.webp)
102
86
 
103
- <sub>**Plugin information** — everything the bar adds: provider and model, subscriptions, spend, balance, peak/off-peak prices and quota. Switch a field off and the bar drops it.</sub>
87
+ **Fields and colors** — one switch and one color per field, in two groups. Turn a field off and the bar drops it.
104
88
 
105
- ## What it does not do
89
+ - **Native information** — the fields DSH's own stats row already showed.
90
+ - **Plugin information** — everything this bar adds: provider and model, subscriptions, spend, balance, pricing and quota.
106
91
 
107
- The boundaries are part of the design, and they are worth stating plainly:
92
+ ![Native information fields](assets/settings-native.webp)
93
+ ![Plugin information fields](assets/settings-plugin.webp)
108
94
 
109
- - **It never passes a guess off as a fact.** Every figure comes from a provider API — or, in the one case where no API exists (OpenAI has no public balance endpoint), is computed from your spending rate and labelled `(estimated)` in the bar. When a number is simply unavailable, the bar says so instead of filling the gap.
110
- - **It never updates itself.** The version reminder only tells you a newer version exists and prepares the command; nothing on your machine changes until you run it.
111
- - **It never binds your ChatGPT account.** That belongs to the companion plugin [dsh-chatgpt-subscription](https://github.com/songoao25). This bar only reads the local token.
112
- - **It never stores conversations.** The ledger records tokens, model, provider and cost — no prompts, no messages, no API keys.
113
- - **It does not work outside the web interface.** There is no terminal or headless equivalent of the bar.
95
+ **Subscription window percentage** — show quota windows as **remaining** (default) or **used**. The low-quota warning always follows remaining ≤ 20%.
114
96
 
115
- ## Billing modes
97
+ ![Subscription window percentage set to remaining](assets/settings-quota.webp)
98
+ ![Subscription window percentage set to used](assets/settings-quota-used.webp)
116
99
 
117
- The bar follows the active session in DSH and picks the display from the provider — no manual mode selection.
100
+ **Time and date** — main and world time zones, plus which of year / month / day / hour / minute / second to display.
118
101
 
119
- ### Balance mode (DeepSeek, Kimi, OpenRouter, StepFun, Xiaomi MiMo, OpenAI reference)
120
-
121
- Shows the **real balance** from the provider's `/user/balance` (or equivalent) API. It refetches the moment the bar opens, refreshes, or the provider changes, then polls every 60 seconds. The last known snapshot is kept on failure so the row never goes blank.
122
-
123
- When the balance drops below ¥20, the amount and a `Low` label turn red.
124
-
125
- ### Subscription mode (ChatGPT / Codex, OpenCode Go, Zhipu, Xiaomi MiMo Token Plan, Command Code, MiniMax Token Plan)
126
-
127
- Shows **quota remaining per window** (5-hour / weekly / monthly, where remaining = 100 − used) and a **countdown to the next reset**. Quota and countdown always come from the same window, so they can never disagree.
128
-
129
- - **ChatGPT / Codex** — decoded **locally** from `~/.codex/auth.json`, showing the real plan tier and expiry date (for example `ChatGPT · Plus | Expires 2026-09-16`). Zero network calls: the values come straight from OpenAI's own login token, never estimated. Not signed in → **Refresh failed** with a reauthorization hint. Binding, token refresh and the `openai-codex` model route belong to the companion plugin [dsh-chatgpt-subscription](https://github.com/songoao25) — this bar only reads the token, and never writes it back.
130
- - **OpenCode Go** — reads quota from `opencode.ai/zen/go/v1/usage` using `OPENCODE_GO_API_KEY` (Settings → Models) or the opencode CLI login at `~/.local/share/opencode/auth.json`. Missing key → a "not configured" hint, not an error.
131
- - **Zhipu** — GLM Coding Plan quota via `ZAI_CODING_CN_API_KEY` (fallback `ZAI_API_KEY`): plan tier plus the 5-hour and weekly windows (including the credit-based plans introduced on 2026-07-30).
132
- - **Xiaomi MiMo Token Plan** — monthly Credits quota via `XIAOMI_TOKEN_PLAN_CN/SGP/AMS_API_KEY` per region (fallback `XIAOMI_API_KEY`): plan name plus the monthly window.
133
- - **Command Code** — reads the official CLI quota endpoints using `COMMAND_CODE_API_KEY` or `CMD_API_KEY` from Settings → Models, the same environment variables, or the CLI login at `~/.commandcode/auth.json`: monthly credits plus 5-hour and weekly windows. Credits are displayed as credits, never as currency; unknown plan IDs do not get an invented monthly percentage.
134
- - **MiniMax Token Plan** — 5-hour and weekly quota from `api.minimax.io` (Global, `MINIMAX_API_KEY`) or `api.minimaxi.com` (CN, `MINIMAX_CN_API_KEY`). Queries require a **Subscription Key**; a pay-as-you-go key is rejected by the server, and the bar says so instead of showing a number it cannot know. Several model buckets are aggregated by the tightest remaining quota.
135
-
136
- Quota windows show **remaining** percent by default; the plugin settings can switch them to **used** under *Subscription window percentage*. Either way, the low-quota warning follows the same rule: remaining 20% or less.
137
-
138
- In compact mode the bar prefers the shortest window (5-hour > weekly > monthly), because it refreshes fastest; if the 5-hour window is unavailable it falls back to weekly, then monthly.
139
-
140
- ### Billing mode (Together, Fireworks, AWS Bedrock, Cloudflare)
141
-
142
- Shows **this month's real spend** from the provider's official billing API, for example `Together | This month $12.34` or `AWS Bedrock | This month $45.60 · Budget 46%`. Cloudflare also shows daily free-quota remaining and a UTC-midnight reset countdown — but **only when the API actually reports a free allowance**; otherwise it shows real usage alone rather than inventing a quota. Missing keys → a "not configured" hint.
143
-
144
- ## Spend tracking
145
-
146
- Every `llm/stream` request is recorded (usage × unit price) and aggregated four ways: **this conversation** (including subagents), **today**, **last 30 days**, and **all time**.
147
-
148
- Subagents bill to the same provider account, so their records are folded into the current session. The billed price is fixed the moment a response completes, so later price-table updates never rewrite history. Models with no known price keep their token counts but are excluded from money totals, and are never given an invented cost.
149
-
150
- Nothing is lost on restart: the journal is synchronously confirmed before the UI counts a new charge, and if that write fails the bar says **Spend not saved** instead of quietly dropping the amount. Ledger details auto-archive into summaries and stats are computed incrementally, so refreshing stays fast even after months of use.
151
-
152
- ## Updating
102
+ ![Time and date settings](assets/settings-time.webp)
153
103
 
154
- Once per full DSH startup the plugin checks npm for a newer version. If one exists, a red **Update available** label appears.
104
+ **Custom text** — up to 64 characters, shown in the bar.
155
105
 
156
- **Click the label** and the update command is copied to your clipboard — paste it into a terminal, run it, then restart `dsh web`. The label briefly confirms with "Update command copied".
106
+ ![Custom text in the bar](assets/bar-custom.webp)
157
107
 
158
- Hovering the label explains both paths: ask an agent that has terminal access, or click to copy the command yourself.
159
-
160
- The copied command matches how the plugin was installed:
161
-
162
- | Installed via | Command you get |
163
- |---|---|
164
- | npm (`dsh plugin add dsh-bottom-info-bar`) | `dsh plugin --profile <profile> add dsh-bottom-info-bar@latest` |
165
- | GitHub address | `dsh plugin --profile <profile> add <the same address>` |
166
- | `link:` — local checkout or the one-command script | `git -C <repo> fetch origin && git -C <repo> checkout main && git -C <repo> merge --ff-only origin/main && node <repo>/scripts/build.mjs` |
167
-
168
- For a `link:` install the plugin reads your checkout's git state (read-only, no command is run) and composes that command from three facts:
169
-
170
- - **It targets the repository's default branch, not the branch you happen to be on.** A checkout parked on a feature branch fast-forwards to nothing useful, so the command switches to the default branch first. Nothing is thrown away: if the working tree is dirty or the branch has local commits, git refuses by itself instead of overwriting anything.
171
- - **It never uses `git pull`.** `pull` fails outright on a branch that was never pushed to a remote (`no such ref was fetched`), which is exactly what a local development branch looks like.
172
- - **It rebuilds `lib/` at the end.** A `link:` install loads the built `lib/`, and your checkout may have `src/` changes that were never built — rebuilding keeps the running code in step with the checkout. This is the same build step `install.sh` runs.
173
-
174
- The version shown in the tooltip is read from what is installed on disk and refreshes with the page, so an update clears the label without waiting for a restart. The host code itself takes effect on the next `dsh web` restart.
175
-
176
- It is never applied automatically. The plugin only tells you a newer version exists; nothing on your machine changes until you run the command yourself.
108
+ **Billing data** — export the ledger as CSV or JSON, or clear it after confirmation. Settings and sign-in information stay untouched.
177
109
 
178
110
  ## Supported providers
179
111
 
180
- The bar detects the provider from DSH's current model list — **no configuration needed**. Set the key up in DSH (Settings → Models) and the bar recognises it. If DSH has not supplied a model yet, the bar waits rather than guessing.
112
+ The bar detects the provider from DSH's current model — no configuration. Set the key in DSH under **Settings → Models**.
181
113
 
182
- ### Balance-based
114
+ ### Balance
183
115
 
184
- | Provider | Display name | Credential | Source |
185
- |---|---|---|---|
186
- | deepseek / deepseek-official | DeepSeek | `DEEPSEEK_API_KEY` | Official API |
187
- | openai | OpenAI | `OPENAI_API_KEY` | Computed from your spending rate — no public balance API, so the bar marks it `(estimated)` |
188
- | moonshotai / moonshotai-cn / kimi-coding | Kimi | `MOONSHOT_API_KEY` (fallback `KIMI_API_KEY`) | Official API |
189
- | openrouter | OpenRouter | `OPENROUTER_API_KEY` | Official API |
190
- | stepfun | StepFun | `STEPFUN_API_KEY` | Official API |
191
- | xiaomi | Xiaomi MiMo | `XIAOMI_API_KEY` | Official API |
116
+ | Provider | Display name | Credential / source |
117
+ |---|---|---|
118
+ | deepseek / deepseek-official | DeepSeek | `DEEPSEEK_API_KEY` |
119
+ | openai | OpenAI | `OPENAI_API_KEY` — estimated from your spending rate; there is no public balance API |
120
+ | moonshotai / moonshotai-cn / kimi-coding | Kimi | `MOONSHOT_API_KEY` (fallback `KIMI_API_KEY`) |
121
+ | openrouter | OpenRouter | `OPENROUTER_API_KEY` |
122
+ | stepfun | StepFun | `STEPFUN_API_KEY` |
123
+ | xiaomi | Xiaomi MiMo | `XIAOMI_API_KEY` |
192
124
 
193
- ### Subscription-based (quota windows)
125
+ ### Subscription quota
194
126
 
195
- | Provider | Display name | Token source |
127
+ | Provider | Display name | Credential / source |
196
128
  |---|---|---|
197
- | codex / chatgpt / openai-codex | ChatGPT / Codex | `~/.codex/auth.json` (read-only; decoded locally) |
198
- | opencode-go / opencode | OpenCode Go | `OPENCODE_GO_API_KEY` or the opencode auth file |
129
+ | codex / chatgpt / openai-codex | ChatGPT / Codex | `~/.codex/auth.json` (read-only, decoded locally) |
130
+ | opencode-go / opencode | OpenCode Go | `OPENCODE_GO_API_KEY` or the opencode CLI login |
199
131
  | zai / zai-coding-cn | Zhipu | `ZAI_CODING_CN_API_KEY` (fallback `ZAI_API_KEY`) |
200
132
  | xiaomi-token-plan-cn / -sgp / -ams | Xiaomi MiMo | `XIAOMI_TOKEN_PLAN_CN/SGP/AMS_API_KEY` (fallback `XIAOMI_API_KEY`) |
201
133
  | command / command-code | Command Code | `COMMAND_CODE_API_KEY` or `CMD_API_KEY`, or `~/.commandcode/auth.json` |
202
- | minimax / minimax-cn | MiniMax | `MINIMAX_API_KEY` (Global) / `MINIMAX_CN_API_KEY` (CN) |
203
-
204
- ### Cloud-billing (real monthly bill)
205
-
206
- | Provider | Display name | Credential | Source |
207
- |---|---|---|---|
208
- | together | Together | `TOGETHER_API_KEY` | Official Usage API (month spend) |
209
- | fireworks | Fireworks | `FIREWORKS_API_KEY` | Official Billing API (period spend) |
210
- | amazon-bedrock | AWS Bedrock | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | Cost Explorer + Budgets (spend + budget %) |
211
- | cloudflare-ai-gateway / cloudflare-workers-ai | Cloudflare | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` (token needs Billing read) | Billable Usage API (real usage) |
212
-
213
- **Anything else** shows a **Not supported** hint instead of borrowing another provider's numbers.
214
-
215
- ### About the model name `DeepSeek-V41-Flash`
216
-
217
- If you have seen `V41-Flash` and read it as a typo, here is the whole story:
218
-
219
- - **It is DeepSeek V4.1 Flash.** That is the model actually serving your requests, priced at V4.1 Flash's official peak/off-peak rates.
220
- - **`DeepSeek-V41-Flash` is how DSH's own model catalogue spells it** — model id `deepseek-flash`, display name written without the dot. The bar mirrors DSH's model switcher, so the odd spelling is DSH's, not this plugin's.
221
- - **It has no effect on anything you do.** Same model, same pricing, same quota. The bar shows whatever DSH shows, so if DSH ever renames it, the bar follows automatically with no update needed.
222
-
223
- `DeepSeek-V4-Flash` is a **different, older** model id — not the same model.
134
+ | minimax / minimax-cn | MiniMax | `MINIMAX_API_KEY` (Global) / `MINIMAX_CN_API_KEY` (CN) — must be a **Subscription Key** |
224
135
 
225
- ## Interface language
136
+ ### Cloud billing
226
137
 
227
- The plugin follows **Settings → General → Language** in DSH. Switching languages updates the bar without a reload, and there is no separate plugin language selector. Host-side messages use DSH's saved preference; a remote browser's unsaved choice cannot change host-only text, and external provider messages keep their original wording. See [localization notes](docs/LOCALIZATION.md).
228
-
229
- ## Data storage
138
+ | Provider | Display name | Credential / source |
139
+ |---|---|---|
140
+ | together | Together | `TOGETHER_API_KEY` — official Usage API, this month's spend |
141
+ | fireworks | Fireworks | `FIREWORKS_API_KEY` — official Billing API, this period's spend |
142
+ | amazon-bedrock | AWS Bedrock | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` — Cost Explorer + Budgets |
143
+ | cloudflare-ai-gateway / cloudflare-workers-ai | Cloudflare | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` (token needs Billing read) |
230
144
 
231
- Everything the plugin records lives in its own directory, isolated from other plugins and from DSH configuration:
145
+ Anything else shows a **Not supported** hint instead of borrowing another provider's numbers.
232
146
 
233
- ```
234
- ~/.dsh/dsh-bottom-info-bar/
235
- ├── usage-records.json # the complete, human-readable ledger
236
- ├── usage-records.journal.jsonl # recovery journal — do not edit by hand
237
- └── usage-records.json.bak # previous complete snapshot, for recovery
238
- ```
147
+ ## Spend tracking
239
148
 
240
- - **Permissions** — directory `0700`, files `0600`, hardened automatically at startup: readable only by your user.
241
- - **Relocate** — set `DSH_BOTTOM_INFO_BAR_DATA_DIR` to move the whole directory (external drive, synced folder, …).
242
- - **Inspect or migrate** — open `usage-records.json` in any editor. To move machines, copy the directory while DSH is closed.
243
- - **Contents** — one entry per model response (`id / ts / model / provider / sessionId / input / cacheRead / cacheWrite / output / currency / cost / status`), where `status` is `completed` or `interrupted`. **No conversation content, prompts, or API keys are ever stored.**
244
- - **Retention** — there is no silent entry cap; keep the directory in your normal backups if you need history beyond this machine.
245
- - **Manage** — **Plugins → bottom-info-bar → Billing data** can export CSV/JSON or clear the plugin's records after confirmation. Settings, sign-in information and pricing data are untouched, and **uninstalling does not delete your data**.
149
+ Every model response is recorded (usage × unit price) and aggregated four ways: **this session** (including subagents), **today**, **last 30 days** and **all time**. The price is locked the moment a response completes, so later price-table updates never rewrite history. A model with no known price keeps its token counts but is excluded from money totals — no amount is ever invented. Records are written to disk before they count, so a restart loses nothing; if a write fails, the bar says **Spend not saved**.
246
150
 
247
- > Money is aggregated only in the active provider's currency (CNY for DeepSeek, USD for the OpenAI reference prices) — currencies are never mixed. DeepSeek peak hours are weekdays 09:00–12:00 and 14:00–18:00 Beijing time; weekends are off-peak all day.
151
+ ## Updating
248
152
 
249
- ## Uninstall
153
+ Once per DSH startup the plugin checks npm for a newer version. When one exists, a red **Update available** label appears in the bar: click it to copy the update command, run it in a terminal, then restart `dsh web`.
250
154
 
251
- ```bash
252
- cd dsh-bottom-info-bar
253
- ./uninstall.sh
254
- # or: dsh plugin --profile web remove dsh-bottom-info-bar
255
- ```
155
+ | Installed via | Command |
156
+ |---|---|
157
+ | npm | `dsh plugin --profile <profile> add dsh-bottom-info-bar@latest` |
158
+ | GitHub address | `dsh plugin --profile <profile> add <the same address>` |
159
+ | local checkout or install script | `git -C <repo> fetch origin && git -C <repo> checkout main && git -C <repo> merge --ff-only origin/main && node <repo>/scripts/build.mjs` |
256
160
 
257
- After a restart the native stats row returns with no residue. Your usage records stay in `~/.dsh/dsh-bottom-info-bar/` — export or clear them from the plugin page (Plugins → bottom-info-bar → Billing data) if you want a clean slate.
161
+ Nothing updates by itself — the bar only tells you that a newer version exists.
258
162
 
259
- ChatGPT binding and token maintenance belong to the separate plugin `dsh-chatgpt-subscription`; removing this info bar does not touch it.
163
+ ## Privacy and security
260
164
 
261
- ## FAQ
262
-
263
- | Symptom | What to do |
264
- |---|---|
265
- | Bar does not appear after refreshing the page | **Restart** `dsh web` — the host composes plugins at startup |
266
- | Balance says **Not configured: DEEPSEEK_API_KEY** | Add the key under Settings → Models |
267
- | Balance shows **Refresh failed** but a figure is still visible | Transient network or key issue; it retries automatically after 60 s and keeps the last known value. **Hover the warning** for details. |
268
- | OpenCode Go shows **Refresh failed** with a missing-credentials tooltip | Add `OPENCODE_GO_API_KEY` under Settings → Models, or log in with the opencode CLI |
269
- | Command Code shows **Refresh failed** or no quota | Add `COMMAND_CODE_API_KEY` or `CMD_API_KEY` under Settings → Models, or log in with the Command Code CLI. The plugin only reads `~/.commandcode/auth.json`; it never writes the login file. |
270
- | ChatGPT shows **Not connected** | Install the companion plugin `dsh-chatgpt-subscription` and sign in |
271
- | ChatGPT plan or expiry is blank | Sign in again or rebind. If the token genuinely lacks those fields the bar leaves them empty rather than guessing. |
272
- | How do I update? | Click the red **Update available** label to copy the command, then restart `dsh web`. See [Updating](#updating). |
273
- | I updated and the label is still there | The version is read from the checkout on disk: refresh the page. If the label persists, check that the update actually changed the installed copy (`link:` installs must rebuild `lib/`). |
274
- | Install fails with **declares no bundle** (中文界面:「这个包没有声明组合包」) | DSH installed a repository whose root was not a package — that was this repository before the package moved to the root. Use the package name `dsh-bottom-info-bar` in the plugin page, or the GitHub address on a release that includes the fix. |
275
- | The model is shown as `V41-Flash` | Not a typo — that is DSH's spelling of **V4.1 Flash**. See [About the model name](#about-the-model-name-deepseek-v41-flash). |
276
- | Compact mode shows a different quota window than full mode | Intentional: compact prefers the shortest window (5-hour > weekly > monthly). Quota and countdown still come from the same window. |
277
- | Why is the model's reasoning not shown? | DSH does not render internal reasoning — a DSH interface limitation, not this plugin |
278
- | I want the original stats row back | Uninstall and restart |
165
+ - **Read-only credentials.** API keys stay in DSH; `~/.codex/auth.json` and `~/.commandcode/auth.json` are read locally and never written back. This plugin does not bind or refresh accounts.
166
+ - **No conversation content.** The ledger stores tokens, model, provider, currency and cost — never prompts, messages or keys.
167
+ - **Local only.** Data lives in `~/.dsh/dsh-bottom-info-bar/` (directory `0700`, files `0600`). Nothing leaves your machine except the provider API requests the bar itself makes.
168
+ - **Uninstalling keeps your data.** Export or clear it from the plugin page (Billing data) if you want a clean slate.
279
169
 
280
170
  ## Development
281
171
 
282
- - **Source** — `src/host.js` (host) and `src/client-bundle.js` (client)
283
- - **Build** — `npm run build` (regenerates `lib/`, which is committed so a GitHub install needs no build; CI checks it stays in step with `src/`)
284
- - **Test** — `node tests/run-all.mjs` (builds first, then runs every suite)
285
- - **Release history** — [CHANGELOG.md](CHANGELOG.md)
286
- - **Contributing** — [CONTRIBUTING.md](CONTRIBUTING.md); day-to-day workflow and the release process: [docs/WORKFLOW.md](docs/WORKFLOW.md)
287
-
288
- Every rule this project learned the hard way is enforced by CI rather than by convention — the test suite, the source guards and the release-chain contract all run on every pull request. See [AGENTS.md](AGENTS.md) for the constraints an agent must respect.
172
+ - Build: `npm run build`
173
+ - Test: `node tests/run-all.mjs`
174
+ - Contributing: [CONTRIBUTING.md](CONTRIBUTING.md)
289
175
 
290
176
  ## License
291
177
 
292
178
  [MIT](LICENSE) © 2026 songoao25
179
+
180
+ 💬 Questions or ideas? Join our WeChat group **DeepThinking** — [QR code](assets/wechat-group.png).
package/README.zh-CN.md CHANGED
@@ -1,291 +1,182 @@
1
1
  # 底部信息栏
2
2
 
3
- <img src="assets/wechat-group.png" width="118" align="right" alt="微信群 DeepThinking —— 扫码加入">
4
-
5
3
  [**English**](README.md) | **中文**
6
4
 
5
+ [![npm 版本](https://img.shields.io/npm/v/dsh-bottom-info-bar)](https://www.npmjs.com/package/dsh-bottom-info-bar)
7
6
  [![License: MIT](https://img.shields.io/github/license/songoao25/dsh-bottom-info-bar)](https://github.com/songoao25/dsh-bottom-info-bar/blob/main/LICENSE)
8
- [![Release](https://img.shields.io/github/v/release/songoao25/dsh-bottom-info-bar)](https://github.com/songoao25/dsh-bottom-info-bar/releases)
9
- [![Last commit](https://img.shields.io/github/last-commit/songoao25/dsh-bottom-info-bar)](https://github.com/songoao25/dsh-bottom-info-bar)
10
- [![CI](https://img.shields.io/github/actions/workflow/status/songoao25/dsh-bottom-info-bar/ci.yml)](https://github.com/songoao25/dsh-bottom-info-bar/actions)
11
7
 
12
- DeepSeek Harness 输入框下方那行统计栏的**直接替代品**:原生统计栏有的(轮次与步数、LLM 与工具耗时、缓存命中率、输入输出 token)**全部保留**,另外补上**服务商与具体模型**、**真实余额**(或订阅额度、或本月真实账单)、**高峰/空闲价格**与切换倒计时,以及**本对话已经花了多少**。
8
+ DeepSeek Harness 插件:把输入框下方那行统计栏换成一行信息栏——服务商与模型、真实余额或订阅额度、高峰/空闲定价,以及本会话已经花了多少。
13
9
 
14
- 装一次、重启一次即可。计费模式自动识别,每一个数字**要么来自服务商接口,要么被明确标注为估算**。
10
+ ![信息栏完整模式](assets/bar-full.zh-CN.webp)
15
11
 
16
- ![信息栏完整视图](assets/info-bar-full.webp)
17
- ![信息栏简洁视图](assets/info-bar-compact.webp)
12
+ ## 它显示什么
18
13
 
19
- <sub>**完整**与**简洁**两种视图(截图为英文界面;DSH 语言设为中文后文案自动切换)。点击信息栏即可切换。</sub>
14
+ | 分组 | 内容 |
15
+ |---|---|
16
+ | 服务商 | 当前会话的服务商与具体模型,跟随 DSH 模型切换器 |
17
+ | 原生统计(保留) | 轮次与步数、模型耗时、工具耗时、缓存命中率、输入/输出 token、上下文占用 |
18
+ | 金额 | 真实余额、订阅额度窗口,或本月云账单 |
19
+ | 定价 | 高峰价与空闲价、当前时段、下次价格切换倒计时 |
20
+ | 花费 | 本会话(含子代理)、今天、近 30 天、累计 |
21
+ | 其它 | 主时间、世界时间、自定义文字 |
20
22
 
21
- > 💬 想提建议或聊聊 AI?扫码加入微信群 **DeepThinking**。<sub>二维码有效期约 7 天,过期了欢迎开个 Issue 提醒我们更新。</sub>
23
+ 点击信息栏可在**完整**与**简洁**之间切换;配色跟随 DSH 的深色或浅色主题。
22
24
 
23
- ## 一眼看懂
25
+ ![信息栏简洁模式](assets/bar-compact.zh-CN.webp)
24
26
 
25
- - **三种计费模式自动切换** —— 余额制、订阅额度制、云账单制,互斥不重叠,无需手动选择。
26
- - **真实数据,且诚实标注** —— 余额、额度、套餐、账单全部来自各家官方 API。唯一拿不到接口的那一项(OpenAI 无公开余额接口)按你的消耗速度推算,并在界面上标出**(估算)**。
27
- - **与 DSH 显示完全一致** —— 服务商与模型名取自 DSH 模型切换器,新模型自动跟随,不用改插件。
28
- - **高峰 / 空闲价** —— 两个价格并列,并给出切换倒计时(周末全天空闲价)。
29
- - **诚实的花费记账** —— 本对话(含子代理)、今天、近 30 天、累计,落盘保存,重启不丢。
30
- - **原生观感** —— 顶替原生统计栏而不是并列重复;点击切换完整/简洁,字段与配色在**插件页**里自选。
27
+ ## 三种计费模式
31
28
 
32
- ## 前置要求
29
+ 信息栏跟随当前会话,按服务商自动选择模式。三种模式互斥,无需手动切换。
33
30
 
34
- - **[DeepSeek Harness](https://github.com/deepseek-ai)**,且使用 **Web 界面**(`dsh web`)。本插件**只支持 Web**——信息栏本身就是一个网页 UI 组件。
35
- - **[pnpm](https://pnpm.io/)** —— `dsh plugin` 用它管理 profile 里的包。
36
- - 你所使用服务商的 API Key,在 DSH 的 **设置 → 模型** 里配置。
31
+ ### 余额制
37
32
 
38
- ## 快速开始
33
+ 显示服务商官方接口返回的真实余额。信息栏打开、页面刷新或切换服务商时立即重查,之后每 60 秒轮询一次;刷新失败时保留上一次的数值。余额低于 20(按账户币种)时,金额与**低**标记转红。
39
34
 
40
- ```bash
41
- dsh plugin --profile web add dsh-bottom-info-bar
42
- ```
35
+ ![余额制](assets/bar-balance.zh-CN.webp)
43
36
 
44
- 也可以在 DSH 里直接装:**插件 → 添加插件**,填包名 `dsh-bottom-info-bar`。那一栏填仓库地址同样可以——包就在仓库根目录。
37
+ ### 订阅额度制
45
38
 
46
- 然后 **重启 `dsh web`** —— 插件在宿主进程启动时组合,**刷新页面不够**。
39
+ 显示各窗口剩余额度(5 小时 / 周 / 月)与距下次重置的倒计时——两者始终取自同一个窗口,不会互相矛盾。窗口默认按**剩余**显示百分比,可在设置里切换为**已用**;低额度告警始终按剩余 ≤ 20% 判定。简洁模式优先显示时长最短的可用窗口(5 小时 → 周 → 月)。
47
40
 
48
- 安装就到这里:配好服务商的 API Key、重启,完事。重启后插件会出现在**插件**页并处于启用状态:
41
+ ### 云账单制
49
42
 
50
- ![插件列表:bottom-info-bar 已安装并启用](assets/plugins-installed.zh-CN.webp)
43
+ 显示官方账单接口返回的本月真实花费,例如 `Together | 本月 $12.34`、`AWS Bedrock | 本月 $45.60 · 预算 46%`。Cloudflare 还会在接口确实返回免费额度时,显示每日免费额度剩余与 UTC 零点重置倒计时。
51
44
 
52
- <details>
53
- <summary>其它安装方式(以及该怎么选)</summary>
45
+ ## 深色模式
54
46
 
55
- **推荐用上面的 npm 命令。** 装的是已发布的版本,更新提醒也只给你一条命令——见 [更新版本](#更新版本)。
47
+ 配色跟随 DSH 主题,无需单独设置。
56
48
 
57
- **从 GitHub 地址安装** —— 包就在仓库根目录,所以 DSH 能直接吃仓库地址(插件页的引导让你填的正是这个):
49
+ ![深色模式](assets/bar-dark.zh-CN.webp)
58
50
 
59
- ```bash
60
- dsh plugin --profile web add https://github.com/songoao25/dsh-bottom-info-bar
61
- ```
51
+ ## 安装
62
52
 
63
- 它跟随默认分支而不是某个发布版本。安装时不跑构建:`lib/` 已入库。更新就是再执行一次同样的命令。
53
+ 需要带 Web 界面的 DeepSeek Harness(`dsh web`)与 pnpm。
64
54
 
65
- **从本地代码安装**(用于开发,或想跑未发布的代码):
55
+ **npm 安装**(推荐,装的是已发布版本):
66
56
 
67
57
  ```bash
68
- git clone https://github.com/songoao25/dsh-bottom-info-bar.git
69
- dsh plugin --profile web add /path/to/dsh-bottom-info-bar
58
+ dsh plugin --profile web add dsh-bottom-info-bar
70
59
  ```
71
60
 
72
- 这会产生 `link:` 安装:它跟随你的本地代码而不是 npm,所以更新方式是 `git pull` 而不是装包——见 [更新版本](#更新版本)。
61
+ **从 GitHub 仓库安装** —— [github.com/songoao25/dsh-bottom-info-bar](https://github.com/songoao25/dsh-bottom-info-bar)(跟随默认分支;`lib/` 已入库,安装时不跑构建):
73
62
 
74
- 如果你在「包移到仓库根」之前用本地代码装过,路径末尾是 `/plugin`。那个目录已经不存在了——先卸载,再用上面的命令装一次。
63
+ ```bash
64
+ dsh plugin --profile web add https://github.com/songoao25/dsh-bottom-info-bar
65
+ ```
75
66
 
76
- **一键脚本** —— clone 与安装一步完成:
67
+ **本地一键脚本**(clone、构建、安装一步完成):
77
68
 
78
69
  ```bash
79
70
  git clone https://github.com/songoao25/dsh-bottom-info-bar.git
80
71
  cd dsh-bottom-info-bar
81
- ./install.sh # 默认装到 web profile;用 --profile <name> 改
72
+ ./install.sh
82
73
  ```
83
74
 
84
- 与「从本地代码安装」一样,这同样是 `link:` 安装。
85
-
86
- 详细步骤与故障排查见 [docs/INSTALL.md](docs/INSTALL.md)。
87
- </details>
88
-
89
- ## 插件配置
90
-
91
- 配置都在**插件页**(插件 → bottom-info-bar)里,不在 DSH 的全局设置里:字段开关、配色、自定义文字、账单数据都在这里,改完自动保存。
92
-
93
- ![插件页:信息栏设置——搜索、两个字段分组、恢复默认、自定义文字与账单数据](assets/plugin-page.zh-CN.webp)
94
-
95
- <sub>**信息栏**里有搜索框、两个字段分组、**恢复默认**与**自定义文字**;在搜索框输入会自动展开两个分组。**账单数据**可导出 CSV/JSON 或清除记录。</sub>
96
-
97
- ![原生信息展开:每个字段一个开关与一个颜色](assets/field-config.zh-CN.webp)
98
-
99
- <sub>**原生信息**——DSH 原生统计栏本来就有的六个字段。每行都有自己的开关与颜色,默认配色跟随 DSH。</sub>
100
-
101
- ![插件信息展开:信息栏自己新增的字段](assets/field-config-plugin.zh-CN.webp)
102
-
103
- <sub>**插件信息**——信息栏新增的全部内容:服务商与模型、订阅、花费、余额、高峰/空闲价格与额度。关掉哪个字段,信息栏就不再显示它。</sub>
104
-
105
- ## 它不做什么
106
-
107
- 边界也是设计的一部分,值得说清楚:
108
-
109
- - **绝不把猜测当成事实。** 每个数字要么来自服务商接口——要么,在唯一没有接口的那一项(OpenAI 无公开余额接口)上,按你的消耗速度推算,并在界面上标注**(估算)**。拿不到数字时,信息栏会明说,而不是填一个猜测上去。
110
- - **绝不自动更新自己。** 版本提醒只负责告诉你"有新版本"并准备好命令;在你亲手运行之前,机器上不会有任何改动。
111
- - **绝不绑定你的 ChatGPT 账号。** 那是配套插件 [dsh-chatgpt-subscription](https://github.com/songoao25) 的职责。本插件只读本地令牌。
112
- - **绝不保存对话内容。** 账本记录的是 token、模型、服务商与花费——没有提示词、没有消息、没有 API Key。
113
- - **不适用于 Web 之外的界面。** 终端或 headless 下没有对应的信息栏。
114
-
115
- ## 三种计费模式
116
-
117
- 信息栏跟随 DSH 的当前会话,按服务商自动决定显示内容,**无需手动切换模式**。
118
-
119
- ### 余额制(DeepSeek / Kimi / OpenRouter / StepFun / 小米 MiMo / OpenAI 参考价)
120
-
121
- 显示服务商官方接口返回的**真实余额**。信息栏打开、刷新、或切换服务商时会立即重新查询,之后每 60 秒轮询一次。查询失败时保留上一次的快照,**这一行永远不会变空白**。
122
-
123
- 余额低于 ¥20 时,金额与「低」标记转为红色。
75
+ 然后**重启 `dsh web`** —— 插件在宿主启动时组合,刷新页面不够。重启后插件出现在插件列表并处于启用状态:
124
76
 
125
- ### 订阅额度制(ChatGPT / Codex、OpenCode Go、智谱、小米 MiMo Token Plan、Command Code、MiniMax Token Plan)
77
+ ![插件列表:底部信息栏已安装](assets/plugins-list.zh-CN.webp)
126
78
 
127
- 显示**各窗口剩余额度**(5 小时 / 周 / 月,剩余 = 100 − 已用)与**距下次重置的倒计时**。额度与倒计时**永远取自同一个窗口**,不会互相矛盾。
128
-
129
- - **ChatGPT / Codex** —— 在**本机离线解析** `~/.codex/auth.json`,显示真实套餐与到期时间(例如 `ChatGPT · Plus | Expires 2026-09-16`)。**零网络请求**:数值直接来自 OpenAI 自己的登录令牌,不做估算。未登录 → 显示**刷新失败**并提示重新授权。令牌的**绑定与续期**由配套插件 [dsh-chatgpt-subscription](https://github.com/songoao25) 负责——本插件**只读**令牌,绝不写回。
130
- - **OpenCode Go** —— 通过 `OPENCODE_GO_API_KEY`(设置 → 模型)或 opencode CLI 的登录(`~/.local/share/opencode/auth.json`)读取 `opencode.ai/zen/go/v1/usage` 的额度。未配置 → 显示「未配置」提示而非报错。
131
- - **智谱** —— 通过 `ZAI_CODING_CN_API_KEY`(回退 `ZAI_API_KEY`)读取 GLM Coding Plan 额度:套餐档位 + 5 小时窗口 + 周窗口(含 2026-07-30 起的积分制套餐)。
132
- - **小米 MiMo Token Plan** —— 通过 `XIAOMI_TOKEN_PLAN_CN/SGP/AMS_API_KEY` 按区域读取月度 Credits 额度(回退 `XIAOMI_API_KEY`):套餐名 + 月度窗口。
133
- - **Command Code** —— 通过 `COMMAND_CODE_API_KEY` / `CMD_API_KEY`(设置 → 模型)、同名环境变量或 CLI 登录文件 `~/.commandcode/auth.json` 读取官方额度端点:月度 credits + 5 小时窗口 + 周窗口。credits 按积分显示,绝不折算成货币;未知套餐不会编造月度百分比。
134
- - **MiniMax Token Plan** —— 通过 `MINIMAX_API_KEY`(Global,`api.minimax.io`)或 `MINIMAX_CN_API_KEY`(国内,`api.minimaxi.com`)读取 5 小时与周窗口额度。查询必须使用 **Subscription Key**;按量 API Key 会被服务端拒绝,信息栏会直接说明原因,而不是显示一个它无从得知的数字。多个模型桶按**最紧的剩余额度**聚合。
135
-
136
- 额度窗口默认显示**剩余**百分比;插件设置里的「订阅窗口百分比方向」可切换为**已用**。无论选哪种,低额度告警始终按「剩余 20%」判定。
137
-
138
- 简洁模式下优先显示**时长最短的窗口**(5 小时 > 周 > 月),因为它刷新最快;5 小时窗口不可用时依次回退到周、月。
139
-
140
- ### 账单制(Together / Fireworks / AWS Bedrock / Cloudflare)
141
-
142
- 显示官方账单接口返回的**本月真实花费**,例如 `Together | 本月 $12.34`、`AWS Bedrock | 本月 $45.60 · 预算 46%`。Cloudflare 还会显示每日免费额度剩余与 UTC 零点重置倒计时——**但仅在接口确实返回免费额度时才显示**;否则只显示真实用量,**绝不编造额度**。未配置密钥 → 显示「未配置」提示。
79
+ 详细步骤与故障排查见 [docs/INSTALL.md](docs/INSTALL.md)。
143
80
 
144
- ## 花费记账
81
+ ## 设置
145
82
 
146
- 每一次 `llm/stream` 请求都会被记录(用量 × 单价),并按四个口径聚合:**本对话**(含子代理)、**今天**、**近 30 天**、**累计**。
83
+ 配置都在插件页(**插件 → bottom-info-bar**),改完自动保存。
147
84
 
148
- 子代理与主会话走同一个服务商账户,因此其花费会并入当前会话。**单价在响应完成的那一刻锁定**,之后的价目表更新绝不会改写历史金额。价目表中没有的模型保留 token 用量但不计入金额,也**不会被编造出一个价格**。
85
+ ![插件设置页总览](assets/settings-overview.zh-CN.webp)
149
86
 
150
- 重启不丢:新账目会先把流水**同步落盘确认**,再计入界面;若这次写入失败,信息栏会明确显示**账单未保存**,而不是悄悄少算一笔。账本明细会自动归档成汇总、统计增量计算,因此用上几个月界面刷新也不会变慢。
87
+ **字段与配色** —— 每个字段一个开关、一个颜色,分两组。关掉哪个字段,信息栏就不再显示它。
151
88
 
152
- ## 更新版本
89
+ - **原生信息** —— DSH 原生统计栏本来就有的字段。
90
+ - **插件信息** —— 信息栏新增的全部内容:服务商与模型、订阅、花费、余额、定价与额度。
153
91
 
154
- 插件在一次完整的 DSH 启动后检查一次 npm 上是否有新版本。有新版时,信息栏会出现红色的**新版本提醒**标签。
92
+ ![原生信息字段](assets/settings-native.zh-CN.webp)
93
+ ![插件信息字段](assets/settings-plugin.zh-CN.webp)
155
94
 
156
- **点击这个标签**,更新命令就会复制到剪贴板——粘到终端执行,然后重启 `dsh web` 即可。复制成功后标签会短暂显示「更新命令已复制」。
95
+ **订阅窗口百分比方向** —— 按**剩余**(默认)或**已用**显示额度窗口。低额度告警始终按剩余 ≤ 20% 判定。
157
96
 
158
- 把鼠标停在标签上会说明两条路径:让有终端权限的 Agent 帮你更新,或者点击标签自己复制命令。
97
+ ![订阅窗口百分比方向:剩余](assets/settings-quota.zh-CN.webp)
98
+ ![订阅窗口百分比方向:已用](assets/settings-quota-used.zh-CN.webp)
159
99
 
160
- 复制的命令与你的安装方式匹配:
100
+ **时间与日期** —— 主时间与世界时间的时区,以及年 / 月 / 日 / 时 / 分 / 秒的显示格式。
161
101
 
162
- | 你的安装方式 | 拿到的命令 |
163
- |---|---|
164
- | npm(`dsh plugin add dsh-bottom-info-bar`) | `dsh plugin --profile <profile> add dsh-bottom-info-bar@latest` |
165
- | GitHub 地址 | `dsh plugin --profile <profile> add <同一个地址>` |
166
- | `link:`(本地代码 / 一键脚本) | `git -C <仓库> fetch origin && git -C <仓库> checkout main && git -C <仓库> merge --ff-only origin/main && node <仓库>/scripts/build.mjs` |
102
+ ![时间与日期设置](assets/settings-time.zh-CN.webp)
167
103
 
168
- `link:` 安装时,插件会**只读地**看一眼你这份工作副本的 git 状态,再据此拼出命令:
104
+ **自定义文字** —— 最多 64 个字符,显示在信息栏中。
169
105
 
170
- - **对准仓库的默认分支,而不是你碰巧所在的分支。** 停在功能分支上的副本快进不到任何有用的东西,所以命令会先切到默认分支。不会丢东西:工作区不干净或分支上有本地提交时,git 自己会拒绝,而不是覆盖你的改动。
171
- - **不会用 `git pull`。** 分支从没推到远端时 `pull` 会直接失败(`no such ref was fetched`)——而本地开发分支本来就是这种状态。
172
- - **末尾会重新构建 `lib/`。** `link:` 安装加载的是构建产物 `lib/`,而你的副本可能改过 `src/` 却没重建——重建一次才保证跑起来的代码和副本一致。这和 `install.sh` 里的构建步骤是同一件事。
106
+ ![信息栏中的自定义文字](assets/bar-custom.zh-CN.webp)
173
107
 
174
- 悬浮提示里的版本号读的是**磁盘上已安装的那一份**,随页面刷新而更新,所以更新完不必等重启,提醒就会消失;宿主代码本身在下次重启 `dsh web` 时生效。
175
-
176
- **它绝不会自动执行更新。** 插件只负责告诉你「有新版本」,在你亲手运行命令之前,机器上不会有任何改动。
108
+ **账单数据** —— 导出 CSV / JSON,或确认后清除记录。设置与登录信息不受影响。
177
109
 
178
110
  ## 支持的服务商
179
111
 
180
- 信息栏从 DSH 当前模型列表识别服务商——**零配置**。在 DSH 的「设置 → 模型」里配好密钥即可。DSH 尚未提供模型时,信息栏会**等待**,而不是猜一个。
112
+ 信息栏从 DSH 当前模型识别服务商,零配置;密钥在 DSH 的**设置 → 模型**里填写。
181
113
 
182
114
  ### 余额制
183
115
 
184
- | 服务商 | 显示名 | 凭据 | 数据来源 |
185
- |---|---|---|---|
186
- | deepseek / deepseek-official | DeepSeek | `DEEPSEEK_API_KEY` | 官方接口 |
187
- | openai | OpenAI | `OPENAI_API_KEY` | 按消耗速度推算——官方无公开余额接口,界面上会标注**(估算)** |
188
- | moonshotai / moonshotai-cn / kimi-coding | Kimi | `MOONSHOT_API_KEY`(回退 `KIMI_API_KEY`) | 官方接口 |
189
- | openrouter | OpenRouter | `OPENROUTER_API_KEY` | 官方接口 |
190
- | stepfun | StepFun | `STEPFUN_API_KEY` | 官方接口 |
191
- | xiaomi | 小米 MiMo | `XIAOMI_API_KEY` | 官方接口 |
116
+ | 服务商 | 显示名 | 凭据 / 数据来源 |
117
+ |---|---|---|
118
+ | deepseek / deepseek-official | DeepSeek | `DEEPSEEK_API_KEY` |
119
+ | openai | OpenAI | `OPENAI_API_KEY` —— 按消耗速度估算;官方没有公开余额接口 |
120
+ | moonshotai / moonshotai-cn / kimi-coding | Kimi | `MOONSHOT_API_KEY`(回退 `KIMI_API_KEY`) |
121
+ | openrouter | OpenRouter | `OPENROUTER_API_KEY` |
122
+ | stepfun | StepFun | `STEPFUN_API_KEY` |
123
+ | xiaomi | 小米 MiMo | `XIAOMI_API_KEY` |
192
124
 
193
- ### 订阅额度制(额度窗口)
125
+ ### 订阅额度制
194
126
 
195
- | 服务商 | 显示名 | 令牌来源 |
127
+ | 服务商 | 显示名 | 凭据 / 数据来源 |
196
128
  |---|---|---|
197
129
  | codex / chatgpt / openai-codex | ChatGPT / Codex | `~/.codex/auth.json`(只读,本机解析) |
198
- | opencode-go / opencode | OpenCode Go | `OPENCODE_GO_API_KEY` 或 opencode 登录文件 |
130
+ | opencode-go / opencode | OpenCode Go | `OPENCODE_GO_API_KEY` 或 opencode CLI 登录 |
199
131
  | zai / zai-coding-cn | 智谱 | `ZAI_CODING_CN_API_KEY`(回退 `ZAI_API_KEY`) |
200
132
  | xiaomi-token-plan-cn / -sgp / -ams | 小米 MiMo | `XIAOMI_TOKEN_PLAN_CN/SGP/AMS_API_KEY`(回退 `XIAOMI_API_KEY`) |
201
- | command / command-code | Command Code | `COMMAND_CODE_API_KEY` / `CMD_API_KEY` 或 `~/.commandcode/auth.json` |
202
- | minimax / minimax-cn | MiniMax | `MINIMAX_API_KEY`(Global)/ `MINIMAX_CN_API_KEY`(国内) |
203
-
204
- ### 账单制(本月真实账单)
205
-
206
- | 服务商 | 显示名 | 凭据 | 数据来源 |
207
- |---|---|---|---|
208
- | together | Together | `TOGETHER_API_KEY` | 官方用量接口(本月花费) |
209
- | fireworks | Fireworks | `FIREWORKS_API_KEY` | 官方账单接口(周期花费) |
210
- | amazon-bedrock | AWS Bedrock | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | Cost Explorer + Budgets(花费 + 预算占比) |
211
- | cloudflare-ai-gateway / cloudflare-workers-ai | Cloudflare | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID`(令牌需 Billing 读权限) | 计费用量接口(真实用量) |
212
-
213
- **不在列表中的服务商**会显示**不支持**提示,而不是拿别的服务商的数据顶上。
214
-
215
- ### 关于模型名 V41-Flash
216
-
217
- 如果你看到 `V41-Flash` 并以为写错了,这里是完整的来龙去脉:
218
-
219
- - **它就是 DeepSeek V4.1 Flash。** 真正在为你提供服务的模型,按 V4.1 Flash 官方公布的高峰/空闲价计费。
220
- - **`DeepSeek-V41-Flash` 是 DSH 自己模型目录里的写法**——模型 id 为 `deepseek-flash`,显示名少了那个点。信息栏刻意与 DSH 的模型切换器保持一致,所以这个写法来自 DSH,不是本插件。
221
- - **它对你的使用没有任何影响。** 同一个模型、同样的价格、同样的额度。信息栏显示的就是 DSH 显示的内容;DSH 哪天改名了,信息栏会自动跟着变,**不需要更新插件**。
133
+ | command / command-code | Command Code | `COMMAND_CODE_API_KEY` 或 `CMD_API_KEY`,或 `~/.commandcode/auth.json` |
134
+ | minimax / minimax-cn | MiniMax | `MINIMAX_API_KEY`(Global)/ `MINIMAX_CN_API_KEY`(国内)—— 必须是 **Subscription Key** |
222
135
 
223
- `DeepSeek-V4-Flash` 则是**另一个**更早的模型 id,不是同一个模型。
136
+ ### 云账单制
224
137
 
225
- ## 界面语言
226
-
227
- 插件跟随 DSH 的 **设置 → 通用 → 语言**。切换语言无需刷新页面即可生效,插件**没有**独立的语言开关。宿主侧文案使用 DSH 已保存的语言偏好;远程浏览器里未保存的语言选择无法改变宿主文案;来自外部服务商/系统的消息保持原文。详见[本地化说明](docs/LOCALIZATION.md)。
228
-
229
- ## 数据存储
230
-
231
- 插件记录的一切都放在它自己的目录里,与其它插件和 DSH 配置完全隔离:
138
+ | 服务商 | 显示名 | 凭据 / 数据来源 |
139
+ |---|---|---|
140
+ | together | Together | `TOGETHER_API_KEY` —— 官方 Usage API,本月花费 |
141
+ | fireworks | Fireworks | `FIREWORKS_API_KEY` —— 官方 Billing API,本周期花费 |
142
+ | amazon-bedrock | AWS Bedrock | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` —— Cost Explorer + Budgets |
143
+ | cloudflare-ai-gateway / cloudflare-workers-ai | Cloudflare | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID`(令牌需 Billing 读权限) |
232
144
 
233
- ```
234
- ~/.dsh/dsh-bottom-info-bar/
235
- ├── usage-records.json # 完整账本,人类可读
236
- ├── usage-records.journal.jsonl # 恢复用流水,请勿手改
237
- └── usage-records.json.bak # 上一份完整快照,用于恢复
238
- ```
145
+ 不在列表中的服务商显示**未适配**提示,绝不借用别的服务商的数据。
239
146
 
240
- - **权限** —— 目录 `0700`、文件 `0600`,启动时自动收敛:只有你自己的账户可读。
241
- - **换位置** —— 设置环境变量 `DSH_BOTTOM_INFO_BAR_DATA_DIR` 可整体迁移(外置硬盘、同步盘等)。
242
- - **查看与迁移** —— 用任意编辑器打开 `usage-records.json`。换电脑时,在 DSH 关闭状态下整目录拷贝即可。
243
- - **内容** —— 每条模型响应一条记录(`id / ts / model / provider / sessionId / input / cacheRead / cacheWrite / output / currency / cost / status`),`status` 为 `completed` 或 `interrupted`。**绝不保存对话内容、提示词或 API Key。**
244
- - **保留** —— 没有静默的条数上限;若需要跨机器长期留存,请把该目录纳入你的常规备份。
245
- - **管理** —— **插件页 → 账单数据**可导出 CSV/JSON,或在确认后清除插件记录。设置、登录信息与价目数据不受影响;**卸载插件也不会删除你的数据**。
147
+ ## 花费记账
246
148
 
247
- > 金额只按当前服务商币种聚合(DeepSeek 为 CNY、OpenAI 参考价为 USD),跨币种绝不混加。DeepSeek 高峰时段为北京时间工作日 09:00–12:00 与 14:00–18:00;周末全天空闲价。
149
+ 每次模型响应都会记录一条(用量 × 单价),并按四个口径聚合:**本会话**(含子代理)、**今天**、**近 30 天**、**累计**。单价在响应完成的那一刻锁定,之后的价目表更新不会改写历史金额。价目表里没有的模型保留 token 用量但不计入金额,绝不编造金额。记录先落盘再计入,重启不丢;写入失败时信息栏显示**账单未保存**。
248
150
 
249
- ## 卸载
151
+ ## 更新
250
152
 
251
- ```bash
252
- cd dsh-bottom-info-bar
253
- ./uninstall.sh
254
- # 或手动:dsh plugin --profile web remove dsh-bottom-info-bar
255
- ```
153
+ 插件在一次完整的 DSH 启动后检查一次 npm 上的新版本。有新版时,信息栏出现红色**新版本提醒**标签:点击即可复制更新命令,到终端执行后重启 `dsh web`。
256
154
 
257
- 重启后原生统计栏自动恢复,无残留。记账数据仍保留在 `~/.dsh/dsh-bottom-info-bar/`——想彻底清零,请在「插件页 → 账单数据」中导出后确认清除。
155
+ | 安装方式 | 命令 |
156
+ |---|---|
157
+ | npm | `dsh plugin --profile <profile> add dsh-bottom-info-bar@latest` |
158
+ | GitHub 地址 | `dsh plugin --profile <profile> add <同一个地址>` |
159
+ | 本地代码或一键脚本 | `git -C <仓库> fetch origin && git -C <仓库> checkout main && git -C <仓库> merge --ff-only origin/main && node <仓库>/scripts/build.mjs` |
258
160
 
259
- ChatGPT 的绑定与令牌维护属于独立插件 `dsh-chatgpt-subscription`,卸载本信息栏不会影响它。
161
+ 插件绝不自动更新——它只负责告诉你「有新版本」。
260
162
 
261
- ## 常见问题
163
+ ## 隐私与安全
262
164
 
263
- | 现象 | 怎么办 |
264
- |---|---|
265
- | 刷新页面后信息栏没出现 | **重启 `dsh web`** —— 宿主在启动时组合插件 |
266
- | 余额显示 **未配置 DEEPSEEK_API_KEY** | 到「设置 → 模型」填入密钥 |
267
- | 余额显示**刷新失败**但仍能看到数字 | 网络或密钥的临时问题;60 秒后自动重试,并保留上次数值。**把鼠标停在警告上**可看详情 |
268
- | OpenCode Go 显示**刷新失败**且提示缺少凭据 | 到「设置 → 模型」填 `OPENCODE_GO_API_KEY`,或用 opencode CLI 登录 |
269
- | ChatGPT 显示**未连接** | 安装配套插件 `dsh-chatgpt-subscription` 并登录 |
270
- | ChatGPT 的套餐或到期时间为空 | 重新登录或重新绑定。若令牌里确实没有这些字段,信息栏会留空而不是猜 |
271
- | 怎么更新插件? | 点红色的**新版本提醒**标签复制命令,然后重启 `dsh web`。详见[更新版本](#更新版本) |
272
- | 更新完了,提醒还在 | 版本号读的是磁盘上已安装的那一份:先刷新页面。若提醒仍在,确认更新真的换掉了已安装的副本(`link:` 安装必须重新构建 `lib/`) |
273
- | 安装报错「这个包没有声明组合包」 | DSH 装到的是一个「根目录不是包」的仓库——那是包移到仓库根之前的本仓库。插件页里改填包名 `dsh-bottom-info-bar`,或用包含该修复的版本上的仓库地址 |
274
- | 模型显示成 `V41-Flash` | 不是错字——那是 DSH 对 **V4.1 Flash** 的写法。详见[关于模型名](#关于模型名-v41-flash) |
275
- | 简洁模式显示的额度窗口和完整模式不同 | 这是有意的:简洁模式优先最短窗口(5 小时 > 周 > 月)。额度与倒计时仍取自同一窗口 |
276
- | 为什么看不到模型的思考过程? | DSH 不在界面上渲染内部推理——这是 DSH 界面层的限制,与本插件无关 |
277
- | 我想换回原来的统计栏 | 卸载插件并重启 |
165
+ - **只读凭据。** API Key 始终留在 DSH 里;`~/.codex/auth.json` 与 `~/.commandcode/auth.json` 只在本机读取,绝不写回。本插件不负责绑定或续期账号。
166
+ - **不保存对话内容。** 账本只记录 token、模型、服务商、币种与花费——没有提示词、消息或密钥。
167
+ - **只存本地。** 数据在 `~/.dsh/dsh-bottom-info-bar/`(目录 `0700`、文件 `0600`)。除了信息栏自身发起的服务商接口请求,没有任何数据离开本机。
168
+ - **卸载不删数据。** 想彻底清零,在插件页的「账单数据」里导出或清除。
278
169
 
279
170
  ## 开发
280
171
 
281
- - **源码** —— `src/host.js`(宿主侧)与 `src/client-bundle.js`(客户端)
282
- - **构建** —— `npm run build`(重新生成 `lib/`;`lib/` 已入库,所以直接装 GitHub 地址无需构建,CI 会检查它与 `src/` 一致)
283
- - **测试** —— `node tests/run-all.mjs`(先构建,再跑全部测试套件)
284
- - **版本记录** —— [CHANGELOG.md](CHANGELOG.md)
285
- - **参与贡献** —— [CONTRIBUTING.md](CONTRIBUTING.md);日常流程与发布机制见 [docs/WORKFLOW.md](docs/WORKFLOW.md)
286
-
287
- 这个项目所有"踩过坑才学会"的规矩都由 **CI 强制执行**而不是靠口头约定——测试套件、源码守卫、发布链条契约都会在每个 PR 上运行。Agent 需要遵守的约束见 [AGENTS.md](AGENTS.md)。
172
+ - 构建:`npm run build`
173
+ - 测试:`node tests/run-all.mjs`
174
+ - 贡献:[CONTRIBUTING.md](CONTRIBUTING.md)
288
175
 
289
176
  ## 许可证
290
177
 
291
178
  [MIT](LICENSE) © 2026 songoao25
179
+
180
+ 💬 有问题或想法?扫码加入微信群 **DeepThinking**:
181
+
182
+ <img src="assets/wechat-group.png" width="180" alt="微信群 DeepThinking 二维码">
package/lib/client.js CHANGED
@@ -158,6 +158,9 @@ const RPC_TIMEOUT_MS = 20000;
158
158
  // 不必干等后台 60s 自动周期。覆盖窗口需容纳会话模型从空到确定的短暂翻转期。
159
159
  const BOOT_AT = Date.now();
160
160
  const FORCE_REFRESH_WINDOW_MS = 6000;
161
+ // 版本提醒的重读间隔:npm 侧由 host 按 TTL 缓存,这里只是读本地接口,
162
+ // 60 秒一次足够让「本地副本更新完」的提醒自己消失,也不会给宿主添负担。
163
+ const UPDATE_INFO_REFRESH_MS = 60000;
161
164
 
162
165
  // 复制文本到剪贴板。
163
166
  // 优先用异步剪贴板 API——它只在「安全上下文」可用:本 GUI 走 http://127.0.0.1 属安全上下文,
@@ -2723,13 +2726,30 @@ module.exports = {
2723
2726
  return function () { document.removeEventListener(BIB_LEDGER_EVENT, onLedgerChanged); };
2724
2727
  }, [load, activeSessionModel]);
2725
2728
 
2726
- // 版本检查由 host 在进程启动时完成;这里仅读取一次缓存,不轮询 NPM。
2729
+ // 版本信息:打开页面读一次,之后每 60 秒、以及页面重新可见/窗口重新获得焦点时重读。
2730
+ // 这样本地副本更新完(git pull + 重建,或 dsh plugin add …@latest)之后,红色提醒会自己消失,
2731
+ // 不必等用户刷新页面或重启 dsh web —— 2026-09-24 用户报的「更新完还显示提醒」就出在这里。
2732
+ // 每次读的都是本地 host 接口,NPM 只在 host 侧按 TTL 重查,这里不轮询 NPM。
2727
2733
  React.useEffect(function () {
2728
2734
  let active = true;
2729
- rpc('getUpdateInfo').then(function (info) {
2730
- if (active && info && typeof info.current === 'string') setUpdateInfo(info);
2731
- }).catch(function () { /* 版本检查失败静默,不影响信息栏 */ });
2732
- return function () { active = false; };
2735
+ const readUpdateInfo = function () {
2736
+ rpc('getUpdateInfo').then(function (info) {
2737
+ if (active && info && typeof info.current === 'string') setUpdateInfo(info);
2738
+ }).catch(function () { /* 版本检查失败静默,不影响信息栏 */ });
2739
+ };
2740
+ readUpdateInfo();
2741
+ const timer = window.setInterval(readUpdateInfo, UPDATE_INFO_REFRESH_MS);
2742
+ const onVisible = function () {
2743
+ if (document.visibilityState === 'visible') readUpdateInfo();
2744
+ };
2745
+ document.addEventListener('visibilitychange', onVisible);
2746
+ window.addEventListener('focus', onVisible);
2747
+ return function () {
2748
+ active = false;
2749
+ window.clearInterval(timer);
2750
+ document.removeEventListener('visibilitychange', onVisible);
2751
+ window.removeEventListener('focus', onVisible);
2752
+ };
2733
2753
  }, []);
2734
2754
 
2735
2755
  // 会话统计变化(回复中 turns/steps/tokens 增长,回复完成时停止)→ 防抖后即时刷新花费,
package/lib/index.js CHANGED
@@ -243,24 +243,49 @@ function updateCommandForInstall() {
243
243
  return { installMode: 'npm', updateCommand: npmCommand }
244
244
  }
245
245
 
246
- async function checkLatestVersion() {
247
- const current = packageVersion()
246
+ // npm 上的最新版本:进程内缓存 + TTL 定期重查。
247
+ // 两个「版本」的时间尺度必须分开,否则就会出现「本地更新完了,提醒还挂着」:
248
+ // · latest(npm 发布版)——变化慢,按 TTL 重查(默认 15 分钟),新版本不必等宿主重启才出现;
249
+ // · current(本机已安装版)——每次 RPC 都从磁盘重读,更新完本地副本后提醒会自己消失。
250
+ // 2026-09-24 用户报的正是这里:旧实现只在进程启动时查一次 npm,客户端也只读一次结果,
251
+ // 于是更新完本地副本后,提醒要等刷新页面(甚至重启宿主)才消失。
252
+ const UPDATE_LATEST_TTL_MS = 15 * 60 * 1000
253
+ const UPDATE_LATEST_RETRY_MS = 60 * 1000
254
+
255
+ // TTL 可被测试覆盖(生产不设置):0 表示每次都重查。
256
+ function updateLatestTtlMs() {
257
+ const raw = Number(process.env.DSH_BOTTOM_INFO_BAR_UPDATE_TTL_MS)
258
+ return Number.isFinite(raw) && raw >= 0 ? raw : UPDATE_LATEST_TTL_MS
259
+ }
260
+
261
+ const updateLatestCache = { value: null, expiresAt: 0, pending: null }
262
+
263
+ // 查一次 npm。任何失败都返回 null —— 提醒宁可不显示,也不误报。
264
+ function fetchLatestVersion() {
248
265
  const controller = new AbortController()
249
266
  const timer = setTimeout(() => controller.abort(), UPDATE_CHECK_TIMEOUT_MS)
250
- try {
251
- const response = await fetch(UPDATE_REGISTRY_URL, {
252
- headers: { accept: 'application/json' },
253
- signal: controller.signal,
254
- })
255
- if (!response.ok) return { available: false, current: current, latest: null }
256
- const body = await response.json()
257
- const latest = body && typeof body.version === 'string' ? body.version : null
258
- return { available: !!latest && compareVersions(latest, current) > 0, current: current, latest: latest }
259
- } catch {
260
- return { available: false, current: current, latest: null }
261
- } finally {
262
- clearTimeout(timer)
263
- }
267
+ return fetch(UPDATE_REGISTRY_URL, {
268
+ headers: { accept: 'application/json' },
269
+ signal: controller.signal,
270
+ }).then((response) => (response.ok ? response.json() : null))
271
+ .then((body) => (body && typeof body.version === 'string' ? body.version : null))
272
+ .catch(() => null)
273
+ .finally(() => clearTimeout(timer))
274
+ }
275
+
276
+ // 读最新版本:命中缓存直接返回;过期则重查(并发调用共用同一次请求)。
277
+ // 查失败时保留上一次的已知值(网络抖动不该让提醒闪烁),并把下次重试提前到 1 分钟。
278
+ function latestVersion() {
279
+ const now = Date.now()
280
+ if (now < updateLatestCache.expiresAt) return Promise.resolve(updateLatestCache.value)
281
+ if (updateLatestCache.pending) return updateLatestCache.pending
282
+ const pending = fetchLatestVersion().then((value) => {
283
+ if (value !== null) updateLatestCache.value = value
284
+ updateLatestCache.expiresAt = Date.now() + (value !== null ? updateLatestTtlMs() : UPDATE_LATEST_RETRY_MS)
285
+ return updateLatestCache.value
286
+ }).finally(() => { updateLatestCache.pending = null })
287
+ updateLatestCache.pending = pending
288
+ return pending
264
289
  }
265
290
 
266
291
  // ---------- 双模式(余额制 / 订阅制)配置 ----------
@@ -1670,8 +1695,8 @@ export default {
1670
1695
  const t = hostLocale.createHostTranslator(ctx);
1671
1696
  // 窗口标签:在 apply 内部按当前语言偏好重新计算(模块顶层 WINDOW_LABELS 仅作安全兜底)。
1672
1697
  const windowLabels = { five_hour: t('host.hour'), seven_day: t('ui.weekly'), monthly: t('ui.monthly') };
1673
- // 版本检查只在 host 进程启动时发起一次;客户端后续只读取这个缓存结果。
1674
- const updateInfoPromise = checkLatestVersion()
1698
+ // 预热一次 npm 版本查询(失败不影响信息栏);此后由 latestVersion() 按 TTL 自行重查。
1699
+ latestVersion()
1675
1700
  // 会话谱系列表是冷安全读取,但仍可能触发持久化查询;短暂缓存避免信息栏轮询
1676
1701
  // 每次都重新扫描全部会话。缓存失效时再次读取,保证新建子代理最终能被纳入。
1677
1702
  const SESSION_LINEAGE_CACHE_MS = 1000
@@ -4516,14 +4541,16 @@ export default {
4516
4541
  const ROUTE_PREFIX = '/_dsh/dsh-bottom-info-bar';
4517
4542
  const ROUTES = {
4518
4543
  getUpdateInfo: async function () {
4519
- // npm 的最新版本只在进程启动时查一次;但「已安装版本」每次都从磁盘重读——
4520
- // 用户更新完本地副本后刷新页面就能看到新版本、提醒随之消失,不必等宿主重启。
4544
+ // latest 按 TTL 重查、current 每次从磁盘重读(见 latestVersion):
4545
+ // 客户端每 60 秒读一次这个接口,所以本地副本更新完后提醒会自己消失,
4546
+ // 不必刷新页面或重启宿主。
4521
4547
  // 命令按安装形态区分,避免把 link: 安装的用户引导到 npm 命令而丢掉本地代码。
4522
- const info = await updateInfoPromise
4548
+ const latest = await latestVersion()
4523
4549
  const current = packageVersion()
4524
- return Object.assign({}, info, updateCommandForInstall(), {
4550
+ return Object.assign({}, updateCommandForInstall(), {
4525
4551
  current: current,
4526
- available: !!info.latest && compareVersions(info.latest, current) > 0,
4552
+ latest: latest,
4553
+ available: !!latest && compareVersions(latest, current) > 0,
4527
4554
  })
4528
4555
  },
4529
4556
  getBalanceSnapshot: async function (args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-bottom-info-bar",
3
- "version": "1.16.1",
3
+ "version": "1.16.2",
4
4
  "description": "Shows the current model, balance and spend under the composer.",
5
5
  "keywords": [
6
6
  "deepseek",