Small TypeScript CLI to send, receive, auto-reply, and inspect WhatsApp messages via Twilio. Works in polling mode or webhook mode (with Tailscale Funnel helper).
You can also talk to WhatsApp directly with a personal WhatsApp Web session (QR login) via `--provider web`—no Twilio needed for send/receive in that mode.
## What it can do
- Send and track delivery for WhatsApp messages over Twilio.
- Auto-reply via webhook or polling, with Claude-backed command replies or simple text templates.
- Run entirely on your personal WhatsApp Web session (`--provider web`) for direct messaging without Twilio.
- One-shot `up` command to launch webhook server, publish via Tailscale Funnel, and point Twilio callbacks automatically.
2) Configure `.env` (see `.env.example`): set `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` (or `TWILIO_API_KEY`/`TWILIO_API_SECRET`), and `TWILIO_WHATSAPP_FROM=whatsapp:+15551234567`. Optional: `TWILIO_SENDER_SID` if you don’t want auto-discovery.
Send direct via personal WhatsApp: `pnpm warelay web:login` (scan QR once) then `pnpm warelay send --provider web --to +12345550000 --message "Hi from warelay"`
- **Polling (`relay --provider twilio`)**: Periodically fetch inbound messages to your WhatsApp number. Easiest to start; no ingress needed. Auto-replies still run.
- **Webhook (`webhook` / `up`)**: Push delivery from Twilio. `webhook` runs the server locally; `up` also enables Tailscale Funnel and points the Twilio sender/webhook to your public Funnel URL (with fallbacks to phone number and messaging service).
- **Web (`--provider web`)** — direct messaging through your personal WhatsApp account (no Twilio). Supports outbound sends and inbound auto-replies when you run `pnpm warelay relay --provider web`. No delivery-status polling for web sends (WhatsApp Web doesn’t expose it). Setup: `pnpm warelay web:login` (alias: `pnpm warelay login`) then either send with `--provider web` or keep `relay --provider web` running. Session data lives in `~/.warelay/credentials.json`; if logged out, rerun `web:login`/`login`. Use at your own risk (personal-account automation can be rate-limited or logged out by WhatsApp).
bodyPrefix: "You are a helpful assistant running on the user's Mac. User writes messages via WhatsApp and you respond. You want to be concise in your responses, at most 1000 characters.\n\n",
- When `command[0]` is `claude`, set `claudeOutputFormat` to `"text"`, `"json"`, or `"stream-json"` and warelay will inject `--output-format` and `-p/--print` automatically.
- For `"json"`/`"stream-json"`, warelay parses Claude's JSON payload and sends just the text content to WhatsApp while keeping the full JSON in logs for debugging.
- The config loader validates `warelay.json` (mode/text/command/claudeOutputFormat/session shape) and logs warnings for invalid combos instead of failing later at runtime.
### Running without Twilio (personal WhatsApp Web)
- Log in once: `pnpm warelay web:login` (or `pnpm warelay login`), scan the QR in your terminal/browser. Credentials are stored locally at `~/.warelay/credentials.json`.
- Auto-reply loop: `pnpm warelay relay --provider web --interval 5 --lookback 10`. Typing indicators are skipped in this mode, but text replies still work.
- You can mix modes: use Twilio for reliable delivery/status, switch to web for quick personal sends. Each command decides the provider independently.
- Templates support `{{Body}}`, `{{BodyStripped}}`, `{{From}}`, `{{To}}`, `{{MessageSid}}`, plus `{{SessionId}}`/`{{IsNewSession}}` when session reuse is enabled.
-`/new` (or any `resetTriggers` value) resets the session. `/new ask…` resets and sends `ask…` as the prompt (via `BodyStripped`).
- Auto-reply send failures now print in red with Twilio code/status and the response body (e.g., policy violation 63112). Watch terminal output when running `relay`, `webhook`, or `up`.
| `inbound.reply.claudeOutputFormat` | `"text" \| "json" \| "stream-json"` | — | When `command[0]` is `claude`, force this output format and auto-add `-p/--print` so Claude exits after emitting output. |