lockwire 0.2.0__tar.gz → 0.2.3__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 (80) hide show
  1. {lockwire-0.2.0 → lockwire-0.2.3}/PKG-INFO +139 -29
  2. {lockwire-0.2.0 → lockwire-0.2.3}/README.md +137 -27
  3. {lockwire-0.2.0 → lockwire-0.2.3}/pyproject.toml +2 -2
  4. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/__init__.py +1 -1
  5. lockwire-0.2.3/src/lockwire/_filelock.py +38 -0
  6. lockwire-0.2.3/src/lockwire/advice.py +215 -0
  7. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/api_payload.py +59 -0
  8. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/bench.py +3 -1
  9. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/catalog_seed.yaml +233 -6
  10. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/checks.py +106 -13
  11. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/cli.py +43 -12
  12. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/copy.py +58 -10
  13. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/describe.py +158 -46
  14. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/edit.py +7 -4
  15. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/emit_ts.py +5 -1
  16. lockwire-0.2.3/src/lockwire/examples/arm/LICENSE +201 -0
  17. lockwire-0.2.3/src/lockwire/examples/arm/NOTICE +5 -0
  18. lockwire-0.2.3/src/lockwire/examples/arm/contract.yaml +299 -0
  19. lockwire-0.2.3/src/lockwire/examples/arm/plan.yaml +9 -0
  20. lockwire-0.2.3/src/lockwire/examples/arm/scene.glb +0 -0
  21. lockwire-0.2.3/src/lockwire/examples/arm/starter.json +7 -0
  22. lockwire-0.2.3/src/lockwire/examples/drone/contract.yaml +617 -0
  23. lockwire-0.2.3/src/lockwire/examples/drone/plan.yaml +10 -0
  24. lockwire-0.2.3/src/lockwire/examples/drone/scene.glb +0 -0
  25. lockwire-0.2.3/src/lockwire/examples/drone/starter.json +7 -0
  26. lockwire-0.2.3/src/lockwire/examples/humanoid/NOTICE +26 -0
  27. lockwire-0.2.3/src/lockwire/examples/humanoid/contract.yaml +1593 -0
  28. lockwire-0.2.3/src/lockwire/examples/humanoid/plan.yaml +10 -0
  29. lockwire-0.2.3/src/lockwire/examples/humanoid/scene.glb +0 -0
  30. lockwire-0.2.3/src/lockwire/examples/humanoid/starter.json +1 -0
  31. lockwire-0.2.3/src/lockwire/examples/quadruped/NOTICE +32 -0
  32. lockwire-0.2.3/src/lockwire/examples/quadruped/contract.yaml +476 -0
  33. lockwire-0.2.3/src/lockwire/examples/quadruped/plan.yaml +5 -0
  34. lockwire-0.2.3/src/lockwire/examples/quadruped/scene.glb +0 -0
  35. lockwire-0.2.3/src/lockwire/examples/quadruped/starter.json +7 -0
  36. lockwire-0.2.3/src/lockwire/examples/rover/NOTICE +12 -0
  37. lockwire-0.2.3/src/lockwire/examples/rover/contract.yaml +315 -0
  38. lockwire-0.2.3/src/lockwire/examples/rover/plan.yaml +9 -0
  39. lockwire-0.2.3/src/lockwire/examples/rover/scene.glb +0 -0
  40. lockwire-0.2.3/src/lockwire/examples/rover/starter.json +1 -0
  41. lockwire-0.2.3/src/lockwire/examples/servo-bench.yaml +113 -0
  42. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/gitref.py +1 -3
  43. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/page.py +147 -42
  44. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/plan.py +33 -8
  45. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/plan_payload.py +3 -0
  46. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/report.py +58 -4
  47. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/report_html.py +17 -0
  48. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/review_draft.py +1 -0
  49. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scan.py +57 -22
  50. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene_import.py +2 -6
  51. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene_urdf.py +12 -10
  52. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene_view.py +21 -6
  53. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/schema.py +87 -42
  54. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/serve.py +70 -11
  55. lockwire-0.2.3/src/lockwire/start_operation.py +170 -0
  56. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/urdf.py +8 -0
  57. lockwire-0.2.0/src/lockwire/advice.py +0 -119
  58. lockwire-0.2.0/src/lockwire/start_operation.py +0 -92
  59. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/__main__.py +0 -0
  60. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/api.py +0 -0
  61. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/blame.py +0 -0
  62. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/catalog.py +0 -0
  63. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/cli_line.py +0 -0
  64. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/constraints.py +0 -0
  65. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/export_mhs.py +0 -0
  66. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/factdiff.py +0 -0
  67. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/forces.py +0 -0
  68. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/hw.py +0 -0
  69. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mcp_server.py +0 -0
  70. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mechanism.py +0 -0
  71. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mesh_dae.py +0 -0
  72. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/mesh_simple.py +0 -0
  73. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/plugins.py +0 -0
  74. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/report_json.py +0 -0
  75. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/scene.py +0 -0
  76. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/sdf.py +0 -0
  77. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/statics.py +0 -0
  78. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/units.py +0 -0
  79. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/vcs.py +0 -0
  80. {lockwire-0.2.0 → lockwire-0.2.3}/src/lockwire/whatif.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockwire
3
- Version: 0.2.0
4
- Summary: The physical-assumption contract for robots: physics lockfile + preflight gate
3
+ Version: 0.2.3
4
+ Summary: The physical-assumption contract for robots: checks that a robot's declared parts (power, weight, bus, torque) add up
5
5
  Keywords: robotics,urdf,ros,preflight,lockfile,ci,hardware,drone
6
6
  Author: Shivansh
7
7
  License-Expression: Apache-2.0
@@ -30,45 +30,156 @@ Provides-Extra: mcp
30
30
  Provides-Extra: serve
31
31
  Description-Content-Type: text/markdown
32
32
 
33
- # lockwire
33
+ <p align="center">
34
+ <picture>
35
+ <source media="(prefers-color-scheme: dark)" srcset="docs/img/logo-dark.svg">
36
+ <img src="docs/img/logo-light.svg" alt="lockwire" height="64">
37
+ </picture>
38
+ </p>
34
39
 
35
40
  **Find out your robot won't work before you buy the parts.**
36
41
 
42
+ ![The drone in 3D with its power parts in red, and a NO-GO finding that the 5 V rail is overloaded](docs/img/hero.png)
43
+
37
44
  Every robot is a stack of numbers from a dozen datasheets: what each part weighs, what each power rail can supply, how much the bus can carry, how much torque each joint gets. Lockwire keeps those numbers in one file and tells you the moment they stop adding up. Use it while you're still planning a build, then keep it in CI so a part swap can't quietly break it later.
38
45
 
39
- Prefer clicking to typing? The free desktop app does the same visually: Open, Design, Declare, Plan, Check.
46
+ ## Try it in 30 seconds
47
+
48
+ Needs Python 3.12 or newer (the Python that ships with macOS is older).
49
+
50
+ ```bash
51
+ pipx install lockwire
52
+ lockwire start --example servo-bench
53
+ lockwire check
54
+ ```
55
+
56
+ ```console
57
+ VIOLATION budgets/bud.power.servo-5v — observed 32.10 W, expected <= 15.00 W (Δ +17.10 W (+114%))
58
+ dependent: dev.pi5, dev.servo1, dev.servo2, dev.servo3, dev.servo4
59
+ ...
60
+ NO-GO — 1 violation(s), 0 warning(s)
61
+ ```
62
+
63
+ A Raspberry Pi 5 and four servos on one 3 A regulator: caught before you ordered either.
64
+
65
+ ## Try it on your robot
40
66
 
41
- ## Plan a robot from scratch
67
+ Point it at your URDF and see what it flags:
68
+
69
+ ```bash
70
+ lockwire start path/to/robot.urdf
71
+ lockwire check
72
+ ```
42
73
 
43
- Start from a URDF (one line is enough), add the parts you're thinking of buying, and check:
74
+ No URDF? Start from the example and swap in your parts:
44
75
 
45
76
  ```bash
46
- pip install "git+https://github.com/Shivansh2703/lockwire@v0.2.0" # not on PyPI
47
- echo '<robot name="rover"><link name="base_link"/></robot>' > robot.urdf
48
- lockwire start robot.urdf
49
- lockwire add rail lockwire.yaml --id rail.5v --voltage "5 V" --capacity "15 W" --source "UBEC datasheet: 5 V 3 A" --witness Sam
50
- lockwire add device lockwire.yaml --id dev.pi5 --family fam.pi --model mod.pi5 --rail rail.5v --draw "12 W" --mass "46 g" --mount-frame base_link --owner you --source "Raspberry Pi 5 product brief" --witness Sam
51
- lockwire add device lockwire.yaml --id dev.servo --family fam.servo --model mod.mg996r --rail rail.5v --draw "6 W" --mass "55 g" --mount-frame base_link --owner you --source "MG996R datasheet" --witness Sam
52
- lockwire add budget lockwire.yaml --id bud.5v --kind rail_power --rail rail.5v --limit "15 W" --owner you --source "UBEC datasheet" --witness Sam
77
+ lockwire start --example servo-bench
78
+ lockwire add device lockwire.yaml --id dev.servo5 --family fam.servo --model mod.mg996r --rail rail.servo-5v --draw "6 W" --mass "55 g" --owner you --source "MG996R datasheet" --witness Sam
79
+ lockwire check
80
+ ```
81
+
82
+ See `lockwire add --help` for rails, budgets and the rest.
83
+
84
+ ## Download the app
85
+
86
+ The free desktop app does the same visually. Get it from the [latest release](https://github.com/Shivansh2703/lockwire/releases/latest).
87
+
88
+ ![Open the app, click the Drone starter, and Lockwire shows the drone in 3D with a NO-GO on its 5 V power rail](docs/img/try-example.gif)
89
+
90
+ - **macOS (Apple silicon):** unzip, open Lockwire. The first open shows a warning because the app is not notarized: open System Settings, Privacy & Security, scroll down and click "Open Anyway".
91
+ - **Linux:** `chmod +x Lockwire-*.AppImage`, then run it. Ubuntu 22.04 needs `sudo apt install libfuse2`; Ubuntu 24.04 needs `sudo apt install libfuse2t64`.
92
+ - **Windows app:** not yet.
93
+
94
+ ## Other ways to use it
95
+
96
+ **Command line.** Make a lockfile and check it from a terminal.
97
+
98
+ ```
99
+ pipx install lockwire
100
+ ```
101
+
102
+ ```
103
+ lockwire start --example drone
104
+ ```
105
+
106
+ Starters: `lockwire start --example <name>` with drone, rover, arm, quadruped or humanoid. Add your own: see [CONTRIBUTING](CONTRIBUTING.md).
107
+
108
+ ```
53
109
  lockwire check lockwire.yaml
54
110
  ```
55
111
 
56
- `--witness` is the person who read the number off the datasheet; use a real name, not "you".
112
+ **Desktop app.** Download it above, click a New robot starter, or open your own lockfile.
57
113
 
58
- ```console
59
- VIOLATION budgets/bud.5v — observed 18.00 W, expected <= 15.00 W (Δ +3.00 W (+20%))
60
- dependent: dev.pi5, dev.servo
61
- owner: you
62
- source: UBEC datasheet: 5 V 3 A
63
- action: shed load on rail 'rail.5v' or raise its capacity with provenance
64
- warning model/links/base_link/mass — observed zero or missing mass, expected a positive mass with units
65
- …
66
- scored 2: 0 add up, 1 do not · 0 not scored
114
+ ```
115
+ https://github.com/Shivansh2703/lockwire/releases/latest
116
+ ```
117
+
118
+ **GitHub Action.** Run the check on every push and keep an HTML report.
119
+
120
+ ```
121
+ - uses: actions/checkout@v4
122
+ - uses: Shivansh2703/lockwire@v0
123
+ with:
124
+ contract: lockwire.yaml
125
+ ```
126
+
127
+ **AI agents (MCP).** Let Claude Desktop, Claude Code or Cursor check your robot before it acts.
128
+
129
+ ```
130
+ pipx install "lockwire[mcp]"
131
+ ```
132
+
133
+ ```
134
+ claude mcp add lockwire -- lockwire mcp
135
+ ```
136
+
137
+ ```
138
+ {
139
+ "mcpServers": {
140
+ "lockwire": { "command": "lockwire", "args": ["mcp"] }
141
+ }
142
+ }
143
+ ```
144
+
145
+ Claude Code: the claude mcp add line. Claude Desktop: put the JSON in claude_desktop_config.json. Cursor: put the JSON in .cursor/mcp.json (or ~/.cursor/mcp.json). If the app cannot find lockwire, use the full path from which lockwire. Then ask: Check my robot's lockwire.yaml and explain the NO-GO.
146
+
147
+ **Agent plan and what-if.** Score an intended plan, or a proposed change, before anything executes.
148
+
149
+ ```
150
+ lockwire plan lockwire.yaml plan.yaml
151
+ ```
152
+
153
+ ```
154
+ lockwire what-if lockwire.yaml --change "devices/dev.onboard-computer/physical/mass=300 g" --source "my scale" --witness me
155
+ ```
156
+
157
+ plan.yaml holds steps, for example: steps: [{kind: rail_load, rail: rail.supply-5v, draw: {value: 10, unit: W}}]
158
+
159
+ **Python library.** Call the same check from your own script.
67
160
 
68
- NO-GO — 1 violation(s), 1 warning(s)
69
161
  ```
162
+ import lockwire.api as lw
163
+ print(lw.check("lockwire.yaml")["verdict"])
164
+ ```
165
+
166
+ **ROS, preflight and the local API.** The go/no-go gate for a powered-on robot, ROS and the local server.
167
+
168
+ Part of Lockwire Pro.
169
+
170
+ ### Features
70
171
 
71
- The check exits 1. A Pi 5 and one servo on a common 3 A regulator: caught before you ordered either. Swap in a 5 A regulator and it passes.
172
+ - **Check:** See which numbers do not add up. `lockwire check lockwire.yaml`
173
+ - **Declare a fact:** Set one value with a source and a witness. `lockwire declare lockwire.yaml devices/dev.power-module.pm02-v3/physical/mass "25 g" --source "my scale" --witness me`
174
+ - **Add a part:** Add a device, rail, bus or battery block. `lockwire add --help`
175
+ - **Design (place, move, rotate, reparent):** Put a part in the 3D model and move it. Switch on the Power, Data and Mechanical connection views to see them drawn on the robot, and use W, A, S, D to move the camera. `lockwire move lockwire.yaml dev.power-module.pm02-v3 --offset 0.01,0,0.02 --unit m --source "tape measure" --witness me`
176
+ - **Plan and what-if:** Score a plan or a change without editing the lockfile. `lockwire plan lockwire.yaml plan.yaml`
177
+ - **Report:** Write one HTML page to share. `lockwire report lockwire.yaml -o report.html --no-open`
178
+ - **Describe:** Write a reference sheet per part. `lockwire describe lockwire.yaml --out ref`
179
+ - **History (diff, log, blame):** See what changed, by whom, and why (needs git). `lockwire diff`
180
+ - **Scene:** Read part positions from a 3D model. `lockwire scene import --help`
181
+ - **Gearbox:** Add a gearbox with its ratio and joints. `lockwire gearbox --help`
182
+ - **Upgrade:** Write a new lockfile from an older one. `lockwire upgrade --help`
72
183
 
73
184
  ## A real-world example: the X500 drone
74
185
 
@@ -141,8 +252,8 @@ The `scored N: …` line above the verdict is where a script should look.
141
252
  contract: lockwire.yaml
142
253
  ```
143
254
 
144
- Inputs: `contract` (default `lockwire.yaml`), `version` (default latest), `python-version` (default 3.12).
145
- Or just `pip install "git+https://github.com/Shivansh2703/lockwire@v0.2.0" && lockwire check lockwire.yaml`.
255
+ Inputs: `contract` (default `lockwire.yaml`), `version` (pin a PyPI release; default installs the action's own code), `python-version` (default 3.12).
256
+ Or just `pipx install lockwire && lockwire check lockwire.yaml`.
146
257
 
147
258
  ## Pre-commit
148
259
 
@@ -179,8 +290,7 @@ No physics simulation, no machine learning, no network. The answer is the same t
179
290
  - **Free forever (this repo):** the lockfile, all deterministic checks including
180
291
  power/thermal/bus/link/torque budgets, edit / describe / plan / what-if, the MCP server, the
181
292
  HTML report, and the desktop planning app (Open, Design, Declare, Plan, Check).
182
- - **Lockwire Pro** adds the powered-on preflight gate (Test, Run), recording analysis
183
- (rosbag2 / ULog), and health.
293
+ - **Lockwire Pro** is the paid tier (powered-on preflight, recording analysis).
184
294
 
185
295
  ## Development
186
296
 
@@ -1,42 +1,153 @@
1
- # lockwire
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/img/logo-dark.svg">
4
+ <img src="docs/img/logo-light.svg" alt="lockwire" height="64">
5
+ </picture>
6
+ </p>
2
7
 
3
8
  **Find out your robot won't work before you buy the parts.**
4
9
 
10
+ ![The drone in 3D with its power parts in red, and a NO-GO finding that the 5 V rail is overloaded](docs/img/hero.png)
11
+
5
12
  Every robot is a stack of numbers from a dozen datasheets: what each part weighs, what each power rail can supply, how much the bus can carry, how much torque each joint gets. Lockwire keeps those numbers in one file and tells you the moment they stop adding up. Use it while you're still planning a build, then keep it in CI so a part swap can't quietly break it later.
6
13
 
7
- Prefer clicking to typing? The free desktop app does the same visually: Open, Design, Declare, Plan, Check.
14
+ ## Try it in 30 seconds
15
+
16
+ Needs Python 3.12 or newer (the Python that ships with macOS is older).
17
+
18
+ ```bash
19
+ pipx install lockwire
20
+ lockwire start --example servo-bench
21
+ lockwire check
22
+ ```
23
+
24
+ ```console
25
+ VIOLATION budgets/bud.power.servo-5v — observed 32.10 W, expected <= 15.00 W (Δ +17.10 W (+114%))
26
+ dependent: dev.pi5, dev.servo1, dev.servo2, dev.servo3, dev.servo4
27
+ ...
28
+ NO-GO — 1 violation(s), 0 warning(s)
29
+ ```
30
+
31
+ A Raspberry Pi 5 and four servos on one 3 A regulator: caught before you ordered either.
32
+
33
+ ## Try it on your robot
8
34
 
9
- ## Plan a robot from scratch
35
+ Point it at your URDF and see what it flags:
36
+
37
+ ```bash
38
+ lockwire start path/to/robot.urdf
39
+ lockwire check
40
+ ```
10
41
 
11
- Start from a URDF (one line is enough), add the parts you're thinking of buying, and check:
42
+ No URDF? Start from the example and swap in your parts:
12
43
 
13
44
  ```bash
14
- pip install "git+https://github.com/Shivansh2703/lockwire@v0.2.0" # not on PyPI
15
- echo '<robot name="rover"><link name="base_link"/></robot>' > robot.urdf
16
- lockwire start robot.urdf
17
- lockwire add rail lockwire.yaml --id rail.5v --voltage "5 V" --capacity "15 W" --source "UBEC datasheet: 5 V 3 A" --witness Sam
18
- lockwire add device lockwire.yaml --id dev.pi5 --family fam.pi --model mod.pi5 --rail rail.5v --draw "12 W" --mass "46 g" --mount-frame base_link --owner you --source "Raspberry Pi 5 product brief" --witness Sam
19
- lockwire add device lockwire.yaml --id dev.servo --family fam.servo --model mod.mg996r --rail rail.5v --draw "6 W" --mass "55 g" --mount-frame base_link --owner you --source "MG996R datasheet" --witness Sam
20
- lockwire add budget lockwire.yaml --id bud.5v --kind rail_power --rail rail.5v --limit "15 W" --owner you --source "UBEC datasheet" --witness Sam
45
+ lockwire start --example servo-bench
46
+ lockwire add device lockwire.yaml --id dev.servo5 --family fam.servo --model mod.mg996r --rail rail.servo-5v --draw "6 W" --mass "55 g" --owner you --source "MG996R datasheet" --witness Sam
47
+ lockwire check
48
+ ```
49
+
50
+ See `lockwire add --help` for rails, budgets and the rest.
51
+
52
+ ## Download the app
53
+
54
+ The free desktop app does the same visually. Get it from the [latest release](https://github.com/Shivansh2703/lockwire/releases/latest).
55
+
56
+ ![Open the app, click the Drone starter, and Lockwire shows the drone in 3D with a NO-GO on its 5 V power rail](docs/img/try-example.gif)
57
+
58
+ - **macOS (Apple silicon):** unzip, open Lockwire. The first open shows a warning because the app is not notarized: open System Settings, Privacy & Security, scroll down and click "Open Anyway".
59
+ - **Linux:** `chmod +x Lockwire-*.AppImage`, then run it. Ubuntu 22.04 needs `sudo apt install libfuse2`; Ubuntu 24.04 needs `sudo apt install libfuse2t64`.
60
+ - **Windows app:** not yet.
61
+
62
+ ## Other ways to use it
63
+
64
+ **Command line.** Make a lockfile and check it from a terminal.
65
+
66
+ ```
67
+ pipx install lockwire
68
+ ```
69
+
70
+ ```
71
+ lockwire start --example drone
72
+ ```
73
+
74
+ Starters: `lockwire start --example <name>` with drone, rover, arm, quadruped or humanoid. Add your own: see [CONTRIBUTING](CONTRIBUTING.md).
75
+
76
+ ```
21
77
  lockwire check lockwire.yaml
22
78
  ```
23
79
 
24
- `--witness` is the person who read the number off the datasheet; use a real name, not "you".
80
+ **Desktop app.** Download it above, click a New robot starter, or open your own lockfile.
25
81
 
26
- ```console
27
- VIOLATION budgets/bud.5v — observed 18.00 W, expected <= 15.00 W (Δ +3.00 W (+20%))
28
- dependent: dev.pi5, dev.servo
29
- owner: you
30
- source: UBEC datasheet: 5 V 3 A
31
- action: shed load on rail 'rail.5v' or raise its capacity with provenance
32
- warning model/links/base_link/mass — observed zero or missing mass, expected a positive mass with units
33
- …
34
- scored 2: 0 add up, 1 do not · 0 not scored
82
+ ```
83
+ https://github.com/Shivansh2703/lockwire/releases/latest
84
+ ```
85
+
86
+ **GitHub Action.** Run the check on every push and keep an HTML report.
87
+
88
+ ```
89
+ - uses: actions/checkout@v4
90
+ - uses: Shivansh2703/lockwire@v0
91
+ with:
92
+ contract: lockwire.yaml
93
+ ```
94
+
95
+ **AI agents (MCP).** Let Claude Desktop, Claude Code or Cursor check your robot before it acts.
96
+
97
+ ```
98
+ pipx install "lockwire[mcp]"
99
+ ```
100
+
101
+ ```
102
+ claude mcp add lockwire -- lockwire mcp
103
+ ```
104
+
105
+ ```
106
+ {
107
+ "mcpServers": {
108
+ "lockwire": { "command": "lockwire", "args": ["mcp"] }
109
+ }
110
+ }
111
+ ```
112
+
113
+ Claude Code: the claude mcp add line. Claude Desktop: put the JSON in claude_desktop_config.json. Cursor: put the JSON in .cursor/mcp.json (or ~/.cursor/mcp.json). If the app cannot find lockwire, use the full path from which lockwire. Then ask: Check my robot's lockwire.yaml and explain the NO-GO.
114
+
115
+ **Agent plan and what-if.** Score an intended plan, or a proposed change, before anything executes.
116
+
117
+ ```
118
+ lockwire plan lockwire.yaml plan.yaml
119
+ ```
120
+
121
+ ```
122
+ lockwire what-if lockwire.yaml --change "devices/dev.onboard-computer/physical/mass=300 g" --source "my scale" --witness me
123
+ ```
124
+
125
+ plan.yaml holds steps, for example: steps: [{kind: rail_load, rail: rail.supply-5v, draw: {value: 10, unit: W}}]
126
+
127
+ **Python library.** Call the same check from your own script.
35
128
 
36
- NO-GO — 1 violation(s), 1 warning(s)
37
129
  ```
130
+ import lockwire.api as lw
131
+ print(lw.check("lockwire.yaml")["verdict"])
132
+ ```
133
+
134
+ **ROS, preflight and the local API.** The go/no-go gate for a powered-on robot, ROS and the local server.
135
+
136
+ Part of Lockwire Pro.
137
+
138
+ ### Features
38
139
 
39
- The check exits 1. A Pi 5 and one servo on a common 3 A regulator: caught before you ordered either. Swap in a 5 A regulator and it passes.
140
+ - **Check:** See which numbers do not add up. `lockwire check lockwire.yaml`
141
+ - **Declare a fact:** Set one value with a source and a witness. `lockwire declare lockwire.yaml devices/dev.power-module.pm02-v3/physical/mass "25 g" --source "my scale" --witness me`
142
+ - **Add a part:** Add a device, rail, bus or battery block. `lockwire add --help`
143
+ - **Design (place, move, rotate, reparent):** Put a part in the 3D model and move it. Switch on the Power, Data and Mechanical connection views to see them drawn on the robot, and use W, A, S, D to move the camera. `lockwire move lockwire.yaml dev.power-module.pm02-v3 --offset 0.01,0,0.02 --unit m --source "tape measure" --witness me`
144
+ - **Plan and what-if:** Score a plan or a change without editing the lockfile. `lockwire plan lockwire.yaml plan.yaml`
145
+ - **Report:** Write one HTML page to share. `lockwire report lockwire.yaml -o report.html --no-open`
146
+ - **Describe:** Write a reference sheet per part. `lockwire describe lockwire.yaml --out ref`
147
+ - **History (diff, log, blame):** See what changed, by whom, and why (needs git). `lockwire diff`
148
+ - **Scene:** Read part positions from a 3D model. `lockwire scene import --help`
149
+ - **Gearbox:** Add a gearbox with its ratio and joints. `lockwire gearbox --help`
150
+ - **Upgrade:** Write a new lockfile from an older one. `lockwire upgrade --help`
40
151
 
41
152
  ## A real-world example: the X500 drone
42
153
 
@@ -109,8 +220,8 @@ The `scored N: …` line above the verdict is where a script should look.
109
220
  contract: lockwire.yaml
110
221
  ```
111
222
 
112
- Inputs: `contract` (default `lockwire.yaml`), `version` (default latest), `python-version` (default 3.12).
113
- Or just `pip install "git+https://github.com/Shivansh2703/lockwire@v0.2.0" && lockwire check lockwire.yaml`.
223
+ Inputs: `contract` (default `lockwire.yaml`), `version` (pin a PyPI release; default installs the action's own code), `python-version` (default 3.12).
224
+ Or just `pipx install lockwire && lockwire check lockwire.yaml`.
114
225
 
115
226
  ## Pre-commit
116
227
 
@@ -147,8 +258,7 @@ No physics simulation, no machine learning, no network. The answer is the same t
147
258
  - **Free forever (this repo):** the lockfile, all deterministic checks including
148
259
  power/thermal/bus/link/torque budgets, edit / describe / plan / what-if, the MCP server, the
149
260
  HTML report, and the desktop planning app (Open, Design, Declare, Plan, Check).
150
- - **Lockwire Pro** adds the powered-on preflight gate (Test, Run), recording analysis
151
- (rosbag2 / ULog), and health.
261
+ - **Lockwire Pro** is the paid tier (powered-on preflight, recording analysis).
152
262
 
153
263
  ## Development
154
264
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "lockwire"
3
- version = "0.2.0"
4
- description = "The physical-assumption contract for robots: physics lockfile + preflight gate"
3
+ version = "0.2.3"
4
+ description = "The physical-assumption contract for robots: checks that a robot's declared parts (power, weight, bus, torque) add up"
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Shivansh" }
@@ -1,6 +1,6 @@
1
1
  """Lockwire — the physical-assumption contract for robots, enforced in CI and at preflight."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.3"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -0,0 +1,38 @@
1
+ """Blocking exclusive lock on an open fd, POSIX and Windows, stdlib only."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import time
7
+
8
+
9
+ def lock(fd: int) -> None:
10
+ if os.name == "nt":
11
+ import msvcrt
12
+
13
+ os.lseek(fd, 0, os.SEEK_SET)
14
+ deadline = time.monotonic() + 30
15
+ while True:
16
+ try:
17
+ msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
18
+ return
19
+ except OSError:
20
+ if time.monotonic() >= deadline:
21
+ raise TimeoutError("lock held by another lockwire process") from None
22
+ time.sleep(0.1)
23
+ else:
24
+ import fcntl
25
+
26
+ fcntl.flock(fd, fcntl.LOCK_EX)
27
+
28
+
29
+ def unlock(fd: int) -> None:
30
+ if os.name == "nt":
31
+ import msvcrt
32
+
33
+ os.lseek(fd, 0, os.SEEK_SET)
34
+ msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
35
+ else:
36
+ import fcntl
37
+
38
+ fcntl.flock(fd, fcntl.LOCK_UN)