wechat — macOS CLI
Setup failures and user reports (v1.18.7+)
Release status: v1.18.7 is published and marked latest. The capabilities below apply to the released build; users on older versions can upgrade with the README install command.
Do not require users to identify their macOS version or whether this is a fresh install before helping. The setup window collects redacted environment and service facts automatically. Read the WXS-… problem code from an error screenshot; if more detail is needed, the user can click “复制诊断信息” on any setup page, including a waiting page. The window also provides a minimal report when core cannot start. Never request keys.json, activation tokens, raw memory output, or private chat data.
Missing system developer-tool authorization is detected before initialization and uses “授权并继续” with the native macOS administrator confirmation. Preserve existing login and grants. Do not suggest reinstalling or globally resetting permissions for an unexplained initialization failure. A v1.18.6 error window can use its existing “修复安装” button to upgrade once to the current release; then follow the specific diagnosis rather than repeatedly reinstalling.
Unified CLI for WeChat on macOS. Send messages and query the encrypted local databases for sessions, contacts, chat history, group members, Moments, favorites. Conversation selection runs inside the managed clone without activating or raising its windows.
Managed WeChat 4.1.9 (v1.18.0+)
The installer automatically copies a supported local 4.1.9 or downloads a pinned Tencent DMG (no prompt). It installs ~/Applications/WeChat-4.1.9-wechat-use.app with its own bundle ID and sandbox container, disables that clone's updates, and saves ~/.wx-rs/managed-wechat.json.
All CLI, daemon, HTTP bridge, and MCP operations default to that clone. Check its current state first and reuse an existing login. If it is closed, launch it with open -g "$HOME/Applications/WeChat-4.1.9-wechat-use.app". Request first-time login only when it is actually required, then run wechat-use init. Do not substitute open -a WeChat, sign the primary /Applications/WeChat.app, remove the managed selection, or fall back to the primary when the clone is closed. Only 4.1.9 receives ongoing compatibility work. When repairing installation, rerun the installer; do not download the latest WeChat.
Unified setup and recovery (v1.18.6+)
Use wechat-use setup --json to inspect the current setup stage without opening UI. For a normal user, the curl installer and interactive wechat-use init use the same native setup window. It preserves existing activation, login and keys, selects compatible installed developer tools for the tool only, starts the official background service, and verifies actual service ownership and permissions.
If a user action is needed, run wechat-use setup or open ~/Applications/WechatUseSetup.app. Let the window handle activation, login, system authorization, drag-and-drop and rechecking. Do not give users sequences of doctor, init, launchctl, or xcode-select repair commands. Never launch a Terminal-owned daemon as a permission workaround. The service must be owned by the official bridge.
The bridge's /setup/status remains available before keys or Accessibility grants are ready. It is diagnostic only; sending remains gated. Setup control uses local signals to the validated launchd process, not an unauthenticated HTTP write endpoint.
An unconfirmed setup test is persisted and is not automatically replayed after closing or reopening the window. --skip-verify explicitly skips sending; permission readiness alone is not delivery evidence. Human identity confirmation and actual macOS grants remain user-controlled.
User setup guide
Background execution
When System Settings shows the tool enabled but the actual launchd bridge still rejects Accessibility, do not treat the displayed switch as proof. Check the installed path and signature, refresh only that tool's switch, and recheck a freshly loaded official service. If those checks still fail, the specific old permission entry may be stale. An authorized local computer-control agent can remove and re-add only the failing tool entry using the current installed binary. Identify the exact row before each action; do not use blind coordinates or reset other apps' permissions. Preserve the dedicated WeChat process and keys. Afterwards verify HTTP health, the daemon's real Accessibility result, and that its parent is the official bridge. A user identity challenge remains a user action.
Normal operations must preserve the user's foreground window. Do not call
open -a, activate/raise/unminimize the clone, switch applications, or use
foreground/global hotkeys as routine preparation. Use process-targeted input
and background conversation navigation, and verify the exact recipient.
This also applies to installation, initialization, permission errors, and service
recovery. Return actionable diagnostics without automatically opening Settings,
Finder, permission dialogs, or retrying a send. doctor --fix-tcc is an explicit
interactive recovery command and opens System Settings.
Foreground recovery is a last resort only after available background methods
are shown to be unavailable. Never activate and blindly resend an unconfirmed
message; inspect delivery first. A blank AX snapshot alone does not justify
foreground recovery or restarting WeChat.
Fast path (read this first)
Send a WeChat message in one call:
wechat-use send "早上好" Lisa # fuzzy name match (remark / nick / alias)
wechat-use send "hi" filehelper # wxid — zero DB lookup, fastest
wechat-use send "提醒一下" 20590343959@chatroom # group wxid (ends in @chatroom)
Resolution rules (applied in order):
- RECIPIENT matches a wxid shape (
wxid_…,…@chatroom,gh_…,biz_…, or reserved likefilehelper) → skip all DB work and send directly. - Otherwise, search the local contact DB (remark / nickname / alias / wxid) with session-recency bias:
- single match → send
- multiple matches but only one has recent activity (30d) → send to that one
- otherwise → exit 2 + JSON
{"status":"ambiguous","candidates":[...]}; the agent picks and retries with the explicit wxid
On ambiguous, a sample response:
{
"status": "ambiguous",
"hint": "Lisa",
"candidates": [
{"wxid": "lishuang683451", "display_name": "lisa", "last_seen": "2026-04-20 05:34:55"},
{"wxid": "wxid_xxx", "display_name": "Lisa (另一个)", "last_seen": ""}
],
"note": "multiple matches; pass one of the wxids explicitly: wechat-use send <text> <wxid>"
}
Agent should: read candidates[0].wxid, retry wechat-use send "<text>" <wxid>. Don't ask the user unless the top candidate has no recent activity or multiple candidates do.
HTTP Bridge for agent integration (v1.10+)
wechat-bridge is a separate binary that wraps the daemon's RPCs as a stable localhost HTTP surface. Use this when wiring WeChat into agent platforms (Hermes, n8n, Dify, LangChain, custom bots) — HTTP is cheaper to integrate than spawning the CLI per call.
# Start bridge (binds 127.0.0.1:18400 by default)
wechat-bridge &
# Health + send-readiness
curl http://127.0.0.1:18400/health
# Send
curl -X POST http://127.0.0.1:18400/send \
-H 'Content-Type: application/json' \
-d '{"wxid":"filehelper","text":"hi"}'
# SSE message stream — ⚠️ ALWAYS pass ?since=<epoch>
# Without ?since, default is 0 = backfills entire local message history
# (1MB+ in seconds for typical accounts). For agent / long-running flows
# always pass a since timestamp; pick "now" for live-only or last-checkpoint.
SINCE=$(date +%s)
curl -N "http://127.0.0.1:18400/messages/stream?since=$SINCE"
Endpoints:
| Method | Path | Maps to |
|---|---|---|
| GET | /health |
ping + send_status |
| GET | /chats |
sessions |
| GET | /unread |
unread |
| GET | /contacts |
contacts (query + limit) |
| GET | /chat/:wxid |
recent N messages for one chat |
| GET | /chat/:wxid/history |
history (limit + since + until) |
| GET | /resolve |
resolve_recipient |
| POST | /send |
send_text — returns {status: delivered / submitted_unconfirmed / status_unknown / failed, diagnostic, ...} |
| POST | /typing |
typing indicator (only when --shape hermes) |
| GET | /messages/stream?since=<epoch> |
new_messages_since polled into SSE; pass since or you'll get the full backlog on first connect |
Account readiness and image sends
Image sending and self-message reads resolve the current account in each message
shard; File Transfer Assistant warmup samples are no longer required on the
managed 4.1.9 path. /health reports wechat.state and wechat.account_state
separately from daemon liveness. not_ready means the account is not ready for
sending; needs_wechat_login in doctor requires completing WeChat login, not
reinitializing or deleting caches. WeChat may still require phone confirmation
or a QR login. The tool never bypasses that confirmation.
Ordinary files, videos and cards are not supported by native send yet.
Images over HTTP
The image path must name a file on the bridge host. Native requests accept
image_path (or imagePath); Hermes requests use imagePath. Omit the text
field for an image-only request. Send text and images separately; combining
both in one request is rejected instead of silently dropping content.
curl -X POST http://127.0.0.1:18400/send \
-H 'Content-Type: application/json' \
-d '{"wxid":"filehelper","image_path":"/absolute/path/photo.png"}'
SSE payload shape (v1.10.28 — Wechaty-aligned + isMentioned)
/messages/stream emits event: messages carrying a JSON array of:
{
messageId: string,
chatId: string, // wxid (DM) or groupid@chatroom
senderId: string, // in group: sender's wxid; in DM: the other party's wxid
senderName: string,
chatName: string,
isGroup: boolean,
body: string, // human-readable text. For URL / quote / mini_program, body is the title — raw XML is NOT exposed here.
hasMedia: boolean,
mediaType: "image"|"voice"|"video"|"file"|"",
mediaUrls: string[], // first entry is CDN URL when applicable
mentionedIds: string[], // v1.10.25+ — authoritative @-mention list resolved by daemon
isMentioned: boolean, // v1.10.28+ — bridge-authoritative "this row @-mentions ME". Self-sent rows are always false.
quotedParticipant: string, // v1.10.27+ — populated from refer.fromUser on quote replies
botIds: string[], // legacy heuristic self-marker; NEW consumers should rely on fromSelf instead
fromSelf: boolean, // v1.10.25+ — bridge-authoritative "this row was produced by our own POST /send"; DROP THESE to avoid self-echo loops
messageKind: "text"|"image"|"audio"|"video"|"contact"|"emoticon"|"location"|
"url"|"attachment"|"mini_program"|"chat_history"|"transfer"|
"red_envelope"|"recalled"|"system"|"unknown", // v1.10.27+, aligned to Wechaty's MessageType enum
urlLink?: { title, description, url, thumbUrl }, // present iff messageKind=url
miniProgram?: { title, description, appId, username, pagePath, thumbUrl }, // present iff messageKind=mini_program
refer?: { svrId, fromUser, chatUser, displayName, content }, // present on quote replies
recall?: { replacedMsgId, text }, // present iff messageKind=recalled
media?: { aesKey, md5, cdnUrl, cdnThumbUrl, length, durationSeconds, localPath }, // structured metadata for image/audio/video/attachment
timestamp: number,
}
The full JSON Schema is committed at wx/schema/sse-payload-v1.10.28.schema.json and enforced by a contract test in the daemon build.
Consumer checklist:
- Filter self-echo with
fromSelf === true. Do NOT usesenderId === myWxid— in DM both directions share the same senderId. - In groups, only respond when
isGroup && isMentioned— the daemon already resolves the authoritative mention comparison, so don't reimplementmentionedIds.includes(myWxid)yourself (your wxid may be a remark / lookup that the daemon resolves correctly). The bridge will also drop non-@group rows automatically whenWECHAT_BRIDGE_GROUP_MENTION_ONLY=1. - Need the URL only?
mediaUrls[0]. Need aesKey + md5 to decrypt or verify?media.cdnUrl / media.aesKey / …. - For
messageKind: "image", do not inline base64 image bytes in chat responses. Callwechat-use image get <messageId> --chat <chatId> --json, parseabsolutePath, then use the host agent's file/image Read capability on that path. Default--from auto(since v1.13.11) tries the daemon's in-memory lookup first (fast, works when the user has opened the image at least once in WeChat) and falls back to CDN replay only on miss. If the result isimage not yet viewed in WeChat (cache empty), and CDN fallback failed, ask the user to open the image once in WeChat and retry.cdn-expiredorneeds local-decrypt adaptationmeans neither path can recover this image — surface that to the user instead of guessing. - Expect
bodyfor URL / quote / mini_program to be the human title. If you were previously parsing raw<appmsg>XML from body, migrate to the dedicatedurlLink/miniProgram/referobjects. - Backward compatible: every pre-v1.10.25 field is preserved in name + type. New fields are additive.
Security notes for agents:
- Bridge binds 127.0.0.1 — not exposed to LAN without tunnelling.
- Set
WECHAT_BRIDGE_BEARER=<secret>env var to requireAuthorization: Bearer <secret>on non-/healthroutes. Use this if tunnelling via Tailscale / SSH. - Activation gating is enforced inside wechatd, not in the bridge. A missing / expired
wechatuse_token → HTTP 401 / 402 on/send. Bridge cannot bypass activation.
Command groups
| Group | Commands | First-time requirement |
|---|---|---|
| Diagnostics | doctor |
— (run first; checks AX permission, daemon status, WeChat binary fingerprint) |
| Setup | init |
Log in to the managed clone once; scans the running 4.1.9 process without restarting it. Reuse cached keys while queries work. |
| Send | send |
Automatically prepares the selected clone's chat context; no manual chat selection or warmup message. |
| Query (messaging) | sessions, unread, new-messages, contacts, history, search, members, stats, export, image, sent (v1.16.12+, cross-chat self-sent) |
init first; daemon auto-starts on demand (v1.7.5) |
| Saved items | favorites |
init first; daemon auto-starts on demand |
| Realtime (v1.3+) | listen |
daemon auto-starts on demand (v1.7.5) |
| Daemon (v1.2+) | daemon start|stop|status|ping |
optional — query/listen commands pull it up automatically when needed |
| HTTP Bridge (v1.10+) | wechat-bridge (separate binary) |
agent / Hermes / n8n integration over localhost HTTP — see section below |
| Wechaty Puppet gateway (v1.10.32+) | wechat-wechaty-gateway (separate binary, gRPC :18401) |
for the human writing a wechaty bot — NOT used by this skill. If the user asks "can I run my wechaty bot on this?", point them to https://github.com/leeguooooo/wechat-use#接-ai-agent and stop. Don't try to write wechaty TS from this skill. |
wechat-use tunnel (v1.11+) |
wechat-use tunnel setup |
Expose local REST bridge to a remote service via Cloudflare Tunnel; details in docs/remote-gateway.md, do NOT inline the full setup flow in this skill. |
wechat-use orchestrate (v1.12+) |
wechat-use orchestrate setup --outbox-url= --webhook-url= --bearer= --webhook-secret= |
Long-running worker that polls a SaaS outbox API and pushes SSE inbound events to a SaaS webhook. NAT-friendly (Mac all-outbound, no public IP / domain). Used by SaaS integrations (cherry-class). Protocol: docs/v1.12-orchestrate-protocol.md. Don't inline the SaaS-side endpoint design here. |
| Auth (v1.9.1+) | auth activate | status | renew |
mandatory activation before send — code from @WechatCliBot on Telegram |
update-guard (v1.16.33+) |
update-guard status | disable | enable |
锁 WeChat 后台自动热更新通道(chflags uchg MacUpdate 路径),Tencent 推新 dylib 不会替换。建议 init 完成后跑一次 disable 让当前 build 的适配 stable;CLI 起手会自检并 warn,不阻塞 |
probe-build (v1.16.32+) |
probe-build |
WeChat 升级到工具不认的新 build 时跑。本机 literal_scan + pattern-match routing 锚点输出 shift 数值,POST /v2/probe 给适配队列。不传 binary,只传 derived 数值。read-side 命令(history / sessions 等)经 literal_scan fallback 立即可用 |
All query commands default to YAML output (agent-friendly, low token). Add --json to get JSON.
🛑 Safety rules (CRITICAL — read before calling send)
Every send call must resolve to a known wxid. No silent default to "current chat" — that flag (--current-chat) was removed pre-1.13; if the resolver can't find a recipient, stop and ask the user.
Correct flows for "给 XXX 发 YYY":
- Just try it:
wechat-use send "YYY" XXX. Fast-path resolver (see top of this doc) handles wxid-shaped targets instantly and fuzzy-matches names against the local contact DB with session-recency bias. - On exit 2 +
status: "ambiguous": ifcandidates[0]haslast_seenwithin ~30 days and others are stale/empty, the CLI already auto-picked it and returned success. If it truly was ambiguous (multiple candidates with recent activity), pick one yourself by asking the user — don't guess. - On
no contact matches "XXX": ask the user for the wxid (or have them confirm a candidate fromwechat-use contacts --query XXX --brief).
Hard rules (the agent MUST follow):
- DO NOT guess or fabricate a wxid. If resolution fails, escalate to the user.
- DO NOT scan the filesystem / grep logs / use AppleScript to hunt for a wxid. The CLI already searches the local contact DB via the fast path — trust it. If it can't find the recipient, stop and ask the user.
- DO NOT invoke
wechat-use contactsfollowed bywechat-use sendas two separate calls unless the first fast-path send already told you it was ambiguous. The one-liner saves ~400ms and one agent round-trip.
Capability matrix
| Capability | Status | Command |
|---|---|---|
| Extract DB key, cache layout (required first step for query commands) | ✅ | wechat-use init |
| Send text to a specific wxid / 群名 / 昵称 | ✅ | wechat-use send "..." <recipient> |
| Any Unicode / emoji / CJK / length | ✅ | built-in |
| Zero UI flash (no focus steal) | ✅ | default for send |
| List recent chat sessions | ✅ | wechat-use sessions |
| Sessions with unread messages | ✅ | wechat-use unread |
| Incremental new messages since last check | ✅ | wechat-use new-messages |
| Contact lookup / fuzzy search | ✅ | wechat-use contacts [--query KW] |
| Chat history (private / group) | ✅ | wechat-use history <chat> [-n 500] |
| LLM-ready group digest | ✅ v1.13.33 (--with-id v1.16.19+) |
wechat-use digest <chat> [--json --with-id] |
| User alias map for chats | ✅ v1.16.19+ | wechat-use alias add/list/rm |
| Full-DB keyword search (FTS5 trigram, v1.16.21+) | ✅ | wechat-use search <kw> [--in CHAT] [--since TIME] |
| Group members | ✅ | wechat-use members <group> |
| Chat statistics (senders / types / hours) | ✅ | wechat-use stats <chat> |
| Export chat → Markdown / JSON | ✅ | wechat-use export <chat> --format markdown -o ... |
| Favorites (text/image/article/...) | ✅ | wechat-use favorites [--type ...] [--query KW] |
| Image media (local in-memory lookup + CDN fallback) | ✅ | wechat-use image get <messageId> --chat <id> |
| Voice media (raw SILK_V3) | ✅ | wechat-use audio get <svr_id> (1.13.21+) |
| Voice transcribe (whisper.cpp + SILK pipeline) | ✅ | wechat-use audio setup 一次 + wechat-use audio transcribe <svr_id> (1.13.25+) |
| First-send chat context | automatic | The daemon selects and verifies the exact recipient before sending; the user does not need to click a chat or send a warmup message. |
| Realtime inbound stream (v1.3) | ✅ | wechat-use listen — watches new messages, push to stdout |
| Inbound callback → shell command (v1.3) | ✅ | wechat-use listen --on-message "handler.sh" (WECHAT_MSG_* env vars) |
| Server-side wxid filter (v1.3) | ✅ | wechat-use listen --wxid filehelper |
| Background daemon (v1.2+, lazy-start v1.7.5) | ✅ | wechat-use daemon start — or auto-spawn by any query command |
| WeChat binary fingerprint verification (v1.7.2+) | ✅ | wechat-use doctor surfaces drift after WeChat hot-fix updates |
| Send image / file | ⏳ roadmap | — |
| Group broadcast | ❌ disallowed | anti-abuse; LICENSE forbids |
| Linux / Windows / Intel Mac | ❌ | macOS arm64 only |
| unverified WeChat build | ⚠️ unverified | adaptation data may drift; wechat-use doctor flags it |
Agent: first-use setup
Step 1 — Check wechat is on PATH:
command -v wechat
If missing:
curl -fsSL https://raw.githubusercontent.com/leeguooooo/wechat-use/main/install.sh | bash
# Ensure ~/.local/bin is on PATH
case "${SHELL##*/}" in
fish) fish_add_path "$HOME/.local/bin" ;;
zsh) grep -q '.local/bin' ~/.zshrc 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc ;;
bash) grep -q '.local/bin' ~/.bashrc 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc ;;
esac
export PATH="$HOME/.local/bin:$PATH"
Step 2 — Initialize the already-logged-in managed WeChat:
wechat-use init
On the managed WeChat 4.1.9 this scans the existing process for per-database keys; it does not need to quit and relaunch WeChat. Keep the existing account, container and login state. Reuse cached keys while queries work, including after a daemon restart; only rescan when keys are missing or fail to decrypt.
If WeChat itself is on a login screen, the user must complete that genuine login challenge. Do not manufacture a new challenge by restarting a logged-in clone, replacing its bundle identity, or deleting its local account data.
Step 3 — Check service permissions:
wechat-use doctor --json
Check the running daemon's daemon_accessibility result and the bridge's
health. Accessibility belongs to the installed helper identities; a signed
upgrade should retain those grants. If missing, run wechat-use init --fix-tcc: it opens the correct pane, selects the binaries in Finder for drag-and-drop, then automatically rechecks the running daemon. Do not ask the user to find hidden paths or type recovery commands themselves.
Step 4 — Verify a send without manual chat selection:
wechat-use send "安装验证" filehelper --json
The daemon reads the selected chat identity and uses background conversation navigation to select the exact requested recipient before typing. It checks the identity again before Enter. Missing build calibration, ambiguous context, a changed recipient, or an unavailable target stops the send before dispatch. Do not ask the user to click filehelper or send a warmup message as a normal setup step. The current local candidate requires a matching recipient-context calibration; distribution of this calibration is still pending and must be tested before release.
Only a verified record in the requested chat counts as delivery. A
delivery_verify_timeout can be a delayed delivery: inspect history before
retrying. If automatic preparation fails, keep the diagnostic output and run
wechat-use doctor --json; do not restart the logged-in WeChat or reactivate
its account as a workaround.
An empty AX tree or blank automation screenshot is not proof that WeChat is blank, logged out, or frozen. Use process/CLI/database evidence and the user's visible state before diagnosing it. Never quit/relaunch a logged-in clone just to recover an automation snapshot.
Send failure modes (agent self-recovery table)
When wechat-use send returns {ok: false, …} or stderr matches the reason: slot in the support payload, use the playbook below before asking the user to debug. Diagnose in the background and request human action only for a confirmed step that cannot be completed there. Never infer permission to take focus from a failed send.
reason: slot |
Root cause | Agent action |
|---|---|---|
desktop_locked |
macOS system lock screen is active; no message was sent | Ask the user to unlock the Mac system screen. Keep the existing WeChat login and account data; do not request WeChat login or manual chat selection. Retry only after the desktop is unlocked. |
delivery_misrouted |
Delivery landed in another chat | Stop further sends, identify the actual destination, and repair recipient selection. Do not silently retry or ask for manual warmup. |
delivery_verify_timeout |
Send path ran but delivery is not yet confirmed | Inspect the requested chat history before retrying; a delayed message may still arrive. |
slot_send_bp_armed_no_fire |
Automatic input-context preparation did not activate the send signal | Keep the diagnostic and run wechat-use doctor --json. Do not ask for manual warmup, restart WeChat, or reactivate the account. |
wechat_not_running |
Selected clone is not running | Launch open -g "$HOME/Applications/WeChat-4.1.9-wechat-use.app", then check whether the existing login recovered. Request login only if required. Never launch the primary app as a fallback. |
slot_send_bp_failed_to_arm (detail: send adapter install did not complete within 30s) |
WeChat has get-task-allow=false (official default signing); wechatd's local debugging interface can't connect |
Run wechat-use doctor to confirm wechat_get_task_allow ✗. Then ask the user to copy-run the merge-mode re-sign block that install.sh prints (or wechat-use doctor suggests). Needs sudo, must be run by the user. Do NOT use a replace-mode codesign --entitlements ... command — it strips WeChat's existing system entitlements and breaks TCC for WeChat itself. |
daemon_accessibility FAIL / ax_trusted=false after a recent install/upgrade |
Multiple ai.wechat.* LaunchAgents on the machine — orchestrate (KeepAlive) re-spawned wechatd with a stale launchd responsibility chain |
Re-run curl -fsSL https://raw.githubusercontent.com/leeguooooo/wechat-use/main/install.sh | bash. v1.16.12+ install.sh bootouts every ai.wechat.*.plist and bootstraps in the right order. Don't try to surgically launchctl kickstart — it doesn't reset the responsibility chain. |
tcc_accessibility_denied (wechat-bridge or wechatd untrusted) |
The required macOS Accessibility grant is missing | Confirm the missing grant with wechat-use doctor --json and report it without opening any windows. If interactive recovery is required, explain that wechat-use doctor --fix-tcc opens permission dialogs and System Settings; the user must enable the relevant grants. Do not run it as an automatic retry. |
activation_* / subscription_* / unauthorized |
Activation code expired / not redeemed | wechat-use auth status to confirm. Then wechat-use auth activate <code>. Direct user to https://t.me/WechatCliBot to request an activation code (research / personal use only — no commercial inquiries answered). |
dylib_fingerprint_unverified |
WeChat auto-updated to a build not in our verified set | wechat-use doctor shows the new fingerprint. Tell user to turn off WeChat auto-update (WeChat → 设置 → 通用 → 「有更新时自动升级」) and repair the managed 4.1.9 clone with the installer. Do not upgrade to another WeChat version. |
Key principle: every reason: slot is stable enum; the support payload includes ax_trusted, input_monitoring, binary_fingerprint, cli_version, daemon_version — match on those exact field names, not on the human-readable Chinese error message (it may evolve across releases).
LaunchAgent / TCC handbook for agents: if you ever need to manually reset responsibility chains (e.g. user's machine has a customized LaunchAgent layout), use launchctl bootout + launchctl bootstrap — never launchctl kickstart -k. kickstart doesn't re-read plist EnvironmentVariables and doesn't reset launchd's responsibility-chain cache. The install.sh upgrade flow is the reference implementation.
Usage — send
# Recipient resolves wxid / 群名 / 昵称 / 备注 (fuzzy match against local contact DB)
wechat-use send "你好 🎉" filehelper
wechat-use send "会议 5 分钟后开始" lishuang683451
wechat-use send "早上好" Lisa # 找不到 → friendly error + 候选
# Group send (resolver also handles group display names)
wechat-use send "今天 19:00 团建" "AI 星球"
# JSON output for agents that parse responses
wechat-use send "ok" filehelper --json
# Dry-run: resolve recipient + validate args, do NOT actually send. Useful when
# the agent wants to verify a fuzzy name → expected wxid before committing.
wechat-use send "draft" "李工" --dry-run --json
send arguments
| Arg | Required | Description |
|---|---|---|
<TEXT> (positional) |
yes | Message body. Any length, any Unicode. |
<RECIPIENT> (positional) or --wxid |
yes | Target wxid / chatroom id / 昵称 / 群名 / 备注. Resolver picks the most-recently-active match if hint is fuzzy. |
--mention <wxid> |
no | Visual @<name> prefix (text-only, no real ping ack — see issue #4). |
--dry-run |
no | Resolve recipient + validate but don't send. Pairs well with --json for agent dry-checks. |
--json |
no | JSON output |
send --json 三态契约 (v1.13.20+)
所有 --json 输出都带顶层 ok: bool,agent 直接 if (r.ok) {...} else {...} 不需要解析三套 schema:
| 状态 | 触发 | shape (顶层字段) |
|---|---|---|
| success | wechat-use send TEXT RECIPIENT --json 真发成功 |
{ok: true, sent: true, reason: null, diagnostic: {…SendResult 全字段…}} |
| dry-run | --dry-run --json(resolver OK + 不真发) |
{ok: true, dry_run: true, text, resolved_wxid} |
| error (early) | --json + 参数错 / resolver 找不到 / ambiguous / 网络断 |
{ok: false, exit_code: <int>, error: "<msg>"} |
| error (send fail) | 发送失败或送达未确认(自动准备失败 / 权限缺失 / 版本未适配等);核对 diagnostic 和历史记录 | {ok: false, sent: false, reason: "<reason>", diagnostic: {…}} |
# 推荐:agent 用 jq 分支
wechat-use send "hi" filehelper --dry-run --json | jq -e '.ok' && echo "✓ resolved" || echo "✗ failed"
stderr 仍然有 human-readable 错误描述(给终端用户看);agent 只需 parse stdout JSON。
Usage — query
# Sessions (recent conversations)
wechat-use sessions -n 20 # full yaml
wechat-use sessions --brief -n 20 # 单行/会话, 带未读数
wechat-use sessions --filter group --json -n 20 # 只看群聊 (chat_type: group / private / official_account / folded / other)
# JSON 字段命名:未读数是 `unread_count`(下划线全名), 不是裸 `unread`。brief 视图渲染成 [N unread] 仅是显示, 实际字段是 unread_count。
# Contacts
wechat-use contacts --query 李 # fuzzy match nickname/remark/wxid
wechat-use contacts --brief -n 50 # 单行/联系人 (姓名 + wxid)
# Unread
wechat-use unread -n 5
# History (chat positional or --chat flag, both accepted)
wechat-use history "张三" -n 2000
wechat-use history --chat 21263894984@chatroom -n 200
wechat-use history "AI 星球" --since "2026-04-01" --until "2026-04-15" -n 200 # ISO date OK
wechat-use history "AI 星球" --since 1719793200 --until 1720484400 -n 200 # epoch OK too
# Search (FTS5 trigram, v1.16.21+)
wechat-use search "会议纪要" # 全局,毫秒级
wechat-use search "报销" --in "财务群" # 群限定
wechat-use search "claude" --since "3 days ago" # 加时间窗
wechat-use search "report" --since "2026-05-01" --until "2026-05-10"
# Search agent UX (v1.16.22+):
wechat-use search "desktop" --in "Helm" --context 5m # 每条命中带前后 5 分钟同群上下文,文本模式 `>` 标记命中
wechat-use search "report" --timeout-ms 5000 # 硬上限 5s,超时 exit 124 + `search_timeout`
# `--json` meta 多带: elapsed_ms / row_count / chat_resolved / chat_hint / context_secs
# HTML 证据页 (v1.16.23+, v1.16.24 重做 WeChat 主题 UI + 默认 inline 图片):
wechat-use search "desktop" --in "Helm" --report # 单文件 HTML,默认 --context 5m,图片 base64 inline
wechat-use search "房价" --in "立水桥" --report --no-media # 跳过图片(escape hatch,渲染成占位卡片)
wechat-use search "通知" --in "群" --report --out report.html # 自定义路径,无 sidecar 目录,转发邮件/微信直接带走
# stdout = 写入的文件路径(text 模式)或 {ok, report_path, hit_count, ...}(--json)
# 输出文件 self-contained:inline CSS,无 JS,无 CDN,可直接邮件/微信/Notion 转发
# Search caveats (FTS5 trigram limits):
# - 查询 < 3 字符: 全局会报 `query_too_short`,加 `--in <chat>` 则
# fallback 到 LIKE 单 shard。例: `wechat-use search 卡 --in <chat>`
# - 首次升级后 daemon 在后台 backfill (266k 消息 ~86s 本机);期间
# 查询会报 `search_index_unavailable`,等一两分钟后再查
# - mirror DB at `~/.wx-rs/search-index.db` (SQLCipher 加密,key 从
# 现有 WeChat key HKDF 派生),mirror 跟 WeChat 数据库一起在 home 目录
# - `--context` 需要配合 `--in <chat>` 才生效(无 chat 时被 daemon 跳过)
# Group members
wechat-use members "AI 星球"
# Stats
wechat-use stats "AI 星球"
history --json payload shape (stable contract for agents)
顶层包装(history / sessions / unread / search / digest 全一致):
{
"meta": {
"chat_latest_timestamp": 1778981425,
"shards_scanned": 2,
"shards_hit": 2,
"status": "ok",
"order": "desc",
"now_unix": 1779178200,
"since_resolved": 1778573400,
"until_resolved": 1779178200
},
"rows": [ {...row...}, {...row...} ]
}
不是裸数组。jq 要 .rows[] 不是 .[]。meta.order 是 v1.16.12+ 加的字段,告诉
你 rows 是 "desc"(新→老,history 默认)还是 "asc"(老→新,digest 默认 / wechat history --order asc)。别拿 rows[0] 当"最新"也别当"最老",先看 meta.order。
meta.status 取值:ok / windowed(传了 --since/--until)/ possibly_stale
(SessionTable 比 history 领先 > 24h,大概率分片漂)/ possibly_stale_unknown_shards
(磁盘有新分片 daemon 不认,要重跑 wechat-use init)。
v1.16.19+ 时间窗回灌(history): meta.now_unix 是查询时的本机 epoch;
meta.since_resolved / meta.until_resolved 是 --since / --until 自然语言解析后的
实际 epoch(没传不出现)。自然语言 --since "2 days ago" 解析完后,agent 不用重算时区,
直接拿这三个值跟用户复述"实际查的是 X 到 Y, 当下 Z"。
每条 message row 字段(snake_case):
| 字段 | 类型 | 说明 |
|---|---|---|
local_id |
int | DB 行主键(per chat 单调)。image get <local_id> --chat <wxid> 用这个取图。 |
server_id |
int | WeChat 服务端 msg id(撤回时引用 replacedMsgId)。 |
local_type |
int | 原始 type code。低 16 位 mask 后 = 1 文本 / 3 图 / 34 语音 / 43 视频 / 49 appmsg / 等。 |
message_kind |
string | enum: text / image / audio / video / url / mini_program / recalled / appmsg / 等。Wechaty 对齐。 |
display_text |
string | 已清洗后的 human-readable body(text 直接 = body;image/url 抽 title;recalled 给替代文案)。 |
message_content |
string | 原始 body(可能是 raw XML / 群消息带 <sender>:\n 前缀)。debug 用,生产逻辑请用 display_text。 |
sender_wxid |
string | null | 群消息 = 真发送者 wxid;DM 两侧都是 null(WeChat DB 在 1:1 chat 不记 sender wxid)。不能单凭这个判 self-sent(DM 会双方都误判 + 系统消息也是 null)。 |
sender_display_name |
string | null | daemon-resolved 展示名(群里的群昵称 / 联系人备注 / 昵称)。v1.16.19+ 改: 联系人 DB 不可达 / 这条 wxid 找不到时,fallback 到裸 wxid 而不是 null(agent 不用同时处理 has/has-not 两种 shape)。DM self-sent / 系统消息仍为 null。 |
real_sender_id |
string | per-chat 自增 ID(字符串,永远非空)。WeChat 给当前账号分配一个固定 id(本机经验值是 "2",不同账号可能不同),其它整数 = 对方/群成员。判 self-sent 用这个:扫 filehelper 历史得到自己的 id(filehelper 100% 自发,占比最大那个就是 self id),其它 chat 用同一 id 过滤。 |
chat_id / username |
string | 会话 wxid(DM)或 xxxx@chatroom(群)。 |
chat_display_name |
string | 群名 / 联系人备注 / 昵称(v1.13.9+ 自动解析)。xxxx@chatroom 直接看得懂。 |
create_time |
int | epoch seconds。 |
created_at |
string | ISO 本地时区(2026-05-18T01:30:45+09:00),v1.13.30+ 派生,人读直接拿这个。 |
is_mentioned |
bool | 当前账号在群里被 @ 了(daemon 端权威解析,客户端别再算一遍)。 |
media |
object | image / voice / video / file 才有: {aesKey, md5, cdnUrl, cdnThumbUrl, length, durationSeconds, localPath, dat_path?, dat_md5?, dat_exists?}。 |
urlLink / miniProgram / refer / recall |
object | type-specific 结构化字段(见 SSE schema)。 |
字段稳定性:增加 = 默认 null / 缺省;不会重命名 / 改类型(契约由 v1.10.27 起的 SSE schema 单测守)。
想跨 chat 拉"我说过什么": 用 wechat-use sent --since "7 days ago" --json(v1.16.12+),
比手动遍历 sessions + filter 干净得多。
群名歧义: wechat-use history "AI 星球" 可能匹配多个(同名群 + 同名联系人)。
撞歧义时 CLI 会列出候选 + 报错,改用 --chat <wxid> 或 --chat <chatroom_id@chatroom>
明确指定。
v1.16.19+ user alias: 长群名 / emoji 前缀群名 fuzzy 经常 miss(詹密群 /
季景铭郡业主三群),用 alias map 一劳永逸:
wechat-use alias add 詹密群 17765974862@chatroom
wechat-use alias add AI星球 20590343959@chatroom
wechat-use alias list # 看现有
wechat-use alias rm 詹密群 # 删
Resolver 优先级:wxid-shape > alias > contact.db fuzzy。wechat-use send /
history / digest / search / recalled / sent / listen --wxid 全部
共享同一查找(写到 ~/.wx-rs/aliases.json)。
# Export
wechat-use export "张三" --format markdown -o zhang.md
wechat-use export "AI 星球" --format json -o ai.json -n 5000
# Incremental (since last checkpoint saved in ~/.wx-rs/cursor.json)
wechat-use new-messages -n 50 # advances checkpoint
wechat-use new-messages --reset # rewind checkpoint to "now" so next call starts fresh
# Favorites
wechat-use favorites # all locally-cached items
# Image media (local in-memory lookup first, CDN fallback)
wechat-use image get <local_id> --chat <chat_id> # decrypts + writes to ~/.wechat/media-cache/<md5>.jpg
wechat-use image inspect <local_id> --chat <chat_id> # dump CDN metadata (no key/url leak)
# Voice media — history auto-transcribes by default (v1.13.25+)
wechat-use audio setup [--model small|medium|large] # one-time: install deps + download model
wechat-use audio transcribe <svr_id> [--language zh] # single-file pipeline
wechat-use audio get <svr_id> # raw SILK_V3 bytes, no decode
Voice — agents reading group history just work (v1.13.25+)
After running wechat-use audio setup once (~2-3 minutes downloads ~1.5GB
medium model + builds silk-decoder), wechat-use history automatically
transcribes voice messages so the agent sees the spoken content in
display_text (and structured in media.transcript) — no more
[语音消息] placeholders breaking conversation context. Transcripts are
cached by SHA-256 of the audio blob, so re-reading the same chat is
near-instant.
# One-time setup
wechat-use audio setup
# Read a chat — voice messages already transcribed inline
# (Output is {meta, rows} not a bare array — use `.rows[]` not `.[]`.)
wechat-use history <chat> --json | jq '.rows[] | {kind: .message_kind, text: .display_text}'
Opt-out (skip transcribe to keep history fast / private):
wechat-use history <chat> --no-transcribe # skip transcribe entirely
wechat-use history <chat> --transcribe-model small # smaller / faster model
wechat-use history <chat> --quiet # silence stderr progress lines
# (auto-on in --json mode)
The media.transcript_status field on each audio row tells the agent
where the text came from: cached / transcribed / no_deps (run
wechat-use audio setup) / failed / skipped_svr_id_zero / invalid_input.
wechat-use doctor reports audio readiness in two rows so machine consumers
can check default-model status without parsing strings:
audio_transcribe_setup— overall ffmpeg / whisper-cli / silk-decoder presence. Alwaysok: true(audio is optional; missing tools must not flip overall doctor status toneeds_init).audio_transcribe_default_model—ggml-medium.binexists.okreflects reality, but excluded from the overall status calculation so a user without me
…(truncated)