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 +23 -3
- package/lib/versions.js +63 -16
- package/package.json +1 -1
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
|
-
|
|
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: {
|
|
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
|
|
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
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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.
|
|
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",
|