@feiyang666/dsh-usage-plugin 1.11.0 → 1.12.2

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 CHANGED
@@ -8,6 +8,30 @@
8
8
 
9
9
  ---
10
10
 
11
+ ## v1.12.2 (2026-08-23)
12
+
13
+ ### 修复 / Fixed
14
+
15
+ - **移动端宽表仍会逐字竖排(兜底加固)**(`lib/client.js`):v1.12.1 的修复依赖运行时向 `document.head` **注入全局样式表**(`.dsh-usage-table{min-width:720px}` + `@media (max-width:540px)` 折叠中间列)。在部分移动端运行环境(如经 mobile-remote 进出的手机页面)下该样式表因加载时序 / CSP 未生效,导致 9–11 列宽表每列被压到约 1 字符宽、标题与单元格逐字竖排、数据无法阅读。本版把 `min-width:720px` **直接写进表格的内联样式**(内联样式必然生效),窄屏时表格保持 720px、外层 `overflow-x` 容器横向滑动,彻底不再依赖样式表注入;原有注入样式表与 ≤540px 折叠逻辑保留,作为进一步优化的增强项。
16
+
17
+ ---
18
+
19
+ ## v1.12.1 (2026-08-23)
20
+
21
+ ### 修复 / Fixed
22
+
23
+ - **修复移动端「消耗表」「消耗明细」等宽表列标题逐字竖排的问题**(`lib/client.js`):窄屏下 9–11 列宽表每列被压到极限宽度,`word-break` 把「调用」「API 服务商」「输入·未命中」等表头拆成单字竖排。现为所有用量表格注入响应式 CSS:表格强制 `min-width: 720px`,外层 `overflow-x` 容器正常横向滚动、不再逐字换行;极窄屏(≤ 540px)下对宽表自动隐藏第 4 列至倒数第 2 列,只保留「模型 / 服务商 / 调用 / 总消耗」等主标识列与合计列,价格表等窄表不折叠、保留横向滚动。
24
+
25
+ ---
26
+
27
+ ## v1.12.0 (2026-08-23)
28
+
29
+ ### 新增 / New
30
+
31
+ - **SSE 实时推送端点**(`lib/index.js`):新增 `GET /usage/api/events` 事件流端点,数据变化(新调用记录写入 / 导入 / 清除等,即 `persistNow` 落盘成功)时向订阅者推送 `data: {"type":"changed","at":<时间戳>}`。桌面端数据中心等客户端订阅该流即可**即时**感知用量更新(毫秒级),不再依赖轮询。无订阅者时零开销,插件独立使用完全不受影响。
32
+
33
+ ---
34
+
11
35
  ## 开源贡献基础:合并 PR #6(@Martin-soaring-dev,2026-08-20)
12
36
 
