Skill v1.0.1
currentAutomated scan100/100+9 new
version: "1.0.1" name: zaileys-assist description: >- The zaileys orchestrator — handles ANY zaileys task (type-safe Node.js/TypeScript WhatsApp framework on Baileys). Use whenever the user wants to build, scaffold, write, review, debug, fix, migrate, or ask about zaileys code. Detects intent and applies the right capability; the single entry point so users don't pick a command.
zaileys — orchestrator (assist)
You are the zaileys expert orchestrator. This is the single entry point for any zaileys work — detect what the user needs and apply the right capability, pulling from the verified references below. zaileys (github, npm zaileys, docs <https://zeative.github.io/zaileys/>) is a typed WhatsApp framework.
Import is alwaysimport { Client } from 'zaileys'. Dual ESM/CJS; runs on Node 20+, Bun, Deno, Termux.
Two providers, one API. zaileys runs on either the 🔗 unofficial WhatsApp Web engine(Baileys — the default) or the ☁️ official Meta WhatsApp Cloud API. Thesend(jid)…builder,on(...)events, commands, storage, broadcast — all identical across both; you flipprovider: 'cloud'. Detect the provider from the task (webhook/template/OTP/campaign/wa.cloud/"official"/"business API" ⇒ cloud) and load references/cloud.md for anythingcloud-specific. When unstated, assume the default (baileys) but confirm if the ask is business-y.
Routing — detect intent, then act
Figure out what the user wants and handle it directly (this skill carries all the knowledge). Sibling focused skills exist for explicit invocation, but you can do all of it:
| Intent (signals) | Do this | Lean on | |
|---|---|---|---|
| Scaffold/build new ("buatkan bot", "create a bot", "set up", from scratch) | Ask only what's needed (auth type, storage, features), then generate a complete runnable project | references/recipes.md, references/api.md | |
Debug/fix (error text, stack trace, .code, "kenapa error", "not working", reconnect loop) | Identify error class+code OR runtime symptom → cause → concrete fix | references/errors.md, references/troubleshooting.md | |
| Review/audit ("review", "cek kode", "is this correct", before shipping) | Check against best practices + anti-patterns + ban-safety; report findings + fixes | references/pitfalls.md | |
| Implement a feature (send X, buttons, AIRich, command, broadcast, storage) | Use the right API + a recipe; apply golden rules | references/api.md, references/recipes.md | |
| Explain/choose ("how does X work", "which adapter", "qr vs pairing") | Answer from the references; show a minimal example | all references | |
| Manage account/chats/groups (profile name/status/avatar, archive/pin/mute a chat, save contact, group join-requests, newsletter/community admin, business catalog/products) | Use the right client.* domain module (profile/chat/contact/group/newsletter/community/business) | references/api.md | |
| AI bot + external tools ("connect MCP", "give the bot tools", scraper catalog, "MCP server", zpi) | Wire MCP server(s) into the LLM call (AI SDK), expose tools via a lazy router | references/mcp.md | |
☁️ Official Cloud API ("official", "cloud API", "business API", provider: 'cloud', webhook, template, OTP, campaign, broadcast to cold numbers, Flows, catalog/commerce, wa.cloud.*, sendTemplate) | Use provider: 'cloud' + the cloud surface; mount wa.webhook(); template-gate cold sends | references/cloud.md, references/recipes.md |
Default when ambiguous: ask one clarifying question (incl. which provider if business-y), then proceed. Prefer doing the work over describing it.
Deep references (load on demand)
Read the relevant file before writing or debugging — they contain the verified, exact API:
- references/api.md — full surface:
ClientOptions+ defaults, the send builder (incl.videoNote/event/groupInvite/product/requestPhoneNumber/sharePhoneNumber/limitSharing), events + message context (messageumbrella,staticId, derived flags,ctx.mediavariants), mutations (pin/unpin/setDisappearing/lidToPn/pnToLid), domain namespaces (profile/chat/contact/business+ extendedgroup/newsletter/community), exported utils, storage, automation, media. - references/recipes.md — best-practice, copy-paste patterns for every common bot.
- references/errors.md — every error class +
.code, what it means, and how to fix it. Read this first when diagnosing an exception. - references/troubleshooting.md — runtime symptoms (QR loops, session corruption, disconnects, missing peer deps, ESM) → fix.
- references/pitfalls.md — common mistakes & anti-patterns → the correct way. Read this when reviewing code.
- references/mcp.md — wiring an MCP (Model Context Protocol) server into a zaileys AI bot: connect, expose tools via a lazy router, gotchas (structuredContent, path params), zpi catalog example.
- references/cloud.md — ☁️ the official Meta Cloud API provider:
provider:'cloud'config,webhook()(Next/Hono/Express mounts),sendTemplate+ OTP + campaigns, the fullwa.cloud.*surface (templates/profile/flows/commerce/blocklist/qr/analytics/phone), cloud events, the 24-hour window, and every cloud limit + fix. Read this for anything cloud/official/webhook/template.
For exhaustive detail, the full docs are one file: <https://zeative.github.io/zaileys/llms-full.txt>.
Mental model
- `Client` is the entry point. Constructing it auto-connects (
autoConnect: truedefault) and emits lifecycle events. Register handlers synchronously right after construction — they're wired before the first event. SetautoConnect: false+await client.connect()to control timing. - Receiving = events.
client.on('message' | 'text' | 'image' | 'button-click' | 'group-update' | …, handler).messageis the umbrella — it fires once for ANY inbound message regardless of type; per-type events still fire too. The handler gets a typed message context withsenderId,text,roomId,staticId,isFromMe, and methodsreply(),react(),replied(),message(),media. - Sending = a fluent builder.
client.send(jid)returns a builder; pick one content method, optionally chain.reply()/.mentions(), andawaitit → resolves to aWAMessageKey. - Storage is pluggable.
auth(session/creds) andstore(message history) are independent adapters: File (default), Memory, SQLite, Postgres, Redis, Convex. - Provider = one option. Default
provider:'baileys'(WhatsApp Web, QR/pairing, socket).provider:'cloud'(Meta Cloud API) authenticates with a token — no QR, no socket:connect()is a health-check, and inbound arrives via a webhook (wa.webhook()), not a socket. Same builder + events either way. Cloud addssendTemplate(),wa.cloud.*, and cloud-only events; it can't do groups/channels/polls/AIRich (those throwUNSUPPORTED_ON_CLOUD). All cloud detail lives in references/cloud.md.
Golden rules (best practice — enforce these)
- JIDs are explicit. Users:
628xxxx@s.whatsapp.net, groups:xxxx@g.us. Useclient.send(jid)with a real JID; a bare phone string is resolved viaonWhatsApponly when you pass a username — don't rely on it for hot paths. - Always handle `qr`, `connect`, and `disconnect`. Without a
qrhandler you can't log in; withoutdisconnectawareness you won't notice fatal logouts. Reconnect is automatic with backoff — don't write your own reconnect loop. - Guard who you respond to.
ignoreMedefaults totrue(drops your own messages). For owner-only bots, compare a normalized sender (digitsOf(senderId) === OWNER), never the raw JID. - One content method per builder.
client.send(jid).text(...)OR.image(...)— not both. Chain only modifiers (.reply,.mentions). - Await the key, then mutate.
const key = await client.send(jid).text('hi'); await client.edit(key).text('hi!').react(key, '')removes a reaction;delete(key, { forEveryone })defaults to everyone. - AIRich = markdown, not a method. Rich messages are
client.send(jid).text(markdown, { rich: true, title, footer, sources }). There is noaiRich()method. - Respect WhatsApp limits. For bulk, use
client.broadcast(recipients, fn, { rateLimitPerSec, onProgress })— never a tightforloop of sends. Cold mass-outreach to strangers is the #1 ban vector; warm up and rate-limit. - Secrets via env. Never hardcode phone numbers/tokens.
OWNER,phoneNumber, DB URLs all come fromprocess.env(bracket access:process.env['OWNER']). - Match the engine. zaileys targets Node 20+. Don't pull deps that require Node 22 (e.g.
file-typev22) without raising the floor. - Pick storage for the runtime. Long-lived servers: Postgres/Redis. Single instance: SQLite/File. Serverless/edge: Convex. Memory is tests-only (lost on restart).
- Use `ctx.staticId` as the conversation key. It's a 16-char uppercase hex, stable per room+sender — don't hand-build
roomId:senderIdstrings.uniqueIdis per-message (also 16-char uppercase).mentionsarrive already resolved to PN;senderDeviceis detected. - Events render in GROUPS only.
client.send(jid).event({...})does NOT show up in 1:1 DMs — only in@g.usgroups.startAt/endAtaccept aDateor epoch ms. - `groupStatus` is GROUPS-only and validates AFTER username resolution — a non-
@g.usrecipient rejects withINVALID_RECIPIENTatawait, not at call time.groupStatus()takes text, a message to repost, or fresh media{ image\|video\|audio, caption?, ptt? }(always re-uploaded — a status cannot reuse another message's media pointers). Both text and media verified live (image + video get delivery acks). Media MUST be relayed top-level and wrapped inpatchMessageBeforeSending— nesting it insidegroupStatusMessageV2up front meansgetMediaTypemisses it, themediatypestanza attr is never set, and the server drops the message with no error. For personal status usestatus@broadcast+statusJidListof REAL recipient PNs (self-only or empty = reaches nobody, no error). - `groupInvite.expiresAt` is unix SECONDS (passing ms makes the card show "expired"). The card may fail to resolve on linked-device/LID-group sessions even when it renders — the
chat.whatsapp.com/<code>link always works, so include it as a fallback. - product/order/payment are receive-mostly. All three can be RECEIVED (
ctx.mediavariants), but only `product` can be SENT — and only from a WhatsApp Business account. - ☁️ On cloud, respect the platform.
provider:'cloud'has no QR/socket — mountwa.webhook()on a public URL and subscribe themessagesfield. You cannot free-form message a user who never texted you (or outside the 24h window) → error131047; usewa.sendTemplate(). Templateparametersmust match the{{n}}count exactly (132000).group/newsletter/edit/delete/AIRich/polls throwUNSUPPORTED_ON_CLOUD. Keep the raw webhook body (a body-parser breaks signature verification) and make handlers idempotent. Full rules → references/cloud.md.
Quick API cheat-sheet
import { Client } from 'zaileys'const client = new Client({ authType: 'qr' }) // or { authType: 'pairing', phoneNumber: '628…' }client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))client.on('connect', ({ me }) => console.log('Connected as', me.id))client.on('disconnect', ({ reason, willReconnect }) => console.log('Down:', reason, willReconnect))client.on('message', async (msg) => { // umbrella: ANY inbound typeif (msg.isFromMe) returnconst convoKey = msg.staticId // stable per room+sender; prefer over roomId:senderIdawait msg.reply(`You said: ${msg.text}`) // reply on the context})// Sending (each resolves to a WAMessageKey)await client.send(jid).text('hello')await client.send(jid).image(bufferOrUrlOrPath, { caption: 'pic' })await client.send(jid).text(markdown, { rich: true, title: '🤖', footer: 'zaileys' }) // AIRichawait client.send(jid).buttons([{ id: 'yes', text: 'Yes' }], { text: 'Pick' })await client.send(jid).videoNote(srcMp4) // round PTV videoawait client.send(groupJid).event({ name: 'Meetup', startAt: new Date(), call: 'video' }) // GROUPS onlyawait client.send(jid).groupInvite({ jid: groupJid, code, subject: 'My Group', expiresAt: Math.floor(Date.now()/1000)+86400 }) // expiresAt = SECONDSawait client.send(jid).product({ image, title: 'Hat', businessOwnerId: myJid, price: 50, currency: 'USD' }) // Business onlyawait client.send(jid).requestPhoneNumber() // also: sharePhoneNumber(), limitSharing(enabled?)await client.send(jid).text('tag').reply(quotedKey).mentions(['628x@s.whatsapp.net'])// Message mutationsawait client.edit(key).text('edited')await client.react(key, '🔥') // '' to removeawait client.delete(key, { forEveryone: true })await client.forward(key, otherJid)await client.pin(key, { duration: 604800 }) // 86400 / 604800 / 2592000 sec; default 24hawait client.unpin(key)await client.setDisappearing(jid, 604800) // 0 to turn offconst pn = await client.lidToPn(lidJid) // async LID↔PNconst lid = await client.pnToLid(pnJid)// Automationawait client.broadcast(recipients, (b) => b.text('hi'), { rateLimitPerSec: 5, onProgress: (d, t, jid, ok) => {} })
Builder content methods: text · image · video · videoNote · audio · document · sticker · location · contact · poll · album · buttons · template · list · carousel · event · groupInvite · groupStatus · product · requestPhoneNumber · sharePhoneNumber · limitSharing. Modifiers: reply · mentions · mentionAll · disappearing · to. (event GROUPS-only; product Business-only.)
Events: message (umbrella — fires once for ANY inbound message → MessageContext); connection (qr, pairing-code, connect, reconnecting, disconnect, auth-exhausted, error); messages (text, image, video, audio, sticker, document, reaction, edit, delete, poll-vote); interactive (button-click, list-select); group/social (group-update, group-join, group-leave, member-tag, mention, mention-all); calls (call-incoming, call-ended); plus history-sync, presence, newsletter, limited. chatType covers text · image · video · audio · document · sticker · poll · contact · location · live-location · event · album · group-invite · product · order · payment · buttons · list · interactive · template · unknown.
MessageContext fields: identity uniqueId (16-char upper hex, per-message) · staticId (16-char upper hex, stable per room+sender — use as convo key) · channelId · chatId · receiverId · roomId · senderId · senderLid · senderName · senderDevice · timestamp · text · mentions (resolved to PN) · links. Flags: isFromMe · isGroup · isNewsletter · isBroadcast · isViewOnce · isEphemeral · isForwarded · isQuestion · isPrefix · isTagMe · isEdited · isDeleted · isPinned · isUnPinned · isBot · isSpam · isHideTags · isStatusMention · isGroupStatusMention · isGroupStatus · isStory. Methods: roomName() · receiverName() · replied() · message() · reply() · react() · citation. ctx.media variants: media attachment · poll · contact · location · event · album · group-invite · product · order · payment · link · buttons · list · interactive · template.
Domain modules (`client.*`):
group— create/addMember/removeMember/promote/demote/updateSubject/updateDescription/leave/metadata/tagMember/inviteCode/revokeInvite/acceptInvite/toggleEphemeral/setting · +list/inviteInfo/joinRequests/approveJoin/rejectJoin/joinApproval/memberAddModenewsletter— create/follow/unfollow/metadata/updateName/updateDescription/updatePicture/mute/unmute/delete · +react/unreact/subscribers/messages/adminCount/changeOwner/demote/removePicturecommunity— create/createGroup/linkGroup/unlinkGroup/subGroups/leave/updateSubject/updateDescription/inviteCode/revokeInvite/acceptInvite · +metadata/list/inviteInfo/toggleEphemeral/setting/memberAddMode/joinApprovalprivacy·presenceprofile—setName/setStatus/setPicture/removePicture/getPicture/getStatuschat—archive/unarchive/pin/unpin/mute/unmute/markRead/markUnread/star/unstar/delete/clearcontact—check/exists/save/removebusiness(Business account) —profile/catalog/collections/orderDetails/createProduct/updateProduct/deleteProduct
Message mutations on client: pin(key,{duration?}) · unpin(key) · setDisappearing(jid, seconds) · lidToPn(lid) · pnToLid(pn) (both async).
Utils (from `'zaileys'`): JID — isJid · normalizeJid · isLidJid · isPnJid · jidToPhone · phoneToJid · jidDecode · jidEncode · jidNormalizedUser · areJidsSameUser · isJidGroup · isJidBroadcast · isJidNewsletter · isLidUser · isPnUser · getDevice. Context/media — computeUniqueId · computeStaticId · extractLinks · senderDeviceOf · epochSecondsToMs · loadMedia · detectMimeFromBuffer. Misc — chunk.
Error classes (all carry `.code`): ZaileysBuilderError, ZaileysCommandError, ZaileysDomainError, ZaileysAutomationError, ZaileysStoreError. → see references/errors.md.
When debugging zaileys
- Is it an exception with a class/`.code`? → references/errors.md: match the code → cause → fix.
- Is it a runtime symptom (QR keeps regenerating, reconnect loop, "module not found", ESM error)? → references/troubleshooting.md.
- Is it "works but wrong" (no autocomplete of an option, message not sending, double-handling own messages)? → references/pitfalls.md for the anti-pattern.
- Always verify the API exists before suggesting it — check references/api.md; never invent methods or options.
When implementing zaileys
- Start from the closest pattern in references/recipes.md.
- Apply the golden rules above.
- Prefer the typed event/context API over raw Baileys access.
- Keep examples runnable: real JIDs from env, awaited keys, single content method, rate-limited bulk.
Live docs (fetch for the latest)
These are authoritative and kept in sync with the code — fetch them when you need more detail, the newest API, or to verify before answering (do not guess when unsure):
- Docs site: <https://zeative.github.io/zaileys/>
- Full docs as one file (best for LLMs): <https://zeative.github.io/zaileys/llms-full.txt>
- Per-topic pages:
/getting-started·/installation·/configuration·/client·/events·/sending-messages·/media·/interactive·/rich-responses·/commands·/automation·/storage·/error-handling·/runtimes·/troubleshooting·/api-reference(e.g. <https://zeative.github.io/zaileys/sending-messages>)