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.
- package/README.md +212 -154
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.d.ts +73 -2
- package/lib/auth/rpc.js +124 -23
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +13 -0
- package/lib/client/OllamaCard.js +94 -0
- package/lib/client/OllamaSettingsManager.d.ts +24 -0
- package/lib/client/OllamaSettingsManager.js +329 -0
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +109 -26
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +257 -46
- package/lib/client/SubscriptionUsageBadge.d.ts +5 -11
- package/lib/client/SubscriptionUsageBadge.js +301 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +259 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +69 -27
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +78 -0
- package/lib/client/locales.js +121 -34
- package/lib/client/ollama-draft.d.ts +229 -0
- package/lib/client/ollama-draft.js +255 -0
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.d.ts +7 -0
- package/lib/client/usage-pace.js +43 -10
- package/lib/client.js +1647 -266
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +28 -3
- package/lib/index.js +9329 -8217
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +14 -1
- package/lib/provider-settings.js +64 -20
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +23 -3
- package/lib/providers/claude.js +167 -92
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.d.ts +14 -0
- package/lib/providers/common.js +118 -38
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.d.ts +9 -2
- package/lib/providers/external-usage-controller.js +30 -7
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +140 -20
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.d.ts +90 -0
- package/lib/providers/ollama-settings.js +213 -0
- package/lib/providers/ollama-web.d.ts +46 -0
- package/lib/providers/ollama-web.js +231 -0
- package/lib/providers/ollama.d.ts +107 -0
- package/lib/providers/ollama.js +608 -0
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +141 -50
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- 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 ||
|
|
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) ||
|
|
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' &&
|
|
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', {
|
|
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 = [
|
|
66
|
-
|
|
67
|
-
|
|
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'
|
|
97
|
+
return provider === 'claude'
|
|
98
|
+
? t('resetCreditsErrorFixed')
|
|
99
|
+
: t('resetCreditsError', { message });
|
|
84
100
|
}
|
|
85
101
|
const summaryStyle = {
|
|
86
|
-
...cardStyles.usageMeta,
|
|
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
|
-
|
|
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' &&
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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)',
|
|
5
|
-
|
|
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: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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',
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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',
|
|
23
|
-
|
|
24
|
-
|
|
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: {
|
|
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',
|
|
31
|
-
|
|
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: {
|
|
35
|
-
|
|
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',
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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',
|
|
46
|
-
|
|
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) => {
|
|
28
|
-
|
|
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;
|