dsh-balance-widget 0.5.4 → 0.6.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
@@ -1,6 +1,6 @@
1
1
  # dsh-balance-widget
2
2
 
3
- [中文](README.md) | **English**
3
+ [中文](https://github.com/LL-cmyk-so/dsh-balance-widget/blob/main/README.md) | **English**
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/dsh-balance-widget?style=flat-square&label=npm)](https://www.npmjs.com/package/dsh-balance-widget)
6
6
  [![Stars](https://img.shields.io/github/stars/LL-cmyk-so/dsh-balance-widget?style=flat-square&label=Stars)](https://github.com/LL-cmyk-so/dsh-balance-widget)
@@ -9,13 +9,13 @@
9
9
  [![Node 24](https://img.shields.io/badge/Node%2024-ready-brightgreen?style=flat-square)](https://nodejs.org)
10
10
  [![Zero deps](https://img.shields.io/badge/dependencies-zero-brightgreen?style=flat-square)]()
11
11
 
12
- A balance & cost widget for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) Web GUI: a persistent sidebar footer card shows the account balance and today's cost; clicking opens a five-tier cost breakdown (balance / last prompt with its session name / today-this-session / today-this-workspace / today-all-workspaces).
12
+ A balance & cost widget for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) **Web GUI and desktop app**: a persistent sidebar footer card shows the account balance and today's cost; clicking opens a five-tier cost breakdown (balance / last prompt with its session name / today-this-session / today-this-workspace / today-all-workspaces).
13
13
 
14
14
  ## Preview
15
15
 
16
16
  | Sidebar card | Five-tier cost popover |
17
17
  | --- | --- |
18
- | ![Sidebar card](docs/screenshot-corner.png) | ![Cost popover](docs/screenshot-popover.png) |
18
+ | ![Sidebar card](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-corner.png) | ![Cost popover](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-popover.png) |
19
19
 
20
20
  ## How it differs from similar plugins
21
21
 
@@ -28,7 +28,7 @@ A balance & cost widget for the [DeepSeek Harness](https://github.com/deepseek-a
28
28
  | **Peak/off-peak pricing** | ✅ Built-in official 2026-09-10 rate table, re-synced from the official page at startup and every 12h | Partial support |
29
29
  | **Security** | ✅ API key stays in the host process; loopback-only guard | Varies |
30
30
 
31
- **In one line**: *The zero-dependency, Node 24-ready balance/cost widget that never breaks `dsh web` boot.*
31
+ **In one line**: *The zero-dependency, Node 24-ready balance/cost widget that never breaks `dsh web` or the desktop app on boot.*
32
32
 
33
33
  ## Features
34
34
 
@@ -37,7 +37,7 @@ A balance & cost widget for the [DeepSeek Harness](https://github.com/deepseek-a
37
37
  - **Today · this session cost (estimate)** — The current session's usage today (calendar day) × DeepSeek's official peak/off-peak price table. Follows the configured model (default `deepseek-v4-flash`, switchable to `deepseek-v4-pro`) and the Beijing-time peak/off-peak windows automatically.
38
38
  - **Today · this workspace cost (estimate)** — Sums today's token usage × price across every session in the current workspace (anchored by the current session).
39
39
  - **Today · all workspaces cost (estimate)** — Walks every session under `~/.dsh/sessions/` and sums today's (calendar day) token usage × price.
40
- - **Peak/off-peak status** — The card and popover borders are tinted by the current window (orange at peak / green at off-peak), a "Peak/Off-peak" tag sits next to the popover title, and hovering it shows the current price tier (input/output per 1M tokens).
40
+ - **Peak/off-peak status** — a status dot before the card's amount is tinted by the current window (amber at peak / green at off-peak), a "Peak/Off-peak" tag sits next to the popover title, and hovering it shows the current price tier (input/output per 1M tokens).
41
41
  - **Token usage** — Also shows the session's input (incl. cache hits) / output tokens.
42
42
  - **One-click top-up** — a "Top up" link in the popover footer jumps to the official DeepSeek top-up page (platform.deepseek.com/top_up) in a new tab.
43
43
  - **Sidebar card** — a persistent card at the sidebar footer shows balance and today's cost; globally visible, auto-refreshes every 60s (balance via the official API, costs parsed locally), and opening the popover triggers an immediate refresh.
@@ -63,7 +63,7 @@ host half (lib/index.js)
63
63
 
64
64
  client half (lib/client.js)
65
65
  ctx.slots.inject("sidebar.footer.action")
66
- → persistent sidebar footer card (balance + today's cost, peak-tinted border)
66
+ → persistent sidebar footer card (balance + today's cost, peak/off-peak status dot; a 36×36 icon button while the sidebar is collapsed)
67
67
  → click opens five-tier cost popover + peak tag + ⓘ term explanations
68
68
  ```
69
69
 
@@ -85,6 +85,8 @@ dsh plugin --profile web add "link:$(pwd)"
85
85
 
86
86
  Then restart `dsh web`.
87
87
 
88
+ **The desktop app (DeepSeek Harness.app) is supported too**, with `desktop` as the install target: install it from the desktop app's sidebar "Plugins" panel, or add the package to `~/.dsh/profiles/desktop/package.json` (dependency plus `dsh.profile.bundles`), then restart the desktop app. Because the desktop app is launched by the GUI, note that this plugin shells out to nothing — there is no PATH to configure.
89
+
88
90
  ## Configuration
89
91
 
90
92
  ### Where the config file lives
@@ -172,7 +174,7 @@ This section is for the DSH Store / plugin audit: dependencies, runtime permissi
172
174
  **Runtime permissions**
173
175
  - `files`: reads only `~/.dsh/sessions/` session JSONL (cost stats); never writes or mutates any session file
174
176
  - `network`: only the DeepSeek official endpoints — `api.deepseek.com` (`GET /user/balance`) and `api-docs.deepseek.com` pricing page (fetched every 12h); no third-party proxy
175
- - `commands`: spawns `zstd -d -c` to decompress session files (macOS needs `brew install zstd`); no other commands
177
+ - `commands`: **none**. Session logs are decompressed with Node's built-in zstd (`node:zlib`, needs Node >=22.15), so no `zstd` binary and no `PATH` dependency
176
178
  - `credentials`: reads `DEEPSEEK_API_KEY` (resolved via the host credentials service), used only in the host process behind a loopback-only route guard; the browser never sees the key
177
179
  - All host routes are bound to the loopback address and unreachable externally
178
180
 
@@ -183,12 +185,30 @@ This section is for the DSH Store / plugin audit: dependencies, runtime permissi
183
185
  **Failure bounds**
184
186
  - Balance fetch failure: the panel shows the error and keeps the last successful snapshot (no interruption)
185
187
  - Pricing fetch failure: falls back to the built-in 2026-09-10 rate table, `pricingSource` marked `default` (`synced` once parsing succeeds)
186
- - Missing `zstd`: returns an actionable error (points to the install command) instead of failing silently
188
+ - Node without built-in zstd (<22.15), or a session file that yields no frame: returns an actionable error instead of failing silently
187
189
  - Missing/corrupt session files: that session is skipped; other sessions are unaffected
188
190
  - All costs are estimates; the provider's bill is authoritative
189
191
 
190
192
  ## Changelog
191
193
 
194
+ ### v0.6.0 — desktop app support, and a UI that matches the desktop shell
195
+ - 🖥️ **Desktop app support (DSH 0.1.7 / DeepSeek Harness.app)**
196
+ - **Session logs are no longer decompressed by the `zstd` CLI** — Node's built-in zstd (`node:zlib`) does it instead. The desktop app is launched by the GUI, so its PATH is only `/usr/bin:/bin:/usr/sbin:/sbin` and the Homebrew `zstd` is invisible: every cost tier (today's spend, last prompt) failed outright there
197
+ - **v4 session logs are decoded frame by frame.** The log is append-only: each flush appends its own zstd frame (a measured 726 KB v4 log held 97 of them). Node's one-shot decompressor returns only the first frame (258 B) — and silently dropping 99% of the data is worse than failing. Frames are now located by their magic number; v0 / v3 / v4 logs decode byte-identically to `zstd -dc`
198
+ - **The refresh glyph is resolved per DSH version.** 0.1.7 renamed product icons to size-neutral weights (`IconRefreshOutlineRegular` plus a `size` prop) and dropped `IconRefreshOutline14`; rendering the missing name as a component throws React #130, and the sidebar slot answers a crashed entry by dropping the whole card — the "card flashes, then vanishes on click" report. The glyph is now resolved from whichever name exists, with a local SVG fallback
199
+ - **The client inject declaration is empty**: `@deepseek-ai/dsh-client-runtime` was never a real package (absent in both 0.1.5 and 0.1.7); the browser half needs only react and the shell's seed modules
200
+ - 🎨 **Visual alignment with the desktop shell**
201
+ - Dropped the card's state border (green off-peak / amber peak, which read as "selected") in favour of a 6px status dot before the amount; fill, 12px radius and 14px amount text now use the same design tokens as the desktop's own session rows
202
+ - The card fills the sidebar column — it used to be flex-shrunk to 131px and overflowed by 8px
203
+ - **New collapsed-rail state**: in the 56px icon rail the card becomes a 36×36 icon button (matching the rail's other buttons) with the status dot as a badge
204
+ - **The popover is portalled into `document.body`**: the sidebar column clips its children (`overflow:hidden`), so in the rail the panel was cut off at the sidebar edge. Its width now follows the card (measured at open time, 240px floor) and re-anchors through a `ResizeObserver` while the sidebar or the window is resized
205
+ - Shadow and focus ring now come from the shell's tokens (`--dsw-shadow-lv3`, `--dsw-focus-ring-*`)
206
+ - 📄 Docs: both README screenshots regenerated for the new look
207
+
208
+ ### v0.5.5 — README assets now render on the npm package page
209
+ - 🐛 **Fixed**: the screenshots and the language switch used repository-relative paths (`docs/screenshot-corner.png`, `README.en.md`). The npm package page renders the README body only and does not resolve in-repo paths, so both screenshots and the language link were broken on npm. They are now absolute URLs: screenshots via `raw.githubusercontent.com`, the language switch via a GitHub blob link
210
+ - 📦 **Scope**: documentation only; no code change
211
+
192
212
  ### v0.5.4 — search calls are now counted (previously dropped entirely)
193
213
  - 🐛 **Fixed**: the auxiliary model calls behind `web_search` contributed nothing to cost. DSH writes only a pre-dispatch `web/deepseek-search-llm-request` event per search (no usage), so any log-derived pricing undercounts systematically — measured 2026-09-22: all 1073 searches of the day were missing. Searches are now priced as requests × a calibrated constant and attributed to their session / workspace / day and turn
194
214
  - 📏 **Calibration**: ~6k cache-miss input + ~2k output tokens per search (¥0.014 off-peak, ¥0.028 at peak). Measured 2026-09-22 from the balance delta: 14 searches in one window, balance delta ¥0.28, minus ¥0.094 of log-derivable conversation cost → **¥0.0133 per search**
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-balance-widget
2
2
 
3
- **中文** | [English](README.en.md)
3
+ **中文** | [English](https://github.com/LL-cmyk-so/dsh-balance-widget/blob/main/README.en.md)
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/dsh-balance-widget?style=flat-square&label=npm)](https://www.npmjs.com/package/dsh-balance-widget)
6
6
  [![Stars](https://img.shields.io/github/stars/LL-cmyk-so/dsh-balance-widget?style=flat-square&label=Stars)](https://github.com/LL-cmyk-so/dsh-balance-widget)
@@ -9,13 +9,13 @@
9
9
  [![Node 24](https://img.shields.io/badge/Node%2024-ready-brightgreen?style=flat-square)](https://nodejs.org)
10
10
  [![Zero deps](https://img.shields.io/badge/dependencies-zero-brightgreen?style=flat-square)]()
11
11
 
12
- DeepSeek Harness (DSH) Web GUI 的余额与成本小部件:侧边栏底部常驻卡片显示账户余额与今日花费,点击弹出五层级成本明细(余额 / 最近提问·标注会话名 / 今日·本会话 / 今日·本工作区 / 今日·所有工作区)。
12
+ DeepSeek Harness (DSH) **Web GUI 与桌面版**通用的余额与成本小部件:侧边栏底部常驻卡片显示账户余额与今日花费,点击弹出五层级成本明细(余额 / 最近提问·标注会话名 / 今日·本会话 / 今日·本工作区 / 今日·所有工作区)。
13
13
 
14
14
  ## 效果预览
15
15
 
16
16
  | 侧边栏卡片(左下角常驻) | 点击弹出五层级成本 |
17
17
  | --- | --- |
18
- | ![侧边栏卡片](docs/screenshot-corner.png) | ![成本明细弹框](docs/screenshot-popover.png) |
18
+ | ![侧边栏卡片](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-corner.png) | ![成本明细弹框](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-popover.png) |
19
19
 
20
20
  ## 与同类插件的区别
21
21
 
@@ -28,7 +28,7 @@ DeepSeek Harness (DSH) Web GUI 的余额与成本小部件:侧边栏底部常
28
28
  | **峰谷定价** | ✅ 内置官方 2026-09-10 峰谷价表,启动时与每 12h 自动从官方页刷新 | 部分支持 |
29
29
  | **安全性** | ✅ API key 仅在宿主进程,loopback-only 守卫 | 参差不齐 |
30
30
 
31
- **一句话**:*零依赖、Node 24 就绪、永不拖垮 dsh web 启动的余额/成本小部件。*
31
+ **一句话**:*零依赖、Node 24 就绪、在 `dsh web` 与桌面版都不拖垮启动的余额/成本小部件。*
32
32
 
33
33
  ## 功能
34
34
 
@@ -37,7 +37,7 @@ DeepSeek Harness (DSH) Web GUI 的余额与成本小部件:侧边栏底部常
37
37
  - **今日·本会话成本(估算)** — 当前会话今天(自然日)产生的 token 用量 × DeepSeek 官方峰谷定价表计算,随当前会话模型(默认 `deepseek-v4-flash`,可在配置中改为 `deepseek-v4-pro`)与北京时间高峰/空闲时段自动切换。
38
38
  - **今日·本工作区成本(估算)** — 遍历当前工作区(由当前会话锚定)下的所有会话,累加今天的 token 用量 × 单价。
39
39
  - **今日·所有工作区成本(估算)** — 遍历 `~/.dsh/sessions/` 下所有工作区的所有会话,累加今天的 token 用量 × 单价。
40
- - **峰谷状态标签** — 卡片与弹框边框按当前时段着色(峰时橙色 / 谷时绿色),弹框标题旁显示「峰时/谷时」标签,悬停可查看当前价格档位(输入/输出单价)。
40
+ - **峰谷状态标签** — 卡片金额前的状态圆点按当前时段着色(峰时琥珀 / 谷时绿色),弹框标题旁显示「峰时/谷时」标签,悬停可查看当前价格档位(输入/输出单价)。
41
41
  - **Token 用量** — 同时展示输入(含缓存命中)/ 输出 token 数。
42
42
  - **一键充值** — 弹层底部「去充值」链接直达 DeepSeek 官方充值页(platform.deepseek.com/top_up),新窗口打开。
43
43
  - **侧边栏常驻卡片** — 侧边栏底部(设置上方)显示余额 + 今日花费,全局可见;每 60 秒自动刷新(余额走官方接口、成本为本地会话解析),打开弹框时也会立即刷新一次,无需手动操作。
@@ -58,7 +58,7 @@ host 半区 (lib/index.js)
58
58
 
59
59
  client 半区 (lib/client.js)
60
60
  ctx.slots.inject("sidebar.footer.action")
61
- → 侧边栏底部常驻卡片(余额 + 今日花费,峰/谷时段描边着色)
61
+ → 侧边栏底部常驻卡片(余额 + 今日花费,峰/谷时段状态圆点;侧边栏收起时变为 36×36 图标按钮)
62
62
  → 点击弹出五层级成本明细 + 峰谷标签 + ⓘ 名词解释
63
63
  ```
64
64
 
@@ -80,6 +80,8 @@ dsh plugin --profile web add "link:$(pwd)"
80
80
 
81
81
  装完重启 `dsh web` 生效。
82
82
 
83
+ **桌面版(DeepSeek Harness.app)同样支持**,安装目标是 `desktop` profile:可在桌面版侧边栏的「插件」面板里安装,或把包加进 `~/.dsh/profiles/desktop/package.json` 的依赖与 `dsh.profile.bundles`;装完**重启桌面版**生效。桌面版由 GUI 启动,本插件不依赖外部命令行,无需额外配置 PATH。
84
+
83
85
  ## 配置
84
86
 
85
87
  ### 配置文件在哪
@@ -169,7 +171,7 @@ DSH 的插件配置统一放在这个文件里:
169
171
  **运行时权限**
170
172
  - `files`:只读 `~/.dsh/sessions/` 下的会话 JSONL(成本统计);不写入、不修改任何会话文件
171
173
  - `network`:仅请求 DeepSeek 官方端点——`api.deepseek.com`(`GET /user/balance` 余额)与 `api-docs.deepseek.com` 定价页(每 12h 抓取);不走任何第三方代理
172
- - `commands`:spawn `zstd -d -c` 解压会话文件(macOS 需 `brew install zstd`);不执行其他命令
174
+ - `commands`:**不执行任何命令**。会话解压改用 Node 内置 zstd(`node:zlib`,需 Node ≥22.15),不再依赖 `zstd` 可执行文件与 `PATH`
173
175
  - `credentials`:读取 `DEEPSEEK_API_KEY`(经宿主凭据服务解析),仅宿主进程使用、loopback-only 路由守卫;浏览器不接触密钥
174
176
  - 所有 host 路由均绑定 load 回环地址,外部不可达
175
177
 
@@ -180,12 +182,30 @@ DSH 的插件配置统一放在这个文件里:
180
182
  **失败边界**
181
183
  - 余额接口失败:面板提示失败信息,保留上次成功快照(不中断)
182
184
  - 定价页抓取失败:回退内置 2026-09-10 价目表,`pricingSource` 标记为 `default`(解析成功则为 `synced`)
183
- - `zstd` 缺失:返回可读错误提示(指引安装),而非静默失败
185
+ - Node 无内置 zstd(<22.15)或会话文件解不出任何帧:返回可读错误提示,而非静默失败
184
186
  - 会话文件缺失/损坏:跳过该会话,不影响其他会话统计
185
187
  - 所有成本为估算值,实际以官方账单为准
186
188
 
187
189
  ## 版本历史
188
190
 
191
+ ### v0.6.0 — 适配 DSH 桌面版,界面与桌面端视觉对齐
192
+ - 🖥️ **适配桌面版(DSH 0.1.7 / DeepSeek Harness.app)**
193
+ - **会话解压不再依赖 `zstd` 命令行**,改用 Node 内置 zstd(`node:zlib`)。桌面版由 GUI 启动,进程 PATH 只有 `/usr/bin:/bin:/usr/sbin:/sbin`,找不到 Homebrew 的 zstd——此前在桌面端「今日花费 / 最近一次提问」等所有成本档位都会直接报错
194
+ - **按帧解压 v4 会话日志**:日志是 append-only 的,每次 flush 追加一个独立 zstd 帧(实测一份 726 KB 的 v4 日志含 97 帧)。Node 的一次性解压只返回第一帧(258 B)——比报错更危险的是它会静默丢掉 99% 以上的数据。现按帧魔数逐帧解码,v0 / v3 / v4 三种日志与 `zstd -dc` 输出字节级一致
195
+ - **刷新图标按版本解析**:0.1.7 把产品图标改为尺寸中性权重(`IconRefreshOutlineRegular` + `size` 属性),旧名 `IconRefreshOutline14` 已不存在;把它当组件渲染会抛 React #130,而侧边栏槽位对崩溃条目的处理是整条摘掉——表现为「卡片闪一下、一点开就消失」。现按可用名解析并带本地 SVG 兜底
196
+ - **客户端注入声明清空**:`@deepseek-ai/dsh-client-runtime` 从不是真实包(0.1.5 与 0.1.7 都没有),浏览器端只依赖 react 与 shell 的 seed 模块
197
+ - 🎨 **视觉贴合桌面端**
198
+ - 去掉卡片的状态边框(谷时绿 / 峰时琥珀,看着像「被选中」);峰/谷改为金额前的 6px 状态圆点。填充色、12px 圆角、金额 14px 字号均对齐桌面端会话行所用的设计 token
199
+ - 卡片占满侧边栏列:此前被 flex 收缩到 131px 宽并横向溢出 8px
200
+ - **新增侧边栏收起态**:在 56px 图标轨里渲染 36×36 图标按钮(与轨道其它按钮同规格),状态点变为图标角标
201
+ - **弹层改挂 `document.body`**:侧边栏列是 `overflow:hidden`,收起态下弹层此前会被裁到只剩左边一小条;面板宽度跟随卡片(打开时实测,240px 起),并用 `ResizeObserver` 在拖动侧边栏 / 缩放窗口时实时跟随
202
+ - 阴影与焦点环改用桌面端 token(`--dsw-shadow-lv3`、`--dsw-focus-ring-*`)
203
+ - 📄 文档:README 两张截图按新外观重新生成
204
+
205
+ ### v0.5.5 — 修复 README 在 npm 包页面上的显示
206
+ - 🐛 **修复**:README 的截图与语言切换此前用相对路径(`docs/screenshot-corner.png`、`README.en.md`)。npm 包页面只渲染 README 正文、不解析仓库内的相对路径,所以在 npm 上两张截图和语言链接都是坏的。现改为绝对 URL:截图走 `raw.githubusercontent.com`,语言切换走 GitHub blob 链接
207
+ - 📦 **范围**:仅文档,代码无变化
208
+
189
209
  ### v0.5.4 — 计入搜索调用(此前全部漏算)
190
210
  - 🐛 **修复**:`web_search` 的辅助模型调用完全不计入成本。DSH 对每次搜索只写一个派发前的 `web/deepseek-search-llm-request` 事件(无 usage),所以按会话日志计价的实现系统性漏算——实测 2026-09-22 一天 1073 次搜索全部没进账。现按「请求次数 × 标定常数」计价,并归入该搜索所属的会话 / 工作区 / 当日及当轮
191
211
  - 📏 **标定**:单次搜索 ≈ 6k 缓存未命中输入 + 2k 输出 token(谷时 ¥0.014、峰时 ¥0.028)。2026-09-22 用余额差实测:同一窗口 14 次搜索、余额差 ¥0.28、扣掉日志可算的对话成本 ¥0.094,得 **¥0.0133/次**
Binary file
Binary file
package/lib/client.js CHANGED
@@ -6,9 +6,44 @@ window.__ModuleLoader__.load({
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
8
  let react = require("react");
9
+ let react_dom = require("react-dom");
9
10
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
+ /**
12
+ * Glyph of the popover's refresh button. DSH 0.1.7 renamed product icons to
13
+ * size-neutral weights (`IconRefreshOutlineRegular` plus a `size` prop —
14
+ * primitives README, decision 2026-09-16); 0.1.5 exported only the
15
+ * artboard-sized `IconRefreshOutline14`. Resolve once and fall back to a
16
+ * local glyph: rendering `undefined` as a component throws React #130, and
17
+ * the slot answers by dropping the whole card.
18
+ */
19
+ const RefreshGlyph = [
20
+ _deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular,
21
+ _deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineMedium,
22
+ _deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14
23
+ ].find((glyph) => typeof glyph === "function" || typeof glyph === "object" && glyph !== null);
24
+ /** Refresh glyph element, stroke-matched to the card's own 16px icon. */
25
+ function refreshGlyph() {
26
+ if (RefreshGlyph !== void 0) return (0, react_jsx_runtime.jsx)(RefreshGlyph, {
27
+ size: 14
28
+ });
29
+ return (0, react_jsx_runtime.jsxs)("svg", {
30
+ width: 14,
31
+ height: 14,
32
+ viewBox: "0 0 24 24",
33
+ fill: "none",
34
+ stroke: "currentColor",
35
+ strokeWidth: 1.7,
36
+ strokeLinecap: "round",
37
+ strokeLinejoin: "round",
38
+ children: [(0, react_jsx_runtime.jsx)("polyline", {
39
+ points: "23 4 23 10 17 10"
40
+ }), (0, react_jsx_runtime.jsx)("path", {
41
+ d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10"
42
+ })]
43
+ });
44
+ }
10
45
  //#region lib/types/client/styles.js
11
- const css = ".dshbw_row{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:2px 0}.dshbw_label{display:inline-flex;align-items:center;min-width:0}.dshbw_val{font-weight:600;color:var(--dsw-alias-label-primary)}.dshbw_val[data-tier=\"sec\"]{font-weight:400;color:var(--dsw-alias-label-secondary)}.dshbw_val[data-tier=\"ter\"]{font-weight:400;color:var(--dsw-alias-label-tertiary)}.dshbw_val[data-level=\"1\"]{color:var(--dsw-alias-state-warn-primary)}.dshbw_val[data-level=\"2\"]{color:var(--dsw-alias-state-error-primary)}.dshbw_err{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:16px;margin-top:6px}.dshbw_link{background:none;border:none;cursor:pointer;color:var(--dsw-alias-state-business-primary);display:inline-flex;align-items:center;gap:4px;padding:2px 6px;border-radius:6px;font-size:12px;line-height:18px;font-family:inherit;text-decoration:none}.dshbw_link:hover{color:var(--dsw-alias-state-business-primary);text-decoration:underline}.dshbw_sideTop{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12px;line-height:18px}.dshbw_sideAmount{font-weight:600}.dshbw_sideAmount[data-level=\"1\"]{color:var(--dsw-alias-state-warn-primary);font-weight:600}.dshbw_sideAmount[data-level=\"2\"]{color:var(--dsw-alias-state-error-primary)}.dshbw_sideFoot{display:flex;justify-content:space-between;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.dshbw_sideWrap{position:relative}.dshbw_side{display:flex;flex-direction:column;gap:4px;width:100%;padding:6px 8px;margin:0 8px 4px;border:none;border-radius:6px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);cursor:pointer;font-family:inherit;text-align:left;transition:transform 120ms cubic-bezier(0.23,1,0.32,1),background 120ms cubic-bezier(0.23,1,0.32,1)}.dshbw_side:active{transform:scale(.97)}.dshbw_side:hover{background:var(--dsw-alias-interactive-bg-hover)}.dshbw_sidePop{position:absolute;bottom:calc(100% + 8px);left:8px;z-index:70;min-width:240px;background:var(--dsw-alias-bg-layer-3);border:none;border-radius:12px;box-shadow:0 0 1px rgba(0,0,0,.3),0 4px 14px rgba(0,0,0,.1);padding:12px 12px;font-size:12px;line-height:20px;color:var(--dsw-alias-label-primary);animation:dshbw-pop-in 160ms cubic-bezier(0.23,1,0.32,1) backwards}.dshbw_sidePopHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.dshbw_sidePopTitle{font-weight:600}.dshbw_sidePopClose{background:none;border:none;cursor:pointer;color:var(--dsw-alias-label-tertiary);font-size:16px;line-height:1;padding:0 2px;font-family:inherit}.dshbw_sidePopClose:hover{color:var(--dsw-alias-label-primary)}.dshbw_sidePopFoot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}.dshbw_sidePopRefresh{background:none;border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-secondary);display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:6px;padding:0;font-family:inherit}.dshbw_sidePopRefresh:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover)}.dshbw_side[data-peak=\"true\"]{border:1px solid var(--dsw-alias-state-warn-primary)}.dshbw_side[data-peak=\"false\"]{border:1px solid var(--dsw-alias-state-success-primary)}.dshbw_peakTag{display:inline-flex;align-items:center;gap:4px;font-size:12px;line-height:16px;padding:1px 7px;border-radius:6px}.dshbw_peakTag[data-peak=\"true\"]{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-tertiary)}.dshbw_peakTag[data-peak=\"false\"]{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-tertiary)}.dshbw_sessionNote{display:flex;align-items:center;gap:4px;margin-top:6px;padding-top:6px;border-top:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary);min-width:0}.dshbw_sessionName{max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dshbw_ico{color:var(--dsw-alias-state-business-primary);display:inline-flex;align-items:center}@keyframes dshbw-pop-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.dshbw_sidePopRefresh,.dshbw_sidePopClose{transition:transform 120ms cubic-bezier(0.23,1,0.32,1),color 120ms cubic-bezier(0.23,1,0.32,1)}.dshbw_sidePopRefresh:active{transform:scale(.92)}.dshbw_sidePopClose:active{transform:scale(.92)}";
46
+ const css = ".dshbw_row{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:2px 0}.dshbw_label{display:inline-flex;align-items:center;min-width:0}.dshbw_val{font-weight:600;color:var(--dsw-alias-label-primary)}.dshbw_val[data-tier=\"sec\"]{font-weight:400;color:var(--dsw-alias-label-secondary)}.dshbw_val[data-tier=\"ter\"]{font-weight:400;color:var(--dsw-alias-label-tertiary)}.dshbw_val[data-level=\"1\"]{color:var(--dsw-alias-state-warn-primary)}.dshbw_val[data-level=\"2\"]{color:var(--dsw-alias-state-error-primary)}.dshbw_err{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:16px;margin-top:6px}.dshbw_link{background:none;border:none;cursor:pointer;color:var(--dsw-alias-state-business-primary);display:inline-flex;align-items:center;gap:4px;padding:2px 6px;border-radius:6px;font-size:12px;line-height:18px;font-family:inherit;text-decoration:none}.dshbw_link:hover{color:var(--dsw-alias-state-business-primary);text-decoration:underline}.dshbw_sideTop{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:14px;line-height:20px}.dshbw_sideAmount{font-weight:600}.dshbw_sideAmount[data-level=\"1\"]{color:var(--dsw-alias-state-warn-primary);font-weight:600}.dshbw_sideAmount[data-level=\"2\"]{color:var(--dsw-alias-state-error-primary)}.dshbw_sideFoot{display:flex;justify-content:space-between;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.dshbw_sideWrap{position:relative;flex:1 1 auto;min-width:0}.dshbw_sideWrap[data-compact=\"true\"]{flex:0 0 auto}.dshbw_sideWrap[data-compact=\"true\"] .dshbw_side{width:36px;height:36px;padding:0;margin:0;gap:0;justify-content:center;align-items:center;border-radius:var(--dsw-radius-md,10px);background:transparent;color:var(--dsw-alias-label-primary)}.dshbw_sideWrap[data-compact=\"true\"] .dshbw_side:hover{background:var(--dsw-alias-interactive-bg-hover)}.dshbw_sideWrap[data-compact=\"true\"] .dshbw_sideTop{justify-content:center;gap:0}.dshbw_sideWrap[data-compact=\"true\"] .dshbw_sideAmount,.dshbw_sideWrap[data-compact=\"true\"] .dshbw_sideFoot{display:none}.dshbw_sideWrap[data-compact=\"true\"] .dshbw_side::after{content:\"\";position:absolute;top:5px;right:5px;width:6px;height:6px;border-radius:50%;display:none}.dshbw_sideWrap[data-compact=\"true\"] .dshbw_side[data-peak=\"true\"]::after{display:block;background:var(--dsw-alias-state-warn-primary)}.dshbw_sideWrap[data-compact=\"true\"] .dshbw_side[data-peak=\"false\"]::after{display:block;background:var(--dsw-alias-state-success-primary)}.dshbw_side{position:relative;display:flex;flex-direction:column;gap:4px;width:100%;padding:6px 8px;margin:0 0 2px;border:none;border-radius:12px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);cursor:pointer;font-family:inherit;text-align:left;transition:transform 120ms cubic-bezier(0.23,1,0.32,1),background 120ms cubic-bezier(0.23,1,0.32,1)}.dshbw_side:active{transform:scale(.97)}.dshbw_side:hover{background:var(--dsw-alias-interactive-bg-active)}.dshbw_sidePop{position:fixed;z-index:70;box-sizing:border-box;min-width:240px;max-width:calc(100vw - 16px);background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:var(--dsw-shadow-lv3,0 0 1px 0 #0003,0 0 4px 0 #00000005,0 12px 32px 0 #00000014);padding:12px 12px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);animation:dshbw-pop-in 160ms cubic-bezier(0.23,1,0.32,1) backwards}.dshbw_sidePopHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.dshbw_sidePopTitle{font-weight:600}.dshbw_sidePopClose{background:none;border:none;cursor:pointer;color:var(--dsw-alias-label-tertiary);font-size:16px;line-height:1;padding:0 2px;font-family:inherit}.dshbw_sidePopClose:hover{color:var(--dsw-alias-label-primary)}.dshbw_sidePopFoot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}.dshbw_sidePopRefresh{background:none;border:1px solid var(--dsw-alias-border-l2);cursor:pointer;color:var(--dsw-alias-label-secondary);display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:6px;padding:0;font-family:inherit}.dshbw_sidePopRefresh:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover)}.dshbw_sideAmount::before{content:\"\";display:none;width:6px;height:6px;border-radius:50%;margin-right:6px;vertical-align:middle}.dshbw_side[data-peak=\"true\"] .dshbw_sideAmount::before{display:inline-block;background:var(--dsw-alias-state-warn-primary)}.dshbw_side[data-peak=\"false\"] .dshbw_sideAmount::before{display:inline-block;background:var(--dsw-alias-state-success-primary)}.dshbw_peakTag{display:inline-flex;align-items:center;gap:4px;font-size:12px;line-height:16px;padding:1px 7px;border-radius:6px}.dshbw_peakTag[data-peak=\"true\"]{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-tertiary)}.dshbw_peakTag[data-peak=\"false\"]{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-tertiary)}.dshbw_sessionNote{display:flex;align-items:center;gap:4px;margin-top:6px;padding-top:6px;border-top:1px solid var(--dsw-alias-border-l1);font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary);min-width:0}.dshbw_sessionName{max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dshbw_ico{color:var(--dsw-alias-state-business-primary);display:inline-flex;align-items:center}@keyframes dshbw-pop-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.dshbw_sidePopRefresh,.dshbw_sidePopClose{transition:transform 120ms cubic-bezier(0.23,1,0.32,1),color 120ms cubic-bezier(0.23,1,0.32,1)}.dshbw_sidePopRefresh:active{transform:scale(.92)}.dshbw_sidePopClose:active{transform:scale(.92)}.dshbw_side:focus-visible,.dshbw_link:focus-visible,.dshbw_sidePopClose:focus-visible,.dshbw_sidePopRefresh:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}";
12
47
  const tagId = "dsh-balance-widget/styles.css";
13
48
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
14
49
  const tag = document.createElement("style");
@@ -185,6 +220,14 @@ window.__ModuleLoader__.load({
185
220
  const [error, setError] = (0, react.useState)(null);
186
221
  const [loading, setLoading] = (0, react.useState)(false);
187
222
  const [peak, setPeak] = (0, react.useState)(null);
223
+ /**
224
+ * Viewport position of the popover. The panel is portalled into
225
+ * document.body because the sidebar column clips its children
226
+ * (`overflow:hidden`), which cut the panel off at the sidebar edge once
227
+ * the shell collapsed into its 56px rail. Body still carries the theme
228
+ * tokens, so a portalled panel keeps its surface colors.
229
+ */
230
+ const [popPos, setPopPos] = (0, react.useState)(null);
188
231
  // The currently selected session id (the session the user is viewing).
189
232
  // This anchors "today · this workspace" to the actual current workspace.
190
233
  const currentSessionId = typeof useSessions === "function"
@@ -242,26 +285,69 @@ window.__ModuleLoader__.load({
242
285
  const timer = setInterval(() => refresh(false), 60000);
243
286
  return () => clearInterval(timer);
244
287
  }, [currentSessionId]);
245
- // Close the popover when clicking outside the card.
288
+ // Close the popover when clicking outside the card or its portalled panel.
246
289
  const wrapRef = (0, react.useRef)(null);
290
+ const popRef = (0, react.useRef)(null);
247
291
  (0, react.useEffect)(() => {
248
292
  if (!open) return;
249
293
  const onDocClick = (event) => {
250
294
  const el = wrapRef.current;
251
- if (el !== null && !el.contains(event.target)) setOpen(false);
295
+ const panel = popRef.current;
296
+ if (el !== null && el.contains(event.target)) return;
297
+ if (panel !== null && panel.contains(event.target)) return;
298
+ setOpen(false);
252
299
  };
253
300
  document.addEventListener("mousedown", onDocClick);
254
301
  return () => document.removeEventListener("mousedown", onDocClick);
255
302
  }, [open]);
303
+ /**
304
+ * Anchor the panel just above the card: same width as the card (never
305
+ * narrower than PANEL_MIN_WIDTH), kept inside the viewport.
306
+ */
307
+ const placePopover = () => {
308
+ const el = wrapRef.current;
309
+ if (el === null) return;
310
+ const rect = el.getBoundingClientRect();
311
+ // The panel matches the card it drops out of, so the two line up
312
+ // whatever width the user gave the sidebar; 240px is the floor for
313
+ // the collapsed rail, where the card is only 36px wide.
314
+ const PANEL_MIN_WIDTH = 240;
315
+ const width = Math.max(PANEL_MIN_WIDTH, Math.round(rect.width));
316
+ setPopPos({
317
+ left: Math.max(8, Math.min(Math.round(rect.left), window.innerWidth - width - 8)),
318
+ bottom: Math.max(8, Math.round(window.innerHeight - rect.top + 8)),
319
+ width: Math.min(width, window.innerWidth - 16)
320
+ });
321
+ };
322
+ // Keep the portalled panel glued to the card while the layout moves:
323
+ // the sidebar can be dragged wider, collapsed or the window resized
324
+ // with the panel still open, and a fixed-position panel cannot follow
325
+ // on its own.
326
+ (0, react.useEffect)(() => {
327
+ if (!open) return;
328
+ const reposition = () => placePopover();
329
+ window.addEventListener("resize", reposition);
330
+ const el = wrapRef.current;
331
+ const observer = typeof ResizeObserver === "function" && el !== null ? new ResizeObserver(reposition) : null;
332
+ if (observer !== null) observer.observe(el);
333
+ return () => {
334
+ window.removeEventListener("resize", reposition);
335
+ if (observer !== null) observer.disconnect();
336
+ };
337
+ }, [open]);
256
338
  const level = balance === null ? 0 : balance.total < criticalThreshold ? 2 : balance.total < lowThreshold ? 1 : 0;
257
339
  const toggle = async () => {
258
340
  const next = !open;
341
+ if (next) placePopover();
259
342
  setOpen(next);
260
343
  if (next) await refresh(true);
261
344
  };
262
345
  return (0, react_jsx_runtime.jsx)("div", {
263
346
  ref: wrapRef,
264
347
  className: styles.sideWrap,
348
+ // The sidebar shell hands the slot a `wide` flag: collapsed, the
349
+ // foot becomes a 56px icon rail, so the card shrinks to one glyph.
350
+ "data-compact": wide === false ? "true" : undefined,
265
351
  children: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, {
266
352
  children: [(0, react_jsx_runtime.jsx)("button", {
267
353
  type: "button",
@@ -292,10 +378,12 @@ window.__ModuleLoader__.load({
292
378
  })
293
379
  })]
294
380
  })
295
- }), open && (0, react_jsx_runtime.jsx)("div", {
381
+ }), open && popPos !== null && (0, react_dom.createPortal)((0, react_jsx_runtime.jsx)("div", {
382
+ ref: popRef,
296
383
  className: styles.sidePop,
297
384
  "data-peak": peak !== null ? String(peak.active) : undefined,
298
385
  role: "dialog",
386
+ style: { left: popPos.left, bottom: popPos.bottom, width: popPos.width },
299
387
  children: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, {
300
388
  children: [(0, react_jsx_runtime.jsx)("div", {
301
389
  className: styles.sidePopHead,
@@ -439,12 +527,12 @@ window.__ModuleLoader__.load({
439
527
  "aria-label": t("refresh.aria"),
440
528
  title: t("refresh.title"),
441
529
  onClick: () => refresh(true),
442
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, {})
530
+ children: refreshGlyph()
443
531
  })]
444
532
  })
445
533
  })]
446
534
  })
447
- })]
535
+ }), document.body)]
448
536
  })
449
537
  });
