dsh-subscriptions 0.2.0 → 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 (99) hide show
  1. package/README.md +212 -180
  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.js +74 -23
  6. package/lib/auth/store.js +80 -31
  7. package/lib/client/CursorCard.d.ts +1 -1
  8. package/lib/client/CursorCard.js +50 -16
  9. package/lib/client/ExternalUsageCards.d.ts +1 -1
  10. package/lib/client/ExternalUsageCards.js +37 -16
  11. package/lib/client/ImageGallery.d.ts +2 -2
  12. package/lib/client/ImageGallery.js +92 -33
  13. package/lib/client/ImageGenerateToolview.js +50 -14
  14. package/lib/client/OllamaCard.d.ts +1 -1
  15. package/lib/client/OllamaCard.js +26 -8
  16. package/lib/client/OllamaSettingsManager.js +113 -30
  17. package/lib/client/ProviderAccountManager.d.ts +1 -1
  18. package/lib/client/ProviderAccountManager.js +164 -49
  19. package/lib/client/ProviderModelEditor.js +107 -26
  20. package/lib/client/SpeedSelect.d.ts +62 -14
  21. package/lib/client/SpeedSelect.js +257 -46
  22. package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
  23. package/lib/client/SubscriptionUsageBadge.js +300 -112
  24. package/lib/client/SubscriptionsSection.js +258 -85
  25. package/lib/client/UsageBadgeDisplaySetting.js +35 -12
  26. package/lib/client/UsageMeter.d.ts +1 -1
  27. package/lib/client/UsageMeter.js +62 -24
  28. package/lib/client/VideoGenerateToolview.js +69 -17
  29. package/lib/client/account-preferences.d.ts +8 -0
  30. package/lib/client/account-preferences.js +20 -5
  31. package/lib/client/host-contract.d.ts +11 -2
  32. package/lib/client/host-contract.js +20 -3
  33. package/lib/client/index.d.ts +5 -5
  34. package/lib/client/index.js +34 -12
  35. package/lib/client/locales.d.ts +10 -0
  36. package/lib/client/locales.js +61 -51
  37. package/lib/client/ollama-draft.js +21 -12
  38. package/lib/client/reset-action.js +6 -4
  39. package/lib/client/reset-credits-view.d.ts +1 -1
  40. package/lib/client/reset-credits-view.js +66 -17
  41. package/lib/client/subscription-card-styles.js +94 -24
  42. package/lib/client/usage-color-preferences.js +4 -2
  43. package/lib/client/usage-pace.js +30 -12
  44. package/lib/client.js +287 -47
  45. package/lib/client.js.map +1 -1
  46. package/lib/http.js +1 -1
  47. package/lib/index.d.ts +3 -3
  48. package/lib/index.js +118 -64
  49. package/lib/model-defaults.js +4 -2
  50. package/lib/provider-settings.d.ts +12 -0
  51. package/lib/provider-settings.js +58 -16
  52. package/lib/providers/account-preferences.js +51 -22
  53. package/lib/providers/accounts.js +15 -9
  54. package/lib/providers/antigravity-oauth-client.js +2 -2
  55. package/lib/providers/antigravity.js +141 -68
  56. package/lib/providers/catalog-store.js +120 -41
  57. package/lib/providers/claude-cli-version.js +3 -3
  58. package/lib/providers/claude.d.ts +9 -2
  59. package/lib/providers/claude.js +153 -90
  60. package/lib/providers/codex-client-version.js +3 -3
  61. package/lib/providers/codex-search.js +40 -13
  62. package/lib/providers/codex.js +196 -112
  63. package/lib/providers/common.js +71 -39
  64. package/lib/providers/copilot.js +143 -82
  65. package/lib/providers/cursor-adapter.js +42 -18
  66. package/lib/providers/cursor-auth.d.ts +1 -1
  67. package/lib/providers/cursor-auth.js +69 -24
  68. package/lib/providers/cursor-model-settings.js +14 -5
  69. package/lib/providers/cursor-usage.js +27 -13
  70. package/lib/providers/external-usage-controller.js +14 -5
  71. package/lib/providers/external-usage.js +59 -23
  72. package/lib/providers/grok.js +152 -89
  73. package/lib/providers/image-pool.js +29 -11
  74. package/lib/providers/minimax-usage.js +31 -11
  75. package/lib/providers/npm-cli-version.js +10 -3
  76. package/lib/providers/ollama-settings.js +24 -8
  77. package/lib/providers/ollama-web.js +29 -11
  78. package/lib/providers/ollama.js +129 -66
  79. package/lib/providers/pool-family.js +11 -3
  80. package/lib/providers/pool-health.js +28 -8
  81. package/lib/providers/pool-usage.js +16 -9
  82. package/lib/providers/pool.d.ts +8 -0
  83. package/lib/providers/pool.js +81 -42
  84. package/lib/providers/rate-limit.js +8 -2
  85. package/lib/providers/replay.js +40 -17
  86. package/lib/providers/reset-redemption.js +18 -6
  87. package/lib/tools/image-generate.js +123 -65
  88. package/lib/tools/registration.js +5 -2
  89. package/lib/tools/video-generate.js +51 -31
  90. package/lib/tools/x-search.js +50 -22
  91. package/lib/translate/anthropic.js +85 -37
  92. package/lib/translate/antigravity-schema.js +71 -28
  93. package/lib/translate/antigravity-thinking.js +22 -9
  94. package/lib/translate/antigravity.js +141 -50
  95. package/lib/translate/chat-completions.js +67 -23
  96. package/lib/translate/resolved.js +55 -21
  97. package/lib/translate/responses.js +69 -29
  98. package/lib/translate/sse.js +4 -1
  99. package/package.json +26 -4
