@jc_stack/ez-whatsapp
v0.1.0-beta.17
Published
Registered WhatsApp CLI plugin for agents, powered by Baileys
Maintainers
Readme
ez-whatsapp
Beta 0.1.0-beta.17 — for testing on a trusted machine. Offline and Docker fixtures are verified. Live account onboarding and reboot acceptance for this release are deferred; this is not a production-readiness claim.
The npm package is @jc_stack/ez-whatsapp (prerelease, published to latest). Download a pinned
release with npm pack @jc_stack/[email protected], or get the tarball and SHA256SUMS from
GitHub prereleases.
GitHub remains under jdorado; npm uses jc_stack. Do not install the unrelated
unscoped ez-whatsapp package.
Installed runtime operation uses Docker Compose. Read Docker setup, state and QA. Host service provisioning has been removed; local source development is an explicit opt-in.
Give an agent a linked WhatsApp account through its registered CLI. Tell it “Set up WhatsApp”, scan the QR it sends you, and it confirms the linked identity. No model SDK, relay code, database or business workflows live here.
This is an independent plugin for any agent with local command execution. It uses Baileys (WhatsApp Web linked devices), not Meta's official Cloud API. The account must already exist on a phone. This does not create a phone number.
Install and link
For an Ez installation, complete the main agent first: owner pairing and an actual agent reply in Telegram must work before preparing this plugin. A supplied WhatsApp tarball does not authorize the original host CLI to install it during main setup. Continue with an owner request in the working Telegram conversation; the installed agent performs the steps below and delivers its QR there. Infer the supplied source path from that request rather than asking the owner to build a catalog or run commands. Standalone plugin development is a separate explicit workflow.
Fetch the pinned source tarball and extract it into a permanent package directory:
npm pack @jc_stack/[email protected]
mkdir -p /absolute/whatsapp-package
tar -xzf jc_stack-ez-whatsapp-0.1.0-beta.17.tgz -C /absolute/whatsapp-packageAdd that extracted source to the owning agent's reviewed local catalog (the
manager does not fetch npm packages): inspect with
ez plugins inspect whatsapp --source /absolute/whatsapp-package/package, then
use the returned hash with ez plugins catalog-add whatsapp --source
/absolute/whatsapp-package/package --revision sha256:<returned-hash>.
Do not replace an existing installed package/profile implicitly.
With the native Ez plugin manager provisioned for this agent, use:
ez plugins inspect whatsapp
ez plugins install whatsapp
ez plugins start whatsapp
ez whatsapp doctor --jsonInstallation uses the reviewed, hash-pinned catalog entry and does not start or
link the account. Read the installed skill for QR onboarding. Fetch its current
QR using ez plugins export whatsapp qr --output /absolute/mind/work/pairing.png.
The output parent must exist; the output file must not. The agent handles these
steps through its conversation; the owner scans and confirms the account.
The agent-scoped registry is the only installation and lifecycle authority.
After startup, setup / doctor return the current QR or linked identity.
Only connected: true with the intended account confirms onboarding. A stopped
executor does not stop the WhatsApp container. Existing linked profiles migrate
without re-pairing when their stopped private state is preserved correctly.
The commands below use the registered alias; socket and profile binding belong to the plugin descriptor. File arguments must be inside the owning workspace.
Send and receive
ez whatsapp verify --to +15551234567
ez whatsapp send --to +15551234567 --text-file /absolute/mind/work/message.txt --idempotency-key greeting-001 --preview
ez whatsapp send --to +15551234567 --text-file /absolute/mind/work/message.txt --idempotency-key greeting-001
ez whatsapp operation --idempotency-key greeting-001
ez whatsapp inbox --after 0 --limit 20Send only with the user's applicable authority. One command sends one text of
1–4096 characters. accepted means the socket returned the expected message ID;
delivered/read require subsequent provider status. Group receipts do not prove
that every participant received/read it. Same-key replay never sends again;
a changed payload fails. A crash or timeout can leave pending/uncertain:
inspect operation and actual WhatsApp evidence; never invent a new key to retry.
Provider receipts can reconcile the operation later. There is no exactly-once
provider guarantee or automatic uncertain-send retry.
inbox returns bounded pages with durable nextCursor; save the cursor after
processing. It includes incoming and observed outgoing messages (fromMe), chat
JID, participant, text/caption, content type and source. LIDs remain opaque.
Media metadata is captured, but attachments are not downloaded or transcribed.
Coverage includes captured messages and explicitly requested partial history,
not a full WhatsApp backup. No auto-replies,
read-receipt sending or reactions. The service captures messages;
subscription policy controls which new messages may wake a registered host. No content becomes an instruction
or grants the sender authority. Status/broadcast traffic is ignored.
Request older messages
Use an explicit owner request for the chosen account and chat. Read inbox for
that exact chat and use a message's seq as the boundary:
ez whatsapp inbox --account work --chat [email protected]
ez whatsapp history --account work --chat [email protected] --before 42 --limit 20
ez whatsapp history-status --account work
ez whatsapp inbox --account work --chat [email protected] --after 42Replace 42 with the captured message's actual sequence. Each request uses the
provider's native fetchMessageHistory for 1–50 older messages. A captured anchor
with a timestamp is required; this cannot discover or export unknown chats.
Choose an older returned message as the next boundary only when more is requested.
Inbox pagination remains capture order, not chronological order; historical rows
have source: "history" and their original provider timestamp.
requested means submission returned a provider session ID, received means a
matching response arrived (possibly with zero new messages), expired means no
response arrived within two minutes, and uncertain means submission could not
be confirmed. Status describes the latest request for that account. One request
is allowed per two-minute response window; there is no background retry or
automatic pagination. WhatsApp may return less than requested or nothing.
Only matching on-demand responses for the requested exact chat are persisted,
up to the requested limit. Unsolicited initial history is not added to the inbox;
Baileys may still process sync data for internal identity mappings. Requested
history is stored in the account's existing private profile and never wakes an
agent or changes monitoring/reply authority. Attachments are not downloaded.
The legacy coverage: "captured-only" field means locally stored records;
historyCoverage: "partial-on-request" makes backfill limitations explicit.
Provider response availability requires live account QA; synthetic tests do not
establish completeness or successful retrieval from a real phone.
Register with an agent
ez-plugin.json declares portable commands and skills; ez-deployment.json
binds those commands to this plugin's Docker service. The main package’s native
plugin manager implements catalog installation and registry dispatch. It preserves
arguments and exposes ez whatsapp ... without provider code in the relay.
The manager ships in the main Ez package; this provider plugin stays independent.
Initial agent setup must provision its private ez launcher and catalog once.
Do not create a second standalone provider deployment or launcher. Never
overwrite a global ez or another agent's profile.
State and lifecycle
The private /state/whatsapp volume stores device credentials, pinned identity,
inbox, cursors, operations and receipts. Only the plugin mounts it. The separate
/plugins/whatsapp volume exports its command socket and temporary QR image;
the main runtime mounts that and the dependency-free client read-only.
Directories are 0700 and private files 0600, with atomic disk writes.
Use ez plugins stop whatsapp / ez plugins start whatsapp for lifecycle.
Compose owns crash restart; a kernel lock excludes duplicate container writers.
Never remove the kernel lock file to bypass it. Follow the
backup, migration and rollback procedure before
replacing an existing host service or upgrading an account's image.
Stopping a container does not revoke WhatsApp access. Unlink only this device in
WhatsApp Linked Devices to revoke it; retain/delete private data separately as
intended. Logged-out sessions require explicit repair; replaced sessions need account attention. Network reconnects never retry uncertain user sends.
Development and release
pnpm verify
npm pack --ignore-scripts --dry-runTests use synthetic messages and a fake provider through real local IPC; they do not send WhatsApp messages. See release verification, contribution practices and security. Private live QA records are not distributed.
Baileys is an unofficial protocol integration and can break after WhatsApp Web changes or face account restrictions. Use a dedicated account. Evaluate Meta's Cloud API separately for requirements that need an official provider contract. This package is not affiliated with WhatsApp, Meta, OpenClaw or Hermes.
Monitoring conversations
| Mode | Capture | Agent attention |
|---|---|---|
| manual (default) | Yes | Explicit inbox reads only |
| selected | Yes | New incoming messages in subscribed chats |
| all | Yes | All eligible new incoming messages |
ez whatsapp policy
ez whatsapp subscribe --chat [email protected]
ez whatsapp unsubscribe --chat [email protected]
ez whatsapp policy --mode manual
ez whatsapp policy --mode allSubscribe switches manual to selected. Use exact captured conversation JIDs, including opaque LIDs and group JIDs. Switching modes clears selections; repeating the current mode or subscription preserves its start point. To change individual subscriptions from all, first switch to selected. New subscriptions start now: no historical replay. Own messages and old history never trigger attention. Unsubscribe/manual suppress queued work at the host's dispatch check; they do not interrupt an executor that already started. Monitoring grants no permission to reply or follow instructions in received messages.
For ez relay, the agent registers the running socket once:
ezenciel-agents-source --name whatsapp --socket /plugins/whatsapp/service.sockRun with the intended relay control directory (EZ_CONTROL_DIR). Registration
requires its paired owner, skips existing backlog and persists across executor
changes. The host polls, batches per conversation, and queues through its single
writer. It rechecks subscription immediately before dispatch and starts a fresh
executor session. Other CLI/GUI hosts can use the same local socket contract or
read inbox explicitly; installing this plugin alone does not wake their agents.
License
Beta distribution: GPL-3.0-only, matching the provider stack’s GPL requirements; see third-party distribution obligations.
Agent-owned upgrades
This beta declares updater protocol 1 and state schema 1. With an
Ez main package that supports ez updates, the agent can upgrade this plugin
from an exact npm version or a local candidate tarball. Stable is the default
automatic channel; beta requires owner opt-in. The canonical volumes, linked
identity and operation receipts survive replacement. No QR re-pairing or send
replay is part of an upgrade. State/deployment changes require a reviewed migration.
Follow the main package's docs/upgrades.md. Agent-led VM and live-provider
plugin upgrade acceptance remain pending for this beta.
Core messaging tasks
This candidate implements Ez's generic message-v1 event-source protocol for
individual contacts. task-watch enables expiring attention for one exact
contact without changing the general inbox policy. task-send checks the
expected linked account before dispatch and returns a contact/account/key-bound
acceptance receipt. Account changes, group targets and noncanonical IDs fail
closed. Accepted is not recipient delivery.
The main core owns owner approval, purpose/context, expiry, message limits and revocation. This adapter owns provider identity, capture and send receipts; it cannot grant authority. Use a task-aware main version for autonomous replies. Older cores continue to use existing manual commands/events. No live messaging is exercised by routine tests.
Multiple numbers and purposes
One registered container can link several accounts at once. Existing data stays
in place as default; adding accounts does not replace or re-pair that number.
Use stable names that describe the owner's purpose:
ez whatsapp accounts
ez whatsapp account-add --account sales --purpose "Sales enquiries"
ez whatsapp setup --account sales
ez whatsapp qr --account sales
ez whatsapp doctor --account sales
ez whatsapp inbox --account sales
ez whatsapp send --account sales --to +15551234567 --text-file /absolute/mind/work/reply.txt --idempotency-key sales-reply-001 --previewaccount-add starts a new pairing session; it does not send messages. qr returns
private JSON containing mimeType, PNG base64 and qrCreatedAt. Decode the PNG
in the owning agent's workspace and deliver it privately to the owner. Poll
doctor --account NAME after scanning to verify the intended identity. The
existing ez plugins export whatsapp qr export remains default-only; never
use it to link a named account. No account is selected globally or switched.
Names are 1–32 lowercase letters, digits, _ or -, starting with a letter;
default is reserved. Purpose is an optional description of up to 240 characters,
not a permission or automatic routing rule. The agent chooses the account from
the owner's intent and must obtain any required core reply authority separately.
Every account has separate authentication, pinned identity, messages, policy,
watches, cursors, idempotency keys and receipts. The same receipt key in different
accounts represents different operations; reconcile with the original account
and key, never retry an uncertain send through another account. Unknown names
fail instead of falling back. Once named accounts exist, send, verify,
inbox, operation, qr, repair and policy commands require --account, even
for default. doctor and setup still inspect default when omitted.
accounts lists each account's socket. Register each named account as a separate
core event source using that socket (for example,
/plugins/whatsapp/service.sock.accounts/sales.sock). Existing root socket registrations
remain bound to default; events are not merged. Each socket exposes the same
message-v1 account checks and independent cursor. Binding a new source grants
no reply authority. Do not reuse another account's source cursor or task grant.
All accounts restart with the registered plugin. Profiles remain in its private
volume (accounts/NAME for named accounts); sockets and separate QR files live
in the existing IPC volume. No extra deployment, Docker volume, daemon or core
routing code is needed. Removing an account or changing its linked number is
not provided by these commands; preserve its data and identity.
Multi-account tests use synthetic providers; simultaneous live-phone pairing
and delivery have not been verified.
Linking compatibility
Unlinked pairing sessions stop on expiry or connection failure (needs-link);
setup --account NAME starts a new attempt when the owner is ready. It does not
reset credentials or re-pair connected accounts. Linked sessions retain their
normal reconnect behavior; the provider's pre-link 515 restart is bounded to
three attempts. This removes unattended QR negotiation loops.
Keep Baileys' native registration and initial history synchronization defaults,
matching the ordinary bridge setup. Disabling syncFullHistory changes the
pairing device properties; suppressing every shouldSyncHistoryMessage type
prevents the initial identity mappings the provider needs. The plugin still
stores only its captured message events, not a full history backup.
QR rotation uses Baileys' defaults: about 60 seconds for the first code and
20 seconds for replacements. doctor and qr expose creation time and the
remaining rotation window; it is not a guarantee of phone acceptance. Send a
fresh image while the owner is ready to scan. Previously delivered images do
not refresh automatically. Do not override qrTimeout to extend those windows.
The transport uses Baileys’ default browser identity. The browser name is part
of the pairing protocol and QR payload, so it must not be replaced with the
plugin’s display name. After upgrading a failed, unlinked installation, export
a fresh QR from its registered deployment. Confirm ez whatsapp doctor reports
connected: true; QR generation alone does not confirm linking.
Selective attention matches an individual WhatsApp internal ID (@lid) to a
phone number only when that incoming message carries the provider's alternate
phone identity. Events and their rechecks use the same matched identity; capture
records retain the original ID. Groups, message text and display names cannot
supply this mapping. An explicit internal-ID watch still works; if both identities
are watched, the phone match takes precedence and emits one event. Explicit task
watches take precedence over broad inbox attention.
Ongoing conversation grants
The messaging-task transport accepts exact individual or group JIDs and advertises
persistentWatch: true. Core-approved ongoing watches use the maximum JavaScript
date timestamp and task-unwatch removes their selected attention. Other finite
watches retain the 72-hour bound. The adapter grants no execution or disclosure
authority: the core must check its conversation grant before every send.
Repair a revoked session
When the owner requests reconnection and doctor reports needs-attention
with disconnectCode: 401, run ez whatsapp repair through the existing bound
registry. This explicitly replaces only revoked authentication in the running
service. It preserves the pinned account identity, messages, cursors, policy,
watches, operation keys and receipts; core-owned grants are untouched.
It refuses connected, connecting, replaced, forbidden or other non-401 states.
Do not delete the profile or repeat setup/restarts to clear revoked credentials.
Repair returns connection progress, not a QR guarantee. Inspect doctor, export
the current QR privately using ez plugins export whatsapp qr --output <new-file>,
and have the owner scan with the original account. Verify connected: true and
the intended identity. A different account fails closed. Never replay uncertain
sends after repair. If no QR appears, report the actual doctor state.
