mesh_check.py
1 #!/usr/bin/env python3 2 """ 3 Mesh Check - Quick status for Claude instances 4 5 Usage: 6 python3 scripts/mesh_check.py # Show mesh status 7 python3 scripts/mesh_check.py health # Show detailed health dashboard 8 python3 scripts/mesh_check.py send MSG # Send message to mesh 9 python3 scripts/mesh_check.py ping # Ping mesh with timestamp 10 python3 scripts/mesh_check.py json # Machine-readable status 11 python3 scripts/mesh_check.py health-json # Machine-readable health 12 13 This is the recommended way for Claude instances to interact with the mesh. 14 Wire into CLAUDE.md for N of X discoverability. 15 """ 16 17 import json 18 import sys 19 import urllib.request 20 import urllib.error 21 from datetime import datetime 22 23 MESH_PORT = 7778 24 MESH_URL = f"http://localhost:{MESH_PORT}" 25 26 27 def get_status(): 28 """Get mesh status - peers connected, node name.""" 29 try: 30 with urllib.request.urlopen(f"{MESH_URL}/", timeout=2) as resp: 31 data = json.loads(resp.read().decode()) 32 return { 33 "online": True, 34 "node": data.get("node", "unknown"), 35 "peers": data.get("peers", 0) 36 } 37 except (urllib.error.URLError, Exception): 38 return {"online": False, "node": None, "peers": 0} 39 40 41 def send_message(content: str, from_name: str = "claude"): 42 """Send a message to the mesh.""" 43 try: 44 msg = json.dumps({ 45 "type": "claude_message", 46 "from": from_name, 47 "content": content, 48 "timestamp": datetime.now().isoformat() 49 }).encode() 50 51 req = urllib.request.Request( 52 f"{MESH_URL}/publish", 53 data=msg, 54 headers={"Content-Type": "application/json"}, 55 method="POST" 56 ) 57 58 with urllib.request.urlopen(req, timeout=2) as resp: 59 result = json.loads(resp.read().decode()) 60 return {"ok": True, "peers": result.get("peers", 0)} 61 except (urllib.error.URLError, Exception) as e: 62 return {"ok": False, "error": str(e)} 63 64 65 def get_health(): 66 """Get detailed mesh health status.""" 67 try: 68 with urllib.request.urlopen(f"{MESH_URL}/health", timeout=3) as resp: 69 return json.loads(resp.read().decode()) 70 except (urllib.error.URLError, Exception): 71 return None 72 73 74 def print_status(): 75 """Print formatted mesh status.""" 76 status = get_status() 77 78 print("╔══════════════════════════════════════════╗") 79 print("║ SOVEREIGN MESH ║") 80 print("╠══════════════════════════════════════════╣") 81 82 if status["online"]: 83 print(f"║ Status: \033[32m● ONLINE\033[0m ║") 84 node = (status["node"] or "unknown")[:25].ljust(25) 85 print(f"║ Node: {node} ║") 86 peers = str(status["peers"]).ljust(3) 87 print(f"║ Peers: \033[36m{peers}\033[0m connected ║") 88 else: 89 print(f"║ Status: \033[31m○ OFFLINE\033[0m ║") 90 print("║ Run: launchctl start com.sovereign.mesh ║") 91 92 print("╚══════════════════════════════════════════╝") 93 94 95 def print_health(): 96 """Print detailed mesh health dashboard.""" 97 health = get_health() 98 99 if not health: 100 print("╔════════════════════════════════════════════════════════════╗") 101 print("║ MESH HEALTH ║") 102 print("╠════════════════════════════════════════════════════════════╣") 103 print("║ Status: \033[31m○ OFFLINE\033[0m - Mesh daemon not running ║") 104 print("║ Run: launchctl start com.sovereign.mesh ║") 105 print("╚════════════════════════════════════════════════════════════╝") 106 return 107 108 status = health.get('status', 'unknown') 109 score = health.get('score', 0) 110 uptime = health.get('uptime_human', '?') 111 112 # Color based on status 113 if status == 'healthy': 114 status_color = '\033[32m' # green 115 status_emoji = '✓' 116 elif status == 'degraded': 117 status_color = '\033[33m' # yellow 118 status_emoji = '⚠' 119 else: 120 status_color = '\033[31m' # red 121 status_emoji = '✗' 122 123 # Score bar 124 score_bar = '█' * (score // 10) + '░' * (10 - score // 10) 125 126 print("╔════════════════════════════════════════════════════════════╗") 127 print("║ MESH HEALTH DASHBOARD ║") 128 print("╠════════════════════════════════════════════════════════════╣") 129 print(f"║ Status: {status_color}{status_emoji} {status.upper()}\033[0m ║"[:68] + "║") 130 print(f"║ Score: [{score_bar}] {score}/100 ║"[:68] + "║") 131 print(f"║ Uptime: {uptime} ║"[:68] + "║") 132 print("╠════════════════════════════════════════════════════════════╣") 133 134 # Network 135 net = health.get('network', {}) 136 print("║ NETWORK ║") 137 print(f"║ Peers: {net.get('peers', 0)} ║"[:68] + "║") 138 print(f"║ Nostr relays: {net.get('nostr_relays', 0)} ║"[:68] + "║") 139 print(f"║ Topic: {net.get('mesh_topic', '?')[:16]} ║"[:68] + "║") 140 141 # Storage 142 store = health.get('storage', {}) 143 print("╠════════════════════════════════════════════════════════════╣") 144 print("║ STORAGE ║") 145 print(f"║ Aha moments: {store.get('aha_moments', 0)} ║"[:68] + "║") 146 print(f"║ Principle candidates: {store.get('principle_candidates', 0)} ║"[:68] + "║") 147 print(f"║ Confirmed principles: {store.get('confirmed_principles', 0)} ║"[:68] + "║") 148 print(f"║ Recent messages: {store.get('recent_messages', 0)} ║"[:68] + "║") 149 print(f"║ FO states: {store.get('fo_states', 0)} ║"[:68] + "║") 150 151 # Activity 152 activity = health.get('activity', {}) 153 print("╠════════════════════════════════════════════════════════════╣") 154 print("║ ACTIVITY ║") 155 print(f"║ Last 5 min: {activity.get('messages_last_5min', 0)} messages ║"[:68] + "║") 156 print(f"║ Last hour: {activity.get('messages_last_hour', 0)} messages ║"[:68] + "║") 157 158 # Issues 159 issues = health.get('issues', []) 160 if issues: 161 print("╠════════════════════════════════════════════════════════════╣") 162 print("║ ISSUES ║") 163 for issue in issues[:5]: 164 sev = issue.get('severity', 'info') 165 msg = issue.get('message', '')[:45] 166 if sev == 'warning': 167 print(f"║ \033[33m⚠ {msg}\033[0m" + " " * (50 - len(msg)) + "║") 168 else: 169 print(f"║ ℹ {msg}" + " " * (50 - len(msg)) + "║") 170 171 print("╚════════════════════════════════════════════════════════════╝") 172 173 174 def main(): 175 if len(sys.argv) < 2: 176 print_status() 177 return 178 179 cmd = sys.argv[1].lower() 180 181 if cmd == "status": 182 print_status() 183 184 elif cmd == "send" and len(sys.argv) > 2: 185 content = " ".join(sys.argv[2:]) 186 result = send_message(content) 187 if result["ok"]: 188 print(f"✓ Sent to {result['peers']} peers") 189 else: 190 print(f"✗ Failed: {result.get('error', 'unknown')}") 191 192 elif cmd == "ping": 193 result = send_message(f"ping from claude @ {datetime.now().isoformat()}") 194 if result["ok"]: 195 print(f"✓ Ping sent to {result['peers']} peers") 196 else: 197 print(f"✗ Mesh offline") 198 199 elif cmd == "json": 200 # Machine-readable output 201 print(json.dumps(get_status())) 202 203 elif cmd == "health": 204 print_health() 205 206 elif cmd == "health-json": 207 # Machine-readable health 208 health = get_health() 209 print(json.dumps(health or {"status": "offline"})) 210 211 else: 212 print(__doc__) 213 214 215 if __name__ == "__main__": 216 main()