@@ -70,18 +70,24 @@ export class DeviceFlowManager {
70
70
  const response = await fetchFn(spec.deviceCodeUrl, {
71
71
  method: 'POST',
72
72
  headers: {
73
- 'accept': 'application/json',
73
+ accept: 'application/json',
74
74
  'content-type': 'application/x-www-form-urlencoded',
75
75
  },
76
- body: new URLSearchParams({ client_id: spec.clientId, scope: spec.scope }).toString(),
76
+ body: new URLSearchParams({
77
+ client_id: spec.clientId,
78
+ scope: spec.scope,
79
+ }).toString(),
77
80
  });
78
81
  if (!response.ok) {
79
82
  throw new Error(`${provider} device-code request failed (HTTP ${String(response.status)})`);
80
83
  }
81
- const wire = await parseProviderJson(response, 'device code request');
82
- if (typeof wire.device_code !== 'string' || wire.device_code.length === 0
83
- || typeof wire.user_code !== 'string' || wire.user_code.length === 0
84
- || typeof wire.verification_uri !== 'string' || wire.verification_uri.length === 0) {
84
+ const wire = (await parseProviderJson(response, 'device code request'));
85
+ if (typeof wire.device_code !== 'string' ||
86
+ wire.device_code.length === 0 ||
87
+ typeof wire.user_code !== 'string' ||
88
+ wire.user_code.length === 0 ||
89
+ typeof wire.verification_uri !== 'string' ||
90
+ wire.verification_uri.length === 0) {
85
91
  throw new Error(`${provider} device-code response is missing device_code/user_code/verification_uri`);
86
92
  }
87
93
  const intervalSec = typeof wire.interval === 'number' && wire.interval > 0
@@ -124,7 +130,7 @@ export class DeviceFlowManager {
124
130
  const pollResponse = await fetchFn(spec.tokenUrl, {
125
131
  method: 'POST',
126
132
  headers: {
127
- 'accept': 'application/json',
133
+ accept: 'application/json',
128
134
  'content-type': 'application/x-www-form-urlencoded',
129
135
  },
130
136
  body: new URLSearchParams({
@@ -134,8 +140,9 @@ export class DeviceFlowManager {
134
140
  }).toString(),
135
141
  signal: controller.signal,
136
142
  });
137
- const result = await parseProviderJson(pollResponse, 'device token poll');
138
- if (typeof result.access_token === 'string' && result.access_token.length > 0) {
143
+ const result = (await parseProviderJson(pollResponse, 'device token poll'));
144
+ if (typeof result.access_token === 'string' &&
145
+ result.access_token.length > 0) {
139
146
  settle(undefined, result.access_token);
140
147
  return;
141
148
  }
@@ -9,9 +9,9 @@ import { createServer } from 'node:http';
9
9
  import { createPkce, randomHex, randomToken } from './pkce.js';
10
10
  /** Default attempt lifetime: three minutes for the user to complete login. */
11
11
  export const DEFAULT_FLOW_TIMEOUT_MS = 180_000;
12
- const SUCCESS_PAGE = '<!doctype html><html><head><meta charset="utf-8"><title>Login successful</title></head>'
13
- + '<body style="font-family:sans-serif"><h1>Login successful</h1>'
14
- + '<p>You can close this tab and return to DeepSeek Harness.</p></body></html>';
12
+ const SUCCESS_PAGE = '<!doctype html><html><head><meta charset="utf-8"><title>Login successful</title></head>' +
13
+ '<body style="font-family:sans-serif"><h1>Login successful</h1>' +
14
+ '<p>You can close this tab and return to DeepSeek Harness.</p></body></html>';
15
15
  function authorizationFailure(error) {
16
16
  // Safe means this failure path carries no provider-controlled free text,
17
17
  // not that no secret can exist elsewhere in the system.
@@ -40,13 +40,13 @@ function authorizationFailure(error) {
40
40
  category = 'temporarily_unavailable';
41
41
  break;
42
42
  }
43
- return 'authorization failed'
44
- + (category === undefined ? '' : ` (${category})`)
45
- + ': [provider message omitted]';
43
+ return ('authorization failed' +
44
+ (category === undefined ? '' : ` (${category})`) +
45
+ ': [provider message omitted]');
46
46
  }
47
47
  function failurePage(detail) {
48
- return '<!doctype html><html><head><meta charset="utf-8"><title>Login failed</title></head>'
49
- + `<body style="font-family:sans-serif"><h1>Login failed</h1><p>${detail.replace(/[<>&]/g, '')}</p></body></html>`;
48
+ return ('<!doctype html><html><head><meta charset="utf-8"><title>Login failed</title></head>' +
49
+ `<body style="font-family:sans-serif"><h1>Login failed</h1><p>${detail.replace(/[<>&]/g, '')}</p></body></html>`);
50
50
  }
51
51
  /**
52
52
  * Loopback addresses one listen host covers. `localhost` resolves to ::1 or
@@ -69,7 +69,7 @@ function familyUnavailable(error) {
69
69
  */
70
70
  async function listen(handler, spec) {
71
71
  const hosts = listenHosts(spec.host);
72
- const candidates = spec.ports.flatMap(port => (port === 0 ? [0, 0, 0] : [port]));
72
+ const candidates = spec.ports.flatMap((port) => port === 0 ? [0, 0, 0] : [port]);
73
73
  let lastError;
74
74
  for (const candidate of candidates) {
75
75
  const servers = [];
@@ -161,6 +161,8 @@ export class OAuthFlowManager {
161
161
  rejectCode = reject;
162
162
  });
163
163
  let settled = false;
164
+ // Assigned after listen(); a callback that arrives first must see it absent.
165
+ // eslint-disable-next-line prefer-const -- the single write is after the listener exists
164
166
  let timer;
165
167
  let servers = [];
166
168
  const handler = (request, response) => {
@@ -177,7 +179,8 @@ export class OAuthFlowManager {
177
179
  response.end('state mismatch');
178
180
  return;
179
181
  }
180
- if (url.searchParams.has('error_description') || url.searchParams.has('error')) {
182
+ if (url.searchParams.has('error_description') ||
183
+ url.searchParams.has('error')) {
181
184
  const detail = authorizationFailure(url.searchParams.get('error'));
182
185
  response.writeHead(200, { 'content-type': 'text/html' });
183
186
  response.end(failurePage(detail));
package/lib/auth/rpc.js CHANGED
@@ -21,18 +21,44 @@ export const SUBSCRIPTIONS_AUTH_PREFIX = 'subscriptions-auth.';
21
21
  * Kept in one place so the route table and the switch cannot drift apart.
22
22
  */
23
23
  export const SUBSCRIPTIONS_AUTH_ENDPOINTS = [
24
- 'providerSettings', 'setProviderSettings',
25
- 'status', 'login', 'manual', 'cancel', 'logout', 'setDefault', 'usage',
26
- 'externalStatus', 'externalUsage',
27
- 'cursorStatus', 'cursorLogin', 'cursorCancel', 'cursorLogout', 'cursorUsage', 'cursorModels',
28
- 'cursorSettings', 'cursorSetSettings',
29
- 'ollamaSettings', 'ollamaSetSettings', 'ollamaModels',
30
- 'image', 'video', 'prepareReset', 'consumeReset',
31
- 'speed', 'setSpeed',
32
- 'modelDefaults', 'setModelDefault',
24
+ 'providerSettings',
25
+ 'setProviderSettings',
26
+ 'status',
27
+ 'login',
28
+ 'manual',
29
+ 'cancel',
30
+ 'logout',
31
+ 'setDefault',
32
+ 'usage',
33
+ 'externalStatus',
34
+ 'externalUsage',
35
+ 'cursorStatus',
36
+ 'cursorLogin',
37
+ 'cursorCancel',
38
+ 'cursorLogout',
39
+ 'cursorUsage',
40
+ 'cursorModels',
41
+ 'cursorSettings',
42
+ 'cursorSetSettings',
43
+ 'ollamaSettings',
44
+ 'ollamaSetSettings',
45
+ 'ollamaModels',
46
+ 'image',
47
+ 'video',
48
+ 'prepareReset',
49
+ 'consumeReset',
50
+ 'speed',
51
+ 'setSpeed',
52
+ 'modelDefaults',
53
+ 'setModelDefault',
33
54
  ];
34
55
  /** Media types the attachment store accepts (ImageMediaType). */
35
- const IMAGE_MEDIA_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
56
+ const IMAGE_MEDIA_TYPES = [
57
+ 'image/png',
58
+ 'image/jpeg',
59
+ 'image/webp',
60
+ 'image/gif',
61
+ ];
36
62
  /** Bare MP4 file names the `video` endpoint accepts (no path separators). */
37
63
  const VIDEO_NAME_PATTERN = /^[\w.-]+\.mp4$/;
38
64
  /** Payload carried no usable provider id — an RPC client bug, not a server failure. */
@@ -42,9 +68,16 @@ function readEnvelope(body) {
42
68
  if (typeof body !== 'object' || body === null)
43
69
  return undefined;
44
70
  const record = body;
45
- if (record.type !== 'client-request' || typeof record.rpcId !== 'string' || typeof record.method !== 'string')
71
+ if (record.type !== 'client-request' ||
72
+ typeof record.rpcId !== 'string' ||
73
+ typeof record.method !== 'string')
46
74
  return undefined;
47
- return { type: 'client-request', rpcId: record.rpcId, method: record.method, payload: record.payload };
75
+ return {
76
+ type: 'client-request',
77
+ rpcId: record.rpcId,
78
+ method: record.method,
79
+ payload: record.payload,
80
+ };
48
81
  }
49
82
  function serverResponse(rpcId, result) {
50
83
  return Response.json({ type: 'server-response', rpcId, result });
@@ -64,8 +97,14 @@ function fetchRouteFor(endpoint, handler) {
64
97
  methods: ['POST'],
65
98
  requestBody: 'buffered',
66
99
  fetch: async (request) => {
67
- if (request.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase() !== 'application/json') {
68
- return new Response('content type must be application/json', { status: 415 });
100
+ if (request.headers
101
+ .get('content-type')
102
+ ?.split(';', 1)[0]
103
+ ?.trim()
104
+ .toLowerCase() !== 'application/json') {
105
+ return new Response('content type must be application/json', {
106
+ status: 415,
107
+ });
69
108
  }
70
109
  let body;
71
110
  try {
@@ -79,7 +118,11 @@ function fetchRouteFor(endpoint, handler) {
79
118
  const rawId = body?.rpcId;
80
119
  return serverResponse(typeof rawId === 'string' ? rawId : 'invalid-request', {
81
120
  ok: false,
82
- error: { code: 'gateway/bad-request', message: 'invalid client-request message', details: { issues: [] } },
121
+ error: {
122
+ code: 'gateway/bad-request',
123
+ message: 'invalid client-request message',
124
+ details: { issues: [] },
125
+ },
83
126
  });
84
127
  }
85
128
  if (envelope.method !== method) {
@@ -103,7 +146,10 @@ function failure(error) {
103
146
  const message = error instanceof Error ? error.message : String(error);
104
147
  if (error instanceof BadRequest) {
105
148
  // The issues array is zod-shaped upstream; this channel validates by hand.
106
- return { ok: false, error: { code: 'bad-request', message, details: { issues: [] } } };
149
+ return {
150
+ ok: false,
151
+ error: { code: 'bad-request', message, details: { issues: [] } },
152
+ };
107
153
  }
108
154
  return { ok: false, error: { code: 'internal', message, details: {} } };
109
155
  }
@@ -111,7 +157,8 @@ function readProvider(payload) {
111
157
  if (typeof payload !== 'object' || payload === null)
112
158
  throw new BadRequest('payload must be an object');
113
159
  const provider = payload.provider;
114
- if (typeof provider !== 'string' || !PROVIDER_IDS.includes(provider)) {
160
+ if (typeof provider !== 'string' ||
161
+ !PROVIDER_IDS.includes(provider)) {
115
162
  throw new BadRequest(`payload.provider must be one of ${PROVIDER_IDS.join(', ')}`);
116
163
  }
117
164
  return provider;
@@ -183,7 +230,8 @@ function readImageRef(payload) {
183
230
  throw new BadRequest('payload.attachmentId must be a non-empty string');
184
231
  }
185
232
  const mediaType = record.mediaType;
186
- if (typeof mediaType !== 'string' || !IMAGE_MEDIA_TYPES.includes(mediaType)) {
233
+ if (typeof mediaType !== 'string' ||
234
+ !IMAGE_MEDIA_TYPES.includes(mediaType)) {
187
235
  throw new BadRequest(`payload.mediaType must be one of ${IMAGE_MEDIA_TYPES.join(', ')}`);
188
236
  }
189
237
  for (const field of ['bytes', 'width', 'height']) {
@@ -202,7 +250,7 @@ function readImageRef(payload) {
202
250
  bytes: record.bytes,
203
251
  width: record.width,
204
252
  height: record.height,
205
- ...name === undefined ? {} : { name: name },
253
+ ...(name === undefined ? {} : { name: name }),
206
254
  };
207
255
  }
208
256
  /**
@@ -253,11 +301,14 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
253
301
  // One provider's failure (a corrupt store entry, a broken flow) must not
254
302
  // blind the whole page: it degrades to an error detail on that provider
255
303
  // while the others still report their real status.
256
- const entries = await Promise.all(PROVIDER_IDS.map(async (provider) => [provider, await controller.status(provider).catch((error) => ({
304
+ const entries = await Promise.all(PROVIDER_IDS.map(async (provider) => [
305
+ provider,
306
+ await controller.status(provider).catch((error) => ({
257
307
  busy: false,
258
308
  accounts: [],
259
309
  detail: error instanceof Error ? error.message : String(error),
260
- }))]));
310
+ })),
311
+ ]));
261
312
  return ok({ providers: Object.fromEntries(entries) });
262
313
  }
263
314
  case 'login': {
@@ -364,8 +415,8 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
364
415
  }
365
416
  }
366
417
  return ok(await ollama.models({
367
- ...apiKey === undefined ? {} : { apiKey },
368
- ...baseURL === undefined ? {} : { baseURL },
418
+ ...(apiKey === undefined ? {} : { apiKey }),
419
+ ...(baseURL === undefined ? {} : { baseURL }),
369
420
  }, signal));
370
421
  }
371
422
  case 'prepareReset':
package/lib/auth/store.js CHANGED
@@ -18,7 +18,13 @@ import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
18
18
  import { dirname } from 'node:path';
19
19
  import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
20
20
  /** Every provider route, in display order. */
21
- export const PROVIDER_IDS = ['codex', 'claude', 'grok', 'copilot', 'antigravity'];
21
+ export const PROVIDER_IDS = [
22
+ 'codex',
23
+ 'claude',
24
+ 'grok',
25
+ 'copilot',
26
+ 'antigravity',
27
+ ];
22
28
  /**
23
29
  * The stable identity of one session's account: Codex keys on workspace AND
24
30
  * user (email fallback, workspace-only for unidentified legacy sessions),
@@ -37,40 +43,53 @@ export function accountKeyOf(provider, session) {
37
43
  switch (provider) {
38
44
  case 'codex': {
39
45
  const codex = session;
40
- const payload = typeof codex.idToken === 'string' ? decodeJwtPayload(codex.idToken) : undefined;
46
+ const payload = typeof codex.idToken === 'string'
47
+ ? decodeJwtPayload(codex.idToken)
48
+ : undefined;
41
49
  const auth = claimObject(payload?.['https://api.openai.com/auth']);
42
50
  const user = nonEmpty(auth?.chatgpt_user_id) ?? nonEmpty(auth?.user_id);
43
51
  const profile = claimObject(payload?.['https://api.openai.com/profile']);
44
- const email = nonEmpty(codex.emailAddress) ?? nonEmpty(payload?.email) ?? nonEmpty(profile?.email);
52
+ const email = nonEmpty(codex.emailAddress) ??
53
+ nonEmpty(payload?.email) ??
54
+ nonEmpty(profile?.email);
45
55
  if (user === undefined && email === undefined)
46
56
  return codex.accountId;
47
57
  // JSON tuple encoding avoids separator collisions and distinguishes IDs from emails.
48
- return JSON.stringify([codex.accountId, user === undefined ? 'email' : 'user', user ?? email.toLowerCase()]);
58
+ return JSON.stringify([
59
+ codex.accountId,
60
+ user === undefined ? 'email' : 'user',
61
+ user ?? email.toLowerCase(),
62
+ ]);
49
63
  }
50
64
  case 'claude':
51
- return session.emailAddress ?? tokenHash(session.refreshToken);
65
+ return (session.emailAddress ??
66
+ tokenHash(session.refreshToken));
52
67
  case 'grok':
53
68
  return session.account ?? tokenHash(session.refreshToken);
54
69
  case 'antigravity':
55
- return session.account ?? tokenHash(session.refreshToken);
70
+ return (session.account ??
71
+ tokenHash(session.refreshToken));
56
72
  case 'copilot':
57
- return session.account ?? tokenHash(session.refreshToken);
73
+ return (session.account ?? tokenHash(session.refreshToken));
58
74
  }
59
75
  }
60
76
  function nonEmpty(value) {
61
- return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
77
+ return typeof value === 'string' && value.trim().length > 0
78
+ ? value.trim()
79
+ : undefined;
62
80
  }
63
81
  function claimObject(value) {
64
82
  return typeof value === 'object' && value !== null && !Array.isArray(value)
65
- ? value : undefined;
83
+ ? value
84
+ : undefined;
66
85
  }
67
86
  /** Resolve only recorded aliases, not an ambiguous workspace-wide match. */
68
87
  function resolveAccount(entry, key) {
69
88
  const seen = new Set();
70
- while (!Object.hasOwn(entry.accounts, key)
71
- && entry.aliases !== undefined
72
- && Object.hasOwn(entry.aliases, key)
73
- && !seen.has(key)) {
89
+ while (!Object.hasOwn(entry.accounts, key) &&
90
+ entry.aliases !== undefined &&
91
+ Object.hasOwn(entry.aliases, key) &&
92
+ !seen.has(key)) {
74
93
  seen.add(key);
75
94
  key = entry.aliases[key];
76
95
  }
@@ -107,9 +126,13 @@ export async function resolveAccountKey(provider, account, path = authFilePath()
107
126
  }
108
127
  /** Migrate workspace-only keys once; retain collisions rather than discard credentials. */
109
128
  function codexEmail(session) {
110
- const payload = typeof session.idToken === 'string' ? decodeJwtPayload(session.idToken) : undefined;
129
+ const payload = typeof session.idToken === 'string'
130
+ ? decodeJwtPayload(session.idToken)
131
+ : undefined;
111
132
  const profile = claimObject(payload?.['https://api.openai.com/profile']);
112
- return (nonEmpty(session.emailAddress) ?? nonEmpty(payload?.email) ?? nonEmpty(profile?.email))?.toLowerCase();
133
+ return (nonEmpty(session.emailAddress) ??
134
+ nonEmpty(payload?.email) ??
135
+ nonEmpty(profile?.email))?.toLowerCase();
113
136
  }
114
137
  function codexUserKey(session) {
115
138
  const key = accountKeyOf('codex', session);
@@ -179,13 +202,16 @@ function assertSessionShape(provider, account, value) {
179
202
  throw new Error(`subscriptions auth store: entry "${provider}/${account}" is not an object; fix or delete the store file`);
180
203
  }
181
204
  const entry = value;
182
- if (typeof entry.accessToken !== 'string' || entry.accessToken.length === 0
183
- || typeof entry.refreshToken !== 'string' || entry.refreshToken.length === 0
184
- || typeof entry.expiresAt !== 'number' || !Number.isFinite(entry.expiresAt)) {
205
+ if (typeof entry.accessToken !== 'string' ||
206
+ entry.accessToken.length === 0 ||
207
+ typeof entry.refreshToken !== 'string' ||
208
+ entry.refreshToken.length === 0 ||
209
+ typeof entry.expiresAt !== 'number' ||
210
+ !Number.isFinite(entry.expiresAt)) {
185
211
  throw new Error(`subscriptions auth store: entry "${provider}/${account}" is missing accessToken/refreshToken/expiresAt; fix or delete the store file`);
186
212
  }
187
- if (provider === 'antigravity'
188
- && (typeof entry.projectId !== 'string' || entry.projectId.length === 0)) {
213
+ if (provider === 'antigravity' &&
214
+ (typeof entry.projectId !== 'string' || entry.projectId.length === 0)) {
189
215
  throw new Error('subscriptions auth store: entry "antigravity" is missing projectId; log out and complete Antigravity login again');
190
216
  }
191
217
  }
@@ -261,12 +287,19 @@ function parseStore(text, path) {
261
287
  continue;
262
288
  }
263
289
  const session = record;
264
- const key = provider === 'codex' ? session.accountId : accountKeyOf(provider, session);
265
- store[provider] = { default: key, accounts: { [key]: session } };
290
+ const key = provider === 'codex'
291
+ ? session.accountId
292
+ : accountKeyOf(provider, session);
293
+ store[provider] = {
294
+ default: key,
295
+ accounts: { [key]: session },
296
+ };
266
297
  continue;
267
298
  }
268
299
  const accounts = record.accounts;
269
- if (typeof accounts !== 'object' || accounts === null || Array.isArray(accounts)) {
300
+ if (typeof accounts !== 'object' ||
301
+ accounts === null ||
302
+ Array.isArray(accounts)) {
270
303
  console.warn(`subscriptions auth store: entry "${provider}" has no accounts map; skipped`);
271
304
  continue;
272
305
  }
@@ -288,7 +321,11 @@ function parseStore(text, path) {
288
321
  const validDefault = record.default === undefined || record.default in kept
289
322
  ? record.default
290
323
  : Object.keys(kept)[0];
291
- store[provider] = { ...record, default: validDefault, accounts: kept };
324
+ store[provider] = {
325
+ ...record,
326
+ default: validDefault,
327
+ accounts: kept,
328
+ };
292
329
  }
293
330
  if (store.codex !== undefined)
294
331
  migrateCodex(store.codex);
@@ -299,9 +336,12 @@ function isValidSessionShape(value) {
299
336
  if (typeof value !== 'object' || value === null)
300
337
  return false;
301
338
  const entry = value;
302
- return typeof entry.accessToken === 'string' && entry.accessToken.length > 0
303
- && typeof entry.refreshToken === 'string' && entry.refreshToken.length > 0
304
- && typeof entry.expiresAt === 'number' && Number.isFinite(entry.expiresAt);
339
+ return (typeof entry.accessToken === 'string' &&
340
+ entry.accessToken.length > 0 &&
341
+ typeof entry.refreshToken === 'string' &&
342
+ entry.refreshToken.length > 0 &&
343
+ typeof entry.expiresAt === 'number' &&
344
+ Number.isFinite(entry.expiresAt));
305
345
  }
306
346
  /** Persist the whole store atomically with owner-only permissions. */
307
347
  async function writeStore(store, path) {
@@ -361,7 +401,10 @@ export async function listAccounts(provider, path = authFilePath()) {
361
401
  const entry = (await loadStore(path))[provider];
362
402
  if (entry === undefined)
363
403
  return [];
364
- const accounts = Object.entries(entry.accounts).map(([key, session]) => ({ key, session }));
404
+ const accounts = Object.entries(entry.accounts).map(([key, session]) => ({
405
+ key,
406
+ session,
407
+ }));
365
408
  accounts.sort((a, b) => Number(b.key === entry.default) - Number(a.key === entry.default));
366
409
  return accounts;
367
410
  }
@@ -402,7 +445,8 @@ export async function saveAccountSession(provider, account, session, path = auth
402
445
  if (entry !== undefined) {
403
446
  account = resolveAccount(entry, account);
404
447
  if (provider === 'codex') {
405
- account = reconcileCodexIdentity(entry, session) ?? accountKeyOf('codex', session);
448
+ account =
449
+ reconcileCodexIdentity(entry, session) ?? accountKeyOf('codex', session);
406
450
  }
407
451
  }
408
452
  ;
@@ -439,7 +483,9 @@ export async function deleteAccountSession(provider, account, path = authFilePat
439
483
  ;
440
484
  store[provider] = {
441
485
  ...entry,
442
- ...entry.default === account ? { default: Object.keys(accounts)[0] } : { default: entry.default },
486
+ ...(entry.default === account
487
+ ? { default: Object.keys(accounts)[0] }
488
+ : { default: entry.default }),
443
489
  accounts,
444
490
  };
445
491
  }
@@ -462,7 +508,10 @@ export async function setDefaultAccount(provider, account, path = authFilePath()
462
508
  throw new Error(`no ${provider} account "${account}" is logged in`);
463
509
  }
464
510
  ;
465
- store[provider] = { ...entry, default: account };
511
+ store[provider] = {
512
+ ...entry,
513
+ default: account,
514
+ };
466
515
  await writeStore(store, path);
467
516
  });
468
517
  }
@@ -2,7 +2,7 @@ import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
2
2
  import type { SubscriptionsSectionInjected } from './SubscriptionsSection.js';
3
3
  type Translate = SubscriptionsSectionInjected['t'];
4
4
  /** Cursor's single connected account, rendered with the same card and Manage dialog as the other providers. */
5
- export declare function CursorCard({ rpc, t }: {
5
+ export declare function CursorCard({ rpc, t, }: {
6
6
  rpc: ConnectionHandle['rpc'];
7
7
  t: Translate;
8
8
  }): import("react").JSX.Element;
@@ -10,7 +10,7 @@ function message(error) {
10
10
  return error instanceof Error ? error.message : String(error);
11
11
  }
12
12
  /** Cursor's single connected account, rendered with the same card and Manage dialog as the other providers. */
13
- export function CursorCard({ rpc, t }) {
13
+ export function CursorCard({ rpc, t, }) {
14
14
  const [status, setStatus] = useState();
15
15
  const [usage, setUsage] = useState();
16
16
  const [observedAt, setObservedAt] = useState();
@@ -56,15 +56,21 @@ export function CursorCard({ rpc, t }) {
56
56
  setLoading(false);
57
57
  }
58
58
  }, [rpc]);
59
- useEffect(() => { void refreshStatus(); }, [refreshStatus]);
59
+ useEffect(() => {
60
+ void refreshStatus();
61
+ }, [refreshStatus]);
60
62
  useEffect(() => {
61
63
  if (status?.busy !== true)
62
64
  return;
63
- const timer = setInterval(() => { void refreshStatus(); }, 2000);
65
+ const timer = setInterval(() => {
66
+ void refreshStatus();
67
+ }, 2000);
64
68
  return () => clearInterval(timer);
65
69
  }, [status?.busy, refreshStatus]);
66
- useEffect(() => { if (status?.authenticated === true)
67
- void refreshUsage(); }, [status?.authenticated, refreshUsage]);
70
+ useEffect(() => {
71
+ if (status?.authenticated === true)
72
+ void refreshUsage();
73
+ }, [status?.authenticated, refreshUsage]);
68
74
  const login = async () => {
69
75
  setError(undefined);
70
76
  try {
@@ -102,16 +108,44 @@ export function CursorCard({ rpc, t }) {
102
108
  }
103
109
  };
104
110
  const connected = status?.authenticated === true;
105
- return _jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.header, children: [_jsx("span", { style: { ...styles.dot, background: status?.busy ? 'var(--dsw-alias-state-warn-label)'
106
- : connected ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-dimmed)' } }), _jsx("span", { style: styles.name, children: t('cursorTitle') })] }), _jsx("p", { style: styles.status, children: status === undefined ? t('checking') : status.busy ? t('loginInProgress')
107
- : connected ? t('loggedInCount', { count: 1 }) : t('notLoggedIn') }), error !== undefined && _jsx("p", { style: styles.error, children: error }), connected && _jsxs("div", { style: styles.account, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("span", { style: styles.defaultStar, title: t('defaultBadge'), children: "\u2605" }), _jsx("span", { style: styles.accountName, children: "Cursor" }), status.expiresAt !== undefined && _jsx("span", { style: styles.status, children: t('accountExpires', { date: new Date(status.expiresAt).toLocaleString() }) }), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(); }, children: t('logout') })] }), _jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && _jsx("span", { style: styles.status, children: t('usagePlan', { plan: usage.plan }) }), _jsx("button", { type: "button", style: styles.usageRefresh, disabled: loading, onClick: () => { void refreshUsage(); }, children: t('usageRefresh') })] }), loading && usage === undefined && _jsx("p", { style: styles.status, children: t('usageLoading') }), usage?.windows?.length === 0 && _jsx("p", { style: styles.status, children: t('usageEmpty') }), usage?.windows?.map((window, index) => {
111
+ return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.header, children: [_jsx("span", { style: {
112
+ ...styles.dot,
113
+ background: status?.busy
114
+ ? 'var(--dsw-alias-state-warn-label)'
115
+ : connected
116
+ ? 'var(--dsw-alias-state-success-primary)'
117
+ : 'var(--dsw-alias-label-dimmed)',
118
+ } }), _jsx("span", { style: styles.name, children: t('cursorTitle') })] }), _jsx("p", { style: styles.status, children: status === undefined
119
+ ? t('checking')
120
+ : status.busy
121
+ ? t('loginInProgress')
122
+ : connected
123
+ ? t('loggedInCount', { count: 1 })
124
+ : t('notLoggedIn') }), error !== undefined && _jsx("p", { style: styles.error, children: error }), connected && (_jsxs("div", { style: styles.account, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("span", { style: styles.defaultStar, title: t('defaultBadge'), children: "\u2605" }), _jsx("span", { style: styles.accountName, children: "Cursor" }), status.expiresAt !== undefined && (_jsx("span", { style: styles.status, children: t('accountExpires', {
125
+ date: new Date(status.expiresAt).toLocaleString(),
126
+ }) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => {
127
+ void logout();
128
+ }, children: t('logout') })] }), _jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.status, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: styles.usageRefresh, disabled: loading, onClick: () => {
129
+ void refreshUsage();
130
+ }, children: t('usageRefresh') })] }), loading && usage === undefined && (_jsx("p", { style: styles.status, children: t('usageLoading') })), usage?.windows?.length === 0 && (_jsx("p", { style: styles.status, children: t('usageEmpty') })), usage?.windows?.map((window, index) => {
108
131
  const percent = displayUsedPercent(window.usedPercent);
109
- const label = window.scope === 'Included' ? t('cursorIncluded')
110
- : window.scope === 'Cursor Models' ? t('cursorModels')
111
- : window.scope === 'Other Models' ? t('cursorOtherModels')
112
- : window.scope === 'Included requests' ? t('cursorIncludedRequests')
113
- : window.scope ?? t('usageWindow');
114
- return _jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: label }), _jsxs("span", { children: [percent === undefined ? t('usageMeterInvalid') : `${percent}%`, window.resetsAt === undefined ? ''
115
- : ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx(UsageMeter, { window: window, t: t, observedAt: observedAt, stale: usageStale })] }, index);
116
- })] })] }), _jsxs("div", { style: styles.actions, children: [status?.authenticated === false && !status.busy && _jsx("button", { type: "button", style: styles.button, onClick: () => { void login(); }, children: t('login') }), _jsx("button", { type: "button", style: styles.button, "aria-haspopup": "dialog", onClick: () => setManage(true), children: t('accountsManage') }), status?.busy === true && _jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(); }, children: t('cancel') })] }), status?.busy === true && authorizeUrl !== undefined && _jsx("a", { href: authorizeUrl, target: "_blank", rel: "noopener noreferrer", children: t('cursorOpenLogin') }), manage && _jsx(ProviderAccountManager, { provider: "cursor-subscription", name: "Cursor", rpc: rpc, t: t, onClose: () => setManage(false) })] });
132
+ const label = window.scope === 'Included'
133
+ ? t('cursorIncluded')
134
+ : window.scope === 'Cursor Models'
135
+ ? t('cursorModels')
136
+ : window.scope === 'Other Models'
137
+ ? t('cursorOtherModels')
138
+ : window.scope === 'Included requests'
139
+ ? t('cursorIncludedRequests')
140
+ : (window.scope ?? t('usageWindow'));
141
+ return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: label }), _jsxs("span", { children: [percent === undefined
142
+ ? t('usageMeterInvalid')
143
+ : `${percent}%`, window.resetsAt === undefined
144
+ ? ''
145
+ : ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx(UsageMeter, { window: window, t: t, observedAt: observedAt, stale: usageStale })] }, index));
146
+ })] })] })), _jsxs("div", { style: styles.actions, children: [status?.authenticated === false && !status.busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => {
147
+ void login();
148
+ }, children: t('login') })), _jsx("button", { type: "button", style: styles.button, "aria-haspopup": "dialog", onClick: () => setManage(true), children: t('accountsManage') }), status?.busy === true && (_jsx("button", { type: "button", style: styles.button, onClick: () => {
149
+ void cancel();
150
+ }, children: t('cancel') }))] }), status?.busy === true && authorizeUrl !== undefined && (_jsx("a", { href: authorizeUrl, target: "_blank", rel: "noopener noreferrer", children: t('cursorOpenLogin') })), manage && (_jsx(ProviderAccountManager, { provider: "cursor-subscription", name: "Cursor", rpc: rpc, t: t, onClose: () => setManage(false) }))] }));
117
151
  }
@@ -12,7 +12,7 @@ type Translate = SubscriptionsSectionInjected['t'];
12
12
  */
13
13
  export declare function externalUsageWindowLabel(t: Translate, window: UsageWindow): string;
14
14
  /** Quota cards for API-key providers already available through DSH itself. */
15
- export declare function ExternalUsageCards({ rpc, t }: {
15
+ export declare function ExternalUsageCards({ rpc, t, }: {
16
16
  rpc: ConnectionHandle['rpc'];
17
17
  t: Translate;
18
18
  }): import("react").JSX.Element;