npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@huwatermelon/wechat-hook-channel

v0.1.2

Published

macOS WeChat Hook channel for Claude Code/mycc and OpenClaw.

Readme

wechat-hook-channel

macOS WeChat channel plugin for Claude Code and OpenClaw. It uses Frida Gadget plus the bundled onebot-wechat-hook bridge to receive WeChat messages and send replies from the selected agent runtime.

Architecture

WeChat.app <-> Frida Gadget <-> onebot-wechat-hook <-> wechat-hook-channel
                                                     |-> MCP server <-> Claude Code / mycc
                                                     |-> OpenClaw channel adapter <-> OpenClaw gateway

Requirements

| Dependency | Version | Notes | | --- | --- | --- | | macOS | 11.0+ arm64 | Apple Silicon target | | WeChat | 4.1.9.52 | Offsets are version-specific | | Claude Code | current channel-capable build | Custom channels still need the development-channel flag | | OpenClaw | 2026.4.5+ | Optional; only needed for the OpenClaw channel adapter | | Node.js | 20+ | Runs the published npm CLI | | Bun | 1.x | Source checkout development and tests | | Go | 1.21+ | Only needed when building onebot-wechat-hook from source | | Frida | 17.x | Gadget/runtime must match the devkit used to build onebot | | frida-core-devkit | 17.9.1 macOS arm64 | Only needed when building onebot-wechat-hook from source |

Required Local Artifacts

This repo vendors the onebot source code and the WeChat offset config, but it does not vendor Tencent's WeChat app or Frida binaries. The npm install path uses the optional @huwatermelon/onebot-wechat-hook-darwin-arm64 package for a prebuilt onebot-wechat-hook binary; source checkouts can still build the same binary locally.

You need these local artifacts:

  • WeChat for macOS with WeChatBundleVersion exactly 4.1.9.52. Download the verified Tencent DMG and keep a local copy. Current public latest builds may be newer and will not match the bundled offsets.
  • Frida Gadget 17.9.1 for macOS universal, downloaded from the official Frida release assets.
  • Frida core devkit 17.9.1 for macOS arm64, downloaded from the same Frida release. This is the SDK/devkit that contains frida-core.h; pip install frida alone is not enough to compile onebot-wechat-hook.
  • onebot-wechat-hook, either from the npm optional binary package or built locally from wechat_chatter/onebot with ./scripts/build-onebot.sh.

The supported WeChat offset file is:

wechat_chatter/wechat_version/4_1_9_52_mac.json

Do not continue if the installed WeChat version is different:

plutil -p /Applications/WeChat.app/Contents/Info.plist | grep WeChatBundleVersion

Install

npm Install

For normal macOS arm64 users, install the channel and prebuilt onebot bridge from npm:

npm install -g @huwatermelon/wechat-hook-channel
wechat-hook-channel doctor

The main package declares @huwatermelon/onebot-wechat-hook-darwin-arm64 as an optional dependency. If that binary package is unavailable in your environment, set ONEBOT_BIN to a locally built onebot-wechat-hook path.

Source Checkout

Clone and build the bridge:

git clone [email protected]:<github-owner>/wechat-hook-channel.git
cd wechat-hook-channel
bun install

FRIDA_VER=17.9.1
FRIDA_DEVKIT="$HOME/.local/frida/devkit-${FRIDA_VER}-macos-arm64"
mkdir -p "$FRIDA_DEVKIT"
curl -L -o /tmp/frida-core-devkit.tar.xz \
  "https://github.com/frida/frida/releases/download/${FRIDA_VER}/frida-core-devkit-${FRIDA_VER}-macos-arm64.tar.xz"
tar xf /tmp/frida-core-devkit.tar.xz -C "$FRIDA_DEVKIT"

./scripts/build-onebot.sh

