dsh-subscriptions 0.1.5 → 0.2.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.
Files changed (110) hide show
  1. package/README.md +212 -154
  2. package/lib/auth/claude-code-creds.js +31 -9
  3. package/lib/auth/device-flow.js +16 -9
  4. package/lib/auth/oauth-flow.js +13 -10
  5. package/lib/auth/rpc.d.ts +73 -2
  6. package/lib/auth/rpc.js +124 -23
  7. package/lib/auth/store.js +80 -31
  8. package/lib/client/CursorCard.d.ts +1 -1
  9. package/lib/client/CursorCard.js +50 -16
  10. package/lib/client/ExternalUsageCards.d.ts +1 -1
  11. package/lib/client/ExternalUsageCards.js +37 -16
  12. package/lib/client/ImageGallery.d.ts +2 -2
  13. package/lib/client/ImageGallery.js +92 -33
  14. package/lib/client/ImageGenerateToolview.js +50 -14
  15. package/lib/client/OllamaCard.d.ts +13 -0
  16. package/lib/client/OllamaCard.js +94 -0
  17. package/lib/client/OllamaSettingsManager.d.ts +24 -0
  18. package/lib/client/OllamaSettingsManager.js +329 -0
  19. package/lib/client/ProviderAccountManager.d.ts +1 -1
  20. package/lib/client/ProviderAccountManager.js +164 -49
  21. package/lib/client/ProviderModelEditor.js +109 -26
  22. package/lib/client/SpeedSelect.d.ts +62 -14
  23. package/lib/client/SpeedSelect.js +257 -46
  24. package/lib/client/SubscriptionUsageBadge.d.ts +5 -11
  25. package/lib/client/SubscriptionUsageBadge.js +301 -112
  26. package/lib/client/SubscriptionsSection.d.ts +2 -0
  27. package/lib/client/SubscriptionsSection.js +259 -85
  28. package/lib/client/UsageBadgeDisplaySetting.js +35 -12
  29. package/lib/client/UsageMeter.d.ts +1 -1
  30. package/lib/client/UsageMeter.js +69 -27
  31. package/lib/client/VideoGenerateToolview.js +69 -17
  32. package/lib/client/account-preferences.d.ts +8 -0
  33. package/lib/client/account-preferences.js +20 -5
  34. package/lib/client/host-contract.d.ts +11 -2
  35. package/lib/client/host-contract.js +20 -3
  36. package/lib/client/index.d.ts +5 -5
  37. package/lib/client/index.js +34 -12
  38. package/lib/client/locales.d.ts +78 -0
  39. package/lib/client/locales.js +121 -34
  40. package/lib/client/ollama-draft.d.ts +229 -0
  41. package/lib/client/ollama-draft.js +255 -0
  42. package/lib/client/reset-action.js +6 -4
  43. package/lib/client/reset-credits-view.d.ts +1 -1
  44. package/lib/client/reset-credits-view.js +66 -17
  45. package/lib/client/subscription-card-styles.js +94 -24
  46. package/lib/client/usage-color-preferences.js +4 -2
  47. package/lib/client/usage-pace.d.ts +7 -0
  48. package/lib/client/usage-pace.js +43 -10
  49. package/lib/client.js +1647 -266
  50. package/lib/client.js.map +1 -1
  51. package/lib/http.js +1 -1
  52. package/lib/index.d.ts +28 -3
  53. package/lib/index.js +9329 -8217
  54. package/lib/model-defaults.js +4 -2
  55. package/lib/provider-settings.d.ts +14 -1
  56. package/lib/provider-settings.js +64 -20
  57. package/lib/providers/account-preferences.js +51 -22
  58. package/lib/providers/accounts.js +15 -9
  59. package/lib/providers/antigravity-oauth-client.js +2 -2
  60. package/lib/providers/antigravity.js +141 -68
  61. package/lib/providers/catalog-store.js +120 -41
  62. package/lib/providers/claude-cli-version.js +3 -3
  63. package/lib/providers/claude.d.ts +23 -3
  64. package/lib/providers/claude.js +167 -92
  65. package/lib/providers/codex-client-version.js +3 -3
  66. package/lib/providers/codex-search.js +40 -13
  67. package/lib/providers/codex.js +196 -112
  68. package/lib/providers/common.d.ts +14 -0
  69. package/lib/providers/common.js +118 -38
  70. package/lib/providers/copilot.js +143 -82
  71. package/lib/providers/cursor-adapter.js +42 -18
  72. package/lib/providers/cursor-auth.d.ts +1 -1
  73. package/lib/providers/cursor-auth.js +69 -24
  74. package/lib/providers/cursor-model-settings.js +14 -5
  75. package/lib/providers/cursor-usage.js +27 -13
  76. package/lib/providers/external-usage-controller.d.ts +9 -2
  77. package/lib/providers/external-usage-controller.js +30 -7
  78. package/lib/providers/external-usage.d.ts +8 -0
  79. package/lib/providers/external-usage.js +140 -20
  80. package/lib/providers/grok.js +152 -89
  81. package/lib/providers/image-pool.js +29 -11
  82. package/lib/providers/minimax-usage.js +31 -11
  83. package/lib/providers/npm-cli-version.js +10 -3
  84. package/lib/providers/ollama-settings.d.ts +90 -0
  85. package/lib/providers/ollama-settings.js +213 -0
  86. package/lib/providers/ollama-web.d.ts +46 -0
  87. package/lib/providers/ollama-web.js +231 -0
  88. package/lib/providers/ollama.d.ts +107 -0
  89. package/lib/providers/ollama.js +608 -0
  90. package/lib/providers/pool-family.js +11 -3
  91. package/lib/providers/pool-health.js +28 -8
  92. package/lib/providers/pool-usage.js +16 -9
  93. package/lib/providers/pool.d.ts +8 -0
  94. package/lib/providers/pool.js +81 -42
  95. package/lib/providers/rate-limit.js +8 -2
  96. package/lib/providers/replay.js +40 -17
  97. package/lib/providers/reset-redemption.js +18 -6
  98. package/lib/tools/image-generate.js +123 -65
  99. package/lib/tools/registration.js +5 -2
  100. package/lib/tools/video-generate.js +51 -31
  101. package/lib/tools/x-search.js +50 -22
  102. package/lib/translate/anthropic.js +85 -37
  103. package/lib/translate/antigravity-schema.js +71 -28
  104. package/lib/translate/antigravity-thinking.js +22 -9
  105. package/lib/translate/antigravity.js +141 -50
  106. package/lib/translate/chat-completions.js +67 -23
  107. package/lib/translate/resolved.js +55 -21
  108. package/lib/translate/responses.js +69 -29
  109. package/lib/translate/sse.js +4 -1
  110. package/package.json +26 -4
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Draft rules of the Ollama Cloud settings dialog, kept free of React and the
3
+ * DOM so they can be unit tested. The node half owns the shapes below; the
4
+ * client restates them locally because the browser bundle does not import
5
+ * node-side modules.
6
+ */
7
+ /**
8
+ * Append the checked candidates to the draft's model list. Existing rows are
9
+ * never replaced or reordered, ids already present are skipped, and adopted
10
+ * rows keep the candidate order. Optional fields are set only when the
11
+ * candidate advertises a value, because `exactOptionalPropertyTypes` (and the
12
+ * store) must not see an explicit `undefined`.
13
+ * @param current - the draft list before adoption.
14
+ * @param candidates - the cloud list as fetched, in display order.
15
+ * @param selectedIds - ids the user checked.
16
+ * @returns a new list; the input array is not mutated.
17
+ */
18
+ export function adoptCandidates(current, candidates, selectedIds) {
19
+ const models = [...(current ?? [])];
20
+ const present = new Set(models.map((model) => model.id));
21
+ const selected = new Set(selectedIds);
22
+ for (const candidate of candidates) {
23
+ if (!selected.has(candidate.id) || present.has(candidate.id))
24
+ continue;
25
+ present.add(candidate.id);
26
+ models.push({
27
+ id: candidate.id,
28
+ name: candidate.name,
29
+ ...(candidate.contextWindow === undefined
30
+ ? {}
31
+ : { contextWindow: candidate.contextWindow }),
32
+ ...(candidate.vision ? { vision: true } : {}),
33
+ ...(candidate.thinking ? { thinking: true } : {}),
34
+ // The vendor's default is not copied: defaultEffort in the store is
35
+ // the user's explicit choice only, so the resolver's priority
36
+ // (user, then vendor, then family) keeps the vendor default live
37
+ // in discovery while the selector stays an override.
38
+ ...(candidate.efforts.length ? { efforts: [...candidate.efforts] } : {}),
39
+ });
40
+ }
41
+ return models;
42
+ }
43
+ /**
44
+ * Swap one model with its neighbour. An id that is absent or a move past
45
+ * either end returns the same order.
46
+ * @param models - the current list.
47
+ * @param id - the row to move.
48
+ * @param delta - `-1` moves up, `1` moves down.
49
+ * @returns a new list; the input array is not mutated.
50
+ */
51
+ export function moveModel(models, id, delta) {
52
+ const next = [...models];
53
+ const index = next.findIndex((model) => model.id === id);
54
+ const target = index + delta;
55
+ if (index === -1 || target < 0 || target >= next.length)
56
+ return next;
57
+ const moved = next[index];
58
+ next[index] = next[target];
59
+ next[target] = moved;
60
+ return next;
61
+ }
62
+ /**
63
+ * Drop one model from the list.
64
+ * @param models - the current list.
65
+ * @param id - the row to drop; an unknown id leaves the list unchanged.
66
+ * @returns a new list; the input array is not mutated.
67
+ */
68
+ export function removeModel(models, id) {
69
+ return models.filter((model) => model.id !== id);
70
+ }
71
+ /**
72
+ * Short context-window label for a model row.
73
+ * @param contextWindow - the advertised window in tokens.
74
+ * @returns `'1M'`, `'262K'`, the bare number below 1000, or `undefined` when unknown.
75
+ */
76
+ export function contextLabel(contextWindow) {
77
+ if (contextWindow === undefined)
78
+ return undefined;
79
+ if (contextWindow < 1000)
80
+ return String(contextWindow);
81
+ if (contextWindow >= 1_000_000) {
82
+ const millions = contextWindow / 1_000_000;
83
+ return `${Number.isInteger(millions) ? millions : Number(millions.toFixed(1))}M`;
84
+ }
85
+ return `${Math.round(contextWindow / 1000)}K`;
86
+ }
87
+ /**
88
+ * Rename one model row. An empty display name deletes the field so the
89
+ * validator keeps accepting the draft; a blank id leaves the list unchanged.
90
+ * @param models - the current list.
91
+ * @param id - the row to rename; an unknown id leaves the list unchanged.
92
+ * @param name - the new display name; whitespace trims before storing.
93
+ * @returns a new list; the input array is not mutated.
94
+ */
95
+ export function renameModel(models, id, name) {
96
+ return models.map((model) => {
97
+ if (model.id !== id)
98
+ return model;
99
+ const next = { ...model };
100
+ if (name === '')
101
+ delete next.name;
102
+ else
103
+ next.name = name;
104
+ return next;
105
+ });
106
+ }
107
+ /**
108
+ * Set or clear one model row's default reasoning effort. An empty choice
109
+ * deletes the field so the resolver falls back to the vendor or family
110
+ * default.
111
+ * @param models - the current list.
112
+ * @param id - the row to edit; an unknown id leaves the list unchanged.
113
+ * @param effort - the chosen level, or `''` to clear.
114
+ * @returns a new list; the input array is not mutated.
115
+ */
116
+ export function setModelEffort(models, id, effort) {
117
+ return models.map((model) => {
118
+ if (model.id !== id)
119
+ return model;
120
+ if (effort === '') {
121
+ const next = { ...model };
122
+ delete next.defaultEffort;
123
+ return next;
124
+ }
125
+ return { ...model, defaultEffort: effort };
126
+ });
127
+ }
128
+ /**
129
+ * Set or clear one model row's local context-window override. `undefined`
130
+ * deletes the field so the model follows the endpoint again; the runtime
131
+ * caps a stored value at the advertised window.
132
+ * @param models - the current list.
133
+ * @param id - the row to edit; an unknown id leaves the list unchanged.
134
+ * @param tokens - a positive safe integer, or `undefined` to clear.
135
+ * @returns a new list; the input array is not mutated.
136
+ */
137
+ export function setModelContext(models, id, tokens) {
138
+ return models.map((model) => {
139
+ if (model.id !== id)
140
+ return model;
141
+ const next = { ...model };
142
+ if (tokens === undefined)
143
+ delete next.contextOverride;
144
+ else
145
+ next.contextOverride = tokens;
146
+ return next;
147
+ });
148
+ }
149
+ /**
150
+ * Parse the context input's text. Only a positive safe integer is a value;
151
+ * an empty string clears the override.
152
+ * @param text - what the user typed.
153
+ * @returns the number, `undefined` for blank, or `null` for text to reject.
154
+ */
155
+ export function parseContextInput(text) {
156
+ const trimmed = text.trim();
157
+ if (trimmed === '')
158
+ return undefined;
159
+ if (!/^\d+$/.test(trimmed))
160
+ return null;
161
+ const value = Number(trimmed);
162
+ return Number.isSafeInteger(value) && value > 0 ? value : null;
163
+ }
164
+ /**
165
+ * Untick one model: drop it from the list and keep its full row (display
166
+ * name, default effort, context, ...) in the stash so ticking it again is
167
+ * lossless, even when the cloud listing no longer carries the id.
168
+ * @param models - the current list.
169
+ * @param stash - the stash before this untick.
170
+ * @param id - the row to untick; an absent id changes nothing.
171
+ * @returns the new list and stash; neither input is mutated.
172
+ */
173
+ export function untickModel(models, stash, id) {
174
+ const row = models.find((model) => model.id === id);
175
+ if (row === undefined)
176
+ return { models: [...models], stash };
177
+ return { models: removeModel(models, id), stash: new Map(stash).set(id, row) };
178
+ }
179
+ /**
180
+ * Tick one model: restore its stashed row when this dialog unticked it,
181
+ * otherwise adopt it from the fetched candidates (which never copies the
182
+ * vendor default effort). A model already in the list, or unknown to both
183
+ * sources, leaves the list unchanged.
184
+ * @param models - the current list.
185
+ * @param stash - rows unticked earlier in this dialog.
186
+ * @param candidates - the cloud list as fetched.
187
+ * @param id - the row to tick.
188
+ * @returns a new list and the stash without the restored row.
189
+ */
190
+ export function tickModel(models, stash, candidates, id) {
191
+ if (models.some((model) => model.id === id))
192
+ return { models: [...models], stash };
193
+ const stashed = stash.get(id);
194
+ if (stashed === undefined)
195
+ return { models: adoptCandidates(models, candidates, [id]), stash };
196
+ const next = new Map(stash);
197
+ next.delete(id);
198
+ return { models: [...models, stashed], stash: next };
199
+ }
200
+ /**
201
+ * Tick every listed id in order. Rows already in the list stay where they
202
+ * are; restored and adopted rows append in the given order.
203
+ * @param models - the current list.
204
+ * @param stash - rows unticked earlier in this dialog.
205
+ * @param candidates - the cloud list as fetched.
206
+ * @param ids - the ids to tick, typically the filtered view.
207
+ * @returns the new list and stash; neither input is mutated.
208
+ */
209
+ export function tickModels(models, stash, candidates, ids) {
210
+ let state = { models: [...models], stash };
211
+ for (const id of ids)
212
+ state = tickModel(state.models, state.stash, candidates, id);
213
+ return state;
214
+ }
215
+ /**
216
+ * Untick every listed id, stashing each removed row. Ids outside the list,
217
+ * such as filtered-out rows, are untouched.
218
+ * @param models - the current list.
219
+ * @param stash - the stash before this untick.
220
+ * @param ids - the ids to untick, typically the filtered view.
221
+ * @returns the new list and stash; neither input is mutated.
222
+ */
223
+ export function untickModels(models, stash, ids) {
224
+ let state = { models: [...models], stash };
225
+ for (const id of ids)
226
+ state = untickModel(state.models, state.stash, id);
227
+ return state;
228
+ }
229
+ /**
230
+ * The effort choices for one ticked row. The row's own `efforts` decide the
231
+ * ids because the store rejects a default outside them, so the selector must
232
+ * never offer anything else. A snapshot of the endpoint (`labels`) may only
233
+ * supply friendlier names for the same ids. A saved default the list no
234
+ * longer contains is kept visible and marked unavailable instead of
235
+ * silently reading as "follow the provider".
236
+ * @param row - the draft row.
237
+ * @param fetched - efforts from this dialog's latest fetch, when it returned the row.
238
+ * @param labels - the opened dialog's snapshot of named efforts for the id.
239
+ * @returns the options in picker order.
240
+ */
241
+ export function effortOptions(row, fetched, labels) {
242
+ const ids = row.efforts ?? fetched ?? labels?.map((entry) => entry.id) ?? [];
243
+ const options = ids.map((id) => ({
244
+ id,
245
+ name: labels?.find((entry) => entry.id === id)?.name ?? id,
246
+ }));
247
+ if (row.defaultEffort !== undefined && !ids.includes(row.defaultEffort)) {
248
+ options.push({
249
+ id: row.defaultEffort,
250
+ name: row.defaultEffort,
251
+ unavailable: true,
252
+ });
253
+ }
254
+ return options;
255
+ }
@@ -1,15 +1,17 @@
1
1
  /** Explain client-side gating; the server independently revalidates before spending. */
