@byokit/usage 0.1.0 → 0.3.0

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/dist/windows.js CHANGED
@@ -1,27 +1,55 @@
1
1
  export const record = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
2
2
  const obj = (v) => record(v) ? v : {};
3
- function window(provider, kind, used, minutes, resetsAt, limited = false, limit) {
4
- if (typeof used !== 'number' || !Number.isFinite(used))
3
+ export function window(provider, kind, used, minutes, resetsAt, limited = false, limit) {
4
+ if (used !== undefined && (typeof used !== 'number' || !Number.isFinite(used))) {
5
+ if (!limited)
6
+ return [];
7
+ used = undefined;
8
+ }
9
+ if (used === undefined && !limited)
5
10
  return [];
6
- return [{ provider, kind, usedPercent: Math.max(0, Math.min(100, used)),
11
+ return [{ provider, kind, ...(typeof used === 'number' ? { usedPercent: Math.max(0, Math.min(100, used)) } : {}),
7
12
  ...(minutes !== undefined && Number.isFinite(minutes) && minutes > 0 ? { minutes } : {}),
8
13
  ...(Number.isFinite(resetsAt) ? { resetsAt } : {}), ...(limited ? { limited: true } : {}), ...(limit ? { limit } : {}) }];
9
14
  }
10
- /** Accepts both the statusline envelope and the provider's usage payload. */
15
+ /** Scope is an allowlisted descriptor, never arbitrary provider data. */
16
+ export function quotaScope(raw) {
17
+ const value = obj(raw);
18
+ const scope = {};
19
+ for (const key of ['model', 'surface']) {
20
+ if (typeof value[key] === 'string' && value[key].length > 0 && value[key].length <= 80 && !/[\x00-\x1f]/.test(value[key]))
21
+ scope[key] = value[key];
22
+ }
23
+ return Object.keys(scope).length ? scope : undefined;
24
+ }
25
+ /** Normalized rows override legacy aggregates of the same kind, even if incomplete. */
11
26
  export function claudeWindows(raw) {
12
27
  const source = obj(obj(raw).rate_limits ?? raw);
13
- return ['five_hour', 'seven_day'].flatMap((id) => {
28
+ const normalized = (Array.isArray(source.limits) ? source.limits : []).filter(record)
29
+ .filter((row) => ['session', 'weekly_all', 'weekly_scoped'].includes(String(row.kind)));
30
+ const rows = normalized.flatMap((w) => {
31
+ const used = typeof w.percent === 'number' && Number.isFinite(w.percent) ? Math.max(0, Math.min(100, w.percent)) : undefined;
32
+ const reset = typeof w.resets_at === 'number' ? w.resets_at * 1000 : Date.parse(String(w.resets_at));
33
+ const scope = quotaScope(w.scope);
34
+ return [{ provider: 'claude', kind: w.kind === 'session' ? 'session' : 'weekly',
35
+ ...(used !== undefined ? { usedPercent: used } : {}), ...(scope ? { scope } : {}),
36
+ ...(Number.isFinite(reset) ? { resetsAt: reset } : {}), ...(w.limit_reached === true ? { limited: true } : {}) }];
37
+ });
38
+ const legacy = ['five_hour', 'seven_day'].flatMap((id) => {
39
+ if (normalized.some((w) => w.kind === (id === 'five_hour' ? 'session' : 'weekly_all')))
40
+ return [];
14
41
  const w = obj(source[id]);
15
- return window('claude', id === 'five_hour' ? 'session' : 'weekly', Number.isFinite(w.utilization) ? w.utilization : w.used_percentage, id === 'five_hour' ? 300 : 10080, typeof w.resets_at === 'number' ? w.resets_at : Date.parse(String(w.resets_at)) / 1000);
42
+ return window('claude', id === 'five_hour' ? 'session' : 'weekly', Number.isFinite(w.utilization) ? w.utilization : w.used_percentage, id === 'five_hour' ? 300 : 10080, typeof w.resets_at === 'number' ? w.resets_at * 1000 : Date.parse(String(w.resets_at)));
16
43
  });
44
+ return [...rows, ...legacy];
17
45
  }
18
46
  /** Accepts the `usage` member; monthly length is deliberately absent. */
19
47
  export function goWindows(raw) {
20
48
  return ['rolling', 'weekly', 'monthly'].flatMap((kind) => {
21
49
  const w = obj(obj(raw)[kind]);
22
- if (typeof w.percent !== 'number' || w.percent < 0 || !['ok', 'rate-limited'].includes(String(w.status)))
50
+ if (typeof w.percent === 'number' && w.percent < 0 || !['ok', 'rate-limited'].includes(String(w.status)))
23
51
  return [];
24
- return window('opencode', kind, w.percent, kind === 'monthly' ? undefined : kind === 'weekly' ? 10080 : 300, Date.parse(String(w.resetsAt)) / 1000, w.status === 'rate-limited');
52
+ return window('opencode', kind, w.percent, kind === 'monthly' ? undefined : kind === 'weekly' ? 10080 : 300, Date.parse(String(w.resetsAt)), w.status === 'rate-limited');
25
53
  });
26
54
  }
27
55
  /** Accepts the `data.limits` member; unknown bucket sizes are skipped. */
@@ -31,7 +59,7 @@ export function zaiWindows(raw) {
31
59
  const key = `${w.unit}:${w.number}`;
32
60
  if (key !== '3:5' && key !== '6:1')
33
61
  return [];
34
- return window('zai', key === '3:5' ? 'session' : 'weekly', w.percentage, key === '3:5' ? 300 : 10080, Number(w.nextResetTime) / 1000);
62
+ return window('zai', key === '3:5' ? 'session' : 'weekly', w.percentage, key === '3:5' ? 300 : 10080, typeof w.nextResetTime === 'number' ? w.nextResetTime : undefined);
35
63
  });
36
64
  }
37
65
  export function codexWindows(result) {
@@ -43,13 +71,15 @@ export function codexWindows(result) {
43
71
  return [];
44
72
  const name = String(raw.limitName ?? raw.limitId ?? 'Codex').replace(/[^\x20-\x7e]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 80) || 'Codex';
45
73
  const rows = ['primary', 'secondary'].flatMap((key) => {
74
+ if (!record(raw[key]))
75
+ return [];
46
76
  const w = obj(raw[key]);
47
77
  const m = typeof w.windowDurationMins === 'number' && w.windowDurationMins > 0 && Number.isFinite(w.windowDurationMins) ? w.windowDurationMins : undefined;
48
- return window('codex', m === 300 ? 'session' : m === 10080 ? 'weekly' : m === 43200 ? 'monthly' : 'custom', w.usedPercent, m, typeof w.resetsAt === 'number' ? w.resetsAt : undefined, false, name.toLowerCase() === 'codex' ? undefined : name);
78
+ return window('codex', m === 300 ? 'session' : m === 10080 ? 'weekly' : m === 43200 ? 'monthly' : 'custom', w.usedPercent, m, typeof w.resetsAt === 'number' ? w.resetsAt * 1000 : undefined, raw.limitReached === true || w.limitReached === true, name.toLowerCase() === 'codex' ? undefined : name);
49
79
  }).sort((a, b) => (a.minutes ?? 0) - (b.minutes ?? 0));
50
80
  return rows.length ? [rows] : [];
51
81
  });
52
- const chosen = groups.flat().sort((a, b) => b.usedPercent - a.usedPercent).slice(0, 8);
82
+ const chosen = groups.flat().sort((a, b) => Number(b.limited === true) - Number(a.limited === true) || (b.usedPercent ?? -1) - (a.usedPercent ?? -1)).slice(0, 8);
53
83
  return groups.map((rows, ordinal) => ({ rows: rows.filter((w) => chosen.includes(w)), ordinal }))
54
- .filter((g) => g.rows.length).sort((a, b) => Math.max(...b.rows.map((w) => w.usedPercent)) - Math.max(...a.rows.map((w) => w.usedPercent)) || a.ordinal - b.ordinal).flatMap((g) => g.rows);
84
+ .filter((g) => g.rows.length).sort((a, b) => Math.max(...b.rows.map((w) => w.usedPercent ?? -1)) - Math.max(...a.rows.map((w) => w.usedPercent ?? -1)) || a.ordinal - b.ordinal).flatMap((g) => g.rows);
55
85
  }
package/dist/words.json CHANGED
@@ -5,5 +5,6 @@
5
5
  "code.no-plan": "This {name} account has no plan with limits.",
6
6
  "code.rate-limited": "{name} asked us to wait. Showing the last reading.",
7
7
  "code.unavailable": "{name} didn't answer. Trying again shortly.",
8
- "code.incomplete": "{name} answered in a way we can't read yet."
8
+ "code.incomplete": "{name} answered in a way we can't read yet.",
9
+ "code.refresh-failed": "This sign-in could not be renewed. Check it in its own app."
9
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/usage",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Read subscription usage windows per provider and account.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",