moxtral 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moxtral-0.4.0/.gitignore +18 -0
- moxtral-0.4.0/LICENSE +21 -0
- moxtral-0.4.0/PKG-INFO +503 -0
- moxtral-0.4.0/README.md +476 -0
- moxtral-0.4.0/pyproject.toml +75 -0
- moxtral-0.4.0/src/moxtral/__init__.py +10 -0
- moxtral-0.4.0/src/moxtral/__main__.py +4 -0
- moxtral-0.4.0/src/moxtral/cli/__init__.py +1 -0
- moxtral-0.4.0/src/moxtral/cli/agent.py +33 -0
- moxtral-0.4.0/src/moxtral/cli/common.py +202 -0
- moxtral-0.4.0/src/moxtral/cli/config.py +142 -0
- moxtral-0.4.0/src/moxtral/cli/docs.md +112 -0
- moxtral-0.4.0/src/moxtral/cli/main.py +55 -0
- moxtral-0.4.0/src/moxtral/cli/ocr.py +242 -0
- moxtral-0.4.0/src/moxtral/cli/runner.py +528 -0
- moxtral-0.4.0/src/moxtral/cli/transcribe.py +210 -0
- moxtral-0.4.0/src/moxtral/config.py +182 -0
- moxtral-0.4.0/src/moxtral/console.py +123 -0
- moxtral-0.4.0/src/moxtral/data/agent_guide.md +129 -0
- moxtral-0.4.0/src/moxtral/dedupe.py +253 -0
- moxtral-0.4.0/src/moxtral/docs.md +143 -0
- moxtral-0.4.0/src/moxtral/errors.py +181 -0
- moxtral-0.4.0/src/moxtral/formatters.py +299 -0
- moxtral-0.4.0/src/moxtral/mistral_client.py +232 -0
- moxtral-0.4.0/src/moxtral/models.py +285 -0
- moxtral-0.4.0/src/moxtral/schema.py +137 -0
- moxtral-0.4.0/src/moxtral/services/__init__.py +14 -0
- moxtral-0.4.0/src/moxtral/services/docs.md +44 -0
- moxtral-0.4.0/src/moxtral/services/ocr.py +41 -0
- moxtral-0.4.0/src/moxtral/services/transcription.py +41 -0
- moxtral-0.4.0/src/moxtral/sources.py +159 -0
- moxtral-0.4.0/src/moxtral/storage.py +256 -0
- moxtral-0.4.0/uv.lock +644 -0
moxtral-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
.coverage
|
|
9
|
+
htmlcov/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.pyright/
|
|
13
|
+
|
|
14
|
+
# Virtual environments
|
|
15
|
+
.venv
|
|
16
|
+
|
|
17
|
+
# Local session logging (Recall plugin)
|
|
18
|
+
.recall/
|
moxtral-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Max Boettinger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
moxtral-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moxtral
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A modern CLI for Mistral OCR and Voxtral audio transcription (independent project, not affiliated with Mistral AI)
|
|
5
|
+
Project-URL: Repository, https://github.com/maxboettinger/moxtral
|
|
6
|
+
Project-URL: Issues, https://github.com/maxboettinger/moxtral/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/maxboettinger/moxtral/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Max Boettinger
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,markdown,mistral,ocr,pdf,speech-to-text,transcription,voxtral
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Text Processing :: General
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: click<9,>=8.4.2
|
|
23
|
+
Requires-Dist: httpx<1,>=0.28.1
|
|
24
|
+
Requires-Dist: mistralai<3,>=2.5.1
|
|
25
|
+
Requires-Dist: tomli-w<2,>=1.2.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# moxtral
|
|
29
|
+
|
|
30
|
+
[](https://github.com/maxboettinger/moxtral/actions/workflows/ci.yml)
|
|
31
|
+
[](https://github.com/maxboettinger/moxtral/actions/workflows/release.yml)
|
|
32
|
+
[](https://pypi.org/project/moxtral/)
|
|
33
|
+
[](https://pypi.org/project/moxtral/)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
|
|
36
|
+
**A modern command-line interface for [Mistral OCR](https://docs.mistral.ai/studio-api/document-processing/basic_ocr) and [Voxtral audio transcription](https://docs.mistral.ai/studio-api/audio/speech_to_text/offline_transcription).**
|
|
37
|
+
|
|
38
|
+
> **Powered by [Mistral AI](https://mistral.ai)** — moxtral is an independent,
|
|
39
|
+
> open-source CLI built on the official Mistral AI platform APIs (Mistral OCR
|
|
40
|
+
> for documents, Voxtral for audio). It is **not affiliated with, endorsed by,
|
|
41
|
+
> or sponsored by Mistral AI.** See
|
|
42
|
+
> [About the name & trademarks](#about-the-name--trademarks).
|
|
43
|
+
|
|
44
|
+
Turn PDFs and images into structured Markdown, and turn audio into text — from
|
|
45
|
+
your terminal, in one command. Point it at a local file or a public URL; it
|
|
46
|
+
saves durable Markdown and JSON, processes batches, and keeps progress output
|
|
47
|
+
cleanly separated from pipe-friendly results.
|
|
48
|
+
|
|
49
|
+
```console
|
|
50
|
+
moxtral ocr report.pdf
|
|
51
|
+
moxtral transcribe interview.mp3
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Highlights
|
|
57
|
+
|
|
58
|
+
- **Two capabilities, one tool** — document OCR and speech-to-text behind a
|
|
59
|
+
single `moxtral` command.
|
|
60
|
+
- **Local files or public URLs** — local files are read and sent as base64 data
|
|
61
|
+
URLs; public HTTP(S) URLs are passed through.
|
|
62
|
+
- **Durable results** — every run saves timestamped Markdown and a stable JSON
|
|
63
|
+
envelope you can diff, re-render, or feed to other tools.
|
|
64
|
+
- **Batch-friendly** — pass many sources at once; one failure never discards the
|
|
65
|
+
successful results.
|
|
66
|
+
- **Skips duplicate work** — rerunning the same source with the same options
|
|
67
|
+
within a recency window costs nothing: the CLI detects it and skips the API
|
|
68
|
+
call; `--force` overrides.
|
|
69
|
+
- **Pipe-clean output** — Markdown goes to stdout on request; progress, paths,
|
|
70
|
+
and errors stay on stderr.
|
|
71
|
+
- **Agent-ready** — `--json` streams stable NDJSON records for machine
|
|
72
|
+
consumption, exit codes are documented and stable, and `moxtral agent`
|
|
73
|
+
prints a compact usage guide and output schema for LLM agents.
|
|
74
|
+
- **Secure by default** — API keys are never passed as CLI arguments, are
|
|
75
|
+
redacted from all output (including tracebacks), and are stored with
|
|
76
|
+
restrictive file permissions.
|
|
77
|
+
|
|
78
|
+
## Requirements
|
|
79
|
+
|
|
80
|
+
- Python 3.11 or newer
|
|
81
|
+
- A [Mistral API key](https://console.mistral.ai/)
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Quickstart
|
|
86
|
+
|
|
87
|
+
**1. Install** from [PyPI](https://pypi.org/project/moxtral/) as an isolated
|
|
88
|
+
command with [uv](https://docs.astral.sh/uv/) (or
|
|
89
|
+
[pipx](https://pipx.pypa.io/)):
|
|
90
|
+
|
|
91
|
+
```console
|
|
92
|
+
uv tool install moxtral # or: pipx install moxtral
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Update later with `uv tool upgrade moxtral` (or `pipx upgrade moxtral`).
|
|
96
|
+
|
|
97
|
+
To install from source instead (e.g. to try unreleased changes):
|
|
98
|
+
|
|
99
|
+
```console
|
|
100
|
+
git clone https://github.com/maxboettinger/moxtral.git
|
|
101
|
+
uv tool install --reinstall --force ./moxtral
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Then verify the installation:
|
|
105
|
+
|
|
106
|
+
```console
|
|
107
|
+
uv tool list # the `moxtral` executable must be listed under `moxtral`
|
|
108
|
+
moxtral --help # must show: agent, config, ocr, transcribe
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> **Note:** don't confuse this tool with the PyPI package named `mistral`
|
|
112
|
+
> (OpenStack Mistral, a workflow service unrelated to Mistral AI) or with
|
|
113
|
+
> Mistral AI's own coding-agent CLI (`vibe`).
|
|
114
|
+
|
|
115
|
+
**2. Store your API key** (hidden, confirmed prompt):
|
|
116
|
+
|
|
117
|
+
```console
|
|
118
|
+
moxtral config set api-key
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**3. Run it:**
|
|
122
|
+
|
|
123
|
+
```console
|
|
124
|
+
# Extract text from a PDF or image
|
|
125
|
+
moxtral ocr report.pdf
|
|
126
|
+
|
|
127
|
+
# Transcribe audio
|
|
128
|
+
moxtral transcribe interview.mp3
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Results are saved to `~/.moxtral/ocr/` and `~/.moxtral/transcriptions/` as both
|
|
132
|
+
Markdown and JSON. Add `--stdout` to also print the Markdown for piping:
|
|
133
|
+
|
|
134
|
+
```console
|
|
135
|
+
moxtral ocr report.pdf --stdout | glow -
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
That's the whole loop. The sections below add detail as you need it.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Usage
|
|
143
|
+
|
|
144
|
+
### OCR
|
|
145
|
+
|
|
146
|
+
Process a local PDF or image, or a publicly accessible URL:
|
|
147
|
+
|
|
148
|
+
```console
|
|
149
|
+
moxtral ocr report.pdf
|
|
150
|
+
moxtral ocr https://example.com/report.pdf
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
> Public URLs must be directly reachable by Mistral. Local files are read and
|
|
154
|
+
> sent as base64 data URLs. Local files are limited to 50 MB (they are sent
|
|
155
|
+
> inline as base64); larger documents should be hosted at a URL.
|
|
156
|
+
|
|
157
|
+
Common options:
|
|
158
|
+
|
|
159
|
+
```console
|
|
160
|
+
moxtral ocr report.pdf \
|
|
161
|
+
--pages 0,2-4 \
|
|
162
|
+
--table-format markdown \
|
|
163
|
+
--extract-header \
|
|
164
|
+
--extract-footer \
|
|
165
|
+
--include-images --image-limit 20 --image-min-size 100 \
|
|
166
|
+
--include-blocks \
|
|
167
|
+
--confidence word
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| Option | Description |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `--pages TEXT` | Zero-based page numbers and ascending ranges, e.g. `0,2-4`. |
|
|
173
|
+
| `--table-format inline\|markdown\|html` | Table representation. Default: `inline`. |
|
|
174
|
+
| `--extract-header` / `--extract-footer` | Return those regions separately. |
|
|
175
|
+
| `--include-images` | Request base64 image data. |
|
|
176
|
+
| `--image-limit N` / `--image-min-size N` | Bound returned images (require `--include-images`; nonnegative). |
|
|
177
|
+
| `--include-blocks` | Request structured blocks and bounding boxes. |
|
|
178
|
+
| `--confidence none\|page\|word` | Confidence detail level. |
|
|
179
|
+
| `--model TEXT` | OCR model. Default: `mistral-ocr-latest`. |
|
|
180
|
+
| `--timeout SECONDS` | Positive request timeout. Default: `300`. |
|
|
181
|
+
| `--retries N` | Retry attempts for rate-limited, server-error, and connection failures, with exponential backoff. Default: `3`; `0` disables. |
|
|
182
|
+
|
|
183
|
+
Some options depend on model capabilities — see the
|
|
184
|
+
[official OCR documentation](https://docs.mistral.ai/studio-api/document-processing/basic_ocr).
|
|
185
|
+
|
|
186
|
+
### Transcription
|
|
187
|
+
|
|
188
|
+
Transcribe a local audio file or a public audio URL:
|
|
189
|
+
|
|
190
|
+
```console
|
|
191
|
+
moxtral transcribe interview.mp3
|
|
192
|
+
moxtral transcribe https://example.com/interview.mp3
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Request speaker labels, contextual hints, and timestamps:
|
|
196
|
+
|
|
197
|
+
```console
|
|
198
|
+
moxtral transcribe interview.mp3 \
|
|
199
|
+
--temperature 0.2 \
|
|
200
|
+
--diarize \
|
|
201
|
+
--context-bias Mistral --context-bias Voxtral \
|
|
202
|
+
--timestamps segment
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
| Option | Description |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| `--language CODE` | Source language code. |
|
|
208
|
+
| `--temperature FLOAT` | Sampling temperature; must be finite. |
|
|
209
|
+
| `--diarize` | Request speaker identification. |
|
|
210
|
+
| `--context-bias TEXT` | Contextual hint; repeatable, up to 100 values. |
|
|
211
|
+
| `--timestamps segment\|word` | Repeatable; duplicates ignored. |
|
|
212
|
+
| `--model TEXT` | Transcription model. Default: `voxtral-mini-latest`. |
|
|
213
|
+
| `--timeout SECONDS` | Positive request timeout. Default: `300`. |
|
|
214
|
+
| `--retries N` | Retry attempts for rate-limited, server-error, and connection failures, with exponential backoff. Default: `3`; `0` disables. |
|
|
215
|
+
|
|
216
|
+
> `--language` and `--timestamps` cannot be combined — the Mistral API does not
|
|
217
|
+
> support that pairing.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Results & batch processing
|
|
222
|
+
|
|
223
|
+
Both commands share these output options:
|
|
224
|
+
|
|
225
|
+
| Option | Description |
|
|
226
|
+
| --- | --- |
|
|
227
|
+
| `--format md\|json\|both` | Which formats to save. Default: `both`. |
|
|
228
|
+
| `--output-dir DIRECTORY` | Override the default result directory. |
|
|
229
|
+
| `--stdout` | Also write rendered Markdown to standard output. |
|
|
230
|
+
| `--json` | Write NDJSON result records to standard output (one per source; mutually exclusive with `--stdout`). |
|
|
231
|
+
| `--quiet` | Suppress progress and summary lines on stderr (errors still shown). |
|
|
232
|
+
| `--no-save` | Skip result files entirely (requires `--json` or `--stdout`). |
|
|
233
|
+
| `--dry-run` | Validate sources and options without calling the API — no key needed. |
|
|
234
|
+
| `--force` | Process a source even if an identical recent result already exists. |
|
|
235
|
+
| `--dedupe-window DAYS` | Look-back window for duplicate detection. Default: `30`. |
|
|
236
|
+
|
|
237
|
+
**Where results go.** By default, into per-command directories, named with a UTC
|
|
238
|
+
timestamp followed by the original source filename:
|
|
239
|
+
|
|
240
|
+
```text
|
|
241
|
+
~/.moxtral/ocr/20260703T121314.123456Z-report.pdf.md
|
|
242
|
+
~/.moxtral/ocr/20260703T121314.123456Z-report.pdf.json
|
|
243
|
+
~/.moxtral/transcriptions/...
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- **Markdown** contains provenance and readable page or transcript content.
|
|
247
|
+
- **JSON** is a stable envelope: `schema_version`, `created_at`, source metadata,
|
|
248
|
+
request options, the complete API response, and `cli_version`.
|
|
249
|
+
|
|
250
|
+
**Batch processing.** Pass multiple local files and URLs to process them
|
|
251
|
+
sequentially:
|
|
252
|
+
|
|
253
|
+
```console
|
|
254
|
+
moxtral ocr chapter-1.pdf chapter-2.pdf https://example.com/appendix.pdf
|
|
255
|
+
moxtral transcribe part-1.mp3 part-2.mp3
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
A failure for one source does not discard successful results or stop later
|
|
259
|
+
sources. The final summary reports successes, failures, and skips, and any
|
|
260
|
+
failure produces a nonzero exit status. With `--stdout` and multiple
|
|
261
|
+
successes, Markdown documents are separated by a horizontal rule.
|
|
262
|
+
|
|
263
|
+
**Duplicate skipping.** Before calling the API, each source is checked against
|
|
264
|
+
a local index of prior results and skipped if an identical, recent one already
|
|
265
|
+
exists — no charge, no network call:
|
|
266
|
+
|
|
267
|
+
- **Content** matches by SHA-256 of a local file's bytes (renames don't
|
|
268
|
+
matter; different bytes never match) or by the literal value of a URL
|
|
269
|
+
source.
|
|
270
|
+
- **Options** must match too — the same model, language, diarization,
|
|
271
|
+
timestamps, pages, table format, etc. (`--timeout` is ignored).
|
|
272
|
+
- **Recency**: the existing result must be within `--dedupe-window DAYS`
|
|
273
|
+
(default `30`).
|
|
274
|
+
- **Coverage**: the existing files must still be on disk and satisfy this
|
|
275
|
+
run's `--format`/`--stdout` needs — a Markdown-only save doesn't dedupe a
|
|
276
|
+
run that also needs the JSON envelope.
|
|
277
|
+
|
|
278
|
+
A skip prints `Skipping duplicate: SOURCE (existing result from TIMESTAMP;
|
|
279
|
+
use --force to reprocess).` plus one `Existing: PATH` line per artifact on
|
|
280
|
+
stderr, re-emits the saved Markdown under `--stdout`, and writes a
|
|
281
|
+
`status: "skipped"` record under `--json`. Pass `--force` to reprocess
|
|
282
|
+
regardless (this still records the new result). `--dry-run` runs the same
|
|
283
|
+
check for free and reports what it *would* skip. Duplicate checks need no API
|
|
284
|
+
key, so a run where every source is a duplicate exits `0` without ever
|
|
285
|
+
resolving a key.
|
|
286
|
+
|
|
287
|
+
The index lives at `~/.moxtral/index.ndjson` (created automatically, `0600`,
|
|
288
|
+
best-effort — a read/write problem falls back to processing normally with a
|
|
289
|
+
warning) and is never written to with `--no-save`.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Machine-readable output & agents
|
|
294
|
+
|
|
295
|
+
With `--json`, each command streams one NDJSON record per source to stdout —
|
|
296
|
+
`status: "ok"` records carry the full result envelope and saved paths,
|
|
297
|
+
`status: "skipped"` records report a duplicate that was skipped instead of
|
|
298
|
+
billed (with the existing saved paths and timestamp), failures appear in-band
|
|
299
|
+
as `status: "error"` records with stable error codes (`input_error`,
|
|
300
|
+
`config_error`, `api_error`, `persistence_error`, `unexpected_error`), and
|
|
301
|
+
every completed run ends with a `status: "summary"` record. Output is
|
|
302
|
+
ASCII-encoded, so it is immune to terminal-control injection and always
|
|
303
|
+
parses:
|
|
304
|
+
|
|
305
|
+
```console
|
|
306
|
+
moxtral ocr --json --quiet report.pdf \
|
|
307
|
+
| jq -r 'select(.status=="ok") | .envelope.response.pages[].markdown'
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Exit codes are part of the stable contract:
|
|
311
|
+
|
|
312
|
+
| Code | Meaning |
|
|
313
|
+
| --- | --- |
|
|
314
|
+
| `0` | Every source succeeded or was skipped as a duplicate (or validated, under `--dry-run`). |
|
|
315
|
+
| `1` | At least one source failed. |
|
|
316
|
+
| `2` | Usage error (bad flags or flag combination). |
|
|
317
|
+
| `3` | Setup failure (missing API key, unreadable config). |
|
|
318
|
+
|
|
319
|
+
For LLM agents and other tooling, `moxtral agent` prints a compact usage guide
|
|
320
|
+
and `moxtral agent --schema` prints the JSON Schema for the NDJSON records.
|
|
321
|
+
Use `--dry-run --json` to validate a batch without an API key or network
|
|
322
|
+
calls — it also reports sources that would be skipped as duplicates.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Configuration
|
|
327
|
+
|
|
328
|
+
### API key
|
|
329
|
+
|
|
330
|
+
The key is resolved from `MISTRAL_API_KEY` first, then the stored config.
|
|
331
|
+
|
|
332
|
+
Store it via a hidden, confirmed prompt:
|
|
333
|
+
|
|
334
|
+
```console
|
|
335
|
+
moxtral config set api-key
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
For automation, pass a single line over stdin (no echo, not in the arg list):
|
|
339
|
+
|
|
340
|
+
```console
|
|
341
|
+
read -r -s MISTRAL_KEY
|
|
342
|
+
printf '%s\n' "$MISTRAL_KEY" | moxtral config set api-key --stdin
|
|
343
|
+
unset MISTRAL_KEY
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Or override per-invocation with the environment variable:
|
|
347
|
+
|
|
348
|
+
```console
|
|
349
|
+
MISTRAL_API_KEY="..." moxtral ocr report.pdf
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Inspect or change the stored configuration without exposing the key:
|
|
353
|
+
|
|
354
|
+
```console
|
|
355
|
+
moxtral config show
|
|
356
|
+
moxtral config path
|
|
357
|
+
moxtral config unset api-key
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Config file
|
|
361
|
+
|
|
362
|
+
The default path is `~/.moxtral/config.toml`. On POSIX systems the CLI creates
|
|
363
|
+
its config directory with mode `0700` and the file with mode `0600`, and writes
|
|
364
|
+
updates atomically. The key is stored as **plaintext**, so protect the account
|
|
365
|
+
and any backups that contain this file.
|
|
366
|
+
|
|
367
|
+
Select a different file with the root `--config` option:
|
|
368
|
+
|
|
369
|
+
```console
|
|
370
|
+
moxtral --config ./private-config.toml config set api-key
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Shell completion
|
|
374
|
+
|
|
375
|
+
Click provides tab completion for commands and options. Add the line for
|
|
376
|
+
your shell to its startup file:
|
|
377
|
+
|
|
378
|
+
```console
|
|
379
|
+
eval "$(_MOXTRAL_COMPLETE=zsh_source moxtral)" # ~/.zshrc
|
|
380
|
+
eval "$(_MOXTRAL_COMPLETE=bash_source moxtral)" # ~/.bashrc
|
|
381
|
+
_MOXTRAL_COMPLETE=fish_source moxtral | source # ~/.config/fish/config.fish
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Errors, debugging & security
|
|
387
|
+
|
|
388
|
+
Expected input, configuration, network, API, and persistence failures are
|
|
389
|
+
reported concisely, without a traceback. Put the root `--debug` flag **before**
|
|
390
|
+
the command to include diagnostic tracebacks:
|
|
391
|
+
|
|
392
|
+
```console
|
|
393
|
+
moxtral --debug transcribe interview.mp3
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Transient failures that fail fast — HTTP 429 rate limits, 5xx server errors,
|
|
397
|
+
refused connections — are retried automatically with exponential backoff
|
|
398
|
+
(`--retries`, default 3). Requests that fail slowly (e.g. timeouts) exhaust
|
|
399
|
+
the retry budget and are not retried. A request that never succeeded is
|
|
400
|
+
never billed.
|
|
401
|
+
|
|
402
|
+
Security properties:
|
|
403
|
+
|
|
404
|
+
- The CLI **never** accepts an API key as a command-line value. Use the hidden
|
|
405
|
+
prompt, `--stdin`, or `MISTRAL_API_KEY`.
|
|
406
|
+
- Known API keys are **redacted** from terminal output and persisted results,
|
|
407
|
+
including debug output.
|
|
408
|
+
- Avoid embedding credentials in source URLs — URLs are recorded as result
|
|
409
|
+
provenance.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## About the name & trademarks
|
|
414
|
+
|
|
415
|
+
moxtral (**M**istral **O**CR + Vo**xtral**) is an independent, open-source
|
|
416
|
+
project. It is not affiliated with, endorsed by, or sponsored by Mistral AI.
|
|
417
|
+
"Mistral", "Mistral OCR", and "Voxtral" are trademarks of their respective
|
|
418
|
+
owner and are used here only to identify the APIs this tool talks to.
|
|
419
|
+
|
|
420
|
+
The CLI deliberately does not claim the bare `mistral` command: that name
|
|
421
|
+
belongs to other projects (the OpenStack Mistral client on PyPI installs a
|
|
422
|
+
`mistral` executable) and plausibly to Mistral AI's own tooling in the future
|
|
423
|
+
(their coding agent currently installs `vibe`). `moxtral` installs alongside
|
|
424
|
+
all of them without conflict.
|
|
425
|
+
|
|
426
|
+
> Historical note: this project was named `mistral-cli` (command `mistral`)
|
|
427
|
+
> up to version 0.3.0.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Development
|
|
432
|
+
|
|
433
|
+
Install locked runtime and development dependencies:
|
|
434
|
+
|
|
435
|
+
```console
|
|
436
|
+
uv sync
|
|
437
|
+
uv run moxtral --help
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Run the full quality gates:
|
|
441
|
+
|
|
442
|
+
```console
|
|
443
|
+
uv run pytest --cov=moxtral --cov-report=term-missing
|
|
444
|
+
uv run ruff check .
|
|
445
|
+
uv run ruff format --check .
|
|
446
|
+
uv run pyright
|
|
447
|
+
uv lock --check
|
|
448
|
+
uv build
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Releasing
|
|
452
|
+
|
|
453
|
+
Releases are fully automated via
|
|
454
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no
|
|
455
|
+
API tokens). Every push to `main` checks whether the `pyproject.toml`
|
|
456
|
+
version already has a `v<version>` tag; if not, it runs the full CI matrix,
|
|
457
|
+
publishes to PyPI, and then tags the commit and creates a GitHub release
|
|
458
|
+
with the built artifacts. So a release is just a version bump:
|
|
459
|
+
|
|
460
|
+
```console
|
|
461
|
+
uv version --bump minor # or: patch / major — updates pyproject.toml + uv.lock
|
|
462
|
+
# update CHANGELOG.md, then:
|
|
463
|
+
git commit -am "release: v$(uv version --short)"
|
|
464
|
+
git push
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
The tag is created only after a successful PyPI upload, so a failed release
|
|
468
|
+
leaves nothing tagged and simply retries on the next push (or via a manual
|
|
469
|
+
re-run of the workflow).
|
|
470
|
+
|
|
471
|
+
### Architecture
|
|
472
|
+
|
|
473
|
+
The package uses a `src/` layout with clear boundaries between the CLI surface,
|
|
474
|
+
domain models, use cases, and the SDK adapter:
|
|
475
|
+
|
|
476
|
+
| Module | Responsibility |
|
|
477
|
+
| --- | --- |
|
|
478
|
+
| `cli/` | Click commands, validation flow, and sanitized console reporting. |
|
|
479
|
+
| `models.py` | SDK-independent request and result types. |
|
|
480
|
+
| `services/` | OCR and transcription use cases behind gateway protocols. |
|
|
481
|
+
| `mistral_client.py` | Adapter for the official Mistral Python SDK. |
|
|
482
|
+
| `config.py` | Typed TOML configuration and secure atomic writes. |
|
|
483
|
+
| `sources.py` | Resolves local files and URLs into request inputs. |
|
|
484
|
+
| `formatters.py` | Renders results to Markdown. |
|
|
485
|
+
| `storage.py` | Persists collision-safe output atomically. |
|
|
486
|
+
| `errors.py`, `console.py` | Translate, redact, and safely present failures. |
|
|
487
|
+
|
|
488
|
+
The request flow is: **CLI** parses and validates options → **services** invoke
|
|
489
|
+
the use case through a gateway protocol → **mistral_client** calls the SDK →
|
|
490
|
+
**formatters** and **storage** render and persist results. Tests isolate the API
|
|
491
|
+
behind fakes and cover command behavior, mapping, formatting, configuration,
|
|
492
|
+
security boundaries, and persistence.
|
|
493
|
+
|
|
494
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## References
|
|
499
|
+
|
|
500
|
+
- [Mistral OCR](https://docs.mistral.ai/studio-api/document-processing/basic_ocr)
|
|
501
|
+
- [Mistral offline transcription](https://docs.mistral.ai/studio-api/audio/speech_to_text/offline_transcription)
|
|
502
|
+
- [Mistral audio transcription endpoint](https://docs.mistral.ai/api/endpoint/audio/transcriptions)
|
|
503
|
+
- [Click documentation](https://click.palletsprojects.com/)
|