meshcode 2.9.2__tar.gz → 2.9.4__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.9.2 → meshcode-2.9.4}/PKG-INFO +1 -1
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/__init__.py +1 -1
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/ascii_art.py +39 -2
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/server.py +44 -1
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/run_agent.py +40 -15
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.9.2 → meshcode-2.9.4}/pyproject.toml +1 -1
- {meshcode-2.9.2 → meshcode-2.9.4}/README.md +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/cli.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/comms_v4.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/invites.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/launcher.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/launcher_install.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/preferences.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/secrets.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/self_update.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/setup_clients.py +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/setup.cfg +0 -0
- {meshcode-2.9.2 → meshcode-2.9.4}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.9.
|
|
2
|
+
__version__ = "2.9.4"
|
|
@@ -153,9 +153,40 @@ COLORS = [
|
|
|
153
153
|
"\033[36m", "\033[35m", "\033[32m", "\033[33m", "\033[34m",
|
|
154
154
|
"\033[91m", "\033[96m", "\033[95m", "\033[92m", "\033[94m",
|
|
155
155
|
]
|
|
156
|
+
# Same palette as hex RGB for nearest-match mapping from dashboard colors
|
|
157
|
+
_ANSI_RGB = [
|
|
158
|
+
(0, 255, 255), # 0: cyan \033[36m
|
|
159
|
+
(255, 0, 255), # 1: magenta \033[35m
|
|
160
|
+
(0, 255, 0), # 2: green \033[32m
|
|
161
|
+
(255, 255, 0), # 3: yellow \033[33m
|
|
162
|
+
(0, 0, 255), # 4: blue \033[34m
|
|
163
|
+
(255, 85, 85), # 5: bright red \033[91m
|
|
164
|
+
(85, 255, 255), # 6: bright cyan \033[96m
|
|
165
|
+
(255, 85, 255), # 7: bright mag \033[95m
|
|
166
|
+
(85, 255, 85), # 8: bright grn \033[92m
|
|
167
|
+
(85, 85, 255), # 9: bright blu \033[94m
|
|
168
|
+
]
|
|
156
169
|
RESET = "\033[0m"
|
|
157
170
|
DIM = "\033[2m"
|
|
158
171
|
BOLD = "\033[1m"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def hex_to_ansi(hex_color: str) -> str:
|
|
175
|
+
"""Map a hex color (e.g. '#F43F5E') to the nearest ANSI color code."""
|
|
176
|
+
hex_color = hex_color.strip().lstrip("#")
|
|
177
|
+
if len(hex_color) != 6:
|
|
178
|
+
return None
|
|
179
|
+
try:
|
|
180
|
+
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
|
|
181
|
+
except ValueError:
|
|
182
|
+
return None
|
|
183
|
+
best_idx, best_dist = 0, float("inf")
|
|
184
|
+
for i, (ar, ag, ab) in enumerate(_ANSI_RGB):
|
|
185
|
+
dist = (r - ar) ** 2 + (g - ag) ** 2 + (b - ab) ** 2
|
|
186
|
+
if dist < best_dist:
|
|
187
|
+
best_dist = dist
|
|
188
|
+
best_idx = i
|
|
189
|
+
return COLORS[best_idx]
|
|
159
190
|
YELLOW = "\033[33m"
|
|
160
191
|
STAR = "★"
|
|
161
192
|
|
|
@@ -409,9 +440,15 @@ def get_tip(agent_name: str) -> str:
|
|
|
409
440
|
|
|
410
441
|
def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str,
|
|
411
442
|
version: str = "", is_commander: bool = False,
|
|
412
|
-
role: str = "", stats: dict = None
|
|
443
|
+
role: str = "", stats: dict = None,
|
|
444
|
+
profile_color: str = None) -> str:
|
|
413
445
|
h = _hash_bytes(agent_name)
|
|
414
|
-
color
|
|
446
|
+
# Use dashboard profile color if available, otherwise MD5 hash fallback
|
|
447
|
+
color = None
|
|
448
|
+
if profile_color:
|
|
449
|
+
color = hex_to_ansi(profile_color)
|
|
450
|
+
if not color:
|
|
451
|
+
color = COLORS[_hash_int(agent_name) % len(COLORS)]
|
|
415
452
|
|
|
416
453
|
traits = get_personality_traits(agent_name, role, stats or {})
|
|
417
454
|
catchphrase = get_catchphrase(agent_name, role)
|
|
@@ -28,8 +28,35 @@ _ANSI_BOLD = "\033[1m"
|
|
|
28
28
|
_ANSI_DIM = "\033[2m"
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
_CACHED_AGENT_COLOR = None # Set once on boot from profile color
|
|
32
|
+
_ANSI_RGB = [
|
|
33
|
+
(0, 255, 255), (255, 0, 255), (0, 255, 0), (255, 255, 0), (0, 0, 255),
|
|
34
|
+
(255, 85, 85), (85, 255, 255), (255, 85, 255), (85, 255, 85), (85, 85, 255),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _hex_to_ansi(hex_color: str) -> str:
|
|
39
|
+
"""Map hex color to nearest ANSI code."""
|
|
40
|
+
hx = hex_color.strip().lstrip("#")
|
|
41
|
+
if len(hx) != 6:
|
|
42
|
+
return None
|
|
43
|
+
try:
|
|
44
|
+
r, g, b = int(hx[0:2], 16), int(hx[2:4], 16), int(hx[4:6], 16)
|
|
45
|
+
except ValueError:
|
|
46
|
+
return None
|
|
47
|
+
best_i, best_d = 0, float("inf")
|
|
48
|
+
for i, (ar, ag, ab) in enumerate(_ANSI_RGB):
|
|
49
|
+
d = (r - ar) ** 2 + (g - ag) ** 2 + (b - ab) ** 2
|
|
50
|
+
if d < best_d:
|
|
51
|
+
best_d = d
|
|
52
|
+
best_i = i
|
|
53
|
+
return _ANSI_COLORS[best_i]
|
|
54
|
+
|
|
55
|
+
|
|
31
56
|
def _agent_color(name: str) -> str:
|
|
32
|
-
"""
|
|
57
|
+
"""ANSI color from profile (dashboard) or MD5 hash fallback."""
|
|
58
|
+
if _CACHED_AGENT_COLOR:
|
|
59
|
+
return _CACHED_AGENT_COLOR
|
|
33
60
|
h = int(_hashlib.md5(name.encode()).hexdigest()[:8], 16)
|
|
34
61
|
return _ANSI_COLORS[h % len(_ANSI_COLORS)]
|
|
35
62
|
|
|
@@ -384,6 +411,22 @@ _register_result = be.register_agent(PROJECT_NAME, AGENT_NAME, AGENT_ROLE or "MC
|
|
|
384
411
|
if isinstance(_register_result, dict) and _register_result.get("error"):
|
|
385
412
|
_mc_log(f" register failed: {_register_result['error']}", "warn")
|
|
386
413
|
|
|
414
|
+
# ── Fetch profile color from dashboard (single source of truth) ──
|
|
415
|
+
try:
|
|
416
|
+
_agent_rows = be.sb_select("mc_agents", f"project_id=eq.{_PROJECT_ID}&name=eq.{AGENT_NAME}", limit=1)
|
|
417
|
+
if _agent_rows and isinstance(_agent_rows, list) and len(_agent_rows) > 0:
|
|
418
|
+
_aid = _agent_rows[0].get("id")
|
|
419
|
+
if _aid:
|
|
420
|
+
_prof_rows = be.sb_select("mc_agent_profiles", f"agent_id=eq.{_aid}", limit=1)
|
|
421
|
+
if _prof_rows and isinstance(_prof_rows, list) and len(_prof_rows) > 0:
|
|
422
|
+
_hex = _prof_rows[0].get("color") or ""
|
|
423
|
+
if _hex and len(_hex.lstrip("#")) == 6:
|
|
424
|
+
_resolved = _hex_to_ansi(_hex)
|
|
425
|
+
if _resolved:
|
|
426
|
+
_CACHED_AGENT_COLOR = _resolved
|
|
427
|
+
except Exception:
|
|
428
|
+
pass # Non-critical — falls back to hash
|
|
429
|
+
|
|
387
430
|
# Flip to online so the dashboard reflects the live MCP session.
|
|
388
431
|
# Use the SECURITY DEFINER RPC (mc_agent_set_status_by_api_key) so we
|
|
389
432
|
# bypass RLS — the publishable key has no JWT context and cannot UPDATE
|
|
@@ -33,42 +33,47 @@ REGISTRY_PATH = WORKSPACES_ROOT / ".registry.json"
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def _fetch_or_generate_art(agent: str, project: str) -> tuple:
|
|
36
|
-
"""Fetch ASCII art + role
|
|
37
|
-
Returns (ascii_art, role_description)."""
|
|
36
|
+
"""Fetch ASCII art + role + profile color from server.
|
|
37
|
+
Returns (ascii_art, role_description, profile_color)."""
|
|
38
38
|
from .ascii_art import generate_art
|
|
39
39
|
try:
|
|
40
40
|
from .setup_clients import _load_supabase_env
|
|
41
41
|
import importlib
|
|
42
42
|
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
43
43
|
except Exception:
|
|
44
|
-
return generate_art(agent), agent
|
|
44
|
+
return generate_art(agent), agent, None
|
|
45
45
|
|
|
46
46
|
profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
47
47
|
api_key = secrets_mod.get_api_key(profile=profile)
|
|
48
48
|
if not api_key:
|
|
49
|
-
return generate_art(agent), agent,
|
|
49
|
+
return generate_art(agent), agent, None
|
|
50
50
|
|
|
51
51
|
sb = _load_supabase_env()
|
|
52
|
+
profile_color = None
|
|
52
53
|
try:
|
|
53
54
|
from urllib.request import Request, urlopen
|
|
54
55
|
import urllib.parse
|
|
55
|
-
# Step 1: resolve project_id
|
|
56
|
+
# Step 1: resolve project_id via RPC (bypasses RLS on mc_projects)
|
|
57
|
+
resolve_body = json.dumps({"p_api_key": api_key, "p_project_name": project})
|
|
56
58
|
proj_req = Request(
|
|
57
|
-
f"{sb['SUPABASE_URL']}/rest/v1/
|
|
59
|
+
f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_resolve_project",
|
|
60
|
+
data=resolve_body.encode(),
|
|
61
|
+
method="POST",
|
|
58
62
|
headers={
|
|
59
63
|
"apikey": sb["SUPABASE_KEY"],
|
|
60
64
|
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
61
|
-
"
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
"Content-Profile": "meshcode",
|
|
62
67
|
},
|
|
63
68
|
)
|
|
64
69
|
with urlopen(proj_req, timeout=5) as resp:
|
|
65
70
|
proj_data = json.loads(resp.read().decode())
|
|
66
|
-
if not proj_data:
|
|
67
|
-
return generate_art(agent), agent
|
|
68
|
-
project_id = proj_data[
|
|
71
|
+
if not proj_data or not proj_data.get("project_id"):
|
|
72
|
+
return generate_art(agent), agent, None
|
|
73
|
+
project_id = proj_data["project_id"]
|
|
69
74
|
# Step 2: fetch existing art + role
|
|
70
75
|
req = Request(
|
|
71
|
-
f"{sb['SUPABASE_URL']}/rest/v1/mc_agents?select=ascii_art,role&name=eq.{urllib.parse.quote(agent)}&project_id=eq.{project_id}",
|
|
76
|
+
f"{sb['SUPABASE_URL']}/rest/v1/mc_agents?select=ascii_art,role,id&name=eq.{urllib.parse.quote(agent)}&project_id=eq.{project_id}",
|
|
72
77
|
headers={
|
|
73
78
|
"apikey": sb["SUPABASE_KEY"],
|
|
74
79
|
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
@@ -77,8 +82,27 @@ def _fetch_or_generate_art(agent: str, project: str) -> tuple:
|
|
|
77
82
|
)
|
|
78
83
|
with urlopen(req, timeout=5) as resp:
|
|
79
84
|
data = json.loads(resp.read().decode())
|
|
80
|
-
if data
|
|
81
|
-
|
|
85
|
+
if data:
|
|
86
|
+
agent_id = data[0].get("id")
|
|
87
|
+
# Step 3: fetch profile color
|
|
88
|
+
if agent_id:
|
|
89
|
+
try:
|
|
90
|
+
prof_req = Request(
|
|
91
|
+
f"{sb['SUPABASE_URL']}/rest/v1/mc_agent_profiles?select=color&agent_id=eq.{agent_id}",
|
|
92
|
+
headers={
|
|
93
|
+
"apikey": sb["SUPABASE_KEY"],
|
|
94
|
+
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
95
|
+
"Accept-Profile": "meshcode",
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
with urlopen(prof_req, timeout=5) as resp:
|
|
99
|
+
prof_data = json.loads(resp.read().decode())
|
|
100
|
+
if prof_data and prof_data[0].get("color"):
|
|
101
|
+
profile_color = prof_data[0]["color"]
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
if data[0].get("ascii_art"):
|
|
105
|
+
return data[0]["ascii_art"], data[0].get("role") or agent, profile_color
|
|
82
106
|
except Exception:
|
|
83
107
|
pass
|
|
84
108
|
|
|
@@ -100,7 +124,7 @@ def _fetch_or_generate_art(agent: str, project: str) -> tuple:
|
|
|
100
124
|
urlopen(req, timeout=5)
|
|
101
125
|
except Exception:
|
|
102
126
|
pass
|
|
103
|
-
return art, agent
|
|
127
|
+
return art, agent, profile_color
|
|
104
128
|
|
|
105
129
|
|
|
106
130
|
def _fetch_agent_stats(agent: str, project: str) -> dict:
|
|
@@ -381,12 +405,13 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
381
405
|
try:
|
|
382
406
|
from .ascii_art import generate_art, render_welcome
|
|
383
407
|
from . import __version__ as cli_version
|
|
384
|
-
ascii_art, agent_role = _fetch_or_generate_art(agent, resolved_project)
|
|
408
|
+
ascii_art, agent_role, profile_color = _fetch_or_generate_art(agent, resolved_project)
|
|
385
409
|
is_cmd = "commander" in agent.lower() or "commander" in agent_role.lower()
|
|
386
410
|
agent_stats = _fetch_agent_stats(agent, resolved_project)
|
|
387
411
|
print(render_welcome(
|
|
388
412
|
agent, resolved_project, ascii_art, cli_version,
|
|
389
413
|
is_commander=is_cmd, role=agent_role, stats=agent_stats,
|
|
414
|
+
profile_color=profile_color,
|
|
390
415
|
))
|
|
391
416
|
except Exception:
|
|
392
417
|
pass # Non-critical — skip banner on error
|
|
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
|
|
File without changes
|