| last_edited | 2026-09-15 |
|---|---|
| title | CLI Reference |
| description | Complete command reference for all msgvault commands. |
Find a command by task below, or use msgvault COMMAND --help for the flags
in your installed binary. This reference follows current main; see
the changelog for the release boundary.
| Task | Commands and guides |
|---|---|
| Add and sync a source | Choose a source, sync, sync-full |
| Import local exports | import-eml, import-mbox, import-maildir, import-emlx, import-pst, import-slackdump, text imports |
| Search and browse | search, tui, show-message, documents, embeddings |
| Maintain people and contacts | person, people guide, CardDAV |
| Organize accounts | identity, collection, update-account |
| Export | export-messages, export-eml, export-attachments |
| Review and remove mail | stage-delete, delete-staged, deduplicate, gc |
| Back up and manage attachment storage | backup, pack-attachments, purge-excluded-media |
| Repair older records | repair-identity, repair-senders, repair-message, repair-derived, repair-labels, repair-list-ids, repair-dates |
| Operate or integrate | setup, daemon, serve, mcp, query, openapi, agent-token |
| Flag | Description |
|---|---|
--config |
Path to config file (default: ~/.msgvault/config.toml) |
--home |
Home directory for all data (overrides MSGVAULT_HOME) |
-v, --verbose |
Verbose output (implies --log-level=debug) |
--local |
Use the local daemon instead of a configured remote for archive-access commands |
--log-file <path> |
Override log file path (default: <data_dir>/logs/msgvault-YYYY-MM-DD.log) |
--log-level <level> |
Log level: debug, info, warn, error (default: info) |
--no-log-file |
Disable file logging for this run (stderr output stays on) |
--log-sql |
Log every SQL query at info level (verbose, for debugging) |
--log-sql-slow-ms <ms> |
Slow query threshold in ms (default: 100; 0 uses built-in default) |
--help |
Show help |
--agent-url <url> |
Daemon base URL for delegated mode; must be supplied together with --agent-token-file |
--agent-token-file <path> |
Path to a file containing a restricted agent token secret; must be supplied together with --agent-url |
--agent-allow-insecure |
Allow a plain-HTTP --agent-url; rejected by default |
Commands that access archive state keep their usual stdout/stderr output while using the same API path as remote access:
- If
[remote].urlis configured and--localis not passed, the CLI talks to that remote server. - Otherwise, archive-access commands discover or start the local background daemon and talk to it over HTTP.
--localselects the local daemon even when[remote].urlis configured; it is not a request to open SQLite in the CLI process.- When
--agent-urland--agent-token-fileare both supplied, the CLI connects to that remote daemon as a restricted delegated caller using the token from the file. Onlydraft-replyis available in this mode. Owner configuration (--config,--home,--local) is rejected, and the token is never written to logs or argv. The token is transmitted in theX-Msgvault-Agent-Tokenrequest header; this header is not modeled in the generated OpenAPI clients — it is a transport detail that the CLI handles internally.
This makes local and remote msgvault behavior the same from the CLI's point of view and avoids opening a large SQLite database from foreground CLI processes.
The local daemon publishes its binary version and API schema version. By default, a newer compatible CLI restarts an older local daemon before issuing the request; configure [server].daemon_auto_restart as newer, never, or always to control that lifecycle behavior. Remote servers are not restarted by clients, so compatibility is negotiated from the API schema version exposed in the OpenAPI document.
Initialize the archive schema through the configured remote server or the local daemon. When no remote is configured, the CLI starts the local daemon if needed and the daemon owns the database initialization work.
msgvault init-dbAdd a Gmail account and authorize via OAuth.
msgvault add-account <email>
msgvault add-account <email> --headless
msgvault add-account <email> --oauth-app <name>| Flag | Description |
|---|---|
--headless |
Show instructions for headless server setup |
--oauth-app |
Use a named OAuth app from [oauth.apps.<name>] in config |
--force |
Delete existing token and re-authorize |
--readonly |
Request Gmail read-only access instead of read + write. Refused if the account already holds write access — see OAuth Setup |
--display-name |
Set a display name for the account |
--no-default-identity |
Do not auto-confirm the email address as this account's "me" identity |
If [oauth].service_account_key or [oauth.apps.<name>].service_account_key is configured, add-account authorizes via Google service account domain-wide delegation instead of browser OAuth. Service-account accounts do not use --headless, --force, or --readonly; their scope comes from the domain-wide delegation grant in the Admin Console.
Add an IMAP account for syncing mail from any standard IMAP server.
msgvault add-imap --host <hostname> --username <email>The command prompts interactively for your password (never accepted as a flag to avoid shell history exposure). For scripting or Docker, set MSGVAULT_IMAP_PASSWORD or pipe via stdin:
MSGVAULT_IMAP_PASSWORD="..." msgvault add-imap --host imap.example.com --username user@example.com
# or
echo "$PASS" | msgvault add-imap --host imap.example.com --username user@example.comIt tests the connection before saving credentials.
| Flag | Default | Description |
|---|---|---|
--host |
(required) | IMAP server hostname |
--username |
(required) | IMAP username or email address |
--port |
993 |
IMAP server port (993 for TLS, 143 for STARTTLS/plain) |
--starttls |
false |
Use STARTTLS instead of implicit TLS |
--no-tls |
false |
Disable TLS entirely (plaintext, not recommended) |
--no-default-identity |
false |
Do not auto-confirm the username as this account's "me" identity |
Credentials are stored in tokens/imap_<hash>.json with restricted file permissions (0600). Use app-specific passwords when your provider supports them.
After adding an account, sync it with msgvault sync-full. IMAP accounts use the same sync and sync-full commands as Gmail. See Setup Guide for a walkthrough.
Create one reply draft from an archived IMAP message. The daemon requires a
confirmed --from identity and an operator grant in [[imap.drafts]].
msgvault draft-reply <message-id> --from <address> --body <text>
msgvault draft-reply <message-id> --from <address> --body= --jsonThe daemon appends the composed message to the configured literal mailbox with
the \Draft flag, then stores the local message and its (mailbox, uidvalidity, uid) receipt. The server must advertise UIDPLUS. An accepted APPEND without a
receipt returns accepted_unidentified, and a lost connection returns
remote_unknown; inspect the mailbox before retrying either result. A request
made while that source is syncing returns sync_active; retry after the sync
finishes.
Configure the grant by editing the daemon host's config.toml, then restart
the daemon:
[[imap.drafts]]
source_id = 42
enabled = true
mailbox = "Drafts"Requests, Settings, source JSON, environment variables, and client config do not grant access or change the mailbox. The host policy applies per source. For owner callers (API key, browser session, or keyless loopback), any owner caller that can reach the daemon can create drafts on a granted source. A delegated caller authenticated with a restricted agent token additionally requires that the target source appear in the token's grant; see agent-token. A later sync reconciles the saved membership when the mailbox's UIDVALIDITY changes. Draft creation never moves an IMAP cursor.
List the selectable folders in one or all configured IMAP accounts, including an approximate message count for each folder.
msgvault list-folders [account]Use the folder names in repeated --folder or --skip-folder flags on
sync-full and sync. When the account argument is omitted, the command lists
folders for every configured IMAP account. See
IMAP Folder Sync for examples and matching rules.
Add a Microsoft 365 or Outlook.com account via OAuth2 with XOAUTH2 IMAP authentication.
msgvault add-o365 <email>The command opens your browser for Microsoft OAuth consent, then configures IMAP with XOAUTH2 automatically. The correct IMAP host is auto-detected: outlook.office.com for personal accounts (hotmail.com, outlook.com, live.com, msn.com) and outlook.office365.com for organizational accounts.
Requires a [microsoft] section with client_id in config.toml. See the OAuth Setup guide for Azure AD app registration.
| Flag | Default | Description |
|---|---|---|
--tenant |
common |
Azure AD tenant ID (restricts which accounts can authorize) |
--no-default-identity |
false |
Do not auto-confirm the email address as this account's "me" identity |
After adding the account, sync it with msgvault sync-full.
Authorize a Microsoft Teams account through delegated Microsoft Graph OAuth and
register a teams source.
msgvault add-teams <email>
msgvault add-teams <email> --tenant <tenant-id>This stores a Teams Graph token under tokens/teams_<email>.json, separate from
the Microsoft IMAP token used by add-o365. Requires [microsoft].client_id in
config.toml and the Graph permissions documented in
Microsoft Teams.
| Flag | Default | Description |
|---|---|---|
--tenant |
common |
Azure AD tenant ID to use for authorization |
--no-default-identity |
false |
Do not auto-confirm the email address as this source's "me" identity |
After adding the account, sync it with msgvault sync-teams.
Validate a Discord bot token and register one or more guilds as discord
sources. The token is read from a masked prompt or piped stdin; there is no
token flag.
msgvault add-discord
msgvault add-discord --guild 123456789012345678
msgvault add-discord --oauth-app archive-bot \
--guild 123456789012345678 --guild 234567890123456789If the bot can access exactly one guild, it is selected automatically and
echoed for confirmation. Otherwise, repeat --guild to select exact guild
IDs. One guild becomes one source. Setup also reports likely Message Content
Intent, channel-history, and private-thread access issues.
| Flag | Default | Description |
|---|---|---|
--guild |
sole accessible guild | Guild ID to register (repeatable/comma-separated) |
--oauth-app |
sole/default bot | Discord token binding label; no [oauth.apps] entry is required |
After registering a guild, sync it with msgvault sync-discord. See
Discord for least-privilege bot setup and multi-bot binding
rules.
Download all messages from a Gmail or IMAP account. The optional account can be an identifier or display name. When it is omitted, the command syncs all configured syncable accounts.
msgvault sync-full [account] [flags]| Flag | Description |
|---|---|
--limit N |
Maximum messages to download |
--after YYYY-MM-DD |
Only messages after this date |
--before YYYY-MM-DD |
Only messages before this date |
--query |
Gmail search query filter |
--noresume |
Ignore checkpoints, start fresh |
--folder NAME |
Scan this IMAP folder (repeatable) |
--skip-folder NAME |
Skip this IMAP folder (repeatable) |
--source-id ID |
Sync exactly one source by numeric ID; mutually exclusive with the account argument |
--verbose |
Detailed progress output |
An account token can select more than one matching source. Use --source-id
when Gmail and IMAP, or two other sources, share the same identifier.
The CLI sends the sync request to the configured remote server or local daemon
and streams the daemon's stdout/stderr back to the terminal. This keeps local
and remote full sync behavior aligned and avoids running a separate direct
SQLite writer beside msgvault serve.
sync-full does not infer source deletion from messages absent from its result
set. This is especially important with --query, --limit, or date filters,
where the listing is intentionally incomplete. Gmail source-deletion
reconciliation happens only during the complete mailbox recovery described
under sync.
Sync new and changed messages. Gmail accounts use the Gmail History API; IMAP accounts perform a mailbox scan and skip messages already in the database. The optional account can be an identifier or display name. When it is omitted, the command syncs all accounts that have completed an initial full sync.
msgvault sync [account] [flags]| Flag | Description |
|---|---|
--folder NAME |
Scan this IMAP folder (repeatable) |
--skip-folder NAME |
Skip this IMAP folder (repeatable) |
--source-id ID |
Sync exactly one source by numeric ID; mutually exclusive with the account argument |
The CLI sends the incremental sync request to the configured remote server or local daemon and streams the daemon's stdout/stderr back to the terminal. The daemon serializes this work with other archive mutations.
Folder filters are applied only to IMAP accounts. See IMAP Folder Sync for examples and matching rules.
For Gmail, normal incremental sync consumes History API deletion events and marks those messages as deleted from the source. If Gmail says the saved history cursor has expired, msgvault recovers with an unfiltered, unlimited snapshot of the complete mailbox, then consumes changes made while that snapshot was in progress. Only a successfully completed snapshot is used to mark missing messages as source-deleted; interrupted or partial listings do not reconcile absence.
Source deletion updates archive metadata rather than deleting local message or
raw MIME data. Search excludes source-deleted messages by default; use
msgvault search ... --deletion-scope deleted or --deletion-scope any to find
retained history. See Source-Deleted Messages.
Sync Microsoft Teams chats and channels for an authorized account.
msgvault sync-teams <email>
msgvault sync-teams <email> --no-channels
msgvault sync-teams <email> --limit 100
msgvault sync-teams <email> --fullFull versus incremental sync is detected from stored cursors and checkpoints.
--full ignores those cursors and re-fetches messages, upserting rows in place
so importer upgrades can repair existing data without creating duplicates.
| Flag | Default | Description |
|---|---|---|
--no-channels |
false |
Sync chats only and skip team channels |
--limit |
0 |
Maximum messages per conversation (0 = unlimited) |
--full |
false |
Ignore stored cursor and re-fetch every message |
See Microsoft Teams for setup, scheduling, search, and inline media backfill.
Sync one registered Discord guild, or every registered guild when the selector is omitted.
msgvault sync-discord [guild-id-or-name]
msgvault sync-discord 123456789012345678 --after 2025-01-01
msgvault sync-discord 123456789012345678 --fullOne-guild selection accepts an exact guild ID or an unambiguous display name. Without a selector, sources run sequentially in stable source order. One guild failure does not block later guilds, but the overall command returns an aggregate error.
| Flag | Default | Description |
|---|---|---|
--full |
false |
Ignore stored cursors and re-fetch all available history, repairing rows and historical deletions |
--after |
— | Exclusive lower bound in YYYY-MM-DD or RFC3339 form |
Normal runs re-scan the configured trailing edit window, seven days by
default. --full --after bounds both re-fetch and deletion repair and leaves
earlier rows untouched. See Discord for per-channel/thread
checkpoint and deletion semantics.
Register a configured Granola account and validate its API key with a live API call.
msgvault add-granola [identifier]Reads the key from the matching [[granola]] entry in config.toml. With a
single configured entry the identifier may be omitted. Granola API keys are
created in the desktop app's settings and require a Business plan. The command
also confirms the source's effective account_email, even when other aliases
already exist. Meeting-source identity confirmation is mandatory.
After adding the account, sync it with msgvault sync-granola. If you later
change account_email or add/remove an alias with msgvault identity, run
msgvault sync-granola <identifier> --full to repair existing is_from_me
attribution.
Sync Granola meeting notes and transcripts.
msgvault sync-granola [identifier]
msgvault sync-granola --limit 5
msgvault sync-granola --full --after 2024-01-01Incremental by default: only notes updated since the last successful run are
fetched. With no identifier, every configured [[granola]] source is synced.
Re-fetched notes are upserted in place, so --full repairs existing rows
without creating duplicates. A partial run with one or more failed notes is
recorded and returned as an error without advancing the successful cursor. If
other notes were added or updated first, the cache is refreshed before the
error is returned. Scheduled sync refuses a configured source that has been
removed from the archive and directs you to run add-granola again.
| Flag | Default | Description |
|---|---|---|
--limit |
0 |
Maximum notes per run (0 = unlimited) |
--after |
— | Full-sync only notes created after this date (YYYY-MM-DD; implies --full) |
--full |
false |
Ignore stored cursor and re-fetch every note |
See Meeting Transcripts for setup and what gets stored.
Validate a configured read-only Notion integration and register its meeting source without printing meeting content.
msgvault add-notion-meetings [identifier]The matching [[notion_meetings]] entry requires account_email and token.
With one entry, the identifier may be omitted.
Sync Notion AI Meeting Notes from the attendee-visible recent window.
msgvault sync-notion-meetings [identifier]
msgvault sync-notion-meetings --limit 3
msgvault sync-notion-meetings --full --after 2026-01-01
msgvault sync-notion-meetings --probeNotion returns at most 50 meeting notes and exposes no discovery cursor.
Every run hydrates and checksum-verifies the selected visible meetings.
--full also sends matching snapshots through archive upsert so cursor/archive
divergence can be repaired; --after filters the visible set locally. Partial
coverage is reported when Notion says more records exist. Due transcript
maintenance runs outside --limit.
| Flag | Default | Description |
|---|---|---|
--limit |
0 |
Maximum visible meetings hydrated and verified; due maintenance is additional |
--after |
— | Local visible-set lower bound in YYYY-MM-DD form; implies --full |
--full |
false |
Force selected snapshots through archive upsert instead of skipping matching checksums |
--probe |
false |
Validate capabilities and result shape without printing meeting content |
See Meeting Transcripts for setup, privacy, retry behavior, and stored evidence.
Authorize a configured Circleback account using browser OAuth (their MCP server uses OAuth with dynamic client registration).
msgvault add-circleback [identifier]The token is stored under tokens/circleback_<identifier>.json. With a
single configured [[circleback]] entry the identifier may be omitted. The
command always confirms the source's effective account_email; there is no
identity opt-out flag.
Circleback redirects OAuth to localhost:8090. A CLI configured for a remote
daemon refuses to proxy this command: run it on the daemon host, where the
token is stored. Over SSH, use ssh -L 8090:localhost:8090 user@daemon-host,
run msgvault --local add-circleback <identifier> in that remote shell, and
open the printed authorization URL in your local browser if necessary. On the
daemon host, --local selects that host's archive; on a workstation it would
target a separate local archive.
After adding the account, sync it with msgvault sync-circleback. Run a
--full sync after changing the primary email or confirmed aliases.
Sync Circleback meetings, notes, action items, and transcripts.
msgvault sync-circleback [identifier]
msgvault sync-circleback --limit 5
msgvault sync-circleback --full --after 2024-01-01
msgvault sync-circleback --probeIncremental by default: each run enumerates meeting IDs without a date bound
so newly created backfills are discovered even when their scheduled date is
old. Unknown meetings and known meetings created within the 48-hour refresh
overlap are fetched in detail; unchanged snapshots are skipped without
invalidating the search cache. With no identifier, every configured
[[circleback]] source is synced. Missing or recognized-empty transcripts
enter a bounded pending state and retry every six hours, normally until seven
days after the scheduled meeting time (48 hours when no usable time exists).
Expired retries become unavailable; a later --full run can check them again.
Due transcript retries are maintenance work and run outside the new-meeting limit. Provider, contract, missing-result, ingest, archive-recovery, and cancellation failures fail the sync and preserve the prior successful cursor.
| Flag | Default | Description |
|---|---|---|
--limit |
0 |
Maximum newly searched meetings; due maintenance items are additional (0 = unlimited) |
--after |
— | Full-sync only meetings after this date (YYYY-MM-DD; implies --full) |
--full |
false |
Ignore stored cursor and re-fetch every meeting |
--probe |
false |
Print the MCP tool inventory and a sample result instead of syncing |
See Meeting Transcripts for setup and what gets stored.
Download remote <img src> images from existing email for offline viewing.
This is separate from the default-off [sync].archive_remote_images setting.
msgvault archive-remote-images --allow-tracking
msgvault archive-remote-images --allow-tracking --source-id 1 --limit 100| Flag | Default | Description |
|---|---|---|
--allow-tracking |
false |
Required consent to image requests that can activate tracking pixels |
--source-id |
0 |
Restrict to one source; 0 includes all email sources |
--limit |
0 |
Maximum messages to scan; 0 scans all matching email |
Requests originate from the archive server, not the browser. Private-network
destinations are blocked, but public hosts can still track requests. The command
requires --allow-tracking even when automatic archiving is enabled.
Already archived images are reused. Failed downloads can be retried by running the command again; successful downloads are preserved if other images fail. The summary reports messages scanned, images downloaded or reused, and errors. Any image errors produce a nonzero exit status. Original messages are unchanged.
Re-fetch Microsoft Teams inline hosted-content media for already imported messages.
msgvault backfill-teams-media <email>
msgvault backfill-teams-media <email> --only-incompleteThe command scans stored Teams HTML bodies for Graph hostedContents URLs and
downloads those images into the attachment store. It is idempotent because
attachments are content-addressed.
| Flag | Default | Description |
|---|---|---|
--only-incomplete |
false |
Retry only messages whose inline media is still missing |
Refresh Discord message attachment metadata and retry pending downloads for one guild or every registered guild.
msgvault backfill-discord-media [guild-id-or-name]
msgvault backfill-discord-media 123456789012345678 --only-incompleteBy default, all archived Discord messages with attachments are scanned;
already-complete messages require no download work. With no selector, guilds
run sequentially using the same aggregate-failure behavior as
sync-discord.
| Flag | Default | Description |
|---|---|---|
--only-incomplete |
false |
Select only messages with pending attachment rows |
Backfill re-fetches each selected source message that has pending attachments to obtain fresh signed CDN URLs. An incomplete attachment is unrecoverable if the source message has since been deleted. See Discord.
Register the chat accounts connected to a locally running
Beeper Desktop as beeper sources, one per network.
msgvault add-beeper
msgvault add-beeper --token-file ~/beeper-token.txt
MSGVAULT_BEEPER_TOKEN="..." msgvault add-beeperRequires Beeper Desktop running on the same machine and an access token
minted in Beeper Desktop (Settings → Developer). The token is stored at
tokens/beeper.json. Accounts filtered out by [beeper].accounts /
exclude_accounts in config.toml are skipped.
Networks Beeper serves natively instead of bridging — iMessage — are absent from its accounts API, so they are found from chat data and reported as found via chats. Re-run the command after connecting a network in Beeper Desktop.
| Flag | Default | Description |
|---|---|---|
--token-file |
Read the access token from a file instead of prompting | |
--no-default-identity |
false |
Do not auto-confirm each account's own identity as that source's "me" identity |
After adding, sync with msgvault sync-beeper.
Sync chats from Beeper Desktop for every registered Beeper account (all
connected networks). The first run backfills full locally-available history and
is resumable; later runs are incremental. Per-account failures do not stop the
run: remaining accounts still sync, the analytics cache is rebuilt for the
successful ones, and the command exits non-zero listing the failures. Without
--account, the [beeper] config accounts/exclude_accounts filters
select which registered sources sync. See Beeper.
msgvault sync-beeper
msgvault sync-beeper --account signal --account telegram
msgvault sync-beeper --full| Flag | Default | Description |
|---|---|---|
--account |
all registered | Beeper accountID to sync (repeatable) |
--limit |
0 |
Max messages per chat this run (0 = no limit; limited backfills resume next run) |
--full |
false |
Ignore stored cursors and re-fetch every message (repairs rows in place) |
--no-media |
false |
Skip attachment downloads for this run |
Retry pending Beeper attachment downloads (media that failed or exceeded the
size cap during sync-beeper). Idempotent: attachments are content-addressed.
If the source message is gone (deleted in Beeper), its pending markers are
cleared permanently — already-downloaded attachments are kept. Transient fetch
errors keep the marker and count as still pending, so re-running retries them.
msgvault backfill-beeper-media
msgvault backfill-beeper-media --account signal| Flag | Default | Description |
|---|---|---|
--account |
all registered | Beeper accountID to backfill (repeatable) |
Register a Slack workspace as a slack source. Requires a
user token (xoxp-…) from an internal Slack app you create (see the usage
guide for the two-minute setup and scope list). The token is validated with
auth.test plus a search.messages probe (thread-reply archiving needs the
search:read scope, so an under-scoped token fails here rather than on
every future sync) and stored at tokens/slack_<team-id>_<user-id>.json.
msgvault add-slack
msgvault add-slack --token-file ~/slack-token.txt
MSGVAULT_SLACK_TOKEN="xoxp-..." msgvault add-slack| Flag | Default | Description |
|---|---|---|
--token-file |
Read the user token from a file instead of prompting | |
--no-default-identity |
false |
Do not auto-confirm the workspace user ID as the source's "me" identity |
After adding, sync with msgvault sync-slack.
Import a Slackdump directory or ZIP without contacting Slack. --me resolves
your account from users.json by exact Slack user ID or unique profile email.
The import preserves conversations, threads, reactions, raw JSON, identities,
and exported files. The path must be local to the daemon host; exports are not
uploaded through a configured remote. See
Slack.
msgvault import-slackdump --me you@example.com /path/to/export
msgvault import-slackdump --me U0123456789 --limit 100 /path/to/export.zip| Flag | Default | Description |
|---|---|---|
--me |
required | Slack user ID or unique profile email in the export |
--limit |
0 |
Max messages imported per conversation (0 = no limit) |
--max-media-mb |
configured/default limit | Max exported file size in MiB (0 = configured/default limit) |
--no-default-identity |
false |
Do not auto-confirm the workspace user ID as the source's "me" identity |
Sync Slack conversations — channels you are a member of, group DMs, and 1:1
DMs — for registered workspaces. The first run backfills full history and is
resumable; later runs are incremental and sweep for thread replies created
since the last run (any thread age). Per-workspace failures do not stop the run: remaining workspaces
still sync and the command exits non-zero listing the failures. The [slack]
config channels/exclude_channels filters select which channels sync. See
Slack.
msgvault sync-slack
msgvault sync-slack T0123456789
msgvault sync-slack --full| Flag | Default | Description |
|---|---|---|
--limit |
0 |
Max messages of work per conversation this run, thread replies included; the reply sweep gets the same budget workspace-wide (0 = no limit; every phase resumes next run so standing limited schedules converge; only the maintenance rescan is skipped) |
--full |
false |
Start (or continue) a repair session: re-fetch every message, upserting in place (catches old thread replies and edits). Interrupted or --limit-scoped repairs resume across later runs of any kind until complete |
--no-threads |
false |
Skip thread-reply fetching for this run (a later threaded run pays the debt automatically) |
--maintenance |
false |
Repair edits/reaction changes on recent messages (ignored by default after capture) |
--no-media |
false |
Skip file downloads for this run (files become pending markers; backfill-slack-media fetches them later) |
Retry pending Slack file downloads (files that failed or exceeded the size
cap during sync-slack). Idempotent: files are content-addressed and
already-downloaded ones are never re-fetched. Files hosted outside
files.slack.com are metadata-only link rows and are never downloaded.
msgvault backfill-slack-media
msgvault backfill-slack-media T0123456789Authorize read-only Google Calendar access for an account and register its calendars for sync. If the account already has a Gmail token, re-consent bundles Gmail + Calendar so Gmail access is not dropped — keep both checked on the consent screen. The Calendar API must be enabled on the OAuth project. By default only owned/writable calendars are registered.
msgvault add-calendar <email> [flags]| Flag | Description |
|---|---|
--oauth-app |
Named OAuth app to use |
--headless |
Print token-copy instructions for a headless host instead of opening a browser |
--all-calendars |
Include reader/freeBusyReader (subscribed, holiday) calendars |
--min-access-role |
Minimum access role: owner, writer, or reader |
--calendars |
Comma-separated calendar IDs to register |
Sync Google Calendar events for an account. The account is resolved from a [[gcal]] config entry (by name or email) or used directly as an email. The first run (or --full) does a full sync that registers calendars; later runs are incremental via the Calendar syncToken. Events are stored as searchable records (message_type = calendar_event) and become eligible for semantic search when the embedding worker runs. Cancelled events are retained and marked cancelled, never deleted. Sync is read-only.
msgvault sync-calendar <name|email> [flags]| Flag | Description |
|---|---|
--full |
Force a full sync (ignore stored sync tokens) |
--limit |
Max events per calendar (0 = unlimited) |
--after / --before |
Bound a full sync to a date range (YYYY-MM-DD); full sync only |
--calendar |
Restrict to specific calendar IDs |
--all-calendars |
Include reader/freeBusyReader calendars |
--min-access-role |
Minimum access role: owner, writer, or reader |
--oauth-app |
Named OAuth app to use |
--noresume |
Do not resume an interrupted full sync |
Import RFC 5322 .eml files inside MailMate-style .mailbox directories.
Arbitrary loose .eml files are not discovered. The directory structure
provides mailbox labels. The command resumes an active checkpoint by default.
msgvault import-eml /path/to/mail --identifier user@example.com| Flag | Default | Description |
|---|---|---|
--identifier |
required | Source identifier for imported messages |
--source-type |
eml |
Source type stored for this import |
--no-resume |
false |
Start a new import rather than resume its checkpoint |
--checkpoint-interval |
200 |
Messages between saved checkpoints |
--no-attachments |
false |
Do not write attachment files |
--no-default-identity |
false |
Do not confirm the identifier as this source's identity |
See local email import for file discovery and repeat-import behavior, and remote images for the separate opt-in that can cause network requests during email imports.
Import a local MBOX archive into msgvault.
msgvault import-mbox <identifier> <export-file>The export file may be a plain mbox file (any extension) or a .zip containing one or more .mbox/.mbx files.
| Flag | Default | Description |
|---|---|---|
--source-type |
mbox |
Source type recorded in database (e.g., hey or google-groups) |
--label |
— | Label(s) to apply to imported messages (repeatable, or comma-separated) |
--no-resume |
false |
Start fresh, ignoring interrupted progress |
--checkpoint-interval |
200 |
Save progress every N messages |
--no-attachments |
false |
Skip writing attachments to disk |
--no-default-identity |
false |
Do not auto-confirm the identifier as this source's "me" identity |
For Google Groups Takeout, use --source-type google-groups. This preserves
Groups thread IDs and labels, and does not auto-confirm the group identifier as
"me". Import a Groups-only ZIP or an extracted group MBOX; non-message files are
skipped.
See Importing Local Email for usage examples.
Import a Maildir archive without changing its files:
msgvault import-maildir ~/Maildir --identifier you@example.comReads regular message files in cur and new; skips tmp, symlinks, and
mailbox bookkeeping files. The root mailbox receives the INBOX label.
Nested directories and Maildir++ folders such as .Projects.Go become
slash-separated labels (Projects/Go). Use a stable snapshot of the mailbox
so delivery and flag renames cannot race the import.
Stores raw MIME, bodies, recipients, and attachments. As with other local email
imports, attachment-write failures are logged; the original attachment bytes
remain in raw MIME, but a rerun does not retry those writes. Exact duplicate raw
messages within the same source are stored once and collect all folder and
flag labels. Rerunning the import adds missing messages and labels; it does
not remove archived messages or labels. Moving a message from new to cur
or changing its filename flags does not create another copy.
Maildir flags become DRAFT, STARRED, PASSED, REPLIED, and TRASH
labels. Messages without the seen (S) flag receive UNREAD. Trashed
messages are still archived.
| Flag | Default | Description |
|---|---|---|
--identifier |
— | Required account identifier |
--source-type |
maildir |
Source type stored for the archive |
--no-resume |
false |
Start a fresh run instead of resuming a checkpoint |
--checkpoint-interval |
200 |
Save progress after this many messages |
--no-attachments |
false |
Skip writing attachment files |
--no-default-identity |
false |
Skip confirming the identifier as the source identity |
Interrupted imports resume by rescanning the tree and skipping stored messages. Messages larger than 128 MiB are reported as errors and left unimported.
Import Apple Mail .emlx files into msgvault. Can auto-discover accounts from macOS Accounts4.sqlite or accept explicit arguments.
# Auto-discover accounts (reads ~/Library/Accounts/Accounts4.sqlite)
msgvault import-emlx
# Specify mail directory
msgvault import-emlx <mail-dir>
# Legacy form: explicit identifier and directory
msgvault import-emlx <identifier> <mail-dir>The mail directory should be an Apple Mail mailbox tree containing .mbox or .imapmbox directories, each with a Messages/ subdirectory of .emlx files. You can also point directly at a single .mbox directory. Labels are derived from directory names.
Apple Mail's N.partial.emlx files are also imported: their message body is
complete even when uncached attachment parts are absent. If both N.emlx and
N.partial.emlx exist, the complete N.emlx copy wins. The command summary
reports the number of partial files imported.
| Flag | Default | Description |
|---|---|---|
--source-type |
apple-mail |
Source type recorded in database |
--account |
— | Filter to specific account(s) during auto-discover (repeatable) |
--accounts-db |
— | Custom path to macOS Accounts4.sqlite |
--identifier |
— | Manual identifier when auto-discover is not suitable |
--no-resume |
false |
Start fresh, ignoring interrupted progress |
--checkpoint-interval |
200 |
Save progress every N messages |
--no-attachments |
false |
Skip writing attachments to disk |
--no-default-identity |
false |
Do not auto-confirm the identifier as this source's "me" identity |
See Importing Local Email for usage examples.
Import a Microsoft Outlook PST archive into msgvault.
msgvault import-pst <identifier> <pst-file>The importer preserves PST folder structure as labels, imports email messages, and skips non-email PST items such as calendar entries, contacts, tasks, and notes.
| Flag | Default | Description |
|---|---|---|
--source-type |
pst |
Source type recorded in database |
--skip-folder |
— | Folder name to skip, case-insensitive; repeat for multiple folders |
--no-resume |
false |
Start fresh, ignoring interrupted progress |
--checkpoint-interval |
200 |
Save progress every N messages |
--no-attachments |
false |
Skip writing attachments to disk |
See Importing Local Email for usage examples.
Import messages from a decrypted Android msgstore.db or Apple
ChatStorage.sqlite WhatsApp database. Apple support currently imports text,
participants, and available names; native Apple attachments are not imported.
msgvault import-whatsapp <msgstore.db> --phone <your-number>The --phone flag is required and must be in E.164 format (e.g., +447700900000).
| Flag | Required | Description |
|---|---|---|
--phone |
Yes | Your phone number in E.164 format (must start with +) |
--contacts |
No | Path to contacts .vcf file for name resolution |
--media-dir |
No | Path to decrypted Media folder for attachments |
--limit |
No | Limit number of messages (for testing) |
--display-name |
No | Display name for the phone owner |
--no-default-identity |
No | Do not auto-confirm the phone number as this source's "me" identity |
See Text Messages for usage examples.
Import messages from the local iMessage database on macOS. Requires Full Disk Access in System Settings.
msgvault import-imessageReads from ~/Library/Messages/chat.db by default. This is a read-only operation.
| Flag | Default | Description |
|---|---|---|
--db-path |
~/Library/Messages/chat.db |
Path to chat.db |
--before |
— | Only messages before this date (YYYY-MM-DD) |
--after |
— | Only messages after this date (YYYY-MM-DD) |
--limit |
0 |
Limit number of messages (for testing) |
--me |
— | Your phone/email for recipient tracking |
--contacts |
— | Path to contacts .vcf file for display-name backfill |
See Text Messages for usage examples.
Import texts, calls, and voicemails from a Google Voice Takeout export.
msgvault import-gvoice <takeout-voice-dir>The directory must be the "Voice" folder from a Google Takeout export, containing Calls/ and Phones.vcf.
| Flag | Default | Description |
|---|---|---|
--before |
— | Only messages before this date (YYYY-MM-DD) |
--after |
— | Only messages after this date (YYYY-MM-DD) |
--limit |
0 |
Limit number of messages (for testing) |
--no-default-identity |
false |
Do not auto-confirm the phone number as this source's "me" identity |
See Google Voice imports for voicemail audio behavior and usage examples.
Import Facebook Messenger conversations from a Download Your Information export.
msgvault import-messenger --me <you@facebook.messenger> <dyi-export-dir>| Flag | Default | Description |
|---|---|---|
--me |
(required) | Your synthetic Messenger identifier, e.g. test.user@facebook.messenger |
--format |
auto |
Export format: auto, json, html, or both |
--limit |
0 |
Limit number of messages (for testing) |
--no-resume |
false |
Start fresh, ignoring interrupted progress |
--checkpoint-interval |
200 |
Save progress every N messages |
See Text Messages for usage examples.
Import SMS Backup & Restore XML or ZIP backups.
msgvault import-synctech-sms <path> --owner-phone <your-number>| Flag | Default | Description |
|---|---|---|
--owner-phone |
(required) | Your phone number in E.164 format |
--sms |
true |
Import SMS records |
--mms |
true |
Import MMS records |
--calls |
true |
Import call logs |
--attachments |
true |
Import MMS attachments |
See Text Messages for usage examples.
Configure a Google Drive source for SMS Backup & Restore backups.
msgvault add-synctech-sms-drive <name> --owner-phone <number> --folder-id <id> --google-account <email>| Flag | Default | Description |
|---|---|---|
--owner-phone |
(required) | Your phone number in E.164 format |
--folder-id |
(required) | Google Drive folder ID containing backups |
--google-account |
(required) | Google account used for Drive access |
--schedule |
30 4 * * * |
Cron schedule used by msgvault serve |
--oauth-app |
— | Named Google OAuth app to use |
Run one configured SMS Backup & Restore source immediately.
msgvault sync-synctech-sms <name>Create, list, verify, and restore incremental archive snapshots in a backup repository.
msgvault backup init --repo ~/Backups/msgvault
msgvault backup create --repo ~/Backups/msgvault
msgvault backup list --repo ~/Backups/msgvault
msgvault backup verify --repo ~/Backups/msgvault
msgvault backup restore --target ~/msgvault-restored --repo ~/Backups/msgvaultEvery backup subcommand requires a repository: pass --repo, or set
[backup].repo in config.toml to omit it. backup init initializes the
repository directory but does not modify config.toml.
backup create is routed through the selected daemon so the daemon can freeze a
consistent SQLite snapshot while it scans pages and attachments. backup verify
and backup restore run locally against the repository because they do not
write the live archive.
msgvault backup init --repo <dir>| Flag | Description |
|---|---|
--repo <dir> |
Backup repository directory |
msgvault backup create [flags]| Flag | Description |
|---|---|
--repo <dir> |
Backup repository directory |
--include-config |
Include config.toml verbatim; may contain API keys |
--include-tokens |
Include OAuth token files |
--allow-plaintext-secrets |
Allow config/tokens in an unencrypted repository |
--tag <text> |
Optional label recorded on the snapshot manifest |
--force-unlock |
Break a stale exclusive repository lock before creating |
--jobs N |
Concurrent attachment capture workers; 0 uses one per CPU |
msgvault backup list [--repo <dir>]Prints snapshot ID, creation time, message count, bytes added, and tag.
msgvault backup verify [snapshot] [flags]| Flag | Description |
|---|---|
--repo <dir> |
Backup repository directory |
--all |
Verify every snapshot instead of only the latest |
--quick |
Skip reading and hash-verifying content blobs |
--force-unlock |
Break a stale exclusive repository lock before verifying |
--jobs N |
Concurrent pack readers; 0 uses one per CPU |
msgvault backup restore [snapshot] --target <dir> [flags]| Flag | Description |
|---|---|
--repo <dir> |
Backup repository directory |
--target <dir> |
Directory to restore into (required) |
--overwrite |
Allow restoring into a non-empty target directory |
--integrity-check |
Run SQLite's full integrity check after restoring (slow for large databases) |
--loose-attachments |
Restore attachments as individual loose files instead of installing compatible packs |
--force-unlock |
Break a stale exclusive repository lock before restoring |
--jobs N |
Concurrent pack readers; 0 uses one per CPU |
By default, restore installs compatible repository packs directly into the
restored attachment store. Every selected attachment is still read and
SHA-256 verified; entries that exceed the target store's maintenance limits,
or packs that use an incompatible representation, are restored loose instead.
The summary reports the resulting packed/loose split and any fallback reasons.
Every restore verifies database pages and content blobs by hash and compares
the restored database statistics with the snapshot manifest. Pass
--integrity-check to additionally run SQLite's full PRAGMA integrity_check
scan; it is optional because it can dominate restore time for large databases.
Use --loose-attachments for downgrade or recovery. Restore into a fresh
target with that flag to guarantee a fully loose result. An overwritten target
can retain uncataloged old pack files, and unpack-attachments processes only
cataloged packs, so overwrite cannot currently make the same guarantee.
Restoring into the live archive home of a running daemon is refused. See
Backup for repository format, scheduling, verification, and
privacy details.
Move every eligible loose content-addressed attachment into sealed immutable pack files:
msgvault pack-attachmentsThe daemon serializes packing against sync and backup operations. Reads remain available from loose, packed, or mixed storage, and the command is safe to rerun as new loose content arrives. Bounded packing also runs after successful attachment-producing operations and during scheduled maintenance; this command processes the complete eligible backlog immediately.
With [data].loose_attachments = true, automatic packing is disabled and this
command refuses to run.
Reclaim dead bytes from sparse attachment packs:
msgvault repack-attachmentsRepack always runs through the selected daemon so it can atomically replace
live blob mappings, retire shared readers, and remove old pack files. It is
safe to retry after interruption or a Windows file-sharing error.
It refuses to run when [data].loose_attachments = true because repacking
creates replacement pack files.
Restore every cataloged packed attachment to a loose file and remove its pack:
msgvault daemon stop
msgvault unpack-attachmentsEach object is SHA-256 verified as it is written. This is the downgrade and
recovery escape hatch because msgvault versions before packed attachment
support cannot read the packs. The command is local-only and refuses to run
while a daemon holds pack readers open. With [remote] configured, run it on
the archive host or pass --local there to select that host's local archive.
Search the archive with Gmail-like query syntax. Supports keyword (FTS5), semantic, and hybrid modes.
msgvault search <query> [flags]| Flag | Description |
|---|---|
-n, --limit N |
Maximum number of results (default: 50) |
--offset N |
Skip first N results (only valid for --mode fts) |
--json |
Output results as JSON |
--account |
Limit results to a specific account |
--collection |
Limit results to all member accounts of a collection |
--message-type |
Limit results to one or more message types, e.g. email, teams, discord, calendar_event, sms |
--deletion-scope |
Source-deletion scope: active (default), deleted, or any. Non-active scopes require --mode fts. |
--mode |
Search mode: fts (default), vector, or hybrid. vector and hybrid require vector search to be configured. |
--explain |
Include per-signal scores (RRF, BM25, vector) in the output. Only applies to --mode vector and --mode hybrid. |
Without an explicit message-type filter, search intentionally returns all
matching cached message types, including meeting transcripts and chats.
Ordinary aggregate views and statistics still default to email-only; use
--message-type (or message_type: in the query) when you need an explicit
search scope.
The query accepts list: and list-id: as equivalent RFC 2919 List-Id
filters. Values are case-insensitive literal substrings; quote values that
contain spaces. Repeating either alias requires every value to match.
--mode vector and --mode hybrid require at least one free-text term in the query (filter-only queries use --mode fts). They do not support pagination (--offset is rejected) or non-active deletion scopes because the vector index covers active messages only. Bump --limit to retrieve a larger candidate pool instead. See Searching for the operator reference and Vector Search for semantic setup.
Re-derive archived email List-Id values from stored raw MIME without contacting a provider.
msgvault repair-list-ids [--apply]The default is a dry run and does not modify the archive. Pass --apply to
write changed values and mark derived analytics stale for the normal rebuild path.
Rebuild an IMAP source's message labels from its stored mailbox memberships, without contacting the provider.
msgvault repair-labels [identifier] [--apply]An add-only label merge — used when a sync's mailbox snapshot is incomplete or
a dedup match is not fully confirmed — never removes a label. If the
message's stored membership never changes again, nothing later revisits it,
and a stray label can persist. repair-labels rebuilds a message's labels
straight from imap_message_memberships, the same rebuild a full mailbox
enumeration performs, without a live IMAP connection.
With no identifier, every IMAP source is repaired. An identifier scopes the
repair to one matching source. The default is a dry run and does not modify
the archive. Pass --apply to write changed labels and mark derived
analytics stale for the normal rebuild path.
| Flag | Description |
|---|---|
--apply |
Write repaired labels to the archive. |
Manage hosted extraction and local full-text indexing for standalone document attachments. Provider operations require a private authenticated capability manifest and exact recorded consent; local search and removal operations do not contact the provider.
msgvault documents probe-mistral --fixtures <private-dir> [--validate-only]
msgvault documents consent-mistral --capabilities <manifest> [--yes]
msgvault documents build --capabilities <manifest> [--limit N] [--full-rebuild] [--yes]
msgvault documents resume --capabilities <manifest> [--limit N] [--yes]
msgvault documents search <query> [flags]
msgvault documents status --capabilities <manifest> [--json]
msgvault documents retry --capabilities <manifest> --hash <sha256>
msgvault documents retire <profile-id> [--yes]
msgvault documents purge-derived --hash <sha256> [--yes]probe-mistral --validate-only checks the complete synthetic fixture set
without credentials or network access. An authenticated probe writes the
manifest to stdout. Run consent and build commands once without --yes to
review their disclosure and preflight.
documents search accepts --source-id, --message-type, --attachment-id,
--message-id, -n/--limit, --cursor, and --json. Its cursor is opaque
and bound to a stable index revision; restart pagination after a stale-cursor
error.
See Document Attachment Indexing for fixture generation, configuration, privacy boundaries, scheduling, and recovery.
Launch the interactive terminal interface.
msgvault tui [flags]| Flag | Description |
|---|---|
--local |
Use the local daemon instead of the configured remote server |
Analytics engine and cache behavior are daemon-managed. Configure [analytics].engine and [analytics].auto_build_cache in config.toml to force live SQL, require DuckDB, or disable automatic cache builds. See Configuration: analytics.
Deprecated in 0.17.0: the older TUI-only --force-sql, --no-cache-build, and --no-sqlite-scanner flags are hidden and no longer control the foreground CLI. Use [analytics].engine = "sql" for live SQL, [analytics].auto_build_cache = false to skip daemon cache builds, or msgvault build-cache to prebuild cache files on the daemon host.
Export a bounded, provider-neutral message window as deterministic JSON Lines. The command reads the archive through the configured daemon and never contacts a provider.
msgvault export-messages \
--start <RFC3339> \
--end <RFC3339> \
[--message-type <type>] \
[--source <type:identifier>] \
[--person-id <id>] \
[--format jsonl]| Flag | Default | Description |
|---|---|---|
--start |
required | Inclusive RFC3339 lower bound |
--end |
required | Exclusive RFC3339 upper bound |
--message-type |
all | Exact message type to include; repeatable |
--source |
all | Exact typed source selector; repeatable |
--person-id |
all | Durable person whose bound participants scope the export |
--format |
jsonl |
Output format; v1 accepts only jsonl |
Use --person-id N to export messages involving a durable person's bound
participants. Linked participants outside those bindings contribute no
messages. Exported authors use the sender's curated person name when present,
keeping the address unchanged.
The stream schema is msgvault-message-export/1. Records appear as one
manifest, all sources, all conversations, all messages, and one completion
record with counts. A missing completion record means the stream is partial
and must be rejected. Stdout contains JSONL only; diagnostics use stderr.
See Exporting Data for the full record, identity, ordering, and validation contract.
Export a message as a .eml file. Accepts either a numeric database ID or a Gmail message ID.
msgvault export-eml <id> [flags]| Flag | Description |
|---|---|
-o, --output <path> |
Output file (default: <gmail_id>.eml, use - for stdout) |
Export an attachment by its SHA-256 content hash.
msgvault export-attachment <content-hash> [flags]| Flag | Description |
|---|---|
-o, --output <path> |
Output file path (use - for stdout) |
--base64 |
Output raw base64 to stdout |
--json |
Output as JSON with base64-encoded data |
The --json, --base64, and --output flags are mutually exclusive.
See Exporting Data for usage examples.
Export all attachments from a message as individual files.
msgvault export-attachments <message-id> [flags]| Flag | Description |
|---|---|
-o, --output <dir> |
Output directory (default: current directory) |
Accepts internal numeric IDs or Gmail message IDs. See Exporting Data for usage examples.
Export a browser-created OAuth refresh token to a remote msgvault instance.
Use this for headless deployments (NAS, cloud VM, any remote server) that cannot run a browser flow.
msgvault export-token <email> [flags]| Flag | Description |
|---|---|
--to <url> |
Remote msgvault URL (or MSGVAULT_REMOTE_URL) |
--api-key <key> |
API key (or MSGVAULT_REMOTE_API_KEY) |
--allow-insecure |
Allow HTTP for trusted networks (for example Tailscale) |
export-token uploads ~/.msgvault/tokens/<email>.json to /api/v1/auth/token/<email>, saves it in the remote token store, and posts account metadata to /api/v1/accounts.
Verify archive integrity against Gmail through the configured remote server or local daemon. The command streams the daemon's stdout/stderr back to the terminal.
msgvault verify <email> [flags]| Flag | Description |
|---|---|
--sample N |
Messages to sample (default: 100) |
--skip-db-check |
Skip SQLite integrity check |
--json |
Emit machine-readable JSON summary |
Show archive statistics.
msgvault stats [flags]| Flag | Description |
|---|---|
--account |
Show stats for a specific account |
--collection |
Show stats for all member accounts of a collection |
Use the people guide for the workflow. Observed contacts use participant IDs; saved profiles use person IDs. Each command below takes the ID named in its arguments.
Read, replace, or append private Notes while retaining earlier values:
| Command | Purpose |
|---|---|
person notes get <person-id> |
Read the current Notes text |
person notes set <person-id> --text <text> |
Replace Notes |
person notes append <person-id> --text <text> |
Append a fragment atomically on a new line |
--text accepts inline text, @path, or - for standard input. All commands
accept --json. set --expected-value-id <id> rejects concurrent changes.
See private notes for
how Notes interact with search, MCP, and CardDAV.
| Command | Purpose |
|---|---|
person search <free-text> [--limit 20] [--json] |
Search curated profiles semantically; requires person embeddings and consent |
person files <person-id> [flags] |
Find archived attachments related to the person's linked identities |
Person search accepts --limit from 1–100. File lookup defaults to the
metadata lane and 100 results. Its main filters are --direction,
--filename, --mime-family, --after, and --before.
--lane documents, visual, or all requires --query; a --cursor belongs
to one lane and cannot be used with all. See
person search and
person files for index
requirements, supported filters, and pagination.
Tracking chooses which profiles automatic maintenance may process. It does not grant consent to a provider or enroll a person in briefs.
| Command | Purpose |
|---|---|
person track <person-id> / person untrack <person-id> |
Enable or stop future profile maintenance for a person |
person provider add <name> [flags] |
Configure and synthetically check a named model provider |
person provider list |
List configured model profiles |
person provider use <name> |
Select a checked profile and enable sweeps |
person provider check [name] |
Run fixed synthetic input without granting consent |
person provider consent [name] --yes |
Grant consent to the exact checked policy |
person provider revoke [name] |
Revoke that policy's consent; --all revokes all stored sweep policies |
person provider remove <name> |
Remove a configured profile |
person provider history [name] [--person <id>] |
Inspect redacted runs and attempts |
person sweep run [--person <id>] [--limit 25] |
Run a bounded maintenance pass for tracked people |
person sweep status |
Read redacted progress and usage |
person sweep history [--person <id>] [--limit 20] |
Read recent maintenance attempts |
person facts catalog [--include-sensitive] |
List eligible fields, their keys, and revisions |
person facts evidence <person-id> |
Inspect saved supporting evidence |
person facts evidence-status <person-id> |
Inspect changes to evidence support |
person facts claims <person-id> |
Inspect proposed values |
person facts decisions <person-id> |
Inspect resolver decisions |
person facts pins <person-id> |
List fields protected from automatic replacement |
person facts pin <person-id> <kind> <key> |
Protect a field's current or empty value |
person facts unpin <person-id> <kind> <key> |
Allow automatic resolution for the field again |
These commands accept --json. Use catalog keys, not slugs, for fact pins.
Evidence, claims, and decisions accept an exact --target; fact-history
commands accept --limit (1–200) and --offset. sweep run --backstop
revisits older evidence in a bounded pass.
Provider configuration edits run on the daemon host. Restart the daemon after changing its active provider or policy. For setup, privacy controls, budgets, and recovery, see profile automation. Provider status, reverify, set, and briefs have additional details below.
Look up public person information through independently configured and consented Exa or Sixtyfour policies:
| Command | Purpose |
|---|---|
person enrichment profiles |
List saved policy fingerprints |
person enrichment status [--limit 20] |
Inspect policies, consent, and bounded suppression history |
person enrichment consent <fingerprint> |
Grant consent for that exact enrichment policy |
person enrichment revoke [fingerprint] |
Revoke an exact policy; --all revokes all enrichment policies |
person enrichment run --person <id> --provider <name> --idempotency-key <key> |
Request a lookup for one person |
person enrichment suppress --person <id> --reason <reason> |
Record an opt-out for the person's current identifiers |
person enrichment suppress --provider <name> --identifier-class <class> --reason <reason> < identifier.txt |
Suppress an identifier read from standard input |
Status, profiles, consent, revoke, and run accept --json. Suppression reasons
are opt_out and data_subject_request. The suppression forms are mutually
exclusive: --person does not accept --provider or --identifier-class.
For the identifier form, <class> is email, phone, public_profile_url,
provider_person_id, or name_company. Supply one non-empty line on standard
input, except for name_company, which requires two: the name, then the company.
Do not put identifier values in command arguments.
See external enrichment for required identity details, configuration, request limits, and the persistent suppression key.
Keep organization details and current or historical jobs in structured records.
org is an alias for organization.
| Command | Purpose |
|---|---|
organization list [--query <text>] [--include-retired] |
Find organizations |
organization create <name> [--domain <domain>] |
Create an organization |
organization show <id> [--history] |
Read an organization and its profile data |
organization set <id> --name <name> [flags] |
Replace mutable organization fields |
organization retire <id> / organization unretire <id> |
Change active status while keeping the record |
organization merge <survivor-id> <losing-id> |
Consolidate duplicate organizations |
organization delete <id> |
Permanently delete an organization |
organization attribute list <id> |
Read attributes; --include-superseded adds history |
organization attribute set <id> --definition <slug> --text <value> |
Set a text attribute |
organization attribute clear <id> <slug> |
Close an attribute value while retaining history |
employment add --person <id> --organization <id> [flags] |
Connect a person to an organization |
employment list --person <id> |
List a person's jobs; --organization <id> lists an organization's jobs |
employment show <id> / employment set <id> [flags] |
Inspect or edit one job |
employment end <id> [--end <date>] |
End a job while keeping its history |
employment set-primary <id> |
Select the primary current job |
employment delete <id> |
Permanently delete a job |
These commands accept --json. Employment creation accepts --title, --role,
--department, --location, --description, --start, --end, and --primary.
Dates can be partial (YYYY, YYYY-MM, or YYYY-MM-DD). Attribute writes
support --dry-run and --expected-value-id for validation and concurrent-edit
checks. See employment and relationships.
Record typed relationships between two saved profiles:
| Command | Purpose |
|---|---|
relationship-type list |
Read forward and reverse labels for available types |
relationship-type create <slug> <forward-label> <reverse-label> |
Create a user-owned type; --symmetric requires identical labels |
relationship-type update <type-id> [flags] |
Change labels and presentation |
relationship-type delete <type-id> |
Delete an eligible user-owned type |
person relationship add <source-person-id> <type-slug> <target-person-id> |
Add a relationship; accepts --from, --until, and --notes |
person relationship list <person-id> [--include-ended] |
Read relationships from that person's perspective |
person relationship end <relationship-id> <until-date> |
End a relationship while keeping history |
person relationship delete <relationship-id> |
Permanently delete a relationship |
person relationship reviews [--status <status>] |
Inspect relationship review candidates |
All accept --json. The forward label means “source is the ___ of target.”
See relationships
for direction and date examples.
Connect one CardDAV account, choose its address-book roles, and resolve sync conflicts. Publishing or resolving a conflict can change the external address book; see the CardDAV guide.
| Command | Purpose |
|---|---|
add-carddav <base-url> <username> [--schedule <cron>] [--disabled] |
Discover and save an account; password is prompted or read from piped stdin |
add-carddav --google <email> [--oauth-app <name>] [--schedule <cron>] [--disabled] |
Connect Google Contacts using an authorized account token |
carddav authorize-google <email> [--oauth-app <name>] [--no-browser] |
Authorize Google Contacts in the browser, preserving existing Google permissions |
sync-carddav [--full] |
Synchronize the account; --full reconciles complete books |
person publish <person-id> / person unpublish <person-id> |
Publish a saved profile or remove its remote card |
person publish <person-id> --preview |
Print the exact vCard and approval token as JSON without publishing |
person publish <person-id> --approve <token> |
Approve reviewed changes; conflict previews also need explicit keep_local resolution |
carddav books |
List discovered books and their roles |
carddav books set-role <book-id> [--write-target] [--subscribed] [--lookup-source] |
Replace all three roles; omitted flags become false |
carddav conflicts list |
List unresolved conflicts |
carddav conflicts show <conflict-id> |
Compare bounded base, local, and remote summaries |
carddav conflicts resolve <conflict-id> <keep_local|keep_remote> |
Choose the local or remote side explicitly |
Directory and integrations can also use the
publication API.
CardDAV commands do not expose a general --json flag; role changes,
publication previews, and conflict list/show commands already print JSON.
Manage the confirmed "me" identifiers for each account.
The identity subcommands use the configured remote server or local daemon by default. --local uses the local daemon even when a remote is configured.
msgvault identity list [--account <account> | --collection <name> | --source-id <id>]
msgvault identity show [<account>] [--source-id <id>]
msgvault identity add [<account>] <identifier> [--source-id <id>]
msgvault identity remove [<account>] <identifier> [--source-id <id>]
msgvault identity discover [<account>] [--source-id <id>] [--apply]
msgvault identity import [<account>] [--source-id <id>] (--file <path> | --stdin)| Command | Description |
|---|---|
identity list |
List confirmed identifiers across accounts |
identity show <account> |
Show one account's identity in detail |
identity add <account> <identifier> |
Add a confirmed identifier |
identity remove <account> <identifier> |
Remove a confirmed identifier |
identity discover <account> |
Preview archived source evidence; --apply confirms strong candidates |
identity import <account> |
Preview or apply source-scoped identifiers from text or JSON |
| Flag | Applies to | Description |
|---|---|---|
--account |
list |
Restrict to a single account |
--collection |
list |
Restrict to all member accounts of a collection |
--source-id |
all subcommands | Select one source unambiguously by numeric ID; mutually exclusive with an account argument or list scope |
--json |
list, show, discover, import |
Output structured JSON; discovery also suppresses progress |
--signal |
add |
Evidence signal name (default manual) |
--apply |
discover |
After the complete preview scan, confirm strong evidence |
--provider |
discover |
Include the source's configured [[fastmail]] alias inventory |
--confirm <address> |
discover |
Explicitly confirm one weak candidate; repeatable and requires --apply |
--file <path> / --stdin |
import |
Read a text or JSON identity list from exactly one input |
--signal |
import |
Evidence signal recorded for imported identities (default manual) |
--apply |
import |
Confirm every validated imported identity; without it the command only previews |
Email sync enriches identities already confirmed for the source with strong
sender evidence from trusted Sent metadata; it does not confirm first-time
aliases. Review candidates with msgvault identity discover and apply strong
candidates with msgvault identity discover --apply. Recipient-only evidence
stays review-only. See People, Profiles, and Source Identities
for classifications, Fastmail inventory, and import formats.
Manage durable person profiles and their typed, historized attributes. A profile can be created by explicitly promoting an observed participant's identity cluster or by importing contacts from a subscribed CardDAV address book.
msgvault person promote <participant-id>
msgvault person list [--json]
msgvault person directory [flags]
msgvault person get <person-id> [--json]
msgvault person set-display-name <person-id> <display-name> [--json]
msgvault person set-display-name <person-id> --clear [--json]
msgvault person delete <person-id>
msgvault person merge <survivor-id> <absorbed-id> [flags]
msgvault person split <source-person-id> [flags]
msgvault person merge-history <person-id> [--json]
msgvault person merge-show <merge-id> [--snapshot] [--json]
msgvault person merge-candidate <candidate-id> [flags]
msgvault person attributes list <person-id> [--slug <slug>] [--history] [--json]
msgvault person attributes set <person-id> <slug> (--value <scalar> | --value-json <json|@path|->) [flags]
msgvault person attributes clear <person-id> <slug> [flags]promote is idempotent. set-display-name preserves the profile's stable ID
and vCard UID. delete permanently retires that UID and removes the profile's
participant bindings. A person with active merge lineage cannot be deleted
until that lineage is fully split.
merge keeps the survivor's ID and vCard UID, moves the absorbed profile into
it, and records a reversible merge packet. Profiles with active CardDAV
publication are rejected. split creates a new person and UID; repeat
--participant to select multiple absorbed lineages, or omit it for a merge
whose absorbed profile had no participants.
| Merge flag | Applies to | Description |
|---|---|---|
--survivor-revision <n> |
merge |
Required expected revision of the surviving person |
--absorbed-revision <n> |
merge |
Required expected revision of the absorbed person |
--idempotency-key <key> |
merge, split |
Required retry key; reusing it with a different request is rejected |
--merge-id <id> |
split |
Required merge record to reverse |
--participant <id> |
split |
Absorbed participant lineage to move; repeat as needed |
--revision <n> |
split, merge-candidate |
Required expected revision of the current person |
--person-id <id> |
merge-candidate |
Person that owns the review candidate |
--decision <accepted|rejected> |
merge-candidate |
Resolve a conflicting single-value attribute |
--snapshot |
merge-show |
Read and verify the immutable merge snapshot |
--json |
all merge commands | Output structured JSON |
Exact splits restore the pre-merge profiles when their lineage and referenced
rows remain available. For partial splits, use --json to inspect ambiguous or
unrestored rows. Complete merge packets retain merge-time profile values even
after later redaction and require the strongest profile-data options when
copied into a subset. See People, Profiles, and Source Identities
for the workflow and lifecycle boundaries.
| Attribute flag | Applies to | Description |
|---|---|---|
--slug <slug> |
list |
Restrict output to one definition |
--history |
list |
Include superseded values instead of current values only |
--value <scalar> |
set |
Coerce text, integer, real, boolean, date, or timestamp input to the definition's type |
--value-json <json|@path|-> |
set |
Supply a structured typed-value envelope |
--ordinal <n> |
set, clear |
Address one slot of a multi-valued definition |
--source <name> |
set |
Provenance: user, carddav_import, vcard_import, archive_observation, extraction, enrichment, or system |
--source-ref <value> |
set |
Resource or message reference that produced the value |
--confidence <0..1> |
set |
Confidence for a derived or suggested value |
--actor <value> |
set |
Actor recorded with the value |
--expected-value-id <id> |
set, clear |
Compare-and-swap guard for the current value |
--dry-run |
set, clear |
Validate and preview without writing |
--json |
list, set, clear |
Output structured JSON |
Setting or clearing a value closes the current history row rather than deleting it. See People, Profiles, and Source Identities for the shipped definitions and complete workflow.
Browse promoted people by last contact through the selected local or configured remote daemon. The default order is most recent first, last_contact_desc. Each page uses the daemon's default of 50 people.
msgvault person directory
msgvault person directory --last-contact-after 2026-06-01 --last-contact-before 2026-07-01 --json
msgvault person directory --sort last_contact_asc
msgvault person directory --sort name
msgvault person directory --last-contact-after 2026-06-01 --cursor "<next_cursor>"| Flag | Default | Description |
|---|---|---|
--last-contact-after <date> |
absent | Inclusive lower bound, YYYY-MM-DD or RFC3339 |
--last-contact-before <date> |
absent | Inclusive upper bound, YYYY-MM-DD or RFC3339 |
--sort <order> |
last_contact_desc |
name, last_contact_desc, or last_contact_asc |
--cursor <cursor> |
absent | Opaque next_cursor from the previous page |
--json |
false |
Output the full Directory page envelope |
Date-only bounds mean midnight UTC on that date. RFC3339 bounds accept offsets and fractional seconds. Both bounds include the specified instant, so --last-contact-before 2026-07-01 includes midnight at the start of July 1. Use a timestamp when you need a different cutoff.
Human output shows ID, DISPLAY NAME, and LAST CONTACT in daemon order. Timestamps use UTC RFC3339 at seconds precision; JSON preserves fractional seconds. Missing display names and timestamps show -. A page with more results prints Next cursor. Pass that value unchanged to --cursor and repeat the same bounds and sort to continue.
JSON contains a people array and optional next_cursor. Each person retains the Directory fields, including categories, organizations, contact state, ID, and revision. Optional fields stay absent when the daemon omits them, including last_contact_at for people without a contact timestamp. person list continues to return the full unpaginated profile collection, with its existing human columns and JSON array output.
Show the exact people inference provider policy and its check and consent state.
The JSON output includes the selected profile name and adds
stale_program_check or stale_program_consent when a verified historical record matches every policy field except the extraction
program and supplies a missing current gate. Human output identifies a
different extraction program and gives the recovery command without provider
credentials or response content.
msgvault person provider status [name] [--json]Run the existing synthetic provider check, then grant consent for the same
current exact provider profile. The check must succeed before consent is
granted. The command prints the provider disclosure and asks for confirmation
unless --yes is supplied.
msgvault person provider reverify [name] --yes [--json]Omit name to use the active provider, as with check and consent.
--yes confirms the disclosure. Human output includes the disclosure even with
--yes; --json returns the final exact check and consent status. The named
profile can be disabled in configuration, and this command still selects it for the operation without enabling scheduled sweeps.
Read a short summary before your next conversation with someone, or generate a new one from their recent supported chat and text messages.
msgvault person brief enroll <person-id> [--track] [--json]
msgvault person brief generate <person-id> [--json]
msgvault person brief show <person-id> [--json]
msgvault person brief history <person-id> [--limit 20] [--json]
msgvault person brief reject <person-id> [--reason "..."] [--json]
msgvault person brief unenroll <person-id> [--json]Enrollment requires tracking; --track enables both. Generation uses the
consented provider and its budget. History accepts limits from 1 to 200;
rejection hides the current version but preserves it in history. Unenrolling
stops future generation without deleting saved versions.
See person briefs for provider setup, supported sources, and a first-run example.
Update the mutable policy fields of an existing named people inference provider profile in place. The protocol, endpoint, auth scheme, credential source, executable, execution boundary, selection, and enablement stay unchanged.
msgvault person provider set <name> --model <model> [flags]The command reruns the exact synthetic provider check and revokes consent for
the previous fingerprint. Grant fresh consent with
msgvault person provider consent <name> --yes after reviewing the updated
policy. A running local daemon requires msgvault daemon restart before its
scheduled sweeps observe the change. Configured remote daemons refuse this
mutation; run it on the daemon host or pass --local.
| Flag | Default | Description |
|---|---|---|
--model |
unchanged | Provider model identifier |
--retention-posture |
unchanged | Provider retention assertion |
--training-posture |
unchanged | Provider training assertion |
--source |
unchanged | Allowed source class; repeatable, replaces the existing list |
--source-since |
unchanged | Earliest disclosed source date |
--source-until |
unchanged | Latest disclosed source date |
--allow-sensitive |
unchanged | Allow sensitive text in provider packets |
--reasoning-effort |
unchanged | Explicit reasoning effort |
--reasoning-mode |
unchanged | Explicit reasoning mode |
--request-timeout |
unchanged | Provider request timeout |
--yes |
false |
Confirm the final provider and privacy values; required |
--json |
false |
Output structured JSON |
Manage portable field metadata. Definitions add no runtime database columns; their universal IDs and slugs remain stable while labels and descriptions may change.
msgvault attribute-definition list [--object-type person|organization] [--include-hidden] [--json]
msgvault attribute-definition get <definition-id> [--json]
msgvault attribute-definition create --definition <json|@path|-> [--dry-run] [--json]
msgvault attribute-definition rename <definition-id> [--label <text>] [--description <text> | --clear-description] [--json]
msgvault attribute-definition delete <definition-id>create --dry-run performs local structural validation but cannot detect a
conflict with definitions already stored by the daemon. rename does not
change the immutable slug or universal ID. Deletion is limited to user-created,
deletable definitions that have no stored values.
Manage named groups of accounts.
The collection subcommands use the configured remote server or local daemon by
default. --local uses the local daemon even when a remote is configured.
msgvault collection create <name> --accounts <account1,account2,...>
msgvault collection list
msgvault collection show <name>
msgvault collection add <name> --accounts <account1,account2,...>
msgvault collection remove <name> --accounts <account1,account2,...>
msgvault collection delete <name>Deleting a collection does not delete sources or messages.
Find and merge duplicate messages within an account or collection.
msgvault deduplicate [flags]By default, each source is deduplicated independently. --collection is the explicit opt-in for cross-source deduplication.
| Flag | Description |
|---|---|
--dry-run |
Scan and report only; do not hide duplicates |
--account |
Scope dedup to one account |
--collection |
Dedup across every member account of a collection |
--content-hash |
Also detect duplicates by normalized raw MIME content |
--prefer |
Comma-separated source type preference order |
--undo <batch-id> |
Restore rows hidden by a previous dedup run; repeatable |
--delete-dups-from-source-server |
Stage same-source pruned duplicates for remote deletion |
--no-backup |
Skip the database backup before merging |
-y, --yes |
Skip confirmation prompt |
Permanently delete dedup-hidden messages from the selected msgvault archive.
msgvault delete-deduped --batch <batch-id>
msgvault delete-deduped --all-hiddenThe CLI sends the request to the configured remote daemon, or to the
auto-started local daemon when no remote is configured. It no longer opens the
SQLite database directly. The delete cannot be undone with deduplicate --undo;
when backups are enabled, the daemon writes the backup next to the database it
owns before deleting. Repeated --batch selections commit as one transaction;
if the request is canceled, none of the selected batches are left half-deleted.
| Flag | Description |
|---|---|
--batch |
Delete rows hidden by this dedup batch ID; repeatable |
--all-hidden |
Delete every dedup-hidden row regardless of batch |
--no-backup |
Skip database backup before deleting |
-y, --yes |
Skip confirmation prompt (--all-hidden still prompts) |
List top senders by message count.
msgvault list-senders [flags]| Flag | Description |
|---|---|
-n, --limit N |
Number of results (default: 50) |
--after YYYY-MM-DD |
Only messages after this date |
--before YYYY-MM-DD |
Only messages before this date |
--json |
Output as JSON |
List top sender domains by message count.
msgvault list-domains [flags]| Flag | Description |
|---|---|
-n, --limit N |
Number of results (default: 50) |
--after YYYY-MM-DD |
Only messages after this date |
--before YYYY-MM-DD |
Only messages before this date |
--json |
Output as JSON |
List all labels with message counts.
msgvault list-labels [flags]| Flag | Description |
|---|---|
-n, --limit N |
Number of results (default: 50) |
--after YYYY-MM-DD |
Only messages after this date |
--before YYYY-MM-DD |
Only messages before this date |
--json |
Output as JSON |
Build or update the Parquet analytics cache through the configured remote server or the local daemon.
msgvault build-cache [flags]| Flag | Description |
|---|---|
--full-rebuild |
Discard existing cache and rebuild |
The CLI sends the request over HTTP and streams the daemon's stdout/stderr back
to the terminal. A local daemon runs the DuckDB export in an isolated child
process so DuckDB's bundled SQLite library never opens the archive inside the
long-lived daemon process. With [remote].url configured, the remote daemon
builds its own cache; use --local only to target this machine's local daemon.
For automatic cache rebuilds after daemon-owned syncs, configure
[analytics].auto_build_cache in config.toml.
Rebuild the SQLite FTS5 search index.
msgvault rebuild-ftsUse this if verify reports FTS5 shadow-table corruption such as a malformed inverted index. The command rebuilds the search index from the canonical messages table.
Manage the vector embedding index used by --mode vector and --mode hybrid search. Requires a build with a vector backend (sqlite_vec for SQLite archives, pgvector for PostgreSQL archives) and a configured [vector.embeddings] endpoint. See Vector Search for prerequisites, model rotation, and troubleshooting.
msgvault embeddings <subcommand> [flags]| Subcommand | Description |
|---|---|
build |
Build or update the index. Incremental by default; --full-rebuild starts a new generation. |
resume |
Continue scan-and-fill embedding for the building or active generation. Incremental by default; --backstop also scans below the watermark. |
list |
List index generations with their state, model, dimension, and pending count. |
activate <generation-id> |
Activate a completed building generation, retiring the current active one. |
retire <generation-id> |
Retire a generation. |
msgvault embeddings build [flags]| Flag | Description |
|---|---|
--full-rebuild |
Create a new index generation and rebuild from scratch. The new generation is activated atomically once coverage reaches zero. Same-model rebuilds keep serving the previous active generation in the meantime, but active-generation top-ups are frozen until activation; model or dimension changes return index_stale for vector/hybrid search until the new generation activates. |
--yes |
Skip the confirmation prompt that --full-rebuild otherwise requires. |
--backstop |
Run a full-scan pass that ignores the per-generation watermark to recover missing coverage skipped by incremental scans. Already-covered messages are skipped. |
--account <identifier> |
Limit embedding to this account, by identifier or display name — numeric source IDs are rejected (repeatable). Overrides [vector.embed.scope] accounts for this run; configured message_types still apply. After activating this one-off scope, add the equivalent accounts to config and restart the daemon before searching. |
--collection <name> |
Limit embedding to this collection's accounts (repeatable). Can be combined with --account; the scope is the union. This is a one-run override; persist the resolved accounts in config before restarting the daemon. |
Without --full-rebuild, the command is incremental: it resumes any in-flight rebuild that matches the configured embedding settings, otherwise scans for live messages still missing coverage in the active generation, then exits. Safe to schedule via cron (or let msgvault serve do it via [vector.embed.schedule]).
The account scope is part of the generation fingerprint, so building with a
different --account/--collection set than the active generation requires
--full-rebuild, exactly like changing the model. See
Scoped Generations.
msgvault embeddings resume [flags]Continue embedding work and finish the current generation. If a generation matching the configured embedding settings is building, this embeds its remaining rows and activates it once coverage reaches zero; otherwise it tops up the active generation. Equivalent to msgvault embeddings build with no flags, but never starts a full rebuild. Accepts the same --account/--collection scope flags as embeddings build.
Pass --backstop to scan for missing coverage below the per-generation watermark
as well. This fills gaps in the selected generation without starting a full
rebuild; it only activates a generation if that generation is building.
msgvault embeddings resume --backstopmsgvault embeddings listPrint one row per index generation: ID, state (building, active, or retired), model, dimension, embedded message count, pending count, fingerprint, and the start, completion, and activation timestamps.
msgvault embeddings activate <generation-id> [flags]Activate a completed building generation and retire the currently active one. By default this refuses to activate a generation that still has messages missing coverage or whose fingerprint does not match the current config.
| Flag | Description |
|---|---|
--yes |
Skip the confirmation prompt. |
--force |
Activate even with missing coverage or a fingerprint mismatch. |
msgvault embeddings retire <generation-id> [flags]Mark a generation as retired. Retiring the active generation requires --force-active, since it leaves no generation serving vector/hybrid search.
| Flag | Description |
|---|---|
--yes |
Skip the confirmation prompt. |
--force-active |
Allow retiring the generation that is currently active. |
msgvault build-embeddings remains as a deprecated alias for msgvault embeddings build (same --full-rebuild and --yes flags).
Show statistics about the analytics cache.
msgvault cache-statsThe command queries the configured msgvault server over HTTP. With local configuration, the CLI auto-starts or reuses the local daemon, and the daemon reads the analytics cache files. With remote configuration, the remote server reports its own cache state.
Run arbitrary SQL against the Parquet analytics cache using an in-memory DuckDB engine.
msgvault query <sql> [flags]If the analytics cache is stale, it is automatically rebuilt before the query runs.
| Flag | Default | Description |
|---|---|---|
--format |
json |
Output format: json, csv, or table |
See SQL Queries for available views and example queries.
Run msgvault mcp status --json to list HTTP MCP listeners started by this
version. The command reads local runtime records without starting a server.
Each entry includes transport, url, pid, backend_url when known,
and token_path when the listener requires a bearer token. Read the token
from that private file; the status output does not print it.
The listener publishes its actual bound port after startup, including when started with port zero, and removes its record on orderly shutdown. Status omits records whose process has exited or whose process-start identity cannot be verified, including when another process reuses the PID. Stdio sessions are not listening endpoints and do not appear. An empty JSON list means no HTTP listeners were found in this application's configured data directory.
Start the Model Context Protocol server for AI assistant integration.
msgvault mcp [flags]| Flag | Default | Description |
|---|---|---|
--force-sql |
false |
Deprecated in 0.17.0; use [analytics].engine = "sql" in config.toml instead. See Configuration: analytics. |
--no-sqlite-scanner |
false |
Deprecated in 0.17.0; cache engine selection is daemon-managed. Use [analytics].engine = "sql" for live SQL. |
--http |
— | Serve MCP over StreamableHTTP on this address instead of stdio. Bare ports bind to loopback, e.g. 8080 becomes 127.0.0.1:8080. Non-loopback addresses require [server].api_key or --http-allow-insecure. |
--http-allow-insecure |
false |
Allow non-loopback HTTP binding without [server].api_key. A configured key is still enforced; without one, use only behind a trusted network boundary or authenticated reverse proxy. |
--http-allow-writes |
false |
Expose Saved View management, attachment export, and deletion staging tools over StreamableHTTP. Enable only for trusted, authenticated clients. |
See MCP Server for configuration and tool reference.
Install or remove the bundled msgvault agent skills:
msgvault skills install
msgvault skills uninstallInstall detects Claude Code and Codex from ~/.claude and ~/.codex, then
writes the msgvault-search, msgvault-attachments, and
msgvault-analytics skills to their user-level skill directories. Existing
generated copies are updated in place; files without msgvault's generation
marker are preserved unless --force is supplied.
| Install flag | Description |
|---|---|
--agent claude / --agent codex |
Restrict installation to one or more detected agents; repeat or comma-separate values |
--dir <path> |
Install into an explicit skill directory instead of detected agents |
--force |
Overwrite skill files that no longer carry the msgvault generation marker |
skills uninstall accepts --agent and --dir with the same target
semantics, and removes only generated copies that still carry the marker. See
Agent Skills for the workflow and safety model.
Print the checked-in msgvault OpenAPI contract without starting the daemon or opening the archive database.
msgvault openapi [flags]| Flag | Default | Description |
|---|---|---|
--version |
3.1 |
OpenAPI version to emit: 3.1 or 3.0 |
--format |
yaml |
Output format: yaml or json |
The OpenAPI info.version is the API schema version, not the msgvault binary version. Use it to reason about forward/backward compatibility when a local or remote CLI talks to a server. The running daemon also serves the same contract at /openapi.json; generated client artifacts are built from the OpenAPI 3.0 form for tool compatibility.
Manage the local background daemon used by HTTP-backed CLI commands.
msgvault daemon start
msgvault daemon status
msgvault daemon stop
msgvault daemon restartstart launches the daemon in the background, status reports its recorded URL/PID/version/API schema/uptime, stop shuts it down, and restart performs a stop followed by a start. Starting a newer compatible binary replaces an older recorded daemon when [server].daemon_auto_restart = "newer"; incompatible running daemons are reported with a prompt to stop them first.
The lifecycle commands have no command-specific flags. All configuration (port, bind address, API key, CORS, account schedules, SyncTech SMS sources, background idle timeout, daemon restart policy, and vector embedding schedule) is read from your config.toml. See Web UI & API Server for endpoint documentation, run msgvault openapi, or fetch /openapi.json from a running server for the generated OpenAPI contract. See Configuration for config options. When vector search is enabled, the daemon can also run the embed worker on a cron and/or after every successful sync, see Configuration: vector.embed.schedule.
Background daemons started by daemon start or auto-started by a CLI command shut down after [server].daemon_idle_timeout with no requests. The default is 20m; set it to "0s" to disable idle shutdown. MSGVAULT_DAEMON_IDLE_TIMEOUT can override the value for a lifecycle-managed background daemon.
[server].daemon_auto_restart controls local daemon replacement when the CLI and recorded daemon versions differ. The default newer restarts only older compatible daemons, never leaves lifecycle to the operator or supervisor, and always restarts on any version mismatch that is safe for the current API schema.
For compatibility with existing scripts, msgvault serve start|status|stop|restart remains accepted without warnings, but these aliases are hidden from help and shell completion.
Start the Web UI and API server with optional background sync scheduling in the foreground.
msgvault servemsgvault serve stays in the foreground until interrupted and is not idle-stopped. Use it for externally supervised, Docker, and NAS deployments; use msgvault daemon for local background lifecycle management.
Run the first-run setup wizard for OAuth and optional remote deployment.
msgvault setupIf configured for a remote server, this command generates <MSGVAULT_HOME>/nas-bundle with:
config.tomlready for container deploymentclient_secret.jsondocker-compose.yml
The wizard also stores remote URL/API key in remote config block so export-token can use it without extra flags.
Configure optional search and people features from the available API keys. The
command reads VOYAGE_API_KEY, OPENAI_API_KEY, and the document provider's
configured key variable (default MISTRAL_API_KEY). For an unset text feature,
it chooses Voyage contextual embeddings, then OpenAI embeddings, then an
available loopback Ollama model at [chat].server. For inference, it chooses
OpenAI, then the configured local Ollama chat model. These are setup choices;
the running daemon does not fall back to another provider after a failure.
Setup prints a plan, asks once per hosted provider, writes config.toml, and
prints the remaining commands. It onboards a new people-sweep provider through
the same check and consent gates as person provider. Other feature-specific
consents remain separate. Visual search needs a valid probe manifest before
setup enables it; document extraction remains manual. See
Recommended Configuration.
The people sweep stays pending unless --allow-sensitive is supplied. This
permits sending sensitive archive excerpts to its inference provider and
inferring sensitive personal attributes. --yes alone does not grant this
permission. Vector lanes also stay pending when the binary lacks the backend
required by the configured database; setup prints rebuild guidance.
Saved retention and training postures on disabled lanes are preserved unless
their corresponding posture flags are explicitly supplied. Custom hosted text
endpoints require explicit configuration of dependent lanes. Document semantic
search requires both documents vectors consent --yes and
documents vectors consent --purpose queries --yes; the latter authorizes
query-text uploads. The status report tracks the two purposes separately.
An existing text model or endpoint is preserved even if the feature is disabled.
Setup does not replace it when a different key appears. Explicit text and visual
schedule keys, including an empty cron and run_after_sync = false, are also
preserved. Setup does not configure source syncs, track people, enroll briefs,
or set provider prices and monetary caps.
msgvault setup providers --dry-run
msgvault setup providers
msgvault setup providers --allow-sensitive
msgvault setup providers --yes --document-retention zdr --document-training opted-out| Flag | Default | Description |
|---|---|---|
--yes |
false |
Accept every provider disclosure without prompting. Required to apply a plan with hosted-provider prompts when stdin is not a terminal or --json is used. It does not grant the separate sensitive-data opt-in. |
--allow-sensitive |
false |
Allow the people sweep to send sensitive archive excerpts and infer sensitive personal attributes |
--dry-run |
false |
Print the plan, the disclosures, and the current lane report without writing |
--document-retention |
standard |
Mistral retention posture to record: standard or zdr |
--document-training |
default-opt-out |
Mistral training posture to record: default-opt-out or opted-out |
--retention-posture |
provider-declared |
Retention assertion recorded for embedding and inference providers |
--training-posture |
provider-declared |
Training assertion recorded for embedding and inference providers |
--json |
false |
Output the plan, applied flag, follow-ups, and report as JSON |
The command edits the config file on this machine and refuses to run against a
configured remote daemon; run it on the daemon host or pass --local.
Report the setup state of each feature (text search, semantic people search,
visual attachments, documents, document vectors, people sweep, activity
projection, media policy): state, provider, model, recorded consent, schedule,
the reason a lane is off, and the command that turns it on. Reads config.toml,
the environment, and the local archive's consent records; never contacts a
provider. Stored provider credentials are checked too. An on state means the
setup checks passed; it does not prove that an index has been built or that a
provider will answer. The MCP summary is a partial list derived from enabled
configuration flags, not the live server's tools/list response. Use the
feature's status command or Web Operations to inspect
index progress and coverage.
msgvault setup status
msgvault setup status --jsonShow full message details.
msgvault show-message <id> [flags]| Flag | Description |
|---|---|
--json |
Output as JSON |
List synced email accounts.
msgvault list-accounts [flags]| Flag | Description |
|---|---|
--json |
Output as JSON |
Update account settings through the configured remote server or local daemon.
Use --local to force the local daemon when a remote is configured.
msgvault update-account [account] [flags]| Flag | Description |
|---|---|
--display-name |
Set a display name for the account |
--source-id ID |
Update exactly one source by numeric ID; mutually exclusive with the account argument |
The account argument accepts an identifier or a unique display name. Supply
either an account or --source-id.
Remove an account or source and all its archived data from the selected msgvault archive. Deletes messages, labels, sync state, credentials no longer shared by another source, and attachment files unique to this source. This is irreversible but does not touch the remote provider.
msgvault remove-account [account] [flags]
msgvault remove-account 123456789012345678 --type discord
msgvault remove-account --source-id 42| Flag | Description |
|---|---|
-y, --yes |
Skip the confirmation prompt (and allow removal when an active sync is in progress) |
--type |
Source type to remove when the same identifier exists across source types (gmail, imap, mbox, discord, etc.) |
--source-id ID |
Remove exactly one source by numeric ID; mutually exclusive with the account argument and --type |
The account argument accepts an identifier or a unique display name.
Attachment files are only deleted when no other account references the same content hash. A Discord bot token is preserved while another registered guild uses it, and deleted after its last source is removed. The shared Parquet analytics cache is rebuilt automatically.
Stage active messages as a pending deletion batch using exactly one selector:
Gmail-like search criteria or a comma-separated list of explicit internal message
IDs. The query form uses the daemon's search and preflight checks before staging;
the ID form sends the existing message_ids field directly to the daemon. The
command does not delete messages from a provider.
In query mode, staging is refused while the daemon is still verifying or rebuilding its full-text search index, or while its analytical cache is unavailable, because an incomplete index could silently omit matching messages. Both states resolve in the background; retry when they finish. These search-index and analytical-cache requirements apply to query mode.
In query mode, like deletion staging in the TUI and Web UI, the selection is resolved against the committed analytical snapshot, so messages synced after the most recent cache build are not included until the daemon refreshes the cache (it does so automatically after each sync). Re-run the command after a refresh to stage newly synced matches.
msgvault stage-delete <query>
msgvault stage-delete --ids 123,456,789Exactly one of <query> and --ids IDS is required. The selectors are mutually
exclusive, and --ids is also mutually exclusive with --source-id.
| Flag | Description |
|---|---|
--dry-run |
Show the staged subset and skipped counts without creating a deletion batch |
--source-id ID |
Restrict staging to one exact source ID |
--ids IDS |
Stage positive, unique, comma-separated internal message IDs instead of a query |
Query staging requires daemon API schema 2.18.0 or newer so the preflight reports the exact deletable subset. Upgrade the daemon if the CLI rejects its schema version.
The query resolves with the same search semantics as msgvault search, and
--dry-run prints the set that staging would create.
Deletion staging covers Gmail-source email only. A search that also matches
chats, meetings, calendar entries, or mail from non-Gmail sources such as Apple
Mail imports stages the Gmail subset and reports how many items it skipped. Only
a search with nothing deletable in it is refused. Legacy Gmail messages imported
before message types existed carry a blank type and count as email, so
message_type:email stages them too.
In ID mode, the daemon resolves live Gmail targets and source boundaries for the requested IDs; IDs that do not resolve to live deletable Gmail messages with provider message IDs are omitted, so the reported count is the number of targets resolved by the daemon; the CLI does not search, probe FTS readiness, call Explore or preflight, or require analytical-cache readiness. A newly started local daemon may initialize its cache in the background for later query consumers.
For a query that resolves to one source, the source selector is optional. For a
multi-source query, provide --source-id for each source:
msgvault stage-delete --source-id 42 "from:newsletter@example.com older_than:1y"Review a created batch with msgvault show-deletion <batch-id>, then execute it
with msgvault delete-staged <batch-id>.
List pending and recent deletion batches.
msgvault list-deletionsShow details of a deletion batch.
msgvault show-deletion <batch-id>Cancel pending or in-progress deletion batches. When called without a batch ID, lists available batches.
msgvault cancel-deletion [batch-id]
msgvault cancel-deletion --all| Flag | Description |
|---|---|
--all |
Cancel all pending and in-progress batches |
Execute staged remote deletions. Gmail and IMAP move messages to Trash by
default. --permanent uses Gmail batch deletion or IMAP UID EXPUNGE; the
IMAP permanent path requires UIDPLUS. Recovery from Trash depends on the
provider. See deletion behavior.
msgvault delete-staged [batch-id] [flags]| Flag | Description |
|---|---|
-y, --yes |
Skip confirmation prompt |
--permanent |
Permanently delete through Gmail batch deletion or IMAP UID EXPUNGE instead of moving to Trash |
--dry-run |
Show what would be deleted without deleting |
-l, --list |
List staged deletion batches |
--account |
Filter to one source by identifier or unique display name |
--source-id ID |
Filter to exactly one source by numeric ID; mutually exclusive with --account |
Starting in v0.20.0, remote deletion remains permanently opt-in. The invoking
CLI may enable it durably with [deletion] remote_enabled = true or for one
command with MSGVAULT_ENABLE_REMOTE_DELETE=1. Both mechanisms are permanent;
there is no planned automatic removal of the guardrail. A remote daemon's own
[deletion] section is not server policy for a command invoked elsewhere.
Staging, --list, and --dry-run remain ungated. --permanent and --yes
are mutually exclusive because permanent deletion always requires the
destructive confirmation prompt.
Confirm a source's own email address and recompute is_from_me for matching
archived messages. This writes immediately; it has no dry-run flag. Sources
whose account address is already confirmed are skipped.
msgvault repair-identity user@example.com
msgvault repair-identity --type imapThe optional identifier narrows the repair. --type selects a source type and
defaults to gmail. Only plain, valid email addresses are confirmed. For
reviewing other aliases, use identity discovery.
Recover missing sender metadata from archived email MIME. The default is a
read-only report; --apply writes repairs and refreshes analytics. This repairs
messages that have neither sender_id nor a From-recipient snapshot and does
not replace existing sender evidence or contact a provider.
msgvault repair-senders
msgvault repair-senders --applyRepair one Gmail message snapshot, or audit stored Gmail MIME without changing it. The repair form can fetch the message from Gmail; the audit is local.
msgvault repair-message --audit
msgvault repair-message --audit --source-id 42 --json
msgvault repair-message 123 --source-id 42| Flag | Description |
|---|---|
--audit |
Inspect stored MIME; accepts no message argument |
--source-id |
Limit to one positive source ID |
--json |
Emit newline-delimited audit results; requires --audit |
Without --audit, supply exactly one internal numeric message ID or Gmail ID.
Recompute derived text and metadata from retained provider payloads without contacting the provider. The command writes immediately and refreshes analytics; it leaves raw payloads, downloaded media, and sync cursors in place.
msgvault repair-derived
msgvault repair-derived --source-type beeperRepeat --source-type or --identifier to narrow the source set. Only source
types with a registered re-derivation pass are supported; an unknown requested
type is an error. Source syncs also run pending re-derivation passes, so use this
command for on-demand repair or retrying an interrupted pass.
Permanently purge all source-deleted messages from a SQLite archive, compact the database, and remove loose attachment blobs no remaining message references. Active messages and messages hidden only by deduplication remain. This does not contact providers and does not run on PostgreSQL.
msgvault gc| Flag | Default | Description |
|---|---|---|
--yes, -y |
false |
Skip interactive confirmation |
--no-backup |
false |
Skip the automatic SQLite database backup |
There is no source/date selector or dry-run flag. The automatic backup covers the database, not attachment bytes removed afterward. Create a complete backup first. After a purge, follow the printed instructions to rebuild each enabled analytics or embedding cache. See local purge for the difference from remote deletion.
Remove locally stored provider media excluded by the current media policy, replacing its occurrences with typed skip markers. Messages remain. Shared blobs stay live while a retained occurrence references them; packed dead bytes are reclaimed by the daemon's normal repack maintenance.
msgvault purge-excluded-media --dry-run
msgvault purge-excluded-media--dry-run previews occurrence, blob, and logical-byte counts without changing
the archive. Applying requires confirmation or --yes (-y). This never
removes media from a provider. Review media policy
and backup before applying.
Fix UTF-8 encoding issues in existing messages through the configured remote server or local daemon. The command streams the daemon's stdout/stderr back to the terminal, and the daemon serializes the repair with other archive mutations.
msgvault repair-encodingReport email messages whose canonical sent_at is missing, before 1990, or
more than 30 days in the future. The command resolves replacements from a
plausible Date header, the oldest plausible Received timestamp, or stored
source metadata, in that order.
The default is a read-only report. Pass --apply to update the archive and
write a JSON audit ledger under the data directory. SQLite archives also
rebuild the Parquet analytics cache; PostgreSQL archives do not use that cache.
Original source files and remote servers are never modified.
msgvault repair-dates
msgvault repair-dates --apply| Flag | Description |
|---|---|
--apply |
Write repaired dates to the archive |
Update msgvault to the latest version.
msgvault update [flags]| Flag | Description |
|---|---|
--check |
Check for updates without installing |
-y, --yes |
Skip confirmation prompt |
-f, --force |
Force update even if already on the latest version |
Print version, commit, build date, and platform information.
msgvault versionGenerate a shell completion script.
msgvault completion [bash|zsh|fish|powershell]To load completions:
Bash:
source <(msgvault completion bash)
# Permanent (Linux):
msgvault completion bash > /etc/bash_completion.d/msgvault
# Permanent (macOS with Homebrew):
msgvault completion bash > $(brew --prefix)/etc/bash_completion.d/msgvaultZsh:
msgvault completion zsh > "${fpath[1]}/_msgvault"If shell completion is not already enabled, add autoload -U compinit; compinit to your ~/.zshrc first.
Fish:
msgvault completion fish > ~/.config/fish/completions/msgvault.fishPowerShell:
msgvault completion powershell | Out-String | Invoke-ExpressionView and tail structured log files from the selected daemon. With [remote].url configured, this shows remote daemon logs; otherwise it starts or contacts the local daemon. File logging must be enabled first (see Configuration: Log).
msgvault logs [flags]| Flag | Default | Description |
|---|---|---|
-f, --follow |
false |
Follow today's log file as new lines are written |
-n, --lines |
50 |
Number of trailing lines to show before following |
--run-id <id> |
— | Filter to a single run (matches on prefix) |
--level <level> |
— | Filter by log level: debug, info, warn, error |
--grep <string> |
— | Substring filter applied to the raw JSON record |
--all |
false |
Read every log file in the logs directory, not just today's |
--path |
false |
Print the selected daemon's log directory path and exit |
Examples:
# Last 50 lines of today's log
msgvault logs
# Follow live
msgvault logs -n 200 -f
# Filter to a single run by its correlation ID
msgvault logs --run-id a1b2c3
# Only errors
msgvault logs --level error
# Substring search across all log files
msgvault logs --all --grep deduplicatePrint a quickstart guide for AI agents.
msgvault quickstartManage restricted agent grants. The daemon must be started with [server] agent_access = true
and a non-empty [server] api_key. All three subcommands require owner authentication
(the owner API key; keyless loopback is not sufficient); a delegated caller cannot issue or modify grants.
A restricted agent token scopes and revokes access for an agent process. It is not a
boundary against an agent that holds shell access as the daemon owner: such an agent can
read provider credentials, rewrite config.toml, or replace the daemon binary. What
tokens buy is scoping and revocability for an agent already constrained by its deployment
environment, plus a usable way to run the daemon somewhere the agent cannot reach.
A token does not defend against a local agent process running under the same user account as the daemon. The supported model keeps owner credentials, provider credentials, daemon configuration, and daemon replacement outside the agent's authority — a remote user-managed daemon achieves that, and so does an isolated local agent environment, while a second daemon or data directory under the same unrestricted user does not.
Tokens are in-memory and process-scoped. All grants are invalidated when the daemon restarts. A grant is valid until it is revoked or the daemon restarts. There is no persistence to disk and no migration needed.
Issue a new restricted grant for one agent. The secret is printed once and not stored by the daemon (only its SHA-256 digest is kept in memory). Write it to a file that the agent can read, never pass it as a flag or environment variable.
msgvault agent-token issue --label <name> \
--permissions draft.create \
--source-ids <id>[,<id>...]| Flag | Description |
|---|---|
--label <name> |
(required) Human-readable name for the grant |
--permissions <perms> |
Comma-separated permission names to grant; accepted values: draft.create |
--source-ids <ids> |
Comma-separated source IDs that the permissions apply to |
The grant is valid until revoked or until the daemon restarts.
The response includes the daemon address, the secret, and the granted source references.
Pass --agent-url <address> and the file path to --agent-token-file when invoking delegated commands.
The address comes from the issuing request. On a default local install it is an
HTTP loopback URL, which works only on that machine and requires
--agent-allow-insecure. For an agent on another machine, use a reachable HTTPS
address instead. Plain HTTP requires --agent-allow-insecure and sends the token
without transport encryption; use it only on a trusted network.
List all active grants. Secrets and digests are never returned.
Agent-token commands return an error when server.agent_access is disabled.
msgvault agent-token listEach row shows the grant ID, label, permissions, sources (as id/type/identifier), and creation time.
Revoke a grant by ID. Returns 204 whether or not the ID existed, so grant IDs cannot be
enumerated by probing revoke.
msgvault agent-token revoke <id>The grant is removed from the in-memory registry immediately. Any request in flight that already passed authentication completes, but the next authentication attempt with the revoked secret is denied without fallback.