meshcode 2.0.1__tar.gz → 2.0.3__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.3}/PKG-INFO +39 -8
- {meshcode-2.0.1 → meshcode-2.0.3}/README.md +38 -7
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/__init__.py +1 -1
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/comms_v4.py +54 -10
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/meshcode_mcp/realtime.py +2 -1
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/meshcode_mcp/server.py +16 -10
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/run_agent.py +83 -8
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/setup_clients.py +40 -3
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode.egg-info/PKG-INFO +39 -8
- {meshcode-2.0.1 → meshcode-2.0.3}/pyproject.toml +1 -1
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/cli.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/invites.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/launcher.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/launcher_install.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/preferences.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/secrets.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode/self_update.py +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.0.1 → meshcode-2.0.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Real-time communication between AI agents — Supabase-backed CLI
|
|
5
5
|
Author-email: MeshCode <hello@meshcode.io>
|
|
6
6
|
License: MIT
|
|
@@ -153,7 +153,9 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
153
153
|
| `meshcode status` | `meshcode status` | Show the logged-in account and version. |
|
|
154
154
|
| `meshcode send` (legacy) | `meshcode send my-project a:b '{"need":"..."}'` | Pre-1.5 manual message send (MCP tools are preferred). |
|
|
155
155
|
| `meshcode read` (legacy) | `meshcode read my-project backend` | Pre-1.5 manual read. |
|
|
156
|
-
| `meshcode
|
|
156
|
+
| `meshcode projects` | `meshcode projects` | List all your meshworks and their agents. |
|
|
157
|
+
| `meshcode list` | `meshcode list` | Alias for `meshcode projects`. |
|
|
158
|
+
| `meshcode --version` | `meshcode --version` | Print current version. |
|
|
157
159
|
| `meshcode help` | `meshcode help` | Top-level help. Per-verb help: `meshcode <verb> --help`. |
|
|
158
160
|
|
|
159
161
|
---
|
|
@@ -251,10 +253,11 @@ pip install meshcode --upgrade
|
|
|
251
253
|
### Reconnect a single agent
|
|
252
254
|
|
|
253
255
|
```bash
|
|
254
|
-
meshcode
|
|
256
|
+
meshcode setup my-project agent-name # ensures workspace exists (safe to re-run)
|
|
257
|
+
meshcode run agent-name # opens your editor
|
|
255
258
|
```
|
|
256
259
|
|
|
257
|
-
|
|
260
|
+
Since v2.0.2, `meshcode run` **auto-recovers** if the workspace was deleted — it queries the server, recreates the workspace, and launches. So `meshcode run agent-name` alone is usually enough. The agent automatically:
|
|
258
261
|
1. Loads its persistent memories (`meshcode_recall()`)
|
|
259
262
|
2. Checks who else is online (`meshcode_status()`)
|
|
260
263
|
3. Picks up any open tasks (`meshcode_tasks()`)
|
|
@@ -326,21 +329,49 @@ If an agent is offline, the dashboard shows a **Reconnect** button on its card.
|
|
|
326
329
|
|
|
327
330
|
## Troubleshooting
|
|
328
331
|
|
|
329
|
-
**1. `
|
|
332
|
+
**1. `no workspace found for agent 'X'`**
|
|
333
|
+
Your local workspace was deleted or never created. Since v2.0.2, `meshcode run` auto-recovers by querying the server. If you're on an older version:
|
|
334
|
+
```bash
|
|
335
|
+
pip install meshcode --upgrade # get v2.0.2+
|
|
336
|
+
meshcode run agent-name # auto-recovers now
|
|
337
|
+
```
|
|
338
|
+
If auto-recovery fails (no API key stored), run setup manually:
|
|
339
|
+
```bash
|
|
340
|
+
meshcode setup my-project agent-name # recreates ~/meshcode/my-project-agent-name/
|
|
341
|
+
meshcode run agent-name
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**2. `meshcode projects` shows no projects (but dashboard shows them)**
|
|
345
|
+
Before v2.0.2, the `projects` command used an unauthenticated query blocked by row-level security. Upgrade:
|
|
346
|
+
```bash
|
|
347
|
+
pip install meshcode --upgrade
|
|
348
|
+
meshcode projects # now uses your API key
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**3. `No credentials found. Run meshcode login first.`**
|
|
330
352
|
You haven't stored a key yet. Grab your `mc_xxx` from https://meshcode.io/onboarding and run:
|
|
331
353
|
```bash
|
|
332
354
|
meshcode login mc_xxxxxxxxxxxx
|
|
333
355
|
```
|
|
334
356
|
|
|
335
|
-
**
|
|
357
|
+
**4. `agent already running in another window`**
|
|
336
358
|
MeshCode prevents split-brain — the same agent name can't be live in two editor windows. Close the other window, or use a different agent name.
|
|
337
359
|
|
|
338
|
-
**
|
|
360
|
+
**5. `scoped api key cannot access this project`**
|
|
339
361
|
You joined a meshwork via an invite, and you're now trying to use a different project. Scoped keys only work for the one mesh they were minted for. Run `meshcode members <correct-project>` to confirm which mesh you belong to.
|
|
340
362
|
|
|
341
|
-
**
|
|
363
|
+
**6. `meshcode: command not found`**
|
|
342
364
|
Either `pip install` didn't add to your PATH (check `which meshcode` and `python -m site --user-base`), or you have a stale pre-1.0.0 `~/bin/meshcode` shim shadowing the real binary — delete it.
|
|
343
365
|
|
|
366
|
+
**7. `could not resolve project 'X': project not found`**
|
|
367
|
+
Wrong project name. Since v2.0.2, `meshcode setup` will suggest your real projects when it can't find the one you typed. Check the exact name:
|
|
368
|
+
```bash
|
|
369
|
+
meshcode projects # lists all your meshworks
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**8. Unknown command (e.g., `meshcode list`)**
|
|
373
|
+
Since v2.0.2, the CLI suggests the closest match instead of dumping the full help text. Common aliases: `list` and `ls` both work for `projects`.
|
|
374
|
+
|
|
344
375
|
**5. `MCP server failed to start` in the Claude Code `/mcp` panel**
|
|
345
376
|
Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
|
|
346
377
|
|
|
@@ -128,7 +128,9 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
128
128
|
| `meshcode status` | `meshcode status` | Show the logged-in account and version. |
|
|
129
129
|
| `meshcode send` (legacy) | `meshcode send my-project a:b '{"need":"..."}'` | Pre-1.5 manual message send (MCP tools are preferred). |
|
|
130
130
|
| `meshcode read` (legacy) | `meshcode read my-project backend` | Pre-1.5 manual read. |
|
|
131
|
-
| `meshcode
|
|
131
|
+
| `meshcode projects` | `meshcode projects` | List all your meshworks and their agents. |
|
|
132
|
+
| `meshcode list` | `meshcode list` | Alias for `meshcode projects`. |
|
|
133
|
+
| `meshcode --version` | `meshcode --version` | Print current version. |
|
|
132
134
|
| `meshcode help` | `meshcode help` | Top-level help. Per-verb help: `meshcode <verb> --help`. |
|
|
133
135
|
|
|
134
136
|
---
|
|
@@ -226,10 +228,11 @@ pip install meshcode --upgrade
|
|
|
226
228
|
### Reconnect a single agent
|
|
227
229
|
|
|
228
230
|
```bash
|
|
229
|
-
meshcode
|
|
231
|
+
meshcode setup my-project agent-name # ensures workspace exists (safe to re-run)
|
|
232
|
+
meshcode run agent-name # opens your editor
|
|
230
233
|
```
|
|
231
234
|
|
|
232
|
-
|
|
235
|
+
Since v2.0.2, `meshcode run` **auto-recovers** if the workspace was deleted — it queries the server, recreates the workspace, and launches. So `meshcode run agent-name` alone is usually enough. The agent automatically:
|
|
233
236
|
1. Loads its persistent memories (`meshcode_recall()`)
|
|
234
237
|
2. Checks who else is online (`meshcode_status()`)
|
|
235
238
|
3. Picks up any open tasks (`meshcode_tasks()`)
|
|
@@ -301,21 +304,49 @@ If an agent is offline, the dashboard shows a **Reconnect** button on its card.
|
|
|
301
304
|
|
|
302
305
|
## Troubleshooting
|
|
303
306
|
|
|
304
|
-
**1. `
|
|
307
|
+
**1. `no workspace found for agent 'X'`**
|
|
308
|
+
Your local workspace was deleted or never created. Since v2.0.2, `meshcode run` auto-recovers by querying the server. If you're on an older version:
|
|
309
|
+
```bash
|
|
310
|
+
pip install meshcode --upgrade # get v2.0.2+
|
|
311
|
+
meshcode run agent-name # auto-recovers now
|
|
312
|
+
```
|
|
313
|
+
If auto-recovery fails (no API key stored), run setup manually:
|
|
314
|
+
```bash
|
|
315
|
+
meshcode setup my-project agent-name # recreates ~/meshcode/my-project-agent-name/
|
|
316
|
+
meshcode run agent-name
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**2. `meshcode projects` shows no projects (but dashboard shows them)**
|
|
320
|
+
Before v2.0.2, the `projects` command used an unauthenticated query blocked by row-level security. Upgrade:
|
|
321
|
+
```bash
|
|
322
|
+
pip install meshcode --upgrade
|
|
323
|
+
meshcode projects # now uses your API key
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**3. `No credentials found. Run meshcode login first.`**
|
|
305
327
|
You haven't stored a key yet. Grab your `mc_xxx` from https://meshcode.io/onboarding and run:
|
|
306
328
|
```bash
|
|
307
329
|
meshcode login mc_xxxxxxxxxxxx
|
|
308
330
|
```
|
|
309
331
|
|
|
310
|
-
**
|
|
332
|
+
**4. `agent already running in another window`**
|
|
311
333
|
MeshCode prevents split-brain — the same agent name can't be live in two editor windows. Close the other window, or use a different agent name.
|
|
312
334
|
|
|
313
|
-
**
|
|
335
|
+
**5. `scoped api key cannot access this project`**
|
|
314
336
|
You joined a meshwork via an invite, and you're now trying to use a different project. Scoped keys only work for the one mesh they were minted for. Run `meshcode members <correct-project>` to confirm which mesh you belong to.
|
|
315
337
|
|
|
316
|
-
**
|
|
338
|
+
**6. `meshcode: command not found`**
|
|
317
339
|
Either `pip install` didn't add to your PATH (check `which meshcode` and `python -m site --user-base`), or you have a stale pre-1.0.0 `~/bin/meshcode` shim shadowing the real binary — delete it.
|
|
318
340
|
|
|
341
|
+
**7. `could not resolve project 'X': project not found`**
|
|
342
|
+
Wrong project name. Since v2.0.2, `meshcode setup` will suggest your real projects when it can't find the one you typed. Check the exact name:
|
|
343
|
+
```bash
|
|
344
|
+
meshcode projects # lists all your meshworks
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**8. Unknown command (e.g., `meshcode list`)**
|
|
348
|
+
Since v2.0.2, the CLI suggests the closest match instead of dumping the full help text. Common aliases: `list` and `ls` both work for `projects`.
|
|
349
|
+
|
|
319
350
|
**5. `MCP server failed to start` in the Claude Code `/mcp` panel**
|
|
320
351
|
Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
|
|
321
352
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.0.
|
|
2
|
+
__version__ = "2.0.3"
|
|
@@ -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)
|
|
@@ -13,6 +13,7 @@ import logging
|
|
|
13
13
|
import ssl
|
|
14
14
|
from collections import deque
|
|
15
15
|
from typing import Any, Awaitable, Callable, Deque, Dict, Optional
|
|
16
|
+
from urllib.parse import quote as url_quote
|
|
16
17
|
|
|
17
18
|
try:
|
|
18
19
|
import certifi
|
|
@@ -138,7 +139,7 @@ class RealtimeListener:
|
|
|
138
139
|
"event": "INSERT",
|
|
139
140
|
"schema": "meshcode",
|
|
140
141
|
"table": "mc_messages",
|
|
141
|
-
"filter": f"to_agent=eq.{self.agent_name}",
|
|
142
|
+
"filter": f"to_agent=eq.{url_quote(self.agent_name, safe='')}",
|
|
142
143
|
}
|
|
143
144
|
]
|
|
144
145
|
}
|
|
@@ -44,18 +44,23 @@ def _try_auto_wake(from_agent: str, preview: str) -> None:
|
|
|
44
44
|
"""
|
|
45
45
|
if _IN_WAIT or not _AUTO_WAKE:
|
|
46
46
|
return
|
|
47
|
-
import subprocess, platform
|
|
48
|
-
|
|
47
|
+
import subprocess, platform, re
|
|
48
|
+
# Sanitize inputs: strip everything except alphanumeric, spaces, basic punctuation
|
|
49
|
+
safe_agent = re.sub(r'[^a-zA-Z0-9_\- ]', '', from_agent)[:50]
|
|
50
|
+
safe_preview = re.sub(r'[^a-zA-Z0-9_\-.,!? ]', '', preview)[:60]
|
|
51
|
+
nudge = f"New mesh message from {safe_agent}: {safe_preview}. Check inbox with meshcode_check()."
|
|
49
52
|
system = platform.system()
|
|
50
53
|
try:
|
|
51
54
|
if system == "Darwin":
|
|
52
|
-
#
|
|
55
|
+
# Use xdotool-style approach: pass nudge as argument, not interpolated script
|
|
53
56
|
parent_app = os.environ.get("TERM_PROGRAM", "Terminal")
|
|
54
57
|
app_name = "iTerm" if "iTerm" in parent_app else "Terminal"
|
|
58
|
+
# Escape for AppleScript string: replace backslash and double-quote
|
|
59
|
+
as_safe = nudge.replace("\\", "\\\\").replace('"', '\\"')
|
|
55
60
|
script = f'''
|
|
56
61
|
tell application "{app_name}"
|
|
57
62
|
tell application "System Events"
|
|
58
|
-
keystroke "{
|
|
63
|
+
keystroke "{as_safe}"
|
|
59
64
|
keystroke return
|
|
60
65
|
end tell
|
|
61
66
|
end tell
|
|
@@ -64,15 +69,17 @@ def _try_auto_wake(from_agent: str, preview: str) -> None:
|
|
|
64
69
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
65
70
|
log.info(f"auto-wake: injected nudge via {app_name} AppleScript")
|
|
66
71
|
elif system == "Windows":
|
|
72
|
+
# Sanitize for SendKeys: strip braces and special SendKeys metacharacters
|
|
73
|
+
sk_safe = nudge.replace("{", "").replace("}", "").replace("+", "").replace("^", "").replace("%", "").replace("~", "")
|
|
67
74
|
ps_script = f'''
|
|
68
75
|
Add-Type -AssemblyName System.Windows.Forms
|
|
69
|
-
[System.Windows.Forms.SendKeys]::SendWait("{
|
|
76
|
+
[System.Windows.Forms.SendKeys]::SendWait("{sk_safe}{{ENTER}}")
|
|
70
77
|
'''
|
|
71
78
|
subprocess.Popen(["powershell", "-Command", ps_script],
|
|
72
79
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
73
80
|
log.info("auto-wake: injected nudge via PowerShell SendKeys")
|
|
74
81
|
else:
|
|
75
|
-
# Linux —
|
|
82
|
+
# Linux — xdotool takes args directly, not shell-interpolated (safe)
|
|
76
83
|
subprocess.Popen(["xdotool", "type", "--clearmodifiers", nudge + "\n"],
|
|
77
84
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
78
85
|
log.info("auto-wake: injected nudge via xdotool")
|
|
@@ -1258,6 +1265,9 @@ def _sync_to_obsidian(key: str, value: Any) -> None:
|
|
|
1258
1265
|
Writes markdown notes with YAML frontmatter to MeshCode/<agent>/ folder.
|
|
1259
1266
|
Failures are logged but never block the main memory write.
|
|
1260
1267
|
"""
|
|
1268
|
+
from datetime import datetime
|
|
1269
|
+
from urllib.request import Request, urlopen
|
|
1270
|
+
from urllib.error import URLError
|
|
1261
1271
|
try:
|
|
1262
1272
|
import importlib
|
|
1263
1273
|
prefs_mod = importlib.import_module("meshcode.preferences")
|
|
@@ -1265,10 +1275,6 @@ def _sync_to_obsidian(key: str, value: Any) -> None:
|
|
|
1265
1275
|
if not config:
|
|
1266
1276
|
return
|
|
1267
1277
|
|
|
1268
|
-
from datetime import datetime
|
|
1269
|
-
from urllib.request import Request, urlopen
|
|
1270
|
-
from urllib.error import URLError
|
|
1271
|
-
|
|
1272
1278
|
# Build markdown note with YAML frontmatter
|
|
1273
1279
|
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
1274
1280
|
if isinstance(value, (dict, list)):
|
|
@@ -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 "")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcode
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Real-time communication between AI agents — Supabase-backed CLI
|
|
5
5
|
Author-email: MeshCode <hello@meshcode.io>
|
|
6
6
|
License: MIT
|
|
@@ -153,7 +153,9 @@ meshcode revoke-member my-project <user> # kick a member instantly
|
|
|
153
153
|
| `meshcode status` | `meshcode status` | Show the logged-in account and version. |
|
|
154
154
|
| `meshcode send` (legacy) | `meshcode send my-project a:b '{"need":"..."}'` | Pre-1.5 manual message send (MCP tools are preferred). |
|
|
155
155
|
| `meshcode read` (legacy) | `meshcode read my-project backend` | Pre-1.5 manual read. |
|
|
156
|
-
| `meshcode
|
|
156
|
+
| `meshcode projects` | `meshcode projects` | List all your meshworks and their agents. |
|
|
157
|
+
| `meshcode list` | `meshcode list` | Alias for `meshcode projects`. |
|
|
158
|
+
| `meshcode --version` | `meshcode --version` | Print current version. |
|
|
157
159
|
| `meshcode help` | `meshcode help` | Top-level help. Per-verb help: `meshcode <verb> --help`. |
|
|
158
160
|
|
|
159
161
|
---
|
|
@@ -251,10 +253,11 @@ pip install meshcode --upgrade
|
|
|
251
253
|
### Reconnect a single agent
|
|
252
254
|
|
|
253
255
|
```bash
|
|
254
|
-
meshcode
|
|
256
|
+
meshcode setup my-project agent-name # ensures workspace exists (safe to re-run)
|
|
257
|
+
meshcode run agent-name # opens your editor
|
|
255
258
|
```
|
|
256
259
|
|
|
257
|
-
|
|
260
|
+
Since v2.0.2, `meshcode run` **auto-recovers** if the workspace was deleted — it queries the server, recreates the workspace, and launches. So `meshcode run agent-name` alone is usually enough. The agent automatically:
|
|
258
261
|
1. Loads its persistent memories (`meshcode_recall()`)
|
|
259
262
|
2. Checks who else is online (`meshcode_status()`)
|
|
260
263
|
3. Picks up any open tasks (`meshcode_tasks()`)
|
|
@@ -326,21 +329,49 @@ If an agent is offline, the dashboard shows a **Reconnect** button on its card.
|
|
|
326
329
|
|
|
327
330
|
## Troubleshooting
|
|
328
331
|
|
|
329
|
-
**1. `
|
|
332
|
+
**1. `no workspace found for agent 'X'`**
|
|
333
|
+
Your local workspace was deleted or never created. Since v2.0.2, `meshcode run` auto-recovers by querying the server. If you're on an older version:
|
|
334
|
+
```bash
|
|
335
|
+
pip install meshcode --upgrade # get v2.0.2+
|
|
336
|
+
meshcode run agent-name # auto-recovers now
|
|
337
|
+
```
|
|
338
|
+
If auto-recovery fails (no API key stored), run setup manually:
|
|
339
|
+
```bash
|
|
340
|
+
meshcode setup my-project agent-name # recreates ~/meshcode/my-project-agent-name/
|
|
341
|
+
meshcode run agent-name
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**2. `meshcode projects` shows no projects (but dashboard shows them)**
|
|
345
|
+
Before v2.0.2, the `projects` command used an unauthenticated query blocked by row-level security. Upgrade:
|
|
346
|
+
```bash
|
|
347
|
+
pip install meshcode --upgrade
|
|
348
|
+
meshcode projects # now uses your API key
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**3. `No credentials found. Run meshcode login first.`**
|
|
330
352
|
You haven't stored a key yet. Grab your `mc_xxx` from https://meshcode.io/onboarding and run:
|
|
331
353
|
```bash
|
|
332
354
|
meshcode login mc_xxxxxxxxxxxx
|
|
333
355
|
```
|
|
334
356
|
|
|
335
|
-
**
|
|
357
|
+
**4. `agent already running in another window`**
|
|
336
358
|
MeshCode prevents split-brain — the same agent name can't be live in two editor windows. Close the other window, or use a different agent name.
|
|
337
359
|
|
|
338
|
-
**
|
|
360
|
+
**5. `scoped api key cannot access this project`**
|
|
339
361
|
You joined a meshwork via an invite, and you're now trying to use a different project. Scoped keys only work for the one mesh they were minted for. Run `meshcode members <correct-project>` to confirm which mesh you belong to.
|
|
340
362
|
|
|
341
|
-
**
|
|
363
|
+
**6. `meshcode: command not found`**
|
|
342
364
|
Either `pip install` didn't add to your PATH (check `which meshcode` and `python -m site --user-base`), or you have a stale pre-1.0.0 `~/bin/meshcode` shim shadowing the real binary — delete it.
|
|
343
365
|
|
|
366
|
+
**7. `could not resolve project 'X': project not found`**
|
|
367
|
+
Wrong project name. Since v2.0.2, `meshcode setup` will suggest your real projects when it can't find the one you typed. Check the exact name:
|
|
368
|
+
```bash
|
|
369
|
+
meshcode projects # lists all your meshworks
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**8. Unknown command (e.g., `meshcode list`)**
|
|
373
|
+
Since v2.0.2, the CLI suggests the closest match instead of dumping the full help text. Common aliases: `list` and `ls` both work for `projects`.
|
|
374
|
+
|
|
344
375
|
**5. `MCP server failed to start` in the Claude Code `/mcp` panel**
|
|
345
376
|
Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
|
|
346
377
|
|
|
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
|