foodforthought-cli 0.2.0__py3-none-any.whl → 0.2.3__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.
- ate/__init__.py +1 -1
- ate/bridge_server.py +622 -0
- ate/cli.py +2625 -242
- ate/compatibility.py +580 -0
- ate/generators/__init__.py +19 -0
- ate/generators/docker_generator.py +461 -0
- ate/generators/hardware_config.py +469 -0
- ate/generators/ros2_generator.py +617 -0
- ate/generators/skill_generator.py +783 -0
- ate/marketplace.py +524 -0
- ate/mcp_server.py +2424 -148
- ate/primitives.py +1016 -0
- ate/robot_setup.py +2222 -0
- ate/skill_schema.py +537 -0
- ate/telemetry/__init__.py +33 -0
- ate/telemetry/cli.py +455 -0
- ate/telemetry/collector.py +444 -0
- ate/telemetry/context.py +318 -0
- ate/telemetry/fleet_agent.py +419 -0
- ate/telemetry/formats/__init__.py +18 -0
- ate/telemetry/formats/hdf5_serializer.py +503 -0
- ate/telemetry/formats/mcap_serializer.py +457 -0
- ate/telemetry/types.py +334 -0
- foodforthought_cli-0.2.3.dist-info/METADATA +300 -0
- foodforthought_cli-0.2.3.dist-info/RECORD +44 -0
- foodforthought_cli-0.2.3.dist-info/top_level.txt +6 -0
- mechdog_labeled/__init__.py +3 -0
- mechdog_labeled/primitives.py +113 -0
- mechdog_labeled/servo_map.py +209 -0
- mechdog_output/__init__.py +3 -0
- mechdog_output/primitives.py +59 -0
- mechdog_output/servo_map.py +203 -0
- test_autodetect/__init__.py +3 -0
- test_autodetect/primitives.py +113 -0
- test_autodetect/servo_map.py +209 -0
- test_full_auto/__init__.py +3 -0
- test_full_auto/primitives.py +113 -0
- test_full_auto/servo_map.py +209 -0
- test_smart_detect/__init__.py +3 -0
- test_smart_detect/primitives.py +113 -0
- test_smart_detect/servo_map.py +209 -0
- foodforthought_cli-0.2.0.dist-info/METADATA +0 -151
- foodforthought_cli-0.2.0.dist-info/RECORD +0 -9
- foodforthought_cli-0.2.0.dist-info/top_level.txt +0 -1
- {foodforthought_cli-0.2.0.dist-info → foodforthought_cli-0.2.3.dist-info}/WHEEL +0 -0
- {foodforthought_cli-0.2.0.dist-info → foodforthought_cli-0.2.3.dist-info}/entry_points.txt +0 -0
ate/telemetry/types.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telemetry data types for FoodforThought
|
|
3
|
+
|
|
4
|
+
Defines the core data structures for trajectory recording, sensor data,
|
|
5
|
+
and execution events.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Dict, List, Optional, Any, Union
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TelemetrySource(str, Enum):
|
|
15
|
+
"""Source of telemetry data"""
|
|
16
|
+
SIMULATION = "simulation"
|
|
17
|
+
HARDWARE = "hardware"
|
|
18
|
+
FLEET = "fleet"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EventType(str, Enum):
|
|
22
|
+
"""Types of execution events"""
|
|
23
|
+
SKILL_START = "skill_start"
|
|
24
|
+
SKILL_END = "skill_end"
|
|
25
|
+
CONTACT = "contact"
|
|
26
|
+
ERROR = "error"
|
|
27
|
+
RECOVERY = "recovery"
|
|
28
|
+
USER_INTERVENTION = "user_intervention"
|
|
29
|
+
WAYPOINT_REACHED = "waypoint_reached"
|
|
30
|
+
GRASP = "grasp"
|
|
31
|
+
RELEASE = "release"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SensorType(str, Enum):
|
|
35
|
+
"""Types of sensor readings"""
|
|
36
|
+
FORCE = "force"
|
|
37
|
+
TORQUE = "torque"
|
|
38
|
+
IMU = "imu"
|
|
39
|
+
CAMERA = "camera"
|
|
40
|
+
LIDAR = "lidar"
|
|
41
|
+
PROXIMITY = "proximity"
|
|
42
|
+
TEMPERATURE = "temperature"
|
|
43
|
+
CURRENT = "current"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Vector3:
|
|
48
|
+
"""3D vector for positions and forces"""
|
|
49
|
+
x: float = 0.0
|
|
50
|
+
y: float = 0.0
|
|
51
|
+
z: float = 0.0
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> Dict[str, float]:
|
|
54
|
+
return {"x": self.x, "y": self.y, "z": self.z}
|
|
55
|
+
|
|
56
|
+
def to_list(self) -> List[float]:
|
|
57
|
+
return [self.x, self.y, self.z]
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_list(cls, data: List[float]) -> "Vector3":
|
|
61
|
+
return cls(x=data[0], y=data[1], z=data[2] if len(data) > 2 else 0.0)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class Quaternion:
|
|
66
|
+
"""Quaternion for orientations (w, x, y, z)"""
|
|
67
|
+
x: float = 0.0
|
|
68
|
+
y: float = 0.0
|
|
69
|
+
z: float = 0.0
|
|
70
|
+
w: float = 1.0
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> Dict[str, float]:
|
|
73
|
+
return {"x": self.x, "y": self.y, "z": self.z, "w": self.w}
|
|
74
|
+
|
|
75
|
+
def to_list(self) -> List[float]:
|
|
76
|
+
return [self.x, self.y, self.z, self.w]
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_list(cls, data: List[float]) -> "Quaternion":
|
|
80
|
+
return cls(x=data[0], y=data[1], z=data[2], w=data[3])
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class Pose:
|
|
85
|
+
"""6-DOF pose with position and orientation"""
|
|
86
|
+
position: Vector3 = field(default_factory=Vector3)
|
|
87
|
+
orientation: Quaternion = field(default_factory=Quaternion)
|
|
88
|
+
|
|
89
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
90
|
+
return {
|
|
91
|
+
"position": self.position.to_dict(),
|
|
92
|
+
"orientation": self.orientation.to_dict(),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
def to_flat_list(self) -> List[float]:
|
|
96
|
+
"""Returns [x, y, z, qx, qy, qz, qw]"""
|
|
97
|
+
return self.position.to_list() + self.orientation.to_list()
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Pose":
|
|
101
|
+
pos = data.get("position", {})
|
|
102
|
+
ori = data.get("orientation", {})
|
|
103
|
+
return cls(
|
|
104
|
+
position=Vector3(
|
|
105
|
+
x=pos.get("x", 0),
|
|
106
|
+
y=pos.get("y", 0),
|
|
107
|
+
z=pos.get("z", 0)
|
|
108
|
+
),
|
|
109
|
+
orientation=Quaternion(
|
|
110
|
+
x=ori.get("x", 0),
|
|
111
|
+
y=ori.get("y", 0),
|
|
112
|
+
z=ori.get("z", 0),
|
|
113
|
+
w=ori.get("w", 1)
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass
|
|
119
|
+
class Contact:
|
|
120
|
+
"""Contact information between robot and environment"""
|
|
121
|
+
body1: str # Name of first body in contact
|
|
122
|
+
body2: str # Name of second body in contact
|
|
123
|
+
position: Vector3 = field(default_factory=Vector3)
|
|
124
|
+
normal: Vector3 = field(default_factory=Vector3)
|
|
125
|
+
force: float = 0.0 # Contact force magnitude
|
|
126
|
+
penetration: float = 0.0
|
|
127
|
+
|
|
128
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
129
|
+
return {
|
|
130
|
+
"body1": self.body1,
|
|
131
|
+
"body2": self.body2,
|
|
132
|
+
"position": self.position.to_dict(),
|
|
133
|
+
"normal": self.normal.to_dict(),
|
|
134
|
+
"force": self.force,
|
|
135
|
+
"penetration": self.penetration,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class SensorReading:
|
|
141
|
+
"""Individual sensor reading"""
|
|
142
|
+
timestamp: float # Seconds from recording start
|
|
143
|
+
sensor_id: str
|
|
144
|
+
sensor_type: SensorType
|
|
145
|
+
data: Union[List[float], bytes] # Raw sensor data or numeric values
|
|
146
|
+
|
|
147
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
148
|
+
return {
|
|
149
|
+
"timestamp": self.timestamp,
|
|
150
|
+
"sensorId": self.sensor_id,
|
|
151
|
+
"sensorType": self.sensor_type.value if isinstance(self.sensor_type, SensorType) else self.sensor_type,
|
|
152
|
+
"data": list(self.data) if isinstance(self.data, (list, tuple)) else None,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass
|
|
157
|
+
class TrajectoryFrame:
|
|
158
|
+
"""Single frame of trajectory data"""
|
|
159
|
+
timestamp: float # Seconds from recording start
|
|
160
|
+
joint_positions: Dict[str, float] = field(default_factory=dict)
|
|
161
|
+
joint_velocities: Dict[str, float] = field(default_factory=dict)
|
|
162
|
+
joint_torques: Dict[str, float] = field(default_factory=dict)
|
|
163
|
+
joint_accelerations: Dict[str, float] = field(default_factory=dict)
|
|
164
|
+
end_effector_pose: Optional[Pose] = None
|
|
165
|
+
contacts: List[Contact] = field(default_factory=list)
|
|
166
|
+
sensor_readings: Dict[str, float] = field(default_factory=dict)
|
|
167
|
+
control_inputs: Dict[str, float] = field(default_factory=dict)
|
|
168
|
+
|
|
169
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
170
|
+
return {
|
|
171
|
+
"timestamp": self.timestamp,
|
|
172
|
+
"jointPositions": self.joint_positions,
|
|
173
|
+
"jointVelocities": self.joint_velocities,
|
|
174
|
+
"jointTorques": self.joint_torques,
|
|
175
|
+
"jointAccelerations": self.joint_accelerations,
|
|
176
|
+
"endEffectorPose": self.end_effector_pose.to_dict() if self.end_effector_pose else None,
|
|
177
|
+
"contacts": [c.to_dict() for c in self.contacts],
|
|
178
|
+
"sensorReadings": self.sensor_readings,
|
|
179
|
+
"controlInputs": self.control_inputs,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@dataclass
|
|
184
|
+
class DomainRandomizationConfig:
|
|
185
|
+
"""Configuration for domain randomization applied during recording"""
|
|
186
|
+
friction_range: Optional[tuple] = None
|
|
187
|
+
mass_range: Optional[tuple] = None
|
|
188
|
+
damping_range: Optional[tuple] = None
|
|
189
|
+
noise_scale: float = 0.0
|
|
190
|
+
visual_variations: bool = False
|
|
191
|
+
|
|
192
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
193
|
+
return {
|
|
194
|
+
"frictionRange": list(self.friction_range) if self.friction_range else None,
|
|
195
|
+
"massRange": list(self.mass_range) if self.mass_range else None,
|
|
196
|
+
"dampingRange": list(self.damping_range) if self.damping_range else None,
|
|
197
|
+
"noiseScale": self.noise_scale,
|
|
198
|
+
"visualVariations": self.visual_variations,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class TrajectoryMetadata:
|
|
204
|
+
"""Metadata about a trajectory recording"""
|
|
205
|
+
duration: float = 0.0 # Total duration in seconds
|
|
206
|
+
frame_rate: float = 0.0 # Frames per second
|
|
207
|
+
total_frames: int = 0
|
|
208
|
+
skill_params: Optional[Dict[str, Any]] = None
|
|
209
|
+
environment_id: Optional[str] = None
|
|
210
|
+
domain_randomization: Optional[DomainRandomizationConfig] = None
|
|
211
|
+
robot_version: Optional[str] = None
|
|
212
|
+
urdf_hash: Optional[str] = None # Hash of URDF for reproducibility
|
|
213
|
+
joint_names: List[str] = field(default_factory=list)
|
|
214
|
+
tags: List[str] = field(default_factory=list)
|
|
215
|
+
|
|
216
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
217
|
+
return {
|
|
218
|
+
"duration": self.duration,
|
|
219
|
+
"frameRate": self.frame_rate,
|
|
220
|
+
"totalFrames": self.total_frames,
|
|
221
|
+
"skillParams": self.skill_params,
|
|
222
|
+
"environmentId": self.environment_id,
|
|
223
|
+
"domainRandomization": self.domain_randomization.to_dict() if self.domain_randomization else None,
|
|
224
|
+
"robotVersion": self.robot_version,
|
|
225
|
+
"urdfHash": self.urdf_hash,
|
|
226
|
+
"jointNames": self.joint_names,
|
|
227
|
+
"tags": self.tags,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass
|
|
232
|
+
class TrajectoryRecording:
|
|
233
|
+
"""Complete trajectory recording with frames and metadata"""
|
|
234
|
+
id: str
|
|
235
|
+
robot_id: str
|
|
236
|
+
skill_id: Optional[str] = None
|
|
237
|
+
source: TelemetrySource = TelemetrySource.HARDWARE
|
|
238
|
+
start_time: Optional[datetime] = None
|
|
239
|
+
end_time: Optional[datetime] = None
|
|
240
|
+
success: bool = True
|
|
241
|
+
frames: List[TrajectoryFrame] = field(default_factory=list)
|
|
242
|
+
metadata: TrajectoryMetadata = field(default_factory=TrajectoryMetadata)
|
|
243
|
+
events: List["ExecutionEvent"] = field(default_factory=list)
|
|
244
|
+
|
|
245
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
246
|
+
return {
|
|
247
|
+
"id": self.id,
|
|
248
|
+
"robotId": self.robot_id,
|
|
249
|
+
"skillId": self.skill_id,
|
|
250
|
+
"source": self.source.value if isinstance(self.source, TelemetrySource) else self.source,
|
|
251
|
+
"startTime": self.start_time.isoformat() if self.start_time else None,
|
|
252
|
+
"endTime": self.end_time.isoformat() if self.end_time else None,
|
|
253
|
+
"success": self.success,
|
|
254
|
+
"metadata": self.metadata.to_dict(),
|
|
255
|
+
"frameCount": len(self.frames),
|
|
256
|
+
"eventCount": len(self.events),
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class ExecutionEvent:
|
|
262
|
+
"""Event that occurred during skill execution"""
|
|
263
|
+
timestamp: float # Seconds from recording start
|
|
264
|
+
event_type: EventType
|
|
265
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
266
|
+
|
|
267
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
268
|
+
return {
|
|
269
|
+
"timestamp": self.timestamp,
|
|
270
|
+
"eventType": self.event_type.value if isinstance(self.event_type, EventType) else self.event_type,
|
|
271
|
+
"data": self.data,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass
|
|
276
|
+
class TelemetryBuffer:
|
|
277
|
+
"""Ring buffer for efficient telemetry storage"""
|
|
278
|
+
max_size: int = 36000 # 10 minutes at 60Hz
|
|
279
|
+
_frames: List[TrajectoryFrame] = field(default_factory=list, repr=False)
|
|
280
|
+
_events: List[ExecutionEvent] = field(default_factory=list, repr=False)
|
|
281
|
+
_head: int = 0
|
|
282
|
+
_count: int = 0
|
|
283
|
+
|
|
284
|
+
def add(self, frame: TrajectoryFrame) -> None:
|
|
285
|
+
"""Add a frame to the buffer (ring buffer behavior)"""
|
|
286
|
+
if len(self._frames) < self.max_size:
|
|
287
|
+
self._frames.append(frame)
|
|
288
|
+
else:
|
|
289
|
+
self._frames[self._head] = frame
|
|
290
|
+
self._head = (self._head + 1) % self.max_size
|
|
291
|
+
self._count = min(self._count + 1, self.max_size)
|
|
292
|
+
|
|
293
|
+
def add_event(self, event: ExecutionEvent) -> None:
|
|
294
|
+
"""Add an event (no ring buffer - events are rare)"""
|
|
295
|
+
self._events.append(event)
|
|
296
|
+
|
|
297
|
+
def get_all_frames(self) -> List[TrajectoryFrame]:
|
|
298
|
+
"""Get all frames in chronological order"""
|
|
299
|
+
if len(self._frames) < self.max_size:
|
|
300
|
+
return self._frames[:]
|
|
301
|
+
# Ring buffer is full, need to reorder
|
|
302
|
+
return self._frames[self._head:] + self._frames[:self._head]
|
|
303
|
+
|
|
304
|
+
def get_all_events(self) -> List[ExecutionEvent]:
|
|
305
|
+
"""Get all events"""
|
|
306
|
+
return self._events[:]
|
|
307
|
+
|
|
308
|
+
def flush(self) -> tuple:
|
|
309
|
+
"""Return all data and clear buffer"""
|
|
310
|
+
frames = self.get_all_frames()
|
|
311
|
+
events = self.get_all_events()
|
|
312
|
+
self.clear()
|
|
313
|
+
return frames, events
|
|
314
|
+
|
|
315
|
+
def clear(self) -> None:
|
|
316
|
+
"""Clear the buffer"""
|
|
317
|
+
self._frames = []
|
|
318
|
+
self._events = []
|
|
319
|
+
self._head = 0
|
|
320
|
+
self._count = 0
|
|
321
|
+
|
|
322
|
+
def is_empty(self) -> bool:
|
|
323
|
+
"""Check if buffer is empty"""
|
|
324
|
+
return len(self._frames) == 0 and len(self._events) == 0
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def frame_count(self) -> int:
|
|
328
|
+
"""Number of frames in buffer"""
|
|
329
|
+
return len(self._frames)
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def event_count(self) -> int:
|
|
333
|
+
"""Number of events in buffer"""
|
|
334
|
+
return len(self._events)
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foodforthought-cli
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: CLI tool for FoodforThought robotics repository platform - manage robot skills and data
|
|
5
|
+
Home-page: https://kindly.fyi/foodforthought
|
|
6
|
+
Author: Kindly Robotics
|
|
7
|
+
Author-email: hello@kindly.fyi
|
|
8
|
+
Project-URL: Homepage, https://kindly.fyi
|
|
9
|
+
Project-URL: Documentation, https://kindly.fyi/foodforthought/cli
|
|
10
|
+
Project-URL: Source, https://github.com/kindlyrobotics/monorepo
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/kindlyrobotics/monorepo/issues
|
|
12
|
+
Keywords: robotics,robot-skills,machine-learning,data-management,cli
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: requests>=2.28.0
|
|
28
|
+
Provides-Extra: robot-setup
|
|
29
|
+
Requires-Dist: pyserial>=3.5; extra == "robot-setup"
|
|
30
|
+
Requires-Dist: anthropic>=0.18.0; extra == "robot-setup"
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: pyserial>=3.5; extra == "all"
|
|
33
|
+
Requires-Dist: anthropic>=0.18.0; extra == "all"
|
|
34
|
+
Dynamic: author
|
|
35
|
+
Dynamic: author-email
|
|
36
|
+
Dynamic: classifier
|
|
37
|
+
Dynamic: description
|
|
38
|
+
Dynamic: description-content-type
|
|
39
|
+
Dynamic: home-page
|
|
40
|
+
Dynamic: keywords
|
|
41
|
+
Dynamic: project-url
|
|
42
|
+
Dynamic: provides-extra
|
|
43
|
+
Dynamic: requires-dist
|
|
44
|
+
Dynamic: requires-python
|
|
45
|
+
Dynamic: summary
|
|
46
|
+
|
|
47
|
+
# FoodforThought CLI
|
|
48
|
+
|
|
49
|
+
GitHub-like CLI tool for the FoodforThought robotics repository platform.
|
|
50
|
+
|
|
51
|
+
**Version**: 0.2.1
|
|
52
|
+
**PyPI**: https://pypi.org/project/foodforthought-cli/
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install foodforthought-cli
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or install from source:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cd foodforthought-cli
|
|
64
|
+
pip install -e .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Set environment variables:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export ATE_API_URL="https://kindly.fyi/api"
|
|
73
|
+
export ATE_API_KEY="your-api-key-here"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
### Initialize a repository
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
ate init my-robot-skill -d "A skill for my robot" -v public
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Clone a repository
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
ate clone <repository-id>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Create a commit
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ate commit -m "Add new control algorithm"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Push to remote
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
ate push -b main
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Deploy to robot
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
ate deploy unitree-r1
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Commands
|
|
109
|
+
|
|
110
|
+
### Repository Management
|
|
111
|
+
- `ate init <name>` - Initialize a new repository
|
|
112
|
+
- `ate clone <repo-id>` - Clone a repository
|
|
113
|
+
- `ate commit -m <message>` - Create a commit
|
|
114
|
+
- `ate push [-b <branch>]` - Push commits to remote
|
|
115
|
+
|
|
116
|
+
### Skill Pipeline
|
|
117
|
+
- `ate pull <skill-id> [--robot <robot>] [--format json|rlds|lerobot] [--output ./data]` - Pull skill data for training
|
|
118
|
+
- `ate upload <video-path> --robot <robot> --task <task> [--project <id>]` - Upload demonstrations for labeling
|
|
119
|
+
- `ate check-transfer --from <source-robot> --to <target-robot> [--skill <id>]` - Check skill transfer compatibility
|
|
120
|
+
- `ate labeling-status <job-id>` - Check labeling job status
|
|
121
|
+
|
|
122
|
+
### Parts & Dependencies (New in v0.2.0)
|
|
123
|
+
- `ate parts list [--category gripper]` - List available hardware parts
|
|
124
|
+
- `ate parts check <skill-id>` - Check part compatibility for a skill
|
|
125
|
+
- `ate parts require <part-id> --skill <skill-id>` - Add part dependency to a skill
|
|
126
|
+
- `ate deps audit` - Verify all dependencies are compatible
|
|
127
|
+
|
|
128
|
+
### Text-to-Skill Generation (New in v0.2.0)
|
|
129
|
+
- `ate generate "<description>" --robot <robot> --output ./skill/` - Generate skill skeleton from natural language
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
```bash
|
|
133
|
+
ate generate "pick up box and place on pallet" --robot franka-panda --output ./new-skill/
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Workflow Orchestration (New in v0.2.0)
|
|
137
|
+
- `ate workflow validate <pipeline.yaml>` - Validate a workflow definition
|
|
138
|
+
- `ate workflow run <pipeline.yaml> --sim` - Run workflow in simulation
|
|
139
|
+
- `ate workflow export <pipeline.yaml> --format ros2` - Export to ROS2 launch format
|
|
140
|
+
|
|
141
|
+
### Team Collaboration (New in v0.2.0)
|
|
142
|
+
- `ate team create <name>` - Create a new team
|
|
143
|
+
- `ate team invite <email> --role member` - Invite a member
|
|
144
|
+
- `ate team list` - List teams and members
|
|
145
|
+
- `ate skill share <skill-id> --team <team-slug>` - Share a skill with a team
|
|
146
|
+
|
|
147
|
+
### Skill Compiler (New in v0.2.1)
|
|
148
|
+
|
|
149
|
+
Compile skill.yaml specifications into deployable packages. See [docs/SKILL_COMPILER.md](docs/SKILL_COMPILER.md) for full documentation.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Validate a skill specification
|
|
153
|
+
ate validate-skill my_skill.skill.yaml
|
|
154
|
+
|
|
155
|
+
# Compile to Python package
|
|
156
|
+
ate compile my_skill.skill.yaml --target python --output ./dist
|
|
157
|
+
|
|
158
|
+
# Compile to ROS2 package
|
|
159
|
+
ate compile my_skill.skill.yaml --target ros2 --robot robots/ur5.urdf
|
|
160
|
+
|
|
161
|
+
# Test the compiled skill
|
|
162
|
+
ate test-skill ./dist --mode dry-run
|
|
163
|
+
|
|
164
|
+
# Check robot compatibility
|
|
165
|
+
ate check-compatibility my_skill.skill.yaml --robot-urdf robots/ur5.urdf
|
|
166
|
+
|
|
167
|
+
# Publish to registry
|
|
168
|
+
ate publish-skill ./dist --visibility public
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Quick Start for AI Assistants:**
|
|
172
|
+
1. `ate_list_primitives` - Discover available building blocks
|
|
173
|
+
2. `ate_validate_skill_spec` - Check skill.yaml for errors
|
|
174
|
+
3. `ate_check_skill_compatibility` - Verify robot compatibility
|
|
175
|
+
4. `ate_compile_skill` - Generate deployable code
|
|
176
|
+
5. `ate_test_compiled_skill` - Test without a robot
|
|
177
|
+
|
|
178
|
+
### Dataset Management (New in v0.2.0)
|
|
179
|
+
- `ate data upload ./sensor-logs/ --skill <id> --stage raw` - Upload sensor data
|
|
180
|
+
- `ate data list --skill <id> --stage annotated` - List datasets
|
|
181
|
+
- `ate data promote <dataset-id> --to skill-abstracted` - Promote to next stage
|
|
182
|
+
- `ate data export <dataset-id> --format rlds` - Export dataset
|
|
183
|
+
|
|
184
|
+
### Deployment & Configuration (New in v0.2.0)
|
|
185
|
+
- `ate deploy --config deploy.yaml --target fleet-alpha` - Deploy with config
|
|
186
|
+
- `ate deploy status <fleet-name>` - Check deployment status
|
|
187
|
+
|
|
188
|
+
### Deployment & Testing
|
|
189
|
+
- `ate deploy <robot-type>` - Deploy to a robot (e.g., unitree-r1)
|
|
190
|
+
- `ate test [-e gazebo|mujoco|pybullet|webots] [-r robot]` - Test skills in simulation
|
|
191
|
+
- `ate benchmark [-t speed|accuracy|robustness|efficiency|all]` - Run performance benchmarks
|
|
192
|
+
- `ate adapt <source-robot> <target-robot>` - Adapt skills between robots
|
|
193
|
+
|
|
194
|
+
### Safety & Validation
|
|
195
|
+
- `ate validate [-c collision|speed|workspace|force|all]` - Validate safety and compliance
|
|
196
|
+
- `ate stream [start|stop|status] [-s sensors...]` - Stream sensor data
|
|
197
|
+
|
|
198
|
+
## Cursor IDE Integration (MCP)
|
|
199
|
+
|
|
200
|
+
FoodforThought CLI includes a full MCP (Model Context Protocol) server for integration with Cursor IDE.
|
|
201
|
+
|
|
202
|
+
### Quick Install via Deep Link
|
|
203
|
+
|
|
204
|
+
Click this link to install in Cursor:
|
|
205
|
+
```
|
|
206
|
+
cursor://anysphere.cursor-deeplink/mcp/install?name=foodforthought&config=eyJtY3BTZXJ2ZXJzIjogeyJmb29kZm9ydGhvdWdodCI6IHsiY29tbWFuZCI6ICJweXRob24iLCAiYXJncyI6IFsiLW0iLCAiYXRlLm1jcF9zZXJ2ZXIiXSwgImVudiI6IHsiQVRFX0FQSV9VUkwiOiAiaHR0cHM6Ly9raW5kbHkuZnlpL2FwaSJ9fX19
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Manual Installation
|
|
210
|
+
|
|
211
|
+
1. Install the CLI:
|
|
212
|
+
```bash
|
|
213
|
+
pip install foodforthought-cli
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
2. Install MCP SDK:
|
|
217
|
+
```bash
|
|
218
|
+
pip install mcp
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
3. Add to your Cursor MCP config (`~/.cursor/mcp.json` or `.cursor/mcp.json`):
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"mcpServers": {
|
|
225
|
+
"foodforthought": {
|
|
226
|
+
"command": "python",
|
|
227
|
+
"args": ["-m", "ate.mcp_server"],
|
|
228
|
+
"env": {
|
|
229
|
+
"ATE_API_URL": "https://kindly.fyi/api"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
4. Restart Cursor
|
|
237
|
+
|
|
238
|
+
### Available MCP Tools (37+)
|
|
239
|
+
|
|
240
|
+
| Category | Tools |
|
|
241
|
+
|----------|-------|
|
|
242
|
+
| **Skill Compiler** | `ate_compile_skill`, `ate_validate_skill_spec`, `ate_test_compiled_skill`, `ate_publish_compiled_skill`, `ate_check_skill_compatibility` |
|
|
243
|
+
| **Primitives** | `ate_list_primitives`, `ate_get_primitive` |
|
|
244
|
+
| **Skills** | `ate_skills_list`, `ate_skills_create`, `ate_skills_get`, `ate_pull`, `ate_upload` |
|
|
245
|
+
| **Testing** | `ate_test`, `ate_simulate` |
|
|
246
|
+
| **Compatibility** | `ate_adapt`, `ate_compatibility_check` |
|
|
247
|
+
| **Parts** | `ate_parts_list`, `ate_parts_check`, `ate_parts_require`, `ate_deps_audit` |
|
|
248
|
+
| **Generation** | `ate_generate` |
|
|
249
|
+
| **Workflows** | `ate_workflow_validate`, `ate_workflow_run`, `ate_workflow_export` |
|
|
250
|
+
| **Teams** | `ate_team_create`, `ate_team_invite`, `ate_team_list`, `ate_team_share` |
|
|
251
|
+
| **Datasets** | `ate_data_upload`, `ate_data_list`, `ate_data_promote`, `ate_data_export` |
|
|
252
|
+
| **Deployment** | `ate_deploy`, `ate_deploy_status` |
|
|
253
|
+
| **Audit** | `ate_audit_trail` |
|
|
254
|
+
|
|
255
|
+
### MCP Resources
|
|
256
|
+
|
|
257
|
+
- `skill://{id}` - Skill details and configuration
|
|
258
|
+
- `part://{id}` - Hardware part specifications
|
|
259
|
+
- `workflow://{id}` - Workflow definition
|
|
260
|
+
- `team://{id}` - Team information
|
|
261
|
+
- `deployment://{id}` - Deployment status
|
|
262
|
+
- `audit://{id}` - Audit trail
|
|
263
|
+
|
|
264
|
+
### MCP Prompts
|
|
265
|
+
|
|
266
|
+
- `setup_workflow` - Guide for multi-skill workflows
|
|
267
|
+
- `deploy_skill` - Step-by-step deployment guide
|
|
268
|
+
- `debug_compatibility` - Debug skill transfer issues
|
|
269
|
+
- `onboard_robot` - Add a new robot to the platform
|
|
270
|
+
- `audit_deployment` - Generate audit reports
|
|
271
|
+
|
|
272
|
+
See the [MCP Server Guide](../docs/MCP_SERVER_GUIDE.md) for full documentation.
|
|
273
|
+
|
|
274
|
+
## CI/CD Integration
|
|
275
|
+
|
|
276
|
+
### GitHub Actions
|
|
277
|
+
|
|
278
|
+
Use the provided actions for CI/CD:
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
name: Skill CI
|
|
282
|
+
on: [push, pull_request]
|
|
283
|
+
jobs:
|
|
284
|
+
test:
|
|
285
|
+
runs-on: ubuntu-latest
|
|
286
|
+
steps:
|
|
287
|
+
- uses: actions/checkout@v4
|
|
288
|
+
- uses: kindly-robotics/test-skill@v1
|
|
289
|
+
with:
|
|
290
|
+
skill-path: ./
|
|
291
|
+
robot-model: ur5
|
|
292
|
+
- uses: kindly-robotics/check-transfer@v1
|
|
293
|
+
with:
|
|
294
|
+
skill: .
|
|
295
|
+
min-score: 0.6
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
Copyright © 2024-2025 Kindly Robotics. All rights reserved.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
ate/__init__.py,sha256=u_wReCsJRGB9lkTU10CCiIoq2bNj_4no5ouqEsKHkHM,105
|
|
2
|
+
ate/bridge_server.py,sha256=FI9PSLzq_eW9JYUS_qzTHZ4NnOkIr7G7sdwTWSYaMnQ,21365
|
|
3
|
+
ate/cli.py,sha256=kjGtQ0bD2UI9rHScBvqo6MljRLO7rDOo4lCIgfMWkAQ,176611
|
|
4
|
+
ate/compatibility.py,sha256=K5pSpPVHLgPSIF-a6kIJrehI9wl5AH3Cx76rOFCUZ2k,23094
|
|
5
|
+
ate/generator.py,sha256=DUPat3cmoe9ufPajHFoI5kK6tfvPtVtB9gLvTlJzPdU,18308
|
|
6
|
+
ate/marketplace.py,sha256=oLZOm8GbOOuxvsB45836KiTW7lCI_wSfIh-VY6crlYA,17474
|
|
7
|
+
ate/mcp_server.py,sha256=hMi2QFcgkxyZGRLbvn8YZwcN2lVPPE2VgjoZjb2lEdc,102256
|
|
8
|
+
ate/primitives.py,sha256=mku_-ivbBiTth90ru6wLZg4by-f6Fkds4D9l6PrroFI,30390
|
|
9
|
+
ate/robot_setup.py,sha256=5KNYHh2RSWy5Y9hA3F56HQYa7HD8Y7VLq3gcuNV-wnQ,91800
|
|
10
|
+
ate/skill_schema.py,sha256=q5zOrYJV2FVR59cW5UN9OowHLxtnwstWMF1pa5XgvtE,17719
|
|
11
|
+
ate/generators/__init__.py,sha256=8tVjL3FpiesAH6SaSAIlwP1ctlP5GBXbptUaOOGMuvI,544
|
|
12
|
+
ate/generators/docker_generator.py,sha256=Q2ux-db_9AZfmogB4O0JL_0s0nv38JzTiSpOnkPEBqI,11779
|
|
13
|
+
ate/generators/hardware_config.py,sha256=FU5ntIzpxPAVecgqRHKCOUdUoFYRwVc_kz9DinZRcXA,15986
|
|
14
|
+
ate/generators/ros2_generator.py,sha256=Sx8GIoYhBCRuv0L9Y9QlZvgoR2GOGm5tCpGyGvG3YmY,19765
|
|
15
|
+
ate/generators/skill_generator.py,sha256=WLNxM437bL0iXf4UlAXYstNI5RSmBhzFSGKQdfJsLAc,28631
|
|
16
|
+
ate/telemetry/__init__.py,sha256=qNUnu4n33vJn53AfUVNvME0C8Fc4smUCmXOFsBHTtzs,669
|
|
17
|
+
ate/telemetry/cli.py,sha256=-Jzp3HXkQsm9_Q0qQgt3HDzOMSqsZsPwycwV3eS2Gl0,15816
|
|
18
|
+
ate/telemetry/collector.py,sha256=3A4JSa_d3XUaVPYi4SVHI1pgkPBh2KFhO56ToBhO8R4,15844
|
|
19
|
+
ate/telemetry/context.py,sha256=km-b7ge31vbfN7HhFEErdCOkdXCEl-a9e6j5szqbziA,10429
|
|
20
|
+
ate/telemetry/fleet_agent.py,sha256=iWKSXmnASumTArvO4ek7Wcv019dvnkJAdLH9spsKnSw,14260
|
|
21
|
+
ate/telemetry/types.py,sha256=5kgwXGoQ-_kuD4gszlaXW7LN9L6lKmSyt4EOPeoZlSo,11066
|
|
22
|
+
ate/telemetry/formats/__init__.py,sha256=a9ylB6nSJMbLIarBq60gEMpznHSs2d3JMY3uIeUt4F4,458
|
|
23
|
+
ate/telemetry/formats/hdf5_serializer.py,sha256=RgACw9S8J5ZRkj1GML0ib4k0WHWx30TAemIw-Nko1l8,18703
|
|
24
|
+
ate/telemetry/formats/mcap_serializer.py,sha256=7wsDzguseApTO6G58nFM4Jp-mTWzy5IaTZCKzpUs0-U,14396
|
|
25
|
+
mechdog_labeled/__init__.py,sha256=qIrQzLk38jm9kfGtyqVHEuBNE-ljz3v3P8jg7aIARF0,160
|
|
26
|
+
mechdog_labeled/primitives.py,sha256=W9AtSjeTBy5gteEKS0NkZgogL1Hf2Etxsdd0hq6LRhs,3846
|
|
27
|
+
mechdog_labeled/servo_map.py,sha256=p86iBqkBDa09JKVm_TWC1j42erOQMTb96eMHWZtdGf4,4543
|
|
28
|
+
mechdog_output/__init__.py,sha256=qIrQzLk38jm9kfGtyqVHEuBNE-ljz3v3P8jg7aIARF0,160
|
|
29
|
+
mechdog_output/primitives.py,sha256=msTdyL1z3sh4qQcP_Cr0yKzg9xAzZUod14FkLd5Aeww,1884
|
|
30
|
+
mechdog_output/servo_map.py,sha256=hygFLlg8kHbmu3CJOQGey1kRTB8XpmPayA1byP4pVIA,3589
|
|
31
|
+
test_autodetect/__init__.py,sha256=qIrQzLk38jm9kfGtyqVHEuBNE-ljz3v3P8jg7aIARF0,160
|
|
32
|
+
test_autodetect/primitives.py,sha256=7em8nBqB8zG93R32F3R6pPs4-rdH4xs3koZN8OH33Gg,3783
|
|
33
|
+
test_autodetect/servo_map.py,sha256=YzYuVN-zuBC9uWgD1lBhFpSRYSbxRtflvGI2Qspeer8,4724
|
|
34
|
+
test_full_auto/__init__.py,sha256=qIrQzLk38jm9kfGtyqVHEuBNE-ljz3v3P8jg7aIARF0,160
|
|
35
|
+
test_full_auto/primitives.py,sha256=W9AtSjeTBy5gteEKS0NkZgogL1Hf2Etxsdd0hq6LRhs,3846
|
|
36
|
+
test_full_auto/servo_map.py,sha256=p86iBqkBDa09JKVm_TWC1j42erOQMTb96eMHWZtdGf4,4543
|
|
37
|
+
test_smart_detect/__init__.py,sha256=qIrQzLk38jm9kfGtyqVHEuBNE-ljz3v3P8jg7aIARF0,160
|
|
38
|
+
test_smart_detect/primitives.py,sha256=W9AtSjeTBy5gteEKS0NkZgogL1Hf2Etxsdd0hq6LRhs,3846
|
|
39
|
+
test_smart_detect/servo_map.py,sha256=p86iBqkBDa09JKVm_TWC1j42erOQMTb96eMHWZtdGf4,4543
|
|
40
|
+
foodforthought_cli-0.2.3.dist-info/METADATA,sha256=f-6CYpzBIRUGKIRmfr3MF1uhRJN_fTT8D4_K12NV3g4,9584
|
|
41
|
+
foodforthought_cli-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
42
|
+
foodforthought_cli-0.2.3.dist-info/entry_points.txt,sha256=JSxWuXCbGllyHqVJpomP_BDlXYpiNglM9Mo6bEmQK1A,37
|
|
43
|
+
foodforthought_cli-0.2.3.dist-info/top_level.txt,sha256=Q8RL26hnZ7yLJbGqHMbthsZJJRho-UvpOJAEtAG4NbI,84
|
|
44
|
+
foodforthought_cli-0.2.3.dist-info/RECORD,,
|