commandagi 0.3.0__py3-none-any.whl
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.
- commandagi/__init__.py +26 -0
- commandagi/agent.py +172 -0
- commandagi/bridge.py +133 -0
- commandagi/client.py +504 -0
- commandagi/gym_env.py +145 -0
- commandagi/lerobot.py +211 -0
- commandagi-0.3.0.dist-info/METADATA +159 -0
- commandagi-0.3.0.dist-info/RECORD +9 -0
- commandagi-0.3.0.dist-info/WHEEL +4 -0
commandagi/lerobot.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Record CommandAGI sim episodes as a LeRobot-style dataset — observation (camera + joint state),
|
|
2
|
+
action, and timestamps per step, written to disk in the LeRobot on-disk layout (parquet per episode
|
|
3
|
+
+ frames + meta/info.json) so it loads into imitation/VLA training pipelines.
|
|
4
|
+
|
|
5
|
+
Two ways to use it:
|
|
6
|
+
|
|
7
|
+
1. Wrap a Gym env — every ``step`` is recorded automatically::
|
|
8
|
+
|
|
9
|
+
from commandagi.gym_env import CommandAGIEnv
|
|
10
|
+
from commandagi.lerobot import RecordingWrapper
|
|
11
|
+
env = RecordingWrapper(CommandAGIEnv(world), root="./datasets/pick", task="pick up the cube")
|
|
12
|
+
obs, _ = env.reset()
|
|
13
|
+
for _ in range(200):
|
|
14
|
+
obs, *_ = env.step(policy(obs))
|
|
15
|
+
env.save() # flush the dataset to ./datasets/pick
|
|
16
|
+
|
|
17
|
+
2. Hand-drive and log directly with :class:`EpisodeRecorder` (e.g. while teleoperating in Sim Studio).
|
|
18
|
+
|
|
19
|
+
This writes the LeRobot v2.1/3.0 directory shape (``meta/info.json``, ``data/chunk-000/episode_*.parquet``,
|
|
20
|
+
``images/observation.image/episode_*/frame_*.jpg``). Requires ``pyarrow`` + ``numpy`` + ``pillow``
|
|
21
|
+
(``pip install "commandagi[lerobot]"``). Stats consolidation (``meta/stats.json``) is left to the
|
|
22
|
+
``lerobot`` tooling; everything needed to compute it is on disk.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import io
|
|
27
|
+
import json
|
|
28
|
+
import time
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Optional
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
import numpy as np
|
|
34
|
+
import pyarrow as pa
|
|
35
|
+
import pyarrow.parquet as pq
|
|
36
|
+
from PIL import Image
|
|
37
|
+
except ImportError as e: # pragma: no cover
|
|
38
|
+
raise ImportError("commandagi.lerobot needs `pyarrow`, `numpy`, `pillow` — pip install 'commandagi[lerobot]'") from e
|
|
39
|
+
|
|
40
|
+
CODEBASE_VERSION = "v2.1"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class EpisodeRecorder:
|
|
44
|
+
"""Accumulates steps for one or more episodes and writes a LeRobot-layout dataset.
|
|
45
|
+
|
|
46
|
+
Each step is ``(state, action, image)`` where ``state``/``action`` are 1-D float arrays and
|
|
47
|
+
``image`` is an HxWx3 uint8 array (optional). Call :meth:`add` per step, :meth:`end_episode` to
|
|
48
|
+
close the current episode, and :meth:`save` to write everything out.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, root: str, *, fps: float = 10.0, task: str = "", image_key: str = "observation.image"):
|
|
52
|
+
self.root = Path(root)
|
|
53
|
+
self.fps = float(fps)
|
|
54
|
+
self.task = task
|
|
55
|
+
self.image_key = image_key
|
|
56
|
+
self._ep: list[dict[str, Any]] = [] # steps in the current episode
|
|
57
|
+
self._episodes: list[list[dict[str, Any]]] = [] # completed episodes
|
|
58
|
+
self._state_dim: Optional[int] = None
|
|
59
|
+
self._action_dim: Optional[int] = None
|
|
60
|
+
self._img_shape: Optional[tuple[int, int, int]] = None
|
|
61
|
+
self._t0 = time.time()
|
|
62
|
+
|
|
63
|
+
def start_episode(self) -> None:
|
|
64
|
+
if self._ep:
|
|
65
|
+
self.end_episode()
|
|
66
|
+
self._ep = []
|
|
67
|
+
self._t0 = time.time()
|
|
68
|
+
|
|
69
|
+
def add(self, state, action, image=None, *, reward: float = 0.0) -> None:
|
|
70
|
+
state = np.asarray(state, dtype=np.float32).reshape(-1)
|
|
71
|
+
action = np.asarray(action, dtype=np.float32).reshape(-1)
|
|
72
|
+
self._state_dim = self._state_dim or state.shape[0]
|
|
73
|
+
self._action_dim = self._action_dim or action.shape[0]
|
|
74
|
+
step: dict[str, Any] = {
|
|
75
|
+
"observation.state": state,
|
|
76
|
+
"action": action,
|
|
77
|
+
"timestamp": time.time() - self._t0,
|
|
78
|
+
"reward": float(reward),
|
|
79
|
+
}
|
|
80
|
+
if image is not None:
|
|
81
|
+
img = np.asarray(image, dtype=np.uint8)
|
|
82
|
+
self._img_shape = self._img_shape or tuple(img.shape) # type: ignore[assignment]
|
|
83
|
+
step["_image"] = img
|
|
84
|
+
self._ep.append(step)
|
|
85
|
+
|
|
86
|
+
def end_episode(self) -> None:
|
|
87
|
+
if self._ep:
|
|
88
|
+
self._episodes.append(self._ep)
|
|
89
|
+
self._ep = []
|
|
90
|
+
|
|
91
|
+
def save(self) -> Path:
|
|
92
|
+
"""Write the dataset (all completed episodes + the current one) to disk; returns the root."""
|
|
93
|
+
self.end_episode()
|
|
94
|
+
if not self._episodes:
|
|
95
|
+
raise ValueError("nothing to save — record at least one step")
|
|
96
|
+
(self.root / "data" / "chunk-000").mkdir(parents=True, exist_ok=True)
|
|
97
|
+
(self.root / "meta").mkdir(parents=True, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
total_frames = 0
|
|
100
|
+
global_index = 0
|
|
101
|
+
for ep_idx, steps in enumerate(self._episodes):
|
|
102
|
+
cols: dict[str, list] = {
|
|
103
|
+
"observation.state": [], "action": [], "timestamp": [], "reward": [],
|
|
104
|
+
"frame_index": [], "episode_index": [], "index": [], "next.done": [],
|
|
105
|
+
}
|
|
106
|
+
has_image = any("_image" in s for s in steps)
|
|
107
|
+
if has_image:
|
|
108
|
+
cols[self.image_key] = []
|
|
109
|
+
img_dir = self.root / "images" / self.image_key / f"episode_{ep_idx:06d}"
|
|
110
|
+
img_dir.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
for f_idx, s in enumerate(steps):
|
|
112
|
+
cols["observation.state"].append(s["observation.state"].tolist())
|
|
113
|
+
cols["action"].append(s["action"].tolist())
|
|
114
|
+
cols["timestamp"].append(float(s["timestamp"]))
|
|
115
|
+
cols["reward"].append(float(s["reward"]))
|
|
116
|
+
cols["frame_index"].append(f_idx)
|
|
117
|
+
cols["episode_index"].append(ep_idx)
|
|
118
|
+
cols["index"].append(global_index)
|
|
119
|
+
cols["next.done"].append(f_idx == len(steps) - 1)
|
|
120
|
+
if has_image:
|
|
121
|
+
rel = f"images/{self.image_key}/episode_{ep_idx:06d}/frame_{f_idx:06d}.jpg"
|
|
122
|
+
if "_image" in s:
|
|
123
|
+
Image.fromarray(s["_image"]).save(self.root / rel, format="JPEG", quality=90)
|
|
124
|
+
cols[self.image_key].append(rel)
|
|
125
|
+
global_index += 1
|
|
126
|
+
total_frames += len(steps)
|
|
127
|
+
pq.write_table(pa.table(cols), self.root / "data" / "chunk-000" / f"episode_{ep_idx:06d}.parquet")
|
|
128
|
+
|
|
129
|
+
self._write_info(total_frames)
|
|
130
|
+
self._write_episodes_meta()
|
|
131
|
+
self._write_tasks_meta()
|
|
132
|
+
return self.root
|
|
133
|
+
|
|
134
|
+
def _features(self) -> dict:
|
|
135
|
+
feats: dict[str, Any] = {
|
|
136
|
+
"observation.state": {"dtype": "float32", "shape": [self._state_dim or 0], "names": None},
|
|
137
|
+
"action": {"dtype": "float32", "shape": [self._action_dim or 0], "names": None},
|
|
138
|
+
"timestamp": {"dtype": "float32", "shape": [1], "names": None},
|
|
139
|
+
"reward": {"dtype": "float32", "shape": [1], "names": None},
|
|
140
|
+
"frame_index": {"dtype": "int64", "shape": [1], "names": None},
|
|
141
|
+
"episode_index": {"dtype": "int64", "shape": [1], "names": None},
|
|
142
|
+
"index": {"dtype": "int64", "shape": [1], "names": None},
|
|
143
|
+
"next.done": {"dtype": "bool", "shape": [1], "names": None},
|
|
144
|
+
}
|
|
145
|
+
if self._img_shape is not None:
|
|
146
|
+
feats[self.image_key] = {"dtype": "image", "shape": list(self._img_shape), "names": ["height", "width", "channel"]}
|
|
147
|
+
return feats
|
|
148
|
+
|
|
149
|
+
def _write_info(self, total_frames: int) -> None:
|
|
150
|
+
info = {
|
|
151
|
+
"codebase_version": CODEBASE_VERSION,
|
|
152
|
+
"robot_type": "commandagi-sim",
|
|
153
|
+
"total_episodes": len(self._episodes),
|
|
154
|
+
"total_frames": total_frames,
|
|
155
|
+
"total_tasks": 1,
|
|
156
|
+
"total_chunks": 1,
|
|
157
|
+
"chunks_size": 1000,
|
|
158
|
+
"fps": self.fps,
|
|
159
|
+
"splits": {"train": f"0:{len(self._episodes)}"},
|
|
160
|
+
"data_path": "data/chunk-{episode_chunk:03d}/episode_{episode_index:06d}.parquet",
|
|
161
|
+
"features": self._features(),
|
|
162
|
+
}
|
|
163
|
+
(self.root / "meta" / "info.json").write_text(json.dumps(info, indent=2))
|
|
164
|
+
|
|
165
|
+
def _write_episodes_meta(self) -> None:
|
|
166
|
+
lines = []
|
|
167
|
+
for ep_idx, steps in enumerate(self._episodes):
|
|
168
|
+
lines.append(json.dumps({"episode_index": ep_idx, "tasks": [self.task] if self.task else [], "length": len(steps)}))
|
|
169
|
+
(self.root / "meta" / "episodes.jsonl").write_text("\n".join(lines) + "\n")
|
|
170
|
+
|
|
171
|
+
def _write_tasks_meta(self) -> None:
|
|
172
|
+
(self.root / "meta" / "tasks.jsonl").write_text(json.dumps({"task_index": 0, "task": self.task}) + "\n")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class RecordingWrapper:
|
|
176
|
+
"""Wrap a :class:`commandagi.gym_env.CommandAGIEnv` so every ``reset``/``step`` is logged.
|
|
177
|
+
|
|
178
|
+
The observation's ``qpos``+``qvel`` become ``observation.state``; ``image`` (if present) is the
|
|
179
|
+
camera frame; the action passed to ``step`` is recorded verbatim. Call :meth:`save` when done.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self, env, root: str, *, task: str = "", fps: Optional[float] = None):
|
|
183
|
+
self.env = env
|
|
184
|
+
self.recorder = EpisodeRecorder(root, fps=fps or (1.0 / getattr(env, "control_dt", 0.1)), task=task)
|
|
185
|
+
self.action_space = env.action_space
|
|
186
|
+
self.observation_space = env.observation_space
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def _state(obs: dict) -> np.ndarray:
|
|
190
|
+
return np.concatenate([np.asarray(obs.get("qpos", []), dtype=np.float32), np.asarray(obs.get("qvel", []), dtype=np.float32)])
|
|
191
|
+
|
|
192
|
+
def reset(self, **kw):
|
|
193
|
+
obs, info = self.env.reset(**kw)
|
|
194
|
+
self.recorder.start_episode()
|
|
195
|
+
self._last_obs = obs
|
|
196
|
+
return obs, info
|
|
197
|
+
|
|
198
|
+
def step(self, action):
|
|
199
|
+
obs, reward, terminated, truncated, info = self.env.step(action)
|
|
200
|
+
# Record the transition: the observation we acted on + the action + the reward received.
|
|
201
|
+
self.recorder.add(self._state(self._last_obs), action, self._last_obs.get("image"), reward=reward)
|
|
202
|
+
self._last_obs = obs
|
|
203
|
+
if terminated or truncated:
|
|
204
|
+
self.recorder.end_episode()
|
|
205
|
+
return obs, reward, terminated, truncated, info
|
|
206
|
+
|
|
207
|
+
def save(self):
|
|
208
|
+
return self.recorder.save()
|
|
209
|
+
|
|
210
|
+
def close(self):
|
|
211
|
+
self.env.close()
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: commandagi
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python SDK for CommandAGI — launch cloud computers and 3D robot simulations and control them programmatically.
|
|
5
|
+
Project-URL: Homepage, https://commandagi.com
|
|
6
|
+
Project-URL: Documentation, https://github.com/CommandAGI/commandagi/blob/main/docs/ROBOT_DEVELOPER_API.md
|
|
7
|
+
Author: CommandAGI
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: agents,computer-use,physics,reinforcement-learning,robotics,simulation
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: requests>=2.28
|
|
12
|
+
Requires-Dist: websocket-client>=1.5
|
|
13
|
+
Provides-Extra: gym
|
|
14
|
+
Requires-Dist: gymnasium>=0.29; extra == 'gym'
|
|
15
|
+
Requires-Dist: numpy>=1.23; extra == 'gym'
|
|
16
|
+
Requires-Dist: pillow>=9.0; extra == 'gym'
|
|
17
|
+
Provides-Extra: lerobot
|
|
18
|
+
Requires-Dist: numpy>=1.23; extra == 'lerobot'
|
|
19
|
+
Requires-Dist: pillow>=9.0; extra == 'lerobot'
|
|
20
|
+
Requires-Dist: pyarrow>=14.0; extra == 'lerobot'
|
|
21
|
+
Provides-Extra: vision
|
|
22
|
+
Requires-Dist: numpy>=1.23; extra == 'vision'
|
|
23
|
+
Requires-Dist: pillow>=9.0; extra == 'vision'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# CommandAGI Python SDK
|
|
27
|
+
|
|
28
|
+
Launch real cloud **computers** and **3D robot simulations** and control them from Python — stream
|
|
29
|
+
the robot's camera, send actions, run episodes. No agent required: you drive.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install commandagi # + `pip install commandagi[vision]` for numpy frames
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Robot testing in a 3D world
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from commandagi import CommandAGI
|
|
39
|
+
|
|
40
|
+
cagi = CommandAGI(api_key="cagi_...") # or set COMMANDAGI_API_KEY
|
|
41
|
+
|
|
42
|
+
with cagi.launch("simulation/warehouse") as world:
|
|
43
|
+
obs = world.observe() # JPEG bytes from the robot's head camera
|
|
44
|
+
for _ in range(20):
|
|
45
|
+
obs = world.step("turn", dir="left") # act, then get the next frame
|
|
46
|
+
world.reset() # robot back to the episode start
|
|
47
|
+
# leaving the block stops the world and releases the cloud VM
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`launch()` provisions a real GCE VM running a 3D physics world, waits until it's streaming, and gives
|
|
51
|
+
you a `World`. Built-in scenes: `simulation/warehouse`, `simulation/house-on-fire`,
|
|
52
|
+
`simulation/school` (a mobile robot in each).
|
|
53
|
+
|
|
54
|
+
### The control vocabulary
|
|
55
|
+
|
|
56
|
+
| World kind | actions |
|
|
57
|
+
| ----------- | ------------------------------------------------------------------------- |
|
|
58
|
+
| robot / sim | `move(speed)`, `back(speed)`, `turn(dir, rate)`, `stop`, `reset` |
|
|
59
|
+
| computer | `click(x, y)`, `type(text)`, `key(key)`, `move(x, y)`, `scroll(x, y, dy)` |
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
world.act("move", speed=0.8) # fire-and-forget
|
|
63
|
+
obs = world.step("move", speed=0.8) # act + return the next observation (settles 0.8s)
|
|
64
|
+
obs = world.observe(fresh=True) # wait for a frame newer than now
|
|
65
|
+
arr = world.observe_array() # HxWx3 uint8 numpy (needs commandagi[vision])
|
|
66
|
+
for frame in world.stream(): # live generator of frames
|
|
67
|
+
...
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Simulator instances (morphology-agnostic robots)
|
|
71
|
+
|
|
72
|
+
The simulator is **morphology-agnostic**: a robot is just a set of named actuators and sites, driven
|
|
73
|
+
by one small **generic** control vocabulary — no `drive`/`gripper`, just `ctrl` / `actuator` / `ik`
|
|
74
|
+
/ `trajectory` / `describe`. Spin up your own instance, choose who can watch or add robots, and
|
|
75
|
+
populate it with one or many robots on a single session.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from commandagi import CommandAGI
|
|
79
|
+
|
|
80
|
+
cagi = CommandAGI(api_key="cagi_...")
|
|
81
|
+
|
|
82
|
+
sim = cagi.launch_sim(scene="the-matrix", visibility="private", title="demo")
|
|
83
|
+
print("instance:", sim.id, "session:", sim.session_id)
|
|
84
|
+
|
|
85
|
+
# Who can do what:
|
|
86
|
+
sim.grant("user_teammate", capability="viewer") # may watch the stream
|
|
87
|
+
sim.grant("user_buddy", capability="operator") # may also launch robots into the world
|
|
88
|
+
|
|
89
|
+
# Add robots (each becomes a embodiment on sim.session_id):
|
|
90
|
+
rover = sim.join_robot(kind="rover") # -> {robotId, embodimentId, sessionId}
|
|
91
|
+
arm = sim.join_robot(kind="arm")
|
|
92
|
+
|
|
93
|
+
cagi.sims() # list instances visible to you
|
|
94
|
+
cagi.get_sim(sim.id) # rehydrate a SimInstance
|
|
95
|
+
sim.view() # instance metadata + attached embodiments
|
|
96
|
+
sim.stop() # release it (or use `with cagi.launch_sim(...) as sim:`)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Generic robot control
|
|
100
|
+
|
|
101
|
+
`World` exposes the morphology-agnostic vocabulary (address a specific robot in a multi-robot embodiment
|
|
102
|
+
with `robot_id`):
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
world = cagi.connect_world(sim.session_id, rover["embodimentId"], kind="robot")
|
|
106
|
+
|
|
107
|
+
desc = world.describe() # actuators, sites, objects (best-effort)
|
|
108
|
+
world.ctrl({"left_wheel": 1.0, "right_wheel": 1.0}) # set actuator targets directly
|
|
109
|
+
world.actuator("left_wheel", 0.0) # one named actuator
|
|
110
|
+
world.ik(target=[0.3, 0.0, 0.4], site="ee", relative=False) # inverse kinematics to a point
|
|
111
|
+
world.trajectory([{"left_wheel": 1.0}, {"left_wheel": 0.0}]) # follow waypoints
|
|
112
|
+
frame = world.observe() # camera frame, as before
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> `describe()` is best-effort: the runtime answers a describe request over the session channel, but
|
|
116
|
+
> there is currently no synchronous describe HTTP endpoint — if nothing echoes back it returns `{}`,
|
|
117
|
+
> and the autonomous agent also obtains descriptions server-side via `/agent/robot-act`.
|
|
118
|
+
|
|
119
|
+
## Autonomous agents over many robots
|
|
120
|
+
|
|
121
|
+
One agent can drive **many** robots in a single session. `RobotAgent` loops perceive → reason → act:
|
|
122
|
+
each step it gathers every embodiment's description + a fresh camera frame, calls `/agent/robot-act` with
|
|
123
|
+
all embodiments, and applies the returned tool calls (`ctrl`/`actuator`/`ik`/`trajectory`) back to the
|
|
124
|
+
addressed embodiment — until a `done` call or `max_steps`.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from commandagi import CommandAGI
|
|
128
|
+
from commandagi.agent import RobotAgent, attach_robots
|
|
129
|
+
|
|
130
|
+
cagi = CommandAGI(api_key="cagi_...")
|
|
131
|
+
sim = cagi.launch_sim(scene="warehouse")
|
|
132
|
+
|
|
133
|
+
embodiments = attach_robots(cagi, sim, kinds=["rover", "arm"]) # two robots, one session
|
|
134
|
+
|
|
135
|
+
with RobotAgent(cagi, sim.session_id, embodiments, goal="bring the red box to the arm") as agent:
|
|
136
|
+
result = agent.run(max_steps=25) # blocks; prints reasoning + applied calls each step
|
|
137
|
+
print("done:", result["done"], "in", result["steps"], "steps")
|
|
138
|
+
|
|
139
|
+
sim.stop()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
A full runnable script lives in [`examples/sim_agent.py`](examples/sim_agent.py).
|
|
143
|
+
|
|
144
|
+
## Computers too
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
with cagi.launch("computer/software-engineer") as pc:
|
|
148
|
+
pc.act("type", text="hello")
|
|
149
|
+
pc.act("key", key="Return")
|
|
150
|
+
screenshot = pc.observe() # PNG bytes of the live Ubuntu desktop
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Auth
|
|
154
|
+
|
|
155
|
+
Create an API key with an `operator` scope (dashboard → API keys, or `POST /me/api-keys`). Pass it to
|
|
156
|
+
`CommandAGI(api_key=...)` or set `COMMANDAGI_API_KEY`. Point at another environment with
|
|
157
|
+
`COMMANDAGI_BASE_URL` (e.g. `https://api-dev.commandagi.com`).
|
|
158
|
+
|
|
159
|
+
Full HTTP + WebSocket reference (what the SDK wraps): [`docs/platform/ROBOT_DEVELOPER_API.md`](../../docs/platform/ROBOT_DEVELOPER_API.md).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
commandagi/__init__.py,sha256=aF679wR7Fa0-7Xg8swPgQZ0PKB7LlyMgiwEiWUWknw0,697
|
|
2
|
+
commandagi/agent.py,sha256=hmnyWYQ9Ma7gZYQVNmXQKv-3YC70FQs_3KjpWXkSaq8,7737
|
|
3
|
+
commandagi/bridge.py,sha256=yS225aHQtYTsLEAz8nAxwG-qqUpidtqcbbh-deflwfU,5260
|
|
4
|
+
commandagi/client.py,sha256=LeWwaqe7zjX86Onr_OtHCgxgNiIryxEPIu1RAugJHpQ,24608
|
|
5
|
+
commandagi/gym_env.py,sha256=a9jKDGr5qI-e49E5Mm-EvGMRlTRvPQKErYsg2yCWv5s,6405
|
|
6
|
+
commandagi/lerobot.py,sha256=b-hP3MXnGLBuOoE0DsWvS_iLYAx3-V3oojttHRSKJdc,9488
|
|
7
|
+
commandagi-0.3.0.dist-info/METADATA,sha256=rAVYv4ChUdWxbutZ2DP9dokFJGZCe0LtmBF6qGySobY,6860
|
|
8
|
+
commandagi-0.3.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
9
|
+
commandagi-0.3.0.dist-info/RECORD,,
|