dsh-balance-plugin 0.1.0 → 0.2.0

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 CHANGED
@@ -15,7 +15,7 @@ Step-by-step release manual for the two official distribution channels (both are
15
15
 
16
16
  ```sh
17
17
  cd ~/Documents/dsh-balance-plugin
18
- git remote add origin git@github.com:<you>/dsh-balance-plugin.git
18
+ git remote add origin git@github.com:luokai-demo/dsh-plugins.git
19
19
  git push -u origin main
20
20
  ```
21
21
 
package/README.md CHANGED
@@ -4,7 +4,7 @@ DeepSeek wallet balance at the sidebar foot — a [DeepSeek Harness](https://git
4
4
 
5
5
  English | [中文](README.zh.md)
6
6
 
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.
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. 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
8
 
9
9
  ![screenshot placeholder: sidebar foot showing `💳 ¥12.34` in green](docs/screenshot.png)
10
10
 
@@ -16,6 +16,7 @@ Shows your DeepSeek account balance as a **card icon + amount** in the sidebar f
16
16
  - zero or below: red
17
17
  - **Refresh on turn end**: the host pushes an SSE `refresh` event when any conversation turn ends — wallet spend settles with the turn.
18
18
  - **Click to refresh**; in-flight refreshes are deduped (a click or turn-end tick during a pending query is a no-op).
19
+ - **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
20
  - **Silent failure**: unconfigured credentials or a failed fetch render nothing — the readout is advisory and never surfaces an error.
20
21
  - **Original currencies**: balances are displayed as the provider reports them; no conversion, no summing.
21
22
 
@@ -29,20 +30,15 @@ Requires a DeepSeek account credential. The plugin resolves `DEEPSEEK_API_KEY` t
29
30
  dsh plugin --profile <name> add dsh-balance-plugin
30
31
  ```
31
32
 
32
- ### From GitHub
33
+ ### From the GitHub Release (no npm account needed)
33
34
 
34
- ```sh
35
- dsh plugin --profile <name> add github:<you>/dsh-balance-plugin#<commit-sha>
36
- ```
37
-
38
- Git installs fetch source, so pnpm ≥ 10 refuses the install-time build until you allow it. Copy the exact package key pnpm prints into the profile's `pnpm-workspace.yaml`:
35
+ Download `dsh-balance-plugin-<version>.tgz` from the [releases page](https://github.com/luokai-demo/dsh-plugins/releases), then:
39
36
 
40
- ```yaml
41
- allowBuilds:
42
- dsh-balance-plugin: true
37
+ ```sh
38
+ dsh plugin --profile <name> add ./dsh-balance-plugin-<version>.tgz
43
39
  ```
44
40
 
45
- then re-run the `add` command. Only install packages whose source you trust, and pin the commit.
41
+ 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).
46
42
 
47
43
  ## Configuration
48
44
 
package/README.zh.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [English](README.md) | 中文
6
6
 
7
- 在侧边栏底部(设置按钮旁)以**信用卡图标 + 金额**显示你的 DeepSeek 账户余额,并按剩余额度着色。在对话结束时、点击时与挂载时刷新——无轮询。
7
+ 在侧边栏底部(设置按钮旁)以**信用卡图标 + 金额**显示你的 DeepSeek 账户余额,并按剩余额度着色。在对话结束时、点击时与挂载时刷新——无轮询。金额变动时,带符号的差额(`+¥3.14` / `-¥1.97`)会从余额旁上飘淡出——每次花费(或充值)都一目了然。
8
8
 
9
9
  ![截图占位:侧边栏底部显示绿色的 `💳 ¥12.34`](docs/screenshot.png)
10
10
 
@@ -16,6 +16,7 @@
16
16
  - 零或以下:红色
17
17
  - **对话结束自动刷新**:宿主在任何对话轮次结束时推送 SSE `refresh` 事件——消耗随轮次结算。
18
18
  - **点击刷新**;在途刷新去重(查询进行中时点击或轮次结束触发均为 no-op)。
19
+ - **差额上飘**:金额发生可见变动时,带符号差额上飘淡出——余额上升为绿色、下降为红色。零变动与不足一分钱的变化静默,首次加载不上飘,且尊重系统 `prefers-reduced-motion`(减弱动态效果时停用动画)。
19
20
  - **失败静默**:凭证未配置或抓取失败时不渲染任何内容——读数仅供参考,绝不显示错误。
20
21
  - **原始币种**:余额按提供方报告显示;不换算、不加总。
21
22
 
@@ -29,20 +30,15 @@
29
30
  dsh plugin --profile <名字> add dsh-balance-plugin
30
31
  ```
31
32
 
32
- ### 从 GitHub 安装
33
+ ### 从 GitHub Release 安装(无需 npm 账号)
33
34
 
34
- ```sh
35
- dsh plugin --profile <名字> add github:<你>/dsh-balance-plugin#<commit-sha>
36
- ```
37
-
38
- Git 安装取到的是源码,pnpm ≥ 10 会拒绝安装时的构建,直到你允许:把 pnpm 打印的包键复制进 profile 的 `pnpm-workspace.yaml`:
35
+ 从 [releases 页面](https://github.com/luokai-demo/dsh-plugins/releases) 下载 `dsh-balance-plugin-<版本>.tgz`,然后:
39
36
 
40
- ```yaml
41
- allowBuilds:
42
- dsh-balance-plugin: true
37
+ ```sh
38
+ dsh plugin --profile <名字> add ./dsh-balance-plugin-<版本>.tgz
43
39
  ```
44
40
 
45
- 然后重跑 `add`。只安装你信任源码的包,并钉住 commit。
41
+ 注意:插件位于 `dsh-plugins` monorepo 的 `plugins/` 子目录,`dsh plugin add github:...` 无法指向子目录——请使用 tarball(或 npm)。
46
42
 
47
43
  ## 配置
48
44
 
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(setReadout).catch(() => setReadout(void 0)).finally(() => {
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", () => ctx.slots.register({
127
- name: "sidebar.footer.action",
128
- id: "balance",
129
- order: 0
130
- }, BalanceAction));
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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-balance-plugin",
3
- "version": "0.1.0",
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.0",
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": {