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.
- package/CONTRIBUTING.md +52 -0
- package/LICENSE +21 -0
- package/README.en.md +264 -0
- package/README.md +245 -0
- package/SECURITY.md +45 -0
- package/dist/bindings.d.ts +4 -0
- package/dist/bindings.js +50 -0
- package/dist/bindings.js.map +1 -0
- package/dist/claude-config.d.ts +4 -0
- package/dist/claude-config.js +49 -0
- package/dist/claude-config.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +345 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +771 -0
- package/dist/daemon.js.map +1 -0
- package/dist/i18n.d.ts +67 -0
- package/dist/i18n.js +249 -0
- package/dist/i18n.js.map +1 -0
- package/dist/ilink.d.ts +49 -0
- package/dist/ilink.js +533 -0
- package/dist/ilink.js.map +1 -0
- package/dist/inbox.d.ts +4 -0
- package/dist/inbox.js +75 -0
- package/dist/inbox.js.map +1 -0
- package/dist/launchd.d.ts +7 -0
- package/dist/launchd.js +71 -0
- package/dist/launchd.js.map +1 -0
- package/dist/monitoring.d.ts +4 -0
- package/dist/monitoring.js +33 -0
- package/dist/monitoring.js.map +1 -0
- package/dist/paths.d.ts +11 -0
- package/dist/paths.js +35 -0
- package/dist/paths.js.map +1 -0
- package/dist/pkg-root.d.ts +2 -0
- package/dist/pkg-root.js +11 -0
- package/dist/pkg-root.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +364 -0
- package/dist/server.js.map +1 -0
- package/dist/session-numbers.d.ts +1 -0
- package/dist/session-numbers.js +44 -0
- package/dist/session-numbers.js.map +1 -0
- package/dist/sessions.d.ts +15 -0
- package/dist/sessions.js +135 -0
- package/dist/sessions.js.map +1 -0
- package/dist/tmux.d.ts +7 -0
- package/dist/tmux.js +47 -0
- package/dist/tmux.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +88 -0
- package/dist/utils.js.map +1 -0
- package/dist/watch-inbox.d.ts +2 -0
- package/dist/watch-inbox.js +102 -0
- package/dist/watch-inbox.js.map +1 -0
- package/package.json +50 -0
- package/templates/com.wechat-claude.daemon.plist.template +31 -0
- package/templates/wechat.md +20 -0
package/CONTRIBUTING.md
ADDED
|
@@ -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;
|