./scripts/build-onebot.sh looks for $HOME/.local/frida/devkit-17.9.1-macos-arm64/frida-core.h by default. Set FRIDA_DEVKIT=/path/to/frida-core-devkit only if you extracted the devkit somewhere else. The Frida Gadget injected into WeChat and this devkit should use the same Frida release version.

The bundled wechat_chatter directory is the source of truth for WeChat 4.1.9.52 offsets and outbound fixes. Do not replace it with an upstream checkout from /tmp/wechat_chatter.

If you want to run the Go tests for the native bridge, use the same Frida CGO flags as the build. A plain go test ./... cannot find frida-core.h.

cd wechat_chatter/onebot
CGO_ENABLED=1 GOARCH=arm64 \
CGO_CFLAGS="-I$HOME/.local/frida/devkit-17.9.1-macos-arm64" \
CGO_LDFLAGS="-L$HOME/.local/frida/devkit-17.9.1-macos-arm64 -lfrida-core -lm -lresolv -Wl,-framework,Foundation,-framework,AppKit" \
CC=/usr/bin/gcc \
go test ./...

WeChat Setup

Install WeChat 4.1.9.52 and disable auto-update in WeChat settings:

WECHAT_DMG_URL="https://dldir1v6.qq.com/weixin/Universal/Mac/xWeChatMac_universal_4.1.9.52_38814.dmg"
curl -L -o "$HOME/Downloads/xWeChatMac_universal_4.1.9.52_38814.dmg" "$WECHAT_DMG_URL"
open "$HOME/Downloads/xWeChatMac_universal_4.1.9.52_38814.dmg"

Then verify the installed version:

plutil -p /Applications/WeChat.app/Contents/Info.plist | grep WeChatBundleVersion

The output must contain:

"WeChatBundleVersion" => "4.1.9.52"

If it shows a newer version, replace the app with the exact 4.1.9.52 build before injecting Frida. The offsets are not compatible across WeChat patch versions.

Inject Frida Gadget:

killall WeChat 2>/dev/null || true

FRIDA_VER=17.9.1
mkdir -p "$HOME/.local/frida"
cd "$HOME/.local/frida"
curl -L -O "https://github.com/frida/frida/releases/download/${FRIDA_VER}/frida-gadget-${FRIDA_VER}-macos-universal.dylib.xz"
xz -dk "frida-gadget-${FRIDA_VER}-macos-universal.dylib.xz"
cp "frida-gadget-${FRIDA_VER}-macos-universal.dylib" /Applications/WeChat.app/Contents/Frameworks/FridaGadget.dylib
chmod +x /Applications/WeChat.app/Contents/Frameworks/FridaGadget.dylib

git clone https://github.com/Tyilo/insert_dylib.git /tmp/insert_dylib
cd /tmp/insert_dylib
gcc -framework Foundation insert_dylib/main.m -o /tmp/insert_dylib_bin

cd /Applications/WeChat.app/Contents/MacOS
/tmp/insert_dylib_bin --inplace --strip-codesig \
  "@executable_path/../Frameworks/FridaGadget.dylib" WeChat

cp /path/to/wechat-hook-channel/wechat_chatter/frida-gadget/FridaGadget.config \
  /Applications/WeChat.app/Contents/Frameworks/

cd /path/to/wechat-hook-channel/wechat_chatter
./frida-gadget/sign.sh

If the patched app will not launch, re-sign the whole bundle:

sudo codesign --force --deep --sign - /Applications/WeChat.app

Start WeChat and confirm Frida is reachable:

open -a WeChat
python3 -c "import frida; mgr = frida.get_device_manager(); dev = mgr.add_remote_device('127.0.0.1:27042'); print(dev.enumerate_processes())"

Configure

Keep runtime settings in the shell, on the launch command, or in the local ignored .env.wechat-hook file. .env.example is only a reference for available variables; never commit real local values.

Minimum chat-listening config for each mycc / Claude Code channel process:

WECHAT_SELF_WXID=<wechat-account-logged-in-on-this-mac>
WECHAT_CHAT_ID=<one-private-chat-id-or-group-chatroom-id>

For group listeners where the logged-in account also sends trigger messages itself, add one more filtering env:

WECHAT_TRIGGER_KEYWORDS=<self-message-prefix>

Those two or three variables are the chat filtering contract. Other envs such as WECHAT_HOOK_CALLBACK_PORT, WECHAT_HOOK_ONEBOT_API_URL, WECHAT_HOOK_ONEBOT_TOKEN, WECHAT_OUTBOUND_MODE, and model/API credentials control transport or outbound behavior; they do not select which chat is listened to.

WECHAT_CHAT_ID replaces the common WECHAT_PRIVATE_MODE + WECHAT_HOOK_ALLOWED_CHAT_IDS setup. The channel treats each process as a one-chat listener:

| Scenario | WECHAT_SELF_WXID | WECHAT_CHAT_ID | | --- | --- | --- | | Logged-in account sends messages to itself | <desktop-self-wxid-or-alias> | <desktop-self-wxid-or-alias> | | Logged-in helper account receives private messages from another account | <helper-account-wxid> | <sender-private-chat-id> | | Logged-in account listens to one group | <desktop-self-wxid-or-alias> | <group-id>@chatroom |

Group filtering is scoped to the selected WECHAT_CHAT_ID:

  • Messages from other group members trigger only when they @ the logged-in account. The channel detects OneBot at segments that target WECHAT_SELF_WXID; set WECHAT_SELF_NICKNAMES when OneBot only exposes the visible display name.
  • Messages sent by WECHAT_SELF_WXID in that group trigger only when they start with one of WECHAT_TRIGGER_KEYWORDS.
  • WECHAT_TRIGGER_OPEN_GROUPS is not used by this channel. Run one mycc process per WECHAT_CHAT_ID.

Defaults are live outbound sends, OneBot API http://127.0.0.1:58080, token MuseBot, and voice handling enabled only when Baidu ASR keys are present. Callback port defaults are:

| Config | Callback port | | --- | --- | | No WECHAT_CHAT_ID | 36060 | | Private WECHAT_CHAT_ID | 36061 | | Group WECHAT_CHAT_ID ending with @chatroom | 36062 |

Set WECHAT_HOOK_CALLBACK_PORT only when you need a non-default port, such as more than one private listener or more than one group listener.

OneBot native send timeouts default to:

WECHAT_HOOK_TEXT_SEND_TIMEOUT_MS=15000
WECHAT_HOOK_IMAGE_UPLOAD_TIMEOUT_MS=300000
WECHAT_HOOK_IMAGE_SEND_TIMEOUT_MS=30000

WECHAT_HOOK_IMAGE_UPLOAD_TIMEOUT_MS is intentionally longer because outbound images wait for WeChat CDN upload completion before the final image message is sent. Large generated PNGs can take more than two minutes on this native path. The OneBot HTTP request also waits for the native send_image completion; if that final WeChat task fails, OneBot retries once and then returns a non-2xx error instead of reporting a false success. Text sends keep the shorter timeout.

WECHAT_SELF_WXID must be the ID of the WeChat account logged in on this Mac. You can read it from onebot logs or from:

curl http://127.0.0.1:58080/hook_state

If you do not know the IDs yet, run a temporary discovery callback. Use a port that is not used by mycc, then either point onebot at that port directly, or add the discovery port as one fanout downstream while onebot keeps sending to the fanout port.

# terminal A
cd /path/to/wechat-hook-channel
bun ./scripts/discover-chats.ts 36070

# terminal B
cd /path/to/wechat-hook-channel
WECHAT_HOOK_ENV_FILE=/dev/null WECHAT_HOOK_CALLBACK_PORT=36070 ./scripts/start-onebot.sh

In fanout mode, discovery is simpler because onebot can keep using the stable fanout endpoint. Add one extra downstream port for discovery:

# terminal A
cd /path/to/wechat-hook-channel
bun ./scripts/discover-chats.ts 36063

# terminal B
cd /path/to/wechat-hook-channel
bun ./scripts/fanout.ts 36060 36061,36062,36063

Now send one message from each WeChat conversation you want to identify. The discovery output prints:

  • self_id: the current Mac logged-in account. Use this as WECHAT_SELF_WXID.
  • private chat_id: the contact/account ID for WECHAT_CHAT_ID.
  • group chat_id: the group ID ending in @chatroom for WECHAT_CHAT_ID.

Discovery also appends JSONL to /tmp/wechat-hook-discover.log by default, so detached screen or tmux sessions are still auditable:

tail -f /tmp/wechat-hook-discover.log

Each record includes copy_env, for example "copy_env":"WECHAT_CHAT_ID=1234567890@chatroom".

The onebot bridge also writes every message it forwards to /tmp/wechat-hook-onebot-inbound.log by default. That log is the primary source of truth for "did onebot receive this at all?"; discovery is only a temporary tap when you want a separate callback endpoint. Set WECHAT_ONEBOT_INBOUND_LOG=off to disable the onebot JSONL log, or set it to a custom file path.

OneBot does not currently provide a reliable group-name field in callbacks, so group selection must use the group chat_id ending in @chatroom; group names are only useful for humans while identifying the right ID. The discovery output and /tmp/wechat-hook-onebot-inbound.log include sender nicknames to help identify the correct group while testing.

For self-chat mode, WECHAT_CHAT_ID is the same value as WECHAT_SELF_WXID. Run one mycc process per chat. For multiple listeners, use one process per WECHAT_CHAT_ID.

Register Global MCP

Use global MCP mode for mycc. If installed from npm, register the global CLI once in ~/.claude/mcp.json:

MCP_FILE="$HOME/.claude/mcp.json"
mkdir -p "$(dirname "$MCP_FILE")"

node - "$MCP_FILE" <<'NODE'
const fs = require('fs')
const [file] = process.argv.slice(2)
const config = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {}
config.mcpServers ??= {}
config.mcpServers['wechat-hook'] = {
  command: 'wechat-hook-channel',
  args: ['mcp'],
}
fs.writeFileSync(file, JSON.stringify(config, null, 2) + '\n')
NODE

For source checkouts, register this checkout once in ~/.claude/mcp.json, and start every mycc process with --dangerously-load-development-channels server:wechat-hook.

WECHAT_HOOK_CHANNEL_ROOT=/path/to/wechat-hook-channel
BUN_PATH="$(command -v bun)"
MCP_FILE="$HOME/.claude/mcp.json"
mkdir -p "$(dirname "$MCP_FILE")"

node - "$MCP_FILE" "$BUN_PATH" "$WECHAT_HOOK_CHANNEL_ROOT" <<'NODE'
const fs = require('fs')
const [file, bunPath, channelRoot] = process.argv.slice(2)
const config = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {}
config.mcpServers ??= {}
config.mcpServers['wechat-hook'] = {
  command: bunPath,
  args: ['--cwd', channelRoot, './src/index.ts'],
}
fs.writeFileSync(file, JSON.stringify(config, null, 2) + '\n')
NODE

Do not keep an installed marketplace/plugin copy exposing the same wechat-hook channel enabled at the same time. Claude Code can discover the plugin copy first and bind WECHAT_HOOK_CALLBACK_PORT from the old directory.

Custom channels are still treated as development channels by Claude Code, so --dangerously-load-development-channels server:wechat-hook is required.

Register OpenClaw Channel

OpenClaw uses the same onebot bridge and chat filtering rules, but it does not use the Claude Code MCP server. If installed from npm, link the installed package directory:

PLUGIN_ROOT="$(npm root -g)/@huwatermelon/wechat-hook-channel"
openclaw plugins install --link --dangerously-force-unsafe-install "$PLUGIN_ROOT"
openclaw plugins inspect wechat-hook-channel

