Skip to main content

CLI reference

Every msgbrowse command runs locally against your read-only archives and the writable data_dir. No command performs network egress except embed, facts, mcp (semantic search), and doctor --check-llm — and all of those talk only to the single configured llm.base_url (a local endpoint by default).

Global flags

These persistent flags work on every subcommand and override the corresponding config keys (see Configuration):

FlagTypeDefaultDescription
--configstringsearch orderConfig file (default: ./config.yaml, ~/.config/msgbrowse/config.yaml, or /etc/msgbrowse/config.yaml).
--archive-rootstringfrom configPath to the signal-export archive (read-only).
--imessage-archive-rootstringfrom configPath to the imessage-exporter archive (read-only).
--whatsapp-archive-rootstringfrom configPath to the whatsapp-chat-exporter output (read-only).
--data-dirstringfrom configWritable directory for the database and caches.
--log-levelstringfrom configLog level: debug, info, warn, error.

msgbrowse export

Runs the upstream exporters msgbrowse reads from, so a fresh install can populate its archives in one step: sigexport into <archive_root>/export/ (per-conversation chat.md + media), imessage-exporter -f txt -c clone -o <imessage_archive_root> for iMessage, and wtsexporter with JSON output into <whatsapp_archive_root> for WhatsApp (which additionally needs a database to read — see the platform prerequisites, passed via --whatsapp-exporter-args). A source whose archive root is unset is skipped. msgbrowse never auto-installs the tools, stores no secrets, and reads no Keychain — the invoked exporters do, with your consent, and their output streams to your terminal.

msgbrowse export
msgbrowse export --skip-on-error
msgbrowse export --imessage-exporter-args="--start-date" --imessage-exporter-args="2024-01-01"
msgbrowse export -- --some-shared-flag # trailing args go to BOTH tools
FlagTypeDefaultDescription
--signal-export-binstringsigexport on PATHPath to the Signal exporter (or set the signal_export_bin config key).
--imessage-exporter-binstringimessage-exporter on PATHPath to imessage-exporter (or set the imessage_exporter_bin config key).
--whatsapp-exporter-binstringwtsexporter on PATHPath to the WhatsApp exporter (or set the whatsapp_exporter_bin config key).
--signal-export-argsstring arraynoneExtra arg for sigexport only; repeatable.
--imessage-exporter-argsstring arraynoneExtra arg for imessage-exporter only; repeatable.
--whatsapp-exporter-argsstring arraynoneExtra arg for wtsexporter only; repeatable (use it to pass the database/backup flags).
--skip-on-errorboolfalseLog and skip a failing or missing source instead of aborting; the run still exits non-zero if any configured source failed.
warning

iMessage export always runs in copy mode (-c clone) so attachments are copied into the archive. Without copy mode, the export records absolute ~/Library/... paths that msgbrowse cannot render — the exact trap msgbrowse doctor diagnoses. The exporter also needs Full Disk Access to read ~/Library/Messages/chat.db.

tip

The exporters' console commands differ from their package names: the Signal exporter's command is sigexport (pip package signal-export, pipx install signal-export) and the WhatsApp exporter's command is wtsexporter (pip package whatsapp-chat-exporter, pipx install whatsapp-chat-exporter). Install imessage-exporter with brew install imessage-exporter.

msgbrowse import

The all-in-one importer: runs the Signal, iMessage, and WhatsApp importers for whichever archive roots are configured (archive_root, imessage_archive_root, and/or whatsapp_archive_root) into one database, then transcodes non-web images (HEIC/TIFF) as a best-effort final step. A source whose root is unset is skipped; a source whose root is set but missing is an error. It does not embed — run msgbrowse embed separately, since that step needs the LLM endpoint.

msgbrowse import
msgbrowse import --full
FlagTypeDefaultDescription
--fullboolfalseIgnore incremental state and re-scan every conversation. Required once after upgrades that change how messages are parsed (e.g. reaction handling).

msgbrowse signal-import

Imports (or refreshes) a signal-export archive: scans the read-only tree, parses each changed conversation's chat.md into the unified SQLite store, and refreshes the encrypted-snapshot inventory. Incremental and idempotent — unchanged conversations are skipped, so re-running is cheap. Every row is tagged source="signal".

msgbrowse signal-import
FlagTypeDefaultDescription
--fullboolfalseIgnore incremental state and re-scan every conversation.

msgbrowse ingest still works as a hidden, deprecated alias for signal-import.

msgbrowse imessage-import

Imports (or refreshes) an imessage-exporter archive — the flat directory of <ChatName>.txt files produced by imessage-exporter -f txt (targets the 4.2.0 txt format). Incremental and idempotent; every row is tagged source="imessage". The path comes from imessage_archive_root / MSGBROWSE_IMESSAGE_ARCHIVE_ROOT / --imessage-archive-root.

msgbrowse imessage-import
FlagTypeDefaultDescription
--fullboolfalseIgnore incremental state and re-scan every conversation.

msgbrowse sync

The one-command refresh: chains every step that turns a fresh install into a populated, browsable archive, reusing the other commands end to end — export → import → media → embed → facts. The database is opened once and shared by every stage; a source whose archive root is unset is skipped, so single-source (or any subset) setups just work.

Error policy: export/import/media failures abort the run unless --skip-on-error. The embed and facts stages need the LLM endpoint and always warn-and-continue on failure, so a fully local run with no reachable LLM still completes export/import/media and exits successfully.

msgbrowse sync
msgbrowse sync --no-export # re-import existing archives only
msgbrowse sync --no-embed --no-facts
FlagTypeDefaultDescription
--no-exportboolfalseSkip the export stage (don't re-run the upstream exporters).
--no-mediaboolfalseSkip the media transcode stage.
--no-embedboolfalseSkip the embed stage.
--no-factsboolfalseSkip the facts stage.
--skip-on-errorboolfalseLog and continue past a failing export/import/media stage instead of aborting (run still exits non-zero).
--signal-export-binstringsigexport on PATHPath to the Signal exporter (as in export).
--imessage-exporter-binstringon PATHPath to imessage-exporter (as in export).
--whatsapp-exporter-binstringwtsexporter on PATHPath to the WhatsApp exporter (as in export).
--signal-export-argsstring arraynonesigexport-only extra arg, repeatable.
--imessage-exporter-argsstring arraynoneimessage-exporter-only extra arg, repeatable.
--whatsapp-exporter-argsstring arraynonewtsexporter-only extra arg, repeatable.

Trailing -- <args> are passed through to every upstream exporter.

msgbrowse serve

Runs the server-rendered HTMX web UI. Binds to loopback by default and opens your browser once the listener is up.

msgbrowse serve
msgbrowse serve --port 8888 --open=false
msgbrowse serve --listen-addr 127.0.0.1:9000
FlagTypeDefaultDescription
--listen-addrstringlisten_addr config (127.0.0.1:8787)Full host:port listen address; overrides --host/--port and config.
--hoststringconfigured hostBind host (e.g. 127.0.0.1 or 0.0.0.0); default keeps the configured host.
--portintconfigured portBind port (1–65535); default keeps the configured port.
--openbooltrueOpen the UI in your default browser on start; --open=false for headless runs.
warning

The UI has no authentication. Only expose it on a non-loopback address behind your own access control.

msgbrowse doctor

Read-only setup diagnostic. Runs checks over your configuration, data directory, archives, and imported attachment rows, printing one line per check ( pass, warn, problem) and a summary. Exits non-zero only if a check fails, so it is safe in scripts. Its headline check catches iMessage exports done without copy mode. See Troubleshooting for every check and its remediation.

doctor makes no off-machine network calls except an optional TCP-connect reachability probe (no data sent) of the configured llm.base_url, behind --check-llm. With device sync enabled it also reads the local sync engine's loopback REST API (engine running?, peers connected?, folder completion) — that traffic never leaves the machine.

msgbrowse doctor
msgbrowse doctor --check-llm
FlagTypeDefaultDescription
--check-llmboolfalseAdditionally TCP-probe the configured llm.base_url for reachability (the single configured egress; no data is sent).

msgbrowse devices

Manages device sync peers (Syncthing-based archive sync between your own machines). Pairing itself happens in the web UI's Settings page via device-ID QR codes; the CLI covers the registry and the operational verbs:

msgbrowse devices list                # paired peers, shared folders, roles
msgbrowse devices status # engine state, peer connections, folder completion
msgbrowse devices unpair XW4UY46 # sever a peer (full device ID or unique prefix)

unpair removes the device from this node's registry and — when the sync engine is running — removes its device entry and folder shares live, so sync to it stops immediately. Archives already synced to this machine stay on disk; only future synchronization stops. status and unpair reach the supervised engine over its loopback REST API only; nothing leaves the machine.

msgbrowse facts

Extracts AI facts about each contact from their messages using the configured chat model, storing atomic, cited facts (e.g. "Has a dog named Biscuit") that appear on the conversation page. Incremental: a per-conversation cursor means re-runs only analyze new messages, and facts are deduplicated per contact. Conversations on journal.exclude_conversations are never sent to the LLM. This command performs network egress to llm.base_url.

msgbrowse facts
msgbrowse facts --conversation 42
msgbrowse facts --reset
FlagTypeDefaultDescription
--resetboolfalseWipe all stored facts and cursors before running.
--batch-sizeint60Messages per extraction call.
--concurrencyint4Conversations processed in parallel.
--conversationint640Limit extraction to a single conversation id.

msgbrowse embed

Sends message text to the configured embedding model and stores the vectors for semantic search. Incremental: only messages without an embedding for the current model are processed. This command performs network egress to llm.base_url — point it at a local endpoint (the default) to keep message content on the machine.

msgbrowse embed
msgbrowse embed --prune
FlagTypeDefaultDescription
--pruneboolfalseRemove embeddings whose message no longer exists before embedding.

msgbrowse media

Converts image attachments browsers can't render (Apple HEIC/HEIF, TIFF) into JPEG derivatives cached under <data_dir>/derived, so the gallery and transcript display them. Incremental, and uses whatever converter is on PATH (sips, magick, convert, heif-convert); with none installed it is a no-op and the UI shows placeholders. import runs this step automatically.

msgbrowse media
msgbrowse media --force
FlagTypeDefaultDescription
--forceboolfalseRe-convert even if a derivative already exists.
--concurrencyint6Number of images to convert in parallel.

msgbrowse mcp

Runs the Model Context Protocol server so an MCP client (Claude Desktop / Claude Code) can query your archive with citation-faithful retrieval tools. Serves over stdio by default; logs go to stderr so they never corrupt the JSON-RPC stream. Semantic search embeds the query via llm.base_url — run msgbrowse embed first so message embeddings exist.

msgbrowse mcp
msgbrowse mcp --http --listen-addr 127.0.0.1:8788
FlagTypeDefaultDescription
--httpboolfalseServe over streamable HTTP instead of stdio.
--listen-addrstring127.0.0.1:8788HTTP listen address when --http is set.

msgbrowse journal

Rebuilds the day-by-day journal and optional LLM digests. Not implemented yet — the command, flags, and config surface are in place; the behavior lands in an upcoming slice.

FlagTypeDefaultDescription
--sincestringnoneOnly process days on or after this date (YYYY-MM-DD).
--backfillboolfalseProcess all days that lack a current digest.
--regenerateboolfalseRegenerate digests even if cached.
--dry-runboolfalsePrint day count and cost estimate; make no LLM calls.

msgbrowse watch

Re-ingests automatically when the archive changes (fsnotify). Not implemented yet. The watch: true config key wires the same behavior into serve once it lands. No flags.

msgbrowse version

Prints version information: version, commit, build date, and Go runtime.

msgbrowse version

No flags.