meshcode 2.4.1__tar.gz → 2.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {meshcode-2.4.1 → meshcode-2.4.2}/PKG-INFO +1 -1
  2. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/__init__.py +1 -1
  3. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/comms_v4.py +121 -32
  4. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/invites.py +8 -1
  5. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/launcher.py +2 -2
  6. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/server.py +12 -2
  7. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/run_agent.py +3 -1
  8. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/self_update.py +3 -3
  9. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode.egg-info/PKG-INFO +1 -1
  10. {meshcode-2.4.1 → meshcode-2.4.2}/pyproject.toml +1 -1
  11. {meshcode-2.4.1 → meshcode-2.4.2}/README.md +0 -0
  12. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/cli.py +0 -0
  13. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/launcher_install.py +0 -0
  14. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/__init__.py +0 -0
  15. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/__main__.py +0 -0
  16. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/backend.py +0 -0
  17. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/realtime.py +0 -0
  18. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/test_backend.py +0 -0
  19. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  20. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  21. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/preferences.py +0 -0
  22. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/protocol_v2.py +0 -0
  23. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/secrets.py +0 -0
  24. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode/setup_clients.py +0 -0
  25. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode.egg-info/SOURCES.txt +0 -0
  26. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode.egg-info/dependency_links.txt +0 -0
  27. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode.egg-info/entry_points.txt +0 -0
  28. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode.egg-info/requires.txt +0 -0
  29. {meshcode-2.4.1 → meshcode-2.4.2}/meshcode.egg-info/top_level.txt +0 -0
  30. {meshcode-2.4.1 → meshcode-2.4.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.4.1
3
+ Version: 2.4.2
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,2 +1,2 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.4.1"
2
+ __version__ = "2.4.2"
@@ -57,7 +57,7 @@ def _load_env_file():
57
57
  return {}
58
58
  out = {}
59
59
  try:
60
- for line in env_path.read_text().splitlines():
60
+ for line in env_path.read_text(encoding="utf-8").splitlines():
61
61
  line = line.strip()
62
62
  if line.startswith("export "):
63
63
  line = line[7:]
@@ -296,7 +296,7 @@ def can_nudge(project, name):
296
296
  nf = get_nudge_file(project, name)
297
297
  if nf and nf.exists():
298
298
  try:
299
- last = float(nf.read_text().strip())
299
+ last = float(nf.read_text(encoding="utf-8").strip())
300
300
  if (time.time() - last) < NUDGE_COOLDOWN:
301
301
  return False
302
302
  except (ValueError, IOError, OSError):
@@ -307,7 +307,7 @@ def can_nudge(project, name):
307
307
  def mark_nudged(project, name):
308
308
  nf = get_nudge_file(project, name)
309
309
  if nf:
310
- nf.write_text(str(time.time()))
310
+ nf.write_text(str(time.time()), encoding="utf-8")
311
311
 
312
312
 
313
313
  def send_notification(project, name, from_agent, pending=1):
@@ -455,7 +455,7 @@ def _throttle_spawn_ok(project, name, max_per_5min=5):
455
455
  history = []
456
456
  if throttle_file.exists():
457
457
  try:
458
- history = [float(x) for x in throttle_file.read_text().split() if x]
458
+ history = [float(x) for x in throttle_file.read_text(encoding="utf-8").split() if x]
459
459
  except Exception:
460
460
  history = []
461
461
  history = [t for t in history if now_ts - t < 300] # last 5 min
@@ -463,7 +463,7 @@ def _throttle_spawn_ok(project, name, max_per_5min=5):
463
463
  return False
464
464
  history.append(now_ts)
465
465
  try:
466
- throttle_file.write_text(" ".join(f"{t:.0f}" for t in history))
466
+ throttle_file.write_text(" ".join(f"{t:.0f}" for t in history), encoding="utf-8")
467
467
  except Exception:
468
468
  pass
469
469
  return True
@@ -615,7 +615,7 @@ def spawn_headless_agent(project, name, project_id, message_body, from_agent):
615
615
  ts_safe = started_iso.replace(":", "-").replace("+", "_")
616
616
  (debug_dir / f"{name}_{ts_safe}.log").write_text(
617
617
  f"=== SYSTEM PROMPT ===\n{system_prompt}\n\n=== USER MESSAGE ===\n{message_body}\n\n=== STDOUT ===\n{stdout_text}\n\n=== STDERR ===\n{stderr_text}\n"
618
- )
618
+ , encoding="utf-8")
619
619
  except Exception:
620
620
  pass
621
621
  if exit_code != 0:
@@ -669,7 +669,7 @@ def nudge_agent(project, name, from_agent=""):
669
669
  return False
670
670
 
671
671
  try:
672
- data = json.loads(session_file.read_text())
672
+ data = json.loads(session_file.read_text(encoding="utf-8"))
673
673
  except (json.JSONDecodeError, IOError, OSError):
674
674
  if _headless_spawn_allowed():
675
675
  log_msg(f"[{project}] NUDGE: {name} session file unreadable, spawning headless (opt-in)")
@@ -818,7 +818,7 @@ def get_session_info():
818
818
  for f in SESSIONS_DIR.iterdir():
819
819
  if f.is_file() and '_' in f.name and not f.name.startswith('.'):
820
820
  try:
821
- data = json.loads(f.read_text())
821
+ data = json.loads(f.read_text(encoding="utf-8"))
822
822
  session_tty = data.get("tty", "")
823
823
  if session_tty and (session_tty in my_tty or my_tty in session_tty):
824
824
  return data.get("project"), data.get("agent")
@@ -861,8 +861,15 @@ def register(project, name, role=""):
861
861
  except (subprocess.SubprocessError, OSError, ValueError):
862
862
  pass
863
863
 
864
- # Register agent via tier-aware RPC (enforces plan limits)
865
- rpc_result = sb_rpc("mc_register_agent", {
864
+ # Register agent via tier-aware RPC (enforces plan limits).
865
+ # Use with_api_key variant so scoped guest keys also work and so
866
+ # the RPC validates owner/member access (no anon bypass).
867
+ _api_key = _load_api_key_for_cli()
868
+ if not _api_key:
869
+ print("[ERROR] Not authenticated. Run `meshcode login <api_key>` first.")
870
+ return
871
+ rpc_result = sb_rpc("mc_register_agent_with_api_key", {
872
+ "p_api_key": _api_key,
866
873
  "p_project_id": project_id,
867
874
  "p_name": name,
868
875
  "p_role": role,
@@ -884,7 +891,7 @@ def register(project, name, role=""):
884
891
 
885
892
  # Save local session file (for nudge TTY detection)
886
893
  session_data = {"project": project, "agent": name, "pid": ppid, "tty": tty, "registered_at": now()}
887
- (SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data))
894
+ (SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data), encoding="utf-8")
888
895
 
889
896
  # Get all agents in project
890
897
  agents = sb_select("mc_agents", f"project_id=eq.{project_id}", order="registered_at.asc")
@@ -1360,7 +1367,7 @@ def _start_heartbeat_daemon(project, name):
1360
1367
  # Stop any existing heartbeat
1361
1368
  try:
1362
1369
  if pid_file.exists():
1363
- old = int(pid_file.read_text().strip())
1370
+ old = int(pid_file.read_text(encoding="utf-8").strip())
1364
1371
  try:
1365
1372
  os.kill(old, 15)
1366
1373
  except Exception:
@@ -1389,12 +1396,17 @@ def _start_heartbeat_daemon(project, name):
1389
1396
  " if pid: post('/rest/v1/rpc/mc_heartbeat', {'p_project_id':pid,'p_agent_name':name})\n"
1390
1397
  " time.sleep(30)\n"
1391
1398
  )
1392
- proc = subprocess.Popen(
1393
- [sys.executable, "-c", code],
1394
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
1395
- start_new_session=True,
1396
- )
1397
- pid_file.write_text(str(proc.pid))
1399
+ # Windows: start_new_session kwarg doesn't exist. Use creationflags.
1400
+ _popen_kwargs = {
1401
+ "stdout": subprocess.DEVNULL,
1402
+ "stderr": subprocess.DEVNULL,
1403
+ }
1404
+ if sys.platform == "win32":
1405
+ _popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
1406
+ else:
1407
+ _popen_kwargs["start_new_session"] = True
1408
+ proc = subprocess.Popen([sys.executable, "-c", code], **_popen_kwargs)
1409
+ pid_file.write_text(str(proc.pid), encoding="utf-8")
1398
1410
  return proc.pid
1399
1411
 
1400
1412
 
@@ -1402,7 +1414,7 @@ def _stop_heartbeat_daemon(project, name):
1402
1414
  pid_file = _heartbeat_loop_pidfile(project, name)
1403
1415
  if pid_file.exists():
1404
1416
  try:
1405
- pid = int(pid_file.read_text().strip())
1417
+ pid = int(pid_file.read_text(encoding="utf-8").strip())
1406
1418
  os.kill(pid, 15)
1407
1419
  except Exception:
1408
1420
  pass
@@ -1426,13 +1438,18 @@ def connect_terminal(project, name, role=""):
1426
1438
  tty, owning = _capture_tty_for_pid(os.getppid())
1427
1439
  ppid = owning or os.getppid()
1428
1440
 
1429
- # Make sure agent identity exists (idempotent)
1430
- sb_rpc("mc_register_agent", {
1431
- "p_project_id": project_id,
1432
- "p_name": name,
1433
- "p_role": role,
1434
- "p_status": "needs_setup",
1435
- })
1441
+ # Make sure agent identity exists (idempotent).
1442
+ # Use with_api_key variant so scoped guest keys work and the RPC
1443
+ # validates owner/member access (no anon bypass).
1444
+ _api_key = _load_api_key_for_cli()
1445
+ if _api_key:
1446
+ sb_rpc("mc_register_agent_with_api_key", {
1447
+ "p_api_key": _api_key,
1448
+ "p_project_id": project_id,
1449
+ "p_name": name,
1450
+ "p_role": role,
1451
+ "p_status": "needs_setup",
1452
+ })
1436
1453
 
1437
1454
  rpc_result = sb_rpc("mc_connect_agent", {
1438
1455
  "p_project_id": project_id,
@@ -1445,7 +1462,7 @@ def connect_terminal(project, name, role=""):
1445
1462
  return
1446
1463
 
1447
1464
  session_data = {"project": project, "agent": name, "pid": ppid, "tty": tty, "registered_at": now()}
1448
- (SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data))
1465
+ (SESSIONS_DIR / f"{project}_{name}").write_text(json.dumps(session_data), encoding="utf-8")
1449
1466
 
1450
1467
  hb_pid = _start_heartbeat_daemon(project, name)
1451
1468
 
@@ -1503,7 +1520,7 @@ def connect(project, name, hook_target="claude", role=""):
1503
1520
  meta_path = Path.home() / ".meshcode" / "profile_meta.json"
1504
1521
  if meta_path.exists():
1505
1522
  try:
1506
- meta = json.loads(meta_path.read_text())
1523
+ meta = json.loads(meta_path.read_text(encoding="utf-8"))
1507
1524
  print(f"[meshcode] Authenticated as {meta.get('display_name', meta.get('email', '?'))}")
1508
1525
  except Exception:
1509
1526
  pass
@@ -1534,7 +1551,7 @@ def connect(project, name, hook_target="claude", role=""):
1534
1551
  "send_command": f"python3 {comms_path} send {project} {name}:{{to}} '{{message}}'",
1535
1552
  "platform": os_name
1536
1553
  }
1537
- config_path.write_text(json.dumps(config, indent=2))
1554
+ config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
1538
1555
  print(f"[meshcode] Config saved to {config_path}")
1539
1556
  print(f"[meshcode] Codex: use read_command and send_command from the config")
1540
1557
 
@@ -1591,7 +1608,7 @@ def login(api_key):
1591
1608
  config_dir = Path.home() / ".meshcode"
1592
1609
  config_dir.mkdir(exist_ok=True)
1593
1610
  meta_path = config_dir / "profile_meta.json"
1594
- meta_path.write_text(json.dumps(meta, indent=2))
1611
+ meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
1595
1612
  try:
1596
1613
  os.chmod(meta_path, 0o600)
1597
1614
  except Exception:
@@ -1601,7 +1618,7 @@ def login(api_key):
1601
1618
  flag = config_dir / ".migrated_to_keychain"
1602
1619
  if not flag.exists():
1603
1620
  try:
1604
- flag.write_text("login")
1621
+ flag.write_text("login", encoding="utf-8")
1605
1622
  except Exception:
1606
1623
  pass
1607
1624
 
@@ -1656,6 +1673,12 @@ ADMIN:
1656
1673
  unregister <proj> <name> Leave project
1657
1674
  prefs View/set preferences
1658
1675
 
1676
+ PROFILES (multi-account):
1677
+ profiles List stored keychain profiles
1678
+ --profile <name> Use a specific profile (global flag)
1679
+ MESHCODE_PROFILE=<name> Env var equivalent
1680
+ (Inside a workspace dir, profile is auto-detected from .mcp.json)
1681
+
1659
1682
  FEATURES: Auto-status (5 states), Agent Replay, Live Spectator,
1660
1683
  Mesh Graph, Session Recording, Hot-reload
1661
1684
 
@@ -1843,6 +1866,31 @@ if __name__ == "__main__":
1843
1866
  for bf in _bare_present:
1844
1867
  flags[bf] = True
1845
1868
 
1869
+ # Global --profile flag: selects which keychain profile api_key comes from.
1870
+ # Priority: --profile <name> > MESHCODE_PROFILE env > "default".
1871
+ # Sets MESHCODE_KEYCHAIN_PROFILE env so every downstream helper picks it up.
1872
+ _profile_flag = flags.pop("profile", None)
1873
+ if _profile_flag:
1874
+ os.environ["MESHCODE_KEYCHAIN_PROFILE"] = _profile_flag
1875
+ elif os.environ.get("MESHCODE_PROFILE") and not os.environ.get("MESHCODE_KEYCHAIN_PROFILE"):
1876
+ os.environ["MESHCODE_KEYCHAIN_PROFILE"] = os.environ["MESHCODE_PROFILE"]
1877
+ else:
1878
+ # Auto-detect: if cwd is a meshcode workspace (~/meshcode/<proj>-<agent>),
1879
+ # read its .mcp.json to find the profile baked in by setup.
1880
+ try:
1881
+ _cwd = Path.cwd()
1882
+ _mcp_json = _cwd / ".mcp.json"
1883
+ if _mcp_json.exists() and str(_cwd).startswith(str(Path.home() / "meshcode")):
1884
+ _doc = json.loads(_mcp_json.read_text(encoding="utf-8"))
1885
+ _servers = _doc.get("mcpServers", {}) or {}
1886
+ for _sid, _sblk in _servers.items():
1887
+ _p = (_sblk.get("env") or {}).get("MESHCODE_KEYCHAIN_PROFILE")
1888
+ if _p and not os.environ.get("MESHCODE_KEYCHAIN_PROFILE"):
1889
+ os.environ["MESHCODE_KEYCHAIN_PROFILE"] = _p
1890
+ break
1891
+ except Exception:
1892
+ pass
1893
+
1846
1894
  if cmd == "register":
1847
1895
  # Backwards-compat alias for `connect`. Mesh-only model: bind THIS
1848
1896
  # terminal to (project, agent) — never spawns claude.
@@ -1951,7 +1999,7 @@ if __name__ == "__main__":
1951
1999
  any_found = True
1952
2000
  agent = f.name[len(prefix):]
1953
2001
  try:
1954
- d = json.loads(f.read_text())
2002
+ d = json.loads(f.read_text(encoding="utf-8"))
1955
2003
  except Exception as e:
1956
2004
  print(f" [{agent}] CORRUPT ({e})")
1957
2005
  continue
@@ -2165,6 +2213,47 @@ if __name__ == "__main__":
2165
2213
  sys.exit(1)
2166
2214
  login(key)
2167
2215
 
2216
+ elif cmd in ("profiles", "whoami"):
2217
+ # List all stored keychain profiles with metadata
2218
+ try:
2219
+ import importlib as _il
2220
+ _sec = _il.import_module("meshcode.secrets")
2221
+ except Exception as _e:
2222
+ print(f"[meshcode] ERROR: cannot load secrets module: {_e}", file=sys.stderr)
2223
+ sys.exit(1)
2224
+
2225
+ profiles = _sec.list_profiles()
2226
+ active = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
2227
+
2228
+ if not profiles:
2229
+ print("[meshcode] No profiles stored yet.")
2230
+ print("[meshcode] Run `meshcode login <api_key>` to create the default profile,")
2231
+ print("[meshcode] or `meshcode join <invite-token>` to create a scoped profile.")
2232
+ sys.exit(0)
2233
+
2234
+ print()
2235
+ print(f"[meshcode] Stored profiles ({len(profiles)}):")
2236
+ print()
2237
+ for name, meta in sorted(profiles.items()):
2238
+ marker = " *ACTIVE*" if name == active else ""
2239
+ kind = meta.get("type", "default" if name == "default" else "scoped")
2240
+ print(f" {name}{marker}")
2241
+ print(f" type: {kind}")
2242
+ if meta.get("meshwork"):
2243
+ print(f" meshwork: {meta['meshwork']}")
2244
+ if meta.get("agent"):
2245
+ print(f" agent: {meta['agent']}")
2246
+ if meta.get("email"):
2247
+ print(f" email: {meta['email']}")
2248
+ if meta.get("expires_at"):
2249
+ print(f" expires: {meta['expires_at']}")
2250
+ if meta.get("backend"):
2251
+ print(f" backend: {meta['backend']}")
2252
+ print()
2253
+ print("[meshcode] Use `--profile <name>` or `MESHCODE_PROFILE=<name>` to switch.")
2254
+ print("[meshcode] In a meshcode workspace dir, the profile is auto-detected from .mcp.json.")
2255
+ sys.exit(0)
2256
+
2168
2257
  elif cmd == "prefs":
2169
2258
  # meshcode prefs permission-mode [bypass|safe|ask] (no arg = show)
2170
2259
  # meshcode prefs auto-update [on|off|reset] (no arg = show)
@@ -79,10 +79,17 @@ def _rpc(name: str, body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
79
79
 
80
80
 
81
81
  def _get_default_api_key() -> Optional[str]:
82
+ """Read the api key for the active profile.
83
+
84
+ Profile resolution: MESHCODE_KEYCHAIN_PROFILE env > "default".
85
+ The CLI dispatcher sets MESHCODE_KEYCHAIN_PROFILE from --profile flag,
86
+ MESHCODE_PROFILE env, or workspace auto-detection.
87
+ """
82
88
  try:
83
89
  import importlib
84
90
  secrets_mod = importlib.import_module("meshcode.secrets")
85
- return secrets_mod.get_api_key(profile="default")
91
+ profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
92
+ return secrets_mod.get_api_key(profile=profile)
86
93
  except Exception:
87
94
  return None
88
95
 
@@ -67,7 +67,7 @@ def load_env() -> tuple[str, str]:
67
67
  key = os.environ.get("SUPABASE_KEY", "").strip()
68
68
  if not (url and key) and ENV_FILE.exists():
69
69
  try:
70
- for raw in ENV_FILE.read_text().splitlines():
70
+ for raw in ENV_FILE.read_text(encoding="utf-8").splitlines():
71
71
  line = raw.strip()
72
72
  if not line or line.startswith("#"):
73
73
  continue
@@ -103,7 +103,7 @@ def write_pidfile() -> None:
103
103
  "uptime_seconds": int(time.time() - _start_time),
104
104
  }
105
105
  try:
106
- PID_FILE.write_text(json.dumps(payload, indent=2))
106
+ PID_FILE.write_text(json.dumps(payload, indent=2), encoding="utf-8")
107
107
  except Exception as e:
108
108
  log(f"pid-file write failed: {e}", err=True)
109
109
 
@@ -885,8 +885,18 @@ def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
885
885
 
886
886
  @mcp.tool()
887
887
  @with_working_status
888
- def meshcode_broadcast(payload: Dict[str, Any]) -> Dict[str, Any]:
889
- """Broadcast to ALL agents in this meshwork (except yourself)."""
888
+ def meshcode_broadcast(payload: Any) -> Dict[str, Any]:
889
+ """Broadcast to ALL agents in this meshwork (except yourself).
890
+
891
+ Accepts payload as either:
892
+ - dict (structured): {"type": "...", "detail": "..."}
893
+ - str (shorthand): wrapped internally as {"text": "..."}
894
+ """
895
+ if isinstance(payload, str):
896
+ payload = {"text": payload}
897
+ elif not isinstance(payload, dict):
898
+ return {"error": "payload must be a string or object", "got_type": type(payload).__name__}
899
+
890
900
  agents = be.get_board(_PROJECT_ID)
891
901
  sent = 0
892
902
  for a in agents:
@@ -44,7 +44,9 @@ def _try_auto_setup(agent: str, project: Optional[str] = None) -> Optional[Tuple
44
44
  except Exception:
45
45
  return None
46
46
 
47
- api_key = secrets_mod.get_api_key(profile="default")
47
+ import os as _os
48
+ profile = _os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or "default"
49
+ api_key = secrets_mod.get_api_key(profile=profile)
48
50
  if not api_key:
49
51
  return None
50
52
 
@@ -144,7 +144,7 @@ def consume_pending_result() -> None:
144
144
  if not RESULT_PATH.exists():
145
145
  return
146
146
  try:
147
- data = json.loads(RESULT_PATH.read_text())
147
+ data = json.loads(RESULT_PATH.read_text(encoding="utf-8"))
148
148
  RESULT_PATH.unlink(missing_ok=True)
149
149
  except Exception:
150
150
  return
@@ -188,7 +188,7 @@ def _acquire_lock() -> bool:
188
188
  return False
189
189
  LOCK_PATH.unlink(missing_ok=True)
190
190
  LOCK_PATH.parent.mkdir(parents=True, exist_ok=True)
191
- LOCK_PATH.write_text(str(os.getpid()))
191
+ LOCK_PATH.write_text(str(os.getpid()), encoding="utf-8")
192
192
  return True
193
193
  except Exception:
194
194
  return False
@@ -233,7 +233,7 @@ except Exception as e:
233
233
  finally:
234
234
  result["finished_at"] = int(time.time())
235
235
  try:
236
- result_path.write_text(json.dumps(result))
236
+ result_path.write_text(json.dumps(result), encoding="utf-8")
237
237
  except Exception:
238
238
  pass
239
239
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.4.1
3
+ Version: 2.4.2
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.4.1"
7
+ version = "2.4.2"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes
File without changes