foodforthought-cli 0.1.1__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 +4 -0
- ate/cli.py +829 -0
- ate/mcp_server.py +514 -0
- foodforthought_cli-0.1.1.dist-info/METADATA +151 -0
- foodforthought_cli-0.1.1.dist-info/RECORD +8 -0
- foodforthought_cli-0.1.1.dist-info/WHEEL +5 -0
- foodforthought_cli-0.1.1.dist-info/entry_points.txt +2 -0
- foodforthought_cli-0.1.1.dist-info/top_level.txt +1 -0
ate/mcp_server.py
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
FoodforThought MCP Server - Model Context Protocol server for Cursor IDE
|
|
4
|
+
Exposes FoodforThought CLI capabilities as MCP tools
|
|
5
|
+
|
|
6
|
+
Installation:
|
|
7
|
+
pip install -r requirements-mcp.txt
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python -m ate.mcp_server
|
|
11
|
+
|
|
12
|
+
Or configure in Cursor's mcp.json:
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"foodforthought": {
|
|
16
|
+
"command": "python",
|
|
17
|
+
"args": ["-m", "ate.mcp_server"],
|
|
18
|
+
"env": {
|
|
19
|
+
"ATE_API_URL": "https://kindly.fyi/api",
|
|
20
|
+
"ATE_API_KEY": "${env:ATE_API_KEY}"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import asyncio
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import sys
|
|
31
|
+
from typing import Any, Dict, List, Optional
|
|
32
|
+
|
|
33
|
+
# Import the existing CLI client
|
|
34
|
+
from ate.cli import ATEClient
|
|
35
|
+
|
|
36
|
+
# MCP SDK imports - using standard MCP Python SDK pattern
|
|
37
|
+
try:
|
|
38
|
+
from mcp.server import Server
|
|
39
|
+
from mcp.server.stdio import stdio_server
|
|
40
|
+
from mcp.types import (
|
|
41
|
+
Tool,
|
|
42
|
+
TextContent,
|
|
43
|
+
Resource,
|
|
44
|
+
Prompt,
|
|
45
|
+
PromptArgument,
|
|
46
|
+
)
|
|
47
|
+
except ImportError:
|
|
48
|
+
try:
|
|
49
|
+
# Alternative import path for some MCP SDK versions
|
|
50
|
+
from mcp import Server, stdio_server
|
|
51
|
+
from mcp.types import Tool, TextContent, Resource, Prompt, PromptArgument
|
|
52
|
+
except ImportError:
|
|
53
|
+
# Fallback if MCP SDK not available - provide helpful error
|
|
54
|
+
print(
|
|
55
|
+
"Error: MCP SDK not installed. Install with: pip install mcp",
|
|
56
|
+
file=sys.stderr,
|
|
57
|
+
)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
# Initialize MCP server
|
|
61
|
+
server = Server("foodforthought")
|
|
62
|
+
|
|
63
|
+
# Initialize ATE client
|
|
64
|
+
client = ATEClient()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@server.list_tools()
|
|
68
|
+
async def list_tools() -> List[Tool]:
|
|
69
|
+
"""List all available MCP tools"""
|
|
70
|
+
return [
|
|
71
|
+
Tool(
|
|
72
|
+
name="ate_init",
|
|
73
|
+
description="Initialize a new FoodforThought repository",
|
|
74
|
+
inputSchema={
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"name": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Repository name",
|
|
80
|
+
},
|
|
81
|
+
"description": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "Repository description",
|
|
84
|
+
},
|
|
85
|
+
"visibility": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"enum": ["public", "private"],
|
|
88
|
+
"description": "Repository visibility",
|
|
89
|
+
"default": "public",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
"required": ["name"],
|
|
93
|
+
},
|
|
94
|
+
),
|
|
95
|
+
Tool(
|
|
96
|
+
name="ate_clone",
|
|
97
|
+
description="Clone a FoodforThought repository to local directory",
|
|
98
|
+
inputSchema={
|
|
99
|
+
"type": "object",
|
|
100
|
+
"properties": {
|
|
101
|
+
"repo_id": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Repository ID to clone",
|
|
104
|
+
},
|
|
105
|
+
"target_dir": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"description": "Target directory (optional)",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
"required": ["repo_id"],
|
|
111
|
+
},
|
|
112
|
+
),
|
|
113
|
+
Tool(
|
|
114
|
+
name="ate_list_repositories",
|
|
115
|
+
description="List available FoodforThought repositories",
|
|
116
|
+
inputSchema={
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"search": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Search query",
|
|
122
|
+
},
|
|
123
|
+
"robot_model": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "Filter by robot model",
|
|
126
|
+
},
|
|
127
|
+
"limit": {
|
|
128
|
+
"type": "number",
|
|
129
|
+
"description": "Maximum number of results",
|
|
130
|
+
"default": 20,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
),
|
|
135
|
+
Tool(
|
|
136
|
+
name="ate_list_robots",
|
|
137
|
+
description="List available robot profiles",
|
|
138
|
+
inputSchema={
|
|
139
|
+
"type": "object",
|
|
140
|
+
"properties": {
|
|
141
|
+
"search": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"description": "Search query",
|
|
144
|
+
},
|
|
145
|
+
"category": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"description": "Filter by category",
|
|
148
|
+
},
|
|
149
|
+
"limit": {
|
|
150
|
+
"type": "number",
|
|
151
|
+
"description": "Maximum number of results",
|
|
152
|
+
"default": 20,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
),
|
|
157
|
+
Tool(
|
|
158
|
+
name="ate_compatibility",
|
|
159
|
+
description="Check skill compatibility between two robot models",
|
|
160
|
+
inputSchema={
|
|
161
|
+
"type": "object",
|
|
162
|
+
"properties": {
|
|
163
|
+
"source_robot_id": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"description": "Source robot ID",
|
|
166
|
+
},
|
|
167
|
+
"target_robot_id": {
|
|
168
|
+
"type": "string",
|
|
169
|
+
"description": "Target robot ID",
|
|
170
|
+
},
|
|
171
|
+
"repository_id": {
|
|
172
|
+
"type": "string",
|
|
173
|
+
"description": "Repository ID to check compatibility for",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
"required": ["source_robot_id", "target_robot_id", "repository_id"],
|
|
177
|
+
},
|
|
178
|
+
),
|
|
179
|
+
Tool(
|
|
180
|
+
name="ate_adapt",
|
|
181
|
+
description="Generate adaptation plan for transferring skills between robots",
|
|
182
|
+
inputSchema={
|
|
183
|
+
"type": "object",
|
|
184
|
+
"properties": {
|
|
185
|
+
"source_robot_id": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Source robot ID",
|
|
188
|
+
},
|
|
189
|
+
"target_robot_id": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "Target robot ID",
|
|
192
|
+
},
|
|
193
|
+
"repository_id": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"description": "Repository ID to adapt",
|
|
196
|
+
},
|
|
197
|
+
"analyze_only": {
|
|
198
|
+
"type": "boolean",
|
|
199
|
+
"description": "Only show compatibility analysis",
|
|
200
|
+
"default": False,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
"required": ["source_robot_id", "target_robot_id", "repository_id"],
|
|
204
|
+
},
|
|
205
|
+
),
|
|
206
|
+
Tool(
|
|
207
|
+
name="ate_get_repository",
|
|
208
|
+
description="Get details of a specific repository",
|
|
209
|
+
inputSchema={
|
|
210
|
+
"type": "object",
|
|
211
|
+
"properties": {
|
|
212
|
+
"repo_id": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "Repository ID",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
"required": ["repo_id"],
|
|
218
|
+
},
|
|
219
|
+
),
|
|
220
|
+
Tool(
|
|
221
|
+
name="ate_get_robot",
|
|
222
|
+
description="Get details of a specific robot profile",
|
|
223
|
+
inputSchema={
|
|
224
|
+
"type": "object",
|
|
225
|
+
"properties": {
|
|
226
|
+
"robot_id": {
|
|
227
|
+
"type": "string",
|
|
228
|
+
"description": "Robot profile ID",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
"required": ["robot_id"],
|
|
232
|
+
},
|
|
233
|
+
),
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@server.call_tool()
|
|
238
|
+
async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
|
239
|
+
"""Handle tool calls"""
|
|
240
|
+
try:
|
|
241
|
+
if name == "ate_init":
|
|
242
|
+
result = client.init(
|
|
243
|
+
arguments["name"],
|
|
244
|
+
arguments.get("description", ""),
|
|
245
|
+
arguments.get("visibility", "public"),
|
|
246
|
+
)
|
|
247
|
+
return [
|
|
248
|
+
TextContent(
|
|
249
|
+
type="text",
|
|
250
|
+
text=f"Repository created successfully!\nID: {result['repository']['id']}\nName: {result['repository']['name']}",
|
|
251
|
+
)
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
elif name == "ate_clone":
|
|
255
|
+
client.clone(
|
|
256
|
+
arguments["repo_id"], arguments.get("target_dir")
|
|
257
|
+
)
|
|
258
|
+
return [
|
|
259
|
+
TextContent(
|
|
260
|
+
type="text",
|
|
261
|
+
text=f"Repository cloned successfully to {arguments.get('target_dir', 'current directory')}",
|
|
262
|
+
)
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
elif name == "ate_list_repositories":
|
|
266
|
+
# Build query params
|
|
267
|
+
params = {}
|
|
268
|
+
if arguments.get("search"):
|
|
269
|
+
params["search"] = arguments["search"]
|
|
270
|
+
if arguments.get("robot_model"):
|
|
271
|
+
params["robotModel"] = arguments["robot_model"]
|
|
272
|
+
params["limit"] = arguments.get("limit", 20)
|
|
273
|
+
|
|
274
|
+
# Make API request
|
|
275
|
+
response = client._request("GET", "/repositories", params=params)
|
|
276
|
+
repos = response.get("repositories", [])
|
|
277
|
+
|
|
278
|
+
result_text = f"Found {len(repos)} repositories:\n\n"
|
|
279
|
+
for repo in repos[:10]: # Limit to first 10
|
|
280
|
+
result_text += f"- {repo['name']} (ID: {repo['id']})\n"
|
|
281
|
+
if repo.get("description"):
|
|
282
|
+
result_text += f" {repo['description'][:100]}...\n"
|
|
283
|
+
|
|
284
|
+
return [TextContent(type="text", text=result_text)]
|
|
285
|
+
|
|
286
|
+
elif name == "ate_list_robots":
|
|
287
|
+
# Build query params
|
|
288
|
+
params = {}
|
|
289
|
+
if arguments.get("search"):
|
|
290
|
+
params["search"] = arguments["search"]
|
|
291
|
+
if arguments.get("category"):
|
|
292
|
+
params["category"] = arguments["category"]
|
|
293
|
+
params["limit"] = arguments.get("limit", 20)
|
|
294
|
+
|
|
295
|
+
# Make API request
|
|
296
|
+
response = client._request("GET", "/robots/profiles", params=params)
|
|
297
|
+
robots = response.get("profiles", [])
|
|
298
|
+
|
|
299
|
+
result_text = f"Found {len(robots)} robot profiles:\n\n"
|
|
300
|
+
for robot in robots[:10]: # Limit to first 10
|
|
301
|
+
result_text += f"- {robot['modelName']} by {robot['manufacturer']} (ID: {robot['id']})\n"
|
|
302
|
+
if robot.get("description"):
|
|
303
|
+
result_text += f" {robot['description'][:100]}...\n"
|
|
304
|
+
|
|
305
|
+
return [TextContent(type="text", text=result_text)]
|
|
306
|
+
|
|
307
|
+
elif name == "ate_compatibility":
|
|
308
|
+
response = client._request(
|
|
309
|
+
"POST",
|
|
310
|
+
"/skills/compatibility",
|
|
311
|
+
json={
|
|
312
|
+
"sourceRobotId": arguments["source_robot_id"],
|
|
313
|
+
"targetRobotId": arguments["target_robot_id"],
|
|
314
|
+
"repositoryId": arguments["repository_id"],
|
|
315
|
+
},
|
|
316
|
+
)
|
|
317
|
+
compatibility = response.get("compatibility", {})
|
|
318
|
+
score = compatibility.get("overallScore", 0) * 100
|
|
319
|
+
|
|
320
|
+
result_text = f"Compatibility Score: {score:.1f}%\n"
|
|
321
|
+
result_text += f"Adaptation Type: {compatibility.get('adaptationType', 'unknown')}\n"
|
|
322
|
+
result_text += f"Estimated Effort: {compatibility.get('estimatedEffort', 'unknown')}\n"
|
|
323
|
+
|
|
324
|
+
return [TextContent(type="text", text=result_text)]
|
|
325
|
+
|
|
326
|
+
elif name == "ate_adapt":
|
|
327
|
+
response = client._request(
|
|
328
|
+
"POST",
|
|
329
|
+
"/skills/adapt",
|
|
330
|
+
json={
|
|
331
|
+
"sourceRobotId": arguments["source_robot_id"],
|
|
332
|
+
"targetRobotId": arguments["target_robot_id"],
|
|
333
|
+
"repositoryId": arguments["repository_id"],
|
|
334
|
+
},
|
|
335
|
+
)
|
|
336
|
+
plan = response.get("adaptationPlan", {})
|
|
337
|
+
compatibility = response.get("compatibility", {})
|
|
338
|
+
|
|
339
|
+
result_text = "Adaptation Plan:\n\n"
|
|
340
|
+
result_text += f"Overview: {plan.get('overview', 'No overview available')}\n\n"
|
|
341
|
+
|
|
342
|
+
if compatibility:
|
|
343
|
+
result_text += f"Compatibility Score: {compatibility.get('overallScore', 0) * 100:.1f}%\n"
|
|
344
|
+
result_text += f"Adaptation Type: {compatibility.get('adaptationType', 'unknown')}\n"
|
|
345
|
+
|
|
346
|
+
return [TextContent(type="text", text=result_text)]
|
|
347
|
+
|
|
348
|
+
elif name == "ate_get_repository":
|
|
349
|
+
response = client._request("GET", f"/repositories/{arguments['repo_id']}")
|
|
350
|
+
repo = response.get("repository", {})
|
|
351
|
+
|
|
352
|
+
result_text = f"Repository: {repo.get('name', 'Unknown')}\n"
|
|
353
|
+
result_text += f"ID: {repo.get('id', 'Unknown')}\n"
|
|
354
|
+
result_text += f"Description: {repo.get('description', 'No description')}\n"
|
|
355
|
+
result_text += f"Visibility: {repo.get('visibility', 'unknown')}\n"
|
|
356
|
+
|
|
357
|
+
return [TextContent(type="text", text=result_text)]
|
|
358
|
+
|
|
359
|
+
elif name == "ate_get_robot":
|
|
360
|
+
response = client._request("GET", f"/robots/profiles/{arguments['robot_id']}")
|
|
361
|
+
robot = response.get("profile", {})
|
|
362
|
+
|
|
363
|
+
result_text = f"Robot: {robot.get('modelName', 'Unknown')}\n"
|
|
364
|
+
result_text += f"Manufacturer: {robot.get('manufacturer', 'Unknown')}\n"
|
|
365
|
+
result_text += f"Category: {robot.get('category', 'Unknown')}\n"
|
|
366
|
+
result_text += f"Description: {robot.get('description', 'No description')}\n"
|
|
367
|
+
|
|
368
|
+
return [TextContent(type="text", text=result_text)]
|
|
369
|
+
|
|
370
|
+
else:
|
|
371
|
+
return [
|
|
372
|
+
TextContent(
|
|
373
|
+
type="text",
|
|
374
|
+
text=f"Unknown tool: {name}",
|
|
375
|
+
)
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
except Exception as e:
|
|
379
|
+
return [
|
|
380
|
+
TextContent(
|
|
381
|
+
type="text",
|
|
382
|
+
text=f"Error executing tool {name}: {str(e)}",
|
|
383
|
+
)
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@server.list_resources()
|
|
388
|
+
async def list_resources() -> List[Resource]:
|
|
389
|
+
"""List available resources"""
|
|
390
|
+
return [
|
|
391
|
+
Resource(
|
|
392
|
+
uri="repository://*",
|
|
393
|
+
name="Repository",
|
|
394
|
+
description="Access FoodforThought repository details",
|
|
395
|
+
mimeType="application/json",
|
|
396
|
+
),
|
|
397
|
+
Resource(
|
|
398
|
+
uri="robot://*",
|
|
399
|
+
name="Robot Profile",
|
|
400
|
+
description="Access robot profile details",
|
|
401
|
+
mimeType="application/json",
|
|
402
|
+
),
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@server.read_resource()
|
|
407
|
+
async def read_resource(uri: str) -> str:
|
|
408
|
+
"""Read a resource"""
|
|
409
|
+
if uri.startswith("repository://"):
|
|
410
|
+
repo_id = uri.replace("repository://", "")
|
|
411
|
+
response = client._request("GET", f"/repositories/{repo_id}")
|
|
412
|
+
return json.dumps(response.get("repository", {}), indent=2)
|
|
413
|
+
elif uri.startswith("robot://"):
|
|
414
|
+
robot_id = uri.replace("robot://", "")
|
|
415
|
+
response = client._request("GET", f"/robots/profiles/{robot_id}")
|
|
416
|
+
return json.dumps(response.get("profile", {}), indent=2)
|
|
417
|
+
else:
|
|
418
|
+
raise ValueError(f"Unknown resource URI: {uri}")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@server.list_prompts()
|
|
422
|
+
async def list_prompts() -> List[Prompt]:
|
|
423
|
+
"""List available prompts"""
|
|
424
|
+
return [
|
|
425
|
+
Prompt(
|
|
426
|
+
name="create_skill",
|
|
427
|
+
description="Guided workflow for creating a new robot skill repository",
|
|
428
|
+
arguments=[
|
|
429
|
+
PromptArgument(
|
|
430
|
+
name="robot_model",
|
|
431
|
+
description="Target robot model",
|
|
432
|
+
required=True,
|
|
433
|
+
),
|
|
434
|
+
PromptArgument(
|
|
435
|
+
name="task_description",
|
|
436
|
+
description="Description of the skill/task",
|
|
437
|
+
required=True,
|
|
438
|
+
),
|
|
439
|
+
],
|
|
440
|
+
),
|
|
441
|
+
Prompt(
|
|
442
|
+
name="adapt_skill",
|
|
443
|
+
description="Guided workflow for adapting a skill between robots",
|
|
444
|
+
arguments=[
|
|
445
|
+
PromptArgument(
|
|
446
|
+
name="source_robot",
|
|
447
|
+
description="Source robot model",
|
|
448
|
+
required=True,
|
|
449
|
+
),
|
|
450
|
+
PromptArgument(
|
|
451
|
+
name="target_robot",
|
|
452
|
+
description="Target robot model",
|
|
453
|
+
required=True,
|
|
454
|
+
),
|
|
455
|
+
PromptArgument(
|
|
456
|
+
name="repository_id",
|
|
457
|
+
description="Repository ID to adapt",
|
|
458
|
+
required=True,
|
|
459
|
+
),
|
|
460
|
+
],
|
|
461
|
+
),
|
|
462
|
+
]
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@server.get_prompt()
|
|
466
|
+
async def get_prompt(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
|
467
|
+
"""Get prompt content"""
|
|
468
|
+
if name == "create_skill":
|
|
469
|
+
return [
|
|
470
|
+
TextContent(
|
|
471
|
+
type="text",
|
|
472
|
+
text=f"""Create a new robot skill for {arguments.get('robot_model', 'your robot')}:
|
|
473
|
+
|
|
474
|
+
1. Initialize repository: Use ate_init with a descriptive name
|
|
475
|
+
2. Add your skill files (code, configs, documentation)
|
|
476
|
+
3. Commit changes: Use ate_commit with a meaningful message
|
|
477
|
+
4. Push to FoodforThought: Use ate_push
|
|
478
|
+
|
|
479
|
+
Task: {arguments.get('task_description', 'Not specified')}
|
|
480
|
+
""",
|
|
481
|
+
)
|
|
482
|
+
]
|
|
483
|
+
elif name == "adapt_skill":
|
|
484
|
+
return [
|
|
485
|
+
TextContent(
|
|
486
|
+
type="text",
|
|
487
|
+
text=f"""Adapt skill from {arguments.get('source_robot')} to {arguments.get('target_robot')}:
|
|
488
|
+
|
|
489
|
+
1. Check compatibility: Use ate_compatibility to see if adaptation is feasible
|
|
490
|
+
2. Generate adaptation plan: Use ate_adapt to get detailed adaptation instructions
|
|
491
|
+
3. Review the plan and apply necessary changes
|
|
492
|
+
4. Test the adapted skill
|
|
493
|
+
|
|
494
|
+
Repository ID: {arguments.get('repository_id')}
|
|
495
|
+
""",
|
|
496
|
+
)
|
|
497
|
+
]
|
|
498
|
+
else:
|
|
499
|
+
return [TextContent(type="text", text=f"Unknown prompt: {name}")]
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
async def main():
|
|
503
|
+
"""Main entry point for MCP server"""
|
|
504
|
+
# Run the server using stdio transport
|
|
505
|
+
# stdio_server() returns (stdin, stdout) streams
|
|
506
|
+
stdin, stdout = stdio_server()
|
|
507
|
+
await server.run(
|
|
508
|
+
stdin, stdout, server.create_initialization_options()
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
if __name__ == "__main__":
|
|
513
|
+
asyncio.run(main())
|
|
514
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foodforthought-cli
|
|
3
|
+
Version: 0.1.1
|
|
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
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description
|
|
32
|
+
Dynamic: description-content-type
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: keywords
|
|
35
|
+
Dynamic: project-url
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
39
|
+
|
|
40
|
+
# FoodforThought CLI
|
|
41
|
+
|
|
42
|
+
GitHub-like CLI tool for the FoodforThought robotics repository platform.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install foodforthought-cli
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or install from source:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd foodforthought-cli
|
|
54
|
+
pip install -e .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Set environment variables:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export ATE_API_URL="https://kindly.fyi/api"
|
|
63
|
+
export ATE_API_KEY="your-api-key-here"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Initialize a repository
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
ate init my-robot-skill -d "A skill for my robot" -v public
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Clone a repository
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
ate clone <repository-id>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Create a commit
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ate commit -m "Add new control algorithm"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Push to remote
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
ate push -b main
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Deploy to robot
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
ate deploy unitree-r1
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Commands
|
|
99
|
+
|
|
100
|
+
### Repository Management
|
|
101
|
+
- `ate init <name>` - Initialize a new repository
|
|
102
|
+
- `ate clone <repo-id>` - Clone a repository
|
|
103
|
+
- `ate commit -m <message>` - Create a commit
|
|
104
|
+
- `ate push [-b <branch>]` - Push commits to remote
|
|
105
|
+
|
|
106
|
+
### Skill Pipeline
|
|
107
|
+
- `ate pull <skill-id> [--robot <robot>] [--format json|rlds|lerobot] [--output ./data]` - Pull skill data for training
|
|
108
|
+
- `ate upload <video-path> --robot <robot> --task <task> [--project <id>]` - Upload demonstrations for labeling
|
|
109
|
+
- `ate check-transfer --from <source-robot> --to <target-robot> [--skill <id>]` - Check skill transfer compatibility
|
|
110
|
+
- `ate labeling-status <job-id>` - Check labeling job status
|
|
111
|
+
|
|
112
|
+
### Deployment & Testing
|
|
113
|
+
- `ate deploy <robot-type>` - Deploy to a robot (e.g., unitree-r1)
|
|
114
|
+
- `ate test [-e gazebo|mujoco|pybullet|webots] [-r robot]` - Test skills in simulation
|
|
115
|
+
- `ate benchmark [-t speed|accuracy|robustness|efficiency|all]` - Run performance benchmarks
|
|
116
|
+
- `ate adapt <source-robot> <target-robot>` - Adapt skills between robots
|
|
117
|
+
|
|
118
|
+
### Safety & Validation
|
|
119
|
+
- `ate validate [-c collision|speed|workspace|force|all]` - Validate safety and compliance
|
|
120
|
+
- `ate stream [start|stop|status] [-s sensors...]` - Stream sensor data
|
|
121
|
+
|
|
122
|
+
## Cursor IDE Integration (MCP)
|
|
123
|
+
|
|
124
|
+
FoodforThought CLI supports Model Context Protocol (MCP) for integration with Cursor IDE.
|
|
125
|
+
|
|
126
|
+
### Installation
|
|
127
|
+
|
|
128
|
+
1. Install MCP dependencies:
|
|
129
|
+
```bash
|
|
130
|
+
pip install -r requirements-mcp.txt
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
2. Get your MCP configuration from the [FoodforThought homepage](https://kindly.fyi/foodforthought)
|
|
134
|
+
|
|
135
|
+
3. Place `mcp.json` in `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project)
|
|
136
|
+
|
|
137
|
+
4. Set your `ATE_API_KEY` environment variable
|
|
138
|
+
|
|
139
|
+
5. Restart Cursor
|
|
140
|
+
|
|
141
|
+
### Available MCP Tools
|
|
142
|
+
|
|
143
|
+
- `ate_init` - Initialize a new repository
|
|
144
|
+
- `ate_clone` - Clone a repository
|
|
145
|
+
- `ate_list_repositories` - List available repositories
|
|
146
|
+
- `ate_list_robots` - List robot profiles
|
|
147
|
+
- `ate_compatibility` - Check skill compatibility between robots
|
|
148
|
+
- `ate_adapt` - Generate adaptation plans for skills
|
|
149
|
+
|
|
150
|
+
See the [MCP documentation](https://cursor.com/docs/context/mcp) for more information.
|
|
151
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ate/__init__.py,sha256=GcVHEGyItqo9e3uAgF6cvKZOXJKHgj0-AnRJi-oC00g,105
|
|
2
|
+
ate/cli.py,sha256=GzSb2CwWj629JuBsT5SO-yp9HT5pAxD0A-7c_3Mi_Mc,33831
|
|
3
|
+
ate/mcp_server.py,sha256=O77UKrRS-PigEAftmlG8y57mw6pZOA5OTrt2C-aKgyU,17814
|
|
4
|
+
foodforthought_cli-0.1.1.dist-info/METADATA,sha256=tQ78BUHETRDkw3KWEC6XmOySwRhehofv5WeOLzoCezk,4353
|
|
5
|
+
foodforthought_cli-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
foodforthought_cli-0.1.1.dist-info/entry_points.txt,sha256=JSxWuXCbGllyHqVJpomP_BDlXYpiNglM9Mo6bEmQK1A,37
|
|
7
|
+
foodforthought_cli-0.1.1.dist-info/top_level.txt,sha256=dlAru-z7a6fWHgnrMWcQhKc8D4eSTXSpFLIUoWZwDlE,4
|
|
8
|
+
foodforthought_cli-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ate
|