Publisher command-line tooling for the Entangled v1.0 protocol, built on the entangled-core library (pinned to tag v0.10.1, spec v1.0-rc.64).
New here? examples/blog is a runnable, end-to-end walkthrough: it authors a post from Markdown, signs the content and the site manifest, and verifies both. It is the quickest way to see what the tool does and how the pieces fit together.
| Command | Status | Purpose |
|---|---|---|
keygen |
working | Key ceremony: derive public material for a role |
content |
working | Convert Markdown into an unsigned content document |
build |
working | Construct and sign a manifest, content, or transaction |
verify |
working | Run the validation pipeline against a document |
init |
working | Scaffold a new site |
Generates a fresh 32-byte key seed, or loads one with --seed-file / --seed-hex, and prints the derived public material. The seed is printed to stdout as hex (with a warning to stderr) for offline storage; the tool never persists it.
publisher: the public key and the 24-word PIP.runtime: the public key (declared in the manifest canary).origin: the public key and the derived Tor v3 onion address.
entangled-tool keygen origin # fresh OS entropy
entangled-tool keygen publisher --seed-file pub.seed # load from a filecontent --markdown <file.md> --path <path> --title <t> --published-at <time> [--seq <n>] [--assets-dir <dir>]
Converts a Markdown file into an unsigned content document (printed to stdout), ready to sign with build content. This saves you from hand-writing the nested block JSON.
The Entangled block grammar is a closed set, so the converter maps the Markdown that fits and rejects, with a clear error, the Markdown that has no Entangled representation rather than dropping it silently:
- Mapped: headings (levels 1-6), paragraphs, bold / italic /
inline code/strikethrough, fenced code blocks (with a language hint), block quotes, flat ordered and unordered lists, horizontal rules (dividers), inline links, and images on their own line. A link to anhttps://URL becomes a citation, to an absolute/patha same-site link, and to anhttp://...onionURL a carrier link. - Rejected: tables, nested lists, raw or inline HTML, footnotes, task lists, math, images mixed into a line of text, and headings past level 6.
For an image  the tool keeps /assets/photo.png as the same-site src and reads the file to fill in the content hash, media type (PNG, JPEG, or WebP), and pixel dimensions, so you do not hand-write them. The file is read from <assets-dir>/assets/photo.png; --assets-dir defaults to the Markdown file's own directory, so keeping the post and its assets/ folder together just works. The image must be on its own line (an Entangled image is a standalone block).
entangled-tool content --markdown post.md --path /articles/my-post \
--title "My post" --published-at 2026-05-07T00:00:00Z > post.unsigned.json
entangled-tool build content --input post.unsigned.json --key-seed-file runtime.seed > post.jsonRuns the document through the entangled-core pipeline and prints the verdict, stopping at the first failing stage. A reject prints the diagnostic code, stage, and message; an accept prints the canary state.
A manifest is driven through the full chain: signature (Stages 2-6), canary (Stage 8), origin binding (Stage 9), and content index (Stage 9b). The stages that need out-of-band context run only when it is supplied:
--nowsets the verified-time reference for the canary and origin-expiry checks (defaults to the current system clock).--fetched-onionis the onion address the manifest was fetched from; with it, Stage 9 origin binding runs. Omit to skip Stage 9.--content-indexis the served/content_index.json; when the manifest declarescontent_root, Stage 9b verifies it (and its absence with a declaredcontent_rootsurfaces the fetch failure).--expected-runtime-pubkeyis the manifest'scanary.runtime_pubkey; for a content or transaction document, it is the key the signature is checked against. Without it, there is no authorized key and the document is rejected withE_SIG_INVALID_KEY(distinct fromE_SIG_VERIFICATION, a bad signature).
Skipped stages are reported, so an accept is never mistaken for a full-pipeline pass.
entangled-tool verify --input manifest.json --fetched-onion dkptfye...onion --content-index content_index.json
entangled-tool verify --input post.json --expected-runtime-pubkey jzFtzi...F7oA content document is signed by a runtime key, and only the manifest declares which runtime key is authorized. So a content or transaction document is verified in the context of its manifest: pass --expected-runtime-pubkey to check its signature against the authorized key. See examples/blog for the full manifest-plus-content flow.
build <kind> --input <unsigned.json> (--key-seed-file <path> | --key-seed-hex <64 hex>) [--now <time>]
Reads the unsigned document JSON (every wire field except sig and kind), deserializes it into the matching unsigned type, signs it with the seed (publisher key for a manifest, runtime key for content and transactions), and prints the signed wire JSON. Exactly one of --key-seed-file or --key-seed-hex is required. A manifest also needs --now (RFC 3339) for the clock-skew check.
entangled-tool build manifest --input manifest.unsigned.json --key-seed-file publisher.seed --now 2026-05-07T00:00:00Z
entangled-tool build content --input post.unsigned.json --key-seed-file runtime.seedA seed is the 32-byte Ed25519 secret from which a signing key is derived (RFC 8032: the key's scalar comes from SHA-512 of the seed). It is not the expanded signing scalar itself, but it is the canonical private-key form: the mapping is deterministic and one-to-one, so anyone who holds the seed can reproduce the signing key and sign on your behalf. Treat it exactly as a private key.
Prefer --seed-file / --key-seed-file over the inline --seed-hex / --key-seed-hex: a value passed on the command line appears in the process argument list (visible to other processes via ps and /proc/<pid>/cmdline) and in your shell history, whereas a file path does not expose the secret itself. Keep seed files with restrictive permissions (e.g. chmod 600). The tool zeroes seed bytes from its own memory after use; keygen prints a fresh seed to stdout for you to store, with a reminder on stderr.
Scaffolds a site under --dir (default .): a manifest.unsigned.json template with REPLACE_WITH_ placeholders, a content/ directory, an nginx.entangled.conf.example, and a README.md with the next steps. It refuses to overwrite existing files. Fill the placeholders with keygen output, then sign with build manifest.
init writes a fail-closed nginx example for the static subset of an
Entangled publisher. Replace the onion host and document root, then connect the
Tor onion service to its loopback listener. It emits signed documents as
application/vnd.entangled+json, the optional content index as
application/json, disables compression and chunking, rejects range requests,
and relies on nginx's static-file path to emit the exact Content-Length.
The example deliberately does not implement submit endpoints. A submit handler
must accept application/vnd.entangled-submit+json by parsing Content-Type as
an RFC 9110 media type, comparing type and subtype case-insensitively, and then
requiring the parsed parameter list to be empty. It must not compare raw header
bytes: Application/Vnd.Entangled-Submit+JSON is valid, while both
;charset=utf-8 and ; charset=utf-8 are rejected after parsing.
Simultaneous multi-origin publication is not supported by v1 clients that keep
publisher history. Use the signed migration_pointer successor flow to replace
an origin; do not serve two origins as concurrently current under one publisher
key.
Requires Rust 1.88+ (the entangled-core MSRV).
cargo build --releaseThe entangled-core dependency is fetched from git by tag; bump the tag in Cargo.toml when the library advances.
MIT OR Apache-2.0.