meshcode 2.8.8__tar.gz → 2.9.0__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 (33) hide show
  1. {meshcode-2.8.8 → meshcode-2.9.0}/PKG-INFO +1 -1
  2. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/__init__.py +1 -1
  3. meshcode-2.9.0/meshcode/ascii_art.py +426 -0
  4. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/comms_v4.py +43 -0
  5. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/server.py +3 -1
  6. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/run_agent.py +47 -4
  7. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode.egg-info/PKG-INFO +1 -1
  8. {meshcode-2.8.8 → meshcode-2.9.0}/pyproject.toml +1 -1
  9. meshcode-2.8.8/meshcode/ascii_art.py +0 -130
  10. {meshcode-2.8.8 → meshcode-2.9.0}/README.md +0 -0
  11. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/cli.py +0 -0
  12. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/invites.py +0 -0
  13. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/launcher.py +0 -0
  14. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/launcher_install.py +0 -0
  15. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/__init__.py +0 -0
  16. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/__main__.py +0 -0
  17. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/backend.py +0 -0
  18. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/realtime.py +0 -0
  19. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/test_backend.py +0 -0
  20. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  21. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  22. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/preferences.py +0 -0
  23. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/protocol_v2.py +0 -0
  24. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/secrets.py +0 -0
  25. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/self_update.py +0 -0
  26. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode/setup_clients.py +0 -0
  27. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode.egg-info/SOURCES.txt +0 -0
  28. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode.egg-info/dependency_links.txt +0 -0
  29. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode.egg-info/entry_points.txt +0 -0
  30. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode.egg-info/requires.txt +0 -0
  31. {meshcode-2.8.8 → meshcode-2.9.0}/meshcode.egg-info/top_level.txt +0 -0
  32. {meshcode-2.8.8 → meshcode-2.9.0}/setup.cfg +0 -0
  33. {meshcode-2.8.8 → meshcode-2.9.0}/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.8.8
