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.
Files changed (46) hide show
  1. ate/__init__.py +1 -1
  2. ate/bridge_server.py +622 -0
  3. ate/cli.py +2625 -242
  4. ate/compatibility.py +580 -0
  5. ate/generators/__init__.py +19 -0
  6. ate/generators/docker_generator.py +461 -0
  7. ate/generators/hardware_config.py +469 -0
  8. ate/generators/ros2_generator.py +617 -0
  9. ate/generators/skill_generator.py +783 -0
  10. ate/marketplace.py +524 -0
  11. ate/mcp_server.py +2424 -148
  12. ate/primitives.py +1016 -0
  13. ate/robot_setup.py +2222 -0
  14. ate/skill_schema.py +537 -0
  15. ate/telemetry/__init__.py +33 -0
  16. ate/telemetry/cli.py +455 -0
  17. ate/telemetry/collector.py +444 -0
  18. ate/telemetry/context.py +318 -0
  19. ate/telemetry/fleet_agent.py +419 -0
  20. ate/telemetry/formats/__init__.py +18 -0
  21. ate/telemetry/formats/hdf5_serializer.py +503 -0
  22. ate/telemetry/formats/mcap_serializer.py +457 -0
  23. ate/telemetry/types.py +334 -0
  24. foodforthought_cli-0.2.3.dist-info/METADATA +300 -0
  25. foodforthought_cli-0.2.3.dist-info/RECORD +44 -0
  26. foodforthought_cli-0.2.3.dist-info/top_level.txt +6 -0
  27. mechdog_labeled/__init__.py +3 -0
  28. mechdog_labeled/primitives.py +113 -0
  29. mechdog_labeled/servo_map.py +209 -0
  30. mechdog_output/__init__.py +3 -0
  31. mechdog_output/primitives.py +59 -0
  32. mechdog_output/servo_map.py +203 -0
  33. test_autodetect/__init__.py +3 -0
  34. test_autodetect/primitives.py +113 -0
  35. test_autodetect/servo_map.py +209 -0
  36. test_full_auto/__init__.py +3 -0
  37. test_full_auto/primitives.py +113 -0
  38. test_full_auto/servo_map.py +209 -0
  39. test_smart_detect/__init__.py +3 -0
  40. test_smart_detect/primitives.py +113 -0
  41. test_smart_detect/servo_map.py +209 -0
  42. foodforthought_cli-0.2.0.dist-info/METADATA +0 -151
  43. foodforthought_cli-0.2.0.dist-info/RECORD +0 -9
  44. foodforthought_cli-0.2.0.dist-info/top_level.txt +0 -1
  45. {foodforthought_cli-0.2.0.dist-info → foodforthought_cli-0.2.3.dist-info}/WHEEL +0 -0
  46. {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,,
@@ -0,0 +1,6 @@
1
+ ate
2
+ mechdog_labeled
3
+ mechdog_output
4
+ test_autodetect
5
+ test_full_auto
6
+ test_smart_detect
@@ -0,0 +1,3 @@
1
+ """Generated robot package for unnamed_robot."""
2
+ from .servo_map import ServoID, SERVO_LIMITS, ALL_SERVOS
3
+ from .primitives import PrimitiveSkills, ServoCommand