/ scripts / mesh_check.py
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()