meshcode 2.10.40__tar.gz → 2.10.42__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.10.40 → meshcode-2.10.42}/PKG-INFO +25 -3
- {meshcode-2.10.40 → meshcode-2.10.42}/README.md +24 -2
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/__init__.py +1 -1
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/comms_v4.py +210 -53
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/backend.py +187 -21
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/server.py +141 -37
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/preferences.py +22 -5
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/run_agent.py +162 -12
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/setup_clients.py +4 -1
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode.egg-info/PKG-INFO +25 -3
- {meshcode-2.10.40 → meshcode-2.10.42}/pyproject.toml +1 -1
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/cli.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/invites.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/launcher.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/secrets.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode/self_update.py +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/setup.cfg +0 -0
- {meshcode-2.10.40 → meshcode-2.10.42}/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.10.
|
|
3
|
+
Version: 2.10.42
|
|
4
4
|
Summary: Real-time communication between AI agents — Supabase-backed CLI
|
|
5
5
|
Author-email: MeshCode <hello@meshcode.io>
|
|
6
6
|
License: MIT
|
|
@@ -400,10 +400,32 @@ meshcode projects # lists all your meshworks
|
|
|
400
400
|
**8. Unknown command (e.g., `meshcode list`)**
|
|
401
401
|
Since v2.0.2, the CLI suggests the closest match instead of dumping the full help text. Common aliases: `list` and `ls` both work for `projects`.
|
|
402
402
|
|
|
403
|
-
**
|
|
403
|
+
**9. Windows: agent registers but never appears online**
|
|
404
|
+
On Windows, especially with Python 3.14+, you may see `ImportError` crashes or agents that register but stay offline. This was fixed in v2.10.40. Upgrade first:
|
|
405
|
+
```bash
|
|
406
|
+
pip install meshcode --upgrade
|
|
407
|
+
```
|
|
408
|
+
If the agent still fails after upgrading, check that your `.mcp.json` command path is valid. On Windows, the MCP config should use `python` (not `python3`, which doesn't exist on Windows):
|
|
409
|
+
```json
|
|
410
|
+
{
|
|
411
|
+
"command": "python",
|
|
412
|
+
"args": ["-m", "meshcode_mcp", "serve"]
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
If `meshcode setup` was run from a conda/venv environment that no longer exists, the baked path in `.mcp.json` will be stale. Re-run `meshcode setup <project> <agent>` from your current Python environment to fix it.
|
|
416
|
+
|
|
417
|
+
As a fast-path workaround, you can set `MESHCODE_PROJECT_ID` as an environment variable to bypass the project name lookup at boot (which can fail on Windows due to RLS visibility):
|
|
418
|
+
```bash
|
|
419
|
+
set MESHCODE_PROJECT_ID=your-project-uuid # Windows cmd
|
|
420
|
+
$env:MESHCODE_PROJECT_ID="your-project-uuid" # Windows PowerShell
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
You can also run `meshcode doctor` (v2.10.41+) to diagnose stale paths, missing dependencies, and config issues across all your workspaces.
|
|
424
|
+
|
|
425
|
+
**10. `MCP server failed to start` in the Claude Code `/mcp` panel**
|
|
404
426
|
Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
|
|
405
427
|
|
|
406
|
-
**
|
|
428
|
+
**11. PyPI says `Could not find version 1.x.x` immediately after a publish**
|
|
407
429
|
CDN propagation. Wait ~60s and force-fetch directly from the origin:
|
|
408
430
|
```bash
|
|
409
431
|
pip install --no-cache-dir -i https://pypi.org/simple/ meshcode
|
|
@@ -374,10 +374,32 @@ meshcode projects # lists all your meshworks
|
|
|
374
374
|
**8. Unknown command (e.g., `meshcode list`)**
|
|
375
375
|
Since v2.0.2, the CLI suggests the closest match instead of dumping the full help text. Common aliases: `list` and `ls` both work for `projects`.
|
|
376
376
|
|
|
377
|
-
**
|
|
377
|
+
**9. Windows: agent registers but never appears online**
|
|
378
|
+
On Windows, especially with Python 3.14+, you may see `ImportError` crashes or agents that register but stay offline. This was fixed in v2.10.40. Upgrade first:
|
|
379
|
+
```bash
|
|
380
|
+
pip install meshcode --upgrade
|
|
381
|
+
```
|
|
382
|
+
If the agent still fails after upgrading, check that your `.mcp.json` command path is valid. On Windows, the MCP config should use `python` (not `python3`, which doesn't exist on Windows):
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"command": "python",
|
|
386
|
+
"args": ["-m", "meshcode_mcp", "serve"]
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
If `meshcode setup` was run from a conda/venv environment that no longer exists, the baked path in `.mcp.json` will be stale. Re-run `meshcode setup <project> <agent>` from your current Python environment to fix it.
|
|
390
|
+
|
|
391
|
+
As a fast-path workaround, you can set `MESHCODE_PROJECT_ID` as an environment variable to bypass the project name lookup at boot (which can fail on Windows due to RLS visibility):
|
|
392
|
+
```bash
|
|
393
|
+
set MESHCODE_PROJECT_ID=your-project-uuid # Windows cmd
|
|
394
|
+
$env:MESHCODE_PROJECT_ID="your-project-uuid" # Windows PowerShell
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
You can also run `meshcode doctor` (v2.10.41+) to diagnose stale paths, missing dependencies, and config issues across all your workspaces.
|
|
398
|
+
|
|
399
|
+
**10. `MCP server failed to start` in the Claude Code `/mcp` panel**
|
|
378
400
|
Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
|
|
379
401
|
|
|
380
|
-
**
|
|
402
|
+
**11. PyPI says `Could not find version 1.x.x` immediately after a publish**
|
|
381
403
|
CDN propagation. Wait ~60s and force-fetch directly from the origin:
|
|
382
404
|
```bash
|
|
383
405
|
pip install --no-cache-dir -i https://pypi.org/simple/ meshcode
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.10.
|
|
2
|
+
__version__ = "2.10.42"
|
|
@@ -74,7 +74,11 @@ SUPABASE_KEY = os.environ.get("SUPABASE_KEY") or _env_file.get("SUPABASE_KEY") o
|
|
|
74
74
|
SCHEMA = "meshcode"
|
|
75
75
|
|
|
76
76
|
# Local paths for session/TTY tracking (still needed for nudge)
|
|
77
|
-
|
|
77
|
+
# NEVER use Path(__file__).parent — writing into the package directory
|
|
78
|
+
# creates residual files that turn site-packages/meshcode/ into a
|
|
79
|
+
# namespace package, breaking editable installs and __version__ imports.
|
|
80
|
+
COMMS_DIR = Path.home() / ".meshcode"
|
|
81
|
+
COMMS_DIR.mkdir(parents=True, exist_ok=True)
|
|
78
82
|
SESSIONS_DIR = COMMS_DIR / "sessions"
|
|
79
83
|
LOG_FILE = COMMS_DIR / "comms.log"
|
|
80
84
|
|
|
@@ -310,29 +314,39 @@ def mark_nudged(project, name):
|
|
|
310
314
|
nf.write_text(str(time.time()), encoding="utf-8")
|
|
311
315
|
|
|
312
316
|
|
|
317
|
+
def _sanitize_notification_text(text: str) -> str:
|
|
318
|
+
"""Strip characters that could escape shell/PS quoting contexts."""
|
|
319
|
+
# Remove quotes, backticks, dollar signs, semicolons, pipes, and backslashes
|
|
320
|
+
# to prevent injection in osascript, PowerShell, and notify-send.
|
|
321
|
+
return "".join(c for c in text if c not in '"\'`$;|\\<>(){}')
|
|
322
|
+
|
|
323
|
+
|
|
313
324
|
def send_notification(project, name, from_agent, pending=1):
|
|
314
325
|
"""Cross-platform notification: macOS (osascript), Windows (PowerShell toast), Linux (notify-send)."""
|
|
315
|
-
title = f"MeshCode [{project}] → {name}"
|
|
316
|
-
body = f"{pending} mensaje(s) pendiente(s) de {from_agent}"
|
|
326
|
+
title = _sanitize_notification_text(f"MeshCode [{project}] → {name}")
|
|
327
|
+
body = _sanitize_notification_text(f"{pending} mensaje(s) pendiente(s) de {from_agent}")
|
|
317
328
|
try:
|
|
318
329
|
if sys.platform == "darwin":
|
|
330
|
+
# osascript: pass as -e argument, quotes are stripped by sanitizer
|
|
319
331
|
subprocess.run(['osascript', '-e',
|
|
320
332
|
f'display notification "{body}" with title "{title}" sound name "Ping"'],
|
|
321
333
|
capture_output=True, timeout=3)
|
|
322
334
|
elif sys.platform == "win32":
|
|
335
|
+
# PowerShell: use single-quoted strings (no variable expansion)
|
|
336
|
+
# and pass title/body via -replace to avoid any interpolation
|
|
323
337
|
ps_script = (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
f
|
|
328
|
-
f
|
|
329
|
-
|
|
330
|
-
|
|
338
|
+
'[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null; '
|
|
339
|
+
'$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent(0); '
|
|
340
|
+
'$text = $xml.GetElementsByTagName("text"); '
|
|
341
|
+
f"$text[0].AppendChild($xml.CreateTextNode('{title}')); "
|
|
342
|
+
f"$text[1].AppendChild($xml.CreateTextNode('{body}')); "
|
|
343
|
+
'$toast = [Windows.UI.Notifications.ToastNotification]::new($xml); '
|
|
344
|
+
'[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("MeshCode").Show($toast)'
|
|
331
345
|
)
|
|
332
|
-
subprocess.run(['powershell', '-Command', ps_script],
|
|
346
|
+
subprocess.run(['powershell', '-NoProfile', '-Command', ps_script],
|
|
333
347
|
capture_output=True, timeout=5)
|
|
334
348
|
else:
|
|
335
|
-
# Linux
|
|
349
|
+
# Linux: notify-send takes title and body as separate args (no shell)
|
|
336
350
|
subprocess.run(['notify-send', title, body], capture_output=True, timeout=3)
|
|
337
351
|
except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
|
|
338
352
|
pass
|
|
@@ -343,9 +357,26 @@ def _is_pid_alive(pid):
|
|
|
343
357
|
if not pid:
|
|
344
358
|
return False
|
|
345
359
|
try:
|
|
346
|
-
|
|
360
|
+
pid = int(pid)
|
|
361
|
+
except (ValueError, TypeError):
|
|
362
|
+
return False
|
|
363
|
+
if sys.platform == "win32":
|
|
364
|
+
# os.kill(pid, 0) doesn't work on Windows — use kernel32 OpenProcess
|
|
365
|
+
try:
|
|
366
|
+
import ctypes
|
|
367
|
+
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
368
|
+
handle = ctypes.windll.kernel32.OpenProcess(
|
|
369
|
+
PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
|
|
370
|
+
if handle:
|
|
371
|
+
ctypes.windll.kernel32.CloseHandle(handle)
|
|
372
|
+
return True
|
|
373
|
+
return False
|
|
374
|
+
except Exception:
|
|
375
|
+
return False
|
|
376
|
+
try:
|
|
377
|
+
os.kill(pid, 0)
|
|
347
378
|
return True
|
|
348
|
-
except
|
|
379
|
+
except OSError:
|
|
349
380
|
return False
|
|
350
381
|
|
|
351
382
|
|
|
@@ -533,7 +564,7 @@ def spawn_headless_agent(project, name, project_id, message_body, from_agent):
|
|
|
533
564
|
f"{message_body}\n\n"
|
|
534
565
|
f"Recent mesh activity:\n{history_text or '(none)'}\n\n"
|
|
535
566
|
"Process the message. If you need to reply, use this exact bash command (one line):\n"
|
|
536
|
-
f"
|
|
567
|
+
f" {sys.executable} -m meshcode send {project} {name}:{from_agent} '<json_response>'\n\n"
|
|
537
568
|
"Then exit. Do not start a long conversation."
|
|
538
569
|
)
|
|
539
570
|
|
|
@@ -579,7 +610,8 @@ def spawn_headless_agent(project, name, project_id, message_body, from_agent):
|
|
|
579
610
|
claude_bin = _find_claude()
|
|
580
611
|
# Augment PATH so any subprocess the agent itself spawns (git, npm,
|
|
581
612
|
# python3, supabase CLI, etc.) can find common tools.
|
|
582
|
-
|
|
613
|
+
if sys.platform != "win32":
|
|
614
|
+
env["PATH"] = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:" + env.get("PATH", "")
|
|
583
615
|
result = subprocess.run(
|
|
584
616
|
[claude_bin, "--print", "--dangerously-skip-permissions",
|
|
585
617
|
"--append-system-prompt", system_prompt, message_body or ""],
|
|
@@ -959,12 +991,34 @@ def register(project, name, role=""):
|
|
|
959
991
|
log_msg(f"{name} joined {project} (tty={tty}, backend=supabase)")
|
|
960
992
|
|
|
961
993
|
|
|
994
|
+
def _resolve_agent_name(project_id, name):
|
|
995
|
+
"""Resolve partial agent name to full registered name."""
|
|
996
|
+
if not name or name == "*":
|
|
997
|
+
return name
|
|
998
|
+
agents = sb_select("mc_agents", f"project_id=eq.{project_id}", limit=50)
|
|
999
|
+
if not agents:
|
|
1000
|
+
return name
|
|
1001
|
+
all_names = [a["name"] for a in agents]
|
|
1002
|
+
if name in all_names:
|
|
1003
|
+
return name
|
|
1004
|
+
prefix = [n for n in all_names if n.startswith(name)]
|
|
1005
|
+
if len(prefix) == 1:
|
|
1006
|
+
return prefix[0]
|
|
1007
|
+
sub = [n for n in all_names if name in n]
|
|
1008
|
+
if len(sub) == 1:
|
|
1009
|
+
return sub[0]
|
|
1010
|
+
return name
|
|
1011
|
+
|
|
1012
|
+
|
|
962
1013
|
def send_msg(project, from_agent, to_agent, content, msg_type="msg", compact=False):
|
|
963
1014
|
project_id = get_project_id(project)
|
|
964
1015
|
if not project_id:
|
|
965
1016
|
print(f"[ERROR] Proyecto '{project}' no encontrado")
|
|
966
1017
|
return
|
|
967
1018
|
|
|
1019
|
+
# Resolve partial agent names
|
|
1020
|
+
to_agent = _resolve_agent_name(project_id, to_agent)
|
|
1021
|
+
|
|
968
1022
|
payload = {}
|
|
969
1023
|
try:
|
|
970
1024
|
payload = json.loads(content)
|
|
@@ -1026,7 +1080,7 @@ def broadcast(project, from_agent, content, msg_type="broadcast"):
|
|
|
1026
1080
|
print(f"[{project}] broadcast from {from_agent} -> {count} agents")
|
|
1027
1081
|
|
|
1028
1082
|
|
|
1029
|
-
def read_messages(project, name, silent=False):
|
|
1083
|
+
def read_messages(project, name, silent=False, send_acks=True):
|
|
1030
1084
|
project_id = get_project_id(project)
|
|
1031
1085
|
if not project_id:
|
|
1032
1086
|
if not silent:
|
|
@@ -1064,21 +1118,68 @@ def read_messages(project, name, silent=False):
|
|
|
1064
1118
|
ack_targets.add(sender)
|
|
1065
1119
|
print("---")
|
|
1066
1120
|
|
|
1067
|
-
# Send automatic ACKs
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
"
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1121
|
+
# Send automatic ACKs (skip when called from watch to avoid ack spam)
|
|
1122
|
+
if send_acks:
|
|
1123
|
+
for sender in ack_targets:
|
|
1124
|
+
ack_payload = {"text": f"{name} read your message"}
|
|
1125
|
+
sb_insert("mc_messages", {
|
|
1126
|
+
"project_id": project_id,
|
|
1127
|
+
"from_agent": name,
|
|
1128
|
+
"to_agent": sender,
|
|
1129
|
+
"type": "ack",
|
|
1130
|
+
"payload": ack_payload,
|
|
1131
|
+
"read": True
|
|
1132
|
+
})
|
|
1078
1133
|
|
|
1079
1134
|
return messages
|
|
1080
1135
|
|
|
1081
1136
|
|
|
1137
|
+
def inbox(project, name):
|
|
1138
|
+
"""All-in-one: show agent status, pending count, tasks, and read new messages."""
|
|
1139
|
+
project_id = get_project_id(project)
|
|
1140
|
+
if not project_id:
|
|
1141
|
+
print(f"[meshcode] Project '{project}' not found")
|
|
1142
|
+
return
|
|
1143
|
+
|
|
1144
|
+
# Agent status
|
|
1145
|
+
agent_rows = sb_select("mc_agents",
|
|
1146
|
+
f"project_id=eq.{project_id}&name=eq.{quote(name)}", limit=1)
|
|
1147
|
+
if agent_rows:
|
|
1148
|
+
a = agent_rows[0]
|
|
1149
|
+
print(f"[{project}] {name} | status: {a.get('status', '?')} | task: {a.get('task', '-')}")
|
|
1150
|
+
else:
|
|
1151
|
+
print(f"[{project}] {name} (not registered)")
|
|
1152
|
+
|
|
1153
|
+
# Pending messages
|
|
1154
|
+
pending = sb_select("mc_messages",
|
|
1155
|
+
f"project_id=eq.{project_id}&to_agent=eq.{quote(name)}&read=eq.false&type=neq.ack",
|
|
1156
|
+
order="created_at.asc")
|
|
1157
|
+
if pending:
|
|
1158
|
+
print(f"\n {len(pending)} unread message(s):")
|
|
1159
|
+
for msg in pending:
|
|
1160
|
+
sender = msg["from_agent"]
|
|
1161
|
+
ts = msg.get("created_at", "")
|
|
1162
|
+
if "T" in ts:
|
|
1163
|
+
ts = ts.replace("T", " ")[:19]
|
|
1164
|
+
payload = msg.get("payload", {})
|
|
1165
|
+
preview = json.dumps(payload, ensure_ascii=False)[:100]
|
|
1166
|
+
print(f" [{ts}] {sender}: {preview}")
|
|
1167
|
+
# Ask if they want to read (mark as read)
|
|
1168
|
+
print(f"\n Run `meshcode read {project} {name}` to read and mark as read.")
|
|
1169
|
+
else:
|
|
1170
|
+
print(" No unread messages.")
|
|
1171
|
+
|
|
1172
|
+
# Open tasks assigned to this agent
|
|
1173
|
+
tasks = sb_select("mc_tasks",
|
|
1174
|
+
f"project_id=eq.{project_id}&assignee=eq.{quote(name)}&status=neq.done&status=neq.canceled",
|
|
1175
|
+
order="created_at.desc", limit=5)
|
|
1176
|
+
if tasks:
|
|
1177
|
+
print(f"\n {len(tasks)} open task(s):")
|
|
1178
|
+
for t in tasks:
|
|
1179
|
+
print(f" [{t.get('priority', '?')}] {t.get('title', '?')} ({t.get('status', '?')})")
|
|
1180
|
+
print()
|
|
1181
|
+
|
|
1182
|
+
|
|
1082
1183
|
def hook_check():
|
|
1083
1184
|
"""Called by PostToolUse hook. Auto-detects project+agent, prints pending.
|
|
1084
1185
|
Also sends heartbeat so dashboard always shows fresh status."""
|
|
@@ -1127,7 +1228,10 @@ def watch(project, name, interval=10, timeout=0):
|
|
|
1127
1228
|
f"project_id=eq.{project_id}&name=eq.{quote(name)}",
|
|
1128
1229
|
{"status": "online", "task": "Listening to user", "last_heartbeat": now_iso()})
|
|
1129
1230
|
sys.exit(0)
|
|
1130
|
-
|
|
1231
|
+
try:
|
|
1232
|
+
signal.signal(signal.SIGINT, handle_interrupt)
|
|
1233
|
+
except ValueError:
|
|
1234
|
+
pass # Non-main thread (Windows restriction)
|
|
1131
1235
|
|
|
1132
1236
|
# Update status to standby
|
|
1133
1237
|
sb_update("mc_agents",
|
|
@@ -1148,13 +1252,18 @@ def watch(project, name, interval=10, timeout=0):
|
|
|
1148
1252
|
if pending:
|
|
1149
1253
|
elapsed = int(time.time() - start)
|
|
1150
1254
|
print(f"\n*** [{project.upper()}] MENSAJE RECIBIDO ({elapsed}s en standby) ***")
|
|
1151
|
-
|
|
1255
|
+
read_messages(project, name, silent=False, send_acks=False)
|
|
1152
1256
|
|
|
1153
1257
|
sb_update("mc_agents",
|
|
1154
1258
|
f"project_id=eq.{project_id}&name=eq.{quote(name)}",
|
|
1155
1259
|
{"status": "working", "task": "Procesando mensaje recibido", "last_heartbeat": now_iso()})
|
|
1156
1260
|
|
|
1157
|
-
|
|
1261
|
+
# Reset to standby and keep polling (don't exit the loop)
|
|
1262
|
+
time.sleep(2)
|
|
1263
|
+
sb_update("mc_agents",
|
|
1264
|
+
f"project_id=eq.{project_id}&name=eq.{quote(name)}",
|
|
1265
|
+
{"status": "standby", "task": "Waiting for messages...", "last_heartbeat": now_iso()})
|
|
1266
|
+
continue
|
|
1158
1267
|
|
|
1159
1268
|
# Heartbeat
|
|
1160
1269
|
sb_rpc("mc_heartbeat", {"p_project_id": project_id, "p_agent_name": name})
|
|
@@ -1181,7 +1290,7 @@ def update_board(project, name, status, task=""):
|
|
|
1181
1290
|
count = len(sb_select("mc_messages",
|
|
1182
1291
|
f"project_id=eq.{project_id}&to_agent=eq.{quote(name)}&read=eq.false&type=neq.ack"))
|
|
1183
1292
|
print(f"[{project}] {name}: NO puedes estar idle — tienes {count} mensaje(s) pendientes.")
|
|
1184
|
-
print(f"[{project}] Ejecuta:
|
|
1293
|
+
print(f"[{project}] Ejecuta: {sys.executable} -m meshcode read {project} {name}")
|
|
1185
1294
|
status = "working"
|
|
1186
1295
|
task = f"{count} pending messages"
|
|
1187
1296
|
|
|
@@ -1215,13 +1324,21 @@ def show_board(project):
|
|
|
1215
1324
|
|
|
1216
1325
|
|
|
1217
1326
|
def show_status(project=None):
|
|
1218
|
-
|
|
1327
|
+
# Try API-key-authenticated RPC first (RLS may block anon SELECT)
|
|
1328
|
+
api_key = _load_api_key_for_cli()
|
|
1329
|
+
if api_key and not project:
|
|
1330
|
+
rpc_data = sb_rpc("mc_list_user_projects", {"p_api_key": api_key})
|
|
1331
|
+
if isinstance(rpc_data, dict) and not rpc_data.get("error"):
|
|
1332
|
+
projects_data = rpc_data.get("projects", [])
|
|
1333
|
+
else:
|
|
1334
|
+
projects_data = sb_select("mc_projects", "", order="created_at.asc")
|
|
1335
|
+
elif project:
|
|
1219
1336
|
projects_data = sb_select("mc_projects", f"name=eq.{quote(project)}")
|
|
1220
1337
|
else:
|
|
1221
1338
|
projects_data = sb_select("mc_projects", "", order="created_at.asc")
|
|
1222
1339
|
|
|
1223
1340
|
if not projects_data:
|
|
1224
|
-
print("[
|
|
1341
|
+
print("[meshcode] No projects found. Try `meshcode projects` or `meshcode login <api_key>` first.")
|
|
1225
1342
|
return
|
|
1226
1343
|
|
|
1227
1344
|
print(f"\n{'='*60}")
|
|
@@ -1398,7 +1515,7 @@ def _start_heartbeat_daemon(project, name):
|
|
|
1398
1515
|
if pid_file.exists():
|
|
1399
1516
|
old = int(pid_file.read_text(encoding="utf-8").strip())
|
|
1400
1517
|
try:
|
|
1401
|
-
|
|
1518
|
+
_terminate_pid(old)
|
|
1402
1519
|
except Exception:
|
|
1403
1520
|
pass
|
|
1404
1521
|
except Exception:
|
|
@@ -1450,12 +1567,21 @@ def _start_heartbeat_daemon(project, name):
|
|
|
1450
1567
|
return proc.pid
|
|
1451
1568
|
|
|
1452
1569
|
|
|
1570
|
+
def _terminate_pid(pid):
|
|
1571
|
+
"""Terminate a process by PID, cross-platform."""
|
|
1572
|
+
if sys.platform == "win32":
|
|
1573
|
+
subprocess.run(["taskkill", "/F", "/PID", str(pid)],
|
|
1574
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
1575
|
+
else:
|
|
1576
|
+
os.kill(pid, 15)
|
|
1577
|
+
|
|
1578
|
+
|
|
1453
1579
|
def _stop_heartbeat_daemon(project, name):
|
|
1454
1580
|
pid_file = _heartbeat_loop_pidfile(project, name)
|
|
1455
1581
|
if pid_file.exists():
|
|
1456
1582
|
try:
|
|
1457
1583
|
pid = int(pid_file.read_text(encoding="utf-8").strip())
|
|
1458
|
-
|
|
1584
|
+
_terminate_pid(pid)
|
|
1459
1585
|
except Exception:
|
|
1460
1586
|
pass
|
|
1461
1587
|
try:
|
|
@@ -1603,19 +1729,11 @@ def connect(project, name, hook_target="claude", role=""):
|
|
|
1603
1729
|
_setup_ws(project, name, actual_role)
|
|
1604
1730
|
|
|
1605
1731
|
elif hook_target == "codex":
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
"check_command": f"python3 {comms_path} check",
|
|
1612
|
-
"read_command": f"python3 {comms_path} read {project} {name}",
|
|
1613
|
-
"send_command": f"python3 {comms_path} send {project} {name}:{{to}} '{{message}}'",
|
|
1614
|
-
"platform": os_name
|
|
1615
|
-
}
|
|
1616
|
-
config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
|
1617
|
-
print(f"[meshcode] Config saved to {config_path}")
|
|
1618
|
-
print(f"[meshcode] Codex: use read_command and send_command from the config")
|
|
1732
|
+
# Codex reads ~/.codex/config.toml globally — workspace .mcp.json is ignored.
|
|
1733
|
+
# Use setup_clients.setup_global to write the proper TOML config.
|
|
1734
|
+
import importlib
|
|
1735
|
+
_setup_global = importlib.import_module("meshcode.setup_clients").setup_global
|
|
1736
|
+
_setup_global("codex", project, name, actual_role)
|
|
1619
1737
|
|
|
1620
1738
|
else:
|
|
1621
1739
|
print(f"[meshcode] Unknown hook target: {hook_target}. Use 'claude' or 'codex'.")
|
|
@@ -1629,9 +1747,10 @@ def connect(project, name, hook_target="claude", role=""):
|
|
|
1629
1747
|
print(f" 3. Start using tools: meshcode_send, meshcode_read, etc.")
|
|
1630
1748
|
print(f" No extra terminal. No watch loops. No AppleScript.")
|
|
1631
1749
|
else:
|
|
1632
|
-
|
|
1633
|
-
print(f"
|
|
1634
|
-
print(f"
|
|
1750
|
+
_py = sys.executable
|
|
1751
|
+
print(f" Read: {_py} {comms_path} read {project} {name}")
|
|
1752
|
+
print(f" Send: {_py} {comms_path} send {project} {name}:<target> \"<message>\"")
|
|
1753
|
+
print(f" Board: {_py} {comms_path} board {project}")
|
|
1635
1754
|
print()
|
|
1636
1755
|
|
|
1637
1756
|
|
|
@@ -1840,6 +1959,15 @@ Show conversation history. Optionally filter to messages between two agents.
|
|
|
1840
1959
|
EXAMPLES:
|
|
1841
1960
|
meshcode history my-app 50
|
|
1842
1961
|
meshcode history my-app 20 backend,frontend
|
|
1962
|
+
""",
|
|
1963
|
+
"inbox": """meshcode inbox <project> <name>
|
|
1964
|
+
|
|
1965
|
+
All-in-one overview: agent status, unread messages, and open tasks.
|
|
1966
|
+
Alias: meshcode next
|
|
1967
|
+
|
|
1968
|
+
EXAMPLES:
|
|
1969
|
+
meshcode inbox my-app backend
|
|
1970
|
+
meshcode next my-app frontend
|
|
1843
1971
|
""",
|
|
1844
1972
|
"projects": """meshcode projects
|
|
1845
1973
|
|
|
@@ -2050,6 +2178,7 @@ if __name__ == "__main__":
|
|
|
2050
2178
|
# Supports both:
|
|
2051
2179
|
# send <project> <from>:<to> <message>
|
|
2052
2180
|
# send --project X --from Y --to Z --msg "text" [--type T]
|
|
2181
|
+
# echo '{"key":"val"}' | meshcode send <project> <from>:<to> -
|
|
2053
2182
|
compact = bool(flags.get("compact"))
|
|
2054
2183
|
if "from" in flags and "to" in flags:
|
|
2055
2184
|
proj = flags.get("project", pos[0] if len(pos) > 0 else "default")
|
|
@@ -2062,6 +2191,9 @@ if __name__ == "__main__":
|
|
|
2062
2191
|
proj = pos[0] if len(pos) > 0 else "default"
|
|
2063
2192
|
target = pos[1] if len(pos) > 1 else ""
|
|
2064
2193
|
message = " ".join(pos[2:]) if len(pos) > 2 else ""
|
|
2194
|
+
# Support stdin: `meshcode send proj from:to -` reads from pipe
|
|
2195
|
+
if message.strip() == "-" and not sys.stdin.isatty():
|
|
2196
|
+
message = sys.stdin.read().strip()
|
|
2065
2197
|
if ":" in target:
|
|
2066
2198
|
from_a, to_a = target.split(":", 1)
|
|
2067
2199
|
else:
|
|
@@ -2092,6 +2224,11 @@ if __name__ == "__main__":
|
|
|
2092
2224
|
name = flags.get("name", pos[1] if len(pos) > 1 else "agent")
|
|
2093
2225
|
read_messages(proj, name)
|
|
2094
2226
|
|
|
2227
|
+
elif cmd in ("inbox", "next"):
|
|
2228
|
+
proj = flags.get("project", pos[0] if len(pos) > 0 else "default")
|
|
2229
|
+
name = flags.get("name", pos[1] if len(pos) > 1 else "agent")
|
|
2230
|
+
inbox(proj, name)
|
|
2231
|
+
|
|
2095
2232
|
elif cmd == "check":
|
|
2096
2233
|
hook_check()
|
|
2097
2234
|
|
|
@@ -2427,6 +2564,7 @@ if __name__ == "__main__":
|
|
|
2427
2564
|
|
|
2428
2565
|
elif cmd == "whoami":
|
|
2429
2566
|
# Show the logged-in user identity from profile_meta.json
|
|
2567
|
+
verbose = "--verbose" in sys.argv or "-v" in sys.argv
|
|
2430
2568
|
meta_path = Path.home() / ".meshcode" / "profile_meta.json"
|
|
2431
2569
|
if meta_path.exists():
|
|
2432
2570
|
try:
|
|
@@ -2441,6 +2579,25 @@ if __name__ == "__main__":
|
|
|
2441
2579
|
print(f" user ID: {meta['user_id']}")
|
|
2442
2580
|
active = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
|
|
2443
2581
|
print(f" profile: {active}")
|
|
2582
|
+
|
|
2583
|
+
if verbose:
|
|
2584
|
+
# Show projects, agents, plan, API key status
|
|
2585
|
+
api_key = _load_api_key_for_cli()
|
|
2586
|
+
if api_key:
|
|
2587
|
+
print(f" api key: {api_key[:8]}...{api_key[-4:]}")
|
|
2588
|
+
# List projects
|
|
2589
|
+
result = sb_rpc("mc_list_user_projects", {"p_api_key": api_key})
|
|
2590
|
+
if isinstance(result, dict) and result.get("ok"):
|
|
2591
|
+
projects = result.get("projects", [])
|
|
2592
|
+
print(f"\n Projects ({len(projects)}):")
|
|
2593
|
+
for p in projects:
|
|
2594
|
+
agent_count = p.get("agent_count", "?")
|
|
2595
|
+
plan = p.get("plan", "free")
|
|
2596
|
+
print(f" {p.get('name', '?'):20s} plan={plan:12s} agents={agent_count}")
|
|
2597
|
+
else:
|
|
2598
|
+
print(f"\n Projects: (could not fetch)")
|
|
2599
|
+
else:
|
|
2600
|
+
print(f" api key: not found")
|
|
2444
2601
|
print()
|
|
2445
2602
|
except Exception:
|
|
2446
2603
|
print("[meshcode] Could not read profile. Run `meshcode login <api_key>`.")
|
|
@@ -2558,7 +2715,7 @@ if __name__ == "__main__":
|
|
|
2558
2715
|
|
|
2559
2716
|
else:
|
|
2560
2717
|
known_cmds = [
|
|
2561
|
-
"register", "send", "broadcast", "read", "check", "watch",
|
|
2718
|
+
"register", "send", "broadcast", "read", "check", "watch", "inbox", "next",
|
|
2562
2719
|
"board", "update", "status", "projects", "list", "ls",
|
|
2563
2720
|
"history", "clear", "unregister", "connect", "disconnect",
|
|
2564
2721
|
"setup", "run", "go", "invite", "join", "invites", "members",
|