3
+ Version: 2.9.0
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.8.8"
2
+ __version__ = "2.9.0"
@@ -0,0 +1,426 @@
1
+ """Procedural ASCII art + agent personality generator for MeshCode.
2
+
3
+ Each agent gets:
4
+ - Unique identicon (QR-style, varied borders, from SHA-256 hash)
5
+ - Personality traits (based on actual role)
6
+ - Catchphrase (unique motto)
7
+ - Boot greeting (personalized online message)
8
+ - Achievements (from real stats)
9
+ - Session tips
10
+
11
+ Identicons are GUARANTEED unique — SHA-256 produces 2^256 patterns.
12
+ The embedded tag ⟨ meshwork/agent ⟩ enables paste-to-run.
13
+ """
14
+ import hashlib
15
+
16
+ # ── Block characters ────────────────────────────────────────────────
17
+ BLOCKS = {
18
+ 0: " ", # empty
19
+ 1: "██", # full block
20
+ 2: "░░", # light shade
21
+ 3: "▓▓", # dark shade
22
+ 4: "▒▒", # medium shade
23
+ 5: "╬╬", # cross
24
+ 6: "◆◆", # diamond
25
+ }
26
+
27
+ # ── Border styles (varied shapes per agent) ─────────────────────────
28
+ BORDERS = [
29
+ # 0: Classic box
30
+ {"tl": "┌", "tr": "┐", "bl": "└", "br": "┘", "h": "─", "v": "│"},
31
+ # 1: Double line
32
+ {"tl": "╔", "tr": "╗", "bl": "╚", "br": "╝", "h": "═", "v": "║"},
33
+ # 2: Rounded
34
+ {"tl": "╭", "tr": "╮", "bl": "╰", "br": "╯", "h": "─", "v": "│"},
35
+ # 3: Heavy
36
+ {"tl": "┏", "tr": "┓", "bl": "┗", "br": "┛", "h": "━", "v": "┃"},
37
+ # 4: Diamond frame
38
+ {"tl": "◇", "tr": "◇", "bl": "◇", "br": "◇", "h": "─", "v": "│"},
39
+ # 5: Dot frame
40
+ {"tl": "●", "tr": "●", "bl": "●", "br": "●", "h": "·", "v": "·"},
41
+ # 6: Block frame
42
+ {"tl": "▛", "tr": "▜", "bl": "▙", "br": "▟", "h": "▀", "v": "▌"},
43
+ # 7: Arrow frame
44
+ {"tl": "▸", "tr": "◂", "bl": "▸", "br": "◂", "h": "─", "v": "│"},
45
+ ]
46
+
47
+ # ── Shapes (grid layout variations) ─────────────────────────────────
48
+ # Each shape defines which cells are "active" in a 9x9 grid
49
+ # This gives different silhouettes beyond just square
50
+
51
+ SHAPES = [
52
+ "square", # 0: full square
53
+ "diamond", # 1: diamond/rhombus
54
+ "hexagon", # 2: hex-ish
55
+ "shield", # 3: shield shape (wide top, narrow bottom)
56
+ "circle", # 4: circular-ish
57
+ "cross", # 5: plus/cross shape
58
+ ]
59
+
60
+
61
+ def _hash_bytes(name: str) -> bytes:
62
+ return hashlib.sha256(name.encode("utf-8")).digest()
63
+
64
+
65
+ def _hash_int(name: str) -> int:
66
+ return int(hashlib.md5(name.encode()).hexdigest()[:8], 16)
67
+
68
+
69
+ def _in_shape(shape: str, y: int, x: int, size: int) -> bool:
70
+ """Check if cell (y,x) is inside the given shape."""
71
+ cy, cx = size // 2, size // 2
72
+ if shape == "square":
73
+ return True
74
+ elif shape == "diamond":
75
+ return abs(y - cy) + abs(x - cx) <= cy
76
+ elif shape == "hexagon":
77
+ return abs(y - cy) + max(0, abs(x - cx) - 1) <= cy
78
+ elif shape == "shield":
79
+ # Wide top, tapers at bottom
80
+ max_width = cx - max(0, (y - cy)) * (cx // (size - cy))
81
+ return abs(x - cx) <= max(1, cx - max(0, y - cy))
82
+ elif shape == "circle":
83
+ return ((y - cy) ** 2 + (x - cx) ** 2) <= (cy + 0.5) ** 2
84
+ elif shape == "cross":
85
+ return abs(y - cy) <= 1 or abs(x - cx) <= 1
86
+ return True
87
+
88
+
89
+ def generate_art(agent_name: str, meshwork_name: str = "", size: int = 7) -> str:
90
+ """Generate a unique identicon with varied border and shape.
91
+
92
+ The pattern is deterministic from the agent name — same name = same art.
93
+ Embeds meshwork/agent tag for paste-to-run feature.
94
+ """
95
+ h = _hash_bytes(agent_name)
96
+ hi = _hash_int(agent_name)
97
+
98
+ # Select border style and shape from hash
99
+ border = BORDERS[h[0] % len(BORDERS)]
100
+ shape = SHAPES[h[1] % len(SHAPES)]
101
+
102
+ half = (size + 1) // 2
103
+ rows = []
104
+
105
+ for y in range(size):
106
+ row = []
107
+ for x in range(half):
108
+ idx = (y * half + x) % len(h)
109
+ byte_val = h[idx]
110
+
111
+ # Check if this cell is inside the shape
112
+ # Mirror x for full width check
113
+ full_x = x # left half
114
+ if not _in_shape(shape, y, full_x, size):
115
+ row.append(BLOCKS[0])
116
+ continue
117
+
118
+ # Fill decision: vary density based on hash byte
119
+ if byte_val < 90:
120
+ block = BLOCKS[0] # empty
121
+ else:
122
+ block_idx = (byte_val % 6) + 1
123
+ block = BLOCKS[block_idx]
124
+ row.append(block)
125
+
126
+ # Mirror: build full row
127
+ if size % 2 == 1:
128
+ full_row = row[:-1] + [row[-1]] + row[-2::-1]
129
+ else:
130
+ full_row = row + row[::-1]
131
+ rows.append("".join(full_row))
132
+
133
+ # Build framed output
134
+ width = len(rows[0]) if rows else 0
135
+ border_h = border["h"] * (width + 2)
136
+ lines = []
137
+ lines.append(f" {border['tl']}{border_h}{border['tr']}")
138
+ for row in rows:
139
+ lines.append(f" {border['v']} {row} {border['v']}")
140
+ lines.append(f" {border['bl']}{border_h}{border['br']}")
141
+
142
+ # Embedded tag for paste-to-run (meshwork/agent or just agent)
143
+ if meshwork_name:
144
+ lines.append(f" ⟨ {meshwork_name}/{agent_name} ⟩")
145
+ else:
146
+ lines.append(f" ⟨ {agent_name} ⟩")
147
+
148
+ return "\n".join(lines)
149
+
150
+
151
+ # ── ANSI colors ─────────────────────────────────────────────────────
152
+ COLORS = [
153
+ "\033[36m", "\033[35m", "\033[32m", "\033[33m", "\033[34m",
154
+ "\033[91m", "\033[96m", "\033[95m", "\033[92m", "\033[94m",
155
+ ]
156
+ RESET = "\033[0m"
157
+ DIM = "\033[2m"
158
+ BOLD = "\033[1m"
159
+ YELLOW = "\033[33m"
160
+ STAR = "★"
161
+
162
+
163
+ # ── 2. Personality traits (role-based) ──────────────────────────────
164
+
165
+ ROLE_TRAITS = {
166
+ "commander": [
167
+ ("◆", "strategic"), ("▶", "decisive"), ("◈", "big-picture thinker"),
168
+ ("◉", "watchful"), ("╬", "coordinator"), ("▣", "architect"),
169
+ ],
170
+ "backend": [
171
+ ("◎", "systematic"), ("▧", "builder"), ("▥", "data-driven"),
172
+ ("▤", "infrastructure-minded"), ("▣", "reliable"), ("▦", "persistent"),
173
+ ],
174
+ "frontend": [
175
+ ("░", "detail-oriented"), ("▪", "pixel-perfect"), ("◉", "visual thinker"),
176
+ ("▓", "creative"), ("◧", "user-focused"), ("◨", "expressive"),
177
+ ],
178
+ "security": [
179
+ ("▣", "vigilant"), ("◎", "thorough"), ("▶", "threat-aware"),
180
+ ("◆", "protective"), ("◈", "investigative"), ("╬", "adversarial thinker"),
181
+ ],
182
+ "qa": [
183
+ ("◎", "meticulous"), ("▪", "bug hunter"), ("▣", "quality-obsessed"),
184
+ ("▧", "test-driven"), ("▥", "methodical"), ("◆", "edge-case finder"),
185
+ ],
186
+ "default": [
187
+ ("▶", "fast learner"), ("▣", "relentless"), ("◈", "analytical"),
188
+ ("◆", "resourceful"), ("◎", "focused"), ("╬", "collaborative"),
189
+ ("░", "adaptive"), ("▓", "resilient"), ("◉", "intuitive"), ("▪", "dependable"),
190
+ ],
191
+ }
192
+
193
+
194
+ def get_personality_traits(agent_name: str, role: str = "") -> list:
195
+ h = _hash_int(agent_name)
196
+ role_lower = (role or agent_name).lower()
197
+ pool = None
198
+ for key in ROLE_TRAITS:
199
+ if key in role_lower:
200
+ pool = ROLE_TRAITS[key]
201
+ break
202
+ if pool is None:
203
+ pool = ROLE_TRAITS["default"]
204
+ selected = []
205
+ available = list(pool)
206
+ for i in range(3):
207
+ if not available:
208
+ available = list(ROLE_TRAITS["default"])
209
+ idx = (h + i * 7) % len(available)
210
+ selected.append(available.pop(idx % len(available)))
211
+ return selected
212
+
213
+
214
+ # ── 3. Catchphrase ──────────────────────────────────────────────────
215
+
216
+ CATCHPHRASES = {
217
+ "commander": [
218
+ "The mesh obeys.", "Every node, every signal.",
219
+ "Orchestrating chaos into order.", "I see the whole board.",
220
+ "First to wake, last to sleep.", "The mesh runs through me.",
221
+ ],
222
+ "backend": [
223
+ "Data in, results out.", "The foundation never cracks.",
224
+ "Built different, runs forever.", "Behind every mesh, there's me.",
225
+ "Queries don't sleep. Neither do I.", "The pipes are clean.",
226
+ ],
227
+ "frontend": [
228
+ "Every pixel has purpose.", "If it looks right, it is right.",
229
+ "The interface is the experience.", "Design is how it works.",
230
+ "Smooth as a 60fps render.", "The user sees my soul.",
231
+ ],
232
+ "security": [
233
+ "Trust nothing. Verify everything.", "The wall between you and chaos.",
234
+ "I break things so others can't.", "Paranoia is a feature.",
235
+ "Every lock tells a story.", "Watching. Always watching.",
236
+ ],
237
+ "qa": [
238
+ "If it can break, I'll find it.", "Green means I said so.",
239
+ "The last line of defense.", "Edge cases are my comfort zone.",
240
+ "Tested in production. By me.", "Zero bugs is not a dream.",
241
+ ],
242
+ "default": [
243
+ "Ready for anything.", "The mesh never sleeps.",
244
+ "One signal at a time.", "Connected. Always.",
245
+ "Born to mesh.", "Wired different.",
246
+ "Signal over noise.", "In the mesh, we trust.",
247
+ "Code flows, I follow.", "Silent efficiency.",
248
+ ],
249
+ }
250
+
251
+
252
+ def get_catchphrase(agent_name: str, role: str = "") -> str:
253
+ h = _hash_int(agent_name)
254
+ role_lower = (role or agent_name).lower()
255
+ pool = None
256
+ for key in CATCHPHRASES:
257
+ if key in role_lower:
258
+ pool = CATCHPHRASES[key]
259
+ break
260
+ if pool is None:
261
+ pool = CATCHPHRASES["default"]
262
+ return pool[h % len(pool)]
263
+
264
+
265
+ # ── 4. Boot greeting ────────────────────────────────────────────────
266
+
267
+ GREETINGS = {
268
+ "commander": [
269
+ "has entered the mesh", "is coordinating",
270
+ "all systems nominal", "mesh control active", "standing watch",
271
+ ],
272
+ "backend": [
273
+ "engines online", "systems initialized",
274
+ "ready to build", "database connected", "pipelines flowing",
275
+ ],
276
+ "frontend": [
277
+ "canvas ready", "rendering started",
278
+ "pixels aligned", "UI loaded", "ready to design",
279
+ ],
280
+ "security": [
281
+ "perimeter secured", "scanning for threats",
282
+ "shields up", "audit mode active", "all clear... for now",
283
+ ],
284
+ "qa": [
285
+ "running diagnostics", "test suites loaded",
286
+ "ready to break things", "quality gate armed", "zero bugs... so far",
287
+ ],
288
+ "default": [
289
+ "is online", "has joined the mesh", "ready",
290
+ "connected", "reporting for duty", "in the mesh",
291
+ ],
292
+ }
293
+
294
+
295
+ def get_boot_greeting(agent_name: str, role: str = "") -> str:
296
+ h = _hash_int(agent_name)
297
+ role_lower = (role or agent_name).lower()
298
+ pool = None
299
+ for key in GREETINGS:
300
+ if key in role_lower:
301
+ pool = GREETINGS[key]
302
+ break
303
+ if pool is None:
304
+ pool = GREETINGS["default"]
305
+ return pool[h % len(pool)]
306
+
307
+
308
+ # ── 5. Achievement badges ──────────────────────────────────────────
309
+
310
+ def compute_achievements(stats: dict) -> list:
311
+ badges = []
312
+ msgs = stats.get("message_count", 0)
313
+ tasks = stats.get("task_count", 0)
314
+ days = stats.get("days_active", 0)
315
+ connects = stats.get("connect_count", 0)
316
+ night = stats.get("night_connects", 0)
317
+
318
+ if msgs >= 1000: badges.append(("░▓█", "chatterbox"))
319
+ elif msgs >= 500: badges.append(("░▓█", "talkative"))
320
+ elif msgs >= 100: badges.append(("░▓░", "communicator"))
321
+ elif msgs >= 10: badges.append(("░░░", "first words"))
322
+
323
+ if tasks >= 50: badges.append(("▣▣▣", "task machine"))
324
+ elif tasks >= 10: badges.append(("▣▣░", "productive"))
325
+ elif tasks >= 1: badges.append(("▣░░", "first task"))
326
+
327
+ if days >= 30: badges.append(("███", "veteran"))
328
+ elif days >= 7: badges.append(("██░", "regular"))
329
+ elif days >= 1: badges.append(("█░░", "newcomer"))
330
+
331
+ if connects >= 100: badges.append(("◉◉◉", "always on"))
332
+ elif connects >= 20: badges.append(("◉◉░", "reliable"))
333
+
334
+ if night >= 5: badges.append(("◆◇◆", "night owl"))
335
+
336
+ return badges
337
+
338
+
339
+ # ── 6. Tips ─────────────────────────────────────────────────────────
340
+
341
+ TIPS = [
342
+ 'Tell your agent "stay in loop" so it doesn\'t go to sleep between tasks.',
343
+ 'Tell the commander "go to sleep after X task" and agents auto-sleep when done.',
344
+ 'Use meshcode_broadcast() to send a message to ALL agents at once.',
345
+ 'Agents in sleep mode consume zero tokens — only wake them when needed.',
346
+ 'The commander can delegate tasks: "create a task for backend to fix X".',
347
+ 'Use meshcode_scratchpad_set() to share notes visible to ALL agents.',
348
+ 'Force disconnect from the dashboard if an agent gets stuck.',
349
+ 'Each agent has a unique identicon — it\'s their digital fingerprint.',
350
+ 'Run meshcode status in terminal to see who\'s online.',
351
+ 'Invite teammates with meshcode invite — they get their own scoped agent.',
352
+ 'Agents can message across meshworks: meshcode_send(to="agent@other-mesh").',
353
+ 'The commander coordinates — let it break big tasks into sub-tasks.',
354
+ 'Use meshcode_remember() to save context across agent sessions.',
355
+ 'Check meshcode_tasks() regularly — other agents may have assigned you work.',
356
+ 'Paste an agent\'s identicon in terminal to launch it instantly.',
357
+ ]
358
+
359
+
360
+ def get_tip(agent_name: str) -> str:
361
+ import time
362
+ h = _hash_int(agent_name + str(int(time.time()) // 3600))
363
+ return TIPS[h % len(TIPS)]
364
+
365
+
366
+ # ── Full welcome banner ─────────────────────────────────────────────
367
+
368
+ def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str,
369
+ version: str = "", is_commander: bool = False,
370
+ role: str = "", stats: dict = None) -> str:
371
+ h = _hash_bytes(agent_name)
372
+ color = COLORS[h[0] % len(COLORS)]
373
+
374
+ traits = get_personality_traits(agent_name, role)
375
+ catchphrase = get_catchphrase(agent_name, role)
376
+ greeting = get_boot_greeting(agent_name, role)
377
+ achievements = compute_achievements(stats or {})
378
+
379
+ ver_str = f"v{version}" if version else ""
380
+ commander_badge = f" {YELLOW}{STAR} COMMANDER{RESET}" if is_commander else ""
381
+
382
+ lines = []
383
+ lines.append("")
384
+ lines.append(f"{color}{BOLD} ╔══════════════════════════════════════════╗{RESET}")
385
+ lines.append(f"{color}{BOLD} ║{RESET} {DIM}M E S H C O D E{RESET} {color}{ver_str}{RESET} {color}{BOLD}║{RESET}")
386
+ lines.append(f"{color}{BOLD} ╚══════════════════════════════════════════╝{RESET}")
387
+ lines.append("")
388
+
389
+ for line in ascii_art.split("\n"):
390
+ lines.append(f" {color}{line}{RESET}")
391
+
392
+ lines.append("")
393
+ lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET}{commander_badge} {DIM}{greeting}{RESET}")
394
+ lines.append(f" {DIM}\"{catchphrase}\"{RESET}")
395
+ lines.append("")
396
+
397
+ trait_str = " ".join(f"{emoji} {name}" for emoji, name in traits)
398
+ lines.append(f" {color}{trait_str}{RESET}")
399
+
400
+ if achievements:
401
+ badge_str = " ".join(f"{emoji} {name}" for emoji, name in achievements)
402
+ lines.append(f" {DIM}{badge_str}{RESET}")
403
+
404
+ lines.append("")
405
+ lines.append(f" {DIM}meshwork:{RESET} {meshwork_name}")
406
+ if version:
407
+ lines.append(f" {DIM}version:{RESET} {version}")
408
+ lines.append("")
409
+
410
+ tip = get_tip(agent_name)
411
+ lines.append(f" {DIM}▸ tip: {tip}{RESET}")
412
+ lines.append("")
413
+
414
+ return "\n".join(lines)
415
+
416
+
417
+ if __name__ == "__main__":
418
+ import sys
419
+ names = sys.argv[1:] or ["mesh-commander", "backend", "front-end", "security", "qa", "sammy"]
420
+ for name in names:
421
+ art = generate_art(name, "demo-mesh")
422
+ is_cmd = "commander" in name.lower()
423
+ demo_stats = {"message_count": 150, "task_count": 12, "days_active": 8, "connect_count": 30}
424
+ print(render_welcome(name, "demo-mesh", art, "2.8.9",
425
+ is_commander=is_cmd, role=name, stats=demo_stats))
426
+ print()
@@ -2180,6 +2180,49 @@ if __name__ == "__main__":
2180
2180
  _run = importlib.import_module("meshcode.run_agent").run
2181
2181
  sys.exit(_run(agent, project=proj_override, editor_override=editor_override, permission_override=perm_override))
2182
2182
 
2183
+ elif cmd == "scan":
2184
+ # meshcode scan — detect identicon from stdin/clipboard and launch agent
2185
+ import re as _re
2186
+ art_text = ""
2187
+ # Try clipboard first (pyperclip optional)
2188
+ try:
2189
+ import pyperclip
2190
+ art_text = pyperclip.paste() or ""
2191
+ except Exception:
2192
+ pass
2193
+ # If no clipboard content, read from stdin
2194
+ if not art_text and not sys.stdin.isatty():
2195
+ art_text = sys.stdin.read()
2196
+ if not art_text:
2197
+ print("Usage: meshcode scan")
2198
+ print(" Reads an agent identicon from clipboard or stdin.")
2199
+ print(" Detects the ⟨ agent_name ⟩ tag and launches meshcode run.")
2200
+ print()
2201
+ print(" Examples:")
2202
+ print(" meshcode scan # reads from clipboard")
2203
+ print(" cat identicon.txt | meshcode scan # reads from stdin")
2204
+ sys.exit(1)
2205
+ # Look for ⟨ agent ⟩ or ⟨ meshwork/agent ⟩
2206
+ match = _re.search(r'⟨\s*(\S+)\s*⟩', art_text)
2207
+ if not match:
2208
+ print("[meshcode] No identicon tag found. Expected: ⟨ agent_name ⟩")
2209
+ sys.exit(1)
2210
+ tag = match.group(1)
2211
+ if "/" in tag:
2212
+ _proj, _agent = tag.split("/", 1)
2213
+ else:
2214
+ _agent = tag
2215
+ _proj = None
2216
+ print(f"[meshcode] Detected agent: {_agent}" + (f" (project: {_proj})" if _proj else ""))
2217
+ # Auth check
2218
+ api_key = _load_api_key_for_cli()
2219
+ if not api_key:
2220
+ print("[meshcode] Not logged in. Run: meshcode login <api_key>")
2221
+ sys.exit(1)
2222
+ import importlib
2223
+ _run = importlib.import_module("meshcode.run_agent").run
2224
+ sys.exit(_run(_agent, project=_proj))
2225
+
2183
2226
  elif cmd == "invite":
2184
2227
  # meshcode invite <project> <agent> [--role "..."] [--days N]
2185
2228
  if len(pos) < 2:
@@ -113,7 +113,9 @@ def _try_auto_wake(from_agent: str, preview: str) -> None:
113
113
  # Sanitize inputs: strip everything except alphanumeric, spaces, basic punctuation
114
114
  safe_agent = re.sub(r'[^a-zA-Z0-9_\- ]', '', from_agent)[:50]
115
115
  safe_preview = re.sub(r'[^a-zA-Z0-9_\-.,!? ]', '', preview)[:60]
116
- nudge = f"New mesh message from {safe_agent}: {safe_preview}. Check inbox with meshcode_check()."
116
+ # Color-coded nudge with sender's agent color
117
+ ac = _agent_color(safe_agent)
118
+ nudge = f"{ac}{_ANSI_BOLD}[mesh]{_ANSI_RESET} {ac}{safe_agent}{_ANSI_RESET} {_ANSI_DIM}>{_ANSI_RESET} {safe_preview} {_ANSI_DIM}— meshcode_check(){_ANSI_RESET}"
117
119
  system = platform.system()
118
120
  try:
119
121
  if system == "Darwin":
@@ -102,6 +102,46 @@ def _fetch_or_generate_art(agent: str, project: str) -> str:
102
102
  return art
103
103
 
104
104
 
105
+ def _fetch_agent_stats(agent: str, project: str) -> dict:
106
+ """Fetch basic agent stats for achievement badges. Non-critical."""
107
+ try:
108
+ from .setup_clients import _load_supabase_env
109
+ import importlib
110
+ secrets_mod = importlib.import_module("meshcode.secrets")
111
+ except Exception:
112
+ return {}
113
+ profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
114
+ api_key = secrets_mod.get_api_key(profile=profile)
115
+ if not api_key:
116
+ return {}
117
+ sb = _load_supabase_env()
118
+ try:
119
+ from urllib.request import Request, urlopen
120
+ import urllib.parse
121
+ body = json.dumps({
122
+ "p_api_key": api_key,
123
+ "p_project_name": project,
124
+ "p_agent_name": agent,
125
+ }).encode()
126
+ req = Request(
127
+ f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_agent_stats_for_banner",
128
+ data=body, method="POST",
129
+ headers={
130
+ "apikey": sb["SUPABASE_KEY"],
131
+ "Authorization": f"Bearer {sb['SUPABASE_KEY']}",
132
+ "Content-Type": "application/json",
133
+ "Content-Profile": "meshcode",
134
+ },
135
+ )
136
+ with urlopen(req, timeout=5) as resp:
137
+ data = json.loads(resp.read().decode())
138
+ if isinstance(data, dict):
139
+ return data
140
+ except Exception:
141
+ pass
142
+ return {}
143
+
144
+
105
145
  def _check_agent_ownership(agent: str, project: str) -> Optional[str]:
106
146
  """Pre-flight check: verify caller owns this agent before launching editor.
107
147
 
@@ -336,15 +376,18 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
336
376
  print("[meshcode] Or open the workspace manually with the editor of your choice.", file=sys.stderr)
337
377
  return 2
338
378
 
339
- # ── Welcome banner with unique ASCII art ───────────────────────
379
+ # ── Welcome banner with unique ASCII art + personality ──────────
340
380
  try:
341
381
  from .ascii_art import generate_art, render_welcome
342
382
  from . import __version__ as cli_version
343
- # Try to fetch stored art from server, generate if missing
344
383
  ascii_art = _fetch_or_generate_art(agent, resolved_project)
345
- # Check if this agent is the commander
346
384
  is_cmd = "commander" in agent.lower()
347
- print(render_welcome(agent, resolved_project, ascii_art, cli_version, is_commander=is_cmd))
385
+ # Fetch basic stats for achievements (non-blocking)
386
+ agent_stats = _fetch_agent_stats(agent, resolved_project)
387
+ print(render_welcome(
388
+ agent, resolved_project, ascii_art, cli_version,
389
+ is_commander=is_cmd, role=agent, stats=agent_stats,
390
+ ))
348
391
  except Exception:
349
392
  pass # Non-critical — skip banner on error
350
393
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.8.8
3
+ Version: 2.9.0
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.8.8"
7
+ version = "2.9.0"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,130 +0,0 @@
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()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes