meshcode 2.8.9__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.
- {meshcode-2.8.9 → meshcode-2.9.0}/PKG-INFO +1 -1
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/__init__.py +1 -1
- meshcode-2.9.0/meshcode/ascii_art.py +426 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/comms_v4.py +43 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.8.9 → meshcode-2.9.0}/pyproject.toml +1 -1
- meshcode-2.8.9/meshcode/ascii_art.py +0 -466
- {meshcode-2.8.9 → meshcode-2.9.0}/README.md +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/cli.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/invites.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/launcher.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/launcher_install.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/preferences.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/run_agent.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/secrets.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/self_update.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode/setup_clients.py +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/setup.cfg +0 -0
- {meshcode-2.8.9 → meshcode-2.9.0}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.
|
|
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:
|
|
@@ -1,466 +0,0 @@
|
|
|
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()
|
|
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
|
|
File without changes
|