wechat-claude-sessions 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CONTRIBUTING.md +52 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +264 -0
  4. package/README.md +245 -0
  5. package/SECURITY.md +45 -0
  6. package/dist/bindings.d.ts +4 -0
  7. package/dist/bindings.js +50 -0
  8. package/dist/bindings.js.map +1 -0
  9. package/dist/claude-config.d.ts +4 -0
  10. package/dist/claude-config.js +49 -0
  11. package/dist/claude-config.js.map +1 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +345 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/daemon.d.ts +2 -0
  16. package/dist/daemon.js +771 -0
  17. package/dist/daemon.js.map +1 -0
  18. package/dist/i18n.d.ts +67 -0
  19. package/dist/i18n.js +249 -0
  20. package/dist/i18n.js.map +1 -0
  21. package/dist/ilink.d.ts +49 -0
  22. package/dist/ilink.js +533 -0
  23. package/dist/ilink.js.map +1 -0
  24. package/dist/inbox.d.ts +4 -0
  25. package/dist/inbox.js +75 -0
  26. package/dist/inbox.js.map +1 -0
  27. package/dist/launchd.d.ts +7 -0
  28. package/dist/launchd.js +71 -0
  29. package/dist/launchd.js.map +1 -0
  30. package/dist/monitoring.d.ts +4 -0
  31. package/dist/monitoring.js +33 -0
  32. package/dist/monitoring.js.map +1 -0
  33. package/dist/paths.d.ts +11 -0
  34. package/dist/paths.js +35 -0
  35. package/dist/paths.js.map +1 -0
  36. package/dist/pkg-root.d.ts +2 -0
  37. package/dist/pkg-root.js +11 -0
  38. package/dist/pkg-root.js.map +1 -0
  39. package/dist/server.d.ts +2 -0
  40. package/dist/server.js +364 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/session-numbers.d.ts +1 -0
  43. package/dist/session-numbers.js +44 -0
  44. package/dist/session-numbers.js.map +1 -0
  45. package/dist/sessions.d.ts +15 -0
  46. package/dist/sessions.js +135 -0
  47. package/dist/sessions.js.map +1 -0
  48. package/dist/tmux.d.ts +7 -0
  49. package/dist/tmux.js +47 -0
  50. package/dist/tmux.js.map +1 -0
  51. package/dist/types.d.ts +150 -0
  52. package/dist/types.js +2 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/utils.d.ts +12 -0
  55. package/dist/utils.js +88 -0
  56. package/dist/utils.js.map +1 -0
  57. package/dist/watch-inbox.d.ts +2 -0
  58. package/dist/watch-inbox.js +102 -0
  59. package/dist/watch-inbox.js.map +1 -0
  60. package/package.json +50 -0
  61. package/templates/com.wechat-claude.daemon.plist.template +31 -0
  62. package/templates/wechat.md +20 -0
