{"_id":"@dongh4o/wechat-ilink-bridge","_rev":"2-2462819d32ef6f025e44241f422b91c0","name":"@dongh4o/wechat-ilink-bridge","dist-tags":{"beta":"0.1.0-beta.1","latest":"0.2.0"},"versions":{"0.1.0-beta.1":{"name":"@dongh4o/wechat-ilink-bridge","version":"0.1.0-beta.1","keywords":["wechat","ilink","bot","cli","agent"],"license":"MIT","_id":"@dongh4o/wechat-ilink-bridge@0.1.0-beta.1","maintainers":[{"name":"dongh4o","email":"donghaoxu1995@gmail.com"}],"homepage":"https://github.com/DONGH4O/wechat-ilink-bridge#readme","bugs":{"url":"https://github.com/DONGH4O/wechat-ilink-bridge/issues"},"bin":{"wxb":"src/cli/index.js"},"dist":{"shasum":"ad1730ff05ca46196c6da6ed0fe47461e797096c","tarball":"https://registry.npmjs.org/@dongh4o/wechat-ilink-bridge/-/wechat-ilink-bridge-0.1.0-beta.1.tgz","fileCount":54,"integrity":"sha512-oII9nrhVGV5Ar9WhqVnXmkbgq1NMA5s2UFwiJfTQ1G/kX2P5jjeokDAoDuii8bBLpvgWbVz/x/b3oqjKEnRjWA==","signatures":[{"sig":"MEYCIQDgYgnX74kLFUOrh7DsIoQyIJrOev/Ob9rgk0q9bJMe9AIhAIvkaLo5oMtX85lHnTSQpO58UguXamJdib1LOWcVOWSD","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":204255},"type":"module","engines":{"node":">=18"},"gitHead":"83cecb030460d4206fdf8b0602df30142b0d6f69","private":false,"scripts":{"test":"node --test","spike":"node scripts/protocol-spike.js","test:unit":"node --test test/unit","pack:dry-run":"npm pack --dry-run","spike:normalize":"node scripts/protocol-spike.js normalize"},"_npmUser":{"name":"dongh4o","email":"donghaoxu1995@gmail.com"},"repository":{"url":"git+https://github.com/DONGH4O/wechat-ilink-bridge.git","type":"git"},"_npmVersion":"11.6.2","description":"Local WeChat iLink Bridge CLI for Agent-safe fetch, send, state, and media handoff workflows.","directories":{},"_nodeVersion":"24.11.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/wechat-ilink-bridge_0.1.0-beta.1_1779025124920_0.29274679443881824","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@dongh4o/wechat-ilink-bridge","version":"0.2.0","private":false,"type":"module","description":"Local WeChat iLink Bridge CLI for Agent-safe fetch, send, state, and media handoff workflows.","keywords":["wechat","ilink","bot","cli","agent"],"bin":{"wxb":"src/cli/index.js","wxb-mcp":"src/mcp/index.js"},"scripts":{"test":"node --test","test:unit":"node --test test/unit","pack:dry-run":"npm pack --dry-run","spike":"node scripts/protocol-spike.js","spike:normalize":"node scripts/protocol-spike.js normalize"},"engines":{"node":">=18"},"repository":{"type":"git","url":"git+https://github.com/DONGH4O/wechat-ilink-bridge.git"},"bugs":{"url":"https://github.com/DONGH4O/wechat-ilink-bridge/issues"},"homepage":"https://github.com/DONGH4O/wechat-ilink-bridge#readme","license":"MIT","gitHead":"7946df5df89e044401bd20938a6495fa23243667","_id":"@dongh4o/wechat-ilink-bridge@0.2.0","_nodeVersion":"24.11.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-U2my4yxO/z6g57oBjoKYh6e2RlB83eu0DX2ulpCrdj88mLWdpaSspmTVKiuf/u0w7SQb26n25HEFMG+9oyf7UQ==","shasum":"99ed152a036fcefec21fee185db0bc618c98a96d","tarball":"https://registry.npmjs.org/@dongh4o/wechat-ilink-bridge/-/wechat-ilink-bridge-0.2.0.tgz","fileCount":65,"unpackedSize":259246,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIC05YiVDq0aVzsc1/VN7CsFSTS4y8lc74HXvKD/GUlPTAiEA3iWPOT2gEBAVUfBBgF+Kdsm69yFl0q8oafqLZ+5U8wU="}]},"_npmUser":{"name":"dongh4o","email":"donghaoxu1995@gmail.com"},"directories":{},"maintainers":[{"name":"dongh4o","email":"donghaoxu1995@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/wechat-ilink-bridge_0.2.0_1779109108445_0.06794616526570829"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-17T13:38:44.789Z","modified":"2026-05-18T12:58:28.762Z","0.1.0-beta.1":"2026-05-17T13:38:45.072Z","0.2.0":"2026-05-18T12:58:28.645Z"},"bugs":{"url":"https://github.com/DONGH4O/wechat-ilink-bridge/issues"},"license":"MIT","homepage":"https://github.com/DONGH4O/wechat-ilink-bridge#readme","keywords":["wechat","ilink","bot","cli","agent"],"repository":{"type":"git","url":"git+https://github.com/DONGH4O/wechat-ilink-bridge.git"},"description":"Local WeChat iLink Bridge CLI for Agent-safe fetch, send, state, and media handoff workflows.","maintainers":[{"name":"dongh4o","email":"donghaoxu1995@gmail.com"}],"readme":"# WeChat-iLink Bridge\n\n[![CI](https://github.com/DONGH4O/wechat-ilink-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/DONGH4O/wechat-ilink-bridge/actions/workflows/ci.yml)\n\nLicense: [MIT](LICENSE)\n\nPublic npm bin 公开稳定 CLI bin `wxb` 和 P2 MCP adapter bin `wxb-mcp`；`wxb-spike` 不作为公开 bin 发布。\n\nWeChat-iLink Bridge（`wxb`）是一个面向 AI Agent 的微信 iLink 本地桥接 CLI。当前源码版本为 `0.2.0`：`wxb send` 支持文本、文件、图片和可选 typing 状态，`wxb-mcp` 暴露 fetch/send/status/listUsers/analyzeMedia 工具，上传密钥和 CDN URL 继续只在 bridge 内部流转。当前 npm 已发布版本仍为 M12 beta。\n\n## P0 能力范围\n\n- `wxb login`：扫码登录并把账号凭证写入本地状态目录。\n- `wxb accounts`：列出账号，stdout 不输出 token。\n- `wxb status`：查看账号、本地游标、会话数和消息历史数。\n- `wxb fetch`：短超时长轮询一批入站消息，先写入本地历史，再输出给 Agent。\n- `wxb send`：向已有入站上下文的用户发送文本，支持 `--text` 和 `--stdin`。\n- 默认 `fetch` 仍只识别非文本消息类型和元数据；显式使用 `--download-media` 时下载并保存附件。\n\n## M7 能力范围\n\n- `wxb poll`：前台重复执行 fetch，用于轻量 keepalive 和本地处理。\n- `wxb heartbeat`：执行一次计划任务友好的 keepalive fetch，不启动 HTTP 服务。\n- `wxb alias set/get/list/resolve/remove`：为 opaque `fromUserId` 维护可读别名。\n- `wxb queue list/clear`：查看或清空延迟补发队列。\n- `wxb cleanup`：按消息保留天数、附件保留天数和最大历史条数清理本地状态。\n- invalid context 触发的发送失败会进入延迟补发队列；后续收到同一用户入站消息时，只补发队列中的第一条。\n\n## M8 能力范围\n\n- `wxb fetch --download-media`：下载图片、文件、语音和视频到本地 `inbox`。\n- 支持 AES-128-ECB 解密，兼容 hex key、base64 原始 16 字节 key、base64 hex 字符串 key。\n- 图片、语音、视频缺少协议 MIME 时，会根据下载后的文件头推断常见类型和扩展名。\n- CLI 输出附件绝对路径、文件名、MIME、字节数和下载状态。\n- CLI 不输出 AES key、CDN 下载 URL 或签名查询参数。\n- 下载失败不会影响同一条消息中的文本项输出，也不会阻断游标和消息历史写入。\n- 附件文件名会清理 Windows 保留字符、路径穿越和极长文件名。\n\n## M11 能力范围\n\n- `wxb send --file <path>`：AES-128-ECB 加密并上传本地文件，再发送给已有上下文的微信用户。\n- `wxb send --image <path>`：校验本地图片 MIME，上传并发送图片消息。\n- `wxb send --typing`：发送前获取 `typing_ticket` 并显示输入状态，发送后尽力停止输入状态。\n- 本地文件不存在、目录路径、超过 `WX_MAX_UPLOAD_BYTES`、未知 MIME 或图片模式传入非图片文件时返回结构化错误。\n- stdout 不输出上传 URL、AES key、签名 query、bot token 或 context token。\n\n## M14 MCP Adapter\n\n- `wxb-mcp`：stdio MCP server，复用现有 core library 和本地状态目录。\n- MCP tools：`fetchMessages`、`sendText`、`sendFile`、`listUsers`、`status`、`analyzeMedia`。\n- Tool schema 不包含 `context_token`；Agent 只传账号、用户、文本或本地文件路径。\n- 媒体内容继续通过 `attachments[].path` 或本地 `filePath` 交付，不暴露 CDN URL、上传参数或 AES key。\n- 使用说明和客户端配置示例见 `docs/m14-mcp-adapter.md`。\n\n## M15 多模态辅助\n\n- `analyzeMedia`：检查本地媒体路径，返回 MIME、bytes、sha256、图片尺寸和 text-like 文件预览。\n- 图片问答、语音转写、视频摘要默认返回 `MULTIMODAL_HELPER_UNAVAILABLE` 降级结果；真实模型能力由 Agent 使用 `attachments[].path` 自行调用，或由自定义 adapter host 注入 optional helper。\n- 不需要模型 API key 时，bridge 的 fetch/send/媒体下载/媒体发送仍完整可用。\n- helper 失败不会修改 cursor、seen ID、context token 或消息历史。\n- 使用说明见 `docs/m15-multimodal-helper.md`。\n\n## 从源码安装\n\nWindows PowerShell：\n\n```powershell\ngit clone https://github.com/DONGH4O/wechat-ilink-bridge.git\ncd wechat-ilink-bridge\nnpm.cmd test\nnode .\\src\\cli\\index.js help\n```\n\n可选安装为本机全局 `wxb` 命令：\n\n```powershell\nnpm.cmd install -g .\nwxb help\n```\n\n## npm Beta 安装\n\n当前公开 beta 已可通过 `@dongh4o/wechat-ilink-bridge@beta` 安装，npm beta dist-tag 仍指向 `0.1.0-beta.1`：\n\n```powershell\nnpm.cmd install -g @dongh4o/wechat-ilink-bridge@beta\nwxb help\nwxb status --json\n```\n\n发布前本地 `.tgz` smoke：\n\n```powershell\nnpm.cmd pack\nnpm.cmd install -g .\\dongh4o-wechat-ilink-bridge-0.2.0.tgz\nwxb help\nwxb status --json\n```\n\n## 稳定版准备状态\n\n- M13 稳定版候选准备已完成，GitHub Actions CI 已在 Windows 2025、Ubuntu、macOS 和 Node.js 18/20/22 上通过。\n- 最新 M13 CI 验证记录见 `docs/m13-validation-report.md`。\n- `0.1.0` stable 发布仍需人工确认后再执行 version bump、`CHANGELOG.md` 稳定版条目、Git tag、GitHub Release 和 npm stable publish。\n- 安装、登录、发送、媒体和状态文件的常见问题见 `docs/troubleshooting.md`。\n\nmacOS/Linux：\n\n```bash\ngit clone https://github.com/DONGH4O/wechat-ilink-bridge.git\ncd wechat-ilink-bridge\nnpm test\nnode ./src/cli/index.js help\n```\n\n## Windows PowerShell 快速开始\n\n建议先指定测试状态目录，避免读写真实默认目录：\n\n```powershell\n$env:WX_STATE_DIR=\"C:\\tmp\\wxb-test\"\n```\n\n运行测试：\n\n```powershell\nnpm.cmd test\nnpm.cmd run pack:dry-run\n```\n\n如果 PowerShell 执行策略拦截 `npm.ps1`，使用 `npm.cmd` 即可，不需要修改系统策略。\n\n直接从源码运行 CLI：\n\n```powershell\nnode .\\src\\cli\\index.js help\nnode .\\src\\cli\\index.js login --json\nnode .\\src\\cli\\index.js accounts --json\nnode .\\src\\cli\\index.js status --json\nnode .\\src\\cli\\index.js fetch --timeout 1000 --json\nnode .\\src\\cli\\index.js fetch --timeout 3000 --download-media --json\nnode .\\src\\cli\\index.js send --user <fromUserId> --text \"收到\" --json\n\"来自 PowerShell 管道的回复\" | node .\\src\\cli\\index.js send --user <fromUserId> --stdin --json\nnode .\\src\\cli\\index.js send --user <fromUserId> --file \"C:\\path\\to\\report.pdf\" --json\nnode .\\src\\cli\\index.js send --user <fromUserId> --image \"C:\\path\\to\\image.jpg\" --typing --json\nnode .\\src\\cli\\index.js poll --limit 3 --interval 1000 --json\nnode .\\src\\cli\\index.js heartbeat --json\nnode .\\src\\cli\\index.js alias set <fromUserId> \"张三\"\nnode .\\src\\cli\\index.js cleanup --dry-run --json\n```\n\n常用全局配置也可以放在命令前：\n\n```powershell\nnode .\\src\\cli\\index.js --state-dir \"C:\\tmp\\wxb 测试 状态\" status --json\nnode .\\src\\cli\\index.js --base-url \"https://ilinkai.weixin.qq.com\" fetch --json\n```\n\n## 配置\n\n配置优先级为 CLI 参数 > 环境变量 > `.env` > 默认值。\n\n| 环境变量 | CLI 参数 | 说明 |\n|---|---|---|\n| `WX_STATE_DIR` | `--state-dir` | 状态目录；支持空格和中文路径 |\n| `WX_BASE_URL` | `--base-url` | iLink API 地址 |\n| `WX_CHANNEL_VERSION` | `--channel-version` | iLink channel version |\n| `WX_FETCH_TIMEOUT_MS` | `--fetch-timeout-ms` | fetch/send 请求超时 |\n| `WX_POLL_INTERVAL_MS` | `--poll-interval-ms` | poll 循环间隔 |\n| `WX_MIN_CHUNK_CHARS` | `--min-chunk-chars` | 文本分片最小长度 |\n| `WX_MAX_CHUNK_CHARS` | `--max-chunk-chars` | 文本分片最大长度 |\n| `WX_MAX_DELIVERY_MESSAGES` | `--max-delivery-messages` | 单次发送最大分片数 |\n| `WX_DELAYED_QUEUE_MAX_ITEMS` | `--delayed-queue-max-items` | 每账号延迟补发队列最大条数 |\n| `WX_MESSAGE_RETENTION_DAYS` | `--message-retention-days` | cleanup 消息历史保留天数 |\n| `WX_ATTACHMENT_RETENTION_DAYS` | `--attachment-retention-days` | cleanup inbox 附件保留天数 |\n| `WX_MAX_HISTORY_MESSAGES` | `--max-history-messages` | cleanup 每账号最多保留消息条数 |\n| `WX_MAX_UPLOAD_BYTES` | `--max-upload-bytes` | 单个发送文件最大字节数，默认 25 MiB |\n\n默认状态目录：\n\n| 平台 | 默认状态目录 |\n|---|---|\n| Windows | `%LOCALAPPDATA%\\wxb` |\n| macOS | `~/Library/Application Support/wxb` |\n| Linux | `${XDG_DATA_HOME:-~/.local/share}/wxb` |\n\n账号 token 和 context token 只保存在本地状态文件中，不会出现在默认 JSON stdout。公开 issue、PR、日志或截图前，请删除 `.env*`、本地状态目录、真实 `m*-*.stdout.json` 捕获、CDN 签名 URL、AES key 和完整 token。\n\n## 发布前检查\n\n准备公开 GitHub 或 npm 前，先运行：\n\n```powershell\nnpm.cmd test\nnpm.cmd run pack:dry-run\n```\n\n检查 `npm pack --dry-run` 的 file list，确保不包含 `.env`、`你的真实测试状态目录`、`m*-*.stdout.json`、`test/fixtures/raw/live-*`、完整 token、CDN 签名 URL 或 AES key。完整流程见 `docs/release-process.md`。\n\n## Agent 收发循环\n\n1. 运行 `fetch --json` 获取新消息。\n2. 把 `data.messages` 视为用户输入。\n3. 生成回复文本。\n4. 使用消息里的 `fromUserId` 调用 `send --user <fromUserId>`。\n\n文本、文件和图片发送：\n\n```powershell\nnode .\\src\\cli\\index.js send --user <fromUserId> --text \"收到\" --json\nnode .\\src\\cli\\index.js send --user <fromUserId> --file \"C:\\path\\to\\report.pdf\" --json\nnode .\\src\\cli\\index.js send --user <fromUserId> --image \"C:\\path\\to\\image.jpg\" --typing --json\n```\n\n`--file` 和 `--image` 只接受本地文件路径。bridge 会负责 AES 加密、上传 URL 获取、CDN 上传和 `sendmessage`，Agent 不需要也不应处理上传密钥、CDN 签名 URL 或 `context_token`。\n\n如果 `send` 返回 `NO_CONTEXT_TOKEN`，说明还没有该用户的可用入站上下文，需要先让用户发来一条消息并运行 `fetch`。如果返回 `SESSION_EXPIRED`，需要重新执行 `login`。\n\n## M7 运维命令\n\n前台 poller：\n\n```powershell\nnode .\\src\\cli\\index.js poll --limit 10 --interval 1000 --timeout 15000 --json\nnode .\\src\\cli\\index.js poll --limit 10 --interval 1000 --jsonl\n```\n\n计划任务 heartbeat：\n\n```powershell\nnode .\\src\\cli\\index.js heartbeat --timeout 15000 --max-attempts 1 --json\n```\n\n无新消息时，如果客户端 timeout 先于 iLink 服务端长轮询返回，heartbeat 会输出 `ok: true` 和 `status: \"idle_timeout\"`，这表示本次 keepalive 没拿到新消息，但计划任务不应判为失败。\n\nalias 管理：\n\n```powershell\nnode .\\src\\cli\\index.js alias set <fromUserId> \"张三\"\nnode .\\src\\cli\\index.js alias get <fromUserId>\nnode .\\src\\cli\\index.js alias resolve \"张三\"\nnode .\\src\\cli\\index.js send --alias \"张三\" --text \"你好\" --json\n```\n\ncleanup：\n\n```powershell\nnode .\\src\\cli\\index.js cleanup --dry-run --message-retention-days 30 --attachment-retention-days 30 --max-history-messages 10000 --json\nnode .\\src\\cli\\index.js cleanup --message-retention-days 30 --attachment-retention-days 30 --max-history-messages 10000 --json\n```\n\n延迟补发队列：\n\n```powershell\nnode .\\src\\cli\\index.js queue list --json\nnode .\\src\\cli\\index.js queue clear --user <fromUserId> --json\n```\n\n当 `send` 因 iLink 返回 `INVALID_CONTEXT_TOKEN` 失败且尚未投递任何分片时，消息会入队。下一次 `fetch` 收到同一用户携带新 context token 的入站消息时，只尝试补发该用户队列中的第一条，避免一次性刷屏。\n\n## M8 媒体下载\n\n```powershell\nnode .\\src\\cli\\index.js fetch --download-media --timeout 15000 --json\n```\n\n带媒体的消息会包含：\n\n```json\n{\n  \"attachments\": [\n    {\n      \"kind\": \"image\",\n      \"fileId\": \"media id\",\n      \"fileName\": \"safe local file name\",\n      \"path\": \"C:\\\\tmp\\\\wxb-test\\\\inbox\\\\bot_id\\\\2026-05-17\\\\message-1-image.jpg\",\n      \"mimeType\": \"image/jpeg\",\n      \"bytes\": 12345,\n      \"encrypted\": true,\n      \"decrypted\": true\n    }\n  ],\n  \"mediaDownload\": {\n    \"requested\": 1,\n    \"succeeded\": 1,\n    \"failed\": 0\n  }\n}\n```\n\n如果下载失败，消息仍会输出文本和元数据，并在对应 item 上标记 `download.ok: false`。\n如果 `download.error.code` 为 `MEDIA_URL_MISSING` 且 `metadata` 为空，请保留该 item 的 `diagnostics.itemKeys` 和 `diagnostics.payloadKeys`，用于补齐真实协议字段映射；这些诊断只包含字段名，不包含 token。\n\n## M8 验收清单\n\n- Windows PowerShell 下 `npm.cmd test` 全部通过。\n- Windows 下能完成 `login`、`fetch`、`send` 手工链路。\n- `poll` 连续运行不会破坏游标。\n- `heartbeat` 可被计划任务调用，不启动常驻服务。\n- `cleanup --dry-run` 与实际清理数量一致，且不删除账号凭证和 context token。\n- alias 不影响 userId 直发。\n- 延迟补发每次入站最多补发一条。\n- `WX_STATE_DIR` 包含空格或中文时命令能正常读写状态。\n- `fetch --download-media` 能保存图片/文件/语音/视频，并返回绝对路径。\n- 下载失败不会影响文本项处理。\n- 附件路径被规范化到 inbox 内，不允许路径穿越。\n- 损坏 JSON/JSONL 状态文件会返回结构化错误和恢复提示。\n- stdout/stderr/错误 JSON 不输出完整 `botToken` 或 `contextToken`。\n- 默认非文本消息不会导致 `fetch` 失败，显式下载时返回附件或下载失败状态。\n","readmeFilename":"README.md"}