meshcode 2.9.1__tar.gz → 2.9.3__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.1 → meshcode-2.9.3}/PKG-INFO +1 -1
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/__init__.py +1 -1
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/ascii_art.py +39 -2
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/server.py +44 -1
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/run_agent.py +35 -14
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.9.1 → meshcode-2.9.3}/pyproject.toml +1 -1
- {meshcode-2.9.1 → meshcode-2.9.3}/README.md +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/cli.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/comms_v4.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/invites.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/launcher.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/launcher_install.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/preferences.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/secrets.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/self_update.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/setup_clients.py +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/setup.cfg +0 -0
- {meshcode-2.9.1 → meshcode-2.9.3}/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.3"
|
|
@@ -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
|
|
@@ -32,22 +32,24 @@ 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) ->
|
|
36
|
-
"""Fetch ASCII art
|
|
35
|
+
def _fetch_or_generate_art(agent: str, project: str) -> tuple:
|
|
36
|
+
"""Fetch ASCII art + role + profile color from server.
|
|
37
|
+
Returns (ascii_art, role_description, profile_color)."""
|
|
37
38
|
from .ascii_art import generate_art
|
|
38
39
|
try:
|
|
39
40
|
from .setup_clients import _load_supabase_env
|
|
40
41
|
import importlib
|
|
41
42
|
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
42
43
|
except Exception:
|
|
43
|
-
return generate_art(agent)
|
|
44
|
+
return generate_art(agent), agent, None
|
|
44
45
|
|
|
45
46
|
profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
46
47
|
api_key = secrets_mod.get_api_key(profile=profile)
|
|
47
48
|
if not api_key:
|
|
48
|
-
return generate_art(agent)
|
|
49
|
+
return generate_art(agent), agent, None
|
|
49
50
|
|
|
50
51
|
sb = _load_supabase_env()
|
|
52
|
+
profile_color = None
|
|
51
53
|
try:
|
|
52
54
|
from urllib.request import Request, urlopen
|
|
53
55
|
import urllib.parse
|
|
@@ -63,11 +65,11 @@ def _fetch_or_generate_art(agent: str, project: str) -> str:
|
|
|
63
65
|
with urlopen(proj_req, timeout=5) as resp:
|
|
64
66
|
proj_data = json.loads(resp.read().decode())
|
|
65
67
|
if not proj_data:
|
|
66
|
-
return generate_art(agent)
|
|
68
|
+
return generate_art(agent), agent, None
|
|
67
69
|
project_id = proj_data[0]["id"]
|
|
68
|
-
# Step 2: fetch existing art
|
|
70
|
+
# Step 2: fetch existing art + role
|
|
69
71
|
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}",
|
|
72
|
+
f"{sb['SUPABASE_URL']}/rest/v1/mc_agents?select=ascii_art,role,id&name=eq.{urllib.parse.quote(agent)}&project_id=eq.{project_id}",
|
|
71
73
|
headers={
|
|
72
74
|
"apikey": sb["SUPABASE_KEY"],
|
|
73
75
|
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
@@ -76,8 +78,27 @@ def _fetch_or_generate_art(agent: str, project: str) -> str:
|
|
|
76
78
|
)
|
|
77
79
|
with urlopen(req, timeout=5) as resp:
|
|
78
80
|
data = json.loads(resp.read().decode())
|
|
79
|
-
if data
|
|
80
|
-
|
|
81
|
+
if data:
|
|
82
|
+
agent_id = data[0].get("id")
|
|
83
|
+
# Step 3: fetch profile color
|
|
84
|
+
if agent_id:
|
|
85
|
+
try:
|
|
86
|
+
prof_req = Request(
|
|
87
|
+
f"{sb['SUPABASE_URL']}/rest/v1/mc_agent_profiles?select=color&agent_id=eq.{agent_id}",
|
|
88
|
+
headers={
|
|
89
|
+
"apikey": sb["SUPABASE_KEY"],
|
|
90
|
+
"Authorization": f"Bearer {sb['SUPABASE_KEY']}",
|
|
91
|
+
"Accept-Profile": "meshcode",
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
with urlopen(prof_req, timeout=5) as resp:
|
|
95
|
+
prof_data = json.loads(resp.read().decode())
|
|
96
|
+
if prof_data and prof_data[0].get("color"):
|
|
97
|
+
profile_color = prof_data[0]["color"]
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
if data[0].get("ascii_art"):
|
|
101
|
+
return data[0]["ascii_art"], data[0].get("role") or agent, profile_color
|
|
81
102
|
except Exception:
|
|
82
103
|
pass
|
|
83
104
|
|
|
@@ -99,7 +120,7 @@ def _fetch_or_generate_art(agent: str, project: str) -> str:
|
|
|
99
120
|
urlopen(req, timeout=5)
|
|
100
121
|
except Exception:
|
|
101
122
|
pass
|
|
102
|
-
return art
|
|
123
|
+
return art, agent, profile_color
|
|
103
124
|
|
|
104
125
|
|
|
105
126
|
def _fetch_agent_stats(agent: str, project: str) -> dict:
|
|
@@ -380,13 +401,13 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
380
401
|
try:
|
|
381
402
|
from .ascii_art import generate_art, render_welcome
|
|
382
403
|
from . import __version__ as cli_version
|
|
383
|
-
ascii_art = _fetch_or_generate_art(agent, resolved_project)
|
|
384
|
-
is_cmd = "commander" in agent.lower()
|
|
385
|
-
# Fetch basic stats for achievements (non-blocking)
|
|
404
|
+
ascii_art, agent_role, profile_color = _fetch_or_generate_art(agent, resolved_project)
|
|
405
|
+
is_cmd = "commander" in agent.lower() or "commander" in agent_role.lower()
|
|
386
406
|
agent_stats = _fetch_agent_stats(agent, resolved_project)
|
|
387
407
|
print(render_welcome(
|
|
388
408
|
agent, resolved_project, ascii_art, cli_version,
|
|
389
|
-
is_commander=is_cmd, role=
|
|
409
|
+
is_commander=is_cmd, role=agent_role, stats=agent_stats,
|
|
410
|
+
profile_color=profile_color,
|
|
390
411
|
))
|
|
391
412
|
except Exception:
|
|
392
413
|
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
|