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.
Files changed (32) hide show
  1. {meshcode-2.9.1 → meshcode-2.9.3}/PKG-INFO +1 -1
  2. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/__init__.py +1 -1
  3. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/ascii_art.py +39 -2
  4. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/server.py +44 -1
  5. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/run_agent.py +35 -14
  6. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.9.1 → meshcode-2.9.3}/pyproject.toml +1 -1
  8. {meshcode-2.9.1 → meshcode-2.9.3}/README.md +0 -0
  9. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/cli.py +0 -0
  10. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/comms_v4.py +0 -0
  11. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/invites.py +0 -0
  12. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/launcher.py +0 -0
  13. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/launcher_install.py +0 -0
  14. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/backend.py +0 -0
  17. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/realtime.py +0 -0
  18. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/test_backend.py +0 -0
  19. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  20. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  21. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/preferences.py +0 -0
  22. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/protocol_v2.py +0 -0
  23. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/secrets.py +0 -0
  24. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/self_update.py +0 -0
  25. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.9.1 → meshcode-2.9.3}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.9.1 → meshcode-2.9.3}/setup.cfg +0 -0
  32. {meshcode-2.9.1 → meshcode-2.9.3}/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.9.1
3
+ Version: 2.9.3
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.9.1"
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) -> str:
443
+ role: str = "", stats: dict = None,
444
+ profile_color: str = None) -> str:
413
445
  h = _hash_bytes(agent_name)
414
- color = COLORS[h[0] % len(COLORS)]
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
- """Deterministic ANSI color from agent name hash."""
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) -> str:
36
- """Fetch ASCII art from server. If not stored yet, generate and save it."""
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 and data[0].get("ascii_art"):
80
- return data[0]["ascii_art"]
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=agent, stats=agent_stats,
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.9.1
3
+ Version: 2.9.3
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.9.1"
7
+ version = "2.9.3"
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
File without changes