name: wechat
description: >
Active WeChat operations via the local wecli CLI — search contacts, look up
contact detail, message a THIRD PARTY on WeChat, or fetch/search filtered
chat history. Use ONLY for out-of-band WeChat requests where the OpenClaw
user explicitly asks the agent to reach someone else or inspect prior
conversations. For replying to an ongoing WeChat DM that is currently being
routed to the agent, do NOT use this skill — the
@fmode/openclaw-wechat-agent channel plugin handles those replies
automatically; just reply normally in the conversation.
metadata:
{
"openclaw":
{
"emoji": "💬",
"requires": { "bins": ["wecli"] },
"install":
[
{
"id": "local",
"kind": "shell",
"label": "Build + link wecli from the local checkout",
"commands":
[
"cd cli && npm install && npm run build && npm link"
],
"bins": ["wecli"]
}
]
}
Use wecli for WeChat operations that the current conversation's channel
plugin cannot perform by itself — i.e. whenever the user asks the agent to
reach OUT of the current DM and do something on the WeChat account.
✅ USE this skill when:
🚫 DON'T use this skill when:
@fmode/openclaw-wechat-agent channel plugin
auto-delivers your reply. Calling wecli messages send on the same
recipient would cause a duplicate message.wecli does NOT wrap /contacts/corp-*./sns/*), 视频号 (/finder/:name), profile management
→ intentionally out of scope for wecli (see cli/README.md).wecli on $PATH. Install with the local install entry above, or run
node <repo>/cli/dist/wecli.js directly.http://8.138.37.248/api/wechat-agent).apiBase configured via ONE of:
WECHAT_API_BASE env var~/.wecli/config.json → {"apiBase":"..."}~/.openclaw/wechat-credentials.json → {"wechatApiBase":"..."} (legacy, may already exist)appId for multi-device isolation — when multiple WeChat
accounts share the same backend, pass --app-id <GEWE_APP_ID> or set via:
WECHAT_APP_ID env var~/.wecli/config.json → {"apiBase":"...","appId":"wx_xxx"}Verify with wecli check-online — should print online yes.
wecli check-online # human-readable
wecli check-online --json # {"ok":true,"online":true,...}
# Find someone by fuzzy keyword (name / wxid fragment / remark)
wecli contacts search "zleo" --json | jq
# Get full detail for a known wxid
wecli contacts detail wxid_m6fljlymhrny22 --json
# Dump the whole address book (cached variant is faster but may be stale)
wecli contacts list --cache --json | jq '.contacts | length'
# All messages from a specific contact, last 20
wecli messages history --wxid wxid_ay08t6ugo2h922 --limit 20 --json
# Incremental: everything since a timestamp (server-side filter + dedupe)
wecli messages history --since 2026-04-21T16:00:00.000Z --json
# Only outbound (what the bot has sent)
wecli messages history --direction sent --limit 50 --json
# Pipe to jq for text-search
wecli messages history --limit 200 --json \
| jq '.messages[] | select(.content | test("合同|报价"; "i"))'
# 1. Find the recipient's wxid first — NEVER guess.
wecli contacts search "张三" --json
# 2. After user confirms the wxid:
wecli messages send \
--to wxid_zhangsan \
--text "今晚 7 点餐厅见" \
--json
# Group @mention (ats = comma-separated wxids)
wecli messages send \
--to 12345@chatroom \
--text "@张三 开会了" \
--at wxid_zhangsan \
--json
wecli conversations list --json | jq '.conversations[:10]'
wecli contacts search "<name>" first and let the user pick.messages send, echo back the
recipient (wxid + nickname/remark) and the message text, and wait for
explicit user confirmation. Especially strict for:
wecli messages send across many wxids
without explicit per-recipient approval. If the user asks for "mass
notify", ask them to confirm the list and then send sequentially with a
short delay, NOT in parallel.wecli messages send --to <same wxid> would duplicate.weixin, fmessage,
medianote, filehelper, floatbottle, or any wxid starting with
gh_ (public accounts) — those are system channels, replies go
nowhere useful.# Step 1: resolve the wxid
wecli contacts search "李总" --json
# → agent finds { wxid: "wxid_lilaozong", nickName: "李总", remark: "朝阳客户李总" }
# Step 2: confirm with user
# Agent: "Found 李总 (wxid_lilaozong, remark '朝阳客户李总'). Send '会议改到周五'? [y/N]"
# Step 3: send after user confirmation
wecli messages send --to wxid_lilaozong --text "会议改到周五" --json
# → {"ok": true, "ret": 200, ...}
# Step 4: report back
# Agent: "Sent."
# No need to look up — user gave a recognizable nick. But verify first:
wecli contacts search "zleo" --json
# → wxid_m6fljlymhrny22
# Fetch recent messages from that contact, then filter by keyword
wecli messages history --wxid wxid_m6fljlymhrny22 --limit 200 --json \
| jq '[.messages[] | select(.direction == "received" and (.content | test("合同|协议"; "i")))]'
# → agent summarizes the matched lines
# Last state: store the timestamp of the newest message seen previously.
# This time, fetch only new ones since then:
wecli messages history --since "$LAST_SEEN_TS" --direction received --json \
| jq '.messages[] | {fromWxid, nickName, content, timestamp}'
# IMPORTANT: if this scan's purpose is to auto-reply, STOP and use the
# channel plugin instead. `wecli` is for one-off human-initiated queries.
cannot determine apiBase — set WECHAT_API_BASE or create
~/.wecli/config.json. See cli/README.md for the full chain.online NO (offline) — the bot account is logged out of WeChat.
Re-scan the login QR code via the web UI at the backend's host, then
retry.contacts search — the upstream POST
/contacts/search field name differs between GEWE builds. wecli sends
both keyword and query for compatibility; if still empty, fall back
to wecli contacts list --cache and filter client-side.messages send returns ret other than 200 — read the msg field;
common causes are "recipient not a contact" (add first via the WeChat
UI) and "rate limited" (wait and retry).--json everywhere when
piping to jq or feeding back to the agent's reasoning loop.wecli is zero-dep Node and ships as a single ~80 KiB bundled
dist/wecli.js. The whole CLI + skill is designed to be copied into a
second machine with nothing but node >= 20 and this repo's cli/
folder.@fmode/openclaw-wechat-agent channel plugin at
../../channel-plugin/.