Local-native WhatsApp memory in your terminal or browser.
wa-memory is an npm CLI that links WhatsApp through wacli, stores messages in local SQLite, embeds chunks via OpenAI for semantic recall, and answers questions with OpenAI. Raw WhatsApp data stays on your machine — only the chunk text needed for embedding/synthesis ever leaves it.
npm install -g wa-memory
wa-memoryThe first wa-memory run starts an interactive setup wizard.
The CLI guides you through:
- Connecting WhatsApp as a linked device (QR code or phone-code pairing).
- Entering an OpenAI API key, chat model, and embedding model.
- Running the initial sync.
- Opening a terminal chat.
For WhatsApp linking, the wizard runs the QR flow by default:
wacli auth --idle-exit 2m --follow --qr-format terminalOr skip QR with wa-memory setup --phone +<your-number> for phone-code pairing.
- Node.js 22+
wacliinstalled and paired by the setup wizard- OpenAI API key (used for both
text-embedding-3-smallrecall and chat synthesis)
Install wacli:
brew install steipete/tap/wacliwa-memory # setup if needed, then open chat
wa-memory setup # rerun setup wizard
wa-memory sync # sync WhatsApp messages again now
wa-memory sync-all # sync every WhatsApp chat without changing saved selection
wa-memory sync --backfill # ask wacli for older messages, then re-import selected chats
wa-memory sync --full-history # re-import all messages wacli can already export
wa-memory chats # choose which conversations are synced/indexed
wa-memory chat # open interactive terminal chat
wa-memory ask "question" # ask one question and exit
wa-memory search "query" # show retrieval evidence only
wa-memory wa <command...> # run any wacli command through wa-memory
wa-memory send --to <chat> --message "hello"
wa-memory send file --to <chat> --file ./photo.jpg --caption "receipt"
wa-memory react --to <chat> --id <message-id> --reaction "+1"
wa-memory presence typing --to <chat>
wa-memory fs ls /chats # list the read-only virtual memory filesystem
wa-memory fs grep "billing" # grep indexed WhatsApp chunks
wa-memory status # show local DB/sync status
wa-memory doctor # check node, wacli, WhatsApp, OpenAI, embedding status
wa-memory serve # start local web/API server on 127.0.0.1:8787Most commands that are useful for scripts support --json:
wa-memory sync --json
wa-memory status --json
wa-memory doctor --json
wa-memory ask "what did Riya say about Goa?" --json
wa-memory wa groups list --jsonStart chat:
wa-memory chatInside chat:
/sync sync WhatsApp messages again now
/status show local database status
/help show commands
/quit exit
You can ask normal questions directly:
wa-memory> what did we decide about the Goa trip?
Question answering embeds the retrieval queries in one batch, retrieves up to 50 vector matches per query, enriches them with exact virtual-filesystem grep hits, and for person or relationship questions also samples the matching direct chat thread when it exists. It then sends the combined context to one answer-synthesis call that privately compresses the evidence before returning the final answer. Normal chat output does not show vector hits, chunks, source counts, virtual paths, or retrieval details. Answers are formatted as terminal-friendly plain text instead of Markdown.
wa-memory can also delegate WhatsApp actions to the paired local wacli install. The broad escape hatch is:
wa-memory wa <wacli command...>Examples:
wa-memory wa contacts list --json
wa-memory wa groups list --json
wa-memory wa messages export --chat <jid> --output messages.jsonCommon write actions have shorter wrappers:
wa-memory send --to <chat-or-jid> --message "Running late"
wa-memory send file --to <chat-or-jid> --file ./receipt.pdf --caption "Receipt"
wa-memory send voice --to <chat-or-jid> --file ./note.ogg
wa-memory send sticker --to <chat-or-jid> --file ./sticker.webp
wa-memory react --to <chat-or-jid> --id <message-id> --reaction "+1"
wa-memory presence typing --to <chat-or-jid>
wa-memory presence paused --to <chat-or-jid>Action commands run wacli in write mode, because sending messages, reactions, and presence updates are intentional side effects. Normal sync, search, ask, chat, and virtual-filesystem commands remain read-only against WhatsApp except for explicit sync/backfill operations.
wa-memory exposes synced/indexed chunks as a read-only virtual filesystem. Each chat is a directory under /chats, each date is a subdirectory, and each chunk is a .txt file:
/chats/family/2026-05-06/100100-chunk-1.txt
Use it when you want agent-style exploration over the local vector/FTS store without exporting real files:
wa-memory fs ls /chats
wa-memory fs find /chats/family 2026-05
wa-memory fs cat /chats/family/2026-05-06/100100-chunk-1.txt
wa-memory fs grep -i "billing" /chats/family
wa-memory fs grep --regex "billing.*owner" /chats --jsongrep first asks SQLite FTS which chunks may contain the term, then reads only those virtual files and runs exact line matching in memory. wa-memory ask and wa-memory chat use this internally as an enrichment layer, bounded to a small number of exact hits so it can improve factual recall without flooding the answer prompt. All write operations are rejected as read-only.
The local server exposes the same primitives for browser or assistant clients:
GET /api/fs/ls?path=/chats
GET /api/fs/cat?path=/chats/family/2026-05-06/100100-chunk-1.txt
GET /api/fs/find?path=/chats/family&pattern=2026-05
GET /api/fs/grep?q=billing&path=/chats/family&ignoreCase=true
POST /api/wa
POST /api/send/text
POST /api/send/file
POST /api/send/voice
POST /api/send/sticker
POST /api/send/react
POST /api/presence/typing
POST /api/presence/paused
Run this any time you want to pull in new WhatsApp messages:
wa-memory syncOr from inside chat:
/sync
Sync is incremental. wa-memory remembers the latest local message timestamp per conversation and only asks wacli for newer messages on later runs.
To sync every WhatsApp chat once without changing your saved selected-chat list, run:
wa-memory sync-allwa-memory sync --all is the same mode. It keeps the normal incremental cursor behavior across all chats; combine it with --full-history or --backfill when you want the corresponding full-export or older-history behavior for every chat.
To request older messages from WhatsApp through wacli's on-demand history backfill, then import the full local export for your selected conversations, run:
wa-memory sync --backfillThis still respects your selected conversations. It runs wacli history backfill --chat <jid> for each selected chat, then ignores the saved per-chat timestamp and asks wacli for the full export for that chat. The import is idempotent: existing messages are skipped, and only missing messages are chunked/indexed.
To repair only a local cursor/import miss without asking WhatsApp for older history, run:
wa-memory sync --full-historyBoth modes require wacli to have at least one local message anchor for a chat before older on-demand history can be requested. Chats with no local anchor are reported as backfill failures, but the sync still imports anything wacli can export.
By default, one wa-memory sync --backfill requests up to 50 older messages per request, makes 3 requests per selected chat, and waits up to 90 seconds for each WhatsApp on-demand history response. Run it again to keep walking further back through a chat's history, or adjust the limits in config/env.
On the first interactive wa-memory sync, the CLI asks which conversations to sync. The top 25 conversations are preselected with checkboxes, and the Others option lets you add specific chat names case-insensitively. After that, the saved selection is reused. Use wa-memory chats any time to change it.
To limit sync/indexing to specific conversations:
wa-memory chats
wa-memory chats --add "Research group"
wa-memory chats --remove "Old group"
wa-memory chats --listAn empty chat allowlist keeps the CLI-compatible behavior of syncing all conversations. The browser UI always saves and sends an explicit conversation selection before starting sync.
Run the local web/API server:
wa-memory serveThen open:
http://127.0.0.1:8787
The browser UI talks to the same local Node server. There is no hosted proxy, tunnel, or remote message store.
When WhatsApp is connected, the browser shows the top 25 conversations as the initial sync set. You can tick or untick any of them, type a name under Others to add another conversation case-insensitively, and then sync only the selected conversations. While sync runs, the UI shows the current phase, chats processed, messages scanned from wacli, and new messages imported.
USER
|
+---------------+----------------+
| |
Terminal CLI Browser UI
wa-memory ... http://127.0.0.1:8787
src/cli/* src/web/*
| |
+---------------+----------------+
|
Local Node Server
src/local/server.ts
- serves dist/web
- handles /api/* aliases
- exposes local JSON API
|
v
Local Runtime
src/local/runtime.ts
|
+-------------------+-------------------+
| | |
v v v
WacliClient MemoryDatabase OpenAI Clients
src/local/wacli local SQLite embeddings + answers
| ~/.wa-memory/ |
| wa_memory.sqlite |
v | |
wacli linked | |
WhatsApp store | |
| | |
+------ sync ------>+<----- retrieval ---+
|
messages, chats, chunks,
FTS index, vectors, traces,
virtual FS, chat turns
all stay local
The setup wizard stores user configuration at:
~/.config/wa-memory/config.json
The local SQLite database defaults to:
~/.wa-memory/wa_memory.sqlite
Environment variables and ~/.wa-memory/.env still work for advanced use. They override saved config:
WA_MEMORY_DATA_DIR=~/.wa-memory
WA_MEMORY_DB=~/.wa-memory/wa_memory.sqlite
WACLI_BIN=wacli
WACLI_STORE_DIR=~/.wacli
WA_MEMORY_WACLI_EXPORT_LIMIT=10000
WA_MEMORY_WACLI_BACKFILL_COUNT=50
WA_MEMORY_WACLI_BACKFILL_REQUESTS=3
WA_MEMORY_WACLI_BACKFILL_WAIT=90s
WA_MEMORY_WACLI_BACKFILL_IDLE_EXIT=5s
OPENAI_API_KEY=
OPENAI_MODEL=gpt-5.2
OPENAI_ALLOWED_MODELS=gpt-5.2,gpt-5.2-mini,gpt-5.2-nano
OPENAI_EMBEDDING_MODEL=text-embedding-3-small- WhatsApp auth and message sync happen locally through
wacli. - Messages and chunks are stored in local SQLite, including
text-embedding-3-smallvectors as BLOBs. - Recall is a cosine top-K over the local vector store; nothing leaves your machine for retrieval.
- OpenAI sees chunk text at sync time for embeddings, then at query time to synthesize an answer from the top retrieved context.
- Without an OpenAI key,
wa-memoryfalls back to FTS keyword search and a local summary-style response.
npm install
npm run dev:cli -- --help
npm run dev:local
npm run typecheck
npm test
npm run buildBuild the package output:
npm run build
node dist/cli/index.js --helpFor frontend iteration, run the local server and Vite dev server in separate terminals:
WA_MEMORY_ALLOW_DEV_ORIGIN=true npm run dev:local
npm run dev:webThe dev-origin flag is only needed for the Vite proxy on port 5173. The packaged local UI is served from the same local origin as the API.
Before publishing or pushing changes:
npm test
npm run build
npx deepsec init .deepsec . --id wa-memory
cd .deepsec
pnpm install
pnpm deepsec scan --project-id wa-memory
pnpm deepsec report --project-id wa-memoryDeepSec writes its workspace under .deepsec/, which is ignored because it contains local audit state.
wa-memory sync --backfilldepends onwacli history backfill, which requests older messages from the primary device using WhatsApp's on-demand history sync. Ifwaclicannot get older rows for a chat,wa-memorycan only import whatwacli messages exportcan already see.- Node's built-in
node:sqliteAPI is experimental in Node 22; the CLI suppresses that warning, but the underlying requirement remains Node 22+.