meshcode 1.8.0__tar.gz → 1.8.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-1.8.0 → meshcode-1.8.2}/PKG-INFO +1 -1
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/__init__.py +1 -1
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/comms_v4.py +36 -1
- meshcode-1.8.2/meshcode/preferences.py +171 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/run_agent.py +10 -2
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode.egg-info/SOURCES.txt +1 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/pyproject.toml +1 -1
- {meshcode-1.8.0 → meshcode-1.8.2}/README.md +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/cli.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/invites.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/launcher.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/launcher_install.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/protocol_v2.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/secrets.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode/setup_clients.py +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-1.8.0 → meshcode-1.8.2}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "1.8.
|
|
2
|
+
__version__ = "1.8.2"
|
|
@@ -1946,9 +1946,14 @@ if __name__ == "__main__":
|
|
|
1946
1946
|
agent = sys.argv[2]
|
|
1947
1947
|
proj_override = flags.get("project")
|
|
1948
1948
|
editor_override = flags.get("editor")
|
|
1949
|
+
perm_override = flags.get("permission") # bypass | safe | ask
|
|
1950
|
+
if "--bypass" in sys.argv:
|
|
1951
|
+
perm_override = "bypass"
|
|
1952
|
+
elif "--safe" in sys.argv:
|
|
1953
|
+
perm_override = "safe"
|
|
1949
1954
|
import importlib
|
|
1950
1955
|
_run = importlib.import_module("meshcode.run_agent").run
|
|
1951
|
-
sys.exit(_run(agent, project=proj_override, editor_override=editor_override))
|
|
1956
|
+
sys.exit(_run(agent, project=proj_override, editor_override=editor_override, permission_override=perm_override))
|
|
1952
1957
|
|
|
1953
1958
|
elif cmd == "invite":
|
|
1954
1959
|
# meshcode invite <project> <agent> [--role "..."] [--days N]
|
|
@@ -2017,6 +2022,36 @@ if __name__ == "__main__":
|
|
|
2017
2022
|
sys.exit(1)
|
|
2018
2023
|
login(key)
|
|
2019
2024
|
|
|
2025
|
+
elif cmd == "prefs":
|
|
2026
|
+
# meshcode prefs permission-mode [bypass|safe|ask] (no arg = show)
|
|
2027
|
+
# meshcode prefs reset
|
|
2028
|
+
from meshcode.preferences import (
|
|
2029
|
+
get_permission_mode, set_permission_mode, reset_permission_mode,
|
|
2030
|
+
VALID_PERMISSION_MODES,
|
|
2031
|
+
)
|
|
2032
|
+
sub = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
2033
|
+
if sub == "permission-mode":
|
|
2034
|
+
if len(sys.argv) > 3:
|
|
2035
|
+
mode = sys.argv[3].lower()
|
|
2036
|
+
if mode not in VALID_PERMISSION_MODES:
|
|
2037
|
+
print(f"[ERROR] mode must be one of: {', '.join(sorted(VALID_PERMISSION_MODES))}")
|
|
2038
|
+
sys.exit(1)
|
|
2039
|
+
ok = set_permission_mode(mode)
|
|
2040
|
+
print(f"[meshcode] permission_mode = {mode}" if ok else "[ERROR] failed to save")
|
|
2041
|
+
sys.exit(0 if ok else 1)
|
|
2042
|
+
else:
|
|
2043
|
+
cur = get_permission_mode()
|
|
2044
|
+
print(f"permission_mode: {cur or '(unset — will prompt on next run)'}")
|
|
2045
|
+
sys.exit(0)
|
|
2046
|
+
elif sub == "reset":
|
|
2047
|
+
reset_permission_mode()
|
|
2048
|
+
print("[meshcode] preferences reset")
|
|
2049
|
+
sys.exit(0)
|
|
2050
|
+
else:
|
|
2051
|
+
print("Usage: meshcode prefs permission-mode [bypass|safe|ask]")
|
|
2052
|
+
print(" meshcode prefs reset")
|
|
2053
|
+
sys.exit(1)
|
|
2054
|
+
|
|
2020
2055
|
elif cmd == "launcher":
|
|
2021
2056
|
# meshcode launcher {install|uninstall|start|stop|restart|status|logs|test}
|
|
2022
2057
|
try:
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Per-user CLI preferences for MeshCode.
|
|
2
|
+
|
|
3
|
+
Stores small non-secret settings (like permission_mode for `meshcode run`)
|
|
4
|
+
in ~/.meshcode/preferences.json with mode 600. Not the same as the API key
|
|
5
|
+
storage in secrets.py — that goes to the OS keychain. This file is for
|
|
6
|
+
opinion/UX preferences only, no credentials.
|
|
7
|
+
|
|
8
|
+
Currently tracked:
|
|
9
|
+
permission_mode one of "bypass" | "safe" | "ask" | None (unset)
|
|
10
|
+
- "bypass": meshcode run passes --dangerously-skip-permissions
|
|
11
|
+
to Claude Code so the autonomous loop runs without prompts.
|
|
12
|
+
- "safe": meshcode run does NOT pass that flag, so Claude
|
|
13
|
+
prompts the user to approve every tool call. Kills the
|
|
14
|
+
autonomous loop in practice — exists for paranoid users.
|
|
15
|
+
- "ask": prompt every launch.
|
|
16
|
+
- None: never set; meshcode run prompts on first use,
|
|
17
|
+
saves the choice, and uses it from then on.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Dict, Optional
|
|
27
|
+
|
|
28
|
+
PREFS_PATH = Path.home() / ".meshcode" / "preferences.json"
|
|
29
|
+
|
|
30
|
+
VALID_PERMISSION_MODES = {"bypass", "safe", "ask"}
|
|
31
|
+
DEFAULT_PERMISSION_MODE_FOR_NON_TTY = "bypass"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_prefs() -> Dict[str, Any]:
|
|
35
|
+
"""Load the prefs file. Returns {} if missing or unparseable."""
|
|
36
|
+
if not PREFS_PATH.exists():
|
|
37
|
+
return {}
|
|
38
|
+
try:
|
|
39
|
+
return json.loads(PREFS_PATH.read_text())
|
|
40
|
+
except Exception:
|
|
41
|
+
return {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def save_prefs(prefs: Dict[str, Any]) -> bool:
|
|
45
|
+
"""Atomic write of the prefs file with mode 600."""
|
|
46
|
+
try:
|
|
47
|
+
PREFS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
tmp = PREFS_PATH.with_suffix(".tmp")
|
|
49
|
+
tmp.write_text(json.dumps(prefs, indent=2))
|
|
50
|
+
try:
|
|
51
|
+
os.chmod(tmp, 0o600)
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
tmp.replace(PREFS_PATH)
|
|
55
|
+
return True
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"[meshcode] WARNING: could not save prefs: {e}", file=sys.stderr)
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_permission_mode() -> Optional[str]:
|
|
62
|
+
"""Returns 'bypass' / 'safe' / 'ask' / None."""
|
|
63
|
+
val = load_prefs().get("permission_mode")
|
|
64
|
+
if isinstance(val, str) and val in VALID_PERMISSION_MODES:
|
|
65
|
+
return val
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def set_permission_mode(mode: str) -> bool:
|
|
70
|
+
"""Persist a new permission mode. Returns True on success."""
|
|
71
|
+
if mode not in VALID_PERMISSION_MODES:
|
|
72
|
+
print(f"[meshcode] ERROR: invalid permission mode '{mode}'. Use: {', '.join(sorted(VALID_PERMISSION_MODES))}", file=sys.stderr)
|
|
73
|
+
return False
|
|
74
|
+
prefs = load_prefs()
|
|
75
|
+
prefs["permission_mode"] = mode
|
|
76
|
+
prefs["permission_mode_set_at"] = int(time.time())
|
|
77
|
+
return save_prefs(prefs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def reset_permission_mode() -> bool:
|
|
81
|
+
"""Forget the permission mode so the next launch prompts again."""
|
|
82
|
+
prefs = load_prefs()
|
|
83
|
+
prefs.pop("permission_mode", None)
|
|
84
|
+
prefs.pop("permission_mode_set_at", None)
|
|
85
|
+
return save_prefs(prefs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def prompt_permission_mode() -> str:
|
|
89
|
+
"""Interactive first-run prompt. Returns the chosen mode and saves it.
|
|
90
|
+
|
|
91
|
+
If stdin is not a TTY (CI, automation, no terminal), silently picks
|
|
92
|
+
DEFAULT_PERMISSION_MODE_FOR_NON_TTY ('bypass') and saves it. The user
|
|
93
|
+
can change it later with `meshcode prefs permission-mode <mode>`.
|
|
94
|
+
"""
|
|
95
|
+
if not sys.stdin.isatty():
|
|
96
|
+
set_permission_mode(DEFAULT_PERMISSION_MODE_FOR_NON_TTY)
|
|
97
|
+
return DEFAULT_PERMISSION_MODE_FOR_NON_TTY
|
|
98
|
+
|
|
99
|
+
print("", file=sys.stderr)
|
|
100
|
+
print("[meshcode] PERMISSION MODE — choose once (saved for future runs)", file=sys.stderr)
|
|
101
|
+
print("[meshcode]", file=sys.stderr)
|
|
102
|
+
print("[meshcode] Mesh agents work best with bypass mode: tools run without", file=sys.stderr)
|
|
103
|
+
print("[meshcode] confirmation prompts, so the autonomous loop is not interrupted.", file=sys.stderr)
|
|
104
|
+
print("[meshcode] Equivalent to running `claude --dangerously-skip-permissions`.", file=sys.stderr)
|
|
105
|
+
print("[meshcode]", file=sys.stderr)
|
|
106
|
+
print("[meshcode] In bypass mode, your agents can run shell commands, edit files,", file=sys.stderr)
|
|
107
|
+
print("[meshcode] and access the network without asking. Only use bypass in", file=sys.stderr)
|
|
108
|
+
print("[meshcode] directories where you're OK with the agent acting as you.", file=sys.stderr)
|
|
109
|
+
print("[meshcode]", file=sys.stderr)
|
|
110
|
+
print("[meshcode] [B] Bypass mode (recommended for autonomous mesh agents)", file=sys.stderr)
|
|
111
|
+
print("[meshcode] [s] Safe mode (you approve every tool call — kills the loop)", file=sys.stderr)
|
|
112
|
+
print("[meshcode] [a] Ask me each launch", file=sys.stderr)
|
|
113
|
+
print("[meshcode]", file=sys.stderr)
|
|
114
|
+
try:
|
|
115
|
+
ans = input("[meshcode] Pick [B/s/a] (default B): ").strip().lower()
|
|
116
|
+
except (EOFError, KeyboardInterrupt):
|
|
117
|
+
ans = ""
|
|
118
|
+
|
|
119
|
+
if ans in ("", "b", "bypass"):
|
|
120
|
+
chosen = "bypass"
|
|
121
|
+
elif ans in ("s", "safe"):
|
|
122
|
+
chosen = "safe"
|
|
123
|
+
elif ans in ("a", "ask"):
|
|
124
|
+
chosen = "ask"
|
|
125
|
+
else:
|
|
126
|
+
print(f"[meshcode] '{ans}' not recognized — defaulting to bypass.", file=sys.stderr)
|
|
127
|
+
chosen = "bypass"
|
|
128
|
+
|
|
129
|
+
set_permission_mode(chosen)
|
|
130
|
+
print(f"[meshcode] ✓ Saved as '{chosen}'. Change later with: meshcode prefs permission-mode <mode>", file=sys.stderr)
|
|
131
|
+
print("", file=sys.stderr)
|
|
132
|
+
return chosen
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def resolve_permission_mode(per_launch_override: Optional[str] = None) -> str:
|
|
136
|
+
"""Resolve the effective permission mode for a single launch.
|
|
137
|
+
|
|
138
|
+
Order:
|
|
139
|
+
1. per-launch override (--bypass / --safe flag) wins
|
|
140
|
+
2. saved preference
|
|
141
|
+
3. interactive prompt (saves choice if user is at a TTY)
|
|
142
|
+
4. fallback to 'bypass' for non-TTY environments
|
|
143
|
+
"""
|
|
144
|
+
if per_launch_override in VALID_PERMISSION_MODES:
|
|
145
|
+
return per_launch_override
|
|
146
|
+
|
|
147
|
+
saved = get_permission_mode()
|
|
148
|
+
if saved == "ask":
|
|
149
|
+
# The user opted into being prompted every launch
|
|
150
|
+
return prompt_permission_mode_quick()
|
|
151
|
+
if saved in ("bypass", "safe"):
|
|
152
|
+
return saved
|
|
153
|
+
|
|
154
|
+
# Unset → first-run prompt + save
|
|
155
|
+
return prompt_permission_mode()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def prompt_permission_mode_quick() -> str:
|
|
159
|
+
"""One-line prompt for users who picked 'ask each launch'.
|
|
160
|
+
|
|
161
|
+
Does NOT save — the saved preference stays as 'ask'.
|
|
162
|
+
"""
|
|
163
|
+
if not sys.stdin.isatty():
|
|
164
|
+
return DEFAULT_PERMISSION_MODE_FOR_NON_TTY
|
|
165
|
+
try:
|
|
166
|
+
ans = input("[meshcode] Permission mode for this launch [B/s] (default B): ").strip().lower()
|
|
167
|
+
except (EOFError, KeyboardInterrupt):
|
|
168
|
+
ans = ""
|
|
169
|
+
if ans in ("s", "safe"):
|
|
170
|
+
return "safe"
|
|
171
|
+
return "bypass"
|
|
@@ -25,6 +25,8 @@ import sys
|
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
from typing import Optional, Tuple
|
|
27
27
|
|
|
28
|
+
from .preferences import resolve_permission_mode
|
|
29
|
+
|
|
28
30
|
WORKSPACES_ROOT = Path.home() / "meshcode"
|
|
29
31
|
REGISTRY_PATH = WORKSPACES_ROOT / ".registry.json"
|
|
30
32
|
|
|
@@ -97,7 +99,7 @@ def _detect_editor() -> Optional[str]:
|
|
|
97
99
|
return None
|
|
98
100
|
|
|
99
101
|
|
|
100
|
-
def run(agent: str, project: Optional[str] = None, editor_override: Optional[str] = None) -> int:
|
|
102
|
+
def run(agent: str, project: Optional[str] = None, editor_override: Optional[str] = None, permission_override: Optional[str] = None) -> int:
|
|
101
103
|
"""Launch the user's editor with ONLY the named agent's MCP server loaded."""
|
|
102
104
|
found = _find_agent_workspace(agent, project)
|
|
103
105
|
if not found:
|
|
@@ -121,12 +123,18 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
121
123
|
if editor == "claude":
|
|
122
124
|
# Claude Code: pass --mcp-config to point at the workspace's .mcp.json
|
|
123
125
|
# and --strict-mcp-config so it ignores ~/.claude.json's mcpServers.
|
|
126
|
+
mode = resolve_permission_mode(permission_override)
|
|
124
127
|
cmd = [
|
|
125
128
|
editor,
|
|
126
129
|
"--mcp-config", str(ws / ".mcp.json"),
|
|
127
130
|
"--strict-mcp-config",
|
|
128
|
-
"--dangerously-skip-permissions",
|
|
129
131
|
]
|
|
132
|
+
if mode == "bypass":
|
|
133
|
+
cmd.append("--dangerously-skip-permissions")
|
|
134
|
+
print(f"[meshcode] Permission mode: bypass (autonomous loop, no prompts)")
|
|
135
|
+
else:
|
|
136
|
+
print(f"[meshcode] Permission mode: safe (Claude will prompt for every tool call)")
|
|
137
|
+
print(f"[meshcode] Tip: change with `meshcode prefs permission-mode bypass`")
|
|
130
138
|
try:
|
|
131
139
|
os.chdir(ws)
|
|
132
140
|
except Exception:
|
|
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
|