keryx publish¶
Description¶
Publish an article's approved outlets to a social platform. (Until spec 0061
this command was keryx post; post is now the noun for a social-media post
of an article, and publish is the verb.) The target is a platform name
(any compiled-in publisher, listed below), all (every enabled + approved
outlet in scope), or due (the approved outlets whose hour has come — the
unattended CI entrypoint). The scope is --article <dir> (the whole
announcement set), narrowed with --reel
<slug> or --post <id> to one piece, or --workspace <slug> (a reel, through
its bundle:); publish due with none sweeps every article the project's
content.sources name, which is what a scheduled pipeline runs, hourly.
Posting is gated and idempotent:
- Refused unless
approved(keryx approvefirst) — the accidental-posting guard (R-POST-2). - An already-
postedplatform is a no-op unless--force(R-POST-3); on success it recordsposted+posted_at+post_urlinsocial.json. --dry-runruns the same pre-flight a real post does and posts nothing (R-POST-1): the platform's media contract (a reel adapter refuses a post with no video, 0060 D3), the rendered reel's presence, and the copy against the platform's constraints. It then reports whether a credential was there to construct the publisher, ascredentials: presentorcredentials: absent, and passes either way, so a pipeline holding no secrets can still prove a set is sendable. For a platform that fetches the image by URL (Instagram, Threads) it also probes that URL and reportsimage URL unreachable (…)without failing; a real send refuses on the same probe, so the platform never fetches a 404 and remembers it.publish allsends each platform independently — one platform failing doesn't abort the others, and the run exits non-zero if any failed (R-POST-5..8).publish dueis the only command CI runs unattended (R-POST-13).- One outlet per platform per run. A platform with several outlets due
(a reel and a post, say) sends the earliest due; the rest report
deferredand go on the next run (spec 0060 D5). - A disabled platform is reported, not sent.
allandduereport an approved outlet on a platform withoutplatforms.<name>.enabled: trueasskipped (platform not enabled; set platforms.<name>.enabled), every run, and leave itapproved, so it goes on the first run after the platform is enabled (spec 0060 D7). It does not fail the run. - An empty run names its scope.
publish duewith nothing to send printsnothing due; swept every articlefor a sweep, ornothing due in <dir>(or the post, reel or workspace) for a scoped run.
A reel piece sends its workspace's reel-<slug>.mp4 (reel build's output); a
post piece sends its image (the bundle file, or the public URL for a platform
that takes only a URL) when it has one, and text alone when it has not, if the
platform's contract allows. The copy comes from the article's social.json
(keryx social).
Usage¶
keryx publish <platform|all|due> [--article <dir> [--reel <slug> | --post <id>] | --workspace <slug>] [--dry-run [--require-credentials]] [--force]
keryx publish due # the hourly pipeline job: every article, every due outlet
keryx publish all --workspace my-post --dry-run # prove the reel's outlets are sendable, no secrets needed
keryx publish bluesky --article content/post/my-post --post launch # send one post's outlet now
Nine publishers are compiled in: bluesky, facebook, instagram,
linkedin, mastodon, threads, tiktok, x and youtube. Each one's
credentials are set up in its own how-to under How-to.
A platform name with no compiled-in publisher fails with an error that lists the
ones that are.
With an object store configured (spec 0039), publish first pulls any media a
reel piece's workspace pins in media.lock but is missing locally — the rendered
reel included — so a fresh CI clone posts without an explicit keryx media pull.
Instagram¶
The Instagram Reels adapter posts to a Professional (Business/Creator) account under Standard Access (no App Review). Configure:
platforms.instagram.enabled: trueandplatforms.instagram.user_id: <id>in config (non-secret).INSTAGRAM_ACCESS_TOKENin the environment (a long-lived token withinstagram_business_content_publish) — never committed;keryx auth instagramcaptures it.
It runs the container-publish flow with the resumable direct upload (no public
URL): create a REELS container → upload → poll to FINISHED → publish → record
the permalink. The caption is the platform's text + hashtags.
Flags¶
| Flag | Description | Default | Required |
|---|---|---|---|
--article |
the article's bundle directory (its announcement set) | ||
--reel |
narrow to one reel piece (a workspace slug) | ||
--post |
narrow to one post piece (its id) | ||
--workspace |
shorthand for --article <its bundle> --reel <slug> |
||
--dry-run |
run the pre-flight, report credential presence, send nothing | ||
--require-credentials |
with --dry-run, fail when a credential is absent rather than reporting it |
||
--force |
re-send an already-published outlet |