2
2
  export function resetActionBlock(usage, now = Date.now()) {
3
- const weekly = usage.windows?.find(w => w.kind === 'weekly' && w.scope === undefined);
3
+ const weekly = usage.windows?.find((w) => w.kind === 'weekly' && w.scope === undefined);
4
4
  if (!weekly || !Number.isFinite(weekly.usedPercent))
5
5
  return 'resetUseRefresh';
6
6
  if (weekly.usedPercent < 80)
7
7
  return 'resetUseGuard';
8
- if (weekly.resetsAt === undefined || weekly.resetsAt <= now || usage.resetCreditsError)
8
+ if (weekly.resetsAt === undefined ||
9
+ weekly.resetsAt <= now ||
10
+ usage.resetCreditsError)
9
11
  return 'resetUseRefresh';
10
- if (!usage.resetCredits?.some(c => c.id))
12
+ if (!usage.resetCredits?.some((c) => c.id))
11
13
  return 'resetUseRestart';
12
- if (!usage.resetCredits.some(c => c.id && (c.expiresAt === undefined || c.expiresAt > now)))
14
+ if (!usage.resetCredits.some((c) => c.id && (c.expiresAt === undefined || c.expiresAt > now)))
13
15
  return 'resetUseRefresh';
14
16
  return undefined;
15
17
  }
