dsh-subscriptions 0.2.0 → 0.2.3

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 (105) hide show
  1. package/README.md +220 -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.d.ts +8 -4
  6. package/lib/auth/rpc.js +89 -25
  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 +1 -1
  16. package/lib/client/OllamaCard.js +26 -8
  17. package/lib/client/OllamaSettingsManager.js +113 -30
  18. package/lib/client/ProviderAccountManager.d.ts +1 -1
  19. package/lib/client/ProviderAccountManager.js +164 -49
  20. package/lib/client/ProviderModelEditor.js +137 -34
  21. package/lib/client/SpeedSelect.d.ts +62 -14
  22. package/lib/client/SpeedSelect.js +261 -49
  23. package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
  24. package/lib/client/SubscriptionUsageBadge.js +300 -112
  25. package/lib/client/SubscriptionsSection.d.ts +2 -0
  26. package/lib/client/SubscriptionsSection.js +265 -85
  27. package/lib/client/UsageBadgeDisplaySetting.js +35 -12
  28. package/lib/client/UsageMeter.d.ts +1 -1
  29. package/lib/client/UsageMeter.js +62 -24
  30. package/lib/client/VideoGenerateToolview.js +69 -17
  31. package/lib/client/account-preferences.d.ts +8 -0
  32. package/lib/client/account-preferences.js +20 -5
  33. package/lib/client/host-contract.d.ts +11 -2
  34. package/lib/client/host-contract.js +20 -3
  35. package/lib/client/index.d.ts +5 -5
  36. package/lib/client/index.js +34 -12
  37. package/lib/client/locales.d.ts +22 -0
  38. package/lib/client/locales.js +73 -51
  39. package/lib/client/model-filter.d.ts +37 -0
  40. package/lib/client/model-filter.js +45 -0
  41. package/lib/client/ollama-draft.js +21 -12
  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.js +30 -12
  48. package/lib/client/usage-window-filter.d.ts +7 -0
  49. package/lib/client/usage-window-filter.js +12 -0
  50. package/lib/client.js +480 -136
  51. package/lib/client.js.map +1 -1
  52. package/lib/http.js +1 -1
  53. package/lib/index.d.ts +3 -3
  54. package/lib/index.js +167 -76
  55. package/lib/model-defaults.js +4 -2
  56. package/lib/provider-settings.d.ts +17 -0
  57. package/lib/provider-settings.js +79 -16
  58. package/lib/providers/account-preferences.js +51 -22
  59. package/lib/providers/accounts.js +15 -9
  60. package/lib/providers/antigravity-oauth-client.js +2 -2
  61. package/lib/providers/antigravity.js +141 -68
  62. package/lib/providers/catalog-store.js +120 -41
  63. package/lib/providers/claude-cli-version.js +3 -3
  64. package/lib/providers/claude.d.ts +9 -2
  65. package/lib/providers/claude.js +153 -90
  66. package/lib/providers/codex-client-version.js +3 -3
  67. package/lib/providers/codex-search.js +40 -13
  68. package/lib/providers/codex.js +196 -112
  69. package/lib/providers/common.js +71 -39
  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.js +14 -5
  77. package/lib/providers/external-usage.js +59 -23
  78. package/lib/providers/grok.js +152 -89
  79. package/lib/providers/image-pool.js +29 -11
  80. package/lib/providers/minimax-usage.js +31 -11
  81. package/lib/providers/npm-cli-version.js +10 -3
  82. package/lib/providers/ollama-settings.js +24 -8
  83. package/lib/providers/ollama-web.js +29 -11
  84. package/lib/providers/ollama.js +129 -66
  85. package/lib/providers/pool-family.js +11 -3
  86. package/lib/providers/pool-health.js +28 -8
  87. package/lib/providers/pool-usage.js +16 -9
  88. package/lib/providers/pool.d.ts +8 -0
  89. package/lib/providers/pool.js +81 -42
  90. package/lib/providers/rate-limit.js +8 -2
  91. package/lib/providers/replay.js +40 -17
  92. package/lib/providers/reset-redemption.js +18 -6
  93. package/lib/tools/image-generate.js +123 -65
  94. package/lib/tools/registration.js +5 -2
  95. package/lib/tools/video-generate.js +51 -31
  96. package/lib/tools/x-search.js +50 -22
  97. package/lib/translate/anthropic.js +85 -37
  98. package/lib/translate/antigravity-schema.js +71 -28
  99. package/lib/translate/antigravity-thinking.js +22 -9
  100. package/lib/translate/antigravity.js +151 -52
  101. package/lib/translate/chat-completions.js +67 -23
  102. package/lib/translate/resolved.js +55 -21
  103. package/lib/translate/responses.js +69 -29
  104. package/lib/translate/sse.js +4 -1
  105. package/package.json +27 -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.d.ts CHANGED
@@ -38,15 +38,15 @@ export interface VideoBytesResult {
38
38
  export type SpeedTier = 'standard' | 'fast';
39
39
  /** `speed` endpoint value: the session's choice plus the visibility list. */
40
40
  export interface SpeedState {
41
- /** The session's current speed tier (default `standard`). */
41
+ /** Explicit session speed, or the subagent's model default when supplied. */
42
42
  tier: SpeedTier;
43
43
  /** Codex model ids whose catalog advertises a fast tier. */
44
44
  fastModels: string[];
45
45
  }
46
- /** Speed state the RPC handler delegates to (in-memory, per session). */
46
+ /** Session overrides plus persisted subagent model defaults. */
47
47
  export interface SpeedController {
48
- /** Current speed state: the session's tier and the fast-capable codex models. */
49
- speed(sessionId: string): Promise<SpeedState>;
48
+ /** Pass the current Codex model to resolve a subagent's default speed. */
49
+ speed(sessionId: string, model?: string): Promise<SpeedState>;
50
50
  /** Set one session's speed tier. */
51
51
  setSpeed(sessionId: string, tier: SpeedTier): Promise<void>;
52
52
  }
@@ -65,6 +65,8 @@ export interface AccountStatus {
65
65
  }
66
66
  /** Login state of one provider, as rendered by the Settings page. */
67
67
  export interface ProviderStatus {
68
+ /** Saved picker visibility, attached by the status RPC from local preferences. */
69
+ visibleModels?: string[];
68
70
  /** Whether a login attempt is currently waiting for its code. */
69
71
  busy: boolean;
70
72
  /** Logged-in accounts, default first. */
@@ -110,6 +112,8 @@ export interface ProviderAccountCatalog {
110
112
  }
111
113
  /** Provider and account preference operations behind providerSettings/setProviderSettings. */
112
114
  export interface ProviderSettingsController {
115
+ /** Local picker selection, without triggering model discovery. */
116
+ visibleModels?(provider: ProviderId): string[] | undefined;
113
117
  get(provider: ProviderId, force: boolean): Promise<unknown>;
114
118
  set(provider: ProviderId, settings: unknown): Promise<void>;
115
119
  }
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,12 +301,26 @@ 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
- }))]));
261
- return ok({ providers: Object.fromEntries(entries) });
310
+ })),
311
+ ]));
312
+ return ok({
313
+ providers: Object.fromEntries(entries.map(([provider, status]) => {
314
+ const visibleModels = providerSettings?.visibleModels?.(provider);
315
+ return [
316
+ provider,
317
+ {
318
+ ...status,
319
+ ...(visibleModels === undefined ? {} : { visibleModels }),
320
+ },
321
+ ];
322
+ })),
323
+ });
262
324
  }
263
325
  case 'login': {
264
326
  const provider = readProvider(payload);
@@ -364,8 +426,8 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
364
426
  }
365
427
  }
366
428
  return ok(await ollama.models({
367
- ...apiKey === undefined ? {} : { apiKey },
368
- ...baseURL === undefined ? {} : { baseURL },
429
+ ...(apiKey === undefined ? {} : { apiKey }),
430
+ ...(baseURL === undefined ? {} : { baseURL }),
369
431
  }, signal));
370
432
  }
371
433
  case 'prepareReset':
@@ -382,7 +444,9 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
382
444
  case 'video':
383
445
  return ok(await controller.readVideo(readVideoName(payload), signal));
384
446
  case 'speed':
385
- return ok(await speed.speed(readSessionId(payload)));
447
+ return ok(await speed.speed(readSessionId(payload), payload.model === undefined
448
+ ? undefined
449
+ : readString(payload, 'model')));
386
450
  case 'setSpeed':
387
451
  await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
388
452
  return ok({ ok: true });
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;