meshcode 1.8.3__tar.gz → 1.8.4__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 (29) hide show
  1. {meshcode-1.8.3 → meshcode-1.8.4}/PKG-INFO +1 -1
  2. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/__init__.py +1 -1
  3. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/comms_v4.py +25 -1
  4. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/preferences.py +75 -0
  5. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/run_agent.py +7 -0
  6. meshcode-1.8.4/meshcode/self_update.py +345 -0
  7. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode.egg-info/PKG-INFO +1 -1
  8. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode.egg-info/SOURCES.txt +1 -0
  9. {meshcode-1.8.3 → meshcode-1.8.4}/pyproject.toml +1 -1
  10. {meshcode-1.8.3 → meshcode-1.8.4}/README.md +0 -0
  11. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/cli.py +0 -0
  12. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/invites.py +0 -0
  13. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/launcher.py +0 -0
  14. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/launcher_install.py +0 -0
  15. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/meshcode_mcp/__init__.py +0 -0
  16. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/meshcode_mcp/__main__.py +0 -0
  17. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/meshcode_mcp/backend.py +0 -0
  18. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/meshcode_mcp/realtime.py +0 -0
  19. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/meshcode_mcp/server.py +0 -0
  20. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/meshcode_mcp/test_backend.py +0 -0
  21. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  22. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/protocol_v2.py +0 -0
  23. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/secrets.py +0 -0
  24. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode/setup_clients.py +0 -0
  25. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode.egg-info/dependency_links.txt +0 -0
  26. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode.egg-info/entry_points.txt +0 -0
  27. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode.egg-info/requires.txt +0 -0
  28. {meshcode-1.8.3 → meshcode-1.8.4}/meshcode.egg-info/top_level.txt +0 -0
  29. {meshcode-1.8.3 → meshcode-1.8.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 1.8.3
3
+ Version: 1.8.4
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__ = "1.8.3"
2
+ __version__ = "1.8.4"
@@ -2024,13 +2024,37 @@ if __name__ == "__main__":
2024
2024
 
2025
2025
  elif cmd == "prefs":
2026
2026
  # meshcode prefs permission-mode [bypass|safe|ask] (no arg = show)
2027
+ # meshcode prefs auto-update [on|off|reset] (no arg = show)
2027
2028
  # meshcode prefs reset
2028
2029
  from meshcode.preferences import (
2029
2030
  get_permission_mode, set_permission_mode, reset_permission_mode,
2031
+ get_auto_update, set_auto_update, reset_auto_update,
2030
2032
  VALID_PERMISSION_MODES,
2031
2033
  )
2032
2034
  sub = sys.argv[2] if len(sys.argv) > 2 else ""
2033
- if sub == "permission-mode":
2035
+ if sub == "auto-update":
2036
+ if len(sys.argv) > 3:
2037
+ arg = sys.argv[3].lower()
2038
+ if arg in ("on", "yes", "y", "true", "1"):
2039
+ ok = set_auto_update(True)
2040
+ print("[meshcode] auto_update = ON" if ok else "[ERROR] failed to save")
2041
+ elif arg in ("off", "no", "n", "false", "0"):
2042
+ ok = set_auto_update(False)
2043
+ print("[meshcode] auto_update = OFF" if ok else "[ERROR] failed to save")
2044
+ elif arg == "reset":
2045
+ reset_auto_update()
2046
+ print("[meshcode] auto_update preference cleared")
2047
+ ok = True
2048
+ else:
2049
+ print("Usage: meshcode prefs auto-update [on|off|reset]")
2050
+ sys.exit(1)
2051
+ sys.exit(0 if ok else 1)
2052
+ else:
2053
+ cur = get_auto_update()
2054
+ label = "(unset — will prompt on next run)" if cur is None else ("ON" if cur else "OFF")
2055
+ print(f"auto_update: {label}")
2056
+ sys.exit(0)
2057
+ elif sub == "permission-mode":
2034
2058
  if len(sys.argv) > 3:
2035
2059
  mode = sys.argv[3].lower()
2036
2060
  if mode not in VALID_PERMISSION_MODES:
@@ -30,6 +30,9 @@ PREFS_PATH = Path.home() / ".meshcode" / "preferences.json"
30
30
  VALID_PERMISSION_MODES = {"bypass", "safe", "ask"}
31
31
  DEFAULT_PERMISSION_MODE_FOR_NON_TTY = "bypass"
32
32
 
33
+ # auto_update: True | False | None (unset → prompt on first interactive run)
34
+ DEFAULT_AUTO_UPDATE_FOR_NON_TTY = False
35
+
33
36
 
34
37
  def load_prefs() -> Dict[str, Any]:
35
38
  """Load the prefs file. Returns {} if missing or unparseable."""
@@ -169,3 +172,75 @@ def prompt_permission_mode_quick() -> str:
169
172
  if ans in ("s", "safe"):
170
173
  return "safe"
171
174
  return "bypass"
175
+
176
+
177
+ # ============================================================
178
+ # Auto-update preferences
179
+ # ============================================================
180
+
181
+ def get_auto_update() -> Optional[bool]:
182
+ """Returns True / False / None (unset)."""
183
+ val = load_prefs().get("auto_update")
184
+ if isinstance(val, bool):
185
+ return val
186
+ return None
187
+
188
+
189
+ def set_auto_update(enabled: bool) -> bool:
190
+ prefs = load_prefs()
191
+ prefs["auto_update"] = bool(enabled)
192
+ prefs["auto_update_set_at"] = int(time.time())
193
+ return save_prefs(prefs)
194
+
195
+
196
+ def reset_auto_update() -> bool:
197
+ prefs = load_prefs()
198
+ prefs.pop("auto_update", None)
199
+ prefs.pop("auto_update_set_at", None)
200
+ return save_prefs(prefs)
201
+
202
+
203
+ def prompt_auto_update() -> bool:
204
+ """Interactive first-run prompt. Returns the chosen value and saves it.
205
+
206
+ Non-TTY: silently picks DEFAULT_AUTO_UPDATE_FOR_NON_TTY (False) and saves.
207
+ """
208
+ if not sys.stdin.isatty():
209
+ set_auto_update(DEFAULT_AUTO_UPDATE_FOR_NON_TTY)
210
+ return DEFAULT_AUTO_UPDATE_FOR_NON_TTY
211
+
212
+ print("", file=sys.stderr)
213
+ print("[meshcode] AUTO-UPDATE — meshcode is iterating fast right now.", file=sys.stderr)
214
+ print("[meshcode]", file=sys.stderr)
215
+ print("[meshcode] When a new version is published to PyPI, meshcode can", file=sys.stderr)
216
+ print("[meshcode] pull it in the background (no interruption to your", file=sys.stderr)
217
+ print("[meshcode] current launch — the next launch picks it up).", file=sys.stderr)
218
+ print("[meshcode]", file=sys.stderr)
219
+ print("[meshcode] [Y] Yes (recommended) — silent background pip install -U", file=sys.stderr)
220
+ print("[meshcode] [n] No — I'll run pip install -U meshcode myself", file=sys.stderr)
221
+ print("[meshcode]", file=sys.stderr)
222
+ try:
223
+ ans = input("[meshcode] Pick [Y/n] (default Y): ").strip().lower()
224
+ except (EOFError, KeyboardInterrupt):
225
+ ans = ""
226
+
227
+ chosen = False if ans in ("n", "no") else True
228
+ set_auto_update(chosen)
229
+ label = "ON" if chosen else "OFF"
230
+ print(f"[meshcode] ✓ Auto-update {label}. Change later with: meshcode prefs auto-update <on|off>", file=sys.stderr)
231
+ print("", file=sys.stderr)
232
+ return chosen
233
+
234
+
235
+ def resolve_auto_update() -> bool:
236
+ """Returns True if auto-update should run for this launch.
237
+
238
+ Order:
239
+ 1. saved preference
240
+ 2. interactive prompt + save (TTY only)
241
+ 3. fallback False for non-TTY
242
+ """
243
+ saved = get_auto_update()
244
+ if saved is not None:
245
+ return saved
246
+ return prompt_auto_update()
@@ -26,6 +26,7 @@ from pathlib import Path
26
26
  from typing import Optional, Tuple
27
27
 
28
28
  from .preferences import resolve_permission_mode
29
+ from . import self_update
29
30
 
30
31
  WORKSPACES_ROOT = Path.home() / "meshcode"
31
32
  REGISTRY_PATH = WORKSPACES_ROOT / ".registry.json"
@@ -101,6 +102,12 @@ def _detect_editor() -> Optional[str]:
101
102
 
102
103
  def run(agent: str, project: Optional[str] = None, editor_override: Optional[str] = None, permission_override: Optional[str] = None) -> int:
103
104
  """Launch the user's editor with ONLY the named agent's MCP server loaded."""
105
+ # Non-blocking self-update check (consumes prior result, may spawn bg pip)
106
+ try:
107
+ self_update.check_and_maybe_update()
108
+ except Exception:
109
+ pass
110
+
104
111
  found = _find_agent_workspace(agent, project)
105
112
  if not found:
106
113
  return 2
@@ -0,0 +1,345 @@
1
+ """Background self-update for the meshcode CLI.
2
+
3
+ Design goals:
4
+ - ZERO added latency to `meshcode run`. The PyPI check + pip install
5
+ runs in a fully detached background subprocess. The current launch
6
+ is never blocked.
7
+ - Opt-in (first-run prompt, like permission_mode). Default ON for
8
+ interactive TTY users, default OFF for CI / non-TTY.
9
+ - Cache the PyPI check for 1 hour so we don't hit the network on
10
+ every launch.
11
+ - Two-launch model: launch N detects + downloads in the background;
12
+ launch N+1 reads the result file, prints "[meshcode] updated X → Y",
13
+ and the new code is already on disk.
14
+ - Safe in all the edge cases that bite Python CLI updaters:
15
+ * editable installs (dev mode) → skip
16
+ * pipx installs → use `pipx upgrade meshcode`
17
+ * MCP server subprocess → skip (we're inside an agent runtime)
18
+ * --no-update flag, MESHCODE_NO_UPDATE=1 env → skip
19
+ * offline / network error → silent skip
20
+ * concurrent runs → file lock so only one bg update at a time
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import os
26
+ import subprocess
27
+ import sys
28
+ import time
29
+ from pathlib import Path
30
+ from typing import Optional, Tuple
31
+
32
+ PKG_NAME = "meshcode"
33
+ PYPI_URL = f"https://pypi.org/pypi/{PKG_NAME}/json"
34
+ CACHE_TTL_SEC = 3600 # 1 hour
35
+ NETWORK_TIMEOUT_SEC = 1.5
36
+
37
+ STATE_DIR = Path.home() / ".meshcode"
38
+ RESULT_PATH = STATE_DIR / ".update_result.json"
39
+ LOCK_PATH = STATE_DIR / ".update.lock"
40
+ LOG_PATH = STATE_DIR / "update.log"
41
+ LOCK_STALE_SEC = 600 # 10 min — if a lock is older, treat as crashed
42
+
43
+
44
+ # ============================================================
45
+ # Detection helpers — when NOT to auto-update
46
+ # ============================================================
47
+
48
+ def _current_version() -> str:
49
+ try:
50
+ from . import __version__
51
+ return __version__
52
+ except Exception:
53
+ return "0.0.0"
54
+
55
+
56
+ def is_editable_install() -> bool:
57
+ """True if meshcode is installed via `pip install -e .` (dev mode)."""
58
+ try:
59
+ import meshcode
60
+ path = os.path.realpath(meshcode.__file__)
61
+ return "site-packages" not in path and "dist-packages" not in path
62
+ except Exception:
63
+ return False
64
+
65
+
66
+ def is_inside_mcp_serve() -> bool:
67
+ """True if we're running inside the MCP subprocess of an agent client."""
68
+ if os.environ.get("MESHCODE_MCP_SERVE") == "1":
69
+ return True
70
+ argv0 = " ".join(sys.argv).lower()
71
+ return "meshcode_mcp" in argv0 or "meshcode.meshcode_mcp" in argv0
72
+
73
+
74
+ def is_pipx_install() -> bool:
75
+ """True if installed via pipx (we should use `pipx upgrade` instead)."""
76
+ exe = os.path.realpath(sys.executable)
77
+ return "/pipx/" in exe or "\\pipx\\" in exe
78
+
79
+
80
+ def update_disabled() -> bool:
81
+ """User explicitly opted out for this run / globally."""
82
+ if os.environ.get("MESHCODE_NO_UPDATE") == "1":
83
+ return True
84
+ if "--no-update" in sys.argv:
85
+ return True
86
+ return False
87
+
88
+
89
+ def _version_tuple(v: str) -> Tuple[int, ...]:
90
+ parts = []
91
+ for p in v.split("."):
92
+ digits = "".join(c for c in p if c.isdigit())
93
+ parts.append(int(digits) if digits else 0)
94
+ return tuple(parts)
95
+
96
+
97
+ def _is_newer(remote: str, local: str) -> bool:
98
+ try:
99
+ return _version_tuple(remote) > _version_tuple(local)
100
+ except Exception:
101
+ return False
102
+
103
+
104
+ # ============================================================
105
+ # Cache + result file (consumed by next launch)
106
+ # ============================================================
107
+
108
+ def _read_prefs() -> dict:
109
+ try:
110
+ from .preferences import load_prefs
111
+ return load_prefs()
112
+ except Exception:
113
+ return {}
114
+
115
+
116
+ def _write_prefs_kv(**kv) -> None:
117
+ try:
118
+ from .preferences import load_prefs, save_prefs
119
+ prefs = load_prefs()
120
+ prefs.update(kv)
121
+ save_prefs(prefs)
122
+ except Exception:
123
+ pass
124
+
125
+
126
+ def _cache_fresh() -> bool:
127
+ last = _read_prefs().get("last_update_check_at", 0)
128
+ return (time.time() - last) < CACHE_TTL_SEC
129
+
130
+
131
+ def _mark_checked(latest: Optional[str]) -> None:
132
+ _write_prefs_kv(
133
+ last_update_check_at=int(time.time()),
134
+ last_known_latest_version=latest or _current_version(),
135
+ )
136
+
137
+
138
+ def consume_pending_result() -> None:
139
+ """Read + delete .update_result.json, print outcome to stderr.
140
+
141
+ Called at the START of each `meshcode run` to surface what the
142
+ previous launch's background updater did.
143
+ """
144
+ if not RESULT_PATH.exists():
145
+ return
146
+ try:
147
+ data = json.loads(RESULT_PATH.read_text())
148
+ RESULT_PATH.unlink(missing_ok=True)
149
+ except Exception:
150
+ return
151
+ cur = _current_version()
152
+ new_v = data.get("version") or "?"
153
+ if data.get("ok"):
154
+ if _is_newer(new_v, cur):
155
+ print(f"[meshcode] updated {cur} → {new_v}. Restart your editor to load the new version.", file=sys.stderr)
156
+ else:
157
+ # update ran but pip didn't actually upgrade (already at latest)
158
+ pass
159
+ else:
160
+ err = data.get("error", "unknown error")
161
+ print(f"[meshcode] WARN: last auto-update failed: {err}", file=sys.stderr)
162
+
163
+
164
+ # ============================================================
165
+ # Network — PyPI version probe
166
+ # ============================================================
167
+
168
+ def fetch_latest_version(timeout: float = NETWORK_TIMEOUT_SEC) -> Optional[str]:
169
+ try:
170
+ import urllib.request
171
+ req = urllib.request.Request(PYPI_URL, headers={"User-Agent": f"meshcode/{_current_version()}"})
172
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
173
+ data = json.loads(resp.read().decode("utf-8"))
174
+ return data.get("info", {}).get("version")
175
+ except Exception:
176
+ return None
177
+
178
+
179
+ # ============================================================
180
+ # File lock — prevent concurrent bg updates
181
+ # ============================================================
182
+
183
+ def _acquire_lock() -> bool:
184
+ try:
185
+ if LOCK_PATH.exists():
186
+ age = time.time() - LOCK_PATH.stat().st_mtime
187
+ if age < LOCK_STALE_SEC:
188
+ return False
189
+ LOCK_PATH.unlink(missing_ok=True)
190
+ LOCK_PATH.parent.mkdir(parents=True, exist_ok=True)
191
+ LOCK_PATH.write_text(str(os.getpid()))
192
+ return True
193
+ except Exception:
194
+ return False
195
+
196
+
197
+ # ============================================================
198
+ # Background update spawn
199
+ # ============================================================
200
+
201
+ _BG_RUNNER_PY = r'''
202
+ import json, os, subprocess, sys, time
203
+ from pathlib import Path
204
+
205
+ result = {"version": None, "ok": False, "error": None, "finished_at": None}
206
+ state_dir = Path.home() / ".meshcode"
207
+ result_path = state_dir / ".update_result.json"
208
+ lock_path = state_dir / ".update.lock"
209
+ log_path = state_dir / "update.log"
210
+ state_dir.mkdir(parents=True, exist_ok=True)
211
+
212
+ mode = sys.argv[1] if len(sys.argv) > 1 else "pip"
213
+ target_version = sys.argv[2] if len(sys.argv) > 2 else None
214
+
215
+ try:
216
+ if mode == "pipx":
217
+ cmd = ["pipx", "upgrade", "meshcode"]
218
+ else:
219
+ cmd = [sys.executable, "-m", "pip", "install", "-U", "--disable-pip-version-check", "--quiet", "meshcode"]
220
+ with open(log_path, "ab") as logf:
221
+ logf.write(f"\n=== {time.strftime('%Y-%m-%d %H:%M:%S')} bg update via {mode} ===\n".encode())
222
+ logf.flush()
223
+ proc = subprocess.run(cmd, stdout=logf, stderr=logf, timeout=180)
224
+ if proc.returncode == 0:
225
+ result["ok"] = True
226
+ result["version"] = target_version
227
+ else:
228
+ result["error"] = f"pip exit {proc.returncode}"
229
+ except subprocess.TimeoutExpired:
230
+ result["error"] = "pip install timed out after 180s"
231
+ except Exception as e:
232
+ result["error"] = str(e)
233
+ finally:
234
+ result["finished_at"] = int(time.time())
235
+ try:
236
+ result_path.write_text(json.dumps(result))
237
+ except Exception:
238
+ pass
239
+ try:
240
+ lock_path.unlink()
241
+ except Exception:
242
+ pass
243
+ '''
244
+
245
+
246
+ def _spawn_background_updater(target_version: str) -> bool:
247
+ """Spawn a fully detached subprocess that runs the updater.
248
+
249
+ The parent (current `meshcode run`) returns immediately. The child
250
+ runs pip install in the background, writes the result to disk, and
251
+ exits. Next `meshcode run` consumes the result.
252
+ """
253
+ if not _acquire_lock():
254
+ return False
255
+
256
+ mode = "pipx" if is_pipx_install() else "pip"
257
+
258
+ # We pass the runner code via stdin so we don't need to ship a
259
+ # second .py file. The child reads it from sys.stdin and execs it.
260
+ runner = f"import sys; exec(sys.stdin.read())"
261
+ args = [sys.executable, "-c", runner, mode, target_version]
262
+
263
+ try:
264
+ if sys.platform == "win32":
265
+ DETACHED_PROCESS = 0x00000008
266
+ CREATE_NEW_PROCESS_GROUP = 0x00000200
267
+ CREATE_NO_WINDOW = 0x08000000
268
+ flags = DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
269
+ proc = subprocess.Popen(
270
+ args,
271
+ stdin=subprocess.PIPE,
272
+ stdout=subprocess.DEVNULL,
273
+ stderr=subprocess.DEVNULL,
274
+ creationflags=flags,
275
+ close_fds=True,
276
+ )
277
+ else:
278
+ proc = subprocess.Popen(
279
+ args,
280
+ stdin=subprocess.PIPE,
281
+ stdout=subprocess.DEVNULL,
282
+ stderr=subprocess.DEVNULL,
283
+ start_new_session=True,
284
+ close_fds=True,
285
+ )
286
+ if proc.stdin:
287
+ proc.stdin.write(_BG_RUNNER_PY.encode("utf-8"))
288
+ proc.stdin.close()
289
+ return True
290
+ except Exception:
291
+ try:
292
+ LOCK_PATH.unlink(missing_ok=True)
293
+ except Exception:
294
+ pass
295
+ return False
296
+
297
+
298
+ # ============================================================
299
+ # Public entrypoint — called from meshcode run
300
+ # ============================================================
301
+
302
+ def check_and_maybe_update(verbose: bool = False) -> None:
303
+ """Non-blocking. Call at the start of `meshcode run`.
304
+
305
+ Order of checks (any failure → silent return, never raise):
306
+ 1. consume any pending result from previous launch
307
+ 2. opt-out gates: --no-update, MESHCODE_NO_UPDATE=1
308
+ 3. context gates: editable install, MCP subprocess
309
+ 4. user preference: resolve_auto_update() (may prompt first time)
310
+ 5. cache TTL: skip if checked < 1h ago
311
+ 6. PyPI fetch (1.5s timeout)
312
+ 7. version compare; if newer → spawn background updater
313
+ """
314
+ try:
315
+ consume_pending_result()
316
+ except Exception:
317
+ pass
318
+
319
+ if update_disabled():
320
+ return
321
+ if is_inside_mcp_serve():
322
+ return
323
+ if is_editable_install():
324
+ return
325
+
326
+ try:
327
+ from .preferences import resolve_auto_update
328
+ if not resolve_auto_update():
329
+ return
330
+ except Exception:
331
+ return
332
+
333
+ if _cache_fresh():
334
+ return
335
+
336
+ latest = fetch_latest_version()
337
+ _mark_checked(latest)
338
+ if not latest:
339
+ return
340
+ if not _is_newer(latest, _current_version()):
341
+ return
342
+
343
+ started = _spawn_background_updater(latest)
344
+ if started and verbose:
345
+ print(f"[meshcode] downloading {latest} in background...", file=sys.stderr)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 1.8.3
3
+ Version: 1.8.4
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -10,6 +10,7 @@ meshcode/preferences.py
10
10
  meshcode/protocol_v2.py
11
11
  meshcode/run_agent.py
12
12
  meshcode/secrets.py
13
+ meshcode/self_update.py
13
14
  meshcode/setup_clients.py
14
15
  meshcode.egg-info/PKG-INFO
15
16
  meshcode.egg-info/SOURCES.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "1.8.3"
7
+ version = "1.8.4"
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
File without changes
File without changes