Clawdbot commited on
Commit
702e56d
·
1 Parent(s): c412eea

Add MEP CLI Provider for autonomous terminal agents

Browse files
Files changed (1) hide show
  1. node/mep_cli_provider.py +159 -0
node/mep_cli_provider.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ MEP CLI Provider
4
+ A specialized node that routes tasks to local autonomous CLI agents
5
+ (e.g., Aider, Claude-Code, Open-Interpreter).
6
+ """
7
+ import asyncio
8
+ import websockets
9
+ import json
10
+ import requests
11
+ import uuid
12
+ import sys
13
+ import os
14
+ import shlex
15
+
16
+ HUB_URL = "http://localhost:8000"
17
+ WS_URL = "ws://localhost:8000"
18
+
19
+ class MEPCLIProvider:
20
+ def __init__(self, node_id: str):
21
+ self.node_id = node_id
22
+ self.balance = 0.0
23
+ self.is_contributing = True
24
+ self.capabilities = ["cli-agent", "bash", "python"]
25
+
26
+ # Security: In production, run this inside a Docker container!
27
+ self.workspace_dir = "/tmp/mep_workspaces"
28
+ os.makedirs(self.workspace_dir, exist_ok=True)
29
+
30
+ async def connect(self):
31
+ """Connect to MEP Hub and start listening for CLI tasks."""
32
+ print(f"[CLI Provider {self.node_id}] Starting...")
33
+
34
+ # Register with hub
35
+ try:
36
+ resp = requests.post(f"{HUB_URL}/register", json={"pubkey": self.node_id})
37
+ self.balance = resp.json().get("balance", 0.0)
38
+ print(f"[CLI Provider] Registered. Balance: {self.balance:.6f} SECONDS")
39
+ except Exception as e:
40
+ print(f"[CLI Provider] Registration failed: {e}")
41
+ return
42
+
43
+ uri = f"{WS_URL}/ws/{self.node_id}"
44
+ try:
45
+ async with websockets.connect(uri) as ws:
46
+ print(f"[CLI Provider] Connected to MEP Hub. Awaiting CLI tasks...")
47
+ while self.is_contributing:
48
+ try:
49
+ msg = await asyncio.wait_for(ws.recv(), timeout=1.0)
50
+ data = json.loads(msg)
51
+
52
+ if data["event"] == "new_task":
53
+ await self.process_task(data["data"])
54
+ elif data["event"] == "rfc":
55
+ await self.handle_rfc(data["data"])
56
+
57
+ except asyncio.TimeoutError:
58
+ continue
59
+ except websockets.exceptions.ConnectionClosed:
60
+ print("[CLI Provider] Connection closed")
61
+ break
62
+ except Exception as e:
63
+ print(f"[CLI Provider] WebSocket error: {e}")
64
+
65
+ async def handle_rfc(self, rfc_data: dict):
66
+ """Evaluate if we should bid on this CLI task."""
67
+ task_id = rfc_data["id"]
68
+ bounty = rfc_data["bounty"]
69
+ model = rfc_data.get("model_requirement")
70
+
71
+ # Only bid if the consumer specifically requested a CLI agent
72
+ if model not in self.capabilities and model is not None:
73
+ return
74
+
75
+ print(f"[CLI Provider] Received matching RFC {task_id[:8]} for {bounty:.6f} SECONDS. Bidding...")
76
+
77
+ try:
78
+ resp = requests.post(f"{HUB_URL}/tasks/bid", json={
79
+ "task_id": task_id,
80
+ "provider_id": self.node_id
81
+ })
82
+
83
+ if resp.status_code == 200:
84
+ data = resp.json()
85
+ if data["status"] == "accepted":
86
+ print(f"[CLI Provider] 🏁 BID WON! Executing CLI agent for task {task_id[:8]}...")
87
+ task_data = {
88
+ "id": task_id,
89
+ "payload": data["payload"],
90
+ "bounty": bounty,
91
+ "consumer_id": data["consumer_id"]
92
+ }
93
+ # Run it in background so we don't block the websocket
94
+ asyncio.create_task(self.process_task(task_data))
95
+ except Exception as e:
96
+ print(f"[CLI Provider] Error placing bid: {e}")
97
+
98
+ async def process_task(self, task_data: dict):
99
+ """Execute the task using a local CLI agent."""
100
+ task_id = task_data["id"]
101
+ payload = task_data["payload"]
102
+ bounty = task_data["bounty"]
103
+
104
+ # Create an isolated workspace for this task
105
+ task_dir = os.path.join(self.workspace_dir, task_id)
106
+ os.makedirs(task_dir, exist_ok=True)
107
+
108
+ # Safely escape the payload to prevent shell injection
109
+ safe_payload = shlex.quote(payload)
110
+
111
+ # --- COMMAND TEMPLATE ---
112
+ # Replace this with: f"aider --message {safe_payload}"
113
+ # or: f"claude-code --print {safe_payload}"
114
+ cmd = f"echo '⚙️ Booting Autonomous CLI Agent...' && sleep 1 && echo 'Analyzing: {safe_payload}' && sleep 1 && echo '✅ Code generated and saved to workspace.'"
115
+
116
+ print(f"\n[CLI Agent] Executing in {task_dir}:")
117
+ print(f"$ {cmd[:100]}...\n")
118
+
119
+ # Run the subprocess
120
+ process = await asyncio.create_subprocess_shell(
121
+ cmd,
122
+ stdout=asyncio.subprocess.PIPE,
123
+ stderr=asyncio.subprocess.PIPE,
124
+ cwd=task_dir
125
+ )
126
+
127
+ stdout, stderr = await process.communicate()
128
+
129
+ output = stdout.decode().strip()
130
+ if stderr:
131
+ output += "\n[Errors/Warnings]:\n" + stderr.decode().strip()
132
+
133
+ print(f"[CLI Agent] Finished with exit code {process.returncode}")
134
+
135
+ # Construct final result payload
136
+ result_payload = f"```bash\n{output}\n```\n*Workspace: {task_dir}*"
137
+
138
+ # Submit result back to Hub
139
+ requests.post(f"{HUB_URL}/tasks/complete", json={
140
+ "task_id": task_id,
141
+ "provider_id": self.node_id,
142
+ "result_payload": result_payload
143
+ })
144
+ print(f"[CLI Provider] Result submitted! Earned {bounty:.6f} SECONDS.\n")
145
+
146
+ if __name__ == "__main__":
147
+ print("=" * 60)
148
+ print("MEP Autonomous CLI Provider")
149
+ print("WARNING: This node executes shell commands. Use sandboxing!")
150
+ print("=" * 60)
151
+
152
+ provider_id = f"cli-agent-{uuid.uuid4().hex[:6]}"
153
+ provider = MEPCLIProvider(provider_id)
154
+
155
+ try:
156
+ asyncio.run(provider.connect())
157
+ except KeyboardInterrupt:
158
+ provider.is_contributing = False
159
+ print("\n[MEP] CLI Provider shut down.")