meshcode 2.10.55__tar.gz → 2.10.57__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 (41) hide show
  1. {meshcode-2.10.55 → meshcode-2.10.57}/PKG-INFO +1 -1
  2. meshcode-2.10.57/meshcode/__init__.py +82 -0
  3. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/comms_v4.py +338 -19
  4. meshcode-2.10.57/meshcode/compat.py +174 -0
  5. meshcode-2.10.57/meshcode/exceptions.py +52 -0
  6. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/backend.py +52 -0
  7. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/realtime.py +31 -7
  8. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/server.py +101 -14
  9. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/setup_clients.py +52 -23
  10. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode.egg-info/PKG-INFO +1 -1
  11. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode.egg-info/SOURCES.txt +8 -0
  12. {meshcode-2.10.55 → meshcode-2.10.57}/pyproject.toml +1 -1
  13. meshcode-2.10.57/tests/test_core.py +216 -0
  14. meshcode-2.10.57/tests/test_cross_agent_messaging.py +366 -0
  15. meshcode-2.10.57/tests/test_esc_deaf_state.py +361 -0
  16. meshcode-2.10.57/tests/test_exceptions.py +107 -0
  17. meshcode-2.10.57/tests/test_realtime_event_freshness.py +236 -0
  18. meshcode-2.10.57/tests/test_rpc_migrations.py +385 -0
  19. {meshcode-2.10.55 → meshcode-2.10.57}/tests/test_status_enum_coverage.py +32 -0
  20. meshcode-2.10.55/meshcode/__init__.py +0 -2
  21. {meshcode-2.10.55 → meshcode-2.10.57}/README.md +0 -0
  22. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/ascii_art.py +0 -0
  23. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/cli.py +0 -0
  24. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/invites.py +0 -0
  25. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/launcher.py +0 -0
  26. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/launcher_install.py +0 -0
  27. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/__init__.py +0 -0
  28. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/__main__.py +0 -0
  29. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_backend.py +0 -0
  30. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  31. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  32. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/preferences.py +0 -0
  33. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/protocol_v2.py +0 -0
  34. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/run_agent.py +0 -0
  35. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/secrets.py +0 -0
  36. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode/self_update.py +0 -0
  37. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode.egg-info/dependency_links.txt +0 -0
  38. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode.egg-info/entry_points.txt +0 -0
  39. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode.egg-info/requires.txt +0 -0
  40. {meshcode-2.10.55 → meshcode-2.10.57}/meshcode.egg-info/top_level.txt +0 -0
  41. {meshcode-2.10.55 → meshcode-2.10.57}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.55
3
+ Version: 2.10.57
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -0,0 +1,82 @@
1
+ """MeshCode — Real-time communication between AI agents."""
2
+ __version__ = "2.10.57"
3
+
4
+ # Exception hierarchy — eagerly imported (lightweight, no deps)
5
+ from meshcode.exceptions import ( # noqa: F401
6
+ MeshCodeError,
7
+ AuthError,
8
+ RPCError,
9
+ MeshCodeTimeoutError,
10
+ MeshCodeConnectionError,
11
+ )
12
+
13
+ # Public API — lazy imports to avoid heavy deps at import time
14
+ def __getattr__(name):
15
+ if name == "backend":
16
+ from meshcode.meshcode_mcp import backend
17
+ return backend
18
+ if name in _BACKEND_EXPORTS:
19
+ from meshcode.meshcode_mcp import backend
20
+ return getattr(backend, name)
21
+ if name in _SECRETS_EXPORTS:
22
+ from meshcode import secrets
23
+ return getattr(secrets, name)
24
+ raise AttributeError(f"module 'meshcode' has no attribute {name!r}")
25
+
26
+
27
+ # Backend: core messaging & agent management
28
+ _BACKEND_EXPORTS = {
29
+ "send_message",
30
+ "read_inbox",
31
+ "count_pending",
32
+ "get_board",
33
+ "heartbeat",
34
+ "set_status",
35
+ "register_agent",
36
+ "get_project_id",
37
+ "sb_rpc",
38
+ "task_create",
39
+ "task_list",
40
+ "encrypt_payload",
41
+ "decrypt_payload",
42
+ }
43
+
44
+ # Secrets: credential management
45
+ _SECRETS_EXPORTS = {
46
+ "get_api_key",
47
+ "set_api_key",
48
+ "list_profiles",
49
+ }
50
+
51
+ __all__ = [
52
+ "__version__",
53
+ "backend",
54
+ # Exceptions
55
+ "MeshCodeError",
56
+ "AuthError",
57
+ "RPCError",
58
+ "MeshCodeTimeoutError",
59
+ "MeshCodeConnectionError",
60
+ # Messaging
61
+ "send_message",
62
+ "read_inbox",
63
+ "count_pending",
64
+ # Agent management
65
+ "register_agent",
66
+ "get_project_id",
67
+ "get_board",
68
+ "heartbeat",
69
+ "set_status",
70
+ # Tasks
71
+ "task_create",
72
+ "task_list",
73
+ # Low-level
74
+ "sb_rpc",
75
+ # Encryption
76
+ "encrypt_payload",
77
+ "decrypt_payload",
78
+ # Credentials
79
+ "get_api_key",
80
+ "set_api_key",
81
+ "list_profiles",
82
+ ]
@@ -166,7 +166,13 @@ def sb_delete(table, filters):
166
166
 
167
167
 
168
168
  def sb_rpc(fn_name, params):
169
- """Call a Supabase RPC function."""
169
+ """Call a Supabase RPC function.
170
+
171
+ Returns the parsed JSON response on success, or None on network/HTTP error.
172
+ HTTP errors are logged and printed to stderr for observability.
173
+ RPC-level errors (e.g. {"error": "..."}) are returned as-is — callers
174
+ must check for result.get("error") themselves.
175
+ """
170
176
  url = f"{SUPABASE_URL}/rest/v1/rpc/{fn_name}"
171
177
  body = json.dumps(params).encode()
172
178
  req = Request(url, data=body, method="POST", headers=_headers(content_profile=False))
@@ -174,8 +180,20 @@ def sb_rpc(fn_name, params):
174
180
  with urlopen(req, timeout=10) as resp:
175
181
  raw = resp.read().decode()
176
182
  return json.loads(raw) if raw.strip() else None
177
- except (HTTPError, URLError) as e:
178
- log_error(f"rpc:{fn_name}", str(e), json.dumps(params, default=str)[:200])
183
+ except HTTPError as e:
184
+ err_body = ""
185
+ try:
186
+ err_body = e.read().decode()
187
+ err_obj = json.loads(err_body)
188
+ msg = err_obj.get("message", err_body[:200])
189
+ except Exception:
190
+ msg = err_body[:200] if err_body else str(e)
191
+ print(f"[ERROR] rpc:{fn_name}: {e.code} {msg}", file=sys.stderr)
192
+ log_error(f"rpc:{fn_name}", f"{e.code} {msg}", json.dumps(params, default=str)[:200])
193
+ return None
194
+ except URLError as e:
195
+ print(f"[ERROR] rpc:{fn_name}: network error: {e.reason}", file=sys.stderr)
196
+ log_error(f"rpc:{fn_name}", f"network: {e.reason}", json.dumps(params, default=str)[:200])
179
197
  return None
180
198
 
181
199
 
@@ -1376,7 +1394,7 @@ def watch(project, name, interval=10, timeout=0):
1376
1394
  print(f"\n*** [{project.upper()}] MENSAJE RECIBIDO ({elapsed}s en standby) ***")
1377
1395
  read_messages(project, name, silent=False, send_acks=False)
1378
1396
 
1379
- _update_agent_status({"status": "working", "task": "Procesando mensaje recibido", "last_heartbeat": now_iso()})
1397
+ _update_agent_status({"status": "working", "task": "Processing received message", "last_heartbeat": now_iso()})
1380
1398
 
1381
1399
  # Reset to standby and keep polling (don't exit the loop)
1382
1400
  time.sleep(2)
@@ -1430,7 +1448,7 @@ def show_board(project):
1430
1448
  project_id = get_project_id(project)
1431
1449
  if not project_id:
1432
1450
  print(f"[{project}] Project not found")
1433
- return
1451
+ sys.exit(1)
1434
1452
 
1435
1453
  _ak = _load_api_key_for_cli()
1436
1454
  agents = None
@@ -1442,7 +1460,7 @@ def show_board(project):
1442
1460
  if agents is None:
1443
1461
  agents = sb_select("mc_agents", f"project_id=eq.{project_id}", order="registered_at.asc")
1444
1462
  if not agents:
1445
- print(f"[{project}] Sin agentes")
1463
+ print(f"[{project}] No agents found")
1446
1464
  return
1447
1465
 
1448
1466
  print(f"\n{'='*60}")
@@ -1502,8 +1520,8 @@ def show_status(project=None):
1502
1520
  def show_history(project, last_n=20, between=None):
1503
1521
  project_id = get_project_id(project)
1504
1522
  if not project_id:
1505
- print(f"[{project}] Sin historial")
1506
- return
1523
+ print(f"[{project}] No history")
1524
+ sys.exit(1)
1507
1525
 
1508
1526
  filters = f"project_id=eq.{project_id}&type=neq.ack"
1509
1527
  if between:
@@ -1515,7 +1533,7 @@ def show_history(project, last_n=20, between=None):
1515
1533
 
1516
1534
  messages = sb_select("mc_messages", filters, order="created_at.desc", limit=last_n)
1517
1535
  if not messages:
1518
- print(f"[{project}] Sin historial")
1536
+ print(f"[{project}] No history")
1519
1537
  return
1520
1538
 
1521
1539
  messages.reverse()
@@ -1523,7 +1541,7 @@ def show_history(project, last_n=20, between=None):
1523
1541
  print(f"\n{'='*60}")
1524
1542
  print(f" {project.upper()} — History ({len(messages)} messages)")
1525
1543
  if between:
1526
- print(f" Filtro: {between}")
1544
+ print(f" Filter: {between}")
1527
1545
  print(f"{'='*60}\n")
1528
1546
 
1529
1547
  for msg in messages:
@@ -1917,6 +1935,222 @@ def connect(project, name, hook_target="claude", role=""):
1917
1935
  print()
1918
1936
 
1919
1937
 
1938
+ def cmd_doctor(flags, pos):
1939
+ """Diagnose common meshcode setup issues."""
1940
+ import subprocess as _sp
1941
+ import shutil
1942
+
1943
+ auto_fix = "--fix" in sys.argv or flags.get("fix")
1944
+ results = [] # (status, label, detail) status: ok/warn/fail
1945
+
1946
+ def ok(label, detail=""):
1947
+ results.append(("ok", label, detail))
1948
+
1949
+ def warn(label, detail=""):
1950
+ results.append(("warn", label, detail))
1951
+
1952
+ def fail(label, detail=""):
1953
+ results.append(("fail", label, detail))
1954
+
1955
+ # 1. Python version
1956
+ py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
1957
+ if sys.version_info >= (3, 9):
1958
+ ok("Python version", py_ver)
1959
+ elif sys.version_info >= (3, 7):
1960
+ warn("Python version", f"{py_ver} (3.9+ recommended)")
1961
+ else:
1962
+ fail("Python version", f"{py_ver} (3.9+ required)")
1963
+
1964
+ # 2. meshcode version vs latest PyPI
1965
+ try:
1966
+ from meshcode import __version__ as mc_ver
1967
+ except Exception:
1968
+ mc_ver = "unknown"
1969
+ latest = None
1970
+ try:
1971
+ req = Request("https://pypi.org/pypi/meshcode/json", method="GET")
1972
+ with urlopen(req, timeout=5) as resp:
1973
+ data = json.loads(resp.read().decode())
1974
+ latest = data.get("info", {}).get("version")
1975
+ except Exception:
1976
+ pass
1977
+ if latest and mc_ver != "unknown":
1978
+ if mc_ver == latest:
1979
+ ok("meshcode version", f"{mc_ver} (latest)")
1980
+ else:
1981
+ warn("meshcode version", f"{mc_ver} (latest: {latest}). Run: pip install --upgrade meshcode")
1982
+ elif mc_ver != "unknown":
1983
+ ok("meshcode version", f"{mc_ver} (could not check PyPI)")
1984
+ else:
1985
+ fail("meshcode version", "could not determine version")
1986
+
1987
+ # 3. Config directory
1988
+ config_dir = Path.home() / ".meshcode"
1989
+ if config_dir.exists():
1990
+ api_key_file = config_dir / "api_key"
1991
+ profile_meta = config_dir / "profile_meta.json"
1992
+ has_key = bool(_load_api_key_for_cli())
1993
+ if has_key:
1994
+ ok("Authentication", "API key found in keychain")
1995
+ elif api_key_file.exists():
1996
+ warn("Authentication", "Legacy api_key file found. Run: meshcode login <key>")
1997
+ else:
1998
+ fail("Authentication", "Not logged in. Run: meshcode login <api_key>")
1999
+ if profile_meta.exists():
2000
+ try:
2001
+ meta = json.loads(profile_meta.read_text(encoding="utf-8"))
2002
+ email = meta.get("email", "unknown")
2003
+ ok("Profile", f"logged in as {email}")
2004
+ except Exception:
2005
+ warn("Profile", "profile_meta.json exists but unreadable")
2006
+ else:
2007
+ warn("Profile", "no profile_meta.json (run meshcode login)")
2008
+ else:
2009
+ fail("Config directory", "~/.meshcode/ does not exist. Run: meshcode login <api_key>")
2010
+
2011
+ # 4. Supabase connectivity (use a lightweight RPC that always exists)
2012
+ try:
2013
+ # Ping via a simple select on a known table with limit=0
2014
+ test_url = f"{SUPABASE_URL}/rest/v1/mc_projects?limit=0"
2015
+ req = Request(test_url, method="GET", headers=_headers())
2016
+ with urlopen(req, timeout=8) as resp:
2017
+ ok("Supabase API", "reachable")
2018
+ except HTTPError as e:
2019
+ if e.code in (401, 403):
2020
+ # Auth error means the API IS reachable, just RLS blocks it
2021
+ ok("Supabase API", "reachable (auth required for data)")
2022
+ else:
2023
+ fail("Supabase API", f"HTTP {e.code}")
2024
+ except Exception as e:
2025
+ fail("Supabase API", f"unreachable: {e}")
2026
+
2027
+ # 5. Stale agents check (requires auth)
2028
+ api_key = _load_api_key_for_cli()
2029
+ if api_key:
2030
+ try:
2031
+ r = sb_rpc("mc_resolve_project", {"p_api_key": api_key, "p_project_name": "*"})
2032
+ # Try listing all projects for this user
2033
+ projects_data = sb_rpc("mc_list_user_projects", {"p_api_key": api_key})
2034
+ if isinstance(projects_data, list):
2035
+ stale_agents = [] # (project_id, agent_name)
2036
+ for proj in projects_data[:5]:
2037
+ pid = proj.get("id") or proj.get("project_id")
2038
+ if not pid:
2039
+ continue
2040
+ agents = sb_select("mc_agents", f"project_id=eq.{pid}")
2041
+ if isinstance(agents, list):
2042
+ for a in agents:
2043
+ hb = a.get("last_heartbeat", "")
2044
+ status = a.get("status", "offline")
2045
+ if status not in ("offline", "needs_setup") and hb:
2046
+ try:
2047
+ from datetime import datetime, timezone
2048
+ hb_dt = datetime.fromisoformat(hb.replace("Z", "+00:00"))
2049
+ age = (datetime.now(timezone.utc) - hb_dt).total_seconds()
2050
+ if age > 180:
2051
+ stale_agents.append((pid, a.get("name", "?")))
2052
+ except Exception:
2053
+ pass
2054
+ if stale_agents:
2055
+ names = ", ".join(n for _, n in stale_agents[:5])
2056
+ warn("Stale agents", f"{len(stale_agents)} stale: {names}")
2057
+ if auto_fix:
2058
+ fixed = 0
2059
+ for pid, aname in stale_agents:
2060
+ try:
2061
+ sb_rpc("mc_disconnect_agent", {
2062
+ "p_project_id": pid, "p_agent_name": aname,
2063
+ })
2064
+ fixed += 1
2065
+ except Exception:
2066
+ pass
2067
+ if fixed:
2068
+ ok("Stale agent fix", f"forced {fixed} agent(s) offline")
2069
+ else:
2070
+ ok("Stale agents", "none detected")
2071
+ else:
2072
+ warn("Stale agents", "could not list projects (RPC not available)")
2073
+ except Exception as e:
2074
+ warn("Stale agents", f"check failed: {e}")
2075
+ else:
2076
+ warn("Stale agents", "skipped (not logged in)")
2077
+
2078
+ # 6. MCP server processes
2079
+ try:
2080
+ out = _sp.run(["ps", "-eo", "pid,ppid,comm"], capture_output=True, text=True, timeout=5)
2081
+ mcp_procs = [l for l in out.stdout.splitlines() if "meshcode_mcp" in l]
2082
+ orphans = [l for l in mcp_procs if l.split()[1].strip() == "1"]
2083
+ if not mcp_procs:
2084
+ ok("MCP processes", "none running (normal if no agents active)")
2085
+ elif len(orphans) > 3:
2086
+ warn("MCP processes", f"{len(mcp_procs)} running, {len(orphans)} orphaned (PPID=1)")
2087
+ if auto_fix:
2088
+ for line in orphans:
2089
+ pid = line.split()[0].strip()
2090
+ try:
2091
+ os.kill(int(pid), 15) # SIGTERM
2092
+ except Exception:
2093
+ pass
2094
+ ok("MCP cleanup", f"sent SIGTERM to {len(orphans)} orphan processes")
2095
+ else:
2096
+ ok("MCP processes", f"{len(mcp_procs)} running")
2097
+ except Exception:
2098
+ warn("MCP processes", "could not check (ps failed)")
2099
+
2100
+ # 7. Claude Code version
2101
+ claude_bin = shutil.which("claude")
2102
+ if claude_bin:
2103
+ try:
2104
+ out = _sp.run([claude_bin, "--version"], capture_output=True, text=True, timeout=5)
2105
+ cc_ver = out.stdout.strip()
2106
+ # Check for known-bad versions
2107
+ blocked = ["2.1.111", "2.1.112", "2.1.113", "2.1.114", "2.1.115",
2108
+ "2.1.116", "2.1.117", "2.1.118", "2.1.119"]
2109
+ ver_num = cc_ver.split()[-1].strip("()") if cc_ver else ""
2110
+ if any(b in cc_ver for b in blocked):
2111
+ fail("Claude Code", f"{cc_ver} (KNOWN MCP REGRESSION). Pin to 2.1.104: npm i -g @anthropic-ai/claude-code@2.1.104")
2112
+ else:
2113
+ ok("Claude Code", cc_ver)
2114
+ # Check for dual install
2115
+ try:
2116
+ out2 = _sp.run(["type", "-a", "claude"], capture_output=True, text=True,
2117
+ timeout=5, shell=True)
2118
+ installs = [l for l in out2.stdout.strip().splitlines() if "claude" in l]
2119
+ if len(installs) > 1:
2120
+ warn("Claude Code installs", f"DUAL INSTALL: {'; '.join(installs)}. Active: {claude_bin}")
2121
+ except Exception:
2122
+ pass
2123
+ except Exception as e:
2124
+ warn("Claude Code", f"found at {claude_bin} but version check failed: {e}")
2125
+ else:
2126
+ warn("Claude Code", "not found in PATH")
2127
+
2128
+ # Print results
2129
+ print()
2130
+ print(" meshcode doctor")
2131
+ print(" " + "=" * 40)
2132
+ ok_count = sum(1 for s, _, _ in results if s == "ok")
2133
+ warn_count = sum(1 for s, _, _ in results if s == "warn")
2134
+ fail_count = sum(1 for s, _, _ in results if s == "fail")
2135
+ for status, label, detail in results:
2136
+ icon = {"ok": "+", "warn": "!", "fail": "x"}[status]
2137
+ detail_str = f" -- {detail}" if detail else ""
2138
+ print(f" [{icon}] {label}{detail_str}")
2139
+ print()
2140
+ summary_parts = []
2141
+ if ok_count:
2142
+ summary_parts.append(f"{ok_count} passed")
2143
+ if warn_count:
2144
+ summary_parts.append(f"{warn_count} warnings")
2145
+ if fail_count:
2146
+ summary_parts.append(f"{fail_count} failed")
2147
+ print(f" {', '.join(summary_parts)}")
2148
+ if warn_count or fail_count:
2149
+ print(" Run with --fix to auto-repair where possible.")
2150
+ print()
2151
+ sys.exit(1 if fail_count else 0)
2152
+
2153
+
1920
2154
  def login(api_key):
1921
2155
  """Authenticate with API key and save it to the OS keychain.
1922
2156
 
@@ -1983,6 +2217,7 @@ def show_help():
1983
2217
  ╚═══════════════════════════════════════════════════════════╝
1984
2218
 
1985
2219
  QUICK START:
2220
+ init Guided setup (opens browser + login)
1986
2221
  login <api_key> Authenticate (get key at meshcode.io)
1987
2222
  go <agent> [--project <name>] Connect agent in one command
1988
2223
 
@@ -2022,6 +2257,10 @@ AGENT CONTROL:
2022
2257
  profile [agent] Show/set agent profile
2023
2258
  connect <proj> <name> Connect existing agent
2024
2259
 
2260
+ DIAGNOSTICS:
2261
+ doctor [--fix] Diagnose setup issues
2262
+ compat Claude Code version compatibility
2263
+
2025
2264
  ADMIN:
2026
2265
  clear <proj> <name> Clear inbox
2027
2266
  unregister <proj> <name> Leave project
@@ -2043,6 +2282,26 @@ Run `meshcode <command> --help` for help on a specific command.
2043
2282
 
2044
2283
  # Per-subcommand help texts
2045
2284
  SUBCOMMAND_HELP = {
2285
+ "init": """meshcode init
2286
+
2287
+ Guided first-time setup. Opens meshcode.io in your browser to create
2288
+ an account, then prompts you to paste your API key.
2289
+
2290
+ After init, run:
2291
+ meshcode go <agent-name> --project <meshwork-name>
2292
+ """,
2293
+ "doctor": """meshcode doctor [--fix]
2294
+
2295
+ Diagnose common setup issues: Python version, meshcode version, auth,
2296
+ Supabase connectivity, stale agents, MCP processes, Claude Code version.
2297
+
2298
+ OPTIONS:
2299
+ --fix Auto-repair where possible (kill orphan MCP processes, etc.)
2300
+
2301
+ EXAMPLES:
2302
+ meshcode doctor # diagnose only
2303
+ meshcode doctor --fix # diagnose + auto-fix
2304
+ """,
2046
2305
  "register": """meshcode register <project> <name> [role]
2047
2306
 
2048
2307
  Join a meshwork as a new agent. Tier limits enforced (free=3 agents).
@@ -2148,8 +2407,8 @@ EXAMPLES:
2148
2407
  "login": """meshcode login <api_key>
2149
2408
 
2150
2409
  Authenticate with a MeshCode API key. Saves credentials to
2151
- ~/.meshcode/credentials.json. Get a key from meshcode.io/settings or
2152
- during the welcome wizard after signup.
2410
+ your OS keychain (macOS Keychain, Linux libsecret, Windows Credential
2411
+ Manager). Get a key from meshcode.io/settings or during signup.
2153
2412
  """,
2154
2413
  "clear": """meshcode clear <project> <name>
2155
2414
 
@@ -2329,6 +2588,53 @@ if __name__ == "__main__":
2329
2588
  except Exception:
2330
2589
  pass
2331
2590
 
2591
+ if cmd == "init":
2592
+ # Guided onboarding: open signup page + prompt for API key
2593
+ import webbrowser
2594
+ print()
2595
+ print(" Welcome to MeshCode!")
2596
+ print(" " + "=" * 40)
2597
+ print()
2598
+ print(" Step 1: Create your account")
2599
+ print(" Opening meshcode.io in your browser...")
2600
+ print()
2601
+ try:
2602
+ webbrowser.open("https://meshcode.io")
2603
+ except Exception:
2604
+ print(" Could not open browser. Visit: https://meshcode.io")
2605
+ print(" Step 2: Copy your API key from Settings")
2606
+ print()
2607
+ try:
2608
+ api_key = input(" Paste your API key (mc_...): ").strip()
2609
+ except (EOFError, KeyboardInterrupt):
2610
+ api_key = ""
2611
+ print()
2612
+ if api_key:
2613
+ login(api_key)
2614
+ print()
2615
+ print(" Step 3: Create your first meshwork")
2616
+ print(" Run: meshcode go <agent-name> --project <meshwork-name>")
2617
+ print()
2618
+ print(" Example:")
2619
+ print(" meshcode go backend --project my-app")
2620
+ print()
2621
+ else:
2622
+ print(" [meshcode] No API key provided. Run: meshcode login <api_key>")
2623
+ sys.exit(1)
2624
+ sys.exit(0)
2625
+
2626
+ # Auth guard: commands that talk to Supabase need a valid API key.
2627
+ # doctor, help, version, login, prefs, launcher don't need auth.
2628
+ _NO_AUTH_CMDS = {"doctor", "compat", "help", "--help", "-h", "login", "init",
2629
+ "prefs", "launcher", "--version", "-V", "version", "whoami",
2630
+ "profiles", "scan"}
2631
+ if cmd not in _NO_AUTH_CMDS:
2632
+ _cli_key = _load_api_key_for_cli()
2633
+ if not _cli_key:
2634
+ print("[meshcode] Not logged in. Run: meshcode login <api_key>")
2635
+ print("[meshcode] Get your API key at: https://meshcode.io/settings")
2636
+ sys.exit(1)
2637
+
2332
2638
  if cmd == "register":
2333
2639
  # Backwards-compat alias for `connect`. Mesh-only model: bind THIS
2334
2640
  # terminal to (project, agent) — never spawns claude.
@@ -2447,7 +2753,7 @@ if __name__ == "__main__":
2447
2753
  elif cmd == "validate-sessions":
2448
2754
  proj = pos[0] if len(pos) > 0 else (sys.argv[2] if len(sys.argv) > 2 else "")
2449
2755
  if not proj:
2450
- print("[meshcode] ERROR: Uso: meshcode validate-sessions <project>")
2756
+ print("[ERROR] Usage: meshcode validate-sessions <project>")
2451
2757
  sys.exit(1)
2452
2758
  ensure_sessions()
2453
2759
  prefix = f"{proj}_"
@@ -2473,7 +2779,7 @@ if __name__ == "__main__":
2473
2779
  proj = pos[0] if len(pos) > 0 else ""
2474
2780
  name = pos[1] if len(pos) > 1 else ""
2475
2781
  if not proj or not name:
2476
- print("[meshcode] ERROR: Uso: meshcode wake-headless <project> <agent>")
2782
+ print("[ERROR] Usage: meshcode wake-headless <project> <agent>")
2477
2783
  sys.exit(1)
2478
2784
  project_id = get_project_id(proj)
2479
2785
  if not project_id:
@@ -2489,7 +2795,7 @@ if __name__ == "__main__":
2489
2795
  proj = pos[0] if len(pos) > 0 else "default"
2490
2796
  name = pos[1] if len(pos) > 1 else ""
2491
2797
  if not name:
2492
- print(f"[meshcode] ERROR: Uso: meshcode {cmd} <project> <agent>")
2798
+ print(f"[ERROR] Usage: meshcode {cmd} <project> <agent>")
2493
2799
  sys.exit(1)
2494
2800
  project_id = get_project_id(proj)
2495
2801
  if not project_id:
@@ -2508,7 +2814,7 @@ if __name__ == "__main__":
2508
2814
  proj = pos[1] if len(pos) > 1 else "default"
2509
2815
  name = pos[2] if len(pos) > 2 else ""
2510
2816
  if not name:
2511
- print("[meshcode] ERROR: Uso: meshcode profile get|set <project> <agent> [flags]")
2817
+ print("[ERROR] Usage: meshcode profile get|set <project> <agent> [flags]")
2512
2818
  sys.exit(1)
2513
2819
  project_id = get_project_id(proj)
2514
2820
  if not project_id:
@@ -2533,7 +2839,7 @@ if __name__ == "__main__":
2533
2839
  sys.exit(1)
2534
2840
  print(f"[{proj}] profile updated for {name}")
2535
2841
  else:
2536
- print(f"[meshcode] ERROR: subcomando desconocido: {sub}. Usa 'get' o 'set'.")
2842
+ print(f"[ERROR] Unknown subcommand: {sub}. Use 'get' or 'set'.")
2537
2843
  sys.exit(1)
2538
2844
 
2539
2845
  elif cmd == "connect":
@@ -2721,7 +3027,7 @@ if __name__ == "__main__":
2721
3027
  elif cmd == "login":
2722
3028
  key = sys.argv[2] if len(sys.argv) > 2 else ""
2723
3029
  if not key:
2724
- print("[meshcode] ERROR: Uso: meshcode login <api_key>")
3030
+ print("[ERROR] Usage: meshcode login <api_key>")
2725
3031
  sys.exit(1)
2726
3032
  login(key)
2727
3033
 
@@ -2886,6 +3192,19 @@ if __name__ == "__main__":
2886
3192
  print(" meshcode prefs reset")
2887
3193
  sys.exit(1)
2888
3194
 
3195
+ elif cmd == "compat":
3196
+ from meshcode.compat import check as cc_check, format_report, RECOMMENDED_VERSION
3197
+ version, status, entry = cc_check()
3198
+ print()
3199
+ print(" meshcode compat")
3200
+ print(" " + "=" * 40)
3201
+ print(format_report(version, entry))
3202
+ print()
3203
+ sys.exit(0 if status in ("safe", "unknown") else 1)
3204
+
3205
+ elif cmd == "doctor":
3206
+ cmd_doctor(flags, pos)
3207
+
2889
3208
  elif cmd == "launcher":
2890
3209
  # meshcode launcher {install|uninstall|start|stop|restart|status|logs|test}
2891
3210
  try:
@@ -2906,7 +3225,7 @@ if __name__ == "__main__":
2906
3225
  "history", "clear", "unregister", "connect", "disconnect",
2907
3226
  "setup", "run", "go", "invite", "join", "invites", "members",
2908
3227
  "revoke-invite", "revoke-member", "login", "prefs", "launcher",
2909
- "help", "profile", "validate-sessions", "wake-headless",
3228
+ "help", "init", "doctor", "compat", "profile", "validate-sessions", "wake-headless",
2910
3229
  ]
2911
3230
  # Simple fuzzy: prefix match + Levenshtein-like best match
2912
3231
  suggestions = [c for c in known_cmds if c.startswith(cmd)]