Repository navigation
Releases
v0.3.9
Fixed
- The readable-chat mode is now a standing choice, not a button that
appears in one state only. Removing the last allowed chat correctly
left an empty allowlist — agents able to read nothing — but the way back
to "every chat" was a plain button at the foot of an empty list, easy to
miss at exactly the moment it mattered. Both modes are now always on
screen with the current one marked, so leaving the empty state never
depends on finding a control inside it, and the warning banner names the
way out rather than only the problem.
v0.3.8
Added
-
Limit which chats agents may read. Connecting an MCP client used to
hand it every chat on the account.setupnow asks what connected
agents should see — every chat, only your own self-chat, or nothing
until you say so — and the choice is editable afterwards from the
dashboard's clients tab or the newscopecommand
(--allow/--deny/--all/--list).Once a limit is on, chat-addressed reads outside it are refused and say
the chat is out of scope rather than pretending it does not exist, so a
model stops asking instead of retrying. Questions asked across all chats
— global search,poll_new_messages, call history, contact search —
come back filtered; contact search included, because an agent that can
still enumerate your address book leaks exactly what the limit exists to
withhold. Enforcement wraps the store once rather than at each call
site, so a read tool added later cannot forget it.The limit is one setting for the whole server, not one per client: every
client authenticates with the same bearer token, so the server cannot
tell them apart. Only you can change it — no MCP tool writes
config.json, the same rule the trust list already follows. And an
empty allowlist means nothing is readable rather than everything,
which is why the mode is stored separately from the list;checknames
that state, since every read tool then returning empty looks exactly
like a broken install. The dashboard is deliberately exempt: it is your
own window onto your own messages. -
A clients tab in the dashboard. Every MCP client detected on this
machine, whether it is installed, whether this server is added to it,
and where its entry points — with one click to add or remove that entry.
A config the doctor calls broken can now be fixed without dropping to a
terminal. The broken flag repeats the doctor's own verdict rather than
inventing a second opinion.
Fixed
-
Pairing a second number no longer shows the first number's data.
Everything that belongs to a WhatsApp account — its messages, downloaded
media, backups, trust list, readable-chat allowlist and pending
scheduled sends — now lives underaccounts/<number>/, and only the
machine's own settings (rate limits, outbox roots, the HTTP token) stay
shared. Switching numbers is clean in both directions: pair the first
one back and its history and settings are exactly as they were.The worst case this closes was not the visible one. A pending scheduled
send sat in a single shared file, so a message scheduled under one
number would have fired from whichever number happened to be paired when
its time came — a real message, to a real person, from an account they
did not expect.Separation is by file rather than by a column every query has to
remember: two accounts cannot see each other because their rows are not
in the same database, including from code written long after this. The
upgrade moves an existing install into the new shape by renaming files,
never rewriting rows — deliberately, because adding an account column to
the existing tables would mean rebuilding them, and that renumbers
SQLite rowids, which here are both the full-text index's link to its
content and the cursorpoll_new_messageshands out. Renumbering them
would have silently corrupted search results and made agents skip
messages they never received.
Changed
trustandscopetake a name or a phone number, not just a JID.
Nobody knows their friend as15551234567@s.whatsapp.net, and the old
failure mode was silent: a mistyped JID was accepted, stored, and simply
never matched anything. Both now accept a contact or group name, a phone
number in any of the shapes people paste, or a JID. A name matching
several people is never guessed at — the CLI lists the matches and asks,
the dashboard shows them as a chooser, and nothing is written until one
is picked. The same goes for the dashboard's trust and readable-chat
boxes.
v0.3.7
Fixed
- Reactions now sit on the message they react to, as a small pill on the
bubble's lower edge, instead of floating as their own[reaction]
bubbles — the way the real app shows them. One pill per person (a newer
reaction from the same person replaces their older one, and an un-react
removes it), with a count when several people used the same emoji. - Images opened from the chat pane now scale to fit the screen. The
lightbox sized the image against a content-derived box, so a tall or
large picture overflowed the viewport; it is now bounded to the viewport
in both dimensions and always fits.
v0.3.6
Fixed
- The chat pane now shows a chat's newest messages again. It paginated by
database rowid (insertion order), which assumed rowid order matched time
order — but a chat can hold a history-sync backfill whose rows were
written long after the moments they record, and the LID fold in 0.3.4
merged chats whose rows interleave in time. The result was an active
chat opening on months-old backfilled messages instead of today's. The
pane (initial load and in-place refresh) is now ordered by timestamp.
No data was lost — only the view's ordering was wrong. The
poll_new_messagesMCP tool keeps its rowid cursor, which is correct
for an agent reacting to newly ingested messages.
Added
- Scroll up in a chat to load older messages — the pane fetches further
back a page at a time and keeps your place as it grows, instead of only
ever showing the newest ~50. - An "↑ older" control in the chat header asks your paired phone for
messages from before the oldest one stored — the way to pull a chat's
history in from scratch, one page at a time. It requests your own
history from your own phone (it messages nobody) and is rate-limited by
two cooldowns — one per chat, one global — so a held button or a script
can't turn into the burst of history requests that risks a number being
flagged; a cooldown shows as a short "try again in Ns" note, not an
error.
v0.3.5
Added
- Message search in the dashboard now works the way a chat app's does:
results arrive as you type (no button), listed in the chat list's
place with the chat's name, who said it, a snippet with the matches
highlighted, and when. Clicking a result opens that chat parked on the
message — scrolled to it and ringed — with a "latest" control to return
to the live tail. With a chat open, results can be narrowed to it. - Pictures in the chat pane open full-size on click, brought forward over
the blurred page; click outside, the close control, or Escape returns.
Fixed
- The pair tab no longer shows the "Start pairing" button alongside the
linked panel when this server is already paired (clicking it only ever
produced "already paired").
v0.3.4
Fixed
- One person is one chat again. WhatsApp increasingly addresses people by
a privacy LID (...@lid) instead of their phone number, and the same
contact could show up twice in the chat list — a phone-number chat with
the history and a nameless LID chat with the newest messages — with the
LID half reading asuser …12345. Every LID the server can pair with a
phone number (from the message itself, or from the pairing table
WhatsApp syncs to the device) is now keyed on the phone number as it is
stored: chats, senders, contacts, read receipts and calls alike. What was
stored split by earlier versions is folded together on the next start,
and again after every history sync, so pairings that arrive late still
take effect. LIDs WhatsApp never pairs stay as they are. - History-sync stubs — the status lines WhatsApp keeps in a chat with no
message behind them — no longer become empty[other]rows, or the
phantom LID chats made of nothing else. Existing ones are dropped on the
next start.
v0.3.3
Fixed
serve --httpno longer gives up after one failed connect at startup.
A service started at login often beat the network to it: with DNS not
yet answering, the single connect attempt failed, and since WhatsApp's
auto-reconnect only covers a connection that once existed, the process
sat paired-but-offline until someone restarted it — with the dashboard
reading "connecting" the entire time. The startup connect is now
retried with a doubling backoff (5s up to a minute) until it lands, each
failure logged with the wait before the next attempt, and a failed
attempt now reportsofflinerather than a permanentconnecting.
v0.3.2
Added
- Refresh buttons on the dashboard's chat screen: one fetches only the
messages that arrived since the pane was loaded (appending them in
place, following the scroll only if you were already at the bottom)
and one reloads the chat list. No background polling — updates load
on demand. - Profile pictures in the dashboard's chat list, served through the
server itself (the page still makes no external requests). Lookups are
cached for a day — including the "no picture" answer — and live
fetches are rate-capped, so rendering the list never turns into a
burst of WhatsApp queries. The initial-letter avatar remains the
fallback whenever no picture is available. - Media in the dashboard's chat screen: images and stickers preview
inline (lazily, so only what scrolls into view is fetched), and every
other attachment gets a download link. Files are served from the same
per-chat media directorydownload_mediauses, so each file is
fetched from WhatsApp at most once, however many times it is viewed.
Content types are verified server-side: only raster images ever
render inline, and everything else — including SVG and HTML — comes
down as an opaque attachment, so WhatsApp-supplied content can never
run script on the dashboard's origin.
v0.3.1
Fixed
- The dashboard UI is no longer cached by the browser: after an upgrade,
a previously opened dashboard could keep showing the old interface
(e.g. the pairing controls while already paired) until a hard refresh.
The Pair tab also renders the correct linked/unpaired panel instantly
from the last known state, and "Paired." only announces a pairing you
actually watched complete.
v0.3.0
The personal-robustness release: the server heals itself after a
WhatsApp-side logout, Windows gets service support, messages get a backup
command — and a local web dashboard arrives, with QR pairing in the
browser, WhatsApp-style chat browsing, trust/schedule/draft management,
and one-click unlink.
Added
- New
logoutcommand and an Unlink button on the dashboard's Pair tab:
both sign this device out on WhatsApp's servers (the phone stops listing
it under Linked devices), keeping local messages and settings, and reset
to unpaired so a new QR pairing can start immediately.removeremains
the local-only variant that never contacts WhatsApp. - Chats with no stored name now resolve a display name through the
contacts table and lid mapping — in the dashboard, inlist_chats/
get_chat, and in send previews — and the dashboard falls back to a
readable "+number" instead of a raw JID. Empty-bodied messages (system
events, removed reactions) render as dim kind placeholders instead of
blank bubbles, and a signed-out dashboard now says so in its header
instead of freezing on the last live state. - The dashboard now shows designed signed-out and not-found pages that say
how to get back in (runwhatsapp-connect-mcp dashboardfor a fresh
login link), unknown API paths answer with a JSON 404, and a browser
opening the bare server address is redirected to the dashboard. - The dashboard's chat window now looks and scrolls like WhatsApp Web's
dark theme: independently scrolling chat list and message pane, message
bubbles with in-bubble times, day dividers, and per-sender colors in
groups. The whole dashboard adopts WhatsApp's dark palette, with
loading skeletons and clearer action feedback throughout. - New
backupcommand: writes a consistent snapshot of the message
database to<data-dir>/backups/(or--dest), safe to run while the
server is up. Message history is the one thing re-pairing cannot
recover. service install|uninstall|restartnow works on Windows, using a Task
Scheduler logon task. The server runs as a minimized console window;
closing it stops the server. Creating the task may require an
Administrator terminal.- Connection-health diagnostics: dropped connections, keepalive timeouts,
refused connects, temporary bans, and an outdated client now each print
one clear line to the server log instead of passing silently. - Failed writes of incoming events to the message store are now counted
and reported (previously silent). check(and the doctor tool) gains aningestfinding that fails when
incoming events are not reaching the message store, and the session
finding now says when the server was logged out by WhatsApp mid-run.statusnow shows connection state, last event time, reconnect count,
and ingest-failure count.- A local web dashboard on the shared HTTP server: open it with the new
dashboardcommand (prints a one-time login link). This first slice
shows connection health, store counts, and doctor findings; pairing and
management arrive in following changes. Localhost-only, protected by
the same bearer token as the HTTP transport. - QR pairing from the dashboard: when the session is unpaired, the Pair
tab shows the QR code in the browser — no terminal needed, including
re-pairing after a WhatsApp-side logout. - Dashboard: read-only chat browsing and full-text search. Media messages
show as placeholders; the dashboard never fetches media. - Dashboard: trust-list management (applies to a running server
immediately), scheduled-send list/cancel, and a one-click backup. - Dashboard: pending send drafts can be reviewed, approved, or discarded.
Approving delivers the exact content the agent drafted (rate-limited as
always); a draft can be committed at most once no matter how the
dashboard and the agent race.
Changed
serve --httpnow starts its listener before waiting for pairing, so
the server is reachable while unpaired: store-backed read tools serve
local data, and live/send tools report "no longer paired" until pairing
completes. stdio serve still waits for pairing before starting.
Fixed
- A WhatsApp-side logout no longer leaves a running server permanently
broken: the server now says clearly that it was logged out, waits, and
reconnects on its own as soon as the device is paired again — no restart
needed. Tool calls while unpaired fail with "no longer paired — run
setup" instead of a generic connection error. - A session-store failure while waiting for pairing (at startup, or after
a logout) is no longer mistaken for a clean shutdown: it now prints a
diagnostic and exits non-zero at startup, so a service manager restarts
the server, and after a logout it says the re-check failed instead of
going silent. - Dashboard: a failed pair start, trust change, schedule cancel, backup,
or draft approve/discard now shows a message in that tab instead of
failing silently. - Dashboard: starting pairing only accepts POST, matching every other
mutating dashboard route.