@rezti/dsh-rez-suite 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -8,4 +8,4 @@ dsh plugin --profile web add @rezti/dsh-rez-suite
8
8
 
9
9
  组成见仓库根 README。子包不要单独 `dsh plugin add`。
10
10
 
11
- 配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。个人微信在卡片的「微信」页扫码,然后直接在微信里说话。
11
+ 配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。Odoo / Nextcloud / WeCom / HA 的地址和密钥都在「配置」页。个人微信在卡片的「微信」页扫码,然后直接在微信里说话。状态页看的是本机已注册的 `mcp__odoo__*` 等工具,不是直接 ping 远端 MCP。
package/README.zh.md CHANGED
@@ -8,6 +8,6 @@ dsh plugin --profile web add @rezti/dsh-rez-suite
8
8
 
9
9
  密钥用环境变量或 dsh-credentials:`REZ_ODOO_TOKEN`、`REZ_NEXTCLOUD_PASSWORD`、`REZ_WECHAT_WEBHOOK`、`REZ_HA_TOKEN`。未配置时不拉起对应 MCP。
10
10
 
11
- 配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。个人微信在卡片的「微信」页扫码,然后直接在微信里说话。
11
+ 配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。Odoo / Nextcloud / 企微 / HA 的地址和密钥都在「配置」页。个人微信在卡片的「微信」页扫码,然后直接在微信里说话。状态页看的是本机已注册的 `mcp__odoo__*` 等工具,不是直接 ping 远端 MCP。
12
12
 
13
13
  子包不要再单独 `dsh plugin add`,否则会重复注册 MCP。
