Keryx (Greek κῆρυξ, pronounced KEH-riks — /ˈkɛrɪks/) is a
self-hosted publishing service for agents: server, CLI, and TUI in a single
Rust binary. An agent hands Keryx a static HTML document — a plan, proposal,
brief, or report — and Keryx proclaims it at a URL that serves the exact
uploaded bytes to every client: browsers, curl, and agent fetch tools alike.
In the ancient Greek world the kēryx was the herald. Under the protection of Hermes and carrying the kerykeion (the herald's staff, better known by its Latin name, the caduceus), heralds were inviolable: they crossed battle lines untouched, convened assemblies, and delivered proclamations between kings, armies, and gods. Homer's heralds — Talthybius for Agamemnon, Eurybates for Odysseus — were trusted to carry a message faithfully and repeat it exactly as given, word for word.
That is precisely the contract this tool makes. Your agents compose the message; Keryx carries it and repeats it byte for byte — no wrapper pages, no rewriting, no consent interstitials — to whoever holds the URL.
- Publish —
keryx upload plan.htmlreturns a public URL and a raw URL. Re-uploading the same file adds a new version; old versions stay addressable at/d/<id>/v/<n>. - Serve — every draft URL returns the exact uploaded HTML with a strict
Content-Security-Policy and
X-Keryx-Draft-Id/X-Keryx-Draft-Versionheaders. The CSP never alters the bytes; it only constrains what the page may do in a browser (no script execution, no network, no form posts). - Ship —
keryx publish --id <id> --output report.pdfrenders the latest immutable version as a paginated A4 PDF.--version <n>selects an older version. The browser-free Fulgur renderer runs on the server, returns bytes without storing a PDF, and the client writes the destination atomically. - Browse — a server-rendered dashboard at
/, akeryx listcommand, and a full TUI (keryx tui) for browsing, opening, and deleting drafts. - Stay small — one binary, a SQLite index for metadata (default
~/.keryx/keryx.db), and the HTML stored as plain files on disk (default~/.keryx/drafts/<draft-id>/<version-id>.html) — easy to inspect, grep, and back up. No external database, no object storage, no OAuth. A single optional API key covers the private bits.
cargo build --release # produces target/release/keryxkeryx serve| Flag / env | Default | Purpose |
|---|---|---|
--port / KERYX_PORT |
7812 |
Listen port |
--host / KERYX_HOST |
127.0.0.1 |
Bind address |
--db / KERYX_DB |
~/.keryx/keryx.db |
SQLite path (metadata index) |
--data-dir / KERYX_DATA_DIR |
~/.keryx |
Root for stored HTML files (written under drafts/) |
--public-base-url / KERYX_PUBLIC_BASE_URL |
request Host header | Base for returned links |
--api-key / KERYX_API_KEY |
unset (open) | Require this Bearer key for mutations, listings, and PDFs |
--max-html-bytes / KERYX_MAX_HTML_BYTES |
524288 |
Upload size cap |
With KERYX_API_KEY set, uploads, listings, deletes, and PDF publication
require the key as a Bearer token; draft serving stays public. With no key, everything is open —
fine on a trusted LAN. The dashboard at / is public either way (it is meant
for your own machine).
Routes: POST /api/uploads, GET/DELETE /api/drafts[/:id],
GET /api/drafts/:id/pdf[?version=n]
(DELETE ...?purge=true for a hard delete), POST /api/drafts/:id/disable,
POST /api/purge, GET /d/:id[/raw], GET /d/:id/v/:n[/raw],
GET /healthz.
keryx upload ./plan.html --description "Q3 migration plan"
keryx list [--json]
keryx raw <draft-id> [-v N] # exact HTML to stdout
keryx publish --id <draft-id> --output ./report.pdf [--version N]
keryx open <draft-id> # open in browser
keryx delete <draft-id> [--yes] # soft delete: stops serving, keeps data
keryx delete <draft-id> --purge # hard delete: removes rows and files, no undo
keryx purge [--yes] # hard-delete everything already soft-deleted
keryx auth set <api-key> # verified against the server, then stored
keryx auth clearThe API URL resolves as: --api-url flag > KERYX_API_URL env >
~/.keryx/config.json > http://localhost:7812. Persist a non-default URL
once with keryx auth set <key> --api-url http://myhost:7812 (or edit
~/.keryx/config.json).
Re-uploading the same file path updates the same draft as a new version;
--new forces a fresh draft, --draft <id> targets a specific one. Uploads
also record best-effort git provenance (branch, commit, dirty state, repo)
for display in listings — never for authorization.
publish is deliberately Keryx-specific: the endpoint accepts a draft ID and
optional version, never arbitrary HTML. The server resolves that stored HTML,
adds a title/version header and publication-date/page footer to a render-only
copy, and returns the PDF without creating a new Keryx version or writing a PDF
on the server. The CLI refuses to overwrite an existing output file.
keryx tui [--api-url http://myhost:7812]j/k move · Enter version history · o open in browser · y show raw
URL · d soft delete · D purge (permanent) · r refresh · q quit.
Both delete keys ask for confirmation.
Uploads may contain inline classic <script> blocks. Rejected at upload time:
external script sources, module scripts, inline event handlers (on*),
javascript:/vbscript:/file: URLs, <form>, <iframe>/<object>/
<embed>/<applet>, <base>, <link>, srcdoc, meta-refresh, and unsafe
inline CSS. Once stored, drafts are served verbatim.
PDF publication supports semantic HTML, paginated tables, inline SVG diagrams,
and base64-embedded PNG, JPEG, and GIF <img> elements. A body-level header
becomes the cover, and top-level sections begin on new pages. Use
keryx-page-flow to keep a section in normal flow, keryx-page-break to force
another element onto a new page, and data-keryx-print="stack" to flatten a
custom multi-column layout for A4. Script-generated content, <canvas>, CSS
imports, external images, and CSS image URLs are rejected for deterministic
publication. An empty data-keryx-version element is filled with the selected
version in the render copy.
Write a complete static HTML file, then:
keryx upload ./plan.htmlHand the printed Raw HTML URL to other agents — curl <url> returns the
document itself. Repo-local agent workflows live in
skills/html-communication/SKILL.md,
skills/keryx-read/SKILL.md, and
skills/keryx-publish/SKILL.md.
This project is a Rust reimplementation derived from PostPlan.
PostPlan is Copyright (c) 2026 t3dotgg and was distributed under the MIT License. A copy of its original license is available in LICENSES/PostPlan-v0.0.4-MIT.md
All new implementation work is Copyright (c) 2026 SimCube Ltd and is distributed under the MIT License in the root LICENSE file.
This project is independently maintained by SimCube Ltd and is not affiliated with or endorsed by the original PostPlan author: Theo Browne (t3dotgg).