blog-editorial-calendar |
The orchestration layer over the other three. Keeps an evidence-backed backlog, picks the next topic so your corpus drifts toward the cluster + format mix you defined in config.json, schedules posts into a rolling daily cadence, reconciles the backlog against what's live on your CMS, and auto-refills via blog-topic-research...
"Run your blog like a queue, not a guessing game.
WebSearchWebFetchReadWriteBash
blog-editorial-calendar
The scheduler and topic-picker that sits on top of the other two skills. blog-topic-research fills a backlog with evidence-backed topics; seo-blog-writer turns one topic into a published post. This skill decides which topic to write next and when to publish it, so the corpus drifts toward the content mix you defined instead of whatever you felt like writing that day.
/blog-editorial-calendar next [N] # pick + write + auto-schedule into the next slots
/blog-editorial-calendar next [N] --draft # pick + write, leave as a draft (no schedule)
/blog-editorial-calendar next [N] <slot>[, <slot> ...] # pick + write + schedule each explicit slot
/blog-editorial-calendar status
/blog-editorial-calendar add "<topic>" --cluster <c> --format <f> [--priority N] [--notes "..."]
/blog-editorial-calendar mark-done <id-or-slug>
/blog-editorial-calendar sync # reconcile the backlog against what's live on your CMS
next always writes. With no flags it schedules each post into the next free slot of the rolling cadence; with --draft it writes but leaves the post as a draft; with explicit slot args it uses the named windows. The author and target platform come from config.json (see below) — this skill never overrides them.
Configuration — config.json
Everything site-specific lives in skills/blog-editorial-calendar/config.json, not in this file. Edit it once for your blog; the reference code below reads it. Example:
{
"target_platform": "ghost",
"author": "Editorial",
"cadence": {
"posts_per_day": 2,
"slots_utc": [[6, 0, 11, 59], [18, 0, 22, 59]]
},
"cluster_targets": {
"core-product": 0.30,
"integrations": 0.25,
"use-cases": 0.25,
"ecosystem": 0.20
},
"format_targets": {
"how-to-fix": 0.35, "x-vs-y": 0.15, "how-to-connect": 0.10,
"how-to-automate": 0.10, "use-case": 0.10, "listicle": 0.07,
"migration": 0.05, "release-recap": 0.05, "what-is": 0.03
},
"cluster_tag_map": {
"core-product": ["Product"],
"integrations": ["Integrations"],
"use-cases": ["Use Cases"],
"ecosystem": ["Ecosystem"]
}
}
cluster_targets — your topic buckets and the share of the corpus each should hold. The names are yours; the example above is a placehold
"Stop using stock photos.
WebSearchWebFetchBash(python3:*)ReadWrite
blog-figure-svg
Produces SVG figures intended for blog posts: in-line illustrations (1 per ~500 body words is the rule of thumb) and a templated OG feature card. Output is a clean SVG file (the editable source) rasterized to a compressed PNG (what the post references). Every figure carries title + desc + role="img" so screen readers can read it.
This skill is platform-agnostic — the SVG and PNG it produces work in any CMS (Ghost, WordPress, Webflow, Sanity) or static-site generator (Hugo, Astro, Eleventy, Jekyll, Next-MDX). It complements seo-blog-writer (handles the publish step for whatever platform you're on) and blog-topic-research (validates the topic). Use it during the illustration step of writing a post — after the prose is stable so the anchor sentences are final.
/blog-figure-svg flow "<title>" --steps "Trigger -> Filter -> HTTP -> Slack"
/blog-figure-svg compare "<title>" --bars "Zapier:0.03,Make:0.015,n8n:0.008" --unit "$ per task"
/blog-figure-svg taxonomy "<title>" --groups "Workflows,Agents,RPA" --notes "see references/style-examples.md"
/blog-figure-svg terminal "<title>" --lines "$ npm install\nadded 42 packages"
/blog-figure-svg feature "<headline>" --accent "#4F46E5" --pill "How To"
All variants write to tmp/blog-drafts/--.svg (editable source, gitignored), then rasterize to --.png (uploaded to the blog CDN).
Before you start
The skill expects a working directory it can write into. Default: tmp/blog-drafts/. The PNG rasterizer requires one of:
- ImageMagick (
magick command) — preferred. magick -density 192 -background white in.svg -resize 1600x out.png.
- rsvg-convert —
rsvg-convert -w 1600 -b white in.svg -o out.png.
- inkscape (CLI) —
inkscape --export-type=png --export-width=1600 in.svg.
- cairosvg (Python) —
pip install cairosvg; cairosvg in.svg -W 1600 -o out.png.
Plus pngquant (or oxipng) for compression — typical 60-80% size reduction with no visible quality loss. Core Web Vitals and ad-network reviews (Mediavine, Raptive) care about image weight.
command -v magick || command -v rsvg-convert || command -v inkscape || python3 -c "import cairosvg" 2>/dev/null \
|| echo "no SVG rasterizer found - install one of magick, rsvg-convert, inkscape, cairosvg"
command -v pngquant || command -v oxipng || echo "no PNG c
"Stop writing blog posts nobody searches for.
WebSearchWebFetchBash(python3:*)ReadWrite
blog-topic-research
Generates topic candidates for a blog with documented user demand. The skill exists to fight hallucinated SEO ideas: every topic it proposes must point to a URL that proves someone is asking about it.
research <N> topics [for cluster <C>] [--append-to <path>]
N - number of topics to return (default 50; cap 100)
cluster - if the blog has cluster taxonomy, restrict to one cluster the user names
--append-to - after presenting results, ask the user before appending accepted topics as JSON to the given path (a backlog file, a CSV, whatever the blog uses)
The skill is content-only: it does no scraping of its own. It drives the agent's WebFetch and WebSearch tools to fetch sources, and (optionally) shells out to a Python similarity script for the cannibalization step.
The contract
For every topic the skill emits, it captures:
| Field |
What it is |
topic |
Full title shaped like a long-tail query |
cluster |
A bucket the user defines for their blog (e.g. n8n, databases, react-hooks) |
format |
One of how-to-fix, how-to-connect, how-to-automate, x-vs-y, what-is, use-case, listicle, migration, release-recap |
demand_signals[] |
One or more, each with type, url, evidence (verbatim text), strength (1-3) |
signal_score |
Sum of strength across all signals; topic accepted only if >=3 |
primary_sources[] |
At least 1 vendor doc / GitHub issue / official changelog URL |
keywords[] |
Primary keyword + 3-5 LSI variants extracted from source text |
commentary |
1-2 sentences on what makes this topic specific (no fluff) |
problem_summary |
1-2 sentences distilling the symptom + trigger from the highest-engagement signal's body, in factual writer-voice (no marketing). Lets the writer skip re-fetching to figure out what the problem actually is. |
confirmed_fixes[] |
Each {kernel, source}: a short fix kernel (one phrase, e.g. "set N8NPAYLOADSIZE_MAX=16000000", "downgrade crewai to 0.113") plus the source URL where that fix is reported. Empty list if no fix is documented yet (still-open issues count). The writer expands kernels into prose and
"Turn a single long-tail query into a publish-ready blog post that ranks in search and gets quoted by AI assistants.
WebSearchWebFetchBash(python3:*)ReadWrite
seo-blog-writer
End-to-end pipeline for shipping a single long-tail blog post: topic -> research -> draft -> scrub -> AI-SEO audit -> publish. Designed for SEO and AI-citation extractability (FAQ blocks, BreadcrumbList + FAQPage + HowTo schema, query-phrased headings).
The writing pipeline is platform-agnostic — it produces a publish-ready bundle (clean HTML, slug, meta, JSON-LD blocks, feature-image alt). The publish step is pluggable: out-of-the-box adapters for Ghost Admin API, WordPress REST, and static-site file output. Adding another CMS (Webflow, Sanity, Strapi, Contentful, Hugo, Astro) is a matter of writing a 20-line POST snippet.
The skill takes one required argument: the topic. Optional flags control the publish target and state.
/seo-blog-writer <topic>
/seo-blog-writer <topic> --target ghost # publish via Ghost adapter
/seo-blog-writer <topic> --target wordpress # publish via WordPress REST
/seo-blog-writer <topic> --target static --out posts/ # write files into a static-site repo
/seo-blog-writer <topic> --target ghost --publish # actually publish (default: draft)
/seo-blog-writer <topic> --target ghost --publish-at <ISO> # schedule for future publish
/seo-blog-writer <topic> --angle "<angle>" # narrow the angle
Default state is draft — the post lands in the platform's editor for human review before going live, unless --publish or --publish-at is passed. --publish-at accepts an ISO 8601 UTC timestamp (e.g. 2026-05-10T07:42:00Z) and is mutually exclusive with --publish.
Default --target is static — writes a self-contained HTML file + a metadata.json next to it so you can wire any platform yourself.
Before you start — preflight
The platform-agnostic checks:
# 1. Python available (rasterizer, scrubber, schema builder)
command -v python3
# 2. Working directory writable
mkdir -p tmp/blog-drafts && touch tmp/blog-drafts/.touch && rm tmp/blog-drafts/.touch
3. (Optional) ai-seo MCP — check before continuing
Check whether the current agent session has access to a tool named audit_page from the ai-seo-mcp server (@automatelab/ai-seo-mcp). That MCP provides a programmatic citation-worthiness and schema score that Step 5 uses automatically when available.
- If the MCP is connected: nothing to do — Step 5 will call
audit_page automatically.
- If the MCP is not connected: ask the user:
> "The ai-seo MCP (@automatelab/ai-seo-mcp
Ready to use publishing-skills?
|
|