dsh-cursor-subscription 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.
package/lib/client.js ADDED
@@ -0,0 +1,853 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-cursor-subscription",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+
11
+ //#region src/client.jsx
12
+ const inject = ["slots", "locale", "connection"];
13
+ const NS = "settings.cursorSubscription";
14
+ const CHANNEL = "/cursor-subscription";
15
+
16
+ const zh = {
17
+ nav: "Cursor 订阅",
18
+ title: "Cursor 订阅",
19
+ connected: "已登录",
20
+ disconnected: "未登录",
21
+ accountLoading: "正在读取账户状态…",
22
+ login: "浏览器登录",
23
+ logout: "退出登录",
24
+ cancel: "取消",
25
+ openLogin: "打开登录页",
26
+ waiting: "正在等待登录完成…",
27
+ failed: "登录失败,请重试。",
28
+ loadFailed: "无法读取 Cursor 状态。",
29
+ note: "在浏览器中登录 Cursor 账户后,即可在模型选择器中使用 Cursor 订阅模型。登录信息仅保存在本机,不会上传。",
30
+ expiresAt: "令牌有效至 {value}",
31
+ usage: "订阅用量",
32
+ usageRefresh: "刷新",
33
+ usageRefreshing: "刷新中…",
34
+ usageNotSignedIn: "登录后可查看 Cursor 返回的用量。",
35
+ usageLoading: "正在读取用量…",
36
+ usageError: "无法读取用量",
37
+ usageUpdated: "更新于 {value}",
38
+ plan: "套餐",
39
+ unlimited: "不限额",
40
+ includedRequests: "包含请求",
41
+ requestsUsed: "已用 {used} / {limit} 次请求({pct}%)",
42
+ onDemandSpend: "按需消费",
43
+ autoUsage: "计划 Cursor 模型使用量",
44
+ otherModels: "计划其他模型使用量",
45
+ modelUsage: "模型用量",
46
+ spendUsed: "已消费 ${used}",
47
+ spendUsedOf: "已消费 ${used} / ${limit}",
48
+ billingCycle: "账单周期",
49
+ billingDaysLeft: "剩余 {value} 天",
50
+ autoPercent: "计划 Cursor 模型 {value}%",
51
+ otherModelsPercent: "计划其他模型 {value}%",
52
+ otherModelsUnknown: "尚未读到该用量",
53
+ modelSpendPct: "{model} 已用 ${used}({pct}%)",
54
+ models: "模型列表",
55
+ modelsRefresh: "刷新模型列表",
56
+ modelsRefreshing: "刷新中…",
57
+ modelsEmpty: "暂无模型数据",
58
+ modelsCount: "{value} 个模型",
59
+ modelsLoading: "正在读取模型列表…",
60
+ runtimeSettings: "运行设置",
61
+ runtimeSettingsNote: "设置会保存到当前 DSH profile,并从下一次 Cursor 请求开始生效。",
62
+ maxToolRounds: "最大工具轮次",
63
+ maxToolRoundsHint: "单次 Cursor 任务允许的工具轮次,超过后以 TOOL_LIMIT 终止。范围 1–1000。",
64
+ retryCount: "重试次数",
65
+ retryCountHint: "匹配状态码时允许的额外请求次数。默认 0(关闭),启用后可能重复 Cursor 远端模型请求或用量。范围 0–10。",
66
+ retryInterval: "重试间隔(毫秒)",
67
+ retryIntervalHint: "每次 HTTP 重试前等待的固定时间。范围 0–300000。",
68
+ retryStatuses: "重试 HTTP 状态码",
69
+ retryStatusesHint: "使用逗号分隔,例如 408, 425, 429, 500, 502, 503, 504。",
70
+ saveSettings: "保存设置",
71
+ savingSettings: "保存中…",
72
+ reloadSettings: "重新加载",
73
+ loadingSettings: "加载中…",
74
+ settingsSaved: "设置已保存。",
75
+ settingsLoadFailed: "无法读取运行设置。",
76
+ settingsSaveFailed: "无法保存运行设置。",
77
+ settingsInvalid: "请检查工具轮次、重试次数、间隔和 HTTP 状态码。",
78
+ };
79
+ const en = {
80
+ nav: "Cursor",
81
+ title: "Cursor subscription",
82
+ connected: "Signed in",
83
+ disconnected: "Not signed in",
84
+ accountLoading: "Reading account status…",
85
+ login: "Browser sign-in",
86
+ logout: "Sign out",
87
+ cancel: "Cancel",
88
+ openLogin: "Open sign-in page",
89
+ waiting: "Waiting for sign-in to finish…",
90
+ failed: "Sign-in failed. Try again.",
91
+ loadFailed: "Could not read Cursor state.",
92
+ note: "After signing in with a Cursor account in the browser, Cursor subscription models become available in the model picker. Credentials stay on this machine.",
93
+ expiresAt: "Token valid until {value}",
94
+ usage: "Subscription usage",
95
+ usageRefresh: "Refresh",
96
+ usageRefreshing: "Refreshing…",
97
+ usageNotSignedIn: "Sign in to read usage reported by Cursor.",
98
+ usageLoading: "Reading usage…",
99
+ usageError: "Could not read usage",
100
+ usageUpdated: "Updated {value}",
101
+ plan: "Plan",
102
+ unlimited: "Unlimited",
103
+ includedRequests: "Included requests",
104
+ requestsUsed: "{used} / {limit} requests used ({pct}%)",
105
+ onDemandSpend: "On-demand spend",
106
+ autoUsage: "Plan Cursor model usage",
107
+ otherModels: "Plan other model usage",
108
+ modelUsage: "Model usage",
109
+ spendUsed: "${used} spent",
110
+ spendUsedOf: "${used} / ${limit} spent",
111
+ billingCycle: "Billing cycle",
112
+ billingDaysLeft: "{value} days left",
113
+ planPercent: "Plan used {value}%",
114
+ autoPercent: "Plan Cursor models {value}%",
115
+ otherModelsPercent: "Plan other models {value}%",
116
+ otherModelsUnknown: "This usage value has not been read yet",
117
+ modelSpendPct: "{model} ${used} ({pct}%)",
118
+ models: "Models",
119
+ modelsRefresh: "Refresh models",
120
+ modelsRefreshing: "Refreshing…",
121
+ modelsEmpty: "No model data yet",
122
+ modelsCount: "{value} models",
123
+ modelsLoading: "Reading model list…",
124
+ runtimeSettings: "Runtime settings",
125
+ runtimeSettingsNote: "Settings are stored in the current DSH profile and apply from the next Cursor request.",
126
+ maxToolRounds: "Maximum tool rounds",
127
+ maxToolRoundsHint: "Tool rounds allowed in one Cursor task before TOOL_LIMIT stops it. Range: 1–1000.",
128
+ retryCount: "Retry count",
129
+ retryCountHint: "Additional attempts for matching statuses. Default: 0 (off). Enabling retries may repeat remote Cursor model work or usage. Range: 0–10.",
130
+ retryInterval: "Retry interval (ms)",
131
+ retryIntervalHint: "Fixed delay before each HTTP retry. Range: 0–300000.",
132
+ retryStatuses: "Retry HTTP status codes",
133
+ retryStatusesHint: "Comma-separated, for example: 408, 425, 429, 500, 502, 503, 504.",
134
+ saveSettings: "Save settings",
135
+ savingSettings: "Saving…",
136
+ reloadSettings: "Reload",
137
+ loadingSettings: "Loading…",
138
+ settingsSaved: "Settings saved.",
139
+ settingsLoadFailed: "Could not read runtime settings.",
140
+ settingsSaveFailed: "Could not save runtime settings.",
141
+ settingsInvalid: "Check the tool rounds, retry count, interval, and HTTP status codes.",
142
+ };
143
+
144
+ const STYLE = `
145
+ .cursorSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
146
+ .cursorSubscription h2,.cursorSubscription h3,.cursorSubscription p{margin:0}
147
+ .cursorSubscriptionHead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
148
+ .cursorSubscription h2{font-size:16px;line-height:24px;font-weight:500}
149
+ .cursorSubscription h3{font-size:14px;line-height:22px;font-weight:500}
150
+ .cursorSubscriptionCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
151
+ .cursorSubscriptionAccountRow{display:flex;align-items:center;justify-content:space-between;gap:12px}
152
+ .cursorSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
153
+ .cursorSubscriptionDot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-dimmed)}
154
+ .cursorSubscriptionDot[data-state=connected]{background:var(--dsw-alias-state-success-primary)}
155
+ .cursorSubscriptionDot[data-state=disconnected]{background:var(--dsw-alias-state-error-primary)}
156
+ .cursorSubscriptionActions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
157
+ .cursorSubscriptionFlow{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-module-platform)}
158
+ .cursorSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
159
+ .cursorSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}
160
+ .cursorSubscriptionNote{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
161
+ .cursorSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
162
+ .cursorSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}
163
+ .cursorSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}
164
+ .cursorSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important;overflow-wrap:normal!important;writing-mode:horizontal-tb!important}
165
+ .cursorSubscriptionRefresh *{white-space:nowrap!important;word-break:keep-all!important;writing-mode:horizontal-tb!important}
166
+ .cursorSubscriptionEmpty{padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
167
+ .cursorSubscriptionUsageRow{display:flex;flex-direction:column;gap:6px}
168
+ .cursorSubscriptionUsageTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
169
+ .cursorSubscriptionUsageLabel{flex:1;min-width:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
170
+ .cursorSubscriptionUsageTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}
171
+ .cursorSubscriptionUsageRow progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-brand-primary,#3964fe);-webkit-appearance:none;appearance:none}
172
+ .cursorSubscriptionUsageRow progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}
173
+ .cursorSubscriptionUsageRow progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
174
+ .cursorSubscriptionUsageRow progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
175
+ .cursorSubscriptionModelUsage{display:flex;flex-direction:column;gap:10px;padding-top:4px}
176
+ .cursorSubscriptionModelUsageHead{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}
177
+ .cursorSubscriptionMetaRow{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
178
+ .cursorSubscriptionModels{display:flex;flex-direction:column;gap:8px}
179
+ .cursorSubscriptionModelChips{display:flex;flex-wrap:wrap;gap:6px}
180
+ .cursorSubscriptionModelChip{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);padding:3px 8px;font:500 12px/18px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
181
+ .cursorSubscriptionSettingsGrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
182
+ .cursorSubscriptionField{display:flex;flex-direction:column;gap:5px;min-width:0}
183
+ .cursorSubscriptionFieldWide{grid-column:1/-1}
184
+ .cursorSubscriptionField label{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}
185
+ .cursorSubscriptionField input{box-sizing:border-box;width:100%;height:34px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);padding:6px 9px;color:var(--dsw-alias-label-primary);font:13px/20px ui-monospace,SFMono-Regular,Consolas,monospace;outline:none}
186
+ .cursorSubscriptionField input:focus{border-color:var(--dsw-alias-brand-primary,#3964fe);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-brand-primary,#3964fe) 18%,transparent)}
187
+ .cursorSubscriptionField input:disabled{opacity:.6}
188
+ .cursorSubscriptionFieldHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
189
+ .cursorSubscriptionSettingsFoot{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
190
+ .cursorSubscriptionSuccess{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
191
+ @container (max-width:520px){.cursorSubscriptionSettingsGrid{grid-template-columns:1fr}.cursorSubscriptionFieldWide{grid-column:auto}}
192
+ `;
193
+
194
+ const unwrap = (response) => {
195
+ if (!response?.ok) throw new Error(response?.error?.message ?? "Cursor RPC failed");
196
+ return response.value;
197
+ };
198
+ const fill = (text, values) => Object.entries(values).reduce((next, [key, value]) => next.replace(`{${key}}`, String(value)), text);
199
+ const validDate = (value) => {
200
+ const date = new Date(value);
201
+ return Number.isFinite(date.getTime()) ? date : undefined;
202
+ };
203
+ const percent = (value) => Number(value).toLocaleString(void 0, { maximumFractionDigits: 1 });
204
+ const money = (value) => Number(value).toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
205
+
206
+ function AccountCard({ rpc, t, account, setAccount, onSignedOut }) {
207
+ const [flow, setFlow] = react.useState();
208
+ const [busy, setBusy] = react.useState(false);
209
+ const [error, setError] = react.useState();
210
+ const call = (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap);
211
+ react.useEffect(() => {
212
+ if (flow?.id === undefined || ["authenticated", "failed", "cancelled"].includes(flow.phase)) return undefined;
213
+ const timer = window.setInterval(() => {
214
+ call("login/status", { id: flow.id })
215
+ .then((next) => {
216
+ setFlow(next);
217
+ if (next.phase === "authenticated") {
218
+ call("status").then(setAccount).catch(() => setError(t("failed")));
219
+ }
220
+ })
221
+ .catch(() => setError(t("failed")));
222
+ }, 1200);
223
+ return () => window.clearInterval(timer);
224
+ }, [flow?.id, flow?.phase]);
225
+ const begin = () => {
226
+ setBusy(true);
227
+ setError(undefined);
228
+ call("login/start", { openExternal: true })
229
+ .then(setFlow)
230
+ .catch(() => setError(t("failed")))
231
+ .finally(() => setBusy(false));
232
+ };
233
+ const cancel = () => {
234
+ if (flow?.id === undefined) return;
235
+ setBusy(true);
236
+ call("login/cancel", { id: flow.id })
237
+ .then(setFlow)
238
+ .finally(() => setBusy(false));
239
+ };
240
+ const logout = () => {
241
+ setBusy(true);
242
+ setError(undefined);
243
+ call("logout")
244
+ .then((next) => {
245
+ setAccount(next);
246
+ setFlow(undefined);
247
+ onSignedOut();
248
+ })
249
+ .catch(() => setError(t("failed")))
250
+ .finally(() => setBusy(false));
251
+ };
252
+ const signedIn = account?.authenticated === true;
253
+ const accountReady = account !== undefined;
254
+ const expiresAt = Number.isFinite(account?.expiresAt) ? new Date(account.expiresAt) : undefined;
255
+ return react_jsx_runtime.jsxs("div", {
256
+ className: "cursorSubscriptionCard",
257
+ children: [
258
+ react_jsx_runtime.jsxs("div", {
259
+ className: "cursorSubscriptionAccountRow",
260
+ children: [
261
+ react_jsx_runtime.jsxs("div", {
262
+ className: "cursorSubscriptionStatus",
263
+ role: "status",
264
+ "aria-live": "polite",
265
+ children: [
266
+ react_jsx_runtime.jsx("span", {
267
+ className: "cursorSubscriptionDot",
268
+ "data-state": accountReady ? signedIn ? "connected" : "disconnected" : "loading",
269
+ "aria-hidden": "true",
270
+ }),
271
+ accountReady ? signedIn ? t("connected") : t("disconnected") : t("accountLoading"),
272
+ ],
273
+ }),
274
+ react_jsx_runtime.jsx("div", {
275
+ className: "cursorSubscriptionActions",
276
+ children: signedIn
277
+ ? react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.Button, {
278
+ type: "button",
279
+ variant: "outline",
280
+ disabled: busy,
281
+ onClick: logout,
282
+ children: t("logout"),
283
+ })
284
+ : accountReady && (flow === undefined || ["failed", "cancelled"].includes(flow.phase))
285
+ ? react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.Button, {
286
+ type: "button",
287
+ variant: "primary",
288
+ disabled: busy,
289
+ onClick: begin,
290
+ children: t("login"),
291
+ })
292
+ : null,
293
+ }),
294
+ ],
295
+ }),
296
+ signedIn && expiresAt !== undefined
297
+ ? react_jsx_runtime.jsx("time", {
298
+ className: "cursorSubscriptionFreshness",
299
+ dateTime: expiresAt.toISOString(),
300
+ children: fill(t("expiresAt"), { value: expiresAt.toLocaleString() }),
301
+ })
302
+ : null,
303
+ !signedIn && flow !== undefined && ["starting", "waiting_browser", "waiting_input"].includes(flow.phase)
304
+ ? react_jsx_runtime.jsxs("div", {
305
+ className: "cursorSubscriptionFlow",
306
+ children: [
307
+ react_jsx_runtime.jsx("p", { children: t("waiting") }),
308
+ flow.authUrl === undefined
309
+ ? null
310
+ : react_jsx_runtime.jsx("a", { href: flow.authUrl, target: "_blank", rel: "noreferrer", children: t("openLogin") }),
311
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.Button, {
312
+ type: "button",
313
+ variant: "outline",
314
+ disabled: busy,
315
+ onClick: cancel,
316
+ children: t("cancel"),
317
+ }),
318
+ ],
319
+ })
320
+ : null,
321
+ flow?.phase === "failed" || error !== undefined
322
+ ? react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, {
323
+ children: [
324
+ react_jsx_runtime.jsx("p", { className: "cursorSubscriptionError", role: "alert", children: error ?? t("failed") }),
325
+ flow?.detail
326
+ ? react_jsx_runtime.jsx("p", { className: "cursorSubscriptionError", children: flow.detail })
327
+ : null,
328
+ ],
329
+ })
330
+ : null,
331
+ ],
332
+ });
333
+ }
334
+
335
+ function UsageRow({ label, used, limit, meta }) {
336
+ const available = typeof used === "number" && Number.isFinite(used) && typeof limit === "number" && Number.isFinite(limit) && limit > 0;
337
+ const usedPct = available ? Math.min(100, Math.round((used / limit) * 1000) / 10) : 0;
338
+ const headline = available ? `${percent(usedPct)}%` : "—";
339
+ return react_jsx_runtime.jsxs("div", {
340
+ className: "cursorSubscriptionUsageRow",
341
+ children: [
342
+ react_jsx_runtime.jsxs("div", {
343
+ className: "cursorSubscriptionUsageTop",
344
+ children: [
345
+ react_jsx_runtime.jsx("span", { className: "cursorSubscriptionUsageLabel", children: label }),
346
+ react_jsx_runtime.jsx("strong", { children: headline }),
347
+ ],
348
+ }),
349
+ react_jsx_runtime.jsx("progress", {
350
+ max: "100",
351
+ value: usedPct,
352
+ "aria-label": `${label} ${headline}`,
353
+ }),
354
+ react_jsx_runtime.jsx("div", {
355
+ className: "cursorSubscriptionMetaRow",
356
+ children: [react_jsx_runtime.jsx("span", { children: meta })],
357
+ }),
358
+ ],
359
+ });
360
+ }
361
+
362
+ function UsageCard({ rpc, t, signedIn, resetKey }) {
363
+ const [usage, setUsage] = react.useState();
364
+ const [busy, setBusy] = react.useState(false);
365
+ const [error, setError] = react.useState();
366
+ const request = react.useRef(0);
367
+ const load = (force) => {
368
+ if (!signedIn) return;
369
+ const id = ++request.current;
370
+ setBusy(true);
371
+ setError(undefined);
372
+ rpc.call(CHANNEL, "usage", { force })
373
+ .then(unwrap)
374
+ .then((next) => {
375
+ if (request.current === id) setUsage(next);
376
+ })
377
+ .catch((err) => {
378
+ if (request.current === id) setError(err.message);
379
+ })
380
+ .finally(() => {
381
+ if (request.current === id) setBusy(false);
382
+ });
383
+ };
384
+ react.useEffect(() => {
385
+ if (signedIn) load(false);
386
+ else {
387
+ request.current += 1;
388
+ setUsage(undefined);
389
+ setError(undefined);
390
+ setBusy(false);
391
+ }
392
+ return () => {
393
+ request.current += 1;
394
+ };
395
+ }, [signedIn, resetKey]);
396
+ const fetchedAt = typeof usage?.fetchedAt === "number" ? validDate(usage.fetchedAt) : undefined;
397
+ const ir = usage?.includedRequests;
398
+ const teamSpend = usage?.teamOnDemand;
399
+ const models = Array.isArray(usage?.models) ? usage.models : [];
400
+ const modelSpendTotal = models.reduce((sum, model) => sum + (Number(model.spentDollars) || 0), 0);
401
+ return react_jsx_runtime.jsxs("div", {
402
+ className: "cursorSubscriptionCard",
403
+ children: [
404
+ react_jsx_runtime.jsxs("div", {
405
+ className: "cursorSubscriptionSectionHead",
406
+ children: [
407
+ react_jsx_runtime.jsxs("div", {
408
+ className: "cursorSubscriptionSectionTitle",
409
+ children: [
410
+ react_jsx_runtime.jsx("h3", { children: t("usage") }),
411
+ fetchedAt === undefined
412
+ ? null
413
+ : react_jsx_runtime.jsx("time", {
414
+ className: "cursorSubscriptionFreshness",
415
+ dateTime: fetchedAt.toISOString(),
416
+ children: fill(t("usageUpdated"), { value: fetchedAt.toLocaleString() }),
417
+ }),
418
+ ],
419
+ }),
420
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.Button, {
421
+ className: "cursorSubscriptionRefresh",
422
+ type: "button",
423
+ variant: "outline",
424
+ disabled: !signedIn || busy,
425
+ "aria-busy": busy,
426
+ onClick: () => load(true),
427
+ children: busy ? t("usageRefreshing") : t("usageRefresh"),
428
+ }),
429
+ ],
430
+ }),
431
+ react_jsx_runtime.jsxs("div", {
432
+ "aria-live": "polite",
433
+ children: [
434
+ !signedIn
435
+ ? react_jsx_runtime.jsx("p", { className: "cursorSubscriptionEmpty", children: t("usageNotSignedIn") })
436
+ : null,
437
+ signedIn && busy && usage === undefined
438
+ ? react_jsx_runtime.jsx("p", { className: "cursorSubscriptionEmpty", role: "status", children: t("usageLoading") })
439
+ : null,
440
+ ],
441
+ }),
442
+ error === undefined
443
+ ? null
444
+ : react_jsx_runtime.jsx("p", { className: "cursorSubscriptionError", role: "alert", children: error }),
445
+ usage === undefined || !signedIn
446
+ ? null
447
+ : react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, {
448
+ children: [
449
+ usage.isUnlimited === true
450
+ ? react_jsx_runtime.jsxs("div", {
451
+ className: "cursorSubscriptionUsageRow",
452
+ children: [
453
+ react_jsx_runtime.jsxs("div", {
454
+ className: "cursorSubscriptionUsageTop",
455
+ children: [
456
+ react_jsx_runtime.jsx("span", { className: "cursorSubscriptionUsageLabel", children: t("plan") }),
457
+ react_jsx_runtime.jsx("strong", { children: t("unlimited") }),
458
+ ],
459
+ }),
460
+ ],
461
+ })
462
+ : null,
463
+ ir !== undefined && usage.isUnlimited !== true
464
+ ? react_jsx_runtime.jsx(UsageRow, {
465
+ label: t("includedRequests"),
466
+ used: ir.used,
467
+ limit: ir.limit,
468
+ meta: fill(t("requestsUsed"), { used: ir.used, limit: ir.limit, pct: percent(ir.pct) }),
469
+ })
470
+ : null,
471
+ usage?.plan?.autoPercentUsed !== undefined
472
+ ? react_jsx_runtime.jsx(UsageRow, {
473
+ label: t("autoUsage"),
474
+ used: usage.plan.autoPercentUsed,
475
+ limit: 100,
476
+ meta: fill(t("autoPercent"), { value: percent(usage.plan.autoPercentUsed) }),
477
+ })
478
+ : null,
479
+ usage?.plan
480
+ ? react_jsx_runtime.jsx(UsageRow, {
481
+ label: t("otherModels"),
482
+ used: typeof usage.plan.apiPercentUsed === "number" ? usage.plan.apiPercentUsed : undefined,
483
+ limit: 100,
484
+ meta: typeof usage.plan.apiPercentUsed === "number" ? fill(t("otherModelsPercent"), { value: percent(usage.plan.apiPercentUsed) }) : t("otherModelsUnknown"),
485
+ })
486
+ : null,
487
+ teamSpend !== undefined && teamSpend.limitDollars !== undefined
488
+ ? react_jsx_runtime.jsx(UsageRow, {
489
+ label: t("onDemandSpend"),
490
+ used: teamSpend.usedDollars,
491
+ limit: teamSpend.limitDollars,
492
+ meta: fill(t("spendUsedOf"), { used: money(teamSpend.usedDollars), limit: money(teamSpend.limitDollars) }),
493
+ })
494
+ : null,
495
+ models.length > 0
496
+ ? react_jsx_runtime.jsxs("div", {
497
+ className: "cursorSubscriptionModelUsage",
498
+ children: [
499
+ react_jsx_runtime.jsx("span", { className: "cursorSubscriptionModelUsageHead", children: t("modelUsage") }),
500
+ ...models.map((model) =>
501
+ react_jsx_runtime.jsx(UsageRow, {
502
+ label: model.id,
503
+ used: model.spentDollars,
504
+ limit: Math.max(modelSpendTotal, model.spentDollars, 0.01),
505
+ meta: fill(t("modelSpendPct"), { model: model.id, used: money(model.spentDollars), pct: percent(model.pct) }),
506
+ }, model.id),
507
+ ),
508
+ ],
509
+ })
510
+ : null,
511
+ usage?.billingCycle !== undefined
512
+ ? react_jsx_runtime.jsx("div", {
513
+ className: "cursorSubscriptionMetaRow",
514
+ children: [
515
+ react_jsx_runtime.jsx("span", { children: `${t("billingCycle")}: ${new Date(usage.billingCycle.start).toLocaleDateString()} – ${new Date(usage.billingCycle.end).toLocaleDateString()}` }),
516
+ usage.billingCycle.daysLeft !== undefined
517
+ ? react_jsx_runtime.jsx("span", { children: fill(t("billingDaysLeft"), { value: usage.billingCycle.daysLeft }) })
518
+ : null,
519
+ ],
520
+ })
521
+ : null,
522
+ ],
523
+ }),
524
+ ],
525
+ });
526
+ }
527
+
528
+ function ModelsCard({ rpc, t, signedIn }) {
529
+ const [data, setData] = react.useState();
530
+ const [busy, setBusy] = react.useState(false);
531
+ const [error, setError] = react.useState();
532
+ const request = react.useRef(0);
533
+ const load = (force) => {
534
+ if (!signedIn) return;
535
+ const id = ++request.current;
536
+ setBusy(true);
537
+ setError(undefined);
538
+ rpc.call(CHANNEL, "models", { force })
539
+ .then(unwrap)
540
+ .then((next) => {
541
+ if (request.current === id) setData(next);
542
+ })
543
+ .catch((err) => {
544
+ if (request.current === id) setError(err.message);
545
+ })
546
+ .finally(() => {
547
+ if (request.current === id) setBusy(false);
548
+ });
549
+ };
550
+ react.useEffect(() => {
551
+ if (signedIn) load(false);
552
+ else {
553
+ request.current += 1;
554
+ setData(undefined);
555
+ setError(undefined);
556
+ setBusy(false);
557
+ }
558
+ return () => {
559
+ request.current += 1;
560
+ };
561
+ }, [signedIn]);
562
+ const models = Array.isArray(data?.models) ? data.models : [];
563
+ return react_jsx_runtime.jsxs("div", {
564
+ className: "cursorSubscriptionCard",
565
+ children: [
566
+ react_jsx_runtime.jsxs("div", {
567
+ className: "cursorSubscriptionSectionHead",
568
+ children: [
569
+ react_jsx_runtime.jsxs("div", {
570
+ className: "cursorSubscriptionSectionTitle",
571
+ children: [
572
+ react_jsx_runtime.jsx("h3", { children: t("models") }),
573
+ models.length > 0
574
+ ? react_jsx_runtime.jsx("span", { className: "cursorSubscriptionFreshness", children: fill(t("modelsCount"), { value: models.length }) })
575
+ : null,
576
+ ],
577
+ }),
578
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.Button, {
579
+ className: "cursorSubscriptionRefresh",
580
+ type: "button",
581
+ variant: "outline",
582
+ disabled: !signedIn || busy,
583
+ "aria-busy": busy,
584
+ onClick: () => load(true),
585
+ children: busy ? t("modelsRefreshing") : t("modelsRefresh"),
586
+ }),
587
+ ],
588
+ }),
589
+ react_jsx_runtime.jsxs("div", {
590
+ className: "cursorSubscriptionModels",
591
+ "aria-live": "polite",
592
+ children: [
593
+ !signedIn
594
+ ? react_jsx_runtime.jsx("p", { className: "cursorSubscriptionEmpty", children: t("usageNotSignedIn") })
595
+ : null,
596
+ signedIn && busy && models.length === 0
597
+ ? react_jsx_runtime.jsx("p", { className: "cursorSubscriptionEmpty", role: "status", children: t("modelsLoading") })
598
+ : null,
599
+ error === undefined
600
+ ? null
601
+ : react_jsx_runtime.jsx("p", { className: "cursorSubscriptionError", role: "alert", children: error }),
602
+ models.length > 0
603
+ ? react_jsx_runtime.jsx("div", {
604
+ className: "cursorSubscriptionModelChips",
605
+ children: models.map((model) =>
606
+ react_jsx_runtime.jsx("span", { className: "cursorSubscriptionModelChip", title: model.name, children: model.id }),
607
+ ),
608
+ })
609
+ : null,
610
+ ],
611
+ }),
612
+ ],
613
+ });
614
+ }
615
+
616
+ function RuntimeSettingsCard({ rpc, t }) {
617
+ const [form, setForm] = react.useState();
618
+ const [baseline, setBaseline] = react.useState();
619
+ const [busy, setBusy] = react.useState(false);
620
+ const [error, setError] = react.useState();
621
+ const [saved, setSaved] = react.useState(false);
622
+ const request = react.useRef(0);
623
+ const call = (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap);
624
+ const show = (value) => ({
625
+ maxToolRounds: String(value.maxToolRounds),
626
+ retryCount: String(value.retryCount),
627
+ retryIntervalMs: String(value.retryIntervalMs),
628
+ retryHttpStatusCodes: value.retryHttpStatusCodes.join(", "),
629
+ });
630
+ const accept = (value) => {
631
+ setForm(show(value));
632
+ setBaseline({
633
+ maxToolRounds: value.maxToolRounds,
634
+ retryCount: value.retryCount,
635
+ retryIntervalMs: value.retryIntervalMs,
636
+ retryHttpStatusCodes: [...value.retryHttpStatusCodes],
637
+ revision: value.revision,
638
+ });
639
+ };
640
+ const load = () => {
641
+ const id = ++request.current;
642
+ setBusy(true);
643
+ setError(undefined);
644
+ setSaved(false);
645
+ call("settings")
646
+ .then((value) => {
647
+ if (request.current === id) accept(value);
648
+ })
649
+ .catch(() => {
650
+ if (request.current === id) setError(t("settingsLoadFailed"));
651
+ })
652
+ .finally(() => {
653
+ if (request.current === id) setBusy(false);
654
+ });
655
+ };
656
+ react.useEffect(() => {
657
+ load();
658
+ return () => {
659
+ request.current += 1;
660
+ };
661
+ }, []);
662
+ const change = (key) => (event) => {
663
+ const value = event.target.value;
664
+ setForm((current) => ({ ...current, [key]: value }));
665
+ setError(undefined);
666
+ setSaved(false);
667
+ };
668
+ const parse = () => {
669
+ const integer = (value, min, max) => {
670
+ if (!/^\d+$/.test(value.trim())) throw new Error("invalid integer");
671
+ const parsed = Number(value);
672
+ if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) throw new Error("integer out of range");
673
+ return parsed;
674
+ };
675
+ const statusText = form.retryHttpStatusCodes.trim();
676
+ if (statusText !== "" && !/^\d{3}(?:\s*,\s*\d{3})*$/.test(statusText)) throw new Error("invalid status list");
677
+ const statuses = statusText === "" ? [] : statusText.split(",").map((value) => integer(value, 400, 599));
678
+ if (new Set(statuses).size !== statuses.length) throw new Error("duplicate status");
679
+ return {
680
+ maxToolRounds: integer(form.maxToolRounds, 1, 1000),
681
+ retryCount: integer(form.retryCount, 0, 10),
682
+ retryIntervalMs: integer(form.retryIntervalMs, 0, 300000),
683
+ retryHttpStatusCodes: statuses,
684
+ };
685
+ };
686
+ const save = () => {
687
+ let value;
688
+ try {
689
+ value = parse();
690
+ } catch {
691
+ setError(t("settingsInvalid"));
692
+ setSaved(false);
693
+ return;
694
+ }
695
+ if (baseline === undefined) return;
696
+ const patch = {};
697
+ for (const key of ["maxToolRounds", "retryCount", "retryIntervalMs", "retryHttpStatusCodes"]) {
698
+ if (JSON.stringify(value[key]) !== JSON.stringify(baseline[key])) patch[key] = value[key];
699
+ }
700
+ if (Object.keys(patch).length === 0) {
701
+ setError(undefined);
702
+ setSaved(true);
703
+ return;
704
+ }
705
+ const id = ++request.current;
706
+ setBusy(true);
707
+ setError(undefined);
708
+ setSaved(false);
709
+ call("settings/update", { ...patch, revision: baseline.revision })
710
+ .then((next) => {
711
+ if (request.current !== id) return;
712
+ accept(next);
713
+ setSaved(true);
714
+ })
715
+ .catch(() => {
716
+ if (request.current === id) setError(t("settingsSaveFailed"));
717
+ })
718
+ .finally(() => {
719
+ if (request.current === id) setBusy(false);
720
+ });
721
+ };
722
+ const field = (key, label, hint, properties = {}) =>
723
+ react_jsx_runtime.jsxs("div", {
724
+ className: `cursorSubscriptionField${properties.wide ? " cursorSubscriptionFieldWide" : ""}`,
725
+ children: [
726
+ react_jsx_runtime.jsx("label", { htmlFor: `cursor-subscription-${key}`, children: t(label) }),
727
+ react_jsx_runtime.jsx("input", {
728
+ id: `cursor-subscription-${key}`,
729
+ type: properties.type ?? "number",
730
+ min: properties.min,
731
+ max: properties.max,
732
+ step: "1",
733
+ disabled: busy || form === undefined,
734
+ value: form?.[key] ?? "",
735
+ onChange: change(key),
736
+ }),
737
+ react_jsx_runtime.jsx("span", { className: "cursorSubscriptionFieldHint", children: t(hint) }),
738
+ ],
739
+ });
740
+ return react_jsx_runtime.jsxs("div", {
741
+ className: "cursorSubscriptionCard",
742
+ children: [
743
+ react_jsx_runtime.jsxs("div", {
744
+ className: "cursorSubscriptionSectionTitle",
745
+ children: [
746
+ react_jsx_runtime.jsx("h3", { children: t("runtimeSettings") }),
747
+ react_jsx_runtime.jsx("p", { className: "cursorSubscriptionFreshness", children: t("runtimeSettingsNote") }),
748
+ ],
749
+ }),
750
+ react_jsx_runtime.jsxs("div", {
751
+ className: "cursorSubscriptionSettingsGrid",
752
+ children: [
753
+ field("maxToolRounds", "maxToolRounds", "maxToolRoundsHint", { min: 1, max: 1000 }),
754
+ field("retryCount", "retryCount", "retryCountHint", { min: 0, max: 10 }),
755
+ field("retryIntervalMs", "retryInterval", "retryIntervalHint", { min: 0, max: 300000 }),
756
+ field("retryHttpStatusCodes", "retryStatuses", "retryStatusesHint", { type: "text", wide: true }),
757
+ ],
758
+ }),
759
+ error === undefined ? null : react_jsx_runtime.jsx("p", { className: "cursorSubscriptionError", role: "alert", children: error }),
760
+ react_jsx_runtime.jsxs("div", {
761
+ className: "cursorSubscriptionSettingsFoot",
762
+ children: [
763
+ saved ? react_jsx_runtime.jsx("p", { className: "cursorSubscriptionSuccess", role: "status", children: t("settingsSaved") }) : react_jsx_runtime.jsx("span", {}),
764
+ react_jsx_runtime.jsx(_deepseek_ai_dsh_client_ui_primitives.Button, {
765
+ type: "button",
766
+ variant: "primary",
767
+ disabled: busy || (form !== undefined && baseline === undefined),
768
+ onClick: form === undefined ? load : save,
769
+ children: busy
770
+ ? form === undefined ? t("loadingSettings") : t("savingSettings")
771
+ : form === undefined ? t("reloadSettings") : t("saveSettings"),
772
+ }),
773
+ ],
774
+ }),
775
+ ],
776
+ });
777
+ }
778
+
779
+ function CursorSection({ rpc, t }) {
780
+ const [account, setAccount] = react.useState();
781
+ const [error, setError] = react.useState();
782
+ const [resetKey, setResetKey] = react.useState(0);
783
+ react.useEffect(() => {
784
+ let live = true;
785
+ rpc.call(CHANNEL, "status", {})
786
+ .then(unwrap)
787
+ .then((next) => {
788
+ if (live) setAccount(next);
789
+ })
790
+ .catch(() => {
791
+ if (live) setError(t("loadFailed"));
792
+ });
793
+ return () => {
794
+ live = false;
795
+ };
796
+ }, [resetKey]);
797
+ const signedIn = account?.authenticated === true;
798
+ return react_jsx_runtime.jsxs("section", {
799
+ className: "cursorSubscription",
800
+ children: [
801
+ react_jsx_runtime.jsx("div", {
802
+ className: "cursorSubscriptionHead",
803
+ children: react_jsx_runtime.jsx("h2", { children: t("title") }),
804
+ }),
805
+ error === undefined
806
+ ? null
807
+ : react_jsx_runtime.jsx("p", { className: "cursorSubscriptionError", role: "alert", children: error }),
808
+ react_jsx_runtime.jsx(AccountCard, {
809
+ rpc,
810
+ t,
811
+ account,
812
+ setAccount,
813
+ onSignedOut: () => setResetKey((value) => value + 1),
814
+ }),
815
+ react_jsx_runtime.jsx(UsageCard, { rpc, t, signedIn, resetKey }),
816
+ react_jsx_runtime.jsx(ModelsCard, { rpc, t, signedIn }),
817
+ react_jsx_runtime.jsx(RuntimeSettingsCard, { rpc, t }),
818
+ react_jsx_runtime.jsx("p", { className: "cursorSubscriptionNote", children: t("note") }),
819
+ ],
820
+ });
821
+ }
822
+
823
+ function apply(ctx) {
824
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "cursor-subscription: copy");
825
+ ctx.effect(() => {
826
+ const tag = document.createElement("style");
827
+ tag.dataset.plugin = "dsh-cursor-subscription";
828
+ tag.textContent = STYLE;
829
+ document.head.append(tag);
830
+ return () => tag.remove();
831
+ }, "cursor-subscription: style");
832
+ const connection = ctx.get("connection");
833
+ const t = ctx.locale.bind(NS);
834
+ ctx.slots.inject("settings.section", () =>
835
+ ctx.slots.register(
836
+ {
837
+ name: "settings.section",
838
+ id: "cursor-subscription",
839
+ order: 20,
840
+ label: () => t("nav"),
841
+ locale: NS,
842
+ inject: () => ({ rpc: connection.rpc, t }),
843
+ },
844
+ CursorSection,
845
+ ),
846
+ );
847
+ }
848
+ //#endregion
849
+ exports.apply = apply;
850
+ exports.inject = inject;
851
+ return module.exports;
852
+ },
853
+ });