@rezti/dsh-rez-suite 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1190 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@rezti/dsh-rez-suite",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ let react_dom_client = require("react-dom/client");
9
+ let react = require("react");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ //#region src/protocol.ts
12
+ const REZ_API = {
13
+ config: "/api/dsh-rez-suite/config",
14
+ test: "/api/dsh-rez-suite/test",
15
+ status: "/api/dsh-rez-suite/status",
16
+ audit: "/api/dsh-rez-suite/audit",
17
+ auditReset: "/api/dsh-rez-suite/audit/reset"
18
+ };
19
+ //#endregion
20
+ //#region src/client/api.ts
21
+ /**
22
+ * Browser-side API client for /api/dsh-rez-suite. Plain same-origin fetch.
23
+ */
24
+ var RezApiError = class extends Error {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = "RezApiError";
28
+ }
29
+ };
30
+ async function readJson(response) {
31
+ let body;
32
+ try {
33
+ body = await response.json();
34
+ } catch {
35
+ throw new RezApiError("HTTP " + response.status + ": invalid JSON response");
36
+ }
37
+ if (!response.ok) throw new RezApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : "HTTP " + response.status);
38
+ return body;
39
+ }
40
+ var RezApi = class {
41
+ async getConfig() {
42
+ return (await readJson(await fetch(REZ_API.config))).config;
43
+ }
44
+ async saveConfig(patch) {
45
+ return (await readJson(await fetch(REZ_API.config, {
46
+ method: "PUT",
47
+ headers: { "content-type": "application/json" },
48
+ body: JSON.stringify(patch)
49
+ }))).config;
50
+ }
51
+ async test() {
52
+ return (await readJson(await fetch(REZ_API.test, { method: "POST" }))).results;
53
+ }
54
+ async status() {
55
+ return readJson(await fetch(REZ_API.status));
56
+ }
57
+ async audit() {
58
+ return (await readJson(await fetch(REZ_API.audit))).audit;
59
+ }
60
+ async resetAudit() {
61
+ await readJson(await fetch(REZ_API.auditReset, { method: "POST" }));
62
+ }
63
+ };
64
+ //#endregion
65
+ //#region src/client/locales.ts
66
+ /**
67
+ * dsh-rez-suite surface copy: zh is the key source, en mirrors every key.
68
+ */
69
+ const zh = {
70
+ "entry.label": "Rez",
71
+ "entry.tooltip": "Rez Suite 配置与审计面板",
72
+ "panel.title": "Rez Suite",
73
+ "tab.config": "配置",
74
+ "tab.status": "状态",
75
+ "tab.audit": "用量",
76
+ "config.role": "角色",
77
+ "config.roleHint": "决定注册给模型的 MCP 工具",
78
+ "role.all": "全部",
79
+ "role.engineer": "工程师",
80
+ "role.sales": "销售",
81
+ "role.operations": "运营",
82
+ "config.enabled": "启用插件",
83
+ "config.transport": "传输方式",
84
+ "config.command": "命令",
85
+ "config.args": "参数(每行一个)",
86
+ "config.url": "URL",
87
+ "config.env": "环境变量 (JSON)",
88
+ "config.headers": "Headers (JSON)",
89
+ "config.jsonInvalid": "JSON 格式错误:{error}",
90
+ "config.odoo": "Odoo",
91
+ "config.odoo.url": "Odoo URL",
92
+ "config.odoo.apiKey": "API Key",
93
+ "config.nextcloud": "Nextcloud",
94
+ "config.nextcloud.url": "Nextcloud URL",
95
+ "config.nextcloud.username": "用户名",
96
+ "config.nextcloud.appPassword": "App Password",
97
+ "config.wecom": "企业微信",
98
+ "config.wecom.botId": "Bot ID",
99
+ "config.wecom.secret": "Secret",
100
+ "config.fs": "本地文件 (fs-mcp-server)",
101
+ "config.fs.root": "根目录",
102
+ "config.sqlite": "SQLite (sqlite-mcp-server)",
103
+ "config.sqlite.path": "数据库路径",
104
+ "config.billing": "计费 / 审计",
105
+ "config.billing.input": "输入单价 / 1k token",
106
+ "config.billing.output": "输出单价 / 1k token",
107
+ "config.billing.budget": "月预算",
108
+ "config.save": "保存",
109
+ "config.test": "测试连接",
110
+ "config.testing": "测试中…",
111
+ "config.saved": "已保存并应用",
112
+ "config.testResult": "{server}: {status}",
113
+ "config.testOk": "成功({tools} 个工具, {latency}ms)",
114
+ "config.testFail": "失败:{error}",
115
+ "status.total": "当前角色 {role},共注册 {count} 个 MCP 工具",
116
+ "status.col.server": "Server",
117
+ "status.col.state": "状态",
118
+ "status.col.tools": "发现",
119
+ "status.col.registered": "已注册",
120
+ "status.col.error": "错误",
121
+ "status.none": "暂无状态",
122
+ "audit.totalCalls": "调用次数",
123
+ "audit.inputTokens": "输入 token",
124
+ "audit.outputTokens": "输出 token",
125
+ "audit.totalCost": "累计费用",
126
+ "audit.monthCost": "本月费用",
127
+ "audit.budget": "月预算",
128
+ "audit.byServer": "按服务统计",
129
+ "audit.recent": "最近记录",
130
+ "audit.col.time": "时间",
131
+ "audit.col.server": "服务",
132
+ "audit.col.tool": "工具",
133
+ "audit.col.tokens": "token",
134
+ "audit.col.cost": "费用",
135
+ "audit.col.status": "状态",
136
+ "audit.ok": "成功",
137
+ "audit.fail": "失败",
138
+ "audit.reset": "清空账本",
139
+ "audit.resetConfirm": "确定清空全部用量记录吗?",
140
+ "audit.empty": "暂无记录",
141
+ "common.close": "关闭",
142
+ "common.loading": "加载中…",
143
+ "common.error": "出错:{error}",
144
+ "common.refresh": "刷新"
145
+ };
146
+ const en = {
147
+ "entry.label": "Rez",
148
+ "entry.tooltip": "Rez Suite configuration and audit panel",
149
+ "panel.title": "Rez Suite",
150
+ "tab.config": "Config",
151
+ "tab.status": "Status",
152
+ "tab.audit": "Usage",
153
+ "config.role": "Role",
154
+ "config.roleHint": "Which MCP tools are registered for the model",
155
+ "role.all": "All",
156
+ "role.engineer": "Engineer",
157
+ "role.sales": "Sales",
158
+ "role.operations": "Operations",
159
+ "config.enabled": "Enable plugin",
160
+ "config.transport": "Transport",
161
+ "config.command": "Command",
162
+ "config.args": "Args (one per line)",
163
+ "config.url": "URL",
164
+ "config.env": "Env vars (JSON)",
165
+ "config.headers": "Headers (JSON)",
166
+ "config.jsonInvalid": "Invalid JSON: {error}",
167
+ "config.odoo": "Odoo",
168
+ "config.odoo.url": "Odoo URL",
169
+ "config.odoo.apiKey": "API Key",
170
+ "config.nextcloud": "Nextcloud",
171
+ "config.nextcloud.url": "Nextcloud URL",
172
+ "config.nextcloud.username": "Username",
173
+ "config.nextcloud.appPassword": "App Password",
174
+ "config.wecom": "WeCom",
175
+ "config.wecom.botId": "Bot ID",
176
+ "config.wecom.secret": "Secret",
177
+ "config.fs": "Local files (fs-mcp-server)",
178
+ "config.fs.root": "Root directory",
179
+ "config.sqlite": "SQLite (sqlite-mcp-server)",
180
+ "config.sqlite.path": "Database path",
181
+ "config.billing": "Billing / audit",
182
+ "config.billing.input": "Input price / 1k tokens",
183
+ "config.billing.output": "Output price / 1k tokens",
184
+ "config.billing.budget": "Monthly budget",
185
+ "config.save": "Save",
186
+ "config.test": "Test connections",
187
+ "config.testing": "Testing…",
188
+ "config.saved": "Saved and applied",
189
+ "config.testResult": "{server}: {status}",
190
+ "config.testOk": "OK ({tools} tools, {latency}ms)",
191
+ "config.testFail": "Failed: {error}",
192
+ "status.total": "Role {role}, {count} MCP tools registered",
193
+ "status.col.server": "Server",
194
+ "status.col.state": "State",
195
+ "status.col.tools": "Discovered",
196
+ "status.col.registered": "Registered",
197
+ "status.col.error": "Error",
198
+ "status.none": "No status yet",
199
+ "audit.totalCalls": "Calls",
200
+ "audit.inputTokens": "Input tokens",
201
+ "audit.outputTokens": "Output tokens",
202
+ "audit.totalCost": "Total cost",
203
+ "audit.monthCost": "Month cost",
204
+ "audit.budget": "Monthly budget",
205
+ "audit.byServer": "By server",
206
+ "audit.recent": "Recent records",
207
+ "audit.col.time": "Time",
208
+ "audit.col.server": "Server",
209
+ "audit.col.tool": "Tool",
210
+ "audit.col.tokens": "Tokens",
211
+ "audit.col.cost": "Cost",
212
+ "audit.col.status": "Status",
213
+ "audit.ok": "OK",
214
+ "audit.fail": "Failed",
215
+ "audit.reset": "Clear ledger",
216
+ "audit.resetConfirm": "Clear all usage records?",
217
+ "audit.empty": "No records",
218
+ "common.close": "Close",
219
+ "common.loading": "Loading…",
220
+ "common.error": "Error: {error}",
221
+ "common.refresh": "Refresh"
222
+ };
223
+ /** Minimal {name} interpolator shared by the panel helpers. */
224
+ function t(dict, key, values) {
225
+ let text = dict[key] ?? key;
226
+ if (values !== void 0) for (const [name, value] of Object.entries(values)) text = text.replaceAll("{" + name + "}", String(value));
227
+ return text;
228
+ }
229
+ //#endregion
230
+ //#region src/client/panel/helpers.ts
231
+ /**
232
+ * Shared panel helpers: active-language dictionary pick and error extraction.
233
+ */
234
+ function dictionary() {
235
+ return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
236
+ }
237
+ function tt(key, values) {
238
+ return t(dictionary(), key, values);
239
+ }
240
+ function errorMessage(error) {
241
+ if (error instanceof Error) return error.message;
242
+ return String(error);
243
+ }
244
+ //#endregion
245
+ //#region src/client/panel/panel.module.css
246
+ var panel_module_default = {
247
+ "badge": "WRGRRW_badge",
248
+ "badgeFail": "WRGRRW_badgeFail",
249
+ "badgeOk": "WRGRRW_badgeOk",
250
+ "button": "WRGRRW_button",
251
+ "card": "WRGRRW_card",
252
+ "cardLabel": "WRGRRW_cardLabel",
253
+ "cards": "WRGRRW_cards",
254
+ "cardValue": "WRGRRW_cardValue",
255
+ "checkbox": "WRGRRW_checkbox",
256
+ "dangerButton": "WRGRRW_dangerButton",
257
+ "entry": "WRGRRW_entry",
258
+ "entryIcon": "WRGRRW_entryIcon",
259
+ "entryLabel": "WRGRRW_entryLabel",
260
+ "error": "WRGRRW_error",
261
+ "field": "WRGRRW_field",
262
+ "iconButton": "WRGRRW_iconButton",
263
+ "input": "WRGRRW_input",
264
+ "label": "WRGRRW_label",
265
+ "message": "WRGRRW_message",
266
+ "panel": "WRGRRW_panel",
267
+ "panelContent": "WRGRRW_panelContent",
268
+ "panelHeader": "WRGRRW_panelHeader",
269
+ "panelTitle": "WRGRRW_panelTitle",
270
+ "primaryButton": "WRGRRW_primaryButton",
271
+ "section": "WRGRRW_section",
272
+ "sectionTitle": "WRGRRW_sectionTitle",
273
+ "select": "WRGRRW_select",
274
+ "tab": "WRGRRW_tab",
275
+ "tabBar": "WRGRRW_tabBar",
276
+ "tabBody": "WRGRRW_tabBody",
277
+ "table": "WRGRRW_table",
278
+ "tableWrap": "WRGRRW_tableWrap",
279
+ "toolbar": "WRGRRW_toolbar",
280
+ "view": "WRGRRW_view"
281
+ };
282
+ //#endregion
283
+ //#region src/client/panel/AuditTab.tsx
284
+ /**
285
+ * Audit tab: token/cost summary and recent ledger rows.
286
+ */
287
+ function AuditTab({ api }) {
288
+ const [audit, setAudit] = (0, react.useState)(null);
289
+ const [error, setError] = (0, react.useState)("");
290
+ const [loading, setLoading] = (0, react.useState)(true);
291
+ const load = async () => {
292
+ setLoading(true);
293
+ setError("");
294
+ try {
295
+ setAudit(await api.audit());
296
+ } catch (err) {
297
+ setError(errorMessage(err));
298
+ } finally {
299
+ setLoading(false);
300
+ }
301
+ };
302
+ (0, react.useEffect)(() => {
303
+ load();
304
+ }, []);
305
+ const reset = async () => {
306
+ if (!window.confirm(tt("audit.resetConfirm"))) return;
307
+ try {
308
+ await api.resetAudit();
309
+ await load();
310
+ } catch (err) {
311
+ setError(errorMessage(err));
312
+ }
313
+ };
314
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
315
+ className: panel_module_default.message,
316
+ children: tt("common.loading")
317
+ });
318
+ if (audit === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
319
+ className: panel_module_default.error,
320
+ children: error
321
+ });
322
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
323
+ className: panel_module_default.tabBody,
324
+ children: [
325
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
326
+ className: panel_module_default.toolbar,
327
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
328
+ type: "button",
329
+ className: panel_module_default.button,
330
+ onClick: () => {
331
+ load();
332
+ },
333
+ children: tt("common.refresh")
334
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
335
+ type: "button",
336
+ className: panel_module_default.button + " " + panel_module_default.dangerButton,
337
+ onClick: () => {
338
+ reset();
339
+ },
340
+ children: tt("audit.reset")
341
+ })]
342
+ }),
343
+ error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
344
+ className: panel_module_default.error,
345
+ children: error
346
+ }),
347
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
348
+ className: panel_module_default.cards,
349
+ children: [
350
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
351
+ className: panel_module_default.card,
352
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
353
+ className: panel_module_default.cardLabel,
354
+ children: tt("audit.totalCalls")
355
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
356
+ className: panel_module_default.cardValue,
357
+ children: audit.totalCalls
358
+ })]
359
+ }),
360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
361
+ className: panel_module_default.card,
362
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
363
+ className: panel_module_default.cardLabel,
364
+ children: tt("audit.inputTokens")
365
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
366
+ className: panel_module_default.cardValue,
367
+ children: audit.totalInputTokens
368
+ })]
369
+ }),
370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
371
+ className: panel_module_default.card,
372
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
373
+ className: panel_module_default.cardLabel,
374
+ children: tt("audit.outputTokens")
375
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
376
+ className: panel_module_default.cardValue,
377
+ children: audit.totalOutputTokens
378
+ })]
379
+ }),
380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
381
+ className: panel_module_default.card,
382
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
383
+ className: panel_module_default.cardLabel,
384
+ children: tt("audit.totalCost")
385
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
386
+ className: panel_module_default.cardValue,
387
+ children: audit.totalCost.toFixed(4)
388
+ })]
389
+ }),
390
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
391
+ className: panel_module_default.card,
392
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
393
+ className: panel_module_default.cardLabel,
394
+ children: tt("audit.monthCost")
395
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
396
+ className: panel_module_default.cardValue,
397
+ children: audit.monthCost.toFixed(4)
398
+ })]
399
+ }),
400
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
401
+ className: panel_module_default.card,
402
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
403
+ className: panel_module_default.cardLabel,
404
+ children: tt("audit.budget")
405
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
406
+ className: panel_module_default.cardValue,
407
+ children: audit.monthlyBudget.toFixed(2)
408
+ })]
409
+ })
410
+ ]
411
+ }),
412
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
413
+ className: panel_module_default.tableWrap,
414
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
415
+ className: panel_module_default.table,
416
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("audit.col.time") }),
418
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("audit.col.server") }),
419
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("audit.col.tool") }),
420
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("audit.col.tokens") }),
421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("audit.col.cost") }),
422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("audit.col.status") })
423
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [audit.recent.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
424
+ colSpan: 6,
425
+ children: tt("audit.empty")
426
+ }) }), audit.recent.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: new Date(row.ts).toLocaleString() }),
428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.server }),
429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.tool }),
430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.inputTokens + row.outputTokens }),
431
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cost.toFixed(4) }),
432
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
433
+ className: panel_module_default.badge + " " + panel_module_default.badgeOk,
434
+ children: tt("audit.ok")
435
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
436
+ className: panel_module_default.badge + " " + panel_module_default.badgeFail,
437
+ children: tt("audit.fail")
438
+ }) })
439
+ ] }, row.id))] })]
440
+ })
441
+ })
442
+ ]
443
+ });
444
+ }
445
+ //#endregion
446
+ //#region src/client/panel/ConfigTab.tsx
447
+ /**
448
+ * Config tab: role, billing rates, and a generic editor for every MCP server
449
+ * in config.servers (stdio and streamable-http), with one-click tests.
450
+ */
451
+ const ROLES = [
452
+ {
453
+ value: "all",
454
+ label: "role.all"
455
+ },
456
+ {
457
+ value: "engineer",
458
+ label: "role.engineer"
459
+ },
460
+ {
461
+ value: "sales",
462
+ label: "role.sales"
463
+ },
464
+ {
465
+ value: "operations",
466
+ label: "role.operations"
467
+ }
468
+ ];
469
+ function Field({ label, children }) {
470
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
471
+ className: panel_module_default.field,
472
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
473
+ className: panel_module_default.label,
474
+ children: label
475
+ }), children]
476
+ });
477
+ }
478
+ function Section({ title, children }) {
479
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
480
+ className: panel_module_default.section,
481
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
482
+ className: panel_module_default.sectionTitle,
483
+ children: title
484
+ }), children]
485
+ });
486
+ }
487
+ function parseRecord(text) {
488
+ const value = JSON.parse(text);
489
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("expected a JSON object");
490
+ const out = {};
491
+ for (const [key, item] of Object.entries(value)) out[key] = String(item);
492
+ return out;
493
+ }
494
+ function ConfigTab({ api }) {
495
+ const [config, setConfig] = (0, react.useState)(null);
496
+ const [loading, setLoading] = (0, react.useState)(true);
497
+ const [saving, setSaving] = (0, react.useState)(false);
498
+ const [testing, setTesting] = (0, react.useState)(false);
499
+ const [message, setMessage] = (0, react.useState)("");
500
+ const [error, setError] = (0, react.useState)("");
501
+ const [results, setResults] = (0, react.useState)([]);
502
+ const [argsText, setArgsText] = (0, react.useState)({});
503
+ const [envText, setEnvText] = (0, react.useState)({});
504
+ const [headerText, setHeaderText] = (0, react.useState)({});
505
+ const initTexts = (next) => {
506
+ const args = {};
507
+ const envs = {};
508
+ const headers = {};
509
+ for (const [id, server] of Object.entries(next.servers)) {
510
+ args[id] = (server.args ?? []).join("\n");
511
+ envs[id] = JSON.stringify(server.env ?? {}, null, 2);
512
+ headers[id] = JSON.stringify(server.headers ?? {}, null, 2);
513
+ }
514
+ setArgsText(args);
515
+ setEnvText(envs);
516
+ setHeaderText(headers);
517
+ };
518
+ const load = async () => {
519
+ try {
520
+ const next = await api.getConfig();
521
+ setConfig(next);
522
+ initTexts(next);
523
+ } catch (err) {
524
+ setError(errorMessage(err));
525
+ } finally {
526
+ setLoading(false);
527
+ }
528
+ };
529
+ (0, react.useEffect)(() => {
530
+ load();
531
+ }, []);
532
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
533
+ className: panel_module_default.message,
534
+ children: tt("common.loading")
535
+ });
536
+ if (config === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
537
+ className: panel_module_default.error,
538
+ children: error
539
+ });
540
+ const setTop = (key, value) => {
541
+ setConfig((prev) => prev === null ? prev : {
542
+ ...prev,
543
+ [key]: value
544
+ });
545
+ };
546
+ const updateServer = (id, patch) => {
547
+ setConfig((prev) => prev === null ? prev : {
548
+ ...prev,
549
+ servers: {
550
+ ...prev.servers,
551
+ [id]: {
552
+ ...prev.servers[id],
553
+ ...patch
554
+ }
555
+ }
556
+ });
557
+ };
558
+ const save = async () => {
559
+ setSaving(true);
560
+ setMessage("");
561
+ setError("");
562
+ try {
563
+ const saved = await api.saveConfig(config);
564
+ setConfig(saved);
565
+ initTexts(saved);
566
+ setMessage(tt("config.saved"));
567
+ } catch (err) {
568
+ setError(errorMessage(err));
569
+ } finally {
570
+ setSaving(false);
571
+ }
572
+ };
573
+ const test = async () => {
574
+ setTesting(true);
575
+ setMessage("");
576
+ setError("");
577
+ setResults([]);
578
+ try {
579
+ setResults(await api.test());
580
+ } catch (err) {
581
+ setError(errorMessage(err));
582
+ } finally {
583
+ setTesting(false);
584
+ }
585
+ };
586
+ const renderResult = (result) => {
587
+ if (result.ok) return tt("config.testOk", {
588
+ tools: result.toolCount ?? 0,
589
+ latency: result.latencyMs ?? 0
590
+ });
591
+ return tt("config.testFail", { error: result.error ?? "" });
592
+ };
593
+ const commitArgs = (id) => {
594
+ updateServer(id, { args: argsText[id] === "" ? [] : argsText[id].split("\n").map((line) => line.trim()).filter(Boolean) });
595
+ };
596
+ const commitRecord = (id, kind) => {
597
+ const text = kind === "env" ? envText[id] : headerText[id];
598
+ try {
599
+ const record = parseRecord(text ?? "{}");
600
+ updateServer(id, kind === "env" ? { env: record } : { headers: record });
601
+ setError("");
602
+ } catch (err) {
603
+ setError(tt("config.jsonInvalid", { error: errorMessage(err) }));
604
+ }
605
+ };
606
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
607
+ className: panel_module_default.tabBody,
608
+ children: [
609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
610
+ title: tt("config.role"),
611
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
612
+ label: tt("config.role"),
613
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
614
+ className: panel_module_default.select,
615
+ value: config.role,
616
+ onChange: (event) => {
617
+ setTop("role", event.target.value);
618
+ },
619
+ children: ROLES.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
620
+ value: role.value,
621
+ children: tt(role.label)
622
+ }, role.value))
623
+ })
624
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
625
+ label: tt("config.enabled"),
626
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
627
+ className: panel_module_default.checkbox,
628
+ type: "checkbox",
629
+ checked: config.enabled,
630
+ onChange: (event) => {
631
+ setTop("enabled", event.target.checked);
632
+ }
633
+ })
634
+ })]
635
+ }),
636
+ Object.entries(config.servers).map(([id, server]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
637
+ title: id,
638
+ children: [
639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
640
+ label: tt("config.enabled"),
641
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
642
+ className: panel_module_default.checkbox,
643
+ type: "checkbox",
644
+ checked: server.enabled,
645
+ onChange: (event) => {
646
+ updateServer(id, { enabled: event.target.checked });
647
+ }
648
+ })
649
+ }),
650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
651
+ label: tt("config.transport"),
652
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
653
+ className: panel_module_default.select,
654
+ value: server.transport,
655
+ onChange: (event) => {
656
+ updateServer(id, { transport: event.target.value });
657
+ },
658
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
659
+ value: "stdio",
660
+ children: "stdio"
661
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
662
+ value: "streamable-http",
663
+ children: "streamable-http"
664
+ })]
665
+ })
666
+ }),
667
+ server.transport === "stdio" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
668
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
669
+ label: tt("config.command"),
670
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
671
+ className: panel_module_default.input,
672
+ value: server.command ?? "",
673
+ onChange: (event) => {
674
+ updateServer(id, { command: event.target.value });
675
+ }
676
+ })
677
+ }),
678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
679
+ label: tt("config.args"),
680
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
681
+ className: panel_module_default.input,
682
+ rows: 3,
683
+ value: argsText[id] ?? "",
684
+ onChange: (event) => {
685
+ setArgsText((prev) => ({
686
+ ...prev,
687
+ [id]: event.target.value
688
+ }));
689
+ },
690
+ onBlur: () => {
691
+ commitArgs(id);
692
+ }
693
+ })
694
+ }),
695
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
696
+ label: tt("config.env"),
697
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
698
+ className: panel_module_default.input,
699
+ rows: 4,
700
+ value: envText[id] ?? "{}",
701
+ onChange: (event) => {
702
+ setEnvText((prev) => ({
703
+ ...prev,
704
+ [id]: event.target.value
705
+ }));
706
+ },
707
+ onBlur: () => {
708
+ commitRecord(id, "env");
709
+ }
710
+ })
711
+ })
712
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
713
+ label: tt("config.url"),
714
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
715
+ className: panel_module_default.input,
716
+ value: server.url ?? "",
717
+ onChange: (event) => {
718
+ updateServer(id, { url: event.target.value });
719
+ }
720
+ })
721
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
722
+ label: tt("config.headers"),
723
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
724
+ className: panel_module_default.input,
725
+ rows: 4,
726
+ value: headerText[id] ?? "{}",
727
+ onChange: (event) => {
728
+ setHeaderText((prev) => ({
729
+ ...prev,
730
+ [id]: event.target.value
731
+ }));
732
+ },
733
+ onBlur: () => {
734
+ commitRecord(id, "headers");
735
+ }
736
+ })
737
+ })] })
738
+ ]
739
+ }, id)),
740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
741
+ title: tt("config.billing"),
742
+ children: [
743
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
744
+ label: tt("config.billing.input"),
745
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
746
+ className: panel_module_default.input,
747
+ type: "number",
748
+ step: "0.0001",
749
+ value: config.billing.inputCostPer1k,
750
+ onChange: (event) => {
751
+ setConfig((prev) => prev === null ? prev : {
752
+ ...prev,
753
+ billing: {
754
+ ...prev.billing,
755
+ inputCostPer1k: Number(event.target.value)
756
+ }
757
+ });
758
+ }
759
+ })
760
+ }),
761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
762
+ label: tt("config.billing.output"),
763
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
764
+ className: panel_module_default.input,
765
+ type: "number",
766
+ step: "0.0001",
767
+ value: config.billing.outputCostPer1k,
768
+ onChange: (event) => {
769
+ setConfig((prev) => prev === null ? prev : {
770
+ ...prev,
771
+ billing: {
772
+ ...prev.billing,
773
+ outputCostPer1k: Number(event.target.value)
774
+ }
775
+ });
776
+ }
777
+ })
778
+ }),
779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
780
+ label: tt("config.billing.budget"),
781
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
782
+ className: panel_module_default.input,
783
+ type: "number",
784
+ step: "0.01",
785
+ value: config.billing.monthlyBudget,
786
+ onChange: (event) => {
787
+ setConfig((prev) => prev === null ? prev : {
788
+ ...prev,
789
+ billing: {
790
+ ...prev.billing,
791
+ monthlyBudget: Number(event.target.value)
792
+ }
793
+ });
794
+ }
795
+ })
796
+ })
797
+ ]
798
+ }),
799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
800
+ className: panel_module_default.toolbar,
801
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
802
+ type: "button",
803
+ className: panel_module_default.button + " " + panel_module_default.primaryButton,
804
+ disabled: saving,
805
+ onClick: () => {
806
+ save();
807
+ },
808
+ children: tt("config.save")
809
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
810
+ type: "button",
811
+ className: panel_module_default.button,
812
+ disabled: testing,
813
+ onClick: () => {
814
+ test();
815
+ },
816
+ children: testing ? tt("config.testing") : tt("config.test")
817
+ })]
818
+ }),
819
+ message !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
820
+ className: panel_module_default.message,
821
+ children: message
822
+ }),
823
+ error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
824
+ className: panel_module_default.error,
825
+ children: error
826
+ }),
827
+ results.map((result) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
828
+ className: result.ok ? panel_module_default.message : panel_module_default.error,
829
+ children: tt("config.testResult", {
830
+ server: result.server,
831
+ status: renderResult(result)
832
+ })
833
+ }, result.server))
834
+ ]
835
+ });
836
+ }
837
+ //#endregion
838
+ //#region src/client/panel/StatusTab.tsx
839
+ /**
840
+ * Status tab: live MCP server states and registered-tool counts.
841
+ */
842
+ function StatusTab({ api }) {
843
+ const [status, setStatus] = (0, react.useState)(null);
844
+ const [error, setError] = (0, react.useState)("");
845
+ const [loading, setLoading] = (0, react.useState)(true);
846
+ const load = async () => {
847
+ setLoading(true);
848
+ setError("");
849
+ try {
850
+ setStatus(await api.status());
851
+ } catch (err) {
852
+ setError(errorMessage(err));
853
+ } finally {
854
+ setLoading(false);
855
+ }
856
+ };
857
+ (0, react.useEffect)(() => {
858
+ load();
859
+ }, []);
860
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
861
+ className: panel_module_default.message,
862
+ children: tt("common.loading")
863
+ });
864
+ if (status === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
865
+ className: panel_module_default.error,
866
+ children: error
867
+ });
868
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
869
+ className: panel_module_default.tabBody,
870
+ children: [
871
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
872
+ className: panel_module_default.toolbar,
873
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
874
+ className: panel_module_default.message,
875
+ children: tt("status.total", {
876
+ role: status.role,
877
+ count: status.totalRegisteredTools
878
+ })
879
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
880
+ type: "button",
881
+ className: panel_module_default.button,
882
+ onClick: () => {
883
+ load();
884
+ },
885
+ children: tt("common.refresh")
886
+ })]
887
+ }),
888
+ error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
889
+ className: panel_module_default.error,
890
+ children: error
891
+ }),
892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
893
+ className: panel_module_default.tableWrap,
894
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
895
+ className: panel_module_default.table,
896
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("status.col.server") }),
898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("status.col.state") }),
899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("status.col.tools") }),
900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("status.col.registered") }),
901
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: tt("status.col.error") })
902
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: status.servers.map((server) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: server.server }),
904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
905
+ className: panel_module_default.badge + " " + (server.state === "connected" ? panel_module_default.badgeOk : panel_module_default.badgeFail),
906
+ children: server.state
907
+ }) }),
908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: server.toolCount }),
909
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: server.registeredTools }),
910
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: server.lastError ?? "" })
911
+ ] }, server.server)) })]
912
+ })
913
+ })
914
+ ]
915
+ });
916
+ }
917
+ //#endregion
918
+ //#region src/client/panel/RezPanel.tsx
919
+ /**
920
+ * Rez Suite panel shell: header + three tabs (config/status/audit).
921
+ */
922
+ const TABS = [
923
+ {
924
+ id: "config",
925
+ label: () => tt("tab.config")
926
+ },
927
+ {
928
+ id: "status",
929
+ label: () => tt("tab.status")
930
+ },
931
+ {
932
+ id: "audit",
933
+ label: () => tt("tab.audit")
934
+ }
935
+ ];
936
+ function RezPanel({ controller, api }) {
937
+ const [activeTab, setActiveTab] = (0, react.useState)("config");
938
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
939
+ className: panel_module_default.panel,
940
+ children: [
941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
942
+ className: panel_module_default.panelHeader,
943
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
944
+ className: panel_module_default.panelTitle,
945
+ children: tt("panel.title")
946
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
947
+ type: "button",
948
+ className: panel_module_default.iconButton,
949
+ title: tt("common.close"),
950
+ "aria-label": tt("common.close"),
951
+ onClick: () => {
952
+ controller.close();
953
+ },
954
+ children: "x"
955
+ })]
956
+ }),
957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
958
+ className: panel_module_default.tabBar,
959
+ role: "tablist",
960
+ children: TABS.map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
961
+ type: "button",
962
+ role: "tab",
963
+ "aria-selected": activeTab === tab.id,
964
+ "data-active": activeTab === tab.id ? "" : void 0,
965
+ className: panel_module_default.tab,
966
+ onClick: () => {
967
+ setActiveTab(tab.id);
968
+ },
969
+ children: tab.label()
970
+ }, tab.id))
971
+ }),
972
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
973
+ className: panel_module_default.panelContent,
974
+ children: [
975
+ activeTab === "config" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigTab, { api }),
976
+ activeTab === "status" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTab, { api }),
977
+ activeTab === "audit" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTab, { api })
978
+ ]
979
+ })
980
+ ]
981
+ });
982
+ }
983
+ //#endregion
984
+ //#region src/client/mount.tsx
985
+ /**
986
+ * Rez panel view mounting. Single-occupant center-column takeover, same
987
+ * mechanism as the task board and ssh panels. The conversation subtree stays
988
+ * mounted under the panel.
989
+ */
990
+ const CONVERSATION_COLUMN_SELECTOR = "[data-pane=\"conversation\"]";
991
+ const ACTIVE_ATTR = "data-dsh-rez-active";
992
+ const OTHER_ACTIVE_ATTRS = ["data-dsh-taskboard-active", "data-dsh-ssh-active"];
993
+ const ACTIVATE_EVENT = "dsh-panel-activate";
994
+ const PANEL_NAME = "rez";
995
+ function conversationColumn() {
996
+ return document.querySelector(CONVERSATION_COLUMN_SELECTOR) ?? void 0;
997
+ }
998
+ function mountPanel(controller, api) {
999
+ let root;
1000
+ let container;
1001
+ const ensure = () => {
1002
+ if (container !== void 0) {
1003
+ if (container.isConnected) return;
1004
+ root?.unmount();
1005
+ root = void 0;
1006
+ container.remove();
1007
+ container = void 0;
1008
+ }
1009
+ const column = conversationColumn();
1010
+ if (column === void 0) return;
1011
+ container = document.createElement("div");
1012
+ container.dataset.dshRezView = "";
1013
+ container.className = panel_module_default.view;
1014
+ column.appendChild(container);
1015
+ root = (0, react_dom_client.createRoot)(container);
1016
+ root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RezPanel, {
1017
+ controller,
1018
+ api
1019
+ }));
1020
+ };
1021
+ const waitObserver = new MutationObserver(() => {
1022
+ ensure();
1023
+ });
1024
+ waitObserver.observe(document.body, {
1025
+ childList: true,
1026
+ subtree: true
1027
+ });
1028
+ const retryTimer = window.setInterval(() => {
1029
+ ensure();
1030
+ if (container?.isConnected === true) window.clearInterval(retryTimer);
1031
+ }, 250);
1032
+ const applyActive = () => {
1033
+ if (controller.getSnapshot().panelOpen) {
1034
+ for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr);
1035
+ document.documentElement.setAttribute(ACTIVE_ATTR, "");
1036
+ document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }));
1037
+ } else document.documentElement.removeAttribute(ACTIVE_ATTR);
1038
+ };
1039
+ const onOtherActivate = (event) => {
1040
+ const detail = event.detail;
1041
+ if ((detail === "taskboard" || detail === "ssh") && controller.getSnapshot().panelOpen) controller.close();
1042
+ };
1043
+ const SIDEBAR_ROW_SELECTOR = "[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]";
1044
+ const onClickSidebarRow = (event) => {
1045
+ if (!controller.getSnapshot().panelOpen) return;
1046
+ const target = event.target;
1047
+ if (target === null) return;
1048
+ if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close();
1049
+ };
1050
+ document.addEventListener("click", onClickSidebarRow, true);
1051
+ document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
1052
+ const unsubscribe = controller.subscribe(applyActive);
1053
+ applyActive();
1054
+ ensure();
1055
+ return () => {
1056
+ window.clearInterval(retryTimer);
1057
+ document.removeEventListener("click", onClickSidebarRow, true);
1058
+ document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
1059
+ waitObserver.disconnect();
1060
+ unsubscribe();
1061
+ document.documentElement.removeAttribute(ACTIVE_ATTR);
1062
+ root?.unmount();
1063
+ root = void 0;
1064
+ container?.remove();
1065
+ container = void 0;
1066
+ };
1067
+ }
1068
+ //#endregion
1069
+ //#region src/client/panel/controller.ts
1070
+ var PanelController = class {
1071
+ panelOpen = false;
1072
+ listeners = /* @__PURE__ */ new Set();
1073
+ getSnapshot() {
1074
+ return { panelOpen: this.panelOpen };
1075
+ }
1076
+ subscribe(fn) {
1077
+ this.listeners.add(fn);
1078
+ return () => {
1079
+ this.listeners.delete(fn);
1080
+ };
1081
+ }
1082
+ open() {
1083
+ if (this.panelOpen) return;
1084
+ this.panelOpen = true;
1085
+ this.notify();
1086
+ }
1087
+ close() {
1088
+ if (!this.panelOpen) return;
1089
+ this.panelOpen = false;
1090
+ this.notify();
1091
+ }
1092
+ toggle() {
1093
+ if (this.panelOpen) this.close();
1094
+ else this.open();
1095
+ }
1096
+ notify() {
1097
+ for (const fn of [...this.listeners]) fn();
1098
+ }
1099
+ };
1100
+ //#endregion
1101
+ //#region src/client/sidebar-entry.ts
1102
+ function sidebarRoot() {
1103
+ const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
1104
+ if (column === null) return void 0;
1105
+ return column.querySelector("[class*=\"logoRow\"]")?.parentElement ?? column.firstElementChild;
1106
+ }
1107
+ function newSessionButton(root) {
1108
+ const nested = root.querySelector("button[class*=\"newSession\"]");
1109
+ if (nested !== null) return nested;
1110
+ for (const child of root.children) if (child.tagName === "BUTTON") return child;
1111
+ }
1112
+ function createEntry(controller) {
1113
+ const entry = document.createElement("button");
1114
+ entry.type = "button";
1115
+ entry.dataset.dshRezEntry = "";
1116
+ entry.className = panel_module_default.entry;
1117
+ entry.setAttribute("aria-label", tt("entry.label"));
1118
+ entry.setAttribute("title", tt("entry.tooltip"));
1119
+ entry.innerHTML = "<span class=\"" + panel_module_default.entryIcon + "\"><svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2.5\" y=\"2.5\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 8l2 2 4-4\"/></svg></span><span class=\"" + panel_module_default.entryLabel + "\">" + tt("entry.label") + "</span>";
1120
+ entry.addEventListener("click", () => {
1121
+ controller.toggle();
1122
+ });
1123
+ return entry;
1124
+ }
1125
+ function placeEntry(root, entry) {
1126
+ const button = newSessionButton(root);
1127
+ if (button === void 0) return false;
1128
+ if (entry.parentElement !== root) {
1129
+ const row = button.closest("[class*=\"logoRow\"]");
1130
+ const base = row !== null && row.parentElement === root ? row : button;
1131
+ const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches("[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-rez-entry]"));
1132
+ const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
1133
+ root.insertBefore(entry, anchor);
1134
+ }
1135
+ return true;
1136
+ }
1137
+ function mountSidebarEntry(controller) {
1138
+ const entry = createEntry(controller);
1139
+ let root;
1140
+ let placed = false;
1141
+ const place = () => {
1142
+ const next = sidebarRoot();
1143
+ if (next === void 0) return;
1144
+ if (root !== void 0 && root !== next) {
1145
+ if (entry.parentElement === root) entry.remove();
1146
+ placed = false;
1147
+ }
1148
+ root = next;
1149
+ if (!placed) placed = placeEntry(root, entry);
1150
+ };
1151
+ const observer = new MutationObserver(place);
1152
+ observer.observe(document.body, {
1153
+ childList: true,
1154
+ subtree: true
1155
+ });
1156
+ place();
1157
+ return () => {
1158
+ observer.disconnect();
1159
+ entry.remove();
1160
+ };
1161
+ }
1162
+ //#endregion
1163
+ //#region src/client/index.ts
1164
+ const NS = "dsh-rez-suite";
1165
+ const inject = ["slots", "locale"];
1166
+ function apply(ctx) {
1167
+ ctx.effect(() => ctx.locale.register(NS, {
1168
+ zh,
1169
+ en
1170
+ }), "dsh-rez-suite: dictionaries");
1171
+ const controller = new PanelController();
1172
+ const api = new RezApi();
1173
+ const disposers = [];
1174
+ try {
1175
+ disposers.push(mountSidebarEntry(controller));
1176
+ disposers.push(mountPanel(controller, api));
1177
+ } catch (error) {
1178
+ console.warn("[dsh-rez-suite] mount failed:", error);
1179
+ }
1180
+ ctx.effect(() => () => {
1181
+ for (const dispose of disposers.splice(0)) dispose();
1182
+ }, "dsh-rez-suite: ui mounts");
1183
+ }
1184
+ //#endregion
1185
+ exports.apply = apply;
1186
+ exports.inject = inject;
1187
+
1188
+ return module.exports;
1189
+ }
1190
+ });