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
### 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`).

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:
**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 <n>` `--lookback <min>` `--json` |
| `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 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 5MB).
@@ -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.

View File

@@ -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));

View File

@@ -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();

View File

@@ -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(