dsh-cost-meter 1.5.24 → 1.5.26
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 +8 -8
- package/README.md +8 -8
- package/lib/client.js +94 -7
- package/lib/coding-plans.js +38 -0
- package/lib/store.js +38 -4
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**Session cost tracking plugin for the DeepSeek Harness web GUI (bilingual UI)**
|
|
6
6
|
|
|
7
|
-
Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00) · pre-switch popup & system-notification alerts for peak/off-peak changes (position / lead time / alert type configurable) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / SCNet)
|
|
7
|
+
Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00) · pre-switch popup & system-notification alerts for peak/off-peak changes (position / lead time / alert type configurable) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet)
|
|
8
8
|
|
|
9
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-cost-meter)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
@@ -29,7 +29,7 @@ English | [中文](README.md)
|
|
|
29
29
|
| Official balance | Sidebar top / Settings page (configurable) | Total / granted / topped-up balance, auto-refresh + manual refresh; optional three-segment progress bar (blue/orange/gray) |
|
|
30
30
|
| Custom provider balance | Sidebar / Settings page (configurable) | Configurable HTTP balance lookup (e.g. LiteLLM); bilingual labels, currency, extract rules; collapsible panel alongside Coding Plan quotas |
|
|
31
31
|
| OpenCode Go quota | Sidebar / Settings / bottom-right dock (configurable) | Rolling-5h / weekly / monthly usage percent and reset times, each window toggleable independently, budget used % can show alongside; key auto-discovered (DSH credential store OPENCODE_GO_API_KEY / env / opencode login) or entered manually |
|
|
32
|
-
| Coding plan quotas | Settings page | Multi-vendor coding-plan quota queries (Anthropic Claude Pro/Max, Z.ai / Zhipu GLM Coding Plan, MiniMax Token Plan, Kimi/Moonshot balance, OpenRouter credits, SiliconFlow balance); per-vendor enable switch and
|
|
32
|
+
| Coding plan quotas | Sidebar / Settings page (per vendor) | Multi-vendor coding-plan quota queries (Anthropic Claude Pro/Max, Z.ai / Zhipu GLM Coding Plan, MiniMax Token Plan, Kimi/Moonshot balance, OpenRouter credits, SiliconFlow balance, CommandCode 5h/weekly windows + monthly credits balance); per-vendor enable switch, key and display position (sidebar card in the same box style as the Go quota; the collapsed rail shows percentages), credentials only sent to official endpoints; neutral hints when no credentials/subscription; SCNet Token Plan has no quota API — monthly usage is estimated from the local ledger via the official credits deduction table (no credentials needed) |
|
|
33
33
|
| Today's cost | Sidebar bottom (above the settings button) | “Today ¥x”, hover for call count and token details |
|
|
34
34
|
| Budget box | Sidebar bottom (between the balance row and the settings button) | Rounded-square frame: budget, used %, progress bar, today's cost & share of budget, used/limit; ≥80% warning, ≥100% over-budget |
|
|
35
35
|
| Summary cards | Settings page | Today / this month / cumulative cost and call counts |
|
|
@@ -44,7 +44,7 @@ English | [中文](README.md)
|
|
|
44
44
|
| Official price sync | Settings page | Fetches and parses the official pricing page, applies with one click |
|
|
45
45
|
| UI language | Settings → Display settings | Simplified Chinese / English / Follow browser (auto); switches instantly and auto-saves |
|
|
46
46
|
| AI price sync | [prompt](docs/AI-PRICE-SYNC-PROMPT.en.md) | DeepSeek official sync; other providers use the verified official price catalog and manual configuration |
|
|
47
|
-
| Model & Plan adaptation guide | [adaptation doc](docs/model-and-plan-adaptation.en.md) | Adaptation matrix for per-model billing and the
|
|
47
|
+
| Model & Plan adaptation guide | [adaptation doc](docs/model-and-plan-adaptation.en.md) | Adaptation matrix for per-model billing and the 8 Coding Plan vendors, the auto-matching mechanism and price sources ([中文](docs/model-and-plan-adaptation.md)) |
|
|
48
48
|
| Peak/off-peak alert guide | [alert doc](docs/peak-alert.en.md) | Fully illustrated guide to the pre-switch popup and system notification: effect screenshots (EN/中文), settings reference and usage tips ([中文](docs/peak-alert.md)) |
|
|
49
49
|
| Multi-provider billing | Settings / ledger | OpenAI, Anthropic, Google Gemini, Mistral and other providers with input/output, cache and reasoning-token pricing isolated by provider + model |
|
|
50
50
|
| Model-name auto-matching | Settings / ledger | Unknown model ids are matched against the price table: case/spaces/hyphens/dots and bracket annotations (e.g. (go)) are ignored — a normalized-equal or containing name hits (e.g. `gpt5.6 luna(go)`); router providers (opencode/zen etc.) search across all vendors; can be restricted to exact match, and unmatched models can be pinned to a specific entry in Settings |
|
|
@@ -194,22 +194,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
|
|
|
194
194
|
dsh plugin --profile web add dsh-cost-meter
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
-
**PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.
|
|
197
|
+
**PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.26`** — review the script before running):
|
|
198
198
|
|
|
199
199
|
```powershell
|
|
200
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
200
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.26/install.ps1 | iex
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
**Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
|
|
204
204
|
|
|
205
205
|
```sh
|
|
206
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.
|
|
206
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.26
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
Without git, use the GitHub tag archive:
|
|
210
210
|
|
|
211
211
|
```sh
|
|
212
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.
|
|
212
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.26.tar.gz
|
|
213
213
|
```
|
|
214
214
|
|
|
215
215
|
After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
|
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**DeepSeek Harness 会话费用统计插件(界面中英双语)**
|
|
6
6
|
|
|
7
|
-
本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / SCNet
|
|
7
|
+
本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet 八家)
|
|
8
8
|
|
|
9
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-cost-meter)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
| 官方余额 | 侧边栏顶部 / 设置页(可配) | 总余额 / 赠送 / 充值,自动刷新 + 手动刷新;可选三段进度条(蓝/橙/灰) |
|
|
30
30
|
| 自定义 Provider 余额 | 侧边栏 / 设置页(可配) | 可配置 HTTP 查询任意 Provider 余额(LiteLLM 等);中/英名称、币种、extract 规则;与 Coding Plan 同区可折叠配置 |
|
|
31
31
|
| OpenCode Go 额度 | 侧边栏 / 设置页 / 右下角(dock,可配) | 滚动 5 小时 / 本周 / 本月用量百分比与重置时间,三档可分别开关,可同时显示预算已用%;Key 自动发现(DSH 凭据库 OPENCODE_GO_API_KEY / 环境变量 / opencode 登录态)或手动填写 |
|
|
32
|
-
| Coding Plan 额度 | 设置页 | 多厂商 coding plan 订阅额度查询(Anthropic Claude Pro/Max、Z.ai/智谱 GLM、MiniMax Token Plan、Kimi/Moonshot 余额、OpenRouter credits、SiliconFlow 余额)
|
|
32
|
+
| Coding Plan 额度 | 侧边栏 / 设置页(每家可配) | 多厂商 coding plan 订阅额度查询(Anthropic Claude Pro/Max、Z.ai/智谱 GLM、MiniMax Token Plan、Kimi/Moonshot 余额、OpenRouter credits、SiliconFlow 余额、CommandCode 5h/周窗口与月度 Credits 余额),各家独立启用开关、Key 与显示位置(侧边栏卡片与 Go 额度同款,收起窄栏显示百分比),凭据只发往官方端点;无凭据/无订阅为中性提示;SCNet 超算互联网 Token Plan 无 API 额度端点,按官方 Credits 抵扣表由本地账本估算月度用量(无需凭据) |
|
|
33
33
|
| 当日费用 | 侧边栏底部(设置按钮上方) | 「今日 ¥x」,悬停见调用次数与 token 明细 |
|
|
34
34
|
| 预算图框 | 侧边栏底部(余额行与设置按钮之间) | 圆角方形图框:预算、已用%、进度条、今日费用与占预算%、已用/额度,≥80% 预警、≥100% 超支 |
|
|
35
35
|
| 汇总卡片 | 设置页 | 今日 / 本月 / 累计费用与调用次数 |
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
| 官方价格同步 | 设置页 | 抓取解析官方定价页,一键应用 |
|
|
46
46
|
| 界面语言 | 设置页 → 显示设置 | 简体中文 / English / 跟随浏览器(自动);切换即时生效并自动保存 |
|
|
47
47
|
| AI 价格同步 | [提示词](docs/AI-PRICE-SYNC-PROMPT.md) | DeepSeek 官方同步;其他 provider 使用已核对的官方价格目录与手动配置 |
|
|
48
|
-
| 模型与 Plan 适配说明 | [适配文档](docs/model-and-plan-adaptation.md) | 各厂商模型计费与
|
|
48
|
+
| 模型与 Plan 适配说明 | [适配文档](docs/model-and-plan-adaptation.md) | 各厂商模型计费与 8 家 Coding Plan 的适配矩阵、自动匹配机制与价格来源([English](docs/model-and-plan-adaptation.en.md)) |
|
|
49
49
|
| 峰/谷切换提醒图解 | [提醒文档](docs/peak-alert.md) | 峰谷切换前弹窗与系统通知的完整图解:效果截图(中/英)、设置项说明与使用建议([English](docs/peak-alert.en.md)) |
|
|
50
50
|
| 多 provider 计费 | 设置页 / 账本 | 支持 OpenAI、Anthropic、Google Gemini、Mistral 等 provider 的 input/output、缓存与 reasoning token 价格,按 provider+model 隔离计费 |
|
|
51
51
|
| 模型名自动匹配 | 设置页 / 账本 | 未知模型 id 自动匹配价格表:忽略大小写/空格/横杠/点号与括号附注,归一化等价或请求名包含表内模型名即命中(如 `gpt5.6 luna(go)`);路由 provider(opencode/zen 等)下跨厂商全库查找;可关闭为仅精确;未命中模型可手动指定计费条目 |
|
|
@@ -196,22 +196,22 @@
|
|
|
196
196
|
dsh plugin --profile web add dsh-cost-meter
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
-
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.
|
|
199
|
+
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.26`**,建议先下载审阅再运行):
|
|
200
200
|
|
|
201
201
|
```powershell
|
|
202
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
202
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.26/install.ps1 | iex
|
|
203
203
|
```
|
|
204
204
|
|
|
205
205
|
**或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
|
|
206
206
|
|
|
207
207
|
```sh
|
|
208
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.
|
|
208
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.26
|
|
209
209
|
```
|
|
210
210
|
|
|
211
211
|
没有 git 时可用 GitHub tag 打包直链:
|
|
212
212
|
|
|
213
213
|
```sh
|
|
214
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.
|
|
214
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.26.tar.gz
|
|
215
215
|
```
|
|
216
216
|
|
|
217
217
|
安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
|
package/lib/client.js
CHANGED
|
@@ -266,6 +266,8 @@ window.__ModuleLoader__.load({
|
|
|
266
266
|
'.cm-mm-row.over .cm-bbox-label,.cm-mm-row.over .cm-bbox-pct{color:var(--dsw-alias-state-error-primary)}',
|
|
267
267
|
'.cm-mm-row.warn .cm-bbox-fill{background:var(--dsw-alias-state-warn-primary)}',
|
|
268
268
|
'.cm-mm-row.over .cm-bbox-fill{background:var(--dsw-alias-state-error-primary)}',
|
|
269
|
+
'.cm-mm-row.wide .cm-bbox-label{width:auto;max-width:64px;flex:0 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
270
|
+
'.cm-mm-row.wide .cm-mm-text{flex:1;min-width:0;font-size:12px;color:var(--dsw-alias-label-secondary);text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
269
271
|
'.cm-bal-line{font-size:13px;color:var(--dsw-alias-label-secondary)}',
|
|
270
272
|
'.cm-bal-line.warn{color:var(--dsw-alias-state-warning-primary,#b45309)}',
|
|
271
273
|
'.cm-bal-line.err,.cm-bal-err{color:var(--dsw-alias-state-error-primary)}',
|
|
@@ -412,6 +414,8 @@ window.__ModuleLoader__.load({
|
|
|
412
414
|
codingPlanRemain5h: '5h',
|
|
413
415
|
codingPlanRemain7d: '7d',
|
|
414
416
|
enableCodingPlan: '启用额度查询',
|
|
417
|
+
codingPlanDisplayLabel: '显示位置',
|
|
418
|
+
codingPlanDisplayNote: '选「主页面侧边栏 / 两者」后,该厂商额度以图框卡片常驻侧边栏(与 Go 额度/余额同款,收起窄栏显示百分比)。',
|
|
415
419
|
codingPlanKeyLabel: 'API Key(可选,留空自动发现)',
|
|
416
420
|
refreshCodingPlan: '刷新',
|
|
417
421
|
codingPlanNotQueried: '未查询额度',
|
|
@@ -518,6 +522,7 @@ window.__ModuleLoader__.load({
|
|
|
518
522
|
codingPlanKimi: 'Kimi / Moonshot',
|
|
519
523
|
codingPlanOpenrouter: 'OpenRouter',
|
|
520
524
|
codingPlanSiliconflow: 'SiliconFlow 硅基流动',
|
|
525
|
+
codingPlanCommandcode: 'CommandCode',
|
|
521
526
|
codingPlanScnet: 'SCNet 超算互联网 Token Plan',
|
|
522
527
|
scnetPlanCreditsLabel: '月度 Credits 额度(基础 60,000 / 标准 240,000 / 高级 600,000)',
|
|
523
528
|
scnetPlanStartLabel: '订阅起始日(可选,格式 YYYY-MM-DD;留空按自然月)',
|
|
@@ -744,6 +749,8 @@ window.__ModuleLoader__.load({
|
|
|
744
749
|
codingPlanRemain5h: '5h',
|
|
745
750
|
codingPlanRemain7d: '7d',
|
|
746
751
|
enableCodingPlan: 'Enable quota queries',
|
|
752
|
+
codingPlanDisplayLabel: 'Display position',
|
|
753
|
+
codingPlanDisplayNote: 'With "Main sidebar / Both" selected, this vendor\'s quota shows as a card in the sidebar (same box style as the Go quota / balance; the collapsed rail shows percentages).',
|
|
747
754
|
codingPlanKeyLabel: 'API key (optional; empty = auto-detect)',
|
|
748
755
|
refreshCodingPlan: 'Refresh',
|
|
749
756
|
codingPlanNotQueried: 'Quota not queried',
|
|
@@ -849,6 +856,7 @@ window.__ModuleLoader__.load({
|
|
|
849
856
|
codingPlanKimi: 'Kimi / Moonshot',
|
|
850
857
|
codingPlanOpenrouter: 'OpenRouter',
|
|
851
858
|
codingPlanSiliconflow: 'SiliconFlow',
|
|
859
|
+
codingPlanCommandcode: 'CommandCode',
|
|
852
860
|
codingPlanScnet: 'SCNet Token Plan',
|
|
853
861
|
scnetPlanCreditsLabel: 'Monthly credits quota (Basic 60,000 / Standard 240,000 / Pro 600,000)',
|
|
854
862
|
scnetPlanStartLabel: 'Plan start date (optional, YYYY-MM-DD; empty = calendar month)',
|
|
@@ -2415,6 +2423,61 @@ window.__ModuleLoader__.load({
|
|
|
2415
2423
|
return el(MiniMaxPlanCard, { five, seven, fetchedAt: live.fetchedAt, t, wide })
|
|
2416
2424
|
}
|
|
2417
2425
|
|
|
2426
|
+
/** 通用 Coding Plan 侧边栏卡片(issue #31):每窗口一行进度条;文本窗口(余额等)整行文本。 */
|
|
2427
|
+
function codingPlanWindowLabel(name, t) {
|
|
2428
|
+
if (name === 'fiveHour' || name === '5h') return '5h'
|
|
2429
|
+
if (name === 'weekly' || name === 'week' || name === '7d') return name === '7d' ? '7d' : t('goShortWeekly')
|
|
2430
|
+
if (name === 'monthly' || name === 'month') return t('goShortMonthly')
|
|
2431
|
+
return String(name).replace(/_/g, ' ')
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
function CodingPlanBox(props) {
|
|
2435
|
+
const { id, state, wide } = props
|
|
2436
|
+
const live = state.codingPlans?.[id]
|
|
2437
|
+
const t = makeT(resolveLocale(state.config?.locale))
|
|
2438
|
+
const rowDef = CODING_PLAN_ROWS.find(r => r.id === id)
|
|
2439
|
+
if (!rowDef || !live || live.status !== 'ok') return null
|
|
2440
|
+
const windows = live.windows !== null && typeof live.windows === 'object' ? live.windows : {}
|
|
2441
|
+
const entries = Object.entries(windows)
|
|
2442
|
+
if (entries.length === 0) return null
|
|
2443
|
+
const pctOf = win => win !== null && typeof win === 'object' && typeof win.percent === 'number'
|
|
2444
|
+
? Math.max(0, Math.min(100, Math.round(Number(win.percent) || 0))) : null
|
|
2445
|
+
const rows = entries.map(([name, win]) => {
|
|
2446
|
+
const pct = pctOf(win)
|
|
2447
|
+
if (pct === null) {
|
|
2448
|
+
return el('div', { key: name, className: 'cm-mm-row wide' },
|
|
2449
|
+
el('span', { className: 'cm-mm-text' }, typeof win?.text === 'string' ? win.text : '—'))
|
|
2450
|
+
}
|
|
2451
|
+
const level = pct >= 100 ? 'over' : pct >= 80 ? 'warn' : 'ok'
|
|
2452
|
+
return el('div', { key: name, className: 'cm-mm-row wide' + (level === 'ok' ? '' : ' ' + level) },
|
|
2453
|
+
el('span', { className: 'cm-bbox-label' }, codingPlanWindowLabel(name, t)),
|
|
2454
|
+
el('div', { className: 'cm-bbox-bar' },
|
|
2455
|
+
el('div', { className: 'cm-bbox-fill', style: { width: pct + '%' } })),
|
|
2456
|
+
el('span', { className: 'cm-bbox-pct cm-num' }, pct + '%'))
|
|
2457
|
+
})
|
|
2458
|
+
const detailParts = entries.map(([name, win]) => {
|
|
2459
|
+
const pct = pctOf(win)
|
|
2460
|
+
const reset = typeof win?.resetsAt === 'string' && win.resetsAt.length > 0
|
|
2461
|
+
? ' · ' + t('goResetAt', { time: new Date(win.resetsAt).toLocaleString() }) : ''
|
|
2462
|
+
const value = pct === null ? (typeof win?.text === 'string' ? win.text : '—') : pct + '%'
|
|
2463
|
+
return codingPlanWindowLabel(name, t) + ' ' + value + reset
|
|
2464
|
+
})
|
|
2465
|
+
if (live.fetchedAt > 0) detailParts.push(t('goQuotaFetchedAt', { time: new Date(live.fetchedAt).toLocaleTimeString() }))
|
|
2466
|
+
const detail = [t(rowDef.labelKey), ...detailParts].join('; ')
|
|
2467
|
+
const pcts = entries.map(([, win]) => pctOf(win)).filter(p => p !== null)
|
|
2468
|
+
const level = pcts.some(p => p >= 100) ? 'over' : pcts.some(p => p >= 80) ? 'warn' : 'ok'
|
|
2469
|
+
const railText = pcts.length > 0
|
|
2470
|
+
? pcts.slice(0, 2).map(p => p + '%').join(' ')
|
|
2471
|
+
: (typeof entries[0][1]?.text === 'string' ? entries[0][1].text : '—')
|
|
2472
|
+
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
2473
|
+
el('div', { className: 'cm-bbox cm-mm' + (level === 'ok' ? '' : ' ' + level) + (wide === false ? ' rail' : '') },
|
|
2474
|
+
wide === false
|
|
2475
|
+
? el('div', { className: 'cm-bbox-rail cm-num' }, railText)
|
|
2476
|
+
: el(Fragment, null,
|
|
2477
|
+
el('div', { className: 'cm-mm-title' }, t(rowDef.labelKey)),
|
|
2478
|
+
...rows)))
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2418
2481
|
function BudgetBoxContent(props) {
|
|
2419
2482
|
const { state, wide } = props
|
|
2420
2483
|
const today = state.today
|
|
@@ -2528,20 +2591,31 @@ window.__ModuleLoader__.load({
|
|
|
2528
2591
|
const goOk = (config.goQuota?.enabled !== false)
|
|
2529
2592
|
&& (config.goQuota?.display === 'sidebar' || config.goQuota?.display === 'both')
|
|
2530
2593
|
&& state.goQuota?.status === 'ok' && state.goQuota?.[goMainKey] !== null
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
const
|
|
2534
|
-
|
|
2535
|
-
|
|
2594
|
+
// Coding Plan 侧边栏卡片(issue #31):每家按 display 门控(侧边栏/两者),启用且查询成功才展示;
|
|
2595
|
+
// MiniMax 沿用专用 5h/7d 余量卡片,其余厂商走通用 CodingPlanBox。
|
|
2596
|
+
const sidebarPlanIds = CODING_PLAN_ROWS
|
|
2597
|
+
.map(r => r.id)
|
|
2598
|
+
.filter(id => {
|
|
2599
|
+
const entry = config.codingPlans?.[id]
|
|
2600
|
+
if (entry?.enabled !== true) return false
|
|
2601
|
+
if (entry.display !== 'sidebar' && entry.display !== 'both') return false
|
|
2602
|
+
const live = state.codingPlans?.[id]
|
|
2603
|
+
if (live?.status !== 'ok') return false
|
|
2604
|
+
const wins = live.windows !== null && typeof live.windows === 'object' ? live.windows : {}
|
|
2605
|
+
return Object.keys(wins).length > 0
|
|
2606
|
+
})
|
|
2607
|
+
const plansOn = sidebarPlanIds.length > 0
|
|
2536
2608
|
const budgetOn = (config.budget ?? {}).enabled === true
|
|
2537
2609
|
const showToday = config.sidebar !== false
|
|
2538
|
-
if (!showBalance && !showCustomBalance && !goOk && !
|
|
2610
|
+
if (!showBalance && !showCustomBalance && !goOk && !plansOn && !budgetOn && !showToday) return null
|
|
2539
2611
|
const nodes = []
|
|
2540
2612
|
if (showBalanceBar) nodes.push(el(BalanceBox, { state, wide }))
|
|
2541
2613
|
else if (showBalance) nodes.push(el(BalanceRowContent, { state, wide }))
|
|
2542
2614
|
if (showCustomBalanceBar) nodes.push(el(CustomBalanceBox, { state, wide }))
|
|
2543
2615
|
else if (showCustomBalance) nodes.push(el(CustomBalanceRowContent, { state, wide }))
|
|
2544
|
-
if (
|
|
2616
|
+
if (plansOn) nodes.push(...sidebarPlanIds.map(id => id === 'minimax'
|
|
2617
|
+
? el(MiniMaxPlanBox, { state, wide })
|
|
2618
|
+
: el(CodingPlanBox, { id, state, wide })))
|
|
2545
2619
|
if (goOk && budgetOn && wide) {
|
|
2546
2620
|
// 同时出现:合并为一张卡片(Go 在上、预算在下,细分隔线),各自保留预警色与自己的详细信息开关。
|
|
2547
2621
|
const goView = goBoxBody(state, config, t)
|
|
@@ -3676,6 +3750,7 @@ window.__ModuleLoader__.load({
|
|
|
3676
3750
|
{ id: 'kimi', labelKey: 'codingPlanKimi' },
|
|
3677
3751
|
{ id: 'openrouter', labelKey: 'codingPlanOpenrouter' },
|
|
3678
3752
|
{ id: 'siliconflow', labelKey: 'codingPlanSiliconflow' },
|
|
3753
|
+
{ id: 'commandcode', labelKey: 'codingPlanCommandcode' },
|
|
3679
3754
|
{ id: 'scnet', labelKey: 'codingPlanScnet' },
|
|
3680
3755
|
]
|
|
3681
3756
|
|
|
@@ -3776,6 +3851,18 @@ window.__ModuleLoader__.load({
|
|
|
3776
3851
|
}),
|
|
3777
3852
|
el('span', null, t('enableCodingPlan'))),
|
|
3778
3853
|
el('button', { className: 'cm-btn small', onClick: () => { void doRefresh(id) }, disabled: busyId !== null || enabled === false }, busyId === id ? t('refreshing') : t('refreshCodingPlan'))),
|
|
3854
|
+
enabled ? el('div', { className: 'cm-field' },
|
|
3855
|
+
el('label', null, t('codingPlanDisplayLabel')),
|
|
3856
|
+
el('select', {
|
|
3857
|
+
className: 'cm-input',
|
|
3858
|
+
value: typeof cfgEntry.display === 'string' ? cfgEntry.display : (id === 'minimax' ? 'both' : 'settings'),
|
|
3859
|
+
onChange: event => setPlan(id, 'display', event.target.value),
|
|
3860
|
+
},
|
|
3861
|
+
el('option', { value: 'sidebar' }, t('balanceSidebar')),
|
|
3862
|
+
el('option', { value: 'settings' }, t('balanceSettings')),
|
|
3863
|
+
el('option', { value: 'both' }, t('balanceBoth')),
|
|
3864
|
+
el('option', { value: 'off' }, t('off'))),
|
|
3865
|
+
el('span', { className: 'cm-hint' }, t('codingPlanDisplayNote'))) : null,
|
|
3779
3866
|
enabled ? (id === 'scnet'
|
|
3780
3867
|
? el(Fragment, null,
|
|
3781
3868
|
el('div', { className: 'cm-field' },
|
package/lib/coding-plans.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* Kimi Code 订阅周窗/5小时窗暂无 API-Key 化公开端点(仅 kimi.com 控制台),以余额窗口接入;
|
|
16
16
|
* - OpenRouter credits 端点 openrouter.ai/api/v1/credits 存活(401,官方文档明确);
|
|
17
17
|
* - SiliconFlow 用户信息端点 api.siliconflow.cn/v1/user/info 存活(30014 Token is invalid);
|
|
18
|
+
* - CommandCode(commandcode.ai)billing credits 端点存活(401 unauthorized;issue #30):
|
|
19
|
+
* GET api.commandcode.ai/alpha/billing/credits,窗口(fiveHour/weekly)+ 月度 Credits 余额;
|
|
18
20
|
* - SCNet(超算互联网)Token Plan 仅有 sk-tp- 专属推理端点(api.scnet.cn),额度用量只在控制台
|
|
19
21
|
* 「模型服务 → Token Plan → 我的订阅/Token 用量」可见,无 API-Key 化查询端点——以本地
|
|
20
22
|
* Credits 计量接入(官方抵扣表折算,见 SCNET_CREDIT_RATES;issue #26);
|
|
@@ -54,6 +56,11 @@ export const CODING_PLAN_PROVIDERS = {
|
|
|
54
56
|
credentialEnvs: ['SILICONFLOW_API_KEY'],
|
|
55
57
|
keyHint: 'SiliconFlow API Key(sk-*;显示账户余额)',
|
|
56
58
|
},
|
|
59
|
+
commandcode: {
|
|
60
|
+
label: 'CommandCode',
|
|
61
|
+
credentialEnvs: ['COMMANDCODE_API_KEY'],
|
|
62
|
+
keyHint: 'commandcode.ai API Key(user_*;显示 5 小时/周窗口用量% 与月度 Credits 余额)',
|
|
63
|
+
},
|
|
57
64
|
scnet: {
|
|
58
65
|
label: 'SCNet 超算互联网 Token Plan',
|
|
59
66
|
credentialEnvs: [],
|
|
@@ -348,6 +355,35 @@ export function parseSiliconFlowInfo(data) {
|
|
|
348
355
|
return win === null ? null : { balance: win }
|
|
349
356
|
}
|
|
350
357
|
|
|
358
|
+
/**
|
|
359
|
+
* 解析 CommandCode(commandcode.ai)额度响应(issue #30,
|
|
360
|
+
* GET https://api.commandcode.ai/alpha/billing/credits)。
|
|
361
|
+
* 官方返回 { credits: { monthlyCredits, ... }, windowLimits: { fiveHour: { used, cap, resetAt },
|
|
362
|
+
* weekly: {...} } }:窗口按 used/cap 输出已用%(resetAt 为 epoch 毫秒);月度 Credits 为
|
|
363
|
+
* 余额池(1 credit ≈ $1 用量,无总量字段),以文本窗口展示(与 Kimi/SiliconFlow 余额同形态)。
|
|
364
|
+
*/
|
|
365
|
+
export function parseCommandCodeCredits(data) {
|
|
366
|
+
if (data === null || typeof data !== 'object') return null
|
|
367
|
+
const windows = {}
|
|
368
|
+
const limits = data.windowLimits
|
|
369
|
+
if (limits !== null && typeof limits === 'object' && !Array.isArray(limits)) {
|
|
370
|
+
for (const [name, raw] of Object.entries(limits)) {
|
|
371
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) continue
|
|
372
|
+
const used = Number(raw.used)
|
|
373
|
+
const cap = Number(raw.cap)
|
|
374
|
+
if (!Number.isFinite(used) || !Number.isFinite(cap) || cap <= 0 || used < 0) continue
|
|
375
|
+
windows[name] = { percent: clampPct((used / cap) * 100), resetsAt: normalizeResetAt(raw.resetAt) }
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const credits = data.credits
|
|
379
|
+
const monthly = Number(credits !== null && typeof credits === 'object' ? credits.monthlyCredits : undefined)
|
|
380
|
+
if (Number.isFinite(monthly) && monthly >= 0) {
|
|
381
|
+
const win = textWindowOf('余额 $' + (Math.round(monthly * 100) / 100).toFixed(2))
|
|
382
|
+
if (win !== null) windows.monthly = win
|
|
383
|
+
}
|
|
384
|
+
return Object.keys(windows).length > 0 ? windows : null
|
|
385
|
+
}
|
|
386
|
+
|
|
351
387
|
// ── SCNet(超算互联网)Token Plan 本地 Credits 计量(issue #26)──────────────
|
|
352
388
|
//
|
|
353
389
|
// SCNet Token Plan 为 Credits 包月订阅(基础 60,000 / 标准 240,000 / 高级 600,000),
|
|
@@ -491,6 +527,7 @@ export const CODING_PLAN_ENDPOINTS = {
|
|
|
491
527
|
kimi: ['https://api.moonshot.cn/v1/users/me/balance'],
|
|
492
528
|
openrouter: ['https://openrouter.ai/api/v1/credits'],
|
|
493
529
|
siliconflow: ['https://api.siliconflow.cn/v1/user/info'],
|
|
530
|
+
commandcode: ['https://api.commandcode.ai/alpha/billing/credits'],
|
|
494
531
|
// SCNet 无 API-Key 化额度端点:本地 Credits 计量(见 scnetTokenPlanWindows),不走网络。
|
|
495
532
|
scnet: [],
|
|
496
533
|
}
|
|
@@ -502,6 +539,7 @@ const CODING_PLAN_PARSERS = {
|
|
|
502
539
|
kimi: parseKimiBalance,
|
|
503
540
|
openrouter: parseOpenRouterCredits,
|
|
504
541
|
siliconflow: parseSiliconFlowInfo,
|
|
542
|
+
commandcode: parseCommandCodeCredits,
|
|
505
543
|
}
|
|
506
544
|
|
|
507
545
|
/**
|
package/lib/store.js
CHANGED
|
@@ -72,7 +72,13 @@ export function defaultConfig() {
|
|
|
72
72
|
// 各家 coding plan 额度查询(默认关闭;开启后按 Key 发现链查询并在设置页展示)。
|
|
73
73
|
anthropic: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
74
74
|
zai: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
75
|
-
|
|
75
|
+
// MiniMax 自引入起即是「启用即在侧边栏展示 5h/7d 卡片」,显示位置配置落地后默认 both 保持该惯例;
|
|
76
|
+
// 其余厂商默认 settings(仅设置页),按需在设置页切换(issue #31)。
|
|
77
|
+
minimax: { enabled: false, display: 'both', refreshMinutes: 15, apiKey: '' },
|
|
78
|
+
kimi: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
79
|
+
openrouter: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
80
|
+
siliconflow: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
81
|
+
commandcode: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
76
82
|
// SCNet Token Plan 无 API 额度端点:按官方 Credits 抵扣表本地估算(issue #26)。
|
|
77
83
|
// planCredits=月度 Credits 额度;planStart=订阅起始日(空 = 自然月)。
|
|
78
84
|
scnet: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '', planCredits: 240000, planStart: '' },
|
|
@@ -726,6 +732,23 @@ export function sanitizeConfig(raw) {
|
|
|
726
732
|
return out
|
|
727
733
|
}
|
|
728
734
|
|
|
735
|
+
/**
|
|
736
|
+
* 一次性配置迁移(账本根 migrations 标记,已应用过的不再重跑,因此不会覆盖用户后续的显式选择)。
|
|
737
|
+
* 只处理「旧版本无对应配置项、行为与新版本语义不符」的场景;迁移在 sanitizeConfig 之前对原始配置执行。
|
|
738
|
+
*/
|
|
739
|
+
const CONFIG_MIGRATIONS = [
|
|
740
|
+
{
|
|
741
|
+
id: 'v1.5.26-coding-plan-sidebar-display',
|
|
742
|
+
// v1.5.26 前 coding plan 无显示位置 UI:MiniMax 启用即在侧边栏展示卡片,而 schema 恒把 display
|
|
743
|
+
// 清洗为默认 'settings'(当时无 UI 可改,存量值必为 schema 默认而非用户选择)。迁移为 'both'
|
|
744
|
+
// 保持旧版「启用 = 侧边栏 + 设置页」的实际行为;其余厂商旧行为本就是仅设置页,无需迁移。
|
|
745
|
+
apply(cfg) {
|
|
746
|
+
const mm = cfg?.codingPlans?.minimax
|
|
747
|
+
if (mm !== null && typeof mm === 'object' && !Array.isArray(mm) && mm.enabled === true) mm.display = 'both'
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
]
|
|
751
|
+
|
|
729
752
|
/**
|
|
730
753
|
* 账本状态容器。所有聚合写内存,持久化走防抖原子写。
|
|
731
754
|
*/
|
|
@@ -734,8 +757,9 @@ export class Ledger {
|
|
|
734
757
|
* @param config - 初始配置(默认值或已持久化配置)。
|
|
735
758
|
* @param days - 已持久化的每日记录对象(date → day)。
|
|
736
759
|
* @param path - 账本文件路径。
|
|
760
|
+
* @param migrations - 已应用的一次性迁移 id 列表(随账本落盘)。
|
|
737
761
|
*/
|
|
738
|
-
constructor(config, days, path) {
|
|
762
|
+
constructor(config, days, path, migrations = []) {
|
|
739
763
|
this.config = config
|
|
740
764
|
this.days = days
|
|
741
765
|
this.path = path
|
|
@@ -744,6 +768,7 @@ export class Ledger {
|
|
|
744
768
|
this.pendingWrite = false
|
|
745
769
|
// 余额差对账参考点({ date, total, granted, topped, at }),由 open() 载入、flush() 落盘。
|
|
746
770
|
this.balanceRef = null
|
|
771
|
+
this.migrations = Array.isArray(migrations) ? migrations.filter(m => typeof m === 'string') : []
|
|
747
772
|
}
|
|
748
773
|
|
|
749
774
|
/** 在 $DSH_HOME 下创建/加载账本。 */
|
|
@@ -753,6 +778,7 @@ export class Ledger {
|
|
|
753
778
|
let config = defaultConfig()
|
|
754
779
|
let days = {}
|
|
755
780
|
let balanceRef = null
|
|
781
|
+
let migrations = []
|
|
756
782
|
try {
|
|
757
783
|
const parsed = JSON.parse(readFileSync(path, 'utf8'))
|
|
758
784
|
if (parsed !== null && typeof parsed === 'object') {
|
|
@@ -760,6 +786,14 @@ export class Ledger {
|
|
|
760
786
|
console.warn(`[dsh-cost-meter] 账本版本 ${String(parsed.version)} 不受支持,按空账本启动`)
|
|
761
787
|
} else {
|
|
762
788
|
const cfg = typeof parsed.config === 'object' && parsed.config !== null ? parsed.config : {}
|
|
789
|
+
// 一次性迁移:先于清洗对原始配置执行,已应用过的(账本 migrations 标记)跳过。
|
|
790
|
+
migrations = Array.isArray(parsed.migrations) ? parsed.migrations.filter(m => typeof m === 'string') : []
|
|
791
|
+
const applied = new Set(migrations)
|
|
792
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
793
|
+
if (applied.has(migration.id)) continue
|
|
794
|
+
migration.apply(cfg)
|
|
795
|
+
migrations.push(migration.id)
|
|
796
|
+
}
|
|
763
797
|
// 新版本新增的配置键用默认值补齐;非法值清洗回落,防止击穿 strict codec。
|
|
764
798
|
config = sanitizeConfig(cfg)
|
|
765
799
|
if (parsed.days !== null && typeof parsed.days === 'object' && !Array.isArray(parsed.days)) {
|
|
@@ -781,7 +815,7 @@ export class Ledger {
|
|
|
781
815
|
console.warn(`[dsh-cost-meter] 账本读取失败,按空账本启动: ${String(error?.message ?? error)}`)
|
|
782
816
|
}
|
|
783
817
|
}
|
|
784
|
-
const ledger = new Ledger(config, days, path)
|
|
818
|
+
const ledger = new Ledger(config, days, path, migrations)
|
|
785
819
|
ledger.balanceRef = balanceRef
|
|
786
820
|
return ledger
|
|
787
821
|
}
|
|
@@ -899,7 +933,7 @@ export class Ledger {
|
|
|
899
933
|
try {
|
|
900
934
|
mkdirSync(dirname(this.path), { recursive: true })
|
|
901
935
|
const tmp = `${this.path}.tmp`
|
|
902
|
-
writeFileSync(tmp, JSON.stringify({ version: LEDGER_VERSION, config: this.config, days: this.days, balanceRef: this.balanceRef ?? null }), 'utf8')
|
|
936
|
+
writeFileSync(tmp, JSON.stringify({ version: LEDGER_VERSION, config: this.config, days: this.days, balanceRef: this.balanceRef ?? null, migrations: this.migrations }), 'utf8')
|
|
903
937
|
renameSync(tmp, this.path)
|
|
904
938
|
} catch (error) {
|
|
905
939
|
console.warn(`[dsh-cost-meter] 账本写入失败: ${String(error?.message ?? error)}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cost-meter",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.26",
|
|
4
4
|
"description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(7 家,含 SCNet Token Plan 本地 Credits 计量)、自定义 Provider 余额查询(可配任意 HTTP 端点)与余额进度条、峰谷计价时段显示与切换前弹窗/系统通知提醒(位置/提前量/类型可配),界面中英双语。Session cost tracking plugin for DeepSeek Harness: per-conversation cost, daily totals, history and official price sync, with multi-vendor model pricing (built-in 90+ model catalog with auto-matching), Coding Plan quota queries & display for 7 vendors (incl. local credits metering for the SCNet Token Plan), custom provider balance lookup (configurable HTTP endpoint) and balance progress bar, plus peak/off-peak pricing display with pre-switch popup & system-notification alerts (position / lead time / type configurable), in a bilingual (Chinese/English) UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|