| name | send-wechat-message |
| description | Send or draft WeChat desktop messages on macOS through GUI automation, including opening WeChat, verifying Accessibility and Automation permissions, navigating the visible chat list, writing exact Unicode text into the composer through AXValue, capturing verification screenshots, and sending only after explicit user confirmation. Use when a user asks to open WeChat or 微信, send a message to a contact or another account, prepare a message before sending, confirm delivery with a screenshot, or troubleshoot Codex control of the macOS WeChat app. |
Send WeChat Message
Overview
Automate the macOS WeChat desktop client conservatively. Prefer deterministic GUI steps, verify state with screenshots, and never send until the user explicitly confirms.
Workflow
- Verify that WeChat is installed and that Codex can control it.
- Normalize the viewport by entering fullscreen if needed and zooming out to fit more content.
- Bring WeChat to the foreground and capture the current window.
- Identify whether the target conversation is already visible in the chat list.
- Prefer keyboard navigation in the visible chat list over mouse clicks.
- If the target is a group chat, confirm it has local chat history before relying on search results.
- Open the target chat, focus the composer, and write the exact message text into the composer.
- Stop and ask for confirmation before sending.
- Send only after confirmation, then capture a proof screenshot.
- Do not clean temporary screenshots automatically; ask the user whether to keep or delete them.
- When the task is to read older messages, scroll the chat history upward in small increments and capture each checkpoint.
Quick Start
Run the helpers from the skill directory:
scripts/check_wechat_access.sh
scripts/open_chat_and_draft_safely.sh "Alice" "hello from Codex"
scripts/capture_wechat_window.sh
If both succeed, use the returned screenshot path with view_image to understand the current WeChat state before making further inputs.
scripts/prepare_wechat_viewport.sh is safe to call repeatedly. It only toggles fullscreen when needed, then keeps zooming out until WeChat no longer offers another 缩小 step.
For performance-sensitive agent flows, prefer scripts/open_chat_and_draft_safely.sh over stitching open_chat_safely.sh and focus_composer_and_set_value.sh separately. It preserves the same guardrails while reusing a single prepared viewport within one process.
For performance-sensitive group-chat @mentions, prefer scripts/open_chat_mention_and_send_safely.sh. It keeps the verified-open guardrails, then resolves the visible @ picker candidate from the lower-left popup before sending.
Navigation Strategy
Prefer these controls in this order:
Command+1 to switch WeChat to the chat list.
scripts/capture_wechat_window.sh to inspect which chat is currently selected.
- Prefer
scripts/open_chat_safely.sh "<chat name>" before drafting or sending.
- This first checks whether the current chat already matches, then prefers the visible home-page sidebar list, and only falls back to search if the target is not visible there.
- Capture again and verify the title area matches the intended recipient.
Use mouse clicks only as a fallback. In this app version, synthetic mouse events may hover a row without selecting it, while arrow-key navigation is reliable.
If the desired contact is not visible in the current chat list, either:
- use WeChat search manually with screenshots and small verification steps, or
- ask the user to bring the target chat into view before continuing.
This skill is optimized for the visible-chat path because WeChat exposes a sparse accessibility tree.
Search Strategy
When the target chat is not visible:
- The default high-level entry is:
scripts/open_chat_safely.sh "<chat name>"
- This first checks the current chat, then tries a visible match in the current home-page sidebar list without scrolling.
- Only if that fails should it fall back to search.
- The lower-level search helper remains:
scripts/search_chat_and_click_local_result.sh "<chat name>"
- This uses
Command+F to focus WeChat search, writes the query through the focused search field's AXValue, waits for the dropdown to render, and prioritizes rows under local sections such as 群聊 or 联系人 instead of the top search suggestions.
- Pressing Return is no longer the preferred path because it may open
搜一搜 instead of the local result.
Do not press Return immediately after typing into search. In the current macOS WeChat build, that often opens the separate 搜一搜 window instead of the local chat result.
If search becomes unstable, IME behavior mutates the query, or WeChat keeps escaping into 搜一搜, fall back to:
scripts/find_chat_in_sidebar_by_ocr.sh "<chat name>"
This scans the visible left chat list with OCR, scrolls that list downward if needed, and clicks the first matching visible row.
For group chats, local search works reliably only when the group already has local history on the current Mac. If the group is missing, ask the user to sync or open the group once manually before retrying.
Before drafting or sending any message, verify the current target object with:
scripts/verify_current_chat_title_by_ocr.sh "<chat name>"
If this fails, do not draft and do not send.
Drafting The Message
After the correct chat is open:
- Focus the composer. In the current WeChat build, one
Tab from the chat view usually lands in the message composer.
- Use
scripts/focus_composer_and_set_value.sh "<message>" instead of simulated typing.
- Capture the window and verify that the exact text appears in the composer.
Prefer direct AXValue assignment over clipboard paste or simulated typing. This avoids IME transformations, candidate-bar interference, and cases where WeChat ignores Command+V even though the focus is already in the composer.
If the draft needs line breaks, pass real newline characters. Do not build the message as a plain single-quoted shell string containing literal \n, because WeChat will receive those characters verbatim.
For group-chat @mentions, do not keep typing the mention target and the body through the IME after opening the @ picker. A more stable path is:
scripts/mention_group_member_and_set_value.sh "阿三" "现在这条消息是AI发出来的"
This opens the @ picker, uses OCR to click the visible member candidate, then appends the body through the composer's AXValue so the IME cannot corrupt the message text.
Reading History
When the task is to inspect older messages in a chat:
- Open the correct conversation first.
- Capture the current screen state.
- Use
scripts/scroll_chat_history.sh with modest increments so messages are not skipped.
- Capture again after each scroll window.
- Stop when you reach the desired date boundary, or when the chat no longer moves upward.
Prefer many small scrolls over a few large jumps. In live testing, moderate pixel-based scrolling was more reliable than Page Up, and less likely to skip context than aggressive jumps.
Recommended starting command:
scripts/scroll_chat_history.sh 8
scripts/capture_wechat_window.sh
If pixels is omitted, the scroll helper derives it from the current window height.
Use a larger steps count only after confirming the direction and density of messages in the current chat.
The scroll helper computes a default focus point inside the chat-history pane. Override the coordinates only when the window layout is unusual.
For continuous review of a whole chat history, prefer:
scripts/capture_chat_history_sequence.sh
This now defaults to 100 pages per batch. If the script reaches that limit without finding the stable top, stop and ask the user whether to continue.
The sequence helper also:
- adapts the scroll distance to the current window size
- uses OCR to best-effort click visible
转文字 buttons before the final page capture
- restricts OCR extraction to the current conversation pane instead of the full WeChat window
- writes per-page OCR text into
ocr/
- builds
conversation-reference.md in the same folder for later review
- writes
conversation-merged.txt with simple overlap-based deduplication across pages
Sending Policy
Always stop after the draft is visible and ask for explicit user confirmation.
Only after the user replies with clear approval such as "发送" or "send":
scripts/send_current_draft.sh
scripts/capture_wechat_window.sh
Return the screenshot path so the user can inspect it, then ask whether those temporary screenshots should be cleaned. Run scripts/cleanup_wechat_temp_screenshots.sh only after the user explicitly asks to delete them.
If the active chat cannot be re-verified, stop before sending. Wrong-recipient prevention is a hard guardrail.
Iteration Policy
Whenever a new macOS WeChat behavior is discovered in real use, fold that behavior back into this skill.
Typical examples:
- search and chat-selection edge cases
- focus and composer-detection failures
- IME or paste failures
- send-key behavior differences
- group-chat discovery constraints
Update the relevant script, SKILL.md, troubleshooting notes, and release notes together so the skill remains the canonical record of operating knowledge.
Privacy And Publishing
Treat this repository as public by default.
Before pushing any update to GitHub:
- Remove local absolute paths that reveal usernames or machine-specific directories.
- Do not commit screenshots captured from real conversations.
- Do not include personal contact names, chat titles, message contents, or IDs unless they are clearly synthetic.
- Keep user-facing examples generic and reusable.
- Prefer placeholders such as
/path/to/..., $HOME, or $CODEX_HOME.
- Ask before cleaning temporary screenshots after successful sends and verification.
If a real interaction taught the workflow, capture the behavior generically and strip the personal context before publishing.
Scripts
scripts/check_wechat_access.sh: Verify that WeChat exists and that System Events can control it.
scripts/prepare_wechat_viewport.sh: Enter fullscreen if needed and zoom out the WeChat viewport before further actions.
scripts/capture_wechat_window.sh [output.png]: Activate WeChat, detect the front window bounds, and capture a window screenshot.
scripts/ocr_wechat_screenshot.sh [--json] [--region left bottom width height] <image.png>: Extract ordered OCR lines from a WeChat screenshot, optionally constrained to a normalized region.
scripts/expand_visible_voice_transcripts.sh <image.png> [timeout_seconds]: Best-effort click visible 转文字 buttons and wait for transcript text to settle.
scripts/find_chat_in_sidebar_by_ocr.sh "<chat name>" [max_scrolls]: Scan the left chat list with OCR and click the first visible matching chat row.
scripts/open_chat_safely.sh "<chat name>": Prefer the visible home-page sidebar entry, fall back to search only if needed, and require OCR title verification before success.
scripts/search_chat_and_click_local_result.sh "<chat name>": Focus the WeChat search box with Command+F, write the query through AXValue, and prefer local-section rows over top search suggestions when selecting a result.
scripts/verify_current_chat_title_by_ocr.sh "<chat name>": Capture the current window and verify the active chat title via OCR before drafting or sending.
scripts/open_chat_and_draft_safely.sh "<chat name>" "<message>": Open a verified chat and write the draft in one process to reduce repeated setup overhead.
scripts/open_chat_mention_and_send_safely.sh "<chat name>" "<member_name>" "<message>": Open a verified group chat, resolve a visible @ picker candidate, append the message body, and send within one prepared process.
scripts/navigate_chat_list.sh <offset>: Move the visible chat selection up or down with arrow keys.
scripts/focus_composer_and_set_value.sh "<message>": Focus the composer, clear the current draft, and write the exact text through the focused text area's AXValue.
scripts/mention_group_member_and_set_value.sh "<member_name>" "<message>": In a group chat, open the @ picker, OCR-click a visible member candidate, and append the body text through AXValue.
scripts/focus_composer_and_paste.sh "<message>": Backward-compatible wrapper that forwards to focus_composer_and_set_value.sh.
scripts/scroll_chat_history.sh [steps] [pixels] [x] [y]: Focus the chat body and scroll older history upward in pixel increments derived from the window size when omitted.
scripts/capture_chat_history_sequence.sh [max_pages] [out_dir]: Capture overlapping screenshots of older chat history into a temporary directory, defaulting to 100 pages per batch.
scripts/merge_ocr_pages.py <output.txt> <page-001.txt> [...]: Merge per-page OCR text files into a single overlap-deduplicated reference transcript.
scripts/send_current_draft.sh: Press Return in WeChat to send the currently visible draft.
scripts/cleanup_wechat_temp_screenshots.sh: Delete tracked WeChat screenshots from the temp directory after verification.
Troubleshooting
Read references/troubleshooting.md when:
osascript reports Accessibility or Automation permission failures
- WeChat opens but cannot be controlled
- typed Chinese text is altered by the IME
Command+V or the Edit menu paste does not land in the composer
- WeChat search opens
搜一搜 instead of the local chat
- a group chat cannot be found until local history is synced
- older history must be read by controlled scroll instead of
Page Up
- temporary screenshots need to be cleaned after sending
- window screenshots are blank or capture the desktop instead of WeChat