For source checkouts, install this checkout as a linked local OpenClaw plugin:

openclaw plugins install --link --dangerously-force-unsafe-install /path/to/wechat-hook-channel
openclaw plugins inspect wechat-hook-channel

The force flag is only for local development installs that OpenClaw's safety scanner cannot classify automatically. Review the local checkout before using it.

Add a channels.wechat-hook block to ~/.openclaw/openclaw.json:

{
  "channels": {
    "wechat-hook": {
      "enabled": true,
      "selfWxid": "<desktop-self-wxid-or-alias>",
      "chatId": "<one-private-chat-id-or-group-chatroom-id>",
      "triggerKeywords": ["<self-message-prefix>"],
      "outboundMode": "live",
      "callbackHost": "127.0.0.1",
      "callbackPort": 36062,
      "onebotApiUrl": "http://127.0.0.1:58080",
      "onebotToken": "MuseBot"
    }
  }
}

For private/self-chat, use callback port 36061; for a group chat, use 36062 unless another listener already owns it. If you need more than one OpenClaw WeChat listener, use named accounts and set a distinct callbackPort for each account:

{
  "channels": {
    "wechat-hook": {
      "accounts": {
        "group-a": {
          "enabled": true,
          "selfWxid": "<desktop-self-wxid-or-alias>",
          "chatId": "<group-a-id>@chatroom",
          "triggerKeywords": ["<self-message-prefix>"],
          "callbackPort": 36062
        },
        "group-b": {
          "enabled": true,
          "selfWxid": "<desktop-self-wxid-or-alias>",
          "chatId": "<group-b-id>@chatroom",
          "triggerKeywords": ["<self-message-prefix>"],
          "callbackPort": 36063
        }
      }
    }
  }
}

Restart and verify:

openclaw gateway restart
openclaw channels list
lsof -nP -iTCP:36062 -sTCP:LISTEN

OpenClaw keeps its own sessions. It does not reuse Claude Code / mycc transcripts. Session files live under OpenClaw's state directory, normally:

~/.openclaw/agents/<agent-id>/sessions/

For group chats, the same OpenClaw agent, account, and chatId resolve to the same session across gateway restarts. For private/self-chat, OpenClaw's default DM scope is the agent's main session. If one OpenClaw agent handles more than one private WeChat chat, set session.dmScope to per-channel-peer or per-account-channel-peer in ~/.openclaw/openclaw.json to avoid sharing one private-chat context.

When one onebot process should feed both mycc and OpenClaw, keep onebot in fanout mode and include the OpenClaw callback port:

cd /path/to/wechat-hook-channel
bun ./scripts/fanout.ts 36060 36061,36062

Only one process can listen on a callback port. Stop the matching mycc listener before assigning that port to OpenClaw. The current OpenClaw adapter supports text inbound/outbound; Claude Code / mycc MCP mode remains the full-feature path for image/file MCP tools.

Start onebot

Start onebot after WeChat is logged in. Onebot is the native WeChat bridge; Claude Code or mycc starts the MCP server separately through global ~/.claude/mcp.json.

For a single self/private listener, start onebot with the same WECHAT_SELF_WXID, WECHAT_CHAT_ID, and callback port that the matching mycc process will use. Private chat examples use port 36061; group examples use port 36062. If you omit WECHAT_HOOK_CALLBACK_PORT, scripts/start-onebot.sh derives that same default from WECHAT_CHAT_ID.

killall WeChat 2>/dev/null || true
open -a WeChat
for i in {1..30}; do
  lsof -nP -iTCP:27042 -sTCP:LISTEN >/dev/null 2>&1 && break
  sleep 1
done

