dsh-ollama-usage 0.1.10 → 0.1.12

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 ADDED
@@ -0,0 +1,137 @@
1
+ <div align="center">
2
+
3
+ # ⚡ dsh-ollama-usage
4
+
5
+ **Ollama Cloud usage & remaining-quota visualization · a DeepSeek Harness plugin**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/dsh-ollama-usage?style=flat-square&logo=npm)](https://www.npmjs.com/package/dsh-ollama-usage)
8
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-ollama-usage?style=flat-square&logo=npm)](https://www.npmjs.com/package/dsh-ollama-usage)
9
+ [![license](https://img.shields.io/npm/l/dsh-ollama-usage?style=flat-square)](LICENSE)
10
+ [![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen?style=flat-square&logo=node.js)](package.json)
11
+ [![GitHub release](https://img.shields.io/github/v/release/SeverusZh/dsh-ollama-usage?style=flat-square&logo=github)](https://github.com/SeverusZh/dsh-ollama-usage/releases)
12
+ [![GitHub stars](https://img.shields.io/github/stars/SeverusZh/dsh-ollama-usage?style=flat-square&logo=github)](https://github.com/SeverusZh/dsh-ollama-usage)
13
+ [![DSH](https://img.shields.io/badge/DSH-0.1.7--rc.1%20%2F%200.2.1--rc.1-blue?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
14
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
15
+
16
+ </div>
17
+
18
+ Ollama Cloud usage & remaining-quota visualization for DeepSeek Harness —
19
+ 5-hour session + weekly quota, always-visible sidebar bars, a full panel on the
20
+ plugin's own detail page, persisted credentials & snapshots, auto-refresh.
21
+
22
+ ---
23
+
24
+ ## ✨ 功能特性 / Features
25
+
26
+ | Capability | Description |
27
+ | --- | --- |
28
+ | 🎯 **Session usage (per 5 hours)** | Ollama Cloud's 5-hour session quota, pinned at the top showing "used X% · remaining Y%" |
29
+ | 📅 **Weekly usage** | This week's quota progress, shown next to the session usage |
30
+ | 🎨 **Sidebar dual bars** | Minimal, transparent style: session 5h in **light purple** + weekly in **deep purple**, each with a percentage, details on hover, refreshed every 60 seconds (auto-hidden on narrow layouts) |
31
+ | 🖥️ **Full panel on the plugin detail page** | Rendered between the description and component rows of the "Plugin list → dsh-ollama-usage" page: session/week usage bars, session and weekly reset countdowns, and the last 5 session-usage history entries |
32
+ | 💾 **Persistence** | The API Key and usage snapshot are written to `$DSH_HOME/storages/ollama-usage/usage.json` (mode 600, 24 history entries kept), restored automatically across conversations / restarts |
33
+ | 🔄 **Auto-refresh** | The host queries every 10 minutes (even when the page is closed); an invalidated Key (401) is cleared automatically with a prompt |
34
+ | 🔑 **Login guidance** | When not signed in, one click opens [ollama.com/settings](https://ollama.com/settings) to sign in and [settings/keys](https://ollama.com/settings/keys) to create an API Key; compatible with the legacy CLI's `~/.ollama/auth.json` |
35
+
36
+ The quota progress data comes from Ollama's official endpoint `GET https://ollama.com/api/balance`
37
+ (the `remaining_percent` and `resets_at` of `included.session` / `included.weekly`), matching the
38
+ [ollama.com/settings](https://ollama.com/settings) page.
39
+
40
+ > **v0.1.11 fix**: in 2026-10 Ollama changed `GET /api/usage` to return only hourly/daily
41
+ > request-volume buckets and no longer include `limits.session` / `limits.weekly`, so older
42
+ > versions parsed both percentages as `null` — the panel showed "no data" and the sidebar bars
43
+ > showed "—". Quota progress now comes from `/api/balance` (`used = 100 - remaining_percent`),
44
+ > and the reset countdown uses the real `resets_at` sent by the endpoint.
45
+
46
+ ## 📦 安装 / Install
47
+
48
+ ```bash
49
+ dsh plugin --profile web add dsh-ollama-usage
50
+ dsh --profile web
51
+ ```
52
+
53
+ > Alternatively, add `dsh-ollama-usage` to the profile `package.json`'s
54
+ > `dsh.profile.bundles` manually and restart.
55
+
56
+ ## 🚀 使用 / Usage
57
+
58
+ 1. Open **Plugin list → dsh-ollama-usage** (the panel is rendered between the description and component rows of that page);
59
+ 2. When not signed in, follow the panel guidance: sign in at [ollama.com](https://ollama.com/settings) →
60
+ create an API Key at [settings/keys](https://ollama.com/settings/keys) →
61
+ paste it and click **Check** (or run `ollama signin` in a terminal and click **Re-check local credentials**);
62
+ 3. On success it is persisted; expand the sidebar to see the dual bars at the bottom, and the panel shows details and history at any time;
63
+ 4. When no longer needed, click **Clear stored Key** to remove the local credential (history is kept).
64
+
65
+ ## 🔒 数据与隐私 / Privacy
66
+
67
+ - Only a Bearer-carrying query request is sent to `https://ollama.com/api/balance`;
68
+ - The Key is stored in plaintext locally in `$DSH_HOME/storages/ollama-usage/usage.json` (mode 600,
69
+ a trust level equal to `~/.ollama/auth.json`), and can be cleared at any time.
70
+
71
+ ## 🏗️ 架构 / Architecture
72
+
73
+ ```
74
+ ┌──────────────────────────┐ ┌─────────────────────────────┐
75
+ │ browser (client bundle) │ RPC │ host (lib/index.js) │
76
+ │ lib/client.js │◄──────►│ webServer /ollama-usage │
77
+ │ · sidebar dual bars │ 信封协议 │ · fetch api/balance(Bearer)│
78
+ │ · settings panel │ │ · $DSH_HOME 持久化(0600) │
79
+ │ · login guidance │ │ · 10min auto-refresh │
80
+ └──────────────────────────┘ └─────────────────────────────┘
81
+ ```
82
+
83
+ - Host entry `lib/index.js`: ESM, exports `name` + the default plugin; `webServer` prefix route +
84
+ the Connection RPC envelope protocol (`check` / `snapshot` / `forget` / `auth-state`).
85
+ - Client entry `lib/client.js`: a `window.__ModuleLoader__.load` bundle that talks to the host via
86
+ `connection.rpc.call('/ollama-usage', endpoint, payload)`.
87
+ - Wiring: `cordis.patch.yml` (Loader row) + the `dsh.bundle.patch` / `dsh.client` declarations in
88
+ `package.json`.
89
+
90
+ ## 🧩 兼容性 / Compatibility
91
+
92
+ Since DSH 0.2.1 the compatibility gate prefers `engines.dsh`. This plugin declares **`engines.dsh: ">=0.1.7-rc.1"`** (no upper bound), keeps the `@deepseek-ai/dsh-home-paths` peer at `>=0.1.7-rc.1`, and has been checked on:
93
+
94
+ | DSH version | Status |
95
+ | --- | --- |
96
+ | 0.1.5-rc.1 | ✅ Tested |
97
+ | 0.1.5-rc.2 | ✅ Tested |
98
+ | 0.1.6-alpha.2 | ✅ Tested |
99
+ | 0.1.7-rc.1 | ✅ Tested |
100
+ | 0.1.7-rc.2 | ✅ Tested |
101
+ | 0.2.0-rc.1 | ✅ Tested |
102
+ | 0.2.1-rc.1 | ✅ Gate + probes |
103
+ | 0.2.1-rc.2 | ✅ Gate |
104
+
105
+ > Only versions that were **actually run** are listed; untested versions are omitted.
106
+
107
+ v0.1.12 targets DSH **≥0.1.5-rc.1** (the `@deepseek-ai/dsh-*` peer was relaxed to `>=0.1.7-rc.1`), verified on **0.1.5-rc.1 / 0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1** (all RPC envelope probes pass; on 0.1.7-rc.2 the `/ollama-usage/snapshot` endpoint returns as measured; on 0.2.0-rc.1 it passes the new compatibility gate and loads normally; on 0.2.1-rc.1 the gate and RPC envelope probes pass, and 0.2.1-rc.2 passes the gate); for older DSH (0.1.0-rc.5 ~ rc.8) use the last compatible npm version, **0.1.5**.
108
+
109
+ ## 🛠️ 开发 / Develop
110
+
111
+ ### 测试 / Tests
112
+
113
+ ```bash
114
+ npm test # bridge-smoke → balance-parse → rpc-envelope-alpha4
115
+ ```
116
+
117
+ - `test/bridge-smoke.mjs` (17 checks): route registration, the no-token / 401 branches, persistence
118
+ read/write and cleanup, snapshot / forget / auth-state, and validation of malformed envelopes and
119
+ request methods.
120
+ - `test/balance-parse.mjs`: a `/api/balance` parsing regression (no network, a fetch stub). It drives
121
+ `check` with real response bodies and asserts the 5h / weekly percentage conversion, `resets_at`
122
+ passthrough, persistence and history appending, plus the unlimited-plan / non-JSON / 401 / 403 /
123
+ network-error branches.
124
+ - `test/rpc-envelope-alpha4.mjs`: every endpoint passes alpha.4's Connection RPC envelope validator.
125
+
126
+ ### 构建与发布 / Build & Publish
127
+
128
+ ```bash
129
+ npm install # 安装依赖(@deepseek-ai/dsh-home-paths)
130
+ npm test # 冒烟测试(见上)
131
+ npm pack # 本地打包检查
132
+ npm publish # 发布到 npm
133
+ ```
134
+
135
+ ## 📄 许可证 / License
136
+
137
+ [MIT](LICENSE) © 2026 dsh-ollama-usage contributors
package/README.md CHANGED
@@ -4,10 +4,16 @@
4
4
 
5
5
  **Ollama Cloud 用量余量可视化 · DeepSeek Harness 插件**
6
6
 
7
- [![npm version](https://img.shields.io/npm/v/dsh-ollama-usage?color=7c66e8&label=npm)](https://www.npmjs.com/package/dsh-ollama-usage)
8
- [![GitHub release](https://img.shields.io/github/v/release/SeverusZh/dsh-ollama-usage?color=b39df7&label=release)](https://github.com/SeverusZh/dsh-ollama-usage/releases)
9
- [![License](https://img.shields.io/badge/license-MIT-9ece6a)](LICENSE)
10
- [![dsh](https://img.shields.io/badge/DSH%200.1.5--rc.1%20%2F%20rc.2%20%2F%200.1.6--alpha.2%20%2F%200.1.7--rc.1%20%2F%20rc.2%20%2F%200.2.0--rc.1-7aa2f7)](https://github.com/deepseek-ai/dsh)
7
+ [![npm version](https://img.shields.io/npm/v/dsh-ollama-usage?style=flat-square&logo=npm)](https://www.npmjs.com/package/dsh-ollama-usage)
8
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-ollama-usage?style=flat-square&logo=npm)](https://www.npmjs.com/package/dsh-ollama-usage)
9
+ [![license](https://img.shields.io/npm/l/dsh-ollama-usage?style=flat-square)](LICENSE)
10
+ [![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen?style=flat-square&logo=node.js)](package.json)
11
+ [![GitHub release](https://img.shields.io/github/v/release/SeverusZh/dsh-ollama-usage?style=flat-square&logo=github)](https://github.com/SeverusZh/dsh-ollama-usage/releases)
12
+ [![GitHub stars](https://img.shields.io/github/stars/SeverusZh/dsh-ollama-usage?style=flat-square&logo=github)](https://github.com/SeverusZh/dsh-ollama-usage)
13
+ [![DSH](https://img.shields.io/badge/DSH-0.1.7--rc.1%20%2F%200.2.1--rc.1-blue?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
14
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
15
+
16
+ </div>
11
17
 
12
18
  可视化你的 **Ollama Cloud** 用量余量 —— 每 5 小时会话用量 + 周用量,
13
19
  侧边栏极简双横条常驻显示,插件详情页完整面板,Key 与快照持久化,自动刷新。
@@ -16,29 +22,31 @@
16
22
  5-hour session + weekly quota, always-visible sidebar bars, a full panel on the
17
23
  plugin's own detail page, persisted credentials & snapshots, auto-refresh.*
18
24
 
19
- </div>
20
-
21
25
  ---
22
26
 
23
- > **兼容性 / Compatibility**:v0.1.10 支持 DSH **≥0.1.5-rc.1**(`@deepseek-ai/dsh-*` peer 放宽为 `>=0.1.7-rc.1`),已在 **0.1.5-rc.1 / 0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1** 实测(RPC envelope 探针全过;0.1.7-rc.2 上 `/ollama-usage/snapshot` 实测返回;0.2.0-rc.1 上通过新的兼容性门禁并正常加载);旧版 DSH(0.1.0-rc.5 ~ rc.8)请使用最后兼容的 npm 版本 **0.1.5**。
24
- > v0.1.10 targets DSH **≥0.1.5-rc.1**, verified on **0.1.5-rc.1 / 0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1**; for older DSH releases use the last compatible npm version **0.1.5**.
25
-
26
- ## ✨ 功能特性 Features
27
+ ## ✨ 功能特性 / Features
27
28
 
28
29
  | 能力 | 说明 |
29
30
  | --- | --- |
30
31
  | 🎯 **会话用量(每 5 小时)** | Ollama Cloud 的 5 小时会话配额,置顶展示「已用 X% · 剩余 Y%」 |
31
32
  | 📅 **周用量(Weekly)** | 本周配额进度,与会话用量并排展示 |
32
33
  | 🎨 **侧边栏双横条** | 极简透明风格:会话 5h **淡紫** + 周用量**深紫**,各带百分比数字,悬停显示详情,每 60 秒自动更新(窄栏自动隐藏) |
33
- | 🖥️ **插件详情页完整面板** | 渲染在「插件列表 → dsh-ollama-usage」页面的描述与组件行之间:会话/周用量进度条、周用量重置倒计时、模型请求数排行、最近 5 条会话用量历史 |
34
+ | 🖥️ **插件详情页完整面板** | 渲染在「插件列表 → dsh-ollama-usage」页面的描述与组件行之间:会话/周用量进度条、会话与周重置倒计时、最近 5 条会话用量历史 |
34
35
  | 💾 **持久化** | API Key 与用量快照写入 `$DSH_HOME/storages/ollama-usage/usage.json`(权限 600,保留 24 条历史),跨对话 / 重启自动恢复 |
35
36
  | 🔄 **自动刷新** | Host 每 10 分钟自动查询(页面关闭也持续);Key 失效(401)自动清除并提示 |
36
37
  | 🔑 **登录引导** | 未登录时一键打开 [ollama.com/settings](https://ollama.com/settings) 登录、到 [settings/keys](https://ollama.com/settings/keys) 创建 API Key;兼容旧版 CLI 的 `~/.ollama/auth.json` |
37
38
 
38
- 数据来源为 Ollama 官方接口 `GET https://ollama.com/api/usage`,与
39
- [ollama.com/settings](https://ollama.com/settings) 页面一致。
39
+ 限额进度数据来源于 Ollama 官方接口 `GET https://ollama.com/api/balance`
40
+ (`included.session` / `included.weekly` 的 `remaining_percent` 与 `resets_at`),
41
+ 与 [ollama.com/settings](https://ollama.com/settings) 页面一致。
42
+
43
+ > **v0.1.11 修复**:Ollama 在 2026-10 把 `GET /api/usage` 改为只返回按小时/天的
44
+ > 请求量分桶,不再包含 `limits.session` / `limits.weekly`,旧版本因此把两个百分比
45
+ > 解析成 `null` —— 面板显示「暂无数据」、侧边栏双横条显示「—」。
46
+ > 现在限额进度改由 `/api/balance` 提供(`已用 = 100 - remaining_percent`),
47
+ > 重置倒计时也直接使用接口下发的真实 `resets_at`。
40
48
 
41
- ## 📦 安装 Install
49
+ ## 📦 安装 / Install
42
50
 
43
51
  ```bash
44
52
  dsh plugin --profile web add dsh-ollama-usage
@@ -48,7 +56,7 @@ dsh --profile web
48
56
  > 也可手动把 `dsh-ollama-usage` 加入 profile `package.json` 的
49
57
  > `dsh.profile.bundles` 后重启。
50
58
 
51
- ## 🚀 使用 Usage
59
+ ## 🚀 使用 / Usage
52
60
 
53
61
  1. 打开 **插件列表 → dsh-ollama-usage**(面板渲染在该页面的描述与组件行之间);
54
62
  2. 未登录时按面板指引:登录 [ollama.com](https://ollama.com/settings) →
@@ -57,19 +65,19 @@ dsh --profile web
57
65
  3. 检测成功后即持久化,展开侧边栏即可看到底部双横条,面板可随时查看详情与历史;
58
66
  4. 不再需要时点 **清除已存 Key** 移除本地凭证(历史保留)。
59
67
 
60
- ## 🔒 数据与隐私 Privacy
68
+ ## 🔒 数据与隐私 / Privacy
61
69
 
62
- - 仅向 `https://ollama.com/api/usage` 发送带 Bearer 的查询请求;
70
+ - 仅向 `https://ollama.com/api/balance` 发送带 Bearer 的查询请求;
63
71
  - Key 以明文保存在本机 `$DSH_HOME/storages/ollama-usage/usage.json`(权限 600,
64
72
  与 `~/.ollama/auth.json` 同等级信任),随时可清除。
65
73
 
66
- ## 🏗️ 架构 Architecture
74
+ ## 🏗️ 架构 / Architecture
67
75
 
68
76
  ```
69
77
  ┌──────────────────────────┐ ┌─────────────────────────────┐
70
78
  │ browser (client bundle) │ RPC │ host (lib/index.js) │
71
79
  │ lib/client.js │◄──────►│ webServer /ollama-usage │
72
- │ · sidebar dual bars │ 信封协议 │ · fetch api/usage (Bearer)│
80
+ │ · sidebar dual bars │ 信封协议 │ · fetch api/balance(Bearer)│
73
81
  │ · settings panel │ │ · $DSH_HOME 持久化(0600) │
74
82
  │ · login guidance │ │ · 10min auto-refresh │
75
83
  └──────────────────────────┘ └─────────────────────────────┘
@@ -82,16 +90,41 @@ dsh --profile web
82
90
  - 接线:`cordis.patch.yml`(Loader 行)+ `package.json` 的
83
91
  `dsh.bundle.patch` / `dsh.client` 声明。
84
92
 
85
- ## 🧪 测试 Tests
93
+ ## 🧩 兼容性 / Compatibility
94
+
95
+ DSH 0.2.1 起兼容门禁优先读取 `engines.dsh`。本插件声明 **`engines.dsh: ">=0.1.7-rc.1"`**(无上限),`@deepseek-ai/dsh-home-paths` peer 保持 `>=0.1.7-rc.1`,并已核对:
96
+
97
+ | DSH 版本 | 状态 |
98
+ | --- | --- |
99
+ | 0.1.5-rc.1 | ✅ 实测通过 |
100
+ | 0.1.5-rc.2 | ✅ 实测通过 |
101
+ | 0.1.6-alpha.2 | ✅ 实测通过 |
102
+ | 0.1.7-rc.1 | ✅ 实测通过 |
103
+ | 0.1.7-rc.2 | ✅ 实测通过 |
104
+ | 0.2.0-rc.1 | ✅ 实测通过 |
105
+ | 0.2.1-rc.1 | ✅ 兼容门禁 + 探针通过 |
106
+ | 0.2.1-rc.2 | ✅ 兼容门禁通过 |
107
+
108
+ > 只列出**实际跑过**的版本;未实测的不列。
109
+
110
+ v0.1.12 支持 DSH **≥0.1.5-rc.1**(`@deepseek-ai/dsh-*` peer 放宽为 `>=0.1.7-rc.1`),已在 **0.1.5-rc.1 / 0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1** 实测(RPC envelope 探针全过;0.1.7-rc.2 上 `/ollama-usage/snapshot` 实测返回;0.2.0-rc.1 上通过新的兼容性门禁并正常加载;0.2.1-rc.1 上兼容门禁与 RPC envelope 探针通过,0.2.1-rc.2 门禁通过);旧版 DSH(0.1.0-rc.5 ~ rc.8)请使用最后兼容的 npm 版本 **0.1.5**。
111
+
112
+ ## 🛠️ 开发 / Develop
113
+
114
+ ### 测试 / Tests
86
115
 
87
116
  ```bash
88
- DSH_HOME=.tmp-dsh-home HOME=.tmp-home node test/bridge-smoke.mjs
117
+ npm test # bridge-smoke → balance-parse → rpc-envelope-alpha4
89
118
  ```
90
119
 
91
- 17 项冒烟测试覆盖:路由注册、no-token / 401 分支、持久化读写与清理、
92
- snapshot / forget / auth-state、非法信封与请求方法校验。
120
+ - `test/bridge-smoke.mjs`(17 项):路由注册、no-token / 401 分支、持久化读写与
121
+ 清理、snapshot / forget / auth-state、非法信封与请求方法校验。
122
+ - `test/balance-parse.mjs`:`/api/balance` 解析回归(不联网,fetch 替身)。用真实
123
+ 响应体驱动 `check`,断言 5h / 周百分比换算、`resets_at` 透传、持久化与历史追加,
124
+ 以及无限额套餐 / 非 JSON / 401 / 403 / 网络异常各分支。
125
+ - `test/rpc-envelope-alpha4.mjs`:逐端点通过 alpha.4 的 Connection RPC 信封校验器。
93
126
 
94
- ## 🛠️ 开发 / 发布 Develop & Publish
127
+ ### 构建与发布 / Build & Publish
95
128
 
96
129
  ```bash
97
130
  npm install # 安装依赖(@deepseek-ai/dsh-home-paths)
@@ -100,6 +133,6 @@ npm pack # 本地打包检查
100
133
  npm publish # 发布到 npm
101
134
  ```
102
135
 
103
- ## 📄 License
136
+ ## 📄 许可证 / License
104
137
 
105
138
  [MIT](LICENSE) © 2026 dsh-ollama-usage contributors
package/lib/client.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 提供三处界面:
5
5
  * - plugins.bundle.config(以包名为键)「Ollama 用量」:完整面板(会话 5h / 周用量
6
- * 进度条、周用量重置倒计时、模型请求数、历史记录、API Key 配置、清除 Key)。
6
+ * 进度条、会话与周用量重置倒计时、历史记录、API Key 配置、清除 Key)。
7
7
  * 渲染在「插件列表 → dsh-ollama-usage」页面的描述与组件行之间 ——
8
8
  * 配置不再位于设置面板(settings.section 已退役);
9
9
  * - sidebar.footer.action:极简双横条(会话 5h 淡紫 / 周用量深紫,透明背景);
@@ -175,11 +175,12 @@ window.__ModuleLoader__.load({
175
175
 
176
176
  function UsageBody(props) {
177
177
  const d = props.data
178
+ const sessionResetText = d.sessionResetAt ? fmtCountdown(d.sessionResetAt) : null
178
179
  const weeklyResetText = d.weeklyResetAt ? fmtCountdown(d.weeklyResetAt) : null
179
180
  const models = Array.isArray(d.models) ? d.models.slice(0, 5) : []
180
181
  const history = Array.isArray(props.history) ? props.history.slice(-5).reverse() : []
181
182
  return React.createElement('div', { className: 'olusa-block' },
182
- React.createElement(BarRow, { label: '会话用量(每 5 小时)', percent: d.sessionPercent, em: true }),
183
+ React.createElement(BarRow, { label: '会话用量(每 5 小时)', percent: d.sessionPercent, em: true, resetText: sessionResetText ? '还有 ' + sessionResetText + ' 重置' : null }),
183
184
  React.createElement(BarRow, { label: '本周用量(Weekly)', percent: d.weeklyPercent, resetText: weeklyResetText ? '还有 ' + weeklyResetText + ' 重置' : null }),
184
185
  (models.length > 0
185
186
  ? React.createElement('div', { className: 'olusa-models' },
@@ -294,7 +295,7 @@ window.__ModuleLoader__.load({
294
295
  React.createElement('button', { className: 'olusa-btn', disabled: busy, onClick: () => doCheck('') }, busy ? '查询中…' : '刷新')
295
296
  ),
296
297
  body,
297
- React.createElement('div', { className: 'olusa-ver' }, 'dsh-ollama-usage · v0.1.4')
298
+ React.createElement('div', { className: 'olusa-ver' }, 'dsh-ollama-usage · v0.1.11')
298
299
  )
299
300
  }
300
301
 
package/lib/index.js CHANGED
@@ -4,7 +4,9 @@
4
4
  * 单一 Loader 行(见 cordis.patch.yml)挂载本模块,职责:
5
5
  * 1. 读取 Ollama 凭证:优先持久化的 API Key(settings 面板检测成功后写入),
6
6
  * 兼容旧版 CLI 的 ~/.ollama/auth.json;
7
- * 2. 请求 https://ollama.com/api/usage 获取用量(会话 5h 桶 + 周桶);
7
+ * 2. 请求 https://ollama.com/api/balance 获取余量:按会话(每 5 小时)与周
8
+ * 限额的「legacy plan」在 included.session / included.weekly 返回
9
+ * { remaining_percent, resets_at },换算成已用进度与重置时间;
8
10
  * 3. 把 Key / 用量快照 / 历史记录持久化到
9
11
  * $DSH_HOME/storages/ollama-usage/usage.json(权限 0600),跨对话/重启恢复;
10
12
  * 4. 每 10 分钟自动刷新一次(浏览器页面关闭也持续,Key 失效自动清除);
@@ -23,7 +25,7 @@ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
23
25
  export const name = 'ollama-usage'
24
26
 
25
27
  const RPC_CHANNEL = '/ollama-usage'
26
- const USAGE_URL = 'https://ollama.com/api/usage'
28
+ const BALANCE_URL = 'https://ollama.com/api/balance'
27
29
  const FETCH_TIMEOUT_MS = 20000
28
30
  const AUTO_REFRESH_MS = 10 * 60 * 1000
29
31
  const HISTORY_CAP = 24
@@ -78,28 +80,45 @@ function readLocalToken() {
78
80
  * 用量查询与解析
79
81
  * ------------------------------------------------------------------ */
80
82
 
81
- async function fetchUsage(token) {
82
- const res = await fetch(USAGE_URL, {
83
+ async function fetchBalance(token) {
84
+ const res = await fetch(BALANCE_URL, {
83
85
  headers: { Authorization: 'Bearer ' + token },
84
86
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
85
87
  })
86
88
  return { status: res.status, body: await res.text() }
87
89
  }
88
90
 
89
- /** API 返回的 usage 是 0~1 的小数(如 0.213 = 21.3%),统一转成百分比数字。 */
90
- function toPercent(v) {
91
+ /**
92
+ * included.session|weekly.remaining_percent 是 0~100 的「剩余百分比」
93
+ * (75 = 还剩 75%);面板展示的是已用进度,因此换算成 100 - remaining。
94
+ */
95
+ function usedPercent(remaining) {
91
96
  let n = null
92
- if (typeof v === 'number' && Number.isFinite(v)) n = v
93
- else if (typeof v === 'string' && v.trim() !== '') {
94
- const c = Number(v)
97
+ if (typeof remaining === 'number' && Number.isFinite(remaining)) n = remaining
98
+ else if (typeof remaining === 'string' && remaining.trim() !== '') {
99
+ const c = Number(remaining)
95
100
  if (Number.isFinite(c)) n = c
96
101
  }
97
102
  if (n === null) return null
98
- const pct = n <= 1 ? n * 100 : n
99
- return Math.round(pct * 10) / 10
103
+ return Math.round(Math.min(100, Math.max(0, 100 - n)) * 10) / 10
104
+ }
105
+
106
+ function resetAt(limit) {
107
+ return typeof limit.resets_at === 'string' && limit.resets_at !== '' ? limit.resets_at : null
100
108
  }
101
109
 
102
- function parseUsage(body, status) {
110
+ /**
111
+ * 解析 GET /api/balance(契约见 https://docs.ollama.com/api/balance)。
112
+ *
113
+ * 带会话(每 5 小时)/周限额的 legacy plan 返回
114
+ * included.session|weekly = { remaining_percent, resets_at }
115
+ * 按美元计费的套餐只返回 included.balance_usd / allowance_usd / period,
116
+ * 没有 5h/周进度可展示 —— 此时两个百分比保持 null,面板显示“暂无数据”。
117
+ *
118
+ * /api/usage 自 2026-10 起只返回按小时/天的请求量分桶(不再有 limits /
119
+ * activity / models 字段),因此限额进度一律以 /api/balance 为唯一来源。
120
+ */
121
+ function parseBalance(body) {
103
122
  let json = null
104
123
  try {
105
124
  json = JSON.parse(body)
@@ -109,57 +128,28 @@ function parseUsage(body, status) {
109
128
  if (!json || typeof json !== 'object' || Array.isArray(json)) {
110
129
  return { ok: false, message: '响应格式异常' }
111
130
  }
112
- const limits = (json && json.limits) || {}
113
- const activity = (json && json.activity) || {}
114
- const sessionUsage = limits.session && typeof limits.session === 'object' ? toPercent(limits.session.usage) : null
115
- const weeklyUsage = limits.weekly && typeof limits.weekly === 'object' ? toPercent(limits.weekly.usage) : null
116
- // 模型请求数:真实响应放在 limits.weekly.models(有时 activity.models 也有),按优先级取
117
- const weeklyModels = limits.weekly && Array.isArray(limits.weekly.models) ? limits.weekly.models : null
118
- const sessionModels = limits.session && Array.isArray(limits.session.models) ? limits.session.models : null
119
- const activityModels = Array.isArray(activity.models) ? activity.models : null
120
- const rawModels =
121
- (weeklyModels && weeklyModels.length ? weeklyModels : null) ||
122
- (sessionModels && sessionModels.length ? sessionModels : null) ||
123
- (activityModels || [])
124
- const models = rawModels
125
- .map((m) => ({ name: String((m && m.name) || ''), requestCount: Number((m && m.request_count)) || 0 }))
126
- .filter((m) => m.name)
127
- .sort((a, b) => b.requestCount - a.requestCount)
131
+ const included = json.included && typeof json.included === 'object' ? json.included : {}
132
+ const session = included.session && typeof included.session === 'object' ? included.session : null
133
+ const weekly = included.weekly && typeof included.weekly === 'object' ? included.weekly : null
128
134
  return {
129
135
  ok: true,
130
136
  usage: {
131
- cost: typeof activity.cost === 'string' ? activity.cost : null,
132
- periodType: activity.period && typeof activity.period.type === 'string' ? activity.period.type : null,
133
- periodStart: activity.period && typeof activity.period.starting_at === 'string' ? activity.period.starting_at : null,
134
- periodEnd: activity.period && typeof activity.period.ending_at === 'string' ? activity.period.ending_at : null,
135
- sessionPercent: sessionUsage,
136
- weeklyPercent: weeklyUsage,
137
- models,
137
+ sessionPercent: session ? usedPercent(session.remaining_percent) : null,
138
+ weeklyPercent: weekly ? usedPercent(weekly.remaining_percent) : null,
139
+ sessionResetAt: session ? resetAt(session) : null,
140
+ weeklyResetAt: weekly ? resetAt(weekly) : null,
141
+ // 模型请求数仍留在快照结构里(客户端按非空才渲染),等官方
142
+ // “usage breakdowns by model”(docs 标注 coming soon)上线后再填充。
143
+ models: [],
138
144
  },
139
145
  }
140
146
  }
141
147
 
142
- /* ------------------------------------------------------------------ *
143
- * 周重置时间推算(API 不提供各限额重置时间,按配额语义推算)
144
- * - weekly:下周一 00:00 UTC(last_4_weeks 周期锚点为周一)
145
- * ------------------------------------------------------------------ */
146
-
147
- function computeResetTimes(nowIso) {
148
- const now = new Date(nowIso)
149
- const daysUntilMonday = ((7 - now.getUTCDay()) % 7) + 1
150
- const weeklyResetAt = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + daysUntilMonday)).toISOString()
151
- return { weeklyResetAt }
152
- }
153
-
154
- function withResetTimes(usage, nowIso) {
155
- return Object.assign({}, usage, computeResetTimes(nowIso))
156
- }
157
-
158
148
  /** 用 token 查询并把结果写入持久化文件;返回 RpcResult({ ok, value|error })。 */
159
149
  async function checkWithToken(token) {
160
150
  let res
161
151
  try {
162
- res = await fetchUsage(token)
152
+ res = await fetchBalance(token)
163
153
  } catch (e) {
164
154
  return fail('network', String((e && e.message) || e))
165
155
  }
@@ -172,7 +162,7 @@ async function checkWithToken(token) {
172
162
  if (res.status >= 400) {
173
163
  return fail('http-error', res.body || ('HTTP ' + res.status))
174
164
  }
175
- const parsed = parseUsage(res.body, res.status)
165
+ const parsed = parseBalance(res.body)
176
166
  if (!parsed.ok) {
177
167
  return fail('internal', parsed.message)
178
168
  }
@@ -182,7 +172,7 @@ async function checkWithToken(token) {
182
172
  const history = Array.isArray(prev && prev.history) ? prev.history.slice() : []
183
173
  history.push({ at: now, sessionPercent: parsed.usage.sessionPercent, weeklyPercent: parsed.usage.weeklyPercent })
184
174
  const trimmed = history.length > HISTORY_CAP ? history.slice(history.length - HISTORY_CAP) : history
185
- const usage = withResetTimes(parsed.usage, now)
175
+ const usage = parsed.usage
186
176
  const persisted = writeFile({ token, updatedAt: now, usage, history: trimmed })
187
177
  return {
188
178
  ok: true,
@@ -231,7 +221,7 @@ async function dispatch(endpoint, payload) {
231
221
  if (endpoint === 'snapshot') {
232
222
  const rec = readFile()
233
223
  if (!rec || !rec.usage) return fail('no-data', '暂无持久化的用量数据')
234
- const usage = withResetTimes(rec.usage, new Date().toISOString())
224
+ const usage = rec.usage
235
225
  // snapshot 数据读自磁盘文件(插件无内存缓存),必然已持久化;与 check 端点一致地报告 persisted
236
226
  return { ok: true, value: { tokenPersisted: !!rec.token, persisted: true, updatedAt: rec.updatedAt, history: (rec.history || []).slice(-5), usage } }
237
227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ollama-usage",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "DeepSeek Harness 插件:可视化 Ollama Cloud 账号用量余量(每 5 小时会话用量 + 周用量),支持侧边栏双横条、插件详情页完整面板、API Key 与用量快照持久化、自动刷新与登录引导。Ollama Pro usage & remaining-quota visualization for DeepSeek Harness: 5-hour session + weekly quota bars in the sidebar footer, a full panel on the plugin's own detail page, persisted API key and usage snapshots, auto-refresh and login guidance.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -32,7 +32,9 @@
32
32
  "0.1.6-alpha.2": "compatible",
33
33
  "0.1.7-rc.1": "compatible",
34
34
  "0.1.7-rc.2": "compatible",
35
- "0.2.0-rc.1": "compatible"
35
+ "0.2.0-rc.1": "compatible",
36
+ "0.2.1-rc.1": "compatible",
37
+ "0.2.1-rc.2": "compatible"
36
38
  },
37
39
  "node": ">=20"
38
40
  }
@@ -94,6 +96,10 @@
94
96
  ],
95
97
  "license": "MIT",
96
98
  "scripts": {
97
- "test": "DSH_HOME=.tmp-dsh-home HOME=.tmp-home node test/bridge-smoke.mjs && DSH_HOME=.tmp-env-dsh-home HOME=.tmp-env-home node test/rpc-envelope-alpha4.mjs"
99
+ "test": "DSH_HOME=.tmp-dsh-home HOME=.tmp-home node test/bridge-smoke.mjs && DSH_HOME=.tmp-balance-dsh-home HOME=.tmp-balance-home node test/balance-parse.mjs && DSH_HOME=.tmp-env-dsh-home HOME=.tmp-env-home node test/rpc-envelope-alpha4.mjs"
100
+ },
101
+ "engines": {
102
+ "node": ">=20",
103
+ "dsh": ">=0.1.7-rc.1"
98
104
  }
99
- }
105
+ }