npm package: @ibrahimwithi/wu-cli
Description: WhatsApp CLI tool — like gh is to GitHub, wu is to WhatsApp
Author: Ibrahim Hajjaj
Latest version: 0.1.19

# wu

WhatsApp CLI tool — like `gh` is to GitHub, `wu` is to WhatsApp.

Built on [@whiskeysockets/baileys](https://github.com/WhiskeySockets/Baileys).

## Install

```bash
npm install -g @ibrahimwithi/wu-cli
```

Requires Node.js 20+.

## Quick Start

```bash
# Authenticate (scan QR code)
wu login

# Or use pairing code instead
wu login --code 15551234567

# Start collecting messages
wu listen

# Search messages
wu messages search "meeting tomorrow"

# Send a message
wu messages send 1234567890@s.whatsapp.net "Hello!"

# List your groups
wu groups list --live
```

By default, wu operates in **opt-in mode** — no messages are collected until you allow specific chats:

```bash
# Allow all group messages (read-only)
wu config allow '*@g.us' --mode read

# Allow full access to a specific chat
wu config allow 1234567890@s.whatsapp.net

# Start collecting
wu listen
```

## Commands

### Authentication

| Command | Description |
|---|---|
| `wu login` | Authenticate with WhatsApp via QR code |
| `wu login --code <phone>` | Authenticate via pairing code |
| `wu logout` | Clear session |
| `wu status` | Show connection status and account info |

### Messages

| Command | Description |
|---|---|
| `wu messages list <jid>` | List messages in a chat |
| `wu messages search <query>` | Full-text search messages (FTS5 ranked) |
| `wu messages send <jid> [text]` | Send text, media, or poll |
| `wu messages react <jid> <id> <emoji>` | React to a message |
| `wu messages delete <jid> <id>` | Delete a message for everyone |
| `wu messages export <jid>` | Export messages to a file (jsonl/json/markdown/csv) |

```bash
# Send with media
wu messages send 1234567890@s.whatsapp.net --media photo.jpg --caption "Check this out"

# Create a poll
wu messages send 120363XXX@g.us --poll "Lunch?" --options "Pizza,Sushi,Tacos"

# Reply to a message
wu messages send 120363XXX@g.us "Agreed" --reply-to BAE5ABC123

# Search within a specific chat
wu messages search "budget" --chat 120363XXX@g.us --limit 20

# Download all undownloaded media in a chat
wu media download-batch 120363XXX@g.us --limit 50 --concurrency 4

# Export all messages since a timestamp to a file
wu messages export 120363XXX@g.us --after 1772548621 --format jsonl --output data/export.jsonl

# Export as readable markdown
wu messages export 120363XXX@g.us --format markdown --output data/chat.md
```

### Chats & Contacts

| Command | Description |
|---|---|
| `wu chats list` | List all chats |
| `wu chats search <query>` | Search chats by name |
| `wu contacts list` | List all contacts |
| `wu contacts search <query>` | Search contacts by name or phone |
| `wu contacts info <jid>` | Show contact details |

### Groups

| Command | Description |
|---|---|
| `wu groups list` | List groups (cached) |
| `wu groups list --live` | Fetch groups from WhatsApp |
| `wu groups info <jid>` | Show group details and participants |
| `wu groups create <name> [jids...]` | Create a new group |
| `wu groups invite <jid>` | Get invite link |
| `wu groups leave <jid>` | Leave a group |
| `wu groups rename <jid> <name>` | Rename a group |
| `wu groups join <code-or-url>` | Join a group by invite code or URL |
| `wu groups participants <jid>` | List group participants |

### Media

| Command | Description |
|---|---|
| `wu media download <msg-id>` | Download media from a message |
| `wu media download-batch <jid>` | Download undownloaded media in parallel |
| `wu media send <jid> <path>` | Send a media file |

### History

| Command | Description |
|---|---|
| `wu history backfill <jid>` | Request older messages from WhatsApp |

```bash
# Backfill 50 older messages for a group
wu history backfill 120363XXX@g.us --count 50

# With JSON output
wu history backfill 120363XXX@g.us --count 20 --json
```

### Daemon

```bash
# Run as a foreground daemon — collects messages continuously
wu daemon

# Install as a systemd service (Linux)
wu daemon install

# Remove systemd service
wu daemon uninstall

# View daemon logs
wu daemon logs
```

The daemon auto-reconnects on connection drops, logs health every 5 minutes, and stores all messages to SQLite.

### Remote Sync

Run the daemon on a VPS collecting messages 24/7, query from your local machine. All communication over SSH — no exposed ports, no extra auth.

```bash
# --- On the VPS ---
npm i -g @ibrahimwithi/wu-cli
wu login
wu daemon install

# --- On your local machine ---
# Add the remote
wu remote add vps user@your-vps-ip

# Push your constraints to the server
wu remote setup vps --push

# Pull the database
wu sync pull

# Continuous sync every 30s
wu sync pull --watch --interval 30

# Install as a systemd timer (Linux)
wu sync install --interval 60
```

| Command | Description |
|---|---|
| `wu remote add <name> <host>` | Add a remote wu instance |
| `wu remote list` | List configured remotes |
| `wu remote remove <name>` | Remove a remote |
| `wu remote default <name>` | Set the default remote |
| `wu remote setup <name>` | Sync constraints between local and remote |
| `wu sync pull [name]` | Pull database from remote |
| `wu sync pull --watch` | Continuously sync on an interval |
| `wu sync install` | Install systemd timer for periodic sync |
| `wu sync uninstall` | Remove systemd sync timer |

When 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.

### MCP Server

```bash
# Start MCP server (stdio transport for AI agents)
wu mcp
```

Exposes 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.

The MCP server operates in three modes:

| Mode | Condition | Reads | Writes |
|---|---|---|---|
| Full local | No daemon running, no remote | WhatsApp (live) | WhatsApp (live) |
| Read-only | Local daemon running | SQLite | Disabled |
| Remote | Remote configured + synced DB | Local SQLite | SSH to remote |

### Configuration

| Command | Description |
|---|---|
| `wu config show` | Print current config (YAML) |
| `wu config set <path> <value>` | Set a config value (dot-notation) |
| `wu config path` | Print config file path |
| `wu config allow <jid>` | Allow a chat (default: full access) |
| `wu config block <jid>` | Block a chat (drop all messages) |
| `wu config remove <jid>` | Remove a per-chat constraint |
| `wu config default [mode]` | Get/set default constraint mode |
| `wu config constraints` | Show all constraints |

### Database

| Command | Description |
|---|---|
| `wu db vacuum` | Run VACUUM and ANALYZE |

## Constraints

The constraint system controls what wu can do with each chat. Three modes:

| Mode | Collect messages | Send messages | Manage group |
|---|---|---|---|
| `full` | yes | yes | yes |
| `read` | yes | no | no |
| `none` | no | no | no |

Resolution order (most specific wins):
1. Exact JID match (`1234567890@s.whatsapp.net`)
2. Wildcard domain (`*@g.us` for all groups, `*@s.whatsapp.net` for all DMs)
3. Default constraint
4. Implicit fallback: `none`

```bash
# Set default to read-only for everything
wu config default read

# Full access for one group
wu config allow 120363XXX@g.us

# Block a specific chat
wu config block 1234567890@s.whatsapp.net

# Read-only for all groups
wu config allow '*@g.us' --mode read
```

## Configuration

Config lives at `~/.wu/config.yaml`:

```yaml
whatsapp:
  read_receipts: false     # Send read receipts (default: false)
  media_max_mb: 50         # Max media auto-download size in MB
  send_delay_ms: 1000      # Delay before sending messages (ms)

constraints:
  default: none            # Default constraint mode
  chats:
    "*@g.us":
      mode: read
    "120363XXX@g.us":
      mode: full

remotes:
  vps:
    host: user@your-vps-ip
    wu_home: ~/.wu
default_remote: vps

db:
  path: ~/.wu/wu.db        # SQLite database path

log:
  level: info              # debug, info, warn, error
```

All runtime data lives under `~/.wu/` (override with `WU_HOME` env var).

## JSON Output

Most commands support `--json` for machine-readable output. When piped, `wu listen` auto-detects and switches to JSON:

```bash
# Pipe messages to jq
wu listen | jq '.body'

# Export messages as JSON
wu messages list 120363XXX@g.us --json --limit 1000
```

## MCP Tools and Resources

When running `wu mcp`, the following are available to AI agents:

**Tools:** `wu_messages_send`, `wu_react`, `wu_media_download`, `wu_media_download_batch`, `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_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_constraints_list`, `wu_constraints_set`, `wu_constraints_remove`, `wu_constraints_default`, `wu_config_show`, `wu_status`

**Resources:** `wu://chats`, `wu://chats/{jid}/messages`, `wu://contacts`, `wu://contacts/{jid}`, `wu://groups`, `wu://groups/{jid}`, `wu://status`

## Exit Codes

| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Constraint violation |
| 3 | Not authenticated |
| 4 | Connection failed |
| 5 | Not found |

## License

MIT