mavctl
Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles.
mavctl is ArduPilot-first and built to be driven by both humans on a terminal and AI coding agents (Claude Code, Codex, OpenClaw, …). A resident daemon keeps the MAVLink link alive and caches vehicle state; every CLI call is one short, structured request to that daemon.
Status: developed and verified against ArduPilot SITL. It has not been proven across the breadth of real MAVLink vehicles and is not presented as ready for production flight on a real aircraft.
Why mavctl
GUI ground stations such as Mission Planner or QGroundControl are excellent for a human at the controls — and a poor interface for a shell script or an LLM agent: clickable UIs, no stable exit codes, no machine-readable output.
mavctl takes the other side of that trade:
- the daemon owns the MAVLink connection and continuously caches telemetry, so each command is quick and stateless;
- every command prints human-readable output by default and structured JSON
with
--json; - failures carry explicit exit codes (3 daemon down, 4 link lost, 5 guard rejection, 6 vehicle NACK / timeout) instead of stack traces;
- dangerous operations pass safety guards before anything reaches the vehicle;
- mavctl embeds no LLM — it is designed to be called by agents such as Claude Code, Codex or OpenClaw, or by plain bash.
Current capabilities
Implemented commands — this is the complete list:
mavctl daemon start|stop|status
mavctl status
mavctl telemetry
mavctl arm
mavctl disarm
mavctl mode <MODE>
mavctl takeoff --alt <metres>
mavctl land
mavctl rtl
Cross-cutting behaviour:
| Flag / behaviour | Meaning |
|---|---|
--json |
structured output on stdout; errors as {"error": {...}} on stderr |
--confirm |
required on every state-changing command; without it exit code 5 |
--dry-run |
run the exact same guards, never reach the vehicle |
--wait --timeout <s> |
block until the target state is reached (default 60 s) |
| idempotent repeats | re-applying an achieved change succeeds ("already armed") |
| transaction safety | ACK/NACK handling, serialized commands, link-loss abort |
Not implemented — current scope only, not a roadmap promise:
Mission upload/download/start
Parameters
Geofence
Log download / analysis
Firmware flashing
Multi-vehicle orchestration
Quickstart with ArduPilot SITL
Requires Python >= 3.10 and uv. Always bring up SITL first; do not point an agent-driven workflow at a real vehicle.
Terminal 1 — start ArduPilot SITL:
sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550
Terminal 2 — install from source and connect:
uv sync
uv run mavctl daemon start --connect udp:127.0.0.1:14550
uv run mavctl status --json
Safe takeoff to 10 m and return to launch:
uv run mavctl mode GUIDED --confirm --wait
uv run mavctl arm --confirm
# Poll status --json until armed=true (the arm ACK can beat the heartbeat)
uv run mavctl takeoff --alt 10 --confirm --wait --timeout 45
uv run mavctl rtl --confirm --wait --timeout 120
uv run mavctl daemon stop
Safety notes — read before pointing mavctl at anything that flies:
- Validate every workflow in SITL first; treat real-aircraft use as its own review process.
- After
arm, pollstatus --jsonuntilarmed=truebefore takeoff: the COMMAND_ACK can arrive about one heartbeat before reported state catches up. - End flights with
rtl/land, notdisarm. Ordinarydisarmrequires provable ground contact (ground_state_unknownotherwise). disarm --forceis an emergency motor stop only — in flight it can cause a crash.- There is no
arm --forceanywhere in mavctl; pre-arm checks cannot be bypassed.
Installation
From PyPI
mavctl's first production release (0.2.0) is being prepared. After the
PyPI release is published, install with:
uv tool install mavctl
uvx mavctl --help
pipx install mavctl
Until that release is published these commands have nothing to fetch from production PyPI; TestPyPI rehearsal artifacts are not production releases. Release status and the publishing runbook live in docs/PUBLISHING.md.
From source
For development from source, use uv sync and uv run mavctl …:
git clone https://github.com/LeaderOnePro/mavctl.git
cd mavctl
uv sync
uv run mavctl --help
Agent Skill
The repository ships a portable agent Skill under skills/mavctl-flight/
(entrypoint plus workflows / safety / troubleshooting references). It is a
source asset of this repo, not an installed package.
To use it with an agent runtime, install or symlink this directory according to that runtime's current Skill discovery convention.
Example for Claude Code, project-local to this repository:
mkdir -p .claude/skills
ln -s ../../skills/mavctl-flight .claude/skills/mavctl-flight
That is one concrete example, not a universal convention — runtimes differ.
Safety model
Short version; full details in skills/mavctl-flight/references/safety.md:
- the daemon owns the vehicle link; CLI calls are short transactions;
- state-changing commands require
--confirm;--dry-runpreviews decisions; - exit 4 = no live vehicle state (link lost / heartbeat expired), including
mid-
--waitloss (link_lost_during_wait); - exit 5 = guard rejection with structured
reason+hint; - exit 6 = vehicle NACK / ACK timeout / wait timeout;
- force-arm does not exist at any layer (CLI option, RPC field, adapter verb);
- ordinary
disarmneeds positive ground evidence, elseground_state_unknown(exit 5); - after link loss,
statusreflects stale cache:armedrenders unknown/n/a, never silently disarmed.
Development
uv sync
uv run ruff check .
uv run mypy .
uv run pytest -m "not sitl"
uv run pytest -m sitl # requires a running ArduPilot SITL
Further reading:
- docs/SITL_ACCEPTANCE.md
- docs/SITL_ACCEPTANCE_PHASE2.md
- AGENTS.md — architecture rules and contribution constraints
- skills/mavctl-flight/SKILL.md — agent-facing flight guidance
License
MIT — Copyright (c) 2026 LeaderOnePro.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mavctl-0.2.0.tar.gz.
File metadata
- Download URL: mavctl-0.2.0.tar.gz
- Upload date:
- Size: 136.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b076de80f9eb091ccc7e308e64a02c4308b5c227b335ca51924b3dc5e09628d
|
|
| MD5 |
be7bb1cf55a6f3a7b091ed545f392bff
|
|
| BLAKE2b-256 |
66d1b9719f7475b705fe8185a03d5a03e0c7df63b54846a87b9abb6a72fdf6f0
|
Provenance
The following attestation bundles were made for mavctl-0.2.0.tar.gz:
Publisher:
publish.yml on LeaderOnePro/mavctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mavctl-0.2.0.tar.gz -
Subject digest:
8b076de80f9eb091ccc7e308e64a02c4308b5c227b335ca51924b3dc5e09628d - Sigstore transparency entry: 2587520406
- Sigstore integration time:
-
Permalink:
LeaderOnePro/mavctl@d33406771762c9631c1fe842c24c10ead3a5f148 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/LeaderOnePro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d33406771762c9631c1fe842c24c10ead3a5f148 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mavctl-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mavctl-0.2.0-py3-none-any.whl
- Upload date:
- Size: 37.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03c35f5b16c26f666540a34b00822beb7c77e0a3abd53cf79fc0176c8b94aaa1
|
|
| MD5 |
e87ae369006afd5892c517e8ee008fdf
|
|
| BLAKE2b-256 |
48a7fa5a4b4ef130142a7ad9c8070dea7a3ca85550091a1f22326c86101b8060
|
Provenance
The following attestation bundles were made for mavctl-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on LeaderOnePro/mavctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mavctl-0.2.0-py3-none-any.whl -
Subject digest:
03c35f5b16c26f666540a34b00822beb7c77e0a3abd53cf79fc0176c8b94aaa1 - Sigstore transparency entry: 2587521181
- Sigstore integration time:
-
Permalink:
LeaderOnePro/mavctl@d33406771762c9631c1fe842c24c10ead3a5f148 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/LeaderOnePro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d33406771762c9631c1fe842c24c10ead3a5f148 -
Trigger Event:
push
-
Statement type: