The idea
Keep your posts as markdown files in a git repo. Run syndicate push, and the CLI renders each post for every target you've configured — your own site, Ghost or Buttondown, dev.to, Mastodon, Bluesky, LinkedIn — and publishes it. The state that makes this work is a syndicate.lock file committed alongside the posts: for every post-and-target pair it records the remote ID, the URL, and a content hash. Edit a post and push again, and the CLI updates the remotes that support editing instead of creating duplicates.
Why build this
Anyone who writes regularly ends up posting the same piece in four places by hand. The copy-paste itself is annoying, but the real cost is drift: you fix a typo on your blog and the dev.to copy keeps the error forever, canonical tags are missing so search engines pick the wrong winner, and nobody can tell you which platforms a given post actually reached.
Existing tools solve the scheduling half of this — Buffer, Typefully, Publer — but they own your content. Your posts live in their database, and syndication is a one-shot broadcast with no notion of updates. Writers who already keep a blog in git want the opposite: files are the source of truth, and the tool is a reconciler, not a CMS.
Now is a reasonable time because the API surface finally supports it. Bluesky's AT Protocol and Mastodon's REST API are both open and unmetered for personal use, dev.to and Hashnode have straightforward article APIs with a canonical_url field, and Ghost's Admin API accepts markdown directly. The awkward remaining case is LinkedIn, which is worth treating as its own problem.
Stack sketch
- CLI: TypeScript on Node, distributed as an npm package. Commander for arg parsing,
@clack/promptsfor the interactive setup. - Post format: markdown with YAML frontmatter (
gray-matter). Frontmatter declarestargets:and per-target overrides — a shorter title for Bluesky, a different tag set for dev.to. - Rendering:
unified/remarkpipeline with per-target plugins. Strip footnotes for platforms that can't render them, rewrite relative image paths to absolute URLs, thread anything over a character limit. - State:
syndicate.lockas sorted JSON — diffable, mergeable, human-readable in a PR. - Adapters: one module per platform behind a small interface (
create,update,delete,capabilities). Start with Mastodon, Bluesky (@atproto/api), dev.to, and Ghost. - Secrets: tokens from environment variables only, resolved via
dotenv, never written to the lockfile. - Images: upload through each platform's media endpoint, cache the returned remote URL in the lockfile keyed by local file hash.
Scope for v1
In:
syndicate init,syndicate status,syndicate push, andsyndicate push --dry-runshowing a per-target diff.- Four adapters: Mastodon, Bluesky, dev.to, Ghost.
- Canonical URL handling — you nominate one target as canonical, everything else points back to it.
- Content-hash change detection with update-in-place where the platform allows it.
Out:
- Scheduling. Cron plus
syndicate pushcovers it. - Analytics or engagement dashboards.
- A web UI of any kind.
- Comment or reply syncing back into the repo.
Where it could go
The natural second step is CI. A GitHub Action that runs push on merge to main turns publishing into a pull request review — you see the rendered per-platform output as a comment before anything goes live, and the lockfile update lands in the same commit. That alone makes the tool worth adopting for anyone already running a docs or blog repo.
After that, the adapter interface is the product. Publish the interface, let people ship adapters for Substack, Medium, Telegram channels, or an internal Slack canvas, and the CLI becomes a small ecosystem rather than a fixed list of integrations. A paid tier could then be a hosted runner that holds tokens for people who don't want secrets in CI, plus a link-health check that flags when a syndicated copy has been edited or removed out from under you.
Watch out for
Platform terms are the real constraint: LinkedIn and Medium both restrict automated posting, and Substack has no official write API, so promising those targets invites a broken product and possibly a banned account. Ship only platforms with a documented write API and say plainly which ones you won't support and why.