Skip to main content

A modern CLI for Mistral OCR and Voxtral audio transcription (independent project, not affiliated with Mistral AI)

Project description

moxtral

CI Release PyPI Python versions License: MIT

A modern command-line interface for Mistral OCR and Voxtral audio transcription.

Powered by Mistral AI — moxtral is an independent, open-source CLI built on the official Mistral AI platform APIs (Mistral OCR for documents, Voxtral for audio). It is not affiliated with, endorsed by, or sponsored by Mistral AI. See About the name & trademarks.

Turn PDFs and images into structured Markdown, and turn audio into text — from your terminal, in one command. Point it at a local file or a public URL; it saves durable Markdown and JSON, processes batches, and keeps progress output cleanly separated from pipe-friendly results.

moxtral ocr report.pdf
moxtral transcribe interview.mp3

Highlights

  • Two capabilities, one tool — document OCR and speech-to-text behind a single moxtral command.
  • Local files or public URLs — local files are read and sent as base64 data URLs; public HTTP(S) URLs are passed through.
  • Durable results — every run saves timestamped Markdown and a stable JSON envelope you can diff, re-render, or feed to other tools.
  • Batch-friendly — pass many sources at once; one failure never discards the successful results.
  • Skips duplicate work — rerunning the same source with the same options within a recency window costs nothing: the CLI detects it and skips the API call; --force overrides.
  • Pipe-clean output — Markdown goes to stdout on request; progress, paths, and errors stay on stderr.
  • Agent-ready--json streams stable NDJSON records for machine consumption, exit codes are documented and stable, and moxtral agent prints a compact usage guide and output schema for LLM agents.
  • Secure by default — API keys are never passed as CLI arguments, are redacted from all output (including tracebacks), and are stored with restrictive file permissions.

Requirements


Quickstart

1. Install from PyPI as an isolated command with uv (or pipx):

uv tool install moxtral          # or: pipx install moxtral

Update later with uv tool upgrade moxtral (or pipx upgrade moxtral).

To install from source instead (e.g. to try unreleased changes):

git clone https://github.com/maxboettinger/moxtral.git
uv tool install --reinstall --force ./moxtral

Then verify the installation:

uv tool list          # the `moxtral` executable must be listed under `moxtral`
moxtral --help        # must show: agent, config, ocr, transcribe

Note: don't confuse this tool with the PyPI package named mistral (OpenStack Mistral, a workflow service unrelated to Mistral AI) or with Mistral AI's own coding-agent CLI (vibe).

2. Store your API key (hidden, confirmed prompt):

moxtral config set api-key

3. Run it:

# Extract text from a PDF or image
moxtral ocr report.pdf

# Transcribe audio
moxtral transcribe interview.mp3

Results are saved to ~/.moxtral/ocr/ and ~/.moxtral/transcriptions/ as both Markdown and JSON. Add --stdout to also print the Markdown for piping:

moxtral ocr report.pdf --stdout | glow -

That's the whole loop. The sections below add detail as you need it.


Usage

OCR

Process a local PDF or image, or a publicly accessible URL:

moxtral ocr report.pdf
moxtral ocr https://example.com/report.pdf

Public URLs must be directly reachable by Mistral. Local files are read and sent as base64 data URLs. Local files are limited to 50 MB (they are sent inline as base64); larger documents should be hosted at a URL.

Common options:

moxtral ocr report.pdf \
  --pages 0,2-4 \
  --table-format markdown \
  --extract-header \
  --extract-footer \
  --include-images --image-limit 20 --image-min-size 100 \
  --include-blocks \
  --confidence word
Option Description
--pages TEXT Zero-based page numbers and ascending ranges, e.g. 0,2-4.
--table-format inline|markdown|html Table representation. Default: inline.
--extract-header / --extract-footer Return those regions separately.
--include-images Request base64 image data.
--image-limit N / --image-min-size N Bound returned images (require --include-images; nonnegative).
--include-blocks Request structured blocks and bounding boxes.
--confidence none|page|word Confidence detail level.
--model TEXT OCR model. Default: mistral-ocr-latest.
--timeout SECONDS Positive request timeout. Default: 300.
--retries N Retry attempts for rate-limited, server-error, and connection failures, with exponential backoff. Default: 3; 0 disables.

Some options depend on model capabilities — see the official OCR documentation.

Transcription

Transcribe a local audio file or a public audio URL:

moxtral transcribe interview.mp3
moxtral transcribe https://example.com/interview.mp3

Request speaker labels, contextual hints, and timestamps:

moxtral transcribe interview.mp3 \
  --temperature 0.2 \
  --diarize \
  --context-bias Mistral --context-bias Voxtral \
  --timestamps segment
Option Description
--language CODE Source language code.
--temperature FLOAT Sampling temperature; must be finite.
--diarize Request speaker identification.
--context-bias TEXT Contextual hint; repeatable, up to 100 values.
--timestamps segment|word Repeatable; duplicates ignored.
--model TEXT Transcription model. Default: voxtral-mini-latest.
--timeout SECONDS Positive request timeout. Default: 300.
--retries N Retry attempts for rate-limited, server-error, and connection failures, with exponential backoff. Default: 3; 0 disables.

