Skip to main content

Installation

The SQLite driver is pure Go (with FTS5 built in), so CGO_ENABLED=0 is the normal build — there is no C toolchain, no build tag, and no shared library to set up either way.

:::info CLI and desktop app are separate installs The CLI ships as a Homebrew formula; the macOS desktop app ships as a Homebrew cask under a different token. brew install msgbrowse gives you the CLI only — it does not install the .app. Homebrew forbids a formula from placing an application bundle in /Applications, which is exactly what casks are for. Install either or both. :::

Install the binary​

Homebrew (preferred)​

brew tap stump-wtf/tap
brew install msgbrowse
msgbrowse version

The formula builds from source, so the binary is compiled on your machine. It therefore never carries macOS's com.apple.quarantine attribute — no Gatekeeper prompt and no xattr -d com.apple.quarantine step, which is what you would hit with a downloaded prebuilt binary.

go install​

Requires Go 1.25+ and nothing else:

go install github.com/joestump/msgbrowse/cmd/msgbrowse@latest

The binary lands in $(go env GOBIN) (or $(go env GOPATH)/bin) — make sure that directory is on your PATH:

export PATH="$PATH:$(go env GOPATH)/bin"
msgbrowse version

:::note macOS Full Disk Access Reading live iMessage data means reading ~/Library/Messages/chat.db, which requires Full Disk Access for your terminal (System Settings → Privacy & Security → Full Disk Access). That is macOS TCC, a different mechanism from Gatekeeper — no install method avoids it. :::

Install the desktop app (macOS)​

The native app is a Wails v2 window over the same embedded web server the CLI serves — same pages, same handlers. On macOS it also bundles the three exporters and a Syncthing binary, so a fresh Mac with no Homebrew and no Python can export and import offline.

brew tap stump-wtf/tap
brew install --cask msgbrowse-desktop

That installs the universal .app to /Applications. To remove it:

brew uninstall --cask msgbrowse-desktop # app only
brew uninstall --zap --cask msgbrowse-desktop # also deletes your data dir

:::warning The cask strips quarantine on purpose The .app is ad-hoc signed (codesign -s -), not yet notarized with an Apple Developer ID. Homebrew quarantines cask downloads by default, and Gatekeeper then blocks not only the app but the exporter binaries it launches as subprocesses — so export and import break, not just the first launch. The cask's postflight runs xattr -dr com.apple.quarantine to prevent that. Installing by hand from the GitHub Release means running that command yourself. Both go away once notarization lands. :::

On Linux, download msgbrowse-desktop_linux_amd64.tar.gz from the release and extract it — the tarball preserves the execute bit. The binary links the system webview, so you need the WebKit2GTK runtime (sudo apt-get install libgtk-3-0 libwebkit2gtk-4.1-0 on Ubuntu 24.04+ / Debian 13). Windows is not built yet; use browser mode (msgbrowse serve) there.

Install the exporters​

msgbrowse reads archives produced by three upstream tools. Install whichever sources you have — any subset (Signal-only, iMessage-only, …) works fine.

Signal — signal-export, installed with pipx:

pipx install signal-export
tip

The pip package is signal-export, but the console command it installs is sigexport — that is the binary msgbrowse export looks up on your PATH.

iMessage — imessage-exporter, installed with Homebrew on macOS:

brew install imessage-exporter

WhatsApp — whatsapp-chat-exporter, installed with pipx (the console command is wtsexporter):

pipx install whatsapp-chat-exporter

msgbrowse never auto-installs these tools and never touches the sensitive sources (the Signal database, the macOS Keychain, chat.db, ChatStorage.sqlite) itself — it only spawns your own, already-installed exporters at your explicit request.

Alternative: Docker​

Prefer containers? The repo ships a Dockerfile and a compose stack — the image is a fully static binary on a distroless base, running non-root with a read-only root filesystem and all capabilities dropped.

git clone https://github.com/joestump/msgbrowse.git
cd msgbrowse
cp .env.example .env
# edit .env:
# MSGBROWSE_ARCHIVE_HOST → your archive's absolute path
# MSGBROWSE_LLM_BASE_URL → your LiteLLM proxy (…/v1), MSGBROWSE_LLM_API_KEY → its key

make up # build + start msgbrowse (points at your external LiteLLM)
make signal-import # import the signal-export archive into the local DB
make embed # compute embeddings for semantic search (optional)
# open http://127.0.0.1:8787

make logs tails the server; make down stops the stack. The archive is mounted read-only (:ro), app data lives in a named volume, and the UI is published to host loopback only.

tip

No LLM proxy yet? Run the bundled, fully local LiteLLM with make up-bundled and set MSGBROWSE_LLM_BASE_URL=http://litellm:4000/v1. Until an endpoint is reachable, embed and the journal fail — browsing and keyword search work without any LLM.

About the LLM endpoint​

msgbrowse only ever talks to your own OpenAI-compatible endpoint — configure it with MSGBROWSE_LLM_BASE_URL (a …/v1 URL) and MSGBROWSE_LLM_API_KEY via env, config.yaml, or flags (see the configuration reference for all keys and their precedence). The default is a local proxy at http://127.0.0.1:4000/v1. This endpoint is the only network egress in the entire application, and it is optional: everything except embed, facts, semantic search, and the journal works without it. The security model documents exactly what is sent to it.

Next step​

With the binary and exporters installed, produce your first archives: Exporting your archives.