dsh-token-use 0.3.1 → 0.3.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/client/client.js CHANGED
@@ -164,6 +164,8 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
164
164
  versionChecked: "检查于 {time}",
165
165
  versionRefresh: "重新检查",
166
166
  versionRefreshing: "检查中…",
167
+ versionFailed: "本次检查未成功:{names}(显示的是上次结果)",
168
+ versionFailedTitle: "失败原因:{reason}",
167
169
  healthCheck: "健康检查",
168
170
  healthRunning: "检查中…",
169
171
  healthOk: "接口正常",
@@ -324,6 +326,8 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
324
326
  versionChecked: "Checked {time}",
325
327
  versionRefresh: "Check again",
326
328
  versionRefreshing: "Checking…",
329
+ versionFailed: "This check failed for {names} — showing the last successful result",
330
+ versionFailedTitle: "Reason: {reason}",
327
331
  healthCheck: "Health check",
328
332
  healthRunning: "Checking…",
329
333
  healthOk: "Endpoint healthy",
@@ -1217,10 +1221,11 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
1217
1221
  h("td", { className: "dshtu_verCell" }, entry.installed === null ? "—" : entry.installed),
1218
1222
  h("td", { className: "dshtu_verCell" }, entry.latest === null ? "—" : entry.latest),
1219
1223
  // The status stays a real table cell so its column lines up with the
1220
- // header; the inline-flex badge lives inside it.
1224
+ // header; the inline-flex badge lives inside it. A failed lookup never
1225
+ // mixes into this label — it is reported under the table instead.
1221
1226
  h("td", null, h("span", { className: "dshtu_state", "data-tone": tone },
1222
1227
  glyph === null ? null : h(glyph, {}),
1223
- entry.error === null ? label : label + "(" + entry.error + ")")));
1228
+ label)));
1224
1229
  }
1225
1230
 
1226
1231
  /** Uptime as `2d 3h` / `45m`, whichever reads better at this scale. */
@@ -1242,6 +1247,11 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
1242
1247
  return h("div", { className: "dshtu_card dshtu_meta" }, props.error === null ? t("loading") : t("error") + ": " + props.error);
1243
1248
  }
1244
1249
  var runtime = config.platform || {};
1250
+ // Packages whose last registry lookup failed: their row keeps the previous
1251
+ // answer, and the reason is reported once, under the table.
1252
+ var failed = (config.packages || []).filter(function (entry) { return entry.error !== null && entry.error !== undefined; });
1253
+ // Cap what reaches the tooltip: a cached error string can be arbitrary.
1254
+ var reasonOf = function (entry) { return String(entry.error).replace(/\s+/g, " ").slice(0, 140); };
1245
1255
  var health = props.health;
1246
1256
  var rows = [];
1247
1257
  if (health && health.state === "ok") {
@@ -1279,7 +1289,17 @@ window.__ModuleLoader__.load({ id: "dsh-token-use", factory: (require) => {
1279
1289
  disabled: props.refreshing === true,
1280
1290
  icon: iconRefresh === null ? null : h(iconRefresh, {}),
1281
1291
  onClick: props.onRefresh
1282
- }, props.refreshing === true ? t("versionRefreshing") : t("versionRefresh")))),
1292
+ }, props.refreshing === true ? t("versionRefreshing") : t("versionRefresh"))),
1293
+ failed.length === 0 ? null : h("div", {
1294
+ className: "dshtu_state",
1295
+ "data-tone": "warn",
1296
+ style: { marginTop: "8px" },
1297
+ // The reason rides the title, not the label: a lookup failure must not
1298
+ // be mistaken for a package's version verdict.
1299
+ title: t("versionFailedTitle").replace("{reason}", failed.map(function (entry) { return entry.label + ": " + reasonOf(entry); }).join(" / "))
1300
+ },
1301
+ iconWarn === null ? null : h(iconWarn, {}),
1302
+ h("span", null, t("versionFailed").replace("{names}", failed.map(function (entry) { return entry.label; }).join("、"))))),
1283
1303
  h("div", { className: "dshtu_card" },
1284
1304
  h("div", { className: "dshtu_head" }, iconGauge === null ? null : h(iconGauge, {}), h("h3", null, t("configRuntime"))),
1285
1305
  h("dl", { className: "dshtu_kv" }, [
package/lib/versions.js CHANGED
@@ -224,17 +224,39 @@ export class VersionStore {
224
224
  /**
225
225
  * The registry's install metadata (versions + dist-tags) — the abbreviated
226
226
  * packument, which is a fraction of the full document's size.
227
+ *
228
+ * `accept-encoding: identity` is deliberate: a proxy in front of a local
229
+ * machine can hand back a compressed body that never gets decoded (seen in the
230
+ * wild as brotli bytes), and these documents are small enough that asking for
231
+ * them uncompressed costs nothing. The body is read as text and validated
232
+ * before parsing, so a bad response produces a short diagnostic instead of a
233
+ * JSON error quoting raw binary.
234
+ *
235
+ * @param name - package name.
236
+ * @param variant - `abbreviated` (default) or `full` for the retry.
227
237
  */
228
- async fetchPackument(name) {
238
+ async fetchPackument(name, variant = 'abbreviated') {
229
239
  const url = `${this.registry}/${name.replace('/', '%2f')}`
230
240
  const response = await fetch(url, {
231
- headers: { accept: 'application/vnd.npm.install-v1+json', 'user-agent': USER_AGENT },
241
+ headers: {
242
+ accept: variant === 'full' ? 'application/json' : 'application/vnd.npm.install-v1+json',
243
+ 'accept-encoding': 'identity',
244
+ 'user-agent': USER_AGENT,
245
+ },
232
246
  signal: AbortSignal.timeout(this.timeoutMs),
233
247
  })
234
248
  if (!response.ok) throw new Error(`http ${response.status}`)
235
- const body = await response.json()
249
+ const type = response.headers.get('content-type') ?? ''
250
+ const text = await response.text()
251
+ if (!type.includes('json')) throw new Error(`content-type ${type.length > 0 ? type : '(none)'}, ${text.length} bytes`)
252
+ let body
253
+ try {
254
+ body = JSON.parse(text)
255
+ } catch {
256
+ throw new Error(`${text.length} bytes that are not JSON`)
257
+ }
236
258
  const versions = Object.keys(body?.versions ?? {})
237
- if (versions.length === 0) throw new Error('no versions in registry response')
259
+ if (versions.length === 0) throw new Error('no versions in the response')
238
260
  return { versions, tags: body['dist-tags'] ?? {} }
239
261
  }
240
262
 
@@ -254,18 +276,9 @@ export class VersionStore {
254
276
  const packages = { ...this.cache.packages }
255
277
  const errors = {}
256
278
  await Promise.all(names.map(async (name) => {
257
- try {
258
- const { versions, tags } = await this.fetchPackument(name)
259
- const next = typeof tags.next === 'string' ? tags.next : undefined
260
- packages[name] = {
261
- versions: versions.slice(-40),
262
- tags,
263
- next: next ?? null,
264
- latest: highestInChannel(versions, channelOf(next ?? '')) ?? highestInChannel(versions, 'stable') ?? versions[versions.length - 1],
265
- }
266
- } catch (error) {
267
- errors[name] = String(error?.message ?? error)
268
- }
279
+ const record = await this.lookup(name)
280
+ if (typeof record === 'string') errors[name] = record
281
+ else packages[name] = record
269
282
  }))
270
283
  this.cache = { checkedAt: Date.now(), packages, errors }
271
284
  this.persist()
@@ -277,6 +290,30 @@ export class VersionStore {
277
290
  }
278
291
  }
279
292
 
293
+ /**
294
+ * One package's registry record, or a short reason string when the lookup
295
+ * failed. A second attempt with the full document rides out the transient
296
+ * failures a proxy can introduce.
297
+ */
298
+ async lookup(name) {
299
+ let first
300
+ for (const variant of ['abbreviated', 'full']) {
301
+ try {
302
+ const { versions, tags } = await this.fetchPackument(name, variant)
303
+ const next = typeof tags.next === 'string' ? tags.next : undefined
304
+ return {
305
+ versions: versions.slice(-40),
306
+ tags,
307
+ next: next ?? null,
308
+ latest: highestInChannel(versions, channelOf(next ?? '')) ?? highestInChannel(versions, 'stable') ?? versions[versions.length - 1],
309
+ }
310
+ } catch (error) {
311
+ if (first === undefined) first = cleanMessage(error?.message ?? error)
312
+ }
313
+ }
314
+ return first
315
+ }
316
+
280
317
  persist() {
281
318
  try {
282
319
  mkdirSync(dirname(this.file), { recursive: true })
@@ -333,6 +370,16 @@ export class VersionStore {
333
370
  }
334
371
  }
335
372
 
373
+ /**
374
+ * Keep a diagnostic to one printable short line. Error text can quote a
375
+ * response body, and a panel has no business rendering raw bytes.
376
+ */
377
+ function cleanMessage(message) {
378
+ const text = String(message).replace(/[^\t\n\r\x20-\x7e]/g, '').replace(/\s+/g, ' ').trim()
379
+ if (text.length === 0) return 'unknown error'
380
+ return text.length > 140 ? `${text.slice(0, 137)}...` : text
381
+ }
382
+
336
383
  /** Single-quote a shell word so paths with spaces survive. */
337
384
  function shellQuote(value) {
338
385
  return `'${String(value).replaceAll("'", `'\\''`)}'`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-token-use",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Real-time token usage and estimated spend for DeepSeek Harness, in a four-tab settings panel: an overview with cost/totals cards, a trend chart with a daily-average line and a 12-month usage heatmap; by-model and by-project tables with day/month/year/date-range queries; and versions, runtime facts, a harness self-check and a one-click restart. Prices come from DeepSeek's official page, refreshed daily, peak/off-peak aware. · 实时 Token 用量与估算金额面板,四个 tab:概览(金额/总量卡片、带日均线的趋势图、近 12 个月使用热力图)、模型统计与项目统计(按天/月/年/时间段/全部查询)、配置信息(版本对比、运行环境、Harness 自检、一键重启)。定价取 DeepSeek 官网、每日自动更新,区分高峰/空闲。",
5
5
  "keywords": [
6
6
  "dsh",