A family noticeboard fed by WhatsApp. Messages in a family chat land on a private website. Two sources, both without a third party in the path: the WhatsApp Desktop app’s own message store on a Mac (personal account, read-only), or Meta’s official Cloud API (a dedicated house number). Zero runtime dependencies, one process.
See plans/PLAN.md for the design, the block contracts, and setup steps.
npm install # dev tooling only (TypeScript, Biome)
node scripts/probe-whatsapp-desktop.mjs # confirms the store is readable
node bin/house.js chats # pick the chat(s) to read
cp .env.example .env # set WHATSAPP_DESKTOP_CHATS=Family and ALLOWED_SENDERS=...
node bin/house.js pull # read new messages once
node bin/house.js serve # open http://127.0.0.1:8080
ALLOWED_SENDERS=local node bin/house.js post --from local --name Me "Dinner at 7"
ALLOWED_SENDERS=local node bin/house.js serve
node bin/house.js ingest test/fixtures/whatsapp-mixed.json # a recorded Cloud API payload
| Command | What it does |
|---|---|
house serve |
Read the WhatsApp Desktop store and/or receive Cloud API webhooks, and serve the site |
house chats |
List chats in the WhatsApp Desktop store, to choose WHATSAPP_DESKTOP_CHATS |
house pull [--reread] [--rewind N] |
Read new WhatsApp Desktop messages once; --reread re-maps stored rows, --rewind N moves the cursor back (0 = start) |
house post [--from ID] [--name NAME] TEXT |
Ingest a text message as if it had arrived |
house ingest FILE |
Ingest a recorded webhook payload, a message, or a list of messages |
house replay |
Re-run all handlers over the ledger (after changing a rule) |
house messages [--limit N] |
Print stored messages |
house items [--limit N] |
Print published items |
house publish [--force] [--check] |
Mirror the site into Cloudflare KV now; --check only verifies access |
Configuration is read from the environment and ./.env (or $HOUSE_ENV_FILE).
Every key is documented in .env.example.
| Path | Purpose |
|---|---|
/ |
The feed, newest first, live-updating |
/api/items?limit=&before= |
The same as JSON |
/media/<file> |
Downloaded photos, videos, audio, documents |
/events |
Server-sent events, one change event per published item |
/healthz |
Liveness, unauthenticated |
Set SITE_USER and SITE_PASSWORD to require HTTP Basic auth on everything
except the webhook and /healthz.
The house can mirror the site into a Cloudflare Workers KV namespace, so the family can read it from anywhere. A 60-line Worker of ours serves the page, the item list and the media from KV behind a password. Everything used is on Cloudflare’s free plan and needs no card: KV (100,000 reads and 1,000 writes a day, 1 GB) and Workers. Media over 25 MiB stays on the Mac.
One-time setup on the Mac, in the repo:
npx wrangler login # opens a browser
npx wrangler kv namespace create HOUSE # prints an id: paste it into wrangler.jsonc and .env
npx wrangler deploy # prints https://house.<you>.workers.dev
npx wrangler secret put SITE_USER # the family login; the Worker serves nothing until both are set
npx wrangler secret put SITE_PASSWORD
Then put CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_KV_NAMESPACE_ID and an API token
with “Workers KV Storage: Edit” into .env (see .env.example), and:
node bin/house.js publish --check # verifies the token and namespace
node bin/house.js publish # writes the page, items.json and media
node bin/house.js serve # from now on, publishes a minute after each change
What goes to Cloudflare: the published items (author, text, time), the media
files they reference, and the page. The ledger and everything the rules do
not publish stay on the Mac. Redeploy the Worker only when cloudflare/worker.js
changes; the page itself lives in KV and is republished by the house.
scripts/check.sh # typecheck + lint + tests; the gate every change must pass
npm test # tests only
Node 24 runs the TypeScript directly; there is no build step. Biome ships a
per-platform binary, so if node_modules was installed on another machine the
gate falls back to one under ~/.local/biome/node_modules.
src/core message model, ledger, content store, pipeline, config, log
src/http tiny router and response helpers
src/sources whatsapp-desktop/ (macOS store reader), whatsapp/ (Cloud API), file (dev)
src/handlers rules that turn messages into items (publish)
src/site HTML rendering, routes, basic auth, the static page
src/publish Cloudflare KV client and the publisher (mirror to the cloud)
cloudflare/ the Worker that serves the mirror; wrangler.jsonc at the root
src/app.ts composition root
src/cli.ts commands
test/ node:test suites and recorded webhook fixtures
data/ SQLite database and media (gitignored)