Read your own WeChat history from the command line, and give an AI only the parts you choose.
A Mac app and CLI. Everything stays on your machine.
Install · Get your key · Usage · Docs · Contribute
I have 1,855,548 WeChat messages on this Mac, and my AI tools cannot read one of them. They sit as 2.98 GB of SQLCipher databases including 6,292 message tables, with about 59 GB of images, voice notes and documents beside them. WeChat can open all of it. It does not offer that context to other tools; the matching database key lives inside the running client rather than in an export you can request.
The export tools that exist solve the wrong half of this. They decrypt the database and write the whole corpus to JSON — which is exactly the artifact you should least want sitting on disk, and exactly the wrong thing to hand a model. A full dump is not context. It is a liability with a search box.
GreenBubbles takes the opposite approach: leave everything where it is, and open it read-only.
- Read your history from the shell. List chats, page through messages, search, fetch one message, pull out one photo or file.
- Browse it in a Mac app. Native, read-only, works on live data or a backup.
- Give an AI a slice, not the lot. Pick the chats, the fields and the dates it may see. Everything else never leaves your machine.
- Back it up so it outlives WeChat. Encrypted with its own key and 24 recovery words, so a backup still opens if you lose the app or the account.
- Turn your history into a living knowledge project. Extract into a git-versioned Python or Markdown project. The Python format carries executable constraints that proactively alert you to cross-domain conflicts — passport expiry vs. upcoming trip, allergy vs. new prescription, conflicting instructions across sessions; the Markdown format keeps the same alerts as notes you maintain by hand.
- Turn a million messages into a wiki. Coding agents you already pay for read your history in parallel and write cited Markdown, one message at a time, never a summary of a summary.
- Never writes. WeChat's own files are opened read-only, always.
macOS 14 or later, Apple silicon.
Download the latest GreenBubbles-*-macos-arm64.dmg from
Releases, verify it, then
drag GreenBubbles to Applications:
grep ' GreenBubbles-0.3.0-macos-arm64.dmg$' SHA256SUMS-0.3.0.txt | \
shasum -a 256 -c -
xcrun stapler validate GreenBubbles-0.3.0-macos-arm64.dmgEvery executable is Developer ID signed and Apple notarized. The same release
ships greenbubbles-*-macos-arm64.zip with the full command-line tool set.
Build from source
Needs Swift 6, the macOS developer tools, and Rust:
git clone https://github.com/bojieli/greenbubbles.git
cd greenbubbles
cargo build --locked --release --manifest-path Native/GreenBubbles/Cargo.toml
swift build --product greenbubbles-history
swift run greenbubbles-historyThe CLI lands at Native/GreenBubbles/target/release/greenbubbles. Point the
app at it when it asks.
WeChat encrypts its databases with a key it derives at login and keeps to itself, so the first step is capturing a copy from your own running client. Three commands, about a minute.
# 1. Let a debugger attach to your copy of WeChat, then restart WeChat.
sudo codesign --force --deep --sign - /Applications/WeChat.app
# 2. Check everything the capture needs is in place.
sudo greenbubbles-acquire preflight
# 3. Arm the capture, then log out of WeChat and log back in.
sudo greenbubbles-acquire captureThe logout in step 3 is the trick: WeChat derives its database keys only while
opening them, which happens at login. capture waits for that moment, reads
the key, and checks it against every database before saving it — on a recent
run, 26 of 26 in 45 seconds.
The key lands in ~/.greenbubbles-acquire/passphrase.txt. Point a
query profile at it once and you never type it again.
Two things worth knowing: step 1 replaces Apple's signature on WeChat until you reinstall or it updates, so repeat it after an update; and the key is stable, so you only capture once.
For more detail — the full mechanism, every failure mode, and what to do when preflight blocks — see acquiring your database key.
# what conversations are there?
greenbubbles conversations list --limit 20
# read one
greenbubbles messages list --conversation <id> --limit 50
# search everything (the term goes in on stdin, so it stays out of your history)
greenbubbles messages search --query-stdin
# who is in your address book?
greenbubbles contacts list --limit 50
# one exact message, and a photo from it
greenbubbles message get --conversation <id> --message <id>
greenbubbles attachment inspect <account-root> \
--conversation <id> --message <id> # see what attachments a message has
greenbubbles attachment materialize <account-root> \
--conversation <id> --message <id> --kind image \
--attachment <id> --output ~/photo.jpgThese local query commands print JSON and exit. They need no daemon or index and upload nothing. Results are paged, so a query returns one screenful rather than your whole history, and each result carries an id you can look up again.
Prefer a window? Open the app and choose Browse Live or Snapshot…, then
pick the db_storage folder for your account. Not sure where that is:
greenbubbles-discover accounts --include-pathsWrite a policy that names the conversations and fields an assistant may see, then let it query through that:
greenbubbles connector-policy-direct <db_storage> policy.json <chat-id>... \
--capabilities list,read,search --fields sender,created-at,content \
--allow-remote-model --passphrase-stdin
greenbubbles connector-query-direct <db_storage> policy.json audit.ndjson \
request.json --passphrase-stdin
GEMINI_API_KEY=... greenbubbles ai-summarize-direct \
<db_storage> policy.json audit.ndjson new-memory-generation \
--requester my-memory-agent --passphrase-stdinAnything outside that policy is refused, every request is logged, and text
inside a message can never widen what the assistant is allowed to read.
The summary command invokes Gemini 3.7 Flash and publishes actual structured
and readable memory, not merely a transcript export. Its model input uses
compact M### evidence aliases; exact canonical message IDs remain in a
private sidecar for citation verification.
There is an agent skill in skills/ that
teaches a compatible assistant to use this properly.
See the AI context guide for the full surface.
GreenBubbles exposes a separate replica query family for AI tools that need structured, policy-scoped access without touching the live database.
# Apply a scope policy to the replica, list recent messages, and search
greenbubbles tool-policy replica.db policy.json <chat-id>... # set scope
greenbubbles tool-list replica.db policy.json # list conversations
greenbubbles tool-recent replica.db policy.json # recent messages
greenbubbles tool-search replica.db policy.json # search
greenbubbles tool-draft replica.db policy.json # non-executing draftai-query getChanges provides a change-feed for incremental consumer sync —
useful when an AI tool needs to stay current with the replica without polling
the full history. ai-export produces static interchange and audit bundles for
offline analysis or archival. ai-memory-export produces QMD/Mem0 projections
with checkpoint IDs and greenbubbles:message:<id> citations that link every
inferred fact back to its source message. The replica tool policy uses
account-scoped one-way hashes that are distinct from the source-bound direct
connector policy, so replica access can be granted and revoked independently.
See docs/AI_CONTEXT_CLI.md for the full surface.
GreenBubbles extracts your message history into a self-evolving software project — typed Python dataclasses with executable constraints, or structured Markdown — following the UserAsCode methodology. Memory is organized by life domain (identity, travel, finance, health, and more) and CRUD-patched incrementally: new facts are added, changed facts are corrected in place, unchanged facts are skipped. The project is git-versioned so every update is diffable and reversible.
# Prepare the corpus once (local, no API cost)
greenbubbles memory prepare /private/path/corpus-v2 \
--selection-policy /private/path/selection-policy.json --profile live-account
# Run an incremental extraction pass into a Python knowledge project
python3 scripts/personal-memory-parallel.py tick \
--corpus /private/path/corpus-v2 \
--user-project ~/memory/me \
--format python \
--agent gemini --model gemini-3.8-flash
# When new messages arrive, extend the corpus, then tick against the new one
greenbubbles memory prepare /private/path/corpus-v3 \
--extend /private/path/corpus-v2 \
--selection-policy /private/path/selection-policy.json --profile live-account
python3 scripts/personal-memory-parallel.py tick \
--corpus /private/path/corpus-v3 \
--user-project ~/memory/me \
--format python \
--agent gemini --model gemini-3.8-flashThe first tick creates ~/memory/me/ as a git repo and processes the full
corpus. Subsequent ticks process only new messages since the last run. Cadence
is user-configured — see docs/PERSONAL_MEMORY.md for
a cost table.
This step sends message text off your machine. Preparation is local, but
extraction is not: tick runs a coding agent — --agent gemini, claude,
codex, pi or your own command — and every page of chat text it reads goes
to whatever model that harness talks to, under that provider's terms.
GreenBubbles makes no request itself and cannot audit what happens next.
Gemini 3.8 Flash is the recommended model; with --agent claude, codex or
gemini and no --model, the harness uses whatever model it is already
configured with. The prepared corpus is equally sensitive — it can duplicate
every eligible message into its own index — so keep it, and the selection
policy, in an owner-only directory and protect them like the source database.
The Python project looks like this after the first pass:
# ~/memory/me/manifest.py
DOMAINS = {
"identity": "Name, DOB, passport | updated 2026-01-20",
"travel": "2 upcoming trips; passport expires 2026-06-01 | updated 2026-01-20",
"health": "Allergies: peanuts; Rx: cetirizine | updated 2025-12-01",
}
ACTIVE_ALERTS: list[str] = [
"[CRITICAL] travel_readiness: Passport expires 2026-06-01, "
"Singapore trip departs 2026-06-15 (only 14 days validity)",
]The equivalent Markdown manifest, where the same alerts are notes the agent writes rather than output of a constraint it can execute:
# Personal Memory Manifest
## Active Alerts
- [CRITICAL] travel_readiness: Passport expires 2026-06-01, Singapore trip departs 2026-06-15See docs/PERSONAL_MEMORY.md, format-python reference, and format-markdown reference.
| User guide | Setup, browsing, backups, recovery |
| FAQ | What goes wrong, and why |
| CLI reference | Every command |
| Giving an AI access | Policies, exports, memory tools |
| Living knowledge project | UserAsCode extraction: corpus, formats, tick, manifest-refresh, revise |
| Replica operations | Replica lifecycle, sync, and recovery |
| AI memory integration | QMD/Mem0 projections, citations, change-feed |
| Backups | The 24 words, rotation, recovery drills |
| Architecture | How it works inside, and why |
| Known limitations | What is unproven or broken |
Everything else is indexed in docs/.
A research alpha for technical users. Reading, searching, browsing, backups and the AI boundary all work and are tested. WeChat's format is closed and changes, so anything GreenBubbles cannot decode is reported as a gap rather than guessed at. Only Apple silicon is released. Sending is in the source but ships closed and cannot be switched on in a public build.
Details in known limitations, with every performance number and its evidence in measurements.
Yes please — see CONTRIBUTING.md. The most useful report is a message type or table GreenBubbles reads incompletely, described structurally with no message content attached. Security issues go to SECURITY.md, never a public issue.
Tests run on synthetic data only. Never add a real database, message, key or path to this repository.
MIT — see LICENSE. Binary releases include third-party notices.
GreenBubbles is an independent project, not affiliated with or endorsed by Tencent. WeChat and other product names are trademarks of their respective owners.