dsh-plugin-subscriptions 0.5.1 → 0.5.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 (50) hide show
  1. package/README.md +42 -1
  2. package/README.zh.md +42 -1
  3. package/lib/auth/device-flow.d.ts +0 -9
  4. package/lib/auth/device-flow.js +2 -1
  5. package/lib/auth/rpc.d.ts +44 -13
  6. package/lib/auth/rpc.js +127 -9
  7. package/lib/auth/store.d.ts +75 -17
  8. package/lib/auth/store.js +148 -27
  9. package/lib/client/SubscriptionsSection.d.ts +26 -3
  10. package/lib/client/SubscriptionsSection.js +263 -67
  11. package/lib/client/index.js +11 -0
  12. package/lib/client/locales.d.ts +82 -10
  13. package/lib/client/locales.js +82 -10
  14. package/lib/client.js +837 -223
  15. package/lib/client.js.map +1 -1
  16. package/lib/http.d.ts +114 -0
  17. package/lib/http.js +402 -0
  18. package/lib/index.d.ts +21 -0
  19. package/lib/index.js +1938 -208
  20. package/lib/providers/accounts.d.ts +102 -0
  21. package/lib/providers/accounts.js +123 -0
  22. package/lib/providers/antigravity.d.ts +90 -0
  23. package/lib/providers/antigravity.js +392 -0
  24. package/lib/providers/claude.d.ts +22 -4
  25. package/lib/providers/claude.js +97 -16
  26. package/lib/providers/codex.d.ts +24 -3
  27. package/lib/providers/codex.js +121 -21
  28. package/lib/providers/common.d.ts +17 -0
  29. package/lib/providers/common.js +67 -3
  30. package/lib/providers/copilot.d.ts +23 -4
  31. package/lib/providers/copilot.js +99 -19
  32. package/lib/providers/grok.d.ts +24 -4
  33. package/lib/providers/grok.js +106 -19
  34. package/lib/providers/pool-family.d.ts +56 -0
  35. package/lib/providers/pool-family.js +45 -0
  36. package/lib/providers/pool-health.d.ts +74 -0
  37. package/lib/providers/pool-health.js +148 -0
  38. package/lib/providers/pool-usage.d.ts +57 -0
  39. package/lib/providers/pool-usage.js +130 -0
  40. package/lib/providers/pool.d.ts +107 -0
  41. package/lib/providers/pool.js +371 -0
  42. package/lib/tools/image-generate.d.ts +3 -3
  43. package/lib/tools/image-generate.js +4 -2
  44. package/lib/tools/video-generate.d.ts +2 -2
  45. package/lib/tools/video-generate.js +4 -2
  46. package/lib/tools/x-search.d.ts +2 -2
  47. package/lib/tools/x-search.js +4 -2
  48. package/lib/translate/antigravity.d.ts +110 -0
  49. package/lib/translate/antigravity.js +303 -0
  50. package/package.json +14 -9
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * Subscriptions settings section: one card per subscription provider with an
4
4
  * OAuth login/logout flow driven by the node half's `/subscriptions-auth` RPC
@@ -54,6 +54,16 @@ export async function callSubscriptionsAuth(rpc, endpoint, payload) {
54
54
  function messageOf(error) {
55
55
  return error instanceof Error ? error.message : String(error);
56
56
  }
57
+ /** Copy a keyed map without the entries whose key is not in `live`. */
58
+ function dropStale(map, live) {
59
+ const stale = Object.keys(map).filter(key => !live.has(key));
60
+ if (stale.length === 0)
61
+ return map;
62
+ const next = { ...map };
63
+ for (const key of stale)
64
+ delete next[key];
65
+ return next;
66
+ }
57
67
  /**
58
68
  * English-dictionary fallback for a missing inject `t` (standalone renders);
59
69
  * the slot inject always supplies the locale-bound one.
@@ -78,6 +88,10 @@ const styles = {
78
88
  border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12,
79
89
  padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
80
90
  },
91
+ proxyCard: {
92
+ padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
93
+ },
94
+ separator: { borderTop: '1px solid var(--dsw-alias-border-l2)' },
81
95
  cardHeader: { display: 'flex', alignItems: 'center', gap: 8 },
82
96
  dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },
83
97
  name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
@@ -110,6 +124,18 @@ const styles = {
110
124
  display: 'flex', justifyContent: 'space-between', gap: 8,
111
125
  fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
112
126
  },
127
+ accountRow: {
128
+ display: 'flex', flexDirection: 'column', gap: 6,
129
+ border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
130
+ padding: '8px 10px', marginTop: 4,
131
+ },
132
+ accountHeader: { display: 'flex', alignItems: 'center', gap: 8 },
133
+ accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },
134
+ starButton: {
135
+ border: 'none', background: 'transparent', padding: 0,
136
+ font: 'inherit', fontSize: 14, lineHeight: '20px', cursor: 'pointer',
137
+ color: 'var(--dsw-alias-state-warn-label)',
138
+ },
113
139
  usageTrack: {
114
140
  height: 6, borderRadius: 3, overflow: 'hidden',
115
141
  background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
@@ -132,12 +158,42 @@ const styles = {
132
158
  fontFamily: 'monospace', fontSize: 18, lineHeight: '24px', letterSpacing: 2,
133
159
  color: 'var(--dsw-alias-label-primary)', userSelect: 'all',
134
160
  },
161
+ proxyField: { display: 'flex', flexDirection: 'column', gap: 4 },
162
+ proxyLabel: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' },
163
+ proxyInput: {
164
+ height: 32, width: '100%', boxSizing: 'border-box',
165
+ border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
166
+ padding: '0 10px', font: 'inherit', fontSize: 14, lineHeight: '22px',
167
+ background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
168
+ },
169
+ proxyHint: {
170
+ margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
171
+ },
172
+ proxyCheck: {
173
+ display: 'flex', alignItems: 'center', gap: 8,
174
+ fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', cursor: 'pointer',
175
+ },
176
+ proxyMessage: { margin: 0, fontSize: 12, lineHeight: '18px' },
177
+ proxyActions: { display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'flex-end', marginTop: 2 },
178
+ modalOverlay: {
179
+ position: 'fixed', inset: 0, zIndex: 1000,
180
+ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16,
181
+ background: 'rgba(0, 0, 0, 0.45)',
182
+ },
183
+ modal: {
184
+ width: 460, maxWidth: '100%', maxHeight: '90vh', overflowY: 'auto',
185
+ boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 12,
186
+ padding: '16px 18px', borderRadius: 12,
187
+ background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
188
+ },
189
+ modalHeader: { display: 'flex', alignItems: 'center', gap: 8 },
190
+ modalTitle: { fontWeight: 600, fontSize: 15, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
135
191
  };
136
192
  /** Status dot color for one provider state. */
137
193
  function dotColor(status) {
138
194
  if (status?.busy === true)
139
195
  return 'var(--dsw-alias-state-warn-label)';
140
- if (status?.loggedIn === true)
196
+ if ((status?.accounts.length ?? 0) > 0)
141
197
  return 'var(--dsw-alias-state-success-primary)';
142
198
  return 'var(--dsw-alias-label-dimmed)';
143
199
  }
@@ -152,20 +208,8 @@ function statusText(t, status) {
152
208
  return t('checking');
153
209
  if (status.busy)
154
210
  return t('loginInProgress');
155
- if (status.loggedIn) {
156
- const params = {};
157
- if (status.account !== undefined)
158
- params.account = status.account;
159
- if (status.expiresAt !== undefined)
160
- params.date = new Date(status.expiresAt).toLocaleString();
161
- if (params.account !== undefined && params.date !== undefined)
162
- return t('loggedInAccountExpires', params);
163
- if (params.account !== undefined)
164
- return t('loggedInAccount', params);
165
- if (params.date !== undefined)
166
- return t('loggedInExpires', params);
167
- return t('loggedIn');
168
- }
211
+ if (status.accounts.length > 0)
212
+ return t('loggedInCount', { count: status.accounts.length });
169
213
  return t('notLoggedIn');
170
214
  }
171
215
  /**
@@ -188,6 +232,24 @@ function usageBarColor(usedPercent) {
188
232
  return 'var(--dsw-alias-state-warn-label)';
189
233
  return 'var(--dsw-alias-state-success-primary)';
190
234
  }
235
+ /** One-line status text of the proxy config card. */
236
+ function proxyStatusText(t, proxy, loadError) {
237
+ if (loadError !== undefined)
238
+ return t('proxyLoadFailed', { message: loadError });
239
+ if (proxy === undefined)
240
+ return t('proxyLoading');
241
+ if (proxy.error !== undefined)
242
+ return t('proxyStatusError', { message: proxy.error });
243
+ if (proxy.enabled)
244
+ return t('proxyStatusEnabled', { url: proxy.url });
245
+ return t('proxyStatusNone');
246
+ }
247
+ /** Feedback-line color of the proxy dialog. */
248
+ function messageColor(tone) {
249
+ return tone === 'error'
250
+ ? 'var(--dsw-alias-state-error-primary)'
251
+ : 'var(--dsw-alias-state-success-primary)';
252
+ }
191
253
  /**
192
254
  * The Subscriptions settings page component.
193
255
  * @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
@@ -204,13 +266,29 @@ export function SubscriptionsSection(props) {
204
266
  /** Pending device-flow codes (copilot), shown while the attempt polls. */
205
267
  const [deviceCodes, setDeviceCodes] = useState({});
206
268
  const [copiedCode, setCopiedCode] = useState(undefined);
269
+ /** Usage snapshots keyed `${provider}:${accountKey}` — every account tracks its own windows. */
207
270
  const [usages, setUsages] = useState({});
208
271
  const [usageErrors, setUsageErrors] = useState({});
209
272
  const [usageLoading, setUsageLoading] = useState({});
210
273
  const mountedRef = useRef(true);
211
274
  const pollersRef = useRef(new Map());
212
- /** Providers with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
275
+ /** Accounts with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
213
276
  const usageInflightRef = useRef(new Set());
277
+ /** Proxy config as last answered by `proxyGet`/`proxySet`. */
278
+ const [proxy, setProxy] = useState(undefined);
279
+ const [proxyLoadError, setProxyLoadError] = useState(undefined);
280
+ /** Proxy dialog state (draft fields; the password never pre-fills). */
281
+ const [proxyOpen, setProxyOpen] = useState(false);
282
+ const [proxyEnabled, setProxyEnabled] = useState(false);
283
+ const [proxyUrl, setProxyUrl] = useState('');
284
+ const [proxyUsername, setProxyUsername] = useState('');
285
+ const [proxyPassword, setProxyPassword] = useState('');
286
+ const [proxyClearPassword, setProxyClearPassword] = useState(false);
287
+ const [proxyBypass, setProxyBypass] = useState('');
288
+ const [proxySaving, setProxySaving] = useState(false);
289
+ const [proxyTesting, setProxyTesting] = useState(false);
290
+ const [proxyMessage, setProxyMessage] = useState(undefined);
291
+ const [proxyTestResult, setProxyTestResult] = useState(undefined);
214
292
  const setProviderError = useCallback((provider, message) => {
215
293
  if (!mountedRef.current)
216
294
  return;
@@ -248,7 +326,7 @@ export function SubscriptionsSection(props) {
248
326
  setStatuses(response.providers);
249
327
  for (const { id } of PROVIDERS) {
250
328
  const status = response.providers[id];
251
- if (status.loggedIn || !status.busy) {
329
+ if (status.accounts.length > 0 || !status.busy) {
252
330
  stopPolling(id);
253
331
  // The attempt settled (success, timeout, or cancel): drop the code card.
254
332
  setDeviceCodes((prev) => {
@@ -288,64 +366,58 @@ export function SubscriptionsSection(props) {
288
366
  pollersRef.current.clear();
289
367
  };
290
368
  }, [refresh, startPolling]);
291
- const loadUsage = useCallback(async (provider) => {
292
- if (rpc === undefined || usageInflightRef.current.has(provider))
369
+ const loadUsage = useCallback(async (provider, account) => {
370
+ const key = `${provider}:${account}`;
371
+ if (rpc === undefined || usageInflightRef.current.has(key))
293
372
  return;
294
- usageInflightRef.current.add(provider);
295
- setUsageLoading(prev => ({ ...prev, [provider]: true }));
373
+ usageInflightRef.current.add(key);
374
+ setUsageLoading(prev => ({ ...prev, [key]: true }));
296
375
  try {
297
- const usage = await callSubscriptionsAuth(rpc, 'usage', { provider });
376
+ const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account });
298
377
  if (!mountedRef.current)
299
378
  return;
300
- setUsages(prev => ({ ...prev, [provider]: usage }));
379
+ setUsages(prev => ({ ...prev, [key]: usage }));
301
380
  setUsageErrors((prev) => {
302
381
  const next = { ...prev };
303
- delete next[provider];
382
+ delete next[key];
304
383
  return next;
305
384
  });
306
385
  }
307
386
  catch (error) {
308
387
  if (mountedRef.current)
309
- setUsageErrors(prev => ({ ...prev, [provider]: messageOf(error) }));
388
+ setUsageErrors(prev => ({ ...prev, [key]: messageOf(error) }));
310
389
  }
311
390
  finally {
312
- usageInflightRef.current.delete(provider);
391
+ usageInflightRef.current.delete(key);
313
392
  if (mountedRef.current)
314
- setUsageLoading(prev => ({ ...prev, [provider]: false }));
393
+ setUsageLoading(prev => ({ ...prev, [key]: false }));
315
394
  }
316
395
  }, [rpc]);
317
- // Fetch usage once a provider is logged in; drop the cached snapshot on
318
- // logout so a re-login refetches. A failed lookup does not auto-retry — the
319
- // per-card Refresh button is the retry path.
396
+ // Fetch usage once an account is logged in; drop the snapshots of accounts
397
+ // that vanished so a re-login refetches. A failed lookup does not auto-retry
398
+ // — the per-account Refresh button is the retry path.
320
399
  useEffect(() => {
400
+ const live = new Set();
321
401
  for (const { id } of PROVIDERS) {
322
- const status = statuses[id];
323
- if (status === undefined)
324
- continue;
325
- if (status.loggedIn) {
326
- if (usages[id] === undefined && usageErrors[id] === undefined)
327
- void loadUsage(id);
328
- }
329
- else if (usages[id] !== undefined || usageErrors[id] !== undefined) {
330
- setUsages((prev) => {
331
- const next = { ...prev };
332
- delete next[id];
333
- return next;
334
- });
335
- setUsageErrors((prev) => {
336
- const next = { ...prev };
337
- delete next[id];
338
- return next;
339
- });
402
+ for (const account of statuses[id]?.accounts ?? []) {
403
+ const key = `${id}:${account.key}`;
404
+ live.add(key);
405
+ if (usages[key] === undefined && usageErrors[key] === undefined)
406
+ void loadUsage(id, account.key);
340
407
  }
341
408
  }
409
+ setUsages(prev => dropStale(prev, live));
410
+ setUsageErrors(prev => dropStale(prev, live));
342
411
  }, [statuses, usages, usageErrors, loadUsage]);
343
- const login = useCallback(async (provider) => {
412
+ const login = useCallback(async (provider, method) => {
344
413
  if (rpc === undefined)
345
414
  return;
346
415
  setProviderError(provider, undefined);
347
416
  try {
348
- const response = await callSubscriptionsAuth(rpc, 'login', { provider });
417
+ const response = await callSubscriptionsAuth(rpc, 'login', {
418
+ provider,
419
+ ...method === undefined ? {} : { method },
420
+ });
349
421
  if (typeof response.authorizeUrl === 'string' && response.authorizeUrl === '') {
350
422
  // Instant login (e.g. imported from Claude Code credentials)
351
423
  await refresh();
@@ -357,7 +429,10 @@ export function SubscriptionsSection(props) {
357
429
  if (!mountedRef.current)
358
430
  return;
359
431
  // Optimistic busy so Cancel and the manual fallback appear before the first poll tick.
360
- setStatuses(prev => ({ ...prev, [provider]: { ...prev[provider], busy: true, loggedIn: false } }));
432
+ setStatuses(prev => ({
433
+ ...prev,
434
+ [provider]: { accounts: prev[provider]?.accounts ?? [], ...prev[provider], busy: true },
435
+ }));
361
436
  if (typeof response.userCode === 'string' && response.userCode.length > 0) {
362
437
  // Device flow: show the code card instead of opening the page blind —
363
438
  // the user copies the code first, then opens the verification page.
@@ -401,20 +476,32 @@ export function SubscriptionsSection(props) {
401
476
  }
402
477
  await refresh();
403
478
  }, [rpc, manualDrafts, setProviderError, refresh]);
404
- const logout = useCallback(async (provider, name) => {
479
+ const logout = useCallback(async (provider, account, display, name) => {
405
480
  if (rpc === undefined)
406
481
  return;
407
- if (!window.confirm(t('logoutConfirm', { provider: name })))
482
+ if (!window.confirm(t('logoutAccountConfirm', { provider: name, account: display })))
408
483
  return;
409
484
  setProviderError(provider, undefined);
410
485
  try {
411
- await callSubscriptionsAuth(rpc, 'logout', { provider });
486
+ await callSubscriptionsAuth(rpc, 'logout', { provider, account });
412
487
  }
413
488
  catch (error) {
414
489
  setProviderError(provider, messageOf(error));
415
490
  }
416
491
  await refresh();
417
492
  }, [rpc, t, setProviderError, refresh]);
493
+ const setDefault = useCallback(async (provider, account) => {
494
+ if (rpc === undefined)
495
+ return;
496
+ setProviderError(provider, undefined);
497
+ try {
498
+ await callSubscriptionsAuth(rpc, 'setDefault', { provider, account });
499
+ }
500
+ catch (error) {
501
+ setProviderError(provider, messageOf(error));
502
+ }
503
+ await refresh();
504
+ }, [rpc, setProviderError, refresh]);
418
505
  const copyDeviceCode = useCallback((provider, userCode) => {
419
506
  void navigator.clipboard?.writeText(userCode).then(() => {
420
507
  if (!mountedRef.current)
@@ -427,22 +514,131 @@ export function SubscriptionsSection(props) {
427
514
  }, 1500);
428
515
  }).catch(() => undefined);
429
516
  }, []);
517
+ // Proxy configuration: load once on mount; the dialog drives proxySet/proxyTest.
518
+ useEffect(() => {
519
+ if (rpc === undefined)
520
+ return;
521
+ let alive = true;
522
+ void callSubscriptionsAuth(rpc, 'proxyGet', {}).then((view) => {
523
+ if (!alive)
524
+ return;
525
+ setProxy(view);
526
+ setProxyLoadError(undefined);
527
+ }).catch((error) => {
528
+ if (alive)
529
+ setProxyLoadError(messageOf(error));
530
+ });
531
+ return () => { alive = false; };
532
+ }, [rpc]);
533
+ useEffect(() => {
534
+ if (!proxyOpen)
535
+ return;
536
+ const onKey = (event) => {
537
+ if (event.key === 'Escape')
538
+ setProxyOpen(false);
539
+ };
540
+ window.addEventListener('keydown', onKey);
541
+ return () => window.removeEventListener('keydown', onKey);
542
+ }, [proxyOpen]);
543
+ const openProxyDialog = useCallback(() => {
544
+ if (proxy === undefined)
545
+ return;
546
+ setProxyEnabled(proxy.enabled);
547
+ setProxyUrl(proxy.url);
548
+ setProxyUsername(proxy.username ?? '');
549
+ setProxyPassword('');
550
+ setProxyClearPassword(false);
551
+ setProxyBypass(proxy.bypass.join(', '));
552
+ setProxyMessage(undefined);
553
+ setProxyTestResult(undefined);
554
+ setProxyOpen(true);
555
+ }, [proxy]);
556
+ const saveProxy = useCallback(async () => {
557
+ if (rpc === undefined)
558
+ return;
559
+ setProxySaving(true);
560
+ setProxyMessage(undefined);
561
+ try {
562
+ const view = await callSubscriptionsAuth(rpc, 'proxySet', {
563
+ enabled: proxyEnabled,
564
+ url: proxyUrl.trim(),
565
+ username: proxyUsername,
566
+ ...proxyClearPassword ? { password: null } : proxyPassword !== '' ? { password: proxyPassword } : {},
567
+ bypass: proxyBypass.split(/[,\n]/).map(entry => entry.trim()).filter(entry => entry !== ''),
568
+ });
569
+ setProxy(view);
570
+ setProxyLoadError(undefined);
571
+ setProxyMessage({ tone: 'success', text: t('proxySaved') });
572
+ setProxyOpen(false);
573
+ }
574
+ catch (error) {
575
+ setProxyMessage({ tone: 'error', text: t('proxySaveFailed', { message: messageOf(error) }) });
576
+ }
577
+ finally {
578
+ setProxySaving(false);
579
+ }
580
+ }, [rpc, proxyEnabled, proxyUrl, proxyUsername, proxyPassword, proxyClearPassword, proxyBypass, t]);
581
+ const testProxy = useCallback(async () => {
582
+ if (rpc === undefined || proxyTesting)
583
+ return;
584
+ setProxyTesting(true);
585
+ setProxyTestResult(undefined);
586
+ try {
587
+ // Test the dialog's current inputs (they do not need to be saved first);
588
+ // the host builds a throwaway agent for the probe.
589
+ setProxyTestResult(await callSubscriptionsAuth(rpc, 'proxyTest', {
590
+ proxy: {
591
+ url: proxyUrl.trim(),
592
+ ...proxyUsername.trim() !== '' ? { username: proxyUsername.trim() } : {},
593
+ ...proxyPassword !== '' ? { password: proxyPassword } : {},
594
+ },
595
+ }));
596
+ }
597
+ catch (error) {
598
+ setProxyTestResult({ ok: false, viaProxy: false, error: messageOf(error) });
599
+ }
600
+ finally {
601
+ setProxyTesting(false);
602
+ }
603
+ }, [rpc, proxyTesting, proxyUrl, proxyUsername, proxyPassword]);
430
604
  if (rpc === undefined) {
431
605
  return _jsx("p", { style: styles.intro, children: t('unavailable') });
432
606
  }
433
- return (_jsxs("div", { style: styles.section, children: [_jsx("p", { style: styles.intro, children: t('intro') }), PROVIDERS.map(({ id, name }) => {
607
+ return (_jsxs("div", { style: styles.section, children: [_jsx("p", { style: styles.intro, children: t('intro') }), _jsxs("div", { style: styles.proxyCard, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: {
608
+ ...styles.dot,
609
+ background: proxy?.enabled === true
610
+ ? 'var(--dsw-alias-state-success-primary)'
611
+ : 'var(--dsw-alias-label-dimmed)',
612
+ } }), _jsx("span", { style: styles.name, children: t('proxyTitle') }), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: openProxyDialog, children: t('proxyConfigure') })] }), _jsx("p", { style: styles.statusLine, children: proxyStatusText(t, proxy, proxyLoadError) })] }), _jsx("div", { style: styles.separator }), PROVIDERS.map(({ id, name }) => {
434
613
  const status = statuses[id];
435
614
  const busy = status?.busy === true;
436
615
  const deviceCode = deviceCodes[id];
437
- const usage = usages[id];
438
- const usageError = usageErrors[id];
439
- // Providers without a usage endpoint answer supported:false — no block.
440
- const showUsage = status?.loggedIn === true && usage?.supported !== false
441
- && (usage !== undefined || usageError !== undefined || usageLoading[id] === true);
442
- return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) } }), _jsx("span", { style: styles.name, children: name })] }), _jsx("p", { style: styles.statusLine, children: statusText(t, status) }), status?.detail !== undefined && status.detail !== '' && (_jsx("p", { style: styles.statusLine, children: status.detail })), errors[id] !== undefined && _jsx("p", { style: styles.errorLine, children: errors[id] }), _jsxs("div", { style: styles.actions, children: [!busy && status?.loggedIn !== true && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('login') })), busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(id); }, children: t('cancel') })), status?.loggedIn === true && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void logout(id, name); }, children: t('logout') }))] }), showUsage && (_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.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: { ...styles.usageRefresh, ...usageLoading[id] === true ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: usageLoading[id] === true, onClick: () => { void loadUsage(id); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
443
- const percent = Math.min(100, Math.max(0, window.usedPercent));
444
- return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [`${String(Math.round(percent))}%`, window.resetsAt !== undefined
445
- && ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx("div", { style: styles.usageTrack, children: _jsx("div", { style: { ...styles.usageFill, width: `${String(percent)}%`, background: usageBarColor(percent) } }) })] }, index));
446
- })] })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { copyDeviceCode(id, deviceCode.userCode); }, children: copiedCode === id ? t('deviceCodeCopied') : t('deviceCodeCopy') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { window.open(deviceCode.verificationUrl, '_blank', 'noopener'); }, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value })) }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void submitManual(id); }, children: t('submit') })] })] }))] }, id));
447
- })] }));
616
+ const accounts = status?.accounts ?? [];
617
+ return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) } }), _jsx("span", { style: styles.name, children: name })] }), _jsx("p", { style: styles.statusLine, children: statusText(t, status) }), status?.detail !== undefined && status.detail !== '' && (_jsx("p", { style: styles.statusLine, children: status.detail })), errors[id] !== undefined && _jsx("p", { style: styles.errorLine, children: errors[id] }), accounts.map((account) => {
618
+ const usageKey = `${id}:${account.key}`;
619
+ const usage = usages[usageKey];
620
+ const usageError = usageErrors[usageKey];
621
+ const display = account.account ?? account.key;
622
+ // Providers without a usage endpoint answer supported:false — no block.
623
+ const showUsage = usage?.supported !== false
624
+ && (usage !== undefined || usageError !== undefined || usageLoading[usageKey] === true);
625
+ return (_jsxs("div", { style: styles.accountRow, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('setDefault'), onClick: () => {
626
+ if (!account.isDefault)
627
+ void setDefault(id, account.key);
628
+ }, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', { date: new Date(account.expiresAt).toLocaleString() }) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(id, account.key, display, name); }, children: t('logout') })] }), showUsage && (_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.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: { ...styles.usageRefresh, ...usageLoading[usageKey] === true ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: usageLoading[usageKey] === true, onClick: () => { void loadUsage(id, account.key); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
629
+ const percent = Math.min(100, Math.max(0, window.usedPercent));
630
+ return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [`${String(Math.round(percent))}%`, window.resetsAt !== undefined
631
+ && ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx("div", { style: styles.usageTrack, children: _jsx("div", { style: { ...styles.usageFill, width: `${String(percent)}%`, background: usageBarColor(percent) } }) })] }, index));
632
+ })] }))] }, account.key));
633
+ }), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'oauth'); }, children: t('addAccountOAuth') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'keychain'); }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('addAccount') })), busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(id); }, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { copyDeviceCode(id, deviceCode.userCode); }, children: copiedCode === id ? t('deviceCodeCopied') : t('deviceCodeCopy') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { window.open(deviceCode.verificationUrl, '_blank', 'noopener'); }, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value })) }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void submitManual(id); }, children: t('submit') })] })] }))] }, id));
634
+ }), proxyOpen && (_jsx("div", { style: styles.modalOverlay, onClick: () => setProxyOpen(false), children: _jsxs("div", { style: styles.modal, onClick: event => event.stopPropagation(), children: [_jsxs("div", { style: styles.modalHeader, children: [_jsx("span", { style: styles.modalTitle, children: t('proxyDialogTitle') }), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto' }, onClick: () => setProxyOpen(false), children: t('proxyDialogClose') })] }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyEnabled, onChange: event => setProxyEnabled(event.target.checked) }), _jsx("span", { children: t('proxyEnabled') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUrl') }), _jsx("input", { style: styles.proxyInput, value: proxyUrl, placeholder: t('proxyUrlPlaceholder'), onChange: event => setProxyUrl(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyUrlHint') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUsername') }), _jsx("input", { style: styles.proxyInput, value: proxyUsername, placeholder: t('proxyUsernamePlaceholder'), onChange: event => setProxyUsername(event.target.value) })] }), _jsxs("div", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyPassword') }), _jsx("input", { type: "password", style: styles.proxyInput, value: proxyPassword, placeholder: t('proxyPasswordPlaceholder'), onChange: event => setProxyPassword(event.target.value) }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyClearPassword, onChange: event => setProxyClearPassword(event.target.checked) }), _jsx("span", { children: t('proxyClearPassword') })] })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyBypass') }), _jsx("input", { style: styles.proxyInput, value: proxyBypass, placeholder: t('proxyBypassPlaceholder'), onChange: event => setProxyBypass(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyBypassHint') })] }), _jsx("p", { style: styles.proxyHint, children: t('proxyNote') }), proxyMessage !== undefined && (_jsx("p", { style: { ...styles.proxyMessage, color: messageColor(proxyMessage.tone) }, children: proxyMessage.text })), proxyTestResult !== undefined && (_jsx("p", { style: {
635
+ ...styles.proxyMessage,
636
+ color: proxyTestResult.ok
637
+ ? 'var(--dsw-alias-state-success-primary)'
638
+ : 'var(--dsw-alias-state-error-primary)',
639
+ }, children: proxyTestResult.ok
640
+ ? (proxyTestResult.viaProxy
641
+ ? t('proxyTestOk', { status: String(proxyTestResult.status), ms: String(proxyTestResult.latencyMs) })
642
+ : t('proxyTestOkDirect', { status: String(proxyTestResult.status), ms: String(proxyTestResult.latencyMs) }))
643
+ : t('proxyTestFail', { message: proxyTestResult.error ?? '' }) })), _jsxs("div", { style: styles.proxyActions, children: [_jsx("button", { type: "button", style: { ...styles.button, ...proxyTesting ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: proxyTesting, onClick: () => { void testProxy(); }, children: proxyTesting ? t('proxyTesting') : t('proxyTest') }), _jsx("button", { type: "button", style: { ...styles.button, ...proxySaving ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: proxySaving, onClick: () => { void saveProxy(); }, children: proxySaving ? t('proxySaving') : t('proxySave') }), _jsx("button", { type: "button", style: styles.button, onClick: () => setProxyOpen(false), children: t('proxyCancel') })] })] }) }))] }));
448
644
  }
@@ -22,6 +22,17 @@ export const inject = ['slots', 'connection', 'locale'];
22
22
  */
23
23
  export function apply(ctx) {
24
24
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-subscriptions: copy dictionaries');
25
+ // Settings-shell nudge: the panel (nav title + header row + section body)
26
+ // sits flush against the panel's top edge; push it down a little to leave
27
+ // breathing room. Scoped by the settings panel's own dialog role + nav child
28
+ // so other aria-modal dialogs (e.g. the attachment lightbox) are untouched.
29
+ ctx.effect(() => {
30
+ const style = document.createElement('style');
31
+ style.setAttribute('data-plugin', 'dsh-plugin-subscriptions');
32
+ style.textContent = 'div[role="dialog"][aria-modal="true"]:has(> nav) { padding-top: 14px; }';
33
+ document.head.appendChild(style);
34
+ return () => style.remove();
35
+ }, 'dsh-plugin-subscriptions: settings panel breathing room');
25
36
  // The client-runtime Context merge types `connection` as the host handle;
26
37
  // in the browser shell the same key holds the full client ConnectionHandle.
27
38
  const connection = ctx.get('connection');
@@ -6,15 +6,19 @@ export declare const en: {
6
6
  unavailable: string;
7
7
  checking: string;
8
8
  loginInProgress: string;
9
- loggedIn: string;
10
- loggedInAccount: string;
11
- loggedInExpires: string;
12
- loggedInAccountExpires: string;
13
9
  notLoggedIn: string;
10
+ loggedInCount: string;
11
+ accountExpires: string;
12
+ defaultBadge: string;
13
+ setDefault: string;
14
+ addAccount: string;
15
+ addAccountOAuth: string;
16
+ addAccountKeychain: string;
17
+ addAccountHint: string;
14
18
  login: string;
15
19
  cancel: string;
16
20
  logout: string;
17
- logoutConfirm: string;
21
+ logoutAccountConfirm: string;
18
22
  manualSummary: string;
19
23
  manualPlaceholder: string;
20
24
  submit: string;
@@ -51,6 +55,38 @@ export declare const en: {
51
55
  speedFastDescription: string;
52
56
  commandFast: string;
53
57
  commandFastUnavailable: string;
58
+ proxyTitle: string;
59
+ proxyStatusNone: string;
60
+ proxyStatusEnabled: string;
61
+ proxyStatusError: string;
62
+ proxyConfigure: string;
63
+ proxyDialogTitle: string;
64
+ proxyDialogClose: string;
65
+ proxyEnabled: string;
66
+ proxyUrl: string;
67
+ proxyUrlPlaceholder: string;
68
+ proxyUrlHint: string;
69
+ proxyUsername: string;
70
+ proxyUsernamePlaceholder: string;
71
+ proxyPassword: string;
72
+ proxyPasswordPlaceholder: string;
73
+ proxyClearPassword: string;
74
+ proxyBypass: string;
75
+ proxyBypassPlaceholder: string;
76
+ proxyBypassHint: string;
77
+ proxyTest: string;
78
+ proxyTesting: string;
79
+ proxyTestOk: string;
80
+ proxyTestOkDirect: string;
81
+ proxyTestFail: string;
82
+ proxySave: string;
83
+ proxyCancel: string;
84
+ proxySaving: string;
85
+ proxyLoading: string;
86
+ proxyLoadFailed: string;
87
+ proxySaved: string;
88
+ proxySaveFailed: string;
89
+ proxyNote: string;
54
90
  };
55
91
  /** zh strings, one per {@link en} key. */
56
92
  export declare const zh: {
@@ -59,15 +95,19 @@ export declare const zh: {
59
95
  unavailable: string;
60
96
  checking: string;
61
97
  loginInProgress: string;
62
- loggedIn: string;
63
- loggedInAccount: string;
64
- loggedInExpires: string;
65
- loggedInAccountExpires: string;
66
98
  notLoggedIn: string;
99
+ loggedInCount: string;
100
+ accountExpires: string;
101
+ defaultBadge: string;
102
+ setDefault: string;
103
+ addAccount: string;
104
+ addAccountOAuth: string;
105
+ addAccountKeychain: string;
106
+ addAccountHint: string;
67
107
  login: string;
68
108
  cancel: string;
69
109
  logout: string;
70
- logoutConfirm: string;
110
+ logoutAccountConfirm: string;
71
111
  manualSummary: string;
72
112
  manualPlaceholder: string;
73
113
  submit: string;
@@ -104,6 +144,38 @@ export declare const zh: {
104
144
  speedFastDescription: string;
105
145
  commandFast: string;
106
146
  commandFastUnavailable: string;
147
+ proxyTitle: string;
148
+ proxyStatusNone: string;
149
+ proxyStatusEnabled: string;
150
+ proxyStatusError: string;
151
+ proxyConfigure: string;
152
+ proxyDialogTitle: string;
153
+ proxyDialogClose: string;
154
+ proxyEnabled: string;
155
+ proxyUrl: string;
156
+ proxyUrlPlaceholder: string;
157
+ proxyUrlHint: string;
158
+ proxyUsername: string;
159
+ proxyUsernamePlaceholder: string;
160
+ proxyPassword: string;
161
+ proxyPasswordPlaceholder: string;
162
+ proxyClearPassword: string;
163
+ proxyBypass: string;
164
+ proxyBypassPlaceholder: string;
165
+ proxyBypassHint: string;
166
+ proxyTest: string;
167
+ proxyTesting: string;
168
+ proxyTestOk: string;
169
+ proxyTestOkDirect: string;
170
+ proxyTestFail: string;
171
+ proxySave: string;
172
+ proxyCancel: string;
173
+ proxySaving: string;
174
+ proxyLoading: string;
175
+ proxyLoadFailed: string;
176
+ proxySaved: string;
177
+ proxySaveFailed: string;
178
+ proxyNote: string;
107
179
  };
108
180
  /** The Subscriptions namespace key union (en is the key-set source of truth). */
109
181
  export type SubscriptionsKey = keyof typeof en;