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.
- {meshcode-2.0.1 → meshcode-2.0.2}/PKG-INFO +1 -1
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/__init__.py +1 -1
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/comms_v4.py +54 -10
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/run_agent.py +83 -8
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/setup_clients.py +40 -3
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.0.1 → meshcode-2.0.2}/pyproject.toml +1 -1
- {meshcode-2.0.1 → meshcode-2.0.2}/README.md +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/cli.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/invites.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/launcher.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/launcher_install.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/preferences.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/secrets.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode/self_update.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.2}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.0.
|
|
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
|
-
|
|
1252
|
-
if not
|
|
1253
|
-
print("[
|
|
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"
|
|
1270
|
+
print(f" YOUR MESHWORKS")
|
|
1258
1271
|
print(f"{'='*60}")
|
|
1259
1272
|
|
|
1260
|
-
for proj in
|
|
1261
|
-
agents =
|
|
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 '
|
|
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
|
|
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
|
-
|
|
2185
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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("
|
|
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 "")
|
|
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
|