
WA-TUI
WhatsApp Web as a keyboard-driven terminal UI — QR pairing, fuzzy search, typing indicators and read receipts, with no Electron window in sight.
Overview
What is WA-TUI
WA-TUI is WhatsApp Web as a keyboard-driven terminal UI. You pair once by QR, then live in a paged chat list with fuzzy search, quoted replies, media downloads, typing indicators in both directions, read receipts and native desktop notifications — with no Electron window anywhere. It installs through a Homebrew tap, npm or npx.
- Role
- Creator & maintainer — designed and built the TUI, the WhatsApp bridge and the release pipeline.
- Platform
- macOS · Linux · Windows (terminal)
- Stack
- Node.js · neo-blessed · whatsapp-web.js · Puppeteer
- Install
- brew · npm · npx
- Open source
- GitHub · CI on Node 18 / 20
Contribution
What I built
- 01Built a full chat client in the terminal: paged chat list with all / direct / group filters, unread-only toggle, three sort orders, quoted replies, media downloads and five colour palettes persisted to ~/.wa-tui.
- 02Wrote the fuzzy finder (Ctrl+K or /): every query term must match as a substring, with higher scores at the start of a field, on word boundaries and for longer terms — across chats and the open thread at once.
- 03Made presence real in both directions: outgoing typing is debounced to one pulse per 480 ms burst, peer typing and voice-recording states are read through a bridge injected into the WhatsApp Web page, and rows show ✓ / ✓✓ / read.
- 04Handled first run properly: WA-TUI installs its own Chromium with a progress bar on the boot screen, prunes stale cache folders first, and keeps the session so you pair once.
- 05Shipped three ways — a Homebrew tap, `npm i -g @gtchakama/wa-tui` and `npx` — with GitHub Actions running the Node 18 / 20 test matrix and cutting a release on every version bump.
How it works
How the data moves
The core paths through the system — a dot travels each hop, and the caption says what happens there.
Sending a message, with typing pulses
Keystrokes become one quiet typing pulse; ⏎ sends through the linked session and the row shows the ticks.
Swipe sideways to see the whole chart.
keysYou type in the composer at the bottom of the TUI.
From the code
A few lines that matter
function fuzzyMatchScore(query, text) {
const parts = searchQueryParts(query);
if (!parts.length) return 0;
const haystack = String(text || '').toLowerCase();
let score = 0;
for (const part of parts) {
const idx = haystack.indexOf(part);
if (idx === -1) return -1; // all parts must be substrings
// Prefer matches at the start or at word boundaries
if (idx === 0) score += 20;
else if (' -_./:@'.includes(haystack[idx - 1] || '')) score += 10;
score += Math.max(0, 10 - idx); // earlier matches score higher
score += part.length; // longer matching parts score higher
}
return score;
}Every term has to match; position, word boundaries and term length decide the order.
const OUTGOING_TYPING_DEBOUNCE_MS = 480;
function scheduleOutgoingTypingPulse() {
if (state.screen !== 'chatDetail' || !state.currentChatId) return;
clearTimeout(outgoingTypingTimer);
outgoingTypingTimer = setTimeout(() => {
outgoingTypingTimer = null;
void waService.pulseOutgoingTyping(state.currentChatId, state.currentRawChat);
}, OUTGOING_TYPING_DEBOUNCE_MS);
}Keystrokes reset the timer; the other side sees a calm "typing…" instead of a flood.
Decisions
Calls that shaped it
A terminal UI instead of another desktop window
The people who want this already live in a terminal; neo-blessed gives panes, colour and keyboard focus without shipping a browser shell.
whatsapp-web.js over a hand-rolled protocol client
Driving the real WhatsApp Web session in headless Chromium keeps multi-device, media and read receipts correct without reverse-engineering the protocol.
Debounce outgoing typing, don't pulse per keystroke
One typing pulse per 480 ms burst is what the other side expects and keeps the session quiet.
Read peer typing through an injected in-page bridge
Typing and recording states are not exposed by the library, so a small bridge in the page reports them back to the TUI.
Stack
- Node.js
- neo-blessed
- whatsapp-web.js
- Puppeteer
- GitHub Actions
- Homebrew