From ai-brain-starter
Ingests recent messages from WhatsApp chats (group or direct) into Obsidian vault as daily markdown files. Auto-creates Decision Log stubs for keywords like incident, outage. Invoke via /ingest-whatsapp <chat> [--days N].
npx claudepluginhub adelaidasofia/ai-brain-starterThis skill uses the workspace's default tool permissions.
Ingests recent WhatsApp messages (from a group chat or a one-on-one conversation) into the vault as markdown the graphify pipeline can read and the rest of the AI Brain Starter substrate (decision log, session-close cascade, hooks) can act on.
Ingests recent Slack channel messages into vault as daily markdown files in External Inputs/Slack. Auto-creates decision log stubs for keywords like incident, outage, pricing. Invoke via /ingest-slack <channel> [--days N] or ingest requests.
Interacts with WhatsApp via CLI: send messages, list chats, read conversations, manage accounts. Authenticates as companion device using QR code or pairing code for AI agents.
Sends WhatsApp messages to contacts or groups and searches/syncs chat history via wacli CLI. Useful for third-party messaging or accessing logs, not user chats.
Share bugs, ideas, or general feedback.
Ingests recent WhatsApp messages (from a group chat or a one-on-one conversation) into the vault as markdown the graphify pipeline can read and the rest of the AI Brain Starter substrate (decision log, session-close cascade, hooks) can act on.
This is one of a small family of ingestion connectors (slack, gmail, linear, github, notion, whatsapp). Adding the next external source means writing a new normalizer, not a new architecture.
WhatsApp is often the operator's primary team channel, so this connector ships with the same trigger-keyword scan and decision-stub pattern as ingest-slack.
/ingest-whatsapp <chat-name-or-jid> (with or without --days N)Do NOT use for:
send_message plus confirm_send)send_reaction, send_reply_quote)list_messages directly)WhatsApp content is one of the highest-PII surfaces in the vault, on par with Gmail and arguably more sensitive in tone. A WhatsApp chat carries real phone numbers as JIDs, given names from the operator's CRM, family conversation, friend group banter, contractor coordination, voice-note transcriptions, and the kind of blunt informal writing people produce when they assume a tight audience. Every ingestion writes a copy of that data into the vault.
Operator obligations:
humanizer and extract-rules-from-vault skills have scrub helpers; use them.If any of these obligations is unclear, do not run the skill. Ask first.
@s.whatsapp.net or @g.us), skip resolution. Otherwise, call search_contacts (for direct chats) or list_chats (for group chats) and match by name.oldest = now - N days (default N=7) as a Unix-seconds cutoff.list_messages(chat_jid, limit=200, include_crm_context=true). The MCP returns messages newest-first; filter to timestamp >= oldest in the orchestrator.## per message, reactions and media references included as bullet metadata).External Inputs/WhatsApp/<chat-slug>/<YYYY-MM-DD>.md.⚙️ Meta/Decisions/<YYYY-MM-DD>-whatsapp-<chat-slug>-<sha8(message_id)>.md.The skill is a thin orchestrator. The actual ingestion runs in Python at ${SKILL_ROOT}/ingest.py. The skill assembles the WhatsApp MCP tool calls, hands the raw payloads to ingest.py, and the script does the normalization, file write, and edge-case scan.
When invoked:
--days N (optional, default 7).@s.whatsapp.net or @g.us, treat it as a JID and skip resolution.search_contacts with the argument and inspect results. If exactly one contact matches, use that JID. If multiple match, list candidates and ask. If none match, fall back to list_chats(limit=50) and look for a chat name that matches the argument; if still ambiguous, ask the user.list_messages(chat_jid, limit=200, include_crm_context=true). The MCP returns the most recent messages first.timestamp >= now - N days. If the count exceeds 200, surface a note that the lookback window may have been clipped and suggest the user run with a smaller --days.ingest.py as JSON on stdin.ingest.py writes the vault file, the decision stubs, and prints a summary.The vault file at External Inputs/WhatsApp/<chat-slug>/<YYYY-MM-DD>.md has frontmatter:
---
type: external-input
source: whatsapp
chat_name: <verbatim chat name>
chat_jid: <jid>
chat_type: group | direct | broadcast
date_range: <YYYY-MM-DD>..<YYYY-MM-DD>
message_count: <int>
ingested_at: <ISO 8601 timestamp>
entity_ids:
whatsapp:
- <jid>
---
Body is chronological. Each message is a ## YYYY-MM-DD HH:MM <author> section with:
- **Media:** image, audio, document, etc.)- **Reactions:** emoji x N from <name>)_(empty message)_Decision Log stubs created at ⚙️ Meta/Decisions/<date>-whatsapp-<chat-slug>-<sha8>.md carry frontmatter that matches the existing decision schema so the aggregator picks them up cleanly.
Re-running /ingest-whatsapp <chat> --days N on the same calendar day overwrites the same vault file. The decision stub filenames hash the WhatsApp message ID, so the same source message produces the same stub filename across re-runs. Re-runs do not duplicate; they refresh.
chat-<sha8(jid)>.A successful run produces:
External Inputs/WhatsApp/<chat-slug>/<date>.md⚙️ Meta/Decisions/<date>-whatsapp-<chat-slug>-<sha8>.mdWrote N messages to <path>. Detected K edge cases. Stubs at: <paths>.If the chat resolves but contains no messages in the window, write the file anyway with message_count: 0 so re-runs are still idempotent and the absence is recorded.
ingest.py does not require the MCP. The MCP calls happen at the orchestration layer. If the LLM cannot reach the MCP, surface that error to the user, do not write a stub file.message_count: 0.chat-<sha8(jid)> as the slug; the JID still ends up in frontmatter.