dsh-packycode-api 1.0.7 → 1.1.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 CHANGED
@@ -29,87 +29,26 @@ window.__ModuleLoader__.load({
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
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
32
+ //#region src/client-copy.js
84
33
  /**
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.
34
+ * The settings panel's copy, in both languages DSH ships.
90
35
  *
91
- * @module dsh-packycode-api/client
36
+ * @module dsh-packycode-api/client-copy
92
37
  */
93
- const NS = "settings.packyCodeApi";
94
- const PROVIDER = "packycode";
95
- const CHANNEL = "/packycode-api";
96
- const REFRESH_MS = 6e4;
97
38
  const zh = {
98
39
  nav: "PackyCode",
99
- intro: "填入 PackyCode(PackyAPI)的 API Key 即可使用内置模型路由;多个 Key 按顺序使用,额度用尽或失效时自动切到下一个。",
100
- accountTitle: "账户余额",
40
+ intro: "填入 PackyCode(PackyAPI)的 API Key 即可使用内置模型路由。多个 Key 按顺序使用:额度用尽或失效时自动切到下一个;某个 Key 的分组用不了所选模型时,自动改用分组合适的 Key。",
41
+ accountsTitle: "账户与 Key",
42
+ refresh: "刷新",
43
+ refreshing: "刷新中…",
101
44
  totalRemaining: "合计剩余",
102
45
  totalUsed: "合计已用",
103
46
  totalLimit: "合计额度",
104
47
  totalPartial: "有 {count} 个 Key 未读到余额,未计入合计。",
105
48
  totalStale: "其中 {count} 个 Key 本次读取失败,显示的是上次读到的余额。",
106
49
  staleNote: "上次读取于 {time},本次读取失败:{error}",
107
- composerStale: "上次读取于 {time},本次读取失败",
108
- accountLimit: "额度",
109
- refresh: "刷新",
110
- refreshing: "刷新中…",
111
- keysTitle: "API Key",
112
- keysHint: "按顺序使用:当前 Key 不可用时自动切到下一个可用 Key。上方的账户余额按备注名逐条列出,可用「重命名」修改备注。",
50
+ empty: "还没有 Key。添加一个即可在模型选择器里看到 PackyCode 的模型。",
51
+ keysHint: "按顺序使用:当前 Key 不可用时自动切到下一个;「切到此 Key」指定优先使用的 Key。",
113
52
  keyActive: "使用中",
114
53
  keyDisabled: "已停用",
115
54
  keyUse: "切到此 Key",
@@ -123,28 +62,35 @@ window.__ModuleLoader__.load({
123
62
  keyNoBalance: "余额未知",
124
63
  keyRemaining: "剩余",
125
64
  keyUsed: "已用",
65
+ accountLimit: "额度",
126
66
  keyName: "账户名",
127
67
  keyNameUnknown: "账户名待读取",
128
- resetMonthly: "每月",
129
- resetWeekly: "每周",
130
- resetDaily: "每日",
68
+ resetMonthly: "每月重置",
69
+ resetWeekly: "每周重置",
70
+ resetDaily: "每日重置",
71
+ loadFailed: "读取失败",
72
+ removeAll: "清空全部",
131
73
  addTitle: "添加 Key",
132
74
  addKeyPlaceholder: "粘贴 API Key",
133
75
  addLabelPlaceholder: "备注(可选)",
134
76
  add: "添加",
135
- removeAll: "清空全部",
136
- empty: "还没有 Key。添加一个即可在模型选择器里看到 PackyCode 的模型。",
77
+ notWritable: "当前 profile 不允许写入设置,改动只在本次运行内生效。",
78
+ error: "读取 PackyCode 状态失败",
79
+ groupsTitle: "分组",
80
+ groupsChecking: "正在读取分组…",
81
+ groupsUnknown: "分组未读到",
82
+ groupsRejected: "Key 被网关拒绝",
83
+ groupsReach: "可用",
84
+ groupsMiss: "不可用",
85
+ groupsPartial: "部分",
86
+ groupsStale: "分组为上次读取的结果",
137
87
  advanced: "高级:接口地址",
138
88
  apiHost: "模型接口地址",
139
89
  statusHost: "余额接口地址",
140
- save: "保存",
141
- saved: "已保存",
142
- notWritable: "当前 profile 不允许写入设置,改动只在本次运行内生效。",
143
90
  composerLabel: "PackyCode 剩余额度",
144
91
  composerNoKey: "无可用 Key",
145
92
  composerUnread: "余额读取失败",
146
- error: "读取 PackyCode 状态失败",
147
- loadFailed: "读取失败",
93
+ composerStale: "上次读取于 {time},本次读取失败",
148
94
  cliTitle: "Claude Code CLI",
149
95
  cliChecking: "正在检测本机的 Claude Code CLI…",
150
96
  cliInstalled: "已安装",
@@ -174,8 +120,8 @@ window.__ModuleLoader__.load({
174
120
  versionCheck: "检查更新",
175
121
  versionChecking: "正在检查更新…",
176
122
  versionFailed: "无法检查最新版本。",
177
- versionUpToDate: "已是最新版本。",
178
- versionAvailable: "发现新版本 v{version}。",
123
+ versionUpToDate: "已是最新版本",
124
+ versionAvailable: "发现新版本 v{version}",
179
125
  versionLinked: "当前是本地开发安装,请在插件仓库拉取代码并重新构建。",
180
126
  versionManual: "无法在这里更新。请在桌面应用的插件页重新安装 dsh-packycode-api。",
181
127
  versionUpdate: "更新插件",
@@ -186,21 +132,48 @@ window.__ModuleLoader__.load({
186
132
  versionRestart: "如何重启",
187
133
  versionRestartHint: "插件无法安全地重启桌面应用。请完全退出 DSH,再重新打开。",
188
134
  versionRefresh: "刷新界面",
189
- versionLater: "稍后"
135
+ versionLater: "稍后",
136
+ searchTitle: "网页搜索",
137
+ searchScope: "DSH 全局设置",
138
+ searchIntro: "DSH 的 web_search 和 Claude CLI 的 MCP 搜索可使用这里选择的 DeepSeek/GPT 后端。每次搜索是独立的模型调用,用插件保存的 Key 计费。",
139
+ searchCliHint: "Claude CLI 始终通过 MCP 使用这里的搜索后端,原生 WebSearch 已禁用。下面的路由选项只控制 DSH 全局搜索,不影响 CLI。",
140
+ searchChecking: "正在读取搜索设置…",
141
+ searchLoadFailed: "搜索设置读取或保存失败",
142
+ searchModeTitle: "DSH 路由",
143
+ searchModeDefault: "不接管",
144
+ searchModeDefaultHint: "不改动 DSH 的搜索路由(默认)。切回此项会移除本插件写入 profile 的搜索补丁。",
145
+ searchModeAuto: "按模型",
146
+ searchModeAutoHint: "PackyCode 模型用 PackyCode 搜索;其他模型仍走原来的搜索(DSH 默认搜索,或其他插件的设置)。",
147
+ searchModePackycode: "始终 PackyCode",
148
+ searchModePackycodeHint: "原本由 DSH 默认搜索处理的请求都改走 PackyCode。Codex、Kimi 等订阅插件接管的模型仍按它们自己的设置。",
149
+ searchBackendTitle: "搜索后端",
150
+ searchBackendDeepseek: "DeepSeek",
151
+ searchBackendDeepseekHint: "{model}:每次搜索约几千 token;需要 Key 的分组包含 DeepSeek。",
152
+ searchBackendGpt: "GPT",
153
+ searchBackendGptHint: "{model}:结果通常更新、更准,但每次搜索约 2–3 万输入 token;需要 Key 的分组包含 Codex。",
154
+ searchEffective: "当前生效:基础搜索 {base} · 运行时 {runtime}",
155
+ searchPatchNote: "「按模型」和「始终」会在本 profile 的 cordis.patch.yml 里写入一段带标记的 web 配置,热加载生效,无需重启。卸载插件前请先切回「不接管」。",
156
+ searchApplyFailed: "写入搜索补丁失败:{error}",
157
+ searchForeign: "检测到 {names} 订阅插件:它们接管的模型仍按各自的搜索设置,其余请求才会到达这里的选择。",
158
+ searchOverridden: "基础搜索现在是 {base},不是 PackyCode:可能还在热加载,或被其他插件的补丁覆盖。",
159
+ searchUnavailable: "当前运行环境没有 DSH 网页搜索服务,全局路由暂不生效;Claude CLI 的 MCP 搜索不受影响。",
160
+ searchNotWritable: "当前 profile 不允许写入设置。",
161
+ searchUnknown: "未知"
190
162
  };
191
163
  const en = {
192
164
  nav: "PackyCode",
193
- 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.",
194
- accountTitle: "Account balance",
165
+ intro: "Paste a PackyCode (PackyAPI) API key to use the built-in model route. Keys are used in order: an exhausted or rejected key hands over to the next one, and a key whose groups cannot reach the selected model is passed over for one whose groups can.",
166
+ accountsTitle: "Accounts and keys",
167
+ refresh: "Refresh",
168
+ refreshing: "Refreshing…",
195
169
  totalRemaining: "Total remaining",
196
170
  totalUsed: "Total used",
197
171
  totalLimit: "Total limit",
198
172
  totalPartial: "{count} key(s) could not be read and are not counted here.",
199
- accountLimit: "Limit",
200
- refresh: "Refresh",
201
- refreshing: "Refreshing…",
202
- keysTitle: "API keys",
203
- keysHint: "Used in order: when the active key cannot serve a request, the next usable one takes over. The balances above are listed by these names; use Rename to change one.",
173
+ totalStale: "{count} of these keys failed this read and show their last read balance.",
174
+ staleNote: "Last read at {time}; this read failed: {error}",
175
+ empty: "No keys yet. Add one and the PackyCode models appear in the model picker.",
176
+ keysHint: "Used in order: when the active key cannot serve a request, the next one takes over. “Use this key” picks the preferred key.",
204
177
  keyActive: "Active",
205
178
  keyDisabled: "Disabled",
206
179
  keyUse: "Use this key",
@@ -214,31 +187,35 @@ window.__ModuleLoader__.load({
214
187
  keyNoBalance: "Balance unknown",
215
188
  keyRemaining: "Remaining",
216
189
  keyUsed: "Used",
217
- keyName: "Account name",
190
+ accountLimit: "Limit",
191
+ keyName: "Account",
218
192
  keyNameUnknown: "Account name not read yet",
219
- resetMonthly: "monthly",
220
- resetWeekly: "weekly",
221
- resetDaily: "daily",
193
+ resetMonthly: "resets monthly",
194
+ resetWeekly: "resets weekly",
195
+ resetDaily: "resets daily",
196
+ loadFailed: "Read failed",
197
+ removeAll: "Remove all",
222
198
  addTitle: "Add a key",
223
199
  addKeyPlaceholder: "Paste API key",
224
200
  addLabelPlaceholder: "Label (optional)",
225
201
  add: "Add",
226
- removeAll: "Remove all",
227
- empty: "No keys yet. Add one and the PackyCode models appear in the model picker.",
202
+ notWritable: "This profile does not allow settings writes; changes apply to this run only.",
203
+ error: "Could not read PackyCode status",
204
+ groupsTitle: "Groups",
205
+ groupsChecking: "Reading groups…",
206
+ groupsUnknown: "Groups not read",
207
+ groupsRejected: "Key rejected by the gateway",
208
+ groupsReach: "Reaches",
209
+ groupsMiss: "Not",
210
+ groupsPartial: "partly",
211
+ groupsStale: "groups from the last read",
228
212
  advanced: "Advanced: endpoints",
229
213
  apiHost: "Model endpoint",
230
214
  statusHost: "Balance endpoint",
231
- save: "Save",
232
- saved: "Saved",
233
- notWritable: "This profile does not allow settings writes; changes apply to this run only.",
234
215
  composerLabel: "PackyCode remaining balance",
235
216
  composerNoKey: "No usable key",
236
217
  composerUnread: "Balance unavailable",
237
- totalStale: "{count} of these keys failed this read and show their last read balance.",
238
- staleNote: "Last read at {time}; this read failed: {error}",
239
218
  composerStale: "last read at {time}; this read failed",
240
- error: "Could not read PackyCode status",
241
- loadFailed: "Read failed",
242
219
  cliTitle: "Claude Code CLI",
243
220
  cliChecking: "Checking this device for the Claude Code CLI…",
244
221
  cliInstalled: "Installed",
@@ -268,8 +245,8 @@ window.__ModuleLoader__.load({
268
245
  versionCheck: "Check for updates",
269
246
  versionChecking: "Checking for updates…",
270
247
  versionFailed: "Could not check the latest version.",
271
- versionUpToDate: "You are on the latest version.",
272
- versionAvailable: "A new version v{version} is available.",
248
+ versionUpToDate: "Up to date",
249
+ versionAvailable: "v{version} is available",
273
250
  versionLinked: "Installed from a local checkout. Pull and rebuild the plugin repository instead.",
274
251
  versionManual: "This install cannot be updated here. Reinstall dsh-packycode-api from the desktop Plugins page.",
275
252
  versionUpdate: "Update plugin",
@@ -280,51 +257,242 @@ window.__ModuleLoader__.load({
280
257
  versionRestart: "How to restart",
281
258
  versionRestartHint: "The plugin cannot safely restart the desktop application. Quit DSH completely, then open it again.",
282
259
  versionRefresh: "Refresh page",
283
- versionLater: "Later"
260
+ versionLater: "Later",
261
+ searchTitle: "Web search",
262
+ searchScope: "DSH-wide setting",
263
+ searchIntro: "DSH’s web_search and Claude CLI’s MCP search can use the DeepSeek/GPT backend selected here. Each search is a separate model call billed to the plugin-stored keys.",
264
+ searchCliHint: "Claude CLI always uses this backend through MCP; native WebSearch is disabled. The routing options below only control DSH-wide search, not CLI search.",
265
+ searchChecking: "Reading the search preference…",
266
+ searchLoadFailed: "Could not read or save the search preference",
267
+ searchModeTitle: "DSH routing",
268
+ searchModeDefault: "Off",
269
+ searchModeDefaultHint: "Leave DSH search routing untouched (the default). Switching back here removes the search patch this plugin wrote into the profile.",
270
+ searchModeAuto: "By model",
271
+ searchModeAutoHint: "PackyCode models search through PackyCode; other models keep the search they had (DSH’s default, or another plugin’s choice).",
272
+ searchModePackycode: "Always PackyCode",
273
+ searchModePackycodeHint: "Every request DSH’s default search would have served goes through PackyCode. Models another subscription plugin (Codex, Kimi) routes keep that plugin’s choice.",
274
+ searchBackendTitle: "Search backend",
275
+ searchBackendDeepseek: "DeepSeek",
276
+ searchBackendDeepseekHint: "{model}: a few thousand tokens per search; needs a key whose group includes DeepSeek.",
277
+ searchBackendGpt: "GPT",
278
+ searchBackendGptHint: "{model}: usually fresher and more precise, but about 20–30K input tokens per search; needs a key whose group includes Codex.",
279
+ searchEffective: "In effect: base search {base} · runtime {runtime}",
280
+ searchPatchNote: "By model and Always write a marked web entry into this profile’s cordis.patch.yml; it hot-applies, no restart needed. Switch back to Off before uninstalling the plugin.",
281
+ searchApplyFailed: "Could not write the search patch: {error}",
282
+ searchForeign: "{names} subscription plugin detected: the models it routes keep its own search choice; only the other requests reach the choice made here.",
283
+ searchOverridden: "The base search is {base}, not PackyCode: the reload may still be running, or another plugin’s patch overrides it.",
284
+ searchUnavailable: "This runtime has no DSH web search service; global routing has no effect. Claude CLI’s MCP search is unaffected.",
285
+ searchNotWritable: "This profile does not allow settings writes.",
286
+ searchUnknown: "unknown"
284
287
  };
288
+ //#endregion
289
+ //#region src/client-style.js
290
+ /**
291
+ * The settings panel's stylesheet, built only from DSH design tokens so it
292
+ * follows the host theme (light and dark) and matches the host's own settings
293
+ * pages: settings cards, hairline-divided rows, 0.5px tags, layer-1 inputs.
294
+ * Fallbacks cover hosts that predate the settings-card tokens.
295
+ *
296
+ * @module dsh-packycode-api/client-style
297
+ */
285
298
  const STYLE = `
286
- .packyCodeSection{display:flex;flex-direction:column;gap:16px;font-size:13px;line-height:20px}
287
- .packyCodeIntro{color:var(--dsw-alias-label-secondary)}
288
- .packyCodeCard{display:flex;flex-direction:column;gap:12px;padding:14px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
289
- .packyCodeCardHeader{display:flex;align-items:center;justify-content:space-between;gap:12px}
290
- .packyCodeCardTitle{font-weight:600}
291
- .packyCodeFigures{display:flex;flex-wrap:wrap;gap:18px}
292
- .packyCodeFigure{display:flex;flex-direction:column;gap:2px}
293
- .packyCodeFigureLabel{color:var(--dsw-alias-label-tertiary);font-size:12px}
294
- .packyCodeFigureValue{font-size:15px;font-weight:600;font-variant-numeric:tabular-nums}
295
- .packyCodeRows{display:flex;flex-direction:column;gap:10px}
296
- .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)}
297
- .packyCodeRowMain{display:flex;flex-direction:column;gap:2px;min-width:180px}
298
- .packyCodeRowName{display:flex;align-items:center;gap:8px;font-weight:500}
299
- .packyCodeRowMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;font-variant-numeric:tabular-nums}
300
- .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)}
301
- .packyCodeChipActive{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary,inherit);border:1px solid var(--dsw-alias-brand-primary,currentColor)}
302
- .packyCodeActions{display:flex;flex-wrap:wrap;gap:8px}
303
- .packyCodeForm{display:flex;flex-wrap:wrap;gap:8px}
304
- .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}
305
- .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}
306
- .packyCodeButton:disabled{opacity:.55;cursor:default}
307
- /* Emphasis without a themed fill: the brand colour may be light in either
308
- theme, and a filled button then hides its own label. */
309
- .packyCodeButtonPrimary{border-color:var(--dsw-alias-brand-primary,currentColor);font-weight:600}
310
- .packyCodeNote{color:var(--dsw-alias-label-tertiary);font-size:12px}
311
- .packyCodeError{color:var(--dsw-alias-state-error-primary,#d9534f);font-size:12px}
312
- .packyCodeAdvanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary)}
313
- .packyCodeAdvancedBody{display:flex;flex-direction:column;gap:10px;padding-top:10px}
314
- .packyCodeField{display:flex;flex-direction:column;gap:4px}
315
- .packyCodeWarning{color:var(--dsw-alias-state-warning-primary,#c27c0e);font-weight:500}
316
- .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}
317
- .packyCodeMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;word-break:break-all}
318
- .packyCodeVersionRows{display:flex;flex-direction:column;gap:6px}
319
- .packyCodeVersionRow{display:flex;justify-content:space-between;gap:12px}
320
- .packyCodeVersionValue{font-variant-numeric:tabular-nums;font-weight:600}
321
- .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}
322
- @media(max-width:640px){.packyCodeRow{align-items:flex-start;flex-direction:column}.packyCodeActions{width:100%}}
299
+ .pkc{display:flex;flex-direction:column;gap:16px;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
300
+ .pkc-intro{margin:0;color:var(--dsw-alias-label-tertiary)}
301
+ .pkc-card{display:flex;flex-direction:column;gap:14px;padding:16px 18px;border:.5px solid var(--dsw-alias-settings-card-stroke,var(--dsw-alias-border-l4,rgba(127,127,127,.3)));border-radius:var(--dsw-radius-xl,20px);background:var(--dsw-alias-settings-card-fill,var(--dsw-alias-bg-layer-2))}
302
+ .pkc-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:28px}
303
+ .pkc-title{display:flex;align-items:center;gap:8px;margin:0;font-size:15px;font-weight:600;line-height:22px}
304
+ .pkc-note{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
305
+ .pkc-warn{margin:0;color:var(--dsw-alias-state-warn-label,#dd8629);font-size:12px;line-height:18px}
306
+ .pkc-error{margin:0;color:var(--dsw-alias-state-error-primary,#ec1313);font-size:12px;line-height:18px}
307
+ .pkc-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}
308
+ .pkc-mono{font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);font-size:12px;word-break:break-all}
309
+ .pkc-detail{margin:0;padding:8px 10px;border-radius:var(--dsw-radius-sm,8px);background:var(--dsw-alias-markdown-code-block);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}
310
+
311
+ .pkc-btn{box-sizing:border-box;display:inline-flex;flex:none;align-items:center;justify-content:center;gap:4px;height:28px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer}
312
+ .pkc-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
313
+ .pkc-btn:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}
314
+ .pkc-btn:disabled{opacity:.4;cursor:not-allowed}
315
+ .pkc-btn[data-size=md]{height:32px;padding:0 14px;border-radius:var(--dsw-radius-md,12px);font-size:13px}
316
+ .pkc-btn[data-variant=primary]{border-color:transparent;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}
317
+ .pkc-btn[data-variant=primary]:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
318
+ .pkc-btn[data-variant=ghost]{border-color:transparent;color:var(--dsw-alias-label-secondary)}
319
+ .pkc-btn[data-variant=danger]{border-color:transparent;color:var(--dsw-alias-state-error-primary)}
320
+ .pkc-btn[data-variant=danger]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
321
+
322
+ .pkc-input{box-sizing:border-box;flex:1 1 200px;min-width:0;height:32px;padding:0 10px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;outline:none}
323
+ .pkc-input:focus{border-color:var(--dsw-alias-state-business-primary)}
324
+ .pkc-input::placeholder{color:var(--dsw-alias-label-caption)}
325
+ .pkc-input:disabled{opacity:.5}
326
+ .pkc-form{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
327
+
328
+ .pkc-chip{flex:none;padding:0 8px;border-radius:999px;font-size:11px;font-weight:500;line-height:18px;white-space:nowrap}
329
+ .pkc-chip[data-tone=success]{background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary)}
330
+ .pkc-chip[data-tone=danger]{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}
331
+ .pkc-chip[data-tone=info]{background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-state-business-primary)}
332
+ .pkc-chip[data-tone=neutral]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
333
+ .pkc-tag{flex:none;padding:0 6px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs,4px);color:var(--dsw-alias-label-secondary);font-family:var(--ds-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);font-size:11px;line-height:18px;cursor:default}
334
+ .pkc-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-state-idle-primary)}
335
+ .pkc-dot[data-tone=success]{background:var(--dsw-alias-state-success-primary)}
336
+ .pkc-dot[data-tone=warn]{background:var(--dsw-alias-state-warn-primary)}
337
+ .pkc-dot[data-tone=danger]{background:var(--dsw-alias-state-error-primary)}
338
+
339
+ .pkc-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
340
+ .pkc-stat{display:flex;flex-direction:column;gap:2px;padding:10px 12px;border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-interactive-bg-hover)}
341
+ .pkc-stat-label{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
342
+ .pkc-stat-value{font-size:20px;font-weight:600;line-height:28px;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
343
+
344
+ .pkc-list{display:flex;flex-direction:column}
345
+ .pkc-row{display:flex;flex-direction:column;gap:8px;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
346
+ .pkc-row:first-child{border-top:0;padding-top:2px}
347
+ .pkc-row-top{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 12px}
348
+ .pkc-row-name{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0;font-weight:500}
349
+ .pkc-row-name>.pkc-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
350
+ .pkc-actions{display:flex;flex-wrap:wrap;gap:4px}
351
+ .pkc-meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums}
352
+ .pkc-figures{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 16px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
353
+ .pkc-figures b{margin-left:4px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;font-variant-numeric:tabular-nums}
354
+ .pkc-bar{height:4px;overflow:hidden;border-radius:999px;background:var(--dsw-alias-interactive-bg-active)}
355
+ .pkc-bar>span{display:block;height:100%;border-radius:inherit;background:var(--dsw-alias-state-success-primary)}
356
+ .pkc-bar[data-level=warn]>span{background:var(--dsw-alias-state-warn-primary)}
357
+ .pkc-bar[data-level=low]>span{background:var(--dsw-alias-state-error-primary)}
358
+ .pkc-groups{display:flex;flex-wrap:wrap;align-items:center;gap:6px 6px}
359
+ .pkc-groups>.pkc-label{margin-right:2px}
360
+ .pkc-reach{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;font-size:12px;line-height:18px}
361
+ .pkc-reach>span{display:inline-flex;align-items:center;gap:4px;cursor:default}
362
+ .pkc-reach>span[data-state=ok]{color:var(--dsw-alias-state-success-primary)}
363
+ .pkc-reach>span[data-state=partial]{color:var(--dsw-alias-state-warn-label,#dd8629)}
364
+ .pkc-reach>span[data-state=no]{color:var(--dsw-alias-label-caption);text-decoration:line-through}
365
+ .pkc-add{display:flex;flex-direction:column;gap:8px;padding-top:14px;border-top:.5px solid var(--dsw-alias-border-l2)}
366
+
367
+ .pkc-setting{display:grid;grid-template-columns:112px minmax(0,1fr);gap:6px 16px;align-items:start}
368
+ .pkc-setting>.pkc-label{padding-top:5px}
369
+ .pkc-setting>div{display:flex;flex-direction:column;gap:6px;min-width:0}
370
+ .pkc-seg{display:inline-flex;flex-wrap:wrap;align-self:flex-start;gap:2px;max-width:100%;padding:2px;border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-interactive-bg-hover)}
371
+ .pkc-seg>button{height:28px;padding:0 12px;border:0;border-radius:calc(var(--dsw-radius-md,12px) - 2px);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}
372
+ .pkc-seg>button:hover:not(:disabled):not([aria-checked=true]){color:var(--dsw-alias-label-primary)}
373
+ .pkc-seg>button[aria-checked=true]{background:var(--dsw-alias-button-elevated-fill);color:var(--dsw-alias-label-primary);font-weight:500;box-shadow:var(--dsw-shadow-lv1)}
374
+ .pkc-seg>button:disabled{cursor:not-allowed;opacity:.5}
375
+ .pkc-status{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;font-size:13px}
376
+ .pkc-status>b{font-weight:600;font-variant-numeric:tabular-nums}
377
+ .pkc-callout{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-state-warn-tertiary)}
378
+ .pkc-callout[data-tone=info]{background:var(--dsw-alias-state-business-tertiary)}
379
+ .pkc-callout>.pkc-title{font-size:13px}
380
+ .pkc-split{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px}
381
+
382
+ .pkc-advanced>summary{width:fit-content;padding:2px 4px;margin-left:-4px;border-radius:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;cursor:pointer}
383
+ .pkc-advanced>summary:hover{background:var(--dsw-alias-interactive-bg-hover)}
384
+ .pkc-advanced[open]>summary{margin-bottom:10px}
385
+ .pkc-field{display:flex;flex-direction:column;gap:4px;margin-bottom:10px}
386
+
387
+ .pkc-composer{display:inline-flex;flex:0 0 auto;align-items:center;box-sizing:border-box;height:28px;color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:20px;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}
388
+
389
+ @media (max-width:640px){
390
+ .pkc-stats{grid-template-columns:1fr}
391
+ .pkc-setting{grid-template-columns:1fr}
392
+ .pkc-setting>.pkc-label{padding-top:0}
393
+ }
323
394
  `;
324
- const unwrap = (response) => {
325
- if (!response?.ok) throw new Error(response?.error?.message ?? "PackyCode RPC failed");
326
- return response.value;
395
+ //#endregion
396
+ //#region src/totals.js
397
+ /**
398
+ * Totals across the keys a balance read returned.
399
+ *
400
+ * The per-key quota (`/api/usage/token/`) is what a key can spend, so the
401
+ * combined figure is the sum of those rows. PackyCode also reports account
402
+ * level billing (`/v1/dashboard/billing/*`), which is a different scope — it
403
+ * counts every token on the account — and is deliberately not what this sum
404
+ * shows.
405
+ *
406
+ * @module dsh-packycode-api/totals
407
+ */
408
+ const amount = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
409
+ /**
410
+ * Add up the balance rows the host returned.
411
+ * @param rows - the `keys` array of a balance projection.
412
+ * @returns summed amounts plus the conditions that make a sum incomplete:
413
+ * keys whose read failed, and keys with no quota limit at all.
414
+ */
415
+ function sumBalances(rows) {
416
+ const total = {
417
+ keys: 0,
418
+ counted: 0,
419
+ unread: 0,
420
+ unlimited: 0,
421
+ remainingUsd: 0,
422
+ usedUsd: 0,
423
+ limitUsd: 0
424
+ };
425
+ for (const row of Array.isArray(rows) ? rows : []) {
426
+ total.keys += 1;
427
+ const usage = row?.usage;
428
+ if (usage === void 0) {
429
+ total.unread += 1;
430
+ continue;
431
+ }
432
+ total.counted += 1;
433
+ if (usage.unlimitedQuota === true) {
434
+ total.unlimited += 1;
435
+ continue;
436
+ }
437
+ const remaining = amount(usage.availableUsd);
438
+ const used = amount(usage.usedUsd);
439
+ const limit = amount(usage.limitUsd);
440
+ if (remaining !== void 0) total.remainingUsd += remaining;
441
+ if (used !== void 0) total.usedUsd += used;
442
+ if (limit !== void 0) total.limitUsd += limit;
443
+ }
444
+ return total;
445
+ }
446
+ //#endregion
447
+ //#region src/client.jsx
448
+ /**
449
+ * Browser half: the PackyCode settings page and the composer balance chip.
450
+ *
451
+ * Every number here comes from the host over the plugin's own loopback RPC
452
+ * channel; no key material crosses it, and each read is a bounded projection
453
+ * the host built.
454
+ *
455
+ * @module dsh-packycode-api/client
456
+ */
457
+ const NS = "settings.packyCodeApi";
458
+ const PROVIDER = "packycode";
459
+ const CHANNEL = "/packycode-api";
460
+ const REFRESH_MS = 6e4;
461
+ const INSTALL_POLL_MS = 2e3;
462
+ /** How long after a search change the panel re-reads what DSH applied (the patch hot-reloads). */
463
+ const SEARCH_SETTLE_MS = 1500;
464
+ const OWN_SEARCH_IDS = {
465
+ auto: "packycode-api-auto",
466
+ packycode: "packycode-api"
327
467
  };
468
+ const FOREIGN_NAMES = {
469
+ codex: "Codex",
470
+ kimi: "Kimi"
471
+ };
472
+ /** The catalog's models by family, for the "reaches" line under each key. */
473
+ const MODEL_FAMILIES = [
474
+ ["DeepSeek", ["deepseek-flash"]],
475
+ ["GPT", ["gpt-6.1-sol", "gpt-6-astra"]],
476
+ ["Claude", [
477
+ "claude-opus-5-5",
478
+ "claude-sonnet-5-5",
479
+ "claude-haiku-5-5"
480
+ ]]
481
+ ];
482
+ /**
483
+ * The one way this bundle talks to the host. The transport drops an
484
+ * `undefined` payload and the channel's envelope requires the field, so every
485
+ * call carries an object.
486
+ */
487
+ function channelCaller(rpc) {
488
+ return (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then((response) => {
489
+ if (!response?.ok) throw new Error(response?.error?.message ?? "PackyCode RPC failed");
490
+ return response.value;
491
+ });
492
+ }
493
+ const useCaller = (rpc) => react.useMemo(() => channelCaller(rpc), [rpc]);
494
+ /** Substitute `{name}` placeholders in a copy string. */
495
+ const fill = (text, values) => text.replace(/\{(\w+)\}/gu, (match, name) => values[name] === void 0 ? match : String(values[name]));
328
496
  function formatUsd(value) {
329
497
  if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
330
498
  try {
@@ -336,19 +504,6 @@ window.__ModuleLoader__.load({
336
504
  return `$${value.toFixed(2)}`;
337
505
  }
338
506
  }
339
- function resetLabel(period, t) {
340
- switch (period) {
341
- case "monthly": return t("resetMonthly");
342
- case "weekly": return t("resetWeekly");
343
- case "daily": return t("resetDaily");
344
- default: return;
345
- }
346
- }
347
- /** Active key's usage row, preferring the key the ring currently points at. */
348
- function activeUsage(balance) {
349
- if (balance === void 0) return void 0;
350
- return balance.keys?.find((entry) => entry.id === balance.activeId) ?? balance.keys?.[0];
351
- }
352
507
  /** The clock time of an epoch-millisecond reading, for a "last read at" note. */
353
508
  function readTime(ms) {
354
509
  if (typeof ms !== "number" || !Number.isFinite(ms)) return "—";
@@ -365,148 +520,804 @@ window.__ModuleLoader__.load({
365
520
  const text = typeof error?.message === "string" && error.message.length > 0 ? error.message : String(error);
366
521
  return text.length > 200 ? text.slice(0, 200) : text;
367
522
  }
368
- const INSTALL_POLL_MS = 2e3;
369
- /** The sentence explaining a failed install, by the code the host classified. */
370
- function installFailure(cli, t) {
371
- const command = cli.command ?? "";
372
- switch (cli.install?.code) {
373
- case "npm-missing": return t("cliNpmMissing");
374
- case "permission": return t(cli.platform === "win32" ? "cliPermissionWindows" : "cliPermission").replace("{command}", command);
375
- case "network": return t("cliNetwork");
376
- case "timeout": return t("cliTimeout");
377
- case "not-found-after-install": return t("cliNotFoundAfterInstall");
378
- default: return;
379
- }
523
+ const RESET_KEYS = {
524
+ monthly: "resetMonthly",
525
+ weekly: "resetWeekly",
526
+ daily: "resetDaily"
527
+ };
528
+ const resetLabel = (period, t) => RESET_KEYS[period] === void 0 ? void 0 : t(RESET_KEYS[period]);
529
+ /** Active key's usage row, preferring the key the ring currently points at. */
530
+ function activeUsage(balance) {
531
+ if (balance === void 0) return void 0;
532
+ return balance.keys?.find((entry) => entry.id === balance.activeId) ?? balance.keys?.[0];
380
533
  }
381
- /**
382
- * Whether this device has the Claude Code CLI, which version, and — when it
383
- * has none — a button that installs it with npm.
384
- */
385
- function ClaudeCliCard({ call, t }) {
386
- const [cli, setCli] = react.useState();
387
- const [checking, setChecking] = react.useState(false);
388
- const [error, setError] = react.useState();
389
- const check = react.useCallback((force = false) => {
390
- setChecking(true);
391
- setError(void 0);
392
- return call("claude-cli/status", { force }).then(setCli).catch((failure) => setError(clearMessage(failure))).finally(() => setChecking(false));
393
- }, [call]);
394
- react.useEffect(() => {
395
- check(false);
396
- }, [check]);
397
- const running = cli?.install?.state === "running";
398
- react.useEffect(() => {
399
- if (!running) return void 0;
400
- let live = true;
401
- const timer = window.setInterval(() => {
402
- call("claude-cli/status").then((value) => {
403
- if (live) setCli(value);
404
- }).catch(() => void 0);
405
- }, INSTALL_POLL_MS);
406
- return () => {
407
- live = false;
408
- window.clearInterval(timer);
409
- };
410
- }, [call, running]);
411
- const install = () => {
412
- setError(void 0);
413
- call("claude-cli/install").then(setCli).catch((failure) => setError(clearMessage(failure)));
414
- };
415
- const usable = cli?.installed === true && cli.version !== void 0;
416
- const failed = cli?.install?.state === "failed";
534
+ /** How much of a key's limit is left, for its bar; undefined without a limit. */
535
+ function remainingShare(usage) {
536
+ if (usage?.unlimitedQuota === true || typeof usage?.limitUsd !== "number" || usage.limitUsd <= 0) return void 0;
537
+ if (typeof usage.availableUsd !== "number") return void 0;
538
+ return Math.max(0, Math.min(1, usage.availableUsd / usage.limitUsd));
539
+ }
540
+ /** Which catalog families a key reaches, from its per-model coverage. */
541
+ function reachOf(coverage) {
542
+ return MODEL_FAMILIES.flatMap(([name, models]) => {
543
+ const known = models.filter((id) => typeof coverage?.[id] === "boolean");
544
+ if (known.length === 0) return [];
545
+ const reached = known.filter((id) => coverage[id] === true);
546
+ return [{
547
+ name,
548
+ state: reached.length === 0 ? "no" : reached.length === known.length ? "ok" : "partial",
549
+ reached,
550
+ missing: known.filter((id) => coverage[id] === false)
551
+ }];
552
+ });
553
+ }
554
+ function Card({ title, aside, children }) {
417
555
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
418
- className: "packyCodeCard",
556
+ className: "pkc-card",
557
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
558
+ className: "pkc-head",
559
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
560
+ className: "pkc-title",
561
+ children: title
562
+ }), aside]
563
+ }), children]
564
+ });
565
+ }
566
+ function Button({ variant, size, children, ...rest }) {
567
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
568
+ type: "button",
569
+ className: "pkc-btn",
570
+ "data-variant": variant,
571
+ "data-size": size,
572
+ ...rest,
573
+ children
574
+ });
575
+ }
576
+ function Segmented({ label, options, value, disabled, onChange }) {
577
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
578
+ className: "pkc-seg",
579
+ role: "radiogroup",
580
+ "aria-label": label,
581
+ children: options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
582
+ type: "button",
583
+ role: "radio",
584
+ "aria-checked": value === option.value,
585
+ disabled,
586
+ onClick: () => value !== option.value && onChange(option.value),
587
+ children: option.label
588
+ }, option.value))
589
+ });
590
+ }
591
+ /** A key's groups and what they reach. */
592
+ function KeyGroups({ group, descriptions, loading, t }) {
593
+ if (group === void 0) return loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
594
+ className: "pkc-meta",
595
+ children: t("groupsChecking")
596
+ }) : null;
597
+ if (group.rejected === true) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
598
+ className: "pkc-error",
599
+ children: t("groupsRejected")
600
+ });
601
+ const reach = reachOf(group.coverage);
602
+ const names = Array.isArray(group.groups) ? group.groups : [];
603
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
605
+ className: "pkc-groups",
606
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
607
+ className: "pkc-label",
608
+ children: t("groupsTitle")
609
+ }), names.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
610
+ className: "pkc-meta",
611
+ children: group.error === void 0 ? "—" : `${t("groupsUnknown")}: ${group.error}`
612
+ }) : names.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
613
+ className: "pkc-tag",
614
+ title: descriptions?.[name] ?? name,
615
+ children: name
616
+ }, name))]
617
+ }),
618
+ reach.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
619
+ className: "pkc-reach",
620
+ children: reach.map((family) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
621
+ "data-state": family.state,
622
+ title: [...family.reached.map((id) => `✓ ${id}`), ...family.missing.map((id) => `✗ ${id}`)].join("\n"),
623
+ children: [
624
+ family.state === "ok" ? "✓" : family.state === "partial" ? "◐" : "✕",
625
+ " ",
626
+ family.name,
627
+ family.state === "partial" && ` (${t("groupsPartial")})`
628
+ ]
629
+ }, family.name))
630
+ }),
631
+ group.stale !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
632
+ className: "pkc-meta",
633
+ children: t("groupsStale")
634
+ })
635
+ ] });
636
+ }
637
+ function KeyRow({ entry, row, group, descriptions, groupsLoading, busy, renaming, onRename, onAction, t }) {
638
+ const usage = row?.usage;
639
+ const share = remainingShare(usage);
640
+ const figures = [
641
+ usage?.unlimitedQuota === true ? [t("keyRemaining"), t("keyUnlimited")] : usage?.availableUsd === void 0 ? void 0 : [t("keyRemaining"), formatUsd(usage.availableUsd)],
642
+ usage?.usedUsd === void 0 ? void 0 : [t("keyUsed"), formatUsd(usage.usedUsd)],
643
+ usage?.limitUsd === void 0 ? void 0 : [t("accountLimit"), formatUsd(usage.limitUsd)]
644
+ ].filter(Boolean);
645
+ const meta = [usage?.name === void 0 ? t("keyNameUnknown") : `${t("keyName")} ${usage.name}`, resetLabel(usage?.resetPeriod, t)].filter(Boolean).join(" · ");
646
+ const editing = renaming?.id === entry.id;
647
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
648
+ className: "pkc-row",
419
649
  children: [
420
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
421
- className: "packyCodeCardHeader",
422
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
423
- className: "packyCodeCardTitle",
424
- children: t("cliTitle")
425
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
426
- type: "button",
427
- className: "packyCodeButton",
428
- disabled: checking || running,
429
- onClick: () => check(true),
430
- children: checking ? t("refreshing") : t("cliRecheck")
650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
651
+ className: "pkc-row-top",
652
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
653
+ className: "pkc-row-name",
654
+ children: [
655
+ editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
656
+ className: "pkc-form",
657
+ onSubmit: (event) => {
658
+ event.preventDefault();
659
+ onRename.submit();
660
+ },
661
+ children: [
662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
663
+ className: "pkc-input",
664
+ type: "text",
665
+ maxLength: 60,
666
+ autoFocus: true,
667
+ placeholder: t("keyRenamePlaceholder"),
668
+ value: renaming.label,
669
+ onChange: (event) => onRename.change(event.target.value),
670
+ onKeyDown: (event) => event.key === "Escape" && onRename.cancel()
671
+ }),
672
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
673
+ type: "submit",
674
+ variant: "primary",
675
+ disabled: busy || renaming.label.trim() === "",
676
+ children: t("keyRenameSave")
677
+ }),
678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
679
+ variant: "ghost",
680
+ onClick: onRename.cancel,
681
+ children: t("keyRenameCancel")
682
+ })
683
+ ]
684
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
685
+ className: "pkc-name",
686
+ children: entry.label
687
+ }),
688
+ entry.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
689
+ className: "pkc-chip",
690
+ "data-tone": "success",
691
+ children: t("keyActive")
692
+ }),
693
+ entry.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
694
+ className: "pkc-chip",
695
+ "data-tone": "danger",
696
+ children: entry.disabledReason ? `${t("keyDisabled")} · ${entry.disabledReason}` : t("keyDisabled")
697
+ })
698
+ ]
699
+ }), !editing && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
700
+ className: "pkc-actions",
701
+ children: [
702
+ !entry.active && !entry.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
703
+ disabled: busy,
704
+ onClick: () => onAction("keys/select", { id: entry.id }),
705
+ children: t("keyUse")
706
+ }),
707
+ entry.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
708
+ disabled: busy,
709
+ onClick: () => onAction("keys/enable", { id: entry.id }),
710
+ children: t("keyEnable")
711
+ }),
712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
713
+ variant: "ghost",
714
+ disabled: busy,
715
+ onClick: () => onRename.start(entry),
716
+ children: t("keyRename")
717
+ }),
718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
719
+ variant: "danger",
720
+ disabled: busy,
721
+ onClick: () => onAction("keys/remove", { id: entry.id }),
722
+ children: t("keyRemove")
723
+ })
724
+ ]
431
725
  })]
