commandagi 0.3.0__tar.gz → 0.5.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 (73) hide show
  1. commandagi-0.5.0/.gitignore +8 -0
  2. commandagi-0.5.0/CHANGELOG.md +87 -0
  3. commandagi-0.5.0/LICENSE +21 -0
  4. commandagi-0.5.0/PKG-INFO +275 -0
  5. commandagi-0.5.0/README.md +247 -0
  6. commandagi-0.5.0/commandagi/__init__.py +63 -0
  7. commandagi-0.5.0/commandagi/_generated.py +663 -0
  8. commandagi-0.5.0/commandagi/client.py +276 -0
  9. commandagi-0.5.0/commandagi/design/__init__.py +115 -0
  10. commandagi-0.5.0/commandagi/design/business.py +211 -0
  11. commandagi-0.5.0/commandagi/design/cad.py +289 -0
  12. commandagi-0.5.0/commandagi/design/cadquery.py +289 -0
  13. commandagi-0.5.0/commandagi/design/documents.py +190 -0
  14. commandagi-0.5.0/commandagi/design/eda.py +226 -0
  15. commandagi-0.5.0/commandagi/design/element.py +163 -0
  16. commandagi-0.5.0/commandagi/design/fab.py +156 -0
  17. commandagi-0.5.0/commandagi/design/graph.py +41 -0
  18. commandagi-0.5.0/commandagi/design/ir.py +224 -0
  19. commandagi-0.5.0/commandagi/design/media.py +832 -0
  20. commandagi-0.5.0/commandagi/design/monitors.py +237 -0
  21. commandagi-0.5.0/commandagi/design/office.py +614 -0
  22. commandagi-0.5.0/commandagi/design/ontology.py +302 -0
  23. commandagi-0.5.0/commandagi/design/pcb.py +565 -0
  24. commandagi-0.5.0/commandagi/design/pdf.py +300 -0
  25. commandagi-0.5.0/commandagi/design/postal.py +111 -0
  26. commandagi-0.5.0/commandagi/design/records.py +87 -0
  27. commandagi-0.5.0/commandagi/design/schema.py +340 -0
  28. commandagi-0.5.0/commandagi/design/schematic.py +487 -0
  29. commandagi-0.5.0/commandagi/design/solids.py +153 -0
  30. commandagi-0.5.0/commandagi/design/source.py +366 -0
  31. commandagi-0.5.0/commandagi/design/standards.py +122 -0
  32. commandagi-0.5.0/commandagi/design/standards_data.py +12 -0
  33. commandagi-0.5.0/commandagi/design/tasks.py +122 -0
  34. commandagi-0.5.0/commandagi/design/threed.py +460 -0
  35. commandagi-0.5.0/commandagi/design/threed_elements.py +1272 -0
  36. commandagi-0.5.0/commandagi/design/threed_schema.py +4 -0
  37. commandagi-0.5.0/commandagi/design/twod.py +579 -0
  38. commandagi-0.5.0/commandagi/design/twod_schema.py +4 -0
  39. commandagi-0.5.0/commandagi/design/workbook_schema.py +4 -0
  40. {commandagi-0.3.0 → commandagi-0.5.0}/commandagi/gym_env.py +11 -11
  41. commandagi-0.5.0/commandagi/kernel.py +443 -0
  42. commandagi-0.5.0/commandagi-sdk.schema.json +2434 -0
  43. commandagi-0.5.0/examples/drive_a_sim.py +19 -0
  44. {commandagi-0.3.0 → commandagi-0.5.0}/pyproject.toml +5 -3
  45. commandagi-0.5.0/tests/test_business.py +72 -0
  46. commandagi-0.5.0/tests/test_design.py +161 -0
  47. commandagi-0.5.0/tests/test_fab.py +69 -0
  48. commandagi-0.5.0/tests/test_kernel.py +198 -0
  49. commandagi-0.5.0/tests/test_media.py +194 -0
  50. commandagi-0.5.0/tests/test_monitors.py +91 -0
  51. commandagi-0.5.0/tests/test_office.py +140 -0
  52. commandagi-0.5.0/tests/test_ontology.py +125 -0
  53. commandagi-0.5.0/tests/test_pcb.py +123 -0
  54. commandagi-0.5.0/tests/test_pdf.py +71 -0
  55. commandagi-0.5.0/tests/test_postal.py +63 -0
  56. commandagi-0.5.0/tests/test_records.py +59 -0
  57. commandagi-0.5.0/tests/test_schematic.py +148 -0
  58. commandagi-0.5.0/tests/test_sdk.py +145 -0
  59. commandagi-0.5.0/tests/test_source.py +114 -0
  60. commandagi-0.5.0/tests/test_standards.py +99 -0
  61. commandagi-0.5.0/tests/test_tasks.py +77 -0
  62. commandagi-0.5.0/tests/test_threed.py +253 -0
  63. commandagi-0.5.0/tests/test_twod.py +279 -0
  64. commandagi-0.5.0/uv.lock +1041 -0
  65. commandagi-0.3.0/.gitignore +0 -65
  66. commandagi-0.3.0/PKG-INFO +0 -159
  67. commandagi-0.3.0/README.md +0 -134
  68. commandagi-0.3.0/commandagi/__init__.py +0 -26
  69. commandagi-0.3.0/commandagi/agent.py +0 -172
  70. commandagi-0.3.0/commandagi/bridge.py +0 -133
  71. commandagi-0.3.0/commandagi/client.py +0 -504
  72. commandagi-0.3.0/examples/sim_agent.py +0 -44
  73. {commandagi-0.3.0 → commandagi-0.5.0}/commandagi/lerobot.py +0 -0
@@ -0,0 +1,8 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+ .turbo/
5
+ __pycache__/
6
+ .venv/
7
+ *.egg-info/
8
+ .DS_Store
@@ -0,0 +1,87 @@
1
+ # Changelog
2
+
3
+ One entry for each release. A release is the tag `v<version>` in this repository and the same version on PyPI. The
4
+ TypeScript and Rust SDKs carry the same version number. A version promises nothing about the next one (README
5
+ § Versions): read this file before you change the version you pin.
6
+
7
+ ## 0.5.0 (2026-10-14)
8
+
9
+ The first release with the design layer. 0.5.0 was prepared on 2026-10-13 and not published; this entry holds all of it.
10
+
11
+ Changed
12
+
13
+ - `Kernel` speaks the kernel contract (`POST <kernel>/v1/<op>`, `GET <kernel>/v1/health`) and is placed by
14
+ `COMMANDAGI_THREED`: `local`, `cloud`, both apart by `,` in the order to try (default `local,cloud`), or a running
15
+ service's URL. `platform` is not a value now: write `cloud`. A place where the kernel is not installed is passed
16
+ over; a refusal of the op is the answer. `Kernel.last_ran` says where each answer ran (`local` is never a cloud
17
+ result), and `Kernel.call(op, doc, params)` gives a whole answer. `observe` takes no `doc_id`.
18
+
19
+ Added
20
+
21
+ - A 3D document's engineering vocabulary (`commandagi.design.threed`, checked against the one schema, as in the
22
+ TypeScript SDK): `body(engineering={…})` (material grade, process, finish, vendor, cost, part number, mass),
23
+ `joint(…)` (`fixed`, `revolute`, `prismatic`, `cylindrical`, `planar`, `screw`, `spherical`, `universal`),
24
+ `mate(…)`, `sheet(…)` (an engineering drawing sheet), and `hole(size="M4 close fit")` (a hole by its standard name).
25
+ - `commandagi.design.standards`: `material`, `mass_grams`, `hole_size`, `sized_hole`, `standard_row`, `MATERIALS`,
26
+ `HOLE_SIZES`, `STANDARD_TABLES`, `STANDARD_SOURCES`: the same tables as the TypeScript SDK, each with where its
27
+ rows were read.
28
+ - `hole(body, at=…, size="M4 close fit", depth=…)` in the code-part primitives (`commandagi.design.cad`).
29
+ - `Kernel.sheet`, `Kernel.bom`, and `Kernel.observe(doc, materials=…)`: a body's `mass["grams"]`.
30
+ - 3D documents: a hole's `positions` and `sketch`; faces and edges chosen by a query, `{"kind", "select": {…}}`,
31
+ beside the pick; a part is built in the order of its elements (a feature that names no `consumes` follows every
32
+ body-making feature written before it).
33
+ - `Kernel.interference`, `Kernel.measure_all`, `linked_parts`; `parts=` on every call (an assembly's files);
34
+ `KernelError.failures`.
35
+
36
+ Changed
37
+
38
+ - A joint's kinds: `hinge` is `revolute`, `slider` is `prismatic`, `ball` is `spherical`. Joints and mates are
39
+ `joint(…)` and `mate(…)` elements: the reader refuses them inside `slot(name="dynamics")` and
40
+ `slot(name="assembly")`.
41
+ - `Kernel.measure(doc, quantity, a, b)` works with the platform too (the service takes `measures`).
42
+ - `Kernel.export`, `measure`, `drawing` and `tessellate` refuse a build in which a feature failed. `tessellate`
43
+ answers each body's triangles, not only their count.
44
+ - The call form (`part`, `subtract`, `hole`, `fillet` …) wires a feature to the heads of its body and to its last
45
+ feature: about two wires a feature, where a plate of 129 holes was 566 kB. `Body.state` is gone; `Body.heads` and
46
+ `Body.acts_on()` are what it has.
47
+ - A union's refusal names the field at fault (`edges/0/select/vertical: is not one of its fields`).
48
+
49
+
50
+ The first release that contains `commandagi.design`. PyPI 0.4.0 was built on 2026-09-23, before the design layer was
51
+ written; the version in the source stayed at 0.4.0 for the 106 commits after it, so the source and the package had
52
+ one number and different contents.
53
+
54
+ Added
55
+
56
+ - `commandagi.design`: documents declared in code, with the standard library only.
57
+ - The code-part primitives: CAD (`part`, `box`, `cylinder`, `sketch`, `extrude`, `subtract`, `hole`,
58
+ `linear_pattern` …), EDA (`circuit`, `board`, `component`, `net`, `connect`, `footprints`), any graph (`graph`,
59
+ `node`, `input_`, `code`), and `run_module`.
60
+ - One module for each document vocabulary: `schematic`, `pcb`, `threed`, `fab`, `office`, `twod`, `media`,
61
+ `ontology`, `business`, `tasks`, `monitors`, `records`, `postal`, `pdf`.
62
+ - `commandagi.design.cadquery`: a CadQuery-style importer.
63
+ - `commandagi.design.source`: the map from each element to the call that made it, for the editors' write-back.
64
+ - `commandagi.kernel.Kernel` (also `commandagi.Kernel`): `observe`, `export`, `measure`, `drawing` and `tessellate` of
65
+ a 3D document, by the CommandAGI 3D design service (OpenCascade). `COMMANDAGI_THREED` chooses the service:
66
+ `platform` (the default, with an API key), `local` (this computer, offline, no account: the `commandagi` npm
67
+ package's `commandagi threed serve`) or the URL of a running one.
68
+ - Client namespaces: `computer`, `billing`, `web`, `postal`, `social`, `phone`, `vpn`; agent constraints.
69
+ - `LICENSE` (MIT, as `pyproject.toml` states), this file, and § Versions in the README.
70
+
71
+ Changed
72
+
73
+ - The desktop control vocabulary has `mouse_down` and `mouse_up`.
74
+
75
+ Known limits
76
+
77
+ - `Kernel.measure` works with a local service only: the platform's route cannot be told which measurement.
78
+ - The hosted platform does not run a `.py` document's kernel calls for you; `Kernel` sends the graph that
79
+ `run_module` made.
80
+
81
+ ## 0.4.0 (2026-09-23)
82
+
83
+ The generated client (`CommandAGI`, `Session` over the thread WebSocket) replaced `RobotBridge`.
84
+
85
+ ## 0.3.0 (2026-09-05)
86
+
87
+ The first release by trusted publishing.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CommandAGI INC
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.
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.5
2
+ Name: commandagi
3
+ Version: 0.5.0
4
+ Summary: The CommandAGI SDK for Python — agent threads, memory and integrations, and live computers, robots and simulated worlds you watch and control.
5
+ Project-URL: Homepage, https://commandagi.com
6
+ Project-URL: Documentation, https://github.com/CommandAGI/commandagi-sdk-python/blob/main/README.md
7
+ Project-URL: Repository, https://github.com/CommandAGI/commandagi-sdk-python
8
+ Project-URL: Changelog, https://github.com/CommandAGI/commandagi-sdk-python/blob/main/CHANGELOG.md
9
+ Author: CommandAGI
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,computer-use,physics,reinforcement-learning,robotics,simulation
13
+ Requires-Python: >=3.9
14
+ Requires-Dist: requests>=2.28
15
+ Requires-Dist: websocket-client>=1.5
16
+ Provides-Extra: gym
17
+ Requires-Dist: gymnasium>=0.29; extra == 'gym'
18
+ Requires-Dist: numpy>=1.23; extra == 'gym'
19
+ Requires-Dist: pillow>=9.0; extra == 'gym'
20
+ Provides-Extra: lerobot
21
+ Requires-Dist: numpy>=1.23; extra == 'lerobot'
22
+ Requires-Dist: pillow>=9.0; extra == 'lerobot'
23
+ Requires-Dist: pyarrow>=14.0; extra == 'lerobot'
24
+ Provides-Extra: vision
25
+ Requires-Dist: numpy>=1.23; extra == 'vision'
26
+ Requires-Dist: pillow>=9.0; extra == 'vision'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # commandagi — the CommandAGI Python SDK
30
+
31
+ Drive the whole CommandAGI platform as code: agent threads, memory and integrations, and live
32
+ **embodiments** (cloud computers, robots, simulated worlds) that you watch and control.
33
+
34
+ ```bash
35
+ pip install commandagi
36
+ export COMMANDAGI_API_KEY=cagi_... # Settings → API keys
37
+ ```
38
+
39
+ ```python
40
+ from commandagi import CommandAGI
41
+
42
+ cagi = CommandAGI() # reads COMMANDAGI_API_KEY
43
+
44
+ # Agents: start one on a goal and follow along.
45
+ t = cagi.threads.create(intent="Summarise this week's robotics news in five bullets")
46
+ cagi.threads.send(t["threadId"], "Add a link for each one.")
47
+
48
+ # Embodiments: launch a world YOU drive (no agent in it), watch it, control it.
49
+ with cagi.launch("simulation/warehouse") as world:
50
+ print(world.controls()) # what it accepts right now, with payload schemas
51
+ world.sim.ik(target=[0.3, 0.0, 0.4]) # typed robot/sim control
52
+ jpeg = world.observe(fresh=True) # the next frame, after the move
53
+ # leaving the block stops the world and releases the machine
54
+
55
+ # Anything else: every platform tool by name.
56
+ cagi.call("list_snapshots")
57
+ ```
58
+
59
+ ## One surface, generated
60
+
61
+ This SDK, the TypeScript SDK (`npm i commandagi`) and the `commandagi` CLI all come from **one
62
+ schema**, `commandagi-sdk.schema.json`, so they can't drift apart:
63
+
64
+ - **Tools**: `cagi.threads`, `cagi.embodiments`, `cagi.memory`, `cagi.integrations`,
65
+ `cagi.social(platform, account)`, `whoami`, `search`, `run` and `post`. Each one is a typed wrapper
66
+ over `call(tool, args)`. Options are passed as keyword arguments, e.g. `snapshot_id=…`, and sent
67
+ over the wire as camelCase (`snapshotId`). Where the schema names the options, they are typed
68
+ keyword-only arguments: `cagi.postal.send("Letters/Ada.letter.tsx", provider="click2mail")` (the
69
+ provider is `"lob"`, the default, or `"click2mail"`, US only; omitted, the account's
70
+ `postal.providers` order picks it).
71
+ - **Control vocabularies** on a live `Session`:
72
+ - `session.desktop` for computers: `click`, `double_click`, `move`, `scroll`, `type`, `key`, `wait`
73
+ - `session.robot` for physical robots: `joint`, `gripper`, `move`, `turn`, `home`, `stop`, …
74
+ - `session.sim` for simulated worlds: `reset`, `sim_mode`, `ctrl`, `actuator`, `ik`,
75
+ `trajectory`, `grab`, `force`, `add_robot`, …
76
+
77
+ Every method sends one action to that embodiment. The platform checks it against the controls the
78
+ embodiment is currently declaring, which `session.controls()` lists along with their payload
79
+ schemas. For anything a runtime declares that no vocabulary covers, use `session.act(action,
80
+ payload)`.
81
+
82
+ The generated part is `commandagi/_generated.py`. Don't edit it: it is regenerated from the schema in
83
+ the CommandAGI monorepo.
84
+
85
+ ## Sessions
86
+
87
+ | | |
88
+ | --- | --- |
89
+ | `cagi.launch(snapshot_id, title=, wait=True, timeout=600)` | a new agentless thread running that snapshot; waits until it declares controls |
90
+ | `cagi.session(thread_id, embodiment_id)` | attach to an embodiment that already exists |
91
+ | `session.frame(fresh=False)` / `observe()` / `observe_array()` / `frames()` | the latest frame, as `{url, channel_id, at}`, image bytes, or a numpy array (`pip install "commandagi[vision]"`); `frames()` yields each new one |
92
+ | `session.describe()` | the live world a sim or robot runtime reports |
93
+ | `session.close()` / `stop()` | stop watching / also stop a world this session launched |
94
+
95
+ Snapshot ids come from `cagi.call("list_snapshots")`, e.g. `simulation/warehouse` or
96
+ `computer/software-engineer`.
97
+
98
+ ## Design in code: `commandagi.design`
99
+
100
+ The same structure-declaring primitives as the TypeScript SDK's `commandagi/design`, producing the same op graph
101
+ (plain JSON). `commandagi.design` itself holds the code-part primitives: CAD (`part`, `box`, `cylinder`, `sketch`,
102
+ `extrude`, `subtract`, `hole`, `linear_pattern` and the rest), EDA (`circuit`, `board`, `component`, `net`, `connect`,
103
+ `footprints`), any graph (`graph`, `node`, `input_`, `code`) and `run_module`. There is no kernel, solver, router or
104
+ renderer; importing `commandagi.design` needs nothing but the standard library.
105
+
106
+ ### Documents: one module per vocabulary
107
+
108
+ Every document a TypeScript file writes in JSX, a Python file writes as calls, element for element. One module per
109
+ vocabulary, one function per tag, named by the tag:
110
+
111
+ | module | documents |
112
+ | --- | --- |
113
+ | `commandagi.design.schematic` | a schematic (`group`) |
114
+ | `commandagi.design.pcb` | a board in code (`board`) |
115
+ | `commandagi.design.threed` | a 3D part or assembly |
116
+ | `commandagi.design.fab` | a machining setup (`cam`), a slicing setup (`slicing`) |
117
+ | `commandagi.design.office` | a workbook, a page, a deck |
118
+ | `commandagi.design.twod` | a drawing, a painting, a photo, a nest |
119
+ | `commandagi.design.media` | a video (clips, `composition(src="Intro.vid.py")`: another video drawn live from its own code, `artboard(src="Poster.draw.py")`: a drawing's page drawn live), a song |
120
+ | `commandagi.design.ontology` | a world, a device definition, a dashboard, a geo project, a node graph |
121
+ | `commandagi.design.business` | a company, an RFC, a case |
122
+ | `commandagi.design.tasks` | a task, a project |
123
+ | `commandagi.design.monitors` | a monitor (`monitor.py`, in the folder of the thing it follows) |
124
+ | `commandagi.design.records` | a contract, a product instance |
125
+ | `commandagi.design.postal` | a letter, a postcard |
126
+ | `commandagi.design.pdf` | a PDF assembled from pages, comments, stamps, signatures, redactions, fields and bookmarks |
127
+
128
+ The rules of a call, the same in every module:
129
+
130
+ - `tag(*children, **attributes)`. The function's name is the tag: `-` becomes `_` (`brush_stroke`), a Python keyword
131
+ gets a trailing `_` (`from_`), the case is kept (`Company`).
132
+ - Children are positional, in order. A tag that holds text takes texts and inline elements:
133
+ `p("The run ", b("passed"), ".")`. A loop is a starred argument: `layer(*[rect(x=i * 10) for i in range(3)])`.
134
+ A leaf takes no children.
135
+ - Attributes are keywords in snake_case of the TypeScript name: `frozen_rows=1` is `frozenRows`, `sch_x=114.3` is
136
+ `schX`. A keyword with an uppercase letter is refused by name.
137
+ - Only the schematic has positional attributes: `resistor("R1", …)`, `group("Divider", …)`, `trace(from, to)`,
138
+ `netlabel(net, connection)`, `unit(part, unit)`.
139
+ - The file's result is `result = <root>(…)` (or `main()`, or `show_object`).
140
+
141
+ ```python
142
+ from commandagi.design.twod import drawing, ellipse, group, layer, rect
143
+
144
+ result = drawing(
145
+ layer(
146
+ rect(x=40, y=40, w=200, h=120, fill="#3b82f6"),
147
+ group(ellipse(cx=500, cy=300, rx=60, ry=60, fill="#f59e0b"), name="Badge"),
148
+ name="Layer 1",
149
+ ),
150
+ name="Poster", width=800, height=600, background="#ffffff",
151
+ )
152
+ ```
153
+
154
+ ```python
155
+ from commandagi.design.schematic import ground, group, netlabel, resistor, trace, voltagesource
156
+
157
+ result = group(
158
+ "Divider",
159
+ voltagesource("V1", voltage="9", sch_x=114.3, sch_y=114.3),
160
+ resistor("R1", resistance="3k", sch_x=114.3, sch_y=88.9, sch_rotation=90),
161
+ ground("#PWR1", sch_x=139.7, sch_y=114.3),
162
+ trace(".V1 > .pos", ".R1 > .pin1"),
163
+ netlabel("OUT", ".R1 > .pin2"),
164
+ )
165
+ ```
166
+
167
+ A graph document (a schematic, a board, a 3D part, a drawing, a deck, a video, a node graph) leaves `run_module` as
168
+ its graph. Any other document (a workbook, a world, a company, a task …) leaves as `document` (`{format, document,
169
+ sources}`) beside an empty graph.
170
+
171
+ ### Where each element is written
172
+
173
+ `run_module` maps the file's calls with Python's `ast` (`commandagi.design.source`). Each element records the call
174
+ that made it. A declaration puts that call where the TypeScript SDK puts a JSX element's source: a node's
175
+ `meta.source`, a node's `meta.sources` by key, a document's `sources` by key. Each one holds the call's span, each
176
+ keyword's span with its literal value or its expression, and how many times the call ran. The whole map rides on the
177
+ graph as `meta.sourceMap`: every call with its arguments, its parent element, its slot among its parent's children,
178
+ and whether it runs in a loop. The editor writes each edit back into the file as the smallest text edit. A keyword
179
+ that is an expression is never replaced with a literal: the edit is refused with its line.
180
+
181
+ ### Code parts and CadQuery
182
+
183
+ `commandagi.design.cadquery` is a CadQuery-style importer: `Workplane("XY").box(…).faces(">Z").workplane()
184
+ .rarray(…).hole(…)` records the same features the primitives declare. It refuses, by name, what needs real topology
185
+ (fillets, edge selectors).
186
+
187
+ A `.py` code part is a script. Its result is `main(**inputs)` if it defines `main`; otherwise what it passed to
188
+ `show_object`; otherwise its `result` variable. `param(name, default, unit=…)` declares an input and reads its value.
189
+ In a CommandAGI editor, a `.py` code node runs under Pyodide in a sandboxed worker, where `import cadquery as cq` is
190
+ this importer (OCCT does not run there). `run_module(source, path, inputs)` is what the sandbox calls. Tests:
191
+ `python -m unittest discover -s tests`.
192
+
193
+ ## The 3D kernel: `commandagi.kernel`
194
+
195
+ `commandagi.design.threed` declares a part and builds nothing. `Kernel` asks the CommandAGI 3D design service
196
+ (OpenCascade) to build it. It uses the standard library only.
197
+
198
+ ```python
199
+ from commandagi.design.threed import box, cylinder, part
200
+ from commandagi.kernel import Kernel
201
+
202
+ plate = part(
203
+ box(id="plate", center=[0, 0, 4], size=[60, 40, 8], operation="new"),
204
+ cylinder(id="hole", center=[0, 0, -1], axis=[0, 0, 1], radius=3, height=10, operation="cut"),
205
+ name="Plate",
206
+ )
207
+ kernel = Kernel() # reads COMMANDAGI_THREED
208
+ seen = kernel.observe(plate)["observation"] # bodies, faces, edges, mass properties; status and failures
209
+ assert seen["status"] == "built", seen["failures"] # "failed": the bodies are built from the features that did not fail
210
+ open("plate.step", "w").write(kernel.export(plate, "step")) # refused (KernelError.failures) when a feature failed
211
+ ```
212
+
213
+ A part is built in the order of its elements: the cut above follows the plate because it is written after it.
214
+ `consumes=[…]` names the features a feature follows instead.
215
+
216
+ One setting says where it runs: `COMMANDAGI_THREED`, the places to try in order, apart by `,` (default `local,cloud`).
217
+ A place where the kernel is not installed is passed over. A refusal of the op itself (a build that is not whole) is
218
+ the answer: it is not asked of the next place. `kernel.last_ran` says where each answer ran (`{"kernel", "build",
219
+ "place"}`); `place` is where the SDK sent the request, so a result made on this computer says `local` and is never a
220
+ cloud result.
221
+
222
+ | place | the kernel | needs |
223
+ | --- | --- | --- |
224
+ | `local` | this computer, offline, no account. `Kernel` starts it and stops it when Python exits; `COMMANDAGI_THREED_URL` names one that runs already (`commandagi threed serve --port 8091`) | Node 22 and, once: `npm install -g commandagi` then `commandagi threed install` (it downloads OpenCascade as WebAssembly, about 130 MB); for a running one, `COMMANDAGI_THREED_TOKEN`, the bearer you started it with |
225
+ | `cloud` | the platform's kernel door (`/kernels/3d/v1/<op>`) | the network and `COMMANDAGI_API_KEY` |
226
+
227
+ A URL in `COMMANDAGI_THREED` is that running service only. Both places speak one contract (`POST <kernel>/v1/<op>`,
228
+ `GET <kernel>/v1/health`), and `kernel.call(op, doc, params)` gives a whole answer (`outputs`, `result`, `ran`).
229
+
230
+ `observe(doc)`, `export(doc, "step" | "iges", bodies=)`, `measure(doc, "distance" | "angle" | "radius" | "closest", a,
231
+ b)`, `measure_all(doc, [{"quantity", "a", "b"}, …])` (many on one build), `interference(doc, clearance=, bodies=)`,
232
+ `drawing(doc, direction=, up=, section=)`, `tessellate(doc, deflection)`. `doc` is what a file declares, its
233
+ graph (`run_module(...)["graph"]`) or the graph's JSON text. An entity is `{"body": "body_0", "kind": "face" | "edge" |
234
+ "vertex" | "body", "index": n}`, with the index as `observe` lists it.
235
+
236
+ - **A build in which a feature failed is not whole.** `observe` answers it with `status: "failed"` and `failures`.
237
+ Every other call refuses it: `KernelError` with `status` 422 and `failures` (each failed feature, with why).
238
+ - **`interference`** compares every pair of bodies: `clashes` (shared volume), `engagements` (shared volume that all
239
+ lies in a declared `thread`: a screw in a tapped hole) and, with `clearance`, `close` (nearer than it). Each body
240
+ has its part's name.
241
+ - **An assembly's files** go with the call: `parts=linked_parts({"bolt.3d.py": bolt, "bracket.step": text})`, each
242
+ under the name its `externalPart` gives.
243
+ - **A STEP file names its parts**: each body is a product of its part's name, an assembly the product they are
244
+ components of.
245
+
246
+ ## Reinforcement learning
247
+
248
+ `commandagi.gym_env.CommandAGIEnv` wraps a sim session as a Gymnasium env (`pip install
249
+ "commandagi[gym]"`), and `commandagi.lerobot` records rollouts as LeRobot datasets (`pip install
250
+ "commandagi[lerobot]"`).
251
+
252
+ ## Environment
253
+
254
+ | variable | meaning |
255
+ | --- | --- |
256
+ | `COMMANDAGI_API_KEY` | your `cagi_…` key. Its scopes decide what every call may do |
257
+ | `COMMANDAGI_BASE_URL` | API origin (default `https://api.commandagi.com`) |
258
+ | `COMMANDAGI_THREAD_ID` | default thread for self-thread methods. Set automatically when your code runs inside the platform |
259
+ | `COMMANDAGI_THREED` | where `Kernel` runs: `local`, `cloud`, both apart by `,` in the order to try (default `local,cloud`), or a running service's URL |
260
+ | `COMMANDAGI_THREED_URL`, `COMMANDAGI_THREED_TOKEN` | a 3D design service that runs already, and its bearer |
261
+
262
+ ## Versions
263
+
264
+ - A release is the tag `v<version>` in this repository and the same version on PyPI. Pin an exact version:
265
+ `commandagi==0.5.0`. A range (`^`, `~=`, `>=`) takes the next release without asking you.
266
+ - The three SDKs (Python, TypeScript, Rust) carry one version number. A vocabulary at version X in one language is
267
+ the vocabulary at version X in the others.
268
+ - A version promises nothing about the next one. CommandAGI keeps no compatibility layers: a release may rename or
269
+ remove a function, an element or an attribute, with no alias and no migration. `CHANGELOG.md` says what changed.
270
+ - So a document belongs with the version that reads it. Keep the pin in the project that holds the documents, and
271
+ change both together. A document you open in a CommandAGI editor is read by the SDK that editor ships.
272
+
273
+ ## Licence
274
+
275
+ MIT (`LICENSE`).
@@ -0,0 +1,247 @@
1
+ # commandagi — the CommandAGI Python SDK
2
+
3
+ Drive the whole CommandAGI platform as code: agent threads, memory and integrations, and live
4
+ **embodiments** (cloud computers, robots, simulated worlds) that you watch and control.
5
+
6
+ ```bash
7
+ pip install commandagi
8
+ export COMMANDAGI_API_KEY=cagi_... # Settings → API keys
9
+ ```
10
+
11
+ ```python
12
+ from commandagi import CommandAGI
13
+
14
+ cagi = CommandAGI() # reads COMMANDAGI_API_KEY
15
+
16
+ # Agents: start one on a goal and follow along.
17
+ t = cagi.threads.create(intent="Summarise this week's robotics news in five bullets")
18
+ cagi.threads.send(t["threadId"], "Add a link for each one.")
19
+
20
+ # Embodiments: launch a world YOU drive (no agent in it), watch it, control it.
21
+ with cagi.launch("simulation/warehouse") as world:
22
+ print(world.controls()) # what it accepts right now, with payload schemas
23
+ world.sim.ik(target=[0.3, 0.0, 0.4]) # typed robot/sim control
24
+ jpeg = world.observe(fresh=True) # the next frame, after the move
25
+ # leaving the block stops the world and releases the machine
26
+
27
+ # Anything else: every platform tool by name.
28
+ cagi.call("list_snapshots")
29
+ ```
30
+
31
+ ## One surface, generated
32
+
33
+ This SDK, the TypeScript SDK (`npm i commandagi`) and the `commandagi` CLI all come from **one
34
+ schema**, `commandagi-sdk.schema.json`, so they can't drift apart:
35
+
36
+ - **Tools**: `cagi.threads`, `cagi.embodiments`, `cagi.memory`, `cagi.integrations`,
37
+ `cagi.social(platform, account)`, `whoami`, `search`, `run` and `post`. Each one is a typed wrapper
38
+ over `call(tool, args)`. Options are passed as keyword arguments, e.g. `snapshot_id=…`, and sent
39
+ over the wire as camelCase (`snapshotId`). Where the schema names the options, they are typed
40
+ keyword-only arguments: `cagi.postal.send("Letters/Ada.letter.tsx", provider="click2mail")` (the
41
+ provider is `"lob"`, the default, or `"click2mail"`, US only; omitted, the account's
42
+ `postal.providers` order picks it).
43
+ - **Control vocabularies** on a live `Session`:
44
+ - `session.desktop` for computers: `click`, `double_click`, `move`, `scroll`, `type`, `key`, `wait`
45
+ - `session.robot` for physical robots: `joint`, `gripper`, `move`, `turn`, `home`, `stop`, …
46
+ - `session.sim` for simulated worlds: `reset`, `sim_mode`, `ctrl`, `actuator`, `ik`,
47
+ `trajectory`, `grab`, `force`, `add_robot`, …
48
+
49
+ Every method sends one action to that embodiment. The platform checks it against the controls the
50
+ embodiment is currently declaring, which `session.controls()` lists along with their payload
51
+ schemas. For anything a runtime declares that no vocabulary covers, use `session.act(action,
52
+ payload)`.
53
+
54
+ The generated part is `commandagi/_generated.py`. Don't edit it: it is regenerated from the schema in
55
+ the CommandAGI monorepo.
56
+
57
+ ## Sessions
58
+
59
+ | | |
60
+ | --- | --- |
61
+ | `cagi.launch(snapshot_id, title=, wait=True, timeout=600)` | a new agentless thread running that snapshot; waits until it declares controls |
62
+ | `cagi.session(thread_id, embodiment_id)` | attach to an embodiment that already exists |
63
+ | `session.frame(fresh=False)` / `observe()` / `observe_array()` / `frames()` | the latest frame, as `{url, channel_id, at}`, image bytes, or a numpy array (`pip install "commandagi[vision]"`); `frames()` yields each new one |
64
+ | `session.describe()` | the live world a sim or robot runtime reports |
65
+ | `session.close()` / `stop()` | stop watching / also stop a world this session launched |
66
+
67
+ Snapshot ids come from `cagi.call("list_snapshots")`, e.g. `simulation/warehouse` or
68
+ `computer/software-engineer`.
69
+
70
+ ## Design in code: `commandagi.design`
71
+
72
+ The same structure-declaring primitives as the TypeScript SDK's `commandagi/design`, producing the same op graph
73
+ (plain JSON). `commandagi.design` itself holds the code-part primitives: CAD (`part`, `box`, `cylinder`, `sketch`,
74
+ `extrude`, `subtract`, `hole`, `linear_pattern` and the rest), EDA (`circuit`, `board`, `component`, `net`, `connect`,
75
+ `footprints`), any graph (`graph`, `node`, `input_`, `code`) and `run_module`. There is no kernel, solver, router or
76
+ renderer; importing `commandagi.design` needs nothing but the standard library.
77
+
78
+ ### Documents: one module per vocabulary
79
+
80
+ Every document a TypeScript file writes in JSX, a Python file writes as calls, element for element. One module per
81
+ vocabulary, one function per tag, named by the tag:
82
+
83
+ | module | documents |
84
+ | --- | --- |
85
+ | `commandagi.design.schematic` | a schematic (`group`) |
86
+ | `commandagi.design.pcb` | a board in code (`board`) |
87
+ | `commandagi.design.threed` | a 3D part or assembly |
88
+ | `commandagi.design.fab` | a machining setup (`cam`), a slicing setup (`slicing`) |
89
+ | `commandagi.design.office` | a workbook, a page, a deck |
90
+ | `commandagi.design.twod` | a drawing, a painting, a photo, a nest |
91
+ | `commandagi.design.media` | a video (clips, `composition(src="Intro.vid.py")`: another video drawn live from its own code, `artboard(src="Poster.draw.py")`: a drawing's page drawn live), a song |
92
+ | `commandagi.design.ontology` | a world, a device definition, a dashboard, a geo project, a node graph |
93
+ | `commandagi.design.business` | a company, an RFC, a case |
94
+ | `commandagi.design.tasks` | a task, a project |
95
+ | `commandagi.design.monitors` | a monitor (`monitor.py`, in the folder of the thing it follows) |
96
+ | `commandagi.design.records` | a contract, a product instance |
97
+ | `commandagi.design.postal` | a letter, a postcard |
98
+ | `commandagi.design.pdf` | a PDF assembled from pages, comments, stamps, signatures, redactions, fields and bookmarks |
99
+
100
+ The rules of a call, the same in every module:
101
+
102
+ - `tag(*children, **attributes)`. The function's name is the tag: `-` becomes `_` (`brush_stroke`), a Python keyword
103
+ gets a trailing `_` (`from_`), the case is kept (`Company`).
104
+ - Children are positional, in order. A tag that holds text takes texts and inline elements:
105
+ `p("The run ", b("passed"), ".")`. A loop is a starred argument: `layer(*[rect(x=i * 10) for i in range(3)])`.
106
+ A leaf takes no children.
107
+ - Attributes are keywords in snake_case of the TypeScript name: `frozen_rows=1` is `frozenRows`, `sch_x=114.3` is
108
+ `schX`. A keyword with an uppercase letter is refused by name.
109
+ - Only the schematic has positional attributes: `resistor("R1", …)`, `group("Divider", …)`, `trace(from, to)`,
110
+ `netlabel(net, connection)`, `unit(part, unit)`.
111
+ - The file's result is `result = <root>(…)` (or `main()`, or `show_object`).
112
+
113
+ ```python
114
+ from commandagi.design.twod import drawing, ellipse, group, layer, rect
115
+
116
+ result = drawing(
117
+ layer(
118
+ rect(x=40, y=40, w=200, h=120, fill="#3b82f6"),
119
+ group(ellipse(cx=500, cy=300, rx=60, ry=60, fill="#f59e0b"), name="Badge"),
120
+ name="Layer 1",
121
+ ),
122
+ name="Poster", width=800, height=600, background="#ffffff",
123
+ )
124
+ ```
125
+
126
+ ```python
127
+ from commandagi.design.schematic import ground, group, netlabel, resistor, trace, voltagesource
128
+
129
+ result = group(
130
+ "Divider",
131
+ voltagesource("V1", voltage="9", sch_x=114.3, sch_y=114.3),
132
+ resistor("R1", resistance="3k", sch_x=114.3, sch_y=88.9, sch_rotation=90),
133
+ ground("#PWR1", sch_x=139.7, sch_y=114.3),
134
+ trace(".V1 > .pos", ".R1 > .pin1"),
135
+ netlabel("OUT", ".R1 > .pin2"),
136
+ )
137
+ ```
138
+
139
+ A graph document (a schematic, a board, a 3D part, a drawing, a deck, a video, a node graph) leaves `run_module` as
140
+ its graph. Any other document (a workbook, a world, a company, a task …) leaves as `document` (`{format, document,
141
+ sources}`) beside an empty graph.
142
+
143
+ ### Where each element is written
144
+
145
+ `run_module` maps the file's calls with Python's `ast` (`commandagi.design.source`). Each element records the call
146
+ that made it. A declaration puts that call where the TypeScript SDK puts a JSX element's source: a node's
147
+ `meta.source`, a node's `meta.sources` by key, a document's `sources` by key. Each one holds the call's span, each
148
+ keyword's span with its literal value or its expression, and how many times the call ran. The whole map rides on the
149
+ graph as `meta.sourceMap`: every call with its arguments, its parent element, its slot among its parent's children,
150
+ and whether it runs in a loop. The editor writes each edit back into the file as the smallest text edit. A keyword
151
+ that is an expression is never replaced with a literal: the edit is refused with its line.
152
+
153
+ ### Code parts and CadQuery
154
+
155
+ `commandagi.design.cadquery` is a CadQuery-style importer: `Workplane("XY").box(…).faces(">Z").workplane()
156
+ .rarray(…).hole(…)` records the same features the primitives declare. It refuses, by name, what needs real topology
157
+ (fillets, edge selectors).
158
+
159
+ A `.py` code part is a script. Its result is `main(**inputs)` if it defines `main`; otherwise what it passed to
160
+ `show_object`; otherwise its `result` variable. `param(name, default, unit=…)` declares an input and reads its value.
161
+ In a CommandAGI editor, a `.py` code node runs under Pyodide in a sandboxed worker, where `import cadquery as cq` is
162
+ this importer (OCCT does not run there). `run_module(source, path, inputs)` is what the sandbox calls. Tests:
163
+ `python -m unittest discover -s tests`.
164
+
165
+ ## The 3D kernel: `commandagi.kernel`
166
+
167
+ `commandagi.design.threed` declares a part and builds nothing. `Kernel` asks the CommandAGI 3D design service
168
+ (OpenCascade) to build it. It uses the standard library only.
169
+
170
+ ```python
171
+ from commandagi.design.threed import box, cylinder, part
172
+ from commandagi.kernel import Kernel
173
+
174
+ plate = part(
175
+ box(id="plate", center=[0, 0, 4], size=[60, 40, 8], operation="new"),
176
+ cylinder(id="hole", center=[0, 0, -1], axis=[0, 0, 1], radius=3, height=10, operation="cut"),
177
+ name="Plate",
178
+ )
179
+ kernel = Kernel() # reads COMMANDAGI_THREED
180
+ seen = kernel.observe(plate)["observation"] # bodies, faces, edges, mass properties; status and failures
181
+ assert seen["status"] == "built", seen["failures"] # "failed": the bodies are built from the features that did not fail
182
+ open("plate.step", "w").write(kernel.export(plate, "step")) # refused (KernelError.failures) when a feature failed
183
+ ```
184
+
185
+ A part is built in the order of its elements: the cut above follows the plate because it is written after it.
186
+ `consumes=[…]` names the features a feature follows instead.
187
+
188
+ One setting says where it runs: `COMMANDAGI_THREED`, the places to try in order, apart by `,` (default `local,cloud`).
189
+ A place where the kernel is not installed is passed over. A refusal of the op itself (a build that is not whole) is
190
+ the answer: it is not asked of the next place. `kernel.last_ran` says where each answer ran (`{"kernel", "build",
191
+ "place"}`); `place` is where the SDK sent the request, so a result made on this computer says `local` and is never a
192
+ cloud result.
193
+
194
+ | place | the kernel | needs |
195
+ | --- | --- | --- |
196
+ | `local` | this computer, offline, no account. `Kernel` starts it and stops it when Python exits; `COMMANDAGI_THREED_URL` names one that runs already (`commandagi threed serve --port 8091`) | Node 22 and, once: `npm install -g commandagi` then `commandagi threed install` (it downloads OpenCascade as WebAssembly, about 130 MB); for a running one, `COMMANDAGI_THREED_TOKEN`, the bearer you started it with |
197
+ | `cloud` | the platform's kernel door (`/kernels/3d/v1/<op>`) | the network and `COMMANDAGI_API_KEY` |
198
+
199
+ A URL in `COMMANDAGI_THREED` is that running service only. Both places speak one contract (`POST <kernel>/v1/<op>`,
200
+ `GET <kernel>/v1/health`), and `kernel.call(op, doc, params)` gives a whole answer (`outputs`, `result`, `ran`).
201
+
202
+ `observe(doc)`, `export(doc, "step" | "iges", bodies=)`, `measure(doc, "distance" | "angle" | "radius" | "closest", a,
203
+ b)`, `measure_all(doc, [{"quantity", "a", "b"}, …])` (many on one build), `interference(doc, clearance=, bodies=)`,
204
+ `drawing(doc, direction=, up=, section=)`, `tessellate(doc, deflection)`. `doc` is what a file declares, its
205
+ graph (`run_module(...)["graph"]`) or the graph's JSON text. An entity is `{"body": "body_0", "kind": "face" | "edge" |
206
+ "vertex" | "body", "index": n}`, with the index as `observe` lists it.
207
+
208
+ - **A build in which a feature failed is not whole.** `observe` answers it with `status: "failed"` and `failures`.
209
+ Every other call refuses it: `KernelError` with `status` 422 and `failures` (each failed feature, with why).
210
+ - **`interference`** compares every pair of bodies: `clashes` (shared volume), `engagements` (shared volume that all
211
+ lies in a declared `thread`: a screw in a tapped hole) and, with `clearance`, `close` (nearer than it). Each body
212
+ has its part's name.
213
+ - **An assembly's files** go with the call: `parts=linked_parts({"bolt.3d.py": bolt, "bracket.step": text})`, each
214
+ under the name its `externalPart` gives.
215
+ - **A STEP file names its parts**: each body is a product of its part's name, an assembly the product they are
216
+ components of.
217
+
218
+ ## Reinforcement learning
219
+
220
+ `commandagi.gym_env.CommandAGIEnv` wraps a sim session as a Gymnasium env (`pip install
221
+ "commandagi[gym]"`), and `commandagi.lerobot` records rollouts as LeRobot datasets (`pip install
222
+ "commandagi[lerobot]"`).
223
+
224
+ ## Environment
225
+
226
+ | variable | meaning |
227
+ | --- | --- |
228
+ | `COMMANDAGI_API_KEY` | your `cagi_…` key. Its scopes decide what every call may do |
229
+ | `COMMANDAGI_BASE_URL` | API origin (default `https://api.commandagi.com`) |
230
+ | `COMMANDAGI_THREAD_ID` | default thread for self-thread methods. Set automatically when your code runs inside the platform |
231
+ | `COMMANDAGI_THREED` | where `Kernel` runs: `local`, `cloud`, both apart by `,` in the order to try (default `local,cloud`), or a running service's URL |
232
+ | `COMMANDAGI_THREED_URL`, `COMMANDAGI_THREED_TOKEN` | a 3D design service that runs already, and its bearer |
233
+
234
+ ## Versions
235
+
236
+ - A release is the tag `v<version>` in this repository and the same version on PyPI. Pin an exact version:
237
+ `commandagi==0.5.0`. A range (`^`, `~=`, `>=`) takes the next release without asking you.
238
+ - The three SDKs (Python, TypeScript, Rust) carry one version number. A vocabulary at version X in one language is
239
+ the vocabulary at version X in the others.
240
+ - A version promises nothing about the next one. CommandAGI keeps no compatibility layers: a release may rename or
241
+ remove a function, an element or an attribute, with no alias and no migration. `CHANGELOG.md` says what changed.
242
+ - So a document belongs with the version that reads it. Keep the pin in the project that holds the documents, and
243
+ change both together. A document you open in a CommandAGI editor is read by the SDK that editor ships.
244
+
245
+ ## Licence
246
+
247
+ MIT (`LICENSE`).