dsh-balance-plugin 0.1.0 → 0.2.1
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/PUBLISHING.md +25 -26
- package/README.md +22 -12
- package/README.zh.md +23 -13
- package/lib/client.js +94 -8
- package/lib/float.js +24 -0
- package/lib/index.js +18 -2
- package/package.json +12 -15
package/PUBLISHING.md
CHANGED
|
@@ -1,45 +1,53 @@
|
|
|
1
1
|
# Publishing dsh-balance-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Release manual for the supported npm and GitHub Release tarball channels.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
7
|
- [npm](https://docs.npmjs.com/cli/) account, logged in: `npm whoami`
|
|
8
8
|
- A GitHub account; the repository will be public.
|
|
9
9
|
|
|
10
|
-
## 1.
|
|
10
|
+
## 1. Build and verify
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
```sh
|
|
13
|
+
# from plugins/dsh-balance-plugin
|
|
14
|
+
pnpm run build
|
|
15
|
+
pnpm test
|
|
16
|
+
pnpm pack --dry-run # must contain lib/, cordis.patch.yml, README files, and LICENSE
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
In an isolated DSH `0.1.5-rc.1` or later profile, verify both installation and update behavior:
|
|
15
20
|
|
|
16
21
|
```sh
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
dsh plugin --profile demo add ./dsh-balance-plugin-<version>.tgz
|
|
23
|
+
dsh plugin --profile demo update dsh-balance-plugin
|
|
24
|
+
dsh --profile demo --dump-config # expect "# == dsh-balance-plugin"
|
|
20
25
|
```
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
Restart the Web profile and hard-refresh the browser to verify the authenticated balance readout.
|
|
23
28
|
|
|
24
29
|
## 2. Publish to npm
|
|
25
30
|
|
|
26
31
|
```sh
|
|
27
|
-
# from ~/Documents/dsh-balance-plugin
|
|
28
32
|
pnpm run build && pnpm test # gates
|
|
29
|
-
npm version patch #
|
|
33
|
+
npm version patch # creates the release version and git tag
|
|
30
34
|
pnpm pack # inspect the tarball contents
|
|
31
35
|
npm publish # publishes prebuilt lib/ — users install with zero friction
|
|
32
36
|
git push && git push --tags
|
|
33
37
|
```
|
|
34
38
|
|
|
35
|
-
npm is the **recommended** channel: users run `dsh plugin --profile <name> add dsh-balance-plugin` and get prebuilt code with no install-time build or permission prompt.
|
|
39
|
+
npm is the **recommended** channel: users run `dsh plugin --profile <name> add dsh-balance-plugin` and get prebuilt code with no install-time build or permission prompt. Existing npm users upgrade with `dsh plugin --profile <name> update dsh-balance-plugin`.
|
|
36
40
|
|
|
37
|
-
## 3.
|
|
41
|
+
## 3. Publish a GitHub Release tarball
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
The package is in this monorepo's `plugins/` subdirectory, so `github:` package specs cannot target it. Publish the prebuilt tarball instead:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pnpm pack
|
|
47
|
+
gh release create v<version> dsh-balance-plugin-<version>.tgz --title "dsh-balance-plugin <version>" --notes-file <notes-file>
|
|
48
|
+
```
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
- pnpm ≥ 10 refuses to run git dependencies' `prepare` until the user opts in via `allowBuilds` in the profile's `pnpm-workspace.yaml`. Document this in your README (already present).
|
|
50
|
+
Users install or replace a tarball release with `dsh plugin --profile <name> add ./dsh-balance-plugin-<version>.tgz`, then run `dsh --profile <name> --dump-config` and restart DSH.
|
|
43
51
|
|
|
44
52
|
## 4. Versioning and release notes
|
|
45
53
|
|
|
@@ -47,16 +55,7 @@ Users can also install directly from GitHub. Two official caveats (from `docs/us
|
|
|
47
55
|
- Create a GitHub Release per tag with the changelog; reference the npm version.
|
|
48
56
|
- Semantic versioning: `patch` for fixes, `minor` for features, `major` for breaking changes.
|
|
49
57
|
|
|
50
|
-
## 5.
|
|
51
|
-
|
|
52
|
-
```sh
|
|
53
|
-
# fresh install from the tarball
|
|
54
|
-
dsh plugin --profile demo add ./dsh-balance-plugin-0.1.0.tgz
|
|
55
|
-
dsh --profile demo --dump-config # expect "# == dsh-balance-plugin" layer
|
|
56
|
-
# then in the web profile with a real credential: the readout renders
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## 6. Local development loop
|
|
58
|
+
## 5. Local development loop
|
|
60
59
|
|
|
61
60
|
```sh
|
|
62
61
|
pnpm install
|
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
DeepSeek wallet balance at the sidebar foot — a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin.
|
|
4
4
|
|
|
5
|
+
> **DeepSeek-only**: this plugin targets the DeepSeek platform exclusively — the official DeepSeek balance endpoint and `DEEPSEEK_API_KEY`. No other platforms or providers are supported.
|
|
6
|
+
|
|
5
7
|
English | [中文](README.zh.md)
|
|
6
8
|
|
|
7
|
-
Shows your DeepSeek account balance as a **card icon + amount** in the sidebar footer (beside Settings), tinted by how much is left. Refreshes when a conversation turn ends, on click, and on mount — no polling.
|
|
9
|
+
Shows your DeepSeek account balance as a **card icon + amount** in the sidebar footer (beside Settings), tinted by how much is left. Refreshes when a conversation turn ends, on click, and on mount — no polling. When the amount changes, a signed delta (`+¥3.14` / `-¥1.97`) floats up and fades next to it — every spend (or top-up) is visible at a glance.
|
|
8
10
|
|
|
9
11
|

|
|
10
12
|
|
|
@@ -16,12 +18,13 @@ Shows your DeepSeek account balance as a **card icon + amount** in the sidebar f
|
|
|
16
18
|
- zero or below: red
|
|
17
19
|
- **Refresh on turn end**: the host pushes an SSE `refresh` event when any conversation turn ends — wallet spend settles with the turn.
|
|
18
20
|
- **Click to refresh**; in-flight refreshes are deduped (a click or turn-end tick during a pending query is a no-op).
|
|
21
|
+
- **Float delta**: on a visible amount change, a signed delta floats up and fades out — green when the balance rises, red when it falls. Zero or sub-cent changes are silent, the first load never floats, and the animation is suppressed under `prefers-reduced-motion`.
|
|
19
22
|
- **Silent failure**: unconfigured credentials or a failed fetch render nothing — the readout is advisory and never surfaces an error.
|
|
20
23
|
- **Original currencies**: balances are displayed as the provider reports them; no conversion, no summing.
|
|
21
24
|
|
|
22
25
|
## Install
|
|
23
26
|
|
|
24
|
-
Requires a DeepSeek account credential. The plugin resolves `DEEPSEEK_API_KEY` through the harness credential
|
|
27
|
+
Requires DeepSeek Harness `0.1.5-rc.1` or later and a DeepSeek account credential. The plugin resolves `DEEPSEEK_API_KEY` through the optional harness credential service (`~/.dsh/.credentials.yaml`) or the environment.
|
|
25
28
|
|
|
26
29
|
### From npm (recommended)
|
|
27
30
|
|
|
@@ -29,20 +32,27 @@ Requires a DeepSeek account credential. The plugin resolves `DEEPSEEK_API_KEY` t
|
|
|
29
32
|
dsh plugin --profile <name> add dsh-balance-plugin
|
|
30
33
|
```
|
|
31
34
|
|
|
32
|
-
### From GitHub
|
|
35
|
+
### From the GitHub Release (no npm account needed)
|
|
36
|
+
|
|
37
|
+
Download `dsh-balance-plugin-<version>.tgz` from the [releases page](https://github.com/luokai-demo/dsh-plugins/releases), then:
|
|
33
38
|
|
|
34
39
|
```sh
|
|
35
|
-
dsh plugin --profile <name> add
|
|
40
|
+
dsh plugin --profile <name> add ./dsh-balance-plugin-<version>.tgz
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
Note: the plugin lives in the `plugins/` subdirectory of the `dsh-plugins` monorepo, so `dsh plugin add github:...` cannot target it — use the tarball (or npm).
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
### Update an existing install
|
|
46
|
+
|
|
47
|
+
For npm installs, update the package in the same profile, confirm that its bundle layer remains active, then restart the Web server:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
dsh plugin --profile <name> update dsh-balance-plugin
|
|
51
|
+
dsh --profile <name> --dump-config # expect a "# == dsh-balance-plugin" layer
|
|
52
|
+
# restart dsh web, then hard-refresh the browser
|
|
43
53
|
```
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
`dsh plugin` forwards pnpm commands and reconciles the installed package's `dsh.bundle` declaration after a successful update. For a Release tarball, run `dsh plugin --profile <name> add ./dsh-balance-plugin-<version>.tgz` with the newer archive, then perform the same verification and restart.
|
|
46
56
|
|
|
47
57
|
## Configuration
|
|
48
58
|
|
|
@@ -63,7 +73,7 @@ The plugin ships with sensible defaults; override via the bundle's plugin row:
|
|
|
63
73
|
|
|
64
74
|
| Half | What it does |
|
|
65
75
|
|---|---|
|
|
66
|
-
| **Host** (Node) | Registers `GET /dsh-balance` (the wallet readout as JSON) and `GET /dsh-balance/events` (an SSE stream).
|
|
76
|
+
| **Host** (Node) | Registers authenticated `GET /dsh-balance` (the wallet readout as JSON) and `GET /dsh-balance/events` (an SSE stream). Each route passes the Harness browser-auth and Origin fence before reading a credential. It listens on `session/event` and emits `refresh` per `turn/end`, resolving credentials **per request** through the optional service before falling back to the environment. |
|
|
67
77
|
| **Client** (browser) | Registers the `sidebar.footer.action` slot entry (the shell's reserved seat beside Settings; on the collapsed rail it stacks above the settings icon). Fetches the readout on mount, on `refresh` SSE events, and on click, with an in-flight guard. The card icon is self-contained (no dependency on the shell's icon library). |
|
|
68
78
|
|
|
69
79
|
Position and interaction follow the shell's own design: the `sidebar.footer.action` hole is the official extension point for "optional actions beside Settings".
|
|
@@ -96,7 +106,7 @@ tests/ # node:test unit tests for balance-core
|
|
|
96
106
|
```sh
|
|
97
107
|
pnpm install
|
|
98
108
|
pnpm run build # emits lib/index.js + lib/client.js
|
|
99
|
-
pnpm test # balance-core unit tests
|
|
109
|
+
pnpm test # balance-core, float, and host-route unit tests
|
|
100
110
|
```
|
|
101
111
|
|
|
102
112
|
Local install check (official verification points):
|
|
@@ -111,7 +121,7 @@ dsh --profile demo --dump-config # expect a "# == dsh-balance-plugin" layer
|
|
|
111
121
|
Before publishing a new version:
|
|
112
122
|
|
|
113
123
|
1. `pnpm run build` and `pnpm test` pass.
|
|
114
|
-
2. Local install
|
|
124
|
+
2. Local install and `dsh plugin --profile demo update dsh-balance-plugin` both keep the `--dump-config` layer; the web profile renders the readout with a real credential.
|
|
115
125
|
3. Version bumped (`npm version patch/minor/major`); git tag matches.
|
|
116
126
|
4. `pnpm pack` — inspect the tarball: it must contain `lib/`, `cordis.patch.yml`, `README.md` (and nothing else heavy).
|
|
117
127
|
5. `npm publish` — publishes prebuilt code, so users install with zero friction.
|
package/README.zh.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
侧边栏底部的 DeepSeek 钱包余额——一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件。
|
|
4
4
|
|
|
5
|
+
> **纯 DeepSeek**:本插件只针对 DeepSeek 平台——官方 DeepSeek 余额端点与 `DEEPSEEK_API_KEY`,不支持任何其他平台或服务商。
|
|
6
|
+
|
|
5
7
|
[English](README.md) | 中文
|
|
6
8
|
|
|
7
|
-
在侧边栏底部(设置按钮旁)以**信用卡图标 + 金额**显示你的 DeepSeek
|
|
9
|
+
在侧边栏底部(设置按钮旁)以**信用卡图标 + 金额**显示你的 DeepSeek 账户余额,并按剩余额度着色。在对话结束时、点击时与挂载时刷新——无轮询。金额变动时,带符号的差额(`+¥3.14` / `-¥1.97`)会从余额旁上飘淡出——每次花费(或充值)都一目了然。
|
|
8
10
|
|
|
9
11
|

|
|
10
12
|
|
|
@@ -16,12 +18,13 @@
|
|
|
16
18
|
- 零或以下:红色
|
|
17
19
|
- **对话结束自动刷新**:宿主在任何对话轮次结束时推送 SSE `refresh` 事件——消耗随轮次结算。
|
|
18
20
|
- **点击刷新**;在途刷新去重(查询进行中时点击或轮次结束触发均为 no-op)。
|
|
21
|
+
- **差额上飘**:金额发生可见变动时,带符号差额上飘淡出——余额上升为绿色、下降为红色。零变动与不足一分钱的变化静默,首次加载不上飘,且尊重系统 `prefers-reduced-motion`(减弱动态效果时停用动画)。
|
|
19
22
|
- **失败静默**:凭证未配置或抓取失败时不渲染任何内容——读数仅供参考,绝不显示错误。
|
|
20
23
|
- **原始币种**:余额按提供方报告显示;不换算、不加总。
|
|
21
24
|
|
|
22
25
|
## 安装
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
需要 DeepSeek Harness `0.1.5-rc.1` 或更高版本,以及一个 DeepSeek 账户凭证。插件通过可选的 harness 凭证服务(`~/.dsh/.credentials.yaml`)或环境变量解析 `DEEPSEEK_API_KEY`。
|
|
25
28
|
|
|
26
29
|
### 从 npm 安装(推荐)
|
|
27
30
|
|
|
@@ -29,20 +32,27 @@
|
|
|
29
32
|
dsh plugin --profile <名字> add dsh-balance-plugin
|
|
30
33
|
```
|
|
31
34
|
|
|
32
|
-
### 从 GitHub
|
|
35
|
+
### 从 GitHub Release 安装(无需 npm 账号)
|
|
36
|
+
|
|
37
|
+
从 [releases 页面](https://github.com/luokai-demo/dsh-plugins/releases) 下载 `dsh-balance-plugin-<版本>.tgz`,然后:
|
|
33
38
|
|
|
34
39
|
```sh
|
|
35
|
-
dsh plugin --profile <名字> add
|
|
40
|
+
dsh plugin --profile <名字> add ./dsh-balance-plugin-<版本>.tgz
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
注意:插件位于 `dsh-plugins` monorepo 的 `plugins/` 子目录,`dsh plugin add github:...` 无法指向子目录——请使用 tarball(或 npm)。
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
### 升级已有安装
|
|
46
|
+
|
|
47
|
+
通过 npm 安装的版本,在同一个 profile 中升级包,确认 bundle 层仍处于启用状态,然后重启 Web 服务:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
dsh plugin --profile <名字> update dsh-balance-plugin
|
|
51
|
+
dsh --profile <名字> --dump-config # 应出现 "# == dsh-balance-plugin" 层
|
|
52
|
+
# 重启 dsh web,然后硬刷新浏览器
|
|
43
53
|
```
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
`dsh plugin` 会转发 pnpm 命令,并在成功升级后重新核对已安装包的 `dsh.bundle` 声明。GitHub Release tarball 则执行 `dsh plugin --profile <名字> add ./dsh-balance-plugin-<版本>.tgz` 安装新归档,再进行相同的验证与重启。
|
|
46
56
|
|
|
47
57
|
## 配置
|
|
48
58
|
|
|
@@ -63,7 +73,7 @@ allowBuilds:
|
|
|
63
73
|
|
|
64
74
|
| 半面 | 做什么 |
|
|
65
75
|
|---|---|
|
|
66
|
-
| **宿主**(Node) |
|
|
76
|
+
| **宿主**(Node) | 注册经认证的 `GET /dsh-balance`(钱包读数 JSON)与 `GET /dsh-balance/events`(SSE 流)。每个路由都会先通过 Harness 的浏览器身份与 Origin 栅栏,再读取凭证。监听 `session/event`,每个 `turn/end` 推送 `refresh`;**每次请求**经可选服务解析凭证,再回退环境变量。 |
|
|
67
77
|
| **客户端**(浏览器) | 注册 `sidebar.footer.action` 插槽条目(shell 预留的设置旁座位;收起 rail 时堆叠在设置图标上方)。在挂载时、`refresh` SSE 事件与点击时抓取读数,带 in-flight 守卫。信用卡图标自包含(不依赖 shell 的图标库)。 |
|
|
68
78
|
|
|
69
79
|
位置与交互遵循 shell 自身设计:`sidebar.footer.action` 是"设置旁可选操作"的官方扩展点。
|
|
@@ -90,13 +100,13 @@ src/
|
|
|
90
100
|
index.ts # 宿主插件:路由 + SSE + 轮次结束监听
|
|
91
101
|
client.tsx # 浏览器插件:侧边栏底部操作
|
|
92
102
|
scripts/build.mjs # esbuild:宿主 ESM + 浏览器 __ModuleLoader__ bundle
|
|
93
|
-
tests/ # balance-core
|
|
103
|
+
tests/ # balance-core、float 与宿主路由的 node:test 单元测试
|
|
94
104
|
```
|
|
95
105
|
|
|
96
106
|
```sh
|
|
97
107
|
pnpm install
|
|
98
108
|
pnpm run build # 生成 lib/index.js + lib/client.js
|
|
99
|
-
pnpm test # balance-core
|
|
109
|
+
pnpm test # balance-core、float 与宿主路由单元测试
|
|
100
110
|
```
|
|
101
111
|
|
|
102
112
|
本地安装检查(官方检验点):
|
|
@@ -111,7 +121,7 @@ dsh --profile demo --dump-config # 应出现 "# == dsh-balance-plugin" 层
|
|
|
111
121
|
发布新版本前:
|
|
112
122
|
|
|
113
123
|
1. `pnpm run build` 与 `pnpm test` 通过。
|
|
114
|
-
2.
|
|
124
|
+
2. 上述本地安装与 `dsh plugin --profile demo update dsh-balance-plugin` 都通过;`--dump-config` 保留该层,且 Web profile 在真实凭证下能渲染读数。
|
|
115
125
|
3. 版本已提升(`npm version patch/minor/major`);git tag 对应。
|
|
116
126
|
4. `pnpm pack`——检查 tarball:必须包含 `lib/`、`cordis.patch.yml`、`README.md`(别无其他大文件)。
|
|
117
127
|
5. `npm publish`——发布预构建代码,用户零摩擦安装。
|
package/lib/client.js
CHANGED
|
@@ -28,10 +28,31 @@ __export(client_exports, {
|
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(client_exports);
|
|
30
30
|
var import_react = require("react");
|
|
31
|
+
|
|
32
|
+
// src/float.ts
|
|
33
|
+
var FLOAT_EPSILON = 5e-3;
|
|
34
|
+
function shouldFloat(prev, next, epsilon = FLOAT_EPSILON) {
|
|
35
|
+
if (prev === void 0 || !Number.isFinite(next)) return false;
|
|
36
|
+
return Math.abs(next - prev) >= epsilon;
|
|
37
|
+
}
|
|
38
|
+
function floatDelta(prev, next) {
|
|
39
|
+
return next - prev;
|
|
40
|
+
}
|
|
41
|
+
function formatFloat(delta, symbol) {
|
|
42
|
+
const sign = delta >= 0 ? "+" : "-";
|
|
43
|
+
return `${sign}${symbol}${Math.abs(delta).toFixed(2)}`;
|
|
44
|
+
}
|
|
45
|
+
function symbolOf(display) {
|
|
46
|
+
const match = /^\D+/.exec(display);
|
|
47
|
+
return match === null ? "" : match[0];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/client.tsx
|
|
31
51
|
var name = "dsh-balance-plugin";
|
|
32
52
|
var inject = ["slots"];
|
|
33
53
|
var BALANCE_ENDPOINT = "/dsh-balance";
|
|
34
54
|
var EVENTS_ENDPOINT = "/dsh-balance/events";
|
|
55
|
+
var FLOAT_LIFETIME_MS = 1300;
|
|
35
56
|
function CardIcon({ size }) {
|
|
36
57
|
return (0, import_react.createElement)(
|
|
37
58
|
"svg",
|
|
@@ -51,6 +72,7 @@ function CardIcon({ size }) {
|
|
|
51
72
|
}
|
|
52
73
|
var styles = {
|
|
53
74
|
button: {
|
|
75
|
+
position: "relative",
|
|
54
76
|
flex: "none",
|
|
55
77
|
display: "inline-flex",
|
|
56
78
|
alignItems: "center",
|
|
@@ -74,10 +96,37 @@ var styles = {
|
|
|
74
96
|
fontVariantNumeric: "tabular-nums",
|
|
75
97
|
lineHeight: 1
|
|
76
98
|
},
|
|
99
|
+
floatLayer: {
|
|
100
|
+
position: "absolute",
|
|
101
|
+
top: -16,
|
|
102
|
+
left: 0,
|
|
103
|
+
right: 0,
|
|
104
|
+
display: "flex",
|
|
105
|
+
justifyContent: "center",
|
|
106
|
+
pointerEvents: "none"
|
|
107
|
+
},
|
|
108
|
+
float: {
|
|
109
|
+
fontSize: 13,
|
|
110
|
+
fontWeight: 700,
|
|
111
|
+
fontVariantNumeric: "tabular-nums",
|
|
112
|
+
lineHeight: 1,
|
|
113
|
+
whiteSpace: "nowrap",
|
|
114
|
+
animation: "dsh-balance-float-up 1.3s ease-out forwards"
|
|
115
|
+
},
|
|
77
116
|
success: { color: "var(--dsw-alias-state-success-primary, #16a34a)" },
|
|
78
117
|
warning: { color: "var(--dsw-static-amber-600, #d97706)" },
|
|
79
118
|
danger: { color: "var(--dsw-alias-state-error-primary, #dc2626)" }
|
|
80
119
|
};
|
|
120
|
+
var FLOAT_CSS = `
|
|
121
|
+
@keyframes dsh-balance-float-up {
|
|
122
|
+
from { transform: translateY(0); opacity: 1; }
|
|
123
|
+
70% { opacity: 1; }
|
|
124
|
+
to { transform: translateY(-26px); opacity: 0; }
|
|
125
|
+
}
|
|
126
|
+
@media (prefers-reduced-motion: reduce) {
|
|
127
|
+
.dsh-balance-float { display: none !important; }
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
81
130
|
function toneOf(readout) {
|
|
82
131
|
if (readout.total >= readout.threshold) return "success";
|
|
83
132
|
if (readout.total > 0) return "warning";
|
|
@@ -86,14 +135,33 @@ function toneOf(readout) {
|
|
|
86
135
|
function BalanceAction({ wide }) {
|
|
87
136
|
const [readout, setReadout] = (0, import_react.useState)(void 0);
|
|
88
137
|
const [turnEpoch, setTurnEpoch] = (0, import_react.useState)(0);
|
|
138
|
+
const [floats, setFloats] = (0, import_react.useState)([]);
|
|
89
139
|
const inFlight = (0, import_react.useRef)(false);
|
|
140
|
+
const floatSeq = (0, import_react.useRef)(0);
|
|
141
|
+
const readoutRef = (0, import_react.useRef)(void 0);
|
|
142
|
+
(0, import_react.useEffect)(() => {
|
|
143
|
+
readoutRef.current = readout;
|
|
144
|
+
}, [readout]);
|
|
145
|
+
const pushFloat = (0, import_react.useCallback)((delta, symbol) => {
|
|
146
|
+
const id = ++floatSeq.current;
|
|
147
|
+
setFloats((items) => [...items, { id, text: formatFloat(delta, symbol), tone: delta > 0 ? "success" : "danger" }]);
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
setFloats((items) => items.filter((item) => item.id !== id));
|
|
150
|
+
}, FLOAT_LIFETIME_MS);
|
|
151
|
+
}, []);
|
|
90
152
|
const refresh = (0, import_react.useCallback)(() => {
|
|
91
153
|
if (inFlight.current) return;
|
|
92
154
|
inFlight.current = true;
|
|
93
|
-
fetch(BALANCE_ENDPOINT, { cache: "no-store" }).then((response) => response.json()).then(
|
|
155
|
+
fetch(BALANCE_ENDPOINT, { cache: "no-store" }).then((response) => response.json()).then((next) => {
|
|
156
|
+
const prev = readoutRef.current;
|
|
157
|
+
if (prev?.status === "ok" && next.status === "ok" && shouldFloat(prev.total, next.total)) {
|
|
158
|
+
pushFloat(floatDelta(prev.total, next.total), symbolOf(next.display));
|
|
159
|
+
}
|
|
160
|
+
setReadout(next);
|
|
161
|
+
}).catch(() => setReadout(void 0)).finally(() => {
|
|
94
162
|
inFlight.current = false;
|
|
95
163
|
});
|
|
96
|
-
}, []);
|
|
164
|
+
}, [pushFloat]);
|
|
97
165
|
(0, import_react.useEffect)(() => {
|
|
98
166
|
refresh();
|
|
99
167
|
}, [refresh, turnEpoch]);
|
|
@@ -119,14 +187,32 @@ function BalanceAction({ wide }) {
|
|
|
119
187
|
onClick: refresh
|
|
120
188
|
},
|
|
121
189
|
(0, import_react.createElement)(CardIcon, { size: wide ? 16 : 18 }),
|
|
122
|
-
wide ? (0, import_react.createElement)("span", { style: { ...styles.amount, ...styles[tone] } }, readout.display) : null
|
|
190
|
+
wide ? (0, import_react.createElement)("span", { style: { ...styles.amount, ...styles[tone] } }, readout.display) : null,
|
|
191
|
+
floats.length > 0 ? (0, import_react.createElement)(
|
|
192
|
+
"span",
|
|
193
|
+
{ style: styles.floatLayer, "aria-hidden": true },
|
|
194
|
+
floats.map((item) => (0, import_react.createElement)(
|
|
195
|
+
"span",
|
|
196
|
+
{ key: item.id, className: "dsh-balance-float", style: { ...styles.float, ...styles[item.tone] } },
|
|
197
|
+
item.text
|
|
198
|
+
))
|
|
199
|
+
) : null
|
|
123
200
|
);
|
|
124
201
|
}
|
|
125
202
|
function apply(ctx) {
|
|
126
|
-
ctx.slots.inject("sidebar.footer.action", () =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
203
|
+
ctx.slots.inject("sidebar.footer.action", () => {
|
|
204
|
+
const styleEl = document.createElement("style");
|
|
205
|
+
styleEl.textContent = FLOAT_CSS;
|
|
206
|
+
document.head.appendChild(styleEl);
|
|
207
|
+
const disposer = ctx.slots.register({
|
|
208
|
+
name: "sidebar.footer.action",
|
|
209
|
+
id: "balance",
|
|
210
|
+
order: 0
|
|
211
|
+
}, BalanceAction);
|
|
212
|
+
return () => {
|
|
213
|
+
disposer();
|
|
214
|
+
styleEl.remove();
|
|
215
|
+
};
|
|
216
|
+
});
|
|
131
217
|
}
|
|
132
218
|
return module.exports; } });
|
package/lib/float.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/float.ts
|
|
2
|
+
var FLOAT_EPSILON = 5e-3;
|
|
3
|
+
function shouldFloat(prev, next, epsilon = FLOAT_EPSILON) {
|
|
4
|
+
if (prev === void 0 || !Number.isFinite(next)) return false;
|
|
5
|
+
return Math.abs(next - prev) >= epsilon;
|
|
6
|
+
}
|
|
7
|
+
function floatDelta(prev, next) {
|
|
8
|
+
return next - prev;
|
|
9
|
+
}
|
|
10
|
+
function formatFloat(delta, symbol) {
|
|
11
|
+
const sign = delta >= 0 ? "+" : "-";
|
|
12
|
+
return `${sign}${symbol}${Math.abs(delta).toFixed(2)}`;
|
|
13
|
+
}
|
|
14
|
+
function symbolOf(display) {
|
|
15
|
+
const match = /^\D+/.exec(display);
|
|
16
|
+
return match === null ? "" : match[0];
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
FLOAT_EPSILON,
|
|
20
|
+
floatDelta,
|
|
21
|
+
formatFloat,
|
|
22
|
+
shouldFloat,
|
|
23
|
+
symbolOf
|
|
24
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -98,13 +98,27 @@ async function fetchBalance(credentials, env, config) {
|
|
|
98
98
|
|
|
99
99
|
// src/index.ts
|
|
100
100
|
var name = "dsh-balance-plugin";
|
|
101
|
-
var inject = ["webServer"];
|
|
101
|
+
var inject = ["webServer", "connection"];
|
|
102
102
|
var Config = Schema.object({
|
|
103
103
|
apiKeyEnv: Schema.string().default("DEEPSEEK_API_KEY"),
|
|
104
104
|
baseURL: Schema.string().default("https://api.deepseek.com"),
|
|
105
105
|
timeoutMs: Schema.natural().min(1).max(12e4).default(12e3),
|
|
106
106
|
lowBalanceThreshold: Schema.number().min(0).default(2)
|
|
107
107
|
});
|
|
108
|
+
function rejectUntrustedRequest(ctx, req, res) {
|
|
109
|
+
const connection = Reflect.get(ctx, "connection");
|
|
110
|
+
const status = connection.requestRejection(req);
|
|
111
|
+
if (status === void 0) return false;
|
|
112
|
+
res.statusCode = status;
|
|
113
|
+
res.end();
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
function rejectNonGetRequest(req, res) {
|
|
117
|
+
if (req.method === "GET") return false;
|
|
118
|
+
res.writeHead(405, { allow: "GET" });
|
|
119
|
+
res.end();
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
108
122
|
function apply(ctx, config) {
|
|
109
123
|
const sseClients = /* @__PURE__ */ new Set();
|
|
110
124
|
const broadcastRefresh = () => {
|
|
@@ -119,7 +133,8 @@ function apply(ctx, config) {
|
|
|
119
133
|
const unregisterBalance = ctx.webServer.register({
|
|
120
134
|
kind: "exact",
|
|
121
135
|
path: "/dsh-balance",
|
|
122
|
-
handler: async (
|
|
136
|
+
handler: async (req, res) => {
|
|
137
|
+
if (rejectUntrustedRequest(ctx, req, res) || rejectNonGetRequest(req, res)) return;
|
|
123
138
|
const credentials = ctx.get("credentials");
|
|
124
139
|
const resolver = credentials === void 0 ? void 0 : { resolve: (ref) => credentials.resolve(ref) };
|
|
125
140
|
const readout = await fetchBalance(resolver, process.env, config);
|
|
@@ -136,6 +151,7 @@ function apply(ctx, config) {
|
|
|
136
151
|
kind: "exact",
|
|
137
152
|
path: "/dsh-balance/events",
|
|
138
153
|
handler: (req, res) => {
|
|
154
|
+
if (rejectUntrustedRequest(ctx, req, res) || rejectNonGetRequest(req, res)) return;
|
|
139
155
|
res.writeHead(200, {
|
|
140
156
|
"content-type": "text/event-stream",
|
|
141
157
|
"cache-control": "no-cache",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-balance-plugin",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "DeepSeek wallet-balance readout at the sidebar foot: card icon + amount, status-tinted, refreshed on turn end and on click",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "DeepSeek wallet-balance readout at the sidebar foot: card icon + amount, status-tinted, refreshed on turn end and on click, with a floating delta animation on change",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -29,21 +29,14 @@
|
|
|
29
29
|
]
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
|
-
"scripts": {
|
|
33
|
-
"build": "node scripts/build.mjs",
|
|
34
|
-
"prepare": "node scripts/build.mjs",
|
|
35
|
-
"test": "node --test tests/*.test.mjs"
|
|
36
|
-
},
|
|
37
32
|
"peerDependencies": {
|
|
38
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
39
|
-
"@deepseek-ai/
|
|
40
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
33
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
34
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
41
35
|
"react": "^18.0.0"
|
|
42
36
|
},
|
|
43
37
|
"devDependencies": {
|
|
44
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
45
|
-
"@deepseek-ai/
|
|
46
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
39
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
47
40
|
"@types/react": "^18.0.0",
|
|
48
41
|
"esbuild": "^0.25.0",
|
|
49
42
|
"react": "^18.0.0",
|
|
@@ -60,5 +53,9 @@
|
|
|
60
53
|
"dsh-plugin",
|
|
61
54
|
"balance",
|
|
62
55
|
"wallet"
|
|
63
|
-
]
|
|
64
|
-
|
|
56
|
+
],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "node scripts/build.mjs",
|
|
59
|
+
"test": "node --test tests/*.test.mjs"
|
|
60
|
+
}
|
|
61
|
+
}
|