meshcode 2.8.7__tar.gz → 2.8.9__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.7 → meshcode-2.8.9}/PKG-INFO +1 -1
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/__init__.py +1 -1
- meshcode-2.8.9/meshcode/ascii_art.py +466 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/server.py +59 -27
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/run_agent.py +48 -3
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.8.7 → meshcode-2.8.9}/pyproject.toml +1 -1
- meshcode-2.8.7/meshcode/ascii_art.py +0 -124
- {meshcode-2.8.7 → meshcode-2.8.9}/README.md +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/cli.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/comms_v4.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/invites.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/launcher.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/launcher_install.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/preferences.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/secrets.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/self_update.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode/setup_clients.py +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/setup.cfg +0 -0
- {meshcode-2.8.7 → meshcode-2.8.9}/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.9"
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
"""Procedural ASCII art + agent personality generator for MeshCode.
|
|
2
|
+
|
|
3
|
+
Each agent gets:
|
|
4
|
+
- Unique identicon (QR-style block pattern from name hash)
|
|
5
|
+
- Personality traits (based on actual role + stats)
|
|
6
|
+
- Catchphrase (unique motto)
|
|
7
|
+
- Boot greeting (personalized online message)
|
|
8
|
+
|
|
9
|
+
All deterministic from agent name — same name = same identity always.
|
|
10
|
+
Zero tokens, pure ANSI aesthetics.
|
|
11
|
+
"""
|
|
12
|
+
import hashlib
|
|
13
|
+
|
|
14
|
+
# ── Block characters for identicon ──────────────────────────────────
|
|
15
|
+
BLOCKS = {
|
|
16
|
+
0: " ", # empty
|
|
17
|
+
1: "██", # full block
|
|
18
|
+
2: "░░", # light shade
|
|
19
|
+
3: "▓▓", # dark shade
|
|
20
|
+
4: "▒▒", # medium shade
|
|
21
|
+
5: "╬╬", # cross
|
|
22
|
+
6: "◆◆", # diamond
|
|
23
|
+
7: "●●", # circle
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# ── ANSI colors ─────────────────────────────────────────────────────
|
|
27
|
+
COLORS = [
|
|
28
|
+
"\033[36m", "\033[35m", "\033[32m", "\033[33m", "\033[34m",
|
|
29
|
+
"\033[91m", "\033[96m", "\033[95m", "\033[92m", "\033[94m",
|
|
30
|
+
]
|
|
31
|
+
RESET = "\033[0m"
|
|
32
|
+
DIM = "\033[2m"
|
|
33
|
+
BOLD = "\033[1m"
|
|
34
|
+
YELLOW = "\033[33m"
|
|
35
|
+
STAR = "★"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _hash_bytes(name: str) -> bytes:
|
|
39
|
+
return hashlib.sha256(name.encode("utf-8")).digest()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _hash_int(name: str) -> int:
|
|
43
|
+
return int(hashlib.md5(name.encode()).hexdigest()[:8], 16)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ── 1. Identicon generator ─────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
def generate_art(agent_name: str, size: int = 7) -> str:
|
|
49
|
+
"""Generate a unique symmetric block pattern from the agent name."""
|
|
50
|
+
h = _hash_bytes(agent_name)
|
|
51
|
+
half = (size + 1) // 2
|
|
52
|
+
rows = []
|
|
53
|
+
for y in range(size):
|
|
54
|
+
row = []
|
|
55
|
+
for x in range(half):
|
|
56
|
+
idx = (y * half + x) % len(h)
|
|
57
|
+
byte_val = h[idx]
|
|
58
|
+
if byte_val < 100:
|
|
59
|
+
block = BLOCKS[0]
|
|
60
|
+
else:
|
|
61
|
+
block_idx = (byte_val % 6) + 1
|
|
62
|
+
block = BLOCKS[block_idx]
|
|
63
|
+
row.append(block)
|
|
64
|
+
if size % 2 == 1:
|
|
65
|
+
full_row = row[:-1] + [row[-1]] + row[-2::-1]
|
|
66
|
+
else:
|
|
67
|
+
full_row = row + row[::-1]
|
|
68
|
+
rows.append("".join(full_row))
|
|
69
|
+
|
|
70
|
+
width = len(rows[0]) if rows else 0
|
|
71
|
+
border_h = "─" * (width + 2)
|
|
72
|
+
lines = []
|
|
73
|
+
lines.append(f" ┌{border_h}┐")
|
|
74
|
+
for row in rows:
|
|
75
|
+
lines.append(f" │ {row} │")
|
|
76
|
+
lines.append(f" └{border_h}┘")
|
|
77
|
+
lines.append(f" ⟨ {agent_name} ⟩")
|
|
78
|
+
return "\n".join(lines)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ── 2. Personality traits (role-based + hash for variety) ───────────
|
|
82
|
+
|
|
83
|
+
# Trait pools by role archetype — each agent gets 3 traits
|
|
84
|
+
ROLE_TRAITS = {
|
|
85
|
+
"commander": [
|
|
86
|
+
("◆", "strategic"),
|
|
87
|
+
("▶", "decisive"),
|
|
88
|
+
("◈", "big-picture thinker"),
|
|
89
|
+
("◉", "watchful"),
|
|
90
|
+
("╬", "coordinator"),
|
|
91
|
+
("▣", "architect"),
|
|
92
|
+
],
|
|
93
|
+
"backend": [
|
|
94
|
+
("◎", "systematic"),
|
|
95
|
+
("▧", "builder"),
|
|
96
|
+
("▥", "data-driven"),
|
|
97
|
+
("▤", "infrastructure-minded"),
|
|
98
|
+
("▣", "reliable"),
|
|
99
|
+
("▦", "persistent"),
|
|
100
|
+
],
|
|
101
|
+
"frontend": [
|
|
102
|
+
("░", "detail-oriented"),
|
|
103
|
+
("▪", "pixel-perfect"),
|
|
104
|
+
("◉", "visual thinker"),
|
|
105
|
+
("▓", "creative"),
|
|
106
|
+
("◧", "user-focused"),
|
|
107
|
+
("◨", "expressive"),
|
|
108
|
+
],
|
|
109
|
+
"security": [
|
|
110
|
+
("▣", "vigilant"),
|
|
111
|
+
("◎", "thorough"),
|
|
112
|
+
("▶", "threat-aware"),
|
|
113
|
+
("◆", "protective"),
|
|
114
|
+
("◈", "investigative"),
|
|
115
|
+
("╬", "adversarial thinker"),
|
|
116
|
+
],
|
|
117
|
+
"qa": [
|
|
118
|
+
("◎", "meticulous"),
|
|
119
|
+
("▪", "bug hunter"),
|
|
120
|
+
("▣", "quality-obsessed"),
|
|
121
|
+
("▧", "test-driven"),
|
|
122
|
+
("▥", "methodical"),
|
|
123
|
+
("◆", "edge-case finder"),
|
|
124
|
+
],
|
|
125
|
+
"default": [
|
|
126
|
+
("▶", "fast learner"),
|
|
127
|
+
("▣", "relentless"),
|
|
128
|
+
("◈", "analytical"),
|
|
129
|
+
("◆", "resourceful"),
|
|
130
|
+
("◎", "focused"),
|
|
131
|
+
("╬", "collaborative"),
|
|
132
|
+
("░", "adaptive"),
|
|
133
|
+
("▓", "resilient"),
|
|
134
|
+
("◉", "intuitive"),
|
|
135
|
+
("▪", "dependable"),
|
|
136
|
+
],
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_personality_traits(agent_name: str, role: str = "") -> list:
|
|
141
|
+
"""Get 3 personality traits based on agent role and name hash.
|
|
142
|
+
|
|
143
|
+
Returns list of (emoji, trait_name) tuples.
|
|
144
|
+
"""
|
|
145
|
+
h = _hash_int(agent_name)
|
|
146
|
+
role_lower = (role or agent_name).lower()
|
|
147
|
+
|
|
148
|
+
# Find the best matching role pool
|
|
149
|
+
pool = None
|
|
150
|
+
for key in ROLE_TRAITS:
|
|
151
|
+
if key in role_lower:
|
|
152
|
+
pool = ROLE_TRAITS[key]
|
|
153
|
+
break
|
|
154
|
+
if pool is None:
|
|
155
|
+
pool = ROLE_TRAITS["default"]
|
|
156
|
+
|
|
157
|
+
# Pick 3 unique traits using hash for deterministic selection
|
|
158
|
+
selected = []
|
|
159
|
+
available = list(pool)
|
|
160
|
+
for i in range(3):
|
|
161
|
+
if not available:
|
|
162
|
+
available = list(ROLE_TRAITS["default"])
|
|
163
|
+
idx = (h + i * 7) % len(available)
|
|
164
|
+
selected.append(available.pop(idx % len(available)))
|
|
165
|
+
|
|
166
|
+
return selected
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ── 3. Catchphrase (unique motto per agent) ─────────────────────────
|
|
170
|
+
|
|
171
|
+
CATCHPHRASES = {
|
|
172
|
+
"commander": [
|
|
173
|
+
"The mesh obeys.",
|
|
174
|
+
"Every node, every signal.",
|
|
175
|
+
"Orchestrating chaos into order.",
|
|
176
|
+
"I see the whole board.",
|
|
177
|
+
"First to wake, last to sleep.",
|
|
178
|
+
"The mesh runs through me.",
|
|
179
|
+
],
|
|
180
|
+
"backend": [
|
|
181
|
+
"Data in, results out.",
|
|
182
|
+
"The foundation never cracks.",
|
|
183
|
+
"Built different, runs forever.",
|
|
184
|
+
"Behind every mesh, there's me.",
|
|
185
|
+
"Queries don't sleep. Neither do I.",
|
|
186
|
+
"The pipes are clean.",
|
|
187
|
+
],
|
|
188
|
+
"frontend": [
|
|
189
|
+
"Every pixel has purpose.",
|
|
190
|
+
"If it looks right, it is right.",
|
|
191
|
+
"The interface is the experience.",
|
|
192
|
+
"Design is how it works.",
|
|
193
|
+
"Smooth as a 60fps render.",
|
|
194
|
+
"The user sees my soul.",
|
|
195
|
+
],
|
|
196
|
+
"security": [
|
|
197
|
+
"Trust nothing. Verify everything.",
|
|
198
|
+
"The wall between you and chaos.",
|
|
199
|
+
"I break things so others can't.",
|
|
200
|
+
"Paranoia is a feature.",
|
|
201
|
+
"Every lock tells a story.",
|
|
202
|
+
"Watching. Always watching.",
|
|
203
|
+
],
|
|
204
|
+
"qa": [
|
|
205
|
+
"If it can break, I'll find it.",
|
|
206
|
+
"Green means I said so.",
|
|
207
|
+
"The last line of defense.",
|
|
208
|
+
"Edge cases are my comfort zone.",
|
|
209
|
+
"Tested in production. By me.",
|
|
210
|
+
"Zero bugs is not a dream.",
|
|
211
|
+
],
|
|
212
|
+
"default": [
|
|
213
|
+
"Ready for anything.",
|
|
214
|
+
"The mesh never sleeps.",
|
|
215
|
+
"One signal at a time.",
|
|
216
|
+
"Connected. Always.",
|
|
217
|
+
"Born to mesh.",
|
|
218
|
+
"Wired different.",
|
|
219
|
+
"Signal over noise.",
|
|
220
|
+
"In the mesh, we trust.",
|
|
221
|
+
"Code flows, I follow.",
|
|
222
|
+
"Silent efficiency.",
|
|
223
|
+
],
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def get_catchphrase(agent_name: str, role: str = "") -> str:
|
|
228
|
+
"""Get a unique catchphrase for this agent."""
|
|
229
|
+
h = _hash_int(agent_name)
|
|
230
|
+
role_lower = (role or agent_name).lower()
|
|
231
|
+
|
|
232
|
+
pool = None
|
|
233
|
+
for key in CATCHPHRASES:
|
|
234
|
+
if key in role_lower:
|
|
235
|
+
pool = CATCHPHRASES[key]
|
|
236
|
+
break
|
|
237
|
+
if pool is None:
|
|
238
|
+
pool = CATCHPHRASES["default"]
|
|
239
|
+
|
|
240
|
+
return pool[h % len(pool)]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ── 4. Boot greeting (personalized online message) ──────────────────
|
|
244
|
+
|
|
245
|
+
GREETINGS = {
|
|
246
|
+
"commander": [
|
|
247
|
+
"has entered the mesh",
|
|
248
|
+
"is coordinating",
|
|
249
|
+
"all systems nominal",
|
|
250
|
+
"mesh control active",
|
|
251
|
+
"standing watch",
|
|
252
|
+
],
|
|
253
|
+
"backend": [
|
|
254
|
+
"engines online",
|
|
255
|
+
"systems initialized",
|
|
256
|
+
"ready to build",
|
|
257
|
+
"database connected",
|
|
258
|
+
"pipelines flowing",
|
|
259
|
+
],
|
|
260
|
+
"frontend": [
|
|
261
|
+
"canvas ready",
|
|
262
|
+
"rendering started",
|
|
263
|
+
"pixels aligned",
|
|
264
|
+
"UI loaded",
|
|
265
|
+
"ready to design",
|
|
266
|
+
],
|
|
267
|
+
"security": [
|
|
268
|
+
"perimeter secured",
|
|
269
|
+
"scanning for threats",
|
|
270
|
+
"shields up",
|
|
271
|
+
"audit mode active",
|
|
272
|
+
"all clear... for now",
|
|
273
|
+
],
|
|
274
|
+
"qa": [
|
|
275
|
+
"running diagnostics",
|
|
276
|
+
"test suites loaded",
|
|
277
|
+
"ready to break things",
|
|
278
|
+
"quality gate armed",
|
|
279
|
+
"zero bugs... so far",
|
|
280
|
+
],
|
|
281
|
+
"default": [
|
|
282
|
+
"is online",
|
|
283
|
+
"has joined the mesh",
|
|
284
|
+
"ready",
|
|
285
|
+
"connected",
|
|
286
|
+
"reporting for duty",
|
|
287
|
+
"in the mesh",
|
|
288
|
+
],
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def get_boot_greeting(agent_name: str, role: str = "") -> str:
|
|
293
|
+
"""Get a personalized boot greeting."""
|
|
294
|
+
h = _hash_int(agent_name)
|
|
295
|
+
role_lower = (role or agent_name).lower()
|
|
296
|
+
|
|
297
|
+
pool = None
|
|
298
|
+
for key in GREETINGS:
|
|
299
|
+
if key in role_lower:
|
|
300
|
+
pool = GREETINGS[key]
|
|
301
|
+
break
|
|
302
|
+
if pool is None:
|
|
303
|
+
pool = GREETINGS["default"]
|
|
304
|
+
|
|
305
|
+
return pool[h % len(pool)]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# ── 5. Achievement badges (computed from stats) ─────────────────────
|
|
309
|
+
|
|
310
|
+
def compute_achievements(stats: dict) -> list:
|
|
311
|
+
"""Compute achievement badges from agent stats.
|
|
312
|
+
|
|
313
|
+
stats keys: message_count, task_count, days_active, connect_count,
|
|
314
|
+
night_connects (connections between midnight-5am)
|
|
315
|
+
|
|
316
|
+
Returns list of (emoji, badge_name) tuples.
|
|
317
|
+
"""
|
|
318
|
+
badges = []
|
|
319
|
+
msgs = stats.get("message_count", 0)
|
|
320
|
+
tasks = stats.get("task_count", 0)
|
|
321
|
+
days = stats.get("days_active", 0)
|
|
322
|
+
connects = stats.get("connect_count", 0)
|
|
323
|
+
night = stats.get("night_connects", 0)
|
|
324
|
+
|
|
325
|
+
# Message milestones
|
|
326
|
+
if msgs >= 1000:
|
|
327
|
+
badges.append(("░▓█", "chatterbox"))
|
|
328
|
+
elif msgs >= 500:
|
|
329
|
+
badges.append(("░▓█", "talkative"))
|
|
330
|
+
elif msgs >= 100:
|
|
331
|
+
badges.append(("░▓░", "communicator"))
|
|
332
|
+
elif msgs >= 10:
|
|
333
|
+
badges.append(("░░░", "first words"))
|
|
334
|
+
|
|
335
|
+
# Task milestones
|
|
336
|
+
if tasks >= 50:
|
|
337
|
+
badges.append(("▣▣▣", "task machine"))
|
|
338
|
+
elif tasks >= 10:
|
|
339
|
+
badges.append(("▣▣░", "productive"))
|
|
340
|
+
elif tasks >= 1:
|
|
341
|
+
badges.append(("▣░░", "first task"))
|
|
342
|
+
|
|
343
|
+
# Uptime/loyalty
|
|
344
|
+
if days >= 30:
|
|
345
|
+
badges.append(("███", "veteran"))
|
|
346
|
+
elif days >= 7:
|
|
347
|
+
badges.append(("██░", "regular"))
|
|
348
|
+
elif days >= 1:
|
|
349
|
+
badges.append(("█░░", "newcomer"))
|
|
350
|
+
|
|
351
|
+
# Connection count
|
|
352
|
+
if connects >= 100:
|
|
353
|
+
badges.append(("◉◉◉", "always on"))
|
|
354
|
+
elif connects >= 20:
|
|
355
|
+
badges.append(("◉◉░", "reliable"))
|
|
356
|
+
|
|
357
|
+
# Night owl
|
|
358
|
+
if night >= 5:
|
|
359
|
+
badges.append(("◆◇◆", "night owl"))
|
|
360
|
+
|
|
361
|
+
return badges
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# ── Full welcome banner ─────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str,
|
|
367
|
+
version: str = "", is_commander: bool = False,
|
|
368
|
+
role: str = "", stats: dict = None) -> str:
|
|
369
|
+
"""Render the full colored welcome banner with personality."""
|
|
370
|
+
h = _hash_bytes(agent_name)
|
|
371
|
+
color = COLORS[h[0] % len(COLORS)]
|
|
372
|
+
|
|
373
|
+
traits = get_personality_traits(agent_name, role)
|
|
374
|
+
catchphrase = get_catchphrase(agent_name, role)
|
|
375
|
+
greeting = get_boot_greeting(agent_name, role)
|
|
376
|
+
achievements = compute_achievements(stats or {})
|
|
377
|
+
|
|
378
|
+
ver_str = f"v{version}" if version else ""
|
|
379
|
+
commander_badge = f" {YELLOW}{STAR} COMMANDER{RESET}" if is_commander else ""
|
|
380
|
+
|
|
381
|
+
lines = []
|
|
382
|
+
lines.append("")
|
|
383
|
+
lines.append(f"{color}{BOLD} ╔══════════════════════════════════════════╗{RESET}")
|
|
384
|
+
lines.append(f"{color}{BOLD} ║{RESET} {DIM}M E S H C O D E{RESET} {color}{ver_str}{RESET} {color}{BOLD}║{RESET}")
|
|
385
|
+
lines.append(f"{color}{BOLD} ╚══════════════════════════════════════════╝{RESET}")
|
|
386
|
+
lines.append("")
|
|
387
|
+
|
|
388
|
+
# Identicon
|
|
389
|
+
for line in ascii_art.split("\n"):
|
|
390
|
+
lines.append(f" {color}{line}{RESET}")
|
|
391
|
+
|
|
392
|
+
lines.append("")
|
|
393
|
+
|
|
394
|
+
# Agent name + greeting
|
|
395
|
+
lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET}{commander_badge} {DIM}{greeting}{RESET}")
|
|
396
|
+
|
|
397
|
+
# Catchphrase
|
|
398
|
+
lines.append(f" {DIM}\"{catchphrase}\"{RESET}")
|
|
399
|
+
|
|
400
|
+
lines.append("")
|
|
401
|
+
|
|
402
|
+
# Personality traits
|
|
403
|
+
trait_str = " ".join(f"{emoji} {name}" for emoji, name in traits)
|
|
404
|
+
lines.append(f" {color}{trait_str}{RESET}")
|
|
405
|
+
|
|
406
|
+
# Achievements (if any)
|
|
407
|
+
if achievements:
|
|
408
|
+
badge_str = " ".join(f"{emoji} {name}" for emoji, name in achievements)
|
|
409
|
+
lines.append(f" {DIM}{badge_str}{RESET}")
|
|
410
|
+
|
|
411
|
+
lines.append("")
|
|
412
|
+
|
|
413
|
+
# Info
|
|
414
|
+
lines.append(f" {DIM}meshwork:{RESET} {meshwork_name}")
|
|
415
|
+
if version:
|
|
416
|
+
lines.append(f" {DIM}version:{RESET} {version}")
|
|
417
|
+
lines.append("")
|
|
418
|
+
|
|
419
|
+
# Tip of the session
|
|
420
|
+
tip = get_tip(agent_name)
|
|
421
|
+
lines.append(f" {DIM}▸ tip: {tip}{RESET}")
|
|
422
|
+
lines.append("")
|
|
423
|
+
|
|
424
|
+
return "\n".join(lines)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# ── 6. Tips (random per session, teach users meshcode) ──────────────
|
|
428
|
+
|
|
429
|
+
TIPS = [
|
|
430
|
+
'Tell your agent "stay in loop" so it doesn\'t go to sleep between tasks.',
|
|
431
|
+
'Tell the commander "go to sleep after X task" and agents auto-sleep when done.',
|
|
432
|
+
'Use meshcode_broadcast() to send a message to ALL agents at once.',
|
|
433
|
+
'Agents in sleep mode consume zero tokens — only wake them when needed.',
|
|
434
|
+
'The commander can delegate tasks: "create a task for backend to fix X".',
|
|
435
|
+
'Use meshcode_scratchpad_set() to share notes visible to ALL agents.',
|
|
436
|
+
'Force disconnect from the dashboard if an agent gets stuck.',
|
|
437
|
+
'Each agent has a unique identicon — it\'s their digital fingerprint.',
|
|
438
|
+
'Run meshcode status in terminal to see who\'s online without opening the dashboard.',
|
|
439
|
+
'Invite teammates with meshcode invite — they get their own scoped agent.',
|
|
440
|
+
'Agents can message across meshworks: meshcode_send(to="agent@other-mesh").',
|
|
441
|
+
'The commander coordinates — let it break big tasks into sub-tasks for agents.',
|
|
442
|
+
'Use meshcode_remember() to save context that persists across agent sessions.',
|
|
443
|
+
'Check meshcode_tasks() regularly — other agents may have assigned you work.',
|
|
444
|
+
'The dashboard shows real-time agent activity — no refresh needed.',
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def get_tip(agent_name: str) -> str:
|
|
449
|
+
"""Get a rotating tip. Changes each session (uses time + name for variety)."""
|
|
450
|
+
import time
|
|
451
|
+
# Rotate based on hour + agent name so different agents show different tips
|
|
452
|
+
h = _hash_int(agent_name + str(int(time.time()) // 3600))
|
|
453
|
+
return TIPS[h % len(TIPS)]
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
if __name__ == "__main__":
|
|
457
|
+
import sys
|
|
458
|
+
names = sys.argv[1:] or ["mesh-commander", "backend", "front-end", "security", "qa", "sammy"]
|
|
459
|
+
for name in names:
|
|
460
|
+
art = generate_art(name)
|
|
461
|
+
is_cmd = "commander" in name.lower()
|
|
462
|
+
role = name # Use name as role hint for demo
|
|
463
|
+
demo_stats = {"message_count": 150, "task_count": 12, "days_active": 8, "connect_count": 30}
|
|
464
|
+
print(render_welcome(name, "demo-mesh", art, "2.8.8",
|
|
465
|
+
is_commander=is_cmd, role=role, stats=demo_stats))
|
|
466
|
+
print()
|
|
@@ -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
|
|
@@ -83,7 +113,9 @@ def _try_auto_wake(from_agent: str, preview: str) -> None:
|
|
|
83
113
|
# Sanitize inputs: strip everything except alphanumeric, spaces, basic punctuation
|
|
84
114
|
safe_agent = re.sub(r'[^a-zA-Z0-9_\- ]', '', from_agent)[:50]
|
|
85
115
|
safe_preview = re.sub(r'[^a-zA-Z0-9_\-.,!? ]', '', preview)[:60]
|
|
86
|
-
|
|
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}"
|
|
87
119
|
system = platform.system()
|
|
88
120
|
try:
|
|
89
121
|
if system == "Darwin":
|
|
@@ -309,7 +341,7 @@ def _get_api_key() -> str:
|
|
|
309
341
|
_API_KEY_CACHE = kc_val
|
|
310
342
|
return kc_val
|
|
311
343
|
except Exception as e:
|
|
312
|
-
|
|
344
|
+
_mc_log(f" keychain lookup failed for profile '{profile}': {e}", "warn")
|
|
313
345
|
_API_KEY_CACHE = ""
|
|
314
346
|
return ""
|
|
315
347
|
|
|
@@ -330,13 +362,13 @@ if not _PROJECT_ID:
|
|
|
330
362
|
if isinstance(_r, dict) and _r.get("project_id"):
|
|
331
363
|
_PROJECT_ID = _r["project_id"]
|
|
332
364
|
elif isinstance(_r, dict) and _r.get("error"):
|
|
333
|
-
|
|
365
|
+
_mc_log(f" mc_resolve_project: {_r['error']}", "warn")
|
|
334
366
|
except Exception as _e:
|
|
335
|
-
|
|
367
|
+
_mc_log(f" mc_resolve_project failed: {_e}", "warn")
|
|
336
368
|
if not _PROJECT_ID:
|
|
337
369
|
_PROJECT_ID = be.get_project_id(PROJECT_NAME)
|
|
338
370
|
if not _PROJECT_ID:
|
|
339
|
-
|
|
371
|
+
_mc_log(f"project '{PROJECT_NAME}' not found (check MESHCODE_KEYCHAIN_PROFILE / MESHCODE_API_KEY)", "error")
|
|
340
372
|
sys.exit(2)
|
|
341
373
|
|
|
342
374
|
# Resolve project plan for adaptive features (heartbeat interval, etc.)
|
|
@@ -350,7 +382,7 @@ except Exception:
|
|
|
350
382
|
|
|
351
383
|
_register_result = be.register_agent(PROJECT_NAME, AGENT_NAME, AGENT_ROLE or "MCP-connected agent", api_key=_get_api_key())
|
|
352
384
|
if isinstance(_register_result, dict) and _register_result.get("error"):
|
|
353
|
-
|
|
385
|
+
_mc_log(f" register failed: {_register_result['error']}", "warn")
|
|
354
386
|
|
|
355
387
|
# Flip to online so the dashboard reflects the live MCP session.
|
|
356
388
|
# Use the SECURITY DEFINER RPC (mc_agent_set_status_by_api_key) so we
|
|
@@ -368,7 +400,7 @@ def _flip_status(status: str, task: str = "") -> bool:
|
|
|
368
400
|
return False
|
|
369
401
|
|
|
370
402
|
if not _flip_status("idle", ""):
|
|
371
|
-
|
|
403
|
+
_mc_log(f" could not flip status to idle", "warn")
|
|
372
404
|
|
|
373
405
|
|
|
374
406
|
# ============================================================
|
|
@@ -530,7 +562,7 @@ def _acquire_lease() -> bool:
|
|
|
530
562
|
})
|
|
531
563
|
except Exception as e:
|
|
532
564
|
# Non-fatal: RPC might not exist on older servers.
|
|
533
|
-
|
|
565
|
+
_mc_log(f"stale-lease pre-clean skipped: {e}", "warn")
|
|
534
566
|
for attempt in range(3):
|
|
535
567
|
try:
|
|
536
568
|
r = be.sb_rpc("mc_acquire_agent_lease", {
|
|
@@ -547,11 +579,11 @@ def _acquire_lease() -> bool:
|
|
|
547
579
|
if attempt < 2:
|
|
548
580
|
# The old lease might be stale — wait and retry
|
|
549
581
|
# (the 90s stale check in the RPC should clear it)
|
|
550
|
-
|
|
582
|
+
_mc_log(f"Lease held by another instance — retrying in {2 * (attempt+1)}s...")
|
|
551
583
|
_time.sleep(2 * (attempt + 1))
|
|
552
584
|
continue
|
|
553
585
|
# Final attempt — force release the old lease and try once more
|
|
554
|
-
|
|
586
|
+
_mc_log("Force-releasing stale lease...")
|
|
555
587
|
try:
|
|
556
588
|
be.sb_rpc("mc_release_agent_lease", {
|
|
557
589
|
"p_api_key": api_key,
|
|
@@ -575,21 +607,21 @@ def _acquire_lease() -> bool:
|
|
|
575
607
|
"p_instance_id": _INSTANCE_ID,
|
|
576
608
|
})
|
|
577
609
|
if isinstance(r2, dict) and r2.get("ok"):
|
|
578
|
-
|
|
610
|
+
_mc_log("Lease acquired after force-release.")
|
|
579
611
|
return True
|
|
580
612
|
except Exception:
|
|
581
613
|
pass
|
|
582
|
-
|
|
583
|
-
|
|
614
|
+
_mc_log(f"Could not start — agent '{AGENT_NAME}' is running in another window.", "error")
|
|
615
|
+
_mc_log("Close the other window first, or use a different agent name.", "error")
|
|
584
616
|
return False
|
|
585
|
-
|
|
617
|
+
_mc_log(f"lease attempt {attempt+1}: {r.get('error')}", "warn")
|
|
586
618
|
else:
|
|
587
619
|
return True
|
|
588
620
|
except Exception as e:
|
|
589
|
-
|
|
621
|
+
_mc_log(f"lease attempt {attempt+1} failed: {e}", "warn")
|
|
590
622
|
if attempt < 2:
|
|
591
623
|
_time.sleep(2)
|
|
592
|
-
|
|
624
|
+
_mc_log(f" lease failed after 3 attempts — proceeding anyway", "warn")
|
|
593
625
|
return True
|
|
594
626
|
|
|
595
627
|
if not _acquire_lease():
|
|
@@ -607,7 +639,7 @@ def _boot_diagnostic() -> None:
|
|
|
607
639
|
be.sb_select("mc_projects", f"id=eq.{_PROJECT_ID}", limit=1)
|
|
608
640
|
checks_passed += 1
|
|
609
641
|
except Exception as e:
|
|
610
|
-
print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.",
|
|
642
|
+
print(f"[meshcode] BOOT CHECK FAILED: Supabase API unreachable ({e}). Fix: check network/VPN.", "warn")
|
|
611
643
|
|
|
612
644
|
# Check 2: Lease valid
|
|
613
645
|
try:
|
|
@@ -617,11 +649,11 @@ def _boot_diagnostic() -> None:
|
|
|
617
649
|
if agent.get("instance_id") == _INSTANCE_ID:
|
|
618
650
|
checks_passed += 1
|
|
619
651
|
else:
|
|
620
|
-
print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.",
|
|
652
|
+
print(f"[meshcode] BOOT CHECK FAILED: Lease mismatch — expected {_INSTANCE_ID}, got {agent.get('instance_id')}. Fix: restart agent.", "warn")
|
|
621
653
|
else:
|
|
622
|
-
print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.",
|
|
654
|
+
print(f"[meshcode] BOOT CHECK FAILED: Agent '{AGENT_NAME}' not found in project. Fix: register agent first.", "warn")
|
|
623
655
|
except Exception as e:
|
|
624
|
-
print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).",
|
|
656
|
+
print(f"[meshcode] BOOT CHECK FAILED: Could not verify lease ({e}).", "warn")
|
|
625
657
|
|
|
626
658
|
# Check 3: Heartbeat recent
|
|
627
659
|
try:
|
|
@@ -630,7 +662,7 @@ def _boot_diagnostic() -> None:
|
|
|
630
662
|
if hb:
|
|
631
663
|
checks_passed += 1
|
|
632
664
|
else:
|
|
633
|
-
print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.",
|
|
665
|
+
print(f"[meshcode] BOOT CHECK WARNING: No heartbeat recorded yet.", "warn")
|
|
634
666
|
else:
|
|
635
667
|
checks_passed += 1 # skip if no agent data
|
|
636
668
|
except Exception:
|
|
@@ -647,9 +679,9 @@ def _boot_diagnostic() -> None:
|
|
|
647
679
|
checks_passed += 1 # non-critical
|
|
648
680
|
|
|
649
681
|
if checks_passed == checks_total:
|
|
650
|
-
print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).",
|
|
682
|
+
print(f"[meshcode] All boot checks passed ({checks_passed}/{checks_total}).", "warn")
|
|
651
683
|
else:
|
|
652
|
-
print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.",
|
|
684
|
+
print(f"[meshcode] Boot checks: {checks_passed}/{checks_total} passed. Agent starting anyway.", "warn")
|
|
653
685
|
|
|
654
686
|
|
|
655
687
|
_boot_diagnostic()
|
|
@@ -1291,9 +1323,9 @@ try:
|
|
|
1291
1323
|
elif isinstance(_ls_val, str):
|
|
1292
1324
|
_LAST_SEEN_TS = _ls_val
|
|
1293
1325
|
if _LAST_SEEN_TS:
|
|
1294
|
-
print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.",
|
|
1326
|
+
print(f"[meshcode] Restored last_seen={_LAST_SEEN_TS} from mesh memory.", "warn")
|
|
1295
1327
|
except Exception as _e:
|
|
1296
|
-
print(f"[meshcode] Could not restore last_seen: {_e}",
|
|
1328
|
+
print(f"[meshcode] Could not restore last_seen: {_e}", "warn")
|
|
1297
1329
|
|
|
1298
1330
|
|
|
1299
1331
|
def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
@@ -2406,7 +2438,7 @@ def _auto_update() -> None:
|
|
|
2406
2438
|
return
|
|
2407
2439
|
|
|
2408
2440
|
# 3. Install the new version (blocking, 60s timeout)
|
|
2409
|
-
print(f"[meshcode] Updating {current} → {latest}...",
|
|
2441
|
+
print(f"[meshcode] Updating {current} → {latest}...", "warn")
|
|
2410
2442
|
try:
|
|
2411
2443
|
result = subprocess.run(
|
|
2412
2444
|
[sys.executable, "-m", "pip", "install", "--upgrade",
|
|
@@ -2424,7 +2456,7 @@ def _auto_update() -> None:
|
|
|
2424
2456
|
return
|
|
2425
2457
|
|
|
2426
2458
|
# 4. Re-exec to load the new code
|
|
2427
|
-
print(f"[meshcode] Updated to {latest}, restarting...",
|
|
2459
|
+
print(f"[meshcode] Updated to {latest}, restarting...", "warn")
|
|
2428
2460
|
os.environ["MESHCODE_UPDATED"] = "1"
|
|
2429
2461
|
try:
|
|
2430
2462
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
|
@@ -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,13 +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
|
-
|
|
384
|
+
is_cmd = "commander" in agent.lower()
|
|
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
|
+
))
|
|
346
391
|
except Exception:
|
|
347
392
|
pass # Non-critical — skip banner on error
|
|
348
393
|
|
|
@@ -1,124 +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
|
-
def render_welcome(agent_name: str, meshwork_name: str, ascii_art: str, version: str = "") -> str:
|
|
92
|
-
"""Render the full colored welcome banner for terminal display."""
|
|
93
|
-
h = _hash_bytes(agent_name)
|
|
94
|
-
color = COLORS[h[0] % len(COLORS)]
|
|
95
|
-
|
|
96
|
-
ver_str = f"v{version}" if version else ""
|
|
97
|
-
lines = []
|
|
98
|
-
lines.append("")
|
|
99
|
-
lines.append(f"{color}{BOLD} ╔══════════════════════════════════════════╗{RESET}")
|
|
100
|
-
lines.append(f"{color}{BOLD} ║{RESET} {DIM}M E S H C O D E{RESET} {color}{ver_str}{RESET} {color}{BOLD}║{RESET}")
|
|
101
|
-
lines.append(f"{color}{BOLD} ╚══════════════════════════════════════════╝{RESET}")
|
|
102
|
-
lines.append("")
|
|
103
|
-
|
|
104
|
-
for line in ascii_art.split("\n"):
|
|
105
|
-
lines.append(f" {color}{line}{RESET}")
|
|
106
|
-
|
|
107
|
-
lines.append("")
|
|
108
|
-
lines.append(f" {BOLD}{color}●{RESET} {BOLD}{agent_name}{RESET} {DIM}is online{RESET}")
|
|
109
|
-
lines.append(f" {DIM}meshwork:{RESET} {meshwork_name}")
|
|
110
|
-
if version:
|
|
111
|
-
lines.append(f" {DIM}version:{RESET} {version}")
|
|
112
|
-
lines.append("")
|
|
113
|
-
|
|
114
|
-
return "\n".join(lines)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if __name__ == "__main__":
|
|
118
|
-
# Demo: show a few agents
|
|
119
|
-
import sys
|
|
120
|
-
names = sys.argv[1:] or ["sammy", "ian", "fis", "backend", "security", "commander"]
|
|
121
|
-
for name in names:
|
|
122
|
-
art = generate_art(name)
|
|
123
|
-
print(render_welcome(name, "demo-mesh", art, "2.8.6"))
|
|
124
|
-
print()
|
|
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
|