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.
- {meshcode-2.8.5 → meshcode-2.8.7}/PKG-INFO +1 -1
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/__init__.py +1 -1
- meshcode-2.8.7/meshcode/ascii_art.py +124 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/comms_v4.py +26 -1
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/run_agent.py +80 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/SOURCES.txt +1 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/pyproject.toml +1 -1
- {meshcode-2.8.5 → meshcode-2.8.7}/README.md +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/cli.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/invites.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/launcher.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/launcher_install.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/preferences.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/secrets.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/self_update.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode/setup_clients.py +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/setup.cfg +0 -0
- {meshcode-2.8.5 → meshcode-2.8.7}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.8.
|
|
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
|
|
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}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|