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.
Files changed (32) hide show
  1. {meshcode-2.9.2 → meshcode-2.9.4}/PKG-INFO +1 -1
  2. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/__init__.py +1 -1
  3. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/ascii_art.py +39 -2
  4. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/server.py +44 -1
  5. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/run_agent.py +40 -15
  6. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.9.2 → meshcode-2.9.4}/pyproject.toml +1 -1
  8. {meshcode-2.9.2 → meshcode-2.9.4}/README.md +0 -0
  9. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/cli.py +0 -0
  10. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/comms_v4.py +0 -0
  11. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/invites.py +0 -0
  12. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/launcher.py +0 -0
  13. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/launcher_install.py +0 -0
  14. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/backend.py +0 -0
  17. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/realtime.py +0 -0
  18. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/test_backend.py +0 -0
  19. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  20. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  21. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/preferences.py +0 -0
  22. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/protocol_v2.py +0 -0
  23. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/secrets.py +0 -0
  24. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/self_update.py +0 -0
  25. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode/setup_clients.py +0 -0
  26. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/SOURCES.txt +0 -0
  27. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/dependency_links.txt +0 -0
  28. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/entry_points.txt +0 -0
  29. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/requires.txt +0 -0
  30. {meshcode-2.9.2 → meshcode-2.9.4}/meshcode.egg-info/top_level.txt +0 -0
  31. {meshcode-2.9.2 → meshcode-2.9.4}/setup.cfg +0 -0
  32. {meshcode-2.9.2 → meshcode-2.9.4}/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.2
3
+ Version: 2.9.4
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.2"
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) -> 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
@@ -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 from server. If not stored yet, generate and save.
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, 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/mc_projects?select=id&name=eq.{urllib.parse.quote(project)}",
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
- "Accept-Profile": "meshcode",
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[0]["id"]
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 and data[0].get("ascii_art"):
81
- return data[0]["ascii_art"], data[0].get("role") or agent
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.9.2
3
+ Version: 2.9.4
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.2"
7
+ version = "2.9.4"
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