432
726
  }),
433
- cli === void 0 && error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
434
- className: "packyCodeNote",
435
- children: t("cliChecking")
727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
728
+ className: "pkc-meta",
729
+ children: meta
436
730
  }),
437
- usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
731
+ figures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
732
+ className: "pkc-figures",
733
+ children: figures.map(([label, value]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: value })] }, label))
734
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
735
+ className: "pkc-meta",
736
+ children: row?.error === void 0 ? t("keyNoBalance") : `${t("loadFailed")}: ${row.error}`
737
+ }),
738
+ share !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
739
+ className: "pkc-bar",
740
+ "data-level": share < .1 ? "low" : share < .3 ? "warn" : "ok",
741
+ role: "presentation",
742
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { width: `${Math.round(share * 100)}%` } })
743
+ }),
744
+ row?.stale !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
745
+ className: "pkc-warn",
746
+ children: fill(t("staleNote"), {
747
+ time: readTime(row.stale.fetchedAt),
748
+ error: row.stale.error ?? ""
749
+ })
750
+ }),
751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyGroups, {
752
+ group,
753
+ descriptions,
754
+ loading: groupsLoading,
755
+ t
756
+ })
757
+ ]
758
+ });
759
+ }
760
+ function AccountsCard({ call, t }) {
761
+ const [status, setStatus] = react.useState();
762
+ const [balance, setBalance] = react.useState();
763
+ const [groups, setGroups] = react.useState();
764
+ const [groupsLoading, setGroupsLoading] = react.useState(true);
765
+ const [busy, setBusy] = react.useState(false);
766
+ const [error, setError] = react.useState();
767
+ const [draft, setDraft] = react.useState({
768
+ key: "",
769
+ label: ""
770
+ });
771
+ const [renaming, setRenaming] = react.useState();
772
+ const generation = react.useRef(0);
773
+ const readGroups = react.useCallback((force = false) => {
774
+ setGroupsLoading(true);
775
+ return call("groups", { force }).then(setGroups).catch(() => void 0).finally(() => setGroupsLoading(false));
776
+ }, [call]);
777
+ const load = react.useCallback((force = false) => {
778
+ const current = ++generation.current;
779
+ setBusy(true);
780
+ setError(void 0);
781
+ readGroups(force);
782
+ return Promise.all([call("status"), call("balance", { force })]).then(([nextStatus, nextBalance]) => {
783
+ if (generation.current !== current) return;
784
+ setStatus(nextStatus);
785
+ setBalance(nextBalance);
786
+ }).catch((failure) => {
787
+ if (generation.current === current) setError(clearMessage(failure));
788
+ }).finally(() => {
789
+ if (generation.current === current) setBusy(false);
790
+ });
791
+ }, [call, readGroups]);
792
+ react.useEffect(() => {
793
+ load(false);
794
+ return () => {
795
+ generation.current += 1;
796
+ };
797
+ }, [load]);
798
+ const mutate = (endpoint, payload) => {
799
+ setBusy(true);
800
+ setError(void 0);
801
+ return call(endpoint, payload).then((nextStatus) => {
802
+ setStatus(nextStatus);
803
+ readGroups(false);
804
+ return call("balance", { force: true });
805
+ }).then(setBalance).catch((failure) => setError(clearMessage(failure))).finally(() => setBusy(false));
806
+ };
807
+ const addKey = (event) => {
808
+ event.preventDefault();
809
+ if (draft.key.trim() === "") return;
810
+ mutate("keys/add", {
811
+ key: draft.key,
812
+ label: draft.label
813
+ }).then(() => setDraft({
814
+ key: "",
815
+ label: ""
816
+ }));
817
+ };
818
+ const rename = {
819
+ start: (entry) => setRenaming({
820
+ id: entry.id,
821
+ label: entry.label
822
+ }),
823
+ change: (label) => setRenaming((current) => ({
824
+ ...current,
825
+ label
826
+ })),
827
+ cancel: () => setRenaming(void 0),
828
+ submit: () => {
829
+ if (renaming === void 0 || renaming.label.trim() === "") return;
830
+ mutate("keys/rename", {
831
+ id: renaming.id,
832
+ label: renaming.label
833
+ }).then(() => setRenaming(void 0));
834
+ }
835
+ };
836
+ const keys = status?.keys ?? [];
837
+ const rows = balance?.keys ?? [];
838
+ const totals = sumBalances(rows);
839
+ const staleCount = rows.filter((row) => row.stale !== void 0).length;
840
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
841
+ title: t("accountsTitle"),
842
+ aside: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
843
+ className: "pkc-actions",
844
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
845
+ disabled: busy,
846
+ onClick: () => load(true),
847
+ children: busy ? t("refreshing") : t("refresh")
848
+ }), keys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
849
+ variant: "danger",
850
+ disabled: busy,
851
+ onClick: () => mutate("keys/remove-all", {}),
852
+ children: t("removeAll")
853
+ })]
854
+ }),
855
+ children: [
856
+ keys.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
857
+ className: "pkc-note",
858
+ children: t("empty")
859
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
861
+ className: "pkc-stats",
862
+ children: [
863
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
864
+ className: "pkc-stat",
865
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
866
+ className: "pkc-stat-label",
867
+ children: t("totalRemaining")
868
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
869
+ className: "pkc-stat-value",
870
+ children: [totals.counted === 0 ? "—" : formatUsd(totals.remainingUsd) ?? "—", totals.unlimited > 0 && ` + ${t("keyUnlimited")}`]
871
+ })]
872
+ }),
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
874
+ className: "pkc-stat",
875
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
876
+ className: "pkc-stat-label",
877
+ children: t("totalUsed")
878
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
879
+ className: "pkc-stat-value",
880
+ children: totals.counted === 0 ? "—" : formatUsd(totals.usedUsd) ?? "—"
881
+ })]
882
+ }),
883
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
884
+ className: "pkc-stat",
885
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
886
+ className: "pkc-stat-label",
887
+ children: t("totalLimit")
888
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
889
+ className: "pkc-stat-value",
890
+ children: totals.counted === 0 ? "—" : totals.unlimited > 0 ? t("keyUnlimited") : formatUsd(totals.limitUsd) ?? "—"
891
+ })]
892
+ })
893
+ ]
894
+ }),
895
+ (totals.unread > 0 || staleCount > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
896
+ className: "pkc-note",
897
+ children: [totals.unread > 0 ? fill(t("totalPartial"), { count: totals.unread }) : void 0, staleCount > 0 ? fill(t("totalStale"), { count: staleCount }) : void 0].filter(Boolean).join(" ")
898
+ }),
438
899
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
439
- className: "packyCodeFigures",
440
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
441
- className: "packyCodeFigure",
900
+ className: "pkc-list",
901
+ children: keys.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyRow, {
902
+ entry,
903
+ row: rows.find((row) => row.id === entry.id),
904
+ group: groups?.keys?.find((group) => group.id === entry.id),
905
+ descriptions: groups?.descriptions,
906
+ groupsLoading,
907
+ busy,
908
+ renaming,
909
+ onRename: rename,
910
+ onAction: mutate,
911
+ t
912
+ }, entry.id))
913
+ }),
914
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
915
+ className: "pkc-note",
916
+ children: t("keysHint")
917
+ })
918
+ ] }),
919
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
920
+ className: "pkc-add",
921
+ onSubmit: addKey,
922
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
923
+ className: "pkc-label",
924
+ children: t("addTitle")
925
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
926
+ className: "pkc-form",
927
+ children: [
928
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
929
+ className: "pkc-input",
930
+ type: "password",
931
+ autoComplete: "off",
932
+ placeholder: t("addKeyPlaceholder"),
933
+ value: draft.key,
934
+ onChange: (event) => setDraft((current) => ({
935
+ ...current,
936
+ key: event.target.value
937
+ }))
938
+ }),
939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
940
+ className: "pkc-input",
941
+ type: "text",
942
+ placeholder: t("addLabelPlaceholder"),
943
+ value: draft.label,
944
+ onChange: (event) => setDraft((current) => ({
945
+ ...current,
946
+ label: event.target.value
947
+ }))
948
+ }),
949
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
950
+ type: "submit",
951
+ variant: "primary",
952
+ size: "md",
953
+ disabled: busy || draft.key.trim() === "",
954
+ children: t("add")
955
+ })
956
+ ]
957
+ })]
958
+ }),
959
+ status !== void 0 && status.writable !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
960
+ className: "pkc-note",
961
+ children: t("notWritable")
962
+ }),
963
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
964
+ className: "pkc-error",
965
+ role: "alert",
966
+ children: `${t("error")}: ${error}`
967
+ }),
968
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
969
+ className: "pkc-advanced",
970
+ children: [
971
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("advanced") }),
972
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
973
+ className: "pkc-field",
442
974
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
443
- className: "packyCodeFigureLabel",
444
- children: t("cliInstalled")
445
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
446
- className: "packyCodeFigureValue",
447
- children: `${t("cliVersion")} ${cli.version}`
975
+ className: "pkc-label",
976
+ children: t("apiHost")
977
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
978
+ className: "pkc-input",
979
+ type: "text",
980
+ disabled: status?.writable !== true,
981
+ defaultValue: status?.hosts?.apiHost ?? "",
982
+ onBlur: (event) => mutate("hosts/update", {
983
+ apiHost: event.target.value,
984
+ statusHost: status?.hosts?.statusHost
985
+ })
986
+ })]
987
+ }),
988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
989
+ className: "pkc-field",
990
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
991
+ className: "pkc-label",
992
+ children: t("statusHost")
993
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
994
+ className: "pkc-input",
995
+ type: "text",
996
+ disabled: status?.writable !== true,
997
+ defaultValue: status?.hosts?.statusHost ?? "",
998
+ onBlur: (event) => mutate("hosts/update", {
999
+ statusHost: event.target.value,
1000
+ apiHost: status?.hosts?.apiHost
1001
+ })
448
1002
  })]
