Skip to content

Repository files navigation

Rite

Iris transports things. Rite reacts to things.

Rite is a self-hostable, minimal event-to-action runtime. It receives authenticated events, normalizes them, matches TOML-configured handlers, and forwards matching events to actions.

GitHub webhook      ──> Rite ──> configured HTTP action
Uptime Kuma webhook ──> Rite ──> configured HTTP action
Iris event          ──> Rite ──> configured HTTP action

Quick start

Create rite.toml:

[[rites]]
name = "github-pr-opened"
source = "github"
match = { event_type = "pull_request", action = "opened" }
action = { type = "http_post", url = "https://example.test/hooks/pr" }

[sources.iris]
enabled = true
base_url = "http://127.0.0.1:3000"

# Required when this ingress source is enabled.
[sources.uptime_kuma]
enabled = true
secret = "change-me"

Run it:

RITE_GITHUB_WEBHOOK_SECRET=change-me cargo run -p rite-cli -- --config rite.toml

Endpoints:

  • GET /health — returns ok
  • GET /sources — configured source adapters
  • POST /event/github — authenticated GitHub webhook ingress using X-Hub-Signature-256
  • POST /event/uptime_kuma — authenticated Uptime Kuma webhook ingress using base64 Signature HMAC-SHA256

CLI and MCP

Rite's agent-facing read operations are generated from api/operations.yaml and run through the same execute_operation dispatch as HTTP. They load the local configuration and print JSON, so no HTTP server or webhook secret is required:

cargo run -p rite-cli -- list-sources --config rite.example.toml
cargo run -p rite-cli -- list-handlers --config rite.example.toml
cargo run -p rite-cli -- get-handler github-pr-opened --config rite.example.toml

rite serve --config rite.toml --github-webhook-secret "$RITE_GITHUB_WEBHOOK_SECRET" explicitly starts the server; omitting the subcommand remains equivalent for compatibility. The generated generated/mcp.json declares the same read operations for MCP hosts over the existing HTTP endpoints: GET /sources, GET /handlers, and GET /handlers/{handler_id}. No separate server surface is needed.

When enabled, the Iris source subscribes to GET /v1/events in the background. Set sources.iris.api_token (or RITE_IRIS_API_TOKEN in Docker) when Iris requires bearer authentication; Rite sends it only as the subscription's Authorization: Bearer header, including on reconnects. Without a token Rite warns at startup so legacy unauthenticated Iris deployments remain supported. It reconnects with exponential backoff (1–60 seconds), so Rite remains healthy while Iris is unavailable. Iris messages become source = "iris" events. event_type is the Iris message kind and metadata contains provider, source_id, sender, kind, iris_metadata, and the complete original Iris message under message for future matching needs.

Self-hosting with Docker

Published images are available from GitHub Container Registry after a release tag: ghcr.io/techgodhq/rite:<version> (or :latest). Supply a webhook secret and, when Rite should subscribe to Iris events, its private Iris URL:

docker run --rm \
  --name rite \
  --publish 127.0.0.1:8080:8080 \
  --env RITE_GITHUB_WEBHOOK_SECRET="${RITE_GITHUB_WEBHOOK_SECRET}" \
  --env RITE_IRIS_BASE_URL="http://iris.internal:9876" \
  --env RITE_IRIS_API_TOKEN="${RITE_IRIS_API_TOKEN}" \
  ghcr.io/techgodhq/rite:latest

RITE_GITHUB_WEBHOOK_SECRET is required for authenticated GitHub webhook ingress. Omit RITE_IRIS_BASE_URL when you do not want an Iris subscription. Set RITE_IRIS_API_TOKEN whenever that Iris instance has IRIS_API_TOKEN configured; it is not written to logs. For a complete private-network example with both services, use the Iris repository's deploy/docker-compose.yml.

HTTP actions

http_post forwards the normalized event as JSON by default. A handler may instead set body_template to render a deterministic text body using {{event_type}}, {{action}}, {{title}}, {{body}}, or nested event metadata such as {{metadata.provider}}; missing fields render empty. Optional headers are forwarded verbatim. Templated bodies default to Content-Type: text/plain unless that header is explicitly set. This remains a generic HTTP action: Discord, Slack, and other webhook targets do not add provider-specific Rite action types.

[[rites]]
name = "deploy-alert"
source = "iris"
match = { body_contains = "URGENT" }
action = { type = "http_post", url = "https://hooks.example.test/alerts", headers = { "content-type" = "application/json" }, body_template = "{\"content\":\"{{title}}: {{body}}\"}" }

Compound matching

Flat match tables AND every entry. For OR or negation, compose conditions with all_of, any_of, and not — arbitrarily nestable, with leaf keys keeping exactly the semantics above:

[[rites]]
name = "page-on-critical"
source = "iris"
match = { any_of = [{ severity = "critical" }, { body_contains = "URGENT" }] }
action = { type = "http_post", url = "https://hooks.example.test/pages" }

[[rites]]
name = "real-prs-only"
source = "github"
match = { all_of = [{ event_type = "pull_request" }, { not = { draft = true } }] }
action = { type = "http_post", url = "https://hooks.example.test/reviews" }

all_of and any_of take arrays of condition tables and reject empty arrays; not takes a single condition table. A table mixing operators with other keys — or using two operators as siblings — is rejected at config load with an actionable error. A scalar under an operator-named key (e.g. not = "draft") is an ordinary metadata lookup, exactly as before: the operator only applies to table-shaped values, so existing configurations never change meaning.

Development

cargo build --all-targets
cargo test --all-targets
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --check

Rite normalizes GitHub push and pull_request payloads plus Uptime Kuma heartbeats, matches configured handlers, and executes http_post actions with the normalized event as JSON. Uptime Kuma heartbeats require a configured secret; status maps deterministically to up (info), down (critical), pending (warning), or maintenance (info). Matching-safe metadata includes monitor_name, monitor_id, status, plus present URL/timing fields; Kuma's msg becomes the optional event body.

About

Standalone webhook intake service: receive, normalize, route. LLM-native. Plugs into Iris.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages