| last_edited | 2026-10-05 |
|---|---|
| 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 0.21.0 changelog for features and upgrade notes.
| 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, import-imazing-csv, text imports |
| Search and browse | search, tui, show-message, documents, embeddings, multimodal, eval |
| Maintain people and contacts | person, people guide, CardDAV |
| Organize accounts | identity, collection, update-account |
| Read meeting evidence | meetings, meeting workflow |
| Export | export-messages, export-eml, export-attachments, create-subset |
| 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, credentials, daemon, serve, activity, mcp, query, openapi, agent-token |
Read archived meeting evidence through the selected daemon. Requires API schema 2.27.0 or newer. See the meeting workflow for coverage and source limits.
msgvault meetings context --id 42 --id 43 --format markdown --output context.md
msgvault meetings actions --status pending --assignee alex@example.com --json
msgvault meetings metrics --domain example.com --after 2026-01-01 --json| Flag | Contract |
|---|---|
--id |
Required meeting message IDs, repeatable; at most 100 |
--format |
markdown (default) or json |
--include-transcript |
Include archived transcript evidence; off by default |
--max-bytes |
UTF-8 content budget, default 131072; range 4096–1048576 |
--output, -o |
Output file; omitted or - writes the exact packet content to stdout |
Mixed or invalid selections fail. Truncated packets identify omitted content and meetings.
These commands share the following scope flags. Values within a filter group are alternatives; different groups intersect.
| Flag | Contract |
|---|---|
--id |
Meeting message ID, repeatable |
--source-id |
Source ID, repeatable |
--domain |
Exact participant domain, repeatable |
--participant-id |
Exact participant ID, repeatable |
--person-id |
Durable person ID; mutually exclusive with --participant-id |
--after, --before |
YYYY-MM-DD at UTC midnight; after inclusive, before exclusive |
--deletion |
Source deletion state: any (default), active, or deleted |
--json |
Structured response including coverage or duration bases |
ID lists and domain lists accept at most 100 values. IDs must be positive JavaScript-safe integers. Locally deleted records are always excluded.
meetings actions also accepts:
| Flag | Contract |
|---|---|
--status |
pending, completed, cancelled, or unknown; default all |
--assignee |
Exact assignee email |
--query |
Literal title or description substring, at most 256 characters |
--limit |
Action rows per page, default 50; range 1–200 |
--cursor |
Opaque continuation cursor from the previous page |
Actions reflect the current archived source snapshot. Keep filters unchanged when continuing a page; refresh from page one to see newer evidence.
| 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. With
[server].daemon_auto_start = false, they use a daemon that is already running or starting and never start one. --localselects the local daemon even when[remote].urlis configured; it is not a request to open SQLite in the CLI process.- With both
--agent-urland--agent-token-file, the CLI connects to a remote daemon as a restricted caller.draft-reply,draft-compose,draft-get,draft-edit,draft-delete, anddraft-recoverare available in this mode. The CLI rejects owner configuration (--config,--home,--local) and never writes the token to logs or argv. It sends the token in theX-Msgvault-Agent-Tokenheader; generated OpenAPI clients do not model this transport detail.
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. Saved across syncs and re-authorization; only explicit --no-default-identity=false clears the choice. See saved identity choice |
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-account (including service accounts), add-imap, add-o365 (IMAP and
--graph), and add-teams save --no-default-identity for later syncs.
Omitting the flag when adding or re-authorizing an existing account keeps its
saved choice. Use --no-default-identity=false to re-enable automatic defaults.
New accounts confirm their default identity unless the flag is set.
The flag does not remove existing identities or prevent the one-time legacy
[identity] migration from adding configured addresses. If the saved sync
configuration cannot be read, automatic identity confirmation logs a warning
and skips the write.
Removing the last confirmed identity also saves the no-default choice for
future syncs. To restore automatic confirmation, run the source's add command
with --no-default-identity=false.
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. Saved across syncs and re-authorization; only explicit --no-default-identity=false clears the choice. See saved identity choice |
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 message to an authorized IMAP
destination, or reply within its original Gmail account. The daemon requires
the matching operator grant. --from is optional when exactly one confirmed
identity is eligible.
msgvault draft-reply <message-id> --body <text>
msgvault draft-reply <message-id> --all --account <account> --from <address> --body <text>
msgvault draft-reply <message-id> --source-id 42 --body= --jsonUse --all to include the parent sender, To, and Cc recipients. Msgvault
deduplicates them in order, removes the destination account's confirmed
identities, and never copies the parent's Bcc recipients. Use --account or
--source-id to choose a destination. An offline parent requires one of those
selectors; msgvault never treats imported provenance as provider credentials.
The 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 and selected sender appear in the token's frozen grant; see
agent-token. A draft From choice is local header selection.
It does not prove provider send-as permission. Draft creation never sends mail.
A later sync reconciles the saved membership when the mailbox's UIDVALIDITY
changes. Draft creation never moves an IMAP cursor.
For Gmail, enable drafting per source in the daemon host's config.toml and
restart the daemon:
[[gmail.drafts]]
source_id = 42
enabled = trueGmail accepts draft writes with gmail.modify, mail.google.com, or
gmail.compose. Creation also lists send-as entries. That read accepts
gmail.settings.basic, gmail.modify, gmail.readonly, or
mail.google.com. The daemon checks saved scope information when available;
for older tokens without it, Gmail decides whether to allow the request.
The daemon accepts --from when it is the primary address or an accepted
alias. Service-account sources use the scopes in their delegated assertion.
Gmail writes use one provider request. A 429 or rate-limit 403 is a rejection;
retry the command after the limit clears. A transport failure, response-read
failure, or 5xx returns remote_unknown. For an uncertain creation, inspect
Gmail using the reported RFC822 Message-ID before creating another draft.
Managed edits and deletes reconcile on retry as described below. The archive
stores the Gmail draft and its DRAFT label after a confirmed response.
draft-reply never sends mail.
Create a new plain-text IMAP draft on one selected live source. Provide at least
one --to, --cc, or --bcc value. The flags are repeatable and keep their
envelope roles.
msgvault draft-compose --account you@example.com \
--from you@example.com --to recipient@example.com \
--subject 'Project update' --body 'Draft text'
msgvault draft-compose --source-id 42 --to recipient@example.com \
--bcc private@example.com --jsonThe source selector is required. The daemon applies the same host draft policy,
confirmed identity check, delegated sender grant, UIDPLUS requirement, and
structured provider outcomes as draft-reply. It stores the Bcc envelope in
the draft so the mail application can use it. It never sends the message or
validates provider send-as rights.
For a Beeper source, draft-compose leaves a text draft in the composer of an
existing chat. --to takes the Beeper chat ID, and --body the text; no other
fields apply. Enable it per source in the daemon host's config.toml, the same
way as Gmail, and keep Beeper Desktop running:
[[beeper.drafts]]
source_id = 42
enabled = truemsgvault draft-compose --source-id 42 --to '!room:beeper.local' --body 'Draft text'The chat's composer must be empty. Each chat has at most one managed draft;
draft_exists reports the existing one. draft-get, draft-edit, and
draft-delete work on the returned draft ID, and draft-delete clears the
composer. The edit body must be nonblank. Delegated tokens need draft.create on the
Beeper source to create; draft-get accepts draft.edit or draft.delete,
draft-edit needs draft.edit, and draft-delete needs draft.delete.
Delete-only agents receive draft metadata without committed or pending text,
including in error responses. A creator's draft_exists result carries the
draft ID without its text. draft-get shows the draft as
Beeper last reported it, which can differ from the text sent because Beeper
formats it. Edit and delete read the composer first and return draft_conflict
when someone changed it in Beeper. Beeper has no conditional write, so text typed
between that read and the write can still be cleared. A write Beeper refuses before anything changed
returns provider_rejected; a new draft whose text Beeper refused is
discarded, so the chat is free for another draft-compose. A write with an
unknown outcome returns remote_unknown and keeps the draft's pending
operation; the next edit or delete settles it from what Beeper shows. An edit
that finds an earlier delete settled, or a new draft's text never delivered,
returns draft_discarded. When Beeper shows text msgvault cannot
attribute, the command returns pending_operation; clear the composer in
Beeper and retry. not_supported means the installed Beeper Desktop does not
report chat drafts, chat_not_found that Beeper has no chat with that ID,
provider_unavailable that Beeper Desktop or its saved token cannot be
reached, and provider_identity_mismatch that Beeper answered
for another chat or account.
Create an IMAP draft that forwards an archived message and reuses its retained
attachment files. Choose exactly one destination with --account or
--source-id, and provide at least one explicit --to, --cc, or --bcc.
The parent message's Bcc recipients are never copied.
This command requires owner access; agent tokens cannot invoke it.
msgvault draft-forward <message-id> --account you@example.com \
--from you@example.com --to recipient@example.com --body 'Please review'
msgvault draft-forward <message-id> --source-id 42 \
--cc team@example.com --jsonEvery nonempty attachment must have a retained, readable file in the archive's catalog, even when its bytes remain in the original message. Forwarding reuses those catalog references; it does not restore skipped or missing files from raw MIME. Empty attachment parts need no stored file.
A pending, skipped, failed, unavailable, missing, unreadable, or unsupported
occurrence stops the command before APPEND. It never creates a partial draft.
The IMAP source must have an enabled [[imap.drafts]] entry, and the selected
sender must be a confirmed identity on that source.
The draft has two inline plain-text parts: your editable note, then a
---------- Forwarded message ---------- separator with the original headers
and text. The original attachments follow, retaining their names and
Content-ID values when present. Attached emails (message/rfc822) keep their
bytes with 8-bit encoding. An attached email that needs binary encoding, such as
one containing NUL bytes, bare line breaks, or lines longer than 998 bytes, is
refused. Unknown MIME dispositions are treated as attachments.
The draft has a new Date and Message-ID. Its subject has one Fwd: prefix;
existing Fw: and Fwd: prefixes are removed. The X-Msgvault-Forward: 1
header identifies the layout for note editing. A mail client that preserves
custom headers may include it when you send the message. msgvault only creates
the draft.
When the server supplies a numeric APPENDLIMIT, msgvault checks the encoded
size before uploading; otherwise the server's APPEND response decides. This
upload check does not limit the memory used to build the draft.
With --json, attachment preflight refusals emit a JSON object on stderr with
status: "attachment_preflight_failed" and a problems array. Each problem has
a reason, plus filename, part_key, and detail when available:
| Reason | Meaning |
|---|---|
missing_catalog_reference |
An original attachment has no matching catalog row. |
attachment_pending, attachment_skipped, attachment_failed, attachment_unavailable |
The catalog records a file that is not stored. detail includes its skip reason when available. |
unreadable_file |
Opening, reading, verifying, or closing a stored file failed. |
catalog_size_mismatch |
The stored file's length differs from the original MIME part. |
unrepresented_attachment |
A catalog row has no corresponding original attachment. |
attachment_reader_unavailable |
The daemon cannot access the attachment store. |
unrepresentable_attachment |
An attachment's metadata or bytes cannot be forwarded, or the generated draft cannot retain its catalog reference. |
Read, edit, or delete a managed draft created by draft-reply,
draft-compose, or draft-forward:
msgvault draft-get <draft-id> [--json]
msgvault draft-edit <draft-id> --revision <n> --body <text> [--json]
msgvault draft-delete <draft-id> --revision <n> [--json]
msgvault draft-recover <draft-id> --revision <n> [--json]The creation result supplies the opaque draft_id and initial revision.
--revisionis required for edit, delete, and recover; use the current positive revision.--bodyis required for edit;--body=sets an empty plain-text body.--jsonemits one JSON result.
draft-edit --body replaces the note in a draft created by draft-forward and
keeps the quoted text and attachments. Other drafts must be text/plain; any
other multipart draft returns invalid_draft before the provider draft changes.
draft-get reads retained archive content, including discarded drafts, without
connecting to a provider or requiring the source's draft mutation grant. For
IMAP drafts, edit and delete continue to require the configured [[imap.drafts]]
policy. For Gmail drafts, edit and delete require the configured
[[gmail.drafts]] policy and one of gmail.modify, mail.google.com, or
gmail.compose. They do not list send-as entries. Delete removes the provider
draft and retains its archived content. Gmail edit and delete inspect the
current provider message ID before making a change. A Gmail web edit advances
the local revision and returns changed_externally; the next mutation must name
that revision. These commands never send mail.
Retry a pending Gmail edit or delete with its current revision. The daemon
checks Gmail before writing again. If the original message is unchanged, it
clears the pending claim and runs the requested operation. If Gmail holds a
recorded replacement, the daemon finishes publishing it locally and returns
recovered with revision_mismatch; review the new revision before retrying.
Other provider edits are adopted with changed_externally. A pending delete
can finish locally when Gmail confirms absence, or retry deletion when the
original draft is still present. Confirmed deletions with unfinished local
cleanup retry that cleanup without another provider request. If Gmail no longer
has a draft during an edit retry, the command returns provider_absent and
keeps the candidate content. Use draft-delete to finish discarding it.
Recovery applies to IMAP drafts only. draft-recover refuses a Gmail draft ID
with not_supported for the owner and not_permitted for delegated tokens.
The delegated refusal does not reveal whether the draft exists. Gmail
reconciliation uses edit and delete retries.
Delegated tokens with draft.create can create Gmail reply drafts.
For a Gmail or IMAP draft, delegated draft-get accepts draft.create,
draft.edit, or draft.delete. draft-edit requires draft.edit, and
draft-delete requires draft.delete. Each command requires the source's exact
type and identifier and the draft's archived From sender in the grant. The
frozen sender selection applies to get, edit, and delete, including when the
owner issued the token with --sender. If Gmail reports an external edit,
the daemon checks the adopted draft's sender again before returning it.
A grant with only draft.delete receives lifecycle metadata, including the
revision, from get and delete responses. These responses omit content,
raw_mime, and candidate_content in JSON and human-readable output, including
pending and refused deletes. A matching draft.create or draft.edit grant
allows content reads. Delegated recovery continues to return metadata only.
A missing command permission returns
HTTP 400 command_not_allowed. A permitted command targeting another source or
an unknown draft ID streams not_permitted before revision checks, draft
policy, source locking, or any provider request. draft-send-as remains
owner-only.
For IMAP drafts, recovery resumes a pending operation from recorded receipts. It can publish a known replacement or finish
confirmed removal without APPEND. Delegated recovery requires
draft.edit for an edit or active repeat and draft.delete for a delete or
discarded repeat, scoped to the source in the grant. An active draft with no
pending operation requires draft.edit, including after a delete was aborted
before writing. Recovery output keeps the saved pending_code.
Refused results use refusal_code; pending cleanup results describe the current
provider result in observation.code. See
Manage a created draft for revision,
provider checks, retention, retry behavior, and recovery limits.
Slack, Teams, and Discord conversations get drafts that live only in msgvault. They never appear in the provider's composer and no command makes a provider request.
msgvault draft-compose --conversation <conversation-id> --body <text> [--reply-to <message-id>] [--json]
msgvault draft-get --conversation <conversation-id> [--json]The conversation's source owns the draft. --reply-to names an archived
message in the same conversation. draft-get --conversation lists that
conversation's drafts oldest first, so a lost draft ID can be found again;
--json returns one array. draft-get, draft-edit, and draft-delete take
the returned chat-draft- ID and report location as msgvault. A stale
revision returns revision_mismatch and leaves the draft unchanged.
draft-recover returns not_supported. Delegated tokens use the same
permissions as mail drafts, scoped to the conversation's source, except that
listing and retrieval need draft.edit or draft.delete. Removing the source or
conversation deletes its local drafts.
List Gmail send-as identities for an owner-invoked Gmail account:
msgvault draft-send-as <account> [--json]Gmail accepts this read with gmail.settings.basic, gmail.modify,
gmail.readonly, or mail.google.com. Saved scope information is checked
when available. The command reports the address, display name, primary and
default flags, verification status, and whether the
address is a confirmed msgvault identity. Delegated agent tokens cannot run
this command. It does not require [[gmail.drafts]] and never changes Gmail.
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) |
--headless |
false |
Sign in with a device code instead of a local browser |
--no-default-identity |
false |
Do not auto-confirm the email address as this account's "me" identity. Saved across syncs and re-authorization; only explicit --no-default-identity=false clears the choice. See saved identity choice |
--graph |
false |
Sync through the Microsoft Graph mail API instead of IMAP. Creates an msmail account. Needs the Mail.Read permission. delete-staged asks for Mail.ReadWrite on first use |
--as |
— | With --graph: sign in as this user and sync <email> as a shared or delegated mailbox the user can open. Needs the Mail.Read.Shared permission. delete-staged does not support these accounts yet |
After adding the account, sync it with msgvault sync-full. For a --graph
account, use msgvault sync. See
Microsoft Graph mail sync.
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 |
--headless |
false |
Sign in with a device code instead of a local browser |
--no-default-identity |
false |
Do not auto-confirm the email address as this source's "me" identity. Saved across syncs and re-authorization; only explicit --no-default-identity=false clears the choice. See saved identity choice |
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 |
--build-cache |
Queue a cache refresh after the sync, even inside min_rebuild_interval |
--no-build-cache |
Skip the post-sync cache refresh |
--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 |
--build-cache |
Queue a cache refresh after the sync, even inside min_rebuild_interval |
--no-build-cache |
Skip the post-sync cache refresh |
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.
The same cache flags apply to message-writing sync-* commands. By default,
manual syncs leave a usable stale cache in place until the minimum rebuild
interval expires. The daemon owns any refresh after the sync command returns.
The two flags are mutually exclusive.
For sync and sync-full, either cache flag requires daemon API schema 2.31.0
or newer. The CLI reports an upgrade error before starting sync against an
older daemon.
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 and register a configured Plaud cloud account using browser OAuth.
msgvault add-plaud [identifier]With one configured [[plaud]] entry, omit the identifier. The browser callback
runs on the daemon host at localhost:8091/callback/plaud. A configured remote
refuses before proxying; use SSH with that port forwarded and run
msgvault --local add-plaud <identifier> on the daemon host. Credentials are
stored in tokens/plaud_<identifier>.json and bound to the exact MCP endpoint.
The live account email must equal configured account_email before source
registration. A source's confirmed owner cannot change under the same
identifier. Use a new identifier for another account. See
Plaud configuration.
Archive Plaud cloud recordings, complete transcripts, and every note tab. Cloud Sync and upstream transcription must already be enabled. Audio is not downloaded; no changes propagate to Plaud.
msgvault sync-plaud [identifier]
msgvault sync-plaud work --limit 20
msgvault sync-plaud work --full --after 2025-01-01
msgvault sync-plaud work --probe| Flag | Description |
|---|---|
--limit n |
Hydrate at most n recordings; newest first, then rotate through least recently attempted. Failed recordings retry on their next turn. 0 is unlimited; negatives fail |
--full |
Force archive repair, preserving stable source and file IDs |
--after YYYY-MM-DD |
Filter recording dates locally; implies --full and retains rotation progress |
--probe |
Print tool names, input schemas, and first-page counts without personal content or archive writes; requires an identifier when multiple accounts are configured |
--build-cache |
Refresh analytics cache after sync |
--no-build-cache |
Skip analytics cache refresh; mutually exclusive with --build-cache |
Without an identifier, sync every configured Plaud entry. Every run validates
the live account owner and checks complete content for edits. Sync refuses an
unregistered source before authentication; run add-plaud first.
Missing pending transcripts and notes preserve previous evidence. Deleted recordings remain archived. Failed and canceled runs save rotation progress and refresh committed changes before returning the error. See the meeting guide for pagination and consistency limits.
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.
Check a configured local Muesli database and register it as a meeting source.
msgvault add-muesli [identifier]The matching [[muesli]] entry requires account_email; db_path defaults
to ~/Library/Application Support/Muesli/muesli.db. With one entry, the
identifier may be omitted. The command fails unless the file opens read-only
as a Muesli database on the daemon's host.
Archive completed meetings from a local Muesli database.
msgvault sync-muesli [identifier]
msgvault sync-muesli mac --limit 5
msgvault sync-muesli --after 2026-01-01
msgvault sync-muesli --fullEvery run reads the whole database read-only and updates changed meetings in
place; unchanged meetings are skipped. Attendees are resolved through Apple
Contacts unless contacts = false; the summary reports whether Contacts was
complete, partial, unavailable, or off. Meetings still recording or processing
wait for a later run, and meetings deleted in Muesli stay archived. With no
identifier, every configured [[muesli]] source is synced.
| Flag | Default | Description |
|---|---|---|
--limit |
0 |
Maximum meetings processed per run (0 = unlimited) |
--after |
— | Only meetings that start on or after this UTC date (YYYY-MM-DD) |
--full |
false |
Rewrite every archived meeting, even unchanged ones, to refresh attribution |
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 setup and scope choices). The token is validated with auth.test
and stored at tokens/slack_<team-id>_<user-id>.json. Public-channel-only
tokens need no search, file, or reaction permissions; sync revisits history
for thread replies when search:read is absent.
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 for registered workspaces. Public-channel-only tokens
can archive all public channels, including unjoined ones. Broader tokens archive
your channel memberships, group DMs, and 1:1 DMs. 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. The
private_channels, dms, and group_dms settings independently select whether
private channels, DMs, and group DMs sync. See
Slack.
msgvault sync-slack
msgvault sync-slack T0123456789
msgvault sync-slack --full
msgvault sync-slack --private-channels=false --dms=false --group-dms=false| 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) |
--dms |
configured | Include one-to-one DMs for this run (true or false) |
--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 |
--group-dms |
configured | Include group DMs for this run (true or false) |
--private-channels |
configured | Include private channels for this run (true or false) |
--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 |
|---|---|
--write |
Also request calendar.events, preserving existing Google scopes; source write permissions remain required |
--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 |
Control live Google Calendar events through the daemon. This is unreleased functionality. Follow Calendar event setup for consent and source permissions.
msgvault calendar create <calendar-id> --account <name|email> --summary <title> --from <start> --to <end>
msgvault calendar update <calendar-id> <event-id> --account <name|email> [field flags]
msgvault calendar delete <calendar-id> <event-id> --account <name|email>
msgvault calendar move <calendar-id> <event-id> <destination-calendar-id> --account <name|email>
msgvault calendar respond <calendar-id> <event-id> --account <name|email> --status accepted
msgvault calendar freebusy <calendar-id> --account <name|email> --from <start> --to <end>
msgvault calendar conflicts <calendar-id> --account <name|email> --from <start> --to <end> --calendars <ids>| Flag | Applies to | Contract |
|---|---|---|
--account |
All | Required OAuth account or configured source name, separate from the target calendar |
--dry-run |
All | Verify live access and return the proposed writes without applying them; availability still reads Google |
--read-only |
All | Reject all mutations |
--json |
All | Print complete results and archive receipts; dry runs and availability always print JSON |
--send-updates |
Writes | none (default), all, or externalOnly |
--summary, --description, --location |
Create/update | Omitted update fields are preserved; explicit empty clears |
--from, --to |
Create/update/availability | RFC3339 with offset, or local YYYY-MM-DDTHH:MM with --tz |
--tz |
Create/update/availability | IANA time zone; event edits require a time bound |
--all-day |
Create/update | Date-only YYYY-MM-DD; end is exclusive |
--attendees |
Create/update | Replace comma-separated guest list; explicit empty clears |
--add-attendee |
Update | Add guest emails while preserving existing guests and RSVP state; cannot combine with --attendees |
--rrule |
Create/update | Repeatable RRULE; explicit empty clears recurrence |
--reminder |
Create/update | Repeatable popup:minutes or email:minutes (0–40320, at most five); default or none cannot combine with overrides |
--scope |
Update/delete/respond | single (default), all, or future; future supports update/delete only |
--original-start |
Update/delete/respond | Original occurrence start, RFC3339 or all-day date; required for single on a series ID and future edits |
--destination |
Move | Required calendar ID or configured alias; standalone events only |
--status |
Respond | Required self RSVP: accepted, declined, or tentative |
--calendars |
Availability | Selected IDs or aliases; default is the positional calendar; at most 50 |
Commands return completed Google writes with archive message IDs. A partial remote failure or an archive failure exits with an error after printing the receipts. Reconcile the reported event; do not repeat a completed mutation.
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. Apple Mail keeps the
attachments of these messages outside the MIME payload, in a sibling
Attachments/N/ directory. The importer restores cached attachments directly
inside the message's outer multipart, within the message size limit.
Attachments in nested parts, such as inside some forwarded messages, are not
restored. An attachment without a cached file stays absent; unreadable files
or directories produce a warning. Re-importing a partial message adds newly
cached attachments to the existing message without creating another copy.
When the archived message already holds every cached attachment, the rerun
skips it instead of rewriting it. If both N.emlx and
N.partial.emlx exist, the complete N.emlx copy wins. The command summary
reports the number of partial files read and how many attachments the run
added to the archive.
| 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 iMessage and SMS history from an iMazing Messages CSV export. Pass the
export root containing csv/ and optional attachments/, or pass its csv/
directory directly.
msgvault import-imazing-csv ~/Downloads/messages-export \
--me +14155550100 --timezone America/Los_Angeles| Flag | Default | Description |
|---|---|---|
--me |
(required) | Your phone number or email address |
--timezone |
local IANA zone (required on Windows) | Timezone used for dates that do not include an offset |
--contacts |
— | vCard file used to fill empty participant names |
The importer accepts comma, tab, and semicolon CSV files with named iMazing headers. It is deterministic across reruns. Available referenced files are stored up to 100 MiB each. Larger files are recorded as skipped; missing files and ambiguous filenames remain visible as missing attachment occurrences. These outcomes do not fail the import or prevent contact enrichment, and a later rerun can fill missing files. Reply links are added only when the exported reply text identifies exactly one earlier message.
CSV exports do not share stable message IDs with chat.db. Importing the same
history through both commands can create cross-source duplicates.
See Text Messages for the full format and rerun behavior.
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 commands sent to the daemon. Scheduled syncs never pack
inline: when one writes new loose blobs, the daemon's attachment-pack job
(every 6 hours) packs them, and the daily attachment-maintenance job packs
and repacks. Each automatic pack pass has a 256 MiB soft raw-byte budget and
finishes its current blob before stopping for that byte limit. Scheduled packing
and daily attachment maintenance also stop after one minute, which lets queued
scheduled work run. Each scheduled pack pass verifies at most 128
existing packed blobs or 32 MiB of their raw content, allowing one oversized
blob. Its durable hash cursor resumes verification after a restart, while new
loose blobs can be packed during each pass. Manual unpacking reads the full
catalog. Cancellation preserves committed packs. A pass with committed
progress resumes behind waiting work; runtime expiry without a checkpoint
records an error and waits for the next trigger. The first pack tick after a
restart also checks for blobs left by the previous daemon.
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.
Search tables show the subject, or the message snippet when the subject is blank.
Redirected or piped output keeps the full sender and subject/snippet on one line,
with terminal controls removed and whitespace collapsed. Terminal tables fit the
available width by shortening sender and subject text, including Unicode characters
and emoji. Fixed IDs, dates, sizes and explanation scores stay complete; very narrow
terminals may wrap. An unknown message size appears as -.
With --json, each result also includes web_url when the selected daemon can
provide a browser link for that message.
The daemon checks full-text index completeness in the background. The CLI
warns that results may be incomplete when the daemon finds an index gap or
is rebuilding the index. The warning also applies while a rebuild waits for
other daemon work to finish. A completeness check alone prints no warning.
API clients can still observe index_state="checking" while that check runs.
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.
The analytics cache exports invalid UTF-8 List-IDs as NULL so mailing-list
filters cannot match unrelated messages through replacement characters.
Use --apply to recover these values when the stored raw MIME has an intact
List-Id header. This cannot recover a header that is itself damaged.
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.
In v0.21.0, build failures include available local causes alongside
the document hash and reason code. documents status lists up to 20 current
failed documents, including failed replacements in an active rebuild. JSON
output adds status.failures and status.failures_exhausted. Each failure has
canonical_blob_hash, reason_code, detail, and state; detail is a bounded
single line and may be empty for historical failures. Use documents retry and
then build again to obtain fresh diagnostics for an older failure.
To include inline documents, set
[attachments.documents.scope].include_inline = true and record exact consent
again. The default is false. See the
scope configuration for the contract.
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 bounded interval from one Discord guild using the older
msgvault-discord-export/1 JSON envelope.
msgvault export-discord <guild-id-or-name> \
--start 2026-01-01T00:00:00Z \
--end 2026-02-01T00:00:00Z| Flag | Description |
|---|---|
--start <RFC3339> |
Inclusive lower bound (required) |
--end <RFC3339> |
Exclusive upper bound (required) |
--format json |
Output format; json is the only supported value |
This compatibility command reads only the archive and does not contact
Discord. New integrations should use export-messages,
which emits the provider-neutral msgvault-message-export/1 JSONL schema. See
the Discord export guide.
Export a message as a .eml file. Accepts an internal message ID or a provider
message ID. A numeric reference selects a live internal ID first, then falls
back to a provider ID if that internal ID does not exist. This also applies
with --thread.
msgvault export-eml <id> [flags]| Flag | Description |
|---|---|
-o, --output <path> |
Output file (default: <source_message_id>.eml, use - for stdout). With --thread, the output directory (default: current directory) |
--thread |
Write visible archived messages in the conversation that have stored MIME as <n>-<source_message_id>.eml, numbered oldest first. Reports skipped messages and when the account last synced. Needs a daemon with API schema 2.33.0 or newer |
--account <email> |
With --thread, the account that holds a provider message ID found in more than one account |
Thread export captures its message list once. Messages added afterward belong to the next export. It reports and skips messages that disappear or lose their stored MIME before they are read; other read or write errors stop the command. Hidden duplicate copies are excluded. The last-sync time describes account freshness, not conversation completeness.
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.
Create a new SQLite archive containing the requested number of most recent
messages and the records they reference. The destination receives its own
msgvault.db and can be opened as a separate msgvault home.
msgvault create-subset --output ./subset-vault --rows 1000
MSGVAULT_HOME=./subset-vault msgvault tui| Flag | Description |
|---|---|
-o, --output <directory> |
Destination directory (required) |
--rows <count> |
Number of most recent messages to copy; must be positive (required) |
--include-identity |
Copy complete identity clusters for included participants |
--include-attributes |
Copy current and historical person and organization attribute values |
--include-profiles |
Copy profiles, profile history and media, relationships, employment history, and referenced organizations |
--include-vcard-resources |
Copy complete native vCards and retired UID aliases; requires --include-profiles |
The command is SQLite-only. The optional identity, attribute, profile, and vCard flags can copy personal records that have no message in the subset; read the command's warning before sharing its output.
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.
Review archive-derived identity suggestions with a token from the current review snapshot:
| Command | Purpose |
|---|---|
identity matches list [--state candidate] [--limit 100] [--offset 0] [--json] |
List candidate, accepted, rejected, conflict, or all match records |
identity matches show <id> [--json] |
Inspect evidence, blockers, and the current review token |
| `identity matches accept --review-token [--notes-file | --notes-stdin] [--json]` |
| `identity matches reject --review-token [--notes-file | --notes-stdin] [--json]` |
Always inspect the match before deciding. Evidence or endpoint changes make a review token stale; fetch the match again and decide with its new token. A successful acceptance records the decision and applies the link. Review notes are private data. See people and profiles for how to review candidates safely.
Run a bounded scoring batch to create identity review suggestions and journal
the results. Scoring never accepts matches. Configure the disabled-by-default
people.identity_scoring settings,
inspect the provider disclosure and data fields, then consent to its fingerprint:
msgvault person scoring status
msgvault person scoring consent <disclosure-fingerprint>
msgvault person scoring run --limit 20
msgvault person scoring history --limit 20| Command | Purpose |
|---|---|
person scoring status [--json] |
Show readiness, credential availability, consent, blockers, and the provider disclosure |
person scoring consent <disclosure-fingerprint> [--json] |
Consent to the exact current disclosure |
person scoring revoke <disclosure-fingerprint> [--json] |
Withdraw that disclosure's consent without changing configuration |
person scoring run [--limit <n>] [--json] |
Score and journal one batch; local blockers prevent provider requests for those pairs |
person scoring history [--candidate-id <id>] [--limit <n>] [--before-id <id>] [--json] |
Read redacted judgments, optionally for one candidate or below a history cursor |
run --limit defaults to the configured batch size and cannot exceed it.
History defaults to 100 rows and accepts limits from 1 through 100. Candidate
and cursor IDs must be nonnegative; zero means no filter. Use the returned
next_before_id with --before-id for older history.
Each run result includes its candidate's review token, proposed action, and blockers. If a batch stops early, completed results remain in the response and history, and the command reports the batch error. Inspect the candidate's current evidence before accepting a proposal. See the people guide for the consent and provider boundaries.
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. Profile removal uses the running daemon to revoke consent and clear checks for both the saved and running policies. If the daemon is incompatible, stop it before removing a profile. With no daemon, removal updates the local archive directly.
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 CardDAV accounts, choose their 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> [--connection <name>] [--schedule <cron>] [--disabled] |
Discover and save an account; password is prompted or read from piped stdin |
add-carddav --google <email> [--connection <name>] [--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 [--connection <name>] [--full] |
Synchronize all enabled connections, or the selected connection; --full reconciles complete books |
carddav connections |
List connection names, enablement, runtime availability and orphaned accounts |
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 [--connection <name>] |
List discovered books, owning connection names and 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 |
Unqualified add-carddav saves default. Explicit sync selects one saved
connection, including a disabled connection for manual repair. Unqualified sync
with no enabled connection fails as unavailable. Aggregate sync prints counts
and safe failure codes per connection, and exits nonzero for partial or failed
outcomes. Book IDs, publication commands and conflict commands remain global;
network operations use the owning connection's credentials.
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 authenticated Gmail profile or 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 seeds a new profile with the first nonblank observed name in its
linked cluster, ordered by participant ID. It leaves the name empty when no
member has an observed name. Repeating promotion preserves the saved name,
including an edited or cleared value. 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.
Read and organize a person's live Kata tasks. Configure
[integrations.kata] on the daemon first.
Tasks belong to one person and stay in the configured Kata project.
msgvault person agenda list <person-id> [--json]
msgvault person agenda create <person-id> --title "Discuss the proposal" [flags]
msgvault person agenda link <person-id> <ref> [--list agenda] [--json]
msgvault person agenda edit <person-id> <ref> --list follow-up [--json]
msgvault person agenda unlink <person-id> <ref> [--json]<ref> is a task reference returned by Kata or an agenda command. create
creates a task and links it to the person; link attaches an existing task.
edit changes only its list. unlink removes the person link without deleting
the task. Complete or reopen tasks, or edit their title, body, priority, and
labels, in Kata.
| Flag | Commands | Default | Description |
|---|---|---|---|
--title |
create |
— | Required task title |
--body |
create |
— | Task body |
--priority |
create |
— | Kata priority from 0 through 4 |
--label |
create |
— | Kata label; repeat for multiple labels |
--list |
create, link, edit |
agenda for create/link |
List name; required for edit |
--idempotency-key |
create |
Generated | Stable key to reuse when retrying the same creation |
--json |
All agenda commands | false |
Print the response as JSON |
create prints its generated retry key to stderr before sending the request.
To retry, pass that key with --idempotency-key and keep the task details the
same. You can also choose the key in advance:
msgvault person agenda create 42 --title "Discuss the proposal" --idempotency-key proposal-discussion-1list returns up to 100 open tasks and recognizes the person's current vCard
UID and aliases. JSON includes truncated when more items remain; use Kata to
view the rest. Oversized Kata responses produce an explicit error. See the
Kata configuration for metadata and
response limits.
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.
Create and synthetically check a named people inference profile. Select a built-in preset to bind its protocol, endpoint, and authentication scheme:
msgvault person provider add primary --provider openrouter --model <model> \
--credential-env OPENROUTER_API_KEY --retention-posture <assertion> \
--training-posture <assertion> --source conversation_text \
--source-since 2026-01-01 --allow-sensitive=true --yes
msgvault person provider consent primary --yes
msgvault person provider use primary--provider accepts openai, openrouter, or venice and requires an explicit
model and privacy policy. It cannot be combined with --custom or
--accept-catalog-prices; conflicting protocol, endpoint, or auth overrides
are rejected. --credential-env reads only the named host variable. Alternatively,
--api-key-stdin reads a key from standard input, or an interactive terminal
prompts for it. A successful synthetic check does not grant consent or select
the profile. See profile automation for custom
protocol profiles and policy fields.
Create, check, consent to, and select a new Codex profile through the daemon. Codex enrollment is unavailable in this release: no Codex build is approved. The daemon returns HTTP 503 before changing credentials or consent. This command requires a terminal and never starts a noninteractive device login.
msgvault person provider enroll-codex <new-name> \
--retention-posture <assertion> --training-posture <assertion> \
--source conversation_text --source-since 2026-01-01 --allow-sensitive=trueThe gated flow prints a verification URL, user code, and local deadline, waits for sign-in, then prompts for an available model and reasoning effort. Existing profile names are rejected. After saving, a synthetic check and separate consent are required before selection. Declining consent leaves the saved profile unselected. Restart the daemon when the reported running profile differs.
| Flag | Default | Description |
|---|---|---|
--model |
prompt | Codex model ID available to the signed-in account |
--reasoning-effort |
prompt | Supported effort for the chosen model |
--retention-posture, --training-posture |
required | Explicit operator assertions |
--source |
required | Repeatable: conversation_text, meeting_text, or document_text |
--source-since |
required | Earliest disclosed date, YYYY-MM-DD |
--source-until |
absent | Latest disclosed date, on or after --source-since |
--allow-sensitive |
explicit decision required | true permits archive evidence; false permits only synthetic checks |
--yes |
false |
Confirm the displayed check disclosure and select the profile without a consent prompt |
Host-side person provider login reauthenticates the selected existing Codex
profile; person provider models lists its models and reasoning efforts. Both
accept --json and remain unavailable behind the same release gate. See
Codex configuration.
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 |
Rebuild all cache files from scratch |
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. See
analytics settings for snapshot freshness,
builder memory, threads, and temporary disk limits.
Build warnings count exported text values repaired with U+FFFD and identity values exported as unknown. Invalid source message IDs, source conversation IDs, and source identifiers export as empty strings; valid keys stay unchanged. Invalid List-IDs export as NULL; see List-Id recovery. The counts cover only datasets written by that build or refresh, on every platform. A source value written to two output columns counts twice; repeated identity comparisons do not add to the count. See encoding repair limits for archive recovery.
Refresh contact activity and last-contact dates from archived messages.
msgvault activity build
msgvault activity build --backstopThe normal build resumes from its watermark. --backstop rescans the complete
archive. msgvault serve also runs this projection on the schedule configured
under [activity].
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. |
optimize [generation-id] |
Build or resume the local SQLite search accelerator, or remove it with --drop. |
prune |
Remove embeddings for hard-deleted messages. |
activate <generation-id> |
Activate a completed building generation, retiring the current active one. |
retire <generation-id> |
Retire a generation. |
prune |
Remove embeddings whose messages were hard-deleted. |
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, generation state, model, dimension, coverage, accelerator state and row count, accelerator timestamps and last error, fingerprint, and generation timestamps.
msgvault embeddings optimize [generation-id] [--drop]Build or resume the SQLite approximate-search accelerator from vectors already stored for a generation. The active generation is used when the ID is omitted. The command never calls the embedding provider. It is safe to interrupt and rerun; the accelerator is not used for search until verification and atomic publication complete. The daemon's operation gate pauses scheduled embedding and other gated writes until the command finishes. Searches remain available. PostgreSQL does not need this command.
Use --drop to remove the accelerator and its build state, including for a
retired generation. Exact vectors remain intact. Freed database pages become
reusable; the database file does not shrink. Re-run optimization after substantial
archive growth to retrain its fixed bucket count.
msgvault embeddings pruneRemove stored message embeddings whose source messages were hard-deleted.
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 embeddings pruneRemove vector rows whose source messages no longer exist. The configured vector backend must support orphan pruning.
msgvault build-embeddings remains as a deprecated alias for msgvault embeddings build (same --full-rebuild and --yes flags).
Build, inspect, and search the optional visual attachment index. The workflow
requires [vector.multimodal] configuration, a probed capability manifest,
and explicit consent for hosted processing. See Visual attachment
search.
| Subcommand | Purpose |
|---|---|
probe --seeds <dir> --out <file> --yes |
Send synthetic fixtures to the configured provider and write a capability manifest. --fixtures keeps the generated fixtures instead of using a temporary directory. |
build --yes |
Record consent for the configured capability profile and build the visual index. |
resume |
Continue a consented build. |
status [--json] |
Report generation state and coverage. |
retry --message <id> --hash <sha256> |
Retry one attachment occurrence. |
retire <generation-id> --yes |
Retire one generation and delete its vectors. Original attachments remain archived. |
Search by text or by one local JPEG, PNG, or WebP image:
msgvault multimodal search "a whiteboard timeline"
msgvault multimodal search --image ./reference.png| Search flag | Default | Description |
|---|---|---|
--image <path> |
— | Use a local query image of at most 20 MiB instead of text |
--limit <count> |
20 |
Results to return; must be 1–100 |
--cursor <value> |
— | Continue from an opaque result cursor |
--sender-person <id> |
— | Attachments sent by one durable person |
--person <id> |
— | Attachments related to one durable person |
--participant <id> |
— | Attachments related to one observed participant |
--direction <value> |
— | from_person, to_person, or group; requires --person or --participant |
--source <id> |
— | Restrict to one source |
--message <id> |
— | Restrict to one owning message |
--filename <text> |
— | Case-insensitive filename substring |
--mime-prefix <text> |
— | Case-insensitive MIME prefix |
--after, --before |
— | YYYY-MM-DD sent-date bounds |
--json |
false |
Emit JSON |
Supply exactly one text query or --image. --person and --participant are
mutually exclusive. --sender-person cannot be combined with either of those
or with --direction.
Compare how well search modes find messages you have rated. The command runs the same full-text, vector, and hybrid retrieval paths used by production search. It reports ranking quality, recall, latency, configuration, and input diagnostics.
# topics.tsv: qid<TAB>query<TAB>optional-category
q1 quarterly planning pointed
# qrels.txt: qid iteration document-id relevance (1 or greater means relevant)
# Replace example-message-001 with a real source_message_id, not a local numeric ID.
q1 0 example-message-001 2
msgvault eval \
--topics topics.tsv \
--qrels qrels.txt \
--modes fts,vector,hybrid \
--limit 100| Flag | Default | Description |
|---|---|---|
--topics <path> |
required | Tab-separated topics: qid, query, and optional category |
--qrels <path> |
required | Whitespace-separated judgments: qid iteration docid relevance; relevance of 1 or greater means relevant |
--modes <list> |
fts,vector,hybrid |
Comma-separated modes to evaluate |
--doc-key <kind> |
message |
Match judgments to message source IDs or conversation source IDs |
-n, --limit <count> |
100 |
Distinct documents retrieved per query |
--json |
false |
Emit the report as JSON |
--rerank-jev <shapes> |
disabled | Opt in to per-candidate, batched, or both Jev shapes |
--rerank-top <count> |
30 |
Between 2 and 30 candidates; cannot exceed --limit |
--rerank-max-requests <count> |
1000 |
Maximum provider requests for the whole invocation |
--rerank-input-usd-per-million <amount> |
required when enabled | Input price supplied for this run |
--rerank-output-usd-per-million <amount> |
required when enabled | Output price supplied for this run |
eval opens the archive selected by local configuration directly; it does not
use [remote]. Vector and hybrid evaluation currently require a SQLite archive,
an sqlite_vec build, enabled vector configuration, and a compatible active
generation. On PostgreSQL, run --modes fts.
--rerank-jev adds an evaluation arm after retrieval. It sends the topic query
and up to 30 message candidates to TypeSafe's fixed Jev endpoint. The command
uses TYPESAFE_API_KEY from the process environment. Selecting a shape is the
per-invocation consent to send that archived text. The reranked arms accept
--doc-key=message because TREC judgments identify messages. Conversation
judgments remain available for the baseline run.
The command prepares candidate text with the same body selection and cleanup
used for embeddings. It sends only the query and the cleaned subject/body.
Candidate text is capped at 2048 UTF-8 bytes and query text at 4096 bytes.
Msgvault calls TypeSafe through Docbank's document/typesafe client, which
sets the request and response size limits and runs at most eight calls at
once. Msgvault gives each ranking 40 seconds for all of its calls, enough for
four waves of 10-second calls at 30 candidates. A ranking's full elapsed time
contributes to the reported latency. The per-candidate and batched shapes
use the same retrieved messages in one invocation.
Cost is the returned input and output tokens times the prices supplied on the
command line. Before opening the archive, the command refuses a run whose
worst-case request count, judged topics times modes times requests per
ranking, exceeds --rerank-max-requests. Msgvault has no local spend limit,
so before a live study, verify an account or order limit that TypeSafe
enforces by rejecting charges above the cap. A displayed balance or alert does
not establish that behavior. A response without token usage fails that arm.
The first provider failure ends reranking for the run. The report keeps the
baseline and the token subtotals observed before the failure,
usage_complete=false marks them as partial, and unknown cost prints unknown
in the table and null in JSON. Requests and tokens count completed rankings
only. The command returns a nonzero result.
Example with placeholder prices:
TYPESAFE_API_KEY=replace-me msgvault eval \
--topics topics.tsv --qrels qrels.txt --modes fts,vector,hybrid \
--limit 100 --rerank-jev per-candidate,batched --rerank-top 30 \
--rerank-input-usd-per-million 1 \
--rerank-output-usd-per-million 2Reranked p95 includes retrieval, shared candidate preparation, and that shape's provider calls. A live TREC Legal 2010 study also needs a permitted matching message collection, topics, qrels, source-id mapping, and the enforced account cap. Consider a search integration only when a complete hybrid shape improves Hit@10 by 0.05 absolute and has end-to-end p95 at or below 2000 ms under the same corpus, topics, qrels, retrieval settings, and top N of 30. Report the paired topic denominator and changes for both shapes. The local threaded fixture checks wiring only.
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]A usable stale cache remains queryable while refresh work runs. The JSON result
includes cache.published_at, and includes stale_reason and
pending_additions when known. If a new publication is required before rows
can be returned, the command reports the build job on stderr, waits for it,
then returns query results. A failed build or interrupted wait exits with an
error. --fresh waits for a check that includes archive writes committed before
the request, rebuilding if needed, before returning rows.
| Flag | Default | Description |
|---|---|---|
--format |
json |
Output format: json, csv, or table |
--fresh |
false |
Wait for a freshness check and any required rebuild before returning rows |
See SQL Queries for available views and example queries.
On unreleased main, manage provider keys on the daemon host without the Web UI:
msgvault credentials set <id> --from-file PATH [--endpoint URL]
msgvault credentials set <id> --stdin [--endpoint URL]
msgvault credentials list [--json]
msgvault credentials import-envSupply exactly one input source. Supported IDs are vector.embeddings,
vector.multimodal, people.enrichment/<name>, and
people.enrichment/suppression. The configured provider supplies the default
endpoint. Suppression keys have no endpoint. Listing prints IDs and bound
origins, never key values. Import copies present configured environment keys
and preserves stored keys. See stored credentials
for input security, runtime precedence, and sweep-provider commands.
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 an effective inbound key or --http-allow-insecure. |
--http-token-file |
— | On unreleased main, read an independent inbound bearer key from an owner-only file; takes priority over --http-token-env. Requires --http. |
--http-token-env |
— | On unreleased main, name the environment variable holding an independent inbound bearer key. Requires --http. |
--http-allow-insecure |
false |
Allow non-loopback HTTP binding without an effective inbound 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. Calendar event mutations also require --allow-calendar-writes. Enable only for trusted, authenticated clients. |
--allow-calendar-writes |
false |
Expose calendar event mutation tools. HTTP also requires --http-allow-writes; only enable for sessions where the user explicitly authorizes calendar writes. |
See MCP Server for configuration and tool reference.
For Kata person agendas, get_person_agenda reads the live open tasks and
returns the person's canonical vCard UID and aliases. Use Kata's MCP tools to
create or change tasks; msgvault's agenda tool is read-only. Use the returned
canonical UID as the scalar msgvault.person metadata value and
msgvault.list for the list name (agenda by default). See
Kata configuration for setup and limits.
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. Configuration comes from config.toml with the runtime environment overrides. 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.
On unreleased main, --bind ADDRESS and --port PORT override environment,
TOML, and defaults. A port of 0 selects an open port. --bind iface:NAME
resolves a named network interface at startup and fails before binding if it
cannot find a usable address. See runtime configuration
for environment-only deployment and persisted API keys.
msgvault serve --bind 0.0.0.0 --port 8080Run the first-run setup wizard. It writes config.toml, optionally stores a Google OAuth credential (needed only for Gmail and Google Calendar; press Enter to skip), and optionally configures a remote deployment.
msgvault setupIf configured for a remote server, this command generates <MSGVAULT_HOME>/nas-bundle with:
config.tomlready for container deploymentclient_secret.json, if a Google OAuth credential is configureddocker-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. Hosted people inference
requires an explicit --provider and --model; an OpenAI key alone does not
select it. Without an OpenAI key or explicit provider choice, setup can offer
the configured local Ollama chat model. 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.
Local people-sweep setup stays pending unless --allow-sensitive is supplied.
An explicit --provider requires --allow-sensitive=true|false and explicit
--retention-posture and --training-posture assertions. Only true permits
sending sensitive archive excerpts and inferring sensitive personal attributes;
false permits the synthetic check but real sweeps cannot process evidence.
--yes alone does not grant this permission. If a sweep is already enabled,
--provider is rejected; use person provider add and person provider use
to choose another profile. 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 |
|---|---|---|
--provider |
absent | Explicit people inference preset: openai, openrouter, or venice; requires model and privacy decisions |
--model |
absent | Required model ID for the selected preset |
--credential-env |
absent | Host environment-variable name for the preset key; mutually exclusive with --api-key-stdin |
--api-key-stdin |
false |
Read the preset API key from standard input; otherwise a terminal can prompt for it |
--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 |
JSON output includes web_url when the selected daemon can provide a browser
link for the message.
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 email from Gmail and Microsoft Graph mail (msmail)
sources only. A search that also matches chats, meetings, calendar entries, or
mail from other sources such as IMAP or Apple Mail imports stages the
deletable 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 targets and source boundaries for the requested IDs; IDs that do not resolve to live deletable 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, and Microsoft Graph mail moves them to Deleted Items. --permanent
uses Gmail batch deletion, IMAP UID EXPUNGE, or Graph permanentDelete; the
IMAP permanent path requires UIDPLUS. The first deletion for a Graph mail
account asks to upgrade its token to Mail.ReadWrite. 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, IMAP UID EXPUNGE, or Graph permanentDelete instead of moving to Trash |
--headless |
Use device-code sign-in for Microsoft Graph permission upgrades; open the printed URL on another device |
--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. For SQLite archives, it also rebuilds the analytics cache. It reports invalid RFC 822 Message-ID values and leaves their original bytes unchanged to avoid making distinct identifiers collide. The analytics cache exports these IDs as NULL. Recover the original values separately from a verified source before rebuilding the cache.
It does not repair source message IDs, source identifiers, recorded sender/recipient envelope addresses, account identity addresses, or participant identifiers. Those fields need separate recovery from verified original values; rebuilding the analytics cache alone cannot recover them.
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>...] \
--sender <source-id>=<address>| Flag | Description |
|---|---|
--label <name> |
(required) Human-readable name for the grant |
--permissions <perms> |
Comma-separated permissions: calendar.read for availability, calendar.event.read for provider-derived event details in delegated plans and write receipts, calendar.write for calendar mutations, and additional calendar.invite for guest changes; draft.create for draft-reply, draft-compose, and draft-get; draft.edit for draft-get, draft-edit, and draft-recover; draft.delete for draft-get, draft-delete, and draft-recover (see managed drafts) |
--source-ids <ids> |
Comma-separated source IDs that the permissions apply to |
--sender <source-id>=<address> |
Restrict a source to one confirmed sender identity; repeat for multiple choices |
The grant is valid until revoked or until the daemon restarts.
When --sender is omitted for a selected source, issuance snapshots every
currently confirmed valid mailbox identity. Sender selections are stored as
canonical mailbox keys and remain fixed until the token is revoked. Adding an
alias later does not expand an existing grant. A source with no selected sender
has no delegated draft sender authority. Calendar grants use exact calendar source
identities and do not require a draft sender selection. Delegated callers can run
calendar commands and mcp over stdio; the delegated MCP bridge exposes only
calendar tools.
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), frozen sender keys, 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.