npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ibrahimwithi/wu-cli

v0.3.0

Published

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

Readme

wu

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

Built on @whiskeysockets/baileys.

Install

npm install -g @ibrahimwithi/wu-cli

Requires Node.js 20+.

Quick Start

# 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 [email protected] "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:

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

# Allow full access to a specific chat
wu config allow [email protected]

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

# Send with media
wu messages send [email protected] --media photo.jpg --caption "Check this out"

# Create a poll
wu messages send [email protected] --poll "Lunch?" --options "Pizza,Sushi,Tacos"

# Reply to a message
wu messages send [email protected] "Agreed" --reply-to BAE5ABC123

# Search within a specific chat
wu messages search "budget" --chat [email protected] --limit 20

# Download all undownloaded media in a chat
wu media download-batch [email protected] --limit 50 --concurrency 4

# Export all messages since a timestamp to a file
wu messages export [email protected] --after 1772548621 --format jsonl --output data/export.jsonl

# Export as readable markdown
wu messages export [email protected] --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 with community tree and constraint status | | wu groups list --live | Fetch from WhatsApp and refresh community linkage | | wu groups list --allowed-only | Skip groups whose constraint is none | | 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 |

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

Communities

| Command | Description | |---|---| | wu communities list | List WhatsApp Communities (parent groups) | | wu communities list --with-subgroups | Include linked subgroups under each parent |

Direct messages (DMs)

| Command | Description | |---|---| | wu dms list | List 1:1 chats you've opted into | | wu dms list --all | Include DMs blocked by constraints | | wu dms search <query> | Search 1:1 chats by name |

DM JIDs contain the contact's phone number, so they're always constraint-gated regardless of group_discovery.

Media

| Command | Description | |---|---| | wu media download <msg-id> | Download media from a message | | wu media download-batch [jid] | Download undownloaded media in parallel (--ids a,b,c for specific messages) | | wu media send <jid> <path> | Send a media file | | wu media transcribe <msg-id> | Transcribe a voice/audio message to text | | wu media ocr <msg-id> | Extract text from an image message | | wu media prune | Delete downloaded media files (--older-than 30d, --chat, --dry-run) |

Transcripts and OCR text are stored on the message and indexed for wu messages search.

Enrichment

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

# See which backends are configured and ready, with how to enable them
wu enrich status

| Capability | Local default | API option | |---|---|---| | transcribe | whisper (or whisper.cpp via a custom command) | any OpenAI-compatible audio API (Groq, OpenAI) | | ocr | tesseract (with ara+eng data) | Anthropic vision, or any OpenAI-compatible vision API |

Configure under enrich in config.yaml (see Configuration below).

Pin the language, per chat. For anything that isn't English this is worth more than the choice of model. Without it Whisper guesses, it guesses again on every ~30s window, and on a voice note a few seconds long it can land on the wrong language or switch part way through.

A single global pin is the wrong shape for most accounts, because the language belongs to the conversation, not to the install: one group carries Arabic voice notes and the next carries English, and whichever one you pin globally makes the other worse. So languages is keyed exactly like constraints.chats - an exact JID, or *@domain for a whole domain - with language as the fallback for everything it doesn't cover:

enrich:
  transcribe:
    language: en                        # fallback; omit it to keep auto-detect
    languages:
      "[email protected]": ar              # this group is Arabic
      "*@s.whatsapp.net": ar            # every DM is Arabic

Exact JID wins over the wildcard, which wins over language. The resolved value fills {lang} in a local command and is sent as the language field on the API path. wu enrich status says which language each backend will use, how many chats are pinned, and says so when it is still guessing.

Choosing a transcription backend. The shipped default is whisper --model small, which is the strongest model that is still a reasonable first-run download (~460 MB) and runs anywhere. Worth knowing before you keep it:

  • A hosted whisper-large-v3 (backend: api) is both the most accurate option and, on a machine without a fast GPU, far and away the quickest - tens of times realtime against roughly realtime for a local CPU run.
  • --model turbo is much faster than large-v3 locally, but its decoder is cut from 32 layers to 4 and the loss falls hardest on under-represented dialects. Don't reach for it as a quality upgrade.
  • On Apple Silicon, mlx_whisper runs large-v3 several times faster than the reference implementation. Its flags are hyphenated (--condition-on-previous-text) where whisper's are underscored, so port the command rather than copying it.

History

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

# Backfill 50 older messages for a group
wu history backfill [email protected] --count 50

# With JSON output
wu history backfill [email protected] --count 20 --json

Daemon

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

# --- 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

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

Exposes WhatsApp as tools and resources for AI agents via the Model Context Protocol. See MCP setup guide 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 | | wu db reindex | Rebuild the full-text search index (fixes database disk image is malformed on search) |

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 ([email protected])
  2. Wildcard domain (*@g.us for all groups, *@s.whatsapp.net for all DMs)
  3. Default constraint
  4. Implicit fallback: none
# Set default to read-only for everything
wu config default read

# Full access for one group
wu config allow [email protected]

# Block a specific chat
wu config block [email protected]

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

Configuration

Config lives at ~/.wu/config.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)
  group_discovery: true    # Cache group metadata even when constraint is 'none'
                           # (jid + name + community shape only; descriptions
                           # and participants stay constraint-gated). Set to
                           # false for strict mode.

constraints:
  default: none            # Default constraint mode
  chats:
    "*@g.us":
      mode: read
    "[email protected]":
      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

enrich:                    # Media enrichment backends (off until configured)
  transcribe:
    backend: local         # local | api | off
    language: ar           # optional fallback; fills {lang} below and the API language field
    languages:             # optional per-chat overrides, keyed like constraints.chats
      "[email protected]": ar
    local:
      cmd: "whisper {input} --model small --language {lang} --condition_on_previous_text False --output_format txt --output_dir {outdir}"
    api:                   # used when backend: api
      provider: openai     # OpenAI-compatible audio (Groq, OpenAI, ...)
      base_url: https://api.groq.com/openai/v1
      key_env: GROQ_API_KEY
      model: whisper-large-v3
  ocr:
    backend: local
    local:
      cmd: "tesseract {input} stdout -l ara+eng"
    api:
      provider: anthropic  # anthropic (messages vision) | openai (chat vision)
      base_url: https://api.anthropic.com/v1
      key_env: ANTHROPIC_API_KEY
      model: claude-haiku-4-5-20251001

The 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). {lang} takes the language resolved for the chat the media came from, and when nothing is pinned the placeholder and the flag holding it both drop out of the command, since a bare --language is an error. Run wu enrich status to see what's detected and how to enable each backend.

--condition_on_previous_text False is in the default for a reason: left on, Whisper feeds each window its own previous output and can lock into repeating a single word for the rest of a long recording. It costs nothing to disable and it is the difference between a usable transcript and a page of one word.

One process caveat: a long-running wu mcp picks up edits to config.yaml as they happen, but it cannot pick up a new environment variable. If you export an API key after starting it, the CLI will report the backend ready while the MCP tool still reports it missing - wu_enrich_status returns the process start time so the two disagreeing is easy to spot. Restart the MCP server after setting a key.

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:

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

# Export messages as JSON
wu messages list [email protected] --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_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

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.

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 |

Notes

Baileys pin policy

@whiskeysockets/baileys is pinned to an exact release candidate (7.0.0-rc13) in package.json - no ^ or ~. @whiskeysockets/baileys is a reverse-engineered client for a protocol WhatsApp doesn't publish, so an -rc build can be yanked or shift behavior between builds; an exact pin keeps deploys deterministic instead of picking up a moving target on npm install.

Upgrade cadence: periodically check upstream for a GA 7.0.0 (or newer) release and bump deliberately - never via npm update. Any bump must be tested against a real WhatsApp session before merging (the characterization tests in tests/ can't simulate the live protocol) and followed by npm audit (the ws advisory that rides this dependency clears on bump).

License

MIT