cd /path/to/wechat-hook-channel
WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_CHAT_ID=<one-private-chat-id-or-group-chatroom-id> \
WECHAT_HOOK_CALLBACK_PORT=36061 \
WECHAT_HOOK_ONEBOT_PORT=58080 \
WECHAT_HOOK_ONEBOT_TOKEN=MuseBot \
WECHAT_HOOK_TEXT_SEND_TIMEOUT_MS=15000 \
WECHAT_HOOK_IMAGE_UPLOAD_TIMEOUT_MS=300000 \
WECHAT_HOOK_IMAGE_SEND_TIMEOUT_MS=30000 \
./scripts/start-onebot.sh

With the npm CLI, use the same environment and replace the script with:

wechat-hook-channel onebot start

Equivalent explicit command:

WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_CHAT_ID=<one-private-chat-id-or-group-chatroom-id> \
~/.local/bin/onebot-wechat-hook -type gadget -gadget_addr 127.0.0.1:27042 \
  -wechat_conf /path/to/wechat-hook-channel/wechat_chatter/wechat_version/4_1_9_52_mac.json \
  -send_url http://127.0.0.1:36061/onebot \
  -receive_host 127.0.0.1:58080 \
  -token MuseBot -conn_type http \
  -send_text_timeout_ms 15000 \
  -upload_image_timeout_ms 300000 \
  -send_image_timeout_ms 30000

For split private/group sessions, or when you want one onebot process to feed multiple mycc listeners, keep onebot on the fanout port 36060, start fanout, and pass WECHAT_CHAT_ID only to each mycc process:

cd /path/to/wechat-hook-channel
WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_HOOK_FANOUT_PORT=36060 \
WECHAT_HOOK_ONEBOT_PORT=58080 \
WECHAT_HOOK_ONEBOT_TOKEN=MuseBot \
WECHAT_HOOK_TEXT_SEND_TIMEOUT_MS=15000 \
WECHAT_HOOK_IMAGE_UPLOAD_TIMEOUT_MS=300000 \
WECHAT_HOOK_IMAGE_SEND_TIMEOUT_MS=30000 \
./scripts/start-onebot.sh fanout

In a second terminal, forward fanout traffic to the callback ports owned by mycc:

cd /path/to/wechat-hook-channel
bun ./scripts/fanout.ts 36060 36061,36062

With the npm CLI:

wechat-hook-channel onebot start fanout
wechat-hook-channel fanout 36060 36061,36062

scripts/start-onebot.sh fanout also accepts the misspelled alias fansout. In fanout mode the script ignores WECHAT_CHAT_ID for callback-port derivation and uses WECHAT_HOOK_FANOUT_PORT, default 36060.

Start mycc With WeChat Hook

For mycc, keep onebot running first, then start mycc with the channel enabled. Pass the same WeChat env values used by the matching onebot process. If API settings are already loaded from an ignored local .env.api, omit the first two ANTHROPIC_* assignments.

Big Account Self-Chat

Use this when the desktop WeChat account is the big account and you send messages from that same account. This is the simplest private assistant mode and does not need fanout.

Start interactively in your current terminal:

cd /path/to/claude-code

ANTHROPIC_BASE_URL=<api-base-url> \
ANTHROPIC_API_KEY=<api-key> \
WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_CHAT_ID=<desktop-self-wxid-or-alias> \
WECHAT_OUTBOUND_MODE=live \
WECHAT_HOOK_CALLBACK_PORT=36061 \
WECHAT_HOOK_ONEBOT_API_URL=http://127.0.0.1:58080 \
WECHAT_HOOK_ONEBOT_TOKEN=MuseBot \
./dist/mycc \
  --dangerously-load-development-channels server:wechat-hook \
  --dangerously-skip-permissions

Small Account To Big Account

Use this when desktop WeChat is logged in as the small account, and the big account sends private messages to it.

Start mycc the same way:

cd /path/to/claude-code

ANTHROPIC_BASE_URL=<api-base-url> \
ANTHROPIC_API_KEY=<api-key> \
WECHAT_SELF_WXID=<helper-account-wxid> \
WECHAT_CHAT_ID=<sender-private-chat-id> \
WECHAT_OUTBOUND_MODE=live \
WECHAT_HOOK_CALLBACK_PORT=36061 \
WECHAT_HOOK_ONEBOT_API_URL=http://127.0.0.1:58080 \
WECHAT_HOOK_ONEBOT_TOKEN=MuseBot \
./dist/mycc \
  --dangerously-load-development-channels server:wechat-hook \
  --dangerously-skip-permissions

Small Account Group Listener

Use this when desktop WeChat is logged in as the small account and one mycc process should own a single group.

cd /path/to/claude-code

ANTHROPIC_BASE_URL=<api-base-url> \
ANTHROPIC_API_KEY=<api-key> \
WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_CHAT_ID=<group-id>@chatroom \
WECHAT_SELF_NICKNAMES=<your-display-name-in-that-group> \
WECHAT_TRIGGER_KEYWORDS=<self-message-prefix> \
WECHAT_OUTBOUND_MODE=live \
WECHAT_HOOK_CALLBACK_PORT=36062 \
WECHAT_HOOK_ONEBOT_API_URL=http://127.0.0.1:58080 \
WECHAT_HOOK_ONEBOT_TOKEN=MuseBot \
./dist/mycc \
  --dangerously-load-development-channels server:wechat-hook \
  --dangerously-skip-permissions

Channel Loading Options

This setup standardizes on global MCP mode. ~/.claude/mcp.json contains one server:wechat-hook template pointing at this checkout; each mycc process starts its own MCP server instance from that template.

Do not pass --bare, --strict-mcp-config, or --mcp-config in this mode. Those options bypass or replace the global MCP config.

mycc Session Resume On Restart

By default, restarting mycc creates a new Claude Code session. For a single self-chat or single private listener, add -c / --continue so mycc resumes the most recent session in the current claude-code directory:

WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_CHAT_ID=<one-private-chat-id> \
WECHAT_HOOK_CALLBACK_PORT=36061 \
./dist/mycc \
  -c \
  --dangerously-load-development-channels server:wechat-hook \
  --dangerously-skip-permissions

For multiple mycc listeners started from the same claude-code directory, do not use --continue blindly. --continue resumes the most recent session for that directory, so a private listener can accidentally resume a group listener's context. Use a fixed --resume <session-id> per listener, or run each listener from a separate working directory so --continue has an unambiguous latest session.

For multiple mycc listeners, keep the same global MCP entry and vary only WECHAT_CHAT_ID. A private chat defaults to callback port 36061; a group defaults to 36062:

WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_CHAT_ID=<one-private-chat-id> \
WECHAT_HOOK_CALLBACK_PORT=36061 \
./dist/mycc \
  --dangerously-load-development-channels server:wechat-hook \
  --dangerously-skip-permissions

WECHAT_SELF_WXID=<desktop-self-wxid-or-alias> \
WECHAT_CHAT_ID=<group-id>@chatroom \
WECHAT_HOOK_CALLBACK_PORT=36062 \
./dist/mycc \
  --dangerously-load-development-channels server:wechat-hook \
  --dangerously-skip-permissions

Background Service

If you start mycc yourself in an interactive terminal, no screen or pseudo-TTY setup is needed. Use screen or tmux only when you want mycc to keep running in the background.

That TTY requirement is only for background mycc, not for WeChat, onebot, or normal interactive startup. Do not redirect mycc stdout/stderr directly to a file in this command; without a TTY, mycc switches to print mode and exits unless a prompt is provided. Reattach with screen -r mycc-wechat to view output.

