dsh-llm-local-token 1.5.0 → 1.5.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
@@ -135,7 +135,8 @@ The badge shows **only the provider serving the currently selected model**: pick
135
135
  Codex's windows, switch to Claude and it swaps — the two are never mixed into one number. When the
136
136
  selected model belongs to another adapter (a plain API key, another plugin) the badge hides itself,
137
137
  because that quota is not this plugin's to report. The popover still lists every route, with the
138
- active one first and marked "current" and the rest dimmed. The selection comes from
138
+ active one first, marked "current" and expanded; the rest are collapsed to a header carrying their
139
+ first two windows, and open on click. Switching model re-applies that default. The selection comes from
139
140
  `ctx.modelDirectories`; a composition without that service (non-Web) falls back to the previous
140
141
  union-of-all-routes view.
141
142
 
package/README.zh.md CHANGED
@@ -120,7 +120,8 @@ GLM 的凭据按下面的顺序解析,这个顺序保证数字是诚实的 —
120
120
 
121
121
  徽标**只显示当前选中模型所属 provider** 的用量:选 Codex 就是 Codex 的窗口,切到 Claude 就换成
122
122
  Claude 的,不会把两家的数字混在一起。选中的模型由别的 adapter 提供(普通 API key、其他插件)时徽标
123
- 直接隐藏 —— 那份额度不属于本插件。展开的弹层仍列出所有路由,当前那条排在最前并标注「当前」,其余淡显。
123
+ 直接隐藏 —— 那份额度不属于本插件。展开的弹层仍列出所有路由:当前那条排在最前、标注「当前」并默认展开;
124
+ 其余折叠成一行,行内保留前两个窗口的百分比,点击即可展开。切换模型会重新套用这个默认。
124
125
  当前选中项来自 `ctx.modelDirectories`;组合里没有该服务时(非 Web)退回旧的「全部路由合并」显示。
125
126
 
126
127
  ## 环境要求
package/lib/client.js CHANGED
@@ -27,6 +27,25 @@ window.__ModuleLoader__.load({
27
27
  ".ltk_cur{padding:0 6px;border-radius:999px;font-size:10px;line-height:16px;background:var(--dsw-alias-state-success-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-label-primary)}" +
28
28
  ".ltk_provHead{display:flex;align-items:center;gap:6px;margin-bottom:6px}" +
29
29
  ".ltk_provName{flex:1;font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary)}" +
30
+ ".ltk_provToggle{width:100%;padding:0;border:none;background:none;font:inherit;color:inherit;cursor:pointer;text-align:left}" +
31
+ ".ltk_provFlat{margin-bottom:0}" +
32
+ // The chevron is the supplied 16px icon, inlined rather than fetched: one
33
+ // path, so it costs no request and no asset route. Source: docs/arrow.svg.
34
+ //
35
+ // Its artwork sits in the right half of the source canvas (x 8..12.1 of 16),
36
+ // so the viewBox is cropped square around the stroke instead of using the
37
+ // original 0 0 16 16 — otherwise nearly half the box is padding and the
38
+ // arrow looks indented against the window rows below it. Square keeps the
39
+ // 90deg open-state rotation pivoting on the artwork's own centre, so the
40
+ // row's left edge never moves when it toggles.
41
+ //
42
+ // Sized with the row's 10px badges, the smallest type in the panel. Note
43
+ // the box is all ink: a cropped SVG at 10px draws a 10px arrow, where a
44
+ // 10px text glyph drew roughly half that inside its em box — so this
45
+ // still reads larger than the character it replaced.
46
+ ".ltk_chev{flex:none;display:block;width:10px;height:10px;color:var(--dsw-alias-label-secondary);transform-origin:50% 50%;transition:transform .12s ease}" +
47
+ ".ltk_chevOpen{transform:rotate(90deg)}" +
48
+ ".ltk_sum{flex:none;font-size:11px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}" +
30
49
  ".ltk_plan{padding:0 6px;border-radius:999px;font-size:10px;line-height:16px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}" +
31
50
  ".ltk_row{margin-top:6px}" +
32
51
  ".ltk_rowTop{display:flex;align-items:baseline;gap:6px;font-size:11px;color:var(--dsw-alias-label-tertiary)}" +
@@ -54,6 +73,7 @@ window.__ModuleLoader__.load({
54
73
  current: "current",
55
74
  "unit.d": "{n} days", "unit.h": "{n} hours", "unit.m": "{n} min",
56
75
  readAt: "read {when} ago",
76
+ expand: "Show {name} quota", collapse: "Hide {name} quota",
57
77
  };
58
78
  const zh = {
59
79
  label: "额度", title: "订阅用量",
@@ -65,6 +85,7 @@ window.__ModuleLoader__.load({
65
85
  current: "当前",
66
86
  "unit.d": "{n} 天", "unit.h": "{n} 小时", "unit.m": "{n} 分钟",
67
87
  readAt: "{when}前读取",
88
+ expand: "展开 {name} 额度", collapse: "收起 {name} 额度",
68
89
  };
69
90
 
70
91
  /** Green under 60%, amber under 85%, red above. */
@@ -151,6 +172,18 @@ window.__ModuleLoader__.load({
151
172
  const activeProvider = props.useModelSelection((snapshot) => snapshot.current?.provider ?? null);
152
173
  const wrapRef = react.useRef(null);
153
174
  const [pos, setPos] = react.useState(null);
175
+ /**
176
+ * Which provider blocks the reader has explicitly opened or closed, by id.
177
+ *
178
+ * Only the route serving the current model is open by default; the rest
179
+ * fold away but stay one click from their numbers. An override is dropped
180
+ * when the selection changes, so switching model re-applies that default
181
+ * instead of stranding an unrelated block open.
182
+ */
183
+ const [openOverrides, setOpenOverrides] = react.useState({});
184
+ react.useEffect(() => {
185
+ setOpenOverrides({});
186
+ }, [activeProvider]);
154
187
  react.useEffect(() => {
155
188
  props.start();
156
189
  props.ensureSelection();
@@ -241,35 +274,69 @@ window.__ModuleLoader__.load({
241
274
  style: pos === null ? { left: "16px", bottom: "72px" } : { left: pos.left + "px", top: pos.top + "px", width: pos.width + "px", transform: pos.transform },
242
275
  children: [
243
276
  jsx("p", { className: "ltk_title", children: t("title") }),
244
- ...ordered.map((entry) => jsxs("div", {
245
- className: activeProvider !== null && !covers(entry, activeProvider) ? "ltk_prov ltk_provDim" : "ltk_prov",
246
- children: [
247
- jsxs("div", {
248
- className: "ltk_provHead",
249
- children: [
250
- jsx("span", { className: "ltk_provName", children: entry.displayName }),
251
- activeProvider === null || !covers(entry, activeProvider) ? null : jsx("span", { className: "ltk_cur", children: t("current") }),
252
- entry.usage?.plan === undefined ? null : jsx("span", { className: "ltk_plan", children: entry.usage.plan }),
253
- ],
254
- }),
255
- entry.usage === null
256
- ? jsx("p", { className: "ltk_hint", children: t("empty") })
257
- : jsxs("div", {
277
+ ...ordered.map((entry) => {
278
+ // Default: only the route serving the current model is open, because
279
+ // that is the one number this session is actually spending. With no
280
+ // model-selection service there is no "current", so everything opens,
281
+ // matching the union-of-all-routes fallback the badge already uses.
282
+ const defaultOpen = activeProvider === null || covers(entry, activeProvider);
283
+ const open = openOverrides[entry.provider] ?? defaultOpen;
284
+ // A collapsed row still carries its first two windows, so folding a
285
+ // route away costs the detail but not the glance.
286
+ const summary = entry.usage === null ? [] : entry.usage.windows.map((w) => Math.round(w.used * 100)).slice(0, 2);
287
+ return jsxs("div", {
288
+ className: activeProvider !== null && !covers(entry, activeProvider) ? "ltk_prov ltk_provDim" : "ltk_prov",
289
+ children: [
290
+ jsxs("button", {
291
+ type: "button",
292
+ className: open ? "ltk_provHead ltk_provToggle" : "ltk_provHead ltk_provToggle ltk_provFlat",
293
+ "aria-expanded": open,
294
+ title: t(open ? "collapse" : "expand", { name: entry.displayName }),
295
+ onClick: () => setOpenOverrides((prev) => ({ ...prev, [entry.provider]: !open })),
258
296
  children: [
259
- // Keyed by position: a provider may report two windows of the
260
- // same kind (GLM sends two token windows), so the label is not
261
- // unique and the order is stable.
262
- ...entry.usage.windows.map((w, index) => jsx(UsageRow, { t, entry: w }, index)),
263
- entry.usage.credits === undefined || entry.usage.creditsUnlimited === true
264
- ? null
265
- : jsx("p", { className: "ltk_hint", children: t("credits", { balance: String(entry.usage.credits) }) }),
266
- agoOf(entry.usage.at).length === 0
267
- ? null
268
- : jsx("p", { className: "ltk_hint", children: t("readAt", { when: agoOf(entry.usage.at) }) }),
297
+ jsx("svg", {
298
+ className: open ? "ltk_chev ltk_chevOpen" : "ltk_chev",
299
+ viewBox: "5.2 3.04 9.7 9.7",
300
+ "aria-hidden": "true",
301
+ // non-scaling-stroke keeps the 1.5px weight the icon was drawn with,
302
+ // independent of how far the cropped viewBox is scaled up.
303
+ children: jsx("path", {
304
+ d: "M8 3.79297L12.1012 7.89419L8 11.9954",
305
+ fill: "none",
306
+ stroke: "currentColor",
307
+ strokeWidth: 1.5,
308
+ strokeLinecap: "round",
309
+ strokeLinejoin: "round",
310
+ vectorEffect: "non-scaling-stroke",
311
+ }),
312
+ }),
313
+ jsx("span", { className: "ltk_provName", children: entry.displayName }),
314
+ activeProvider === null || !covers(entry, activeProvider) ? null : jsx("span", { className: "ltk_cur", children: t("current") }),
315
+ entry.usage?.plan === undefined ? null : jsx("span", { className: "ltk_plan", children: entry.usage.plan }),
316
+ open || summary.length === 0 ? null : jsx("span", { className: "ltk_sum", children: summary.map((p) => p + "%").join(" · ") }),
269
317
  ],
270
318
  }),
271
- ],
272
- }, entry.provider)),
319
+ !open
320
+ ? null
321
+ : entry.usage === null
322
+ ? jsx("p", { className: "ltk_hint", children: t("empty") })
323
+ : jsxs("div", {
324
+ children: [
325
+ // Keyed by position: a provider may report two windows of the
326
+ // same kind (GLM sends two token windows), so the label is not
327
+ // unique and the order is stable.
328
+ ...entry.usage.windows.map((w, index) => jsx(UsageRow, { t, entry: w }, index)),
329
+ entry.usage.credits === undefined || entry.usage.creditsUnlimited === true
330
+ ? null
331
+ : jsx("p", { className: "ltk_hint", children: t("credits", { balance: String(entry.usage.credits) }) }),
332
+ agoOf(entry.usage.at).length === 0
333
+ ? null
334
+ : jsx("p", { className: "ltk_hint", children: t("readAt", { when: agoOf(entry.usage.at) }) }),
335
+ ],
336
+ }),
337
+ ],
338
+ }, entry.provider);
339
+ }),
273
340
  ],
274
341
  }),
275
342
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-llm-local-token",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "DeepSeek Harness plugin: serve LLM calls with the OAuth tokens your local Codex CLI and Claude Code already hold, instead of a separately configured API key — plus a composer badge showing subscription usage for those routes and for GLM Coding Plan.",
5
5
  "keywords": [
6
6
  "deepseek-harness",