signalmesh-meshd 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 acecalisto3
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: signalmesh-meshd
3
+ Version: 0.1.2
4
+ Summary: Outbound-only edge daemon for the SignalMesh Submesh Protocol — wrap local software as callable mesh coords with zero inbound ports.
5
+ Author-email: acecalisto3 <acecalisto3@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://huggingface.co/spaces/acecalisto3/SignalMesh
8
+ Project-URL: HuggingFace Space, https://huggingface.co/spaces/acecalisto3/SignalMesh
9
+ Project-URL: Live App, https://acecalisto3-signalmesh.hf.space
10
+ Project-URL: Live Lander, https://hyperagent.com/s/VpnZ7xsnstxjTfHNoYnoag
11
+ Keywords: mesh,agents,protocol,websocket,cli
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: System :: Networking
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: click>=8.0
30
+ Requires-Dist: websockets>=12
31
+ Requires-Dist: PyYAML>=6.0
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest>=7.0; extra == "test"
34
+ Dynamic: license-file
35
+
36
+ # signalmesh-meshd
37
+
38
+ Outbound-only edge daemon for the SignalMesh Submesh Protocol. It holds one
39
+ persistent WebSocket to the SignalMesh cloud, walks
40
+ `~/.signalmesh/harnesses/*/SKILL.md` for op manifests, and executes
41
+ `call_request`s as local subprocesses — no inbound ports, ever.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pipx install signalmesh-meshd
47
+ ```
48
+
49
+ Not on PyPI yet? Install straight from the built wheel:
50
+
51
+ ```bash
52
+ pipx install /path/to/signalmesh_meshd-0.1.2-py3-none-any.whl
53
+ ```
54
+
55
+ ## Quick start
56
+
57
+ Pair once — exchanges your SignalMesh key for a `device_id` + `device_token`,
58
+ written to `~/.signalmesh/meshd.yaml` at mode `0600`:
59
+
60
+ ```bash
61
+ meshd pair --key=smesh-XXXXXX --name=my-desktop
62
+ ```
63
+
64
+ Connect — opens the outbound WebSocket, advertises discovered ops, and blocks
65
+ serving `call_request`s until you kill it:
66
+
67
+ ```bash
68
+ meshd connect
69
+ ```
70
+
71
+ Invoke a coord from the cloud (this is what the SignalMesh backend does on
72
+ your behalf when a mesh caller routes to your device):
73
+
74
+ ```bash
75
+ curl -X POST https://acecalisto3-signalmesh.hf.space/api/submesh/call \
76
+ -H "Content-Type: application/json" \
77
+ -H "X-SignalMesh-Key: smesh-XXXXXX" \
78
+ -d '{"coord": "submesh.my_desktop.echo_test.say", "input": {"msg": "hello mesh"}}'
79
+ ```
80
+
81
+ ## Writing a SKILL.md manifest
82
+
83
+ Drop a directory under `~/.signalmesh/harnesses/<harness-name>/` containing a
84
+ `SKILL.md` with YAML frontmatter. meshd parses the frontmatter block, refuses
85
+ any `exec` argv containing shell metacharacters, and advertises one coord per
86
+ subcommand.
87
+
88
+ `~/.signalmesh/harnesses/echo-test/SKILL.md`:
89
+
90
+ ```markdown
91
+ ---
92
+ binary: /bin/echo
93
+ subcommands:
94
+ - op_id: say
95
+ exec: ["{msg}"]
96
+ summary: "Echo a string back through the mesh."
97
+ params_schema:
98
+ msg: string
99
+ timeout_ms: 5000
100
+ ---
101
+
102
+ # echo-test
103
+
104
+ Minimal harness proving the mesh call → subprocess → response round trip.
105
+ ```
106
+
107
+ This advertises `submesh.<device_name>.echo_test.say`, callable with
108
+ `{"msg": "..."}`.
109
+
110
+ ## Safety model
111
+
112
+ Every argv token is checked against a shell-metachar denylist twice: once at
113
+ manifest-load time (before a coord is even advertised) and once again after
114
+ `{param}` substitution (before the subprocess actually runs) — a manifest
115
+ can't sneak a malicious literal past you, and a caller can't sneak one in
116
+ through input params either. Subprocesses always run with `shell=False`.
117
+ Path-typed params are additionally checked against each subcommand's
118
+ `allow_paths` glob list; anything that resolves outside the allowlist is
119
+ rejected before it ever reaches `argv`.
120
+
121
+ ## Links
122
+
123
+ - 🤗 **HuggingFace Space** (canonical): https://huggingface.co/spaces/acecalisto3/SignalMesh
124
+ - Live app (API endpoints): https://acecalisto3-signalmesh.hf.space
125
+ - Interactive lander: https://hyperagent.com/s/VpnZ7xsnstxjTfHNoYnoag
126
+ - Protocol docs: see the Space's `/api/submesh/*` routes (`device/pair`,
127
+ `device/{id}/revoke`, `devices`, `edge`, `call`)
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,96 @@
1
+ # signalmesh-meshd
2
+
3
+ Outbound-only edge daemon for the SignalMesh Submesh Protocol. It holds one
4
+ persistent WebSocket to the SignalMesh cloud, walks
5
+ `~/.signalmesh/harnesses/*/SKILL.md` for op manifests, and executes
6
+ `call_request`s as local subprocesses — no inbound ports, ever.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pipx install signalmesh-meshd
12
+ ```
13
+
14
+ Not on PyPI yet? Install straight from the built wheel:
15
+
16
+ ```bash
17
+ pipx install /path/to/signalmesh_meshd-0.1.2-py3-none-any.whl
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ Pair once — exchanges your SignalMesh key for a `device_id` + `device_token`,
23
+ written to `~/.signalmesh/meshd.yaml` at mode `0600`:
24
+
25
+ ```bash
26
+ meshd pair --key=smesh-XXXXXX --name=my-desktop
27
+ ```
28
+
29
+ Connect — opens the outbound WebSocket, advertises discovered ops, and blocks
30
+ serving `call_request`s until you kill it:
31
+
32
+ ```bash
33
+ meshd connect
34
+ ```
35
+
36
+ Invoke a coord from the cloud (this is what the SignalMesh backend does on
37
+ your behalf when a mesh caller routes to your device):
38
+
39
+ ```bash
40
+ curl -X POST https://acecalisto3-signalmesh.hf.space/api/submesh/call \
41
+ -H "Content-Type: application/json" \
42
+ -H "X-SignalMesh-Key: smesh-XXXXXX" \
43
+ -d '{"coord": "submesh.my_desktop.echo_test.say", "input": {"msg": "hello mesh"}}'
44
+ ```
45
+
46
+ ## Writing a SKILL.md manifest
47
+
48
+ Drop a directory under `~/.signalmesh/harnesses/<harness-name>/` containing a
49
+ `SKILL.md` with YAML frontmatter. meshd parses the frontmatter block, refuses
50
+ any `exec` argv containing shell metacharacters, and advertises one coord per
51
+ subcommand.
52
+
53
+ `~/.signalmesh/harnesses/echo-test/SKILL.md`:
54
+
55
+ ```markdown
56
+ ---
57
+ binary: /bin/echo
58
+ subcommands:
59
+ - op_id: say
60
+ exec: ["{msg}"]
61
+ summary: "Echo a string back through the mesh."
62
+ params_schema:
63
+ msg: string
64
+ timeout_ms: 5000
65
+ ---
66
+
67
+ # echo-test
68
+
69
+ Minimal harness proving the mesh call → subprocess → response round trip.
70
+ ```
71
+
72
+ This advertises `submesh.<device_name>.echo_test.say`, callable with
73
+ `{"msg": "..."}`.
74
+
75
+ ## Safety model
76
+
77
+ Every argv token is checked against a shell-metachar denylist twice: once at
78
+ manifest-load time (before a coord is even advertised) and once again after
79
+ `{param}` substitution (before the subprocess actually runs) — a manifest
80
+ can't sneak a malicious literal past you, and a caller can't sneak one in
81
+ through input params either. Subprocesses always run with `shell=False`.
82
+ Path-typed params are additionally checked against each subcommand's
83
+ `allow_paths` glob list; anything that resolves outside the allowlist is
84
+ rejected before it ever reaches `argv`.
85
+
86
+ ## Links
87
+
88
+ - 🤗 **HuggingFace Space** (canonical): https://huggingface.co/spaces/acecalisto3/SignalMesh
89
+ - Live app (API endpoints): https://acecalisto3-signalmesh.hf.space
90
+ - Interactive lander: https://hyperagent.com/s/VpnZ7xsnstxjTfHNoYnoag
91
+ - Protocol docs: see the Space's `/api/submesh/*` routes (`device/pair`,
92
+ `device/{id}/revoke`, `devices`, `edge`, `call`)
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "signalmesh-meshd"
7
+ version = "0.1.2"
8
+ description = "Outbound-only edge daemon for the SignalMesh Submesh Protocol — wrap local software as callable mesh coords with zero inbound ports."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "acecalisto3", email = "acecalisto3@gmail.com" },
14
+ ]
15
+ keywords = ["mesh", "agents", "protocol", "websocket", "cli"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: System :: Networking",
29
+ "Topic :: System :: Systems Administration",
30
+ "Topic :: Software Development :: Libraries :: Python Modules",
31
+ ]
32
+ dependencies = [
33
+ "click>=8.0",
34
+ "websockets>=12",
35
+ "PyYAML>=6.0",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ test = [
40
+ "pytest>=7.0",
41
+ ]
42
+
43
+ [project.scripts]
44
+ meshd = "signalmesh_meshd.cli:main"
45
+
46
+ [project.urls]
47
+ Homepage = "https://huggingface.co/spaces/acecalisto3/SignalMesh"
48
+ "HuggingFace Space" = "https://huggingface.co/spaces/acecalisto3/SignalMesh"
49
+ "Live App" = "https://acecalisto3-signalmesh.hf.space"
50
+ "Live Lander" = "https://hyperagent.com/s/VpnZ7xsnstxjTfHNoYnoag"
51
+
52
+ [tool.setuptools.packages.find]
53
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ """signalmesh-meshd — outbound-only edge daemon for the SignalMesh Submesh Protocol.
2
+
3
+ Bridges local software into the SignalMesh cloud without opening a single
4
+ inbound port: holds one persistent outbound WebSocket, advertises locally
5
+ discovered ops (from ~/.signalmesh/harnesses/*/SKILL.md), and executes
6
+ call_requests as local subprocesses.
7
+ """
8
+ from .cli import cli, main
9
+ from .config import MeshdConfig
10
+ from .daemon import run_edge_client
11
+
12
+ __version__ = "0.1.2"
13
+
14
+ __all__ = [
15
+ "__version__",
16
+ "cli",
17
+ "main",
18
+ "MeshdConfig",
19
+ "run_edge_client",
20
+ ]
@@ -0,0 +1,5 @@
1
+ """Allows `python -m signalmesh_meshd`."""
2
+ from .cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,164 @@
1
+ """meshd CLI — click-based entry point.
2
+
3
+ meshd pair --key=smesh-XXXXXX --name=my-desktop
4
+ meshd connect # long-running WS client
5
+ meshd status # show current pairing state
6
+ meshd disconnect --revoke # invalidate device server-side
7
+ """
8
+ import asyncio
9
+ import json
10
+ import os
11
+ import random
12
+ import sys
13
+ import time
14
+ from typing import Optional
15
+
16
+ try:
17
+ import click
18
+ except ImportError:
19
+ print("meshd requires: pip install click websockets PyYAML", file=sys.stderr)
20
+ sys.exit(1)
21
+
22
+ from .config import CLOUD_HTTPS, CONFIG_DIR, CONFIG_PATH, MeshdConfig
23
+ from .daemon import run_edge_client
24
+ from .safety import log
25
+
26
+ try:
27
+ import yaml
28
+ except ImportError:
29
+ yaml = None
30
+
31
+
32
+ @click.group()
33
+ def cli() -> None:
34
+ """meshd — SignalMesh outbound-only edge daemon."""
35
+
36
+
37
+ @cli.command()
38
+ @click.option("--key", required=True,
39
+ help="Your X-SignalMesh-Key (free tier or paid).")
40
+ @click.option("--name", default=None,
41
+ help="Device name (default: this machine's hostname).")
42
+ @click.option("--cloud", default=CLOUD_HTTPS,
43
+ help=f"SignalMesh cloud base URL (default: {CLOUD_HTTPS}).")
44
+ def pair(key: str, name: Optional[str], cloud: str) -> None:
45
+ """One-time pairing — exchanges key for device_id + device_token."""
46
+ import socket
47
+ import urllib.request
48
+
49
+ device_name = name or socket.gethostname()
50
+ body = json.dumps({"user_key": key, "device_name": device_name}).encode()
51
+ req = urllib.request.Request(
52
+ f"{cloud.rstrip('/')}/api/submesh/device/pair",
53
+ data=body,
54
+ headers={
55
+ "Content-Type": "application/json",
56
+ "X-SignalMesh-Key": key,
57
+ },
58
+ method="POST",
59
+ )
60
+ try:
61
+ with urllib.request.urlopen(req, timeout=15) as r:
62
+ result = json.loads(r.read())
63
+ except Exception as e:
64
+ log.error("Pairing failed: %s", e)
65
+ sys.exit(1)
66
+
67
+ ws_url = cloud.replace("https://", "wss://").rstrip("/") + "/api/submesh/edge"
68
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
69
+ CONFIG_PATH.write_text(yaml.safe_dump({
70
+ "device_id": result["device_id"],
71
+ "device_token": result["device_token"],
72
+ "device_name": result["device_name"],
73
+ "cloud_url": ws_url,
74
+ "exposed_harnesses": {},
75
+ }))
76
+ os.chmod(CONFIG_PATH, 0o600)
77
+ click.echo(f"Paired as {device_name}")
78
+ click.echo(f" device_id: {result['device_id']}")
79
+ click.echo(f" config: {CONFIG_PATH} (mode 0600)")
80
+ click.echo(f" cloud: {ws_url}")
81
+ click.echo(f" next: meshd connect")
82
+
83
+
84
+ def _run_with_retry(cfg) -> None:
85
+ """Run edge client with exponential-backoff retry. Ctrl+C exits cleanly.
86
+ Fatal after 20 consecutive failures."""
87
+ attempt = 0
88
+ while True:
89
+ try:
90
+ asyncio.run(run_edge_client(cfg))
91
+ log.info("Edge client returned normally; exiting.")
92
+ return
93
+ except KeyboardInterrupt:
94
+ log.info("Shutting down (KeyboardInterrupt).")
95
+ return
96
+ except Exception as e:
97
+ attempt += 1
98
+ if attempt > 20:
99
+ log.error("Too many failures (%d); giving up. Last: %s: %s",
100
+ attempt, type(e).__name__, e)
101
+ raise
102
+ delay = min(60.0, 0.5 * (2 ** min(attempt, 7))) * (0.5 + random.random())
103
+ log.warning("Edge client failed (attempt %d): %s: %s — retrying in %.1fs",
104
+ attempt, type(e).__name__, e, delay)
105
+ time.sleep(delay)
106
+
107
+
108
+ @cli.command()
109
+ def connect() -> None:
110
+ """Connect to the cloud mesh and serve local capabilities."""
111
+ cfg = MeshdConfig.load()
112
+ click.echo(f"Connecting as {cfg.device_name} → {cfg.cloud_url}")
113
+ _run_with_retry(cfg)
114
+
115
+
116
+ @cli.command()
117
+ def status() -> None:
118
+ """Show current pairing state."""
119
+ if not CONFIG_PATH.exists():
120
+ click.echo("Not paired. Run: meshd pair --key=...")
121
+ return
122
+ cfg = MeshdConfig.load()
123
+ click.echo(f"Paired as {cfg.device_name} ({cfg.device_id})")
124
+ click.echo(f" cloud: {cfg.cloud_url}")
125
+ click.echo(f" config: {CONFIG_PATH}")
126
+
127
+
128
+ @cli.command()
129
+ @click.option("--revoke", is_flag=True,
130
+ help="Also call the server-side revoke endpoint to invalidate the device.")
131
+ @click.option("--key", default=None,
132
+ help="Your pairing key (required when --revoke is set — the mesh-issued "
133
+ "device_token cannot authorize revocation of its own device).")
134
+ def disconnect(revoke: bool, key: str) -> None:
135
+ """Delete local config; optionally revoke server-side."""
136
+ if not CONFIG_PATH.exists():
137
+ click.echo("No config to remove. Already disconnected.")
138
+ return
139
+ if revoke:
140
+ if not key:
141
+ raise click.UsageError("--revoke requires --key=<pairing-key> "
142
+ "(the same key you passed to `meshd pair`).")
143
+ cfg = MeshdConfig.load()
144
+ import urllib.request
145
+ cloud = cfg.cloud_url.replace("wss://", "https://").split("/api/")[0]
146
+ revoke_url = f"{cloud}/api/submesh/device/{cfg.device_id}/revoke"
147
+ req = urllib.request.Request(revoke_url, method="POST",
148
+ headers={"X-SignalMesh-Key": key})
149
+ try:
150
+ urllib.request.urlopen(req, timeout=8)
151
+ click.echo(f"Revoked device {cfg.device_id} on server.")
152
+ except Exception as e:
153
+ click.echo(f"Revoke request failed (proceeding with local disconnect): {e}")
154
+ CONFIG_PATH.unlink(missing_ok=True)
155
+ click.echo(f"Local config removed: {CONFIG_PATH}")
156
+
157
+
158
+ def main() -> None:
159
+ """Console-script entry point (`meshd` and `python -m signalmesh_meshd`)."""
160
+ cli()
161
+
162
+
163
+ if __name__ == "__main__":
164
+ main()
@@ -0,0 +1,42 @@
1
+ """Config load/save for meshd — pairing state lives at ~/.signalmesh/meshd.yaml."""
2
+ import sys
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+
6
+ try:
7
+ import yaml
8
+ except ImportError:
9
+ print("meshd requires: pip install click websockets PyYAML", file=sys.stderr)
10
+ sys.exit(1)
11
+
12
+ from .safety import log
13
+
14
+ CONFIG_DIR = Path.home() / ".signalmesh"
15
+ CONFIG_PATH = CONFIG_DIR / "meshd.yaml"
16
+ HARNESS_DIR = CONFIG_DIR / "harnesses"
17
+ CLOUD_HTTPS = "https://acecalisto3-signalmesh.hf.space"
18
+ CLOUD_WSS = "wss://acecalisto3-signalmesh.hf.space/api/submesh/edge"
19
+
20
+
21
+ @dataclass
22
+ class MeshdConfig:
23
+ device_id: str
24
+ device_token: str
25
+ device_name: str
26
+ cloud_url: str = CLOUD_WSS
27
+ exposed_harnesses: dict = field(default_factory=dict)
28
+
29
+ @classmethod
30
+ def load(cls) -> "MeshdConfig":
31
+ if not CONFIG_PATH.exists():
32
+ log.error("No config at %s — run `meshd pair --key=...` first.",
33
+ CONFIG_PATH)
34
+ sys.exit(1)
35
+ raw = yaml.safe_load(CONFIG_PATH.read_text())
36
+ return cls(
37
+ device_id=raw["device_id"],
38
+ device_token=raw["device_token"],
39
+ device_name=raw["device_name"],
40
+ cloud_url=raw.get("cloud_url", CLOUD_WSS),
41
+ exposed_harnesses=raw.get("exposed_harnesses", {}),
42
+ )
@@ -0,0 +1,108 @@
1
+ """The connect loop: WebSocket handshake, capability advertise, heartbeat,
2
+ and message dispatch (call_request / revoke / ack)."""
3
+ import asyncio
4
+ import json
5
+ import random
6
+ import sys
7
+ import time
8
+
9
+ try:
10
+ import websockets
11
+ from websockets.exceptions import ConnectionClosed
12
+ except ImportError:
13
+ print("meshd requires: pip install click websockets PyYAML", file=sys.stderr)
14
+ sys.exit(1)
15
+
16
+ from .config import MeshdConfig
17
+ from .harness import _execute_op, _load_local_ops
18
+ from .safety import log
19
+
20
+
21
+ async def run_edge_client(cfg: MeshdConfig) -> None:
22
+ attempt = 0
23
+ while True:
24
+ try:
25
+ log.info("Connecting to %s", cfg.cloud_url)
26
+ async with websockets.connect(cfg.cloud_url,
27
+ ping_interval=20,
28
+ ping_timeout=10) as ws:
29
+ # ── Handshake: hello ────────────────────────────────
30
+ await ws.send(json.dumps({
31
+ "type": "hello",
32
+ "device_id": cfg.device_id,
33
+ "device_token": cfg.device_token,
34
+ "device_name": cfg.device_name,
35
+ "meshd_version": "0.1.0",
36
+ "platform": sys.platform,
37
+ }))
38
+ resp = json.loads(await ws.recv())
39
+ if resp.get("type") == "error":
40
+ log.error("Auth rejected: %s (fatal, not retrying)",
41
+ resp.get("code", "unknown"))
42
+ return
43
+ if resp.get("type") != "auth_accepted":
44
+ log.error("Unexpected server response: %s", resp)
45
+ return
46
+ log.info("Connected as %s (device %s). session_ttl_ms=%s",
47
+ cfg.device_name, cfg.device_id,
48
+ resp.get("session_ttl_ms"))
49
+ attempt = 0 # reset backoff on successful connect
50
+
51
+ # ── Advertise capabilities ──────────────────────────
52
+ # Phase 2 MVP: empty ops list. Phase 2.1 walks
53
+ # ~/.signalmesh/harnesses/*/SKILL.md and emits one op per
54
+ # subcommand group. See core/nicoli_catalog.py server-side
55
+ # for the same extraction pattern.
56
+ ops = _load_local_ops(cfg)
57
+ await ws.send(json.dumps({
58
+ "type": "capabilities_advertise",
59
+ "device_id": cfg.device_id,
60
+ "ops": ops,
61
+ }))
62
+ ack = json.loads(await ws.recv())
63
+ log.info("Advertised %d ops. Server ack: %s",
64
+ len(ops), ack.get("op_count"))
65
+
66
+ # ── Heartbeat coroutine ─────────────────────────────
67
+ async def heartbeat_loop():
68
+ while True:
69
+ await asyncio.sleep(30)
70
+ try:
71
+ await ws.send(json.dumps({
72
+ "type": "heartbeat",
73
+ "device_id": cfg.device_id,
74
+ "ts": time.time(),
75
+ }))
76
+ except Exception:
77
+ break
78
+ heartbeat_task = asyncio.create_task(heartbeat_loop())
79
+
80
+ # ── Main receive loop ───────────────────────────────
81
+ try:
82
+ async for raw in ws:
83
+ msg = json.loads(raw)
84
+ mtype = msg.get("type", "")
85
+ if mtype == "call_request":
86
+ log.info("call_request coord=%s call_id=%s",
87
+ msg.get("coord"), msg.get("call_id"))
88
+ result = await _execute_op(msg.get("coord",""),
89
+ msg.get("input",{}) or {})
90
+ await ws.send(json.dumps({"type":"call_result",
91
+ "call_id":msg.get("call_id"),
92
+ **result}))
93
+ elif mtype == "revoke":
94
+ log.warning("Server sent revoke — closing.")
95
+ break
96
+ elif mtype == "ack":
97
+ pass
98
+ else:
99
+ log.debug("Unhandled server message: %s", mtype)
100
+ finally:
101
+ heartbeat_task.cancel()
102
+
103
+ except (ConnectionClosed, OSError, asyncio.TimeoutError) as e:
104
+ delay = min(30, 1.5 * (2 ** attempt)) + random.uniform(0, 1)
105
+ attempt += 1
106
+ log.warning("Connection lost (%s); reconnecting in %.1fs",
107
+ type(e).__name__, delay)
108
+ await asyncio.sleep(delay)
@@ -0,0 +1,132 @@
1
+ """SKILL.md walker + op execution.
2
+
3
+ Walks ~/.signalmesh/harnesses/*/SKILL.md for YAML-frontmatter manifests,
4
+ builds the ops list advertised to the cloud, and executes call_requests as
5
+ local subprocesses (shell=False) once the cloud routes a call to a known
6
+ coord.
7
+ """
8
+ import asyncio
9
+ import os
10
+ import re as _re
11
+ import time
12
+ from pathlib import Path
13
+
14
+ try:
15
+ import yaml
16
+ except ImportError: # pragma: no cover - guarded identically in config.py
17
+ yaml = None
18
+
19
+ from .config import HARNESS_DIR, MeshdConfig
20
+ from .safety import has_shell_metachars, log, path_under
21
+
22
+ _OPS_INDEX: dict = {}
23
+
24
+
25
+ def _load_local_ops(cfg: MeshdConfig) -> list:
26
+ """
27
+ Walk ~/.signalmesh/harnesses/*/SKILL.md for YAML-frontmatter manifests.
28
+ Populates _OPS_INDEX for the call_request handler. Refuses argv tokens
29
+ containing shell metacharacters.
30
+ """
31
+ ops = []
32
+ _OPS_INDEX.clear()
33
+ if not HARNESS_DIR.exists():
34
+ return ops
35
+ for hd in HARNESS_DIR.iterdir():
36
+ if not hd.is_dir():
37
+ continue
38
+ skill_md = hd / "SKILL.md"
39
+ if not skill_md.exists():
40
+ continue
41
+ try:
42
+ content = skill_md.read_text()
43
+ m = _re.match(r'^---\n(.*?)\n---', content, _re.DOTALL)
44
+ if not m:
45
+ continue
46
+ manifest = yaml.safe_load(m.group(1)) or {}
47
+ except Exception as e:
48
+ log.warning("skipping %s: %s", skill_md, e)
49
+ continue
50
+ binary = manifest.get("binary", "")
51
+ harness_name = hd.name
52
+ for sub in manifest.get("subcommands", []) or []:
53
+ op_id = sub.get("op_id")
54
+ argv = sub.get("exec", [])
55
+ if not op_id or not isinstance(argv, list) or not argv:
56
+ continue
57
+ if has_shell_metachars(argv):
58
+ log.error("refusing %s.%s: shell metachars in argv", harness_name, op_id)
59
+ continue
60
+ device_slug = cfg.device_name.lower().replace("-", "_")
61
+ coord = f"submesh.{device_slug}.{harness_name}.{op_id}".lower().replace("-", "_")
62
+ ops.append({
63
+ "coord": coord,
64
+ "harness": harness_name,
65
+ "op_id": op_id,
66
+ "method": "EXEC",
67
+ "summary": sub.get("summary", ""),
68
+ "params_schema": sub.get("params_schema", {}),
69
+ "streaming": True,
70
+ })
71
+ _OPS_INDEX[coord] = {
72
+ "binary": binary,
73
+ "argv": argv,
74
+ "allow_paths": sub.get("allow_paths", []),
75
+ "timeout_ms": sub.get("timeout_ms", 60000),
76
+ "params_schema": sub.get("params_schema", {}),
77
+ }
78
+ return ops
79
+
80
+
81
+ async def _execute_op(coord: str, input_params: dict) -> dict:
82
+ """Resolve coord → argv → subprocess.run (shell=False) → call_result envelope."""
83
+ entry = _OPS_INDEX.get(coord)
84
+ if not entry:
85
+ return {"status": "error", "error": {"code": "coord_not_found", "message": coord}}
86
+ binary = entry["binary"]
87
+ if binary and not os.access(binary, os.X_OK):
88
+ return {"status": "error",
89
+ "error": {"code": "binary_not_executable", "message": binary}}
90
+ resolved = [binary] if binary else []
91
+ params_schema = entry.get("params_schema", {})
92
+ allow_paths = entry.get("allow_paths", [])
93
+ for tok in entry["argv"]:
94
+ s = str(tok)
95
+ if s.startswith("{") and s.endswith("}"):
96
+ key = s[1:-1]
97
+ val = input_params.get(key, "")
98
+ if params_schema.get(key) == "path" and allow_paths:
99
+ p = Path(str(val)).expanduser().resolve()
100
+ allowed = any(True for pattern in allow_paths
101
+ if path_under(p, Path(pattern.split("**")[0]).expanduser().resolve()))
102
+ if not allowed:
103
+ return {"status": "error", "error": {"code": "path_outside_allowlist",
104
+ "message": f"{val!r} not in {allow_paths}"}}
105
+ val = str(p)
106
+ resolved.append(str(val))
107
+ else:
108
+ resolved.append(s)
109
+ if has_shell_metachars(resolved):
110
+ return {"status": "error", "error": {"code": "shell_metachar_after_subst",
111
+ "message": " ".join(resolved)}}
112
+ started = time.time()
113
+ try:
114
+ proc = await asyncio.create_subprocess_exec(
115
+ *resolved, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
116
+ try:
117
+ stdout, _ = await asyncio.wait_for(proc.communicate(),
118
+ timeout=entry["timeout_ms"] / 1000.0)
119
+ except asyncio.TimeoutError:
120
+ proc.kill()
121
+ return {"status": "error", "duration_ms": int((time.time() - started) * 1000),
122
+ "error": {"code": "timeout"}}
123
+ except FileNotFoundError:
124
+ return {"status": "error", "error": {"code": "binary_not_found",
125
+ "message": resolved[0] if resolved else ""}}
126
+ duration_ms = int((time.time() - started) * 1000)
127
+ out = stdout.decode(errors="replace") if stdout else ""
128
+ if proc.returncode == 0:
129
+ return {"status": "success", "duration_ms": duration_ms, "exit_code": 0,
130
+ "output": {"stdout": out[-8000:], "argv": resolved}}
131
+ return {"status": "error", "duration_ms": duration_ms, "exit_code": proc.returncode,
132
+ "error": {"code": "nonzero_exit", "message": out[-2000:]}}
@@ -0,0 +1,23 @@
1
+ """Shared logging + the shell-metachar guard used by harness.py before any
2
+ subprocess exec, both at advertise-time (argv literals) and at call-time
3
+ (argv after {param} substitution)."""
4
+ import logging
5
+ from pathlib import Path
6
+
7
+ logging.basicConfig(level=logging.INFO,
8
+ format="%(asctime)s %(levelname)s meshd: %(message)s")
9
+ log = logging.getLogger("meshd")
10
+
11
+ SHELL_METACHARS = set(";&|`$()<>\\\"'\n")
12
+
13
+
14
+ def has_shell_metachars(tokens) -> bool:
15
+ return any(any(c in SHELL_METACHARS for c in str(tok)) for tok in tokens)
16
+
17
+
18
+ def path_under(p: Path, base: Path) -> bool:
19
+ try:
20
+ p.relative_to(base)
21
+ return True
22
+ except ValueError:
23
+ return False
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: signalmesh-meshd
3
+ Version: 0.1.2
4
+ Summary: Outbound-only edge daemon for the SignalMesh Submesh Protocol — wrap local software as callable mesh coords with zero inbound ports.
5
+ Author-email: acecalisto3 <acecalisto3@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://huggingface.co/spaces/acecalisto3/SignalMesh
8
+ Project-URL: HuggingFace Space, https://huggingface.co/spaces/acecalisto3/SignalMesh
9
+ Project-URL: Live App, https://acecalisto3-signalmesh.hf.space
10
+ Project-URL: Live Lander, https://hyperagent.com/s/VpnZ7xsnstxjTfHNoYnoag
11
+ Keywords: mesh,agents,protocol,websocket,cli
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: System :: Networking
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: click>=8.0
30
+ Requires-Dist: websockets>=12
31
+ Requires-Dist: PyYAML>=6.0
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest>=7.0; extra == "test"
34
+ Dynamic: license-file
35
+
36
+ # signalmesh-meshd
37
+
38
+ Outbound-only edge daemon for the SignalMesh Submesh Protocol. It holds one
39
+ persistent WebSocket to the SignalMesh cloud, walks
40
+ `~/.signalmesh/harnesses/*/SKILL.md` for op manifests, and executes
41
+ `call_request`s as local subprocesses — no inbound ports, ever.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pipx install signalmesh-meshd
47
+ ```
48
+
49
+ Not on PyPI yet? Install straight from the built wheel:
50
+
51
+ ```bash
52
+ pipx install /path/to/signalmesh_meshd-0.1.2-py3-none-any.whl
53
+ ```
54
+
55
+ ## Quick start
56
+
57
+ Pair once — exchanges your SignalMesh key for a `device_id` + `device_token`,
58
+ written to `~/.signalmesh/meshd.yaml` at mode `0600`:
59
+
60
+ ```bash
61
+ meshd pair --key=smesh-XXXXXX --name=my-desktop
62
+ ```
63
+
64
+ Connect — opens the outbound WebSocket, advertises discovered ops, and blocks
65
+ serving `call_request`s until you kill it:
66
+
67
+ ```bash
68
+ meshd connect
69
+ ```
70
+
71
+ Invoke a coord from the cloud (this is what the SignalMesh backend does on
72
+ your behalf when a mesh caller routes to your device):
73
+
74
+ ```bash
75
+ curl -X POST https://acecalisto3-signalmesh.hf.space/api/submesh/call \
76
+ -H "Content-Type: application/json" \
77
+ -H "X-SignalMesh-Key: smesh-XXXXXX" \
78
+ -d '{"coord": "submesh.my_desktop.echo_test.say", "input": {"msg": "hello mesh"}}'
79
+ ```
80
+
81
+ ## Writing a SKILL.md manifest
82
+
83
+ Drop a directory under `~/.signalmesh/harnesses/<harness-name>/` containing a
84
+ `SKILL.md` with YAML frontmatter. meshd parses the frontmatter block, refuses
85
+ any `exec` argv containing shell metacharacters, and advertises one coord per
86
+ subcommand.
87
+
88
+ `~/.signalmesh/harnesses/echo-test/SKILL.md`:
89
+
90
+ ```markdown
91
+ ---
92
+ binary: /bin/echo
93
+ subcommands:
94
+ - op_id: say
95
+ exec: ["{msg}"]
96
+ summary: "Echo a string back through the mesh."
97
+ params_schema:
98
+ msg: string
99
+ timeout_ms: 5000
100
+ ---
101
+
102
+ # echo-test
103
+
104
+ Minimal harness proving the mesh call → subprocess → response round trip.
105
+ ```
106
+
107
+ This advertises `submesh.<device_name>.echo_test.say`, callable with
108
+ `{"msg": "..."}`.
109
+
110
+ ## Safety model
111
+
112
+ Every argv token is checked against a shell-metachar denylist twice: once at
113
+ manifest-load time (before a coord is even advertised) and once again after
114
+ `{param}` substitution (before the subprocess actually runs) — a manifest
115
+ can't sneak a malicious literal past you, and a caller can't sneak one in
116
+ through input params either. Subprocesses always run with `shell=False`.
117
+ Path-typed params are additionally checked against each subcommand's
118
+ `allow_paths` glob list; anything that resolves outside the allowlist is
119
+ rejected before it ever reaches `argv`.
120
+
121
+ ## Links
122
+
123
+ - 🤗 **HuggingFace Space** (canonical): https://huggingface.co/spaces/acecalisto3/SignalMesh
124
+ - Live app (API endpoints): https://acecalisto3-signalmesh.hf.space
125
+ - Interactive lander: https://hyperagent.com/s/VpnZ7xsnstxjTfHNoYnoag
126
+ - Protocol docs: see the Space's `/api/submesh/*` routes (`device/pair`,
127
+ `device/{id}/revoke`, `devices`, `edge`, `call`)
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/signalmesh_meshd/__init__.py
5
+ src/signalmesh_meshd/__main__.py
6
+ src/signalmesh_meshd/cli.py
7
+ src/signalmesh_meshd/config.py
8
+ src/signalmesh_meshd/daemon.py
9
+ src/signalmesh_meshd/harness.py
10
+ src/signalmesh_meshd/safety.py
11
+ src/signalmesh_meshd.egg-info/PKG-INFO
12
+ src/signalmesh_meshd.egg-info/SOURCES.txt
13
+ src/signalmesh_meshd.egg-info/dependency_links.txt
14
+ src/signalmesh_meshd.egg-info/entry_points.txt
15
+ src/signalmesh_meshd.egg-info/requires.txt
16
+ src/signalmesh_meshd.egg-info/top_level.txt
17
+ tests/test_smoke.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ meshd = signalmesh_meshd.cli:main
@@ -0,0 +1,6 @@
1
+ click>=8.0
2
+ websockets>=12
3
+ PyYAML>=6.0
4
+
5
+ [test]
6
+ pytest>=7.0
@@ -0,0 +1 @@
1
+ signalmesh_meshd
@@ -0,0 +1,38 @@
1
+ """Smoke test: proves the built wheel is importable end-to-end and that
2
+ MeshdConfig can be exercised against a fake path (never touches the real
3
+ ~/.signalmesh/meshd.yaml on the machine running the tests)."""
4
+ import os
5
+
6
+ import yaml
7
+
8
+ import signalmesh_meshd
9
+ from signalmesh_meshd import config as meshd_config
10
+
11
+
12
+ def test_version():
13
+ assert signalmesh_meshd.__version__ == "0.1.0"
14
+
15
+
16
+ def test_top_level_exports():
17
+ assert hasattr(signalmesh_meshd, "cli")
18
+ assert hasattr(signalmesh_meshd, "main")
19
+ assert hasattr(signalmesh_meshd, "MeshdConfig")
20
+ assert hasattr(signalmesh_meshd, "run_edge_client")
21
+
22
+
23
+ def test_config_load_from_fake_path(tmp_path, monkeypatch):
24
+ fake_config_path = tmp_path / "meshd.yaml"
25
+ fake_config_path.write_text(yaml.safe_dump({
26
+ "device_id": "dev-fake-0001",
27
+ "device_token": "tok-fake-secret",
28
+ "device_name": "test-harness",
29
+ "cloud_url": "wss://example.invalid/api/submesh/edge",
30
+ "exposed_harnesses": {},
31
+ }))
32
+
33
+ monkeypatch.setattr(meshd_config, "CONFIG_PATH", fake_config_path)
34
+
35
+ cfg = meshd_config.MeshdConfig.load()
36
+ assert cfg.device_id == "dev-fake-0001"
37
+ assert cfg.device_name == "test-harness"
38
+ assert cfg.cloud_url == "wss://example.invalid/api/submesh/edge"