{
  "markdown": "# wu\n\nWhatsApp CLI tool — like `gh` is to GitHub, `wu` is to WhatsApp.\n\nBuilt on [@whiskeysockets/baileys](https://github.com/WhiskeySockets/Baileys).\n\n## Install\n\n```bash\nnpm install -g @ibrahimwithi/wu-cli\n```\n\nRequires Node.js 20+.\n\n## Quick Start\n\n```bash\n# Authenticate (scan QR code)\nwu login\n\n# Or use pairing code instead\nwu login --code 15551234567\n\n# Start collecting messages\nwu listen\n\n# Search messages\nwu messages search \"meeting tomorrow\"\n\n# Send a message\nwu messages send 1234567890@s.whatsapp.net \"Hello!\"\n\n# List your groups\nwu groups list --live\n```\n\nBy default, wu operates in **opt-in mode** — no messages are collected until you allow specific chats:\n\n```bash\n# Allow all group messages (read-only)\nwu config allow '*@g.us' --mode read\n\n# Allow full access to a specific chat\nwu config allow 1234567890@s.whatsapp.net\n\n# Start collecting\nwu listen\n```\n\n## Commands\n\n### Authentication\n\n| Command | Description |\n|---|---|\n| `wu login` | Authenticate with WhatsApp via QR code |\n| `wu login --code <phone>` | Authenticate via pairing code |\n| `wu logout` | Clear session |\n| `wu status` | Show connection status and account info |\n\n### Messages\n\n| Command | Description |\n|---|---|\n| `wu messages list <jid>` | List messages in a chat |\n| `wu messages search <query>` | Full-text search messages (FTS5 ranked) |\n| `wu messages send <jid> [text]` | Send text, media, or poll |\n| `wu messages react <jid> <id> <emoji>` | React to a message |\n| `wu messages delete <jid> <id>` | Delete a message for everyone |\n| `wu messages export <jid>` | Export messages to a file (jsonl/json/markdown/csv) |\n\n```bash\n# Send with media\nwu messages send 1234567890@s.whatsapp.net --media photo.jpg --caption \"Check this out\"\n\n# Create a poll\nwu messages send 120363XXX@g.us --poll \"Lunch?\" --options \"Pizza,Sushi,Tacos\"\n\n# Reply to a message\nwu messages send 120363XXX@g.us \"Agreed\" --reply-to BAE5ABC123\n\n# Search within a specific chat\nwu messages search \"budget\" --chat 120363XXX@g.us --limit 20\n\n# Download all undownloaded media in a chat\nwu media download-batch 120363XXX@g.us --limit 50 --concurrency 4\n\n# Export all messages since a timestamp to a file\nwu messages export 120363XXX@g.us --after 1772548621 --format jsonl --output data/export.jsonl\n\n# Export as readable markdown\nwu messages export 120363XXX@g.us --format markdown --output data/chat.md\n```\n\n### Chats & Contacts\n\n| Command | Description |\n|---|---|\n| `wu chats list` | List all chats |\n| `wu chats search <query>` | Search chats by name |\n| `wu contacts list` | List all contacts |\n| `wu contacts search <query>` | Search contacts by name or phone |\n| `wu contacts info <jid>` | Show contact details |\n\n### Groups\n\n| Command | Description |\n|---|---|\n| `wu groups list` | List groups with community tree and constraint status |\n| `wu groups list --live` | Fetch from WhatsApp and refresh community linkage |\n| `wu groups list --allowed-only` | Skip groups whose constraint is `none` |\n| `wu groups info <jid>` | Show group details and participants |\n| `wu groups create <name> [jids...]` | Create a new group |\n| `wu groups invite <jid>` | Get invite link |\n| `wu groups leave <jid>` | Leave a group |\n| `wu groups rename <jid> <name>` | Rename a group |\n| `wu groups join <code-or-url>` | Join a group by invite code or URL |\n| `wu groups participants <jid>` | List group participants |\n\nBy default `wu groups list` shows every group you're in (jid + name + community shape) so you can see what's there before opting in. Description, full participant list, and messages stay constraint-gated. Set `whatsapp.group_discovery: false` in the config to revert to the old behavior where group metadata is only stored when the constraint allows.\n\n### Communities\n\n| Command | Description |\n|---|---|\n| `wu communities list` | List WhatsApp Communities (parent groups) |\n| `wu communities list --with-subgroups` | Include linked subgroups under each parent |\n\n### Direct messages (DMs)\n\n| Command | Description |\n|---|---|\n| `wu dms list` | List 1:1 chats you've opted into |\n| `wu dms list --all` | Include DMs blocked by constraints |\n| `wu dms search <query>` | Search 1:1 chats by name |\n\nDM JIDs contain the contact's phone number, so they're always constraint-gated regardless of `group_discovery`.\n\n### Media\n\n| Command | Description |\n|---|---|\n| `wu media download <msg-id>` | Download media from a message |\n| `wu media download-batch [jid]` | Download undownloaded media in parallel (`--ids a,b,c` for specific messages) |\n| `wu media send <jid> <path>` | Send a media file |\n| `wu media transcribe <msg-id>` | Transcribe a voice/audio message to text |\n| `wu media ocr <msg-id>` | Extract text from an image message |\n| `wu media prune` | Delete downloaded media files (`--older-than 30d`, `--chat`, `--dry-run`) |\n\nTranscripts and OCR text are stored on the message and indexed for `wu messages search`.\n\n### Enrichment\n\nTranscription and OCR run through pluggable backends — a local binary (default) or a hosted API. wu bundles no models; install the binary or set an API key.\n\n```bash\n# See which backends are configured and ready, with how to enable them\nwu enrich status\n```\n\n| Capability | Local default | API option |\n|---|---|---|\n| `transcribe` | `whisper` (or whisper.cpp via a custom command) | any OpenAI-compatible audio API (Groq, OpenAI) |\n| `ocr` | `tesseract` (with `ara+eng` data) | Anthropic vision, or any OpenAI-compatible vision API |\n\nConfigure under `enrich` in `config.yaml` (see Configuration below).\n\n### History\n\n| Command | Description |\n|---|---|\n| `wu history backfill <jid>` | Request older messages from WhatsApp |\n\n```bash\n# Backfill 50 older messages for a group\nwu history backfill 120363XXX@g.us --count 50\n\n# With JSON output\nwu history backfill 120363XXX@g.us --count 20 --json\n```\n\n### Daemon\n\n```bash\n# Run as a foreground daemon — collects messages continuously\nwu daemon\n\n# Install as a systemd service (Linux)\nwu daemon install\n\n# Remove systemd service\nwu daemon uninstall\n\n# View daemon logs\nwu daemon logs\n```\n\nThe daemon auto-reconnects on connection drops, logs health every 5 minutes, and stores all messages to SQLite.\n\n### Remote Sync\n\nRun the daemon on a VPS collecting messages 24/7, query from your local machine. All communication over SSH — no exposed ports, no extra auth.\n\n```bash\n# --- On the VPS ---\nnpm i -g @ibrahimwithi/wu-cli\nwu login\nwu daemon install\n\n# --- On your local machine ---\n# Add the remote\nwu remote add vps user@your-vps-ip\n\n# Push your constraints to the server\nwu remote setup vps --push\n\n# Pull the database\nwu sync pull\n\n# Continuous sync every 30s\nwu sync pull --watch --interval 30\n\n# Install as a systemd timer (Linux)\nwu sync install --interval 60\n```\n\n| Command | Description |\n|---|---|\n| `wu remote add <name> <host>` | Add a remote wu instance |\n| `wu remote list` | List configured remotes |\n| `wu remote remove <name>` | Remove a remote |\n| `wu remote default <name>` | Set the default remote |\n| `wu remote setup <name>` | Sync constraints between local and remote |\n| `wu sync pull [name]` | Pull database from remote |\n| `wu sync pull --watch` | Continuously sync on an interval |\n| `wu sync install` | Install systemd timer for periodic sync |\n| `wu sync uninstall` | Remove systemd sync timer |\n\nWhen a remote is configured and no local daemon is running, `wu mcp` starts in **remote mode** — reads from the local synced DB, routes writes (send, react, etc.) through SSH to the VPS.\n\n### MCP Server\n\n```bash\n# Start MCP server (stdio transport for AI agents)\nwu mcp\n```\n\nExposes WhatsApp as tools and resources for AI agents via the [Model Context Protocol](https://modelcontextprotocol.io). See [MCP setup guide](docs/mcp-setup.md) for Claude Code, Cursor, Codex CLI, and Gemini CLI configuration.\n\nThe MCP server operates in three modes:\n\n| Mode | Condition | Reads | Writes |\n|---|---|---|---|\n| Full local | No daemon running, no remote | WhatsApp (live) | WhatsApp (live) |\n| Read-only | Local daemon running | SQLite | Disabled |\n| Remote | Remote configured + synced DB | Local SQLite | SSH to remote |\n\n### Configuration\n\n| Command | Description |\n|---|---|\n| `wu config show` | Print current config (YAML) |\n| `wu config set <path> <value>` | Set a config value (dot-notation) |\n| `wu config path` | Print config file path |\n| `wu config allow <jid>` | Allow a chat (default: full access) |\n| `wu config block <jid>` | Block a chat (drop all messages) |\n| `wu config remove <jid>` | Remove a per-chat constraint |\n| `wu config default [mode]` | Get/set default constraint mode |\n| `wu config constraints` | Show all constraints |\n\n### Database\n\n| Command | Description |\n|---|---|\n| `wu db vacuum` | Run VACUUM and ANALYZE |\n| `wu db reindex` | Rebuild the full-text search index (fixes `database disk image is malformed` on search) |\n\n## Constraints\n\nThe constraint system controls what wu can do with each chat. Three modes:\n\n| Mode | Collect messages | Send messages | Manage group |\n|---|---|---|---|\n| `full` | yes | yes | yes |\n| `read` | yes | no | no |\n| `none` | no | no | no |\n\nResolution order (most specific wins):\n1. Exact JID match (`1234567890@s.whatsapp.net`)\n2. Wildcard domain (`*@g.us` for all groups, `*@s.whatsapp.net` for all DMs)\n3. Default constraint\n4. Implicit fallback: `none`\n\n```bash\n# Set default to read-only for everything\nwu config default read\n\n# Full access for one group\nwu config allow 120363XXX@g.us\n\n# Block a specific chat\nwu config block 1234567890@s.whatsapp.net\n\n# Read-only for all groups\nwu config allow '*@g.us' --mode read\n```\n\n## Configuration\n\nConfig lives at `~/.wu/config.yaml`:\n\n```yaml\nwhatsapp:\n  read_receipts: false     # Send read receipts (default: false)\n  media_max_mb: 50         # Max media auto-download size in MB\n  send_delay_ms: 1000      # Delay before sending messages (ms)\n  group_discovery: true    # Cache group metadata even when constraint is 'none'\n                           # (jid + name + community shape only; descriptions\n                           # and participants stay constraint-gated). Set to\n                           # false for strict mode.\n\nconstraints:\n  default: none            # Default constraint mode\n  chats:\n    \"*@g.us\":\n      mode: read\n    \"120363XXX@g.us\":\n      mode: full\n\nremotes:\n  vps:\n    host: user@your-vps-ip\n    wu_home: ~/.wu\ndefault_remote: vps\n\ndb:\n  path: ~/.wu/wu.db        # SQLite database path\n\nlog:\n  level: info              # debug, info, warn, error\n\nenrich:                    # Media enrichment backends (off until configured)\n  transcribe:\n    backend: local         # local | api | off\n    local:\n      cmd: \"whisper {input} --model base --output_format txt --output_dir {outdir}\"\n    api:                   # used when backend: api\n      provider: openai     # OpenAI-compatible audio (Groq, OpenAI, ...)\n      base_url: https://api.groq.com/openai/v1\n      key_env: GROQ_API_KEY\n      model: whisper-large-v3\n  ocr:\n    backend: local\n    local:\n      cmd: \"tesseract {input} stdout -l ara+eng\"\n    api:\n      provider: anthropic  # anthropic (messages vision) | openai (chat vision)\n      base_url: https://api.anthropic.com/v1\n      key_env: ANTHROPIC_API_KEY\n      model: claude-haiku-4-5-20251001\n```\n\nThe local `cmd` runs with `{input}` replaced by the media path; it must either print the text to stdout (e.g. tesseract) or write a `.txt` into `{outdir}` (e.g. whisper). Run `wu enrich status` to see what's detected and how to enable each backend.\n\nAll runtime data lives under `~/.wu/` (override with `WU_HOME` env var).\n\n## JSON Output\n\nMost commands support `--json` for machine-readable output. When piped, `wu listen` auto-detects and switches to JSON:\n\n```bash\n# Pipe messages to jq\nwu listen | jq '.body'\n\n# Export messages as JSON\nwu messages list 120363XXX@g.us --json --limit 1000\n```\n\n## MCP Tools and Resources\n\nWhen running `wu mcp`, the following are available to AI agents:\n\n**Tools:** `wu_messages_send`, `wu_react`, `wu_media_download`, `wu_media_download_batch`, `wu_media_prune`, `wu_media_transcribe`, `wu_media_ocr`, `wu_enrich_status`, `wu_messages_search`, `wu_messages_list`, `wu_messages_context`, `wu_messages_count`, `wu_messages_export`, `wu_history_backfill`, `wu_chats_list`, `wu_chats_search`, `wu_dms_list`, `wu_contacts_list`, `wu_contacts_search`, `wu_groups_list`, `wu_groups_info`, `wu_groups_invite`, `wu_groups_create`, `wu_groups_leave`, `wu_groups_rename`, `wu_groups_join`, `wu_communities_list`, `wu_constraints_list`, `wu_constraints_set`, `wu_constraints_remove`, `wu_constraints_default`, `wu_config_show`, `wu_status`\n\n`wu_messages_export` takes `download_media: true` to download the window's image/document media and write a `<output>.manifest.jsonl` mapping each item to its local file path. Add `enrich: true` to also OCR images and transcribe audio in the same pass, writing the text onto each message and into the manifest rows (`ocr_text`, `transcript`). Enrichment respects the configured backends — a disabled one is skipped and reported, never fatal.\n\n**Resources:** `wu://chats`, `wu://chats/{jid}/messages`, `wu://contacts`, `wu://contacts/{jid}`, `wu://groups`, `wu://groups/{jid}`, `wu://status`\n\n## Exit Codes\n\n| Code | Meaning |\n|---|---|\n| 0 | Success |\n| 1 | General error |\n| 2 | Constraint violation |\n| 3 | Not authenticated |\n| 4 | Connection failed |\n| 5 | Not found |\n\n## Notes\n\n### Baileys pin policy\n\n`@whiskeysockets/baileys` is pinned to an exact release candidate\n(`7.0.0-rc13`) in `package.json` - no `^` or `~`. `@whiskeysockets/baileys` is\na reverse-engineered client for a protocol WhatsApp doesn't publish, so an\n`-rc` build can be yanked or shift behavior between builds; an exact pin keeps\ndeploys deterministic instead of picking up a moving target on `npm install`.\n\nUpgrade cadence: periodically check upstream for a GA `7.0.0` (or newer)\nrelease and bump deliberately - never via `npm update`. Any bump must be\ntested against a real WhatsApp session before merging (the characterization\ntests in `tests/` can't simulate the live protocol) and followed by `npm\naudit` (the `ws` advisory that rides this dependency clears on bump).\n\n## License\n\nMIT\n",
  "bytes": 14162,
  "sha": "13d0bc3508279e5e0539a436d010d6a227b63e2610203af89fc990bcab1c5cbd",
  "repo_slug": "ibrahimhajjaj/wu-cli",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_ibrahimhajjaj_wu_cli_wu_whatsapp_e053dd08/readme"
}