450
538
  }
package/lib/index.js CHANGED
@@ -523,51 +523,58 @@ function awaitImportFs() {
523
523
  return _fs;
524
524
  }
525
525
 
526
- /** Cache the zstd availability probe (run once per process). */
527
- let _zstdAvailable = void 0;
526
+ /** Zstandard frame magic number (RFC 8878 §3.1.1). */
527
+ const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
528
528
 
529
529
  /**
530
- * Probe whether the zstd CLI is available, caching the result.
531
- * macOS does not ship zstd by default, so this must be checked lazily and
532
- * surfaced as a distinct error rather than a silent "session not found".
533
- */
534
- async function isZstdAvailable() {
535
- if (_zstdAvailable !== void 0) return _zstdAvailable;
536
- const { execFile } = await import("node:child_process");
537
- const { promisify } = await import("node:util");
538
- const execFileP = promisify(execFile);
539
- try {
540
- await execFileP("zstd", ["--version"], { timeout: 5000 });
541
- _zstdAvailable = true;
542
- } catch {
543
- _zstdAvailable = false;
544
- }
545
- return _zstdAvailable;
546
- }
547
-
548
- /**
549
- * Decompress a .zstd session file to text via the zstd CLI.
530
+ * Decompress a .zstd session file using Node's built-in zstd (>=22.15), which
531
+ * is what DSH itself writes with — no external CLI and no PATH dependency, so
532
+ * this also works when the host was launched by the desktop app.
533
+ *
534
+ * The log is append-only: each flush appends its own complete frame, so a real
535
+ * log holds dozens of frames back to back (a 459 KB 0.1.7 log measured 97).
536
+ * Node's decompressor stops at the end of the first frame and would silently
537
+ * return a few hundred bytes out of a few hundred kilobytes, so frames are
538
+ * located by their magic number and decoded one at a time. A magic sequence
539
+ * occurring inside compressed data fails to decode and is skipped.
550
540
  * @returns { ok: true, text } | { ok: false, error }
551
541
  */