@@ -0,0 +1,52 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in wechat-claude!
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ git clone https://github.com/Neil-UWA/wechat-claude.git
9
+ cd wechat-claude
10
+ npm install
11
+ npm run build
12
+ npm test
13
+ ```
14
+
15
+ To run your checkout as the real thing (instead of the published package),
16
+ link it and then set up as usual:
17
+
18
+ ```bash
19
+ npm link # puts the `wechat-claude` bin on your PATH
20
+ wechat-claude setup
21
+ wechat-claude daemon install
22
+ ```
23
+
24
+ - Source is TypeScript in `src/`, compiled to `dist/` (gitignored).
25
+ - Files shipped to users live in `templates/` — the `/wechat` slash command
26
+ and the launchd plist template. `.claude/commands/wechat.md` is a symlink
27
+ to `templates/wechat.md`, so edit the template and both stay in sync.
28
+ - The CLI (`src/cli.ts`) is the only entry point users have after a global
29
+ install; the `daemon:*` npm scripts just delegate to it. Add new operations
30
+ as CLI subcommands, not as npm scripts.
31
+ - Tests use [vitest](https://vitest.dev): `npm test` (or `npm run test:watch`).
32
+ - The daemon and MCP server share logic through small modules
33
+ (`src/sessions.ts`, `src/inbox.ts`, `src/tmux.ts`, etc.) — add behavior
34
+ there so both stay in sync, and cover it with a unit test.
35
+
36
+ ## Conventions
37
+
38
+ - Strict TypeScript: no `any`, no non-null assertions, explicit return types
39
+ on exported functions.
40
+ - User-facing WeChat strings live in `src/i18n.ts` with `zh` (default) and
41
+ `en` catalogs — add both when you add a message, and localize content
42
+ markers via `marker()` and relative times via `formatAgo(ms, lang)`.
43
+ - Never build a shell command by string interpolation of user input. Use
44
+ `spawnSync` with an argv array (see `src/tmux.ts`).
45
+
46
+ ## Pull requests
47
+
48
+ - Keep PRs focused; include tests for new behavior.
49
+ - Run `npm run build && npm test` before pushing — CI runs the same.
50
+ - Describe user-facing changes so `/help`, both READMEs (`README.md` is the
51
+ Chinese default, `README.en.md` the English version — keep them in sync), and MCP tool
52
+ descriptions can be kept in sync.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Neil Lu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,264 @@
1
+ [中文](README.md) | English
2
+
3
+ # wechat-claude
4
+
5
+ Control Claude Code from your phone via WeChat. Message a task like
6
+ `/run fix the login bug` and a Claude Code session runs it on your machine and
7
+ sends the result back to your chat. Built on the ilink Bot API — no
8
+ third-party dependencies beyond the MCP SDK.
9
+
10
+ > ⚠️ **This runs code on your computer from chat messages.** Read
11
+ > [SECURITY.md](SECURITY.md) before using it. Run it only on a machine you
12
+ > control and an account only you can message.
13
+
14
+ ## Prerequisites
15
+
16
+ - **Node.js 20+** and **[Claude Code](https://claude.com/claude-code)**.
17
+ - **WeChat with ClawBot / ilink Bot access.** The bot is bound to *your* WeChat
18
+ account by scanning a QR code at login; it is not publicly discoverable and
19
+ strangers cannot message it. macOS is the first-class platform (launchd
20
+ autostart + native notifications); Linux/Windows run the daemon manually.
21
+
22
+ ## Architecture
23
+
24
+ ```
25
+ WeChat User ←→ ilink Bot API ←→ Daemon (polling + routing) ←→ Inbox Files ←→ MCP Server (Claude Code)
26
+ ```
27
+
28
+ - **Daemon**: Standalone Node.js process that polls WeChat for messages, handles routing commands, and writes messages to per-session inbox files. Runs independently of Claude Code.
29
+ - **MCP Server**: Lightweight tool server registered with Claude Code. Reads from its session's inbox, sends replies via ilink API. No polling.
30
+
31
+ ## Setup
32
+
33
+ ```bash
34
+ npm install -g wechat-claude-sessions
35
+ wechat-claude setup
36
+ ```
37
+
38
+ Install it **globally**, not with a bare `npx` — `setup` registers absolute
39
+ paths (MCP server, launchd service) that must survive past the current shell,
40
+ and npx's cache is not a stable location. Building from a git checkout works
41
+ too: see [CONTRIBUTING.md](CONTRIBUTING.md).
42
+
43
+ `setup` registers the MCP server, installs the `/wechat` slash command into
44
+ `~/.claude/commands/`, and walks you through QR login (it opens the QR image in
45
+ your browser — scan it with WeChat). The bot token is saved to
46
+ `~/.claude/wechat/session.json` (mode 0600); you won't scan again unless it
47
+ expires.
48
+
49
+ Then start the daemon and enable monitoring:
50
+
51
+ ```bash
52
+ wechat-claude daemon install # macOS: install as a launchd service (auto-start + restart)
53
+ # or, without autostart:
54
+ wechat-claude daemon # run it in the foreground / your own supervisor
55
+ ```
56
+
57
+ Finally, type `/wechat` in any Claude Code session. That starts a persistent
58
+ watcher (`dist/watch-inbox.js`) that reacts to messages instantly via
59
+ `fs.watch` and marks the session `[monitoring]`. The daemon also auto-starts on
60
+ `/wechat` and after a successful login if it isn't already running (it's a
61
+ singleton, guarded by a pid file).
62
+
63
+ `wechat-claude daemon install` generates the launchd plist for *your* paths
64
+ from a packaged template and writes it to
65
+ `~/Library/LaunchAgents/com.wechat-claude.daemon.plist` — no manual editing.
66
+ Re-run it after a Node version switch or a global upgrade to refresh the paths.
67
+ Other CLI commands: `wechat-claude login` (re-auth) and `wechat-claude status`.
68
+
69
+ If the WeChat login expires, the daemon posts a macOS notification and sets a
70
+ flag that `wechat_status` surfaces, so the next `/wechat` prompts a re-login —
71
+ see [Troubleshooting](#troubleshooting) since you'll be away from the Mac.
72
+
73
+ ## WeChat Commands
74
+
75
+ Send these from WeChat to control routing:
76
+
77
+ | Command | Description |
78
+ |---------|-------------|
79
+ | `/sessions` or `/ls` | List active Claude Code sessions (`[监控中]` = actively monitoring) |
80
+ | `/s <number> <message>` | Send message to session by its number. Numbers are stable for a session's lifetime — they never shift when other sessions open or close (retired numbers aren't reused; numbering resets once all sessions are gone) |
81
+ | `/use <number\|name\|pid>` | Bind your chat to one session: every plain message goes straight to it (survives daemon restarts). `/use off` unbinds; `/use` shows the current binding. Closing the bound session clears the binding automatically |
82
+ | `/s <name> <message>` | Send message to session by name (fuzzy match; if ambiguous, prefers the monitored / most recently active one) |
83
+ | `/s <pid> <message>` | Send message to session by pid (duplicate names are listed as `name#pid`) |
84
+ | `/run [--safe] [dir] <task>` | Start a new Claude session in tmux to run a task. The launched session is instructed to send its result back to WeChat. Runs unattended by default (`--dangerously-skip-permissions`); pass `--safe` for `--permission-mode acceptEdits`, where bash commands wait for confirmation at the computer. The first unattended run accepts skip-permissions mode in `~/.claude.json` on your behalf — a detached session cannot answer Claude Code's one-time dialog — and says so in its reply; see [SECURITY.md](SECURITY.md) |
85
+ | `/runs` | List running `/run` task sessions |
86
+ | `/stop <name>` | Kill a `/run` task session (names start with `wc-`) |
87
+ | `/close <number\|name\|pid>` | Close a Claude session remotely — terminates its Claude process (and MCP server) and removes it from the list. Unsaved work in that session is lost. If a name matches several sessions it lists them instead of guessing; `/close <name> all` closes all matches, `/close idle` cleans up every unmonitored session idle for 2+ hours. Every close reply ends with a remaining-session summary |
88
+ | `/help` | Show command help |
89
+ | *(no prefix)* | Send to the most recently active **monitoring** session (falls back to most recently active overall) |
90
+
91
+ Delivery feedback: if a message lands in a session that isn't monitoring its
92
+ inbox, the daemon warns you immediately; if a delivered message is still
93
+ unread after 2 minutes, it sends a reminder.
94
+
95
+ Incoming images are downloaded and decrypted automatically to
96
+ `~/.claude/wechat/media/` (cleaned up after 7 days); the routed message text
97
+ contains the local path (`[图片: /path/to/file.png]`) so the receiving Claude
98
+ session can open the file directly.
99
+
100
+ ### /run directory resolution
101
+
102
+ `/run <name> <task>` resolves `<name>` in this order:
103
+
104
+ 1. An active session whose name (or directory basename) matches
105
+ 2. An absolute path
106
+ 3. `<dir>/<name>` for each search directory — the `repoDirs` entries in
107
+ `~/.claude/wechat/config.json` (optional, e.g. `{"repoDirs": ["~/code"]}`;
108
+ entries must be absolute or `~`-prefixed, invalid entries are reported)
109
+ plus the parent directory of every active session's cwd (the home
110
+ directory itself is never used as a search root)
111
+
112
+ If the first word looks like a directory name but resolves nowhere, the run
113
+ is cancelled with an explanation instead of silently executing in another
114
+ directory. Use `/run . <task>` to force the default directory when the task
115
+ text happens to start with a path-like word.
116
+
117
+ ## MCP Tools
118
+
119
+ | Tool | Description |
120
+ |------|-------------|
121
+ | `wechat_login` | Generate QR code for WeChat login |
122
+ | `wechat_login_poll` | Poll QR code scan status |
123
+ | `wechat_get_messages` | Read and clear incoming messages from inbox |
124
+ | `wechat_send_text` | Send a text reply to a WeChat user |
125
+ | `wechat_send_image` | Send an image file (with optional caption) to a WeChat user |
126
+ | `wechat_set_session_name` | Set a custom name for routing |
127
+ | `wechat_status` | Check connection, daemon, and session info |
128
+ | `wechat_logout` | Disconnect and clear session |
129
+
130
+ ## Session Naming
131
+
132
+ Sessions are automatically named based on the working directory:
133
+
134
+ - Git repo: `reponame:branch` (e.g., `myapp:main`)
135
+ - Worktree: `reponame/worktree-name` (e.g., `myapp/feature-x`)
136
+ - Non-git: directory name
137
+
138
+ Use `wechat_set_session_name` to set a custom name.
139
+
140
+ ## File Layout
141
+
142
+ ```
143
+ ~/.claude/wechat/
144
+ ├── session.json # ilink bot token (persisted login)
145
+ ├── config.json # optional settings (e.g. repoDirs for /run)
146
+ ├── bindings.json # /use bindings (WeChat user -> session)
147
+ ├── session-numbers.json # stable session number registry
148
+ ├── media/ # downloaded incoming images (7-day retention)
149
+ ├── context_tokens.json # shared context tokens (daemon ↔ MCP server)
150
+ ├── daemon.pid # daemon process ID
151
+ ├── daemon.log # daemon output
152
+ ├── cursor.txt # message polling cursor
153
+ ├── expired.flag # present when the WeChat login has expired
154
+ ├── sessions/ # registered Claude Code sessions
155
+ │ └── <pid>.json
156
+ ├── inbox/ # per-session message queues
157
+ │ └── <pid>.json
158
+ ├── heartbeat/ # watcher heartbeats (session is [monitoring])
159
+ │ └── <pid>
160
+ └── typing/ # typing indicator state
161
+ └── <userId>
162
+ ```
163
+
164
+ ## CLI
165
+
166
+ ```bash
167
+ wechat-claude setup # Register MCP server + /wechat command, then log in
168
+ wechat-claude login # (Re)authenticate by scanning a QR code
169
+ wechat-claude status # Show login / daemon / service state
170
+ wechat-claude daemon # Run the daemon in the foreground
171
+ wechat-claude daemon restart # Restart it (use after upgrading)
172
+ wechat-claude daemon install # Install as a launchd service (macOS)
173
+ wechat-claude daemon uninstall # Remove the launchd service
174
+ wechat-claude daemon status # Check daemon / service state
175
+ wechat-claude daemon log # Tail the daemon log
176
+ ```
177
+
178
+ In a git checkout the same commands are available as npm scripts
179
+ (`npm run daemon:install`, …), which just delegate to the CLI.
180
+
181
+ ### Upgrading
182
+
183
+ ```bash
184
+ npm install -g wechat-claude-sessions@latest
185
+ wechat-claude daemon restart
186
+ ```
187
+
188
+ Both long-lived processes keep the old code in memory until they are replaced.
189
+ `daemon restart` handles the daemon (reloading the launchd job when one is
190
+ installed, otherwise stopping and respawning it). The MCP server belongs to
191
+ Claude Code, so reconnect it there — `/mcp` → `wechat` → Reconnect — and run
192
+ `/wechat` again to re-attach the inbox watcher, which is bound to the old
193
+ server's pid.
194
+
195
+ ## How It Works
196
+
197
+ 1. **Daemon** polls WeChat via the ilink Bot API (`getupdates` long-polling)
198
+ 2. Incoming messages are parsed and routed:
199
+ - Commands like `/sessions`, `/run`, `/close`, `/use` are handled by the daemon
200
+ - `/s <target> <msg>` routes to a specific session's inbox
201
+ - A plain message goes to your bound session (`/use`), else the most
202
+ recently active session that is `[monitoring]`
203
+ 3. When a message is routed, the daemon starts a "typing" indicator on WeChat
204
+ 4. **MCP Server** reads from its inbox when Claude calls `wechat_get_messages`
205
+ 5. Claude processes the message and replies via `wechat_send_text`
206
+ 6. The MCP server clears the typing indicator file; the daemon detects this and stops typing
207
+
208
+ ## Language
209
+
210
+ Bot replies default to Chinese (the WeChat audience). To switch to English, set
211
+ `lang` in `~/.claude/wechat/config.json`:
212
+
213
+ ```json
214
+ { "lang": "en" }
215
+ ```
216
+
217
+ All user-facing strings live in `src/i18n.ts` (`zh` + `en`); the `WECHAT_LANG`
218
+ environment variable overrides the config value.
219
+
220
+ ## Security
221
+
222
+ wechat-claude executes code on your machine in response to chat messages. Read
223
+ [SECURITY.md](SECURITY.md) for the full threat model. In short:
224
+
225
+ - Only the account that scanned the login QR can message the bot; it is not
226
+ publicly discoverable and (currently) cannot be added to group chats.
227
+ - `/run` runs unattended by default (`--dangerously-skip-permissions`); use
228
+ `/run --safe <task>` to require confirmation for bash commands.
229
+ - The bot token lives in `~/.claude/wechat/session.json` (0600); the whole
230
+ data directory is 0700. Run only on a machine you control.
231
+
232
+ ## Troubleshooting
233
+
234
+ - **The bot stopped replying.** The WeChat login likely expired — the daemon
235
+ exits on expiry and can no longer send messages. Back at the Mac, run
236
+ `wechat-claude status`; if logged out, `wechat-claude login` (or
237
+ `/wechat` in a session) to re-scan. Restart the daemon if needed.
238
+ - **`/run` says "找不到目录".** The name didn't resolve to a project; add its
239
+ parent to `repoDirs` in `~/.claude/wechat/config.json`, pass an absolute
240
+ path, or use `/run . <task>` to run in the default directory.
241
+ - **A message got no response.** Check `wechat-claude daemon status` and
242
+ `wechat-claude daemon log`. If the target session isn't `[monitoring]`, run
243
+ `/wechat` in it or bind with `/use <n>`.
244
+ - **`daemon install` did nothing on Linux/Windows.** launchd is macOS-only;
245
+ run the daemon under your own supervisor (`wechat-claude daemon`, systemd,
246
+ pm2…).
247
+
248
+ ## Uninstall
249
+
250
+ ```bash
251
+ wechat-claude daemon uninstall # remove the launchd service (macOS)
252
+ claude mcp remove wechat # unregister the MCP server
253
+ rm ~/.claude/commands/wechat.md # remove the slash command
254
+ rm -rf ~/.claude/wechat # remove tokens, inboxes, media, config
255
+ npm uninstall -g wechat-claude-sessions
256
+ ```
257
+
258
+ ## Requirements
259
+
260
+ - Node.js 20+
261
+ - Claude Code
262
+ - WeChat with ClawBot / ilink Bot access
263
+ - macOS for launchd autostart and native notifications; Linux/Windows work
264
+ with a manually supervised daemon
package/README.md ADDED
@@ -0,0 +1,245 @@
1
+ 中文 | [English](README.en.md)
2
+
3
+ # wechat-claude
4
+
5
+ 用微信从手机上操控 Claude Code。发一条 `/run 修复登录的 bug`,你电脑上就会跑起一个
6
+ Claude Code session 去执行,结果再发回聊天窗口。基于 ilink Bot API 构建,除 MCP SDK
7
+ 外没有第三方依赖。
8
+
9
+ > ⚠️ **它会根据聊天消息在你的电脑上执行代码。** 使用前请先读
10
+ > [SECURITY.md](SECURITY.md)。只在你自己掌控的机器上运行,并且只绑定只有你能发消息的账号。
11
+
12
+ ## 前置条件
13
+
14
+ - **Node.js 20+** 和 **[Claude Code](https://claude.com/claude-code)**。
15
+ - **拥有 ClawBot / ilink Bot 权限的微信账号。** 机器人在登录时通过扫码绑定到*你自己的*
16
+ 微信账号;它不会被公开检索到,陌生人也无法给它发消息。macOS 是一等公民(launchd
17
+ 开机自启 + 原生通知);Linux/Windows 需要自己托管 daemon。
18
+
19
+ ## 架构
20
+
21
+ ```
22
+ 微信用户 ←→ ilink Bot API ←→ Daemon(轮询 + 路由)←→ Inbox 文件 ←→ MCP Server(Claude Code)
23
+ ```
24
+
25
+ - **Daemon**:独立的 Node.js 进程,轮询微信消息、处理路由命令、把消息写进各 session 的
26
+ inbox 文件。独立于 Claude Code 运行。
27
+ - **MCP Server**:注册到 Claude Code 的轻量工具服务。只读自己 session 的 inbox,通过
28
+ ilink API 发回复。不做轮询。
29
+
30
+ ## 安装
31
+
32
+ ```bash
33
+ npm install -g wechat-claude-sessions
34
+ wechat-claude setup
35
+ ```
36
+
37
+ 必须**全局安装**,不要用裸 `npx` —— `setup` 会注册一批绝对路径(MCP server、launchd
38
+ 服务),这些路径得活得比当前 shell 更久,而 npx 的缓存目录并不稳定。从 git 检出构建也
39
+ 可以,见 [CONTRIBUTING.md](CONTRIBUTING.md)。
40
+
41
+ `setup` 会注册 MCP server、把 `/wechat` 斜杠命令装到 `~/.claude/commands/`,然后带你
42
+ 走完扫码登录(它会在浏览器里打开二维码图片,用微信扫)。机器人 token 保存在
43
+ `~/.claude/wechat/session.json`(权限 0600);除非过期,否则不用再扫。
44
+
45
+ 接着启动 daemon 并开启监控:
46
+
47
+ ```bash
48
+ wechat-claude daemon install # macOS:装成 launchd 服务(开机自启 + 崩溃重启)
49
+ # 或者不要自启:
50
+ wechat-claude daemon # 前台运行 / 交给你自己的进程管理器
51
+ ```
52
+
53
+ 最后,在任意 Claude Code session 里输入 `/wechat`。这会启动一个常驻 watcher
54
+ (`dist/watch-inbox.js`),它通过 `fs.watch` 即时响应消息,并把该 session 标记为
55
+ `[监控中]`。daemon 在 `/wechat` 时和登录成功后也会自动拉起(它是单例,由 pid 文件守护)。
56
+
57
+ `wechat-claude daemon install` 会用打包好的模板为*你这台机器*生成 launchd plist,写到
58
+ `~/Library/LaunchAgents/com.wechat-claude.daemon.plist`,不需要手工编辑。切换 Node 版本
59
+ 或全局升级之后重跑一次即可刷新路径。其他 CLI 命令:`wechat-claude login`(重新认证)和
60
+ `wechat-claude status`。
61
+
62
+ 如果微信登录过期,daemon 会发一条 macOS 通知并设置一个标志位,`wechat_status` 会把它暴露
63
+ 出来,于是下一次 `/wechat` 就会提示重新登录 —— 见[故障排查](#故障排查),因为那时你多半
64
+ 不在电脑旁。
65
+
66
+ ## 微信命令
67
+
68
+ 在微信里发这些命令来控制路由:
69
+
70
+ | 命令 | 说明 |
71
+ |------|------|
72
+ | `/sessions` 或 `/ls` | 列出活跃的 Claude Code session(`[监控中]` = 正在实时监控) |
73
+ | `/s <编号> <消息>` | 按编号发消息给某个 session。编号在 session 生命周期内固定不变 —— 其他 session 开启或关闭都不会让它移位(退役的编号不会被复用;所有 session 都消失后编号重新计数) |
74
+ | `/use <编号\|名字\|pid>` | 把你的聊天绑定到某个 session:之后每条不带前缀的消息都直接发给它(daemon 重启后依然有效)。`/use off` 解绑;`/use` 查看当前绑定。被绑定的 session 关闭时会自动解绑 |
75
+ | `/s <名字> <消息>` | 按名字发消息(模糊匹配;有歧义时优先选正在监控的 / 最近活跃的) |
76
+ | `/s <pid> <消息>` | 按 pid 发消息(同名的会以 `名字#pid` 形式列出) |
77
+ | `/run [--safe] [目录] <任务>` | 在 tmux 里起一个新的 Claude session 执行任务。被启动的 session 会被要求把结果发回微信。默认无人值守运行(`--dangerously-skip-permissions`);加 `--safe` 则用 `--permission-mode acceptEdits`,此时 bash 命令会在电脑端等待确认。第一次无人值守运行时,会替你在 `~/.claude.json` 里接受免确认模式 —— detached 的 session 无法回答 Claude Code 的一次性对话框 —— 并在回复里说明;详见 [SECURITY.md](SECURITY.md) |
78
+ | `/runs` | 列出运行中的 `/run` 任务 session |
79
+ | `/stop <名字>` | 终止一个 `/run` 任务 session(名字以 `wc-` 开头) |
80
+ | `/close <编号\|名字\|pid>` | 远程关闭一个 Claude session —— 终结它的 Claude 进程(以及 MCP server)并从列表移除。该 session 里未保存的工作会丢失。如果一个名字匹配到多个,会列出来而不是瞎猜;`/close <名字> all` 关闭全部匹配项,`/close idle` 清理所有闲置 2 小时以上且未被监控的 session。每次关闭的回复末尾都会附上剩余 session 概览 |
81
+ | `/help` | 显示命令帮助 |
82
+ | *(无前缀)* | 发给最近活跃的**监控中** session(没有的话退回到最近活跃的那个) |
83
+
84
+ **投递反馈**:如果消息落到了一个没在监控 inbox 的 session,daemon 会立刻警告你;如果一条
85
+ 已投递的消息 2 分钟后仍未被读取,它会再发一条提醒。
86
+
87
+ **图片**会被自动下载解密到 `~/.claude/wechat/media/`(保留 7 天);路由后的消息文本里带
88
+ 本地路径(`[图片: /path/to/file.png]`),接收方的 Claude session 可以直接打开该文件。
89
+
90
+ ### /run 的目录解析
91
+
92
+ `/run <名字> <任务>` 按以下顺序解析 `<名字>`:
93
+
94
+ 1. 名字(或目录 basename)匹配的活跃 session
95
+ 2. 绝对路径
96
+ 3. 各搜索目录下的 `<目录>/<名字>` —— 搜索目录来自 `~/.claude/wechat/config.json` 里的
97
+ `repoDirs`(可选,例如 `{"repoDirs": ["~/code"]}`;条目必须是绝对路径或以 `~` 开头,
98
+ 无效条目会被报出来),外加每个活跃 session 工作目录的父目录(home 目录本身永远不会
99
+ 被当作搜索根)
100
+
101
+ 如果第一个词看起来像目录名但哪里都解析不到,这次运行会被取消并给出解释,而不是悄悄跑到
102
+ 别的目录里去。当任务文本恰好以一个像路径的词开头时,用 `/run . <任务>` 强制使用默认目录。
103
+
104
+ ## MCP 工具
105
+
106
+ | 工具 | 说明 |
107
+ |------|------|
108
+ | `wechat_login` | 生成微信登录二维码 |
109
+ | `wechat_login_poll` | 轮询扫码状态 |
110
+ | `wechat_get_messages` | 读取并清空 inbox 里的新消息 |
111
+ | `wechat_send_text` | 给微信用户发文本回复 |
112
+ | `wechat_send_image` | 给微信用户发图片文件(可带说明文字) |
113
+ | `wechat_set_session_name` | 为路由设置自定义 session 名 |
114
+ | `wechat_status` | 查看连接、daemon 和 session 状态 |
115
+ | `wechat_logout` | 断开连接并清除 session |
116
+
117
+ ## Session 命名
118
+
119
+ Session 会根据工作目录自动命名:
120
+
121
+ - Git 仓库:`仓库名:分支`(例如 `myapp:main`)
122
+ - Worktree:`仓库名/worktree名`(例如 `myapp/feature-x`)
123
+ - 非 Git 目录:目录名
124
+
125
+ 用 `wechat_set_session_name` 可以设置自定义名字。
126
+
127
+ ## 文件布局
128
+
129
+ ```
130
+ ~/.claude/wechat/
131
+ ├── session.json # ilink 机器人 token(持久化登录)
132
+ ├── config.json # 可选配置(例如 /run 用的 repoDirs)
133
+ ├── bindings.json # /use 绑定关系(微信用户 -> session)
134
+ ├── session-numbers.json # 稳定 session 编号注册表
135
+ ├── media/ # 下载的图片(保留 7 天)
136
+ ├── context_tokens.json # 共享上下文 token(daemon ↔ MCP server)
137
+ ├── daemon.pid # daemon 进程 ID
138
+ ├── daemon.log # daemon 输出
139
+ ├── cursor.txt # 消息轮询游标
140
+ ├── expired.flag # 微信登录过期时存在
141
+ ├── sessions/ # 已注册的 Claude Code session
142
+ │ └── <pid>.json
143
+ ├── inbox/ # 各 session 的消息队列
144
+ │ └── <pid>.json
145
+ ├── heartbeat/ # watcher 心跳(session 处于 [监控中])
146
+ │ └── <pid>
147
+ └── typing/ # 输入状态
148
+ └── <userId>
149
+ ```
150
+
151
+ ## CLI
152
+
153
+ ```bash
154
+ wechat-claude setup # 注册 MCP server + /wechat 命令,然后登录
155
+ wechat-claude login # (重新)扫码认证
156
+ wechat-claude status # 查看登录 / daemon / 服务状态
157
+ wechat-claude daemon # 前台运行 daemon
158
+ wechat-claude daemon restart # 重启 daemon(升级后用)
159
+ wechat-claude daemon install # 装成 launchd 服务(macOS)
160
+ wechat-claude daemon uninstall # 移除 launchd 服务
161
+ wechat-claude daemon status # 查看 daemon / 服务状态
162
+ wechat-claude daemon log # 跟踪 daemon 日志
163
+ ```
164
+
165
+ 在 git 检出里,同样的命令也有对应的 npm script(`npm run daemon:install` 等),它们只是
166
+ 转调 CLI。
167
+
168
+ ### 升级
169
+
170
+ ```bash
171
+ npm install -g wechat-claude-sessions@latest
172
+ wechat-claude daemon restart
173
+ ```
174
+
175
+ 两个长期存活的进程在被替换之前都还揣着旧代码。`daemon restart` 负责 daemon(装了 launchd
176
+ 就重载该 job,否则停掉再重新拉起)。MCP server 归 Claude Code 管,所以要在那边重连 ——
177
+ `/mcp` → `wechat` → Reconnect —— 然后再跑一次 `/wechat` 重新挂上 inbox watcher,因为它绑
178
+ 的是旧 server 的 pid。
179
+
180
+ ## 工作原理
181
+
182
+ 1. **Daemon** 通过 ilink Bot API 轮询微信(`getupdates` 长轮询)
183
+ 2. 收到的消息被解析并路由:
184
+ - `/sessions`、`/run`、`/close`、`/use` 这类命令由 daemon 自己处理
185
+ - `/s <目标> <消息>` 路由到指定 session 的 inbox
186
+ - 不带前缀的消息发给你绑定的 session(`/use`),否则发给最近活跃且处于 `[监控中]`
187
+ 的那个
188
+ 3. 消息被路由时,daemon 会在微信上打开"正在输入"指示
189
+ 4. **MCP Server** 在 Claude 调用 `wechat_get_messages` 时读取自己的 inbox
190
+ 5. Claude 处理消息并通过 `wechat_send_text` 回复
191
+ 6. MCP server 清除输入状态文件,daemon 检测到后停止"正在输入"
192
+
193
+ ## 语言
194
+
195
+ 机器人回复默认用中文(面向微信用户)。要切换成英文,在
196
+ `~/.claude/wechat/config.json` 里设置 `lang`:
197
+
198
+ ```json
199
+ { "lang": "en" }
200
+ ```
201
+
202
+ 所有面向用户的字符串都在 `src/i18n.ts`(`zh` + `en`);环境变量 `WECHAT_LANG` 会覆盖配置
203
+ 文件里的值。
204
+
205
+ ## 安全
206
+
207
+ wechat-claude 会响应聊天消息、在你的机器上执行代码。完整威胁模型见
208
+ [SECURITY.md](SECURITY.md)。简而言之:
209
+
210
+ - 只有扫码登录的那个账号能给机器人发消息;它不会被公开检索到,(目前)也不能被拉进群聊。
211
+ - `/run` 默认无人值守运行(`--dangerously-skip-permissions`);用
212
+ `/run --safe <任务>` 可以让 bash 命令需要确认。
213
+ - 机器人 token 存在 `~/.claude/wechat/session.json`(0600),整个数据目录是 0700。
214
+ 只在你自己掌控的机器上运行。
215
+
216
+ ## 故障排查
217
+
218
+ - **机器人不回消息了。** 多半是微信登录过期 —— daemon 在过期时会退出,也就再也发不出消息。
219
+ 回到 Mac 上运行 `wechat-claude status`;如果显示未登录,用 `wechat-claude login`
220
+ (或在某个 session 里 `/wechat`)重新扫码。需要的话重启 daemon。
221
+ - **`/run` 提示"找不到目录"。** 那个名字没解析到项目;把它的父目录加到
222
+ `~/.claude/wechat/config.json` 的 `repoDirs` 里,或者传绝对路径,或者用
223
+ `/run . <任务>` 在默认目录里跑。
224
+ - **消息发出去没反应。** 检查 `wechat-claude daemon status` 和
225
+ `wechat-claude daemon log`。如果目标 session 不在 `[监控中]`,在它里面跑 `/wechat`,
226
+ 或者用 `/use <编号>` 绑定。
227
+ - **Linux/Windows 上 `daemon install` 什么都没做。** launchd 是 macOS 专有的;请用你自己
228
+ 的进程管理器托管 daemon(`wechat-claude daemon`、systemd、pm2……)。
229
+
230
+ ## 卸载
231
+
232
+ ```bash
233
+ wechat-claude daemon uninstall # 移除 launchd 服务(macOS)
234
+ claude mcp remove wechat # 注销 MCP server
235
+ rm ~/.claude/commands/wechat.md # 删除斜杠命令
236
+ rm -rf ~/.claude/wechat # 删除 token、inbox、图片、配置
237
+ npm uninstall -g wechat-claude-sessions
238
+ ```
239
+
240
+ ## 环境要求
241
+
242
+ - Node.js 20+
243
+ - Claude Code
244
+ - 拥有 ClawBot / ilink Bot 权限的微信账号
245
+ - macOS 才有 launchd 自启和原生通知;Linux/Windows 需要自己托管 daemon
package/SECURITY.md ADDED
@@ -0,0 +1,45 @@
1
+ # Security Policy
2
+
3
+ ## Threat model — read this before running
4
+
5
+ wechat-claude bridges a **chat channel** to **command execution on your
6
+ computer**. A message like `/run <task>` starts a Claude Code session on your
7
+ machine, and `/close` / `/stop` terminate processes. Treat the bot as a remote
8
+ control for your laptop.
9
+
10
+ Key facts to understand:
11
+
12
+ - **Anyone who can message your bot can drive your machine.** There is
13
+ currently no per-sender authorization check — every incoming WeChat message
14
+ is acted on. Only enable the bot for an account that only you can message,
15
+ and never share or publicize the bot.
16
+ - **`/run` defaults to `--dangerously-skip-permissions`.** Tasks run
17
+ unattended without per-action confirmation. Use `/run --safe <task>` for
18
+ `--permission-mode acceptEdits` (bash commands then wait for confirmation at
19
+ the computer).
20
+ - **The first such `/run` accepts skip-permissions mode on your behalf.**
21
+ Claude Code gates that mode behind a one-time interactive dialog, which a
22
+ detached tmux session can never answer — the task would hang on the prompt
23
+ forever. So the daemon sets `bypassPermissionsModeAccepted` in
24
+ `~/.claude.json` before launching, and says so in its reply the one time it
25
+ does. If you would rather grant that yourself, accept the dialog once by
26
+ running `claude --dangerously-skip-permissions` at the computer, or stay on
27
+ `/run --safe`, which never touches the flag.
28
+ - **The bot token is stored at `~/.claude/wechat/session.json`** (mode 0600).
29
+ The whole `~/.claude/wechat/` directory is created 0700. Anyone with read
30
+ access to your home directory can impersonate your bot.
31
+ - **Run only on machines you control.** Do not run the daemon on shared or
32
+ multi-user hosts, and do not expose it to untrusted contacts.
33
+
34
+ ## Recommended hardening
35
+
36
+ - Keep the bot conversation private (a single-user chat).
37
+ - Prefer `/run --safe` unless you are actively watching the task.
38
+ - Review `~/.claude/wechat/config.json` `repoDirs` — only list directories you
39
+ are comfortable running tasks in.
40
+
41
+ ## Reporting a vulnerability
42
+
43
+ Please report security issues privately by opening a
44
+ [GitHub security advisory](https://github.com/Neil-UWA/wechat-claude/security/advisories/new)
45
+ rather than a public issue. We aim to respond within a week.
@@ -0,0 +1,4 @@
1
+ export declare function getBinding(userId: string): string | undefined;
2
+ export declare function setBinding(userId: string, sessionId: string): void;
3
+ export declare function clearBinding(userId: string): void;
4
+ export declare function clearBindingsToSession(sessionId: string): void;