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

wpp-tui

v1.4.1

Published

WhatsApp TUI client — terminal interface for WhatsApp

Readme

wpp-tui

WhatsApp TUI client — terminal-based WhatsApp with chat, archive, rename, poll decryption, and event logging.

npm GitHub License

Built with Baileys (WhatsApp Web reverse-engineered protocol) + Ink (React for terminal).

Package

npm install -g wpp-tui    # global CLI
npx wpp-tui               # or run directly

Or clone the repo:

git clone https://github.com/jaweed3/wpp-tui
cd wpp-tui
pnpm install
pnpm dev

Scan the QR code with WhatsApp > Settings > Linked Devices. On subsequent runs it reconnects automatically.

Screenshots

┌─ WA Bot | [1:Chat]  2:Stats   3:Debug  4:Status ── ● me ─┐
├─ CHATS ──────────────────────┐┌─ MESSAGES — General ───────┐
│ > # General            (343) ││ 18:36 me           eae  ✓✓│
│   # He4rt Delas 💕      (52) ││ 18:37 Clinton      oi    │
│   # Vagas             (343) ││ 18:38 Bruna     🖼️ sticker│
│   @ danielhe4rt              ││ 18:38 me           📊 PHP │
│                              ││   ■ Sim!! (3)   ■□ Oke(5)│
│                              ││ 18:39 Mina    🗳️ → Sim 👍│
└──────────────────────────────┘└──────────────────────────┘
┌─ f/:search  i:type  jk:nav  hl:tab  1-4:tabs  T:theme ──┐

Features

TUI (Ink + React)

  • 4 tabs: Chat, Status, Stats, Debug — switch via 1-4, Tab/Shift+Tab, h/l, or /chat etc.
  • Lazygit-style navigation: vim keys j/k up/down, h/l tab switch, arrows everywhere
  • Chat tab: sidebar with groups (#) and DMs (@), message feed with auto-scroll
  • Status tab: view incoming WhatsApp status updates (photos, text)
  • Stats tab: top senders bar chart, message type distribution, group activity
  • Debug tab: real-time event stream with scroll (j/k too), connection info, store counters
  • Chat search: / or f to open search bar — type to filter contacts, Enter to select
  • Input mode: i to type messages, Esc to exit — prevents accidental navigation
  • Message actions: r to reply, d to delete selected message
  • Emoji reactions inline: reactions from other users appear next to the message
  • Typing indicators: typing... shown in the input bar when someone is typing
  • Theme system: Shift+T or /theme to cycle themes (default + dark/tokyonight)
  • QR code rendered inline in the terminal
  • Message history loaded from logs on startup — history persists across restarts
  • Group metadata fetched via WhatsApp API and cached to disk
  • Poll decryption: votes decrypted with AES-256-GCM + HMAC-SHA256, shown per-user
  • Message status indicators: ◌ pending, ✓ sent, ✓✓ delivered, ✓✓✓ blue read
  • Optimistic send: messages appear instantly; deduplicated when server confirms
  • Contact names from phonebook (contacts.update), fallback to WhatsApp pushName, fallback to JID
  • Rename contacts from TUI (/rename or /rn) — synced to WhatsApp server and persisted locally; applies to all messages
  • Archive/unarchive chats (/archive / /unarchive), Ctrl+K toggle archived view

Event Logging

Every WhatsApp event is persisted to logs/<event>/<date>.json with timestamps. Supported events:

| Event | Description | |---|---| | messages.upsert | Incoming/outgoing messages | | messages.update | Delivery/read status changes | | messages.reaction | Emoji reactions | | chats.update | Chat metadata changes | | presence.update | Typing/online indicators | | message-receipt.update | Read receipts | | contacts.update | Contact name changes | | group.member-tag.update | Group member tags/roles | | connection.update | Connection state changes | | creds.update | Auth credential updates |

Data Flow

WhatsApp servers
  ↓ (WebSocket via Baileys)
use-socket.ts hook ← manages connection, auth, reconnect
  ↓ emits events
  ├→ event-logger.ts    → logs/<event>/<date>.json (raw persistence)
  ├→ message-store.ts   → logs/message-store/<id>.json (for poll decryption)
  ├→ React state        → messages[], chats[], pollResults{}, debugEvents[]
  └→ group-cache.ts     → logs/group-metadata.json (fetched once via API)

React state
  ↓
app.tsx (tab router + keyboard)
  ├→ Chat tab:  chat-list.tsx + message-feed.tsx
  ├→ Status tab: status-view.tsx
  ├→ Stats tab: stats-view.tsx (computed from messages[])
  └→ Debug tab: debug-view.tsx (from debugEvents[])

Payload Extraction (make extract-payload)

Parses raw logs into structured JSON files in extracted/<date>/:

| File | Content | |---|---| | messages.json | All messages with parsed content | | users.json | Users with phone, name, groups | | groups-full.json | Group metadata, admins, description, member tags | | reactions.json | Emoji reactions per message, top emojis, top reactors | | threads.json | Reply chains, most quoted messages | | polls.json | Decrypted poll results with voter names | | member-profiles.json | Users with group tags, admin status, activity breakdown | | activity.json | Messages per hour, media breakdown, conversation starters | | stickers.json | Sticker metadata (animated, AI, lottie) | | receipts.json | Read/delivery timestamps | | presence.json | Typing/online events | | timeline.json | Unified chronological feed | | stats.json | Summary statistics |

Webhook Proxy

Optionally forward all events to an external webhook:

WEBHOOK_URL=https://your-webhook.example.com pnpm dev

Modo headless

Para rodar o coletor como serviço 24/7 (sem TUI, ex.: num server via systemd), use o Modo headless. Ele reaproveita o mesmo núcleo de conexão da TUI (startCollectorCore, ver docs/adr/0001): uma única fonte de verdade para os dois modos.

pnpm build              # gera dist/
pnpm start:headless     # = node --env-file-if-exists=.env dist/index.js --headless

O modo é ativado por --headless na linha de comando ou pela env HEADLESS=1 (aceita 1/true/yes). Sem nenhum dos dois, o dist/index.js sobe a TUI normalmente.

Pré-provisionar a sessão (sem QR)

O headless não exibe QR nem faz pairing-code (ver docs/adr/0002). Ele exige uma sessão já pareada:

  1. Pareie uma vez no Modo TUI (pnpm dev) lendo o QR no celular.
  2. Copie a pasta baileys_auth_info/ para o WorkingDirectory do server:
    scp -r ./baileys_auth_info usuario@server:/opt/wpp-tui/baileys_auth_info

Se o headless receber um evento de QR (sinal de que não há sessão válida), ele loga FATAL (sem sessão válida; pré-provisione baileys_auth_info) e sai com código ≠ 0 — em vez de ficar gerando QRs num loop. Um loggedOut no server também derruba o coletor: é preciso re-provisionar a sessão manualmente.

Variáveis obrigatórias

No headless, WEBHOOK_URL e WHATSAPP_WEBHOOK_SECRET são obrigatórias. Se faltar qualquer uma, o processo loga FATAL e sai com código ≠ 0 antes de conectar (fail-fast). No Modo TUI o coletor continua opcional. Veja o .env.example para todas as variáveis.

Logs (JSON na stdout)

O headless emite logs em JSON na stdout por padrão (ideal para journald). Defina LOG_PRETTY=1 para formato legível via pino-pretty (debug local). Não escreve wa-logs.txt a menos que WA_LOG_FILE seja definido explicitamente.

dois loggers, controlados separadamente, e toda linha carrega um campo component (whatsapp / collector / retention / baileys):

| Variável | Logger | Padrão | |---|---|---| | LOG_LEVEL | app / coletor | info | | BAILEYS_LOG_LEVEL | protocolo Baileys (barulhento) | warn |

Se LOG_RETENTION_DAYS estiver ausente/0, o boot emite um WARN alertando sobre crescimento ilimitado de disco.

Deploy via systemd

Há uma unit pronta em deploy/whatsapp-collector.service (com comentários explicando cada diretiva e o passo de pré-provisionamento):

sudo cp deploy/whatsapp-collector.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now whatsapp-collector

# Ler os logs (a saída JSON cai no journald):
journalctl -u whatsapp-collector -f

O sinal de saúde é a linha de heartbeat dos logs (não há endpoint HTTP /health nesta versão).

Keyboard Shortcuts

| Key / Command | Action | |---|---| | Tab / l / | Next tab | | Shift+Tab / h / | Previous tab | | 1/2/3/4 | Jump to Chat/Stats/Debug/Status tab | | j / | Navigate down (chats, messages, debug scroll) | | k / | Navigate up (chats, messages, debug scroll) | | Enter | Open selected chat / Open full message / Go back | | Esc | Cancel search / Exit input mode / Back to chat list | | / or f | Search/filter chats by name (Chat tab) | | i | Enter message-typing mode (input mode) | | r | Reply to last/selected message | | d | Delete selected message | | T | Cycle theme (default → dark → default) | | Ctrl+K | Toggle archived chats view | | Ctrl+Q | Quit | | /help | Show available commands | | /theme or /t | Cycle theme or /theme dark for specific theme | | /rename <name> or /rn <name> | Rename selected contact (synced to server) | | /archive or /a | Archive selected chat | | /unarchive or /ua | Unarchive selected chat | | /arch | Toggle archived chats view | | /delete or /d | Delete selected message | | /chat through /debug | Switch to specific tab | | /quit or /q | Quit | | /s <query> | Filter chats by name |

Scripts

pnpm dev              # dev server with hot reload
pnpm build            # production build with tsup
pnpm start            # run production build (TUI)
pnpm start:headless   # run production build in headless mode (collector, no TUI)
pnpm typecheck        # type check without emitting
make extract-payload  # extract structured data from logs

Project Structure

src/
├── index.tsx              # entry point — routes to TUI or headless (dynamic import)
├── app.tsx                # tab system, keyboard handling, layout (Chat/Status/Stats/Debug)
├── theme.tsx              # theme system (ThemeProvider, useTheme, default + dark themes)
├── message-utils.ts       # shared parseContent, formatContent, trunc (single source for message parsing)
├── app-render.tsx         # TUI renderer (extracted from index.tsx for dynamic import)
├── headless.ts            # headless runner — bootstraps the collector core without UI
├── logging.ts             # logger factory (app + baileys loggers with component tags)
├── logger.ts              # legacy pino logger (file output, used by the TUI)
├── types.ts               # shared TypeScript types
├── commands/              # slash command system (registry, chat commands, navigation commands)
│   ├── registry.ts
│   ├── types.ts
│   ├── chat-commands.ts
│   ├── navigation-commands.ts
│   └── system-commands.ts
├── event-logger.ts        # persists raw events to disk + optional webhook
├── history.ts             # loads message history from logs on startup
├── message-store.ts       # persists raw messages for poll decryption (TUI-only)
├── group-cache.ts         # caches group metadata to disk
├── persist-archive.ts     # persists archived chat state to disk
├── persist-renames.ts     # persists contact renames to disk
├── poll-decrypt.ts        # AES-256-GCM poll vote decryption
├── retention.ts           # periodic cleanup of old raw logs
├── collector/
│   ├── core.ts            # collector core (connection, auth, routing) — shared by TUI + headless
│   ├── headless-config.ts # environment resolution for headless mode (fail-fast validation)
│   ├── shutdown.ts        # graceful shutdown handler (SIGTERM/SIGINT + force-exit timeout)
│   ├── outbox.ts          # SQLite event queue
│   ├── event-router.ts    # routes events to the collector outbox
│   ├── webhook-sender.ts  # sends queued events to the webhook
│   ├── heartbeat.ts       # periodic health signal
│   ├── extractors.ts      # event data extraction helpers
│   ├── helpers.ts         # collector utility functions
│   └── types.ts           # collector-specific types
├── hooks/
│   └── use-socket.ts      # React hook wrapping the collector core (TUI-only: storeMessage, parseContent, dedup)
└── components/
    ├── header.tsx          # status bar with tab navigation
    ├── chat-list.tsx       # sidebar with groups/DMs
    ├── message-feed.tsx    # message display with poll results and status icons
    ├── input-bar.tsx       # text input with commands
    ├── status-view.tsx     # incoming WhatsApp status updates
    ├── qr-view.tsx         # QR code renderer
    ├── stats-view.tsx      # statistics dashboard
    └── debug-view.tsx      # real-time event log

scripts/
└── extract.py             # payload extraction with poll decryption

Tech Stack

Links

Credits

Originally based on he4rt/wpp-tui by He4rt Developers. The original project was released under the ISC License. This fork contains significant modifications and additional features, released under GPL-3.0.

License

GPL-3.0