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
@@ -6,7 +6,10 @@ import { parseProviderJson } from './common.js';
6
6
  * bar.
7
7
  */
8
8
  function usedFromCounts(total, count) {
9
- return typeof total === 'number' && Number.isFinite(total) && total > 0 && typeof count === 'number'
9
+ return typeof total === 'number' &&
10
+ Number.isFinite(total) &&
11
+ total > 0 &&
12
+ typeof count === 'number'
10
13
  ? (1 - count / total) * 100
11
14
  : undefined;
12
15
  }
@@ -36,13 +39,18 @@ export async function fetchMiniMaxUsage(key, region, http = fetch, signal) {
36
39
  if (key.startsWith('sk-api-'))
37
40
  throw new Error('MiniMax requires a subscription key, not a pay-as-you-go key');
38
41
  const response = await http(`https://www.minimax.${region === 'cn' ? 'cn' : 'io'}/v1/token_plan/remains`, {
39
- headers: { authorization: `Bearer ${key}`, 'content-type': 'application/json' },
42
+ headers: {
43
+ authorization: `Bearer ${key}`,
44
+ 'content-type': 'application/json',
45
+ },
40
46
  ...(signal === undefined ? {} : { signal }),
41
47
  });
42
48
  if (!response.ok)
43
49
  throw new Error(`MiniMax usage endpoint returned HTTP ${response.status}`);
44
- const body = await parseProviderJson(response, 'MiniMax usage');
45
- if ((body?.base_resp?.status_code !== undefined && body.base_resp.status_code !== 0) || !Array.isArray(body?.model_remains)) {
50
+ const body = (await parseProviderJson(response, 'MiniMax usage'));
51
+ if ((body?.base_resp?.status_code !== undefined &&
52
+ body.base_resp.status_code !== 0) ||
53
+ !Array.isArray(body?.model_remains)) {
46
54
  throw new Error('MiniMax usage response failed or has no quota records');
47
55
  }
48
56
  const windows = [];
@@ -60,22 +68,34 @@ export async function fetchMiniMaxUsage(key, region, http = fetch, signal) {
60
68
  if (status === 3)
61
69
  continue; // Unlimited pools cannot have a finite percentage.
62
70
  const remaining = row[`${prefix}_remaining_percent`];
63
- const boost = prefix === 'current_weekly' ? row.weekly_boost_permille ?? 1000 : 1000;
71
+ const boost = prefix === 'current_weekly' ? (row.weekly_boost_permille ?? 1000) : 1000;
64
72
  if (typeof boost !== 'number' || !Number.isFinite(boost) || boost <= 0)
65
73
  continue;
66
74
  // An explicit percentage needs no counts: time-based plans send zero
67
75
  // counts beside it. Official CLI compatibility: without a percentage, the
68
76
  // legacy counts are the quota remaining.
69
- const usedPercent = typeof remaining === 'number' ? 100 - remaining * boost / 1000
77
+ const usedPercent = typeof remaining === 'number'
78
+ ? 100 - (remaining * boost) / 1000
70
79
  : usedFromCounts(row[`${prefix}_total_count`], row[`${prefix}_usage_count`]);
71
- if (usedPercent === undefined || !Number.isFinite(usedPercent) || usedPercent < 0 || usedPercent > 100)
80
+ if (usedPercent === undefined ||
81
+ !Number.isFinite(usedPercent) ||
82
+ usedPercent < 0 ||
83
+ usedPercent > 100)
72
84
  continue;
73
85
  const start = row[startKey];
74
86
  const end = row[endKey];
75
- const validBounds = typeof start === 'number' && Number.isFinite(start) && start > 0
76
- && typeof end === 'number' && Number.isFinite(end) && end > start;
77
- windows.push({ kind: fallback === 'session' && validBounds && end - start !== 18_000_000 ? 'other' : fallback,
78
- scope: row.model_name, usedPercent,
87
+ const validBounds = typeof start === 'number' &&
88
+ Number.isFinite(start) &&
89
+ start > 0 &&
90
+ typeof end === 'number' &&
91
+ Number.isFinite(end) &&
92
+ end > start;
93
+ windows.push({
94
+ kind: fallback === 'session' && validBounds && end - start !== 18_000_000
95
+ ? 'other'
96
+ : fallback,
97
+ scope: row.model_name,
98
+ usedPercent,
79
99
  ...(validBounds ? { startsAt: start, resetsAt: end } : {}),
80
100
  });
81
101
  }
@@ -34,13 +34,17 @@ export class NpmCliVersionCache {
34
34
  this.timeoutMs = options.timeoutMs ?? 5000;
35
35
  }
36
36
  /** A manual catalog refresh also checks for a newly released CLI. */
37
- invalidate() { this.expiresAt = 0; }
37
+ invalidate() {
38
+ this.expiresAt = 0;
39
+ }
38
40
  resolve() {
39
41
  if (this.pending !== undefined)
40
42
  return this.pending;
41
43
  if (this.presented !== undefined && this.now() < this.expiresAt)
42
44
  return Promise.resolve(this.presented.version);
43
- this.pending = this.refresh().finally(() => { this.pending = undefined; });
45
+ this.pending = this.refresh().finally(() => {
46
+ this.pending = undefined;
47
+ });
44
48
  return this.pending;
45
49
  }
46
50
  /**
@@ -82,7 +86,10 @@ export class NpmCliVersionCache {
82
86
  }, this.timeoutMs);
83
87
  });
84
88
  // Also bounded when an injected transport ignores cancellation.
85
- this.presented = { version: await Promise.race([lookup(), timeout]), source: 'npm' };
89
+ this.presented = {
90
+ version: await Promise.race([lookup(), timeout]),
91
+ source: 'npm',
92
+ };
86
93
  this.expiresAt = this.now() + 6 * 60 * 60_000;
87
94
  }
88
95
  catch {
@@ -73,7 +73,9 @@ function readModel(entry, index) {
73
73
  const record = entry;
74
74
  // The id is the wire id, so surrounding whitespace is always a paste
75
75
  // artifact; trim it before the duplicate check sees it.
76
- const model = { id: readNonEmptyString(record.id, `models[${index}].id`).trim() };
76
+ const model = {
77
+ id: readNonEmptyString(record.id, `models[${index}].id`).trim(),
78
+ };
77
79
  if (record.name !== undefined)
78
80
  model.name = readNonEmptyString(record.name, `models[${index}].name`);
79
81
  if (record.contextWindow !== undefined) {
@@ -169,20 +171,34 @@ export class OllamaSettingsStore {
169
171
  }
170
172
  }
171
173
  /** Why the stored file is ignored, when it is. */
172
- corruption() { return this.corrupt; }
173
- get() { return structuredClone(this.current); }
174
+ corruption() {
175
+ return this.corrupt;
176
+ }
177
+ get() {
178
+ return structuredClone(this.current);
179
+ }
174
180
  /** The stored catalog, or `undefined` when the store carries no override. */
175
- models() { return this.current.models; }
176
- web() { return this.current.web; }
177
- baseURL() { return this.current.baseURL; }
178
- apiKeyRef() { return this.current.apiKeyRef; }
181
+ models() {
182
+ return this.current.models;
183
+ }
184
+ web() {
185
+ return this.current.web;
186
+ }
187
+ baseURL() {
188
+ return this.current.baseURL;
189
+ }
190
+ apiKeyRef() {
191
+ return this.current.apiKeyRef;
192
+ }
179
193
  set(input) {
180
194
  const validated = validateOllamaSettings(input);
181
195
  const run = this.writes.then(async () => {
182
196
  await mkdir(dirname(this.path), { recursive: true });
183
197
  const temporary = `${this.path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
184
198
  try {
185
- await writeFile(temporary, JSON.stringify(validated, null, 2), { mode: 0o600 });
199
+ await writeFile(temporary, JSON.stringify(validated, null, 2), {
200
+ mode: 0o600,
201
+ });
186
202
  await rename(temporary, this.path);
187
203
  }
188
204
  finally {
@@ -18,7 +18,9 @@ export const OLLAMA_WEB_MAX_RESULTS = 10;
18
18
  export const OLLAMA_WEB_MAX_BYTES = 2 * 1024 * 1024;
19
19
  /** The effective base URL; both forms are read fresh so saves apply live. */
20
20
  function baseURLOf(options) {
21
- return typeof options.baseURL === 'function' ? options.baseURL() : options.baseURL;
21
+ return typeof options.baseURL === 'function'
22
+ ? options.baseURL()
23
+ : options.baseURL;
22
24
  }
23
25
  function throwIfAborted(signal) {
24
26
  signal?.throwIfAborted();
@@ -48,6 +50,7 @@ async function postJson(url, apiKey, payload, options, signal) {
48
50
  controller.abort(new Error('ollama web request timed out'));
49
51
  }, timeoutMs);
50
52
  if (typeof timer.unref === 'function') {
53
+ ;
51
54
  timer.unref();
52
55
  }
53
56
  const onCallerAbort = () => controller.abort(signal?.reason);
@@ -57,8 +60,8 @@ async function postJson(url, apiKey, payload, options, signal) {
57
60
  method: 'POST',
58
61
  headers: {
59
62
  'content-type': 'application/json',
60
- 'accept': 'application/json',
61
- 'authorization': `Bearer ${apiKey}`,
63
+ accept: 'application/json',
64
+ authorization: `Bearer ${apiKey}`,
62
65
  ...attributionHeaders(),
63
66
  },
64
67
  body: JSON.stringify(payload),
@@ -69,7 +72,7 @@ async function postJson(url, apiKey, payload, options, signal) {
69
72
  throw new WebError(`ollama web request failed (HTTP ${String(response.status)})`, 'OLLAMA_WEB_BAD_RESPONSE');
70
73
  }
71
74
  try {
72
- return await readBoundedJson(response, 'ollama web request', OLLAMA_WEB_MAX_BYTES);
75
+ return (await readBoundedJson(response, 'ollama web request', OLLAMA_WEB_MAX_BYTES));
73
76
  }
74
77
  catch (error) {
75
78
  if (signal?.aborted)
@@ -158,23 +161,33 @@ export class OllamaWebSearchProvider {
158
161
  const base = baseURLOf(this.options).replace(/\/+$/, '');
159
162
  const payload = await postJson(`${base}/web_search`, apiKey, {
160
163
  query: request.query,
161
- ...(request.maxResults === undefined ? {} : { max_results: Math.min(request.maxResults, OLLAMA_WEB_MAX_RESULTS) }),
164
+ ...(request.maxResults === undefined
165
+ ? {}
166
+ : {
167
+ max_results: Math.min(request.maxResults, OLLAMA_WEB_MAX_RESULTS),
168
+ }),
162
169
  }, this.options, signal);
163
170
  const results = recordOf(payload)?.['results'];
164
171
  if (!Array.isArray(results))
165
172
  throw new WebError('ollama web search returned no results array', 'OLLAMA_WEB_BAD_REPLY');
166
173
  // Official shape per docs: {title, url, content}. The snippet rides
167
174
  // `content`, not `snippet`.
168
- const sources = results.flatMap(entry => {
175
+ const sources = results.flatMap((entry) => {
169
176
  const record = recordOf(entry);
170
177
  const url = record?.['url'];
171
178
  if (typeof url !== 'string' || url.length === 0)
172
179
  return [];
173
- return [{
180
+ return [
181
+ {
174
182
  url,
175
- ...(typeof record?.['title'] === 'string' ? { title: record?.['title'] } : {}),
176
- ...(typeof record?.['content'] === 'string' ? { snippet: record?.['content'] } : {}),
177
- }];
183
+ ...(typeof record?.['title'] === 'string'
184
+ ? { title: record?.['title'] }
185
+ : {}),
186
+ ...(typeof record?.['content'] === 'string'
187
+ ? { snippet: record?.['content'] }
188
+ : {}),
189
+ },
190
+ ];
178
191
  });
179
192
  return { sources, truncated: false };
180
193
  }
@@ -208,6 +221,11 @@ export class OllamaWebFetchProvider {
208
221
  if (typeof content !== 'string') {
209
222
  throw new WebError('ollama web fetch returned a malformed reply', 'OLLAMA_WEB_BAD_REPLY');
210
223
  }
211
- return { url: request.url, statusCode: 200, body: { kind: 'text', content }, truncated: false };
224
+ return {
225
+ url: request.url,
226
+ statusCode: 200,
227
+ body: { kind: 'text', content },
228
+ truncated: false,
229
+ };
212
230
  }
213
231
  }