diff --git a/CHANGELOG.md b/CHANGELOG.md index aa33b3dc7..23e462532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 0.1.0 — 2025-11-25 ### CLI & Providers -- Bundles a single `warelay` CLI with commands for `send`, `relay`, `status`, `webhook`, `up`, `web:login`/`login`, and tmux helpers `relay:tmux` / `relay:tmux:attach` (see `src/cli/program.ts`). +- Bundles a single `warelay` CLI with commands for `send`, `relay`, `status`, `webhook`, `up`, `login`, and tmux helpers `relay:tmux` / `relay:tmux:attach` (see `src/cli/program.ts`). - Supports two messaging backends: **Twilio** (default) and **personal WhatsApp Web**; `relay --provider auto` selects Web when a cached login exists, otherwise falls back to Twilio polling (`provider-web.ts`, `cli/program.ts`). - `send` can target either provider, optionally wait for delivery status (Twilio only), output JSON, dry-run payloads, and attach media (`commands/send.ts`). - `status` merges inbound + outbound Twilio traffic with formatted lines or JSON output (`commands/status.ts`, `twilio/messages.ts`). diff --git a/README.md b/README.md index c6ac70967..35d52b208 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ Send, receive, auto-reply, and inspect WhatsApp messages over **Twilio** or your Install from npm (global): `npm install -g warelay` (Node 22+). Then choose **one** path: **A) Personal WhatsApp Web (preferred: no Twilio creds, fastest setup)** -1. Link your account: `warelay web:login` (scan the QR). -2. Send a message: `warelay send --provider web --to +12345550000 --message "Hi from warelay"`. -3. Stay online & auto-reply: `warelay relay --provider web --verbose`. +1. Link your account: `warelay login` (scan the QR). +2. Send a message: `warelay send --to +12345550000 --message "Hi from warelay"` (add `--provider web` if you want to force the web session). +3. Stay online & auto-reply: `warelay relay --verbose` (defaults to Web when logged in, falls back to Twilio otherwise). **B) Twilio WhatsApp number (for delivery status + webhooks)** 1. Copy `.env.example` → `.env`; set `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` **or** `TWILIO_API_KEY`/`TWILIO_API_SECRET`, and `TWILIO_WHATSAPP_FROM=whatsapp:+15551234567` (optional `TWILIO_SENDER_SID`). @@ -35,7 +35,7 @@ Install from npm (global): `npm install -g warelay` (Node 22+). Then choose **on | `warelay status` | Show recent sent/received messages | `--limit ` `--lookback ` `--json` | | `warelay webhook` | Run local inbound webhook server | `--port ` `--path ` `--reply ` `--verbose` `--yes` `--dry-run` | | `warelay up` | Turn on webhook + Tailscale Funnel + Twilio callback | `--port ` `--path ` `--verbose` `--yes` `--dry-run` | -| `warelay web:login` (`login`) | Link personal WhatsApp Web via QR | `--verbose` | +| `warelay login` | Link personal WhatsApp Web via QR | `--verbose` | ### Sending images - Twilio: `warelay send --to +1... --message "Hi" --media ./pic.jpg --serve-media` (needs `warelay webhook`/`up` or `--serve-media` to auto-host via Funnel; max 5 MB). @@ -44,7 +44,7 @@ Install from npm (global): `npm install -g warelay` (Node 22+). Then choose **on ## Providers - **Twilio (default):** needs `.env` creds + WhatsApp-enabled number; supports delivery tracking, polling, webhooks, and auto-reply typing indicators. -- **Web (`--provider web`):** uses your personal WhatsApp via Baileys; supports send/receive + auto-reply, but no delivery-status wait; cache lives in `~/.warelay/credentials/` (rerun `web:login` if logged out). +- **Web (`--provider web`):** uses your personal WhatsApp via Baileys; supports send/receive + auto-reply, but no delivery-status wait; cache lives in `~/.warelay/credentials/` (rerun `login` if logged out). - **Auto-select (`relay` only):** `--provider auto` uses Web when logged in, otherwise Twilio polling. Best practice: use a dedicated WhatsApp account (separate SIM/eSIM or business account) for automation instead of your primary personal account to avoid unexpected logouts or rate limits. @@ -117,13 +117,13 @@ Templating tokens: `{{Body}}`, `{{BodyStripped}}`, `{{From}}`, `{{To}}`, `{{Mess ## Troubleshooting Tips - Send/receive issues: run `pnpm warelay status --limit 20 --lookback 240 --json` to inspect recent traffic. - Auto-reply not firing: ensure sender is in `allowFrom` (or unset), and confirm `.env` + `warelay.json` are loaded (reload shell after edits). -- Web provider dropped: rerun `pnpm warelay web:login`; credentials live in `~/.warelay/credentials/`. +- Web provider dropped: rerun `pnpm warelay login`; credentials live in `~/.warelay/credentials/`. - Tailscale Funnel errors: update tailscale/tailscaled; check admin console that Funnel is enabled for this device. ## FAQ & Safety (quick answers) - Twilio errors: **63016 “permission to send an SMS has not been enabled”** → ensure your number is WhatsApp-enabled; **63007 template not approved** → send a free-form session message within 24h or use an approved template; **63112 policy violation** → adjust content, shorten to <1600 chars, avoid links that trigger spam filters. Re-run `pnpm warelay status` to see the exact Twilio response body. - Does this store my messages? Warelay only writes `~/.warelay/warelay.json` (config), `~/.warelay/credentials/` (WhatsApp Web auth), and `~/.warelay/sessions.json` (session IDs + timestamps). It does **not** persist message bodies beyond the session store. Logs print to stdout/stderr; redirect or rotate if needed. -- Personal WhatsApp safety: Automation on personal accounts can be rate-limited or logged out by WhatsApp. Use `--provider web` sparingly, keep messages human-like, and re-run `web:login` if the session is dropped. +- Personal WhatsApp safety: Automation on personal accounts can be rate-limited or logged out by WhatsApp. Use `--provider web` sparingly, keep messages human-like, and re-run `login` if the session is dropped. - Limits to remember: WhatsApp text limit ~1600 chars; avoid rapid bursts—space sends by a few seconds; keep webhook replies under a couple seconds for good UX; command auto-replies time out after 600s by default. - Deploy / keep running: Use `tmux` or `screen` for ad-hoc (`tmux new -s warelay -- pnpm warelay relay --provider twilio`). For long-running hosts, wrap `pnpm warelay relay ...` or `pnpm warelay up ...` in a systemd service or macOS LaunchAgent; ensure environment variables are loaded in that context. -- Rotating credentials: Update `.env` (Twilio keys), rerun your process; for Web provider, delete `~/.warelay/credentials/` and rerun `pnpm warelay web:login` to relink. +- Rotating credentials: Update `.env` (Twilio keys), rerun your process; for Web provider, delete `~/.warelay/credentials/` and rerun `pnpm warelay login` to relink. diff --git a/src/cli/program.ts b/src/cli/program.ts index f0a417573..b3d96466b 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -24,23 +24,9 @@ export function buildProgram() { .description("WhatsApp relay CLI (Twilio or WhatsApp Web session)") .version("1.0.0"); - program - .command("web:login") - .description("Link your personal WhatsApp via QR (web provider)") - .option("--verbose", "Verbose connection logs", false) - .action(async (opts) => { - setVerbose(Boolean(opts.verbose)); - try { - await loginWeb(Boolean(opts.verbose)); - } catch (err) { - defaultRuntime.error(danger(`Web login failed: ${String(err)}`)); - defaultRuntime.exit(1); - } - }); - program .command("login") - .description("Alias for web:login (personal WhatsApp Web QR link)") + .description("Link your personal WhatsApp via QR (web provider)") .option("--verbose", "Verbose connection logs", false) .action(async (opts) => { setVerbose(Boolean(opts.verbose)); diff --git a/src/index.commands.test.ts b/src/index.commands.test.ts index 010b04808..4172bdc8d 100644 --- a/src/index.commands.test.ts +++ b/src/index.commands.test.ts @@ -26,10 +26,9 @@ afterEach(() => { }); describe("CLI commands", () => { - it("exposes login alias", () => { + it("exposes login command", () => { const names = index.program.commands.map((c) => c.name()); expect(names).toContain("login"); - expect(names).toContain("web:login"); }); it("send command routes to web provider", async () => { @@ -87,7 +86,7 @@ describe("CLI commands", () => { ); }); - it("login alias calls web login", async () => { + it("login command calls web login", async () => { const spy = vi.spyOn(providerWeb, "loginWeb").mockResolvedValue(); await index.program.parseAsync(["login"], { from: "user" }); expect(spy).toHaveBeenCalled(); diff --git a/src/provider-web.ts b/src/provider-web.ts index e065a2a86..818977fcf 100644 --- a/src/provider-web.ts +++ b/src/provider-web.ts @@ -65,9 +65,7 @@ export async function createWaSocket(printQr: boolean, verbose: boolean) { if (connection === "close") { const status = getStatusCode(lastDisconnect?.error); if (status === DisconnectReason.loggedOut) { - console.error( - danger("WhatsApp session logged out. Run: warelay web:login"), - ); + console.error(danger("WhatsApp session logged out. Run: warelay login")); } } if (connection === "open" && verbose) { @@ -191,10 +189,10 @@ export async function loginWeb( await fs.rm(WA_WEB_AUTH_DIR, { recursive: true, force: true }); console.error( danger( - "WhatsApp reported the session is logged out. Cleared cached web session; please rerun warelay web:login and scan the QR again.", + "WhatsApp reported the session is logged out. Cleared cached web session; please rerun warelay login and scan the QR again.", ), ); - throw new Error("Session logged out; cache cleared. Re-run web:login."); + throw new Error("Session logged out; cache cleared. Re-run login."); } const formatted = formatError(err); console.error(