dsh-packycode-api 0.0.0-stage → 1.0.1

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-packycode-api",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region \0rolldown/runtime.js
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
16
+ key = keys[i];
17
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
18
+ get: ((k) => from[k]).bind(null, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+ //#endregion
29
+ let react = require("react");
30
+ react = __toESM(react, 1);
31
+ let react_jsx_runtime = require("react/jsx-runtime");
32
+ //#region src/totals.js
33
+ /**
34
+ * Totals across the keys a balance read returned.
35
+ *
36
+ * The per-key quota (`/api/usage/token/`) is what a key can spend, so the
37
+ * combined figure is the sum of those rows. PackyCode also reports account
38
+ * level billing (`/v1/dashboard/billing/*`), which is a different scope — it
39
+ * counts every token on the account — and is deliberately not what this sum
40
+ * shows.
41
+ *
42
+ * @module dsh-packycode-api/totals
43
+ */
44
+ const amount = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
45
+ /**
46
+ * Add up the balance rows the host returned.
47
+ * @param rows - the `keys` array of a balance projection.
48
+ * @returns summed amounts plus the conditions that make a sum incomplete:
49
+ * keys whose read failed, and keys with no quota limit at all.
50
+ */
51
+ function sumBalances(rows) {
52
+ const total = {
53
+ keys: 0,
54
+ counted: 0,
55
+ unread: 0,
56
+ unlimited: 0,
57
+ remainingUsd: 0,
58
+ usedUsd: 0,
59
+ limitUsd: 0
60
+ };
61
+ for (const row of Array.isArray(rows) ? rows : []) {
62
+ total.keys += 1;
63
+ const usage = row?.usage;
64
+ if (usage === void 0) {
65
+ total.unread += 1;
66
+ continue;
67
+ }
68
+ total.counted += 1;
69
+ if (usage.unlimitedQuota === true) {
70
+ total.unlimited += 1;
71
+ continue;
72
+ }
73
+ const remaining = amount(usage.availableUsd);
74
+ const used = amount(usage.usedUsd);
75
+ const limit = amount(usage.limitUsd);
76
+ if (remaining !== void 0) total.remainingUsd += remaining;
77
+ if (used !== void 0) total.usedUsd += used;
78
+ if (limit !== void 0) total.limitUsd += limit;
79
+ }
80
+ return total;
81
+ }
82
+ //#endregion
83
+ //#region src/client.jsx
84
+ /**
85
+ * Browser half: the PackyCode settings page and the composer balance chip.
86
+ *
87
+ * Every number here comes from the host over the plugin's own loopback RPC
88
+ * channel; no key material crosses it, and each read is a bounded projection
89
+ * the host built.
90
+ *
91
+ * @module dsh-packycode-api/client
92
+ */
93
+ const NS = "settings.packyCodeApi";
94
+ const PROVIDER = "packycode";
95
+ const CHANNEL = "/packycode-api";
96
+ const REFRESH_MS = 6e4;
97
+ const zh = {
98
+ nav: "PackyCode",
99
+ intro: "填入 PackyCode(PackyAPI)的 API Key 即可使用内置模型路由;多个 Key 按顺序使用,额度用尽或失效时自动切到下一个。",
100
+ accountTitle: "账户余额",
101
+ totalRemaining: "合计剩余",
102
+ totalUsed: "合计已用",
103
+ totalLimit: "合计额度",
104
+ totalPartial: "有 {count} 个 Key 未读到余额,未计入合计。",
105
+ accountLimit: "额度",
106
+ refresh: "刷新",
107
+ refreshing: "刷新中…",
108
+ keysTitle: "API Key",
109
+ keysHint: "按顺序使用:当前 Key 不可用时自动切到下一个可用 Key。上方的账户余额按 PackyCode 账户名逐条列出。",
110
+ keyActive: "使用中",
111
+ keyDisabled: "已停用",
112
+ keyUse: "切到此 Key",
113
+ keyEnable: "重新启用",
114
+ keyRemove: "删除",
115
+ keyUnlimited: "不限额度",
116
+ keyNoBalance: "余额未知",
117
+ keyRemaining: "剩余",
118
+ keyUsed: "已用",
119
+ keyName: "账户名",
120
+ keyNameUnknown: "账户名待读取",
121
+ resetMonthly: "每月",
122
+ resetWeekly: "每周",
123
+ resetDaily: "每日",
124
+ addTitle: "添加 Key",
125
+ addKeyPlaceholder: "粘贴 API Key",
126
+ addLabelPlaceholder: "备注(可选)",
127
+ add: "添加",
128
+ removeAll: "清空全部",
129
+ empty: "还没有 Key。添加一个即可在模型选择器里看到 PackyCode 的模型。",
130
+ advanced: "高级:接口地址",
131
+ apiHost: "模型接口地址",
132
+ statusHost: "余额接口地址",
133
+ save: "保存",
134
+ saved: "已保存",
135
+ notWritable: "当前 profile 不允许写入设置,改动只在本次运行内生效。",
136
+ composerLabel: "PackyCode 剩余额度",
137
+ composerNoKey: "无可用 Key",
138
+ error: "读取 PackyCode 状态失败",
139
+ loadFailed: "读取失败",
140
+ cliTitle: "Claude Code CLI",
141
+ cliChecking: "正在检测本机的 Claude Code CLI…",
142
+ cliInstalled: "已安装",
143
+ cliVersion: "版本",
144
+ cliPath: "位置",
145
+ cliRecheck: "重新检测",
146
+ cliMissing: "未检测到 Claude Code CLI",
147
+ cliMissingHint: "不安装将无法正常使用 Claude 模型(claude-opus-5-5、claude-sonnet-5-5、claude-haiku-5-5):PackyCode 只把 Claude 模型提供给官方 Claude Code CLI,插件需要调用本机的 CLI 来运行它。其它模型不受影响。",
148
+ cliConfiguredMissing: "设置里指定的 CLI 路径不存在:{path}",
149
+ cliBroken: "找到了 CLI,但无法运行",
150
+ cliInstall: "安装 Claude Code CLI",
151
+ cliReinstall: "重新安装",
152
+ cliInstalling: "正在安装…(通常需要一两分钟,请勿关闭 DSH)",
153
+ cliInstallCommand: "将在本机执行:{command}(需要 Node.js 18 或更高版本)",
154
+ cliInstallSucceeded: "安装完成。",
155
+ cliInstallFailed: "安装失败",
156
+ cliNpmMissing: "没有找到 npm。请先从 https://nodejs.org 安装 Node.js(18 或更高版本),然后再点安装。",
157
+ cliPermission: "npm 没有权限写入全局目录。请在终端执行 sudo {command},或把 npm 的全局目录改到用户目录后重试。",
158
+ cliPermissionWindows: "npm 没有权限写入全局目录。请以管理员身份打开终端执行 {command},然后点「重新检测」。",
159
+ cliNetwork: "下载失败,请检查网络或 npm 镜像设置后重试。",
160
+ cliTimeout: "安装超时,请检查网络后重试。",
161
+ cliNotFoundAfterInstall: "npm 显示安装完成,但仍找不到 claude 命令。请在终端执行 claude --version 确认,然后点「重新检测」。",
162
+ cliWindowsNote: "Windows 上 Claude Code 还需要 Git for Windows(https://git-scm.com/downloads/win)。"
163
+ };
164
+ const en = {
165
+ nav: "PackyCode",
166
+ intro: "Paste a PackyCode (PackyAPI) API key to use the built-in model route. Multiple keys are used in order; an exhausted or rejected key hands over to the next one.",
167
+ accountTitle: "Account balance",
168
+ totalRemaining: "Total remaining",
169
+ totalUsed: "Total used",
170
+ totalLimit: "Total limit",
171
+ totalPartial: "{count} key(s) could not be read and are not counted here.",
172
+ accountLimit: "Limit",
173
+ refresh: "Refresh",
174
+ refreshing: "Refreshing…",
175
+ keysTitle: "API keys",
176
+ keysHint: "Used in order: when the active key cannot serve a request, the next usable one takes over. The balances above are listed per PackyCode account.",
177
+ keyActive: "Active",
178
+ keyDisabled: "Disabled",
179
+ keyUse: "Use this key",
180
+ keyEnable: "Re-enable",
181
+ keyRemove: "Remove",
182
+ keyUnlimited: "No quota limit",
183
+ keyNoBalance: "Balance unknown",
184
+ keyRemaining: "Remaining",
185
+ keyUsed: "Used",
186
+ keyName: "Account name",
187
+ keyNameUnknown: "Account name not read yet",
188
+ resetMonthly: "monthly",
189
+ resetWeekly: "weekly",
190
+ resetDaily: "daily",
191
+ addTitle: "Add a key",
192
+ addKeyPlaceholder: "Paste API key",
193
+ addLabelPlaceholder: "Label (optional)",
194
+ add: "Add",
195
+ removeAll: "Remove all",
196
+ empty: "No keys yet. Add one and the PackyCode models appear in the model picker.",
197
+ advanced: "Advanced: endpoints",
198
+ apiHost: "Model endpoint",
199
+ statusHost: "Balance endpoint",
200
+ save: "Save",
201
+ saved: "Saved",
202
+ notWritable: "This profile does not allow settings writes; changes apply to this run only.",
203
+ composerLabel: "PackyCode remaining balance",
204
+ composerNoKey: "No usable key",
205
+ error: "Could not read PackyCode status",
206
+ loadFailed: "Read failed",
207
+ cliTitle: "Claude Code CLI",
208
+ cliChecking: "Checking this device for the Claude Code CLI…",
209
+ cliInstalled: "Installed",
210
+ cliVersion: "Version",
211
+ cliPath: "Location",
212
+ cliRecheck: "Check again",
213
+ cliMissing: "Claude Code CLI not found",
214
+ cliMissingHint: "Without it the Claude models (claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5) cannot work: PackyCode serves Claude models only to the official Claude Code CLI, so the plugin runs the CLI on this device. Other models are unaffected.",
215
+ cliConfiguredMissing: "The CLI path set in settings does not exist: {path}",
216
+ cliBroken: "The CLI was found but could not run",
217
+ cliInstall: "Install Claude Code CLI",
218
+ cliReinstall: "Reinstall",
219
+ cliInstalling: "Installing… (usually a minute or two; keep DSH open)",
220
+ cliInstallCommand: "Runs on this device: {command} (requires Node.js 18 or later)",
221
+ cliInstallSucceeded: "Installed.",
222
+ cliInstallFailed: "Install failed",
223
+ cliNpmMissing: "npm was not found. Install Node.js 18 or later from https://nodejs.org, then click Install again.",
224
+ cliPermission: "npm cannot write its global directory. Run sudo {command} in a terminal, or point npm’s global prefix at a folder you own, then retry.",
225
+ cliPermissionWindows: "npm cannot write its global directory. Open a terminal as administrator, run {command}, then click Check again.",
226
+ cliNetwork: "The download failed. Check your network or npm registry settings and retry.",
227
+ cliTimeout: "The install timed out. Check your network and retry.",
228
+ cliNotFoundAfterInstall: "npm reported success, but the claude command still cannot be found. Run claude --version in a terminal, then click Check again.",
229
+ cliWindowsNote: "On Windows, Claude Code also needs Git for Windows (https://git-scm.com/downloads/win)."
230
+ };
231
+ const STYLE = `
232
+ .packyCodeSection{display:flex;flex-direction:column;gap:16px;font-size:13px;line-height:20px}
233
+ .packyCodeIntro{color:var(--dsw-alias-label-secondary)}
234
+ .packyCodeCard{display:flex;flex-direction:column;gap:12px;padding:14px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
235
+ .packyCodeCardHeader{display:flex;align-items:center;justify-content:space-between;gap:12px}
236
+ .packyCodeCardTitle{font-weight:600}
237
+ .packyCodeFigures{display:flex;flex-wrap:wrap;gap:18px}
238
+ .packyCodeFigure{display:flex;flex-direction:column;gap:2px}
239
+ .packyCodeFigureLabel{color:var(--dsw-alias-label-tertiary);font-size:12px}
240
+ .packyCodeFigureValue{font-size:15px;font-weight:600;font-variant-numeric:tabular-nums}
241
+ .packyCodeRows{display:flex;flex-direction:column;gap:10px}
242
+ .packyCodeRow{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-module-platform)}
243
+ .packyCodeRowMain{display:flex;flex-direction:column;gap:2px;min-width:180px}
244
+ .packyCodeRowName{display:flex;align-items:center;gap:8px;font-weight:500}
245
+ .packyCodeRowMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;font-variant-numeric:tabular-nums}
246
+ .packyCodeChip{padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
247
+ .packyCodeChipActive{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary,inherit);border:1px solid var(--dsw-alias-brand-primary,currentColor)}
248
+ .packyCodeActions{display:flex;flex-wrap:wrap;gap:8px}
249
+ .packyCodeForm{display:flex;flex-wrap:wrap;gap:8px}
250
+ .packyCodeInput{flex:1 1 220px;min-width:0;height:32px;padding:0 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-default,rgba(127,127,127,.35));background:var(--dsw-alias-bg-module-platform);color:inherit;font:inherit}
251
+ .packyCodeButton{height:32px;padding:0 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-default,rgba(127,127,127,.35));background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary,inherit);font:inherit;cursor:pointer}
252
+ .packyCodeButton:disabled{opacity:.55;cursor:default}
253
+ /* Emphasis without a themed fill: the brand colour may be light in either
254
+ theme, and a filled button then hides its own label. */
255
+ .packyCodeButtonPrimary{border-color:var(--dsw-alias-brand-primary,currentColor);font-weight:600}
256
+ .packyCodeNote{color:var(--dsw-alias-label-tertiary);font-size:12px}
257
+ .packyCodeError{color:var(--dsw-alias-state-error-primary,#d9534f);font-size:12px}
258
+ .packyCodeAdvanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary)}
259
+ .packyCodeAdvancedBody{display:flex;flex-direction:column;gap:10px;padding-top:10px}
260
+ .packyCodeField{display:flex;flex-direction:column;gap:4px}
261
+ .packyCodeWarning{color:var(--dsw-alias-state-warning-primary,#c27c0e);font-weight:500}
262
+ .packyCodeDetail{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;white-space:pre-wrap;word-break:break-all;max-height:160px;overflow:auto}
263
+ .packyCodeMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;word-break:break-all}
264
+ .packyCodeComposer{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}
265
+ @media(max-width:640px){.packyCodeRow{align-items:flex-start;flex-direction:column}.packyCodeActions{width:100%}}
266
+ `;
267
+ const unwrap = (response) => {
268
+ if (!response?.ok) throw new Error(response?.error?.message ?? "PackyCode RPC failed");
269
+ return response.value;
270
+ };
271
+ function formatUsd(value) {
272
+ if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
273
+ try {
274
+ return new Intl.NumberFormat(void 0, {
275
+ style: "currency",
276
+ currency: "USD"
277
+ }).format(value);
278
+ } catch {
279
+ return `$${value.toFixed(2)}`;
280
+ }
281
+ }
282
+ function resetLabel(period, t) {
283
+ switch (period) {
284
+ case "monthly": return t("resetMonthly");
285
+ case "weekly": return t("resetWeekly");
286
+ case "daily": return t("resetDaily");
287
+ default: return;
288
+ }
289
+ }
290
+ /** Active key's usage row, preferring the key the ring currently points at. */
291
+ function activeUsage(balance) {
292
+ if (balance === void 0) return void 0;
293
+ return balance.keys?.find((entry) => entry.id === balance.activeId) ?? balance.keys?.[0];
294
+ }
295
+ function clearMessage(error) {
296
+ const text = typeof error?.message === "string" && error.message.length > 0 ? error.message : String(error);
297
+ return text.length > 200 ? text.slice(0, 200) : text;
298
+ }
299
+ const INSTALL_POLL_MS = 2e3;
300
+ /** The sentence explaining a failed install, by the code the host classified. */
301
+ function installFailure(cli, t) {
302
+ const command = cli.command ?? "";
303
+ switch (cli.install?.code) {
304
+ case "npm-missing": return t("cliNpmMissing");
305
+ case "permission": return t(cli.platform === "win32" ? "cliPermissionWindows" : "cliPermission").replace("{command}", command);
306
+ case "network": return t("cliNetwork");
307
+ case "timeout": return t("cliTimeout");
308
+ case "not-found-after-install": return t("cliNotFoundAfterInstall");
309
+ default: return;
310
+ }
311
+ }
312
+ /**
313
+ * Whether this device has the Claude Code CLI, which version, and — when it
314
+ * has none — a button that installs it with npm.
315
+ */
316
+ function ClaudeCliCard({ call, t }) {
317
+ const [cli, setCli] = react.useState();
318
+ const [checking, setChecking] = react.useState(false);
319
+ const [error, setError] = react.useState();
320
+ const check = react.useCallback((force = false) => {
321
+ setChecking(true);
322
+ setError(void 0);
323
+ return call("claude-cli/status", { force }).then(setCli).catch((failure) => setError(clearMessage(failure))).finally(() => setChecking(false));
324
+ }, [call]);
325
+ react.useEffect(() => {
326
+ check(false);
327
+ }, [check]);
328
+ const running = cli?.install?.state === "running";
329
+ react.useEffect(() => {
330
+ if (!running) return void 0;
331
+ let live = true;
332
+ const timer = window.setInterval(() => {
333
+ call("claude-cli/status").then((value) => {
334
+ if (live) setCli(value);
335
+ }).catch(() => void 0);
336
+ }, INSTALL_POLL_MS);
337
+ return () => {
338
+ live = false;
339
+ window.clearInterval(timer);
340
+ };
341
+ }, [call, running]);
342
+ const install = () => {
343
+ setError(void 0);
344
+ call("claude-cli/install").then(setCli).catch((failure) => setError(clearMessage(failure)));
345
+ };
346
+ const usable = cli?.installed === true && cli.version !== void 0;
347
+ const failed = cli?.install?.state === "failed";
348
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
349
+ className: "packyCodeCard",
350
+ children: [
351
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
352
+ className: "packyCodeCardHeader",
353
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
354
+ className: "packyCodeCardTitle",
355
+ children: t("cliTitle")
356
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
357
+ type: "button",
358
+ className: "packyCodeButton",
359
+ disabled: checking || running,
360
+ onClick: () => check(true),
361
+ children: checking ? t("refreshing") : t("cliRecheck")
362
+ })]
363
+ }),
364
+ cli === void 0 && error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
365
+ className: "packyCodeNote",
366
+ children: t("cliChecking")
367
+ }),
368
+ usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
370
+ className: "packyCodeFigures",
371
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
372
+ className: "packyCodeFigure",
373
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
374
+ className: "packyCodeFigureLabel",
375
+ children: t("cliInstalled")
376
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
377
+ className: "packyCodeFigureValue",
378
+ children: `${t("cliVersion")} ${cli.version}`
379
+ })]
380
+ })
381
+ }),
382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
383
+ className: "packyCodeNote",
384
+ children: [`${t("cliPath")}:`, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
385
+ className: "packyCodeMono",
386
+ children: cli.path
387
+ })]
388
+ }),
389
+ cli.install?.state === "succeeded" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
390
+ className: "packyCodeNote",
391
+ children: t("cliInstallSucceeded")
392
+ })
393
+ ] }),
394
+ cli !== void 0 && !usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
395
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
396
+ className: "packyCodeWarning",
397
+ children: cli.installed ? t("cliBroken") : t("cliMissing")
398
+ }),
399
+ cli.installed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
400
+ className: "packyCodeNote",
401
+ children: [`${t("cliPath")}:`, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
402
+ className: "packyCodeMono",
403
+ children: cli.path
404
+ })]
405
+ }), cli.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
406
+ className: "packyCodeDetail",
407
+ children: cli.error
408
+ })] }),
409
+ cli.configuredPath !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
410
+ className: "packyCodeNote",
411
+ children: t("cliConfiguredMissing").replace("{path}", cli.configuredPath)
412
+ }),
413
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
414
+ className: "packyCodeIntro",
415
+ children: t("cliMissingHint")
416
+ }),
417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
418
+ className: "packyCodeActions",
419
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
420
+ type: "button",
421
+ className: "packyCodeButton packyCodeButtonPrimary",
422
+ disabled: running || checking,
423
+ onClick: install,
424
+ children: running ? t("cliInstalling") : cli.installed ? t("cliReinstall") : t("cliInstall")
425
+ })
426
+ }),
427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
428
+ className: "packyCodeNote",
429
+ children: [t("cliInstallCommand").replace("{command}", cli.command ?? ""), cli.platform === "win32" && ` ${t("cliWindowsNote")}`]
430
+ })
431
+ ] }),
432
+ failed && !usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
433
+ className: "packyCodeError",
434
+ children: installFailure(cli, t) === void 0 ? t("cliInstallFailed") : `${t("cliInstallFailed")}:${installFailure(cli, t)}`
435
+ }), cli.install.detail !== void 0 && cli.install.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
436
+ className: "packyCodeDetail",
437
+ children: cli.install.detail
438
+ })] }),
439
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
440
+ className: "packyCodeError",
441
+ children: `${t("error")}: ${error}`
442
+ })
443
+ ]
444
+ });
445
+ }
446
+ function PackyCodeSection({ rpc, t }) {
447
+ const call = react.useMemo(() => (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap), [rpc]);
448
+ const [status, setStatus] = react.useState();
449
+ const [balance, setBalance] = react.useState();
450
+ const [busy, setBusy] = react.useState(false);
451
+ const [error, setError] = react.useState();
452
+ const [draft, setDraft] = react.useState({
453
+ key: "",
454
+ label: ""
455
+ });
456
+ const generation = react.useRef(0);
457
+ const load = react.useCallback((force = false) => {
458
+ const current = ++generation.current;
459
+ setBusy(true);
460
+ setError(void 0);
461
+ return Promise.all([call("status"), call("balance", { force })]).then(([nextStatus, nextBalance]) => {
462
+ if (generation.current !== current) return;
463
+ setStatus(nextStatus);
464
+ setBalance(nextBalance);
465
+ }).catch((failure) => {
466
+ if (generation.current === current) setError(clearMessage(failure));
467
+ }).finally(() => {
468
+ if (generation.current === current) setBusy(false);
469
+ });
470
+ }, [call]);
471
+ react.useEffect(() => {
472
+ load(false);
473
+ return () => {
474
+ generation.current += 1;
475
+ };
476
+ }, [load]);
477
+ const mutate = (endpoint) => (payload) => {
478
+ setBusy(true);
479
+ setError(void 0);
480
+ return call(endpoint, payload).then((nextStatus) => {
481
+ setStatus(nextStatus);
482
+ return call("balance", { force: true });
483
+ }).then((nextBalance) => setBalance(nextBalance)).catch((failure) => setError(clearMessage(failure))).finally(() => setBusy(false));
484
+ };
485
+ const addKey = (event) => {
486
+ event.preventDefault();
487
+ if (draft.key.trim() === "") return;
488
+ mutate("keys/add")({
489
+ key: draft.key,
490
+ label: draft.label
491
+ }).then(() => setDraft({
492
+ key: "",
493
+ label: ""
494
+ }));
495
+ };
496
+ const rows = balance?.keys ?? [];
497
+ const usageOf = (id) => rows.find((row) => row.id === id);
498
+ const keys = status?.keys ?? [];
499
+ const totals = sumBalances(rows);
500
+ /** One account row: the PackyCode token name carries the match to a key. */
501
+ const accountRow = (row) => {
502
+ const usage = row.usage;
503
+ const figures = [
504
+ usage?.unlimitedQuota === true ? t("keyUnlimited") : usage?.availableUsd === void 0 ? void 0 : `${t("keyRemaining")} ${formatUsd(usage.availableUsd)}`,
505
+ usage?.usedUsd === void 0 ? void 0 : `${t("keyUsed")} ${formatUsd(usage.usedUsd)}`,
506
+ usage?.limitUsd === void 0 ? void 0 : `${t("accountLimit")} ${formatUsd(usage.limitUsd)}`,
507
+ resetLabel(usage?.resetPeriod, t) === void 0 ? void 0 : resetLabel(usage.resetPeriod, t)
508
+ ].filter(Boolean);
509
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
510
+ className: "packyCodeRow",
511
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
512
+ className: "packyCodeRowMain",
513
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
514
+ className: "packyCodeRowName",
515
+ children: [
516
+ usage?.name ?? row.label,
517
+ row.id === status?.activeId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
518
+ className: "packyCodeChip packyCodeChipActive",
519
+ children: t("keyActive")
520
+ }),
521
+ row.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
522
+ className: "packyCodeChip",
523
+ children: t("keyDisabled")
524
+ })
525
+ ]
526
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
527
+ className: "packyCodeRowMeta",
528
+ children: figures.length > 0 ? figures.join(" · ") : row.error === void 0 ? t("keyNoBalance") : `${t("loadFailed")}: ${row.error}`
529
+ })]
530
+ })
531
+ }, row.id);
532
+ };
533
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
534
+ className: "packyCodeSection",
535
+ children: [
536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
537
+ className: "packyCodeIntro",
538
+ children: t("intro")
539
+ }),
540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClaudeCliCard, {
541
+ call,
542
+ t
543
+ }),
544
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
545
+ className: "packyCodeCard",
546
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
547
+ className: "packyCodeCardHeader",
548
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
549
+ className: "packyCodeCardTitle",
550
+ children: t("accountTitle")
551
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
552
+ type: "button",
553
+ className: "packyCodeButton",
554
+ disabled: busy,
555
+ onClick: () => load(true),
556
+ children: busy ? t("refreshing") : t("refresh")
557
+ })]
558
+ }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
559
+ className: "packyCodeNote",
560
+ children: t("empty")
561
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
563
+ className: "packyCodeRows",
564
+ children: rows.map(accountRow)
565
+ }),
566
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
567
+ className: "packyCodeFigures",
568
+ children: [
569
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
570
+ className: "packyCodeFigure",
571
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
572
+ className: "packyCodeFigureLabel",
573
+ children: t("totalRemaining")
574
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
575
+ className: "packyCodeFigureValue",
576
+ children: [totals.counted === 0 ? "—" : formatUsd(totals.remainingUsd) ?? "—", totals.unlimited > 0 && ` + ${t("keyUnlimited")}`]
577
+ })]
578
+ }),
579
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
580
+ className: "packyCodeFigure",
581
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
582
+ className: "packyCodeFigureLabel",
583
+ children: t("totalUsed")
584
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
585
+ className: "packyCodeFigureValue",
586
+ children: totals.counted === 0 ? "—" : formatUsd(totals.usedUsd) ?? "—"
587
+ })]
588
+ }),
589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
590
+ className: "packyCodeFigure",
591
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
592
+ className: "packyCodeFigureLabel",
593
+ children: t("totalLimit")
594
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
595
+ className: "packyCodeFigureValue",
596
+ children: totals.counted === 0 || totals.unlimited > 0 ? t("keyUnlimited") : formatUsd(totals.limitUsd) ?? "—"
597
+ })]
598
+ })
599
+ ]
600
+ }),
601
+ totals.unread > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
602
+ className: "packyCodeNote",
603
+ children: t("totalPartial").replace("{count}", String(totals.unread))
604
+ })
605
+ ] })]
606
+ }),
607
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
608
+ className: "packyCodeCard",
609
+ children: [
610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
611
+ className: "packyCodeCardHeader",
612
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
613
+ className: "packyCodeCardTitle",
614
+ children: t("keysTitle")
615
+ }), keys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
616
+ type: "button",
617
+ className: "packyCodeButton",
618
+ disabled: busy,
619
+ onClick: () => mutate("keys/remove-all")({}),
620
+ children: t("removeAll")
621
+ })]
622
+ }),
623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
624
+ className: "packyCodeNote",
625
+ children: t("keysHint")
626
+ }),
627
+ keys.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
628
+ className: "packyCodeNote",
629
+ children: t("empty")
630
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
631
+ className: "packyCodeRows",
632
+ children: keys.map((key) => {
633
+ const row = usageOf(key.id);
634
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
635
+ className: "packyCodeRow",
636
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
637
+ className: "packyCodeRowMain",
638
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
639
+ className: "packyCodeRowName",
640
+ children: [
641
+ key.label,
642
+ key.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
643
+ className: "packyCodeChip packyCodeChipActive",
644
+ children: t("keyActive")
645
+ }),
646
+ key.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
647
+ className: "packyCodeChip",
648
+ children: `${t("keyDisabled")}: ${key.disabledReason ?? ""}`.trim()
649
+ })
650
+ ]
651
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
652
+ className: "packyCodeRowMeta",
653
+ children: row?.usage?.name === void 0 ? t("keyNameUnknown") : `${t("keyName")} ${row.usage.name}`
654
+ })]
655
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
656
+ className: "packyCodeActions",
657
+ children: [
658
+ !key.active && !key.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
659
+ type: "button",
660
+ className: "packyCodeButton",
661
+ disabled: busy,
662
+ onClick: () => mutate("keys/select")({ id: key.id }),
663
+ children: t("keyUse")
664
+ }),
665
+ key.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
666
+ type: "button",
667
+ className: "packyCodeButton",
668
+ disabled: busy,
669
+ onClick: () => mutate("keys/enable")({ id: key.id }),
670
+ children: t("keyEnable")
671
+ }),
672
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
673
+ type: "button",
674
+ className: "packyCodeButton",
675
+ disabled: busy,
676
+ onClick: () => mutate("keys/remove")({ id: key.id }),
677
+ children: t("keyRemove")
678
+ })
679
+ ]
680
+ })]
681
+ }, key.id);
682
+ })
683
+ }),
684
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
685
+ className: "packyCodeForm",
686
+ onSubmit: addKey,
687
+ children: [
688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
689
+ className: "packyCodeInput",
690
+ type: "password",
691
+ autoComplete: "off",
692
+ placeholder: t("addKeyPlaceholder"),
693
+ value: draft.key,
694
+ onChange: (event) => setDraft((current) => ({
695
+ ...current,
696
+ key: event.target.value
697
+ }))
698
+ }),
699
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
700
+ className: "packyCodeInput",
701
+ type: "text",
702
+ placeholder: t("addLabelPlaceholder"),
703
+ value: draft.label,
704
+ onChange: (event) => setDraft((current) => ({
705
+ ...current,
706
+ label: event.target.value
707
+ }))
708
+ }),
709
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
710
+ type: "submit",
711
+ className: "packyCodeButton packyCodeButtonPrimary",
712
+ disabled: busy || draft.key.trim() === "",
713
+ children: t("add")
714
+ })
715
+ ]
716
+ }),
717
+ status?.writable !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
718
+ className: "packyCodeNote",
719
+ children: t("notWritable")
720
+ }),
721
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
722
+ className: "packyCodeError",
723
+ children: `${t("error")}: ${error}`
724
+ })
725
+ ]
726
+ }),
727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
728
+ className: "packyCodeAdvanced",
729
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("advanced") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
730
+ className: "packyCodeAdvancedBody",
731
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
732
+ className: "packyCodeField",
733
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
734
+ className: "packyCodeFigureLabel",
735
+ children: t("apiHost")
736
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
737
+ className: "packyCodeInput",
738
+ type: "text",
739
+ disabled: status?.writable !== true,
740
+ defaultValue: status?.hosts?.apiHost ?? "",
741
+ onBlur: (event) => mutate("hosts/update")({
742
+ apiHost: event.target.value,
743
+ statusHost: status?.hosts?.statusHost
744
+ })
745
+ })]
746
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
747
+ className: "packyCodeField",
748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
749
+ className: "packyCodeFigureLabel",
750
+ children: t("statusHost")
751
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
752
+ className: "packyCodeInput",
753
+ type: "text",
754
+ disabled: status?.writable !== true,
755
+ defaultValue: status?.hosts?.statusHost ?? "",
756
+ onBlur: (event) => mutate("hosts/update")({
757
+ statusHost: event.target.value,
758
+ apiHost: status?.hosts?.apiHost
759
+ })
760
+ })]
761
+ })]
762
+ })]
763
+ })
764
+ ]
765
+ });
766
+ }
767
+ function PackyCodeComposerQuota({ rpc, t, directory }) {
768
+ const call = react.useMemo(() => (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap), [rpc]);
769
+ const enabled = react.useSyncExternalStore((listener) => directory.subscribe(listener), () => directory.getSnapshot())?.current?.provider === PROVIDER;
770
+ const [balance, setBalance] = react.useState();
771
+ react.useEffect(() => {
772
+ if (!enabled) {
773
+ setBalance(void 0);
774
+ return;
775
+ }
776
+ let live = true;
777
+ const tick = () => {
778
+ call("balance").then((value) => {
779
+ if (live) setBalance(value);
780
+ }).catch(() => {
781
+ if (live) setBalance(void 0);
782
+ });
783
+ };
784
+ tick();
785
+ const timer = window.setInterval(tick, REFRESH_MS);
786
+ return () => {
787
+ live = false;
788
+ window.clearInterval(timer);
789
+ };
790
+ }, [call, enabled]);
791
+ if (!enabled || balance === void 0) return null;
792
+ const usage = activeUsage(balance)?.usage;
793
+ const display = usage?.unlimitedQuota === true ? t("keyUnlimited") : formatUsd(usage?.availableUsd) ?? t("composerNoKey");
794
+ const label = `${t("composerLabel")}: ${display}`;
795
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
796
+ className: "packyCodeComposer",
797
+ role: "status",
798
+ "aria-label": label,
799
+ title: label,
800
+ children: display
801
+ });
802
+ }
803
+ const inject = [
804
+ "slots",
805
+ "locale",
806
+ "connection",
807
+ "modelDirectories"
808
+ ];
809
+ function apply(ctx) {
810
+ ctx.effect(() => ctx.locale.register(NS, {
811
+ zh,
812
+ en
813
+ }), "packycode-api: copy");
814
+ ctx.effect(() => {
815
+ const tag = document.createElement("style");
816
+ tag.dataset.plugin = "dsh-packycode-api";
817
+ tag.textContent = STYLE;
818
+ document.head.append(tag);
819
+ return () => tag.remove();
820
+ }, "packycode-api: style");
821
+ const connection = ctx.get("connection");
822
+ const t = ctx.locale.bind(NS);
823
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
824
+ name: "settings.section",
825
+ id: "packycode-api",
826
+ order: 17,
827
+ label: () => t("nav"),
828
+ locale: NS,
829
+ inject: () => ({
830
+ rpc: connection.rpc,
831
+ t
832
+ })
833
+ }, PackyCodeSection));
834
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
835
+ name: "conversation.input.right",
836
+ id: "packycode-api-balance",
837
+ order: 17,
838
+ locale: NS,
839
+ inject: (sessionId) => ({
840
+ rpc: connection.rpc,
841
+ t,
842
+ directory: ctx.modelDirectories.directoryFor(sessionId).store
843
+ })
844
+ }, PackyCodeComposerQuota));
845
+ }
846
+ //#endregion
847
+ exports.apply = apply;
848
+ exports.inject = inject;
849
+ return module.exports;
850
+ }
851
+ });
852
+
853
+ //# sourceMappingURL=client.js.map