552
542
  async function decompressSession(path) {
553
- if (!(await isZstdAvailable())) {
543
+ const zlib = await import("node:zlib");
544
+ if (typeof zlib.zstdDecompressSync !== "function") {
554
545
  return {
555
546
  ok: false,
556
- error: "zstd CLI not found — install it (e.g. `brew install zstd`) to read session files"
547
+ error: "this Node build has no built-in zstd — Node >=22.15 is required to read session files"
557
548
  };
558
549
  }
559
- const { execFile } = await import("node:child_process");
560
- const { promisify } = await import("node:util");
561
- const execFileP = promisify(execFile);
550
+ const fs = await awaitImportFs();
551
+ let buf;
562
552
  try {
563
- const { stdout } = await execFileP("zstd", ["-d", "-c", path], { maxBuffer: 512 * 1024 * 1024 });
564
- return { ok: true, text: stdout };
553
+ buf = await fs.promises.readFile(path);
565
554
  } catch (error) {
566
555
  return {
567
556
  ok: false,
568
- error: `failed to decompress session file: ${error instanceof Error ? error.message : String(error)}`
557
+ error: `failed to read session file: ${error instanceof Error ? error.message : String(error)}`
569
558
  };
570
559
  }
560
+ /** Decoded frame payloads, concatenated as bytes so a frame boundary that
561
+ * splits a UTF-8 character still decodes correctly. */
562
+ const parts = [];
563
+ let offset = 0;
564
+ while (offset < buf.length) {
565
+ const start = buf.indexOf(ZSTD_MAGIC, offset);
566
+ if (start === -1) break;
567
+ try {
568
+ parts.push(zlib.zstdDecompressSync(buf.subarray(start)));
569
+ offset = start + ZSTD_MAGIC.length;
570
+ } catch {
571
+ offset = start + 1;
572
+ }
573
+ }
574
+ if (parts.length === 0) {
575
+ return { ok: false, error: `no zstd frame decoded (${buf.length} bytes)` };
576
+ }
577
+ return { ok: true, text: Buffer.concat(parts).toString("utf8") };
571
578
  }
572
579
 
573
580
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-balance-widget",
3
- "description": "Balance & cost widget for the dsh web GUI: a sidebar footer card shows the DeepSeek account balance and today's costs; clicking opens a popover with a five-tier cost breakdown (last prompt with session name / today-this-session / today-this-workspace / today-all-workspaces), peak/off-peak status tags, term explanations, and a one-click top-up link. Zero external dependencies — works on Node 24.",
4
- "version": "0.5.4",
3
+ "description": "Balance & cost widget for the DeepSeek Harness GUI (web and desktop app): a sidebar footer card shows the DeepSeek account balance and today's costs; clicking opens a popover with a five-tier cost breakdown (last prompt with session name / today-this-session / today-this-workspace / today-all-workspaces), peak/off-peak status tag, term explanations, and a one-click top-up link. Zero external dependencies — works on Node 24.",
4
+ "version": "0.6.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "files": [
@@ -25,10 +25,7 @@
25
25
  "patch": "./cordis.patch.yml"
26
26
  },
27
27
  "client": {
28
- "inject": [
29
- "@deepseek-ai/dsh-client-runtime",
30
- "@deepseek-ai/dsh-client-ui-conversation"
31
- ],
28
+ "inject": [],
32
29
  "platform": "web"
33
30
  }
34
31
  },