Every ink-drop stage reads and writes plain folders, and the folder is the only interface between them. A tool that keeps the three rules below is a stage, in any language, and a tool that reads a folder is a sink: Syncthing, an OPDS server, a tablet's own library.
- An item is one document,
<name>.epub,<name>.pdfor<name>.html, with one JSON sidecar at.ink/<name>.<ext>.jsonin the same folder. The hidden.ink/keeps sidecars out of a reader's book list. - The sidecar is written before the document, and each file lands by rename from a temporary file. A sink therefore never sees half a document, nor a document without its sidecar.
- A stage reads item paths on stdin, one per line, writes its output items,
and prints each output path on stdout. A stage that starts a chain,
ink-fetchfor one, reads URLs instead. Stages chain with a pipe and run from cron orxargsunchanged.
echo https://example.org/post | ink-fetch | ink-readable | ink-epub -to ~/Books.ink/feeds/ holds the ids of the entries ink-feed has already seen, one
file per feed.
{
"schema": 1,
"file": "post.epub",
"title": "A post",
"source": "https://example.org/post",
"fetched": "2026-10-10T09:00:00Z",
"stage": "ink-epub"
}A reader of a sidecar rejects any schema it does not know, and any file
other than the document's own name. A sidecar is data: it is parsed, never
evaluated. folder.Put and
folder.Get implement both rules for Go
stages.