mavctl 0.2.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.
Files changed (46) hide show
  1. mavctl-0.2.0/.github/workflows/publish.yml +78 -0
  2. mavctl-0.2.0/.gitignore +29 -0
  3. mavctl-0.2.0/AGENTS.md +61 -0
  4. mavctl-0.2.0/CLAUDE.md +1 -0
  5. mavctl-0.2.0/LICENSE +21 -0
  6. mavctl-0.2.0/PKG-INFO +225 -0
  7. mavctl-0.2.0/README.md +196 -0
  8. mavctl-0.2.0/docs/PUBLISHING.md +144 -0
  9. mavctl-0.2.0/docs/SITL_ACCEPTANCE.md +148 -0
  10. mavctl-0.2.0/docs/SITL_ACCEPTANCE_PHASE2.md +324 -0
  11. mavctl-0.2.0/pyproject.toml +86 -0
  12. mavctl-0.2.0/skills/mavctl-flight/SKILL.md +79 -0
  13. mavctl-0.2.0/skills/mavctl-flight/references/safety.md +110 -0
  14. mavctl-0.2.0/skills/mavctl-flight/references/troubleshooting.md +143 -0
  15. mavctl-0.2.0/skills/mavctl-flight/references/workflows.md +155 -0
  16. mavctl-0.2.0/src/mavctl/__init__.py +3 -0
  17. mavctl-0.2.0/src/mavctl/adapter/__init__.py +28 -0
  18. mavctl-0.2.0/src/mavctl/adapter/base.py +86 -0
  19. mavctl-0.2.0/src/mavctl/adapter/pymavlink_adapter.py +584 -0
  20. mavctl-0.2.0/src/mavctl/cli/__init__.py +10 -0
  21. mavctl-0.2.0/src/mavctl/cli/app.py +406 -0
  22. mavctl-0.2.0/src/mavctl/cli/render.py +56 -0
  23. mavctl-0.2.0/src/mavctl/daemon/__init__.py +19 -0
  24. mavctl-0.2.0/src/mavctl/daemon/__main__.py +41 -0
  25. mavctl-0.2.0/src/mavctl/daemon/client.py +80 -0
  26. mavctl-0.2.0/src/mavctl/daemon/guards.py +447 -0
  27. mavctl-0.2.0/src/mavctl/daemon/process.py +166 -0
  28. mavctl-0.2.0/src/mavctl/daemon/server.py +496 -0
  29. mavctl-0.2.0/src/mavctl/daemon/wire.py +30 -0
  30. mavctl-0.2.0/src/mavctl/models/__init__.py +33 -0
  31. mavctl-0.2.0/src/mavctl/models/commands.py +69 -0
  32. mavctl-0.2.0/src/mavctl/models/protocol.py +62 -0
  33. mavctl-0.2.0/src/mavctl/models/state.py +53 -0
  34. mavctl-0.2.0/src/mavctl/models/telemetry.py +41 -0
  35. mavctl-0.2.0/src/mavctl/paths.py +39 -0
  36. mavctl-0.2.0/tests/__init__.py +1 -0
  37. mavctl-0.2.0/tests/conftest.py +19 -0
  38. mavctl-0.2.0/tests/fakes.py +85 -0
  39. mavctl-0.2.0/tests/test_adapter.py +825 -0
  40. mavctl-0.2.0/tests/test_cli.py +240 -0
  41. mavctl-0.2.0/tests/test_guards.py +410 -0
  42. mavctl-0.2.0/tests/test_project_docs.py +222 -0
  43. mavctl-0.2.0/tests/test_server.py +507 -0
  44. mavctl-0.2.0/tests/test_sitl.py +126 -0
  45. mavctl-0.2.0/tests/test_skill_docs.py +222 -0
  46. mavctl-0.2.0/uv.lock +944 -0
@@ -0,0 +1,78 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishing is restricted to formal PEP 440 release tags (vX.Y.Z).
4
+ # Milestone tags such as v0.2.0-phase2 are NEVER published: the broad "v*"
5
+ # trigger exists only so wrong tags fail loudly instead of silently doing
6
+ # nothing; the first step enforces the strict vX.Y.Z pattern.
7
+ #
8
+ # ONE-TIME MANUAL SETUP (required before the first real tag push):
9
+ # 1. On pypi.org, register a Trusted Publisher for this project:
10
+ # PyPI project name : mavctl
11
+ # Owner : LeaderOnePro
12
+ # Repository : mavctl
13
+ # Workflow filename : publish.yml
14
+ # Environment : leave empty (no environment is used here);
15
+ # if you ever add `environment:` below, the name
16
+ # on PyPI must match exactly.
17
+ # 2. Rehearse on TestPyPI first and validate dist/ locally — see
18
+ # docs/PUBLISHING.md before cutting the first real vX.Y.Z tag.
19
+ #
20
+ # This workflow uses OIDC Trusted Publishing only; it never requires or reads
21
+ # a PyPI token secret.
22
+
23
+ on:
24
+ push:
25
+ tags:
26
+ - "v*"
27
+
28
+ permissions:
29
+ contents: read
30
+
31
+ jobs:
32
+ publish:
33
+ runs-on: ubuntu-latest
34
+ permissions:
35
+ id-token: write # OIDC identity for PyPI Trusted Publishing
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+
39
+ - name: Enforce PEP 440 release tag (vX.Y.Z)
40
+ env:
41
+ RELEASE_TAG_PATTERN: '^v[0-9]+\.[0-9]+\.[0-9]+$'
42
+ run: |
43
+ if [[ ! "${GITHUB_REF_NAME}" =~ ${RELEASE_TAG_PATTERN} ]]; then
44
+ echo "::error::'${GITHUB_REF_NAME}' is not a formal release tag." \
45
+ "Only vX.Y.Z (e.g. v0.2.0) publishes to PyPI." \
46
+ "Milestone tags like v0.2.0-phase2 are never published."
47
+ exit 1
48
+ fi
49
+
50
+ - name: Install uv
51
+ uses: astral-sh/setup-uv@v5
52
+
53
+ - name: Install dependencies
54
+ run: uv sync
55
+
56
+ - name: Lint (ruff)
57
+ run: uv run ruff check .
58
+
59
+ - name: Type-check (mypy)
60
+ run: uv run mypy .
61
+
62
+ - name: Test (non-SITL)
63
+ run: uv run pytest -m "not sitl"
64
+
65
+ - name: Build distributions
66
+ run: uv build
67
+
68
+ - name: Verify tag matches package version
69
+ run: |
70
+ expected="v$(grep -m1 '^version' pyproject.toml | cut -d '"' -f2)"
71
+ if [[ "${GITHUB_REF_NAME}" != "${expected}" ]]; then
72
+ echo "::error::Tag ${GITHUB_REF_NAME} != package version ${expected}." \
73
+ "Land a release commit setting version = \"${GITHUB_REF_NAME#v}\" first."
74
+ exit 1
75
+ fi
76
+
77
+ - name: Publish to PyPI
78
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # Virtual envs / uv
11
+ .venv/
12
+ venv/
13
+ uv.lock.bak
14
+
15
+ # Tooling caches
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ .pytest_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # mavctl runtime state
23
+ .mavctl/
24
+
25
+ # OS / editor
26
+ .DS_Store
27
+ *.swp
28
+ .idea/
29
+ .vscode/
mavctl-0.2.0/AGENTS.md ADDED
@@ -0,0 +1,61 @@
1
+ # mavctl
2
+
3
+ Headless ground control station CLI for MAVLink vehicles (ArduPilot-first),
4
+ designed to be driven by AI coding agents (Claude Code, Codex, etc.) as well as humans.
5
+
6
+ ## 架构(不可违背的核心决策)
7
+
8
+ 三层架构,依赖方向严格单向:CLI → Daemon → Adapter
9
+
10
+ 1. **CLI 层**(`mavctl/cli/`):Typer 实现。薄壳,只做参数解析、
11
+ 调用 daemon、格式化输出。不含任何业务逻辑。
12
+ 2. **Daemon 层**(`mavctl/daemon/`):常驻进程,维持 MAVLink 连接、
13
+ 缓存遥测状态、执行安全护栏检查。通过 Unix domain socket
14
+ (JSON-RPC 风格)与 CLI 通信。
15
+ 3. **Adapter 层**(`mavctl/adapter/`):pymavlink 的唯一使用场所。
16
+ pymavlink 的 import 不得出现在其他任何层。
17
+
18
+ ## 技术栈
19
+
20
+ - Python >= 3.10,包管理用 uv,构建配置 pyproject.toml
21
+ - CLI: typer | 数据模型: pydantic v2 | MAVLink: pymavlink
22
+ - 测试: pytest + pytest-asyncio | Lint: ruff | 类型: mypy(strict)
23
+ - Daemon 内部用 asyncio;pymavlink 的阻塞调用包在 executor 里
24
+
25
+ ## 铁律
26
+
27
+ - **所有命令支持 `--json`**:输出结构化 JSON 到 stdout;人类可读格式为默认
28
+ - **退出码语义**:0=成功 1=通用错误 2=参数错误 3=daemon 未运行
29
+ 4=飞控未连接 5=安全护栏拒绝 6=飞控 NACK/超时
30
+ - **安全护栏**:arm / takeoff / mode / rtl 等改变载具状态的命令
31
+ 必须要求 `--confirm` 标志,否则拒绝执行(退出码 5)并说明原因
32
+ - **危险命令支持 `--dry-run`**
33
+ - 错误信息输出到 stderr,JSON 模式下为 `{"error": {...}}`
34
+ - 每个公共函数写类型注解;不写无意义的注释
35
+
36
+ ## 设计原则(Phase 2 起贯彻,与铁律同等约束力)
37
+
38
+ - **异步操作语义**:所有会改变载具状态且需要时间完成的命令
39
+ (takeoff / land / rtl / mode 等)必须支持两种模式:
40
+ - 默认:发出指令并确认飞控 ACK 后立即返回
41
+ - `--wait`:阻塞直到目标状态达成(如到达目标高度、完成降落),
42
+ 配套 `--timeout <秒>`(默认 60),超时返回退出码 6
43
+ - **status 输出自描述**:`mavctl status` 的输出必须让 agent 单次查询
44
+ 就能重建对载具的完整认知,至少包含:connected、armed、mode、
45
+ system_status、relative_alt、landed_state(若可用)、battery、
46
+ gps_fix、home_position(若已设置)
47
+ - **幂等友好**:重复执行已达成的状态变更应返回成功而非报错
48
+ (如对已 armed 的载具执行 arm,返回 exit 0 并在输出中标注
49
+ "already armed"),确保 agent 重试时不被绊倒
50
+
51
+ ## 测试要求
52
+
53
+ - Adapter 层:用 mock MAVLink 连接做单元测试
54
+ - 集成测试:标记 `@pytest.mark.sitl`,需要本地 ArduPilot SITL
55
+ (默认 udp:127.0.0.1:14550),CI 中可跳过
56
+ - 每次修改后运行:`ruff check . && mypy . && pytest -m "not sitl"`
57
+
58
+ ## 常用命令
59
+
60
+ - 安装依赖:`uv sync`
61
+ - 启动 SITL(若已安装):`sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550`
mavctl-0.2.0/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
mavctl-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LeaderOnePro
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.
mavctl-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.5
2
+ Name: mavctl
3
+ Version: 0.2.0
4
+ Summary: Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles
5
+ Project-URL: Homepage, https://github.com/LeaderOnePro/mavctl
6
+ Project-URL: Repository, https://github.com/LeaderOnePro/mavctl
7
+ Project-URL: Issues, https://github.com/LeaderOnePro/mavctl/issues
8
+ Project-URL: Documentation, https://github.com/LeaderOnePro/mavctl/tree/main/docs
9
+ Author: LeaderOnePro
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: MacOS :: MacOS X
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: System :: Networking
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: pydantic>=2.6
26
+ Requires-Dist: pymavlink>=2.4.40
27
+ Requires-Dist: typer>=0.12
28
+ Description-Content-Type: text/markdown
29
+
30
+ # mavctl
31
+
32
+ > Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles.
33
+
34
+ mavctl is **ArduPilot-first** and built to be driven by both humans on a
35
+ terminal and AI coding agents (Claude Code, Codex, OpenClaw, …). A resident
36
+ daemon keeps the MAVLink link alive and caches vehicle state; every CLI call
37
+ is one short, structured request to that daemon.
38
+
39
+ **Status:** developed and verified against ArduPilot SITL. It has not been
40
+ proven across the breadth of real MAVLink vehicles and is **not** presented as
41
+ ready for production flight on a real aircraft.
42
+
43
+ ## Why mavctl
44
+
45
+ GUI ground stations such as Mission Planner or QGroundControl are excellent
46
+ for a human at the controls — and a poor interface for a shell script or an
47
+ LLM agent: clickable UIs, no stable exit codes, no machine-readable output.
48
+
49
+ mavctl takes the other side of that trade:
50
+
51
+ - the daemon owns the MAVLink connection and continuously caches telemetry,
52
+ so each command is quick and stateless;
53
+ - every command prints human-readable output by default and structured JSON
54
+ with `--json`;
55
+ - failures carry explicit exit codes (3 daemon down, 4 link lost, 5 guard
56
+ rejection, 6 vehicle NACK / timeout) instead of stack traces;
57
+ - dangerous operations pass safety guards before anything reaches the vehicle;
58
+ - mavctl embeds no LLM — it is designed to be *called* by agents such as
59
+ Claude Code, Codex or OpenClaw, or by plain bash.
60
+
61
+ ## Current capabilities
62
+
63
+ Implemented commands — this is the complete list:
64
+
65
+ ```bash
66
+ mavctl daemon start|stop|status
67
+ mavctl status
68
+ mavctl telemetry
69
+ mavctl arm
70
+ mavctl disarm
71
+ mavctl mode <MODE>
72
+ mavctl takeoff --alt <metres>
73
+ mavctl land
74
+ mavctl rtl
75
+ ```
76
+
77
+ Cross-cutting behaviour:
78
+
79
+ | Flag / behaviour | Meaning |
80
+ | ---------------- | ------- |
81
+ | `--json` | structured output on stdout; errors as `{"error": {...}}` on stderr |
82
+ | `--confirm` | required on every state-changing command; without it exit code 5 |
83
+ | `--dry-run` | run the exact same guards, never reach the vehicle |
84
+ | `--wait --timeout <s>` | block until the target state is reached (default 60 s) |
85
+ | idempotent repeats | re-applying an achieved change succeeds ("already armed") |
86
+ | transaction safety | ACK/NACK handling, serialized commands, link-loss abort |
87
+
88
+ Not implemented — current scope only, not a roadmap promise:
89
+
90
+ ```text
91
+ Mission upload/download/start
92
+ Parameters
93
+ Geofence
94
+ Log download / analysis
95
+ Firmware flashing
96
+ Multi-vehicle orchestration
97
+ ```
98
+
99
+ ## Quickstart with ArduPilot SITL
100
+
101
+ Requires Python >= 3.10 and [uv](https://docs.astral.sh/uv/). Always bring up
102
+ SITL first; do not point an agent-driven workflow at a real vehicle.
103
+
104
+ Terminal 1 — start ArduPilot SITL:
105
+
106
+ ```bash
107
+ sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550
108
+ ```
109
+
110
+ Terminal 2 — install from source and connect:
111
+
112
+ ```bash
113
+ uv sync
114
+ uv run mavctl daemon start --connect udp:127.0.0.1:14550
115
+ uv run mavctl status --json
116
+ ```
117
+
118
+ Safe takeoff to 10 m and return to launch:
119
+
120
+ ```bash
121
+ uv run mavctl mode GUIDED --confirm --wait
122
+ uv run mavctl arm --confirm
123
+ # Poll status --json until armed=true (the arm ACK can beat the heartbeat)
124
+ uv run mavctl takeoff --alt 10 --confirm --wait --timeout 45
125
+ uv run mavctl rtl --confirm --wait --timeout 120
126
+ uv run mavctl daemon stop
127
+ ```
128
+
129
+ Safety notes — read before pointing mavctl at anything that flies:
130
+
131
+ - Validate every workflow in SITL first; treat real-aircraft use as its own
132
+ review process.
133
+ - After `arm`, poll `status --json` until `armed=true` before takeoff: the
134
+ COMMAND_ACK can arrive about one heartbeat before reported state catches up.
135
+ - End flights with `rtl` / `land`, not `disarm`. Ordinary `disarm` requires
136
+ provable ground contact (`ground_state_unknown` otherwise).
137
+ - `disarm --force` is an emergency motor stop only — in flight it can cause a
138
+ crash.
139
+ - There is no `arm --force` anywhere in mavctl; pre-arm checks cannot be
140
+ bypassed.
141
+
142
+ ## Installation
143
+
144
+ ### From PyPI
145
+
146
+ mavctl's first production release (`0.2.0`) is being prepared. After the
147
+ PyPI release is published, install with:
148
+
149
+ ```bash
150
+ uv tool install mavctl
151
+ uvx mavctl --help
152
+ pipx install mavctl
153
+ ```
154
+
155
+ Until that release is published these commands have nothing to fetch from
156
+ production PyPI; TestPyPI rehearsal artifacts are not production releases.
157
+ Release status and the publishing runbook live in
158
+ [docs/PUBLISHING.md](docs/PUBLISHING.md).
159
+
160
+ ### From source
161
+
162
+ For development from source, use `uv sync` and `uv run mavctl …`:
163
+
164
+ ```bash
165
+ git clone https://github.com/LeaderOnePro/mavctl.git
166
+ cd mavctl
167
+ uv sync
168
+ uv run mavctl --help
169
+ ```
170
+
171
+ ## Agent Skill
172
+
173
+ The repository ships a portable agent Skill under `skills/mavctl-flight/`
174
+ (entrypoint plus workflows / safety / troubleshooting references). It is a
175
+ source asset of this repo, not an installed package.
176
+
177
+ To use it with an agent runtime, install or symlink this directory according
178
+ to that runtime's current Skill discovery convention.
179
+
180
+ Example for Claude Code, project-local to this repository:
181
+
182
+ ```bash
183
+ mkdir -p .claude/skills
184
+ ln -s ../../skills/mavctl-flight .claude/skills/mavctl-flight
185
+ ```
186
+
187
+ That is one concrete example, not a universal convention — runtimes differ.
188
+
189
+ ## Safety model
190
+
191
+ Short version; full details in
192
+ [skills/mavctl-flight/references/safety.md](skills/mavctl-flight/references/safety.md):
193
+
194
+ - the daemon owns the vehicle link; CLI calls are short transactions;
195
+ - state-changing commands require `--confirm`; `--dry-run` previews decisions;
196
+ - exit 4 = no live vehicle state (link lost / heartbeat expired), including
197
+ mid-`--wait` loss (`link_lost_during_wait`);
198
+ - exit 5 = guard rejection with structured `reason` + `hint`;
199
+ - exit 6 = vehicle NACK / ACK timeout / wait timeout;
200
+ - force-arm does not exist at any layer (CLI option, RPC field, adapter verb);
201
+ - ordinary `disarm` needs positive ground evidence, else `ground_state_unknown`
202
+ (exit 5);
203
+ - after link loss, `status` reflects stale cache: `armed` renders unknown/n/a,
204
+ never silently disarmed.
205
+
206
+ ## Development
207
+
208
+ ```bash
209
+ uv sync
210
+ uv run ruff check .
211
+ uv run mypy .
212
+ uv run pytest -m "not sitl"
213
+ uv run pytest -m sitl # requires a running ArduPilot SITL
214
+ ```
215
+
216
+ Further reading:
217
+
218
+ - [docs/SITL_ACCEPTANCE.md](docs/SITL_ACCEPTANCE.md)
219
+ - [docs/SITL_ACCEPTANCE_PHASE2.md](docs/SITL_ACCEPTANCE_PHASE2.md)
220
+ - [AGENTS.md](AGENTS.md) — architecture rules and contribution constraints
221
+ - [skills/mavctl-flight/SKILL.md](skills/mavctl-flight/SKILL.md) — agent-facing flight guidance
222
+
223
+ ## License
224
+
225
+ [MIT](LICENSE) — Copyright (c) 2026 LeaderOnePro.
mavctl-0.2.0/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # mavctl
2
+
3
+ > Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles.
4
+
5
+ mavctl is **ArduPilot-first** and built to be driven by both humans on a
6
+ terminal and AI coding agents (Claude Code, Codex, OpenClaw, …). A resident
7
+ daemon keeps the MAVLink link alive and caches vehicle state; every CLI call
8
+ is one short, structured request to that daemon.
9
+
10
+ **Status:** developed and verified against ArduPilot SITL. It has not been
11
+ proven across the breadth of real MAVLink vehicles and is **not** presented as
12
+ ready for production flight on a real aircraft.
13
+
14
+ ## Why mavctl
15
+
16
+ GUI ground stations such as Mission Planner or QGroundControl are excellent
17
+ for a human at the controls — and a poor interface for a shell script or an
18
+ LLM agent: clickable UIs, no stable exit codes, no machine-readable output.
19
+
20
+ mavctl takes the other side of that trade:
21
+
22
+ - the daemon owns the MAVLink connection and continuously caches telemetry,
23
+ so each command is quick and stateless;
24
+ - every command prints human-readable output by default and structured JSON
25
+ with `--json`;
26
+ - failures carry explicit exit codes (3 daemon down, 4 link lost, 5 guard
27
+ rejection, 6 vehicle NACK / timeout) instead of stack traces;
28
+ - dangerous operations pass safety guards before anything reaches the vehicle;
29
+ - mavctl embeds no LLM — it is designed to be *called* by agents such as
30
+ Claude Code, Codex or OpenClaw, or by plain bash.
31
+
32
+ ## Current capabilities
33
+
34
+ Implemented commands — this is the complete list:
35
+
36
+ ```bash
37
+ mavctl daemon start|stop|status
38
+ mavctl status
39
+ mavctl telemetry
40
+ mavctl arm
41
+ mavctl disarm
42
+ mavctl mode <MODE>
43
+ mavctl takeoff --alt <metres>
44
+ mavctl land
45
+ mavctl rtl
46
+ ```
47
+
48
+ Cross-cutting behaviour:
49
+
50
+ | Flag / behaviour | Meaning |
51
+ | ---------------- | ------- |
52
+ | `--json` | structured output on stdout; errors as `{"error": {...}}` on stderr |
53
+ | `--confirm` | required on every state-changing command; without it exit code 5 |
54
+ | `--dry-run` | run the exact same guards, never reach the vehicle |
55
+ | `--wait --timeout <s>` | block until the target state is reached (default 60 s) |
56
+ | idempotent repeats | re-applying an achieved change succeeds ("already armed") |
57
+ | transaction safety | ACK/NACK handling, serialized commands, link-loss abort |
58
+
59
+ Not implemented — current scope only, not a roadmap promise:
60
+
61
+ ```text
62
+ Mission upload/download/start
63
+ Parameters
64
+ Geofence
65
+ Log download / analysis
66
+ Firmware flashing
67
+ Multi-vehicle orchestration
68
+ ```
69
+
70
+ ## Quickstart with ArduPilot SITL
71
+
72
+ Requires Python >= 3.10 and [uv](https://docs.astral.sh/uv/). Always bring up
73
+ SITL first; do not point an agent-driven workflow at a real vehicle.
74
+
75
+ Terminal 1 — start ArduPilot SITL:
76
+
77
+ ```bash
78
+ sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550
79
+ ```
80
+
81
+ Terminal 2 — install from source and connect:
82
+
83
+ ```bash
84
+ uv sync
85
+ uv run mavctl daemon start --connect udp:127.0.0.1:14550
86
+ uv run mavctl status --json
87
+ ```
88
+
89
+ Safe takeoff to 10 m and return to launch:
90
+
91
+ ```bash
92
+ uv run mavctl mode GUIDED --confirm --wait
93
+ uv run mavctl arm --confirm
94
+ # Poll status --json until armed=true (the arm ACK can beat the heartbeat)
95
+ uv run mavctl takeoff --alt 10 --confirm --wait --timeout 45
96
+ uv run mavctl rtl --confirm --wait --timeout 120
97
+ uv run mavctl daemon stop
98
+ ```
99
+
100
+ Safety notes — read before pointing mavctl at anything that flies:
101
+
102
+ - Validate every workflow in SITL first; treat real-aircraft use as its own
103
+ review process.
104
+ - After `arm`, poll `status --json` until `armed=true` before takeoff: the
105
+ COMMAND_ACK can arrive about one heartbeat before reported state catches up.
106
+ - End flights with `rtl` / `land`, not `disarm`. Ordinary `disarm` requires
107
+ provable ground contact (`ground_state_unknown` otherwise).
108
+ - `disarm --force` is an emergency motor stop only — in flight it can cause a
109
+ crash.
110
+ - There is no `arm --force` anywhere in mavctl; pre-arm checks cannot be
111
+ bypassed.
112
+
113
+ ## Installation
114
+
115
+ ### From PyPI
116
+
117
+ mavctl's first production release (`0.2.0`) is being prepared. After the
118
+ PyPI release is published, install with:
119
+
120
+ ```bash
121
+ uv tool install mavctl
122
+ uvx mavctl --help
123
+ pipx install mavctl
124
+ ```
125
+
126
+ Until that release is published these commands have nothing to fetch from
127
+ production PyPI; TestPyPI rehearsal artifacts are not production releases.
128
+ Release status and the publishing runbook live in
129
+ [docs/PUBLISHING.md](docs/PUBLISHING.md).
130
+
131
+ ### From source
132
+
133
+ For development from source, use `uv sync` and `uv run mavctl …`:
134
+
135
+ ```bash
136
+ git clone https://github.com/LeaderOnePro/mavctl.git
137
+ cd mavctl
138
+ uv sync
139
+ uv run mavctl --help
140
+ ```
141
+
142
+ ## Agent Skill
143
+
144
+ The repository ships a portable agent Skill under `skills/mavctl-flight/`
145
+ (entrypoint plus workflows / safety / troubleshooting references). It is a
146
+ source asset of this repo, not an installed package.
147
+
148
+ To use it with an agent runtime, install or symlink this directory according
149
+ to that runtime's current Skill discovery convention.
150
+
151
+ Example for Claude Code, project-local to this repository:
152
+
153
+ ```bash
154
+ mkdir -p .claude/skills
155
+ ln -s ../../skills/mavctl-flight .claude/skills/mavctl-flight
156
+ ```
157
+
158
+ That is one concrete example, not a universal convention — runtimes differ.
159
+
160
+ ## Safety model
161
+
162
+ Short version; full details in
163
+ [skills/mavctl-flight/references/safety.md](skills/mavctl-flight/references/safety.md):
164
+
165
+ - the daemon owns the vehicle link; CLI calls are short transactions;
166
+ - state-changing commands require `--confirm`; `--dry-run` previews decisions;
167
+ - exit 4 = no live vehicle state (link lost / heartbeat expired), including
168
+ mid-`--wait` loss (`link_lost_during_wait`);
169
+ - exit 5 = guard rejection with structured `reason` + `hint`;
170
+ - exit 6 = vehicle NACK / ACK timeout / wait timeout;
171
+ - force-arm does not exist at any layer (CLI option, RPC field, adapter verb);
172
+ - ordinary `disarm` needs positive ground evidence, else `ground_state_unknown`
173
+ (exit 5);
174
+ - after link loss, `status` reflects stale cache: `armed` renders unknown/n/a,
175
+ never silently disarmed.
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ uv sync
181
+ uv run ruff check .
182
+ uv run mypy .
183
+ uv run pytest -m "not sitl"
184
+ uv run pytest -m sitl # requires a running ArduPilot SITL
185
+ ```
186
+
187
+ Further reading:
188
+
189
+ - [docs/SITL_ACCEPTANCE.md](docs/SITL_ACCEPTANCE.md)
190
+ - [docs/SITL_ACCEPTANCE_PHASE2.md](docs/SITL_ACCEPTANCE_PHASE2.md)
191
+ - [AGENTS.md](AGENTS.md) — architecture rules and contribution constraints
192
+ - [skills/mavctl-flight/SKILL.md](skills/mavctl-flight/SKILL.md) — agent-facing flight guidance
193
+
194
+ ## License
195
+
196
+ [MIT](LICENSE) — Copyright (c) 2026 LeaderOnePro.