meshcode 2.10.42__tar.gz → 2.10.43__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.42 → meshcode-2.10.43}/PKG-INFO +1 -1
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/__init__.py +1 -1
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/comms_v4.py +45 -1
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/server.py +29 -8
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/run_agent.py +30 -6
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/secrets.py +29 -8
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/setup_clients.py +20 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.10.42 → meshcode-2.10.43}/pyproject.toml +1 -1
- {meshcode-2.10.42 → meshcode-2.10.43}/README.md +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/cli.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/invites.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/launcher.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/preferences.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode/self_update.py +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/setup.cfg +0 -0
- {meshcode-2.10.42 → meshcode-2.10.43}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.10.
|
|
2
|
+
__version__ = "2.10.43"
|
|
@@ -352,6 +352,48 @@ def send_notification(project, name, from_agent, pending=1):
|
|
|
352
352
|
pass
|
|
353
353
|
|
|
354
354
|
|
|
355
|
+
def _nudge_windows_terminal(name, pending, tty, pid):
|
|
356
|
+
"""Activate the agent's terminal window on Windows.
|
|
357
|
+
|
|
358
|
+
Uses PowerShell + kernel32 to find the console window by process ID and
|
|
359
|
+
bring it to the foreground. Does NOT send keystrokes — that would risk
|
|
360
|
+
confirming prompts or interrupting bypass mode. The window activation
|
|
361
|
+
combined with the desktop notification is enough to alert the user.
|
|
362
|
+
"""
|
|
363
|
+
if not pid:
|
|
364
|
+
return
|
|
365
|
+
try:
|
|
366
|
+
pid = int(pid)
|
|
367
|
+
except (ValueError, TypeError):
|
|
368
|
+
return
|
|
369
|
+
try:
|
|
370
|
+
ps_script = f'''
|
|
371
|
+
$proc = Get-Process -Id {pid} -ErrorAction SilentlyContinue
|
|
372
|
+
if ($proc -and $proc.MainWindowHandle -ne 0) {{
|
|
373
|
+
Add-Type @"
|
|
374
|
+
using System;
|
|
375
|
+
using System.Runtime.InteropServices;
|
|
376
|
+
public class WinApi {{
|
|
377
|
+
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
378
|
+
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
379
|
+
}}
|
|
380
|
+
"@
|
|
381
|
+
[WinApi]::ShowWindow($proc.MainWindowHandle, 9)
|
|
382
|
+
[WinApi]::SetForegroundWindow($proc.MainWindowHandle)
|
|
383
|
+
}}
|
|
384
|
+
'''
|
|
385
|
+
subprocess.run(
|
|
386
|
+
["powershell", "-NoProfile", "-Command", ps_script],
|
|
387
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
388
|
+
timeout=10,
|
|
389
|
+
)
|
|
390
|
+
log_msg(f"[nudge] Windows: activated terminal for {name} (pid={pid})")
|
|
391
|
+
except subprocess.TimeoutExpired:
|
|
392
|
+
log_msg(f"[nudge] Windows: PowerShell timed out for {name}")
|
|
393
|
+
except Exception as e:
|
|
394
|
+
log_msg(f"[nudge] Windows: failed for {name}: {e}")
|
|
395
|
+
|
|
396
|
+
|
|
355
397
|
def _is_pid_alive(pid):
|
|
356
398
|
"""Return True iff the given pid corresponds to a live process."""
|
|
357
399
|
if not pid:
|
|
@@ -796,8 +838,10 @@ def nudge_agent(project, name, from_agent=""):
|
|
|
796
838
|
message = f"Tienes {pending} mensaje(s). Revisa: meshcode read {project} {name}"
|
|
797
839
|
escaped_message = message.replace('\\', '\\\\').replace('"', '\\"')
|
|
798
840
|
|
|
799
|
-
#
|
|
841
|
+
# Non-macOS: use platform-specific terminal nudge
|
|
800
842
|
if sys.platform != "darwin":
|
|
843
|
+
if sys.platform == "win32":
|
|
844
|
+
_nudge_windows_terminal(name, pending, tty, cached_pid)
|
|
801
845
|
send_notification(project, name, from_agent, pending)
|
|
802
846
|
mark_nudged(project, name)
|
|
803
847
|
return True
|
|
@@ -108,14 +108,14 @@ _SEEN_TTL = 300.0 # 5 minutes
|
|
|
108
108
|
# ============================================================
|
|
109
109
|
# Auto-wake: when agent is NOT in meshcode_wait and a message
|
|
110
110
|
# arrives, inject text into the terminal to wake the agent.
|
|
111
|
-
# DEFAULT: ON. Disable with MESHCODE_AUTO_WAKE=0 if you don't want it.
|
|
112
111
|
# ============================================================
|
|
113
112
|
_IN_WAIT = False # True while meshcode_wait is blocking
|
|
114
|
-
# Default OFF
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# MESHCODE_AUTO_WAKE=1
|
|
113
|
+
# Default OFF — keystroke injection can corrupt stdin on some terminals.
|
|
114
|
+
# The primary nudge path is now in comms_v4.nudge_agent() which uses
|
|
115
|
+
# platform-specific window activation (SetForegroundWindow on Windows,
|
|
116
|
+
# AppleScript on macOS) + desktop notification.
|
|
117
|
+
# Set MESHCODE_AUTO_WAKE=1 to also inject text into the terminal from
|
|
118
|
+
# the MCP server side (useful if comms_v4 nudge doesn't reach the agent).
|
|
119
119
|
_AUTO_WAKE = os.environ.get("MESHCODE_AUTO_WAKE", "0").lower() in ("1", "true", "yes")
|
|
120
120
|
|
|
121
121
|
|
|
@@ -197,8 +197,10 @@ def _try_auto_wake(from_agent: str, preview: str) -> None:
|
|
|
197
197
|
Add-Type -AssemblyName System.Windows.Forms
|
|
198
198
|
[System.Windows.Forms.SendKeys]::SendWait("{sk_safe}{{ENTER}}")
|
|
199
199
|
'''
|
|
200
|
-
subprocess.
|
|
201
|
-
|
|
200
|
+
# Use subprocess.run with timeout to prevent orphaned PowerShell processes
|
|
201
|
+
subprocess.run(["powershell", "-NoProfile", "-Command", ps_script],
|
|
202
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
203
|
+
timeout=10)
|
|
202
204
|
log.info("auto-wake: injected nudge via PowerShell SendKeys")
|
|
203
205
|
else:
|
|
204
206
|
# Linux — xdotool takes args directly, not shell-interpolated (safe)
|
|
@@ -3026,6 +3028,25 @@ def meshcode_recall_search(query: str) -> Dict[str, Any]:
|
|
|
3026
3028
|
})
|
|
3027
3029
|
|
|
3028
3030
|
|
|
3031
|
+
# ----------------- BUG REPORTING -----------------
|
|
3032
|
+
|
|
3033
|
+
@mcp.tool()
|
|
3034
|
+
@with_working_status
|
|
3035
|
+
def meshcode_report_bug(description: str) -> Dict[str, Any]:
|
|
3036
|
+
"""Report a bug from within the mesh. Visible in admin panel.
|
|
3037
|
+
|
|
3038
|
+
Args:
|
|
3039
|
+
description: What went wrong — include steps to reproduce if possible.
|
|
3040
|
+
"""
|
|
3041
|
+
api_key = _get_api_key()
|
|
3042
|
+
return be.sb_rpc("mc_report_bug", {
|
|
3043
|
+
"p_api_key": api_key,
|
|
3044
|
+
"p_description": description,
|
|
3045
|
+
"p_meshwork_name": PROJECT_NAME,
|
|
3046
|
+
"p_agent_name": AGENT_NAME,
|
|
3047
|
+
})
|
|
3048
|
+
|
|
3049
|
+
|
|
3029
3050
|
# ----------------- HEALTH CHECK -----------------
|
|
3030
3051
|
|
|
3031
3052
|
@mcp.tool()
|
|
@@ -363,13 +363,30 @@ def _claude_supports_bypass(editor_cmd: str) -> bool:
|
|
|
363
363
|
"""
|
|
364
364
|
try:
|
|
365
365
|
use_shell = sys.platform == "win32" and editor_cmd.lower().endswith((".cmd", ".bat"))
|
|
366
|
+
print(f"[meshcode] bypass-detect: checking '{editor_cmd}' (shell={use_shell})", file=sys.stderr)
|
|
366
367
|
r = subprocess.run(
|
|
367
368
|
[editor_cmd, "--help"],
|
|
368
369
|
capture_output=True, text=True, timeout=10,
|
|
369
370
|
shell=use_shell,
|
|
370
371
|
)
|
|
371
|
-
|
|
372
|
-
|
|
372
|
+
found = "--dangerously-skip-permissions" in r.stdout
|
|
373
|
+
if found:
|
|
374
|
+
print(f"[meshcode] bypass-detect: ✓ flag found — bypass mode available", file=sys.stderr)
|
|
375
|
+
else:
|
|
376
|
+
stdout_len = len(r.stdout)
|
|
377
|
+
stderr_preview = (r.stderr or "")[:200]
|
|
378
|
+
print(f"[meshcode] bypass-detect: ✗ flag NOT found in --help output ({stdout_len} chars, exit={r.returncode})", file=sys.stderr)
|
|
379
|
+
if stderr_preview:
|
|
380
|
+
print(f"[meshcode] bypass-detect: stderr: {stderr_preview}", file=sys.stderr)
|
|
381
|
+
return found
|
|
382
|
+
except subprocess.TimeoutExpired:
|
|
383
|
+
print(f"[meshcode] bypass-detect: ✗ '{editor_cmd} --help' timed out after 10s", file=sys.stderr)
|
|
384
|
+
return False
|
|
385
|
+
except FileNotFoundError:
|
|
386
|
+
print(f"[meshcode] bypass-detect: ✗ '{editor_cmd}' not found in PATH", file=sys.stderr)
|
|
387
|
+
return False
|
|
388
|
+
except Exception as e:
|
|
389
|
+
print(f"[meshcode] bypass-detect: ✗ unexpected error: {e}", file=sys.stderr)
|
|
373
390
|
return False
|
|
374
391
|
|
|
375
392
|
|
|
@@ -377,17 +394,22 @@ def _detect_editor() -> Optional[str]:
|
|
|
377
394
|
"""Pick the user's preferred MCP-aware editor."""
|
|
378
395
|
override = os.environ.get("MESHCODE_EDITOR", "").strip().lower()
|
|
379
396
|
if override:
|
|
380
|
-
|
|
381
|
-
|
|
397
|
+
resolved = shutil.which(override)
|
|
398
|
+
if resolved:
|
|
399
|
+
# On Windows, return resolved path to preserve .cmd/.bat extension
|
|
400
|
+
return resolved if sys.platform == "win32" else override
|
|
382
401
|
print(f"[meshcode] WARNING: MESHCODE_EDITOR='{override}' not found in PATH", file=sys.stderr)
|
|
383
402
|
|
|
384
403
|
# Detection order: most likely to be installed + best per-workspace MCP
|
|
385
404
|
# support first. codex is last because its MCP config is GLOBAL only —
|
|
386
405
|
# launching it doesn't take a workspace-scoped .mcp.json, so it should
|
|
387
406
|
# only be picked if nothing else is available.
|
|
407
|
+
# On Windows, return the fully-resolved path so .cmd/.bat extension is
|
|
408
|
+
# preserved — needed for shell=True detection in bypass and launch paths.
|
|
388
409
|
for cmd in ("claude", "cursor", "code", "windsurf", "codex"):
|
|
389
|
-
|
|
390
|
-
|
|
410
|
+
resolved = shutil.which(cmd)
|
|
411
|
+
if resolved:
|
|
412
|
+
return resolved if sys.platform == "win32" else cmd
|
|
391
413
|
|
|
392
414
|
# Windows fallback: check common install locations not in PATH
|
|
393
415
|
if sys.platform == "win32":
|
|
@@ -655,6 +677,8 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
655
677
|
# MCP" bucket and survive ESC. Verified with a minimal FastMCP
|
|
656
678
|
# repro side-by-side: Built-in dies, Local survives, same SDK code.
|
|
657
679
|
mode = resolve_permission_mode(permission_override)
|
|
680
|
+
print(f"[meshcode] Editor resolved: {editor}", file=sys.stderr)
|
|
681
|
+
print(f"[meshcode] Permission mode resolved: {mode}", file=sys.stderr)
|
|
658
682
|
cmd = [editor]
|
|
659
683
|
if mode == "bypass":
|
|
660
684
|
if _claude_supports_bypass(editor):
|
|
@@ -47,6 +47,33 @@ DEFAULT_PROFILE = "default"
|
|
|
47
47
|
LEGACY_CREDS_FILE = Path.home() / ".meshcode" / "credentials.json"
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
# ============================================================
|
|
51
|
+
# File permission helpers
|
|
52
|
+
# ============================================================
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _restrict_perms(path: "Path") -> None:
|
|
56
|
+
"""Restrict file to owner-only access on all platforms.
|
|
57
|
+
|
|
58
|
+
On Windows, os.chmod(0o600) is a no-op — must use icacls to remove
|
|
59
|
+
inherited ACLs and grant only the current user Full Control.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
if sys.platform == "win32":
|
|
63
|
+
import subprocess
|
|
64
|
+
username = os.environ.get("USERNAME", os.environ.get("USER", ""))
|
|
65
|
+
if username:
|
|
66
|
+
subprocess.run(
|
|
67
|
+
["icacls", str(path), "/inheritance:r",
|
|
68
|
+
"/grant:r", f"{username}:(F)"],
|
|
69
|
+
capture_output=True, timeout=5,
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
os.chmod(path, 0o600)
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
50
77
|
# ============================================================
|
|
51
78
|
# Keyring backend probe
|
|
52
79
|
# ============================================================
|
|
@@ -129,10 +156,7 @@ def _save_index(idx: Dict[str, Any]) -> None:
|
|
|
129
156
|
PROFILE_INDEX.parent.mkdir(parents=True, exist_ok=True)
|
|
130
157
|
tmp = PROFILE_INDEX.with_suffix(".tmp")
|
|
131
158
|
tmp.write_text(json.dumps(idx, indent=2), encoding="utf-8")
|
|
132
|
-
|
|
133
|
-
os.chmod(tmp, 0o600)
|
|
134
|
-
except Exception:
|
|
135
|
-
pass
|
|
159
|
+
_restrict_perms(tmp)
|
|
136
160
|
tmp.replace(PROFILE_INDEX)
|
|
137
161
|
|
|
138
162
|
|
|
@@ -182,10 +206,7 @@ def set_api_key(api_key: str, profile: str = DEFAULT_PROFILE,
|
|
|
182
206
|
fallback_path = Path.home() / ".meshcode" / f"credentials.{profile}.json"
|
|
183
207
|
fallback_path.parent.mkdir(parents=True, exist_ok=True)
|
|
184
208
|
fallback_path.write_text(json.dumps({"api_key": api_key, **(meta or {})}, indent=2), encoding="utf-8")
|
|
185
|
-
|
|
186
|
-
os.chmod(fallback_path, 0o600)
|
|
187
|
-
except Exception:
|
|
188
|
-
pass
|
|
209
|
+
_restrict_perms(fallback_path)
|
|
189
210
|
_index_set(profile, {"backend": "file-fallback", **(meta or {})})
|
|
190
211
|
return True
|
|
191
212
|
|
|
@@ -204,11 +204,31 @@ def _build_server_block(project: str, project_id: str, agent: str, role: str,
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
def _restrict_perms(path: Path) -> None:
|
|
208
|
+
"""Restrict file to owner-only access (Windows-aware)."""
|
|
209
|
+
try:
|
|
210
|
+
if sys.platform == "win32":
|
|
211
|
+
import subprocess
|
|
212
|
+
username = os.environ.get("USERNAME", os.environ.get("USER", ""))
|
|
213
|
+
if username:
|
|
214
|
+
subprocess.run(
|
|
215
|
+
["icacls", str(path), "/inheritance:r",
|
|
216
|
+
"/grant:r", f"{username}:(F)"],
|
|
217
|
+
capture_output=True, timeout=5,
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
os.chmod(path, 0o600)
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
|
|
207
225
|
def _atomic_write_json(path: Path, data: dict) -> None:
|
|
208
226
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
209
227
|
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
210
228
|
tmp.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
229
|
+
_restrict_perms(tmp)
|
|
211
230
|
tmp.replace(path)
|
|
231
|
+
_restrict_perms(path)
|
|
212
232
|
|
|
213
233
|
|
|
214
234
|
def _toml_escape(s: str) -> str:
|
|
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
|