@@ -29,7 +29,7 @@ export declare function resetCreditRowName(t: Translate, credit: ResetCreditView
29
29
  /** Claude omits an empty grant list; Codex still shows a defined, possibly empty, list. */
30
30
  export declare function showsResetCredits(provider: 'codex' | 'claude', credits: readonly ResetCreditView[] | undefined): boolean;
31
31
  /** The error line the settings card mounts. Claude never interpolates provider text. */
32
- export declare function ResetCreditsErrorLine({ provider, message, t }: {
32
+ export declare function ResetCreditsErrorLine({ provider, message, t, }: {
33
33
  provider: 'codex' | 'claude';
34
34
  message: string;
35
35
  t: Translate;
@@ -19,7 +19,8 @@ export function resetCreditState(credit, now) {
19
19
  return 'paused';
20
20
  if (credit.resetsLeft === 0)
21
21
  return 'exhausted';
22
- if ((credit.cooldownUntil !== undefined && credit.cooldownUntil > now) || credit.usableNow === false)
22
+ if ((credit.cooldownUntil !== undefined && credit.cooldownUntil > now) ||
23
+ credit.usableNow === false)
23
24
  return 'notUsable';
24
25
  return 'available';
25
26
  }
@@ -37,7 +38,9 @@ export function resetCreditsAvailableCount(credits, now, mode) {
37
38
  const state = resetCreditState(credit, now);
38
39
  if (state === 'expired' || state === 'paused')
39
40
  continue;
40
- if (typeof credit.resetsLeft === 'number' && Number.isFinite(credit.resetsLeft) && credit.resetsLeft > 0) {
41
+ if (typeof credit.resetsLeft === 'number' &&
42
+ Number.isFinite(credit.resetsLeft) &&
43
+ credit.resetsLeft > 0) {
41
44
  sum += credit.resetsLeft;
42
45
  }
43
46
  }
@@ -46,7 +49,9 @@ export function resetCreditsAvailableCount(credits, now, mode) {
46
49
  function expiryText(t, credit) {
47
50
  return credit.expiresAt === undefined
48
51
  ? t('resetCreditExpiryUnknown')
49
- : t('resetCreditExpires', { date: new Date(credit.expiresAt).toLocaleString() });
52
+ : t('resetCreditExpires', {
53
+ date: new Date(credit.expiresAt).toLocaleString(),
54
+ });
50
55
  }
51
56
  function remainingText(t, credit) {
52
57
  const left = credit.resetsLeft;
@@ -62,9 +67,18 @@ function remainingText(t, credit) {
62
67
  /** Accessible name of one Claude grant row: state, remaining, and expiry, not a control name. */
63
68
  export function resetCreditRowName(t, credit, now) {
64
69
  const state = resetCreditState(credit, now);
65
- const parts = [t('resetCreditFull'), t(STATE_KEYS[state]), remainingText(t, credit), expiryText(t, credit)];
66
- if (state === 'notUsable' && credit.cooldownUntil !== undefined && credit.cooldownUntil > now) {
67
- parts.push(t('resetCreditCooldownUntil', { date: new Date(credit.cooldownUntil).toLocaleString() }));
70
+ const parts = [
71
+ t('resetCreditFull'),
72
+ t(STATE_KEYS[state]),
73
+ remainingText(t, credit),
74
+ expiryText(t, credit),
75
+ ];
76
+ if (state === 'notUsable' &&
77
+ credit.cooldownUntil !== undefined &&
78
+ credit.cooldownUntil > now) {
79
+ parts.push(t('resetCreditCooldownUntil', {
80
+ date: new Date(credit.cooldownUntil).toLocaleString(),
81
+ }));
68
82
  }
69
83
  return parts.join(', ');
70
84
  }
@@ -75,15 +89,21 @@ export function showsResetCredits(provider, credits) {
75
89
  return (credits?.length ?? 0) > 0;
76
90
  }
77
91
  /** The error line the settings card mounts. Claude never interpolates provider text. */
78
- export function ResetCreditsErrorLine({ provider, message, t }) {
79
- return _jsx("p", { style: cardStyles.error, children: resetCreditsErrorText(provider, message, t) });
92
+ export function ResetCreditsErrorLine({ provider, message, t, }) {
93
+ return (_jsx("p", { style: cardStyles.error, children: resetCreditsErrorText(provider, message, t) }));
80
94
  }
81
95
  /** Fixed sentence for Claude. Codex may include the lookup message; Claude must not. */
82
96
  export function resetCreditsErrorText(provider, message, t) {
83
- return provider === 'claude' ? t('resetCreditsErrorFixed') : t('resetCreditsError', { message });
97
+ return provider === 'claude'
98
+ ? t('resetCreditsErrorFixed')
99
+ : t('resetCreditsError', { message });
84
100
  }
85
101
  const summaryStyle = {
86
- ...cardStyles.usageMeta, cursor: 'pointer', listStyle: 'none', justifyContent: 'flex-start', gap: 6,
102
+ ...cardStyles.usageMeta,
103
+ cursor: 'pointer',
104
+ listStyle: 'none',
105
+ justifyContent: 'flex-start',
106
+ gap: 6,
87
107
  };
88
108
  /**
89
109
  * The closed disclosure both providers share. Claude rows are plain text:
@@ -92,12 +112,41 @@ const summaryStyle = {
92
112
  */
93
113
  export function ResetCreditsDisclosure({ mode, credits, t, now = Date.now(), nextCredit, useAction, statusMessage, }) {
94
114
  const count = resetCreditsAvailableCount(credits, now, mode);
95
- return (_jsxs("details", { className: "subscriptions-reset-credits", style: cardStyles.usageRow, children: [_jsxs("summary", { style: summaryStyle, children: [_jsx("svg", { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 16 16", style: { flexShrink: 0 }, children: _jsx("path", { d: "m6 3 5 5-5 5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsx("span", { children: t('resetCreditsTitle') }), mode === 'grants' && _jsx("span", { children: t('resetCreditReadOnlyLabel') }), _jsx("span", { style: { marginLeft: 'auto' }, children: t('resetCreditsAvailable', { count }) })] }), _jsx("style", { children: '.subscriptions-reset-credits > summary::-webkit-details-marker { display: none; }'
96
- + '.subscriptions-reset-credits[open] > summary > svg { transform: rotate(90deg); }' }), _jsxs("div", { style: { paddingLeft: 18, marginTop: 6 }, children: [mode === 'grants' && _jsx("p", { style: cardStyles.status, children: t('resetCreditReadOnly') }), credits.map((credit, index) => {
115
+ return (_jsxs("details", { className: "subscriptions-reset-credits", style: cardStyles.usageRow, children: [_jsxs("summary", { style: summaryStyle, children: [_jsx("svg", { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 16 16", style: { flexShrink: 0 }, children: _jsx("path", { d: "m6 3 5 5-5 5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsx("span", { children: t('resetCreditsTitle') }), mode === 'grants' && _jsx("span", { children: t('resetCreditReadOnlyLabel') }), _jsx("span", { style: { marginLeft: 'auto' }, children: t('resetCreditsAvailable', { count }) })] }), _jsx("style", { children: '.subscriptions-reset-credits > summary::-webkit-details-marker { display: none; }' +
116
+ '.subscriptions-reset-credits[open] > summary > svg { transform: rotate(90deg); }' }), _jsxs("div", { style: { paddingLeft: 18, marginTop: 6 }, children: [mode === 'grants' && (_jsx("p", { style: cardStyles.status, children: t('resetCreditReadOnly') })), credits.map((credit, index) => {
97
117
  const state = resetCreditState(credit, now);
98
- const showUse = mode === 'credits' && useAction !== undefined && credit === nextCredit;
99
- return (_jsxs("div", { "data-reset-state": mode === 'grants' ? state : undefined, "data-reset-claimable": mode === 'grants' && credit.claimable === true ? 'true' : undefined, role: mode === 'grants' ? 'group' : undefined, "aria-label": mode === 'grants' ? resetCreditRowName(t, credit, now) : undefined, style: { ...cardStyles.usageMeta, minHeight: 28, gap: 12, cursor: mode === 'grants' ? 'default' : undefined }, children: [_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 8, flexShrink: 0 }, children: [t('resetCreditFull'), mode === 'grants' && (_jsx("span", { "aria-hidden": "true", "data-reset-state-word": state, children: t(STATE_KEYS[state]) })), showUse && (_jsx("button", { type: "button", "aria-disabled": useAction.disabled, title: useAction.title, style: { minHeight: 22, height: 22, padding: '0 7px', borderRadius: 6,
100
- opacity: useAction.disabled ? 0.35 : 1, cursor: useAction.disabled ? 'not-allowed' : 'pointer' }, onClick: () => { if (!useAction.disabled)
101
- useAction.onUse(); }, children: useAction.busy ? t('resetUseChecking') : useAction.label }))] }), _jsxs("span", { style: { textAlign: 'right' }, children: [mode === 'grants' && _jsxs("span", { "aria-hidden": "true", children: [remainingText(t, credit), " \u00B7 "] }), mode === 'grants' && state === 'notUsable' && credit.cooldownUntil !== undefined && credit.cooldownUntil > now && (_jsxs("span", { "aria-hidden": "true", children: [t('resetCreditCooldownUntil', { date: new Date(credit.cooldownUntil).toLocaleString() }), " \u00B7 "] })), _jsx("span", { "aria-hidden": mode === 'grants' ? true : undefined, children: expiryText(t, credit) })] })] }, credit.id ?? index));
102
- }), statusMessage !== undefined && statusMessage !== '' && _jsx("p", { role: "status", style: cardStyles.status, children: statusMessage })] })] }));
118
+ const showUse = mode === 'credits' &&
119
+ useAction !== undefined &&
120
+ credit === nextCredit;
121
+ return (_jsxs("div", { "data-reset-state": mode === 'grants' ? state : undefined, "data-reset-claimable": mode === 'grants' && credit.claimable === true
122
+ ? 'true'
123
+ : undefined, role: mode === 'grants' ? 'group' : undefined, "aria-label": mode === 'grants'
124
+ ? resetCreditRowName(t, credit, now)
125
+ : undefined, style: {
126
+ ...cardStyles.usageMeta,
127
+ minHeight: 28,
128
+ gap: 12,
129
+ cursor: mode === 'grants' ? 'default' : undefined,
130
+ }, children: [_jsxs("span", { style: {
131
+ display: 'inline-flex',
132
+ alignItems: 'center',
133
+ gap: 8,
134
+ flexShrink: 0,
135
+ }, children: [t('resetCreditFull'), mode === 'grants' && (_jsx("span", { "aria-hidden": "true", "data-reset-state-word": state, children: t(STATE_KEYS[state]) })), showUse && (_jsx("button", { type: "button", "aria-disabled": useAction.disabled, title: useAction.title, style: {
136
+ minHeight: 22,
137
+ height: 22,
138
+ padding: '0 7px',
139
+ borderRadius: 6,
140
+ opacity: useAction.disabled ? 0.35 : 1,
141
+ cursor: useAction.disabled ? 'not-allowed' : 'pointer',
142
+ }, onClick: () => {
143
+ if (!useAction.disabled)
144
+ useAction.onUse();
145
+ }, children: useAction.busy ? t('resetUseChecking') : useAction.label }))] }), _jsxs("span", { style: { textAlign: 'right' }, children: [mode === 'grants' && (_jsxs("span", { "aria-hidden": "true", children: [remainingText(t, credit), " \u00B7 "] })), mode === 'grants' &&
146
+ state === 'notUsable' &&
147
+ credit.cooldownUntil !== undefined &&
148
+ credit.cooldownUntil > now && (_jsxs("span", { "aria-hidden": "true", children: [t('resetCreditCooldownUntil', {
149
+ date: new Date(credit.cooldownUntil).toLocaleString(),
150
+ }), ' ', "\u00B7", ' '] })), _jsx("span", { "aria-hidden": mode === 'grants' ? true : undefined, children: expiryText(t, credit) })] })] }, credit.id ?? index));
151
+ }), statusMessage !== undefined && statusMessage !== '' && (_jsx("p", { role: "status", style: cardStyles.status, children: statusMessage }))] })] }));
103
152
  }
@@ -1,48 +1,118 @@
1
1
  /** Shared visual language for OAuth, Cursor, and built-in provider cards. */
2
2
  export const subscriptionCardStyles = {
3
3
  card: {
4
- border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12,
5
- padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
4
+ border: '1px solid var(--dsw-alias-border-l2)',
5
+ borderRadius: 12,
6
+ padding: '12px 14px',
7
+ display: 'flex',
8
+ flexDirection: 'column',
9
+ gap: 6,
6
10
  color: 'var(--dsw-alias-label-primary)',
7
11
  },
8
12
  header: { display: 'flex', alignItems: 'center', gap: 8 },
9
13
  dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },
10
- name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
11
- status: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
12
- error: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },
13
- actions: { display: 'flex', gap: 8, marginTop: 4, alignItems: 'center', flexWrap: 'wrap' },
14
+ name: {
15
+ fontWeight: 500,
16
+ fontSize: 14,
17
+ lineHeight: '22px',
18
+ color: 'var(--dsw-alias-label-primary)',
19
+ },
20
+ status: {
21
+ margin: 0,
22
+ fontSize: 12,
23
+ lineHeight: '18px',
24
+ color: 'var(--dsw-alias-label-tertiary)',
25
+ },
26
+ error: {
27
+ margin: 0,
28
+ fontSize: 12,
29
+ lineHeight: '18px',
30
+ color: 'var(--dsw-alias-state-error-primary)',
31
+ },
32
+ actions: {
33
+ display: 'flex',
34
+ gap: 8,
35
+ marginTop: 4,
36
+ alignItems: 'center',
37
+ flexWrap: 'wrap',
38
+ },
14
39
  button: {
15
- boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
16
- height: 28, padding: '0 10px', borderRadius: 14,
17
- border: '1px solid var(--dsw-alias-border-l2)', background: 'transparent',
18
- color: 'var(--dsw-alias-label-primary)', font: 'inherit', fontSize: 12, lineHeight: '18px',
40
+ boxSizing: 'border-box',
41
+ display: 'inline-flex',
42
+ alignItems: 'center',
43
+ justifyContent: 'center',
44
+ height: 28,
45
+ padding: '0 10px',
46
+ borderRadius: 14,
47
+ border: '1px solid var(--dsw-alias-border-l2)',
48
+ background: 'transparent',
49
+ color: 'var(--dsw-alias-label-primary)',
50
+ font: 'inherit',
51
+ fontSize: 12,
52
+ lineHeight: '18px',
19
53
  cursor: 'pointer',
20
54
  },
21
55
  account: {
22
- display: 'flex', flexDirection: 'column', gap: 6,
23
- border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
24
- padding: '8px 10px', marginTop: 4,
56
+ display: 'flex',
57
+ flexDirection: 'column',
58
+ gap: 6,
59
+ border: '1px solid var(--dsw-alias-border-l2)',
60
+ borderRadius: 8,
61
+ padding: '8px 10px',
62
+ marginTop: 4,
25
63
  },
26
64
  accountHeader: { display: 'flex', alignItems: 'center', gap: 8 },
27
- accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },
65
+ accountName: {
66
+ fontSize: 13,
67
+ lineHeight: '20px',
68
+ color: 'var(--dsw-alias-label-primary)',
69
+ userSelect: 'all',
70
+ },
28
71
  defaultStar: { color: 'var(--dsw-alias-state-warn-label)' },
29
72
  usage: {
30
- display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,
31
- borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8,
73
+ display: 'flex',
74
+ flexDirection: 'column',
75
+ gap: 6,
76
+ marginTop: 4,
77
+ borderTop: '1px solid var(--dsw-alias-border-l2)',
78
+ paddingTop: 8,
32
79
  },
33
80
  usageHeader: { display: 'flex', alignItems: 'center', gap: 8 },
34
- usageTitle: { fontSize: 12, lineHeight: '18px', fontWeight: 500, color: 'var(--dsw-alias-label-secondary)' },
35
- usagePlan: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
81
+ usageTitle: {
82
+ fontSize: 12,
83
+ lineHeight: '18px',
84
+ fontWeight: 500,
85
+ color: 'var(--dsw-alias-label-secondary)',
86
+ },
87
+ usagePlan: {
88
+ fontSize: 12,
89
+ lineHeight: '18px',
90
+ color: 'var(--dsw-alias-label-tertiary)',
91
+ },
36
92
  usageRefresh: {
37
- boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
38
- height: 22, padding: '0 8px', borderRadius: 11, marginLeft: 'auto',
39
- border: '1px solid var(--dsw-alias-border-l2)', background: 'transparent',
40
- color: 'var(--dsw-alias-label-secondary)', font: 'inherit', fontSize: 12, lineHeight: '18px',
93
+ boxSizing: 'border-box',
94
+ display: 'inline-flex',
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ height: 22,
98
+ padding: '0 8px',
99
+ borderRadius: 11,
100
+ marginLeft: 'auto',
101
+ border: '1px solid var(--dsw-alias-border-l2)',
102
+ background: 'transparent',
103
+ color: 'var(--dsw-alias-label-secondary)',
104
+ font: 'inherit',
105
+ fontSize: 12,
106
+ lineHeight: '18px',
41
107
  cursor: 'pointer',
42
108
  },
43
109
  usageRow: { display: 'flex', flexDirection: 'column', gap: 3 },
44
110
  usageMeta: {
45
- display: 'flex', justifyContent: 'space-between', gap: 8,
46
- fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
111
+ display: 'flex',
112
+ justifyContent: 'space-between',
113
+ gap: 8,
114
+ fontSize: 12,
115
+ lineHeight: '18px',
116
+ color: 'var(--dsw-alias-label-tertiary)',
47
117
  },
48
118
  };
@@ -24,8 +24,10 @@ export function setUsageColorPreset(value) {
24
24
  window.dispatchEvent(new Event(EVENT));
25
25
  }
26
26
  export function subscribeUsageColorPreset(notify) {
27
- const storage = (event) => { if (event.key === KEY || event.key === null)
28
- notify(); };
27
+ const storage = (event) => {
28
+ if (event.key === KEY || event.key === null)
29
+ notify();
30
+ };
29
31
  window.addEventListener(EVENT, notify);
30
32
  window.addEventListener('storage', storage);
31
33
  return () => {
@@ -40,3 +40,10 @@ export declare function isUsageFresh(window: UsageWindow, observation: UsageObse
40
40
  export declare function elapsedPercent(window: UsageWindow, now?: number): number | undefined;
41
41
  export declare function usageColorState(used: number, elapsed: number | undefined, preset: UsageColorPreset, fresh: boolean): UsageColorState;
42
42
  export declare function usageBarColor(used: number, elapsed?: number, preset?: UsageColorPreset, fresh?: boolean): string;
43
+ /**
44
+ * Marker position from the window's display-only assumed start
45
+ * (`displayStartsAt`): the reading and its age are irrelevant, exactly
46
+ * like the verified marker. Returns undefined when the window carries no
47
+ * assumed start or its reset has passed.
48
+ */
49
+ export declare function displayStartPercent(window: UsageWindow, now?: number): number | undefined;