feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
"""hermes webhook — manage dynamic webhook subscriptions from the CLI.
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
hermes webhook subscribe <name> [options]
|
|
|
|
|
hermes webhook list
|
|
|
|
|
hermes webhook remove <name>
|
|
|
|
|
hermes webhook test <name> [--payload '{"key": "value"}']
|
|
|
|
|
|
|
|
|
|
Subscriptions persist to ~/.hermes/webhook_subscriptions.json and are
|
|
|
|
|
hot-reloaded by the webhook adapter without a gateway restart.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import secrets
|
|
|
|
|
import time
|
|
|
|
|
from pathlib import Path
|
refactor: codebase-wide lint cleanup — unused imports, dead code, and inefficient patterns (#5821)
Comprehensive cleanup across 80 files based on automated (ruff, pyflakes, vulture)
and manual analysis of the entire codebase.
Changes by category:
Unused imports removed (~95 across 55 files):
- Removed genuinely unused imports from all major subsystems
- agent/, hermes_cli/, tools/, gateway/, plugins/, cron/
- Includes imports in try/except blocks that were truly unused
(vs availability checks which were left alone)
Unused variables removed (~25):
- Removed dead variables: connected, inner, channels, last_exc,
source, new_server_names, verify, pconfig, default_terminal,
result, pending_handled, temperature, loop
- Dropped unused argparse subparser assignments in hermes_cli/main.py
(12 instances of add_parser() where result was never used)
Dead code removed:
- run_agent.py: Removed dead ternary (None if False else None) and
surrounding unreachable branch in identity fallback
- run_agent.py: Removed write-only attribute _last_reported_tool
- hermes_cli/providers.py: Removed dead @property decorator on
module-level function (decorator has no effect outside a class)
- gateway/run.py: Removed unused MCP config load before reconnect
- gateway/platforms/slack.py: Removed dead SessionSource construction
Undefined name bugs fixed (would cause NameError at runtime):
- batch_runner.py: Added missing logger = logging.getLogger(__name__)
- tools/environments/daytona.py: Added missing Dict and Path imports
Unnecessary global statements removed (14):
- tools/terminal_tool.py: 5 functions declared global for dicts
they only mutated via .pop()/[key]=value (no rebinding)
- tools/browser_tool.py: cleanup thread loop only reads flag
- tools/rl_training_tool.py: 4 functions only do dict mutations
- tools/mcp_oauth.py: only reads the global
- hermes_time.py: only reads cached values
Inefficient patterns fixed:
- startswith/endswith tuple form: 15 instances of
x.startswith('a') or x.startswith('b') consolidated to
x.startswith(('a', 'b'))
- len(x)==0 / len(x)>0: 13 instances replaced with pythonic
truthiness checks (not x / bool(x))
- in dict.keys(): 5 instances simplified to in dict
- Redefined unused name: removed duplicate _strip_mdv2 import in
send_message_tool.py
Other fixes:
- hermes_cli/doctor.py: Replaced undefined logger.debug() with pass
- hermes_cli/config.py: Consolidated chained .endswith() calls
Test results: 3934 passed, 17 failed (all pre-existing on main),
19 skipped. Zero regressions.
2026-04-07 10:25:31 -07:00
|
|
|
from typing import Dict
|
feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
|
2026-03-28 23:47:21 -07:00
|
|
|
from hermes_constants import display_hermes_home
|
|
|
|
|
|
feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
|
|
|
|
|
_SUBSCRIPTIONS_FILENAME = "webhook_subscriptions.json"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _hermes_home() -> Path:
|
refactor: replace inline HERMES_HOME re-implementations with get_hermes_home()
16 callsites across 14 files were re-deriving the hermes home path
via os.environ.get('HERMES_HOME', ...) instead of using the canonical
get_hermes_home() from hermes_constants. This breaks profiles — each
profile has its own HERMES_HOME, and the inline fallback defaults to
~/.hermes regardless.
Fixed by importing and calling get_hermes_home() at each site. For
files already inside the hermes process (agent/, hermes_cli/, tools/,
gateway/, plugins/), this is always safe. Files that run outside the
process context (mcp_serve.py, mcp_oauth.py) already had correct
try/except ImportError fallbacks and were left alone.
Skipped: hermes_constants.py (IS the implementation), env_loader.py
(bootstrap), profiles.py (intentionally manipulates the env var),
standalone scripts (optional-skills/, skills/), and tests.
2026-04-07 10:40:34 -07:00
|
|
|
from hermes_constants import get_hermes_home
|
|
|
|
|
return get_hermes_home()
|
feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _subscriptions_path() -> Path:
|
|
|
|
|
return _hermes_home() / _SUBSCRIPTIONS_FILENAME
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_subscriptions() -> Dict[str, dict]:
|
|
|
|
|
path = _subscriptions_path()
|
|
|
|
|
if not path.exists():
|
|
|
|
|
return {}
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
return data if isinstance(data, dict) else {}
|
|
|
|
|
except Exception:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _save_subscriptions(subs: Dict[str, dict]) -> None:
|
|
|
|
|
path = _subscriptions_path()
|
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
tmp_path = path.with_suffix(".tmp")
|
|
|
|
|
tmp_path.write_text(
|
|
|
|
|
json.dumps(subs, indent=2, ensure_ascii=False),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
os.replace(str(tmp_path), str(path))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_webhook_config() -> dict:
|
|
|
|
|
"""Load webhook platform config. Returns {} if not configured."""
|
|
|
|
|
try:
|
|
|
|
|
from hermes_cli.config import load_config
|
|
|
|
|
cfg = load_config()
|
|
|
|
|
return cfg.get("platforms", {}).get("webhook", {})
|
|
|
|
|
except Exception:
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_webhook_enabled() -> bool:
|
|
|
|
|
return bool(_get_webhook_config().get("enabled"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_webhook_base_url() -> str:
|
|
|
|
|
wh = _get_webhook_config().get("extra", {})
|
|
|
|
|
host = wh.get("host", "0.0.0.0")
|
|
|
|
|
port = wh.get("port", 8644)
|
|
|
|
|
display_host = "localhost" if host == "0.0.0.0" else host
|
|
|
|
|
return f"http://{display_host}:{port}"
|
|
|
|
|
|
|
|
|
|
|
2026-03-28 23:47:21 -07:00
|
|
|
def _setup_hint() -> str:
|
|
|
|
|
_dhh = display_hermes_home()
|
|
|
|
|
return f"""
|
feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
Webhook platform is not enabled. To set it up:
|
|
|
|
|
|
|
|
|
|
1. Run the gateway setup wizard:
|
|
|
|
|
hermes gateway setup
|
|
|
|
|
|
2026-03-28 23:47:21 -07:00
|
|
|
2. Or manually add to {_dhh}/config.yaml:
|
feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
platforms:
|
|
|
|
|
webhook:
|
|
|
|
|
enabled: true
|
|
|
|
|
extra:
|
|
|
|
|
host: "0.0.0.0"
|
|
|
|
|
port: 8644
|
|
|
|
|
secret: "your-global-hmac-secret"
|
|
|
|
|
|
2026-03-28 23:47:21 -07:00
|
|
|
3. Or set environment variables in {_dhh}/.env:
|
feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
WEBHOOK_ENABLED=true
|
|
|
|
|
WEBHOOK_PORT=8644
|
|
|
|
|
WEBHOOK_SECRET=your-global-secret
|
|
|
|
|
|
|
|
|
|
Then start the gateway: hermes gateway run
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _require_webhook_enabled() -> bool:
|
|
|
|
|
"""Check webhook is enabled. Print setup guide and return False if not."""
|
|
|
|
|
if _is_webhook_enabled():
|
|
|
|
|
return True
|
2026-03-28 23:47:21 -07:00
|
|
|
print(_setup_hint())
|
feat(webhook): hermes webhook CLI + skill for event-driven subscriptions (#3578)
Adds 'hermes webhook' CLI subcommand and a skill — zero new model tools.
CLI commands (require webhook platform to be enabled):
hermes webhook subscribe <name> [--events, --prompt, --deliver, ...]
hermes webhook list
hermes webhook remove <name>
hermes webhook test <name>
All commands gate on webhook platform being enabled in config. If not
configured, prints setup instructions (gateway setup wizard, manual
config.yaml, or env vars).
The agent uses these via terminal tool, guided by the webhook-subscriptions
skill which documents setup, common patterns (GitHub, Stripe, CI/CD,
monitoring), prompt template syntax, security, and troubleshooting.
Adapter enhancement: webhook.py hot-reloads dynamic subscriptions from
~/.hermes/webhook_subscriptions.json on each incoming request (mtime-gated).
Static config.yaml routes always take precedence.
Docs: updated webhooks.md with Dynamic Subscriptions section, added
hermes webhook to cli-commands.md reference.
No new model tools. No toolset changes.
24 new tests for CLI CRUD, persistence, enabled-gate, and adapter
dynamic route loading.
2026-03-28 14:33:35 -07:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def webhook_command(args):
|
|
|
|
|
"""Entry point for 'hermes webhook' subcommand."""
|
|
|
|
|
sub = getattr(args, "webhook_action", None)
|
|
|
|
|
|
|
|
|
|
if not sub:
|
|
|
|
|
print("Usage: hermes webhook {subscribe|list|remove|test}")
|
|
|
|
|
print("Run 'hermes webhook --help' for details.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not _require_webhook_enabled():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if sub in ("subscribe", "add"):
|
|
|
|
|
_cmd_subscribe(args)
|
|
|
|
|
elif sub in ("list", "ls"):
|
|
|
|
|
_cmd_list(args)
|
|
|
|
|
elif sub in ("remove", "rm"):
|
|
|
|
|
_cmd_remove(args)
|
|
|
|
|
elif sub == "test":
|
|
|
|
|
_cmd_test(args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_subscribe(args):
|
|
|
|
|
name = args.name.strip().lower().replace(" ", "-")
|
|
|
|
|
if not re.match(r'^[a-z0-9][a-z0-9_-]*$', name):
|
|
|
|
|
print(f"Error: Invalid name '{name}'. Use lowercase alphanumeric with hyphens/underscores.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
subs = _load_subscriptions()
|
|
|
|
|
is_update = name in subs
|
|
|
|
|
|
|
|
|
|
secret = args.secret or secrets.token_urlsafe(32)
|
|
|
|
|
events = [e.strip() for e in args.events.split(",")] if args.events else []
|
|
|
|
|
|
|
|
|
|
route = {
|
|
|
|
|
"description": args.description or f"Agent-created subscription: {name}",
|
|
|
|
|
"events": events,
|
|
|
|
|
"secret": secret,
|
|
|
|
|
"prompt": args.prompt or "",
|
|
|
|
|
"skills": [s.strip() for s in args.skills.split(",")] if args.skills else [],
|
|
|
|
|
"deliver": args.deliver or "log",
|
|
|
|
|
"created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if args.deliver_chat_id:
|
|
|
|
|
route["deliver_extra"] = {"chat_id": args.deliver_chat_id}
|
|
|
|
|
|
|
|
|
|
subs[name] = route
|
|
|
|
|
_save_subscriptions(subs)
|
|
|
|
|
|
|
|
|
|
base_url = _get_webhook_base_url()
|
|
|
|
|
status = "Updated" if is_update else "Created"
|
|
|
|
|
|
|
|
|
|
print(f"\n {status} webhook subscription: {name}")
|
|
|
|
|
print(f" URL: {base_url}/webhooks/{name}")
|
|
|
|
|
print(f" Secret: {secret}")
|
|
|
|
|
if events:
|
|
|
|
|
print(f" Events: {', '.join(events)}")
|
|
|
|
|
else:
|
|
|
|
|
print(" Events: (all)")
|
|
|
|
|
print(f" Deliver: {route['deliver']}")
|
|
|
|
|
if route.get("prompt"):
|
|
|
|
|
prompt_preview = route["prompt"][:80] + ("..." if len(route["prompt"]) > 80 else "")
|
|
|
|
|
print(f" Prompt: {prompt_preview}")
|
|
|
|
|
print(f"\n Configure your service to POST to the URL above.")
|
|
|
|
|
print(f" Use the secret for HMAC-SHA256 signature validation.")
|
|
|
|
|
print(f" The gateway must be running to receive events (hermes gateway run).\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_list(args):
|
|
|
|
|
subs = _load_subscriptions()
|
|
|
|
|
if not subs:
|
|
|
|
|
print(" No dynamic webhook subscriptions.")
|
|
|
|
|
print(" Create one with: hermes webhook subscribe <name>")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
base_url = _get_webhook_base_url()
|
|
|
|
|
print(f"\n {len(subs)} webhook subscription(s):\n")
|
|
|
|
|
for name, route in subs.items():
|
|
|
|
|
events = ", ".join(route.get("events", [])) or "(all)"
|
|
|
|
|
deliver = route.get("deliver", "log")
|
|
|
|
|
desc = route.get("description", "")
|
|
|
|
|
print(f" ◆ {name}")
|
|
|
|
|
if desc:
|
|
|
|
|
print(f" {desc}")
|
|
|
|
|
print(f" URL: {base_url}/webhooks/{name}")
|
|
|
|
|
print(f" Events: {events}")
|
|
|
|
|
print(f" Deliver: {deliver}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_remove(args):
|
|
|
|
|
name = args.name.strip().lower()
|
|
|
|
|
subs = _load_subscriptions()
|
|
|
|
|
|
|
|
|
|
if name not in subs:
|
|
|
|
|
print(f" No subscription named '{name}'.")
|
|
|
|
|
print(" Note: Static routes from config.yaml cannot be removed here.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
del subs[name]
|
|
|
|
|
_save_subscriptions(subs)
|
|
|
|
|
print(f" Removed webhook subscription: {name}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_test(args):
|
|
|
|
|
"""Send a test POST to a webhook route."""
|
|
|
|
|
name = args.name.strip().lower()
|
|
|
|
|
subs = _load_subscriptions()
|
|
|
|
|
|
|
|
|
|
if name not in subs:
|
|
|
|
|
print(f" No subscription named '{name}'.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
route = subs[name]
|
|
|
|
|
secret = route.get("secret", "")
|
|
|
|
|
base_url = _get_webhook_base_url()
|
|
|
|
|
url = f"{base_url}/webhooks/{name}"
|
|
|
|
|
|
|
|
|
|
payload = args.payload or '{"test": true, "event_type": "test", "message": "Hello from hermes webhook test"}'
|
|
|
|
|
|
|
|
|
|
import hmac
|
|
|
|
|
import hashlib
|
|
|
|
|
sig = "sha256=" + hmac.new(
|
|
|
|
|
secret.encode(), payload.encode(), hashlib.sha256
|
|
|
|
|
).hexdigest()
|
|
|
|
|
|
|
|
|
|
print(f" Sending test POST to {url}")
|
|
|
|
|
try:
|
|
|
|
|
import urllib.request
|
|
|
|
|
req = urllib.request.Request(
|
|
|
|
|
url,
|
|
|
|
|
data=payload.encode(),
|
|
|
|
|
headers={
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"X-Hub-Signature-256": sig,
|
|
|
|
|
"X-GitHub-Event": "test",
|
|
|
|
|
},
|
|
|
|
|
method="POST",
|
|
|
|
|
)
|
|
|
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
|
|
|
body = resp.read().decode()
|
|
|
|
|
print(f" Response ({resp.status}): {body}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f" Error: {e}")
|
|
|
|
|
print(" Is the gateway running? (hermes gateway run)")
|