449
1003
  })
1004
+ ]
1005
+ })
1006
+ ]
1007
+ });
1008
+ }
1009
+ /** Web search routing and backend, written through the host. */
1010
+ function SearchCard({ call, t }) {
1011
+ const [state, setState] = react.useState();
1012
+ const [busy, setBusy] = react.useState(false);
1013
+ const [error, setError] = react.useState();
1014
+ const generation = react.useRef(0);
1015
+ const read = react.useCallback(() => {
1016
+ const current = ++generation.current;
1017
+ return call("search/status").then((value) => {
1018
+ if (generation.current === current) setState(value);
1019
+ }).catch((failure) => {
1020
+ if (generation.current === current) setError(clearMessage(failure));
1021
+ });
1022
+ }, [call]);
1023
+ react.useEffect(() => {
1024
+ read();
1025
+ return () => {
1026
+ generation.current += 1;
1027
+ };
1028
+ }, [read]);
1029
+ const update = (patch) => {
1030
+ if (busy || state?.writable !== true) return;
1031
+ setBusy(true);
1032
+ setError(void 0);
1033
+ call("search/update", patch).then((value) => {
1034
+ setState(value);
1035
+ window.setTimeout(() => {
1036
+ read();
1037
+ }, SEARCH_SETTLE_MS);
1038
+ }).catch((failure) => setError(clearMessage(failure))).finally(() => setBusy(false));
1039
+ };
1040
+ const mode = state?.searchProvider;
1041
+ const backend = state?.searchBackend;
1042
+ const models = state?.models ?? {};
1043
+ const base = state?.effective?.base;
1044
+ const runtime = state?.effective?.runtime;
1045
+ const foreign = Object.entries(state?.foreign ?? {}).filter(([, present]) => present).map(([name]) => FOREIGN_NAMES[name] ?? name);
1046
+ const expected = OWN_SEARCH_IDS[mode];
1047
+ const overridden = expected !== void 0 && state?.applied?.state === "applied" && base !== void 0 && base !== expected;
1048
+ const disabled = busy || state?.writable !== true;
1049
+ const modeHint = {
1050
+ default: "searchModeDefaultHint",
1051
+ auto: "searchModeAutoHint",
1052
+ packycode: "searchModePackycodeHint"
1053
+ }[mode];
1054
+ const backendHint = backend === "gpt" ? fill(t("searchBackendGptHint"), { model: models.gpt ?? "gpt-6.1-sol" }) : fill(t("searchBackendDeepseekHint"), { model: models.deepseek ?? "deepseek-flash" });
1055
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
1056
+ title: t("searchTitle"),
1057
+ aside: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1058
+ className: "pkc-chip",
1059
+ "data-tone": "neutral",
1060
+ children: t("searchScope")
1061
+ }),
1062
+ children: [
1063
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1064
+ className: "pkc-note",
1065
+ children: t("searchIntro")
1066
+ }),
1067
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1068
+ className: "pkc-note",
1069
+ children: t("searchCliHint")
1070
+ }),
1071
+ state === void 0 && error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1072
+ className: "pkc-note",
1073
+ role: "status",
1074
+ children: t("searchChecking")
1075
+ }),
1076
+ state !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1078
+ className: "pkc-setting",
1079
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1080
+ className: "pkc-label",
1081
+ children: t("searchModeTitle")
1082
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Segmented, {
1083
+ label: t("searchModeTitle"),
1084
+ value: mode,
1085
+ disabled,
1086
+ onChange: (value) => update({ searchProvider: value }),
1087
+ options: [
1088
+ {
1089
+ value: "default",
1090
+ label: t("searchModeDefault")
1091
+ },
1092
+ {
1093
+ value: "auto",
1094
+ label: t("searchModeAuto")
1095
+ },
1096
+ {
1097
+ value: "packycode",
1098
+ label: t("searchModePackycode")
1099
+ }
1100
+ ]
1101
+ }), modeHint !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1102
+ className: "pkc-note",
1103
+ children: t(modeHint)
1104
+ })] })]
450
1105
  }),
451
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
452
- className: "packyCodeNote",
453
- children: [`${t("cliPath")}:`, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
454
- className: "packyCodeMono",
455
- children: cli.path
1106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1107
+ className: "pkc-setting",
1108
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1109
+ className: "pkc-label",
1110
+ children: t("searchBackendTitle")
1111
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Segmented, {
1112
+ label: t("searchBackendTitle"),
1113
+ value: backend,
1114
+ disabled,
1115
+ onChange: (value) => update({ searchBackend: value }),
1116
+ options: [{
1117
+ value: "deepseek",
1118
+ label: t("searchBackendDeepseek")
1119
+ }, {
1120
+ value: "gpt",
1121
+ label: t("searchBackendGpt")
1122
+ }]
1123
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1124
+ className: "pkc-note",
1125
+ children: backendHint
1126
+ })] })]
1127
+ }),
1128
+ state.available === true && (base !== void 0 || runtime !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1129
+ className: "pkc-status",
1130
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1131
+ className: "pkc-dot",
1132
+ "data-tone": overridden ? "warn" : expected !== void 0 && base === expected ? "success" : void 0
1133
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1134
+ className: "pkc-meta",
1135
+ children: fill(t("searchEffective"), {
1136
+ base: base ?? t("searchUnknown"),
1137
+ runtime: runtime ?? base ?? t("searchUnknown")
1138
+ })
456
1139
  })]
457
1140
  }),
458
- cli.install?.state === "succeeded" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
459
- className: "packyCodeNote",
460
- children: t("cliInstallSucceeded")
461
- })
462
- ] }),
463
- cli !== void 0 && !usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
464
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
465
- className: "packyCodeWarning",
466
- children: cli.installed ? t("cliBroken") : t("cliMissing")
1141
+ overridden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1142
+ className: "pkc-warn",
1143
+ children: fill(t("searchOverridden"), { base })
467
1144
  }),
468
- cli.installed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
469
- className: "packyCodeNote",
470
- children: [`${t("cliPath")}:`, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
471
- className: "packyCodeMono",
472
- children: cli.path
473
- })]
474
- }), cli.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
475
- className: "packyCodeDetail",
476
- children: cli.error
477
- })] }),
478
- cli.configuredPath !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
479
- className: "packyCodeNote",
480
- children: t("cliConfiguredMissing").replace("{path}", cli.configuredPath)
1145
+ foreign.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1146
+ className: "pkc-note",
1147
+ children: fill(t("searchForeign"), { names: foreign.join(" / ") })
481
1148
  }),
482
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
483
- className: "packyCodeIntro",
484
- children: t("cliMissingHint")
1149
+ state.applied?.state === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1150
+ className: "pkc-error",
1151
+ role: "alert",
1152
+ children: fill(t("searchApplyFailed"), { error: state.applied.error ?? "" })
485
1153
  }),
