feat(clawbot): add native WeChat ClawBot protocol engine (clawbot.py) and WechatClawModal UI

Browse Source
This commit is contained in:
Yin Kou committed 2026-11-07 00:51:38 +08:00
1 parent 07d2050c5f
commit 8fa46a2b8d
14 files changed
+1551 -201

No files matched your search

+331
View File
@@ -14,10 +14,12 @@
"@radix-ui/react-slot": "^1.5.0", "@radix-ui/react-slot": "^1.5.0",
"@radix-ui/react-switch": "^1.4.1", "@radix-ui/react-switch": "^1.4.1",
"@radix-ui/react-tabs": "^1.2.1", "@radix-ui/react-tabs": "^1.2.1",
"@types/qrcode": "^1.5.6",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"lucide-react": "^0.475.0", "lucide-react": "^0.475.0",
"qrcode": "^1.5.4",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.1",
@@ -2148,6 +2150,24 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": {
"version": "26.6.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.6.tgz",
"integrity": "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==",
"license": "MIT",
"dependencies": {
"undici-types": "~8.9.0"
}
},
"node_modules/@types/qrcode": {
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
"integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.3.0", "version": "19.3.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
@@ -2189,6 +2209,30 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
} }
}, },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/aria-hidden": { "node_modules/aria-hidden": {
"version": "1.2.6", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -2248,6 +2292,15 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001823", "version": "1.0.30001823",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001823.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001823.tgz",
@@ -2281,6 +2334,17 @@
"url": "https://polar.sh/cva" "url": "https://polar.sh/cva"
} }
}, },
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/clsx": { "node_modules/clsx": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -2306,6 +2370,24 @@
"react-dom": "^18 || ^19 || ^19.0.0-rc" "react-dom": "^18 || ^19 || ^19.0.0-rc"
} }
}, },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -2338,6 +2420,15 @@
} }
} }
}, },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2354,6 +2445,12 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.478", "version": "1.5.478",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.478.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.478.tgz",
@@ -2361,6 +2458,12 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.26.1", "version": "5.26.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.1.tgz",
@@ -2445,6 +2548,19 @@
} }
} }
}, },
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2470,6 +2586,15 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-nonce": { "node_modules/get-nonce": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
@@ -2486,6 +2611,15 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
@@ -2802,6 +2936,18 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -2867,6 +3013,51 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2887,6 +3078,15 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.32", "version": "8.5.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
@@ -2916,6 +3116,23 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.3.1", "version": "19.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz",
@@ -3016,6 +3233,21 @@
} }
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.67.5", "version": "4.67.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.67.5.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.67.5.tgz",
@@ -3078,6 +3310,12 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
@@ -3088,6 +3326,32 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tailwind-merge": { "node_modules/tailwind-merge": {
"version": "3.7.0", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz",
@@ -3156,6 +3420,12 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/undici-types": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
"license": "MIT"
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.3.4", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
@@ -3305,6 +3575,32 @@
} }
} }
}, },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -3312,6 +3608,41 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/zustand": { "node_modules/zustand": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.1.0.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.1.0.tgz",
+2
View File
@@ -15,10 +15,12 @@
"@radix-ui/react-slot": "^1.5.0", "@radix-ui/react-slot": "^1.5.0",
"@radix-ui/react-switch": "^1.4.1", "@radix-ui/react-switch": "^1.4.1",
"@radix-ui/react-tabs": "^1.2.1", "@radix-ui/react-tabs": "^1.2.1",
"@types/qrcode": "^1.5.6",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"lucide-react": "^0.475.0", "lucide-react": "^0.475.0",
"qrcode": "^1.5.4",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.1",
+4
View File
@@ -6,6 +6,7 @@ import { SshShelf } from './components/SshShelf';
import { CommandPalette } from './components/CommandPalette'; import { CommandPalette } from './components/CommandPalette';
import { AgyTerminalModal } from './components/AgyTerminalModal'; import { AgyTerminalModal } from './components/AgyTerminalModal';
import { AuthGateCard } from './components/AuthGateModal'; import { AuthGateCard } from './components/AuthGateModal';
import { WechatClawModal } from './components/WechatClawModal';
import { StorybookWorkbench } from './components/StorybookWorkbench'; import { StorybookWorkbench } from './components/StorybookWorkbench';
import { LAYOUT_PRESETS } from './design-system/tokens'; import { LAYOUT_PRESETS } from './design-system/tokens';
import type { ServiceGroup } from './types/telemetry'; import type { ServiceGroup } from './types/telemetry';
@@ -244,6 +245,9 @@ export const App: React.FC = () => {
{/* 5. Embedded AGY Cloud Terminal Modal */} {/* 5. Embedded AGY Cloud Terminal Modal */}
<AgyTerminalModal /> <AgyTerminalModal />
{/* 5b. WeChat ClawBot QR Code & Live Status Modal */}
<WechatClawModal />
{/* 6. Custom SSO Auth Gate Modal (Replaces Browser Native Basic Auth Popup!) */} {/* 6. Custom SSO Auth Gate Modal (Replaces Browser Native Basic Auth Popup!) */}
{authRedirect && ( {authRedirect && (
<div className="fixed inset-0 z-50 bg-black/75 backdrop-blur-sm flex items-center justify-center p-4"> <div className="fixed inset-0 z-50 bg-black/75 backdrop-blur-sm flex items-center justify-center p-4">
+17 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { BookOpen, LayoutGrid, Moon, Search, Sun, Terminal } from 'lucide-react'; import { BookOpen, LayoutGrid, MessageSquare, Moon, Search, Sun, Terminal } from 'lucide-react';
import { usePortalStore } from '../store/usePortalStore'; import { usePortalStore } from '../store/usePortalStore';
import type { RouteModeId } from '../types/telemetry'; import type { RouteModeId } from '../types/telemetry';
import { Button } from './ui/button'; import { Button } from './ui/button';
@@ -24,6 +24,7 @@ export const TopRibbon: React.FC = () => {
setRouteMode, setRouteMode,
setViewMode, setViewMode,
setShowAgyModal, setShowAgyModal,
setShowWechatModal,
setShowCmdPalette, setShowCmdPalette,
} = usePortalStore(); } = usePortalStore();
@@ -98,6 +99,21 @@ export const TopRibbon: React.FC = () => {
</kbd> </kbd>
</Button> </Button>
<Button
variant="outline"
onClick={() => setShowWechatModal(true)}
title="连接微信 ClawBot,在微信内直接收发消息与控制 Mac mini"
>
<span
className="w-2 h-2 rounded-full"
style={{
backgroundColor: telemetry.services.clawbot ? 'var(--success)' : 'var(--accent)',
}}
/>
<MessageSquare className="w-3.5 h-3.5" />
<span>微信 ClawBot</span>
</Button>
<Button onClick={() => setShowAgyModal(true)}> <Button onClick={() => setShowAgyModal(true)}>
<Terminal className="w-3.5 h-3.5" /> <Terminal className="w-3.5 h-3.5" />
<span>AGY 终端</span> <span>AGY 终端</span>
@@ -0,0 +1,301 @@
import React, { useEffect, useState } from 'react';
import QRCode from 'qrcode';
import {
CheckCircle2,
MessageSquare,
QrCode,
RefreshCw,
Send,
Smartphone,
Unplug,
} from 'lucide-react';
import { usePortalStore } from '../store/usePortalStore';
import type { ClawbotPublicStatus } from '../types/telemetry';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { Dialog, DialogContent, DialogDescription, DialogTitle } from './ui/dialog';
import { Input } from './ui/input';
export const WechatClawModal: React.FC = () => {
const { showWechatModal, setShowWechatModal, showToast, fetchTelemetry } = usePortalStore();
const [statusData, setStatusData] = useState<ClawbotPublicStatus | null>(null);
const [qrDataUrl, setQrDataUrl] = useState<string>('');
const [loadingQr, setLoadingQr] = useState(false);
const [verifyCode, setVerifyCode] = useState('');
const [submittingCode, setSubmittingCode] = useState(false);
const generateNewQr = async () => {
setLoadingQr(true);
try {
const res = await fetch('/api/clawbot/qrcode', { method: 'POST' });
const data = await res.json();
setStatusData(data);
const url = data?.login?.qrcode_url;
if (url) {
const svgUrl = await QRCode.toDataURL(url, {
width: 240,
margin: 2,
color: { dark: '#09090b', light: '#ffffff' },
});
setQrDataUrl(svgUrl);
}
} catch {
showToast('获取微信 ClawBot 二维码失败');
} finally {
setLoadingQr(false);
}
};
// Initial load & polling while modal is open
useEffect(() => {
if (!showWechatModal) return;
let cancelled = false;
const init = async () => {
try {
const r = await fetch('/api/clawbot/status');
const d: ClawbotPublicStatus = await r.json();
if (cancelled) return;
setStatusData(d);
if (!d.connected && (!d.login?.active || !d.login?.qrcode_url)) {
await generateNewQr();
} else if (d.login?.qrcode_url) {
const svgUrl = await QRCode.toDataURL(d.login.qrcode_url, {
width: 240,
margin: 2,
color: { dark: '#09090b', light: '#ffffff' },
});
if (!cancelled) setQrDataUrl(svgUrl);
}
} catch {
// ignore
}
};
init();
const timer = setInterval(async () => {
try {
const r = await fetch('/api/clawbot/status');
if (!r.ok) return;
const d: ClawbotPublicStatus = await r.json();
if (cancelled) return;
setStatusData(d);
if (d.login?.qrcode_url) {
const svgUrl = await QRCode.toDataURL(d.login.qrcode_url, {
width: 240,
margin: 2,
color: { dark: '#09090b', light: '#ffffff' },
});
if (!cancelled) setQrDataUrl(svgUrl);
}
} catch {
// ignore
}
}, 1500);
return () => {
cancelled = true;
clearInterval(timer);
};
}, [showWechatModal]);
const handleVerifySubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!verifyCode.trim()) return;
setSubmittingCode(true);
try {
const res = await fetch('/api/clawbot/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: verifyCode.trim() }),
});
const data = await res.json();
setStatusData(data);
setVerifyCode('');
showToast('配对码已提交,正在等待微信验证...');
} catch {
showToast('配对码提交失败');
} finally {
setSubmittingCode(false);
}
};
const handleDisconnect = async () => {
try {
const res = await fetch('/api/clawbot/disconnect', { method: 'POST' });
const data = await res.json();
setStatusData(data);
setQrDataUrl('');
fetchTelemetry();
showToast('已断开微信 ClawBot 连接');
} catch {
showToast('断开失败');
}
};
const connected = Boolean(statusData?.connected);
const loginState = statusData?.login;
return (
<Dialog open={showWechatModal} onOpenChange={setShowWechatModal}>
<DialogContent className="max-w-lg p-6 space-y-5">
{/* Header */}
<div className="flex items-start justify-between gap-3 border-b hl-border pb-4">
<div className="flex items-center gap-3">
<div
className="w-10 h-10 rounded-xl flex items-center justify-center border hl-border shrink-0"
style={{ backgroundColor: 'rgba(16, 185, 129, 0.14)', color: 'var(--success)' }}
>
<MessageSquare className="w-5 h-5" />
</div>
<div>
<div className="flex items-center gap-2">
<DialogTitle className="text-sm font-bold">
微信 ClawBot · M6 Mac mini 直连通道
</DialogTitle>
<Badge variant={connected ? 'success' : 'accent'}>
{connected ? '已连接在线' : '扫码绑定'}
</Badge>
</div>
<DialogDescription className="text-xs font-mono-num mt-0.5">
@tencent-weixin/openclaw-weixin · ilinkai.weixin.qq.com
</DialogDescription>
</div>
</div>
</div>
{/* Body: Connected vs QR Login */}
{connected ? (
<div className="space-y-4">
<div
className="p-4 rounded-xl border flex items-center justify-between"
style={{
backgroundColor: 'var(--success-soft)',
borderColor: 'rgba(16, 185, 129, 0.3)',
}}
>
<div className="flex items-center gap-3">
<CheckCircle2 className="w-5 h-5 shrink-0" style={{ color: 'var(--success)' }} />
<div>
<div className="text-xs font-bold">微信 ClawBot 长轮询守护线程运行中</div>
<div className="text-[11px] font-mono-num mt-0.5" style={{ color: 'var(--text-muted)' }}>
Bot ID: {statusData?.bot_id} · 累计收发: {statusData?.msg_count ?? 0} 条
</div>
</div>
</div>
<Button variant="outline" size="sm" onClick={handleDisconnect}>
<Unplug className="w-3.5 h-3.5" />
<span>重新绑定</span>
</Button>
</div>
<div className="p-4 rounded-xl hl-muted border hl-border space-y-2.5 text-xs">
<div className="font-semibold">📱 现在打开手机微信「微信 ClawBot」可直接发送:</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 font-mono-num text-[11px]">
<div className="p-2 rounded hl-card">
<span style={{ color: 'var(--accent)' }}>状态</span> · 查看 M6 & 极空间遥测
</div>
<div className="p-2 rounded hl-card">
<span style={{ color: 'var(--accent)' }}>备份</span> · 立即打包备份至 20TB NAS
</div>
<div className="p-2 rounded hl-card">
<span style={{ color: 'var(--accent)' }}>屏幕</span> · 截取 AGY 终端实时画面
</div>
<div className="p-2 rounded hl-card">
<span style={{ color: 'var(--accent)' }}>$ df -h</span> · 执行任意 Mac mini 命令
</div>
<div className="p-2 rounded hl-card sm:col-span-2">
<span style={{ color: 'var(--accent)' }}>&gt; 你好</span> 或直接发任意问题 · 注入 AGY 终端或调用 AI 回答
</div>
</div>
</div>
</div>
) : (
<div className="space-y-4">
<div className="flex flex-col items-center justify-center p-5 rounded-xl hl-muted border hl-border space-y-3">
{qrDataUrl ? (
<div className="p-3 bg-white rounded-xl shadow-md border">
<img
src={qrDataUrl}
alt="WeChat ClawBot QR Code"
className="w-48 h-48 block"
/>
</div>
) : (
<div className="w-48 h-48 rounded-xl hl-card flex items-center justify-center text-xs font-mono-num">
{loadingQr ? '正在向微信申请二维码...' : '点击下方按钮生成二维码'}
</div>
)}
<div className="flex items-center gap-2 text-xs font-medium text-center">
<Smartphone className="w-4 h-4 shrink-0" style={{ color: 'var(--accent)' }} />
<span>{loginState?.message || '请使用手机微信扫描上方二维码启用 ClawBot'}</span>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={generateNewQr}
disabled={loadingQr}
>
<RefreshCw className="w-3.5 h-3.5" />
<span>{loadingQr ? '刷新中...' : '刷新二维码'}</span>
</Button>
{loginState?.qrcode_url && (
<Button variant="ghost" size="sm" asChild>
<a
href={loginState.qrcode_url}
target="_blank"
rel="noopener noreferrer"
className="font-mono-num"
>
<QrCode className="w-3.5 h-3.5" />
<span>打开备用链接 ↗</span>
</a>
</Button>
)}
</div>
</div>
{/* Verify Code Input (if WeChat prompts for pairing digits) */}
{loginState?.status === 'need_verifycode' && (
<form
onSubmit={handleVerifySubmit}
className="p-4 rounded-xl border space-y-2.5"
style={{
backgroundColor: 'var(--accent-soft)',
borderColor: 'var(--accent)',
}}
>
<label className="block text-xs font-bold">
🔢 手机微信上显示了配对数字,请在下方输入:
</label>
<div className="flex items-center gap-2">
<Input
type="text"
value={verifyCode}
onChange={(e) => setVerifyCode(e.target.value)}
placeholder="输入手机微信显示的 6 位数字..."
className="font-mono-num"
autoFocus
/>
<Button type="submit" disabled={submittingCode}>
<Send className="w-3.5 h-3.5" />
<span>验证</span>
</Button>
</div>
</form>
)}
</div>
)}
<div className="flex items-center justify-end pt-1">
<Button variant="outline" onClick={() => setShowWechatModal(false)}>
关闭窗口
</Button>
</div>
</DialogContent>
</Dialog>
);
};
@@ -9,6 +9,7 @@ interface PortalState {
layoutPreset: LayoutPresetId; layoutPreset: LayoutPresetId;
viewMode: 'dashboard' | 'storybook'; viewMode: 'dashboard' | 'storybook';
showAgyModal: boolean; showAgyModal: boolean;
showWechatModal: boolean;
showCmdPalette: boolean; showCmdPalette: boolean;
toast: string; toast: string;
actionLoading: boolean; actionLoading: boolean;
@@ -19,6 +20,7 @@ interface PortalState {
setLayoutPreset: (preset: LayoutPresetId) => void; setLayoutPreset: (preset: LayoutPresetId) => void;
setViewMode: (view: 'dashboard' | 'storybook') => void; setViewMode: (view: 'dashboard' | 'storybook') => void;
setShowAgyModal: (open: boolean) => void; setShowAgyModal: (open: boolean) => void;
setShowWechatModal: (open: boolean) => void;
setShowCmdPalette: (open: boolean) => void; setShowCmdPalette: (open: boolean) => void;
showToast: (msg: string) => void; showToast: (msg: string) => void;
fetchTelemetry: () => Promise<void>; fetchTelemetry: () => Promise<void>;
@@ -34,11 +36,11 @@ const getSystemDark = () =>
const applyDomTheme = (mode: ThemeId): 'light' | 'dark' => { const applyDomTheme = (mode: ThemeId): 'light' | 'dark' => {
const isDark = mode === 'system' ? getSystemDark() : mode === 'dark'; const isDark = mode === 'system' ? getSystemDark() : mode === 'dark';
const domTheme = isDark ? 'catppuccin' : 'zinc-light'; const domTheme = isDark ? 'dark' : 'light';
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
document.documentElement.setAttribute('data-theme', domTheme); document.documentElement.setAttribute('data-theme', domTheme);
} }
return isDark ? 'dark' : 'light'; return domTheme;
}; };
const storedTheme = localStorage.getItem('hl_theme_v2') as ThemeId | null; const storedTheme = localStorage.getItem('hl_theme_v2') as ThemeId | null;
@@ -62,6 +64,7 @@ export const usePortalStore = create<PortalState>((set, get) => ({
layoutPreset: initialLayout, layoutPreset: initialLayout,
viewMode: initialView, viewMode: initialView,
showAgyModal: false, showAgyModal: false,
showWechatModal: false,
showCmdPalette: false, showCmdPalette: false,
toast: '', toast: '',
actionLoading: false, actionLoading: false,
@@ -105,6 +108,7 @@ export const usePortalStore = create<PortalState>((set, get) => ({
clash: true, clash: true,
router: true, router: true,
uptime: true, uptime: true,
clawbot: false,
}, },
}, },
@@ -138,6 +142,7 @@ export const usePortalStore = create<PortalState>((set, get) => ({
}, },
setShowAgyModal: (showAgyModal) => set({ showAgyModal }), setShowAgyModal: (showAgyModal) => set({ showAgyModal }),
setShowWechatModal: (showWechatModal) => set({ showWechatModal }),
setShowCmdPalette: (showCmdPalette) => set({ showCmdPalette }), setShowCmdPalette: (showCmdPalette) => set({ showCmdPalette }),
showToast: (msg) => { showToast: (msg) => {
+20 -1
View File
@@ -1,6 +1,23 @@
export type ThemeId = 'system' | 'light' | 'dark'; export type ThemeId = 'system' | 'light' | 'dark';
export type RouteModeId = 'auto' | 'edge443' | 'home10443' | 'lan'; export type RouteModeId = 'auto' | 'edge443' | 'home10443' | 'lan';
export interface ClawbotPublicStatus {
connected: boolean;
bot_id?: string;
user_id?: string;
base_url?: string;
connected_at?: string;
msg_count?: number;
last_inbound_text?: string;
last_reply_text?: string;
login?: {
active: boolean;
qrcode_url: string;
status: string;
message: string;
};
}
export interface TelemetryData { export interface TelemetryData {
timestamp?: string; timestamp?: string;
mini: { mini: {
@@ -53,7 +70,9 @@ export interface TelemetryData {
clash: boolean; clash: boolean;
router: boolean; router: boolean;
uptime: boolean; uptime: boolean;
clawbot?: boolean;
}; };
clawbot?: ClawbotPublicStatus;
} }
export interface StatCell { export interface StatCell {
@@ -68,7 +87,7 @@ export interface ServiceCardItem {
subtitle: string; subtitle: string;
iconUrl: string; iconUrl: string;
online: boolean; online: boolean;
actionType?: 'drawer' | 'backup'; actionType?: 'drawer' | 'backup' | 'clawbot';
desc: string; desc: string;
edgeUrl: string; edgeUrl: string;
homeUrl: string; homeUrl: string;