@rezti/dsh-rez-suite 0.1.1 → 0.1.2
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 +1 -1
- package/README.zh.md +1 -1
- package/cordis.patch.yml +0 -12
- package/lib/client.d.ts +24 -0
- package/lib/client.js +183 -3
- package/lib/index.js +7 -5
- package/lib/style.css +9 -0
- package/package.json +6 -6
- package/src/client/api.ts +25 -0
- package/src/client/locales.ts +20 -0
- package/src/client/panel/RezPanel.tsx +4 -1
- package/src/client/panel/WeixinTab.tsx +99 -0
- package/src/client/panel/panel.module.css +9 -0
- package/src/index.ts +5 -4
- package/src/presets.ts +0 -5
- package/src/protocol.ts +14 -0
package/README.md
CHANGED
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`。
|
|
10
10
|
|
|
11
|
-
个人微信(QClaw/ClawBot
|
|
11
|
+
个人微信(QClaw/ClawBot)在 Rez 面板「微信」页扫码,然后直接在微信里说话。
|
|
12
12
|
|
|
13
13
|
子包不要再单独 `dsh plugin add`,否则会重复注册 MCP。
|
package/cordis.patch.yml
CHANGED
|
@@ -53,18 +53,6 @@
|
|
|
53
53
|
env:
|
|
54
54
|
WECOM_WEBHOOK_URL: !!js process.env.REZ_WECHAT_WEBHOOK ?? ''
|
|
55
55
|
failOnStartupError: false
|
|
56
|
-
# Personal WeChat (QClaw / ClawBot): reuse weixin-mcp, do not write a WebSocket client.
|
|
57
|
-
# Employee login once: npx weixin-mcp login (微信 → 发现 → 小程序 → ClawBot)
|
|
58
|
-
- id: rez-mcp-weixin
|
|
59
|
-
name: '@deepseek-ai/dsh-mcp-client'
|
|
60
|
-
config:
|
|
61
|
-
serverName: weixin
|
|
62
|
-
transport: stdio
|
|
63
|
-
command: npx
|
|
64
|
-
args:
|
|
65
|
-
- -y
|
|
66
|
-
- weixin-mcp
|
|
67
|
-
failOnStartupError: false
|
|
68
56
|
- id: rez-ha-bridge
|
|
69
57
|
name: '@rezti/dsh-rez-ha-bridge'
|
|
70
58
|
- id: rez-mcp-homeassistant
|
package/lib/client.d.ts
CHANGED
|
@@ -7256,6 +7256,7 @@ declare const zh: {
|
|
|
7256
7256
|
readonly 'entry.tooltip': "Rez Suite 配置与审计面板";
|
|
7257
7257
|
readonly 'panel.title': "Rez Suite";
|
|
7258
7258
|
readonly 'tab.config': "配置";
|
|
7259
|
+
readonly 'tab.weixin': "微信";
|
|
7259
7260
|
readonly 'tab.status': "状态";
|
|
7260
7261
|
readonly 'tab.audit': "用量";
|
|
7261
7262
|
readonly 'config.role': "角色";
|
|
@@ -7327,6 +7328,15 @@ declare const zh: {
|
|
|
7327
7328
|
readonly 'common.loading': "加载中…";
|
|
7328
7329
|
readonly 'common.error': "出错:{error}";
|
|
7329
7330
|
readonly 'common.refresh': "刷新";
|
|
7331
|
+
readonly 'weixin.intro': "和 QClaw 一样:扫一次码,然后直接在微信里跟本机 dsh 说话。不用再跑命令,也不用记工具名。";
|
|
7332
|
+
readonly 'weixin.login': "扫码登录";
|
|
7333
|
+
readonly 'weixin.logout': "退出微信";
|
|
7334
|
+
readonly 'weixin.verify': "提交配对码";
|
|
7335
|
+
readonly 'weixin.verifyPlaceholder': "手机上显示的数字";
|
|
7336
|
+
readonly 'weixin.qrAlt': "微信登录二维码";
|
|
7337
|
+
readonly 'weixin.listening': "通道在听:微信发消息 → 本机 dsh → 回微信。";
|
|
7338
|
+
readonly 'weixin.loggedIn': "已登录,通道未在听。再点扫码登录即可拉起。";
|
|
7339
|
+
readonly 'weixin.loggedOut': "尚未登录。";
|
|
7330
7340
|
};
|
|
7331
7341
|
type RezKey = keyof typeof zh;
|
|
7332
7342
|
//#endregion
|
|
@@ -7421,6 +7431,16 @@ interface RezStatusResponse {
|
|
|
7421
7431
|
servers: RezServerStatus[];
|
|
7422
7432
|
totalRegisteredTools: number;
|
|
7423
7433
|
}
|
|
7434
|
+
/** QClaw-style WeChat channel status (scan QR, then talk in WeChat). */
|
|
7435
|
+
type RezWeixinPhase = 'idle' | 'login' | 'need_verify' | 'listening' | 'error';
|
|
7436
|
+
interface RezWeixinStatus {
|
|
7437
|
+
loggedIn: boolean;
|
|
7438
|
+
listening: boolean;
|
|
7439
|
+
phase: RezWeixinPhase;
|
|
7440
|
+
qrContent?: string;
|
|
7441
|
+
qrDataUrl?: string;
|
|
7442
|
+
hint: string;
|
|
7443
|
+
}
|
|
7424
7444
|
/** One audit ledger row. */
|
|
7425
7445
|
interface RezAuditRecord {
|
|
7426
7446
|
id: number;
|
|
@@ -7459,6 +7479,10 @@ declare class RezApi {
|
|
|
7459
7479
|
status(): Promise<RezStatusResponse>;
|
|
7460
7480
|
audit(): Promise<RezAuditResponse>;
|
|
7461
7481
|
resetAudit(): Promise<void>;
|
|
7482
|
+
weixinStatus(): Promise<RezWeixinStatus>;
|
|
7483
|
+
weixinLogin(): Promise<RezWeixinStatus>;
|
|
7484
|
+
weixinVerify(code: string): Promise<RezWeixinStatus>;
|
|
7485
|
+
weixinLogout(): Promise<RezWeixinStatus>;
|
|
7462
7486
|
}
|
|
7463
7487
|
//#endregion
|
|
7464
7488
|
//#region src/client/panel/RezPanel.d.ts
|
package/lib/client.js
CHANGED
|
@@ -14,7 +14,9 @@ const REZ_API = {
|
|
|
14
14
|
test: "/api/dsh-rez-suite/test",
|
|
15
15
|
status: "/api/dsh-rez-suite/status",
|
|
16
16
|
audit: "/api/dsh-rez-suite/audit",
|
|
17
|
-
auditReset: "/api/dsh-rez-suite/audit/reset"
|
|
17
|
+
auditReset: "/api/dsh-rez-suite/audit/reset",
|
|
18
|
+
weixin: "/api/dsh-rez-suite/weixin",
|
|
19
|
+
weixinVerify: "/api/dsh-rez-suite/weixin/verify"
|
|
18
20
|
};
|
|
19
21
|
//#endregion
|
|
20
22
|
//#region src/client/api.ts
|
|
@@ -60,6 +62,22 @@ var RezApi = class {
|
|
|
60
62
|
async resetAudit() {
|
|
61
63
|
await readJson(await fetch(REZ_API.auditReset, { method: "POST" }));
|
|
62
64
|
}
|
|
65
|
+
async weixinStatus() {
|
|
66
|
+
return readJson(await fetch(REZ_API.weixin));
|
|
67
|
+
}
|
|
68
|
+
async weixinLogin() {
|
|
69
|
+
return readJson(await fetch(REZ_API.weixin, { method: "POST" }));
|
|
70
|
+
}
|
|
71
|
+
async weixinVerify(code) {
|
|
72
|
+
return readJson(await fetch(REZ_API.weixinVerify, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: { "content-type": "application/json" },
|
|
75
|
+
body: JSON.stringify({ code })
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
async weixinLogout() {
|
|
79
|
+
return readJson(await fetch(REZ_API.weixin, { method: "DELETE" }));
|
|
80
|
+
}
|
|
63
81
|
};
|
|
64
82
|
//#endregion
|
|
65
83
|
//#region src/client/locales.ts
|
|
@@ -71,6 +89,7 @@ const zh = {
|
|
|
71
89
|
"entry.tooltip": "Rez Suite 配置与审计面板",
|
|
72
90
|
"panel.title": "Rez Suite",
|
|
73
91
|
"tab.config": "配置",
|
|
92
|
+
"tab.weixin": "微信",
|
|
74
93
|
"tab.status": "状态",
|
|
75
94
|
"tab.audit": "用量",
|
|
76
95
|
"config.role": "角色",
|
|
@@ -141,13 +160,23 @@ const zh = {
|
|
|
141
160
|
"common.close": "关闭",
|
|
142
161
|
"common.loading": "加载中…",
|
|
143
162
|
"common.error": "出错:{error}",
|
|
144
|
-
"common.refresh": "刷新"
|
|
163
|
+
"common.refresh": "刷新",
|
|
164
|
+
"weixin.intro": "和 QClaw 一样:扫一次码,然后直接在微信里跟本机 dsh 说话。不用再跑命令,也不用记工具名。",
|
|
165
|
+
"weixin.login": "扫码登录",
|
|
166
|
+
"weixin.logout": "退出微信",
|
|
167
|
+
"weixin.verify": "提交配对码",
|
|
168
|
+
"weixin.verifyPlaceholder": "手机上显示的数字",
|
|
169
|
+
"weixin.qrAlt": "微信登录二维码",
|
|
170
|
+
"weixin.listening": "通道在听:微信发消息 → 本机 dsh → 回微信。",
|
|
171
|
+
"weixin.loggedIn": "已登录,通道未在听。再点扫码登录即可拉起。",
|
|
172
|
+
"weixin.loggedOut": "尚未登录。"
|
|
145
173
|
};
|
|
146
174
|
const en = {
|
|
147
175
|
"entry.label": "Rez",
|
|
148
176
|
"entry.tooltip": "Rez Suite configuration and audit panel",
|
|
149
177
|
"panel.title": "Rez Suite",
|
|
150
178
|
"tab.config": "Config",
|
|
179
|
+
"tab.weixin": "WeChat",
|
|
151
180
|
"tab.status": "Status",
|
|
152
181
|
"tab.audit": "Usage",
|
|
153
182
|
"config.role": "Role",
|
|
@@ -218,7 +247,16 @@ const en = {
|
|
|
218
247
|
"common.close": "Close",
|
|
219
248
|
"common.loading": "Loading…",
|
|
220
249
|
"common.error": "Error: {error}",
|
|
221
|
-
"common.refresh": "Refresh"
|
|
250
|
+
"common.refresh": "Refresh",
|
|
251
|
+
"weixin.intro": "Same as QClaw: scan once, then talk in WeChat. No extra CLI, no MCP tool names.",
|
|
252
|
+
"weixin.login": "Scan to log in",
|
|
253
|
+
"weixin.logout": "Log out",
|
|
254
|
+
"weixin.verify": "Submit pairing code",
|
|
255
|
+
"weixin.verifyPlaceholder": "Digits shown on the phone",
|
|
256
|
+
"weixin.qrAlt": "WeChat login QR",
|
|
257
|
+
"weixin.listening": "Channel is live: WeChat → this dsh → WeChat.",
|
|
258
|
+
"weixin.loggedIn": "Logged in, listener idle. Scan again to start it.",
|
|
259
|
+
"weixin.loggedOut": "Not logged in."
|
|
222
260
|
};
|
|
223
261
|
/** Minimal {name} interpolator shared by the panel helpers. */
|
|
224
262
|
function t(dict, key, values) {
|
|
@@ -268,6 +306,7 @@ var panel_module_default = {
|
|
|
268
306
|
"panelHeader": "WRGRRW_panelHeader",
|
|
269
307
|
"panelTitle": "WRGRRW_panelTitle",
|
|
270
308
|
"primaryButton": "WRGRRW_primaryButton",
|
|
309
|
+
"qr": "WRGRRW_qr",
|
|
271
310
|
"section": "WRGRRW_section",
|
|
272
311
|
"sectionTitle": "WRGRRW_sectionTitle",
|
|
273
312
|
"select": "WRGRRW_select",
|
|
@@ -915,6 +954,142 @@ function StatusTab({ api }) {
|
|
|
915
954
|
});
|
|
916
955
|
}
|
|
917
956
|
//#endregion
|
|
957
|
+
//#region src/client/panel/WeixinTab.tsx
|
|
958
|
+
/**
|
|
959
|
+
* WeChat / QClaw tab: scan QR once, then talk in WeChat. No CLI, no MCP tools.
|
|
960
|
+
*/
|
|
961
|
+
function WeixinTab({ api }) {
|
|
962
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
963
|
+
const [error, setError] = (0, react.useState)("");
|
|
964
|
+
const [code, setCode] = (0, react.useState)("");
|
|
965
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
966
|
+
const load = async () => {
|
|
967
|
+
try {
|
|
968
|
+
setStatus(await api.weixinStatus());
|
|
969
|
+
setError("");
|
|
970
|
+
} catch (err) {
|
|
971
|
+
setError(errorMessage(err));
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
(0, react.useEffect)(() => {
|
|
975
|
+
load();
|
|
976
|
+
const timer = setInterval(() => {
|
|
977
|
+
load();
|
|
978
|
+
}, 1500);
|
|
979
|
+
return () => {
|
|
980
|
+
clearInterval(timer);
|
|
981
|
+
};
|
|
982
|
+
}, []);
|
|
983
|
+
const login = async () => {
|
|
984
|
+
setBusy(true);
|
|
985
|
+
try {
|
|
986
|
+
setStatus(await api.weixinLogin());
|
|
987
|
+
setError("");
|
|
988
|
+
} catch (err) {
|
|
989
|
+
setError(errorMessage(err));
|
|
990
|
+
} finally {
|
|
991
|
+
setBusy(false);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
const verify = async () => {
|
|
995
|
+
setBusy(true);
|
|
996
|
+
try {
|
|
997
|
+
setStatus(await api.weixinVerify(code));
|
|
998
|
+
setCode("");
|
|
999
|
+
setError("");
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
setError(errorMessage(err));
|
|
1002
|
+
} finally {
|
|
1003
|
+
setBusy(false);
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
const logout = async () => {
|
|
1007
|
+
setBusy(true);
|
|
1008
|
+
try {
|
|
1009
|
+
setStatus(await api.weixinLogout());
|
|
1010
|
+
setError("");
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
setError(errorMessage(err));
|
|
1013
|
+
} finally {
|
|
1014
|
+
setBusy(false);
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1018
|
+
className: panel_module_default.tabBody,
|
|
1019
|
+
children: [
|
|
1020
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1021
|
+
className: panel_module_default.message,
|
|
1022
|
+
children: tt("weixin.intro")
|
|
1023
|
+
}),
|
|
1024
|
+
error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1025
|
+
className: panel_module_default.error,
|
|
1026
|
+
children: error
|
|
1027
|
+
}),
|
|
1028
|
+
status !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1029
|
+
className: panel_module_default.message,
|
|
1030
|
+
children: status.hint
|
|
1031
|
+
}),
|
|
1032
|
+
status?.qrDataUrl !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
1033
|
+
className: panel_module_default.qr,
|
|
1034
|
+
src: status.qrDataUrl,
|
|
1035
|
+
alt: tt("weixin.qrAlt")
|
|
1036
|
+
}),
|
|
1037
|
+
status?.qrContent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1038
|
+
className: panel_module_default.message,
|
|
1039
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1040
|
+
href: status.qrContent,
|
|
1041
|
+
target: "_blank",
|
|
1042
|
+
rel: "noreferrer",
|
|
1043
|
+
children: status.qrContent
|
|
1044
|
+
})
|
|
1045
|
+
}),
|
|
1046
|
+
status?.phase === "need_verify" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1047
|
+
className: panel_module_default.toolbar,
|
|
1048
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1049
|
+
className: panel_module_default.input,
|
|
1050
|
+
value: code,
|
|
1051
|
+
onChange: (event) => {
|
|
1052
|
+
setCode(event.target.value);
|
|
1053
|
+
},
|
|
1054
|
+
placeholder: tt("weixin.verifyPlaceholder")
|
|
1055
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1056
|
+
type: "button",
|
|
1057
|
+
className: panel_module_default.button,
|
|
1058
|
+
disabled: busy,
|
|
1059
|
+
onClick: () => {
|
|
1060
|
+
verify();
|
|
1061
|
+
},
|
|
1062
|
+
children: tt("weixin.verify")
|
|
1063
|
+
})]
|
|
1064
|
+
}),
|
|
1065
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1066
|
+
className: panel_module_default.toolbar,
|
|
1067
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1068
|
+
type: "button",
|
|
1069
|
+
className: panel_module_default.button,
|
|
1070
|
+
disabled: busy,
|
|
1071
|
+
onClick: () => {
|
|
1072
|
+
login();
|
|
1073
|
+
},
|
|
1074
|
+
children: tt("weixin.login")
|
|
1075
|
+
}), status?.loggedIn === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1076
|
+
type: "button",
|
|
1077
|
+
className: panel_module_default.button,
|
|
1078
|
+
disabled: busy,
|
|
1079
|
+
onClick: () => {
|
|
1080
|
+
logout();
|
|
1081
|
+
},
|
|
1082
|
+
children: tt("weixin.logout")
|
|
1083
|
+
})]
|
|
1084
|
+
}),
|
|
1085
|
+
status !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1086
|
+
className: panel_module_default.message,
|
|
1087
|
+
children: status.listening ? tt("weixin.listening") : status.loggedIn ? tt("weixin.loggedIn") : tt("weixin.loggedOut")
|
|
1088
|
+
})
|
|
1089
|
+
]
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
//#endregion
|
|
918
1093
|
//#region src/client/panel/RezPanel.tsx
|
|
919
1094
|
/**
|
|
920
1095
|
* Rez Suite panel shell: header + three tabs (config/status/audit).
|
|
@@ -924,6 +1099,10 @@ const TABS = [
|
|
|
924
1099
|
id: "config",
|
|
925
1100
|
label: () => tt("tab.config")
|
|
926
1101
|
},
|
|
1102
|
+
{
|
|
1103
|
+
id: "weixin",
|
|
1104
|
+
label: () => tt("tab.weixin")
|
|
1105
|
+
},
|
|
927
1106
|
{
|
|
928
1107
|
id: "status",
|
|
929
1108
|
label: () => tt("tab.status")
|
|
@@ -973,6 +1152,7 @@ function RezPanel({ controller, api }) {
|
|
|
973
1152
|
className: panel_module_default.panelContent,
|
|
974
1153
|
children: [
|
|
975
1154
|
activeTab === "config" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigTab, { api }),
|
|
1155
|
+
activeTab === "weixin" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeixinTab, { api }),
|
|
976
1156
|
activeTab === "status" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTab, { api }),
|
|
977
1157
|
activeTab === "audit" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTab, { api })
|
|
978
1158
|
]
|
package/lib/index.js
CHANGED
|
@@ -2847,7 +2847,9 @@ const REZ_API = {
|
|
|
2847
2847
|
test: "/api/dsh-rez-suite/test",
|
|
2848
2848
|
status: "/api/dsh-rez-suite/status",
|
|
2849
2849
|
audit: "/api/dsh-rez-suite/audit",
|
|
2850
|
-
auditReset: "/api/dsh-rez-suite/audit/reset"
|
|
2850
|
+
auditReset: "/api/dsh-rez-suite/audit/reset",
|
|
2851
|
+
weixin: "/api/dsh-rez-suite/weixin",
|
|
2852
|
+
weixinVerify: "/api/dsh-rez-suite/weixin/verify"
|
|
2851
2853
|
};
|
|
2852
2854
|
//#endregion
|
|
2853
2855
|
//#region src/store.ts
|
|
@@ -7550,13 +7552,13 @@ const COMPOSED_SERVERS = [
|
|
|
7550
7552
|
"odoo",
|
|
7551
7553
|
"nextcloud",
|
|
7552
7554
|
"wechat",
|
|
7553
|
-
"weixin",
|
|
7554
7555
|
"homeassistant"
|
|
7555
7556
|
];
|
|
7556
7557
|
const REZ_GUIDANCE = [
|
|
7557
|
-
"本机已安装 ReZ-TI 套件(一次安装):工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 /
|
|
7558
|
-
"工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、
|
|
7559
|
-
"密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN
|
|
7558
|
+
"本机已安装 ReZ-TI 套件(一次安装):工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。",
|
|
7559
|
+
"工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。",
|
|
7560
|
+
"密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN。",
|
|
7561
|
+
"个人微信(QClaw/ClawBot):打开 Rez 面板「微信」页扫码,然后直接在微信里说话,不必再跑命令、也不要调用微信 MCP 工具。",
|
|
7560
7562
|
"意图用 rez_set_intent(erp/files/chat/home/auto)。审计用 rez_audit。",
|
|
7561
7563
|
"涉及资金、对外发送、设备控制、生产写入时先征得用户批准。"
|
|
7562
7564
|
].join("");
|
package/lib/style.css
CHANGED
|
@@ -219,6 +219,15 @@ html[data-dsh-rez-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-act
|
|
|
219
219
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
.WRGRRW_qr {
|
|
223
|
+
background: #fff;
|
|
224
|
+
border-radius: 8px;
|
|
225
|
+
width: 240px;
|
|
226
|
+
height: 240px;
|
|
227
|
+
margin: 8px 0;
|
|
228
|
+
display: block;
|
|
229
|
+
}
|
|
230
|
+
|
|
222
231
|
.WRGRRW_primaryButton {
|
|
223
232
|
background: var(--dsw-alias-state-business-primary);
|
|
224
233
|
border-color: var(--dsw-alias-state-business-primary);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rezti/dsh-rez-suite",
|
|
3
3
|
"description": "ReZ-TI employee plugin for DeepSeek Harness: one install enables company MCP (Odoo / Nextcloud / WeCom / Weixin-ClawBot / Home Assistant) and work identity.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
38
38
|
"zod": "^4.4.3",
|
|
39
|
+
"@rezti/dsh-rez-nextcloud": "0.1.0",
|
|
40
|
+
"@rezti/dsh-rez-odoo": "0.1.0",
|
|
39
41
|
"@rezti/dsh-rez-ha-bridge": "0.1.0",
|
|
40
|
-
"@rezti/dsh-rez-
|
|
42
|
+
"@rezti/dsh-rez-intent": "0.1.2",
|
|
41
43
|
"@rezti/dsh-rez-token-manager": "0.1.0",
|
|
42
|
-
"@rezti/dsh-rez-wechat": "0.1.
|
|
43
|
-
"@rezti/dsh-rez-
|
|
44
|
-
"@rezti/dsh-rez-nextcloud": "0.1.0",
|
|
45
|
-
"@rezti/dsh-rez-odoo": "0.1.0"
|
|
44
|
+
"@rezti/dsh-rez-wechat": "0.1.2",
|
|
45
|
+
"@rezti/dsh-rez-sso": "0.1.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"react": "^18.2.0",
|
package/src/client/api.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type RezServerStatus,
|
|
10
10
|
type RezStatusResponse,
|
|
11
11
|
type RezTestResult,
|
|
12
|
+
type RezWeixinStatus,
|
|
12
13
|
} from '../protocol.ts'
|
|
13
14
|
|
|
14
15
|
export class RezApiError extends Error {
|
|
@@ -72,4 +73,28 @@ export class RezApi {
|
|
|
72
73
|
const response = await fetch(REZ_API.auditReset, { method: 'POST' })
|
|
73
74
|
await readJson<{ ok: boolean }>(response)
|
|
74
75
|
}
|
|
76
|
+
|
|
77
|
+
async weixinStatus(): Promise<RezWeixinStatus> {
|
|
78
|
+
const response = await fetch(REZ_API.weixin)
|
|
79
|
+
return readJson<RezWeixinStatus>(response)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async weixinLogin(): Promise<RezWeixinStatus> {
|
|
83
|
+
const response = await fetch(REZ_API.weixin, { method: 'POST' })
|
|
84
|
+
return readJson<RezWeixinStatus>(response)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async weixinVerify(code: string): Promise<RezWeixinStatus> {
|
|
88
|
+
const response = await fetch(REZ_API.weixinVerify, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'content-type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ code }),
|
|
92
|
+
})
|
|
93
|
+
return readJson<RezWeixinStatus>(response)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async weixinLogout(): Promise<RezWeixinStatus> {
|
|
97
|
+
const response = await fetch(REZ_API.weixin, { method: 'DELETE' })
|
|
98
|
+
return readJson<RezWeixinStatus>(response)
|
|
99
|
+
}
|
|
75
100
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const zh = {
|
|
|
7
7
|
'entry.tooltip': 'Rez Suite 配置与审计面板',
|
|
8
8
|
'panel.title': 'Rez Suite',
|
|
9
9
|
'tab.config': '配置',
|
|
10
|
+
'tab.weixin': '微信',
|
|
10
11
|
'tab.status': '状态',
|
|
11
12
|
'tab.audit': '用量',
|
|
12
13
|
// config
|
|
@@ -82,6 +83,15 @@ export const zh = {
|
|
|
82
83
|
'common.loading': '加载中…',
|
|
83
84
|
'common.error': '出错:{error}',
|
|
84
85
|
'common.refresh': '刷新',
|
|
86
|
+
'weixin.intro': '和 QClaw 一样:扫一次码,然后直接在微信里跟本机 dsh 说话。不用再跑命令,也不用记工具名。',
|
|
87
|
+
'weixin.login': '扫码登录',
|
|
88
|
+
'weixin.logout': '退出微信',
|
|
89
|
+
'weixin.verify': '提交配对码',
|
|
90
|
+
'weixin.verifyPlaceholder': '手机上显示的数字',
|
|
91
|
+
'weixin.qrAlt': '微信登录二维码',
|
|
92
|
+
'weixin.listening': '通道在听:微信发消息 → 本机 dsh → 回微信。',
|
|
93
|
+
'weixin.loggedIn': '已登录,通道未在听。再点扫码登录即可拉起。',
|
|
94
|
+
'weixin.loggedOut': '尚未登录。',
|
|
85
95
|
} as const
|
|
86
96
|
|
|
87
97
|
export type RezKey = keyof typeof zh
|
|
@@ -91,6 +101,7 @@ export const en: Record<RezKey, string> = {
|
|
|
91
101
|
'entry.tooltip': 'Rez Suite configuration and audit panel',
|
|
92
102
|
'panel.title': 'Rez Suite',
|
|
93
103
|
'tab.config': 'Config',
|
|
104
|
+
'tab.weixin': 'WeChat',
|
|
94
105
|
'tab.status': 'Status',
|
|
95
106
|
'tab.audit': 'Usage',
|
|
96
107
|
'config.role': 'Role',
|
|
@@ -162,6 +173,15 @@ export const en: Record<RezKey, string> = {
|
|
|
162
173
|
'common.loading': 'Loading…',
|
|
163
174
|
'common.error': 'Error: {error}',
|
|
164
175
|
'common.refresh': 'Refresh',
|
|
176
|
+
'weixin.intro': 'Same as QClaw: scan once, then talk in WeChat. No extra CLI, no MCP tool names.',
|
|
177
|
+
'weixin.login': 'Scan to log in',
|
|
178
|
+
'weixin.logout': 'Log out',
|
|
179
|
+
'weixin.verify': 'Submit pairing code',
|
|
180
|
+
'weixin.verifyPlaceholder': 'Digits shown on the phone',
|
|
181
|
+
'weixin.qrAlt': 'WeChat login QR',
|
|
182
|
+
'weixin.listening': 'Channel is live: WeChat → this dsh → WeChat.',
|
|
183
|
+
'weixin.loggedIn': 'Logged in, listener idle. Scan again to start it.',
|
|
184
|
+
'weixin.loggedOut': 'Not logged in.',
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
/** Minimal {name} interpolator shared by the panel helpers. */
|
|
@@ -9,9 +9,10 @@ import { tt } from './helpers.ts'
|
|
|
9
9
|
import { AuditTab } from './AuditTab.tsx'
|
|
10
10
|
import { ConfigTab } from './ConfigTab.tsx'
|
|
11
11
|
import { StatusTab } from './StatusTab.tsx'
|
|
12
|
+
import { WeixinTab } from './WeixinTab.tsx'
|
|
12
13
|
import css from './panel.module.css'
|
|
13
14
|
|
|
14
|
-
export type RezTab = 'config' | 'status' | 'audit'
|
|
15
|
+
export type RezTab = 'config' | 'weixin' | 'status' | 'audit'
|
|
15
16
|
|
|
16
17
|
export interface RezPanelProps {
|
|
17
18
|
controller: PanelController
|
|
@@ -20,6 +21,7 @@ export interface RezPanelProps {
|
|
|
20
21
|
|
|
21
22
|
const TABS: ReadonlyArray<{ id: RezTab; label: () => string }> = [
|
|
22
23
|
{ id: 'config', label: () => tt('tab.config') },
|
|
24
|
+
{ id: 'weixin', label: () => tt('tab.weixin') },
|
|
23
25
|
{ id: 'status', label: () => tt('tab.status') },
|
|
24
26
|
{ id: 'audit', label: () => tt('tab.audit') },
|
|
25
27
|
]
|
|
@@ -42,6 +44,7 @@ export function RezPanel({ controller, api }: RezPanelProps) {
|
|
|
42
44
|
</div>
|
|
43
45
|
<div className={css.panelContent}>
|
|
44
46
|
{activeTab === 'config' && <ConfigTab api={api} />}
|
|
47
|
+
{activeTab === 'weixin' && <WeixinTab api={api} />}
|
|
45
48
|
{activeTab === 'status' && <StatusTab api={api} />}
|
|
46
49
|
{activeTab === 'audit' && <AuditTab api={api} />}
|
|
47
50
|
</div>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat / QClaw tab: scan QR once, then talk in WeChat. No CLI, no MCP tools.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useEffect, useState } from 'react'
|
|
6
|
+
import type { RezApi } from '../api.ts'
|
|
7
|
+
import type { RezWeixinStatus } from '../../protocol.ts'
|
|
8
|
+
import { errorMessage, tt } from './helpers.ts'
|
|
9
|
+
import css from './panel.module.css'
|
|
10
|
+
|
|
11
|
+
export function WeixinTab({ api }: { api: RezApi }) {
|
|
12
|
+
const [status, setStatus] = useState<RezWeixinStatus | null>(null)
|
|
13
|
+
const [error, setError] = useState('')
|
|
14
|
+
const [code, setCode] = useState('')
|
|
15
|
+
const [busy, setBusy] = useState(false)
|
|
16
|
+
|
|
17
|
+
const load = async (): Promise<void> => {
|
|
18
|
+
try {
|
|
19
|
+
setStatus(await api.weixinStatus())
|
|
20
|
+
setError('')
|
|
21
|
+
} catch (err) {
|
|
22
|
+
setError(errorMessage(err))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
void load()
|
|
28
|
+
const timer = setInterval(() => { void load() }, 1500)
|
|
29
|
+
return () => { clearInterval(timer) }
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
const login = async (): Promise<void> => {
|
|
33
|
+
setBusy(true)
|
|
34
|
+
try {
|
|
35
|
+
setStatus(await api.weixinLogin())
|
|
36
|
+
setError('')
|
|
37
|
+
} catch (err) {
|
|
38
|
+
setError(errorMessage(err))
|
|
39
|
+
} finally {
|
|
40
|
+
setBusy(false)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const verify = async (): Promise<void> => {
|
|
45
|
+
setBusy(true)
|
|
46
|
+
try {
|
|
47
|
+
setStatus(await api.weixinVerify(code))
|
|
48
|
+
setCode('')
|
|
49
|
+
setError('')
|
|
50
|
+
} catch (err) {
|
|
51
|
+
setError(errorMessage(err))
|
|
52
|
+
} finally {
|
|
53
|
+
setBusy(false)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const logout = async (): Promise<void> => {
|
|
58
|
+
setBusy(true)
|
|
59
|
+
try {
|
|
60
|
+
setStatus(await api.weixinLogout())
|
|
61
|
+
setError('')
|
|
62
|
+
} catch (err) {
|
|
63
|
+
setError(errorMessage(err))
|
|
64
|
+
} finally {
|
|
65
|
+
setBusy(false)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className={css.tabBody}>
|
|
71
|
+
<p className={css.message}>{tt('weixin.intro')}</p>
|
|
72
|
+
{error !== '' && <div className={css.error}>{error}</div>}
|
|
73
|
+
{status !== null && <p className={css.message}>{status.hint}</p>}
|
|
74
|
+
{status?.qrDataUrl !== undefined && (
|
|
75
|
+
<img className={css.qr} src={status.qrDataUrl} alt={tt('weixin.qrAlt')} />
|
|
76
|
+
)}
|
|
77
|
+
{status?.qrContent !== undefined && (
|
|
78
|
+
<p className={css.message}><a href={status.qrContent} target="_blank" rel="noreferrer">{status.qrContent}</a></p>
|
|
79
|
+
)}
|
|
80
|
+
{status?.phase === 'need_verify' && (
|
|
81
|
+
<div className={css.toolbar}>
|
|
82
|
+
<input className={css.input} value={code} onChange={event => { setCode(event.target.value) }} placeholder={tt('weixin.verifyPlaceholder')} />
|
|
83
|
+
<button type="button" className={css.button} disabled={busy} onClick={() => { void verify() }}>{tt('weixin.verify')}</button>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
<div className={css.toolbar}>
|
|
87
|
+
<button type="button" className={css.button} disabled={busy} onClick={() => { void login() }}>{tt('weixin.login')}</button>
|
|
88
|
+
{status?.loggedIn === true && (
|
|
89
|
+
<button type="button" className={css.button} disabled={busy} onClick={() => { void logout() }}>{tt('weixin.logout')}</button>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
{status !== null && (
|
|
93
|
+
<p className={css.message}>
|
|
94
|
+
{status.listening ? tt('weixin.listening') : status.loggedIn ? tt('weixin.loggedIn') : tt('weixin.loggedOut')}
|
|
95
|
+
</p>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -226,6 +226,15 @@ html[data-dsh-rez-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-act
|
|
|
226
226
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
.qr {
|
|
230
|
+
display: block;
|
|
231
|
+
width: 240px;
|
|
232
|
+
height: 240px;
|
|
233
|
+
margin: 8px 0;
|
|
234
|
+
background: #fff;
|
|
235
|
+
border-radius: 8px;
|
|
236
|
+
}
|
|
237
|
+
|
|
229
238
|
.primaryButton {
|
|
230
239
|
background: var(--dsw-alias-state-business-primary);
|
|
231
240
|
border-color: var(--dsw-alias-state-business-primary);
|
package/src/index.ts
CHANGED
|
@@ -54,12 +54,13 @@ export const Config: z<RezConfig> = z.object({
|
|
|
54
54
|
|
|
55
55
|
const SECTION_ORDER = 150
|
|
56
56
|
|
|
57
|
-
const COMPOSED_SERVERS = ['odoo', 'nextcloud', 'wechat', '
|
|
57
|
+
const COMPOSED_SERVERS = ['odoo', 'nextcloud', 'wechat', 'homeassistant'] as const
|
|
58
58
|
|
|
59
59
|
export const REZ_GUIDANCE = [
|
|
60
|
-
'本机已安装 ReZ-TI 套件(一次安装):工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 /
|
|
61
|
-
'工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、
|
|
62
|
-
'密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN
|
|
60
|
+
'本机已安装 ReZ-TI 套件(一次安装):工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。',
|
|
61
|
+
'工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。',
|
|
62
|
+
'密钥:REZ_ODOO_TOKEN、REZ_NEXTCLOUD_PASSWORD、REZ_WECHAT_WEBHOOK、REZ_HA_TOKEN。',
|
|
63
|
+
'个人微信(QClaw/ClawBot):打开 Rez 面板「微信」页扫码,然后直接在微信里说话,不必再跑命令、也不要调用微信 MCP 工具。',
|
|
63
64
|
'意图用 rez_set_intent(erp/files/chat/home/auto)。审计用 rez_audit。',
|
|
64
65
|
'涉及资金、对外发送、设备控制、生产写入时先征得用户批准。',
|
|
65
66
|
].join('')
|
package/src/presets.ts
CHANGED
|
@@ -60,9 +60,6 @@ const RULES: Record<Exclude<RezRoleId, 'all'>, RoleRule[]> = {
|
|
|
60
60
|
// WeCom one-to-one / group sends.
|
|
61
61
|
{ server: 'wecom', pattern: '*send*' },
|
|
62
62
|
{ server: 'wecom', pattern: '*message*' },
|
|
63
|
-
{ server: 'wechat', pattern: '*send*' },
|
|
64
|
-
{ server: 'weixin', pattern: '*send*' },
|
|
65
|
-
{ server: 'weixin', pattern: '*poll*' },
|
|
66
63
|
],
|
|
67
64
|
operations: [
|
|
68
65
|
// Odoo reporting and fulfilment.
|
|
@@ -92,8 +89,6 @@ const RULES: Record<Exclude<RezRoleId, 'all'>, RoleRule[]> = {
|
|
|
92
89
|
// WeCom broadcasts.
|
|
93
90
|
{ server: 'wecom', pattern: '*broadcast*' },
|
|
94
91
|
{ server: 'wecom', pattern: '*send*' },
|
|
95
|
-
{ server: 'wechat', pattern: '*send*' },
|
|
96
|
-
{ server: 'weixin', pattern: '*send*' },
|
|
97
92
|
// Local audit cache.
|
|
98
93
|
{ server: 'sqlite', pattern: '*query*' },
|
|
99
94
|
{ server: 'sqlite', pattern: '*maintenance*' },
|
package/src/protocol.ts
CHANGED
|
@@ -89,6 +89,18 @@ export interface RezStatusResponse {
|
|
|
89
89
|
totalRegisteredTools: number
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/** QClaw-style WeChat channel status (scan QR, then talk in WeChat). */
|
|
93
|
+
export type RezWeixinPhase = 'idle' | 'login' | 'need_verify' | 'listening' | 'error'
|
|
94
|
+
|
|
95
|
+
export interface RezWeixinStatus {
|
|
96
|
+
loggedIn: boolean
|
|
97
|
+
listening: boolean
|
|
98
|
+
phase: RezWeixinPhase
|
|
99
|
+
qrContent?: string
|
|
100
|
+
qrDataUrl?: string
|
|
101
|
+
hint: string
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
/** One audit ledger row. */
|
|
93
105
|
export interface RezAuditRecord {
|
|
94
106
|
id: number
|
|
@@ -130,4 +142,6 @@ export const REZ_API = {
|
|
|
130
142
|
status: REZ_API_BASE + '/status',
|
|
131
143
|
audit: REZ_API_BASE + '/audit',
|
|
132
144
|
auditReset: REZ_API_BASE + '/audit/reset',
|
|
145
|
+
weixin: REZ_API_BASE + '/weixin',
|
|
146
|
+
weixinVerify: REZ_API_BASE + '/weixin/verify',
|
|
133
147
|
} as const
|