486
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
487
- className: "packyCodeActions",
488
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
489
- type: "button",
490
- className: "packyCodeButton packyCodeButtonPrimary",
491
- disabled: running || checking,
492
- onClick: install,
493
- children: running ? t("cliInstalling") : cli.installed ? t("cliReinstall") : t("cliInstall")
494
- })
1154
+ state.available !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1155
+ className: "pkc-note",
1156
+ children: t("searchUnavailable")
1157
+ }),
1158
+ state.writable !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1159
+ className: "pkc-note",
1160
+ children: t("searchNotWritable")
1161
+ }),
1162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1163
+ className: "pkc-note",
1164
+ children: t("searchPatchNote")
1165
+ })
1166
+ ] }),
1167
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1168
+ className: "pkc-error",
1169
+ role: "alert",
1170
+ children: `${t("searchLoadFailed")}: ${error}`
1171
+ })
1172
+ ]
1173
+ });
1174
+ }
1175
+ const INSTALL_FAILURES = {
1176
+ "npm-missing": () => "cliNpmMissing",
1177
+ permission: (cli) => cli.platform === "win32" ? "cliPermissionWindows" : "cliPermission",
1178
+ network: () => "cliNetwork",
1179
+ timeout: () => "cliTimeout",
1180
+ "not-found-after-install": () => "cliNotFoundAfterInstall"
1181
+ };
1182
+ /** The sentence explaining a failed install, by the code the host classified. */
1183
+ function installFailure(cli, t) {
1184
+ const key = INSTALL_FAILURES[cli.install?.code]?.(cli);
1185
+ return key === void 0 ? void 0 : fill(t(key), { command: cli.command ?? "" });
1186
+ }
1187
+ /**
1188
+ * Whether this device has the Claude Code CLI, which version, and — when it
1189
+ * has none — a button that installs it with npm.
1190
+ */
1191
+ function ClaudeCliCard({ call, t }) {
1192
+ const [cli, setCli] = react.useState();
1193
+ const [checking, setChecking] = react.useState(false);
1194
+ const [error, setError] = react.useState();
1195
+ const check = react.useCallback((force = false) => {
1196
+ setChecking(true);
1197
+ setError(void 0);
1198
+ return call("claude-cli/status", { force }).then(setCli).catch((failure) => setError(clearMessage(failure))).finally(() => setChecking(false));
1199
+ }, [call]);
1200
+ react.useEffect(() => {
1201
+ check(false);
1202
+ }, [check]);
1203
+ const running = cli?.install?.state === "running";
1204
+ react.useEffect(() => {
1205
+ if (!running) return void 0;
1206
+ let live = true;
1207
+ const timer = window.setInterval(() => {
1208
+ call("claude-cli/status").then((value) => {
1209
+ if (live) setCli(value);
1210
+ }).catch(() => void 0);
1211
+ }, INSTALL_POLL_MS);
1212
+ return () => {
1213
+ live = false;
1214
+ window.clearInterval(timer);
1215
+ };
1216
+ }, [call, running]);
1217
+ const install = () => {
1218
+ setError(void 0);
1219
+ call("claude-cli/install").then(setCli).catch((failure) => setError(clearMessage(failure)));
1220
+ };
1221
+ const usable = cli?.installed === true && cli.version !== void 0;
1222
+ const failed = cli?.install?.state === "failed";
1223
+ const failure = cli === void 0 ? void 0 : installFailure(cli, t);
1224
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
1225
+ title: t("cliTitle"),
1226
+ aside: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1227
+ disabled: checking || running,
1228
+ onClick: () => check(true),
1229
+ children: checking ? t("refreshing") : t("cliRecheck")
1230
+ }),
1231
+ children: [
1232
+ cli === void 0 && error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1233
+ className: "pkc-note",
1234
+ children: t("cliChecking")
1235
+ }),
1236
+ usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1238
+ className: "pkc-status",
1239
+ children: [
1240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1241
+ className: "pkc-dot",
1242
+ "data-tone": "success"
1243
+ }),
1244
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("cliInstalled") }),
1245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: `${t("cliVersion")} ${cli.version}` })
1246
+ ]
495
1247
  }),
496
1248
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
497
- className: "packyCodeNote",
498
- children: [t("cliInstallCommand").replace("{command}", cli.command ?? ""), cli.platform === "win32" && ` ${t("cliWindowsNote")}`]
1249
+ className: "pkc-meta",
1250
+ children: [
1251
+ t("cliPath"),
1252
+ " ",
1253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1254
+ className: "pkc-mono",
1255
+ children: cli.path
1256
+ })
1257
+ ]
1258
+ }),
1259
+ cli.install?.state === "succeeded" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1260
+ className: "pkc-note",
1261
+ children: t("cliInstallSucceeded")
499
1262
  })
500
1263
  ] }),
501
- failed && !usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
502
- className: "packyCodeError",
503
- children: installFailure(cli, t) === void 0 ? t("cliInstallFailed") : `${t("cliInstallFailed")}:${installFailure(cli, t)}`
1264
+ cli !== void 0 && !usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1265
+ className: "pkc-callout",
1266
+ children: [
1267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1268
+ className: "pkc-status",
1269
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1270
+ className: "pkc-dot",
1271
+ "data-tone": cli.installed ? "danger" : "warn"
1272
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: cli.installed ? t("cliBroken") : t("cliMissing") })]
1273
+ }),
1274
+ cli.installed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1275
+ className: "pkc-meta",
1276
+ children: [
1277
+ t("cliPath"),
1278
+ " ",
1279
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1280
+ className: "pkc-mono",
1281
+ children: cli.path
1282
+ })
1283
+ ]
1284
+ }), cli.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1285
+ className: "pkc-detail",
1286
+ children: cli.error
1287
+ })] }),
1288
+ cli.configuredPath !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1289
+ className: "pkc-note",
1290
+ children: fill(t("cliConfiguredMissing"), { path: cli.configuredPath })
1291
+ }),
1292
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1293
+ className: "pkc-note",
1294
+ children: t("cliMissingHint")
1295
+ }),
1296
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1297
+ className: "pkc-split",
1298
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1299
+ variant: "primary",
1300
+ size: "md",
1301
+ disabled: running || checking,
1302
+ onClick: install,
1303
+ children: running ? t("cliInstalling") : cli.installed ? t("cliReinstall") : t("cliInstall")
1304
+ })
1305
+ }),
1306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1307
+ className: "pkc-note",
1308
+ children: [fill(t("cliInstallCommand"), { command: cli.command ?? "" }), cli.platform === "win32" && ` ${t("cliWindowsNote")}`]
1309
+ })
1310
+ ]
1311
+ }),
1312
+ failed && !usable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1313
+ className: "pkc-error",
1314
+ children: failure === void 0 ? t("cliInstallFailed") : `${t("cliInstallFailed")} · ${failure}`
504
1315
  }), cli.install.detail !== void 0 && cli.install.detail !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
505
- className: "packyCodeDetail",
1316
+ className: "pkc-detail",
506
1317
  children: cli.install.detail
507
1318
  })] }),
508
- error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
509
- className: "packyCodeError",
1319
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1320
+ className: "pkc-error",
510
1321
  children: `${t("error")}: ${error}`
511
1322
  })
512
1323
  ]
@@ -549,109 +1360,92 @@ window.__ModuleLoader__.load({
549
1360
  };
550
1361
  const installKind = info?.install?.kind;
551
1362
  const showStatus = info !== void 0 && updated === void 0;
552
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
553
- className: "packyCodeCard",
1363
+ const canUpdate = showStatus && info.updateAvailable && installKind === "npm";
1364
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
1365
+ title: t("versionTitle"),
1366
+ aside: updated === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1367
+ className: "pkc-actions",
1368
+ children: [canUpdate && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1369
+ variant: "primary",
1370
+ disabled: updating,
1371
+ onClick: update,
1372
+ children: updating ? t("versionUpdating") : t("versionUpdate")
1373
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1374
+ disabled: loading || updating,
1375
+ onClick: () => load(true),
1376
+ children: loading ? t("versionChecking") : t("versionCheck")
1377
+ })]
1378
+ }),
554
1379
  children: [
555
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
556
- className: "packyCodeCardHeader",
557
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
558
- className: "packyCodeCardTitle",
559
- children: t("versionTitle")
560
- }), updated === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
561
- type: "button",
562
- className: "packyCodeButton",
563
- disabled: loading || updating,
564
- onClick: () => load(true),
565
- children: loading ? t("versionChecking") : t("versionCheck")
566
- })]
567
- }),
568
- loading && info === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
569
- className: "packyCodeNote",
1380
+ loading && info === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1381
+ className: "pkc-note",
570
1382
  role: "status",
571
1383
  children: t("versionChecking")
572
1384
  }),
573
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
574
- className: "packyCodeError",
1385
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1386
+ className: "pkc-error",
575
1387
  role: "alert",
576
1388
  children: t("versionFailed")
577
1389
  }),
578
- info !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
579
- className: "packyCodeVersionRows",
580
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
581
- className: "packyCodeVersionRow",
582
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("versionCurrent") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
583
- className: "packyCodeVersionValue",
584
- children: ["v", info.current]
585
- })]
586
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
587
- className: "packyCodeVersionRow",
588
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("versionLatest") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
589
- className: "packyCodeVersionValue",
590
- children: ["v", info.latest]
591
- })]
592
- })]
593
- }),
594
- showStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
595
- className: "packyCodeNote",
1390
+ showStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1391
+ className: "pkc-status",
596
1392
  role: "status",
597
- children: info.updateAvailable ? t("versionAvailable").replace("{version}", info.latest) : t("versionUpToDate")
598
- }),
599
- showStatus && info.updateAvailable && installKind === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
600
- className: "packyCodeActions",
601
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
602
- type: "button",
603
- className: "packyCodeButton packyCodeButtonPrimary",
604
- disabled: updating,
605
- onClick: update,
606
- children: updating ? t("versionUpdating") : t("versionUpdate")
607
- })
1393
+ children: [
1394
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1395
+ className: "pkc-dot",
1396
+ "data-tone": info.updateAvailable ? "warn" : "success"
1397
+ }),
1398
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: `v${info.current}` }),
1399
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1400
+ className: "pkc-meta",
1401
+ children: [info.updateAvailable ? fill(t("versionAvailable"), { version: info.latest }) : t("versionUpToDate"), info.updateAvailable && ` · ${t("versionLatest")} v${info.latest}`]
1402
+ })
1403
+ ]
608
1404
  }),
609
- showStatus && installKind === "link" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
610
- className: "packyCodeNote",
1405
+ showStatus && installKind === "link" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1406
+ className: "pkc-note",
611
1407
  children: t("versionLinked")
612
1408
  }),
613
- showStatus && info.updateAvailable && installKind === "unknown" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
614
- className: "packyCodeNote",
1409
+ showStatus && info.updateAvailable && installKind === "unknown" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1410
+ className: "pkc-note",
615
1411
  children: t("versionManual")
616
1412
  }),
617
- updateFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
618
- className: "packyCodeError",
1413
+ updateFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1414
+ className: "pkc-error",
619
1415
  role: "alert",
620
1416
  children: t("versionUpdateFailed")
621
1417
  }),
622
1418
  updated !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1419
+ className: "pkc-callout",
1420
+ "data-tone": "info",
623
1421
  role: "status",
624
1422
  children: [
625
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
626
- className: "packyCodeNote",
627
- children: t("versionUpdated").replace("{version}", updated.version)
1423
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
1424
+ className: "pkc-title",
1425
+ children: fill(t("versionUpdated"), { version: updated.version })
628
1426
  }),
629
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
630
- className: "packyCodeNote",
1427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1428
+ className: "pkc-note",
631
1429
  children: t("versionUpdatedHint")
632
1430
  }),
633
- restartHint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
634
- className: "packyCodeNote",
1431
+ restartHint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1432
+ className: "pkc-note",
635
1433
  children: t("versionRestartHint")
636
1434
  }),
637
1435
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
638
- className: "packyCodeActions",
1436
+ className: "pkc-actions",
639
1437
  children: [
640
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
641
- type: "button",
642
- className: "packyCodeButton",
643
- onClick: () => setRestartHint(true),
644
- children: t("versionRestart")
645
- }),
646
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
647
- type: "button",
648
- className: "packyCodeButton packyCodeButtonPrimary",
1438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1439
+ variant: "primary",
649
1440
  onClick: () => window.location.reload(),
650
1441
  children: t("versionRefresh")
651
1442
  }),
652
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
653
- type: "button",
654
- className: "packyCodeButton",
1443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1444
+ onClick: () => setRestartHint(true),
1445
+ children: t("versionRestart")
1446
+ }),
1447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1448
+ variant: "ghost",
655
1449
  onClick: () => setUpdated(void 0),
656
1450
  children: t("versionLater")
657
1451
  })
@@ -663,393 +1457,35 @@ window.__ModuleLoader__.load({
663
1457
  });
664
1458
  }
665
1459
  function PackyCodeSection({ rpc, t }) {
666
- const call = react.useMemo(() => (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap), [rpc]);
667
- const [status, setStatus] = react.useState();
668
- const [balance, setBalance] = react.useState();
669
- const [busy, setBusy] = react.useState(false);
670
- const [error, setError] = react.useState();
671
- const [draft, setDraft] = react.useState({
672
- key: "",
673
- label: ""
674
- });
675
- const [renaming, setRenaming] = react.useState();
676
- const generation = react.useRef(0);
677
- const load = react.useCallback((force = false) => {
678
- const current = ++generation.current;
679
- setBusy(true);
680
- setError(void 0);
681
- return Promise.all([call("status"), call("balance", { force })]).then(([nextStatus, nextBalance]) => {
682
- if (generation.current !== current) return;
683
- setStatus(nextStatus);
684
- setBalance(nextBalance);
685
- }).catch((failure) => {
686
- if (generation.current === current) setError(clearMessage(failure));
687
- }).finally(() => {
688
- if (generation.current === current) setBusy(false);
689
- });
690
- }, [call]);
691
- react.useEffect(() => {
692
- load(false);
693
- return () => {
694
- generation.current += 1;
695
- };
696
- }, [load]);
697
- const mutate = (endpoint) => (payload) => {
698
- setBusy(true);
699
- setError(void 0);
700
- return call(endpoint, payload).then((nextStatus) => {
701
- setStatus(nextStatus);
702
- return call("balance", { force: true });
703
- }).then((nextBalance) => setBalance(nextBalance)).catch((failure) => setError(clearMessage(failure))).finally(() => setBusy(false));
704
- };
705
- const addKey = (event) => {
706
- event.preventDefault();
707
- if (draft.key.trim() === "") return;
708
- mutate("keys/add")({
709
- key: draft.key,
710
- label: draft.label
711
- }).then(() => setDraft({
712
- key: "",
713
- label: ""
714
- }));
715
- };
716
- const submitRename = (event) => {
717
- event.preventDefault();
718
- if (renaming === void 0 || renaming.label.trim() === "") return;
719
- mutate("keys/rename")({
720
- id: renaming.id,
721
- label: renaming.label
722
- }).then(() => setRenaming(void 0));
723
- };
724
- const rows = balance?.keys ?? [];
725
- const usageOf = (id) => rows.find((row) => row.id === id);
726
- const keys = status?.keys ?? [];
727
- const totals = sumBalances(rows);
728
- /** One account row: the PackyCode token name carries the match to a key. */
729
- const accountRow = (row) => {
730
- const usage = row.usage;
731
- const figures = [
732
- usage?.unlimitedQuota === true ? t("keyUnlimited") : usage?.availableUsd === void 0 ? void 0 : `${t("keyRemaining")} ${formatUsd(usage.availableUsd)}`,
733
- usage?.usedUsd === void 0 ? void 0 : `${t("keyUsed")} ${formatUsd(usage.usedUsd)}`,
734
- usage?.limitUsd === void 0 ? void 0 : `${t("accountLimit")} ${formatUsd(usage.limitUsd)}`,
735
- resetLabel(usage?.resetPeriod, t) === void 0 ? void 0 : resetLabel(usage.resetPeriod, t)
736
- ].filter(Boolean);
737
- const tokenName = usage?.name !== void 0 && usage.name !== row.label ? `${t("keyName")} ${usage.name}` : void 0;
738
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
739
- className: "packyCodeRow",
740
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
741
- className: "packyCodeRowMain",
742
- children: [
743
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
744
- className: "packyCodeRowName",
745
- children: [
746
- row.label,
747
- row.id === status?.activeId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
748
- className: "packyCodeChip packyCodeChipActive",
749
- children: t("keyActive")
750
- }),
751
- row.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
752
- className: "packyCodeChip",
753
- children: t("keyDisabled")
754
- })
755
- ]
756
- }),
757
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
758
- className: "packyCodeRowMeta",
759
- children: figures.length > 0 ? [tokenName, ...figures].filter(Boolean).join(" · ") : row.error === void 0 ? t("keyNoBalance") : `${t("loadFailed")}: ${row.error}`
760
- }),
761
- row.stale !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
762
- className: "packyCodeRowMeta",
763
- children: t("staleNote").replace("{time}", readTime(row.stale.fetchedAt)).replace("{error}", row.stale.error ?? "")
764
- })
765
- ]
766
- })
767
- }, row.id);
768
- };
1460
+ const call = useCaller(rpc);
769
1461
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
770
- className: "packyCodeSection",
1462
+ className: "pkc",
771
1463
  children: [
772
1464
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
773
- className: "packyCodeIntro",
1465
+ className: "pkc-intro",
774
1466
  children: t("intro")
775
1467
  }),
