meshcode 2.8.6__tar.gz → 2.8.8__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.6 → meshcode-2.8.8}/PKG-INFO +1 -1
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/__init__.py +1 -1
- meshcode-2.8.8/meshcode/ascii_art.py +130 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/comms_v4.py +14 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/server.py +56 -26
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/run_agent.py +82 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode.egg-info/SOURCES.txt +1 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/pyproject.toml +1 -1
- {meshcode-2.8.6 → meshcode-2.8.8}/README.md +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/cli.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/invites.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/launcher.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/launcher_install.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/preferences.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/secrets.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/self_update.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode/setup_clients.py +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/setup.cfg +0 -0
- {meshcode-2.8.6 → meshcode-2.8.8}/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.8"
|
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
YELLOW = "\033[33m"
|
|
92
|
+
STAR = "★"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str,
|
|
96
|
+
version: str = "", is_commander: bool = False) -> str:
|
|
97
|
+
"""Render the full colored welcome banner for terminal display."""
|
|
98
|
+
h = _hash_bytes(agent_name)
|
|
99
|
+
color = COLORS[h[0] % len(COLORS)]
|
|
100
|
+
|
|
101
|
+
ver_str = f"v{version}" if version else ""
|
|
102
|
+
commander_badge = f" {YELLOW}{STAR} COMMANDER{RESET}" if is_commander else ""
|
|
103
|
+
lines = []
|
|
104
|
+
lines.append("")
|
|
105
|
+
lines.append(f"{color}{BOLD} ╔══════════════════════════════════════════╗{RESET}")
|
|
106
|
+
lines.append(f"{color}{BOLD} ║{RESET} {DIM}M E S H C O D E{RESET} {color}{ver_str}{RESET} {color}{BOLD}║{RESET}")
|
|
107
|
+
lines.append(f"{color}{BOLD} ╚══════════════════════════════════════════╝{RESET}")
|
|
108
|
+
lines.append("")
|
|
109
|
+
|
|
110
|
+
for line in ascii_art.split("\n"):
|
|
111
|
+
lines.append(f" {color}{line}{RESET}")
|
|
112
|
+
|
|
113
|
+
lines.append("")
|
|
114
|
+
lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET}{commander_badge} {DIM}is online{RESET}")
|
|
115
|
+
lines.append(f" {DIM}meshwork:{RESET} {meshwork_name}")
|
|
116
|
+
if version:
|
|
117
|
+
lines.append(f" {DIM}version:{RESET} {version}")
|
|
118
|
+
lines.append("")
|
|
119
|
+
|
|
120
|
+
return "\n".join(lines)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
# Demo: show a few agents
|
|
125
|
+
import sys
|
|
126
|
+
names = sys.argv[1:] or ["sammy", "ian", "fis", "backend", "security", "commander"]
|
|
127
|
+
for name in names:
|
|
128
|
+
art = generate_art(name)
|
|
129
|
+
print(render_welcome(name, "demo-mesh", art, "2.8.6"))
|
|
130
|
+
print()
|
|
@@ -1484,6 +1484,20 @@ def connect_terminal(project, name, role=""):
|
|
|
1484
1484
|
session_data = {"project": project, "agent": name, "pid": ppid, "tty": tty, "registered_at": now()}
|
|
1485
1485
|
(SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data), encoding="utf-8")
|
|
1486
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
|
+
|
|
1487
1501
|
hb_pid = _start_heartbeat_daemon(project, name)
|
|
1488
1502
|
|
|
1489
1503
|
pending = (rpc_result or {}).get("pending_messages", 0)
|
|
@@ -12,11 +12,41 @@ import json
|
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
|
+
import hashlib as _hashlib
|
|
15
16
|
from collections import deque
|
|
16
17
|
from contextlib import asynccontextmanager
|
|
17
18
|
from typing import Any, Dict, List, Optional, Union
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
# ── Agent color for terminal logs (pure ANSI, zero tokens) ──────
|
|
22
|
+
_ANSI_COLORS = [
|
|
23
|
+
"\033[36m", "\033[35m", "\033[32m", "\033[33m", "\033[34m",
|
|
24
|
+
"\033[91m", "\033[96m", "\033[95m", "\033[92m", "\033[94m",
|
|
25
|
+
]
|
|
26
|
+
_ANSI_RESET = "\033[0m"
|
|
27
|
+
_ANSI_BOLD = "\033[1m"
|
|
28
|
+
_ANSI_DIM = "\033[2m"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _agent_color(name: str) -> str:
|
|
32
|
+
"""Deterministic ANSI color from agent name hash."""
|
|
33
|
+
h = int(_hashlib.md5(name.encode()).hexdigest()[:8], 16)
|
|
34
|
+
return _ANSI_COLORS[h % len(_ANSI_COLORS)]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _mc_log(msg: str, level: str = "info") -> None:
|
|
38
|
+
"""Colored [meshcode-mcp] log line. Uses agent color if available."""
|
|
39
|
+
agent = os.environ.get("MESHCODE_AGENT", "")
|
|
40
|
+
c = _agent_color(agent) if agent else "\033[36m"
|
|
41
|
+
prefix = f"{c}{_ANSI_BOLD}[meshcode-mcp]{_ANSI_RESET}"
|
|
42
|
+
if level == "error":
|
|
43
|
+
print(f"{prefix} \033[91mERROR:{_ANSI_RESET} {msg}", "warn")
|
|
44
|
+
elif level == "warn":
|
|
45
|
+
print(f"{prefix} \033[33mWARNING:{_ANSI_RESET} {msg}", "warn")
|
|
46
|
+
else:
|
|
47
|
+
print(f"{prefix} {c}{msg}{_ANSI_RESET}", "warn")
|
|
48
|
+
|
|
49
|
+
|
|
20
50
|
# ============================================================
|
|
21
51
|
# Dedupe: track IDs of messages we've already returned from
|
|
22
52
|
# meshcode_wait / meshcode_check so the same row doesn't show
|
|
@@ -309,7 +339,7 @@ def _get_api_key() -> str:
|
|
|
309
339
|
_API_KEY_CACHE = kc_val
|
|
310
340
|
return kc_val
|
|
311
341
|
except Exception as e:
|
|
312
|
-
|
|
342
|
+
_mc_log(f" keychain lookup failed for profile '{profile}': {e}", "warn")
|
|
313
343
|
_API_KEY_CACHE = ""
|
|
314
344
|
return ""
|
|
315
345
|
|
|
@@ -330,13 +360,13 @@ if not _PROJECT_ID:
|
|
|
330
360
|
if isinstance(_r, dict) and _r.get("project_id"):
|
|
331
361
|
_PROJECT_ID = _r["project_id"]
|
|
332
362
|
elif isinstance(_r, dict) and _r.get("error"):
|
|
333
|
-
|
|
363
|
+
_mc_log(f" mc_resolve_project: {_r['error']}", "warn")
|
|
334
364
|
except Exception as _e:
|
|
335
|
-
|
|
365
|
+
_mc_log(f" mc_resolve_project failed: {_e}", "warn")
|
|
336
366
|
if not _PROJECT_ID:
|
|
337
367
|
_PROJECT_ID = be.get_project_id(PROJECT_NAME)
|
|
338
368
|
if not _PROJECT_ID:
|
|
339
|
-
|
|
369
|
+
_mc_log(f"project '{PROJECT_NAME}' not found (check MESHCODE_KEYCHAIN_PROFILE / MESHCODE_API_KEY)", "error")
|
|
340
370
|
sys.exit(2)
|
|
341
371
|
|
|
342
372
|
# Resolve project plan for adaptive features (heartbeat interval, etc.)
|
|
@@ -350,7 +380,7 @@ except Exception:
|
|
|
350
380
|
|
|
351
381
|
_register_result = be.register_agent(PROJECT_NAME, AGENT_NAME, AGENT_ROLE or "MCP-connected agent", api_key=_get_api_key())
|
|
352
382
|
if isinstance(_register_result, dict) and _register_result.get("error"):
|
|
353
|
-
|
|
383
|
+
_mc_log(f" register failed: {_register_result['error']}", "warn")
|
|
354
384
|
|
|
355
385
|
# Flip to online so the dashboard reflects the live MCP session.
|
|
356
386
|
# Use the SECURITY DEFINER RPC (mc_agent_set_status_by_api_key) so we
|
|
@@ -368,7 +398,7 @@ def _flip_status(status: str, task: str = "") -> bool:
|
|
|
368
398
|
return False
|
|
369
399
|
|
|
370
400
|
if not _flip_status("idle", ""):
|
|
371
|
-
|
|
401
|
+
_mc_log(f" could not flip status to idle", "warn")
|
|
372
402
|
|
|
373
403
|
|
|
374
404
|
# ============================================================
|
|
@@ -530,7 +560,7 @@ def _acquire_lease() -> bool:
|
|
|
530
560
|
})
|
|
531
561
|
except Exception as e:
|
|
532
562
|
# Non-fatal: RPC might not exist on older servers.
|
|
533
|
-
|
|
563
|
+
_mc_log(f"stale-lease pre-clean skipped: {e}", "warn")
|
|
534
564
|
for attempt in range(3):
|
|
535
565
|
try:
|
|
536
566
|
r = be.sb_rpc("mc_acquire_agent_lease", {
|
|
@@ -547,11 +577,11 @@ def _acquire_lease() -> bool:
|
|
|
547
577
|
if attempt < 2:
|
|
548
578
|
# The old lease might be stale — wait and retry
|
|
549
579
|
# (the 90s stale check in the RPC should clear it)
|
|
550
|
-
|
|
580
|
+
_mc_log(f"Lease held by another instance — retrying in {2 * (attempt+1)}s...")
|
|
551
581
|
_time.sleep(2 * (attempt + 1))
|
|
552
582
|
continue
|
|
553
583
|
# Final attempt — force release the old lease and try once more
|
|
554
|
-
|
|
584
|
+
_mc_log("Force-releasing stale lease...")
|
|
555
585
|
try:
|
|
556
586
|
be.sb_rpc("mc_release_agent_lease", {
|
|
557
587
|
"p_api_key": api_key,
|
|
@@ -575,21 +605,21 @@ def _acquire_lease() -> bool:
|
|
|
575
605
|
"p_instance_id": _INSTANCE_ID,
|
|
576
606
|
})
|
|
577
607
|
if isinstance(r2, dict) and r2.get("ok"):
|
|
578
|
-
|
|
608
|
+
_mc_log("Lease acquired after force-release.")
|
|
579
609
|
return True
|
|
580
610
|
except Exception:
|
|
581
611
|
pass
|
|
582
|
-
|
|
583
|
-
|
|
612
|
+
_mc_log(f"Could not start — agent '{AGENT_NAME}' is running in another window.", "error")
|
|
613
|
+
_mc_log("Close the other window first, or use a different agent name.", "error")
|
|
584
614
|
return False
|
|
585
|
-
|
|
615
|
+
_mc_log(f"lease attempt {attempt+1}: {r.get('error')}", "warn")
|
|
586
616
|
else:
|
|
587
617
|
return True
|
|
588
618
|
except Exception as e:
|
|
589
|
-
|
|
619
|
+
_mc_log(f"lease attempt {attempt+1} failed: {e}", "warn")
|
|
590
620
|
if attempt < 2:
|
|
591
621
|
_time.sleep(2)
|
|
592
|
-
|
|
622
|
+
_mc_log(f" lease failed after 3 attempts — proceeding anyway", "warn")
|
|
593
623
|
return True
|
|
594
624
|
|
|
595
625
|
if not _acquire_lease():
|
|
@@ -607,7 +637,7 @@ def _boot_diagnostic() -> None:
|
|
|
607
637
|
be.sb_select("mc_projects", f"id=eq.{_PROJECT_ID}", limit=1)
|
|
608
638
|
checks_passed += 1
|
|
609
639
|
except Exception as e:
|
|
610
|
-
print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.",
|
|
640
|
+
print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.", "warn")
|
|
611
641
|
|
|
612
642
|
# Check 2: Lease valid
|
|
613
643
|
try:
|
|
@@ -617,11 +647,11 @@ def _boot_diagnostic() -> None:
|
|
|
617
647
|
if agent.get("instance_id") == _INSTANCE_ID:
|
|
618
648
|
checks_passed += 1
|
|
619
649
|
else:
|
|
620
|
-
print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.",
|
|
650
|
+
print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.", "warn")
|
|
621
651
|
else:
|
|
622
|
-
print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.",
|
|
652
|
+
print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.", "warn")
|
|
623
653
|
except Exception as e:
|
|
624
|
-
print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).",
|
|
654
|
+
print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).", "warn")
|
|
625
655
|
|
|
626
656
|
# Check 3: Heartbeat recent
|
|
627
657
|
try:
|
|
@@ -630,7 +660,7 @@ def _boot_diagnostic() -> None:
|
|
|
630
660
|
if hb:
|
|
631
661
|
checks_passed += 1
|
|
632
662
|
else:
|
|
633
|
-
print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.",
|
|
663
|
+
print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.", "warn")
|
|
634
664
|
else:
|
|
635
665
|
checks_passed += 1 # skip if no agent data
|
|
636
666
|
except Exception:
|
|
@@ -647,9 +677,9 @@ def _boot_diagnostic() -> None:
|
|
|
647
677
|
checks_passed += 1 # non-critical
|
|
648
678
|
|
|
649
679
|
if checks_passed == checks_total:
|
|
650
|
-
print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).",
|
|
680
|
+
print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).", "warn")
|
|
651
681
|
else:
|
|
652
|
-
print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.",
|
|
682
|
+
print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.", "warn")
|
|
653
683
|
|
|
654
684
|
|
|
655
685
|
_boot_diagnostic()
|
|
@@ -1291,9 +1321,9 @@ try:
|
|
|
1291
1321
|
elif isinstance(_ls_val, str):
|
|
1292
1322
|
_LAST_SEEN_TS = _ls_val
|
|
1293
1323
|
if _LAST_SEEN_TS:
|
|
1294
|
-
print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.",
|
|
1324
|
+
print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.", "warn")
|
|
1295
1325
|
except Exception as _e:
|
|
1296
|
-
print(f"[meshcode] Could not restore last_seen: {_e}",
|
|
1326
|
+
print(f"[meshcode] Could not restore last_seen: {_e}", "warn")
|
|
1297
1327
|
|
|
1298
1328
|
|
|
1299
1329
|
def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
@@ -2406,7 +2436,7 @@ def _auto_update() -> None:
|
|
|
2406
2436
|
return
|
|
2407
2437
|
|
|
2408
2438
|
# 3. Install the new version (blocking, 60s timeout)
|
|
2409
|
-
print(f"[meshcode] Updating {current} → {latest}...",
|
|
2439
|
+
print(f"[meshcode] Updating {current} → {latest}...", "warn")
|
|
2410
2440
|
try:
|
|
2411
2441
|
result = subprocess.run(
|
|
2412
2442
|
[sys.executable, "-m", "pip", "install", "--upgrade",
|
|
@@ -2424,7 +2454,7 @@ def _auto_update() -> None:
|
|
|
2424
2454
|
return
|
|
2425
2455
|
|
|
2426
2456
|
# 4. Re-exec to load the new code
|
|
2427
|
-
print(f"[meshcode] Updated to {latest}, restarting...",
|
|
2457
|
+
print(f"[meshcode] Updated to {latest}, restarting...", "warn")
|
|
2428
2458
|
os.environ["MESHCODE_UPDATED"] = "1"
|
|
2429
2459
|
try:
|
|
2430
2460
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
|
@@ -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,18 @@ 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
|
+
# Check if this agent is the commander
|
|
346
|
+
is_cmd = "commander" in agent.lower()
|
|
347
|
+
print(render_welcome(agent, resolved_project, ascii_art, cli_version, is_commander=is_cmd))
|
|
348
|
+
except Exception:
|
|
349
|
+
pass # Non-critical — skip banner on error
|
|
350
|
+
|
|
269
351
|
print(f"[meshcode] Launching {editor} for agent '{agent}' (project: {resolved_project})")
|
|
270
352
|
print(f"[meshcode] Workspace: {ws}")
|
|
271
353
|
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
|