@lyk308/dsh-token-dashboard 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,55 +1,60 @@
1
- # dsh-token-dashboard
2
-
3
- [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
-
5
- A DeepSeek Harness plugin that puts your **real-time account balance** and **token usage** on one dashboard. Local-first: reads your DSH session logs and the DeepSeek `/user/balance` API. No cloud account beyond your existing DeepSeek key.
6
-
7
- ## Features
8
-
9
- - 💰 **Account balance** — live balance from the DeepSeek official `/user/balance` API (CNY/USD aware), with force-refresh.
10
- - 📊 **Token usage** — Today / This month / All-time cards, plus request count, cache hit rate, and session count.
11
- - 📈 **Usage distribution** — toggle between **by model** and **by workspace**, with a proportional share bar (top 8 + "other" aggregate). Percentages match the bar exactly.
12
- - 🗓 **Activity heatmap** — 30 / 90 / 180 day switch, green-shade intensity maps token usage.
13
- - 🤖 **Model breakdown** — a table of Req / Input / Cache / Output per model.
14
-
15
- ## Installation
16
-
17
- This is a standard DSH bundle plugin, so install it with the official CLI:
18
-
19
- ```bash
20
- dsh plugin --profile web add <path-or-git-url-to-this-repo>
21
- ```
22
-
23
- If published to npm, you can also install by name:
24
-
25
- ```bash
26
- dsh plugin --profile web add dsh-token-dashboard
27
- ```
28
-
29
- After installing, **refresh the web UI**. The dashboard appears under **Settings → 用量看板** (Usage Dashboard).
30
-
31
- > Note: the plugin reads the DeepSeek API key from DSH's credentials service (`DEEPSEEK_API_KEY`). Without a valid key the balance card shows a hint instead of a number; token usage still works because it comes from local session logs.
32
-
33
- ## Usage
34
-
35
- 1. Open **Settings 用量看板**.
36
- 2. Refresh the server to load the latest data at any time.
37
- 3. Switch between **by model / by workspace** and the **30 / 90 / 180-day** heatmap ranges.
38
-
39
- ## Configuration
40
-
41
- The plugin mostly works with no configuration. The host half resolves sensible defaults in `lib/index.js`:
42
- - `sessionsRoot` — defaults to `$DSH_HOME/sessions`.
43
- - `balanceCacheMs` — balance cache TTL (default 300000 ms = 5 min).
44
- - `sweepIntervalMs` — stats refresh interval (default 60000 ms).
45
-
46
- ## Development
47
-
48
- - `lib/index.js` — host half: session scanning (multi-frame zstd), token aggregation, balance proxy.
49
- - `lib/client.js` — client half: the settings.section UI.
50
-
51
- Edit `lib/client.js` → refresh the page to see changes. Edit `lib/index.js` → restart the DSH web profile.
52
-
53
- ## License
54
-
55
- [MIT](./LICENSE)
1
+ # dsh-token-dashboard
2
+
3
+ [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
+
5
+ A DeepSeek Harness plugin that puts your **real-time account balance** and **token usage** on one dashboard. Local-first: reads your DSH session logs and the DeepSeek `/user/balance` API. No cloud account beyond your existing DeepSeek key.
6
+
7
+ ## Screenshots
8
+
9
+ ![Dashboard](docs/screenshot.png)
10
+
11
+ ![Token usage & models](docs/screenshot2.png)
12
+
13
+ ## Features
14
+
15
+ - 💰 **Account balance** — live balance from the DeepSeek official `/user/balance` API (CNY/USD aware), with force-refresh.
16
+ - 📊 **Token usage** — Today / This month / All-time cards, plus request count, cache hit rate, and session count.
17
+ - 📈 **Usage distribution** toggle between **by model** and **by workspace**, with a proportional share bar (top 8 + "other" aggregate). Percentages match the bar exactly.
18
+ - 🗓 **Activity heatmap** — 30 / 90 / 180 day switch, green-shade intensity maps token usage.
19
+ - 🤖 **Model breakdown** — a table of Req / Input / Cache / Output per model.
20
+
21
+ ## Installation
22
+
23
+ This is a standard DSH bundle plugin, so install it with the official CLI:
24
+
25
+ ```bash
26
+ # From GitHub
27
+ dsh plugin --profile web add github:lyk308/dsh-token-dashboard
28
+
29
+ # From npm
30
+ dsh plugin --profile web add @lyk308/dsh-token-dashboard
31
+ ```
32
+
33
+ After installing, **refresh the web UI**. The dashboard appears under **Settings → 用量看板** (Usage Dashboard).
34
+
35
+ > Note: the plugin reads the DeepSeek API key from DSH's credentials service (`DEEPSEEK_API_KEY`). Without a valid key the balance card shows a hint instead of a number; token usage still works because it comes from local session logs.
36
+
37
+ ## Usage
38
+
39
+ 1. Open **Settings → 用量看板**.
40
+ 2. Refresh the server to load the latest data at any time.
41
+ 3. Switch between **by model / by workspace** and the **30 / 90 / 180-day** heatmap ranges.
42
+
43
+ ## Configuration
44
+
45
+ The plugin mostly works with no configuration. The host half resolves sensible defaults in `lib/index.js`:
46
+
47
+ - `sessionsRoot` — defaults to `$DSH_HOME/sessions`.
48
+ - `balanceCacheMs` — balance cache TTL (default 300000 ms = 5 min).
49
+ - `sweepIntervalMs` — stats refresh interval (default 60000 ms).
50
+
51
+ ## Development
52
+
53
+ - `lib/index.js` — host half: session scanning (multi-frame zstd), token aggregation, balance proxy.
54
+ - `lib/client.js` — client half: the settings.section UI.
55
+
56
+ Edit `lib/client.js` → refresh the page to see changes. Edit `lib/index.js` → restart the DSH web profile.
57
+
58
+ ## License
59
+
60
+ [MIT](./LICENSE)
package/README.zh.md CHANGED
@@ -1,55 +1,60 @@
1
- # dsh-token-dashboard
2
-
3
- [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
-
5
- 一个 DeepSeek Harness 插件,把**实时账户余额**和 **Token 使用量**整合在一个看板上。本地优先:读取你的 DSH 会话日志 + DeepSeek 官方 `/user/balance` 接口,无需额外云端账号(沿用你已有的 DeepSeek key)。
6
-
7
- ## 功能
8
-
9
- - 💰 **账户余额** — 通过 DeepSeek 官方 `/user/balance` 接口实时获取(自动识别 CNY/USD),支持强制刷新。
10
- - 📊 **Token 使用量** — 今日 / 本月 / 总计三卡,加请求数、缓存命中率、会话数。
11
- - 📈 **用量分布** — 在**按模型**和**按工作区**之间切换,带比例占比条(前 8 项 + 「其他」聚合),百分比与占比条完全一致。
12
- - 🗓 **活跃度热力图** — 30 / 90 / 180 天切换,绿色深浅映射 Token 用量。
13
- - 🤖 **按模型明细** — 每个模型的 Req / Input / Cache / Output 表格。
14
-
15
- ## 安装
16
-
17
- 这是一个标准 DSH bundle 插件,用官方 CLI 安装:
18
-
19
- ```bash
20
- dsh plugin --profile web add <此仓库的路径或 git 地址>
21
- ```
22
-
23
- 如果发布到 npm,也可按包名安装:
24
-
25
- ```bash
26
- dsh plugin --profile web add dsh-token-dashboard
27
- ```
28
-
29
- 安装后**刷新网页**即可。看板位于 **设置 用量看板**。
30
-
31
- > 说明:插件从 DSH 的凭据服务读取 DeepSeek API Key(`DEEPSEEK_API_KEY`)。没有有效 key 时余额卡片显示引导文案;Token 用量仍可正常统计(来自本地会话日志)。
32
-
33
- ## 使用方法
34
-
35
- 1. 打开 **设置 用量看板**。
36
- 2. 随时刷新服务器加载最新数据。
37
- 3. 切换 **按模型 / 按工作区**,以及 **30 / 90 / 180 天**热力图范围。
38
-
39
- ## 配置
40
-
41
- 插件基本无需配置即可使用,host 半在 `lib/index.js` 里解析合理默认值:
42
- - `sessionsRoot` — 默认为 `$DSH_HOME/sessions`。
43
- - `balanceCacheMs` — 余额缓存时长(默认 300000 毫秒 = 5 分钟)。
44
- - `sweepIntervalMs` — 统计刷新间隔(默认 60000 毫秒)。
45
-
46
- ## 开发
47
-
48
- - `lib/index.js` — host 半:会话扫描(多帧 zstd)、Token 聚合、余额代理。
49
- - `lib/client.js` — client 半:settings.section 界面。
50
-
51
- `lib/client.js` → 刷新页面即可看到变化。改 `lib/index.js` → 重启 DSH web profile。
52
-
53
- ## 许可证
54
-
55
- [MIT](./LICENSE)
1
+ # dsh-token-dashboard
2
+
3
+ [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
+
5
+ 一个 DeepSeek Harness 插件,把**实时账户余额**和 **Token 使用量**整合在一个看板上。本地优先:读取你的 DSH 会话日志 + DeepSeek 官方 `/user/balance` 接口,无需额外云端账号(沿用你已有的 DeepSeek key)。
6
+
7
+ ## 效果预览
8
+
9
+ ![用量看板](docs/screenshot.png)
10
+
11
+ ![Token 用量与模型明细](docs/screenshot2.png)
12
+
13
+ ## 功能
14
+
15
+ - 💰 **账户余额** — 通过 DeepSeek 官方 `/user/balance` 接口实时获取(自动识别 CNY/USD),支持强制刷新。
16
+ - 📊 **Token 使用量** — 今日 / 本月 / 总计三卡,加请求数、缓存命中率、会话数。
17
+ - 📈 **用量分布** 在**按模型**和**按工作区**之间切换,带比例占比条(前 8 项 + 「其他」聚合),百分比与占比条完全一致。
18
+ - 🗓 **活跃度热力图** — 30 / 90 / 180 天切换,绿色深浅映射 Token 用量。
19
+ - 🤖 **按模型明细** — 每个模型的 Req / Input / Cache / Output 表格。
20
+
21
+ ## 安装
22
+
23
+ 这是一个标准 DSH bundle 插件,用官方 CLI 安装:
24
+
25
+ ```bash
26
+ # GitHub 安装
27
+ dsh plugin --profile web add github:lyk308/dsh-token-dashboard
28
+
29
+ # npm 安装
30
+ dsh plugin --profile web add @lyk308/dsh-token-dashboard
31
+ ```
32
+
33
+ 安装后**刷新网页**即可。看板位于 **设置 → 用量看板**。
34
+
35
+ > 说明:插件从 DSH 的凭据服务读取 DeepSeek API Key(`DEEPSEEK_API_KEY`)。没有有效 key 时余额卡片显示引导文案;Token 用量仍可正常统计(来自本地会话日志)。
36
+
37
+ ## 使用方法
38
+
39
+ 1. 打开 **设置 → 用量看板**。
40
+ 2. 随时刷新服务器加载最新数据。
41
+ 3. 切换 **按模型 / 按工作区**,以及 **30 / 90 / 180 天**热力图范围。
42
+
43
+ ## 配置
44
+
45
+ 插件基本无需配置即可使用,host 半在 `lib/index.js` 里解析合理默认值:
46
+
47
+ - `sessionsRoot` — 默认为 `$DSH_HOME/sessions`。
48
+ - `balanceCacheMs` — 余额缓存时长(默认 300000 毫秒 = 5 分钟)。
49
+ - `sweepIntervalMs` — 统计刷新间隔(默认 60000 毫秒)。
50
+
51
+ ## 开发
52
+
53
+ - `lib/index.js` — host 半:会话扫描(多帧 zstd)、Token 聚合、余额代理。
54
+ - `lib/client.js` — client 半:settings.section 界面。
55
+
56
+ 改 `lib/client.js` → 刷新页面即可看到变化。改 `lib/index.js` → 重启 DSH web profile。
57
+
58
+ ## 许可证
59
+
60
+ [MIT](./LICENSE)
package/lib/client.js CHANGED
@@ -94,6 +94,7 @@ window.__ModuleLoader__.load({
94
94
  const [balanceLoading, setBalanceLoading] = useState(true);
95
95
  const [groupBy, setGroupBy] = useState("model"); // "model" | "workspace"
96
96
  const [range, setRange] = useState(90); // 30 | 90 | 180 days
97
+ const [metric, setMetric] = useState("cost"); // "cost" | "token"
97
98
 
98
99
  const loadStats = useCallback(async () => {
99
100
  try {
@@ -162,14 +163,18 @@ window.__ModuleLoader__.load({
162
163
  // both reflect "share of all usage".
163
164
  const groupSum = groupEntries.reduce((s, g) => s + (Number(g.total) || 0), 0);
164
165
 
165
- // heatmap: build days from byDay within range
166
+ // heatmap: build days from byDay within range. byDay entries are either
167
+ // {total, cost, ...} objects (new) or a bare number (legacy), so normalize.
166
168
  const byDay = stats.byDay || {};
167
169
  const todayD = new Date();
168
170
  const cells = [];
169
171
  for (let i = range - 1; i >= 0; i--) {
170
172
  const d = new Date(todayD); d.setDate(d.getDate() - i);
171
173
  const key = d.toISOString().slice(0, 10);
172
- cells.push({ key, val: Number(byDay[key]) || 0 });
174
+ const rec = byDay[key];
175
+ const total = (rec && typeof rec === "object") ? Number(rec.total) || 0 : Number(rec) || 0;
176
+ const cost = (rec && typeof rec === "object") ? Number(rec.cost) || 0 : 0;
177
+ cells.push({ key, val: total, cost });
173
178
  }
174
179
  const heatMax = Math.max(1, ...cells.map((c) => c.val));
175
180
  // group cells into weeks for the grid (7 per week)
@@ -234,18 +239,43 @@ window.__ModuleLoader__.load({
234
239
  jsx("div", { style: { width: "14px", height: "14px", borderRadius: "2px", background: heatColor(c.val, heatMax) }, title: c.key + ": " + fmt(c.val) }, c.key)
235
240
  ) });
236
241
 
242
+ // --- trend chart: a lightweight SVG polyline of daily metric over `range`.
243
+ // metric = "cost" or "token". Uses only inline SVG — no chart lib.
244
+ const trendValues = cells.map((c) => (metric === "cost" ? c.cost : c.val));
245
+ const tMax = Math.max(1, ...trendValues);
246
+ const TW = 320, TH = 80, PAD = 6;
247
+ let trendSvg;
248
+ if (trendValues.length <= 1) {
249
+ trendSvg = jsx("div", { style: { color: "var(--dsw-alias-label-secondary)", fontSize: "12px", padding: "8px 0" }, children: "数据不足" });
250
+ } else {
251
+ const step = TW / (trendValues.length - 1);
252
+ const pts = trendValues.map((v, i) => {
253
+ const x = PAD + i * step;
254
+ const y = TH - PAD - (v / tMax) * (TH - 2 * PAD);
255
+ return x.toFixed(1) + "," + y.toFixed(1);
256
+ }).join(" ");
257
+ const lastPt = pts.split(" ")[pts.split(" ").length - 1];
258
+ const lastV = trendValues[trendValues.length - 1];
259
+ trendSvg = jsxs("svg", { width: "100%", viewBox: "0 0 " + TW + " " + TH, style: { display: "block", maxHeight: "90px", width: "100%" }, children: [
260
+ jsx("polyline", { points: pts, fill: "none", stroke: "var(--dsw-alias-label-primary)", strokeWidth: "2", strokeLinejoin: "round", strokeLinecap: "round" }),
261
+ jsx("circle", { cx: lastPt.split(",")[0], cy: lastPt.split(",")[1], r: "3", fill: "var(--dsw-alias-label-primary)" }),
262
+ jsx("text", { x: "10", y: "12", fill: "var(--dsw-alias-label-tertiary)", fontSize: "10", children: (metric === "cost" ? ("¥" + fmtMoney(lastV)) : fmt(lastV)) })
263
+ ] });
264
+ }
265
+
237
266
  const modelTable = jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: "13px" }, children: [
238
267
  jsxs("thead", { children: [jsxs("tr", { children: [
239
268
  jsx("th", { style: thStyle, children: "模型" }), jsx("th", { style: thR, children: "Req" }),
240
269
  jsx("th", { style: thR, children: "Input" }), jsx("th", { style: thR, children: "Cache" }),
241
- jsx("th", { style: thR, children: "Output" })
270
+ jsx("th", { style: thR, children: "Output" }), jsx("th", { style: thR, children: "Cost" })
242
271
  ] })] }),
243
272
  jsxs("tbody", { children: modelRows.map((r) => jsxs("tr", { style: { borderTop: "1px solid var(--dsw-alias-border-l1)" }, children: [
244
273
  jsx("td", { style: tdStyle, children: r.key }),
245
274
  jsx("td", { style: tdR, children: r.req }),
246
275
  jsx("td", { style: tdR, children: fmt(r.input) }),
247
276
  jsx("td", { style: tdR, children: fmt(r.cacheRead) }),
248
- jsx("td", { style: tdR, children: fmt(r.output) })
277
+ jsx("td", { style: tdR, children: fmt(r.output) }),
278
+ jsx("td", { style: { ...tdR, color: "var(--dsw-alias-label-primary)", fontWeight: 600 }, children: "¥" + fmtMoney(r.cost || 0) })
249
279
  ] }, r.key)) })
250
280
  ] });
251
281
 
@@ -263,6 +293,15 @@ window.__ModuleLoader__.load({
263
293
  jsx("div", { style: SEC_TITLE, children: "📊 Token 使用量" }),
264
294
  statBar,
265
295
  jsxs("div", { style: { ...ROW, marginTop: "10px" }, children: [
296
+ jsx("span", { children: "今日花费" }), jsx("span", { ...MONO, children: "¥" + fmtMoney(stats.todayCost) })
297
+ ] }),
298
+ jsxs("div", { style: ROW, children: [
299
+ jsx("span", { children: "本月花费" }), jsx("span", { ...MONO, children: "¥" + fmtMoney(stats.thisMonthCost) })
300
+ ] }),
301
+ jsxs("div", { style: ROW, children: [
302
+ jsx("span", { children: "总花费" }), jsx("span", { ...MONO, children: "¥" + fmtMoney(stats.cost) })
303
+ ] }),
304
+ jsxs("div", { style: { ...ROW, marginTop: "6px", borderTop: "1px solid var(--dsw-alias-border-l1)", paddingTop: "6px" }, children: [
266
305
  jsx("span", { children: "请求数" }), jsx("span", { ...MONO, children: fmt(req) })
267
306
  ] }),
268
307
  jsxs("div", { style: ROW, children: [
@@ -291,6 +330,15 @@ window.__ModuleLoader__.load({
291
330
  heatGrid
292
331
  ] }),
293
332
 
333
+ // Trend chart
334
+ jsxs("div", { style: CARD, children: [
335
+ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "10px" }, children: [
336
+ jsx("div", { style: SEC_TITLE, children: "📉 趋势" }),
337
+ jsx(Seg, { options: [{ label: "成本", value: "cost" }, { label: "Token", value: "token" }], value: metric, onChange: setMetric })
338
+ ] }),
339
+ trendSvg
340
+ ] }),
341
+
294
342
  // Models table
295
343
  jsxs("div", { style: CARD, children: [
296
344
  jsx("div", { style: SEC_TITLE, children: "🤖 按模型" }),
package/lib/index.js CHANGED
@@ -21,6 +21,51 @@ import { once } from "node:events";
21
21
 
22
22
  const ZSTD_MAGIC = 0xfd2fb528; // bytes 28 b5 2f fd (little-endian)
23
23
 
24
+ // ---- DeepSeek V4 pricing (CNY per 1,000,000 tokens) ----
25
+ // Source: https://api-docs.deepseek.com/zh-cn/quick_start/pricing
26
+ // Prices are peak/off-peak. Off-peak = half of peak. Peak = Mon–Fri 09:00–12:00, 14:00–18:00.
27
+ // deepseek-v4-flash and deepseek-v4-flash-vision-exp share the same price.
28
+ const MODEL_PRICES = {
29
+ "deepseek-v4-flash": { cacheHit: [0.05, 0.10], cacheMiss: [1.5, 3.0], output: [4.5, 9.0] },
30
+ "deepseek-v4-flash-vision-exp": { cacheHit: [0.05, 0.10], cacheMiss: [1.5, 3.0], output: [4.5, 9.0] },
31
+ "deepseek-v4-pro": { cacheHit: [0.15, 0.30], cacheMiss: [4.5, 9.0], output: [13.5, 27.0] }
32
+ };
33
+ // Fallback for any model not in the table (use flash's price).
34
+ const FALLBACK_PRICE = MODEL_PRICES["deepseek-v4-flash"];
35
+
36
+ /**
37
+ * Whether the given timestamp falls in DeepSeek's peak period.
38
+ * Peak = Mon–Fri 09:00–12:00 and 14:00–18:00 (Beijing time / local device time).
39
+ * @param {number|null} timeMs - epoch milliseconds of the event.
40
+ * @returns {boolean}
41
+ */
42
+ function isPeakTime(timeMs) {
43
+ if (!timeMs) return false;
44
+ const d = new Date(timeMs);
45
+ const dow = d.getDay(); // 0=Sun .. 6=Sat
46
+ if (dow === 0 || dow === 6) return false; // weekends are off-peak
47
+ const mins = d.getHours() * 60 + d.getMinutes();
48
+ // 09:00-12:00 => 540-720 ; 14:00-18:00 => 840-1080
49
+ return (mins >= 540 && mins < 720) || (mins >= 840 && mins < 1080);
50
+ }
51
+
52
+ /**
53
+ * Compute the CNY cost of one usage event given its token breakdown + timestamp.
54
+ * @param {{input:number, output:number, cacheRead:number}} t
55
+ * @param {number|null} timeMs
56
+ * @param {string} model
57
+ * @returns {number}
58
+ */
59
+ function costOf(t, timeMs, model) {
60
+ const p = MODEL_PRICES[model] || FALLBACK_PRICE;
61
+ const peak = isPeakTime(timeMs) ? 1 : 0;
62
+ // cacheRead is charged at the cache-hit input price; reasoning tokens are billed
63
+ // as output per DeepSeek's model—fold reasoning into output for cost.
64
+ const inputCost = (t.cacheRead * p.cacheHit[peak] + (t.input) * p.cacheMiss[peak]) / 1e6;
65
+ const outputCost = ((t.output + t.reasoning || 0) * p.output[peak]) / 1e6;
66
+ return inputCost + outputCost;
67
+ }
68
+
24
69
  /**
25
70
  * Split a zstd buffer into its constituent frames. DSH writes multi-frame zstd
26
71
  * (one frame per chunk). We walk frame headers to find each frame boundary by
@@ -95,7 +140,7 @@ function zstdDecompressToText(buf) {
95
140
  }
96
141
 
97
142
  /** Normalize a token-usage event into our source-neutral shape. */
98
- function usagesFromEvent(obj, acc, currentModel) {
143
+ function usagesFromEvent(obj, acc, currentModel, timeMs) {
99
144
  if (!obj || typeof obj !== "object") return;
100
145
  const data = obj.data;
101
146
  if (!data) return;
@@ -122,13 +167,14 @@ function usagesFromEvent(obj, acc, currentModel) {
122
167
  const model = chunk.model || data.model || obj.model || currentModel || "unknown";
123
168
  if (model) {
124
169
  let m = acc.byModel[model];
125
- if (m === undefined) { m = acc.byModel[model] = { req: 0, input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0 }; }
170
+ if (m === undefined) { m = acc.byModel[model] = { req: 0, input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0, cost: 0 }; }
126
171
  m.req += 1;
127
172
  m.input += input;
128
173
  m.output += output;
129
174
  m.cacheRead += cacheRead;
130
175
  m.reasoning += reasoning;
131
176
  m.total += sum;
177
+ m.cost += costOf({ input, output, cacheRead, reasoning }, timeMs, model);
132
178
  }
133
179
  }
134
180
 
@@ -156,7 +202,7 @@ function findSessionLogs(root) {
156
202
  /** Aggregate token stats across all session logs under `root`. */
157
203
  function aggregateTokens(root) {
158
204
  const acc = {
159
- total: 0, input: 0, output: 0, cacheRead: 0, reasoning: 0,
205
+ total: 0, input: 0, output: 0, cacheRead: 0, reasoning: 0, cost: 0,
160
206
  events: 0, req: 0, cacheHits: 0, sessions: 0, files: 0,
161
207
  byModel: {}, byDay: {}, byWorkspace: {}
162
208
  };
@@ -204,22 +250,32 @@ function aggregateTokens(root) {
204
250
  const sum = inTok + outTok + cTok + rTok;
205
251
  const t = o.time ?? o.createdAt;
206
252
  const day = t ? new Date(t).toISOString().slice(0, 10) : null;
253
+ // cost of this single event (peak/off-peak aware, model-aware)
254
+ const cost = costOf({ input: inTok, output: outTok, cacheRead: cTok, reasoning: rTok }, t, currentModel || "unknown");
255
+ acc.cost += cost;
207
256
  if (day !== null) {
208
- acc.byDay[day] = (acc.byDay[day] || 0) + sum;
209
- if (day === todayKey) acc.today = (acc.today || 0) + sum;
210
- if (day.slice(0, 7) === monthKey) acc.thisMonth = (acc.thisMonth || 0) + sum;
257
+ let d = acc.byDay[day];
258
+ if (d === undefined) { d = acc.byDay[day] = { total: 0, input: 0, output: 0, cacheRead: 0, cost: 0 }; }
259
+ d.total += sum;
260
+ d.input += inTok;
261
+ d.output += outTok;
262
+ d.cacheRead += cTok;
263
+ d.cost += cost;
264
+ if (day === todayKey) { acc.today = (acc.today || 0) + sum; acc.todayCost = (acc.todayCost || 0) + cost; }
265
+ if (day.slice(0, 7) === monthKey) { acc.thisMonth = (acc.thisMonth || 0) + sum; acc.thisMonthCost = (acc.thisMonthCost || 0) + cost; }
211
266
  }
212
267
  // per-workspace broken-down counters
213
268
  let w = acc.byWorkspace[ws];
214
- if (w === undefined) { w = acc.byWorkspace[ws] = { req: 0, input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0 }; }
269
+ if (w === undefined) { w = acc.byWorkspace[ws] = { req: 0, input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0, cost: 0 }; }
215
270
  w.req += 1;
216
271
  w.input += inTok;
217
272
  w.output += outTok;
218
273
  w.cacheRead += cTok;
219
274
  w.reasoning += rTok;
220
275
  w.total += sum;
276
+ w.cost += cost;
221
277
  }
222
- usagesFromEvent(o, acc, currentModel);
278
+ usagesFromEvent(o, acc, currentModel, o.time ?? o.createdAt);
223
279
  }
224
280
  }
225
281
  return acc;
package/package.json CHANGED
@@ -1,49 +1,41 @@
1
- {
2
- "name": "@lyk308/dsh-token-dashboard",
3
- "version": "0.1.0",
4
- "description": "DeepSeek Harness 实时看板:账户余额 + Token 使用量(按模型/工作区/缓存命中),本地读取会话日志 + DeepSeek /user/balance。",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "exports": {
8
- ".": "./lib/index.js",
9
- "./client": "./lib/client.js",
10
- "./package.json": "./package.json"
11
- },
12
- "dsh": {
13
- "bundle": {
14
- "patch": "./cordis.patch.yml"
15
- },
16
- "client": {
17
- "inject": [
18
- "@deepseek-ai/dsh-client-runtime",
19
- "@deepseek-ai/dsh-client-locale",
20
- "@deepseek-ai/dsh-client-ui-slots",
21
- "@deepseek-ai/dsh-client-ui-primitives"
22
- ],
23
- "platform": "web"
24
- }
25
- },
26
- "files": [
27
- "lib/index.js",
28
- "lib/client.js",
29
- "cordis.patch.yml",
30
- "README.md",
31
- "README.zh.md",
32
- "LICENSE"
33
- ],
34
- "keywords": [
35
- "deepseek-harness",
36
- "dsh",
37
- "plugin",
38
- "token",
39
- "balance",
40
- "usage",
41
- "dashboard"
42
- ],
43
- "license": "MIT",
44
- "repository": {
45
- "type": "git",
46
- "url": "https://github.com/lyk308/dsh-token-dashboard.git"
47
- },
48
- "homepage": "https://github.com/lyk308/dsh-token-dashboard"
49
- }
1
+ {
2
+ "name": "@lyk308/dsh-token-dashboard",
3
+ "version": "0.1.1",
4
+ "description": "DeepSeek Harness 实时看板:账户余额 + Token 使用量(按模型/工作区/缓存命中),本地读取会话日志 + DeepSeek /user/balance。",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./client": "./lib/client.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "dsh": {
13
+ "bundle": {
14
+ "patch": "./cordis.patch.yml"
15
+ },
16
+ "client": {
17
+ "inject": [
18
+ "@deepseek-ai/dsh-client-runtime",
19
+ "@deepseek-ai/dsh-client-locale",
20
+ "@deepseek-ai/dsh-client-ui-slots",
21
+ "@deepseek-ai/dsh-client-ui-primitives"
22
+ ],
23
+ "platform": "web"
24
+ }
25
+ },
26
+ "files": [
27
+ "lib/index.js",
28
+ "lib/client.js",
29
+ "cordis.patch.yml"
30
+ ],
31
+ "keywords": [
32
+ "deepseek-harness",
33
+ "dsh",
34
+ "plugin",
35
+ "token",
36
+ "balance",
37
+ "usage",
38
+ "dashboard"
39
+ ],
40
+ "license": "MIT"
41
+ }