776
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionCard, {
1468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsCard, {
777
1469
  call,
778
1470
  t
779
1471
  }),
780
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClaudeCliCard, {
1472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchCard, {
781
1473
  call,
782
1474
  t
783
1475
  }),
784
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
785
- className: "packyCodeCard",
786
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
787
- className: "packyCodeCardHeader",
788
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
789
- className: "packyCodeCardTitle",
790
- children: t("accountTitle")
791
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
792
- type: "button",
793
- className: "packyCodeButton",
794
- disabled: busy,
795
- onClick: () => load(true),
796
- children: busy ? t("refreshing") : t("refresh")
797
- })]
798
- }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
799
- className: "packyCodeNote",
800
- children: t("empty")
801
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
802
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
803
- className: "packyCodeRows",
804
- children: rows.map(accountRow)
805
- }),
806
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
807
- className: "packyCodeFigures",
808
- children: [
809
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
810
- className: "packyCodeFigure",
811
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
812
- className: "packyCodeFigureLabel",
813
- children: t("totalRemaining")
814
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
815
- className: "packyCodeFigureValue",
816
- children: [totals.counted === 0 ? "—" : formatUsd(totals.remainingUsd) ?? "—", totals.unlimited > 0 && ` + ${t("keyUnlimited")}`]
817
- })]
818
- }),
819
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
820
- className: "packyCodeFigure",
821
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
822
- className: "packyCodeFigureLabel",
823
- children: t("totalUsed")
824
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
825
- className: "packyCodeFigureValue",
826
- children: totals.counted === 0 ? "—" : formatUsd(totals.usedUsd) ?? "—"
827
- })]
828
- }),
829
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
830
- className: "packyCodeFigure",
831
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
832
- className: "packyCodeFigureLabel",
833
- children: t("totalLimit")
834
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
835
- className: "packyCodeFigureValue",
836
- children: totals.counted === 0 ? "—" : totals.unlimited > 0 ? t("keyUnlimited") : formatUsd(totals.limitUsd) ?? "—"
837
- })]
838
- })
839
- ]
840
- }),
841
- totals.unread > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
842
- className: "packyCodeNote",
843
- children: t("totalPartial").replace("{count}", String(totals.unread))
844
- }),
845
- rows.some((row) => row.stale !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
846
- className: "packyCodeNote",
847
- children: t("totalStale").replace("{count}", String(rows.filter((row) => row.stale !== void 0).length))
848
- })
849
- ] })]
850
- }),
851
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
852
- className: "packyCodeCard",
853
- children: [
854
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
855
- className: "packyCodeCardHeader",
856
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
857
- className: "packyCodeCardTitle",
858
- children: t("keysTitle")
859
- }), keys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
860
- type: "button",
861
- className: "packyCodeButton",
862
- disabled: busy,
863
- onClick: () => mutate("keys/remove-all")({}),
864
- children: t("removeAll")
865
- })]
866
- }),
867
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
868
- className: "packyCodeNote",
869
- children: t("keysHint")
870
- }),
871
- keys.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
872
- className: "packyCodeNote",
873
- children: t("empty")
874
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
875
- className: "packyCodeRows",
876
- children: keys.map((key) => {
877
- const row = usageOf(key.id);
878
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
879
- className: "packyCodeRow",
880
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
881
- className: "packyCodeRowMain",
882
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
883
- className: "packyCodeRowName",
884
- children: [
885
- renaming?.id === key.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
886
- className: "packyCodeForm",
887
- onSubmit: submitRename,
888
- children: [
889
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
890
- className: "packyCodeInput",
891
- type: "text",
892
- maxLength: 60,
893
- autoFocus: true,
894
- placeholder: t("keyRenamePlaceholder"),
895
- value: renaming.label,
896
- onChange: (event) => setRenaming({
897
- id: key.id,
898
- label: event.target.value
899
- }),
900
- onKeyDown: (event) => event.key === "Escape" && setRenaming(void 0)
901
- }),
902
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
903
- type: "submit",
904
- className: "packyCodeButton",
905
- disabled: busy || renaming.label.trim() === "",
906
- children: t("keyRenameSave")
907
- }),
908
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
909
- type: "button",
910
- className: "packyCodeButton",
911
- onClick: () => setRenaming(void 0),
912
- children: t("keyRenameCancel")
913
- })
914
- ]
915
- }) : key.label,
916
- key.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
917
- className: "packyCodeChip packyCodeChipActive",
918
- children: t("keyActive")
919
- }),
920
- key.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
921
- className: "packyCodeChip",
922
- children: `${t("keyDisabled")}: ${key.disabledReason ?? ""}`.trim()
923
- })
924
- ]
925
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
926
- className: "packyCodeRowMeta",
927
- children: row?.usage?.name === void 0 ? t("keyNameUnknown") : `${t("keyName")} ${row.usage.name}`
928
- })]
929
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
930
- className: "packyCodeActions",
931
- children: [
932
- !key.active && !key.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
933
- type: "button",
934
- className: "packyCodeButton",
935
- disabled: busy,
936
- onClick: () => mutate("keys/select")({ id: key.id }),
937
- children: t("keyUse")
938
- }),
939
- key.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
940
- type: "button",
941
- className: "packyCodeButton",
942
- disabled: busy,
943
- onClick: () => mutate("keys/enable")({ id: key.id }),
944
- children: t("keyEnable")
945
- }),
946
- renaming?.id !== key.id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
947
- type: "button",
948
- className: "packyCodeButton",
949
- disabled: busy,
950
- onClick: () => setRenaming({
951
- id: key.id,
952
- label: key.label
953
- }),
954
- children: t("keyRename")
955
- }),
956
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
957
- type: "button",
958
- className: "packyCodeButton",
959
- disabled: busy,
960
- onClick: () => mutate("keys/remove")({ id: key.id }),
961
- children: t("keyRemove")
962
- })
963
- ]
964
- })]
965
- }, key.id);
966
- })
967
- }),
968
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
969
- className: "packyCodeForm",
970
- onSubmit: addKey,
971
- children: [
972
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
973
- className: "packyCodeInput",
974
- type: "password",
975
- autoComplete: "off",
976
- placeholder: t("addKeyPlaceholder"),
977
- value: draft.key,
978
- onChange: (event) => setDraft((current) => ({
979
- ...current,
980
- key: event.target.value
981
- }))
982
- }),
983
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
984
- className: "packyCodeInput",
985
- type: "text",
986
- placeholder: t("addLabelPlaceholder"),
987
- value: draft.label,
988
- onChange: (event) => setDraft((current) => ({
989
- ...current,
990
- label: event.target.value
991
- }))
992
- }),
993
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
994
- type: "submit",
995
- className: "packyCodeButton packyCodeButtonPrimary",
996
- disabled: busy || draft.key.trim() === "",
997
- children: t("add")
998
- })
999
- ]
1000
- }),
1001
- status?.writable !== true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1002
- className: "packyCodeNote",
1003
- children: t("notWritable")
1004
- }),
1005
- error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1006
- className: "packyCodeError",
1007
- children: `${t("error")}: ${error}`
1008
- })
1009
- ]
1476
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClaudeCliCard, {
1477
+ call,
1478
+ t
1010
1479
  }),
1011
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
1012
- className: "packyCodeAdvanced",
1013
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("advanced") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1014
- className: "packyCodeAdvancedBody",
1015
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1016
- className: "packyCodeField",
1017
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1018
- className: "packyCodeFigureLabel",
1019
- children: t("apiHost")
1020
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1021
- className: "packyCodeInput",
1022
- type: "text",
1023
- disabled: status?.writable !== true,
1024
- defaultValue: status?.hosts?.apiHost ?? "",
1025
- onBlur: (event) => mutate("hosts/update")({
1026
- apiHost: event.target.value,
1027
- statusHost: status?.hosts?.statusHost
1028
- })
1029
- })]
1030
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1031
- className: "packyCodeField",
1032
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1033
- className: "packyCodeFigureLabel",
1034
- children: t("statusHost")
1035
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1036
- className: "packyCodeInput",
1037
- type: "text",
1038
- disabled: status?.writable !== true,
1039
- defaultValue: status?.hosts?.statusHost ?? "",
1040
- onBlur: (event) => mutate("hosts/update")({
1041
- statusHost: event.target.value,
1042
- apiHost: status?.hosts?.apiHost
1043
- })
1044
- })]
1045
- })]
1046
- })]
1480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionCard, {
1481
+ call,
1482
+ t
1047
1483
  })
1048
1484
  ]
1049
1485
  });
1050
1486
  }
1051
1487
  function PackyCodeComposerQuota({ rpc, t, directory }) {
1052
- const call = react.useMemo(() => (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap), [rpc]);
1488
+ const call = useCaller(rpc);
1053
1489
  const enabled = react.useSyncExternalStore((listener) => directory.subscribe(listener), () => directory.getSnapshot())?.current?.provider === PROVIDER;
1054
1490
  const [balance, setBalance] = react.useState();
1055
1491
  react.useEffect(() => {
@@ -1077,14 +1513,13 @@ window.__ModuleLoader__.load({
1077
1513
  const usage = row?.usage;
1078
1514
  const display = usage?.unlimitedQuota === true ? t("keyUnlimited") : formatUsd(usage?.availableUsd) ?? (row?.error === void 0 ? t("composerNoKey") : t("composerUnread"));
1079
1515
  const stale = row?.stale !== void 0 && usage !== void 0;
1080
- const shown = stale ? `≈${display}` : display;
1081
- const label = stale ? `${t("composerLabel")}: ${display}(${t("composerStale").replace("{time}", readTime(row.stale.fetchedAt))})` : `${t("composerLabel")}: ${display}`;
1516
+ const label = stale ? `${t("composerLabel")}: ${display}(${fill(t("composerStale"), { time: readTime(row.stale.fetchedAt) })})` : `${t("composerLabel")}: ${display}`;
1082
1517
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1083
- className: "packyCodeComposer",
1518
+ className: "pkc-composer",
1084
1519
  role: "status",
1085
1520
  "aria-label": label,
1086
1521
  title: label,
1087
- children: shown
1522
+ children: stale ? `≈${display}` : display
1088
1523
  });
1089
1524
  }
1090
1525
  const inject = [