--language and --timestamps cannot be combined — the Mistral API does not support that pairing.


Results & batch processing

Both commands share these output options:

Option Description
--format md|json|both Which formats to save. Default: both.
--output-dir DIRECTORY Override the default result directory.
--stdout Also write rendered Markdown to standard output.
--json Write NDJSON result records to standard output (one per source; mutually exclusive with --stdout).
--quiet Suppress progress and summary lines on stderr (errors still shown).
--no-save Skip result files entirely (requires --json or --stdout).
--dry-run Validate sources and options without calling the API — no key needed.
--force Process a source even if an identical recent result already exists.
--dedupe-window DAYS Look-back window for duplicate detection. Default: 30.

Where results go. By default, into per-command directories, named with a UTC timestamp followed by the original source filename:

~/.moxtral/ocr/20260703T121314.123456Z-report.pdf.md
~/.moxtral/ocr/20260703T121314.123456Z-report.pdf.json
~/.moxtral/transcriptions/...
  • Markdown contains provenance and readable page or transcript content.
  • JSON is a stable envelope: schema_version, created_at, source metadata, request options, the complete API response, and cli_version.

Batch processing. Pass multiple local files and URLs to process them sequentially:

moxtral ocr chapter-1.pdf chapter-2.pdf https://example.com/appendix.pdf
moxtral transcribe part-1.mp3 part-2.mp3

A failure for one source does not discard successful results or stop later sources. The final summary reports successes, failures, and skips, and any failure produces a nonzero exit status. With --stdout and multiple successes, Markdown documents are separated by a horizontal rule.

Duplicate skipping. Before calling the API, each source is checked against a local index of prior results and skipped if an identical, recent one already exists — no charge, no network call:

  • Content matches by SHA-256 of a local file's bytes (renames don't matter; different bytes never match) or by the literal value of a URL source.
  • Options must match too — the same model, language, diarization, timestamps, pages, table format, etc. (--timeout is ignored).
  • Recency: the existing result must be within --dedupe-window DAYS (default 30).
  • Coverage: the existing files must still be on disk and satisfy this run's --format/--stdout needs — a Markdown-only save doesn't dedupe a run that also needs the JSON envelope.

A skip prints Skipping duplicate: SOURCE (existing result from TIMESTAMP; use --force to reprocess). plus one Existing: PATH line per artifact on stderr, re-emits the saved Markdown under --stdout, and writes a status: "skipped" record under --json. Pass --force to reprocess regardless (this still records the new result). --dry-run runs the same check for free and reports what it would skip. Duplicate checks need no API key, so a run where every source is a duplicate exits 0 without ever resolving a key.

The index lives at ~/.moxtral/index.ndjson (created automatically, 0600, best-effort — a read/write problem falls back to processing normally with a warning) and is never written to with --no-save.


Machine-readable output & agents

With --json, each command streams one NDJSON record per source to stdout — status: "ok" records carry the full result envelope and saved paths, status: "skipped" records report a duplicate that was skipped instead of billed (with the existing saved paths and timestamp), failures appear in-band as status: "error" records with stable error codes (input_error, config_error, api_error, persistence_error, unexpected_error), and every completed run ends with a status: "summary" record. Output is ASCII-encoded, so it is immune to terminal-control injection and always parses:

moxtral ocr --json --quiet report.pdf \
  | jq -r 'select(.status=="ok") | .envelope.response.pages[].markdown'

Exit codes are part of the stable contract:

Code Meaning
0 Every source succeeded or was skipped as a duplicate (or validated, under --dry-run).
1 At least one source failed.
2 Usage error (bad flags or flag combination).
3 Setup failure (missing API key, unreadable config).

For LLM agents and other tooling, moxtral agent prints a compact usage guide and moxtral agent --schema prints the JSON Schema for the NDJSON records. Use --dry-run --json to validate a batch without an API key or network calls — it also reports sources that would be skipped as duplicates.


Configuration

API key

The key is resolved from MISTRAL_API_KEY first, then the stored config.

Store it via a hidden, confirmed prompt:

moxtral config set api-key

For automation, pass a single line over stdin (no echo, not in the arg list):

read -r -s MISTRAL_KEY
printf '%s\n' "$MISTRAL_KEY" | moxtral config set api-key --stdin
unset MISTRAL_KEY

Or override per-invocation with the environment variable:

MISTRAL_API_KEY="..." moxtral ocr report.pdf

Inspect or change the stored configuration without exposing the key:

moxtral config show
moxtral config path
moxtral config unset api-key

Config file

The default path is ~/.moxtral/config.toml. On POSIX systems the CLI creates its config directory with mode 0700 and the file with mode 0600, and writes updates atomically. The key is stored as plaintext, so protect the account and any backups that contain this file.

Select a different file with the root --config option:

moxtral --config ./private-config.toml config set api-key

Shell completion

Click provides tab completion for commands and options. Add the line for your shell to its startup file:

eval "$(_MOXTRAL_COMPLETE=zsh_source moxtral)"     # ~/.zshrc
eval "$(_MOXTRAL_COMPLETE=bash_source moxtral)"    # ~/.bashrc
_MOXTRAL_COMPLETE=fish_source moxtral | source     # ~/.config/fish/config.fish

Errors, debugging & security

Expected input, configuration, network, API, and persistence failures are reported concisely, without a traceback. Put the root --debug flag before the command to include diagnostic tracebacks:

moxtral --debug transcribe interview.mp3

Transient failures that fail fast — HTTP 429 rate limits, 5xx server errors, refused connections — are retried automatically with exponential backoff (--retries, default 3). Requests that fail slowly (e.g. timeouts) exhaust the retry budget and are not retried. A request that never succeeded is never billed.

Security properties:

  • The CLI never accepts an API key as a command-line value. Use the hidden prompt, --stdin, or MISTRAL_API_KEY.
  • Known API keys are redacted from terminal output and persisted results, including debug output.
  • Avoid embedding credentials in source URLs — URLs are recorded as result provenance.

About the name & trademarks

moxtral (Mistral OCR + Voxtral) is an independent, open-source project. It is not affiliated with, endorsed by, or sponsored by Mistral AI. "Mistral", "Mistral OCR", and "Voxtral" are trademarks of their respective owner and are used here only to identify the APIs this tool talks to.

The CLI deliberately does not claim the bare mistral command: that name belongs to other projects (the OpenStack Mistral client on PyPI installs a mistral executable) and plausibly to Mistral AI's own tooling in the future (their coding agent currently installs vibe). moxtral installs alongside all of them without conflict.

Historical note: this project was named mistral-cli (command mistral) up to version 0.3.0.


Development

Install locked runtime and development dependencies:

uv sync
uv run moxtral --help

Run the full quality gates:

uv run pytest --cov=moxtral --cov-report=term-missing
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv lock --check
uv build

Releasing

Releases are fully automated via Trusted Publishing (OIDC — no API tokens). Every push to main checks whether the pyproject.toml version already has a v<version> tag; if not, it runs the full CI matrix, publishes to PyPI, and then tags the commit and creates a GitHub release with the built artifacts. So a release is just a version bump:

uv version --bump minor          # or: patch / major — updates pyproject.toml + uv.lock
# update CHANGELOG.md, then:
git commit -am "release: v$(uv version --short)"
git push

The tag is created only after a successful PyPI upload, so a failed release leaves nothing tagged and simply retries on the next push (or via a manual re-run of the workflow).

Architecture

The package uses a src/ layout with clear boundaries between the CLI surface, domain models, use cases, and the SDK adapter:

Module Responsibility
cli/ Click commands, validation flow, and sanitized console reporting.
models.py SDK-independent request and result types.
services/ OCR and transcription use cases behind gateway protocols.
mistral_client.py Adapter for the official Mistral Python SDK.
config.py Typed TOML configuration and secure atomic writes.
sources.py Resolves local files and URLs into request inputs.
formatters.py Renders results to Markdown.
storage.py Persists collision-safe output atomically.
errors.py, console.py Translate, redact, and safely present failures.

The request flow is: CLI parses and validates options → services invoke the use case through a gateway protocol → mistral_client calls the SDK → formatters and storage render and persist results. Tests isolate the API behind fakes and cover command behavior, mapping, formatting, configuration, security boundaries, and persistence.

See CHANGELOG.md for release history.


References

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

moxtral-0.4.0.tar.gz (75.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

moxtral-0.4.0-py3-none-any.whl (47.7 kB view details)

Uploaded Python 3

File details

Details for the file moxtral-0.4.0.tar.gz.

File metadata

  • Download URL: moxtral-0.4.0.tar.gz
  • Upload date:
  • Size: 75.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for moxtral-0.4.0.tar.gz
Algorithm Hash digest
SHA256 3562767aa650747ccda92f3e2085272c5e39b29e3a8c32336e60ec9570b12931
MD5 e3990fdcc52e1eac8ef32eb7347ed868
BLAKE2b-256 41d903eaf59f9aa7dadfdf4938df0fce63643203a806a588555cacb624fc07a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for moxtral-0.4.0.tar.gz:

Publisher: release.yml on maxboettinger/moxtral

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file moxtral-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: moxtral-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 47.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for moxtral-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 011f8134c4132deef776b055f8aebfcb88253c4c3dae787cba03ba072aabaeb2
MD5 8bdea507065e47d01a31a4931901ee4c
BLAKE2b-256 bfc45a67b8ec8b417028158a3794603cf58fae39ed9638ca9d0c24fa87ea2741

See more details on using hashes here.

Provenance

The following attestation bundles were made for moxtral-0.4.0-py3-none-any.whl:

Publisher: release.yml on maxboettinger/moxtral

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page