dsh-subusage 0.10.6 → 0.10.8

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KouzakiUmi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **English.** dsh-subusage shows subscription quota and balance for the AI providers you already use, as a pill next to the model selector in DeepSeek Harness. Provider switches and credentials live in one settings page, and other plugins or agents can read the same snapshot through a read-only quota API.
8
8
 
9
- [![npm version](https://img.shields.io/badge/npm-0.10.6-blue)](https://www.npmjs.com/package/dsh-subusage)
9
+ [![npm version](https://img.shields.io/badge/npm-0.10.8-blue)](https://www.npmjs.com/package/dsh-subusage)
10
10
  [![license](https://img.shields.io/badge/license-MIT-green)](#license--security--许可与安全)
11
11
  [![DSH](https://img.shields.io/badge/DSH-%3E%3D0.2.0--rc.1%20%3C0.3.0--0-informational)](#compatibility--兼容性)
12
12
 
@@ -45,8 +45,6 @@
45
45
  | MiniMax(国际) | `minimax` | 短周期 / 周 | Bearer | 关 |
46
46
  | ARK Agent Plan Team (arkcli) | `arkcli-agent-plan-team` | 席位额度(AFP) | IAM AK/SK 签名 | 关 |
47
47
  | ARK Coding Plan Team (arkcli) | `arkcli-coding-plan-team` | 席位额度 | IAM AK/SK 签名 | 关 |
48
- | Ark Coding Plan(中国,旧插件路由) | `ark-coding-plan-cn` | 订阅窗口 | IAM AK/SK 签名 | 关 |
49
- | Ark Agent Plan(中国,旧插件路由) | `ark-agent-plan-cn` | 订阅窗口(AFP) | IAM AK/SK 签名 | 关 |
50
48
  | Ark Coding Plan(BytePlus,旧插件路由) | `ark-coding-plan-byteplus` | 订阅窗口 | IAM AK/SK 签名 | 关 |
51
49
  | SiliconFlow | `siliconflow` | 余额 | Bearer,与推理同一把 Key | 关 |
52
50
  | OpenRouter | `openrouter` | 限额窗口 + 余额 | 推理 Key(余额需 management / provisioning key) | 关 |
@@ -64,15 +62,53 @@
64
62
 
65
63
  非公开控制台接口可能变更;缺失或非法百分比不会被当作零用量;「读取成功」只表示接口读取健康度,不保证仍有额度。
66
64
 
65
+ ## Support status / 实测覆盖
66
+
67
+ ### 已实测验证
68
+
69
+ 下面这些在**真实账号**上跑通过(本仓库开发机 + 各家的官方 CLI / 插件):
70
+
71
+ | 提供商 | 验证范围 | 凭据来源 |
72
+ |---|---|---|
73
+ | 火山方舟 Agent Plan(个人版,`arkcli-agent-plan`) | 5 小时 / 周 / 月 AFP 绝对值 + 日限额 + 档位 | 本机 IAM AK/SK 直连 `GetAFPUsage`,HTTP 200 |
74
+ | 火山方舟 Coding Plan(个人版,`arkcli-coding-plan`) | session / weekly / monthly 三个窗口 | 同一组 AK/SK 直连 `GetCodingPlanUsage`,HTTP 200 |
75
+ | Kimi Coding | 5 小时 / 周 / 月窗口 | 真实 Key |
76
+ | 小米 MiMo | 余额 + 套餐额度 | 真实平台会话 Cookie(含自动登录) |
77
+ | Command Code | 月额度 | 提供方插件的凭据链 |
78
+ | SuperGrok | 统一周池 + 加量余额 | 真实 `~/.grok/auth.json` |
79
+ | Codex (ChatGPT) | 主 / 次窗口 + 档位 | 真实 `~/.codex/auth.json`(本插件默认关闭) |
80
+
81
+ ### 未验证
82
+
83
+ 其余提供商**只经过桩测试**——响应形态取自公开文档、第三方实现或抓包,**没有在真实账号上跑通过**:
84
+
85
+ > Z.ai(中国与国际)、OpenCode Go、MiniMax(中国与国际)、Synthetic、NanoGPT、SiliconFlow、DeepSeek、OpenRouter、Novita、Hyperbolic、DeepInfra、Chutes、Ollama Cloud、Vercel AI Gateway、ZenMux、LiteLLM,以及火山方舟的**团队版席位**与 **BytePlus 站点**。
86
+
87
+ 它们可能可用,也可能因为接口变更、字段差异或权限要求而失败。**本插件只保证上面那张表里的**。
88
+
89
+ 如果你用了其中之一,不管成功还是失败,都欢迎[开 issue](https://github.com/KouzakiUmi/dsh-subusage/issues)——附上提供商名称、错误文案与 DSH 版本即可(**不要贴 Key 或 Cookie**)。失败和成功一样有用:失败帮我们修适配,成功帮我们把这一行挪进上表。
90
+
91
+ ### 主要风险
92
+
93
+ - **额度数字仅供参考,不要当计费依据。** 各家的额度接口都是未公开或半公开的,字段语义可能随时变化;本插件不做任何换算,读到什么显示什么。
94
+ - **未验证的提供商会随接口变化而失效**(见上一节)。失效时在设置页关掉那一家的检测即可,其余不受影响;欢迎开 issue 帮我们补上。
95
+ - **凭据以明文落盘**:本插件的配置不是加密凭据库;Windows 上真正的访问边界取决于目录 ACL,`chmod` 不能替代它。
96
+ - **火山方舟是账号级读取**:一组 IAM AK/SK 能查到该账号名下**所有**套餐(Agent Plan、Coding Plan、团队席位)。把装了本插件的机器或配置文件交给别人时请注意这一点。
97
+ - **浏览器自动化**:MiMo 的自动登录会启动一个隔离的 Chromium 会话——不点「登录并自动导入」就不会触发。
98
+ - **收录不等于安全审计**:无论 DSH STORE 还是 awesome-dsh-plugin,收录只表示满足它们各自的清单规则,都不是对代码的安全审查。
99
+
67
100
  ## Compatibility / 兼容性
68
101
 
69
102
  | 项目 | 值 | 来源 |
70
103
  |---|---|---|
71
- | 核心 peer 范围 | `@deepseek-ai/dsh` 及各核心包均为 `>=0.2.0-rc.1 <0.3.0-0`(允许该范围内的预发布版本) | [`package.json`](package.json) 的 `peerDependencies` |
72
- | 开发目标版本 | DeepSeek Harness `0.2.0-rc.2` | [开发与验证说明](docs/development.md) |
73
- | 最后验证日期 | `2026-10-09` | 同上 |
104
+ | 核心 peer 范围 | `@deepseek-ai/dsh` 及各核心包均为 `>=0.2.0-rc.1 <0.2.1-0 \|\| >=0.2.1-0 <0.3.0-0` | [`package.json`](package.json) 的 `peerDependencies` |
105
+ | Node.js | `>=22.19.0` | `engines` 与 `dsh.compatibility.node` |
106
+ | 已验证的 Core 版本 | `0.2.1-alpha.1` | 本机实机运行;逐版本状态见 `dsh.compatibility.dshReleases` |
107
+ | 最后验证日期 | `2026-10-10` | [开发与验证说明](docs/development.md) |
108
+
109
+ > **范围里那个 `||` 分支不是冗余。** node-semver 只有在范围中**某个比较符与该版本的 `major.minor.patch` 元组完全一致、且自身带预发布标签**时,才放行预发布版本。所以 `>=0.2.0-rc.1 <0.3.0-0` 这种看起来覆盖 0.2.x 的写法会**静默排除** `0.2.1-alpha.1`——而它正是当前活跃版本。必须为每个 minor 元组各写一个带预发布标签的分支。
74
110
 
75
- 声明范围不代表范围内所有版本都已实机验证;实际验证范围(在线接口、真实账号、浏览器像素验收等)见 [开发与验证说明](docs/development.md)。这是树外 Host / Client bundle,通过 `cordis.patch.yml` 插入,不修改 DSH 核心、安装树或 ASAR。
111
+ `dsh.compatibility.dshReleases` 逐版本声明已知状态:**只有实际验证过的标 `compatible`**,其余一律 `unknown`——没有证据不猜。声明范围不代表范围内所有版本都已实机验证;实际验证范围(在线接口、真实账号、浏览器像素验收等)见 [开发与验证说明](docs/development.md)。这是树外 Host / Client bundle,通过 `cordis.patch.yml` 插入,不修改 DSH 核心、安装树或 ASAR。
76
112
 
77
113
  ## Install / 安装
78
114
 
@@ -83,10 +119,13 @@
83
119
  | 本地开发目录 | 仓库路径 | 以链接方式使用工作副本 |
84
120
 
85
121
  ```console
86
- # npm 包名
122
+ # npm 包名(推荐:预构建,免构建授权)
87
123
  dsh plugin --profile <profile> add dsh-subusage
88
124
 
89
- # GitHub Release tarball(尚未经市场收录时的完整 URL)
125
+ # GitHub 固定 Commit(DSH STORE / awesome-dsh-plugin 只接受这种来源)
126
+ dsh plugin --profile <profile> add 'git+https://github.com/KouzakiUmi/dsh-subusage.git#<40 位完整 Commit>'
127
+
128
+ # GitHub Release tarball(资产名不带版本号,所以这个地址不会随发版而失效)
90
129
  dsh plugin --profile <profile> add https://github.com/KouzakiUmi/dsh-subusage/releases/latest/download/dsh-subusage.tgz
91
130
 
92
131
  # 本地开发目录(link 工作副本)
@@ -102,8 +141,10 @@ dsh plugin --profile <profile> remove dsh-subusage
102
141
  ```
103
142
 
104
143
  - `<profile>` 换成目标 profile;**Desktop 请使用随包 DSH CLI 或应用内插件管理页**,PATH 上的 npm 全局 `dsh` 不一定是它。参数与 profile 名以目标部署的 `dsh plugin --help` 为准;桌面插件页同样接受包名、GitHub 地址或本地目录。
144
+ - **不要用浮动分支**(`#main`)安装:市场与商店只认固定到完整 40 位 Commit 的来源,浮动分支无法核实你装到的到底是哪份代码。
105
145
  - 安装只把包放进 profile,**不等于已启用**;按部署方式重载或重启,让 Host 与 Client 一起生效(只刷新页面不保证 Host 升级)。
106
146
  - 只取压缩包:`npm pack dsh-subusage`;核对发布结果:`npm view dsh-subusage version dist-tags`。
147
+ - 本包**没有** `preinstall` / `install` / `postinstall` / `prepare` 脚本,安装期不执行任何代码;也不需要 pnpm 的 `allowBuilds` 授权。
107
148
 
108
149
  ## Quick start / 快速开始
109
150
 
@@ -142,7 +183,7 @@ $env:DEEPSEEK_API_KEY = "<你的 DeepSeek API Key>" # 启动环境;改完需
142
183
  - **设置页路径**:设置 → **订阅用量** → 提供商管理。「没有检测到API的默认隐藏」默认开启;首次没有可见条目时管理区自动展开。关闭的提供商仍可配置凭据,只是不会发起用量请求。
143
184
  - **凭据从哪来**:每一类凭据的**官网入口、环境变量名与界面填入位置**都写在[凭据获取指引](docs/credentials.md)——包括火山的 IAM AK/SK(子用户还要挂 `ArkReadOnlyAccess` 且不限制到项目)、MiMo 的平台会话 Cookie、以及各家 API Key 的对照表。**注意推理 Key 与额度凭据不通用。**
144
185
  - **默认开关策略**:新安装默认开启 10 条、默认关闭 20 条(**Codex 默认关闭**——它的提供方插件自带用量药丸);开关即时保存,升级保留已保存的开关,不重算默认值。默认隐藏只影响显示,不会自动开启被关闭的提供商。
145
- - **火山方舟是一组**:7 条路由共用同一组 IAM AK/SK,所以设置页只呈现**一张卡片、一个开关**(组内最后选的那条作为代表),卡片里写明本机实际装了哪几条。这些 provider id 并没有合并——药丸仍按 id 匹配路由。
186
+ - **火山方舟是一组**:5 条路由共用同一组 IAM AK/SK,所以设置页只呈现**一张卡片、一个开关**(组内最后选的那条作为代表),卡片里写明本机实际装了哪几条。这些 provider id 并没有合并——药丸仍按 id 匹配路由。旧插件 `@volcengine/ark-plan-api` 的两个国内路由(`ark-coding-plan-cn` / `ark-agent-plan-cn`)**已移除**:它们与 arkcli 的路由查同一份订阅,并列显示只会让人以为买了两个套餐;装了旧插件的用户,药丸仍按那两个 id 显示同一份数据(入口归并到 arkcli 路由)。
146
187
  - **火山方舟不按路由收起**:它的额度走**账号级管控面**,一组 AK/SK 就能查到名下所有套餐(`GetCodingPlanUsage` 与 `GetAFPUsage` 是同一套协议、同一组凭据)。推理路由装没装只影响模型能不能用,**不影响额度能不能查**——所以只持有 Coding Plan、却没在 DSH 里配 coding-plan 路由时,那条额度照样会显示(前提是配了 AK/SK)。
147
188
  - **凭据来源优先级**(`inherit` 模式):**凭据服务 → 启动环境 → 旧手动配置兜底**;切到自定义(`manual`)模式时只使用保存的手动 Key。界面会区分这三种来源。
148
189
  - **不接受本地 Key 的提供商**:`commandcode`(凭据链属提供方插件)、`xai-oauth`、`openai-codex`(只读各自 CLI 的登录文件),以及 MiMo(Cookie 会话)。
@@ -167,7 +208,7 @@ $env:DEEPSEEK_API_KEY = "<你的 DeepSeek API Key>" # 启动环境;改完需
167
208
  | Command Code | `COMMANDCODE_API_KEY` | 凭据由提供方插件管理,本页只读继承;兜底读取 `~/.commandcode/auth.json`(`cmd login`) |
168
209
  | SuperGrok | 无 | `xai-oauth` 路由;只读 dsh-grok-kit / Grok CLI 共享的 OAuth 登录文件 `~/.grok/auth.json`(旧版 `~/.dsh/.xai-oauth-auth.json`),不保存、**不刷新 token**;access token 过期时直接说明过期时间并提示去刷(不代刷——refresh-token 轮换由 Grok CLI / grok-kit 各自的锁协议管理,第三端刷新会把它们的轮换顶掉)。API Key(`XAI_API_KEY`)取不到订阅周池 |
169
210
  | Codex | 无 | `openai-codex` 路由;只读 Codex CLI 的 ChatGPT 订阅登录文件(`$CODEX_HOME/auth.json` 或 `~/.codex/auth.json`,要求 `auth_mode` 为 `chatgpt`),不保存、不刷新 token |
170
- | 火山方舟 Ark(7 条路由) | `VOLC_ACCESSKEY` + `VOLC_SECRETKEY` | 额度查询要 **IAM Access Key 的 AK/SK 配对**,与推理 API Key(`ARKCLI_*_API_KEY` / `ARK_*_API_KEY`)**不是同一套**;7 条路由共用一组 AK/SK。这一组 AK/SK 能**同时**查到 Agent Plan 与 Coding Plan(含团队版席位)——不存在「Coding Plan 专用查询 key」 |
211
+ | 火山方舟 Ark(5 条路由) | `VOLC_ACCESSKEY` + `VOLC_SECRETKEY` | 额度查询要 **IAM Access Key 的 AK/SK 配对**,与推理 API Key(`ARKCLI_*_API_KEY` / `ARK_*_API_KEY`)**不是同一套**;5 条路由共用一组 AK/SK。这一组 AK/SK 能**同时**查到 Agent Plan 与 Coding Plan(含团队版席位)——不存在「Coding Plan 专用查询 key」 |
171
212
  | SiliconFlow | `SILICONFLOW_API_KEY` | 余额型:`GET /v1/user/info` 返回账户余额,**与推理是同一把 Key** |
172
213
  | OpenRouter | `OPENROUTER_API_KEY` | key 限额与用量用推理 key 即可;**账户余额需要 management / provisioning key**,普通 key 会被 403 拒——此时静默降级为只显示限额,不判失败 |
173
214
  | Synthetic | `SYNTHETIC_API_KEY` | 模型订阅请求额度 |
@@ -181,7 +222,7 @@ $env:DEEPSEEK_API_KEY = "<你的 DeepSeek API Key>" # 启动环境;改完需
181
222
  | ZenMux | `ZENMUX_MANAGEMENT_API_KEY` | 额度端点**只认 Management API Key**(推理 key 不适用),所以变量名单独区分。在 <https://zenmux.ai/platform/management> 创建该 key |
182
223
  | LiteLLM | `LITELLM_API_KEY` + 代理地址 | 自建网关:除虚拟 Key(**与推理同一把**)还要在凭据区填写自己的 proxy 地址。管理端点在 **proxy 根**——填了 `/v1` 也会被去掉,不会拼成 `/v1/key/info` |
183
224
 
184
- 按 provider id 的字面映射(补齐上表按语义分组、未逐条列出的 7 条火山路由,逐条对应 `PROVIDERS[id].envName`):
225
+ 按 provider id 的字面映射(补齐上表按语义分组、未逐条列出的 5 条火山路由,逐条对应 `PROVIDERS[id].envName`):
185
226
 
186
227
  ```text
187
228
  deepseek DEEPSEEK_API_KEY
@@ -211,8 +252,6 @@ arkcli-agent-plan ARKCLI_AGENT_PLAN_API_KEY (额度用 VOLC_ACCESS
211
252
  arkcli-coding-plan ARKCLI_CODING_PLAN_API_KEY
212
253
  arkcli-agent-plan-team ARKCLI_AGENT_PLAN_TEAM_API_KEY
213
254
  arkcli-coding-plan-team ARKCLI_CODING_PLAN_TEAM_API_KEY
214
- ark-coding-plan-cn ARK_CODING_PLAN_CN_API_KEY
215
- ark-agent-plan-cn ARK_AGENT_PLAN_CN_API_KEY
216
255
  ark-coding-plan-byteplus ARK_CODING_PLAN_BYTEPLUS_API_KEY
217
256
  ```
218
257
 
@@ -231,7 +270,17 @@ ark-coding-plan-byteplus ARK_CODING_PLAN_BYTEPLUS_API_KEY
231
270
  | `~/.grok/auth.json`(回退 `~/.dsh/.xai-oauth-auth.json`) | Grok CLI / dsh-grok-kit 的 OAuth 登录 | **只读**,不保存、不刷新 |
232
271
  | `~/.commandcode/auth.json` | Command Code CLI(`cmd login`)的 Key | **只读**兜底 |
233
272
 
234
- **网络访问**:只连上面清单里各家自己的额度 / 余额端点,以及 `platform.xiaomimimo.com`(MiMo 用量与登录)、`api.commandcode.ai`、`cli-chat-proxy.grok.com`、`chatgpt.com/backend-api`、火山管控面 `open.volcengineapi.com` / `ark.ap-southeast-1.byteplusapi.com`,外加你自己填写的 LiteLLM proxy 地址。完整域名表见 [网络端点](docs/providers.md#网络端点)。MiMo 自动登录会另外打开官方平台页面,由你在该页面自行输入密码与验证码。
273
+ **网络访问**:只连上面清单里各家自己的额度 / 余额端点,以及 `platform.xiaomimimo.com`(MiMo 用量与登录)、`api.commandcode.ai`、`cli-chat-proxy.grok.com`、`chatgpt.com/backend-api`、火山管控面 `open.volcengineapi.com`,外加你自己填写的 LiteLLM proxy 地址。完整域名表见 [网络端点](docs/providers.md#网络端点)。MiMo 自动登录会另外打开官方平台页面,由你在该页面自行输入密码与验证码。**本插件没有自己的服务器,不做遥测,也不把你的数据发给任何第三方。**
274
+
275
+ **命令、进程与安装期行为**
276
+
277
+ | 项 | 情况 |
278
+ |---|---|
279
+ | 执行外部命令 | **不执行**。没有任何 shell 调用 |
280
+ | 生命周期脚本 | `preinstall` / `install` / `postinstall` / `prepare` **全部没有**;安装期不运行任何代码 |
281
+ | 运行依赖 | `playwright-core`(唯一一个)。只用于可选的 MiMo 自动登录;**不下载浏览器**,用你本机已有的 Chrome/Chromium |
282
+ | 浏览器 | 只有你点「登录并自动导入」时才启动一个**隔离的**临时会话,五分钟未完成自动超时;不读你日常浏览器的配置、Cookie 或 profile |
283
+ | DSH 核心 | 不修改源码树、不碰任何 `@deepseek-ai/*` 包、不遮蔽官方插件清单;只用公开 Host 服务与 `cordis.patch.yml` 插入自己的行 |
235
284
 
236
285
  **密钥如何处理**
237
286
 
@@ -252,7 +301,7 @@ ark-coding-plan-byteplus ARK_CODING_PLAN_BYTEPLUS_API_KEY
252
301
 
253
302
  调用约定(**结果永远不为空**):
254
303
 
255
- - 模型手里的名字往往不是这里的 provider id(它更可能看到 DSH 的路由名,例如 `zai`),所以匹配是**宽松**的:一个名字命中多条路由时**全部返回**(`zai` → 中国版 + 国际版,`ark` → 7 条),每条各自带着真实 `state`(没启用的如实标 `disabled`),由调用方自己判断要看哪条。
304
+ - 模型手里的名字往往不是这里的 provider id(它更可能看到 DSH 的路由名,例如 `zai`),所以匹配是**宽松**的:一个名字命中多条路由时**全部返回**(`zai` → 中国版 + 国际版,`ark` → 5 条),每条各自带着真实 `state`(没启用的如实标 `disabled`),由调用方自己判断要看哪条。
256
305
  - 一个名字都认不出时,返回里会**先**给一条说明(写明可用写法),**再附上全部 30 条数据**——数据先给出去,判断交给调用方,而不是回一个空结果。
257
306
 
258
307
  **Host 服务**(Cordis `Service`,key 为 `subUsage`):
package/lib/client.js CHANGED
@@ -279,10 +279,9 @@ window.__ModuleLoader__.load({
279
279
  // 企业版/团队版:额度按席位下发,需 AK/SK 有查看席位的权限。
280
280
  "arkcli-agent-plan-team": { label: "ARK Agent Plan Team (arkcli)", short: "ARK Plan Team", envName: "ARKCLI_AGENT_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
281
281
  "arkcli-coding-plan-team": { label: "ARK Coding Plan Team (arkcli)", short: "ARK Code Team", envName: "ARKCLI_CODING_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
282
- // 旧插件 @volcengine/ark-plan-api 的路由(保留兼容);short 与上面区分开,避免标签重名。
283
- "ark-coding-plan-cn": { label: "Ark Coding Plan (CN, legacy plugin)", short: "Ark Coding", envName: "ARK_CODING_PLAN_CN_API_KEY", volc: true, defaultEnabled: false },
284
- "ark-agent-plan-cn": { label: "Ark Agent Plan (CN, legacy plugin)", short: "Ark Agent", envName: "ARK_AGENT_PLAN_CN_API_KEY", volc: true, defaultEnabled: false },
285
- "ark-coding-plan-byteplus": { label: "Ark Coding Plan (BytePlus, legacy plugin)", short: "Ark BytePlus", envName: "ARK_CODING_PLAN_BYTEPLUS_API_KEY", volc: true, defaultEnabled: false },
282
+ // 旧插件 @volcengine/ark-plan-api 的国内两条路由已从 Host 移除(实测与 arkcli 查同一份订阅、
283
+ // 同一组 AK/SK)。它们的 id 不在本表里,由 ROUTE_PROVIDER_ALIAS 在入口归并到 arkcli 路由,
284
+ // 装了旧插件的用户选中那些模型的药丸照常显示。
286
285
  // 余额型:接口给账户余额而不是订阅窗口,药丸用余额兜底文案显示。
287
286
  "siliconflow": { label: "SiliconFlow", short: "SiliconFlow", envName: "SILICONFLOW_API_KEY", defaultEnabled: false },
288
287
  // 余额端点只对 management / provisioning key 开放;普通 key 只显示限额与用量。
@@ -300,10 +299,18 @@ window.__ModuleLoader__.load({
300
299
  "litellm": { label: "LiteLLM", short: "LiteLLM", envName: "LITELLM_API_KEY", defaultEnabled: false }
301
300
  };
302
301
  // 顺序只影响展示:DeepSeek 放在末尾以免打乱既有条目位置;药丸匹配靠 provider id,与顺序无关。
303
- const PROVIDER_ORDER = ["zai-coding-cn", "kimi-coding", "xiaomi-token-plan-cn", "opencode-go", "commandcode", "openai-codex", "xai-oauth", "minimax", "minimax-cn", "zai-coding", "synthetic", "nanogpt", "ark-coding-plan-cn", "ark-agent-plan-cn", "ark-coding-plan-byteplus", "siliconflow", "deepseek", "openrouter", "novita", "hyperbolic", "deepinfra", "chutes", "ollama-cloud", "vercel-ai-gateway", "zenmux", "litellm", "arkcli-agent-plan", "arkcli-coding-plan", "arkcli-agent-plan-team", "arkcli-coding-plan-team"];
302
+ const PROVIDER_ORDER = ["zai-coding-cn", "kimi-coding", "xiaomi-token-plan-cn", "opencode-go", "commandcode", "openai-codex", "xai-oauth", "minimax", "minimax-cn", "zai-coding", "synthetic", "nanogpt", "siliconflow", "deepseek", "openrouter", "novita", "hyperbolic", "deepinfra", "chutes", "ollama-cloud", "vercel-ai-gateway", "zenmux", "litellm", "arkcli-agent-plan", "arkcli-coding-plan", "arkcli-agent-plan-team", "arkcli-coding-plan-team"];
304
303
  // 火山方舟的 7 条路由共用同一组 IAM AK/SK,而一般用户只持有一个套餐,所以设置页只给一张卡片、
305
304
  // 一个开关(组开关落在 Host 的 visibility.ark 上)。这些 id 不能真正合并——药丸靠 provider id 匹配路由。
306
305
  const ARK_IDS = PROVIDER_ORDER.filter(id => PROVIDER_META[id]?.volc);
306
+ /**
307
+ * 旧插件 @volcengine/ark-plan-api 注册的两个国内路由 id **已从本插件移除**:它们与 arkcli 的路由
308
+ * 查的是同一份订阅(同 Action、同 host、同 AK/SK,实测请求 URL 逐字相同),并列显示只会让人以为
309
+ * 买了两个套餐,数字还会因为各自缓存而差出几十秒的量。
310
+ * 但装了旧插件的用户,模型路由仍然是这两个 id —— 药丸必须在入口把它们归并到 arkcli 路由,
311
+ * 否则那些用户会突然失去用量显示。(第三条 BytePlus 已随实现一并移除:无 BytePlus 账号可验证。)
312
+ */
313
+ const ROUTE_PROVIDER_ALIAS = { "ark-agent-plan-cn": "arkcli-agent-plan", "ark-coding-plan-cn": "arkcli-coding-plan" };
307
314
  function providerEnabled(settings, id) {
308
315
  if (PROVIDER_META[id]?.volc) {
309
316
  // 组开关优先;旧配置里没有它时按「组内任一条开着」合并过来,全新配置则沿用默认。
@@ -319,13 +326,15 @@ window.__ModuleLoader__.load({
319
326
  function providerRoutable(result, id) { return result?.configured?.[id] !== false; }
320
327
  function providerVisible(settings, id, entry, result) {
321
328
  if (!providerEnabled(settings, id)) return false;
322
- // 关掉「没有检测到 API 的默认隐藏」就是要看全部,不再按路由或凭据收起。
329
+ // 关掉「没有检测到 API 的默认隐藏」就是要看全部,不再按路由、凭据或订阅状态收起。
323
330
  if (settings?.visibility?.hideWithoutApi === false) return true;
324
- // 火山方舟**不按路由收起**:额度是账号级订阅,与「本机装没装这条推理路由」无关——一组 IAM AK/SK
325
- // 就能查到名下所有套餐(GetCodingPlanUsage 与 GetAFPUsage 同协议同凭据,2026-10-10 实测通过)。
326
- // 路由只是模型推理入口,「订了 Coding Plan、却没在 DSH 里配 coding-plan 路由」完全正常;
327
- // 按路由收起会让这类用户凭空少一条真正有数据的额度,看到的是「本机什么都没有」。
328
- if (!PROVIDER_META[id]?.volc && !providerRoutable(result, id)) return false;
331
+ // 火山方舟**不按推理路由收起**:额度是账号级订阅,路由装没装与额度无关——一组 IAM AK/SK 就能
332
+ // 查到名下所有套餐(GetCodingPlanUsage 与 GetAFPUsage 同协议同凭据)。但它**要按接口回报的
333
+ // 订阅状态收起**:7 条路由一律列出来,用户根本分不清哪个是哪个,而其中多数是「未检测到该套餐订阅」。
334
+ // `subscribed === undefined` 表示还没拉到数据,按「未知」放行——不能因为一次没拉到就把有订阅的藏起来。
335
+ if (PROVIDER_META[id]?.volc) {
336
+ if (entry?.subscribed === false) return false;
337
+ } else if (!providerRoutable(result, id)) return false;
329
338
  return entry?.apiDetected !== false && !["no-key", "no-cookie"].includes(entry?.state);
330
339
  }
331
340
  /** token 数紧凑格式:1,234,567,890 → 1.23B */
@@ -1561,8 +1570,12 @@ window.__ModuleLoader__.load({
1561
1570
  function SubusagePillEntry({ store, readEntry, t, getLocale, cc, setCommandCodeAccount }) {
1562
1571
  const state = react.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
1563
1572
  const provider = state.current && state.current.provider;
1564
- const info = PROVIDER_ORDER.includes(provider) ? PROVIDER_META[provider] : null;
1565
- const meta = info ? { label: provider === "commandcode" ? info.label : info.short, id: provider } : null;
1573
+ // 旧插件 @volcengine/ark-plan-api 的两个国内路由 id 不在本表里,但它们是**同一个 plan**:
1574
+ // 不管模型路由由哪个插件提供,都归并到 arkcli 路由再查——所以只会有**一个**面板,
1575
+ // 数据来自同一份订阅(实测两条路由的请求 URL 逐字相同)。
1576
+ const resolved = ROUTE_PROVIDER_ALIAS[provider] ?? provider;
1577
+ const info = PROVIDER_ORDER.includes(resolved) ? PROVIDER_META[resolved] : null;
1578
+ const meta = info ? { label: resolved === "commandcode" ? info.label : info.short, id: resolved } : null;
1566
1579
  // 所有 Hook 必须先于条件返回:模型切换或重连时 provider 可能暂时为空。
1567
1580
  // 稳定引用:否则父组件每次渲染都新建函数,药丸 effect 会反复重启轮询。
1568
1581
  const providerId = meta ? meta.id : null;
package/lib/index.js CHANGED
@@ -54,12 +54,13 @@ const PROVIDERS = {
54
54
  // 企业版/团队版:额度挂在席位上,先 ListSeatInfos 取 SeatID 再查该席位(volcSeat 决定用哪对 Action)。
55
55
  "arkcli-agent-plan-team": { label: "ARK Agent Plan Team (arkcli)", envName: "ARKCLI_AGENT_PLAN_TEAM_API_KEY", credentialKind: "volc", volcSeat: "agent", volcScene: "agent_plan_enterprise", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
56
56
  "arkcli-coding-plan-team": { label: "ARK Coding Plan Team (arkcli)", envName: "ARKCLI_CODING_PLAN_TEAM_API_KEY", credentialKind: "volc", volcSeat: "coding", volcScene: "coding_plan_enterprise", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
57
- // ── 旧插件 @volcengine/ark-plan-api 注册的路由(保留兼容)─────────────────
58
- // 两条路线的 provider id 不同,故并存;装旧插件的用户仍会走这几条。
59
- "ark-coding-plan-cn": { label: "Ark Coding Plan (CN, legacy plugin)", envName: "ARK_CODING_PLAN_CN_API_KEY", credentialKind: "volc", volcAction: "GetCodingPlanUsage", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
60
- "ark-agent-plan-cn": { label: "Ark Agent Plan (CN, legacy plugin)", envName: "ARK_AGENT_PLAN_CN_API_KEY", credentialKind: "volc", volcAction: "GetAFPUsage", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
61
- // BytePlus(海外)Coding Plan:同一套 AK/SK,但走 BytePlus 管控面与 ap-southeast-1 区域。
62
- "ark-coding-plan-byteplus": { label: "Ark Coding Plan (BytePlus, legacy plugin)", envName: "ARK_CODING_PLAN_BYTEPLUS_API_KEY", credentialKind: "volc", volcAction: "GetCodingPlanUsage", volcHost: "ark.ap-southeast-1.byteplusapi.com", volcRegion: "ap-southeast-1", defaultEnabled: false },
57
+ // ── 旧插件 @volcengine/ark-plan-api 的三条路由已全部移除 ──────────────────
58
+ // 国内两条(`ark-coding-plan-cn` / `ark-agent-plan-cn`)与 arkcli 的路由请求 URL 逐字相同(同 Action、
59
+ // 同 host、同 AK/SK、同一份订阅),并列显示只会让人以为买了两个套餐,数字还会因各自缓存而差出几十秒。
60
+ // BytePlus 那条(`ark-coding-plan-byteplus`)走 `ark.ap-southeast-1.byteplusapi.com`:域名与 Action
61
+ // **确实存在**(返回的是业务信封错误 NotFound.BillingType,不是路由不存在),但本机没有 BytePlus 账号
62
+ // 可验证,实现正确性无从确认,因此不再保留——取舍与证据记在 docs/provider-coverage.md。
63
+ // 国内那两个 id 药丸仍认:Client 的 ROUTE_PROVIDER_ALIAS 把它们归并到 arkcli 路由。
63
64
  // DeepSeek 官方:余额型(不是订阅窗口),与推理**同一把** API Key,端点与响应见官方文档
64
65
  // https://api-docs.deepseek.com/zh-cn/api/get-user-balance
65
66
  "deepseek": { label: "DeepSeek", envName: "DEEPSEEK_API_KEY" },
@@ -399,7 +400,7 @@ function volcFailure(action, error) {
399
400
  }
400
401
  async function fetchJson(fetcher, url, headers, lifetimeSignal, init = {}) {
401
402
  let response;
402
- try { response = await fetcher(url, { method: init.method ?? "GET", ...(init.body !== undefined ? { body: init.body } : {}), headers: { accept: "application/json", "user-agent": "dsh-subusage/0.10.6", ...headers }, signal: lifetimeSignal ? AbortSignal.any([AbortSignal.timeout(10000), lifetimeSignal]) : AbortSignal.timeout(10000), redirect: "manual" }); }
403
+ try { response = await fetcher(url, { method: init.method ?? "GET", ...(init.body !== undefined ? { body: init.body } : {}), headers: { accept: "application/json", "user-agent": "dsh-subusage/0.10.8", ...headers }, signal: lifetimeSignal ? AbortSignal.any([AbortSignal.timeout(10000), lifetimeSignal]) : AbortSignal.timeout(10000), redirect: "manual" }); }
403
404
  catch { throw failure("subusage/network", "网络请求失败或超时", true); }
404
405
  if (response.status >= 300 && response.status < 400 || response.status === 401 || response.status === 403) throw failure("subusage/auth", "登录或凭据已失效");
405
406
  if (!response.ok) {
@@ -968,6 +969,10 @@ export class SubUsageService extends TypertRemoteService {
968
969
  const signed = volcSignature({ accessKeyId: identity.key, secretAccessKey: identity.secret, action, host: spec.volcHost, region: spec.volcRegion, body });
969
970
  const response = await get(signed.url, signed.headers, { method: "POST", body });
970
971
  const apiError = volcError(response);
972
+ // 「这个账号没有这份套餐」不是故障,是答案:实测没有 BytePlus Coding Plan 的账号会返回
973
+ // HTTP 404 `NotFound.BillingType`(coding plan config is not exist),`OperationDenied.NotSubscribed`
974
+ // 同义。把它当错误抛出去,界面上就多一张红色卡片;正确做法是回报 subscribed:false 让前端收起。
975
+ if (apiError && /^(NotFound\.BillingType|OperationDenied\.NotSubscribed)$/.test(apiError.code)) return { notSubscribed: true };
971
976
  if (apiError) throw volcFailure(action, apiError);
972
977
  return response;
973
978
  };
@@ -977,7 +982,8 @@ export class SubUsageService extends TypertRemoteService {
977
982
  // 企业版/团队版:额度挂在席位上,先取 SeatID 再查该席位。
978
983
  if (spec.volcSeat) {
979
984
  const seatIds = parseVolcSeatIds(await call("ListSeatInfos", { Filter: {}, Scene: spec.volcScene, PageNum: 1, PageSize: 100 }));
980
- if (!seatIds.length) return { windows: [], extras: [{ kind: "plan", value: "该账号下没有可读的席位(或当前身份无权查看)" }], coverage: "partial" };
985
+ // 没有可读席位就是「这条团队版订阅用不上」——对用户而言与未订阅同义,如实回报以便前端收起。
986
+ if (!seatIds.length) return { windows: [], extras: [{ kind: "plan", value: "该账号下没有可读的席位(或当前身份无权查看)" }], coverage: "partial", subscribed: false };
981
987
  const seatId = seatIds[0];
982
988
  // 多个席位时只看第一个,但如实说明总数,不假装看全了。
983
989
  const seatNote = seatIds.length > 1 ? `${seatId}(共 ${seatIds.length} 个席位)` : seatId;
@@ -993,9 +999,10 @@ export class SubUsageService extends TypertRemoteService {
993
999
  if (parsed.plan) extras.push({ kind: "plan", value: parsed.plan });
994
1000
  // 与个人版口径一致:没有窗口时说清「本周期没有可读额度」,而不是只给一个 partial 状态。
995
1001
  extras.push({ kind: "account", value: parsed.windows.length ? `席位 ${seatNote}` : `席位 ${seatNote}(本周期没有可读的额度窗口)` });
996
- return { windows: toWindows(parsed), extras, coverage: parsed.windows.length ? "complete" : "partial" };
1002
+ return { windows: toWindows(parsed), extras, coverage: parsed.windows.length ? "complete" : "partial", subscribed: parsed.subscribed === true };
997
1003
  }
998
1004
  const body = await call(spec.volcAction);
1005
+ if (body?.notSubscribed) return { windows: [], extras: [{ kind: "plan", value: "该账号没有订阅这个套餐" }], coverage: "complete", subscribed: false };
999
1006
  let parsed;
1000
1007
  try { parsed = spec.volcAction === "GetAFPUsage" ? parseVolcAgentPlan(body) : parseVolcCodingPlan(body); }
1001
1008
  catch { throw failure("subusage/response", `火山方舟 ${spec.volcAction} 的额度结构无法解析`); }
@@ -1004,7 +1011,9 @@ export class SubUsageService extends TypertRemoteService {
1004
1011
  if (parsed.plan) extras.push({ kind: "plan", value: parsed.plan });
1005
1012
  // HTTP 200 但窗口为空是「未订阅」,不是 0% 用量:显式说明,不画零额度的假象。
1006
1013
  else if (!windows.length) extras.push({ kind: "plan", value: parsed.subscribed ? "本周期暂无窗口数据" : "未检测到该套餐订阅" });
1007
- return { windows, extras, coverage: windows.length ? "complete" : "partial" };
1014
+ // 订阅状态要**结构化**带出去,不能只写进 extras 文案:同一组 AK/SK 能查到名下所有套餐,
1015
+ // 界面上若把 7 条路由一律列出来,用户根本分不清哪个是哪个。前端据此收起没订阅的那些。
1016
+ return { windows, extras, coverage: windows.length ? "complete" : "partial", subscribed: parsed.subscribed === true };
1008
1017
  }
1009
1018
  const headers = { authorization: `Bearer ${identity.key}` };
1010
1019
  if (id === "synthetic") return normalizeSynthetic(await get("https://api.synthetic.new/v2/quotas", headers));
package/package.json CHANGED
@@ -1,16 +1,8 @@
1
1
  {
2
2
  "name": "dsh-subusage",
3
+ "version": "0.10.8",
3
4
  "displayName": "订阅用量显示",
4
- "version": "0.10.6",
5
- "publishConfig": {
6
- "access": "public",
7
- "registry": "https://registry.npmjs.org/"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/KouzakiUmi/dsh-subusage.git"
12
- },
13
- "description": "显示 DeepSeek 官方余额、火山方舟(arkcli 官方 CLI 路由与旧插件路由、Agent/Coding Plan 个人版与企业版席位,设置页合并为一组)、Z.ai(中国与国际)、Kimi、MiMo、OpenCode Go、Command Code、SuperGrok、Codex(ChatGPT 订阅,默认关闭)、MiniMax(中国)、Synthetic、NanoGPT、SiliconFlow、OpenRouter、Novita、Hyperbolic、DeepInfra、Chutes、Ollama Cloud、Vercel AI Gateway、ZenMux、LiteLLM 的订阅余量与余额;共享刷新与凭据管理,默认关闭 20 家。",
5
+ "description": "显示 DeepSeek 官方余额、火山方舟(arkcli 官方 CLI 路由:Agent / Coding Plan 个人版与企业版席位,设置页合并为一组)、Z.ai(中国与国际)、Kimi、MiMo、OpenCode Go、Command Code、SuperGrok、Codex(ChatGPT 订阅,默认关闭)、MiniMax(中国)、Synthetic、NanoGPT、SiliconFlow、OpenRouter、Novita、Hyperbolic、DeepInfra、Chutes、Ollama Cloud、Vercel AI Gateway、ZenMux、LiteLLM 的订阅余量与余额;共享刷新与凭据管理,默认关闭 17 家。",
14
6
  "keywords": [
15
7
  "dsh",
16
8
  "dsh-plugin",
@@ -20,33 +12,60 @@
20
12
  "quota"
21
13
  ],
22
14
  "license": "MIT",
23
- "peerDependencies": {
24
- "@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.3.0-0",
25
- "@deepseek-ai/dsh-credentials": ">=0.2.0-rc.1 <0.3.0-0",
26
- "@deepseek-ai/dsh-launch-environment": ">=0.2.0-rc.1 <0.3.0-0",
27
- "@deepseek-ai/dsh-tools": ">=0.2.0-rc.1 <0.3.0-0",
28
- "@deepseek-ai/dsh-typert-protocol": ">=0.2.0-rc.1 <0.3.0-0"
29
- },
30
15
  "type": "module",
31
16
  "main": "./lib/index.js",
17
+ "engines": {
18
+ "node": ">=22.19.0"
19
+ },
32
20
  "exports": {
33
21
  ".": "./lib/index.js",
34
22
  "./client": "./lib/client.js",
35
23
  "./cordis.patch.yml": "./cordis.patch.yml",
36
- "./package.json": "./package.json",
37
- "./locale/*": "./locale/*"
24
+ "./package.json": "./package.json"
38
25
  },
39
26
  "files": [
40
27
  "lib",
41
- "locale",
42
28
  "cordis.patch.yml",
43
- "README.md"
29
+ "README.md",
30
+ "LICENSE"
44
31
  ],
32
+ "scripts": {
33
+ "test": "node tests/run-all.mjs",
34
+ "check": "node tests/run-all.mjs && node scripts/check-manifest.mjs"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org/"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/KouzakiUmi/dsh-subusage.git"
43
+ },
44
+ "homepage": "https://github.com/KouzakiUmi/dsh-subusage#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/KouzakiUmi/dsh-subusage/issues"
47
+ },
48
+ "peerDependencies": {
49
+ "@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-0 <0.3.0-0",
50
+ "@deepseek-ai/dsh-credentials": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-0 <0.3.0-0",
51
+ "@deepseek-ai/dsh-launch-environment": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-0 <0.3.0-0",
52
+ "@deepseek-ai/dsh-tools": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-0 <0.3.0-0",
53
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-0 <0.3.0-0"
54
+ },
45
55
  "dsh": {
46
56
  "manifestVersion": 1,
47
57
  "bundle": {
48
58
  "patch": "./cordis.patch.yml"
49
59
  },
60
+ "compatibility": {
61
+ "node": ">=22.19.0",
62
+ "dshReleases": {
63
+ "0.2.0-rc.1": "unknown",
64
+ "0.2.0-rc.2": "unknown",
65
+ "0.2.1-alpha.1": "compatible",
66
+ "0.2.1-alpha.2": "unknown"
67
+ }
68
+ },
50
69
  "client": {
51
70
  "immediately": true,
52
71
  "inject": [
package/locale/en.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "name": "Subscription usage",
3
- "description": "Shows subscription quota and balance for 30 AI providers (DeepSeek, Volcengine Ark, Z.ai, Kimi, MiMo, Codex, MiniMax, OpenRouter, SiliconFlow, LiteLLM, ...), with one settings page for detection switches and credentials plus a read-only quota API."
4
- }
package/locale/zh.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "name": "订阅用量显示",
3
- "description": "显示 30 家 AI 提供商(DeepSeek、火山方舟 Ark、Z.ai、Kimi、MiMo、Codex、MiniMax、OpenRouter、SiliconFlow、LiteLLM 等)的订阅余量与余额;统一管理检测开关与凭据,并提供只读额度查询接口。"
4
- }