CLI: drop web:login alias and simplify web quickstart

This commit is contained in:
Peter Steinberger
2025-11-25 12:30:41 +01:00
parent 2ba56b82e7
commit e6b98cb445
5 changed files with 15 additions and 32 deletions

View File

@@ -3,7 +3,7 @@
## 0.1.0 — 2025-11-25 ## 0.1.0 — 2025-11-25
### CLI & Providers ### 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`). - 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`). - `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`). - `status` merges inbound + outbound Twilio traffic with formatted lines or JSON output (`commands/status.ts`, `twilio/messages.ts`).

View File

@@ -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: Install from npm (global): `npm install -g warelay` (Node 22+). Then choose **one** path:
**A) Personal WhatsApp Web (preferred: no Twilio creds, fastest setup)** **A) Personal WhatsApp Web (preferred: no Twilio creds, fastest setup)**
1. Link your account: `warelay web:login` (scan the QR). 1. Link your account: `warelay login` (scan the QR).
2. Send a message: `warelay send --provider web --to +12345550000 --message "Hi from warelay"`. 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 --provider web --verbose`. 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)** **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`). 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 <n>` `--lookback <min>` `--json` | | `warelay status` | Show recent sent/received messages | `--limit <n>` `--lookback <min>` `--json` |
| `warelay webhook` | Run local inbound webhook server | `--port <port>` `--path <path>` `--reply <text>` `--verbose` `--yes` `--dry-run` | | `warelay webhook` | Run local inbound webhook server | `--port <port>` `--path <path>` `--reply <text>` `--verbose` `--yes` `--dry-run` |
| `warelay up` | Turn on webhook + Tailscale Funnel + Twilio callback | `--port <port>` `--path <path>` `--verbose` `--yes` `--dry-run` | | `warelay up` | Turn on webhook + Tailscale Funnel + Twilio callback | `--port <port>` `--path <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 ### 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 5MB). - 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 5MB).
@@ -44,7 +44,7 @@ Install from npm (global): `npm install -g warelay` (Node 22+). Then choose **on
## Providers ## Providers
- **Twilio (default):** needs `.env` creds + WhatsApp-enabled number; supports delivery tracking, polling, webhooks, and auto-reply typing indicators. - **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. - **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. 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 ## Troubleshooting Tips
- Send/receive issues: run `pnpm warelay status --limit 20 --lookback 240 --json` to inspect recent traffic. - 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). - 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. - Tailscale Funnel errors: update tailscale/tailscaled; check admin console that Funnel is enabled for this device.
## FAQ & Safety (quick answers) ## 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. - 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. - 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. - 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. - 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.

View File

@@ -24,23 +24,9 @@ export function buildProgram() {
.description("WhatsApp relay CLI (Twilio or WhatsApp Web session)") .description("WhatsApp relay CLI (Twilio or WhatsApp Web session)")
.version("1.0.0"); .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 program
.command("login") .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) .option("--verbose", "Verbose connection logs", false)
.action(async (opts) => { .action(async (opts) => {
setVerbose(Boolean(opts.verbose)); setVerbose(Boolean(opts.verbose));

View File

@@ -26,10 +26,9 @@ afterEach(() => {
}); });
describe("CLI commands", () => { describe("CLI commands", () => {
it("exposes login alias", () => { it("exposes login command", () => {
const names = index.program.commands.map((c) => c.name()); const names = index.program.commands.map((c) => c.name());
expect(names).toContain("login"); expect(names).toContain("login");
expect(names).toContain("web:login");
}); });
it("send command routes to web provider", async () => { 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(); const spy = vi.spyOn(providerWeb, "loginWeb").mockResolvedValue();
await index.program.parseAsync(["login"], { from: "user" }); await index.program.parseAsync(["login"], { from: "user" });
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();

View File

@@ -65,9 +65,7 @@ export async function createWaSocket(printQr: boolean, verbose: boolean) {
if (connection === "close") { if (connection === "close") {
const status = getStatusCode(lastDisconnect?.error); const status = getStatusCode(lastDisconnect?.error);
if (status === DisconnectReason.loggedOut) { if (status === DisconnectReason.loggedOut) {
console.error( console.error(danger("WhatsApp session logged out. Run: warelay login"));
danger("WhatsApp session logged out. Run: warelay web:login"),
);
} }
} }
if (connection === "open" && verbose) { if (connection === "open" && verbose) {
@@ -191,10 +189,10 @@ export async function loginWeb(
await fs.rm(WA_WEB_AUTH_DIR, { recursive: true, force: true }); await fs.rm(WA_WEB_AUTH_DIR, { recursive: true, force: true });
console.error( console.error(
danger( 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); const formatted = formatError(err);
console.error( console.error(