{"_id":"@bryanhuang7878/weixin-gateway","_rev":"7-a0a9be0ee7b2a69a0b96d1dbc4dea41e","name":"@bryanhuang7878/weixin-gateway","dist-tags":{"latest":"0.2.6"},"versions":{"0.1.0":{"name":"@bryanhuang7878/weixin-gateway","version":"0.1.0","keywords":["weixin","wechat","gateway","agent","bot"],"license":"MIT","_id":"@bryanhuang7878/weixin-gateway@0.1.0","maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"bin":{"weixin-gateway":"src/cli.js"},"dist":{"shasum":"fe7b4478e55f61af4db1708603a3afec8eed967d","tarball":"https://registry.npmjs.org/@bryanhuang7878/weixin-gateway/-/weixin-gateway-0.1.0.tgz","fileCount":31,"integrity":"sha512-tlFlrhEOKCDMAEKY9Q4u6R+/sfjSoDklBJ89wCNBy7O4sW4LxsiEg2kYz2+6NT6rbyXmfzqed5vxaVRGshZjvQ==","signatures":[{"sig":"MEQCIBoxwjDWGYpj6XO67rstzqBrfpYYBcSViN8VuI4l0oWLAiBbV7ZFfg64gftwkerxO8jHadZ5yyGnv5qUvkRiI0WjKA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":130959},"type":"module","engines":{"node":">=22"},"gitHead":"0ba833d952346f60cd4c4aa8c258372aaa25659e","private":false,"scripts":{"cli":"node src/cli.js","dev":"node --watch src/server.js","test":"node --test src/config.test.js src/auth/login-qr.test.js src/cli.test.js src/api/weixin-api.test.js src/runtime/poller.test.js src/media/send-media.test.js src/media/inbound-media.test.js","start":"node src/server.js"},"_npmUser":{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"},"repository":{"url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git","type":"git"},"_npmVersion":"10.9.0","description":"Minimal standalone Weixin gateway for agent runtimes","directories":{},"_nodeVersion":"22.11.0","preferGlobal":true,"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/weixin-gateway_0.1.0_1774320678835_0.004175989672729807","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@bryanhuang7878/weixin-gateway","version":"0.2.0","keywords":["weixin","wechat","gateway","agent","bot"],"license":"MIT","_id":"@bryanhuang7878/weixin-gateway@0.2.0","maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"bin":{"weixin-gateway":"src/cli.js"},"dist":{"shasum":"64725b748515b7100220a1282360a722258b2115","tarball":"https://registry.npmjs.org/@bryanhuang7878/weixin-gateway/-/weixin-gateway-0.2.0.tgz","fileCount":32,"integrity":"sha512-V3vessr3+reertAqz2FK6zYXKnF9GvFsBDvT4pjt+ha53yIAKTc03BaDBQk7xvxb5xLcgW4utzXdVkL0hVjJjQ==","signatures":[{"sig":"MEUCIGYBhvpO2jhd5mDphjIYuJJDleSxpSvtjC1KnVgdBxkDAiEA/K6u4dHV546a52AV94gQedeuEvDsrg8E7Z97mt4eiuo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":157680},"type":"module","engines":{"node":">=22"},"gitHead":"7f756357a7ced2a6b4178ae6981830534cef9a4c","private":false,"scripts":{"cli":"node src/cli.js","dev":"node --watch src/server.js","test":"node --test src/config.test.js src/auth/login-qr.test.js src/cli.test.js src/api/weixin-api.test.js src/runtime/poller.test.js src/media/send-media.test.js src/media/inbound-media.test.js src/store/file-store.test.js","start":"node src/server.js"},"_npmUser":{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"},"repository":{"url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git","type":"git"},"_npmVersion":"10.9.0","description":"Standalone Weixin gateway for agent runtimes with callback and inbox delivery","directories":{},"_nodeVersion":"22.11.0","preferGlobal":true,"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/weixin-gateway_0.2.0_1774362187210_0.9005296895866726","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@bryanhuang7878/weixin-gateway","version":"0.2.2","keywords":["weixin","wechat","gateway","agent","bot"],"license":"MIT","_id":"@bryanhuang7878/weixin-gateway@0.2.2","maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"bin":{"weixin-gateway":"src/cli.js"},"dist":{"shasum":"94c619aacd2289e26c8159f42ed69919c72b6364","tarball":"https://registry.npmjs.org/@bryanhuang7878/weixin-gateway/-/weixin-gateway-0.2.2.tgz","fileCount":32,"integrity":"sha512-YYCPD0XYKGBlUR9pANF6Z7dQ9omBnOtlkctPWBvnffxsv0OEs9c4biwMaOxVe7kMukLDc3c/bZPh8ZGwr7PHKA==","signatures":[{"sig":"MEYCIQCAOvCyEO2DA/lf77hCwlPKUZbCvMTKNTgyL3EAEIDkpgIhAOW1lV1pBhJAXrTXuDwTtzLO1DpZhfnO0wV7Pm7roaAa","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":160479},"type":"module","engines":{"node":">=22"},"gitHead":"143cacbd9ebe470ecf1df2bded9e4041f935e03e","private":false,"scripts":{"cli":"node src/cli.js","dev":"node --watch src/server.js","test":"node --test src/config.test.js src/auth/login-qr.test.js src/cli.test.js src/api/weixin-api.test.js src/runtime/poller.test.js src/media/send-media.test.js src/media/inbound-media.test.js src/store/file-store.test.js","start":"node src/server.js"},"_npmUser":{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"},"repository":{"url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git","type":"git"},"_npmVersion":"10.9.0","description":"Standalone Weixin gateway for agent runtimes with callback and inbox delivery","directories":{},"_nodeVersion":"22.11.0","preferGlobal":true,"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/weixin-gateway_0.2.2_1774430321867_0.48289047340701385","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@bryanhuang7878/weixin-gateway","version":"0.2.3","keywords":["weixin","wechat","gateway","agent","bot"],"license":"MIT","_id":"@bryanhuang7878/weixin-gateway@0.2.3","maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"bin":{"weixin-gateway":"src/cli.js"},"dist":{"shasum":"2b50a8ca3aeee34e2f45564d96972b1a74412e63","tarball":"https://registry.npmjs.org/@bryanhuang7878/weixin-gateway/-/weixin-gateway-0.2.3.tgz","fileCount":33,"integrity":"sha512-Ggx7XYAoxgHzN+HrxW/wp+W0GxMI6xCEooR8su9irHmV3OYIF00uldFowcEG4mQCCpCP7M8H35999w+mao4NiA==","signatures":[{"sig":"MEQCIQD8IPl016ImeD/tAbn3gAAat9LHsUGsJzvaw7VmTWX2QAIfGvoDNixHm4eolIbI5hWhhKYpg+CBcHTDa1YAnhE+og==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":163615},"type":"module","engines":{"node":">=22"},"gitHead":"bcd30b2686db617b2250e7e02fc7d3f3c73aa27b","private":false,"scripts":{"cli":"node src/cli.js","dev":"node --watch src/server.js","test":"node --test src/config.test.js src/auth/login-qr.test.js src/cli.test.js src/api/weixin-api.test.js src/cdn/cdn-upload.test.js src/runtime/poller.test.js src/media/send-media.test.js src/media/inbound-media.test.js src/store/file-store.test.js","start":"node src/server.js"},"_npmUser":{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"},"repository":{"url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git","type":"git"},"_npmVersion":"10.9.0","description":"Standalone Weixin gateway for agent runtimes with callback and inbox delivery","directories":{},"_nodeVersion":"22.11.0","preferGlobal":true,"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/weixin-gateway_0.2.3_1774450807275_0.29117210824422757","host":"s3://npm-registry-packages-npm-production"}},"0.2.4":{"name":"@bryanhuang7878/weixin-gateway","version":"0.2.4","keywords":["weixin","wechat","gateway","agent","bot"],"license":"MIT","_id":"@bryanhuang7878/weixin-gateway@0.2.4","maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"bin":{"weixin-gateway":"src/cli.js"},"dist":{"shasum":"26287ba893cf9f6a80aaf6916060b35973945ac6","tarball":"https://registry.npmjs.org/@bryanhuang7878/weixin-gateway/-/weixin-gateway-0.2.4.tgz","fileCount":33,"integrity":"sha512-h6x6pf6PCxaoCWtmmcnJaLJ5vz+PT2K/KKmTD5Jn8u5iZY1XvVkOOTxWdo3Mx4+Do1J48u6qGAZjG96KIH+Fyw==","signatures":[{"sig":"MEUCIEOoBCYaRLW79sTmEsYwXv9U9l/yoW1tFbGApZ3nMtjzAiEAzsis2yLROnIAZPQCdlbVeZAcHxGpiBLRFNaJ730RJdI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":168663},"type":"module","engines":{"node":">=22"},"gitHead":"b2aad9f49c3690c21619e961b1db67dc22898768","private":false,"scripts":{"cli":"node src/cli.js","dev":"node --watch src/server.js","test":"node --test src/config.test.js src/auth/login-qr.test.js src/cli.test.js src/api/weixin-api.test.js src/cdn/cdn-upload.test.js src/runtime/poller.test.js src/media/send-media.test.js src/media/inbound-media.test.js src/store/file-store.test.js","start":"node src/server.js"},"_npmUser":{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"},"repository":{"url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git","type":"git"},"_npmVersion":"10.9.0","description":"Standalone Weixin gateway for agent runtimes with callback and inbox delivery","directories":{},"_nodeVersion":"22.11.0","preferGlobal":true,"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/weixin-gateway_0.2.4_1774589912137_0.7334619466560186","host":"s3://npm-registry-packages-npm-production"}},"0.2.5":{"name":"@bryanhuang7878/weixin-gateway","version":"0.2.5","keywords":["weixin","wechat","gateway","agent","bot"],"license":"MIT","_id":"@bryanhuang7878/weixin-gateway@0.2.5","maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"bin":{"weixin-gateway":"src/cli.js"},"dist":{"shasum":"d7def705f46b9bf9d2fcc311684526ff998d2f59","tarball":"https://registry.npmjs.org/@bryanhuang7878/weixin-gateway/-/weixin-gateway-0.2.5.tgz","fileCount":33,"integrity":"sha512-hiij48lxGY5dPlX9PRRPbrooefnoF5cEHmxXxRZyI3V6o1DLPX5IJWrW0uvRT7zSWTlNuCeKNMh/Bq8IrIVvtg==","signatures":[{"sig":"MEQCIB242WFlppknctsJpwCTDEcx0AEgnPdjYSSnzoqrM/cOAiBatwkhECokH4arKAHVEIU1VcbSDVRDkClBGDUoLWCp6w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":173733},"type":"module","engines":{"node":">=22"},"gitHead":"6e39c843ad9a1b5e5b879e2221b3965f1d2060ad","private":false,"scripts":{"cli":"node src/cli.js","dev":"node --watch src/server.js","test":"node --test src/config.test.js src/auth/login-qr.test.js src/cli.test.js src/api/weixin-api.test.js src/cdn/cdn-upload.test.js src/runtime/poller.test.js src/media/send-media.test.js src/media/inbound-media.test.js src/store/file-store.test.js","start":"node src/server.js"},"_npmUser":{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"},"repository":{"url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git","type":"git"},"_npmVersion":"10.9.0","description":"Standalone Weixin gateway for agent runtimes with callback and inbox delivery","directories":{},"_nodeVersion":"22.11.0","preferGlobal":true,"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/weixin-gateway_0.2.5_1774621077032_0.43477922822490855","host":"s3://npm-registry-packages-npm-production"}},"0.2.6":{"name":"@bryanhuang7878/weixin-gateway","version":"0.2.6","private":false,"type":"module","description":"Standalone Weixin gateway for agent runtimes with callback and inbox delivery","license":"MIT","preferGlobal":true,"repository":{"type":"git","url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git"},"homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"keywords":["weixin","wechat","gateway","agent","bot"],"publishConfig":{"access":"public"},"engines":{"node":">=22"},"bin":{"weixin-gateway":"src/cli.js"},"scripts":{"test":"node --test src/config.test.js src/auth/login-qr.test.js src/cli.test.js src/api/weixin-api.test.js src/cdn/cdn-upload.test.js src/runtime/poller.test.js src/media/mime.test.js src/media/silk-transcode.test.js src/media/send-media.test.js src/media/inbound-media.test.js src/store/file-store.test.js","cli":"node src/cli.js","dev":"node --watch src/server.js","start":"node src/server.js"},"_id":"@bryanhuang7878/weixin-gateway@0.2.6","gitHead":"db8ae4e6ea17152aa7df1a95d3960cc54047466f","_nodeVersion":"22.11.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-YCj3sYXMZ8+aY3M22c7Nr2KAR4t/KdIl9UUPDMeY8MLkxC9KtUIEpMRfyrsrnYuvFnt16TTUZ6bFcNNu+r9Zpw==","shasum":"1a90c825d4a5f5fb0eff7024b58d7a541957d6ba","tarball":"https://registry.npmjs.org/@bryanhuang7878/weixin-gateway/-/weixin-gateway-0.2.6.tgz","fileCount":35,"unpackedSize":178218,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIF5PPD1sjBLW6DbM86OCc7M4P6mrUItxQ9aKE6IQfQOEAiAzItQz4Mi/51wbkemP3eJGBN/6O9S2hXYWjGBeeOtJow=="}]},"_npmUser":{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"},"directories":{},"maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/weixin-gateway_0.2.6_1774694129435_0.36800813927194054"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-24T02:51:18.700Z","modified":"2026-03-28T10:35:29.728Z","0.1.0":"2026-03-24T02:51:18.982Z","0.2.0":"2026-03-24T14:23:07.353Z","0.2.2":"2026-03-25T09:18:42.022Z","0.2.3":"2026-03-25T15:00:07.428Z","0.2.4":"2026-03-27T05:38:32.258Z","0.2.5":"2026-03-27T14:17:57.182Z","0.2.6":"2026-03-28T10:35:29.606Z"},"bugs":{"url":"https://github.com/huangyong7878/xuanji-weixin-gateway/issues"},"license":"MIT","homepage":"https://github.com/huangyong7878/xuanji-weixin-gateway#readme","keywords":["weixin","wechat","gateway","agent","bot"],"repository":{"type":"git","url":"git+https://github.com/huangyong7878/xuanji-weixin-gateway.git"},"description":"Standalone Weixin gateway for agent runtimes with callback and inbox delivery","maintainers":[{"name":"bryanhuang7878","email":"bryan.huang7878@gmail.com"}],"readme":"# Weixin Gateway\n\n[![npm version](https://img.shields.io/npm/v/%40bryanhuang7878%2Fweixin-gateway)](https://www.npmjs.com/package/@bryanhuang7878/weixin-gateway)\n\n一个独立的微信网关，可对接任意上游 Agent 服务。\n\n- [License](./LICENSE)\n- [Contributing](./CONTRIBUTING.md)\n- [Changelog](./CHANGELOG.md)\n- [Release Checklist](./RELEASE_CHECKLIST.md)\n- [Repo Split Guide](./REPO_SPLIT_GUIDE.md)\n- [Publishing Notes](./PUBLISHING.md)\n- [FastAPI Demo](./examples/fastapi-demo/README.md)\n\n## npm\n\n已发布到 npm：\n\n- `@bryanhuang7878/weixin-gateway`\n\n安装：\n\n```bash\nnpm install -g @bryanhuang7878/weixin-gateway\n```\n\n安装后可直接使用：\n\n```bash\nweixin-gateway health\nweixin-gateway login:start\n```\n\n## Demo\n\n[![Weixin Gateway Demo](./demo-cover.png)](https://youtu.be/kmpNYUXBEZo)\n\n- 点击封面图可查看演示视频\n- 当前视频展示的是：\n  - 扫码登录\n  - FastAPI demo 接收微信文本\n  - 文本回声回复到微信\n\n## 适合什么场景\n\n你可以把它当成一个独立服务来用：\n\n- 管理微信 Bot 登录\n- 持续拉取微信消息\n- 把消息转发给你的 Agent 服务\n- 把 Agent 的回复发回微信\n\n当前已支持：\n\n- 文本\n- 图片\n- 视频\n- 语音\n- 文件\n- 二维码登录\n- 自动轮询\n- 账号管理\n- API / CLI\n- 两种上游投递模式：\n  - `callback`\n  - `inbox`\n- 失效账号状态标记（`expired`）\n\n## 最小上游示例\n\n如果你想最快看懂“上游应该怎么接”，可以直接看：\n\n- [examples/fastapi-demo](./examples/fastapi-demo/README.md)\n\n这个 demo 只实现：\n\n- 扫码登录\n- 接收微信文本\n- 文本回声回复\n\n## 1 分钟上手\n\n### 1. 启动服务\n\n如果你已经全局安装了 npm 包：\n\n```bash\nweixin-gateway\n```\n\n如果你是在仓库源码里本地开发：\n\n```bash\ncd /Users/yonghuang/Codes/xuanji-weixin-gateway\nnode src/server.js\n```\n\n默认监听：\n\n- `http://127.0.0.1:8787`\n\n### 2. 登录微信账号\n\n**CLI**\n\n```bash\nweixin-gateway login:start\n```\n\n会输出：\n\n- `session_id`\n- `qrcode`\n- `qrcode_url`\n\n扫码后盯状态：\n\n```bash\nweixin-gateway login:watch --session-id <session_id>\n```\n\n登录成功后可查看账号：\n\n```bash\nweixin-gateway accounts\n```\n\n**API**\n\n创建二维码登录会话：\n\n```http\nPOST /login/qr/start\nContent-Type: application/json\n```\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"api_base_url\": \"https://ilinkai.weixin.qq.com\"\n}\n```\n\n返回结果里会包含：\n\n- `session_id`\n- `qrcode`\n- `qrcode_url`\n\n然后轮询状态：\n\n```http\nGET /login/qr/status?session_id=<session_id>\n```\n\n直到状态变成：\n\n- `completed`\n\n登录成功后可通过：\n\n```http\nGET /accounts\n```\n\n确认账号是否已经写入。\n\n### 3. 自动轮询\n\n默认行为：\n\n- 服务启动后，如果本地已经有账号，会自动开始轮询\n- 新账号登录成功后，也会自动开始轮询\n\n**CLI**\n\n查看轮询状态：\n\n```bash\nweixin-gateway poll:status\n```\n\n手动启动或停止：\n\n```bash\nweixin-gateway poll:start\nweixin-gateway poll:stop\n```\n\n**API**\n\n查看轮询状态：\n\n```http\nGET /poll/status\n```\n\n手动启动或停止：\n\n```http\nPOST /poll/start\nPOST /poll/stop\n```\n\n### 4. 对接你的上游 Agent\n\n从 `v0.2.0` 开始，gateway 支持两种上游投递模式：\n\n- `callback`\n  - 保持原有行为\n  - 微信消息到达后直接回调你的上游 HTTP 服务\n- `inbox`\n  - 不主动回调\n  - 把消息存进 gateway inbox，等待你的 Agent 主动拉取\n\n默认模式仍然是：\n\n- `callback`\n\n账号状态说明：\n\n- `active`\n  - 当前账号仍会参与自动轮询\n- `expired`\n  - 该账号在轮询中出现过 `session timeout`\n  - 记录会保留，但自动轮询会默认跳过它\n\n如果你准备让 Codex 或其他本地 agent 主动轮询处理消息，推荐设置：\n\n```bash\nexport WEIXIN_GATEWAY_DELIVERY_MODE=inbox\n```\n\n需要配置上游服务地址：\n\n```bash\nexport UPSTREAM_BASE_URL=http://127.0.0.1:8000\nexport UPSTREAM_EVENTS_PATH=/callback/weixin-gateway\n```\n\nGateway 会把入站微信消息转发到：\n\n- `UPSTREAM_BASE_URL + UPSTREAM_EVENTS_PATH`\n\n例如：\n\n- `http://127.0.0.1:8000/callback/weixin-gateway`\n\n在 `inbox` 模式下，这组 `UPSTREAM_*` 配置不是必需的；消息会保存在 gateway 本地 inbox，等待你的 agent 通过 API 或 CLI 拉取。\n\n你的上游服务需要做两件事：\n\n1. 提供一个接收微信入站事件的 HTTP 接口\n2. 在处理完成后，调用 gateway 的 `/send` 把回复发回微信\n\n最小闭环就是：\n\n微信 -> gateway -> 你的 Agent -> gateway -> 微信\n\n**CLI**\n\n这一段通常不通过 CLI 对接，而是：\n\n- 用环境变量指定上游地址\n- 让 gateway 自动把微信消息推给你的上游服务\n\n**API**\n\n需要配置：\n\n```bash\nexport UPSTREAM_BASE_URL=http://127.0.0.1:8000\nexport UPSTREAM_EVENTS_PATH=/callback/weixin-gateway\n```\n\n这样 gateway 会把入站事件发到：\n\n- `http://127.0.0.1:8000/callback/weixin-gateway`\n\n#### 上游 callback 要怎么实现\n\n你的上游服务需要提供一个 `POST` 接口，例如：\n\n- `POST /callback/weixin-gateway`\n\ngateway 会把微信消息以 JSON 形式发给这个接口。\n\n#### Request schema\n\n请求头：\n\n```http\nContent-Type: application/json\nAuthorization: Bearer <UPSTREAM_SHARED_SECRET>\n```\n\n如果没有配置 `UPSTREAM_SHARED_SECRET`，则不会带 `Authorization`。\n\n请求体结构：\n\n```json\n{\n  \"type\": \"message\",\n  \"account_id\": \"wx-account-1\",\n  \"event_id\": \"evt-1\",\n  \"chat_id\": \"wx-user-1\",\n  \"user_id\": \"wx-user-1\",\n  \"text\": \"你好\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"raw\": {}\n}\n```\n\n字段说明：\n\n- `type: string`\n  - 当前固定为 `message`\n- `account_id: string`\n  - 当前使用的微信 Bot 账号 ID\n- `event_id: string`\n  - 当前消息事件 ID，可用于去重\n- `chat_id: string`\n  - 当前会话对端 ID\n- `user_id: string`\n  - 当前用户 ID\n- `text: string`\n  - gateway 转换后的文本内容\n- `context_token: string`\n  - 回消息时建议原样带回\n- `chat_type: string`\n  - 当前阶段固定为 `c2c`\n- `raw: object`\n  - 原始微信消息对象\n\n当前 `raw` 里可能还会带：\n\n- `item_list`\n- `message_type`\n- `message_id`\n- `create_time_ms`\n\n你的上游服务收到后，至少需要：\n\n- 读取 `account_id`\n- 读取 `user_id` 或 `chat_id`\n- 读取 `text`\n- 保留 `context_token`\n\n然后生成回复。\n\n#### 上游如何接收入站图片、文件、视频、语音\n\n**CLI**\n\n这一部分通常不通过 CLI 处理。  \n入站附件会随着 callback 事件一起推给你的上游服务。\n\n**API**\n\n当用户从微信发送附件时，gateway 会先把附件下载到本地，再转发事件给上游。\n\n上游收到的事件里：\n\n- `text` 会包含附件提示\n- 事件对象里会带 `attachments`\n\n示例：\n\n```json\n{\n  \"type\": \"message\",\n  \"account_id\": \"wx-account-1\",\n  \"event_id\": \"evt-1\",\n  \"chat_id\": \"wx-user-1\",\n  \"user_id\": \"wx-user-1\",\n  \"text\": \"用户发送了以下附件：\\n- image: /path/to/image.png\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"attachments\": [\n    {\n      \"kind\": \"image\",\n      \"path\": \"/Users/you/weixin-gateway/.data/inbound/weixin-inbound-1.png\",\n      \"filename\": \"weixin-inbound-1.png\",\n      \"mime_type\": \"image/png\"\n    }\n  ],\n  \"raw\": {}\n}\n```\n\n`attachments` 当前可能出现的 `kind`：\n\n- `image`\n- `video`\n- `voice`\n- `file`\n\n建议上游这样处理：\n\n- 对图片：读取本地文件路径，交给 vision / multimodal 能力\n- 对视频：读取本地文件路径，交给视频分析链路\n- 对语音：读取本地文件路径，交给语音转写或音频理解链路\n- 对文件：读取本地文件路径，按文档或通用文件处理\n\n当前入站附件默认落盘到：\n\n- `WEIXIN_GATEWAY_DATA_DIR/inbound`\n\n如果未设置 `WEIXIN_GATEWAY_DATA_DIR`，默认是：\n\n- `./.data/inbound`\n\n#### Response schema\n\n最小要求：\n\n- 返回任意 `2xx` 状态码即可\n\n最简单可以是：\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\n```\n\n```json\n{\n  \"ok\": true\n}\n```\n\n也可以直接返回空响应体。\n\n注意：\n\n- gateway **不会**读取这个响应体里的消息内容\n- 这个 callback 的职责只是“接收事件”\n- 真正发回微信，请单独调用 gateway 的 `POST /send`\n\n#### 上游怎么把回复发回微信\n\n**CLI**\n\n如果只是手工调试，你可以直接调用 gateway 的 HTTP API。\n\n**API**\n\n你的上游服务生成回复后，需要调用 gateway 的：\n\n- `POST /send`\n\n例如 gateway 跑在本机默认端口：\n\n- `http://127.0.0.1:8787/send`\n\n文本回复示例：\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"text\",\n      \"text\": \"你好，我收到了。\"\n    }\n  ]\n}\n```\n\n这里最重要的是：\n\n- `account_id` 用入站事件里的 `account_id`\n- `to_user_id` 用入站事件里的 `user_id` 或 `chat_id`\n- `context_token` 尽量原样带回\n\n如果你想发图片、视频、语音或文件，也同样走 `/send`。\n\n#### `/send` request schema\n\n**CLI**\n\n当前没有单独的 `send` CLI 命令。  \n推荐方式是让你的上游服务直接调用 `POST /send`。\n\n**API**\n\n请求头：\n\n```http\nContent-Type: application/json\n```\n\n请求体结构：\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"items\": []\n}\n```\n\n字段说明：\n\n- `account_id: string`\n  - 要使用哪个微信 Bot 账号发消息\n- `to_user_id: string`\n  - 目标微信用户 ID\n- `context_token: string`\n  - 建议从入站事件原样带回\n- `chat_type: string`\n  - 当前建议固定为 `c2c`\n- `items: array`\n  - 要发送的消息内容\n\n#### `/send` 文本消息\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"text\",\n      \"text\": \"你好，我收到了。\"\n    }\n  ]\n}\n```\n\n#### `/send` 图片消息\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"file\",\n      \"file_type\": 1,\n      \"url\": \"https://example.com/image.png\"\n    }\n  ]\n}\n```\n\n#### `/send` 视频消息\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"file\",\n      \"file_type\": 2,\n      \"url\": \"https://example.com/video.mp4\"\n    }\n  ]\n}\n```\n\n#### `/send` 语音消息\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"file\",\n      \"file_type\": 3,\n      \"url\": \"https://example.com/voice.mp3\"\n    }\n  ]\n}\n```\n\n#### `/send` 文件消息\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"file\",\n      \"file_type\": 4,\n      \"url\": \"https://example.com/report.pdf\"\n    }\n  ]\n}\n```\n\n`file_type` 约定：\n\n- `1` 图片\n- `2` 视频\n- `3` 语音\n- `4` 文件\n\n语音当前支持的上传格式：\n\n- `.mp3`\n- `.silk`\n- `.amr`\n- `.ogg`\n\n媒体和文件发送约定：\n\n- `url` 支持三种形式：\n  - `https://...`\n  - `http://...`\n  - `file:///absolute/path/to/file`\n  - `/absolute/path/to/file`\n- 只要 gateway 进程自己能读到这个文件即可\n- 对 `http(s)`，gateway 会先下载文件\n- 对 `file://` 或绝对路径，gateway 会直接读取本地文件\n- gateway 会把文件转成微信可发送的媒体\n- 如果是大文件或视频，微信端可能会有短暂延迟后才显示\n\n## 推荐使用流程\n\n### 用 CLI\n\n适合本地调试、单机运行、运维排障。\n\n常用命令：\n\n```bash\nweixin-gateway health\nweixin-gateway accounts\nweixin-gateway accounts:show --account-id <account_id>\nweixin-gateway accounts:remove --account-id <account_id>\nweixin-gateway login:start\nweixin-gateway login:status --session-id <session_id>\nweixin-gateway login:watch --session-id <session_id>\nweixin-gateway login:cancel --session-id <session_id>\nweixin-gateway poll:status\nweixin-gateway poll:start\nweixin-gateway poll:stop\nweixin-gateway inbox:list --status pending\nweixin-gateway inbox:claim --message-id <message_id> --worker-id codex\nweixin-gateway inbox:complete --message-id <message_id>\nweixin-gateway inbox:fail --message-id <message_id> --error \"...\"\nweixin-gateway typing:send --account-id <account_id> --to-user-id <user_id>\nweixin-gateway typing:cancel --account-id <account_id> --to-user-id <user_id>\n```\n\n### 用 API\n\n适合接入其他 Agent、平台或自定义服务。\n\n基础地址默认是：\n\n- `http://127.0.0.1:8787`\n\n## API 概览\n\n### `GET /health`\n\n**Request**\n\n- 无请求体\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"service\": \"weixin-gateway\",\n  \"phase\": \"text-mvp\",\n  \"delivery_mode\": \"callback\",\n  \"polling\": {\n    \"running\": true,\n    \"interval_ms\": 5000\n  }\n}\n```\n\n### `GET /accounts`\n\n**Request**\n\n- 无请求体\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"accounts\": [\n    {\n      \"account_id\": \"wx-account-1\",\n      \"api_base_url\": \"https://ilinkai.weixin.qq.com\",\n      \"wechat_uin\": \"user@im.wechat\",\n      \"cursor\": \"\",\n      \"created_at\": \"2026-03-24T00:00:00.000Z\",\n      \"updated_at\": \"2026-03-24T00:00:00.000Z\",\n      \"status\": {\n        \"session_state\": \"active\",\n        \"polling_running\": true,\n        \"has_cursor\": false,\n        \"last_forwarded\": 0,\n        \"last_error\": \"\",\n        \"last_cursor\": \"\",\n        \"last_poll_finished_at\": \"\"\n      }\n    }\n  ]\n}\n```\n\n### `GET /accounts/:account_id`\n\n**Request**\n\n- 路径参数：`account_id`\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"account\": {\n    \"account_id\": \"wx-account-1\",\n    \"api_base_url\": \"https://ilinkai.weixin.qq.com\",\n    \"wechat_uin\": \"user@im.wechat\",\n    \"cursor\": \"\",\n    \"created_at\": \"2026-03-24T00:00:00.000Z\",\n    \"updated_at\": \"2026-03-24T00:00:00.000Z\",\n    \"status\": {\n      \"session_state\": \"active\",\n      \"polling_running\": true,\n      \"has_cursor\": false,\n      \"last_forwarded\": 0,\n      \"last_error\": \"\",\n      \"last_cursor\": \"\",\n      \"last_poll_finished_at\": \"\"\n    }\n  }\n}\n```\n\n找不到账号时：\n\n```json\n{\n  \"ok\": false,\n  \"error\": \"unknown account_id\"\n}\n```\n\n### `POST /accounts/register`\n\n用于手工注册账号。\n\n**Request**\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"api_base_url\": \"https://ilinkai.weixin.qq.com\",\n  \"bot_token\": \"token-from-login\",\n  \"wechat_uin\": \"user@im.wechat\"\n}\n```\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"account\": {\n    \"account_id\": \"wx-account-1\",\n    \"api_base_url\": \"https://ilinkai.weixin.qq.com\",\n    \"wechat_uin\": \"user@im.wechat\",\n    \"cursor\": \"\",\n    \"created_at\": \"2026-03-24T00:00:00.000Z\",\n    \"updated_at\": \"2026-03-24T00:00:00.000Z\",\n    \"status\": {\n      \"polling_running\": true,\n      \"has_cursor\": false,\n      \"last_forwarded\": 0,\n      \"last_error\": \"\",\n      \"last_cursor\": \"\",\n      \"last_poll_finished_at\": \"\"\n    }\n  }\n}\n```\n\n缺少字段时：\n\n```json\n{\n  \"ok\": false,\n  \"error\": \"account_id, api_base_url, bot_token, wechat_uin are required\"\n}\n```\n\n### `DELETE /accounts/:account_id`\n\n**Request**\n\n- 路径参数：`account_id`\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"removed\": {\n    \"account_id\": \"wx-account-1\",\n    \"api_base_url\": \"https://ilinkai.weixin.qq.com\",\n    \"wechat_uin\": \"user@im.wechat\",\n    \"cursor\": \"\",\n    \"created_at\": \"2026-03-24T00:00:00.000Z\",\n    \"updated_at\": \"2026-03-24T00:00:00.000Z\",\n    \"status\": {\n      \"session_state\": \"active\",\n      \"polling_running\": false,\n      \"has_cursor\": false,\n      \"last_forwarded\": 0,\n      \"last_error\": \"\",\n      \"last_cursor\": \"\",\n      \"last_poll_finished_at\": \"\"\n    }\n  },\n  \"polling\": {\n    \"running\": false\n  }\n}\n```\n\n### `POST /login/qr/start`\n\n**Request**\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"api_base_url\": \"https://ilinkai.weixin.qq.com\"\n}\n```\n\n`account_id` 可选；不传时 gateway 会生成一个临时账号 ID。\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"session\": {\n    \"session_id\": \"sess-1\",\n    \"account_id\": \"wx-account-1\",\n    \"state\": \"pending\",\n    \"qrcode\": \"qr-123\",\n    \"qrcode_url\": \"https://example.com/qr.png\"\n  }\n}\n```\n\n### `GET /login/qr/status?session_id=...`\n\n**Request**\n\n- 查询参数：`session_id`\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"session\": {\n    \"session_id\": \"sess-1\",\n    \"account_id\": \"wx-account-1\",\n    \"state\": \"completed\",\n    \"message\": \"微信登录成功。\"\n  }\n}\n```\n\n状态值包括：\n\n- `pending`\n- `scaned`\n- `completed`\n- `expired`\n- `cancelled`\n\n缺少 `session_id` 时：\n\n```json\n{\n  \"ok\": false,\n  \"error\": \"session_id is required\"\n}\n```\n\n### `POST /login/qr/cancel`\n\n**Request**\n\n```json\n{\n  \"session_id\": \"sess-1\"\n}\n```\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"session\": {\n    \"session_id\": \"sess-1\",\n    \"state\": \"cancelled\"\n  }\n}\n```\n\n### `POST /login/qr/complete`\n\n用于手工补录一次登录结果。大多数场景不需要它；通常只在调试或特殊登录流程里使用。\n\n**Request**\n\n```json\n{\n  \"session_id\": \"sess-1\",\n  \"api_base_url\": \"https://ilinkai.weixin.qq.com\",\n  \"bot_token\": \"token-from-login\",\n  \"wechat_uin\": \"user@im.wechat\"\n}\n```\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"session\": {\n    \"session_id\": \"sess-1\",\n    \"state\": \"completed\"\n  },\n  \"account\": {\n    \"account_id\": \"wx-account-1\",\n    \"api_base_url\": \"https://ilinkai.weixin.qq.com\",\n    \"wechat_uin\": \"user@im.wechat\",\n    \"cursor\": \"\"\n  }\n}\n```\n\n### `GET /poll/status`\n\n**Request**\n\n- 无请求体\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"delivery_mode\": \"callback\",\n  \"polling\": {\n    \"running\": true,\n    \"interval_ms\": 5000,\n    \"last_started_at\": \"2026-03-24T00:00:00.000Z\",\n    \"last_finished_at\": \"2026-03-24T00:00:01.000Z\",\n    \"last_error\": \"\",\n    \"last_results\": []\n  }\n}\n```\n\n### `POST /poll/start`\n\n**Request**\n\n- 空 JSON 或无请求体均可\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"polling\": {\n    \"running\": true,\n    \"interval_ms\": 5000\n  }\n}\n```\n\n### `POST /poll/stop`\n\n**Request**\n\n- 空 JSON 或无请求体均可\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"polling\": {\n    \"running\": false,\n    \"interval_ms\": 5000\n  }\n}\n```\n\n### `POST /poll/run-once`\n\n**Request**\n\n- 空 JSON 或无请求体均可\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"delivery_mode\": \"callback\",\n  \"results\": [\n    {\n      \"account_id\": \"wx-account-1\",\n      \"forwarded\": 1,\n      \"cursor\": \"next-cursor\"\n    }\n  ]\n}\n```\n\n### `GET /inbox/messages`\n\n用于拉取 gateway 已暂存的入站消息。适合本地 agent、Codex automation 或任何不想开 webhook server 的上游。\n\n**CLI**\n\n```bash\nweixin-gateway inbox:list --status pending --limit 10\n```\n\n**Request**\n\n- 查询参数：\n  - `status`，默认 `pending`\n  - `limit`，默认 `20`\n  - `account_id`，可选\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"messages\": [\n    {\n      \"id\": \"msg-1\",\n      \"type\": \"message\",\n      \"status\": \"pending\",\n      \"account_id\": \"wx-account-1\",\n      \"event_id\": \"evt-1\",\n      \"chat_id\": \"wx-user-1\",\n      \"user_id\": \"wx-user-1\",\n      \"chat_type\": \"c2c\",\n      \"text\": \"帮我看一下仓库状态\",\n      \"context_token\": \"ctx-1\",\n      \"attachments\": [],\n      \"callback_attempted\": false,\n      \"callback_succeeded\": false,\n      \"claim\": null,\n      \"error\": \"\",\n      \"created_at\": \"2026-03-24T00:00:00.000Z\",\n      \"updated_at\": \"2026-03-24T00:00:00.000Z\"\n    }\n  ]\n}\n```\n\n### `GET /inbox/messages/:message_id`\n\n**CLI**\n\n```bash\nweixin-gateway inbox:show --message-id msg-1\n```\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"message\": {\n    \"id\": \"msg-1\",\n    \"status\": \"pending\",\n    \"account_id\": \"wx-account-1\",\n    \"user_id\": \"wx-user-1\",\n    \"text\": \"帮我看一下仓库状态\"\n  }\n}\n```\n\n### `POST /inbox/messages/:message_id/claim`\n\nclaim 用于告诉 gateway：这条消息已经被某个 worker 接手，避免被重复消费。\n\n**CLI**\n\n```bash\nweixin-gateway inbox:claim --message-id msg-1 --worker-id codex\n```\n\n**Request**\n\n```json\n{\n  \"worker_id\": \"codex\"\n}\n```\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"message\": {\n    \"id\": \"msg-1\",\n    \"status\": \"claimed\",\n    \"claim\": {\n      \"worker_id\": \"codex\",\n      \"claimed_at\": \"2026-03-24T00:00:05.000Z\"\n    }\n  }\n}\n```\n\n### `POST /inbox/messages/:message_id/complete`\n\n**CLI**\n\n```bash\nweixin-gateway inbox:complete --message-id msg-1 --completion-note \"reply sent\"\n```\n\n**Request**\n\n```json\n{\n  \"completion_note\": \"reply sent\"\n}\n```\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"message\": {\n    \"id\": \"msg-1\",\n    \"status\": \"completed\",\n    \"completed_at\": \"2026-03-24T00:00:30.000Z\"\n  }\n}\n```\n\n### `POST /inbox/messages/:message_id/fail`\n\n**CLI**\n\n```bash\nweixin-gateway inbox:fail --message-id msg-1 --error \"temporary failure\"\n```\n\n**Request**\n\n```json\n{\n  \"error\": \"temporary failure\"\n}\n```\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"message\": {\n    \"id\": \"msg-1\",\n    \"status\": \"failed\",\n    \"error\": \"temporary failure\",\n    \"failed_at\": \"2026-03-24T00:00:30.000Z\"\n  }\n}\n```\n\n### `POST /accounts/:account_id/poll-once`\n\n**Request**\n\n- 路径参数：`account_id`\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"result\": {\n    \"account_id\": \"wx-account-1\",\n    \"forwarded\": 1,\n    \"cursor\": \"next-cursor\"\n  }\n}\n```\n\n找不到账号时：\n\n```json\n{\n  \"ok\": false,\n  \"error\": \"unknown account_id\"\n}\n```\n\n### `POST /typing`\n\n**CLI**\n\n```bash\nweixin-gateway typing:send --account-id <account_id> --to-user-id <user_id>\nweixin-gateway typing:cancel --account-id <account_id> --to-user-id <user_id>\n```\n\n**API**\n\n**Request**\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-123\",\n  \"status\": \"typing\"\n}\n```\n\n`status` 支持：\n\n- `typing`\n- `cancel`\n\n**Response**\n\n```json\n{\n  \"ok\": true,\n  \"typing\": {\n    \"account_id\": \"wx-account-1\",\n    \"to_user_id\": \"wx-user-1\",\n    \"status\": \"typing\"\n  }\n}\n```\n\n### `POST /send`\n\n支持：\n\n- 文本\n- 图片\n- 视频\n- 语音\n- 文件\n\n**Request**\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-123\",\n  \"chat_type\": \"c2c\",\n  \"items\": []\n}\n```\n\n顶层字段：\n\n- `account_id: string`\n  - 用哪个微信 Bot 账号发送\n- `to_user_id: string`\n  - 发给哪个微信用户\n- `context_token: string`\n  - 建议从入站事件原样带回\n- `chat_type: string`\n  - 当前建议固定为 `c2c`\n- `items: array`\n  - 实际要发送的消息内容\n\n`items` 目前支持两类：\n\n### `items[].type = \"text\"`\n\n```json\n{\n  \"type\": \"text\",\n  \"text\": \"你好\"\n}\n```\n\n字段：\n\n- `type: \"text\"`\n- `text: string`\n\n### `items[].type = \"file\"`\n\n```json\n{\n  \"type\": \"file\",\n  \"file_type\": 1,\n  \"url\": \"https://example.com/image.png\"\n}\n```\n\n字段：\n\n- `type: \"file\"`\n- `file_type: number`\n- `url: string`\n\n`url` 支持：\n\n- `https://...`\n- `http://...`\n- `file:///absolute/path/to/file`\n- `/absolute/path/to/file`\n\n文本示例：\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-123\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"text\",\n      \"text\": \"你好\"\n    }\n  ]\n}\n```\n\n文件或媒体示例：\n\n```json\n{\n  \"account_id\": \"wx-account-1\",\n  \"to_user_id\": \"wx-user-1\",\n  \"context_token\": \"ctx-123\",\n  \"chat_type\": \"c2c\",\n  \"items\": [\n    {\n      \"type\": \"file\",\n      \"file_type\": 1,\n      \"url\": \"https://example.com/image.png\",\n      \"srv_send_msg\": true\n    }\n  ]\n}\n```\n\n`file_type` 约定：\n\n- `1` 图片\n- `2` 视频\n- `3` 语音\n- `4` 文件\n\n语音当前支持的上传格式：\n\n- `.mp3`\n- `.silk`\n- `.amr`\n- `.ogg`\n\n**Response**\n\n成功时：\n\n```json\n{\n  \"ok\": true\n}\n```\n\n发送失败时：\n\n```json\n{\n  \"ok\": false,\n  \"error\": \"...\"\n}\n```\n\n常见失败原因：\n\n- `unknown account: ...`\n- `missing file item`\n- `unsupported file_type: ...`\n- `unsupported voice format: ...`\n\n## 上游事件\n\nGateway 会把入站微信消息转成统一事件并转发给你的上游服务。\n\n### Request schema\n\n```json\n{\n  \"type\": \"message\",\n  \"account_id\": \"wx-account-1\",\n  \"event_id\": \"evt-1\",\n  \"chat_id\": \"wx-user-1\",\n  \"user_id\": \"wx-user-1\",\n  \"text\": \"你好\",\n  \"context_token\": \"ctx-1\",\n  \"chat_type\": \"c2c\",\n  \"raw\": {}\n}\n```\n\n顶层字段：\n\n- `type: string`\n  - 当前固定为 `message`\n- `account_id: string`\n  - 当前使用的微信 Bot 账号\n- `event_id: string`\n  - 当前消息事件 ID，可用于幂等和去重\n- `chat_id: string`\n  - 当前会话 ID\n- `user_id: string`\n  - 当前微信用户 ID\n- `text: string`\n  - gateway 整理后的文本内容\n- `context_token: string`\n  - 回复时建议原样带回\n- `chat_type: string`\n  - 当前固定为 `c2c`\n- `attachments: array`\n  - 可选，存在附件时会出现\n- `raw: object`\n  - 原始微信消息对象\n\n### `attachments[]` schema\n\n当微信消息里带附件时，gateway 会把附件下载到本地，再在事件里附带 `attachments`。\n\n示例：\n\n```json\n{\n  \"attachments\": [\n    {\n      \"kind\": \"image\",\n      \"path\": \"/Users/you/weixin-gateway/.data/inbound/weixin-inbound-1.png\",\n      \"filename\": \"weixin-inbound-1.png\",\n      \"mime_type\": \"image/png\"\n    }\n  ]\n}\n```\n\n字段说明：\n\n- `kind: string`\n  - 附件类型\n  - 当前可能值：\n    - `image`\n    - `video`\n    - `voice`\n    - `file`\n- `path: string`\n  - gateway 本地落盘后的绝对路径\n- `filename: string`\n  - 保存后的文件名\n- `mime_type: string`\n  - 推断得到的 MIME 类型\n\n当前入站附件会落盘到：\n\n- `WEIXIN_GATEWAY_INBOUND_DIR`\n\n如果未显式设置，默认路径是：\n\n- `WEIXIN_GATEWAY_DATA_DIR/inbound`\n\n## 环境变量\n\n```bash\nPORT=8787\nWEIXIN_GATEWAY_DATA_DIR=.data\nWEIXIN_GATEWAY_INBOUND_DIR=.data/inbound\nWEIXIN_GATEWAY_DELIVERY_MODE=callback\nUPSTREAM_BASE_URL=http://127.0.0.1:8000\nUPSTREAM_EVENTS_PATH=/callback/weixin-gateway\nUPSTREAM_SHARED_SECRET=\nWEIXIN_GATEWAY_POLL_INTERVAL_MS=5000\nWEIXIN_GATEWAY_AUTO_START=true\nWEIXIN_GATEWAY_LOGIN_SESSION_TTL_MS=600000\nWEIXIN_GATEWAY_VERBOSE_UPDATES=false\n```\n\n兼容说明：\n\n- 仍兼容旧的 `XUANJI_BASE_URL`\n- 仍兼容旧的 `XUANJI_WEIXIN_CALLBACK_PATH`\n- 仍兼容旧的 `XUANJI_SHARED_SECRET`\n- 如果新旧同时存在，优先使用 `UPSTREAM_*`\n- `WEIXIN_GATEWAY_DELIVERY_MODE` 支持：\n  - `callback`\n  - `inbox`\n\n## 常见操作\n\n### 导出成独立仓库\n\n```bash\ncd /Users/yonghuang/Codes/xuanji-weixin-gateway\nbash scripts/export-standalone.sh ~/Codes/xuanji-weixin-gateway\n```\n\n### 用 pnpm 调 CLI\n\n```bash\npnpm cli -- health\npnpm cli -- login:start\n```\n\n## 当前建议\n\n如果你是第一次接入，最顺的路径是：\n\n1. 启动 `weixin-gateway`\n2. 执行 `weixin-gateway login:start`\n3. 扫码后执行 `weixin-gateway login:watch --session-id <session_id>`\n4. 用 `weixin-gateway accounts` 确认账号已登录\n5. 用 `weixin-gateway poll:status` 确认轮询在运行\n6. 选择一种上游模式：\n   - webhook 模式：配置 `UPSTREAM_BASE_URL` 和 `UPSTREAM_EVENTS_PATH`\n   - inbox 模式：配置 `WEIXIN_GATEWAY_DELIVERY_MODE=inbox`\n7. 如果是 inbox 模式，先用 `weixin-gateway inbox:list --status pending` 验证消息是否可拉取\n8. 开始用 `/send` 和 callback 或 inbox API 接入你的 Agent\n","readmeFilename":"README.md"}