screen -dmS mycc-wechat zsh -lc '
  cd /path/to/claude-code
  export ANTHROPIC_BASE_URL=...
  export ANTHROPIC_API_KEY=...
  export WECHAT_SELF_WXID=<desktop-self-wxid-or-alias>
  export WECHAT_CHAT_ID=<one-private-chat-id-or-group-chatroom-id>
  export WECHAT_OUTBOUND_MODE=live
  export WECHAT_HOOK_CALLBACK_PORT=36061
  export WECHAT_HOOK_ONEBOT_API_URL=http://127.0.0.1:58080
  export WECHAT_HOOK_ONEBOT_TOKEN=MuseBot
  ./dist/mycc \
    --dangerously-load-development-channels server:wechat-hook \
    --dangerously-skip-permissions
'

Single self/private listening does not need fanout as long as onebot and mycc use the same WECHAT_CHAT_ID-derived callback port. Use fanout when one onebot process must feed multiple Claude Code or mycc sessions, or when you want the onebot side fixed on 36060 while each mycc process listens on its own derived port. In that case onebot sends to fanout on 36060, and each mycc listens on its derived port.

Official Claude Code Notes

This plugin works with official Claude Code signed in through claude.ai; model selection, billing, and account state are controlled by official Claude Code.

Terminal slash commands still work in Claude Code. WeChat-side remote slash commands such as /clear and /model are not guaranteed in official Claude Code unless the official client supports the channel command notification path. Treat those as a fork-specific convenience, not a stable plugin feature.

Features

| Feature | Status | | --- | --- | | Receive text | Supported | | Send text | Supported | | Receive images | Supported | | Send images | Supported | | Receive voice | Supported for private chats with Baidu ASR config; inbound records are saved as raw SILK before ASR | | Send voice | Supported for SILK records on WeChat 4.1.9.52 | | Receive files | Supported; passes local file path and metadata to Claude Code | | Send files | Supported through shareable file URLs when S3-compatible storage is configured | | Group mention trigger | Supported | | Article cards | URL is passed through for Claude Code to fetch |

File receiving does not rely on .doc or .docx extensions. The channel recognizes OneBot file segments and WeChat appmsg metadata such as type=6, appattach/totallen, fileext, attachid, cdnattachurl, and aeskey.

Troubleshooting

If onebot fails with script-load errors, verify the WeChat version and restart WeChat plus onebot. If messages stop after a WeChat crash, the Frida connection is stale and onebot must be restarted after WeChat is back in the main UI. Repeated unable to intercept function ... please file a bug errors usually mean onebot was restarted against an already-hooked WeChat process; stop onebot, restart WeChat, wait for 127.0.0.1:27042, then start onebot again.

If WeChat is still running but the UI stops responding and CPU is high, sample the process before restarting. A stack stuck near req2bufEnterAddr with FridaGadget.dylib usually means a native send timed out before the Frida send state was reset. Current onebot waits for native text/image completion, reports fatal native StartTask errors back to Go, and marks outbound native send unhealthy after fatal access violation / abort was called errors or native send timeouts. When /hook_state returns "native_send_unhealthy":true or /send_* returns 503, restart WeChat and onebot before sending again; onebot intentionally refuses further outbound sends in that state.

Useful checks:

curl http://127.0.0.1:58080/hook_state
lsof -nP -iTCP:27042 -sTCP:LISTEN
lsof -nP -iTCP:36060 -sTCP:LISTEN  # fanout, when used
lsof -nP -iTCP:36061 -sTCP:LISTEN  # private/self mycc callback
lsof -nP -iTCP:58080 -sTCP:LISTEN
tail -f /tmp/wechat-hook-onebot-inbound.log

For main-account self-chat, hook_state should include the configured account ID, for example "my_wechat_id":"<desktop-self-wxid>", and "startTaskReady":true. Use a phone message in that same account's self-chat as the final inbound test; calling OneBot /send_private_msg from the Mac only verifies outbound send.

Repository Layout

.
├── .claude-plugin/
│   ├── marketplace.json
│   └── plugin.json
├── .mcp.json
├── src/                    # MCP channel server
├── scripts/                # onebot/channel helper scripts
├── wechat_chatter/         # bundled WeChat hook source and offsets
└── package.json