dsh-subscriptions 0.3.0 → 0.3.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
@@ -161,8 +161,8 @@ text. No live account was used to verify what Anthropic returns. See
161
161
 
162
162
  ## Install
163
163
 
164
- Current source is tested with DSH `0.1.7-rc.2`, `0.2.0-rc.2`, `0.2.1-rc.2`, and
165
- `0.2.1-rc.5`. The package's peers admit `>=0.1.7-rc.2 <0.3.0-0`: untested
164
+ Current source is tested with DSH `0.1.7-rc.2`, `0.2.0-rc.2`, `0.2.1-rc.5`, and
165
+ `0.2.2-rc.1`. The package's peers admit `>=0.1.7-rc.2 <0.3.0-0`: untested
166
166
  versions inside that range install but are unsupported, and versions outside it
167
167
  need an explicit `dsh plugin allow-version` exemption. `dsh plugin add` checks
168
168
  those ranges with `includePrerelease` semantics, so the admitted set covers
@@ -68,6 +68,7 @@ export declare function groupUsageDisplays(roster: readonly UsageRosterEntry[],
68
68
  export declare function loadBadgeRoster(rpc: ConnectionHandle['rpc']): Promise<{
69
69
  roster: UsageRosterEntry[];
70
70
  refreshed: Set<BadgeProvider>;
71
+ visibleModels: Map<BadgeProvider, string[]>;
71
72
  }>;
72
73
  export declare function usageOf(rpc: ConnectionHandle['rpc'], { provider, account }: UsageRosterEntry): Promise<ProviderUsage>;
73
74
  /**
@@ -119,7 +120,12 @@ export declare function rotatingDisplay(displays: readonly ProviderUsageDisplay[
119
120
  export declare function expandedDisplays(displays: readonly ProviderUsageDisplay[], current: string | undefined): readonly ProviderUsageDisplay[];
120
121
  type Translate = (key: SubscriptionsKey, params?: Record<string, unknown>) => string;
121
122
  export declare function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs, }: SubscriptionUsageBadgeProps): import("react").JSX.Element;
122
- /** Preview each account independently; all remaining quotas stay accessible. */
123
+ /** Warn only when every displayed Codex account has a known exhausted window. */
124
+ export declare function CodexExhaustionNotice({ display, translate, }: {
125
+ display: ProviderUsageDisplay;
126
+ translate: Translate;
127
+ }): import("react").JSX.Element | null;
128
+ /** Preview each account independently; remaining visible quotas stay accessible. */
123
129
  export declare function AccountWindows({ windows, model, provider, translate, observedAt, stale, }: {
124
130
  windows: readonly UsageWindow[];
125
131
  model: string | undefined;
@@ -35,6 +35,7 @@ import { en } from './locales.js';
35
35
  import { hostIcon } from './host-icons.js';
36
36
  import { HOST_CONTRACT, markerSelector } from './host-contract.js';
37
37
  import { USAGE_BADGE_REFRESH_EVENT, useUsageBadgeMode, } from './usage-badge-preferences.js';
38
+ import { visibleUsageWindows } from './usage-window-filter.js';
38
39
  /** DSH renamed the data icon in 0.1.7; retain older supported hosts too. */
39
40
  export function usageBadgeIcon(icons) {
40
41
  return hostIcon(icons, 'DataOutline');
@@ -115,6 +116,7 @@ export async function loadBadgeRoster(rpc) {
115
116
  ]);
116
117
  const roster = [];
117
118
  const refreshed = new Set();
119
+ const visibleModels = new Map();
118
120
  if (subscriptions.status === 'fulfilled') {
119
121
  for (const provider of [
120
122
  'codex',
@@ -124,6 +126,9 @@ export async function loadBadgeRoster(rpc) {
124
126
  'antigravity',
125
127
  ]) {
126
128
  refreshed.add(provider);
129
+ const visible = subscriptions.value.providers[provider]?.visibleModels;
130
+ if (visible !== undefined)
131
+ visibleModels.set(provider, visible);
127
132
  for (const account of accountsOf(subscriptions.value.providers[provider]))
128
133
  roster.push({ provider, account });
129
134
  }
@@ -144,7 +149,7 @@ export async function loadBadgeRoster(rpc) {
144
149
  });
145
150
  }
146
151
  }
147
- return { roster, refreshed };
152
+ return { roster, refreshed, visibleModels };
148
153
  }
149
154
  export async function usageOf(rpc, { provider, account }) {
150
155
  if (provider === 'opencode-go' ||
@@ -370,7 +375,7 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs, }) {
370
375
  return;
371
376
  inflightRef.current = true;
372
377
  try {
373
- const { roster, refreshed } = await loadBadgeRoster(rpc);
378
+ const { roster, refreshed, visibleModels } = await loadBadgeRoster(rpc);
374
379
  if (!mountedRef.current)
375
380
  return;
376
381
  const lastKnown = lastKnownRef.current;
@@ -421,6 +426,9 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs, }) {
421
426
  ...display,
422
427
  accounts: display.accounts.map((account) => ({
423
428
  ...account,
429
+ // Filter only the display row, not lastKnown: restoring visibility
430
+ // must work even when the next usage request fails and uses cache.
431
+ windows: visibleUsageWindows(display.provider, account.windows, visibleModels.get(display.provider)),
424
432
  ...observationsRef.current.get(`${display.provider}:${account.key}`),
425
433
  })),
426
434
  })),
@@ -612,7 +620,7 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs, }) {
612
620
  }, onMouseLeave: () => {
613
621
  setHover(false);
614
622
  }, onClick: toggle, children: [_jsx(UsageBadgeIcon, {}), _jsx("span", { style: styles.label, children: label })] }), open &&
615
- createPortal(_jsxs("div", { ref: panelRef, role: "dialog", "aria-label": title, className: "dsh-subscription-usage-panel", style: { ...styles.panel, ...(pos ?? MEASURE_STYLE) }, children: [_jsx("div", { style: styles.title, children: _jsxs("span", { style: styles.titleLabel, children: [_jsx(UsageBadgeIcon, {}), title] }) }), _jsx("div", { style: styles.titleRule, "aria-hidden": true }), expanded.map((d, index) => (_jsxs("section", { style: index === 0 ? undefined : styles.section, children: [_jsxs("div", { style: styles.providerRow, children: [_jsxs("span", { style: styles.providerName, children: [d.name, d.provider === current && (_jsx("span", { style: styles.currentTag, children: translate('usageBadgeCurrent') }))] }), d.accounts.length === 1 && (_jsx(AccountMeta, { account: d.accounts[0], translate: translate }))] }), d.accounts.map((account, accountIndex) => (_jsxs("div", { style: accountIndex === 0 ? undefined : styles.accountBlock, children: [d.accounts.length > 1 && (_jsx("div", { style: styles.accountRow, children: _jsx(AccountMeta, { account: account, translate: translate }) })), _jsx(AccountWindows, { windows: account.windows, observedAt: account.observedAt, stale: account.stale, model: d.provider === dialogProvider ? dialogModel : undefined, provider: d.provider, translate: translate }, `${d.provider}:${dialogModel ?? ''}`)] }, account.key)))] }, d.provider)))] }), document.body)] }));
623
+ createPortal(_jsxs("div", { ref: panelRef, role: "dialog", "aria-label": title, className: "dsh-subscription-usage-panel", style: { ...styles.panel, ...(pos ?? MEASURE_STYLE) }, children: [_jsx("div", { style: styles.title, children: _jsxs("span", { style: styles.titleLabel, children: [_jsx(UsageBadgeIcon, {}), title] }) }), _jsx("div", { style: styles.titleRule, "aria-hidden": true }), expanded.map((d, index) => (_jsxs("section", { style: index === 0 ? undefined : styles.section, children: [_jsxs("div", { style: styles.providerRow, children: [_jsxs("span", { style: styles.providerName, children: [d.name, d.provider === current && (_jsx("span", { style: styles.currentTag, children: translate('usageBadgeCurrent') }))] }), d.accounts.length === 1 && (_jsx(AccountMeta, { account: d.accounts[0], translate: translate }))] }), _jsx(CodexExhaustionNotice, { display: d, translate: translate }), d.accounts.map((account, accountIndex) => (_jsxs("div", { style: accountIndex === 0 ? undefined : styles.accountBlock, children: [d.accounts.length > 1 && (_jsx("div", { style: styles.accountRow, children: _jsx(AccountMeta, { account: account, translate: translate }) })), _jsx(AccountWindows, { windows: account.windows, observedAt: account.observedAt, stale: account.stale, model: d.provider === dialogProvider ? dialogModel : undefined, provider: d.provider, translate: translate }, `${d.provider}:${dialogModel ?? ''}`)] }, account.key)))] }, d.provider)))] }), document.body)] }));
616
624
  return (_jsxs(_Fragment, { children: [seat, statsRow !== null && statsRow.isConnected
617
625
  ? createPortal(pill, statsRow)
618
626
  : pill] }));
@@ -635,6 +643,15 @@ function statsScopeOf(seat) {
635
643
  }
636
644
  return seat.parentElement;
637
645
  }
646
+ /** Warn only when every displayed Codex account has a known exhausted window. */
647
+ export function CodexExhaustionNotice({ display, translate, }) {
648
+ if (display.provider !== 'codex' ||
649
+ display.accounts.length === 0 ||
650
+ display.accounts.some((account) => account.windows.length === 0 ||
651
+ !account.windows.some((window) => window.usedPercent >= 100)))
652
+ return null;
653
+ return (_jsx("p", { role: "status", style: { ...styles.providerMeta, margin: '8px 0 0' }, children: translate('usageBadgeAllCodexExhausted') }));
654
+ }
638
655
  /**
639
656
  * One account's handle and plan: `★ ys@example.com · 计划:pro`. The star
640
657
  * marks the default account (the one direct routes serve and the collapsed
@@ -649,8 +666,10 @@ function AccountMeta({ account, translate, }) {
649
666
  ].filter((part) => part !== undefined && part !== '');
650
667
  return (_jsxs("span", { style: styles.providerMeta, title: account.account, children: [account.isDefault && (_jsxs("span", { style: styles.defaultStar, "aria-label": "default", children: ["\u2605", ' '] })), parts.join(' · ')] }));
651
668
  }
652
- /** Preview each account independently; all remaining quotas stay accessible. */
669
+ /** Preview each account independently; remaining visible quotas stay accessible. */
653
670
  export function AccountWindows({ windows, model, provider, translate, observedAt, stale, }) {
671
+ if (windows.length === 0)
672
+ return (_jsx("p", { style: styles.providerMeta, children: translate('usageNoVisibleModels') }));
654
673
  const { shown, hidden } = previewWindows(windows, model, provider);
655
674
  const rows = (items) => (_jsx("dl", { style: styles.details, children: items.map((w, i) => (_jsx(WindowRow, { label: `${usageWindowLabel(translate, w)}${model !== undefined && w.scope === model ? ` · ${translate('usageBadgeCurrent')}` : ''}`, window: w, t: translate, observedAt: observedAt, stale: stale }, i))) }));
656
675
  return (_jsxs(_Fragment, { children: [rows(shown), hidden.length > 0 && (_jsxs("details", { style: styles.moreWindows, children: [_jsx("summary", { style: styles.moreSummary, children: translate('usageBadgeMoreWindows', { count: hidden.length }) }), rows(hidden)] }))] }));
@@ -188,6 +188,7 @@ export declare const en: {
188
188
  usageBadgeModelCount: string;
189
189
  usageBadgeModelUnavailable: string;
190
190
  usageBadgeMoreWindows: string;
191
+ usageBadgeAllCodexExhausted: string;
191
192
  speed: string;
192
193
  speedStandard: string;
193
194
  speedStandardDescription: string;
@@ -418,6 +419,7 @@ export declare const zh: {
418
419
  usageBadgeModelCount: string;
419
420
  usageBadgeModelUnavailable: string;
420
421
  usageBadgeMoreWindows: string;
422
+ usageBadgeAllCodexExhausted: string;
421
423
  speed: string;
422
424
  speedStandard: string;
423
425
  speedStandardDescription: string;
@@ -188,6 +188,7 @@ export const en = {
188
188
  usageBadgeModelCount: '{count} model quotas',
189
189
  usageBadgeModelUnavailable: 'Current model quota unavailable',
190
190
  usageBadgeMoreWindows: 'More quota windows ({count})',
191
+ usageBadgeAllCodexExhausted: 'All displayed Codex accounts report exhausted subscription quota. New requests stop before spending paid credits. After an external reset, use Settings > Subscriptions > Refresh to re-check usage.',
191
192
  speed: 'Speed',
192
193
  speedStandard: 'Standard',
193
194
  speedStandardDescription: 'Default speed',
@@ -426,6 +427,7 @@ export const zh = {
426
427
  usageBadgeModelCount: '{count} 个模型配额',
427
428
  usageBadgeModelUnavailable: '当前模型暂无配额数据',
428
429
  usageBadgeMoreWindows: '更多配额窗口({count})',
430
+ usageBadgeAllCodexExhausted: '当前显示的所有 Codex 账号都报告订阅额度已耗尽。新请求会在消耗付费额度前停止;外部额度重置后,请使用“设置 > 订阅 > 刷新”重新检查用量。',
429
431
  speed: '速度',
430
432
  speedStandard: '标准',
431
433
  speedStandardDescription: '默认速度',
package/lib/client.js CHANGED
@@ -223,6 +223,7 @@ const en = {
223
223
  usageBadgeModelCount: "{count} model quotas",
224
224
  usageBadgeModelUnavailable: "Current model quota unavailable",
225
225
  usageBadgeMoreWindows: "More quota windows ({count})",
226
+ usageBadgeAllCodexExhausted: "All displayed Codex accounts report exhausted subscription quota. New requests stop before spending paid credits. After an external reset, use Settings > Subscriptions > Refresh to re-check usage.",
226
227
  speed: "Speed",
227
228
  speedStandard: "Standard",
228
229
  speedStandardDescription: "Default speed",
@@ -453,6 +454,7 @@ const zh = {
453
454
  usageBadgeModelCount: "{count} 个模型配额",
454
455
  usageBadgeModelUnavailable: "当前模型暂无配额数据",
455
456
  usageBadgeMoreWindows: "更多配额窗口({count})",
457
+ usageBadgeAllCodexExhausted: "当前显示的所有 Codex 账号都报告订阅额度已耗尽。新请求会在消耗付费额度前停止;外部额度重置后,请使用“设置 > 订阅 > 刷新”重新检查用量。",
456
458
  speed: "速度",
457
459
  speedStandard: "标准",
458
460
  speedStandardDescription: "默认速度",
@@ -5793,6 +5795,7 @@ async function loadBadgeRoster(rpc) {
5793
5795
  const [subscriptions, external] = await Promise.allSettled([callSubscriptionsAuth(rpc, "status", {}), callSubscriptionsAuth(rpc, "externalStatus", {})]);
5794
5796
  const roster = [];
5795
5797
  const refreshed = /* @__PURE__ */ new Set();
5798
+ const visibleModels = /* @__PURE__ */ new Map();
5796
5799
  if (subscriptions.status === "fulfilled") for (const provider of [
5797
5800
  "codex",
5798
5801
  "claude",
@@ -5801,6 +5804,8 @@ async function loadBadgeRoster(rpc) {
5801
5804
  "antigravity"
5802
5805
  ]) {
5803
5806
  refreshed.add(provider);
5807
+ const visible = subscriptions.value.providers[provider]?.visibleModels;
5808
+ if (visible !== void 0) visibleModels.set(provider, visible);
5804
5809
  for (const account of accountsOf(subscriptions.value.providers[provider])) roster.push({
5805
5810
  provider,
5806
5811
  account
@@ -5824,7 +5829,8 @@ async function loadBadgeRoster(rpc) {
5824
5829
  }
5825
5830
  return {
5826
5831
  roster,
5827
- refreshed
5832
+ refreshed,
5833
+ visibleModels
5828
5834
  };
5829
5835
  }
5830
5836
  async function usageOf(rpc, { provider, account }) {
@@ -5993,7 +5999,7 @@ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
5993
5999
  if (rpc === void 0 || inflightRef.current) return;
5994
6000
  inflightRef.current = true;
5995
6001
  try {
5996
- const { roster, refreshed } = await loadBadgeRoster(rpc);
6002
+ const { roster, refreshed, visibleModels } = await loadBadgeRoster(rpc);
5997
6003
  if (!mountedRef.current) return;
5998
6004
  const lastKnown = lastKnownRef.current;
5999
6005
  const live = new Set(roster.map(({ provider, account }) => usageKeyOf(provider, account)));
@@ -6040,6 +6046,7 @@ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
6040
6046
  ...display,
6041
6047
  accounts: display.accounts.map((account) => ({
6042
6048
  ...account,
6049
+ windows: visibleUsageWindows(display.provider, account.windows, visibleModels.get(display.provider)),
6043
6050
  ...observationsRef.current.get(`${display.provider}:${account.key}`)
6044
6051
  }))
6045
6052
  })), ...previous.filter((display) => !refreshed.has(display.provider)).map((display) => ({
@@ -6226,35 +6233,42 @@ function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
6226
6233
  }),
6227
6234
  expanded.map((d, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6228
6235
  style: index === 0 ? void 0 : styles.section,
6229
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6230
- style: styles.providerRow,
6231
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6232
- style: styles.providerName,
6233
- children: [d.name, d.provider === current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6234
- style: styles.currentTag,
6235
- children: translate("usageBadgeCurrent")
6236
+ children: [
6237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6238
+ style: styles.providerRow,
6239
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6240
+ style: styles.providerName,
6241
+ children: [d.name, d.provider === current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6242
+ style: styles.currentTag,
6243
+ children: translate("usageBadgeCurrent")
6244
+ })]
6245
+ }), d.accounts.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
6246
+ account: d.accounts[0],
6247
+ translate
6236
6248
  })]
6237
- }), d.accounts.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
6238
- account: d.accounts[0],
6249
+ }),
6250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexExhaustionNotice, {
6251
+ display: d,
6239
6252
  translate
6240
- })]
6241
- }), d.accounts.map((account, accountIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6242
- style: accountIndex === 0 ? void 0 : styles.accountBlock,
6243
- children: [d.accounts.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6244
- style: styles.accountRow,
6245
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
6246
- account,
6253
+ }),
6254
+ d.accounts.map((account, accountIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6255
+ style: accountIndex === 0 ? void 0 : styles.accountBlock,
6256
+ children: [d.accounts.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6257
+ style: styles.accountRow,
6258
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMeta, {
6259
+ account,
6260
+ translate
6261
+ })
6262
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountWindows, {
6263
+ windows: account.windows,
6264
+ observedAt: account.observedAt,
6265
+ stale: account.stale,
6266
+ model: d.provider === dialogProvider ? dialogModel : void 0,
6267
+ provider: d.provider,
6247
6268
  translate
6248
- })
6249
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountWindows, {
6250
- windows: account.windows,
6251
- observedAt: account.observedAt,
6252
- stale: account.stale,
6253
- model: d.provider === dialogProvider ? dialogModel : void 0,
6254
- provider: d.provider,
6255
- translate
6256
- }, `${d.provider}:${dialogModel ?? ""}`)]
6257
- }, account.key))]
6269
+ }, `${d.provider}:${dialogModel ?? ""}`)]
6270
+ }, account.key))
6271
+ ]
6258
6272
  }, d.provider))
6259
6273
  ]
6260
6274
  }), document.body)
@@ -6279,6 +6293,18 @@ function statsScopeOf(seat) {
6279
6293
  }
6280
6294
  return seat.parentElement;
6281
6295
  }
6296
+ /** Warn only when every displayed Codex account has a known exhausted window. */
6297
+ function CodexExhaustionNotice({ display, translate }) {
6298
+ if (display.provider !== "codex" || display.accounts.length === 0 || display.accounts.some((account) => account.windows.length === 0 || !account.windows.some((window$1) => window$1.usedPercent >= 100))) return null;
6299
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6300
+ role: "status",
6301
+ style: {
6302
+ ...styles.providerMeta,
6303
+ margin: "8px 0 0"
6304
+ },
6305
+ children: translate("usageBadgeAllCodexExhausted")
6306
+ });
6307
+ }
6282
6308
  /**
6283
6309
  * One account's handle and plan: `★ ys@example.com · 计划:pro`. The star
6284
6310
  * marks the default account (the one direct routes serve and the collapsed
@@ -6296,8 +6322,12 @@ function AccountMeta({ account, translate }) {
6296
6322
  }), parts.join(" · ")]
6297
6323
  });
6298
6324
  }
6299
- /** Preview each account independently; all remaining quotas stay accessible. */
6325
+ /** Preview each account independently; remaining visible quotas stay accessible. */
6300
6326
  function AccountWindows({ windows, model, provider, translate, observedAt, stale }) {
6327
+ if (windows.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6328
+ style: styles.providerMeta,
6329
+ children: translate("usageNoVisibleModels")
6330
+ });
6301
6331
  const { shown, hidden } = previewWindows(windows, model, provider);
6302
6332
  const rows = (items) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
6303
6333
  style: styles.details,