# wh Terminal WhatsApp client (ratatui) with per-chat and global AI autoreply via an OpenAI-compatible API. ## Build ```sh cargo build --release ``` ## Cross-compile: Linux x86_64 static (musl) Built from Windows with cargo-zigbuild. Zig provides the C cross-compiler and linker for the bundled C code (SQLite, aws-lc-sys, ring). One-time prerequisites: ``` rustup target add x86_64-unknown-linux-musl winget install -e --id zig.zig # or: pip install ziglang cargo install --locked cargo-zigbuild ``` Build: ``` cargo zigbuild --release --target x86_64-unknown-linux-musl ``` Output: `target/x86_64-unknown-linux-musl/release/wh` Verify on Linux: ```sh file wh # ELF 64-bit x86-64, statically linked chmod +x wh && ./wh ``` ### Data directory On Linux, config and databases live in `~/.config/wh/` (`config.toml`, `session.db`, `history.db`); on Windows in `%APPDATA%\wh`. Copy `session.db` from Windows to reuse the linked session instead of scanning the QR again. ## Configuration Settings live in `config.toml` in the data directory. Start from `config.toml.global.template`: copy it there and fill `openai_api_key` and `system_prompt`. A missing file means defaults; without `openai_api_key` and `system_prompt` the app runs as a plain WhatsApp client. | Key | Default | Description | | --- | --- | --- | | `openai_api_key` | unset | API key of the OpenAI-compatible endpoint (required for AI) | | `openai_base_url` | `https://ollama.com/v1` | endpoint base URL | | `openai_model` | `deepseek-v4.1-flash` | model for autoreplies | | `openai_vision_model` | unset | vision model for images; unset disables image understanding | | `system_prompt` | unset | responder identity (required for AI) | | `ai_log` | `false` | append every AI request to `ai_requests.log` | | `cooldown_seconds` | `30` | min seconds between autoreplies in a private chat / to a group message addressed to you (0 = off) | | `group_cooldown_seconds` | `300` | same, group messages not addressed to you (0 = off) | | `catch_up_seconds` | `7200` | after a restart, answer the last unanswered private message up to this age (0 = off) | | `ai_retries` | `2` | retries of a failed AI request, 2s, 4s, ... backoff (0 = off; HTTP 4xx except 429 never retried) | | `web_port` | unset | port of the web control panel (1-65535); unset disables it | ## Keys | Key | Action | | --- | --- | | `Tab` | focus list/input | | `Enter` | open chat / send | | `F2` | chats/contacts | | typing (list focused) | filter names; `Backspace` edits, `Esc` clears | | `Canc`/`Delete` twice | delete chat locally (history only, not on WhatsApp) | | `Ctrl+A` | AI autoreply for the open chat | | `Ctrl+Shift+A` | AI autoreply for all chats (persistent; footer shortcut turns green when on) | | `Ctrl+Q` | quit | ## Web control panel - Enabled by `web_port` in `config.toml` (e.g. `web_port = 8080`); open `http://:/`. - Listens on `0.0.0.0` without authentication: use it only on a trusted network (e.g. a VPN). - The page (HTML/CSS/JS) is embedded in the binary: no extra files to deploy. - Features: "AI tutte" switch (same as `Ctrl+Shift+A`), per-chat AI toggle grid (private chats and groups only), "Chiudi bot" button to quit. The page refreshes every 5 seconds; changes show up in the TUI too. - API (for scripts): `GET /api/state` (JSON `{global_autoreply, chats:[{jid,name,autoreply}], status}`); `POST /api/global`; `POST /api/chat` with the jid as plain-text body; `POST /api/quit`. Every POST requires the header `X-Wh-Request: 1` (otherwise 403), which blocks cross-site requests from other pages. Example: ```sh curl -X POST -H "X-Wh-Request: 1" http://host:8080/api/global ```