capsule-hermes 0.4.0__py3-none-any.whl
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.
- capsule_hermes/__init__.py +146 -0
- capsule_hermes/config.py +69 -0
- capsule_hermes/normalize.py +75 -0
- capsule_hermes/transport.py +53 -0
- capsule_hermes/types.py +31 -0
- capsule_hermes-0.4.0.dist-info/METADATA +60 -0
- capsule_hermes-0.4.0.dist-info/RECORD +9 -0
- capsule_hermes-0.4.0.dist-info/WHEEL +4 -0
- capsule_hermes-0.4.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Capsule plugin for the Hermes autonomous AI agent runtime.
|
|
2
|
+
|
|
3
|
+
Forwards Hermes hook events to the Capsule agentsecurity service for centralized
|
|
4
|
+
policy enforcement and audit.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import threading
|
|
12
|
+
import uuid
|
|
13
|
+
from typing import Any, Mapping
|
|
14
|
+
|
|
15
|
+
from .config import CapsulePluginConfig
|
|
16
|
+
from .normalize import normalize
|
|
17
|
+
from .transport import CapsuleTransport
|
|
18
|
+
from .types import HermesHookEnvelope
|
|
19
|
+
|
|
20
|
+
PLUGIN_ID = "capsule"
|
|
21
|
+
PLUGIN_VERSION = "0.4.0"
|
|
22
|
+
|
|
23
|
+
_BLOCKING_HOOKS = ("pre_tool_call",)
|
|
24
|
+
_OBSERVATION_HOOKS = (
|
|
25
|
+
"pre_llm_call",
|
|
26
|
+
"pre_gateway_dispatch",
|
|
27
|
+
"pre_approval_request",
|
|
28
|
+
"post_tool_call",
|
|
29
|
+
"post_llm_call",
|
|
30
|
+
"post_approval_response",
|
|
31
|
+
"subagent_stop",
|
|
32
|
+
"on_session_start",
|
|
33
|
+
"on_session_end",
|
|
34
|
+
"on_session_finalize",
|
|
35
|
+
"on_session_reset",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger("capsule_hermes")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _load_config_block() -> Mapping[str, Any]:
|
|
42
|
+
"""Read the `plugins.entries.capsule` block from Hermes' config.yaml.
|
|
43
|
+
|
|
44
|
+
Returns an empty dict when the block is absent so env vars can still drive
|
|
45
|
+
configuration in CI/dev environments.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
from hermes_cli.config import cfg_get, load_config # type: ignore
|
|
49
|
+
except Exception:
|
|
50
|
+
return {}
|
|
51
|
+
try:
|
|
52
|
+
cfg = load_config()
|
|
53
|
+
block = cfg_get(cfg, "plugins", "entries", PLUGIN_ID, default={})
|
|
54
|
+
return block if isinstance(block, dict) else {}
|
|
55
|
+
except Exception:
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _map_verdict(body: Any, block_on_risk: bool) -> dict | None:
|
|
60
|
+
"""Translate a Capsule server response into a Hermes block directive."""
|
|
61
|
+
if not isinstance(body, Mapping):
|
|
62
|
+
return None
|
|
63
|
+
if body.get("block") is True and block_on_risk:
|
|
64
|
+
return {
|
|
65
|
+
"action": "block",
|
|
66
|
+
"message": body.get("blockReason") or "Blocked by Capsule policy",
|
|
67
|
+
}
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def register(ctx: Any) -> None:
|
|
72
|
+
"""Plugin entry point — called by Hermes during discovery."""
|
|
73
|
+
try:
|
|
74
|
+
cfg = CapsulePluginConfig.load(_load_config_block())
|
|
75
|
+
except ValueError as exc:
|
|
76
|
+
logger.warning("capsule_hermes: not registering hooks — %s", exc)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
transport = CapsuleTransport(cfg)
|
|
80
|
+
user = cfg.user or os.environ.get("USER") or "unknown"
|
|
81
|
+
|
|
82
|
+
def dispatch(hook_event_name: str, payload: Mapping[str, Any]):
|
|
83
|
+
correlation_id = str(uuid.uuid4())
|
|
84
|
+
envelope = HermesHookEnvelope(
|
|
85
|
+
hook=normalize(hook_event_name, payload),
|
|
86
|
+
user=user,
|
|
87
|
+
plugin_version=PLUGIN_VERSION,
|
|
88
|
+
)
|
|
89
|
+
try:
|
|
90
|
+
return transport.post(envelope, correlation_id), correlation_id
|
|
91
|
+
except Exception as exc:
|
|
92
|
+
logger.error(
|
|
93
|
+
"capsule_hermes: request failed: %s (correlation_id=%s)",
|
|
94
|
+
exc,
|
|
95
|
+
correlation_id,
|
|
96
|
+
)
|
|
97
|
+
return None, correlation_id
|
|
98
|
+
|
|
99
|
+
def make_blocking_handler(name: str):
|
|
100
|
+
def handler(**kwargs: Any):
|
|
101
|
+
response, correlation_id = dispatch(name, kwargs)
|
|
102
|
+
if response is None:
|
|
103
|
+
return None if cfg.fail_open else {
|
|
104
|
+
"action": "block",
|
|
105
|
+
"message": "Capsule policy unavailable",
|
|
106
|
+
}
|
|
107
|
+
if not response.ok:
|
|
108
|
+
logger.warning(
|
|
109
|
+
"capsule_hermes: server returned %s (correlation_id=%s)",
|
|
110
|
+
response.status,
|
|
111
|
+
correlation_id,
|
|
112
|
+
)
|
|
113
|
+
return None if cfg.fail_open else {
|
|
114
|
+
"action": "block",
|
|
115
|
+
"message": "Capsule policy unavailable",
|
|
116
|
+
}
|
|
117
|
+
return _map_verdict(response.body, cfg.block_on_risk)
|
|
118
|
+
|
|
119
|
+
handler.__name__ = f"capsule_{name}"
|
|
120
|
+
return handler
|
|
121
|
+
|
|
122
|
+
def make_observation_handler(name: str):
|
|
123
|
+
def handler(**kwargs: Any):
|
|
124
|
+
t = threading.Thread(
|
|
125
|
+
target=dispatch, args=(name, kwargs), daemon=True, name=f"capsule-{name}"
|
|
126
|
+
)
|
|
127
|
+
t.start()
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
handler.__name__ = f"capsule_{name}"
|
|
131
|
+
return handler
|
|
132
|
+
|
|
133
|
+
for name in _BLOCKING_HOOKS:
|
|
134
|
+
ctx.register_hook(name, make_blocking_handler(name))
|
|
135
|
+
for name in _OBSERVATION_HOOKS:
|
|
136
|
+
ctx.register_hook(name, make_observation_handler(name))
|
|
137
|
+
|
|
138
|
+
logger.info(
|
|
139
|
+
"capsule_hermes: registered (endpoint=%s, block_on_risk=%s, fail_open=%s)",
|
|
140
|
+
cfg.endpoint,
|
|
141
|
+
cfg.block_on_risk,
|
|
142
|
+
cfg.fail_open,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
__all__ = ["register", "CapsulePluginConfig", "PLUGIN_ID", "PLUGIN_VERSION"]
|
capsule_hermes/config.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Plugin configuration parsing for the Capsule Hermes plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Mapping
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class CapsulePluginConfig:
|
|
12
|
+
"""Validated Capsule plugin configuration.
|
|
13
|
+
|
|
14
|
+
Source precedence (highest first): env vars `CAPSULE_HERMES_*`, then the
|
|
15
|
+
`plugins.entries.capsule.*` block in Hermes' config.yaml.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
endpoint: str
|
|
19
|
+
token: str
|
|
20
|
+
block_on_risk: bool = True
|
|
21
|
+
fail_open: bool = True
|
|
22
|
+
timeout_ms: int = 5000
|
|
23
|
+
user: str | None = None
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def load(cls, raw: Mapping[str, Any] | None = None) -> "CapsulePluginConfig":
|
|
27
|
+
raw = raw or {}
|
|
28
|
+
endpoint = os.environ.get("CAPSULE_HERMES_ENDPOINT") or raw.get("endpoint")
|
|
29
|
+
token = os.environ.get("CAPSULE_HERMES_TOKEN") or raw.get("token")
|
|
30
|
+
if not isinstance(endpoint, str) or not endpoint:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"capsule_hermes: endpoint missing (set CAPSULE_HERMES_ENDPOINT or "
|
|
33
|
+
"plugins.entries.capsule.endpoint in config.yaml)"
|
|
34
|
+
)
|
|
35
|
+
if not isinstance(token, str) or not token:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"capsule_hermes: token missing (set CAPSULE_HERMES_TOKEN or "
|
|
38
|
+
"plugins.entries.capsule.token in config.yaml)"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def _bool(key: str, default: bool) -> bool:
|
|
42
|
+
env = os.environ.get(f"CAPSULE_HERMES_{key.upper()}")
|
|
43
|
+
if env is not None:
|
|
44
|
+
return env.strip().lower() in {"1", "true", "yes", "on"}
|
|
45
|
+
v = raw.get(key, default)
|
|
46
|
+
return bool(v)
|
|
47
|
+
|
|
48
|
+
def _int(key: str, default: int) -> int:
|
|
49
|
+
env = os.environ.get(f"CAPSULE_HERMES_{key.upper()}")
|
|
50
|
+
if env is not None:
|
|
51
|
+
try:
|
|
52
|
+
return int(env)
|
|
53
|
+
except ValueError:
|
|
54
|
+
return default
|
|
55
|
+
v = raw.get(key, default)
|
|
56
|
+
try:
|
|
57
|
+
return int(v)
|
|
58
|
+
except (TypeError, ValueError):
|
|
59
|
+
return default
|
|
60
|
+
|
|
61
|
+
user_raw = raw.get("user")
|
|
62
|
+
return cls(
|
|
63
|
+
endpoint=endpoint,
|
|
64
|
+
token=token,
|
|
65
|
+
block_on_risk=_bool("block_on_risk", True),
|
|
66
|
+
fail_open=_bool("fail_open", True),
|
|
67
|
+
timeout_ms=_int("timeout_ms", 5000),
|
|
68
|
+
user=user_raw if isinstance(user_raw, str) else None,
|
|
69
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Normalize Hermes plugin-runtime kwargs into the Capsule backend DTO shape.
|
|
2
|
+
|
|
3
|
+
The Hermes Python runtime calls plugin hooks with flat kwargs (e.g.
|
|
4
|
+
`tool_name`, `args`, `session_id`, `task_id`). The Capsule agentsecurity
|
|
5
|
+
service expects a normalized envelope with a `context` block, `params`
|
|
6
|
+
instead of `args`, and a required ISO `timestamp`. This module owns that
|
|
7
|
+
translation so the rest of the plugin can stay shape-agnostic.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Any, Mapping
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_CONTEXT_KEYS = (
|
|
17
|
+
"agent_id",
|
|
18
|
+
"session_id",
|
|
19
|
+
"run_id",
|
|
20
|
+
"parent_session_id",
|
|
21
|
+
"gateway",
|
|
22
|
+
"gateway_channel_id",
|
|
23
|
+
"user_id",
|
|
24
|
+
"trace_id",
|
|
25
|
+
"span_id",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _iso_now() -> str:
|
|
30
|
+
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _build_context(kwargs: Mapping[str, Any]) -> dict[str, Any]:
|
|
34
|
+
"""Lift known context fields out of flat kwargs into a `context` dict.
|
|
35
|
+
|
|
36
|
+
`task_id` is mapped to `run_id` to match how the backend interprets the
|
|
37
|
+
per-task scope (a Hermes task ~= one agent run).
|
|
38
|
+
"""
|
|
39
|
+
ctx: dict[str, Any] = {}
|
|
40
|
+
for key in _CONTEXT_KEYS:
|
|
41
|
+
v = kwargs.get(key)
|
|
42
|
+
if isinstance(v, str) and v:
|
|
43
|
+
ctx[key] = v
|
|
44
|
+
task_id = kwargs.get("task_id")
|
|
45
|
+
if isinstance(task_id, str) and task_id and "run_id" not in ctx:
|
|
46
|
+
ctx["run_id"] = task_id
|
|
47
|
+
return ctx
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def normalize(hook_event_name: str, kwargs: Mapping[str, Any]) -> dict[str, Any]:
|
|
51
|
+
"""Return the backend-shaped payload for a Hermes hook invocation.
|
|
52
|
+
|
|
53
|
+
Rules applied to every hook:
|
|
54
|
+
* `hook_event_name` is set explicitly.
|
|
55
|
+
* `timestamp` is filled with ISO-now if Hermes did not provide one.
|
|
56
|
+
* `context` collects session/run/agent/gateway identifiers so the
|
|
57
|
+
backend's `extractSessionId` / `generateAgentExternalId` work.
|
|
58
|
+
|
|
59
|
+
Per-hook rules:
|
|
60
|
+
* `pre_tool_call` / `post_tool_call`: rename `args` to `params`.
|
|
61
|
+
"""
|
|
62
|
+
payload: dict[str, Any] = {
|
|
63
|
+
k: v
|
|
64
|
+
for k, v in kwargs.items()
|
|
65
|
+
if k not in _CONTEXT_KEYS and k != "task_id"
|
|
66
|
+
}
|
|
67
|
+
payload["hook_event_name"] = hook_event_name
|
|
68
|
+
payload["context"] = _build_context(kwargs)
|
|
69
|
+
payload.setdefault("timestamp", _iso_now())
|
|
70
|
+
|
|
71
|
+
if hook_event_name in {"pre_tool_call", "post_tool_call"}:
|
|
72
|
+
if "args" in payload and "params" not in payload:
|
|
73
|
+
payload["params"] = payload.pop("args")
|
|
74
|
+
|
|
75
|
+
return payload
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""HTTP transport for forwarding Hermes hook events to the Capsule agentsecurity service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .config import CapsulePluginConfig
|
|
12
|
+
from .types import HermesHookEnvelope
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class CapsuleResponse:
|
|
17
|
+
ok: bool
|
|
18
|
+
status: int
|
|
19
|
+
body: Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CapsuleTransport:
|
|
23
|
+
"""Synchronous HTTP client that posts hook envelopes to Capsule.
|
|
24
|
+
|
|
25
|
+
Hermes invokes hook callbacks synchronously, so the transport is sync too.
|
|
26
|
+
Non-2xx responses are surfaced via `CapsuleResponse.ok` rather than raised
|
|
27
|
+
— the plugin's dispatch wrapper decides fail-open / fail-closed.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, config: CapsulePluginConfig) -> None:
|
|
31
|
+
self._config = config
|
|
32
|
+
url = config.endpoint.rstrip("/")
|
|
33
|
+
if not url.endswith("/hermes/hooks/events") and not url.endswith("/v1/hermes/hooks/events"):
|
|
34
|
+
url = f"{url}/v1/hermes/hooks/events"
|
|
35
|
+
self._url = url
|
|
36
|
+
|
|
37
|
+
def post(self, envelope: HermesHookEnvelope, correlation_id: str) -> CapsuleResponse:
|
|
38
|
+
headers = {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"Authorization": f"Bearer {self._config.token}",
|
|
41
|
+
"X-Correlation-Id": correlation_id,
|
|
42
|
+
}
|
|
43
|
+
timeout = self._config.timeout_ms / 1000.0
|
|
44
|
+
with httpx.Client(timeout=timeout) as client:
|
|
45
|
+
response = client.post(self._url, json=envelope.to_dict(), headers=headers)
|
|
46
|
+
body: Any = None
|
|
47
|
+
text = response.text
|
|
48
|
+
if text:
|
|
49
|
+
try:
|
|
50
|
+
body = json.loads(text)
|
|
51
|
+
except json.JSONDecodeError:
|
|
52
|
+
body = text
|
|
53
|
+
return CapsuleResponse(ok=response.is_success, status=response.status_code, body=body)
|
capsule_hermes/types.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Shared payload shapes for the Capsule Hermes plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class HermesHookEnvelope:
|
|
11
|
+
"""Top-level envelope POSTed to `/v1/hermes/hooks/events`.
|
|
12
|
+
|
|
13
|
+
Mirrors the request DTO the Capsule agentsecurity service accepts: the raw
|
|
14
|
+
Hermes hook payload plus the user identity and plugin version captured at
|
|
15
|
+
the source.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
hook: Mapping[str, Any]
|
|
19
|
+
user: str
|
|
20
|
+
plugin_version: str
|
|
21
|
+
transcript_lines: list[str] = field(default_factory=list)
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> dict[str, Any]:
|
|
24
|
+
payload: dict[str, Any] = {
|
|
25
|
+
"hook": dict(self.hook),
|
|
26
|
+
"user": self.user,
|
|
27
|
+
"plugin_version": self.plugin_version,
|
|
28
|
+
}
|
|
29
|
+
if self.transcript_lines:
|
|
30
|
+
payload["transcript_lines"] = list(self.transcript_lines)
|
|
31
|
+
return payload
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: capsule-hermes
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Capsule plugin for Hermes — forwards hook events to the Capsule agentsecurity service for centralized policy enforcement and audit.
|
|
5
|
+
Project-URL: Repository, https://github.com/capsulesecurity/capsule
|
|
6
|
+
Project-URL: Documentation, https://docs.capsulesecurity.io/guides/hermes
|
|
7
|
+
Author: Capsule Security
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Keywords: agent-security,capsule,hermes,hermes-plugin,policy,security
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# capsule-hermes
|
|
18
|
+
|
|
19
|
+
Capsule plugin for the [Hermes](https://nousresearch.com/hermes) autonomous AI agent runtime. Forwards Hermes hook events to the Capsule `agentsecurity` service for centralized policy enforcement and audit.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install capsule-hermes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configure
|
|
28
|
+
|
|
29
|
+
Register the plugin in your Hermes runtime config (e.g. `hermes.yaml`):
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
plugins:
|
|
33
|
+
- module: capsule_hermes
|
|
34
|
+
config:
|
|
35
|
+
endpoint: "https://agents.capsulesecurity.io/v1/hermes/hooks/events"
|
|
36
|
+
token: ${CAPSULE_HERMES_TOKEN}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `endpoint` and `token` values are issued by Capsule when you install the Hermes integration in the Capsule portal (**Integrations → Hermes**).
|
|
40
|
+
|
|
41
|
+
### Optional config
|
|
42
|
+
|
|
43
|
+
| Key | Default | Description |
|
|
44
|
+
| --------------- | ------- | -------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `block_on_risk` | `true` | Apply server `block` verdicts. Set to `false` to run the plugin in observe-only mode. |
|
|
46
|
+
| `fail_open` | `true` | Allow the event through if Capsule is unreachable or returns a non-2xx response. Set to `false` to fail closed. |
|
|
47
|
+
| `timeout_ms` | `5000` | Per-request HTTP timeout in milliseconds. |
|
|
48
|
+
|
|
49
|
+
## Hooks covered
|
|
50
|
+
|
|
51
|
+
The plugin registers handlers for the Hermes runtime hooks Capsule supports:
|
|
52
|
+
|
|
53
|
+
- **Blocking:** `pre_tool_call`, `pre_llm_call`, `pre_gateway_dispatch`, `pre_approval_request`
|
|
54
|
+
- **Observation:** `post_tool_call`, `post_llm_call`, `post_approval_response`, `subagent_stop`, `on_session_start`, `on_session_end`, `on_session_finalize`, `on_session_reset`
|
|
55
|
+
|
|
56
|
+
Blocking hooks await Capsule's verdict and return `{"action": "block", "message": ...}` to Hermes when the server denies the action. Observation hooks dispatch fire-and-forget so telemetry never stalls the agent.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
Apache-2.0.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
capsule_hermes/__init__.py,sha256=_pHdzkRSxuv69cOaIOnUBf396dkGJUe1cPy3AJhH83w,4540
|
|
2
|
+
capsule_hermes/config.py,sha256=hx-lAATDwIj8H1gKl1a_hBijcidLbXAhmaKMx-frbnI,2413
|
|
3
|
+
capsule_hermes/normalize.py,sha256=SiqUOgzCqWhK8qSGn_7Weeny-TYGXJhk-UybVe84SQU,2430
|
|
4
|
+
capsule_hermes/transport.py,sha256=xTT1ADE7b0NWam_3RywSF7NrW8ZyEf9NtG65XaHW6Ls,1821
|
|
5
|
+
capsule_hermes/types.py,sha256=7JTAXVUE_F2httNIod25SR0f4HnTFTEtNDoDx9oFWb4,929
|
|
6
|
+
capsule_hermes-0.4.0.dist-info/METADATA,sha256=XHIxJ5RtcCkm93idvoCh0t4m87nxZg13GPf0eu93bYA,2728
|
|
7
|
+
capsule_hermes-0.4.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
capsule_hermes-0.4.0.dist-info/entry_points.txt,sha256=C9gefFW5FPZ5bAXkd0WX4hh_viS5W0x3iI-wZ-t-RYM,48
|
|
9
|
+
capsule_hermes-0.4.0.dist-info/RECORD,,
|