Machine-readable output 1.3
Since engine 1.3.0, every CLI mode except lsp and completions takes --json, and doctor and check take --markdown (alias --md). --format=json and --format=markdown work too; given both, JSON wins. Engine 1.2.2 and older print text only.
pypilot doctor --json > report.json
pypilot check mediapipe==0.10.14 --json
pypilot doctor --markdown # paste into an issueWith --json the command prints exactly one JSON document on stdout; progress lines go to stderr. Exit codes are the same as without it. (pypilot logs --json is the exception: it prints the raw log lines, one JSON object per line, without the envelope. See Logs.)
The human-readable text of the other modes is for people and may change between releases. Do not parse it; use --json.
The envelope
Every document starts with the same three keys, then the command's own fields:
{ "pypilot": "1.3.0", "command": "check", "ok": true }| Key | Meaning |
|---|---|
pypilot | Engine version that wrote it |
command | doctor, doctor --fix, check, setup, install, fix python, fix cuda, update-data, migrate-conda, migrate-to-pixi, install-pixi, version |
ok | The command's own verdict (see the table below). A failed command also exits non-zero, except doctor, check and update-data, which only report (update-data --check exits 1 when stale, 2 when a fetch failed) |
error | Only when the command failed before it had a report: { "pypilot", "command", "ok": false, "error": "…" } |
Stability. Field names are stable: new fields may be added, existing ones are never renamed, removed or retyped. Versions are strings ("3.12"); a set of Pythons is a list of them, oldest first. Paths are strings. A command's fields are always present, null when there is no value (the variant-specific keys of pixi.lock aside). Key order is not part of the contract: most documents keep the order shown here, version and update-data --check print theirs alphabetically.
Per command
| Command | ok means | Fields |
|---|---|---|
doctor | no finding has severity error | workspace, package_manager, environment (uv, venv, interpreters), project (sources, requires_python, pixi_python, dependencies, conda_dependencies, has_conda_env), pixi (null, or manifest, platforms, lock, environments), compatibility (null without dependencies, else platform, supported, supported_range, recommended, packages, unresolved, unreachable, conflicts), target_python, findings, data_tables (the text report's one-line refresh state) |
check | the package installs and runs here | requirement, analysis, supported_range, platform, installed, environment_python, pixi_project, verdict (kind: supported, unsupported, no_environment, no_files or conflict; status: ok, error or info; message), conflict (the project dependency it shares no Python with, as a conflict pair, or null), why (reasons it fails here), alternative (the conda-forge build, or null) |
setup | summary.ok | workspace, summary, pixi_advice |
install | summary.ok | workspace, package, summary, pixi_advice |
fix python | summary.ok | workspace, target_python, summary (null with a message when already on it), pixi_advice |
fix cuda | every framework re-pinned | message, frameworks (package, finding, spec, index_url, action: pinned_in_pyproject, installed or skipped, ok, detail), error |
update-data | every dataset refreshed | datasets (name, ok, error) |
update-data --check | the tables in use match main | check: true, status (current, stale or error), refresh_days, summary, cache_dir, tables: [{ name, bundled_schema, cache_age_days, cache_schema, in_use: "bundled" | "cache", main: "same" | "differs" | "missing" | "error", main_schema, main_matches_bundled, error }] |
version | always | version, commit, commit_date, target (each null when the build did not record it), data: { bundled_date, refresh_days, summary, tables: [{ name, bundled_schema, cache_age_days, cache_schema, in_use }] } |
migrate-conda | written | written, dependencies, environment_file; with --to pixi, as migrate-to-pixi |
migrate-to-pixi | the requested pixi install succeeded (always, for a preview) | summary: manifest_path, manifest, preview, written, sources, python, platforms, channels, deps (name, value, kind: conda or pypi, from, reason), skipped, notes, install, next_command |
install-pixi | installed | path, version, already_installed, refresh_path |
Shared shapes
- finding:
{ "severity": "error" | "warning" | "info", "title", "detail", "fix": { "kind", "python"? } }.fix.kindissetup_environment,recreate_with_python(withpython),migrate_conda,migrate_to_pixiormanual. - package analysis:
{ "name", "version", "supported", "has_platform_wheels", "sdist_only", "has_foreign_wheels", "no_files_for_platform", "wheel_platforms", "requires_python" }. - conflict:
{ "low": analysis, "high": analysis, "explanation" }.lowis the package with the lowest newest Python,highthe one with the highest oldest Python. - summary (what
setup,installandfix pythonran):{ "package_manager", "resolved_mode": "uv_full" | "uv_pip_compat" | "pure_pip" | "pixi", "python", "venv_path", "why", "steps": [{ "name", "ok", "detail" }], "ok" }.
Dry runs
With --dry-run, setup, install, fix python, fix cuda and doctor --fix print a plan instead of their usual document:
{ "pypilot": "1.3.0", "command": "setup", "ok": true, "dry_run": true,
"plan": { "mode": "uv_full", "python": "3.12", "why": "…",
"steps": [{ "name", "command": [argv], "writes": [paths], "why" }],
"notes": [{ "name", "ok", "detail" }], "blocked": null } }ok is blocked == null. The fields and a full example are on Dry runs.
Examples
pypilot doctor --json in the project from the home page:
{
"pypilot": "1.3.0",
"command": "doctor",
"ok": true,
"workspace": "C:\\src\\demo",
"package_manager": "auto",
"environment": {
"uv": {
"path": "uv",
"version": "0.13.5",
"managed": false
},
"venv": null,
"interpreters": [
{
"command": "python3",
"path": "C:\\Users\\me\\AppData\\Roaming\\uv\\python\\cpython-3.15-windows-x86_64-none\\python.exe",
"version": "3.15"
},
{
"command": "C:\\Python314\\python.exe",
"path": "C:\\Python314\\python.exe",
"version": "3.14"
}
]
},
"project": {
"sources": [
"C:\\src\\demo\\requirements.txt"
],
"requires_python": null,
"pixi_python": null,
"dependencies": [
"mediapipe==0.10.14",
"numpy"
],
"conda_dependencies": [],
"has_conda_env": false
},
"pixi": null,
"compatibility": {
"platform": "Windows x64",
"supported": [
"3.12"
],
"supported_range": "3.12",
"recommended": "Python 3.12",
"packages": [
{
"name": "mediapipe",
"version": "0.10.14",
"supported": [
"3.9",
"3.10",
"3.11",
"3.12"
],
"has_platform_wheels": true,
"sdist_only": false,
"has_foreign_wheels": false,
"no_files_for_platform": false,
"wheel_platforms": [],
"requires_python": null
},
{
"name": "numpy",
"version": "2.5.4",
"supported": [
"3.12",
"3.13",
"3.14",
"3.15"
],
"has_platform_wheels": true,
"sdist_only": false,
"has_foreign_wheels": false,
"no_files_for_platform": false,
"wheel_platforms": [],
"requires_python": ">=3.12"
}
],
"unresolved": [],
"unreachable": [],
"conflicts": []
},
"target_python": "3.12",
"findings": [
{
"severity": "warning",
"title": "No environment set up yet",
"detail": "Dependencies support Python 3.12. Recommended: 3.12 (upper bound set by mediapipe, which supports 3.9–3.12).",
"fix": {
"kind": "setup_environment"
}
}
],
"data_tables": "refreshed 3 hours ago (next in 6 days); 1 of 5 bundled"
}pypilot check mediapipe==0.10.14 --json:
{
"pypilot": "1.3.0",
"command": "check",
"ok": true,
"requirement": "mediapipe==0.10.14",
"analysis": {
"name": "mediapipe",
"version": "0.10.14",
"supported": [
"3.9",
"3.10",
"3.11",
"3.12"
],
"has_platform_wheels": true,
"sdist_only": false,
"has_foreign_wheels": false,
"no_files_for_platform": false,
"wheel_platforms": [],
"requires_python": null
},
"supported_range": "3.9–3.12",
"platform": "Windows x64",
"installed": false,
"environment_python": null,
"pixi_project": false,
"verdict": {
"kind": "no_environment",
"status": "info",
"message": "This project has no virtual environment yet. Python 3.12 would suit it."
},
"conflict": null,
"why": [],
"alternative": null
}pypilot doctor --markdown prints the report the editors show under Show details:
# PyPilot report
## Environment
- uv: v0.13.5
- virtualenv: none
- interpreters: 3.15, 3.14
## Compatibility
- Supported Python across dependencies: **3.12**
- Recommended interpreter: **Python 3.12**
| Package | Supported Python |
|---|---|
| mediapipe | 3.9–3.12 |
| numpy | 3.12–3.15 |
## Findings
### Warning: No environment set up yet
Dependencies support Python 3.12. Recommended: 3.12 (upper bound set by mediapipe, which supports 3.9–3.12).
_Fix: run "Fix environment", or `pypilot setup`._version --json
pypilot version --json:
{
"command": "version",
"commit": "035ad2f2045a",
"commit_date": "2026-10-29",
"data": {
"bundled_date": "2026-10-29",
"refresh_days": 7,
"summary": "refreshed just now (next in 6 days); 1 of 5 bundled",
"tables": [
{ "bundled_schema": 3, "cache_age_days": 0.0, "cache_schema": 3, "in_use": "cache", "name": "nvidia.json" },
{ "bundled_schema": 1, "cache_age_days": null, "cache_schema": null, "in_use": "bundled", "name": "conda_forge_packages.json" }
]
},
"ok": true,
"pypilot": "1.3.0",
"target": "x86_64-pc-windows-msvc",
"version": "1.3.0"
}(Three of the five tables trimmed.)
explain --json
explain predates the envelope and keeps its own top level: { "pypilot", "kind": "package" | "project", "explanation": { … } }. See the example on the command reference.
In CI
Fail a job when the project has an error-level finding (no supported Python, a conflict, a Python the environment cannot run):
pip install pypilot-cli
pypilot doctor --json > pypilot.json
python -c "import json,sys; sys.exit(0 if json.load(open('pypilot.json'))['ok'] else 1)"doctor itself always exits 0 when it ran, so the check reads ok. This needs pypilot-cli 1.3.0 or newer, which runs engine 1.3.0; pypilot-cli 1.2.2 pins engine 1.2.2, which has no --json. PYPILOT_ENGINE_VERSION=vX.Y.Z can also select a newer engine release (see environment variables).
From Python, pypilot.run([...], capture_output=True).json() parses the document for you; see the Python API.
To check that the data tables are current in the same job:
pypilot update-data --check --json > tables.json || echo "tables stale or unreachable (exit $?)"