meshcode 2.8.5__tar.gz → 2.8.7__tar.gz

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.
Files changed (32) hide show
  1. {meshcode-2.8.5 → meshcode-2.8.7}/PKG-INFO +1 -1
  2. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/__init__.py +1 -1
  3. meshcode-2.8.7/meshcode/ascii_art.py +124 -0
  4. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/comms_v4.py +26 -1
  5. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/run_agent.py +80 -0
  6. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/SOURCES.txt +1 -0
  8. {meshcode-2.8.5 → meshcode-2.8.7}/pyproject.toml +1 -1
  9. {meshcode-2.8.5 → meshcode-2.8.7}/README.md +0 -0
  10. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/cli.py +0 -0
  11. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/invites.py +0 -0
  12. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/launcher.py +0 -0
  13. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/launcher_install.py +0 -0
  14. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/backend.py +0 -0
  17. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/realtime.py +0 -0
  18. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/server.py +0 -0
  19. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/test_backend.py +0 -0
  20. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  21. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  22. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/preferences.py +0 -0
  23. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/protocol_v2.py +0 -0
  24. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/secrets.py +0 -0
  25. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/self_update.py +0 -0
  26. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/setup_clients.py +0 -0
  27. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.8.5 → meshcode-2.8.7}/setup.cfg +0 -0
  32. {meshcode-2.8.5 → meshcode-2.8.7}/tests/test_status_enum_coverage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.8.5
3
+ Version: 2.8.7
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,2 +1,2 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.8.5"
2
+ __version__ = "2.8.7"
@@ -0,0 +1,124 @@
1
+ """Procedural ASCII art generator for MeshCode agents.
2
+
3
+ Each agent gets a unique visual identity — like a QR code / identicon
4
+ but made of block characters. Generated deterministically from the
5
+ agent name hash, so it's always the same for the same agent.
6
+ Stored in mc_agents.ascii_art, displayed on terminal launch.
7
+ """
8
+ import hashlib
9
+
10
+ # Block characters for the pattern
11
+ BLOCKS = {
12
+ 0: " ", # empty
13
+ 1: "██", # full block
14
+ 2: "░░", # light shade
15
+ 3: "▓▓", # dark shade
16
+ 4: "▒▒", # medium shade
17
+ 5: "╬╬", # cross
18
+ 6: "◆◆", # diamond
19
+ 7: "●●", # circle
20
+ }
21
+
22
+ # ANSI colors
23
+ COLORS = [
24
+ "\033[36m", # cyan
25
+ "\033[35m", # magenta
26
+ "\033[32m", # green
27
+ "\033[33m", # yellow
28
+ "\033[34m", # blue
29
+ "\033[91m", # bright red
30
+ "\033[96m", # bright cyan
31
+ "\033[95m", # bright magenta
32
+ "\033[92m", # bright green
33
+ "\033[94m", # bright blue
34
+ ]
35
+ RESET = "\033[0m"
36
+ DIM = "\033[2m"
37
+ BOLD = "\033[1m"
38
+
39
+
40
+ def _hash_bytes(name: str) -> bytes:
41
+ """Get deterministic hash bytes from agent name."""
42
+ return hashlib.sha256(name.encode("utf-8")).digest()
43
+
44
+
45
+ def generate_art(agent_name: str, size: int = 7) -> str:
46
+ """Generate a unique symmetric block pattern from the agent name.
47
+
48
+ Creates a (size x size) grid that's mirrored horizontally for symmetry
49
+ (like identicons / QR codes). The pattern is deterministic — same name
50
+ always produces the same art.
51
+ """
52
+ h = _hash_bytes(agent_name)
53
+ half = (size + 1) // 2 # columns to generate (left half + center)
54
+ rows = []
55
+
56
+ for y in range(size):
57
+ row = []
58
+ for x in range(half):
59
+ # Each cell uses a byte from the hash
60
+ idx = (y * half + x) % len(h)
61
+ byte_val = h[idx]
62
+
63
+ # Decide if cell is filled (60% chance) and which block
64
+ if byte_val < 100:
65
+ block = BLOCKS[0] # empty
66
+ else:
67
+ block_idx = (byte_val % 6) + 1 # blocks 1-6
68
+ block = BLOCKS[block_idx]
69
+ row.append(block)
70
+
71
+ # Mirror: build full row (left + center + right)
72
+ if size % 2 == 1:
73
+ full_row = row[:-1] + [row[-1]] + row[-2::-1]
74
+ else:
75
+ full_row = row + row[::-1]
76
+ rows.append("".join(full_row))
77
+
78
+ # Add border
79
+ width = len(rows[0]) if rows else 0
80
+ border_h = "─" * (width + 2)
81
+ lines = []
82
+ lines.append(f" ┌{border_h}┐")
83
+ for row in rows:
84
+ lines.append(f" │ {row} │")
85
+ lines.append(f" └{border_h}┘")
86
+ lines.append(f" ⟨ {agent_name} ⟩")
87
+
88
+ return "\n".join(lines)
89
+
90
+
91
+ def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str, version: str = "") -> str:
92
+ """Render the full colored welcome banner for terminal display."""
93
+ h = _hash_bytes(agent_name)
94
+ color = COLORS[h[0] % len(COLORS)]
95
+
96
+ ver_str = f"v{version}" if version else ""
97
+ lines = []
98
+ lines.append("")
99
+ lines.append(f"{color}{BOLD} ╔══════════════════════════════════════════╗{RESET}")
100
+ lines.append(f"{color}{BOLD} ║{RESET} {DIM}M E S H C O D E{RESET} {color}{ver_str}{RESET} {color}{BOLD}║{RESET}")
101
+ lines.append(f"{color}{BOLD} ╚══════════════════════════════════════════╝{RESET}")
102
+ lines.append("")
103
+
104
+ for line in ascii_art.split("\n"):
105
+ lines.append(f" {color}{line}{RESET}")
106
+
107
+ lines.append("")
108
+ lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET} {DIM}is online{RESET}")
109
+ lines.append(f" {DIM}meshwork:{RESET} {meshwork_name}")
110
+ if version:
111
+ lines.append(f" {DIM}version:{RESET} {version}")
112
+ lines.append("")
113
+
114
+ return "\n".join(lines)
115
+
116
+
117
+ if __name__ == "__main__":
118
+ # Demo: show a few agents
119
+ import sys
120
+ names = sys.argv[1:] or ["sammy", "ian", "fis", "backend", "security", "commander"]
121
+ for name in names:
122
+ art = generate_art(name)
123
+ print(render_welcome(name, "demo-mesh", art, "2.8.6"))
124
+ print()
@@ -1392,9 +1392,20 @@ def _start_heartbeat_daemon(project, name):
1392
1392
  " d=json.loads(urllib.request.urlopen(req,timeout=10).read())\n"
