A terminal user interface (TUI) for WhatsApp built with Go, Bubble Tea, and the whatsmeow library.
Note: This project stores WhatsApp session data and message history locally in SQLite databases (
whatsapp.db,messages.db). Treat these files as sensitive.
wha-cli lets you read and send WhatsApp messages from your terminal in a keyboard-driven UI. It connects to WhatsApp using whatsmeow (companion-device style login via QR code), renders a two-pane chat/messages experience with Bubble Tea + Lip Gloss, and persists chat/message data locally so the UI can load quickly on subsequent runs.
If you live in the terminal, switching to a phone/desktop app to reply to messages is disruptive. wha-cli provides a lightweight, distraction-minimizing interface to:
- See your available chats (contacts + selected groups)
- Open a chat and read recent history
- Send replies without leaving the terminal
- QR-based login on first run (session persisted in
whatsapp.db) - Chat list from WhatsApp contact store + joined groups
- Local caching of chats + messages in
messages.db(SQLite) - Loads recent history per chat (currently last 50 messages)
- Unread badge counter (tracked in local DB/UI)
- Automatic reconnect with backoff when the WhatsApp connection drops
- Receive and display incoming messages in real time
- Send text messages from the input panel
- Basic handling for non-text message types (rendered as placeholders like
📷 [Image],🎥 [Video], etc.) - Message deduplication at the database layer (
INSERT OR IGNOREon(id, chat_jid))
- Bubble Tea alt-screen UI with three focus areas: chat list, messages, input
- Keyboard navigation and message scrolling
- Timestamps per message and “older/newer messages” scroll hints
- Language: Go
- TUI:
github.com/charmbracelet/bubbletea,github.com/charmbracelet/lipgloss - WhatsApp client:
go.mau.fi/whatsmeow - Storage: SQLite (
github.com/mattn/go-sqlite3) - Config:
.envloader (github.com/joho/godotenv) - QR rendering:
github.com/mdp/qrterminal/v3
cmd/
main.go # Entry point: config + DB + WhatsApp + TUI wiring
internal/
client/ # whatsmeow wrapper, event conversion, chat filtering
config/ # reads contacts.env (ALLOWED_GROUP_*)
store/ # SQLite schema + chat/message queries
sync/ # background event loop + reconnect + Bubble Tea commands
types/ # shared types and Bubble Tea messages
ui/ # Bubble Tea model/update/view + rendering
startup
-> load config (contacts.env)
-> open messages.db (migrate schema)
-> open whatsapp.db (whatsmeow device/session store)
-> connect (QR on first login)
-> load chats:
messages.db first
else WhatsApp contacts + joined groups (filtered) -> persist to messages.db
-> start Bubble Tea program (TUI)
-> start background sync loop:
WhatsApp events -> persist -> send Bubble Tea messages -> update UI
- Go (see
go.modfor the configured version) - A working C toolchain for CGO (required by
github.com/mattn/go-sqlite3)- macOS: Xcode Command Line Tools
- Linux:
gcc/clang+ standard build tools - Windows: MSVC build tools
git clone "https://github.com/Dharshan2208/wha-cli"
cd wha-cli
go mod download
go build -o wha-cli ./cmd./wha-cliOn the first run, the app prints a QR code to the terminal. Scan it with WhatsApp (Linked Devices) to authorize.
By default, databases are created in the current working directory:
whatsapp.db: WhatsApp session/device store (managed bywhatsmeow)messages.db: app cache for chats/messages (managed by this project)
These files are ignored by .gitignore and should not be committed.
Tab: cycle focus (Chats → Messages → Input)↑/↓:- In Chats: move selection (with scrolling)
- In Messages: scroll older/newer messages
Enter:- In Chats: open selected chat (loads history, clears unread)
- In Input: send typed message
Esc: return to chat listBackspace: delete character (Input focus)q: quit (only when not typing)Ctrl+C: quit
On startup, the app loads contacts.env from the repository root (via godotenv.Load("contacts.env")).
Supported variables:
ALLOWED_GROUP_JIDS: comma-separated group JIDs (e.g.1203...@g.us)ALLOWED_GROUP_NAMES: comma-separated group names (case-insensitive)
Example:
ALLOWED_GROUP_JIDS=1212234567890@g.us,12036333028324722@g.us
ALLOWED_GROUP_NAMES=E204-Roommates,RandomInfoNotes / assumptions based on current code:
- Groups are shown only if they match an allowed JID or name.
- Contacts are shown only if WhatsApp has a display name for them (push name or full name). On fresh sessions, some contacts may be hidden until WhatsApp metadata is populated.
messages.db contains two tables:
chats: per-chat metadata (name, last message preview, unread count)messages: per-chat messages (id + timestamp + sender + text + from-me flag)
Key details:
messages(chat_jid)has a foreign key tochats(jid).- Messages are deduplicated with a composite primary key:
(id, chat_jid). - An index supports fetching recent history efficiently:
idx_messages_chat_jid (chat_jid, timestamp DESC).
Contributions are welcome.
Based on the current implementation:
- Message history loading is fixed to the most recent 50 messages per chat (no pagination UI yet, even though the store supports
GetMessagesBefore). - Sending is text-only (no media sending).
- Group listing is opt-in via
ALLOWED_GROUP_*; if you don’t configure it, groups won’t appear. - Contact filtering may hide contacts without populated names in the WhatsApp store on first login.
- Groups name is still not coming
- Groups not in .env file is still appearing in the chatlist
Ideas that fit the current design:
- Paginated history loading (“load older messages” using
GetMessagesBefore) - Search/filter in chat list
- Better contact/group metadata sync (names, avatars)
- Media preview improvements and/or media download
This project is licensed under the AGPL-3.0 License — see the LICENSE file for details.