deepseek-harness-wallet 0.2.3 → 0.2.5
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 +17 -0
- package/index.js +52 -18
- package/lib/client.js +723 -113
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.2.5 - 2026-08-23
|
|
6
|
+
|
|
7
|
+
- 修复浮动峰谷卡片在窗口缩放、卡片放大或历史坐标过期后可能跑出视口的问题;加载、缩放、排版切换和 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.
|
|
8
|
+
- 恢复 Portal 挂载,确保自由浮动卡片和控制面板脱离侧边栏溢出裁剪。 / Restored Portal mounting so floating cards and the control panel escape sidebar overflow clipping.
|
|
9
|
+
- 完成主题回退清理,移除峰谷控制面板、浮动卡片和开关中的硬编码浅色背景/文字回退。 / Completed theme fallback cleanup for the peak panel, floating card, and switches.
|
|
10
|
+
- 保留账户安全、存储权限、刷新异常和偏好同步加固,并避免原始凭证错误文本进入日志或浏览器。 / Kept account-safety, storage-permission, refresh-failure, and preference-sync hardening without exposing raw credential errors to logs or the browser.
|
|
11
|
+
- 新增浮动坐标边界、Portal 和完整主题回退回归测试;当前测试基线为 78/78。 / Added regression coverage for floating-coordinate bounds, Portal mounting, and complete theme fallbacks; the test baseline is now 78/78.
|
|
12
|
+
|
|
13
|
+
## 0.2.4 - 2026-08-22
|
|
14
|
+
|
|
15
|
+
- 彻底修复暗色模式(Dark Mode)主题适配(Issue #26):移除所有未主题化的浅色硬编码回退(`--dsw-alias-bg-elevated,#fff`、`--dsw-alias-bg-overlay,#fff` 等),全站统一使用 DSH 原生主题变量(`--dsw-alias-bg-layer-*` 与 `--dsw-alias-label-dimmed`),在深色模式下完美融入背景。 / Fully fixed Dark Mode theme compliance (Issue #26): replaced unthemed hardcoded light fallbacks with DSH native theme variables (`--dsw-alias-bg-layer-*`), rendering dark themes seamlessly without blinding white boxes.
|
|
16
|
+
- 新增峰谷时钟专属控制面板(Dedicated Control Panel):点击时钟卡片即可就地弹出专属浮动控制面板,支持实时查看峰谷优惠说明、账户余额明细与快速充值,并提供排版切换、缩放调节与归位管理。 / Added a dedicated peak clock control popover: clicking the clock card opens an interactive panel for live rate info, quick recharge, orientation toggles, and layout controls.
|
|
17
|
+
- 峰谷时钟支持自由拖拽与持久化记忆:卡片支持在屏幕上任意拖拽移动,脱离侧边栏限制(Portal 渲染),拖拽释放自动持久化坐标,并在浮动卡片右上角和控制面板提供一键「↩ 归位」回到侧边栏底部。 / Added free-floating drag & drop for the peak clock: smoothly movable across the viewport with boundary clamping, position persistence in localStorage, and one-click dock reset.
|
|
18
|
+
- 峰谷时钟横向与纵向排版自由切换:支持标准的横向卡片与 ~140px 纤细纵向胶囊卡片(侧边栏内居中、浮动时轻巧紧凑)。 / Added customizable layout: seamlessly toggle between horizontal card layout and slim ~140px vertical capsule layout.
|
|
19
|
+
- 充值按钮内嵌排版优化:将充值按钮移至第三行倒计时右侧,彻底消除竖排按钮对第二行金额造成的水平挤压与 `¥...` 截断问题,金额与本场消耗 100% 完整显示。 / Relocated recharge button inline next to the countdown row, completely eliminating text truncation on the balance and session cost line.
|
|
20
|
+
- 新增时钟缩放滑条(100% ~ 120%)与充值按钮独立开关:支持按需放大时钟或收缩整体卡片尺寸。 / Added a dedicated scale slider (100% ~ 120%) and recharge button visibility toggle in both the popup panel and settings page.
|
|
21
|
+
|
|
5
22
|
## 0.2.3 - 2026-08-21
|
|
6
23
|
|
|
7
24
|
- 修复 Provider 分桶实际未参与计费的问题;已勾选的包装路由现在真正进入官方 token/花费桶,并记录新观察到的 Provider。 / Fixed provider aliases not reaching the billing path: opted-in wrapper routes now enter the official token/cost bucket, and newly observed providers are recorded.
|
package/index.js
CHANGED
|
@@ -78,7 +78,9 @@ export const PRICE_POLICIES = [
|
|
|
78
78
|
export function ratesFor(model, atMs) {
|
|
79
79
|
let entry
|
|
80
80
|
for (const policy of PRICE_POLICIES) {
|
|
81
|
-
|
|
81
|
+
// Own-property check: a model named `__proto__`/`toString` would otherwise
|
|
82
|
+
// resolve to an Object.prototype member and produce NaN costs.
|
|
83
|
+
if (atMs >= policy.since && Object.hasOwn(policy.models, model)) entry = policy
|
|
82
84
|
}
|
|
83
85
|
if (entry === undefined) return null
|
|
84
86
|
const rates = entry.models[model]
|
|
@@ -258,9 +260,10 @@ function persistStore(logger) {
|
|
|
258
260
|
saveTimer = null
|
|
259
261
|
}
|
|
260
262
|
try {
|
|
261
|
-
mkdirSync(dirname(STORE_PATH), { recursive: true })
|
|
263
|
+
mkdirSync(dirname(STORE_PATH), { recursive: true, mode: 0o700 })
|
|
262
264
|
const tmp = STORE_PATH + '.tmp'
|
|
263
|
-
|
|
265
|
+
// Owner-only: the store carries usage accounting next to credential files.
|
|
266
|
+
writeFileSync(tmp, JSON.stringify(store), { mode: 0o600 })
|
|
264
267
|
renameSync(tmp, STORE_PATH)
|
|
265
268
|
} catch (error) {
|
|
266
269
|
if (logger && typeof logger.warn === 'function') {
|
|
@@ -325,9 +328,10 @@ function persistAccounts(logger) {
|
|
|
325
328
|
accountsSaveTimer = null
|
|
326
329
|
}
|
|
327
330
|
try {
|
|
328
|
-
mkdirSync(dirname(ACCOUNTS_PATH), { recursive: true })
|
|
331
|
+
mkdirSync(dirname(ACCOUNTS_PATH), { recursive: true, mode: 0o700 })
|
|
329
332
|
const tmp = ACCOUNTS_PATH + '.tmp'
|
|
330
|
-
|
|
333
|
+
// This file holds plaintext API keys: owner-only, never group/world readable.
|
|
334
|
+
writeFileSync(tmp, JSON.stringify(accounts), { mode: 0o600 })
|
|
331
335
|
renameSync(tmp, ACCOUNTS_PATH)
|
|
332
336
|
} catch (error) {
|
|
333
337
|
if (logger && typeof logger.warn === 'function') {
|
|
@@ -436,15 +440,20 @@ async function resolveBalanceKey(ctx) {
|
|
|
436
440
|
*/
|
|
437
441
|
export async function activateAccount(ctx, id) {
|
|
438
442
|
const account = findAccount(id)
|
|
439
|
-
if (account === null) return { ok: false, error: 'account
|
|
443
|
+
if (account === null) return { ok: false, error: 'account-not-found' }
|
|
440
444
|
const credentials = ctx.get('credentials')
|
|
441
|
-
if (credentials === undefined) return { ok: false, error: 'credentials
|
|
445
|
+
if (credentials === undefined) return { ok: false, error: 'credentials-unavailable' }
|
|
442
446
|
try {
|
|
443
447
|
await credentials.set(CREDENTIAL_REF, account.apiKey)
|
|
444
448
|
} catch (error) {
|
|
445
449
|
// e.g. the launching environment supplies DEEPSEEK_API_KEY read-only, so
|
|
446
|
-
// the write is shadowed and refused by the credentials provider.
|
|
447
|
-
|
|
450
|
+
// the write is shadowed and refused by the credentials provider. Keep the
|
|
451
|
+
// browser error bounded and do not log provider text that might contain
|
|
452
|
+
// paths, environment details, or key fragments.
|
|
453
|
+
if (ctx.logger && typeof ctx.logger.warn === 'function') {
|
|
454
|
+
ctx.logger.warn('dsh-wallet: credential write refused')
|
|
455
|
+
}
|
|
456
|
+
return { ok: false, error: 'credential-write-refused' }
|
|
448
457
|
}
|
|
449
458
|
accounts.activeId = account.id
|
|
450
459
|
scheduleAccountsSave(ctx.logger)
|
|
@@ -509,12 +518,12 @@ let balance = { fetchedAt: 0, available: false, balances: [], error: null }
|
|
|
509
518
|
let balanceRefresh = null
|
|
510
519
|
|
|
511
520
|
async function performBalanceRefresh(ctx) {
|
|
512
|
-
const credentials = ctx.get('credentials')
|
|
513
|
-
if (credentials === undefined) {
|
|
514
|
-
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-credentials' }
|
|
515
|
-
return
|
|
516
|
-
}
|
|
517
521
|
try {
|
|
522
|
+
const credentials = ctx.get('credentials')
|
|
523
|
+
if (credentials === undefined) {
|
|
524
|
+
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-credentials' }
|
|
525
|
+
return
|
|
526
|
+
}
|
|
518
527
|
const key = await resolveBalanceKey(ctx)
|
|
519
528
|
if (key === undefined || key === '') {
|
|
520
529
|
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-api-key' }
|
|
@@ -564,9 +573,24 @@ async function performBalanceRefresh(ctx) {
|
|
|
564
573
|
|
|
565
574
|
function refreshBalance(ctx) {
|
|
566
575
|
if (balanceRefresh !== null) return balanceRefresh
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
576
|
+
// Never let a refresh escape as an unhandled rejection: every caller uses
|
|
577
|
+
// `void refreshBalance(ctx)`, so a throw here would take the host process
|
|
578
|
+
// down (Node exits on unhandled rejections). Absorb it into the error state.
|
|
579
|
+
balanceRefresh = performBalanceRefresh(ctx)
|
|
580
|
+
.catch((error) => {
|
|
581
|
+
balance = {
|
|
582
|
+
fetchedAt: Date.now(),
|
|
583
|
+
available: false,
|
|
584
|
+
balances: [],
|
|
585
|
+
error: 'balance-unavailable',
|
|
586
|
+
}
|
|
587
|
+
if (ctx && ctx.logger && typeof ctx.logger.warn === 'function') {
|
|
588
|
+
ctx.logger.warn('dsh-wallet: balance refresh failed')
|
|
589
|
+
}
|
|
590
|
+
})
|
|
591
|
+
.finally(() => {
|
|
592
|
+
balanceRefresh = null
|
|
593
|
+
})
|
|
570
594
|
return balanceRefresh
|
|
571
595
|
}
|
|
572
596
|
|
|
@@ -609,8 +633,18 @@ function balanceTotal() {
|
|
|
609
633
|
|
|
610
634
|
function sessionView(sessionId) {
|
|
611
635
|
if (sessionId === undefined || sessionId === '') return { official: null, third: null }
|
|
636
|
+
// Own-property lookup only: session ids like `__proto__`, `constructor` or
|
|
637
|
+
// `toString` otherwise resolve to an Object.prototype member, slip past the
|
|
638
|
+
// undefined guard, and throw inside bucketTotals.
|
|
639
|
+
if (!Object.prototype.hasOwnProperty.call(store.sessions, sessionId)) {
|
|
640
|
+
return { official: null, third: null }
|
|
641
|
+
}
|
|
612
642
|
const session = store.sessions[sessionId]
|
|
613
|
-
if (session ===
|
|
643
|
+
if (session === null || typeof session !== 'object'
|
|
644
|
+
|| session.official === null || typeof session.official !== 'object'
|
|
645
|
+
|| session.third === null || typeof session.third !== 'object') {
|
|
646
|
+
return { official: null, third: null }
|
|
647
|
+
}
|
|
614
648
|
const official = bucketTotals(session.official)
|
|
615
649
|
const third = bucketTotals(session.third)
|
|
616
650
|
return {
|