terrylica/cc-skills/plugins/whatsapp-commander/skills/send-message/SKILL.md
send-message
user wants to send a WhatsApp message, share a link or document via WhatsApp, generate a wa.me click-to-chat link, or message a contact on WhatsApp by phone number.
- Source repository stars
- 61
- Declared platforms
- 0
- Static risk flags
- 3
- Last source update
- 2026-08-26
- Source checked
- 2026-08-28
Decision brief
What it does: where it fits
Reach a contact on WhatsApp. Unlike Telegram (tlg, full MTProto personal send) and Gmail (gmail-commander, OAuth API send), WhatsApp has no sanctioned personal-account send API — Meta only offers programmatic send through the Business Cloud API, and everything else (WhatsApp Web…
Not for
- Tasks that require unconfirmed production actions or broad system permissions.
- Environments where the pinned source and install steps cannot be inspected.
Compatibility matrix
Platform support, with evidence labels
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
Inspect first. Install second.
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/terrylica/cc-skills --skill "plugins/whatsapp-commander/skills/send-message"Inspect the Agent Skill "send-message" from https://github.com/terrylica/cc-skills/blob/05f53c5b24a445c1895e9b0590212e66cd70f39e/plugins/whatsapp-commander/skills/send-message/SKILL.md at commit 05f53c5b24a445c1895e9b0590212e66cd70f39e. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
What the source asks the agent to do
- 01
Usage: wa-cli.ts
The CLI is function/enum-driven Bun TypeScript — run it directly with bun (no build step). The link command writes the URL to stdout and its preflight/round-trip proof to stderr (e.g. ✓ link carries the full 439-char body), so URL=$(bun … link …) still captures a clean URL. It e…
The CLI is function/enum-driven Bun TypeScript — run it directly with bun (no build step). The link command writes the URL to stdout and its preflight/round-trip proof to stderr (e.g. ✓ link carries the full 439-char bo…bash /usr/bin/env bash << 'WAEOF' SCRIPT="$(cc-plugin-root whatsapp-commander)/scripts/wa-cli.ts" - 02
Capability Tiers (pick the lowest that works)
Default to Tier 1. Only use Tier 3 when the user has explicitly set up the Business Cloud API and the credentials resolve (see Preflight).
Default to Tier 1. Only use Tier 3 when the user has explicitly set up the Business Cloud API and the credentials resolve (see Preflight). - 03
Preflight
1. You need the recipient's phone number in international format (any punctuation is fine — the CLI strips non-digits). If you only have an email/username, ask for the number; WhatsApp is phone-number addressed. 2. WhatsApp registration is not verifiable from here — a wa.me link…
You need the recipient's phone number in international format (any punctuation isWhatsApp registration is not verifiable from here — a wa.me link still opens evenFor Tier 3 only, confirm credentials are present: - 04
Tier 1 — build a click-to-chat link (the recommended default; BMP-only body, round-trip verified)
bun "$SCRIPT" link "+1 (555) 123-4567" "Hi {{CONTACT}} — short note and a link: https://example.com/x"
bun "$SCRIPT" link "+1 (555) 123-4567" "Hi {{CONTACT}} — short note and a link: https://example.com/x" - 05
Tier 1 — body read from a file (multi-paragraph messages are awkward inline)
bun "$SCRIPT" link 15551234567 --file ./whatsapp-message.txt
bun "$SCRIPT" link 15551234567 --file ./whatsapp-message.txt
Permission review
Static risk signals and limitations
Network access
The documentation includes network, browsing, or remote request actions.
bun "$SCRIPT" link "+1 (555) 123-4567" "Hi {{CONTACT}} — short note and a link: https://example.com/x"Runs scripts
The documentation asks the agent to run terminal commands or scripts.
bun "$SCRIPT" link "+1 (555) 123-4567" "Hi {{CONTACT}} — short note and a link: https://example.com/x"Reads files
The documentation asks the agent to read local files, directories, or repositories.
# Tier 1 — body read from a file (multi-paragraph messages are awkward inline)Runs scripts
The documentation asks the agent to run terminal commands or scripts.
bun "$SCRIPT" link 15551234567 --file ./whatsapp-message.txtEvidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 92/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 61 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Provenance and original SKILL.md
- Repository
- terrylica/cc-skills
- Skill path
- plugins/whatsapp-commander/skills/send-message/SKILL.md
- Commit
- 05f53c5b24a445c1895e9b0590212e66cd70f39e
- License
- MIT
- Collected
- 2026-08-28
- Default branch
- main
View the original SKILL.md
Send WhatsApp Message
Reach a contact on WhatsApp. Unlike Telegram (tlg, full MTProto personal send) and
Gmail (gmail-commander, OAuth API send), WhatsApp has no sanctioned personal-account
send API — Meta only offers programmatic send through the Business Cloud API, and
everything else (WhatsApp Web automation, whatsmeow/Baileys/wppconnect libraries)
is unofficial and risks account bans. This skill therefore defaults to the safe,
zero-credential path and escalates only when the user has set up the Cloud API.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
Capability Tiers (pick the lowest that works)
| Tier | Method | Actually sends? | Credentials | When to use |
|---|---|---|---|---|
| 1 (default) | wa.me click-to-chat link | No — opens WhatsApp with text pre-filled; the human taps Send | None | Almost always. ToS-safe, instant, works for any number. |
| 2 | WhatsApp Web browser automation | Yes, but brittle | A logged-in WhatsApp Web session (QR) | Not implemented here — DOM-fragile and ToS-gray. Document, don't build, unless the user insists. |
| 3 | WhatsApp Business Cloud API | Yes, officially | WHATSAPP_TOKEN + WHATSAPP_PHONE_NUMBER_ID (1Password) | The user has a Meta Business app + registered number. See references/business-cloud-api.md. |
Default to Tier 1. Only use Tier 3 when the user has explicitly set up the Business Cloud API and the credentials resolve (see Preflight).
Preflight
- You need the recipient's phone number in international format (any punctuation is fine — the CLI strips non-digits). If you only have an email/username, ask for the number; WhatsApp is phone-number addressed.
- WhatsApp registration is not verifiable from here — a
wa.melink still opens even if the number is not on WhatsApp (it shows an "invalid number" notice on tap). State this assumption rather than claiming delivery. - For Tier 3 only, confirm credentials are present:
[ -n "${WHATSAPP_TOKEN:-}" ] && [ -n "${WHATSAPP_PHONE_NUMBER_ID:-}" ] && echo "cloud-api: READY" || echo "cloud-api: NOT CONFIGURED — use the link tier"
Usage: wa-cli.ts
The CLI is function/enum-driven Bun TypeScript — run it directly with bun (no build step).
The link command writes the URL to stdout and its preflight/round-trip proof to
stderr (e.g. ✓ link carries the full 439-char body), so URL=$(bun … link …) still
captures a clean URL. It exits 2 if the body has emoji/astral chars (they would tofu).
/usr/bin/env bash << 'WA_EOF'
SCRIPT="$(cc-plugin-root whatsapp-commander)/scripts/wa-cli.ts"
# Tier 1 — build a click-to-chat link (the recommended default; BMP-only body, round-trip verified)
bun "$SCRIPT" link "+1 (555) 123-4567" "Hi {{CONTACT}} — short note and a link: https://example.com/x"
# Tier 1 — body read from a file (multi-paragraph messages are awkward inline)
bun "$SCRIPT" link 15551234567 --file ./whatsapp-message.txt
# Tier 3 — actually send via the Business Cloud API (creds resolved into env first)
export WHATSAPP_TOKEN="$(op item get <ITEM> --vault 'Claude Automation' --fields token --reveal)"
export WHATSAPP_PHONE_NUMBER_ID="$(op item get <ITEM> --vault 'Claude Automation' --fields phone_number_id --reveal)"
bun "$SCRIPT" send 15551234567 "Your appointment is confirmed."
WA_EOF
What to hand back to the user
- Tier 1: present the
wa.meURL as a Markdown link and tell them tapping it opens the chat with the text pre-filled — the final Send tap is theirs (WhatsApp does not allow automating that tap). On desktop they need WhatsApp Web logged in; on phone it deep-links.- You cannot see the recipient's rendered message on this tier — there is no feedback
loop. So get it right in one shot: the CLI now runs a preflight (hard-fails on
emoji/astral chars, warns on lone list-number lines / trailing
——/ over-length) and a round-trip check, printing✓ link carries the full N-char bodyto stderr. Relay that N as proof of completeness rather than eyeballing. - The
api.whatsapp.com/sendgray box is a scrollable PREVIEW, not the message. It truncates visually (a long or list-heavy body "ends at 1"), which triggers false "it got cut off" reports. Tell the user to click "Continue to WhatsApp Web" / "Open app" — the full body drops into the real compose box. Don't assert this blind; the round-trip✓line is the actual evidence the link is complete.
- You cannot see the recipient's rendered message on this tier — there is no feedback
loop. So get it right in one shot: the CLI now runs a preflight (hard-fails on
emoji/astral chars, warns on lone list-number lines / trailing
- Tier 3: the API returns JSON with a
messages[0].id; report the message id as proof of submission (subject to the 24-hour window / template rules — see references).
Cloud API Window Rules (Tier 3)
The Business Cloud API does not let you send arbitrary text to anyone at any time:
- Inside the 24-hour customer-service window (the user messaged you in the last 24h):
free-form
type: "text"messages are allowed.wa-cli.ts sendsends exactly this. - Outside the window (first contact / re-engagement): you may send only
pre-approved message templates (
type: "template"), not free text. A free-text send outside the window returns error131047. For first contact, prefer Tier 1, or send an approved template via the raw Graph API (see references).
Message Body Encoding — keep wa.me bodies emoji-free (BMP only)
Do not put emoji or any other astral-plane (> U+FFFF) character in a Tier-1 wa.me link
body. They arrive on the recipient's device as � (U+FFFD replacement char / a
diamond-? tofu) — even though the source file and the percent-encoded URL are both
perfectly valid UTF-8. The wa.me → WhatsApp deep-link handoff mangles 4-byte codepoints
(surrogate pairs), while Basic-Multilingual-Plane text — including all CJK — survives
untouched. That asymmetry (Chinese renders fine, emoji become �) is the diagnostic tell:
it is NOT a source-encoding bug, so don't go re-saving the file as UTF-8 — the bytes are
already correct.
-
Verified 2026-07-04 (CPC ↔ {{CONTACT}} zh-Hans report): all Chinese rendered correctly but
👋 ✅ ❌and keycap sequences (1️⃣) showed as�in WhatsApp. Source bytes were valid (👋=f0 9f 91 8b; the URL held%F0%9F%91%8B). Removing the emoji fixed it entirely. -
Substitute BMP markers: headings
【…】, bullets-, numbering1)/2), quotes「…」, arrows—. All ≤ U+FFFF, render everywhere. -
Guard before building a link — fail if any codepoint is astral or an emoji selector:
python3 - "$MSG_FILE" <<'PY' import sys t = open(sys.argv[1], encoding="utf-8").read() bad = sorted({hex(ord(c)) for c in t if ord(c) > 0xFFFF or ord(c) in (0xFE0F, 0x20E3) or 0x1F000 <= ord(c) or 0x2600 <= ord(c) <= 0x27BF}) print("ASTRAL/EMOJI PRESENT — will tofu to ⬜/�:", bad) if bad else print("BMP-only: safe") PY
Tier 3 Cloud API send transmits real UTF-8 in the POST body and does not have this
problem — the mangling is specific to the Tier-1 click-to-chat deep link.
The
linkcommand now enforces the emoji check itself (hard error, exit 2) and prints a round-trip✓ link carries the full N-char bodyline — so you rarely need to run the guard above by hand, but it's kept for quick pre-checks on a draft file.
Keep Tier-1 bodies short and structurally flat
The landing-page preview is a fixed, scrollable box. Long or list-heavy bodies scroll out of view and look truncated, which reads as a bug to the recipient/operator even though the link carries everything. Two of the three round-trips in the CPC ↔ {{CONTACT}} case (2026-07-04) were this false alarm, not real defects. So, for Tier-1:
- Keep it concise (the CLI warns past ~700 chars). WhatsApp messages are read on phones; a tight 300–450-char note beats a wall of text.
- Inline the asks as
(1) … (2) …inside a paragraph — do NOT start a line with a bare1)/1.. A line that is just1is the classic "message got cut at 1" preview artifact. - Never end a line with
——flowing into a numbered item — it renders as a dangling-- 1. Use a colon lead-in (…问两件事:) instead. - The CLI's preflight warns on all three; treat the warnings as "fix before you hand it over".
Anti-Patterns (NEVER DO)
| Anti-Pattern | Why It Fails |
|---|---|
Claiming a wa.me link "sent" the message | It only pre-fills; the human taps Send. Say so. |
Using an unofficial library (Baileys, whatsmeow, wppconnect) to send as a personal account | Violates WhatsApp ToS; risks number ban. Not part of this skill. |
| Passing a number without a country code | wa.me needs full E.164; the CLI rejects <8 digits. |
| Free-text Cloud API send to a cold contact | Outside the 24h window only approved templates send (error 131047). |
| Hardcoding the Cloud API token in a script/commit | Resolve from 1Password into env at call time (mirrors gmail-commander/tlg). |
Emoji / astral-plane chars (👋✅❌, keycaps) in a wa.me link body | Arrive as � (U+FFFD) — the deep-link handoff drops 4-byte codepoints; BMP/CJK survive. Keep Tier-1 bodies BMP-only. |
Error Handling
| Symptom | Cause | Fix |
|---|---|---|
CLI: too few digits | Number missing country code | Pass full international format |
CLI: Cloud API send needs WHATSAPP_TOKEN… | Tier 3 creds absent | Use Tier 1 link, or resolve creds via 1Password |
API Cloud API HTTP 401 | Token expired/invalid | Rotate the token in Meta → update 1Password |
API error 131047 | Outside 24h window | Send an approved template, or use Tier 1 |
wa.me opens but shows "invalid number" | Recipient not on WhatsApp / wrong number | Confirm the number with the user |
Recipient sees � / diamond-? where emoji were | Astral-plane emoji lost in the wa.me→WhatsApp deep-link decode (source UTF-8 fine; CJK survives) | Strip emoji from the body; use BMP markers (【】, -, 1)). See "Message Body Encoding". |
| User reports the message "ends at 1" / looks cut off | The api.whatsapp.com/send gray box is a scrollable preview, not the message; long/list-y bodies scroll out of view | Not a bug — the round-trip ✓ carries N chars proves completeness. Tell them to click "Continue to WhatsApp Web"; keep bodies short & inline (1)(2). |
Evolution Log
-
2026-07-04 (later) — hardened the Tier-1 path against the two failure modes above. Added to
wa-cli.ts link: (1) an emoji/astral preflight (hard error, exit 2) so a tofu body is never emitted; (2) structural warnings for lone list-number lines / trailing——/ over-length that make the preview look truncated; (3) a round-trip check that decodes the built URL and asserts it equals the input, printing✓ link carries the full N-char body. Lesson baked in: on Tier-1 the agent has no view of the rendered result, so verify what's verifiable (round-trip) and never assert preview behavior blind. Cost that motivated this: one CPC ↔ {{CONTACT}} message took three user round-trips (emoji → dangling dash → false "truncated" preview) — all now caught before hand-off. -
2026-07-04 — emoji tofu on the Tier-1 link path. A zh-Hans report (CPC ↔ {{CONTACT}}) rendered all Chinese correctly but showed
👋 ✅ ❌/ keycaps as�in WhatsApp. Root cause: thewa.me→ WhatsApp deep-link handoff drops astral-plane (4-byte) codepoints while BMP text, including CJK, survives — the source file and percent-encoded URL were both valid UTF-8. Fix + guard documented in the new "Message Body Encoding" section: keep Tier-1 bodies BMP-only. -
2026-06-22 — initial skill. WhatsApp has no personal-account send API. Modelled on
tlg/gmail-commanderconventions but split into capability tiers because the platform forbids the equivalent of MTProto/OAuth personal send. Default =wa.melink (the path proven in the VanJobbers ↔ CPC engagement); Cloud API send wired but credential-gated. CLI written as function/enum-driven Bun TypeScript per the repo language preference.
Post-Execution Reflection
After this skill completes, check before closing:
- Did the command succeed? — If not, fix the instruction or error table that caused it.
- Did
wa-cli.tsflags or output change? — Update the Usage block to match. - Was a workaround needed? — Capture it here so the next run doesn't rediscover it.
- New convention learned about WhatsApp's limits? — Add it to the tier table / window rules.
Only update if the issue is real and reproducible — not speculative.
Frequently asked questions
What to verify before installation and use
What does the send-message source document cover?
Reach a contact on WhatsApp. Unlike Telegram (tlg, full MTProto personal send) and Gmail (gmail-commander, OAuth API send), WhatsApp has no sanctioned personal-account send API — Meta only offers programmatic send through the Business Cloud API, and everything else (WhatsApp Web…
How do I install send-message?
The source record exposes this install command: npx skills add https://github.com/terrylica/cc-skills --skill "plugins/whatsapp-commander/skills/send-message". Inspect the command and pinned source before running it.
Which permission-related actions were detected?
Static rules flagged network, exec-script, read-files in the source; the page lists the matching lines and excerpts.
Alternatives
Compare before choosing
coreyhaines31/marketingskills
ab-testing
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program
garrytan/gbrain
bulk-ingestion
End-to-end discipline for turning any large data source (audio libraries, email takeouts, document corpora, chat exports, API dumps) into brain pages at scale. The lifecycle spine: SCHEMA → ACCESS → TRIAL → EVALUATE → IMPROVE → CODIFY → TEST → SKILLIFY → BULK → MONITOR. State is tracked in a durable JSON manifest (see MANIFEST-PATTERN.md) so any crash, session boundary, or subagent fan-out resumes from ground truth instead of memory.
alirezarezvani/claude-skills
app-store-optimization
App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklist
dotnet/skills
migrate-vstest-to-mtp
Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP). Use when user asks to "migrate to MTP", "switch from VSTest", "enable Microsoft.Testing.Platform", "use MTP runner", set OutputType=Exe only for test projects in Directory.Build.props, or mentions EnableMSTestRunner, EnableNUnitRunner, or UseMicrosoftTestingPlatformRunner. USE FOR: MTP behavioral differences vs VSTest (exit code 8, zero tests discovered, --ignore-exit-code, TESTINGPLATFORM_EXITCODE_IGNORE); centralizing