package/lib/client.d.ts CHANGED
@@ -7275,16 +7275,23 @@ declare const zh: {
7275
7275
  readonly 'config.env': "环境变量 (JSON)";
7276
7276
  readonly 'config.headers': "Headers (JSON)";
7277
7277
  readonly 'config.jsonInvalid': "JSON 格式错误:{error}";
7278
+ readonly 'config.mcpHint': "四个公司 MCP 在这里配。密钥写入 ~/.dsh/.credentials.yaml(REZ_*)。新填的密钥会立刻拉起连接;改 URL 后如果已经连上,需要重启 dsh web。";
7279
+ readonly 'config.secretConfigured': "已配置(留空则保持)";
7280
+ readonly 'config.secretMissing': "未配置";
7278
7281
  readonly 'config.odoo': "Odoo";
7279
- readonly 'config.odoo.url': "Odoo URL";
7280
- readonly 'config.odoo.apiKey': "API Key";
7282
+ readonly 'config.odoo.url': "Odoo MCP URL";
7283
+ readonly 'config.odoo.apiKey': "API Token";
7281
7284
  readonly 'config.nextcloud': "Nextcloud";
7282
- readonly 'config.nextcloud.url': "Nextcloud URL";
7285
+ readonly 'config.nextcloud.url': "Nextcloud 主机";
7283
7286
  readonly 'config.nextcloud.username': "用户名";
7284
7287
  readonly 'config.nextcloud.appPassword': "App Password";
7285
7288
  readonly 'config.wecom': "企业微信";
7286
7289
  readonly 'config.wecom.botId': "Bot ID";
7287
7290
  readonly 'config.wecom.secret': "Secret";
7291
+ readonly 'config.wecom.webhook': "群机器人 Webhook";
7292
+ readonly 'config.ha': "Home Assistant";
7293
+ readonly 'config.ha.url': "HA MCP URL";
7294
+ readonly 'config.ha.token': "长期访问令牌";
7288
7295
  readonly 'config.fs': "本地文件 (fs-mcp-server)";
7289
7296
  readonly 'config.fs.root': "根目录";
7290
7297
  readonly 'config.sqlite': "SQLite (sqlite-mcp-server)";
@@ -7300,6 +7307,7 @@ declare const zh: {
7300
7307
  readonly 'config.testResult': "{server}: {status}";
7301
7308
  readonly 'config.testOk': "成功({tools} 个工具, {latency}ms)";
7302
7309
  readonly 'config.testFail': "失败:{error}";
7310
+ readonly 'status.hint': "绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client 还没挂上。";
7303
7311
  readonly 'status.total': "当前角色 {role},共注册 {count} 个 MCP 工具";
7304
7312
  readonly 'status.col.server': "Server";
7305
7313
  readonly 'status.col.state': "状态";
package/lib/client.js CHANGED
@@ -36,16 +36,23 @@ const zh = {
36
36
  "config.env": "环境变量 (JSON)",
37
37
  "config.headers": "Headers (JSON)",
38
38
  "config.jsonInvalid": "JSON 格式错误:{error}",
39
+ "config.mcpHint": "四个公司 MCP 在这里配。密钥写入 ~/.dsh/.credentials.yaml(REZ_*)。新填的密钥会立刻拉起连接;改 URL 后如果已经连上,需要重启 dsh web。",
40
+ "config.secretConfigured": "已配置(留空则保持)",
41
+ "config.secretMissing": "未配置",
39
42
  "config.odoo": "Odoo",
40
- "config.odoo.url": "Odoo URL",
41
- "config.odoo.apiKey": "API Key",
43
+ "config.odoo.url": "Odoo MCP URL",
44
+ "config.odoo.apiKey": "API Token",
42
45
  "config.nextcloud": "Nextcloud",
43
- "config.nextcloud.url": "Nextcloud URL",
46
+ "config.nextcloud.url": "Nextcloud 主机",
44
47
  "config.nextcloud.username": "用户名",
45
48
  "config.nextcloud.appPassword": "App Password",
46
49
  "config.wecom": "企业微信",
47
50
  "config.wecom.botId": "Bot ID",
48
51
  "config.wecom.secret": "Secret",
52
+ "config.wecom.webhook": "群机器人 Webhook",
53
+ "config.ha": "Home Assistant",
54
+ "config.ha.url": "HA MCP URL",
55
+ "config.ha.token": "长期访问令牌",
49
56
  "config.fs": "本地文件 (fs-mcp-server)",
50
57
  "config.fs.root": "根目录",
51
58
  "config.sqlite": "SQLite (sqlite-mcp-server)",
@@ -61,6 +68,7 @@ const zh = {
61
68
  "config.testResult": "{server}: {status}",
62
69
  "config.testOk": "成功({tools} 个工具, {latency}ms)",
63
70
  "config.testFail": "失败:{error}",
71
+ "status.hint": "绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client 还没挂上。",
64
72
  "status.total": "当前角色 {role},共注册 {count} 个 MCP 工具",
65
73
  "status.col.server": "Server",
66
74
  "status.col.state": "状态",
@@ -125,16 +133,23 @@ const en = {
125
133
  "config.env": "Env vars (JSON)",
126
134
  "config.headers": "Headers (JSON)",
127
135
  "config.jsonInvalid": "Invalid JSON: {error}",
136
+ "config.mcpHint": "Company MCP lives here. Secrets go to ~/.dsh/.credentials.yaml (REZ_*). A newly saved secret starts the connection immediately; URL changes after a live connection need a dsh web restart.",
137
+ "config.secretConfigured": "Configured (leave blank to keep)",
138
+ "config.secretMissing": "Not configured",
128
139
  "config.odoo": "Odoo",
129
- "config.odoo.url": "Odoo URL",
130
- "config.odoo.apiKey": "API Key",
140
+ "config.odoo.url": "Odoo MCP URL",
141
+ "config.odoo.apiKey": "API token",
131
142
  "config.nextcloud": "Nextcloud",
132
- "config.nextcloud.url": "Nextcloud URL",
143
+ "config.nextcloud.url": "Nextcloud host",
133
144
  "config.nextcloud.username": "Username",
134
- "config.nextcloud.appPassword": "App Password",
145
+ "config.nextcloud.appPassword": "App password",
135
146
  "config.wecom": "WeCom",
136
147
  "config.wecom.botId": "Bot ID",
137
148
  "config.wecom.secret": "Secret",
149
+ "config.wecom.webhook": "Group bot webhook",
150
+ "config.ha": "Home Assistant",
151
+ "config.ha.url": "HA MCP URL",
152
+ "config.ha.token": "Long-lived access token",
138
153
  "config.fs": "Local files (fs-mcp-server)",
139
154
  "config.fs.root": "Root directory",
140
155
  "config.sqlite": "SQLite (sqlite-mcp-server)",
@@ -150,6 +165,7 @@ const en = {
150
165
  "config.testResult": "{server}: {status}",
151
166
  "config.testOk": "OK ({tools} tools, {latency}ms)",
152
167
  "config.testFail": "Failed: {error}",
168
+ "status.hint": "Green means this host registered mcp__<system>__* tools. A live remote MCP with a red row means dsh-mcp-client has not attached yet.",
153
169
  "status.total": "Role {role}, {count} MCP tools registered",
154
170
  "status.col.server": "Server",
155
171
  "status.col.state": "State",
@@ -489,8 +505,8 @@ function AuditTab({ api }) {
489
505
  //#endregion
490
506
  //#region src/client/panel/ConfigTab.tsx
491
507
  /**
492
- * Config tab: role, billing rates, and a generic editor for every MCP server
493
- * in config.servers (stdio and streamable-http), with one-click tests.
508
+ * Config tab: company MCP connections (Odoo / Nextcloud / WeCom / HA),
509
+ * role, and billing. Secrets are write-only and land in credentials.yaml.
494
510
  */
495
511
  const ROLES = [
496
512
  {
@@ -510,6 +526,12 @@ const ROLES = [
510
526
  label: "role.operations"
511
527
  }
512
528
  ];
529
+ const EMPTY_SECRETS = {
530
+ odoo: "",
531
+ nextcloud: "",
532
+ wechat: "",
533
+ homeassistant: ""
534
+ };
513
535
  function Field({ label, children }) {
514
536
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
515
537
  className: panel_module_default.field,
@@ -528,42 +550,81 @@ function Section({ title, children }) {
528
550
  }), children]
529
551
  });
530
552
  }
531
- function parseRecord(text) {
532
- const value = JSON.parse(text);
533
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("expected a JSON object");
534
- const out = {};
535
- for (const [key, item] of Object.entries(value)) out[key] = String(item);
536
- return out;
553
+ function SecretField({ label, configured, value, onChange }) {
554
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
555
+ label,
556
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
557
+ className: panel_module_default.input,
558
+ type: "password",
559
+ autoComplete: "off",
560
+ value,
561
+ placeholder: configured ? tt("config.secretConfigured") : tt("config.secretMissing"),
562
+ onChange: (event) => {
563
+ onChange(event.target.value);
564
+ }
565
+ })
566
+ });
567
+ }
568
+ function McpSection({ title, row, secret, onEnabled, onUrl, onUsername, onSecret, urlLabel, secretLabel, usernameLabel }) {
569
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
570
+ title,
571
+ children: [
572
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
573
+ label: tt("config.enabled"),
574
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
575
+ className: panel_module_default.checkbox,
576
+ type: "checkbox",
577
+ checked: row.enabled,
578
+ onChange: (event) => {
579
+ onEnabled(event.target.checked);
580
+ }
581
+ })
582
+ }),
583
+ onUrl !== void 0 && urlLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
584
+ label: urlLabel,
585
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
586
+ className: panel_module_default.input,
587
+ value: row.url ?? "",
588
+ onChange: (event) => {
589
+ onUrl(event.target.value);
590
+ }
591
+ })
592
+ }),
593
+ onUsername !== void 0 && usernameLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
594
+ label: usernameLabel,
595
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
596
+ className: panel_module_default.input,
597
+ value: row.username ?? "",
598
+ onChange: (event) => {
599
+ onUsername(event.target.value);
600
+ }
601
+ })
602
+ }),
603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
604
+ label: secretLabel,
605
+ configured: row.secretConfigured,
606
+ value: secret,
607
+ onChange: onSecret
608
+ }),
609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
610
+ className: panel_module_default.message,
611
+ children: row.secretRef
612
+ })
613
+ ]
614
+ });
537
615
  }
538
616
  function ConfigTab({ api }) {
539
617
  const [config, setConfig] = (0, react.useState)(null);
618
+ const [secrets, setSecrets] = (0, react.useState)(EMPTY_SECRETS);
540
619
  const [loading, setLoading] = (0, react.useState)(true);
541
620
  const [saving, setSaving] = (0, react.useState)(false);
542
621
  const [testing, setTesting] = (0, react.useState)(false);
543
622
  const [message, setMessage] = (0, react.useState)("");
544
623
  const [error, setError] = (0, react.useState)("");
545
624
  const [results, setResults] = (0, react.useState)([]);
546
- const [argsText, setArgsText] = (0, react.useState)({});
547
- const [envText, setEnvText] = (0, react.useState)({});
548
- const [headerText, setHeaderText] = (0, react.useState)({});
549
- const initTexts = (next) => {
550
- const args = {};
551
- const envs = {};
552
- const headers = {};
553
- for (const [id, server] of Object.entries(next.servers)) {
554
- args[id] = (server.args ?? []).join("\n");
555
- envs[id] = JSON.stringify(server.env ?? {}, null, 2);
556
- headers[id] = JSON.stringify(server.headers ?? {}, null, 2);
557
- }
558
- setArgsText(args);
559
- setEnvText(envs);
560
- setHeaderText(headers);
561
- };
562
625
  const load = async () => {
563
626
  try {
564
- const next = await api.getConfig();
565
- setConfig(next);
566
- initTexts(next);
627
+ setConfig(await api.getConfig());
567
628
  } catch (err) {
568
629
  setError(errorMessage(err));
569
630
  } finally {
@@ -587,13 +648,13 @@ function ConfigTab({ api }) {
587
648
  [key]: value
588
649
  });
589
650
  };
590
- const updateServer = (id, patch) => {
651
+ const patchConnection = (id, patch) => {
591
652
  setConfig((prev) => prev === null ? prev : {
592
653
  ...prev,
593
- servers: {
594
- ...prev.servers,
654
+ connections: {
655
+ ...prev.connections,
595
656
  [id]: {
596
- ...prev.servers[id],
657
+ ...prev.connections[id],
597
658
  ...patch
598
659
  }
599
660
  }
@@ -604,9 +665,32 @@ function ConfigTab({ api }) {
604
665
  setMessage("");
605
666
  setError("");
606
667
  try {
607
- const saved = await api.saveConfig(config);
668
+ const payload = {
669
+ enabled: config.enabled,
670
+ role: config.role,
671
+ billing: config.billing,
672
+ connections: {
673
+ odoo: {
674
+ ...config.connections.odoo,
675
+ secret: secrets.odoo || void 0
676
+ },
677
+ nextcloud: {
678
+ ...config.connections.nextcloud,
679
+ secret: secrets.nextcloud || void 0
680
+ },
681
+ wechat: {
682
+ ...config.connections.wechat,
683
+ secret: secrets.wechat || void 0
684
+ },
685
+ homeassistant: {
686
+ ...config.connections.homeassistant,
687
+ secret: secrets.homeassistant || void 0
688
+ }
689
+ }
690
+ };
691
+ const saved = await api.saveConfig(payload);
608
692
  setConfig(saved);
609
- initTexts(saved);
693
+ setSecrets(EMPTY_SECRETS);
610
694
  setMessage(tt("config.saved"));
611
695
  } catch (err) {
612
696
  setError(errorMessage(err));
@@ -628,28 +712,22 @@ function ConfigTab({ api }) {
628
712
  }
629
713
  };
630
714
  const renderResult = (result) => {
631
- if (result.ok) return tt("config.testOk", {
632
- tools: result.toolCount ?? 0,
633
- latency: result.latencyMs ?? 0
634
- });
635
- return tt("config.testFail", { error: result.error ?? "" });
636
- };
637
- const commitArgs = (id) => {
638
- updateServer(id, { args: argsText[id] === "" ? [] : argsText[id].split("\n").map((line) => line.trim()).filter(Boolean) });
639
- };
640
- const commitRecord = (id, kind) => {
641
- const text = kind === "env" ? envText[id] : headerText[id];
642
- try {
643
- const record = parseRecord(text ?? "{}");
644
- updateServer(id, kind === "env" ? { env: record } : { headers: record });
645
- setError("");
646
- } catch (err) {
647
- setError(tt("config.jsonInvalid", { error: errorMessage(err) }));
715
+ if (result.ok) {
716
+ if (result.serverInfo !== void 0) return result.serverInfo;
717
+ return tt("config.testOk", {
718
+ tools: result.toolCount ?? 0,
719
+ latency: result.latencyMs ?? 0
720
+ });
648
721
  }
722
+ return tt("config.testFail", { error: result.error ?? "" });
649
723
  };
650
724
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
651
725
  className: panel_module_default.tabBody,
652
726
  children: [
727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
728
+ className: panel_module_default.message,
729
+ children: tt("config.mcpHint")
730
+ }),
653
731
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
654
732
  title: tt("config.role"),
655
733
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
@@ -677,110 +755,82 @@ function ConfigTab({ api }) {
677
755
  })
678
756
  })]
679
757
  }),
680
- Object.entries(config.servers).map(([id, server]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
681
- title: id,
682
- children: [
683
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
684
- label: tt("config.enabled"),
685
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
686
- className: panel_module_default.checkbox,
687
- type: "checkbox",
688
- checked: server.enabled,
689
- onChange: (event) => {
690
- updateServer(id, { enabled: event.target.checked });
691
- }
692
- })
693
- }),
694
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
695
- label: tt("config.transport"),
696
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
697
- className: panel_module_default.select,
698
- value: server.transport,
699
- onChange: (event) => {
700
- updateServer(id, { transport: event.target.value });
701
- },
702
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
703
- value: "stdio",
704
- children: "stdio"
705
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
706
- value: "streamable-http",
707
- children: "streamable-http"
708
- })]
709
- })
710
- }),
711
- server.transport === "stdio" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
712
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
713
- label: tt("config.command"),
714
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
715
- className: panel_module_default.input,
716
- value: server.command ?? "",
717
- onChange: (event) => {
718
- updateServer(id, { command: event.target.value });
719
- }
720
- })
721
- }),
722
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
723
- label: tt("config.args"),
724
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
725
- className: panel_module_default.input,
726
- rows: 3,
727
- value: argsText[id] ?? "",
728
- onChange: (event) => {
729
- setArgsText((prev) => ({
730
- ...prev,
731
- [id]: event.target.value
732
- }));
733
- },
734
- onBlur: () => {
735
- commitArgs(id);
736
- }
737
- })
738
- }),
739
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
740
- label: tt("config.env"),
741
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
742
- className: panel_module_default.input,
743
- rows: 4,
744
- value: envText[id] ?? "{}",
745
- onChange: (event) => {
746
- setEnvText((prev) => ({
747
- ...prev,
748
- [id]: event.target.value
749
- }));
750
- },
751
- onBlur: () => {
752
- commitRecord(id, "env");
753
- }
754
- })
755
- })
756
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
757
- label: tt("config.url"),
758
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
759
- className: panel_module_default.input,
760
- value: server.url ?? "",
761
- onChange: (event) => {
762
- updateServer(id, { url: event.target.value });
763
- }
764
- })
765
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
766
- label: tt("config.headers"),
767
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
768
- className: panel_module_default.input,
769
- rows: 4,
770
- value: headerText[id] ?? "{}",
771
- onChange: (event) => {
772
- setHeaderText((prev) => ({
773
- ...prev,
774
- [id]: event.target.value
775
- }));
776
- },
777
- onBlur: () => {
778
- commitRecord(id, "headers");
779
- }
780
- })
781
- })] })
782
- ]
783
- }, id)),
758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
759
+ title: tt("config.odoo"),
760
+ row: config.connections.odoo,
761
+ secret: secrets.odoo,
762
+ onEnabled: (enabled) => {
763
+ patchConnection("odoo", { enabled });
764
+ },
765
+ onUrl: (url) => {
766
+ patchConnection("odoo", { url });
767
+ },
768
+ onSecret: (value) => {
769
+ setSecrets((prev) => ({
770
+ ...prev,
771
+ odoo: value
772
+ }));
773
+ },
774
+ urlLabel: tt("config.odoo.url"),
775
+ secretLabel: tt("config.odoo.apiKey")
776
+ }),
777
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
778
+ title: tt("config.nextcloud"),
779
+ row: config.connections.nextcloud,
780
+ secret: secrets.nextcloud,
781
+ onEnabled: (enabled) => {
782
+ patchConnection("nextcloud", { enabled });
783
+ },
784
+ onUrl: (url) => {
785
+ patchConnection("nextcloud", { url });
786
+ },
787
+ onUsername: (username) => {
788
+ patchConnection("nextcloud", { username });
789
+ },
790
+ onSecret: (value) => {
791
+ setSecrets((prev) => ({
792
+ ...prev,
793
+ nextcloud: value
794
+ }));
795
+ },
796
+ urlLabel: tt("config.nextcloud.url"),
797
+ usernameLabel: tt("config.nextcloud.username"),
798
+ secretLabel: tt("config.nextcloud.appPassword")
799
+ }),
800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
801
+ title: tt("config.wecom"),
802
+ row: config.connections.wechat,
803
+ secret: secrets.wechat,
804
+ onEnabled: (enabled) => {
805
+ patchConnection("wechat", { enabled });
806
+ },
807
+ onSecret: (value) => {
808
+ setSecrets((prev) => ({
809
+ ...prev,
810
+ wechat: value
811
+ }));
812
+ },
813
+ secretLabel: tt("config.wecom.webhook")
814
+ }),
815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
816
+ title: tt("config.ha"),
817
+ row: config.connections.homeassistant,
818
+ secret: secrets.homeassistant,
819
+ onEnabled: (enabled) => {
820
+ patchConnection("homeassistant", { enabled });
821
+ },
822
+ onUrl: (url) => {
823
+ patchConnection("homeassistant", { url });
824
+ },
825
+ onSecret: (value) => {
826
+ setSecrets((prev) => ({
827
+ ...prev,
828
+ homeassistant: value
829
+ }));
830
+ },
831
+ urlLabel: tt("config.ha.url"),
832
+ secretLabel: tt("config.ha.token")
833
+ }),
784
834
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
785
835
  title: tt("config.billing"),
786
836
  children: [
@@ -929,6 +979,10 @@ function StatusTab({ api }) {
929
979
  children: tt("common.refresh")
930
980
  })]
931
981
  }),
982
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
983
+ className: panel_module_default.message,
984
+ children: tt("status.hint")
985
+ }),
932
986
  error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
933
987
  className: panel_module_default.error,
934
988
  children: error
package/lib/index.d.ts CHANGED
@@ -1702,12 +1702,32 @@ interface RezMcpServerSpec {
1702
1702
  /** Per-tool-call timeout in milliseconds. */
1703
1703
  toolCallTimeoutMs?: number;
1704
1704
  }
1705
+ /** Company MCP endpoints persisted under ~/.dsh/dsh-rez-suite.json. Secrets are not stored here. */
1706
+ interface RezConnections {
1707
+ odoo: {
1708
+ enabled: boolean;
1709
+ url: string;
1710
+ };
1711
+ nextcloud: {
1712
+ enabled: boolean;
1713
+ url: string;
1714
+ username: string;
1715
+ };
1716
+ wechat: {
1717
+ enabled: boolean;
1718
+ };
1719
+ homeassistant: {
1720
+ enabled: boolean;
1721
+ url: string;
1722
+ };
1723
+ }
1705
1724
  /** Full plugin configuration persisted under ~/.dsh/dsh-rez-suite.json. */
1706
1725
  interface RezConfig {
1707
1726
  enabled: boolean;
1708
1727
  announceToAgent: boolean;
1709
1728
  role: RezRoleId;
1710
1729
  servers: Record<string, RezMcpServerSpec>;
1730
+ connections: RezConnections;
1711
1731
  billing: {
1712
1732
  inputCostPer1k: number;
1713
1733
  outputCostPer1k: number;