Working name — a lighter, smaller thing than a Ghost. Find-and-replace freely.
An agent-first, minimal, open-source publishing system for programmatic blogs and news sites. Articles are MDX files in Git, written and optimized by agents, rendered by a deployable frontend with SEO and AI-search (GEO) built into the core. No database, no CMS UI, no human-editor bloat.
Ghost gave humans a beautiful editor and a database. Strand gives agents a strict schema, a Git repo, and a set of skills — and keeps only the SEO/structured-data core that actually earns traffic.
Everything an editor does — drafting, SEO, structured data, scheduling, publishing — an agent can now do against a well-defined contract. So Strand throws out the database, the WYSIWYG editor, members/subscriptions, newsletters, theme marketplaces, and roles, and keeps the ~15% of a CMS that matters: a validated content schema and a publishing-quality core. Then it pushes that core further than Ghost ever did, toward AI search engines.
See DESIGN.md for the full architecture, the keep/cut analysis, and diagrams.
| Package | What it is | Status |
|---|---|---|
packages/core |
@strand-cms/core — content schema (Zod), MDX loader, and the SEO/GEO generators (sitemap, JSON-LD, RSS, robots, llms.txt, the .md endpoint) |
✅ built + verified |
packages/create-strand |
The npm create strand interactive scaffolder |
✅ built + verified |
packages/cli |
@strand-cms/cli — the strand bin: the MCP server (real git + filesystem ops) and strand validate |
✅ built + verified |
skills/ |
Five native skills (strand-publish, strand-content-schema, strand-fact-check-cite, strand-review-gate, humanizer) + the skill install/dedup resolver |
✅ |
profile-dist/ |
A reference Hermes editor profile distribution (SOUL + config + cron) | ✅ |
packages/next |
@strand-cms/next — the default theme: a Next.js publication with the strand rail, MDX rendering, and all SEO/GEO routes |
✅ built + verified |
packages/content-api |
@strand-cms/content-api — typed query layer + framework-agnostic JSON API (Hono/Next/Bun) + typed client + static snapshot |
✅ built + verified |
Every code package typechecks under strict + noUncheckedIndexedAccess and has been
exercised against real inputs (MDX fixtures, a temp git repo, and a live MCP stdio session).
npm create strand@latestThe scaffolder asks for: frontend (Next.js / headless / Astro), analytics (cookieless Plausible by default), author personas, an optional subscriptions adapter, agent integration mode, which marketing skills to install, and — for MCP/Both — whether to provision a dedicated Hermes editor profile. AI-search optimization is always on.
It writes a ready-to-run project: the content schema, sample content, the frontend with all SEO/GEO routes wired, CI frontmatter validation, a pre-commit hook, the chosen adapters, the skill set (installing only what's missing), and the editor profile.
Strand is driven three ways, chosen at scaffold time:
- Skills + CLI — marketing skills + native skills installed into your terminal coding agent. Human-in-the-loop.
- Skills + MCP — the
strand mcpserver exposes structured tools (create_draft,validate_post,publish_post,get_analytics, …) to any MCP client. Headless. - Both — and this unlocks a dedicated Hermes editor profile: a separate agent
(its own SOUL, skills, cron, MCP connection) that owns the publication and operates
inside the blog repo via
terminal.cwd. Emitted as a reproducible profile distribution.
Skills come from the coreyhaines31/marketingskills
pack (tiered into mandatory / optional / subscription-gated) plus three native skills for
Strand's own mechanics. The publishing core: programmatic-seo, ai-seo, schema-markup,
site-architecture, seo-audit, content-strategy, copywriting, copy-editing,
analytics-tracking, plus the natives: strand-content-schema, strand-fact-check-cite,
strand-publish, strand-review-gate (the pre-publish audit that catches off-topic bodies
deterministic validation cannot), and humanizer (MIT, from
blader/humanizer — mandatory on all content output;
AI-pattern prose kills credibility). The resolver treats skills already resolvable in the
target — including Hermes bundled categorized skills — as installed, and never creates a
top-level shadow copy: Hermes ≥0.20 refuses ambiguous skill names, and a shadowed name is
silently skipped on every cron wake.
Classic SEO: sitemap.xml, robots.txt, feed.xml, per-page canonical, OpenGraph +
Twitter cards, and JSON-LD (Article/NewsArticle/BlogPosting).
GEO, for ChatGPT / Perplexity / Claude / AI Overviews — always on: llms.txt +
llms-full.txt, a content-negotiated .md version of every page (clean source, not
hydrated DOM), FAQPage/speakable schema, author-entity E-E-A-T markup, and visible
cited sources.
This indexed surface is emitted statically at build (rebuilt on merge), which is what
SEO/GEO needs — content in the server's HTML, not assembled client-side. The headless
@strand-cms/content-api is a separate JSON data channel for app/programmatic consumers, not
the crawlable surface; keep article pages and these artifacts static (or cached). See
packages/content-api for the details.
npm install # workspace install (links @strand-cms/core locally)
npm run -ws typecheck # typecheck every packageRequires Node 20+. The repo is an npm workspace.
Publication policy is a scaffold-time decision by the human, not a publish-time
decision by the agent. strand.json carries publishMode: in review (the
default) every post ships as a PR against the base branch and merging is the act
of publishing — publish_post refuses to commit to the base branch; in direct
publish_post commits to the base branch and pushes. There is no tool argument,
flag, or environment variable that changes the mode: agents provably copy any
publish-status flag they can see into their invocations (a Strand dogfood site
shipped three invisible draft briefs exactly this way), so the surface doesn't
exist. In both modes frontmatter status is forced to published at publish
time — everything on the base branch is live, and not-ready work stays on a
branch instead of hiding behind a draft flag. publish_post never force-pushes
and only auto-merges when the repo itself allows it. The content schema is
enforced in CI and a pre-commit hook, so a malformed post cannot merge. The
editor agent is instructed never to fabricate sources.
The publishable packages (@strand-cms/core, @strand-cms/cli, @strand-cms/content-api, create-strand)
build with tsup to dist/*.js + .d.ts, with exports pointed at the built output —
so they run under plain node (and npx) on a clean machine, not just via tsx/bundlers.
npm run -ws build # build every packageEach carries a prepublishOnly build hook; create-strand bundles the native skills into its
own tarball at build time. @strand-cms/next is a private app, built with next build.
Scaffolded sites ship a deploy-on-merge GitHub Actions workflow for the chosen target — Vercel, Cloudflare Pages, Netlify, or self-host — that gates every deploy on content validation and reships the SEO/GEO artifacts on each build. Connecting the repo in the provider dashboard is the zero-config alternative.
Early but substantial. The design plus five code packages are real and tested: @strand-cms/core,
create-strand, @strand-cms/cli (MCP server), @strand-cms/next (default theme), and @strand-cms/content-api
(headless) — all building to dist and verified under plain node. The managed cloud tier is
the next build. See DESIGN.md §10 for the roadmap.
MIT.