deepseek-harness-wallet 0.2.5 → 0.2.6
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/CHANGELOG.md +5 -0
- package/README.md +18 -8
- package/index.js +15 -2
- package/lib/client.js +56 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.2.6 - 2026-08-23
|
|
6
|
+
|
|
7
|
+
- 跟随 DeepSeek 官方规则更新峰谷计费:自 2026-08-23 北京时间 00:00 起,周六、周日全天按低谷价计费;工作日高峰仍为 09:00–12:00、14:00–18:00。 / Updated peak/off-peak billing to match DeepSeek's official rule: from 2026-08-23 00:00 Beijing, Saturday and Sunday are all-day off-peak; weekday peak windows remain 09:00–12:00 and 14:00–18:00.
|
|
8
|
+
- 环形卡片同步显示周末全天低谷,并将倒计时指向下一个工作日 09:00。 / Updated the ring card to show weekend all-day off-peak and count down to the next weekday 09:00.
|
|
9
|
+
|
|
5
10
|
## 0.2.5 - 2026-08-23
|
|
6
11
|
|
|
7
12
|
- 修复浮动峰谷卡片在窗口缩放、卡片放大或历史坐标过期后可能跑出视口的问题;加载、缩放、排版切换和 resize 都会重新钳制位置。 / Fixed floating peak cards escaping the viewport after resize, scale changes, or stale saved coordinates; load, scale, layout changes, and resize now re-clamp the position.
|
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
余额 ¥5.89 · 本场 ¥0.72 · 官 18.8M | 三方 800K · ↗充
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
- **Official DeepSeek** — live balance (60s global refresh with fast boot retries), current-session cost locked to the price active for each usage event
|
|
26
|
-
- **
|
|
25
|
+
- **Official DeepSeek** — live balance (60s global refresh with fast boot retries), an estimated current-session cost (not an official bill) locked to the price active for each usage event, including the 2026-08-17 peak/off-peak rollout, and token breakdown.
|
|
26
|
+
- **v4 peak/off-peak ring clock** — a resident 24-hour sidebar footer widget for `v4-flash` and `v4-pro`, indicating real-time pricing windows (peak vs. 50% discount off-peak), countdown to the next switch, and optional desktop switch notifications. Weekdays use 09:00–12:00 and 14:00–18:00 Beijing time; Saturday and Sunday are all-day off-peak from 2026-08-23. Other models keep their flat rates.
|
|
27
27
|
- **Third-party total** — current-session tokens (input / cache read / output). No balance guessing, no cost math, zero configuration.
|
|
28
28
|
- **Provider classification** — observed wrapper routes appear in the settings page; opted-in routes join the official token/cost bucket and are priced with the official table.
|
|
29
29
|
- **Click the chip** to open the detail panel: correctly formatted per-currency balances, cost and token splits, a freely editable low-balance threshold for the active account and currency (two decimals, persisted per account; alerts never mix currencies), manual refresh, and a jump to the official recharge page (first click shows the domain for confirmation — anti-phishing).
|
|
@@ -34,13 +34,14 @@
|
|
|
34
34
|
- **Low-balance alert** — below the threshold the chip turns red with a breathing animation and fires one desktop notification; it resets automatically once the balance recovers.
|
|
35
35
|
- **Theme-native UI** — built entirely on `--dsw-alias-*` theme variables, so light and dark themes both render correctly; the panel closes when you click outside and flips open-direction near screen edges.
|
|
36
36
|
- **Clear current-session wallet data** — one button clears only the open conversation's token/cost records; it does not delete the conversation, and every other conversation is untouched.
|
|
37
|
-
|
|
38
37
|
|
|
39
|
-
-
|
|
38
|
+
## Multi-account
|
|
39
|
+
|
|
40
|
+
- Open the wallet panel → **Account Management(账户管理)** to add accounts (name + API key), switch the active one, or remove them.
|
|
40
41
|
- The first account added becomes the active account automatically and is synced into the credentials seam.
|
|
41
42
|
- Switching prompts a confirmation because it changes **LLM billing** for subsequent requests: the switch writes the account key into the credentials seam (`credentials.set('DEEPSEEK_API_KEY', ...)`), and since the llm-deepseek provider route resolves that reference per request, the very next LLM call is billed with the new account — no restart needed.
|
|
42
|
-
- Account keys are stored plaintext in `$DSH_HOME/storages/accounts.json`; the UI only
|
|
43
|
-
- Session
|
|
43
|
+
- Account keys are stored in plaintext in `$DSH_HOME/storages/accounts.json`; the UI only shows masked keys. Treat the DSH data directory as a local credential store and protect access to it. Balance lookups prefer the active account's key and fall back to the credentials seam when no account is active.
|
|
44
|
+
- Session usage estimates follow the active account's currency: USD-settled accounts show `本约 $x`, converted from the CNY price table at the vendor's long-standing list ratio (not a live FX rate); CNY accounts show `本场 ¥x`. These are local estimates, not an official invoice.
|
|
44
45
|
- If `DEEPSEEK_API_KEY` is supplied by the launching environment, switching is refused with a clear error (the credentials provider rejects shadowed writes) — unset it in your shell to enable switching.
|
|
45
46
|
|
|
46
47
|
|
|
@@ -78,6 +79,14 @@ dsh plugin --profile web add github:feibi-mochi/deepseek-harness-control-center
|
|
|
78
79
|
|
|
79
80
|
Restart `dsh web`, then hard-refresh the page.
|
|
80
81
|
|
|
82
|
+
## Quick use
|
|
83
|
+
|
|
84
|
+
1. Click the wallet or peak/off-peak card to open its control panel.
|
|
85
|
+
2. Choose horizontal or vertical layout, then use the scale control between 100% and 120%.
|
|
86
|
+
3. Turn the official recharge button off when you need a smaller card; official and third-party rows can also be shown independently.
|
|
87
|
+
4. Drag the card to any open area. If it is hard to find after a layout change, use **Reset/Dock(归位/停靠)** in the panel to return it to the sidebar.
|
|
88
|
+
5. The card follows the host light/dark theme. A hard refresh after upgrading makes sure the new client bundle is loaded.
|
|
89
|
+
|
|
81
90
|
### Update
|
|
82
91
|
|
|
83
92
|
```sh
|
|
@@ -129,8 +138,8 @@ For buildable DSH hosts, the npm package and repository include a versioned [Age
|
|
|
129
138
|
| Item | Behavior |
|
|
130
139
|
| --- | --- |
|
|
131
140
|
| Token accounting | Listens to the `llm/stream` event and buckets per session and provider: `deepseek-official` plus explicitly opted-in wrapper routes use the official bucket; other providers stay third-party; each usage event also locks its contemporaneous official price, so multiple sessions and pricing windows never mix. |
|
|
132
|
-
| Balance | The
|
|
133
|
-
| Accounts | Keys live in `$DSH_HOME/storages/accounts.json`
|
|
141
|
+
| Balance | The wallet plugin itself sends the active key directly only to the official `/user/balance` endpoint. When multi-account switching is enabled, the selected key is also written into the DSH credentials seam; DSH may then use it for subsequent model requests. |
|
|
142
|
+
| Accounts | Keys live in `$DSH_HOME/storages/accounts.json` in plaintext, matching the harness's own credential storage. The UI only shows masked keys, and switching writes the chosen key into the credentials seam for subsequent LLM billing. Protect the DSH data directory as you would any local credential store. |
|
|
134
143
|
| Session log | The plugin writes no events; its data lives in `$DSH_HOME/storages/wallet.json`. |
|
|
135
144
|
| Local settings | Layout, scale, visibility, reminder, and panel settings stay in browser-compatible local storage. |
|
|
136
145
|
| Permanent deletion | Opt-in and host-gated. The wallet never advertises the action unless the host implements the matching session deletion path. |
|
|
@@ -146,6 +155,7 @@ CNY per 1M tokens, curated from official announcements (cache writes are not bil
|
|
|
146
155
|
- Since 2026-08-17 00:00 Beijing — peak/off-peak pricing for the v4 models (peak windows Beijing 09:00–12:00 / 14:00–18:00; off-peak is half the peak rate):
|
|
147
156
|
- v4-flash (off-peak / peak): cache read 0.05 / 0.10, input 1.5 / 3, output 4.5 / 9
|
|
148
157
|
- v4-pro (off-peak / peak): cache read 0.15 / 0.30, input 4.5 / 9, output 13.5 / 27
|
|
158
|
+
- Since 2026-08-23 00:00 Beijing — Saturday and Sunday are no longer split into peak/off-peak windows; weekend calls use the off-peak rates all day. Weekday peak windows remain 09:00–12:00 and 14:00–18:00.
|
|
149
159
|
|
|
150
160
|
deepseek-chat and deepseek-reasoner keep their flat rates. Each usage event is priced when it arrives; upgrading from 0.1.2 migrates legacy counters once using the then-current rate. Costs are estimates; the API-returned balance is authoritative.
|
|
151
161
|
|
package/index.js
CHANGED
|
@@ -35,14 +35,24 @@ const DEFAULT_THRESHOLD = 5
|
|
|
35
35
|
const BALANCE_REFRESH_MS = 60_000
|
|
36
36
|
const STORE_VERSION = 2
|
|
37
37
|
|
|
38
|
-
// Beijing (UTC+8, no DST) peak windows: 09:00-12:00 and 14:00-18:00.
|
|
38
|
+
// Beijing (UTC+8, no DST) weekday peak windows: 09:00-12:00 and 14:00-18:00.
|
|
39
|
+
// From 2026-08-23 00:00 Beijing, Saturday and Sunday are off-peak all day.
|
|
39
40
|
// Exposed to clients via snapshot.pricingWindows so the ring clock renders
|
|
40
41
|
// from the active policy instead of hard-coding hours in the bundle.
|
|
41
42
|
const PEAK_WINDOWS = [{ startHour: 9, endHour: 12 }, { startHour: 14, endHour: 18 }]
|
|
42
43
|
const OFF_PEAK_RATE = 0.5
|
|
43
44
|
const BEIJING_OFFSET_MS = 8 * 3600_000
|
|
45
|
+
const WEEKEND_OFF_PEAK_SINCE = Date.UTC(2026, 7, 22, 16)
|
|
46
|
+
|
|
47
|
+
function isBeijingWeekend(atMs) {
|
|
48
|
+
if (!Number.isFinite(atMs)) return false
|
|
49
|
+
const beijingDate = new Date(atMs + BEIJING_OFFSET_MS)
|
|
50
|
+
const day = beijingDate.getUTCDay()
|
|
51
|
+
return day === 0 || day === 6
|
|
52
|
+
}
|
|
44
53
|
|
|
45
54
|
export function isBeijingPeak(atMs) {
|
|
55
|
+
if (atMs >= WEEKEND_OFF_PEAK_SINCE && isBeijingWeekend(atMs)) return false
|
|
46
56
|
const hours = ((atMs + BEIJING_OFFSET_MS) % 86_400_000) / 3_600_000
|
|
47
57
|
return (hours >= 9 && hours < 12) || (hours >= 14 && hours < 18)
|
|
48
58
|
}
|
|
@@ -661,6 +671,7 @@ function sessionView(sessionId) {
|
|
|
661
671
|
function snapshotView(sessionId) {
|
|
662
672
|
const currency = balanceCurrency(balance.balances)
|
|
663
673
|
const active = activeAccount()
|
|
674
|
+
const now = Date.now()
|
|
664
675
|
return {
|
|
665
676
|
ok: true,
|
|
666
677
|
balance: {
|
|
@@ -692,7 +703,9 @@ function snapshotView(sessionId) {
|
|
|
692
703
|
offsetMinutes: 480,
|
|
693
704
|
windows: PEAK_WINDOWS.map(w => ({ startHour: w.startHour, endHour: w.endHour })),
|
|
694
705
|
offPeakRate: OFF_PEAK_RATE,
|
|
695
|
-
isPeak: isBeijingPeak(
|
|
706
|
+
isPeak: isBeijingPeak(now),
|
|
707
|
+
weekendOffPeak: now >= WEEKEND_OFF_PEAK_SINCE && isBeijingWeekend(now),
|
|
708
|
+
weekendOffPeakSince: WEEKEND_OFF_PEAK_SINCE,
|
|
696
709
|
},
|
|
697
710
|
accounts: {
|
|
698
711
|
activeId: accounts.activeId,
|
package/lib/client.js
CHANGED
|
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
|
|
24
24
|
var POLL_MS = 15000
|
|
25
25
|
// Keep in lockstep with package.json; a test enforces the sync.
|
|
26
|
-
var WALLET_VERSION = '0.2.
|
|
26
|
+
var WALLET_VERSION = '0.2.6'
|
|
27
27
|
var CONFIRM_KEY = 'dsh-wallet-recharge-confirmed'
|
|
28
28
|
var CHIP_LAYOUT_KEY = 'dshw-chip-layout-v4'
|
|
29
29
|
var PANEL_POS_KEY = 'dshw-panel-pos-v1'
|
|
@@ -552,7 +552,7 @@ window.__ModuleLoader__.load({
|
|
|
552
552
|
// Noon = 0h at 12 o'clock, clockwise. Optional size scales the 76-unit
|
|
553
553
|
// viewBox down for tight hosts (sidebar foot row / rail circle); optional
|
|
554
554
|
// ariaText overrides the fallback label with the full screen-reader state.
|
|
555
|
-
function peakRingSVG(windows, nowHour, size, ariaText) {
|
|
555
|
+
function peakRingSVG(windows, nowHour, size, ariaText, allOffPeak) {
|
|
556
556
|
var sizePx = typeof size === 'number' && size > 0 ? size : 76
|
|
557
557
|
var R = 28
|
|
558
558
|
var CX = 38
|
|
@@ -561,6 +561,18 @@ window.__ModuleLoader__.load({
|
|
|
561
561
|
// No pricing policy resolved: one neutral full ring, no arcs, no pointer
|
|
562
562
|
// — the clock shows an unconfigured state and claims no price.
|
|
563
563
|
if (!Array.isArray(windows) || windows.length === 0) {
|
|
564
|
+
if (allOffPeak === true) {
|
|
565
|
+
return React.createElement('svg', {
|
|
566
|
+
width: sizePx, height: sizePx, viewBox: '0 0 76 76',
|
|
567
|
+
role: 'img', className: 'dshw_peakRing',
|
|
568
|
+
'aria-label': ariaText || '当前为周末低谷时段,全天半价'
|
|
569
|
+
},
|
|
570
|
+
React.createElement('circle', {
|
|
571
|
+
key: 'weekend-off', cx: CX, cy: CY, r: R, fill: 'none',
|
|
572
|
+
className: 'dshw_ringOff dshw_ringNow', strokeWidth: 6.5,
|
|
573
|
+
strokeDasharray: CIRC.toFixed(2) + ' 0', strokeDashoffset: '0'
|
|
574
|
+
}))
|
|
575
|
+
}
|
|
564
576
|
return React.createElement('svg', {
|
|
565
577
|
width: sizePx, height: sizePx, viewBox: '0 0 76 76',
|
|
566
578
|
role: 'img', className: 'dshw_peakRing',
|
|
@@ -1722,8 +1734,47 @@ window.__ModuleLoader__.load({
|
|
|
1722
1734
|
var windows = policy && Array.isArray(policy.windows) ? policy.windows.filter(function (w) {
|
|
1723
1735
|
return w && Number.isFinite(w.startHour) && Number.isFinite(w.endHour) && w.endHour > w.startHour
|
|
1724
1736
|
}) : []
|
|
1737
|
+
var weekendOffPeak = policy && policy.weekendOffPeak === true
|
|
1738
|
+
if (weekendOffPeak) {
|
|
1739
|
+
var offsetMinutes = policy && Number.isFinite(policy.offsetMinutes) ? policy.offsetMinutes : 480
|
|
1740
|
+
var beijingNow = Number.isFinite(nowMs) ? new Date(nowMs + offsetMinutes * 60000) : null
|
|
1741
|
+
var day = beijingNow && Number.isFinite(beijingNow.getTime()) ? beijingNow.getUTCDay() : 6
|
|
1742
|
+
var daysUntilMonday = day === 6 ? 2 : 1
|
|
1743
|
+
var nextSwitchMs = beijingNow && Number.isFinite(beijingNow.getTime())
|
|
1744
|
+
? Date.UTC(
|
|
1745
|
+
beijingNow.getUTCFullYear(), beijingNow.getUTCMonth(), beijingNow.getUTCDate() + daysUntilMonday,
|
|
1746
|
+
9 - (offsetMinutes / 60), 0, 0, 0
|
|
1747
|
+
) - offsetMinutes * 60000
|
|
1748
|
+
: NaN
|
|
1749
|
+
var weekendMsLeft = Number.isFinite(nextSwitchMs) && Number.isFinite(nowMs)
|
|
1750
|
+
? Math.max(0, nextSwitchMs - nowMs)
|
|
1751
|
+
: 0
|
|
1752
|
+
var weekendHours = Math.floor(weekendMsLeft / 3600000)
|
|
1753
|
+
var weekendMinutes = Math.floor((weekendMsLeft % 3600000) / 60000)
|
|
1754
|
+
var weekendLeftText = weekendHours > 0
|
|
1755
|
+
? weekendHours + ' 小时 ' + weekendMinutes + ' 分'
|
|
1756
|
+
: weekendMinutes + ' 分钟'
|
|
1757
|
+
var weekendLeftShort = weekendHours > 0
|
|
1758
|
+
? weekendHours + 'h' + (weekendMinutes > 0 ? weekendMinutes + 'm' : '')
|
|
1759
|
+
: weekendMinutes + 'm'
|
|
1760
|
+
var weekendDateKey = beijingNow && Number.isFinite(beijingNow.getTime())
|
|
1761
|
+
? [beijingNow.getUTCFullYear(), String(beijingNow.getUTCMonth() + 1).padStart(2, '0'), String(beijingNow.getUTCDate()).padStart(2, '0')].join('-')
|
|
1762
|
+
: 'weekend'
|
|
1763
|
+
var weekendTzName = policy.timezone || 'Asia/Shanghai'
|
|
1764
|
+
return {
|
|
1765
|
+
configured: true, windows: [], weekendOffPeak: true, inPeak: false,
|
|
1766
|
+
nextHour: 9,
|
|
1767
|
+
ariaText: '当前为周末低谷时段,全天按半价计费,周一 09:00 恢复标准价',
|
|
1768
|
+
tip: '峰谷时钟 · 当前周末低谷 · 周一 09:00 恢复标准价 · 还有 ' + weekendLeftText + ' · ' + weekendTzName,
|
|
1769
|
+
periodName: '周末低谷', rateBadge: '半价',
|
|
1770
|
+
countdownSummary: '周一 09:00 切换 · 剩 ' + weekendLeftShort,
|
|
1771
|
+
windowSummary: '周末全天低谷 · 工作日高峰 09:00–12:00 / 14:00–18:00',
|
|
1772
|
+
periodId: 'weekend-off-' + weekendDateKey,
|
|
1773
|
+
switchBody: '周末低谷结束,已进入工作日高峰时段,按标准价计费',
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1725
1776
|
if (windows.length === 0) {
|
|
1726
|
-
return { configured: false, windows: [], ariaText: '峰谷计费时段未配置', tip: '峰谷时钟 · 计费时段未配置', periodId: null }
|
|
1777
|
+
return { configured: false, windows: [], weekendOffPeak: false, ariaText: '峰谷计费时段未配置', tip: '峰谷时钟 · 计费时段未配置', periodId: null }
|
|
1727
1778
|
}
|
|
1728
1779
|
var inPeak = false
|
|
1729
1780
|
var segStart = null
|
|
@@ -1786,7 +1837,7 @@ window.__ModuleLoader__.load({
|
|
|
1786
1837
|
var tip = '峰谷时钟 · 当前' + period + ' · ' + switchText + ' · 还有 ' + leftText
|
|
1787
1838
|
+ ' · 高峰 ' + winText + '(' + tzName + ')' + localNote
|
|
1788
1839
|
return {
|
|
1789
|
-
configured: true, windows: windows, inPeak: inPeak,
|
|
1840
|
+
configured: true, windows: windows, weekendOffPeak: false, inPeak: inPeak,
|
|
1790
1841
|
nextHour: nextHour, ariaText: ariaText, tip: tip,
|
|
1791
1842
|
periodName: periodName, rateBadge: rateBadge,
|
|
1792
1843
|
countdownSummary: countdownSummary, windowSummary: windowSummary,
|
|
@@ -2240,7 +2291,7 @@ window.__ModuleLoader__.load({
|
|
|
2240
2291
|
onClick: handleResetDock
|
|
2241
2292
|
}, '↩ 归位') : null,
|
|
2242
2293
|
React.createElement('div', { style: { flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center' } },
|
|
2243
|
-
peakRingSVG(state.configured ? state.windows : null, wallHourIn(tzName, offsetMinutes, new Date(nowMs)), clockSize, state.ariaText)
|
|
2294
|
+
peakRingSVG(state.configured ? state.windows : null, wallHourIn(tzName, offsetMinutes, new Date(nowMs)), clockSize, state.ariaText, state.weekendOffPeak)
|
|
2244
2295
|
),
|
|
2245
2296
|
!isRail ? React.createElement('div', { className: 'dshw_footRingLabel' },
|
|
2246
2297
|
React.createElement('div', { className: 'dshw_footRingHeader' },
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-harness-wallet",
|
|
3
3
|
"description": "Local-first account monitoring, usage accounting, official recharge, completion reminders, flexible layout, host-gated session controls, and multi-account management with hot account switching for DeepSeek Harness.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|