foodforthought-cli 0.2.7__py3-none-any.whl → 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.
Files changed (131) hide show
  1. ate/__init__.py +6 -0
  2. ate/__main__.py +16 -0
  3. ate/auth/__init__.py +1 -0
  4. ate/auth/device_flow.py +141 -0
  5. ate/auth/token_store.py +96 -0
  6. ate/behaviors/__init__.py +100 -0
  7. ate/behaviors/approach.py +399 -0
  8. ate/behaviors/common.py +686 -0
  9. ate/behaviors/tree.py +454 -0
  10. ate/cli.py +855 -3995
  11. ate/client.py +90 -0
  12. ate/commands/__init__.py +168 -0
  13. ate/commands/auth.py +389 -0
  14. ate/commands/bridge.py +448 -0
  15. ate/commands/data.py +185 -0
  16. ate/commands/deps.py +111 -0
  17. ate/commands/generate.py +384 -0
  18. ate/commands/memory.py +907 -0
  19. ate/commands/parts.py +166 -0
  20. ate/commands/primitive.py +399 -0
  21. ate/commands/protocol.py +288 -0
  22. ate/commands/recording.py +524 -0
  23. ate/commands/repo.py +154 -0
  24. ate/commands/simulation.py +291 -0
  25. ate/commands/skill.py +303 -0
  26. ate/commands/skills.py +487 -0
  27. ate/commands/team.py +147 -0
  28. ate/commands/workflow.py +271 -0
  29. ate/detection/__init__.py +38 -0
  30. ate/detection/base.py +142 -0
  31. ate/detection/color_detector.py +399 -0
  32. ate/detection/trash_detector.py +322 -0
  33. ate/drivers/__init__.py +39 -0
  34. ate/drivers/ble_transport.py +405 -0
  35. ate/drivers/mechdog.py +942 -0
  36. ate/drivers/wifi_camera.py +477 -0
  37. ate/interfaces/__init__.py +187 -0
  38. ate/interfaces/base.py +273 -0
  39. ate/interfaces/body.py +267 -0
  40. ate/interfaces/detection.py +282 -0
  41. ate/interfaces/locomotion.py +422 -0
  42. ate/interfaces/manipulation.py +408 -0
  43. ate/interfaces/navigation.py +389 -0
  44. ate/interfaces/perception.py +362 -0
  45. ate/interfaces/sensors.py +247 -0
  46. ate/interfaces/types.py +371 -0
  47. ate/llm_proxy.py +239 -0
  48. ate/mcp_server.py +387 -0
  49. ate/memory/__init__.py +35 -0
  50. ate/memory/cloud.py +244 -0
  51. ate/memory/context.py +269 -0
  52. ate/memory/embeddings.py +184 -0
  53. ate/memory/export.py +26 -0
  54. ate/memory/merge.py +146 -0
  55. ate/memory/migrate/__init__.py +34 -0
  56. ate/memory/migrate/base.py +89 -0
  57. ate/memory/migrate/pipeline.py +189 -0
  58. ate/memory/migrate/sources/__init__.py +13 -0
  59. ate/memory/migrate/sources/chroma.py +170 -0
  60. ate/memory/migrate/sources/pinecone.py +120 -0
  61. ate/memory/migrate/sources/qdrant.py +110 -0
  62. ate/memory/migrate/sources/weaviate.py +160 -0
  63. ate/memory/reranker.py +353 -0
  64. ate/memory/search.py +26 -0
  65. ate/memory/store.py +548 -0
  66. ate/recording/__init__.py +83 -0
  67. ate/recording/demonstration.py +378 -0
  68. ate/recording/session.py +415 -0
  69. ate/recording/upload.py +304 -0
  70. ate/recording/visual.py +416 -0
  71. ate/recording/wrapper.py +95 -0
  72. ate/robot/__init__.py +221 -0
  73. ate/robot/agentic_servo.py +856 -0
  74. ate/robot/behaviors.py +493 -0
  75. ate/robot/ble_capture.py +1000 -0
  76. ate/robot/ble_enumerate.py +506 -0
  77. ate/robot/calibration.py +668 -0
  78. ate/robot/calibration_state.py +388 -0
  79. ate/robot/commands.py +3735 -0
  80. ate/robot/direction_calibration.py +554 -0
  81. ate/robot/discovery.py +441 -0
  82. ate/robot/introspection.py +330 -0
  83. ate/robot/llm_system_id.py +654 -0
  84. ate/robot/locomotion_calibration.py +508 -0
  85. ate/robot/manager.py +270 -0
  86. ate/robot/marker_generator.py +611 -0
  87. ate/robot/perception.py +502 -0
  88. ate/robot/primitives.py +614 -0
  89. ate/robot/profiles.py +281 -0
  90. ate/robot/registry.py +322 -0
  91. ate/robot/servo_mapper.py +1153 -0
  92. ate/robot/skill_upload.py +675 -0
  93. ate/robot/target_calibration.py +500 -0
  94. ate/robot/teach.py +515 -0
  95. ate/robot/types.py +242 -0
  96. ate/robot/visual_labeler.py +1048 -0
  97. ate/robot/visual_servo_loop.py +494 -0
  98. ate/robot/visual_servoing.py +570 -0
  99. ate/robot/visual_system_id.py +906 -0
  100. ate/transports/__init__.py +121 -0
  101. ate/transports/base.py +394 -0
  102. ate/transports/ble.py +405 -0
  103. ate/transports/hybrid.py +444 -0
  104. ate/transports/serial.py +345 -0
  105. ate/urdf/__init__.py +30 -0
  106. ate/urdf/capture.py +582 -0
  107. ate/urdf/cloud.py +491 -0
  108. ate/urdf/collision.py +271 -0
  109. ate/urdf/commands.py +708 -0
  110. ate/urdf/depth.py +360 -0
  111. ate/urdf/inertial.py +312 -0
  112. ate/urdf/kinematics.py +330 -0
  113. ate/urdf/lifting.py +415 -0
  114. ate/urdf/meshing.py +300 -0
  115. ate/urdf/models/__init__.py +110 -0
  116. ate/urdf/models/depth_anything.py +253 -0
  117. ate/urdf/models/sam2.py +324 -0
  118. ate/urdf/motion_analysis.py +396 -0
  119. ate/urdf/pipeline.py +468 -0
  120. ate/urdf/scale.py +256 -0
  121. ate/urdf/scan_session.py +411 -0
  122. ate/urdf/segmentation.py +299 -0
  123. ate/urdf/synthesis.py +319 -0
  124. ate/urdf/topology.py +336 -0
  125. ate/urdf/validation.py +371 -0
  126. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
  127. foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
  128. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
  129. foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
  130. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
  131. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,330 @@
1
+ """
2
+ Robot capability introspection.
3
+
4
+ Inspect what a robot can do by examining its interfaces and methods.
5
+ """
6
+
7
+ import inspect
8
+ from typing import Dict, List, Set, Any, Optional, Type
9
+ from dataclasses import dataclass
10
+
11
+ from ..interfaces import (
12
+ RobotInterface,
13
+ SafetyInterface,
14
+ QuadrupedLocomotion,
15
+ BipedLocomotion,
16
+ WheeledLocomotion,
17
+ AerialLocomotion,
18
+ ArmInterface,
19
+ GripperInterface,
20
+ CameraInterface,
21
+ DepthCameraInterface,
22
+ LidarInterface,
23
+ IMUInterface,
24
+ BodyPoseInterface,
25
+ NavigationInterface,
26
+ ObjectDetectionInterface,
27
+ )
28
+
29
+
30
+ @dataclass
31
+ class MethodInfo:
32
+ """Information about a robot method."""
33
+ name: str
34
+ interface: str
35
+ description: str
36
+ parameters: List[str]
37
+ return_type: str
38
+
39
+
40
+ @dataclass
41
+ class CapabilityInfo:
42
+ """Information about a robot capability."""
43
+ name: str
44
+ interface_class: str
45
+ description: str
46
+ methods: List[MethodInfo]
47
+ available: bool = True
48
+
49
+
50
+ # Map interfaces to capability names
51
+ INTERFACE_CAPABILITIES = {
52
+ QuadrupedLocomotion: "quadruped_locomotion",
53
+ BipedLocomotion: "bipedal_locomotion",
54
+ WheeledLocomotion: "wheeled_locomotion",
55
+ AerialLocomotion: "aerial_locomotion",
56
+ ArmInterface: "arm",
57
+ GripperInterface: "gripper",
58
+ CameraInterface: "camera",
59
+ DepthCameraInterface: "depth_camera",
60
+ LidarInterface: "lidar",
61
+ IMUInterface: "imu",
62
+ BodyPoseInterface: "body_pose",
63
+ SafetyInterface: "safety",
64
+ NavigationInterface: "navigation",
65
+ ObjectDetectionInterface: "object_detection",
66
+ }
67
+
68
+
69
+ def get_capabilities(robot: Any) -> Dict[str, CapabilityInfo]:
70
+ """
71
+ Get all capabilities of a robot instance.
72
+
73
+ Args:
74
+ robot: Robot instance
75
+
76
+ Returns:
77
+ Dict mapping capability name to CapabilityInfo
78
+ """
79
+ capabilities = {}
80
+
81
+ for interface, cap_name in INTERFACE_CAPABILITIES.items():
82
+ if isinstance(robot, interface):
83
+ methods = get_interface_methods(interface)
84
+ capabilities[cap_name] = CapabilityInfo(
85
+ name=cap_name,
86
+ interface_class=interface.__name__,
87
+ description=interface.__doc__ or "",
88
+ methods=methods,
89
+ available=True,
90
+ )
91
+
92
+ return capabilities
93
+
94
+
95
+ def get_methods(robot: Any) -> Dict[str, List[MethodInfo]]:
96
+ """
97
+ Get all available methods grouped by capability.
98
+
99
+ Args:
100
+ robot: Robot instance
101
+
102
+ Returns:
103
+ Dict mapping capability name to list of methods
104
+ """
105
+ result = {}
106
+
107
+ for interface, cap_name in INTERFACE_CAPABILITIES.items():
108
+ if isinstance(robot, interface):
109
+ methods = get_interface_methods(interface)
110
+ if methods:
111
+ result[cap_name] = methods
112
+
113
+ return result
114
+
115
+
116
+ def get_interface_methods(interface: Type) -> List[MethodInfo]:
117
+ """
118
+ Get methods defined by an interface.
119
+
120
+ Args:
121
+ interface: Interface class
122
+
123
+ Returns:
124
+ List of MethodInfo
125
+ """
126
+ methods = []
127
+
128
+ for name, method in inspect.getmembers(interface, predicate=inspect.isfunction):
129
+ if name.startswith("_"):
130
+ continue
131
+
132
+ # Get signature
133
+ try:
134
+ sig = inspect.signature(method)
135
+ params = [
136
+ p.name for p in sig.parameters.values()
137
+ if p.name != "self"
138
+ ]
139
+ return_type = str(sig.return_annotation) if sig.return_annotation != inspect.Signature.empty else "None"
140
+ except (ValueError, TypeError):
141
+ params = []
142
+ return_type = "Unknown"
143
+
144
+ # Clean up return type
145
+ return_type = return_type.replace("typing.", "").replace("<class '", "").replace("'>", "")
146
+
147
+ methods.append(MethodInfo(
148
+ name=name,
149
+ interface=interface.__name__,
150
+ description=method.__doc__ or "",
151
+ parameters=params,
152
+ return_type=return_type,
153
+ ))
154
+
155
+ return methods
156
+
157
+
158
+ def test_capability(robot: Any, capability: str) -> Dict[str, Any]:
159
+ """
160
+ Test if a capability is working.
161
+
162
+ Args:
163
+ robot: Robot instance
164
+ capability: Capability name to test
165
+
166
+ Returns:
167
+ Test result with status and details
168
+ """
169
+ result = {
170
+ "capability": capability,
171
+ "status": "unknown",
172
+ "tests": [],
173
+ }
174
+
175
+ # Find the interface for this capability
176
+ interface = None
177
+ for iface, cap_name in INTERFACE_CAPABILITIES.items():
178
+ if cap_name == capability:
179
+ interface = iface
180
+ break
181
+
182
+ if interface is None:
183
+ result["status"] = "error"
184
+ result["error"] = f"Unknown capability: {capability}"
185
+ return result
186
+
187
+ if not isinstance(robot, interface):
188
+ result["status"] = "not_available"
189
+ result["error"] = f"Robot does not have capability: {capability}"
190
+ return result
191
+
192
+ # Run capability-specific tests
193
+ try:
194
+ if capability == "camera":
195
+ result["tests"].append(_test_camera(robot))
196
+ elif capability == "quadruped_locomotion":
197
+ result["tests"].append(_test_locomotion(robot))
198
+ elif capability == "body_pose":
199
+ result["tests"].append(_test_body_pose(robot))
200
+ elif capability == "safety":
201
+ result["tests"].append(_test_safety(robot))
202
+ else:
203
+ result["tests"].append({
204
+ "name": "basic_check",
205
+ "status": "passed",
206
+ "message": f"Robot implements {interface.__name__}",
207
+ })
208
+
209
+ # Determine overall status
210
+ if all(t.get("status") == "passed" for t in result["tests"]):
211
+ result["status"] = "passed"
212
+ elif any(t.get("status") == "failed" for t in result["tests"]):
213
+ result["status"] = "failed"
214
+ else:
215
+ result["status"] = "partial"
216
+
217
+ except Exception as e:
218
+ result["status"] = "error"
219
+ result["error"] = str(e)
220
+
221
+ return result
222
+
223
+
224
+ def _test_camera(robot: Any) -> Dict[str, Any]:
225
+ """Test camera capability."""
226
+ test = {"name": "camera_capture", "status": "unknown"}
227
+
228
+ try:
229
+ image = robot.get_image()
230
+ if image.width > 0 and image.height > 0:
231
+ test["status"] = "passed"
232
+ test["message"] = f"Captured {image.width}x{image.height} image"
233
+ else:
234
+ test["status"] = "failed"
235
+ test["message"] = "Image capture returned empty"
236
+ except Exception as e:
237
+ test["status"] = "failed"
238
+ test["error"] = str(e)
239
+
240
+ return test
241
+
242
+
243
+ def _test_locomotion(robot: Any) -> Dict[str, Any]:
244
+ """Test locomotion capability (non-destructive)."""
245
+ test = {"name": "locomotion_status", "status": "unknown"}
246
+
247
+ try:
248
+ if hasattr(robot, "is_moving"):
249
+ is_moving = robot.is_moving()
250
+ test["status"] = "passed"
251
+ test["message"] = f"Robot is {'moving' if is_moving else 'stationary'}"
252
+ else:
253
+ test["status"] = "passed"
254
+ test["message"] = "Locomotion interface available"
255
+ except Exception as e:
256
+ test["status"] = "failed"
257
+ test["error"] = str(e)
258
+
259
+ return test
260
+
261
+
262
+ def _test_body_pose(robot: Any) -> Dict[str, Any]:
263
+ """Test body pose capability."""
264
+ test = {"name": "body_pose_read", "status": "unknown"}
265
+
266
+ try:
267
+ height = robot.get_body_height()
268
+ test["status"] = "passed"
269
+ test["message"] = f"Body height: {height:.3f}m"
270
+ except Exception as e:
271
+ test["status"] = "failed"
272
+ test["error"] = str(e)
273
+
274
+ return test
275
+
276
+
277
+ def _test_safety(robot: Any) -> Dict[str, Any]:
278
+ """Test safety capability."""
279
+ test = {"name": "safety_status", "status": "unknown"}
280
+
281
+ try:
282
+ estopped = robot.is_estopped()
283
+ test["status"] = "passed"
284
+ test["message"] = f"E-stop: {'active' if estopped else 'inactive'}"
285
+ except Exception as e:
286
+ test["status"] = "failed"
287
+ test["error"] = str(e)
288
+
289
+ return test
290
+
291
+
292
+ def format_capabilities(capabilities: Dict[str, CapabilityInfo]) -> str:
293
+ """Format capabilities for CLI display."""
294
+ lines = []
295
+
296
+ for name, info in capabilities.items():
297
+ lines.append(f"\n{name.upper()}")
298
+ lines.append("-" * len(name))
299
+
300
+ if info.description:
301
+ first_line = info.description.strip().split("\n")[0]
302
+ lines.append(f" {first_line}")
303
+
304
+ lines.append(" Methods:")
305
+ for method in info.methods[:5]: # Show first 5 methods
306
+ params = ", ".join(method.parameters[:3]) # Show first 3 params
307
+ if len(method.parameters) > 3:
308
+ params += ", ..."
309
+ lines.append(f" - {method.name}({params})")
310
+
311
+ if len(info.methods) > 5:
312
+ lines.append(f" ... and {len(info.methods) - 5} more")
313
+
314
+ return "\n".join(lines)
315
+
316
+
317
+ def format_methods(methods: Dict[str, List[MethodInfo]]) -> str:
318
+ """Format methods for CLI display."""
319
+ lines = []
320
+
321
+ for capability, method_list in methods.items():
322
+ lines.append(f"\n[{capability}]")
323
+ for method in method_list:
324
+ params = ", ".join(method.parameters)
325
+ lines.append(f" {method.name}({params}) -> {method.return_type}")
326
+ if method.description:
327
+ first_line = method.description.strip().split("\n")[0][:60]
328
+ lines.append(f" {first_line}")
329
+
330
+ return "\n".join(lines)