npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wabery/cli

v0.11.5

Published

Wabery CLI and MCP server for building WhatsApp, Instagram, and Messenger automations with Wabery.

Readme

Wabery CLI and MCP Server

Wabery is a developer platform for building messaging workflows on WhatsApp, Instagram, and Messenger. It provides APIs for channels, contacts, conversations, messages, WhatsApp Flows, hosted functions, automations, dispatches, submissions, registration intents, and signed webhooks, so you can build your own product experience instead of wiring together a closed inbox tool.

This package ships the wabery command and a local MCP server for AI coding agents such as Claude Code, Codex, and OpenCode.

Full documentation: https://www.wabery.com/docs

Setup

Install the CLI, then sign in through your browser:

npm i -g @wabery/cli
wabery login

No global install is required when the wabery runner package is available:

npx wabery login
npx wabery doctor

wabery login prints an authorization URL and tries to open it in your browser. Approve the request on app.wabery.com, choose the project the CLI should manage, copy the one-time API key the page shows you, and paste it back into the terminal. The key is scoped to that project and stored in your local CLI config file. No local callback server is required — if the browser does not open, just open the printed URL yourself. Use wabery login --no-open to skip the open attempt. You can still override credentials explicitly:

export WABERY_API_KEY="wab_live_..."
export WABERY_BASE_URL="https://api.wabery.com/v1"

Do not pass real keys to wabery mcp-config --api-key; generated MCP snippets use a placeholder so credentials can live in your CLI config, shell, MCP client secret storage, or another local secret manager. Public API keys support scoped permissions. Use the smallest scope set the integration needs.

Working across multiple projects

API keys are scoped to a single project, so the CLI stores one key per project and auto-selects the right one based on the repository you run from.

  • wabery login (run once per project) stores that project's key in the global config and, the first time, writes a committed, secret-free .wabery.json ({"project_id": "..."}) at the repo root.
  • When you cd into a repo, the CLI walks up from the current directory to find .wabery.json (falling back to a wabery.config.json's project_id) and uses the matching stored key automatically — no flags needed.
  • wabery use <project-id-or-name> binds the current repo to a different project you've logged into (rewrites .wabery.json).
  • wabery whoami shows the active key (masked), org, project, and how it was resolved.
  • wabery logout [--project <id|name>] removes one stored key; wabery logout --all clears them all.

.wabery.json is safe to commit — it never contains a key. A teammate who clones the repo runs wabery login once to add their own key for that project.

Resolution precedence for which key is used: --api-key / WABERY_API_KEY > the stored key for the project from --project-id > WABERY_PROJECT_ID > .wabery.json > (if exactly one project is stored) that one.

Print a ready-to-paste env block for a project (API key, base URL, project id, channel id, webhook URL, and the webhook signing secret):

wabery env --project-id "proj_id"

The signing secret is read from GET /projects/{id}, which reveals it for a single project (the list never does). If a project has no secret yet, set one from your own secret manager (bring-your-own) so both sides stay in sync:

# Set name / webhook URL / a known signing secret on an existing project
wabery projects update "proj_id" \
  --webhook-url "https://yourapp.com/webhooks/wabery" \
  --webhook-secret "$WABERY_WEBHOOK_SECRET"

# Or rotate to a fresh server-generated secret (returned once)
wabery projects rotate-secret "proj_id"

# Reveal a project (includes webhook_secret)
wabery projects get "proj_id"

The same secret signs both inbound webhooks and outbound WhatsApp Flow data-exchange calls.

First sandbox message

For a brand-new project, use the shared Wabery sandbox before connecting your own WhatsApp Business number:

  1. Run wabery login, choose the project, paste the one-time key back into the terminal, then run wabery doctor.
  2. In the dashboard, send the project's join code (wab-ABC123 style) to the Wabery sandbox number from your test phone. From the CLI, run wabery projects get "project_id" to read join_code and enrollment_channel.phone_number.
  3. Send one normal WhatsApp message to the same sandbox chat. The join-code message only connects the phone; the normal message creates the visible conversation.
  4. Inspect and reply:
wabery channels list
wabery projects get "project_id"
wabery conversations list
wabery conversations messages "conversation_id"
wabery messages send \
  --channel-id "channel_id" \
  --conversation-id "conversation_id" \
  --text "Thanks for your message"

To scaffold app files after login, run wabery init --name "My project". It writes .env.wabery, wabery.config.json, and wabery-webhook-handler.ts without overwriting existing files unless you pass --force.

Channel routing

Inbound on a dedicated channel is handled by its own routing_mode, which overrides the project's. wabery channels list shows a routing_warning when a channel would silently drop inbound (e.g. a FLOWS channel under an EXTERNAL, webhook-backed project). Point a number at your webhook:

wabery channels list
wabery channels get "channel_id"
wabery channels update "channel_id" --routing-mode EXTERNAL

You can also set the project-wide default (inherited by new channels) with wabery projects update "proj_id" --routing-mode EXTERNAL.

For local repository development, build the workspace package:

pnpm --filter @wabery/cli build

Run diagnostics and MCP config from the installed CLI:

wabery login
wabery doctor
wabery mcp-config claude --raw
wabery mcp-config codex --raw
wabery mcp-config opencode --raw
wabery mcp

MCP starts in read-only mode. To allow mutation tools for a trusted local session, set WABERY_MCP_MODE=write or generate config with --write. High-risk tools also require confirmation tokens:

  • wabery_apply_config: confirmation_token: "apply_config"
  • wabery_publish_flow: confirmation_token: "publish:<flow_id>"
  • wabery_unenroll_contact with erase=true: confirmation_token: "erase:<contact_id>"

Claude Code

Add a Wabery MCP server to .mcp.json:

wabery mcp-config claude --raw

Installed CLI:

{
	"mcpServers": {
		"wabery": {
			"command": "wabery",
			"args": ["mcp"],
			"env": {
				"WABERY_API_KEY": "wab_live_...",
				"WABERY_BASE_URL": "https://api.wabery.com/v1"
			}
		}
	}
}

Codex

Add a Wabery MCP server to Codex config.toml:

wabery mcp-config codex --raw

Installed CLI:

[mcp_servers.wabery]
command = "wabery"
args = ["mcp"]
env = { WABERY_API_KEY = "wab_live_...", WABERY_BASE_URL = "https://api.wabery.com/v1" }

OpenCode

Add a Wabery MCP server to OpenCode opencode.json:

wabery mcp-config opencode --raw

Installed CLI:

{
	"$schema": "https://opencode.ai/config.json",
	"mcp": {
		"wabery": {
			"type": "local",
			"command": ["wabery", "mcp"],
			"enabled": true,
			"environment": {
				"WABERY_API_KEY": "wab_live_...",
				"WABERY_BASE_URL": "https://api.wabery.com/v1"
			}
		}
	}
}

MCP Tools

  • wabery_check_connection
  • wabery_get_config_example
  • wabery_get_config_schema
  • wabery_export_config
  • wabery_validate_config
  • wabery_preview_config_diff
  • wabery_apply_config
  • wabery_list_business_agents
  • wabery_check_business_agent_eligibility
  • wabery_apply_business_agent
  • wabery_get_business_agent_settings
  • wabery_update_business_agent_settings
  • wabery_list_business_agent_connectors
  • wabery_create_business_agent_connector
  • wabery_create_business_agent_connector_tool
  • wabery_list_business_agent_connector_tools
  • wabery_get_business_agent_connector_tool
  • wabery_update_business_agent_connector_tool
  • wabery_delete_business_agent_connector_tool
  • wabery_business_agent_thread_control
  • wabery_test_business_agent
  • wabery_get_business_agent_eval
  • wabery_list_business_agent_skills
  • wabery_set_business_agent_skills
  • wabery_list_projects
  • wabery_get_project
  • wabery_update_project
  • wabery_rotate_webhook_secret
  • wabery_list_channels
  • wabery_get_channel
  • wabery_update_channel
  • wabery_create_registration_intent
  • wabery_get_registration_intent
  • wabery_get_limits
  • wabery_list_flows
  • wabery_get_flow
  • wabery_publish_flow
  • wabery_list_functions
  • wabery_get_function
  • wabery_create_function
  • wabery_deploy_function
  • wabery_test_function
  • wabery_update_function
  • wabery_invoke_function
  • wabery_tail_function_logs
  • wabery_delete_function
  • wabery_enroll_contact
  • wabery_list_contacts
  • wabery_get_contact
  • wabery_unenroll_contact
  • wabery_list_conversations
  • wabery_get_conversation
  • wabery_list_conversation_messages
  • wabery_send_message
  • wabery_get_message
  • wabery_list_templates
  • wabery_get_template
  • wabery_create_template
  • wabery_wait_template
  • wabery_send_flow
  • wabery_list_submissions
  • wabery_list_dispatches
  • wabery_get_dispatch

For agent workflows, inspect channel publish_readiness before template creation. Templates require a dedicated non-sandbox WhatsApp channel, Meta business verification, and a Meta payment method. Use preferred_language when enrolling contacts if later flow sends should resolve locale-specific config keys. After wabery_send_message, use wabery_get_message to inspect provider status and Meta failure details. Use wabery_get_template with refresh or wabery_wait_template to track approval.

Hosted function MCP tools let agents create project-scoped TypeScript handlers, deploy source, test them, expose them as tool-callable logic, invoke them with structured arguments, and read invocation logs. Write-mode function tools require confirmation tokens such as create_function, deploy_function, update_function, invoke_function, and delete_function.

Typical Developer Workflow

  1. List the project your key is scoped to:
wabery login
wabery doctor
wabery projects list
  1. Enroll a WhatsApp phone number and capture the returned contact_id and channel_id:
wabery contacts enroll \
	--project-id "proj_or_agent_id" \
	--phone "+15555550123" \
	--name "Jane Developer" \
	--preferred-language "es" \
	--reference-id "user_123"
wabery contacts get "contact_id"

# Update language / metadata later (metadata is shallow-merged):
wabery contacts update "contact_id" \
	--preferred-language "id" \
	--metadata '{"linked": true}'

For self-serve customer onboarding, mint a single-use WhatsApp registration link and poll it with the publishable key used by your widget:

wabery registration-intents create \
	--project-id "proj_or_agent_id" \
	--customer-reference "user_123" \
	--metadata '{"plan": "starter"}'
wabery registration-intents get "intent_id"
  1. Generate and validate wabery.config.json:
wabery config init wabery.config.json --project-id "proj_or_agent_id"
wabery config validate wabery.config.json
wabery config diff wabery.config.json
wabery config apply wabery.config.json

wabery.config.json can manage the project's webhook declaratively, so config apply keeps the webhook URL and signing secret in sync alongside flows:

{
  "project_id": "proj_or_agent_id",
  "webhook": {
    "url": "https://yourapp.com/webhooks/wabery",
    "signing_enabled": true,
    "secret": "whsec_your_own_secret_from_your_secret_manager"
  },
  "flows": []
}

Omit secret to have the server generate one; set signing_enabled: false to clear it. The webhook setup is scriptable after the project exists: create or select the project in the dashboard, create the project-scoped API key with wabery login, run config apply to point the webhook, and run wabery env to emit the secret for your deploy.

  1. Publish a flow explicitly after review. Without --wait, this queues the publish job and returns immediately; --wait polls until Meta reports it published, or fails with the rejection reason:
wabery flows list
wabery flows publish "flow_id" --wait          # default 300s; --wait=600 for longer
wabery flows status "flow_id"

--wait exits non-error while Meta is still publishing (publishing is async), and prints Meta's real reason + validation_errors on a terminal failure instead of a bare "Invalid parameter".

Inspect, debug, and clean up flows:

wabery flows get "flow_id"          # includes the compiled flow_json + field_schema
wabery flows events "flow_id"       # lifecycle log: publish/submission/reconcile detail
wabery flows reconcile "flow_id"    # re-sync status from Meta (re-adopts a drifted flow)
wabery flows delete "flow_id"       # draft → delete on Meta, published → deprecate
  1. Send a published flow:
wabery channels list
wabery flows send "flow_id" --channel-id "channel_id" --contact-id "contact_id"

For locale-aware sends, publish one flow per language sharing a config_key (omit locale for the default) and send by key — Wabery resolves the variant for the contact's preferred_language, falling back to the default:

wabery flows send --config-key "lead_intake" --locale "es" \
	--channel-id "channel_id" --contact-id "contact_id"

For sandbox projects, wabery contacts enroll returns the shared sandbox channel_id that is authorized for that phone number. Use that channel_id when sending a flow to the returned contact_id.

To remove a phone's sandbox authorization:

wabery contacts unenroll "contact_id"

wabery config apply intentionally does not publish flows to Meta. Agents should call wabery_publish_flow only after explicit user confirmation and with the required confirmation token.

Template review can take longer than flow publishing. Templates require a dedicated WhatsApp channel with a Meta-verified business account and a Meta payment method; the Wabery sandbox cannot submit your templates. Use wabery channels get <channel_id> to inspect publish_readiness, then wabery templates status <template_id> to force a live Meta status refresh. For scripts, wabery templates wait <template_id> [--wait=86400] blocks until approval or fails on rejection/timeout. You can also subscribe to the project's signed template.status webhook event.

After sending a message, inspect the latest provider status with:

wabery messages get "message_id"

Send richer WhatsApp payloads with first-class flags or --body:

wabery messages send --channel-id "channel_id" \
	--conversation-id "conversation_id" \
	--latitude 37.7749 --longitude -122.4194 --location-name "Office"

wabery messages send --channel-id "channel_id" \
	--conversation-id "conversation_id" \
	--sticker-link "https://example.com/sticker.webp"
  1. Deploy hosted logic for a workflow or Business Agent tool:
wabery functions create --slug quote-customer \
	--name "Quote customer" --trigger-type ANY_MESSAGE
wabery functions deploy "function_id" --source ./quote-customer.ts
wabery functions test "function_id" --text "I need a quote"
wabery functions update "function_id" \
	--expose-as-mcp-tool true \
	--input-schema '{"type":"object"}'
wabery functions invoke "function_id" --arguments '{"orderId":"ord_123"}'
wabery functions logs "function_id"

Use backing_type: "HOSTED_FUNCTION" and backing_function_id when a Meta Business Agent connector tool should call hosted logic instead of a Flow or external endpoint.

Local testing (no deploy, no API key)

Exercise your own webhook and data-exchange endpoints by signing a fake payload with your webhook secret and POSTing it to a local URL:

# Drive your webhook handler:
wabery webhooks send-test --url http://localhost:3000/webhooks \
  --secret "$WABERY_WEBHOOK_SECRET" --event flow.completed

# Drive a message.received webhook with an inbound media file payload:
wabery webhooks send-test --url http://localhost:3000/webhooks \
  --secret "$WABERY_WEBHOOK_SECRET" \
  --payload '{
    "event":"message.received",
    "payload":{
      "message_id":"msg_test",
      "from":"+15551234567",
      "text":null,
      "assets":[{
        "id":"asset_test",
        "message_id":"msg_test",
        "type":"image",
        "url":"https://storage.wabery.com/assets/inbound/asset_test?token=...",
        "expires_at":"2026-06-27T14:21:10.000Z",
        "mime_type":"image/jpeg",
        "file_name":"msg_test-image.jpg",
        "file_size":248913,
        "provider":"whatsapp",
        "provider_media_id":"wamid...",
        "status":"available"
      }]
    },
    "sentAt":"2026-06-20T14:21:10.000Z"
  }'

# Drive your data-exchange endpoint (one screen advance):
wabery flows test --url http://localhost:3000/flow \
  --secret "$WABERY_WEBHOOK_SECRET" \
  --action data_exchange --screen SELECT --data '{"workspace":"ws_1"}'

# Walk the whole journey, then fire flow.completed at your webhook:
wabery flows simulate --url http://localhost:3000/flow \
  --webhook-url http://localhost:3000/webhooks \
  --secret "$WABERY_WEBHOOK_SECRET" \
  --steps '[{"action":"INIT"},{"action":"data_exchange","screen":"SELECT","data":{"workspace":"ws_1"}}]' \
  --submission '{"workspace":"ws_1","project":"proj_a"}'

Both send a valid x-wabery-signature so you can verify it exactly as in production. The SDK's signPayload(body, secret) does the same for code-level fixtures.

Programmatic use

This package exposes two subpath entry points in addition to the wabery binary, both fully typed:

// A thin authenticated HTTP client for the Wabery REST API.
import { WaberyApiClient, WaberyApiError } from "@wabery/cli/api-client";

const client = new WaberyApiClient({ apiKey: process.env.WABERY_API_KEY });
const projects = await client.get("/projects");

// The MCP server, to embed in your own host.
import { createWaberyMcpServer, runMcpServer } from "@wabery/cli/mcp-server";

For building application features (sending messages, flows, webhooks, typed event parsing), prefer the @wabery/sdk package — it shares the same REST surface with richer types and helpers (constructEvent, verifyDataExchange, the flow builders). @wabery/cli is focused on local config management and MCP tooling for AI agents.