13
37
  > 本期(以及后续 v1.9.2 / v1.9.3)建立在社区贡献分支 **`Martin-soaring-dev/prepare_for_contribution`** 之上。该分支由用户上传、经你合并(GitHub Pull Request #6,合并 commit `12a52be`),是插件转向对外开源贡献的形态基础。
@@ -23,6 +47,14 @@
23
47
 
24
48
  ---
25
49
 
50
+ ## v1.11.1 (2026-08-23)
51
+
52
+ - **修复:数据加载后页面空白**:加载动画三目分支误用逗号表达式,只返回最后一个表达式值(`null`),导致加载完成后各子视图全部不渲染;改为将四个子视图作为容器 `div` 的并列子元素,加载完成后正常显示。
53
+ - **计费说明改为面板级固定显示**:「高峰 / 空闲时段说明」固定在用量面板子页签下方,概览、用量日历、缓存命中列表、价格表四个页面切换时始终可见(含数据加载期间)。
54
+ - **当前时段精确显示**:用量面板顶部徽标与价格表页顶部新增「当前 · 工作日高峰时段 / 工作日空闲时段 / 周末空闲时段」动态状态并附说明文字;周末(自 2026-08-23 起)全天按空闲价计费,规则生效前仍按原规则区分。
55
+
56
+ ---
57
+
26
58
  ## v1.11.0 (2026-08-23)
27
59
 
28
60
  - **高峰 / 空闲时段说明**:概览、用量日历、缓存命中列表、价格表四个页面顶部统一新增「高峰 / 空闲时段说明」,清楚区分**工作日高峰**(周一至周五 9:00–12:00、14:00–18:00)、**工作日空闲**(其余时间)与**周末全天空闲**(自 2026-08-23 起按空闲价,此前仍按原规则)。
package/lib/client.js CHANGED
@@ -40,7 +40,9 @@ window.__ModuleLoader__.load({
40
40
  scroll: { overflowX: "auto", overflowY: "hidden", width: "100%", maxWidth: "100%", minWidth: 0 },
41
41
  // 表格宽度自适应容器(width:100% + 列内容允许换行),不再用 max-content 撑出横向滚动条;
42
42
  // 极窄窗口下容器仍保留 overflow-x 兜底。
43
- tbl: { width: "100%", borderCollapse: "collapse", fontSize: fs(12) },
43
+ // minWidth:720 直接写在内联样式里(内联样式必然生效),不依赖下方注入的全局样式表:
44
+ // 窄屏时表格保持 720px、外层 st.scroll 容器横向滚动,避免 9–11 列被压到逐字竖排。
45
+ tbl: { width: "100%", minWidth: 720, borderCollapse: "collapse", fontSize: fs(12) },
44
46
  th: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
45
47
  thFirst: { textAlign: "left", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word", opacity: 0.55, fontWeight: 500, lineHeight: 1.5 },
46
48
  td: { textAlign: "right", padding: "7px 8px", borderBottom: "1px solid rgba(128,128,128,.2)", whiteSpace: "normal", wordBreak: "break-word" },
@@ -82,6 +84,7 @@ window.__ModuleLoader__.load({
82
84
  selDayTitle: { fontSize: fs(13), fontWeight: 600, marginTop: 14 },
83
85
  billHint: { border: "1px solid rgba(90,140,255,.35)", background: "rgba(90,140,255,.08)", borderRadius: 8, padding: "10px 14px", marginTop: 10, fontSize: fs(12), lineHeight: 1.9 },
84
86
  billHintTitle: { fontWeight: 600, marginBottom: 4, color: "#5a8cff" },
87
+ nowPeriod: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", marginTop: 10, fontSize: fs(12) },
85
88
  loading: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, padding: "48px 0", color: "inherit" },
86
89
  spinner: { width: 28, height: 28, borderRadius: "50%", border: "3px solid rgba(90,140,255,.22)", borderTopColor: "#5a8cff", animation: "dshUsageSpin .8s linear infinite", flexShrink: 0 },
87
90
  loadText: { fontSize: fs(12), opacity: 0.65 }
@@ -91,7 +94,14 @@ window.__ModuleLoader__.load({
91
94
  if (typeof document !== "undefined" && document.head && !document.getElementById("dsh-usage-plugin-style")) {
92
95
  var styleSheetEl = document.createElement("style");
93
96
  styleSheetEl.id = "dsh-usage-plugin-style";
94
- styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}";
97
+ styleSheetEl.textContent = "@keyframes dshUsageSpin{to{transform:rotate(360deg)}}" +
98
+ // 所有用量表格:保证最小宽度,避免在窄屏被压缩导致标题/单元格逐字竖排;
99
+ // 外层 st.scroll 容器已设 overflow-x:auto,无法完整展示时可横向滚动。
100
+ ".dsh-usage-table{min-width:720px}" +
101
+ // 极窄屏 (≤ 540px):对带 collapse-mobile 的宽表隐藏第 4 列至倒数第 2 列,
102
+ // 只保留前 3 个主标识列 + 最后合计列(如 消耗表/明细表 保留 模型/服务商/调用/总消耗),
103
+ // 避免每列被压到极限宽度而逐字竖排。价格表等窄表不折叠,保留横向滚动。
104
+ "@media (max-width:540px){.dsh-usage-table.collapse-mobile tr>*:not(:nth-child(-n+3)):not(:last-child){display:none}.dsh-usage-table.collapse-mobile{min-width:320px}}";
95
105
  document.head.appendChild(styleSheetEl);
96
106
  }
97
107
 
@@ -141,6 +151,24 @@ window.__ModuleLoader__.load({
141
151
  var t = d.getUTCHours() * 60 + d.getUTCMinutes();
142
152
  return (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60);
143
153
  };
154
+ // 返回当前计费时段 { peak, label, desc },区分:工作日高峰 / 工作日空闲 / 周末空闲(周末自 2026-08-23 起全天按空闲价)
155
+ var periodNow = function (ts) {
156
+ ts = ts || Date.now();
157
+ var d = new Date(ts + 8 * 3600 * 1000);
158
+ var wd = d.getUTCDay(); // 0=周日 1=周一 … 6=周六
159
+ var isWeekend = wd === 0 || wd === 6;
160
+ var t = d.getUTCHours() * 60 + d.getUTCMinutes();
161
+ var inPeakHours = (t >= 9 * 60 && t < 12 * 60) || (t >= 14 * 60 && t < 18 * 60);
162
+ if (isWeekend) {
163
+ if (ts >= WEEKEND_FLAT_AT) return { peak: false, label: "周末空闲时段", desc: "周末(周六、周日)全天按空闲价计费" };
164
+ return inPeakHours
165
+ ? { peak: true, label: "周末高峰时段", desc: "2026-08-23 前仍按原规则分峰谷" }
166
+ : { peak: false, label: "周末空闲时段", desc: "2026-08-23 前仍按原规则分峰谷" };
167
+ }
168
+ return inPeakHours
169
+ ? { peak: true, label: "工作日高峰时段", desc: "周一至周五 9:00–12:00、14:00–18:00" }
170
+ : { peak: false, label: "工作日空闲时段", desc: "工作日其余时间" };
171
+ };
144
172
  // 计费档位选择:auto 用 autoCost(按生效日期自动切换),base 用 baseCost,其余用 peakValleyCost
145
173
  // 计费档位显示文案
146
174
  var regimeLabel = function (regime) {
@@ -592,7 +620,6 @@ window.__ModuleLoader__.load({
592
620
  function setCustomTo(v) { setToDate(v); setPreset("custom"); }
593
621
 
594
622
  return el("div", null,
595
- el(BillingHint, null),
596
623
  el("div", { style: st.calBar },
597
624
  el("div", { style: st.seg },
598
625
  presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
@@ -617,7 +644,7 @@ window.__ModuleLoader__.load({
617
644
  ),
618
645
  el("div", { style: st.sec }, "消耗表(按模型)"),
619
646
  el("div", { style: st.scroll },
620
- el("table", { style: st.tbl },
647
+ el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
621
648
  el("thead", null, el("tr", null,
622
649
  el("th", { style: st.thFirst }, "模型"), el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
623
650
  el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
@@ -657,7 +684,7 @@ window.__ModuleLoader__.load({
657
684
  ),
658
685
  el("div", { style: st.sec, marginTop: 14 }, "消耗明细(按 API 服务商 × 模型)"),
659
686
  el("div", { style: st.scroll },
660
- el("table", { style: st.tbl },
687
+ el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
661
688
  el("thead", null, el("tr", null,
662
689
  el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"),
663
690
  el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"),
@@ -774,7 +801,6 @@ window.__ModuleLoader__.load({
774
801
  function goToday() { setYm({ y: nowY, m: nowM }); setSelectedDay(bjKey(Date.now())); }
775
802
 
776
803
  return el("div", null,
777
- el(BillingHint, null),
778
804
  el("div", { style: st.calBar },
779
805
  el("button", { style: st.btn, onClick: prevMonth }, "‹ 上月"),
780
806
  el("span", { style: { fontSize: fs(14), fontWeight: 600, minWidth: 120, textAlign: "center" } }, y + "年" + (m + 1) + "月"),
@@ -808,7 +834,7 @@ window.__ModuleLoader__.load({
808
834
  monthRows.length === 0
809
835
  ? el("div", { style: st.empty }, "本月暂无记录。")
810
836
  : el("div", { style: st.scroll },
811
- el("table", { style: st.tbl },
837
+ el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
812
838
  el("thead", null, el("tr", null,
813
839
  el("th", { style: st.thFirst }, "日期"), el("th", { style: st.th }, "调用"), el("th", { style: st.th }, "高峰/空闲"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "高峰消耗"), el("th", { style: st.th }, "空闲消耗"), el("th", { style: st.th }, "总消耗(自动)")
814
840
  )),
@@ -839,7 +865,7 @@ window.__ModuleLoader__.load({
839
865
  selRecords.length === 0
840
866
  ? el("div", { style: st.empty }, "该日无记录。")
841
867
  : el("div", { style: st.scroll },
842
- el("table", { style: st.tbl },
868
+ el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
843
869
  el("thead", null, el("tr", null,
844
870
  el("th", { style: st.thFirst }, "时间(北京)"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"), el("th", { style: st.th }, "时段"), el("th", { style: st.th }, "结束"), el("th", { style: st.th }, "消耗")
845
871
  )),
@@ -924,7 +950,6 @@ window.__ModuleLoader__.load({
924
950
  var sums = splitTotals(filtered, regime);
925
951
 
926
952
  return el("div", null,
927
- el(BillingHint, null),
928
953
  el("div", { style: st.calBar },
929
954
  el("div", { style: st.seg },
930
955
  presetBtn("today", "今天"), presetBtn("7d", "近7天"), presetBtn("30d", "近30天"), presetBtn("all", "全部")
@@ -948,7 +973,7 @@ window.__ModuleLoader__.load({
948
973
  pageBtn("next", "下一页 ›", curPage >= totalPages, function () { setPage(curPage + 1); })
949
974
  ),
950
975
  el("div", { style: st.scroll },
951
- el("table", { style: st.tbl },
976
+ el("table", { className: "dsh-usage-table collapse-mobile", style: st.tbl },
952
977
  el("thead", null, el("tr", null,
953
978
  el("th", { style: st.thFirst }, "时间(北京)"), el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "输入·未命中"), el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "缓存写入"), el("th", { style: st.th }, "输出"), el("th", { style: st.th }, "推理"), el("th", { style: st.th }, "命中率"), el("th", { style: st.th }, "时段"), el("th", { style: st.th }, "结束"), el("th", { style: st.th }, "消耗")
954
979
  )),
@@ -1041,7 +1066,10 @@ window.__ModuleLoader__.load({
1041
1066
  });
1042
1067
 
1043
1068
  return el("div", null,
1044
- el(BillingHint, null),
1069
+ el("div", { style: st.nowPeriod },
1070
+ el("span", { style: periodNow().peak ? st.badgePeak : st.badgeValley }, "当前 · " + periodNow().label),
1071
+ el("span", { style: st.sub }, periodNow().desc)
1072
+ ),
1045
1073
  el("div", { style: st.sec }, "今日 $ / ¥ 汇率"),
1046
1074
  el("div", { style: st.cards, marginTop: 8 },
1047
1075
  el(Card, { label: "$ → ¥", value: fx.rate ? ("$1 = ¥" + Number(fx.rate).toFixed(4)) : "暂不可用", hint: fx.date ? ("汇率日期 " + fx.date) : "等待获取" }),
@@ -1072,7 +1100,7 @@ window.__ModuleLoader__.load({
1072
1100
  : null,
1073
1101
  showRegime(regime) === "base"
1074
1102
  ? el("div", { style: st.scroll },
1075
- el("table", { style: st.tbl },
1103
+ el("table", { className: "dsh-usage-table", style: st.tbl },
1076
1104
  el("thead", null, el("tr", null,
1077
1105
  el("th", { style: st.thFirst }, "模型"), el("th", { style: st.th }, "缓存命中(输入)"), el("th", { style: st.th }, "输入 · 未命中"), el("th", { style: st.th }, "输出")
1078
1106
  )),
@@ -1080,7 +1108,7 @@ window.__ModuleLoader__.load({
1080
1108
  )
1081
1109
  )
1082
1110
  : el("div", { style: st.scroll },
1083
- el("table", { style: st.tbl },
1111
+ el("table", { className: "dsh-usage-table", style: st.tbl },
1084
1112
  el("thead", null, el("tr", null,
1085
1113
  el("th", { style: st.thFirst }, "模型"),
1086
1114
  el("th", { style: st.th }, "空闲 · 缓存命中"), el("th", { style: st.th }, "空闲 · 输入"), el("th", { style: st.th }, "空闲 · 输出"),
@@ -1095,7 +1123,7 @@ window.__ModuleLoader__.load({
1095
1123
  el("div", { style: st.sec, marginTop: 18 }, "第三方平台价格与覆盖状态(2026-08-19 核验)"),
1096
1124
  el("div", { style: st.note, marginTop: 6 }, "只把能与记录中的 provider/model 可靠匹配的价格用于自动计费;美元价格先按 USD 计算,再乘 USD/CNY 汇率统一折算成人民币。"),
1097
1125
  el("div", { style: st.scroll, marginTop: 8 },
1098
- el("table", { style: st.tbl },
1126
+ el("table", { className: "dsh-usage-table", style: st.tbl },
1099
1127
  el("thead", null, el("tr", null,
1100
1128
  el("th", { style: st.thFirst }, "API 服务商"), el("th", { style: st.thFirst }, "模型 / 状态"),
1101
1129
  el("th", { style: st.th }, "缓存命中"), el("th", { style: st.th }, "输入"), el("th", { style: st.th }, "输出"), el("th", { style: st.thFirst }, "说明")
@@ -1232,7 +1260,7 @@ window.__ModuleLoader__.load({
1232
1260
 
1233
1261
  var records = data.records || [];
1234
1262
  var dataPath = data.dataPath || "";
1235
- var nowPeak = isPeakNow(Date.now());
1263
+ var nowPeriod = periodNow(Date.now());
1236
1264
  var effectiveAt = data.effectiveAt || 0;
1237
1265
  var nowEffective = effectiveAt ? (Date.now() >= effectiveAt) : true;
1238
1266
  return el("div", { style: st.root },
@@ -1243,7 +1271,7 @@ window.__ModuleLoader__.load({
1243
1271
  el("div", { style: st.title }, "用量与消耗"),
1244
1272
  el("div", { style: st.sub }, "记录插件激活后的每一次模型调用")
1245
1273
  ),
1246
- el("span", { style: nowPeak ? st.badgePeak : st.badgeValley }, nowPeak ? "当前 · 高峰时段" : "当前 · 空闲时段"),
1274
+ el("span", { style: nowPeriod.peak ? st.badgePeak : st.badgeValley }, "当前 · " + nowPeriod.label),
1247
1275
  el("span", { style: nowEffective ? st.badgeHit : st.badgeValley }, nowEffective ? "新价格已生效" : "新价格未生效")
1248
1276
  ),
1249
1277
  el("div", { style: st.actions },
@@ -1260,15 +1288,18 @@ window.__ModuleLoader__.load({
1260
1288
  }, t.t);
1261
1289
  })
1262
1290
  ),
1291
+ el(BillingHint, null),
1263
1292
  el("div", { style: st.note, marginTop: 6 },
1264
1293
  "计价说明:DeepSeek 官方与 SiliconFlow 按人民币价格;DigitalOcean 按美元官方价 × USD/CNY 汇率折算人民币;千问暂不计费;AMD GPU Cloud DeepSeek V4 Flash 免费按 ¥0。"
1265
1294
  ),
1266
1295
  loading
1267
1296
  ? el(LoadingView, null)
1268
- : (tab === "overview" ? el(OverviewView, { records: records, regime: "auto" }) : null,
1269
- tab === "calendar" ? el(CalendarView, { records: records, regime: "auto", days: data.days || [] }) : null,
1270
- tab === "cache" ? el(CacheListView, { records: records, regime: "auto" }) : null,
1271
- tab === "prices" ? el(PriceView, { pricing: data.pricing, effectiveAt: data.effectiveAt, fx: data.fx || {}, onFxRefresh: function () { api({ action: "fxRefresh" }).then(refresh).catch(function () {}); }, onChanged: refresh }) : null),
1297
+ : el("div", null,
1298
+ tab === "overview" ? el(OverviewView, { records: records, regime: "auto" }) : null,
1299
+ tab === "calendar" ? el(CalendarView, { records: records, regime: "auto", days: data.days || [] }) : null,
1300
+ tab === "cache" ? el(CacheListView, { records: records, regime: "auto" }) : null,
1301
+ tab === "prices" ? el(PriceView, { pricing: data.pricing, effectiveAt: data.effectiveAt, fx: data.fx || {}, onFxRefresh: function () { api({ action: "fxRefresh" }).then(refresh).catch(function () {}); }, onChanged: refresh }) : null
1302
+ ),
1272
1303
  el("div", { style: st.actions, marginTop: 4 },
1273
1304
  el("button", { style: st.btn, onClick: function () { doExport("csv"); } }, "导出 CSV"),
1274
1305
  el("button", { style: st.btn, onClick: function () { doExport("json"); } }, "导出 JSON"),
package/lib/index.js CHANGED
@@ -286,10 +286,21 @@ export default {
286
286
  return undefined
287
287
  }
288
288
 
289
+ // SSE 实时推送:数据变化(新记录写入 / 导入 / 清除等)时通知订阅者。
290
+ // 订阅端点 /usage/api/events。无订阅者时是 no-op,插件独立使用不受影响。
291
+ const sseClients = new Set()
292
+ function notifyChanged() {
293
+ if (sseClients.size === 0) return
294
+ const payload = 'data: {"type":"changed","at":' + Date.now() + '}\n\n'
295
+ for (const res of sseClients) {
296
+ try { res.write(payload) } catch (e) { sseClients.delete(res) }
297
+ }
298
+ }
299
+
289
300
  function persistNow() {
290
301
  if (!dataPath || !persistOk) return Promise.resolve()
291
302
  const text = JSON.stringify(records)
292
- writeChain = writeChain.then(() => nfsWriteText(dataPath, text)).catch(() => {})
303
+ writeChain = writeChain.then(() => nfsWriteText(dataPath, text)).then(() => { notifyChanged() }).catch(() => {})
293
304
  return writeChain
294
305
  }
295
306
 
@@ -1182,6 +1193,27 @@ export default {
1182
1193
  } catch (e) {
1183
1194
  push('route-register-threw: ' + (e && e.stack ? e.stack : msg(e)))
1184
1195
  }
1196
+ try {
1197
+ // SSE 实时推送端点:数据变化时向订阅者推送 {"type":"changed"}。
1198
+ // 桌面端数据中心订阅该流即可即时感知用量数据更新。
1199
+ webServer.register({
1200
+ kind: 'exact',
1201
+ path: '/usage/api/events',
1202
+ handler: (req, res) => {
1203
+ res.writeHead(200, {
1204
+ 'Content-Type': 'text/event-stream; charset=utf-8',
1205
+ 'Cache-Control': 'no-cache',
1206
+ Connection: 'keep-alive',
1207
+ 'X-Accel-Buffering': 'no',
1208
+ })
1209
+ res.write(':ok\n\n')
1210
+ sseClients.add(res)
1211
+ req.on('close', () => { sseClients.delete(res) })
1212
+ }
1213
+ })
1214
+ } catch (e) {
1215
+ push('events-route-register-threw: ' + msg(e))
1216
+ }
1185
1217
  } else {
1186
1218
  push('route-not-registered (no webServer)')
1187
1219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feiyang666/dsh-usage-plugin",
3
- "version": "1.11.0",
3
+ "version": "1.12.2",
4
4
  "description": "DeepSeek Harness usage & cost tracker plugin: per-call token/cache-hit stats, peak/off-peak billing, DeepSeek balance query, CSV/JSON/PNG export with custom destination, and persistent local storage. Ships a host half plus a web client half in one npm package; installs into a DSH profile as a dsh.bundle with one command (dsh plugin --profile web add @feiyang666/dsh-usage-plugin).",
5
5
  "keywords": [
6
6
  "deepseek",
@@ -4,7 +4,8 @@ import fs from 'node:fs'
4
4
  import path from 'node:path'
5
5
 
6
6
  const REPO = 'feiyang-dev/dsh-usage-plugin'
7
- const TAG = 'v1.11.0'
7
+ const VERSION = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')).version
8
+ const TAG = `v${VERSION}`
8
9
 
9
10
  function getCredential() {
10
11
  const input = 'protocol=https\nhost=github.com\n\n'
@@ -76,9 +77,10 @@ if (created.status !== 201) {
76
77
  const release = JSON.parse(created.body)
77
78
  console.log('release id:', release.id, 'url:', release.html_url)
78
79
 
79
- // 打包并上传 tarball 作为附件(Windows 下 npm 命令名为 npm.cmd,spawnSync 需要完整命令名)
80
- const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'
81
- const pack = spawnSync(npmCmd, ['pack'], { encoding: 'utf8' })
80
+ // 打包并上传 tarball 作为附件(Windows 下 npm npm.cmd 批处理脚本,经 cmd /c 执行)
81
+ const pack = process.platform === 'win32'
82
+ ? spawnSync('cmd', ['/c', 'npm', 'pack'], { encoding: 'utf8' })
83
+ : spawnSync('npm', ['pack'], { encoding: 'utf8' })
82
84
  const tgzName = (pack.stdout || '').trim().split('\n').pop()
83
85
  if (tgzName && fs.existsSync(tgzName)) {
84
86
  const buf = fs.readFileSync(tgzName)
@@ -3,7 +3,7 @@ import https from 'node:https'
3
3
  import fs from 'node:fs'
4
4
 
5
5
  const REPO = 'feiyang-dev/dsh-usage-plugin'
6
- const RELEASE_ID = 375097521
6
+ const RELEASE_ID = 375098880
7
7
 
8
8
  function getCredential() {
9
9
  const input = 'protocol=https\nhost=github.com\n\n'