Clawdbot commited on
Commit ·
702e56d
1
Parent(s): c412eea
Add MEP CLI Provider for autonomous terminal agents
Browse files- 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.")
|