dsh-claude-subscription 0.2.0

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,769 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-claude-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
+ //#region src/composer-quota.js
11
+ const SHARED_WINDOW_IDS = Object.freeze(["five_hour", "seven_day"]);
12
+ const MODEL_WINDOW_IDS = Object.freeze({
13
+ opus: "seven_day_opus",
14
+ sonnet: "seven_day_sonnet"
15
+ });
16
+ const isDisplayableWindow = (window) => Number.isFinite(window?.remainingPercent) && window.remainingPercent >= 0 && window.remainingPercent <= 100;
17
+ const normalized = (value) => String(value ?? "").toLocaleLowerCase("en-US");
18
+ function applicableIds(model) {
19
+ const name = normalized(model);
20
+ const family = Object.entries(MODEL_WINDOW_IDS).find(([key]) => name.includes(key));
21
+ return family === void 0 ? SHARED_WINDOW_IDS : [...SHARED_WINDOW_IDS, family[1]];
22
+ }
23
+ /** Pick the tightest quota window that constrains the given model. */
24
+ function selectModelQuota(usage, model) {
25
+ const ids = applicableIds(model);
26
+ const windows = Array.isArray(usage?.windows) ? usage.windows.filter((window) => ids.includes(window?.id) && isDisplayableWindow(window)) : [];
27
+ if (windows.length === 0) return void 0;
28
+ const selected = windows.reduce((lowest, candidate) => candidate.remainingPercent < lowest.remainingPercent ? candidate : lowest);
29
+ return {
30
+ id: selected.id,
31
+ remainingPercent: selected.remainingPercent,
32
+ ...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {}
33
+ };
34
+ }
35
+ //#endregion
36
+ //#region src/settings-contract.js
37
+ const SETTINGS_NAMESPACE = "claude-subscription";
38
+ const QUICK_QUOTA_FIELD = "quickQuotaVisible";
39
+ Object.freeze({ [QUICK_QUOTA_FIELD]: false });
40
+ //#endregion
41
+ //#region src/client.jsx
42
+ const inject = [
43
+ "slots",
44
+ "locale",
45
+ "connection",
46
+ "remote",
47
+ "settingsScope",
48
+ "modelDirectories"
49
+ ];
50
+ const NS = "settings.claudeSubscription";
51
+ const CHANNEL = "/claude-subscription";
52
+ const PROVIDER = "anthropic";
53
+ const QUICK_QUOTA_REFRESH_EVENT = "dsh-claude-subscription:refresh-quick-quota";
54
+ const QUICK_QUOTA_REFRESH_MS = 6e4;
55
+ const zh = {
56
+ nav: "Claude 訂閱",
57
+ title: "Claude 訂閱",
58
+ connected: "已登入",
59
+ disconnected: "未登入",
60
+ accountLoading: "正在讀取帳戶狀態…",
61
+ browserLogin: "瀏覽器登入",
62
+ logout: "登出",
63
+ cancel: "取消",
64
+ submit: "提交授權碼",
65
+ openLogin: "開啟登入頁",
66
+ manualCode: "若瀏覽器回呼沒有自動完成,請貼上授權碼或完整的重新導向網址。",
67
+ waiting: "正在等待登入完成…",
68
+ failed: "登入失敗,請重試。",
69
+ loadFailed: "無法讀取 Claude 狀態。",
70
+ quickQuotaSetting: "輸入框額度",
71
+ quickQuotaBeta: "Beta",
72
+ quickQuotaStatus: "Claude 剩餘額度 {value}%",
73
+ preferenceFailed: "無法更新設定,請重試。",
74
+ usage: "訂閱額度",
75
+ refresh: "重新整理",
76
+ refreshing: "重新整理中…",
77
+ noUsage: "登入後可讀取 Anthropic 回報的額度視窗。",
78
+ usageLoading: "正在讀取額度…",
79
+ usageEmpty: "此帳戶沒有回傳可顯示的額度視窗。請稍後重新整理;這不代表額度為零。",
80
+ usageUpdated: "更新於 {value}",
81
+ remaining: "剩餘 {value}%",
82
+ windowFiveHour: "5 小時額度",
83
+ windowSevenDay: "7 天額度",
84
+ windowSevenDayOpus: "Opus 7 天額度",
85
+ windowSevenDaySonnet: "Sonnet 7 天額度",
86
+ resets: "重置於 {value}",
87
+ resetUnknown: "重置時間未提供",
88
+ extraUsage: "額外用量",
89
+ extraUsageOff: "未啟用",
90
+ extraUsageUsed: "已用 {used} / {limit}",
91
+ extraUsageUsedOnly: "已用 {used}",
92
+ extraUsageUnknown: "暫無數據"
93
+ };
94
+ const en = {
95
+ nav: "Claude",
96
+ title: "Claude subscription",
97
+ connected: "Signed in",
98
+ disconnected: "Not signed in",
99
+ accountLoading: "Reading account status…",
100
+ browserLogin: "Browser sign-in",
101
+ logout: "Sign out",
102
+ cancel: "Cancel",
103
+ submit: "Submit authorization code",
104
+ openLogin: "Open sign-in page",
105
+ manualCode: "If the browser callback did not finish automatically, paste the code or full redirect URL.",
106
+ waiting: "Waiting for sign-in to finish…",
107
+ failed: "Sign-in failed. Try again.",
108
+ loadFailed: "Could not read Claude status.",
109
+ quickQuotaSetting: "Composer quota",
110
+ quickQuotaBeta: "Beta",
111
+ quickQuotaStatus: "Claude quota: {value}% remaining",
112
+ preferenceFailed: "Could not update the setting. Try again.",
113
+ usage: "Subscription quota",
114
+ refresh: "Refresh",
115
+ refreshing: "Refreshing…",
116
+ noUsage: "Sign in to read the quota windows reported by Anthropic.",
117
+ usageLoading: "Reading quota…",
118
+ usageEmpty: "This account returned no displayable quota windows. Refresh later; this does not mean zero quota.",
119
+ usageUpdated: "Updated {value}",
120
+ remaining: "{value}% remaining",
121
+ windowFiveHour: "5-hour quota",
122
+ windowSevenDay: "7-day quota",
123
+ windowSevenDayOpus: "Opus 7-day quota",
124
+ windowSevenDaySonnet: "Sonnet 7-day quota",
125
+ resets: "Resets {value}",
126
+ resetUnknown: "Reset time not provided",
127
+ extraUsage: "Extra usage",
128
+ extraUsageOff: "Not enabled",
129
+ extraUsageUsed: "{used} / {limit} used",
130
+ extraUsageUsedOnly: "{used} used",
131
+ extraUsageUnknown: "No data yet"
132
+ };
133
+ const STYLE = `
134
+ .claudeSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
135
+ .claudeSubscription h2,.claudeSubscription h3,.claudeSubscription p{margin:0}
136
+ .claudeSubscription h2{font-size:16px;line-height:24px;font-weight:500}.claudeSubscription h3{font-size:14px;line-height:22px;font-weight:500}
137
+ .claudeSubscriptionTag{border:1px solid var(--dsw-alias-border-l3);border-radius:4px;padding:1px 6px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}
138
+ .claudeSubscriptionCard{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}
139
+ .claudeSubscriptionUsageCard{padding:12px 14px;gap:9px}.claudeSubscriptionPreferencesCard{padding:12px 14px;gap:10px}
140
+ .claudeSubscriptionPreference{min-height:32px;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;line-height:20px}
141
+ .claudeSubscriptionPreferenceLabel{display:flex;align-items:center;gap:6px;min-width:0}
142
+ .claudeSubscriptionSwitch{position:relative;flex:0 0 auto;width:32px;height:18px;padding:0;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;background:var(--dsw-alias-bg-module-platform);cursor:pointer}.claudeSubscriptionSwitch:disabled{cursor:not-allowed;opacity:.5}.claudeSubscriptionSwitch[aria-checked=true]{background:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-label-secondary)}
143
+ .claudeSubscriptionSwitchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:var(--dsw-alias-bg-layer-1);transition:transform 120ms var(--ds-ease-in-out)}.claudeSubscriptionSwitch[aria-checked=true] .claudeSubscriptionSwitchKnob{transform:translateX(14px)}
144
+ .claudeSubscriptionAccountRow,.claudeSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}
145
+ .claudeSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
146
+ .claudeSubscriptionDot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-dimmed)}.claudeSubscriptionDot[data-state=connected]{background:var(--dsw-alias-state-success-primary)}.claudeSubscriptionDot[data-state=disconnected]{background:var(--dsw-alias-state-error-primary)}
147
+ .claudeSubscriptionActions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
148
+ .claudeSubscriptionFlow{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-module-platform)}
149
+ .claudeSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
150
+ .claudeSubscriptionInput{width:100%;box-sizing:border-box}
151
+ .claudeSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}
152
+ .claudeSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}.claudeSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
153
+ .claudeSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important}
154
+ .claudeSubscriptionEmpty{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)}
155
+ .claudeSubscriptionLimits{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:6px}
156
+ .claudeSubscriptionLimit{min-width:0;border-radius:10px;padding:9px 12px;background:var(--dsw-alias-bg-module-platform);display:flex;flex-direction:column;gap:6px}
157
+ .claudeSubscriptionLimitTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
158
+ .claudeSubscriptionLimitLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
159
+ .claudeSubscriptionLimit strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}
160
+ .claudeSubscriptionLimit progress{width:100%;height:4px;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}
161
+ .claudeSubscriptionLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.claudeSubscriptionLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.claudeSubscriptionLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
162
+ .claudeSubscriptionLimitMeta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
163
+ .claudeSubscriptionExtra{display:flex;align-items:baseline;justify-content:space-between;gap:12px;border-radius:10px;padding:9px 12px;background:var(--dsw-alias-bg-module-platform);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
164
+ .claudeSubscriptionExtra strong{font:600 14px/20px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary)}
165
+ .claudeComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;margin-right:-4px;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}
166
+ @container (max-width:480px){.claudeSubscriptionAccountRow,.claudeSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.claudeSubscriptionActions{width:100%}}
167
+ @media(max-width:640px){.claudeSubscriptionCard{padding:14px}}
168
+ `;
169
+ const unwrap = (response) => {
170
+ if (!response?.ok) throw new Error(response?.error?.message ?? "Claude RPC failed");
171
+ return response.value;
172
+ };
173
+ const fill = (text, values) => Object.entries(values).reduce((next, [key, value]) => next.replace(`{${key}}`, String(value)), text);
174
+ const percent = (value) => Number(value).toLocaleString(void 0, { maximumFractionDigits: 1 });
175
+ const WINDOW_LABELS = {
176
+ five_hour: "windowFiveHour",
177
+ seven_day: "windowSevenDay",
178
+ seven_day_opus: "windowSevenDayOpus",
179
+ seven_day_sonnet: "windowSevenDaySonnet"
180
+ };
181
+ const windowLabel = (id, t) => t(WINDOW_LABELS[id] ?? "windowSevenDay");
182
+ const validDate = (value) => {
183
+ const date = new Date(value);
184
+ return Number.isFinite(date.getTime()) ? date : void 0;
185
+ };
186
+ const notifyQuickQuota = () => window.dispatchEvent(new Event(QUICK_QUOTA_REFRESH_EVENT));
187
+ /**
188
+ * Read the toggle from the settings scope, and fall back to the loopback RPC
189
+ * while the settings document is still unavailable.
190
+ */
191
+ function createPreferenceController(scope, rpc) {
192
+ let updating = false;
193
+ let error = false;
194
+ let fallbackStatus = "loading";
195
+ let fallback;
196
+ let generation = 0;
197
+ const nativeSnapshot = () => scope.getSnapshot();
198
+ const read = () => {
199
+ const native = nativeSnapshot();
200
+ const current = native.status === "ready" ? native : fallbackStatus === "ready" ? fallback : native;
201
+ return Object.freeze({
202
+ status: updating ? "updating" : current.status,
203
+ visible: current.value?.[QUICK_QUOTA_FIELD] === true,
204
+ writable: !updating && current.status === "ready" && current.writable === true,
205
+ error
206
+ });
207
+ };
208
+ let snapshot = read();
209
+ const listeners = /* @__PURE__ */ new Set();
210
+ const publish = () => {
211
+ snapshot = read();
212
+ for (const listener of listeners) listener();
213
+ };
214
+ const disposeScope = scope.subscribe(() => {
215
+ error = false;
216
+ publish();
217
+ });
218
+ const acceptFallback = (value) => {
219
+ fallbackStatus = "ready";
220
+ fallback = {
221
+ status: "ready",
222
+ value: { [QUICK_QUOTA_FIELD]: value?.[QUICK_QUOTA_FIELD] === true },
223
+ writable: value?.writable === true
224
+ };
225
+ };
226
+ const load = async () => {
227
+ const current = ++generation;
228
+ updating = false;
229
+ fallbackStatus = "loading";
230
+ fallback = void 0;
231
+ error = false;
232
+ publish();
233
+ if (nativeSnapshot().status === "ready") return;
234
+ try {
235
+ const value = unwrap(await rpc.call(CHANNEL, "preferences/status", {}));
236
+ if (current !== generation || nativeSnapshot().status === "ready") return;
237
+ acceptFallback(value);
238
+ publish();
239
+ } catch {
240
+ if (current !== generation || nativeSnapshot().status === "ready") return;
241
+ fallbackStatus = "unavailable";
242
+ publish();
243
+ }
244
+ };
245
+ const set = async (value) => {
246
+ if (snapshot.status !== "ready" || snapshot.writable !== true) return;
247
+ const current = ++generation;
248
+ updating = true;
249
+ error = false;
250
+ publish();
251
+ try {
252
+ if (nativeSnapshot().status === "ready") {
253
+ await scope.set(QUICK_QUOTA_FIELD, value);
254
+ if (current !== generation) return;
255
+ error = nativeSnapshot().value?.[QUICK_QUOTA_FIELD] !== value;
256
+ } else {
257
+ const accepted = unwrap(await rpc.call(CHANNEL, "preferences/update", { [QUICK_QUOTA_FIELD]: value }));
258
+ if (current !== generation) return;
259
+ acceptFallback(accepted);
260
+ }
261
+ } catch {
262
+ if (current === generation) error = true;
263
+ } finally {
264
+ if (current === generation) {
265
+ updating = false;
266
+ publish();
267
+ }
268
+ }
269
+ };
270
+ return {
271
+ getSnapshot: () => snapshot,
272
+ subscribe: (listener) => {
273
+ listeners.add(listener);
274
+ return () => listeners.delete(listener);
275
+ },
276
+ load,
277
+ set,
278
+ dispose: disposeScope
279
+ };
280
+ }
281
+ const usePreferenceSnapshot = (preference) => (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
282
+ function useQuickQuota(rpc, enabled, model) {
283
+ const [quota, setQuota] = (0, react.useState)();
284
+ (0, react.useEffect)(() => {
285
+ if (!enabled) {
286
+ setQuota(void 0);
287
+ return;
288
+ }
289
+ let live = true;
290
+ let loading = false;
291
+ const load = async () => {
292
+ if (loading) return;
293
+ loading = true;
294
+ try {
295
+ const account = unwrap(await rpc.call(CHANNEL, "status", {}));
296
+ if (!live) return;
297
+ if (account?.authenticated !== true) {
298
+ setQuota(void 0);
299
+ return;
300
+ }
301
+ const usage = unwrap(await rpc.call(CHANNEL, "usage", { force: false }));
302
+ if (live) setQuota(selectModelQuota(usage, model));
303
+ } catch {
304
+ if (live) setQuota(void 0);
305
+ } finally {
306
+ loading = false;
307
+ }
308
+ };
309
+ const refresh = () => {
310
+ load();
311
+ };
312
+ load();
313
+ const timer = window.setInterval(refresh, QUICK_QUOTA_REFRESH_MS);
314
+ window.addEventListener(QUICK_QUOTA_REFRESH_EVENT, refresh);
315
+ return () => {
316
+ live = false;
317
+ window.clearInterval(timer);
318
+ window.removeEventListener(QUICK_QUOTA_REFRESH_EVENT, refresh);
319
+ };
320
+ }, [
321
+ rpc,
322
+ enabled,
323
+ model
324
+ ]);
325
+ return quota;
326
+ }
327
+ function ClaudeComposerQuota({ preference, rpc, t, directory }) {
328
+ const preferenceSnapshot = usePreferenceSnapshot(preference);
329
+ const current = (0, react.useSyncExternalStore)((listener) => directory.subscribe(listener), () => directory.getSnapshot()).current;
330
+ const enabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.visible && current?.provider === PROVIDER;
331
+ const quota = useQuickQuota(rpc, enabled, current?.model);
332
+ if (!enabled || quota === void 0) return null;
333
+ const value = percent(quota.remainingPercent);
334
+ const label = fill(t("quickQuotaStatus"), { value });
335
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
336
+ className: "claudeComposerQuota",
337
+ role: "status",
338
+ "aria-label": label,
339
+ title: label,
340
+ children: [value, "%"]
341
+ });
342
+ }
343
+ function PreferencesCard({ preference, t }) {
344
+ const snapshot = usePreferenceSnapshot(preference);
345
+ const visible = snapshot.visible;
346
+ const writable = snapshot.status === "ready" && snapshot.writable === true;
347
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
348
+ className: "claudeSubscriptionCard claudeSubscriptionPreferencesCard",
349
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
350
+ className: "claudeSubscriptionPreference",
351
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
352
+ className: "claudeSubscriptionPreferenceLabel",
353
+ children: [t("quickQuotaSetting"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
354
+ className: "claudeSubscriptionTag",
355
+ children: t("quickQuotaBeta")
356
+ })]
357
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
358
+ className: "claudeSubscriptionSwitch",
359
+ type: "button",
360
+ role: "switch",
361
+ "aria-checked": visible,
362
+ "aria-label": t("quickQuotaSetting"),
363
+ disabled: !writable,
364
+ onClick: () => {
365
+ preference.set(!visible);
366
+ },
367
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
368
+ className: "claudeSubscriptionSwitchKnob",
369
+ "aria-hidden": "true"
370
+ })
371
+ })]
372
+ }), snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
373
+ className: "claudeSubscriptionError",
374
+ role: "alert",
375
+ children: t("preferenceFailed")
376
+ }) : null]
377
+ });
378
+ }
379
+ function AccountCard({ rpc, t, account, setAccount, onSignedOut }) {
380
+ const [flow, setFlow] = (0, react.useState)();
381
+ const [manualCode, setManualCode] = (0, react.useState)("");
382
+ const [busy, setBusy] = (0, react.useState)(false);
383
+ const [error, setError] = (0, react.useState)();
384
+ const call = (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap);
385
+ (0, react.useEffect)(() => {
386
+ if (flow?.id === void 0 || [
387
+ "authenticated",
388
+ "failed",
389
+ "cancelled"
390
+ ].includes(flow.phase)) return void 0;
391
+ const timer = window.setInterval(() => {
392
+ call("login/status", { id: flow.id }).then((next) => {
393
+ setFlow(next);
394
+ if (next.phase === "authenticated") call("status").then((account) => {
395
+ setAccount(account);
396
+ notifyQuickQuota();
397
+ }).catch(() => setError(t("failed")));
398
+ }).catch(() => setError(t("failed")));
399
+ }, 800);
400
+ return () => window.clearInterval(timer);
401
+ }, [flow?.id, flow?.phase]);
402
+ const begin = () => {
403
+ setBusy(true);
404
+ setError(void 0);
405
+ call("login/start", {
406
+ method: "browser",
407
+ openExternal: true
408
+ }).then(setFlow).catch(() => setError(t("failed"))).finally(() => setBusy(false));
409
+ };
410
+ const cancel = () => {
411
+ if (flow?.id === void 0) return;
412
+ setBusy(true);
413
+ call("login/cancel", { id: flow.id }).then(setFlow).finally(() => setBusy(false));
414
+ };
415
+ const submit = (event) => {
416
+ event.preventDefault();
417
+ if (flow?.id === void 0 || manualCode.trim() === "") return;
418
+ setBusy(true);
419
+ call("login/submit", {
420
+ id: flow.id,
421
+ value: manualCode.trim()
422
+ }).then((next) => {
423
+ setManualCode("");
424
+ setFlow(next);
425
+ }).catch(() => setError(t("failed"))).finally(() => setBusy(false));
426
+ };
427
+ const logout = () => {
428
+ setBusy(true);
429
+ setError(void 0);
430
+ call("logout").then((next) => {
431
+ setAccount(next);
432
+ setFlow(void 0);
433
+ onSignedOut();
434
+ notifyQuickQuota();
435
+ }).catch(() => setError(t("failed"))).finally(() => setBusy(false));
436
+ };
437
+ const signedIn = account?.authenticated === true;
438
+ const accountReady = account !== void 0;
439
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
440
+ className: "claudeSubscriptionCard",
441
+ children: [
442
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
443
+ className: "claudeSubscriptionAccountRow",
444
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
445
+ className: "claudeSubscriptionStatus",
446
+ role: "status",
447
+ "aria-live": "polite",
448
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
449
+ className: "claudeSubscriptionDot",
450
+ "data-state": accountReady ? signedIn ? "connected" : "disconnected" : "loading",
451
+ "aria-hidden": "true"
452
+ }), accountReady ? signedIn ? t("connected") : t("disconnected") : t("accountLoading")]
453
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
454
+ className: "claudeSubscriptionActions",
455
+ children: signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
456
+ type: "button",
457
+ variant: "outline",
458
+ disabled: busy,
459
+ onClick: logout,
460
+ children: t("logout")
461
+ }) : accountReady && (flow === void 0 || ["failed", "cancelled"].includes(flow.phase)) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
462
+ type: "button",
463
+ variant: "primary",
464
+ disabled: busy,
465
+ onClick: begin,
466
+ children: t("browserLogin")
467
+ }) : null
468
+ })]
469
+ }),
470
+ !signedIn && flow?.phase === "waiting_input" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
471
+ className: "claudeSubscriptionFlow",
472
+ onSubmit: submit,
473
+ children: [
474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("manualCode") }),
475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
476
+ className: "claudeSubscriptionInput",
477
+ value: manualCode,
478
+ onChange: (event) => setManualCode(event.currentTarget.value),
479
+ autoComplete: "off",
480
+ spellCheck: false
481
+ }),
482
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
483
+ className: "claudeSubscriptionActions",
484
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
485
+ type: "submit",
486
+ variant: "primary",
487
+ disabled: busy || manualCode.trim() === "",
488
+ children: t("submit")
489
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
490
+ type: "button",
491
+ variant: "outline",
492
+ disabled: busy,
493
+ onClick: cancel,
494
+ children: t("cancel")
495
+ })]
496
+ })
497
+ ]
498
+ }) : null,
499
+ !signedIn && flow !== void 0 && ["starting", "waiting_browser"].includes(flow.phase) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
500
+ className: "claudeSubscriptionFlow",
501
+ children: [
502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("waiting") }),
503
+ flow.authUrl === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
504
+ href: flow.authUrl,
505
+ target: "_blank",
506
+ rel: "noreferrer",
507
+ children: t("openLogin")
508
+ }),
509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
510
+ type: "button",
511
+ variant: "outline",
512
+ disabled: busy,
513
+ onClick: cancel,
514
+ children: t("cancel")
515
+ })
516
+ ]
517
+ }) : null,
518
+ flow?.phase === "failed" || error !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
519
+ className: "claudeSubscriptionError",
520
+ role: "alert",
521
+ children: error ?? t("failed")
522
+ }) : null
523
+ ]
524
+ });
525
+ }
526
+ function ResetTime({ resetsAt, t }) {
527
+ const date = Number.isSafeInteger(resetsAt) ? validDate(resetsAt * 1e3) : void 0;
528
+ if (date === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetUnknown") });
529
+ const value = date.toLocaleString(void 0, {
530
+ month: "numeric",
531
+ day: "numeric",
532
+ hour: "2-digit",
533
+ minute: "2-digit"
534
+ });
535
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
536
+ dateTime: date.toISOString(),
537
+ title: date.toLocaleString(),
538
+ children: fill(t("resets"), { value })
539
+ });
540
+ }
541
+ function ExtraUsageRow({ extraUsage, t }) {
542
+ const detail = !extraUsage.enabled ? t("extraUsageOff") : extraUsage.usedCredits === void 0 ? t("extraUsageUnknown") : extraUsage.monthlyLimit === void 0 ? fill(t("extraUsageUsedOnly"), { used: extraUsage.usedCredits }) : fill(t("extraUsageUsed"), {
543
+ used: extraUsage.usedCredits,
544
+ limit: extraUsage.monthlyLimit
545
+ });
546
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
547
+ className: "claudeSubscriptionExtra",
548
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("extraUsage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: detail })]
549
+ });
550
+ }
551
+ function UsageCard({ rpc, t, signedIn, resetKey }) {
552
+ const [usage, setUsage] = (0, react.useState)();
553
+ const [busy, setBusy] = (0, react.useState)(false);
554
+ const [error, setError] = (0, react.useState)();
555
+ const request = (0, react.useRef)(0);
556
+ const load = (force) => {
557
+ if (!signedIn) return;
558
+ const id = ++request.current;
559
+ setBusy(true);
560
+ setError(void 0);
561
+ rpc.call(CHANNEL, "usage", { force }).then(unwrap).then((next) => {
562
+ if (request.current === id) {
563
+ setUsage(next);
564
+ if (force) notifyQuickQuota();
565
+ }
566
+ }).catch((error) => {
567
+ if (request.current === id) setError(error.message);
568
+ }).finally(() => {
569
+ if (request.current === id) setBusy(false);
570
+ });
571
+ };
572
+ (0, react.useEffect)(() => {
573
+ if (signedIn) load(false);
574
+ else {
575
+ request.current += 1;
576
+ setUsage(void 0);
577
+ setError(void 0);
578
+ setBusy(false);
579
+ }
580
+ return () => {
581
+ request.current += 1;
582
+ };
583
+ }, [signedIn, resetKey]);
584
+ const visibleUsage = signedIn ? usage : void 0;
585
+ const windows = visibleUsage?.windows ?? [];
586
+ const hasDetails = windows.length > 0 || visibleUsage?.extraUsage !== void 0;
587
+ const fetchedAt = typeof visibleUsage?.fetchedAt === "number" ? validDate(visibleUsage.fetchedAt) : void 0;
588
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
589
+ className: "claudeSubscriptionCard claudeSubscriptionUsageCard",
590
+ children: [
591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
592
+ className: "claudeSubscriptionSectionHead",
593
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
594
+ className: "claudeSubscriptionSectionTitle",
595
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usage") }), fetchedAt === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
596
+ className: "claudeSubscriptionFreshness",
597
+ dateTime: fetchedAt.toISOString(),
598
+ children: fill(t("usageUpdated"), { value: fetchedAt.toLocaleString() })
599
+ })]
600
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
601
+ className: "claudeSubscriptionRefresh",
602
+ type: "button",
603
+ variant: "outline",
604
+ disabled: !signedIn || busy,
605
+ "aria-busy": busy,
606
+ onClick: () => load(true),
607
+ children: busy ? t("refreshing") : t("refresh")
608
+ })]
609
+ }),
610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
611
+ "aria-live": "polite",
612
+ children: [
613
+ !signedIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
614
+ className: "claudeSubscriptionEmpty",
615
+ children: t("noUsage")
616
+ }) : null,
617
+ signedIn && busy && usage === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
618
+ className: "claudeSubscriptionEmpty",
619
+ role: "status",
620
+ children: t("usageLoading")
621
+ }) : null,
622
+ signedIn && !busy && error === void 0 && usage !== void 0 && !hasDetails ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
623
+ className: "claudeSubscriptionEmpty",
624
+ role: "status",
625
+ children: t("usageEmpty")
626
+ }) : null
627
+ ]
628
+ }),
629
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
630
+ className: "claudeSubscriptionError",
631
+ role: "alert",
632
+ children: error
633
+ }),
634
+ windows.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
635
+ className: "claudeSubscriptionLimits",
636
+ children: windows.map((window) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
637
+ className: "claudeSubscriptionLimit",
638
+ children: [
639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
640
+ className: "claudeSubscriptionLimitTop",
641
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
642
+ className: "claudeSubscriptionLimitLabel",
643
+ children: windowLabel(window.id, t)
644
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("strong", { children: [percent(window.remainingPercent), "%"] })]
645
+ }),
646
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
647
+ max: "100",
648
+ value: window.remainingPercent,
649
+ "aria-label": `${windowLabel(window.id, t)} ${fill(t("remaining"), { value: percent(window.remainingPercent) })}`
650
+ }),
651
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
652
+ className: "claudeSubscriptionLimitMeta",
653
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetTime, {
654
+ resetsAt: window.resetsAt,
655
+ t
656
+ })
657
+ })
658
+ ]
659
+ }, window.id))
660
+ }),
661
+ visibleUsage?.extraUsage === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExtraUsageRow, {
662
+ extraUsage: visibleUsage.extraUsage,
663
+ t
664
+ })
665
+ ]
666
+ });
667
+ }
668
+ function ClaudeSection({ preference, rpc, t }) {
669
+ const [account, setAccount] = (0, react.useState)();
670
+ const [error, setError] = (0, react.useState)();
671
+ const [resetKey, setResetKey] = (0, react.useState)(0);
672
+ (0, react.useEffect)(() => {
673
+ let live = true;
674
+ rpc.call(CHANNEL, "status", {}).then(unwrap).then((next) => {
675
+ if (live) setAccount(next);
676
+ }).catch(() => {
677
+ if (live) setError(t("loadFailed"));
678
+ });
679
+ return () => {
680
+ live = false;
681
+ };
682
+ }, []);
683
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
684
+ className: "claudeSubscription",
685
+ children: [
686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: t("title") }),
687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
688
+ rpc,
689
+ t,
690
+ account,
691
+ setAccount,
692
+ onSignedOut: () => setResetKey((value) => value + 1)
693
+ }),
694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferencesCard, {
695
+ preference,
696
+ t
697
+ }),
698
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
699
+ rpc,
700
+ t,
701
+ signedIn: account?.authenticated === true,
702
+ resetKey
703
+ }),
704
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
705
+ className: "claudeSubscriptionError",
706
+ role: "alert",
707
+ children: error
708
+ })
709
+ ]
710
+ });
711
+ }
712
+ function apply(ctx) {
713
+ ctx.effect(() => ctx.locale.register(NS, {
714
+ zh,
715
+ en
716
+ }), "claude-subscription: copy");
717
+ ctx.effect(() => {
718
+ const tag = document.createElement("style");
719
+ tag.dataset.plugin = "dsh-claude-subscription";
720
+ tag.textContent = STYLE;
721
+ document.head.append(tag);
722
+ return () => tag.remove();
723
+ }, "claude-subscription: style");
724
+ const connection = ctx.get("connection");
725
+ const preference = createPreferenceController(ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE }), connection.rpc);
726
+ ctx.effect(() => {
727
+ preference.load();
728
+ const disposeReset = ctx.on("connection/reset", () => {
729
+ preference.load();
730
+ });
731
+ return () => {
732
+ disposeReset?.();
733
+ preference.dispose();
734
+ };
735
+ }, "claude-subscription: preferences");
736
+ const t = ctx.locale.bind(NS);
737
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
738
+ name: "settings.section",
739
+ id: "claude-subscription",
740
+ order: 16,
741
+ label: () => t("nav"),
742
+ locale: NS,
743
+ inject: () => ({
744
+ preference,
745
+ rpc: connection.rpc,
746
+ t
747
+ })
748
+ }, ClaudeSection));
749
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
750
+ name: "conversation.input.right",
751
+ id: "claude-subscription-quota",
752
+ order: 16,
753
+ locale: NS,
754
+ inject: (sessionId) => ({
755
+ preference,
756
+ rpc: connection.rpc,
757
+ t,
758
+ directory: ctx.modelDirectories.directoryFor(sessionId).store
759
+ })
760
+ }, ClaudeComposerQuota));
761
+ }
762
+ //#endregion
763
+ exports.apply = apply;
764
+ exports.inject = inject;
765
+ return module.exports;
766
+ }
767
+ });
768
+
769
+ //# sourceMappingURL=client.js.map