1393
1393
  " return d[0]['id'] if d else None\n"
1394
1394
  " except Exception: return None\n"
1395
+ "def check_still_leased(pid):\n"
1396
+ " \"\"\"Return False if force-disconnected (instance_id cleared).\"\"\"\n"
1397
+ " try:\n"
1398
+ " req=urllib.request.Request(url+'/rest/v1/mc_agents?select=instance_id&project_id=eq.'+pid+'&name=eq.'+urllib.parse.quote(name),\n"
1399
+ " headers={'apikey':key,'Authorization':'Bearer '+key,'Accept-Profile':'meshcode'})\n"
1400
+ " d=json.loads(urllib.request.urlopen(req,timeout=10).read())\n"
1401
+ " return bool(d and d[0].get('instance_id'))\n"
1402
+ " except Exception: return True\n"
1395
1403
  "pid=get_pid_for_project()\n"
1396
1404
  "while True:\n"
1397
- " if pid: post('/rest/v1/rpc/mc_heartbeat', {'p_project_id':pid,'p_agent_name':name})\n"
1405
+ " if pid:\n"
1406
+ " if not check_still_leased(pid):\n"
1407
+ " sys.exit(0)\n"
1408
+ " post('/rest/v1/rpc/mc_heartbeat', {'p_project_id':pid,'p_agent_name':name})\n"
1398
1409
  " time.sleep(30)\n"
1399
1410
  )
1400
1411
  # Windows: start_new_session kwarg doesn't exist. Use creationflags.
@@ -1473,6 +1484,20 @@ def connect_terminal(project, name, role=""):
1473
1484
  session_data = {"project": project, "agent": name, "pid": ppid, "tty": tty, "registered_at": now()}
1474
1485
  (SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data), encoding="utf-8")
1475
1486
 
1487
+ # Track install/connection for admin metrics (fire-and-forget)
1488
+ try:
1489
+ import platform as _platform
1490
+ sb_rpc("mc_track_install", {
1491
+ "p_api_key": _api_key,
1492
+ "p_project_id": project_id,
1493
+ "p_agent_name": name,
1494
+ "p_cli_version": VERSION,
1495
+ "p_platform": sys.platform,
1496
+ "p_editor": os.environ.get("MESHCODE_EDITOR", "unknown"),
1497
+ })
1498
+ except Exception:
1499
+ pass # Non-critical — don't block agent boot
1500
+
1476
1501
  hb_pid = _start_heartbeat_daemon(project, name)
1477
1502
 
