{"id":50266226,"url":"https://github.com/formulahendry/wechat-acp","last_synced_at":"2026-05-30T12:01:39.405Z","repository":{"id":346378813,"uuid":"1189639284","full_name":"formulahendry/wechat-acp","owner":"formulahendry","description":"Bridge WeChat chat messages to any ACP-compatible AI agent (Claude, Codex, Copilot, Qwen, Gemini, OpenCode and more)","archived":false,"fork":false,"pushed_at":"2026-05-26T14:17:39.000Z","size":478,"stargazers_count":566,"open_issues_count":15,"forks_count":64,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-05-26T16:21:18.644Z","etag":null,"topics":["acp","agent-client-protocol","agentclientprotocol","wechat-bot","weixin"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/wechat-acp","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/formulahendry.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-23T14:21:00.000Z","updated_at":"2026-05-26T15:51:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/formulahendry/wechat-acp","commit_stats":null,"previous_names":["formulahendry/wechat-acp"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/formulahendry/wechat-acp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formulahendry%2Fwechat-acp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formulahendry%2Fwechat-acp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formulahendry%2Fwechat-acp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formulahendry%2Fwechat-acp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/formulahendry","download_url":"https://codeload.github.com/formulahendry/wechat-acp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formulahendry%2Fwechat-acp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33568859,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acp","agent-client-protocol","agentclientprotocol","wechat-bot","weixin"],"created_at":"2026-05-27T14:01:26.489Z","updated_at":"2026-05-30T12:01:39.396Z","avatar_url":"https://github.com/formulahendry.png","language":"TypeScript","funding_links":[],"categories":["Integrations and Bridges"],"sub_categories":["Plugins and add-ons"],"readme":"# WeChat ACP\r\n\r\n[![NPM Downloads](https://img.shields.io/npm/d18m/wechat-acp)](https://www.npmjs.com/package/wechat-acp)\r\n\r\nBridge WeChat direct messages to any ACP-compatible AI agent.\r\n\r\n`wechat-acp` logs in with the WeChat iLink bot API, polls incoming 1:1 messages, forwards them to an ACP agent over stdio, and sends the agent reply back to WeChat.\r\n\r\n\u003cimg src=\"./resources/screenshot.jpg\" alt=\"wechat-acp screenshot\" width=\"400\" /\u003e\r\n\r\n## Features\r\n\r\n- WeChat QR login with terminal QR rendering\r\n- One ACP agent session per WeChat user\r\n- Built-in ACP agent presets for common CLIs\r\n- Custom raw agent command support\r\n- Auto-allow permission requests from the agent\r\n- Direct message only; group chats are ignored\r\n- Background daemon mode\r\n\r\n## Requirements\r\n\r\n- Node.js 20+\r\n- A WeChat environment that can use the iLink bot API\r\n- An ACP-compatible agent available locally or through `npx`\r\n\r\n## Quick Start\r\n\r\nStart with a built-in agent preset:\r\n\r\n```bash\r\nnpx -y wechat-acp@latest --agent copilot\r\n```\r\n\r\nOr use a raw custom command:\r\n\r\n```bash\r\nnpx -y wechat-acp@latest --agent \"npx my-agent --acp\"\r\n```\r\n\r\nOn first run, the bridge will:\r\n\r\n1. Start WeChat QR login\r\n2. Render a QR code in the terminal\r\n3. Save the login token under `~/.wechat-acp`\r\n4. Begin polling direct messages\r\n\r\n## Built-in Agent Presets\r\n\r\nList the bundled presets:\r\n\r\n```bash\r\nnpx wechat-acp agents\r\n```\r\n\r\nCurrent presets:\r\n\r\n- `copilot`\r\n- `claude`\r\n- `gemini`\r\n- `qwen`\r\n- `codex`\r\n- `opencode`\r\n- `openclaw`\r\n- `kiro`\r\n- `hermes`\r\n- `kimi`\r\n- `pi`\r\n\r\nThese presets resolve to concrete `command + args` pairs internally, so users do not need to type long `npx ...` commands.\r\n\r\n## CLI Usage\r\n\r\n```text\r\nwechat-acp --agent \u003cpreset|command\u003e [options]\r\nwechat-acp agents\r\nwechat-acp inject --text \u003ctext\u003e\r\nwechat-acp stop\r\nwechat-acp status\r\n```\r\n\r\nOptions:\r\n\r\n- `--agent \u003cvalue\u003e`: built-in preset name or raw agent command\r\n- `--cwd \u003cdir\u003e`: working directory for the agent process\r\n- `--login`: force QR re-login and replace the saved token\r\n- `--daemon`: run in background after startup\r\n- `--config \u003cfile\u003e`: load JSON config file\r\n- `--instance \u003cname\u003e`: run as a named, isolated instance. See \"Running multiple instances\" below.\r\n- `--idle-timeout \u003cminutes\u003e`: session idle timeout, default `1440` (use `0` for unlimited)\r\n- `--max-sessions \u003ccount\u003e`: maximum concurrent user sessions, default `10`\r\n- `--inbox-dir \u003cdir\u003e`: directory where received binary files are saved (default: `\u003cstorage.dir\u003e/inbox`). The agent sees the absolute saved path in the prompt and can read the file directly.\r\n- `--no-inbox`: do not save received files; the agent only sees a size notice.\r\n- `--hide-thoughts`: do not forward agent thinking to WeChat (default: forwarded)\r\n- `--hide-diffs`: do not forward ACP file diffs to WeChat (default: forwarded)\r\n- `inject --text \u003ctext\u003e`: enqueue a local text message for the running daemon\r\n- `-V, --version`: print version and exit\r\n- `-h, --help`: show help\r\n\r\nExamples:\r\n\r\n```bash\r\nnpx -y wechat-acp@latest --agent copilot\r\nnpx -y wechat-acp@latest --agent claude --cwd D:\\code\\project\r\nnpx -y wechat-acp@latest --agent \"npx @github/copilot --acp\"\r\nnpx -y wechat-acp@latest --agent gemini --daemon\r\n```\r\n\r\n## Running multiple instances\r\n\r\nBy default everything (saved login token, daemon pid/log, sync state, telemetry id) lives under `~/.wechat-acp/`, which means a single machine can only host one bridge at a time. Pass `--instance \u003cname\u003e` to namespace all of that under `~/.wechat-acp/instances/\u003cname\u003e/` and run several bridges side by side, each with its own WeChat account and project directory.\r\n\r\nTypical setup: WeChat account 1 drives project A, WeChat account 2 drives project B.\r\n\r\n```bash\r\n# Terminal 1: scan with WeChat account 1\r\nnpx -y wechat-acp@latest --instance projA --agent copilot --cwd D:\\code\\repo-a\r\n\r\n# Terminal 2: scan with WeChat account 2\r\nnpx -y wechat-acp@latest --instance projB --agent copilot --cwd D:\\code\\repo-b\r\n```\r\n\r\nThe first run of each instance prints its own QR code. Tokens are saved per instance, so subsequent runs reuse them independently.\r\n\r\nThe `stop` and `status` subcommands also honor `--instance`:\r\n\r\n```bash\r\nnpx -y wechat-acp@latest status --instance projA\r\nnpx -y wechat-acp@latest stop   --instance projB\r\n```\r\n\r\nWithout `--instance`, paths fall back to `~/.wechat-acp/` exactly as before, so existing installs are unaffected.\r\n\r\n## Configuration File\r\n\r\nYou can provide a JSON config file with `--config`.\r\n\r\nExample:\r\n\r\n```json\r\n{\r\n  \"agent\": {\r\n    \"preset\": \"copilot\",\r\n    \"cwd\": \"D:/code/project\",\r\n    \"showDiffs\": true\r\n  },\r\n  \"session\": {\r\n    \"idleTimeoutMs\": 86400000,\r\n    \"maxConcurrentUsers\": 10\r\n  }\r\n}\r\n```\r\n\r\nYou can also override or add agent presets:\r\n\r\n```json\r\n{\r\n  \"agent\": {\r\n    \"preset\": \"my-agent\"\r\n  },\r\n  \"agents\": {\r\n    \"my-agent\": {\r\n      \"label\": \"My Agent\",\r\n      \"description\": \"Internal team agent\",\r\n      \"command\": \"npx\",\r\n      \"args\": [\"my-agent-cli\", \"--acp\"]\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n## Customizing bridge command names (aliases)\r\n\r\nBridge slash commands like `/acp-config` and `/acp-cancel` have fixed\r\nbuilt-in names that may not feel natural to everyone, and can clash with\r\nslash commands built into the underlying agent. You can map any bridge\r\ncommand to one or more custom aliases via the `commandAliases` config map:\r\n\r\n```json\r\n{\r\n  \"commandAliases\": {\r\n    \"/acp-cancel\": [\"/cancel\", \"/取消\", \"取消\"],\r\n    \"/acp-config\": [\"/config\", \"/设置\"]\r\n  }\r\n}\r\n```\r\n\r\nWith this config:\r\n\r\n- Sending `/取消` cancels the current turn (same as `/acp-cancel`), and\r\n  `/取消 all` works like `/acp-cancel all`.\r\n- Sending `/设置` lists ACP session config (same as `/acp-config`), and\r\n  `/设置 set \u003cconfigId\u003e \u003cvalue\u003e` works like `/acp-config set ...`.\r\n- The original built-in names always keep working as a fallback.\r\n\r\nTwo alias styles are supported:\r\n\r\n- **Slash aliases** (start with `/`, e.g. `/cancel`) behave like the\r\n  built-in commands: they match the command token and may be followed by\r\n  arguments (`/cancel all`). They must not contain whitespace.\r\n- **Bare-phrase aliases** (no leading `/`, e.g. `取消`) match only when\r\n  they equal the *entire* message. This is handy for WeChat voice input,\r\n  where saying `/取消` out loud feels unnatural — a transcribed `取消`\r\n  triggers the command. Because they require an exact full-message match,\r\n  they take no arguments.\r\n\r\nNotes:\r\n\r\n- Keys must be a known bridge command (`/acp-config`, `/acp-cancel`, `/acp-prompt-start`, or `/acp-prompt-done`).\r\n- An alias may not collide with a built-in command name or be mapped to\r\n  more than one command. Invalid configs are rejected at startup.\r\n\r\n## Runtime Behavior\r\n\r\n- Each WeChat user gets a dedicated ACP session and subprocess.\r\n- Messages are processed serially per user.\r\n- Replies are formatted for WeChat before sending.\r\n- Typing indicators are sent when supported by the WeChat API.\r\n- Sessions are cleaned up after inactivity (set `idleTimeoutMs` to `0` to disable idle cleanup).\r\n\r\n## WeChat ACP config command\r\n\r\n`wechat-acp` reserves a bridge-level chat command for inspecting and changing ACP session configuration without exposing a UI picker in WeChat:\r\n\r\n```text\r\n/acp-config\r\n/acp-config set \u003cconfigId\u003e \u003cvalue\u003e\r\n```\r\n\r\nExamples:\r\n\r\n```text\r\n/acp-config\r\n/acp-config set model gpt-5-mini\r\n/acp-config set mode plan\r\n/acp-config set reasoning_effort low\r\n```\r\n\r\nNotes:\r\n\r\n- The command only works after the WeChat user already has an active ACP session. If not, send a normal message first so the session is created.\r\n- Available `configId` values come directly from the ACP agent's `configOptions`, so the exact list depends on the configured agent.\r\n- This command is handled by `wechat-acp` itself and is **not** forwarded to the underlying agent.\r\n- You can give this command your own aliases via `commandAliases` (see [Customizing bridge command names](#customizing-bridge-command-names-aliases)).\r\n\r\n## WeChat ACP cancel command\r\n\r\nWeChat does not offer a stop button for an in-flight agent turn, so the bridge exposes a chat command instead:\r\n\r\n```text\r\n/acp-cancel\r\n/acp-cancel all\r\n```\r\n\r\nBehavior:\r\n\r\n- `/acp-cancel` sends `session/cancel` to the agent for the current turn. The in-flight `prompt()` resolves with `stopReason: \"cancelled\"`, any partial output already streamed is delivered to WeChat with a `[cancelled]` suffix, and the next queued message (if any) is processed as usual.\r\n- `/acp-cancel all` does the same and also drops every message that was queued behind the current turn. Local injections (`wechat-acp inject`) waiting on those queued messages are rejected.\r\n- If no turn is in flight, the command replies with a notice and is a no-op.\r\n- This command is handled by `wechat-acp` itself and is **not** forwarded to the underlying agent.\r\n- You can give this command your own aliases via `commandAliases` (see [Customizing bridge command names](#customizing-bridge-command-names-aliases)).\r\n\r\n## Multi-part message buffering\r\n\r\nWeChat does not allow sending images, files, and text in a single message. To work around this, the bridge provides a buffering mode that collects multiple messages and sends them to the agent as one combined request:\r\n\r\n```text\r\n/acp-prompt-start\r\n/acp-prompt-done\r\n```\r\n\r\nUsage:\r\n\r\n1. Send `/acp-prompt-start` to enter buffering mode. The bridge replies with a confirmation.\r\n2. Send any number of messages (text, images, files) in any order. These are collected locally and **not** forwarded to the agent.\r\n3. Send `/acp-prompt-done` to flush the buffer. All collected content is combined into a single agent request.\r\n\r\nThis avoids triggering multiple agent turns (and multiple replies) when a user needs to send mixed content.\r\n\r\n- If `/acp-prompt-done` is sent with nothing buffered, the bridge replies with a warning and no agent request is made.\r\n- If `/acp-prompt-start` is sent while already buffering, the bridge reminds the user and keeps the existing buffer.\r\n- Buffering is per-user and held in memory. It does not persist across bridge restarts.\r\n- Buffers expire after 10 minutes of inactivity. A maximum of 50 content blocks can be collected per buffer.\r\n- This command is handled by `wechat-acp` itself and is **not** forwarded to the underlying agent.\r\n\r\n## Injecting messages locally\r\n\r\n`wechat-acp inject` lets local automation enqueue a text message for the running daemon. The daemon treats it like an incoming direct message from the target user, sends it to the configured ACP agent, and replies through WeChat.\r\n\r\nThis is useful for cron or launchd jobs, for example a daily AI news prompt:\r\n\r\n```bash\r\nnpx wechat-acp inject --instance main --text \"今日 AI 资讯\"\r\n```\r\n\r\nTargets:\r\n\r\n- Default target: `last-active-user`\r\n- Custom target: `--to \u003cwechat-user-id\u003e`\r\n\r\nThe daemon learns `last-active-user` from real incoming WeChat messages and stores the latest `userId + contextToken` under the instance storage directory. If no user has messaged the bot yet, ask the target user to send any message once, then retry the injection.\r\n\r\nInjected messages are stored as JSON files under:\r\n\r\n```text\r\n~/.wechat-acp/inject/\r\n~/.wechat-acp/instances/\u003cname\u003e/inject/\r\n```\r\n\r\nThe queue is file-based:\r\n\r\n```text\r\ninject/\r\n├── pending/\r\n├── processing/\r\n├── done/\r\n└── failed/\r\n```\r\n\r\n`inject` only writes to `pending/`; the daemon moves files through the other directories as it processes them. If the daemon is not running, the message remains queued and will be processed after the daemon starts.\r\n\r\nFor longer prompts, use a file:\r\n\r\n```bash\r\nnpx wechat-acp inject --instance main --file ./prompt.txt\r\n```\r\n\r\nExample Linux cron entry:\r\n\r\n```cron\r\n0 7 * * * /usr/local/bin/wechat-acp inject --instance main --text \"今日 AI 资讯\"\r\n```\r\n\r\n## Receiving files\r\n\r\nWhen a WeChat user sends a binary file (PDF, image, audio recording exported as a file, ZIP, etc.), `wechat-acp` downloads and decrypts it from the WeChat CDN, then **saves it to disk** so the ACP agent can read it by absolute path. The agent receives a text block like:\r\n\r\n```\r\n[Received file: 报告.pdf (484067 bytes) — saved to: /Users/me/.wechat-acp/inbox/2026-05-21T09-29-12-492Z-报告.pdf]\r\n```\r\n\r\nAny ACP agent that can read local files (Copilot CLI, Claude Code, Codex, …) can then open the saved path with its normal file tools.\r\n\r\nDefaults:\r\n\r\n- Save location: `\u003cstorage.dir\u003e/inbox`, i.e. `~/.wechat-acp/inbox` by default, or `~/.wechat-acp/instances/\u003cname\u003e/inbox` when `--instance` is used.\r\n- Filename: `\u003cISO-timestamp\u003e-\u003coriginal-name\u003e`, with filesystem-unsafe characters in the original name replaced by `_`. Unicode (including Chinese) filenames are preserved.\r\n- No automatic cleanup. Files live until you delete them; agents may reference them long after the WeChat message arrives. Periodically run e.g. `find ~/.wechat-acp/inbox -mtime +30 -delete` if you want to prune.\r\n\r\nOverrides:\r\n\r\n- `--inbox-dir /some/path` — write files somewhere else (handy if you want them under iCloud Drive, a project folder, etc.)\r\n- `--no-inbox` — keep the pre-0.3 behavior where the file buffer is dropped after download and the agent only sees `[Received file: name, N bytes]`.\r\n\r\nText-typed files (`.md`, `.json`, source code, …) and images keep their previous behavior: their content is embedded inline in the prompt as a `resource` / `image` block, no disk write needed.\r\n\r\n## Storage\r\n\r\nBy default, runtime files are stored under:\r\n\r\n```text\r\n~/.wechat-acp\r\n```\r\n\r\nThis directory is used for:\r\n\r\n- saved login token\r\n- daemon pid file\r\n- daemon log file\r\n- sync state\r\n- anonymous telemetry install id (`telemetry-id`, see Telemetry section)\r\n- `inbox/` — binary files received from WeChat (see \"Receiving files\"); disable with `--no-inbox` or relocate with `--inbox-dir`\r\n- `state.json` — last active user and context token for local injection\r\n- `inject/` — local injected message queue\r\n\r\nWhen `--instance \u003cname\u003e` is used, the same files live under `~/.wechat-acp/instances/\u003cname\u003e/` instead, fully isolated from other instances.\r\n\r\n## Current Limitations\r\n\r\n- Direct messages only; group chats are ignored\r\n- MCP servers are not used\r\n- Permission requests are auto-approved\r\n- Agent communication is subprocess-only over stdio\r\n- Some preset agents may require separate authentication before they can respond successfully\r\n\r\n## Development\r\n\r\nFor local development:\r\n\r\n```bash\r\nnpm install\r\nnpm run build\r\n```\r\n\r\nRun the built CLI locally:\r\n\r\n```bash\r\nnode dist/bin/wechat-acp.js --help\r\n```\r\n\r\nWatch mode:\r\n\r\n```bash\r\nnpm run dev\r\n```\r\n\r\n## Telemetry\r\n\r\n`wechat-acp` collects anonymous usage telemetry via Azure Application Insights to help understand which agent presets are used and to detect crashes.\r\n\r\n**To disable telemetry**, set the `WECHAT_ACP_TELEMETRY` environment variable to `0`, `false`, or `off` before running:\r\n\r\n```bash\r\nWECHAT_ACP_TELEMETRY=0 npx wechat-acp --agent copilot\r\n```\r\n\r\n**What is collected** (15 event types only):\r\n\r\n- `app.start` / `app.stop` — process lifecycle, agent preset name, daemon flag, uptime\r\n- `login.success` / `login.failure` / `token.reused` — WeChat login outcomes (no token, no QR URL)\r\n- `message.received` — message arrived; only the categorical kind (`text` / `image` / `voice` / `file` / `video` / `empty`) and a hashed user id\r\n- `message.injected` — local injection queued for processing; only target kind (`last-active-user` / `explicit`) and a hashed user id\r\n- `command.acp_config.view` — `/acp-config` invoked to list options; whether a session exists and the option count\r\n- `command.acp_config.set` — `/acp-config set` succeeded; `configId`, option type (`select` / `boolean`), and the resolved option value (all from the agent's declared `configOptions`, never the user's raw input)\r\n- `command.acp_cancel` — `/acp-cancel` invoked; whether the queue was drained, whether an in-flight turn was actually cancelled, and how many queued messages were dropped\r\n- `command.buffer_start` — `/acp-prompt-start` invoked to enter buffering mode\r\n- `command.buffer_done` — `/acp-prompt-done` invoked to flush buffer; number of content blocks collected\r\n- `session.created` — new ACP session opened\r\n- `prompt.completed` — ACP turn finished; agent preset, stop reason, duration, reply length\r\n- `reply.sent` — reply pushed back to WeChat; segment count, total length\r\n\r\nPlus exception reports for `monitor`, `prompt`, `reply`, `auth`, `agent_spawn`, `enqueue`, `buffer`, `command`, and `state` failures.\r\n\r\n**What is never collected**: message bodies, filenames, voice transcripts, image URLs, login tokens, QR codes, raw agent command strings, environment variables, working directory paths, raw WeChat user IDs.\r\n\r\nUser IDs are sha256-hashed with a per-install salt stored in `~/.wechat-acp/telemetry-id`. The salt is generated on first run and never leaves your machine. Delete the file to rotate it.\r\n\r\n## License\r\n\r\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformulahendry%2Fwechat-acp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fformulahendry%2Fwechat-acp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformulahendry%2Fwechat-acp/lists"}