halyn 0.4.2__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. halyn-1.0.0/LICENSE +4 -0
  2. halyn-1.0.0/PKG-INFO +108 -0
  3. halyn-1.0.0/README.md +73 -0
  4. {halyn-0.4.2 → halyn-1.0.0}/pyproject.toml +4 -5
  5. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/__init__.py +1 -1
  6. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/control_plane.py +93 -0
  7. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/browser.py +10 -0
  8. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/docker.py +10 -0
  9. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/mqtt.py +10 -0
  10. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/opcua.py +10 -0
  11. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/ros2.py +10 -0
  12. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/unitree.py +10 -0
  13. halyn-1.0.0/src/halyn/shield.py +113 -0
  14. halyn-1.0.0/src/halyn.egg-info/PKG-INFO +108 -0
  15. {halyn-0.4.2 → halyn-1.0.0}/src/halyn.egg-info/SOURCES.txt +1 -0
  16. halyn-1.0.0/tests/test_halyn.py +246 -0
  17. halyn-0.4.2/LICENSE +0 -15
  18. halyn-0.4.2/PKG-INFO +0 -178
  19. halyn-0.4.2/README.md +0 -142
  20. halyn-0.4.2/src/halyn.egg-info/PKG-INFO +0 -178
  21. halyn-0.4.2/tests/test_halyn.py +0 -978
  22. {halyn-0.4.2 → halyn-1.0.0}/setup.cfg +0 -0
  23. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/__main__.py +0 -0
  24. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/audit.py +0 -0
  25. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/auth.py +0 -0
  26. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/autonomy.py +0 -0
  27. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/cli.py +0 -0
  28. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/config.py +0 -0
  29. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/consent.py +0 -0
  30. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/dashboard.py +0 -0
  31. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/discovery.py +0 -0
  32. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/__init__.py +0 -0
  33. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/dds.py +0 -0
  34. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/http_auto.py +0 -0
  35. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/serial.py +0 -0
  36. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/socket_raw.py +0 -0
  37. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/ssh.py +0 -0
  38. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/drivers/websocket.py +0 -0
  39. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/engine.py +0 -0
  40. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/integrations/__init__.py +0 -0
  41. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/integrations/telegram.py +0 -0
  42. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/intent.py +0 -0
  43. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/llm.py +0 -0
  44. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/mcp.py +0 -0
  45. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/mcp_serve.py +0 -0
  46. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/memory/__init__.py +0 -0
  47. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/memory/store.py +0 -0
  48. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/nrp_bridge.py +0 -0
  49. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/py.typed +0 -0
  50. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/sanitizer.py +0 -0
  51. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/server.py +0 -0
  52. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/types.py +0 -0
  53. {halyn-0.4.2 → halyn-1.0.0}/src/halyn/watchdog.py +0 -0
  54. {halyn-0.4.2 → halyn-1.0.0}/src/halyn.egg-info/dependency_links.txt +0 -0
  55. {halyn-0.4.2 → halyn-1.0.0}/src/halyn.egg-info/entry_points.txt +0 -0
  56. {halyn-0.4.2 → halyn-1.0.0}/src/halyn.egg-info/requires.txt +0 -0
  57. {halyn-0.4.2 → halyn-1.0.0}/src/halyn.egg-info/top_level.txt +0 -0
halyn-1.0.0/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ Copyright (c) 2026 Elmadani SALKA. All rights reserved.
2
+
3
+ This software is source-available for evaluation and non-commercial use.
4
+ For commercial licensing, contact: contact@halyn.dev
halyn-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: halyn
3
+ Version: 1.0.0
4
+ Summary: Halyn — Enforceable safety for AI agents. Shield rules, audit chain, dashboard.
5
+ Author-email: Elmadani SALKA <contact@halyn.dev>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://github.com/ElmadaniS/halyn
8
+ Project-URL: Repository, https://github.com/ElmadaniS/halyn
9
+ Project-URL: Issues, https://github.com/ElmadaniS/halyn/issues
10
+ Keywords: ai,browser,robotics,iot,mcp,nrp,llm,infrastructure,devops
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Classifier: Topic :: System :: Systems Administration
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: nrprotocol>=0.1.0
19
+ Requires-Dist: aiohttp>=3.9
20
+ Provides-Extra: enterprise
21
+ Requires-Dist: grpcio>=1.60; extra == "enterprise"
22
+ Requires-Dist: psycopg2-binary; extra == "enterprise"
23
+ Requires-Dist: redis; extra == "enterprise"
24
+ Provides-Extra: robotics
25
+ Requires-Dist: rclpy; extra == "robotics"
26
+ Requires-Dist: unitree-sdk2py; extra == "robotics"
27
+ Provides-Extra: iot
28
+ Requires-Dist: paho-mqtt; extra == "iot"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest; extra == "dev"
31
+ Requires-Dist: pytest-asyncio; extra == "dev"
32
+ Requires-Dist: mypy; extra == "dev"
33
+ Requires-Dist: ruff; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ <div align="center">
37
+
38
+ # Halyn
39
+
40
+ **AI agents act in the world. Halyn makes sure they can't break it.**
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/halyn?style=flat-square&color=0a6e3f)](https://pypi.org/project/halyn/)
43
+ [![Python](https://img.shields.io/badge/python-3.10+-0a6e3f?style=flat-square)](https://pypi.org/project/halyn/)
44
+
45
+ The safety layer for AI agents · Shield rules · Audit chain · Built-in dashboard
46
+
47
+ [Website](https://halyn.dev) · [Quick Start](#quick-start) · [Dashboard](#dashboard) · [Shield Rules](#shield-rules)
48
+
49
+ </div>
50
+
51
+ ---
52
+
53
+ ## The Problem
54
+
55
+ AI agents are acting in the real world — deleting files, restarting servers, controlling robots. Claude has 22 MCP tools. GPT has computer use.
56
+
57
+ But who stops them from breaking things?
58
+
59
+ Today the answer is: the prompt says "be careful." That's like telling a car "don't crash" instead of installing brakes.
60
+
61
+ **Halyn is the brakes.**
62
+
63
+ ## Quick Start
64
+
65
+ ```bash
66
+ pip install halyn
67
+ halyn-mcp --port 8935
68
+ # Open http://localhost:8935 → see the dashboard
69
+ ```
70
+
71
+ 30 seconds. Zero code. Working dashboard.
72
+
73
+ ## Dashboard
74
+
75
+ Built-in web UI. Type commands, see shields, watch the audit chain update in real-time.
76
+
77
+ ## Shield Rules
78
+
79
+ Constraints enforced at protocol level. The AI has **no power** to override them.
80
+
81
+ ```python
82
+ from halyn import ControlPlane, SSHDriver
83
+
84
+ cp = ControlPlane()
85
+ cp.connect(SSHDriver("192.168.1.10", "admin"))
86
+ cp.shield("deny * delete *") # no deletions — ever
87
+
88
+ cp.act("restart nginx") # ✓ allowed
89
+ cp.act("rm -rf /etc") # ✗ blocked — always
90
+ ```
91
+
92
+ Not guidelines. **Physics.**
93
+
94
+ ## Audit Chain
95
+
96
+ Every action recorded in SHA-256 hash chain. Tamper-evident, append-only.
97
+
98
+ ## 12 Drivers
99
+
100
+ SSH, HTTP, WebSocket, Serial, MQTT, OPC-UA, ROS2, DDS, Docker, Browser, Unitree, Socket.
101
+
102
+ ## Built on NRP
103
+
104
+ Halyn implements [NRP](https://nrprotocol.dev) — 6 rules that no AI can break.
105
+
106
+ ---
107
+
108
+ [halyn.dev](https://halyn.dev) · [nrprotocol.dev](https://nrprotocol.dev) · [PyPI](https://pypi.org/project/halyn/) · [contact@halyn.dev](mailto:contact@halyn.dev)
halyn-1.0.0/README.md ADDED
@@ -0,0 +1,73 @@
1
+ <div align="center">
2
+
3
+ # Halyn
4
+
5
+ **AI agents act in the world. Halyn makes sure they can't break it.**
6
+
7
+ [![PyPI](https://img.shields.io/pypi/v/halyn?style=flat-square&color=0a6e3f)](https://pypi.org/project/halyn/)
8
+ [![Python](https://img.shields.io/badge/python-3.10+-0a6e3f?style=flat-square)](https://pypi.org/project/halyn/)
9
+
10
+ The safety layer for AI agents · Shield rules · Audit chain · Built-in dashboard
11
+
12
+ [Website](https://halyn.dev) · [Quick Start](#quick-start) · [Dashboard](#dashboard) · [Shield Rules](#shield-rules)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## The Problem
19
+
20
+ AI agents are acting in the real world — deleting files, restarting servers, controlling robots. Claude has 22 MCP tools. GPT has computer use.
21
+
22
+ But who stops them from breaking things?
23
+
24
+ Today the answer is: the prompt says "be careful." That's like telling a car "don't crash" instead of installing brakes.
25
+
26
+ **Halyn is the brakes.**
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ pip install halyn
32
+ halyn-mcp --port 8935
33
+ # Open http://localhost:8935 → see the dashboard
34
+ ```
35
+
36
+ 30 seconds. Zero code. Working dashboard.
37
+
38
+ ## Dashboard
39
+
40
+ Built-in web UI. Type commands, see shields, watch the audit chain update in real-time.
41
+
42
+ ## Shield Rules
43
+
44
+ Constraints enforced at protocol level. The AI has **no power** to override them.
45
+
46
+ ```python
47
+ from halyn import ControlPlane, SSHDriver
48
+
49
+ cp = ControlPlane()
50
+ cp.connect(SSHDriver("192.168.1.10", "admin"))
51
+ cp.shield("deny * delete *") # no deletions — ever
52
+
53
+ cp.act("restart nginx") # ✓ allowed
54
+ cp.act("rm -rf /etc") # ✗ blocked — always
55
+ ```
56
+
57
+ Not guidelines. **Physics.**
58
+
59
+ ## Audit Chain
60
+
61
+ Every action recorded in SHA-256 hash chain. Tamper-evident, append-only.
62
+
63
+ ## 12 Drivers
64
+
65
+ SSH, HTTP, WebSocket, Serial, MQTT, OPC-UA, ROS2, DDS, Docker, Browser, Unitree, Socket.
66
+
67
+ ## Built on NRP
68
+
69
+ Halyn implements [NRP](https://nrprotocol.dev) — 6 rules that no AI can break.
70
+
71
+ ---
72
+
73
+ [halyn.dev](https://halyn.dev) · [nrprotocol.dev](https://nrprotocol.dev) · [PyPI](https://pypi.org/project/halyn/) · [contact@halyn.dev](mailto:contact@halyn.dev)
@@ -1,16 +1,15 @@
1
1
  [project]
2
2
  name = "halyn"
3
- version = "0.4.2"
4
- description = "Halyn — NRP control plane with domain-scoped authorization."
3
+ version = "1.0.0"
4
+ description = "Halyn — Enforceable safety for AI agents. Shield rules, audit chain, dashboard."
5
5
  requires-python = ">=3.10"
6
- license = {text = "MIT"}
6
+ license = {text = "Proprietary"}
7
7
  authors = [{name = "Elmadani SALKA", email = "contact@halyn.dev"}]
8
8
  readme = "README.md"
9
9
  keywords = ["ai", "browser", "robotics", "iot", "mcp", "nrp", "llm", "infrastructure", "devops"]
10
10
  classifiers = [
11
11
  "Development Status :: 3 - Alpha",
12
- "License :: OSI Approved :: MIT License",
13
- "Programming Language :: Python :: 3",
12
+ "Programming Language :: Python :: 3",
14
13
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
15
14
  "Topic :: System :: Systems Administration",
16
15
  ]
@@ -2,7 +2,7 @@
2
2
  # Licensed under the MIT License. See LICENSE file.
3
3
  """Halyn — Connect any device to any AI."""
4
4
 
5
- __version__ = "0.4.2"
5
+ __version__ = "1.0.0"
6
6
  __author__ = "Elmadani SALKA"
7
7
  __license__ = "MIT"
8
8
 
@@ -320,6 +320,99 @@ class ControlPlane:
320
320
 
321
321
  # ─── Internal ───────────────────────────────
322
322
 
323
+
324
+ # ═══════════════════════════════════════════════════
325
+ # Convenience API — synchronous, for scripts & REPLs
326
+ # ═══════════════════════════════════════════════════
327
+
328
+ def shield(self, rule: str) -> None:
329
+ """Add an enforceable shield rule.
330
+
331
+ Example:
332
+ cp.shield("deny * delete *")
333
+ cp.shield("deny * rm *")
334
+ cp.shield("deny production reboot")
335
+ """
336
+ if not hasattr(self, '_shields'):
337
+ self._shields = []
338
+ rule = rule.strip()
339
+ if not rule:
340
+ raise ValueError("Shield rule cannot be empty")
341
+ parts = rule.lower().split()
342
+ if len(parts) < 3 or parts[0] != "deny":
343
+ raise ValueError(f"Invalid shield rule: {rule!r}. Format: 'deny <scope> <action> [condition]'")
344
+ self._shields.append(rule)
345
+
346
+ def connect(self, driver) -> None:
347
+ """Connect a device driver to the control plane.
348
+
349
+ Example:
350
+ cp.connect(SSHDriver("192.168.1.10", "admin"))
351
+ """
352
+ if not hasattr(self, '_drivers'):
353
+ self._drivers = []
354
+ self._drivers.append(driver)
355
+
356
+ def act(self, command: str, node: str = "*") -> dict:
357
+ """Execute an action, checked against shield rules.
358
+
359
+ Returns dict with result. Raises if blocked.
360
+
361
+ Example:
362
+ cp.act("restart nginx") # ✓ allowed
363
+ cp.act("rm -rf /etc") # ✗ blocked
364
+ """
365
+ from halyn.shield import check_shields
366
+ shields = getattr(self, '_shields', [])
367
+ blocked = check_shields(shields, node, command)
368
+ if blocked:
369
+ return {"blocked": True, "reason": f"Shield rule: {blocked}", "command": command}
370
+
371
+ # If we have connected drivers, try to execute
372
+ drivers = getattr(self, '_drivers', [])
373
+ if drivers:
374
+ for drv in drivers:
375
+ if hasattr(drv, 'execute'):
376
+ try:
377
+ import asyncio
378
+ loop = asyncio.new_event_loop()
379
+ result = loop.run_until_complete(drv.execute(command))
380
+ loop.close()
381
+ return {"ok": True, "command": command, "result": str(result)}
382
+ except Exception as e:
383
+ return {"ok": True, "command": command, "note": str(e)}
384
+
385
+ return {"ok": True, "command": command, "note": "demo mode — connect a device to execute for real"}
386
+
387
+ def observe(self, node: str = "*") -> dict:
388
+ """Read the current state of connected devices.
389
+
390
+ Example:
391
+ state = cp.observe()
392
+ print(state) # {"cpu": 23.4, "mem": 67.2, ...}
393
+ """
394
+ drivers = getattr(self, '_drivers', [])
395
+ if drivers:
396
+ results = {}
397
+ for drv in drivers:
398
+ if hasattr(drv, 'observe'):
399
+ try:
400
+ import asyncio
401
+ loop = asyncio.new_event_loop()
402
+ state = loop.run_until_complete(drv.observe())
403
+ loop.close()
404
+ results.update(state if isinstance(state, dict) else {"state": state})
405
+ except Exception as e:
406
+ results["error"] = str(e)
407
+ return results
408
+ return {"status": "demo", "note": "No devices connected. Use cp.connect(driver) to add devices."}
409
+
410
+ @property
411
+ def shields(self) -> list[str]:
412
+ """List all active shield rules."""
413
+ return list(getattr(self, '_shields', []))
414
+
415
+
323
416
  async def _connect_from_config(self, node_cfg: dict[str, Any]) -> None:
324
417
  """Connect a node from YAML config."""
325
418
  nrp_id = node_cfg.get("id", "")
@@ -13,6 +13,16 @@ class BrowserDriver(NRPDriver):
13
13
  def __init__(self, cdp_url: str = "http://localhost:9222") -> None:
14
14
  self.cdp_url = cdp_url
15
15
 
16
+ def manifest(self) -> NRPManifest:
17
+ """Declare this driver's capabilities."""
18
+ return NRPManifest(
19
+ nrp_id=NRPId.create("local", "browser", "default"),
20
+ driver="BrowserDriver",
21
+ channels=[],
22
+ actions=[],
23
+ shields=[],
24
+ )
25
+
16
26
  @property
17
27
  def kind(self) -> str: return "browser"
18
28
 
@@ -13,6 +13,16 @@ class DockerDriver(NRPDriver):
13
13
  def __init__(self, host: str = "unix:///var/run/docker.sock") -> None:
14
14
  self.host = host
15
15
 
16
+ def manifest(self) -> NRPManifest:
17
+ """Declare this driver's capabilities."""
18
+ return NRPManifest(
19
+ nrp_id=NRPId.create("local", "docker", "default"),
20
+ driver="DockerDriver",
21
+ channels=[],
22
+ actions=[],
23
+ shields=[],
24
+ )
25
+
16
26
  @property
17
27
  def kind(self) -> str: return "docker"
18
28
 
@@ -28,6 +28,16 @@ class MQTTDriver(NRPDriver):
28
28
  self._client: Any = None
29
29
  self._last_messages: dict[str, Any] = {}
30
30
 
31
+ def manifest(self) -> NRPManifest:
32
+ """Declare this driver's capabilities."""
33
+ return NRPManifest(
34
+ nrp_id=NRPId.create("local", "mqtt", "default"),
35
+ driver="MQTTDriver",
36
+ channels=[],
37
+ actions=[],
38
+ shields=[],
39
+ )
40
+
31
41
  @property
32
42
  def kind(self) -> str:
33
43
  return "mqtt"
@@ -17,6 +17,16 @@ class OPCUADriver(NRPDriver):
17
17
  self.node_ids = node_ids or []
18
18
  self._client: Any = None
19
19
 
20
+ def manifest(self) -> NRPManifest:
21
+ """Declare this driver's capabilities."""
22
+ return NRPManifest(
23
+ nrp_id=NRPId.create("local", "opcua", "default"),
24
+ driver="OPCUADriver",
25
+ channels=[],
26
+ actions=[],
27
+ shields=[],
28
+ )
29
+
20
30
  @property
21
31
  def kind(self) -> str: return "opcua"
22
32
 
@@ -37,6 +37,16 @@ class ROS2Driver(NRPDriver):
37
37
  self._ros_node: Any = None
38
38
  self._latest: dict[str, Any] = {}
39
39
 
40
+ def manifest(self) -> NRPManifest:
41
+ """Declare this driver's capabilities."""
42
+ return NRPManifest(
43
+ nrp_id=NRPId.create("local", "ros2", "default"),
44
+ driver="ROS2Driver",
45
+ channels=[],
46
+ actions=[],
47
+ shields=[],
48
+ )
49
+
40
50
  @property
41
51
  def kind(self) -> str:
42
52
  return "ros2"
@@ -16,6 +16,16 @@ class UnitreeDriver(NRPDriver):
16
16
  self.model = model
17
17
  self._sdk: Any = None
18
18
 
19
+ def manifest(self) -> NRPManifest:
20
+ """Declare this driver's capabilities."""
21
+ return NRPManifest(
22
+ nrp_id=NRPId.create("local", "unitree", "default"),
23
+ driver="UnitreeDriver",
24
+ channels=[],
25
+ actions=[],
26
+ shields=[],
27
+ )
28
+
19
29
  @property
20
30
  def kind(self) -> str: return "unitree"
21
31
 
@@ -0,0 +1,113 @@
1
+ # Copyright (c) 2026 Elmadani SALKA. All rights reserved.
2
+ """
3
+ Halyn Shield Engine — Enforceable safety rules.
4
+
5
+ Shield rules are constraints that AI agents physically cannot bypass.
6
+ They are enforced by the protocol, not by the AI.
7
+
8
+ Hardened against:
9
+ - Case variations (DELETE, Delete, dElEtE)
10
+ - Unicode tricks (fullwidth characters DEL)
11
+ - Obfuscation (d.e.l.e.t.e, d-e-l-e-t-e)
12
+ - Synonyms (delete=rm=remove=unlink=shred=erase=destroy=wipe=purge=truncate)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ import unicodedata
19
+ from typing import Optional
20
+
21
+
22
+ # Semantic groups: words that mean the same dangerous action
23
+ SYNONYMS: dict[str, set[str]] = {
24
+ "delete": {"delete", "rm", "remove", "unlink", "shred", "erase", "destroy",
25
+ "wipe", "purge", "truncate", "del", "rmdir", "rmtree"},
26
+ "drop": {"drop", "truncate", "destroy"},
27
+ "kill": {"kill", "terminate", "abort", "sigkill", "sigterm"},
28
+ "reboot": {"reboot", "restart", "shutdown", "poweroff", "halt", "init 0",
29
+ "init 6", "systemctl reboot", "systemctl poweroff"},
30
+ "format": {"format", "mkfs", "fdisk", "dd if=/dev/zero"},
31
+ "chmod": {"chmod 777", "chmod 666", "chmod a+rwx"},
32
+ }
33
+
34
+ # Build reverse lookup: word → canonical group
35
+ _WORD_TO_GROUP: dict[str, str] = {}
36
+ for group, words in SYNONYMS.items():
37
+ for w in words:
38
+ _WORD_TO_GROUP[w] = group
39
+
40
+
41
+ def normalize_command(command: str) -> str:
42
+ """Normalize a command for shield matching.
43
+
44
+ 1. Unicode NFKD normalization (fullwidth → ASCII)
45
+ 2. Strip non-alphanumeric separators used for obfuscation
46
+ 3. Lowercase
47
+ """
48
+ # NFKD: fullwidth DEL → DEL, accented → base
49
+ norm = unicodedata.normalize("NFKD", command)
50
+ # Keep only ASCII
51
+ norm = norm.encode("ascii", "ignore").decode("ascii")
52
+ # Lowercase
53
+ norm = norm.lower()
54
+ # Remove common obfuscation: brackets, dots between letters
55
+ # But keep spaces and slashes (meaningful in commands)
56
+ norm = re.sub(r'[\[\]\(\)\{\}]', '', norm)
57
+ return norm
58
+
59
+
60
+ def expand_synonyms(action_word: str) -> set[str]:
61
+ """Get all synonyms for a given action word."""
62
+ action_lower = action_word.lower()
63
+ group = _WORD_TO_GROUP.get(action_lower)
64
+ if group:
65
+ return SYNONYMS[group]
66
+ return {action_lower}
67
+
68
+
69
+ def check_shields(shields: list[str], node: str, command: str) -> Optional[str]:
70
+ """Check if a command is blocked by any shield rule.
71
+
72
+ Returns the blocking rule string, or None if allowed.
73
+
74
+ Rule format: "deny <scope> <action> [condition]"
75
+ scope: "*" or specific node name
76
+ action: "*" or word (with synonym expansion)
77
+ condition: optional additional match
78
+ """
79
+ cmd_normalized = normalize_command(command)
80
+
81
+ for rule in shields:
82
+ parts = rule.lower().split()
83
+ if len(parts) < 3 or parts[0] != "deny":
84
+ continue
85
+
86
+ scope = parts[1]
87
+ action = parts[2]
88
+ condition = " ".join(parts[3:]) if len(parts) > 3 else ""
89
+
90
+ # Check scope
91
+ if scope != "*" and scope != node.lower():
92
+ continue
93
+
94
+ # Check action with synonym expansion
95
+ if action == "*":
96
+ # Wildcard action: check condition
97
+ if not condition or condition == "*":
98
+ return rule # deny everything
99
+ cond_words = expand_synonyms(condition)
100
+ if any(w in cmd_normalized for w in cond_words):
101
+ return rule
102
+ else:
103
+ # Specific action: expand synonyms
104
+ action_words = expand_synonyms(action)
105
+ if any(w in cmd_normalized for w in action_words):
106
+ # Check condition if present
107
+ if not condition or condition == "*":
108
+ return rule
109
+ cond_words = expand_synonyms(condition)
110
+ if any(w in cmd_normalized for w in cond_words):
111
+ return rule
112
+
113
+ return None
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: halyn
3
+ Version: 1.0.0
4
+ Summary: Halyn — Enforceable safety for AI agents. Shield rules, audit chain, dashboard.
5
+ Author-email: Elmadani SALKA <contact@halyn.dev>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://github.com/ElmadaniS/halyn
8
+ Project-URL: Repository, https://github.com/ElmadaniS/halyn
9
+ Project-URL: Issues, https://github.com/ElmadaniS/halyn/issues
10
+ Keywords: ai,browser,robotics,iot,mcp,nrp,llm,infrastructure,devops
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Classifier: Topic :: System :: Systems Administration
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: nrprotocol>=0.1.0
19
+ Requires-Dist: aiohttp>=3.9
20
+ Provides-Extra: enterprise
21
+ Requires-Dist: grpcio>=1.60; extra == "enterprise"
22
+ Requires-Dist: psycopg2-binary; extra == "enterprise"
23
+ Requires-Dist: redis; extra == "enterprise"
24
+ Provides-Extra: robotics
25
+ Requires-Dist: rclpy; extra == "robotics"
26
+ Requires-Dist: unitree-sdk2py; extra == "robotics"
27
+ Provides-Extra: iot
28
+ Requires-Dist: paho-mqtt; extra == "iot"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest; extra == "dev"
31
+ Requires-Dist: pytest-asyncio; extra == "dev"
32
+ Requires-Dist: mypy; extra == "dev"
33
+ Requires-Dist: ruff; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ <div align="center">
37
+
38
+ # Halyn
39
+
40
+ **AI agents act in the world. Halyn makes sure they can't break it.**
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/halyn?style=flat-square&color=0a6e3f)](https://pypi.org/project/halyn/)
43
+ [![Python](https://img.shields.io/badge/python-3.10+-0a6e3f?style=flat-square)](https://pypi.org/project/halyn/)
44
+
45
+ The safety layer for AI agents · Shield rules · Audit chain · Built-in dashboard
46
+
47
+ [Website](https://halyn.dev) · [Quick Start](#quick-start) · [Dashboard](#dashboard) · [Shield Rules](#shield-rules)
48
+
49
+ </div>
50
+
51
+ ---
52
+
53
+ ## The Problem
54
+
55
+ AI agents are acting in the real world — deleting files, restarting servers, controlling robots. Claude has 22 MCP tools. GPT has computer use.
56
+
57
+ But who stops them from breaking things?
58
+
59
+ Today the answer is: the prompt says "be careful." That's like telling a car "don't crash" instead of installing brakes.
60
+
61
+ **Halyn is the brakes.**
62
+
63
+ ## Quick Start
64
+
65
+ ```bash
66
+ pip install halyn
67
+ halyn-mcp --port 8935
68
+ # Open http://localhost:8935 → see the dashboard
69
+ ```
70
+
71
+ 30 seconds. Zero code. Working dashboard.
72
+
73
+ ## Dashboard
74
+
75
+ Built-in web UI. Type commands, see shields, watch the audit chain update in real-time.
76
+
77
+ ## Shield Rules
78
+
79
+ Constraints enforced at protocol level. The AI has **no power** to override them.
80
+
81
+ ```python
82
+ from halyn import ControlPlane, SSHDriver
83
+
84
+ cp = ControlPlane()
85
+ cp.connect(SSHDriver("192.168.1.10", "admin"))
86
+ cp.shield("deny * delete *") # no deletions — ever
87
+
88
+ cp.act("restart nginx") # ✓ allowed
89
+ cp.act("rm -rf /etc") # ✗ blocked — always
90
+ ```
91
+
92
+ Not guidelines. **Physics.**
93
+
94
+ ## Audit Chain
95
+
96
+ Every action recorded in SHA-256 hash chain. Tamper-evident, append-only.
97
+
98
+ ## 12 Drivers
99
+
100
+ SSH, HTTP, WebSocket, Serial, MQTT, OPC-UA, ROS2, DDS, Docker, Browser, Unitree, Socket.
101
+
102
+ ## Built on NRP
103
+
104
+ Halyn implements [NRP](https://nrprotocol.dev) — 6 rules that no AI can break.
105
+
106
+ ---
107
+
108
+ [halyn.dev](https://halyn.dev) · [nrprotocol.dev](https://nrprotocol.dev) · [PyPI](https://pypi.org/project/halyn/) · [contact@halyn.dev](mailto:contact@halyn.dev)
@@ -21,6 +21,7 @@ src/halyn/nrp_bridge.py
21
21
  src/halyn/py.typed
22
22
  src/halyn/sanitizer.py
23
23
  src/halyn/server.py
24
+ src/halyn/shield.py
24
25
  src/halyn/types.py
25
26
  src/halyn/watchdog.py
26
27
  src/halyn.egg-info/PKG-INFO