Metadata-Version: 2.5
Name: mavctl
Version: 0.2.0
Summary: Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles
Project-URL: Homepage, https://github.com/LeaderOnePro/mavctl
Project-URL: Repository, https://github.com/LeaderOnePro/mavctl
Project-URL: Issues, https://github.com/LeaderOnePro/mavctl/issues
Project-URL: Documentation, https://github.com/LeaderOnePro/mavctl/tree/main/docs
Author: LeaderOnePro
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: System :: Networking
Requires-Python: >=3.10
Requires-Dist: pydantic>=2.6
Requires-Dist: pymavlink>=2.4.40
Requires-Dist: typer>=0.12
Description-Content-Type: text/markdown

# 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:

```bash
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:

```text
Mission upload/download/start
Parameters
Geofence
Log download / analysis
Firmware flashing
Multi-vehicle orchestration
```

## Quickstart with ArduPilot SITL

Requires Python >= 3.10 and [uv](https://docs.astral.sh/uv/). Always bring up
SITL first; do not point an agent-driven workflow at a real vehicle.

Terminal 1 — start ArduPilot SITL:

```bash
sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550
```

Terminal 2 — install from source and connect:

```bash
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:

```bash
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`, poll `status --json` until `armed=true` before takeoff: the
  COMMAND_ACK can arrive about one heartbeat before reported state catches up.
- End flights with `rtl` / `land`, not `disarm`. Ordinary `disarm` requires
  provable ground contact (`ground_state_unknown` otherwise).
- `disarm --force` is an emergency motor stop only — in flight it can cause a
  crash.
- There is no `arm --force` anywhere 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:

```bash
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](docs/PUBLISHING.md).

### From source

For development from source, use `uv sync` and `uv run mavctl …`:

```bash
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:

```bash
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](skills/mavctl-flight/references/safety.md):

- the daemon owns the vehicle link; CLI calls are short transactions;
- state-changing commands require `--confirm`; `--dry-run` previews decisions;
- exit 4 = no live vehicle state (link lost / heartbeat expired), including
  mid-`--wait` loss (`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 `disarm` needs positive ground evidence, else `ground_state_unknown`
  (exit 5);
- after link loss, `status` reflects stale cache: `armed` renders unknown/n/a,
  never silently disarmed.

## Development

```bash
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.md)
- [docs/SITL_ACCEPTANCE_PHASE2.md](docs/SITL_ACCEPTANCE_PHASE2.md)
- [AGENTS.md](AGENTS.md) — architecture rules and contribution constraints
- [skills/mavctl-flight/SKILL.md](skills/mavctl-flight/SKILL.md) — agent-facing flight guidance

## License

[MIT](LICENSE) — Copyright (c) 2026 LeaderOnePro.
