@hystudio/dsh-quota-pill 0.2.6 → 0.2.7

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.js CHANGED
@@ -1295,7 +1295,14 @@ window.__ModuleLoader__.load({
1295
1295
  h('div', { className: 'quota-pill-win-main' },
1296
1296
  h('div', { className: 'quota-pill-win-text' },
1297
1297
  h('span', { className: 'quota-pill-win-label', title: label }, label),
1298
- reset ? h('span', { className: 'quota-pill-win-reset', title: reset }, reset) : null,
1298
+ // Reserve the reset line even when there is nothing to show.
1299
+ // Conditionally omitting it made rows with a reset taller than those
1300
+ // without one, so the column of numbers did not line up.
1301
+ h('span', {
1302
+ className: 'quota-pill-win-reset',
1303
+ title: reset || undefined,
1304
+ style: reset ? undefined : { visibility: 'hidden' },
1305
+ }, reset || '\u00a0'),
1299
1306
  ),
1300
1307
  h('div', { className: 'quota-pill-win-gauge' },
1301
1308
  showCredits
@@ -1528,6 +1535,8 @@ window.__ModuleLoader__.load({
1528
1535
  '.quota-pill-win-unit{flex:none;margin-left:3px;color:var(--dsw-alias-label-tertiary)}',
1529
1536
 
1530
1537
  '.quota-pill-win-ring{flex:none;display:inline-flex}',
1538
+ // 固定行高:没有重置时间的行也要占住这一行,数字才对得齐
1539
+ '.quota-pill-win-reset{min-height:14px}',
1531
1540
  // Sidecar not running: show the fix instead of a bare dash.
1532
1541
  '.quota-pill-offline{display:flex;flex-direction:column;gap:6px;padding:10px 2px}',
1533
1542
  '.quota-pill-offline-title{font-weight:600;color:var(--dsw-alias-label-primary,inherit)}',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hystudio/dsh-quota-pill",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Quota pill for DeepSeek Harness: remaining quota for every LLM subscription you have, in one collapsible pill",
@@ -11,6 +11,27 @@ import { getJson } from './http.js';
11
11
  const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
12
12
  const round2 = (n) => Math.round(n * 100) / 100;
13
13
 
14
+ /**
15
+ * Normalise a reset timestamp to ISO, or null when there is none.
16
+ *
17
+ * Upstreams are inconsistent: some send ISO strings, some epoch milliseconds,
18
+ * and some use `0` to mean "this window has no reset pending" (Command Code's
19
+ * five-hour window does exactly that). Treating 0 as a date would render 1970.
20
+ */
21
+ function isoReset(v) {
22
+ if (v == null || v === '' || v === 0) return null;
23
+ if (typeof v === 'string') {
24
+ // Already ISO-ish: keep as-is so the client formats it consistently.
25
+ const t = Date.parse(v);
26
+ return Number.isFinite(t) ? new Date(t).toISOString() : null;
27
+ }
28
+ const n = Number(v);
29
+ if (!Number.isFinite(n) || n <= 0) return null;
30
+ // Seconds vs milliseconds: anything below ~year 33658 in ms is really seconds.
31
+ const ms = n < 1e11 ? n * 1000 : n;
32
+ return new Date(ms).toISOString();
33
+ }
34
+
14
35
  /** Expand `products` (window → used%) into the `windows` rows the pill renders. */
15
36
  function toWindows(products, resetByKey = {}) {
16
37
  const out = [];
@@ -217,7 +238,7 @@ export async function commandcode() {
217
238
  const pct = round2((Number(used) / Number(cap)) * 100);
218
239
  products[label] = pct;
219
240
  ratios.push(100 - pct);
220
- if (tight === null || pct > tight[1]) tight = [key, pct, w.resetAt];
241
+ if (tight === null || pct > tight[1]) tight = [key, pct, isoReset(w.resetAt)];
221
242
  }
222
243
 
223
244
  const monthlyLeft = cred.monthlyCredits;
@@ -237,14 +258,21 @@ export async function commandcode() {
237
258
 
238
259
  // Plan identity (GOAT / Pro / Max) lives on a sibling endpoint; failure there
239
260
  // must not lose the quota we already have.
261
+ // Plan identity and the monthly period live on a sibling endpoint, nested under
262
+ // `data`. Getting this wrong is silent: the quota still renders, just with a
263
+ // null plan and a monthly window that cannot show when it renews.
240
264
  let plan = null;
265
+ let monthlyReset = null;
241
266
  try {
242
267
  const sub = await getJson('https://api.commandcode.ai/alpha/billing/subscriptions', { token });
243
- if (sub.ok && sub.data) {
244
- plan = sub.data.plan || (sub.data.subscription && sub.data.subscription.plan) || null;
245
- }
268
+ const d = sub.ok && sub.data ? (sub.data.data || {}) : {};
269
+ plan = d.planId || null;
270
+ // currentPeriodEnd is the real monthly renewal — better than deriving it
271
+ // from the weekly window, which was my earlier guess.
272
+ monthlyReset = d.currentPeriodEnd || null;
246
273
  } catch {
247
274
  plan = null;
275
+ monthlyReset = null;
248
276
  }
249
277
 
250
278
  const remaining = round2(Math.min(...ratios));
@@ -261,8 +289,12 @@ export async function commandcode() {
261
289
  const r = {};
262
290
  for (const [label, key] of [['5h', 'fiveHour'], ['weekly', 'weekly']]) {
263
291
  const w = win[key] || {};
264
- if (w.resetAt) r[label] = w.resetAt;
292
+ const iso = isoReset(w.resetAt);
293
+ if (iso) r[label] = iso;
265
294
  }
295
+ // The monthly ceiling is derived, so its reset must come from the
296
+ // subscription period; the credits endpoint does not carry one.
297
+ if (monthlyReset) r.monthly = monthlyReset;
266
298
  return r;
267
299
  })()),
268
300
  raw: { source: 'api.commandcode.ai/alpha/billing/credits', plan },
@@ -72,6 +72,14 @@ function isoFromEpoch(sec) {
72
72
  }
73
73
 
74
74
 
75
+ /** Same reset for every product row — xAI reports one billing period for all. */
76
+ function fillReset(products, resetAt) {
77
+ const out = {};
78
+ if (!resetAt) return out;
79
+ for (const key of Object.keys(products || {})) out[key] = resetAt;
80
+ return out;
81
+ }
82
+
75
83
  /**
76
84
  * Expand `products` (window → used%) into the `windows` array the client
77
85
  * renders. The client shows one row per window with its own reset time, so an
@@ -142,7 +150,10 @@ export async function xai(model) {
142
150
  window: period.type || null,
143
151
  resetAt: period.end || cfg.billingPeriodEnd || null,
144
152
  products,
145
- windows: toWindows(products, {}),
153
+ // Every per-product row shares the account's billing period, so they all
154
+ // carry the same reset. Leaving it empty made those rows look like they
155
+ // never expire while the summary right above them showed a date.
156
+ windows: toWindows(products, fillReset(products, period.end || cfg.billingPeriodEnd)),
146
157
  raw: { source: 'billing?format=credits', config: cfg },
147
158
  };
148
159
  }
@@ -175,7 +186,7 @@ export async function xai(model) {
175
186
  window: period.type || null,
176
187
  resetAt: period.end || cfg.billingPeriodEnd || null,
177
188
  products,
178
- windows: toWindows(products, {}),
189
+ windows: toWindows(products, fillReset(products, period.end || cfg.billingPeriodEnd)),
179
190
  detail: {
180
191
  tokens: `${rl.remaining_tokens}/${rl.limit_tokens}`,
181
192
  requests: `${rl.remaining_requests}/${rl.limit_requests}`,