meshcode 2.0.1__tar.gz → 2.0.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 (29) hide show
  1. {meshcode-2.0.1 → meshcode-2.0.2}/PKG-INFO +1 -1
  2. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/__init__.py +1 -1
  3. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/comms_v4.py +54 -10
  4. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/run_agent.py +83 -8
  5. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/setup_clients.py +40 -3
  6. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.0.1 → meshcode-2.0.2}/pyproject.toml +1 -1
  8. {meshcode-2.0.1 → meshcode-2.0.2}/README.md +0 -0
  9. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/cli.py +0 -0
  10. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/invites.py +0 -0
  11. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/launcher.py +0 -0
  12. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/launcher_install.py +0 -0
  13. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/__init__.py +0 -0
  14. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/__main__.py +0 -0
  15. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/backend.py +0 -0
  16. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/realtime.py +0 -0
  17. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/server.py +0 -0
  18. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/test_backend.py +0 -0
  19. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  20. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/preferences.py +0 -0
  21. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/protocol_v2.py +0 -0
  22. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/secrets.py +0 -0
  23. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/self_update.py +0 -0
  24. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/SOURCES.txt +0 -0
  25. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/dependency_links.txt +0 -0
  26. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/entry_points.txt +0 -0
  27. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/requires.txt +0 -0
  28. {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/top_level.txt +0 -0
  29. {meshcode-2.0.1 → meshcode-2.0.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.0.1
3
+ Version: 2.0.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.0.1"
2
+ __version__ = "2.0.2"
@@ -1248,19 +1248,32 @@ def show_history(project, last_n=20, between=None):
1248
1248
 
1249
1249
 
1250
1250
  def list_projects():
1251
- projects_data = sb_select("mc_projects", "", order="created_at.asc")
1252
- if not projects_data:
1253
- print("[COMMS] Sin proyectos")
1251
+ api_key = _load_api_key_for_cli()
1252
+ if not api_key:
1253
+ print("[meshcode] Not authenticated. Run `meshcode login <api_key>` first.")
1254
+ return
1255
+
1256
+ data = sb_rpc("mc_list_user_projects", {"p_api_key": api_key})
1257
+ if isinstance(data, dict) and data.get("error"):
1258
+ print(f"[meshcode] ERROR: {data['error']}")
1259
+ return
1260
+
1261
+ projects = []
1262
+ if isinstance(data, dict):
1263
+ projects = data.get("projects", [])
1264
+
1265
+ if not projects:
1266
+ print("[meshcode] No projects found. Create one at meshcode.io or with `meshcode_create_meshwork`.")
1254
1267
  return
1255
1268
 
1256
1269
  print(f"\n{'='*60}")
1257
- print(f" PROYECTOS ACTIVOS (Supabase)")
1270
+ print(f" YOUR MESHWORKS")
1258
1271
  print(f"{'='*60}")
1259
1272
 
1260
- for proj in projects_data:
1261
- agents = sb_select("mc_agents", f"project_id=eq.{proj['id']}", order="name.asc")
1273
+ for proj in projects:
1274
+ agents = proj.get("agents", [])
1262
1275
  statuses = [f"{a['name']}({a.get('status','?')})" for a in agents]
1263
- print(f" [{proj['name']}] {', '.join(statuses) if statuses else 'sin agentes'}")
1276
+ print(f" [{proj['name']}] {', '.join(statuses) if statuses else 'no agents'}")
1264
1277
  print()
1265
1278
 
1266
1279
 
@@ -1894,7 +1907,7 @@ if __name__ == "__main__":
1894
1907
  proj = sys.argv[2] if len(sys.argv) > 2 else None
1895
1908
  show_status(proj)
1896
1909
 
1897
- elif cmd == "projects":
1910
+ elif cmd in ("projects", "list", "ls"):
1898
1911
  list_projects()
1899
1912
 
1900
1913
  elif cmd == "history":
@@ -2181,6 +2194,37 @@ if __name__ == "__main__":
2181
2194
  show_help()
2182
2195
 
2183
2196
  else:
2184
- print(f"[ERROR] Comando desconocido: {cmd}")
2185
- show_help()
2197
+ known_cmds = [
2198
+ "register", "send", "broadcast", "read", "check", "watch",
2199
+ "board", "update", "status", "projects", "list", "ls",
2200
+ "history", "clear", "unregister", "connect", "disconnect",
2201
+ "setup", "run", "invite", "join", "invites", "members",
2202
+ "revoke-invite", "revoke-member", "login", "prefs", "launcher",
2203
+ "help", "profile", "validate-sessions", "wake-headless",
2204
+ ]
2205
+ # Simple fuzzy: prefix match + Levenshtein-like best match
2206
+ suggestions = [c for c in known_cmds if c.startswith(cmd)]
2207
+ if not suggestions:
2208
+ # Try substring match
2209
+ suggestions = [c for c in known_cmds if cmd in c]
2210
+ if not suggestions:
2211
+ # Levenshtein distance 2 or less
2212
+ def _dist(a, b):
2213
+ if len(a) > len(b): a, b = b, a
2214
+ dists = list(range(len(a) + 1))
2215
+ for j, cb in enumerate(b):
2216
+ new = [j + 1]
2217
+ for i, ca in enumerate(a):
2218
+ cost = 0 if ca == cb else 1
2219
+ new.append(min(new[-1] + 1, dists[i + 1] + 1, dists[i] + cost))
2220
+ dists = new
2221
+ return dists[-1]
2222
+ scored = [(c, _dist(cmd, c)) for c in known_cmds]
2223
+ suggestions = [c for c, d in scored if d <= 2]
2224
+
2225
+ if suggestions:
2226
+ print(f"[meshcode] Unknown command: '{cmd}'. Did you mean: {', '.join(suggestions[:3])}?")
2227
+ else:
2228
+ print(f"[meshcode] Unknown command: '{cmd}'.")
2229
+ print(f"[meshcode] Run `meshcode help` for all commands.")
2186
2230
  sys.exit(1)
@@ -32,6 +32,77 @@ WORKSPACES_ROOT = Path.home() / "meshcode"
32
32
  REGISTRY_PATH = WORKSPACES_ROOT / ".registry.json"
33
33
 
34
34
 
35
+ def _try_auto_setup(agent: str, project: Optional[str] = None) -> Optional[Tuple[Path, str]]:
36
+ """If agent exists on the server but has no local workspace, auto-create it.
37
+
38
+ Returns (workspace_path, project_name) on success, None on failure.
39
+ """
40
+ try:
41
+ from .setup_clients import _load_supabase_env, setup_workspace
42
+ import importlib
43
+ secrets_mod = importlib.import_module("meshcode.secrets")
44
+ except Exception:
45
+ return None
46
+
47
+ api_key = secrets_mod.get_api_key(profile="default")
48
+ if not api_key:
49
+ return None
50
+
51
+ sb = _load_supabase_env()
52
+
53
+ # Ask the server which project(s) this agent belongs to
54
+ try:
55
+ from urllib.request import Request, urlopen
56
+ body = json.dumps({"p_api_key": api_key, "p_agent_name": agent}).encode()
57
+ req = Request(
58
+ f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_resolve_agent_projects",
59
+ data=body,
60
+ method="POST",
61
+ headers={
62
+ "apikey": sb["SUPABASE_KEY"],
63
+ "Authorization": f"Bearer {sb['SUPABASE_KEY']}",
64
+ "Content-Type": "application/json",
65
+ },
66
+ )
67
+ with urlopen(req, timeout=10) as resp:
68
+ data = json.loads(resp.read().decode())
69
+ except Exception:
70
+ return None
71
+
72
+ if not isinstance(data, dict) or data.get("error"):
73
+ return None
74
+
75
+ projects = data.get("projects", [])
76
+ if not projects:
77
+ return None
78
+
79
+ # If user specified a project, filter to that one
80
+ if project:
81
+ projects = [p for p in projects if p["project_name"] == project]
82
+ if not projects:
83
+ return None
84
+
85
+ if len(projects) > 1:
86
+ print(f"[meshcode] Agent '{agent}' exists in multiple projects:", file=sys.stderr)
87
+ for p in projects:
88
+ print(f"[meshcode] meshcode run {agent} --project {p['project_name']}", file=sys.stderr)
89
+ print(f"[meshcode] Specify which one with --project.", file=sys.stderr)
90
+ return None
91
+
92
+ resolved_project = projects[0]["project_name"]
93
+ role = projects[0].get("role", "")
94
+
95
+ print(f"[meshcode] Workspace recreated automatically for agent '{agent}' (project: {resolved_project})")
96
+ rc = setup_workspace(resolved_project, agent, role)
97
+ if rc != 0:
98
+ return None
99
+
100
+ ws = WORKSPACES_ROOT / f"{resolved_project}-{agent}"
101
+ if ws.exists():
102
+ return ws, resolved_project
103
+ return None
104
+
105
+
35
106
  def _load_registry() -> dict:
36
107
  if not REGISTRY_PATH.exists():
37
108
  return {}
@@ -41,11 +112,12 @@ def _load_registry() -> dict:
41
112
  return {}
42
113
 
43
114
 
44
- def _find_agent_workspace(agent: str, project: Optional[str] = None) -> Optional[Tuple[Path, str]]:
115
+ def _find_agent_workspace(agent: str, project: Optional[str] = None, quiet: bool = False) -> Optional[Tuple[Path, str]]:
45
116
  """Look up the agent in the registry. Returns (workspace_path, project_name) or None.
46
117
 
47
118
  If multiple agents share the same name across projects, requires the
48
119
  user to disambiguate by passing project explicitly.
120
+ When quiet=True, suppresses error messages (used before auto-setup fallback).
49
121
  """
50
122
  reg = _load_registry()
51
123
  agents = reg.get("agents", {})
@@ -55,11 +127,11 @@ def _find_agent_workspace(agent: str, project: Optional[str] = None) -> Optional
55
127
  if info:
56
128
  ws = Path(info["workspace"])
57
129
  if not ws.exists():
58
- print(f"[meshcode] ERROR: workspace dir for '{agent}' is missing: {ws}", file=sys.stderr)
59
- print(f"[meshcode] Re-run: meshcode setup {info.get('project','<project>')} {agent}", file=sys.stderr)
130
+ # Don't print error in quiet mode caller will try auto-setup
60
131
  return None
61
132
  if project and info.get("project") != project:
62
- print(f"[meshcode] ERROR: agent '{agent}' belongs to project '{info.get('project')}', not '{project}'", file=sys.stderr)
133
+ if not quiet:
134
+ print(f"[meshcode] ERROR: agent '{agent}' belongs to project '{info.get('project')}', not '{project}'", file=sys.stderr)
63
135
  return None
64
136
  return ws, info.get("project", "")
65
137
 
@@ -81,8 +153,6 @@ def _find_agent_workspace(agent: str, project: Optional[str] = None) -> Optional
81
153
  print(f"[meshcode] Disambiguate: meshcode run {agent} --project <name>", file=sys.stderr)
82
154
  return None
83
155
 
84
- print(f"[meshcode] ERROR: no workspace found for agent '{agent}'", file=sys.stderr)
85
- print(f"[meshcode] Run `meshcode setup <project> {agent}` first.", file=sys.stderr)
86
156
  return None
87
157
 
88
158
 
@@ -122,9 +192,14 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
122
192
  print(f"[meshcode] (or copy this exact line into a new terminal: meshcode run {agent})", file=sys.stderr)
123
193
  return 2
124
194
 
125
- found = _find_agent_workspace(agent, project)
195
+ found = _find_agent_workspace(agent, project, quiet=True)
126
196
  if not found:
127
- return 2
197
+ # Auto-setup: if agent exists on the server, recreate workspace
198
+ found = _try_auto_setup(agent, project)
199
+ if not found:
200
+ print(f"[meshcode] ERROR: no workspace found for agent '{agent}'", file=sys.stderr)
201
+ print(f"[meshcode] Run `meshcode setup <project> {agent}` first.", file=sys.stderr)
202
+ return 2
128
203
  ws, resolved_project = found
129
204
  server_id = f"meshcode-{resolved_project}-{agent}"
130
205
 
@@ -120,6 +120,8 @@ def _resolve_project_id(api_key: str, project: str, sb: Dict[str, str]) -> str:
120
120
  return data["project_id"]
121
121
  if isinstance(data, dict) and data.get("error"):
122
122
  print(f"[meshcode] ERROR: could not resolve project '{project}': {data['error']}", file=sys.stderr)
123
+ # Try to suggest the user's actual projects
124
+ _suggest_projects(api_key, sb)
123
125
  sys.exit(2)
124
126
  except Exception as e:
125
127
  print(f"[meshcode] ERROR: could not resolve project '{project}': {e}", file=sys.stderr)
@@ -128,6 +130,32 @@ def _resolve_project_id(api_key: str, project: str, sb: Dict[str, str]) -> str:
128
130
  return ""
129
131
 
130
132
 
133
+ def _suggest_projects(api_key: str, sb: dict):
134
+ """Try to list the user's projects to help with typos."""
135
+ try:
136
+ from urllib.request import Request as _Req, urlopen as _urlopen
137
+ body = json.dumps({"p_api_key": api_key}).encode()
138
+ req = _Req(
139
+ f"{sb['SUPABASE_URL']}/rest/v1/rpc/mc_list_user_projects",
140
+ data=body,
141
+ method="POST",
142
+ headers={
143
+ "apikey": sb["SUPABASE_KEY"],
144
+ "Authorization": f"Bearer {sb['SUPABASE_KEY']}",
145
+ "Content-Type": "application/json",
146
+ },
147
+ )
148
+ with _urlopen(req, timeout=10) as resp:
149
+ data = json.loads(resp.read().decode())
150
+ projects = data.get("projects", []) if isinstance(data, dict) else []
151
+ if projects:
152
+ print(f"[meshcode] Your projects:", file=sys.stderr)
153
+ for p in projects:
154
+ print(f"[meshcode] - {p['name']}", file=sys.stderr)
155
+ except Exception:
156
+ pass
157
+
158
+
131
159
  def _build_server_block(project: str, project_id: str, agent: str, role: str,
132
160
  api_key: str, sb: Dict[str, str],
133
161
  keychain_profile: str = "default") -> Dict[str, Any]:
@@ -591,17 +619,26 @@ def setup(*args) -> int:
591
619
  if args[0] == "claude-desktop":
592
620
  # Claude Desktop needs global config — only exception
593
621
  if len(args) < 3:
594
- print("Usage: meshcode setup claude-desktop <project> <agent> [role]", file=sys.stderr)
622
+ print(f"[meshcode] Missing agent name.", file=sys.stderr)
623
+ if len(args) >= 2:
624
+ print(f"[meshcode] Usage: meshcode setup claude-desktop {args[1]} <agent-name>", file=sys.stderr)
625
+ else:
626
+ print(f"[meshcode] Usage: meshcode setup claude-desktop <project> <agent>", file=sys.stderr)
595
627
  return 1
596
628
  return setup_global(args[0], args[1], args[2], args[3] if len(args) > 3 else "")
597
629
  # All other clients: redirect to workspace flow (no global pollution)
598
630
  print(f"[meshcode] NOTE: 'meshcode setup {args[0]}' is deprecated. Using workspace flow.", file=sys.stderr)
599
631
  if len(args) < 3:
600
- print("Usage: meshcode setup <project> <agent> [role]", file=sys.stderr)
632
+ print(f"[meshcode] Missing agent name.", file=sys.stderr)
633
+ if len(args) >= 2:
634
+ print(f"[meshcode] Usage: meshcode setup {args[1]} <agent-name>", file=sys.stderr)
635
+ else:
636
+ print(f"[meshcode] Usage: meshcode setup <project> <agent>", file=sys.stderr)
601
637
  return 1
602
638
  return setup_workspace(args[1], args[2], args[3] if len(args) > 3 else "")
603
639
 
604
640
  if len(args) < 2:
605
- print("Usage: meshcode setup <project> <agent> [role]", file=sys.stderr)
641
+ print(f"[meshcode] Missing agent name.", file=sys.stderr)
642
+ print(f"[meshcode] Usage: meshcode setup {args[0]} <agent-name>", file=sys.stderr)
606
643
  return 1
607
644
  return setup_workspace(args[0], args[1], args[2] if len(args) > 2 else "")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.0.1
3
+ Version: 2.0.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.0.1"
7
+ version = "2.0.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
File without changes
File without changes