1478
1503
  pending = (rpc_result or {}).get("pending_messages", 0)
@@ -32,6 +32,76 @@ WORKSPACES_ROOT = Path.home() / "meshcode"
32
32
  REGISTRY_PATH = WORKSPACES_ROOT / ".registry.json"
33
33
 
34
34
 
35
+ def _fetch_or_generate_art(agent: str, project: str) -> str:
36
+ """Fetch ASCII art from server. If not stored yet, generate and save it."""
37
+ from .ascii_art import generate_art
38
+ try:
39
+ from .setup_clients import _load_supabase_env
40
+ import importlib
41
+ secrets_mod = importlib.import_module("meshcode.secrets")
42
+ except Exception:
43
+ return generate_art(agent)
44
+
45
+ profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
46
+ api_key = secrets_mod.get_api_key(profile=profile)
47
+ if not api_key:
48
+ return generate_art(agent)
49
+
50
+ sb = _load_supabase_env()
51
+ try:
52
+ from urllib.request import Request, urlopen
53
+ import urllib.parse
54
+ # Step 1: resolve project_id
55
+ proj_req = Request(
56
+ f"{sb['SUPABASE_URL']}/rest/v1/mc_projects?select=id&name=eq.{urllib.parse.quote(project)}",
57
+ headers={
58
+ "apikey": sb["SUPABASE_KEY"],
59
+ "Authorization": f"Bearer {sb['SUPABASE_KEY']}",
60
+ "Accept-Profile": "meshcode",
61
+ },
62
+ )
63
+ with urlopen(proj_req, timeout=5) as resp:
64
+ proj_data = json.loads(resp.read().decode())
65
+ if not proj_data:
66
+ return generate_art(agent)
67
+ project_id = proj_data[0]["id"]
68
+ # Step 2: fetch existing art
69
+ req = Request(
70
+ f"{sb['SUPABASE_URL']}/rest/v1/mc_agents?select=ascii_art&name=eq.{urllib.parse.quote(agent)}&project_id=eq.{project_id}",
71
+ headers={
72
+ "apikey": sb["SUPABASE_KEY"],
73
+ "Authorization": f"Bearer {sb['SUPABASE_KEY']}",
74
+ "Accept-Profile": "meshcode",
75
+ },
76
+ )
77
+ with urlopen(req, timeout=5) as resp:
78
+ data = json.loads(resp.read().decode())
79
+ if data and data[0].get("ascii_art"):
80
+ return data[0]["ascii_art"]
81
+ except Exception:
82
+ pass
83
+
84
+ # Generate and store
85
+ art = generate_art(agent)
86
+ try:
87
+ body = json.dumps({"p_project_name": project, "p_agent_name": agent, "p_ascii_art": art})
88
+ req = Request(
89
+ f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_store_agent_art",
90
+ data=body.encode(),
91
+ method="POST",
92
+ headers={
93
+ "apikey": sb["SUPABASE_KEY"],
94
+ "Authorization": f"Bearer {sb['SUPABASE_KEY']}",
95
+ "Content-Type": "application/json",
96
+ "Content-Profile": "meshcode",
97
+ },
98
+ )
99
+ urlopen(req, timeout=5)
100
+ except Exception:
101
+ pass
102
+ return art
103
+
104
+
35
105
  def _check_agent_ownership(agent: str, project: str) -> Optional[str]:
36
106
  """Pre-flight check: verify caller owns this agent before launching editor.
37
107
 
@@ -266,6 +336,16 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
266
336
  print("[meshcode] Or open the workspace manually with the editor of your choice.", file=sys.stderr)
267
337
  return 2
268
338
 
339
+ # ── Welcome banner with unique ASCII art ───────────────────────
340
+ try:
341
+ from .ascii_art import generate_art, render_welcome
342
+ from . import __version__ as cli_version
343
+ # Try to fetch stored art from server, generate if missing
344
+ ascii_art = _fetch_or_generate_art(agent, resolved_project)
345
+ print(render_welcome(agent, resolved_project, ascii_art, cli_version))
346
+ except Exception:
347
+ pass # Non-critical — skip banner on error
348
+
269
349
  print(f"[meshcode] Launching {editor} for agent '{agent}' (project: {resolved_project})")
270
350
  print(f"[meshcode] Workspace: {ws}")
271
351
  print(f"[meshcode] MCP server: {server_id}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.8.5
3
+ Version: 2.8.7
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,6 +1,7 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  meshcode/__init__.py
4
+ meshcode/ascii_art.py
4
5
  meshcode/cli.py
5
6
  meshcode/comms_v4.py
6
7
  meshcode/invites.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.8.5"
7
+ version = "2.8.7"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes