Agent Blog Skill
Manage a blog built on the blog template. Full lifecycle: write or import drafts, format articles, set titles and hero images, add SEO structures, build, preview, and publish.
Project layout
config.py site constants (SITE_URL, SITE_NAME, CTA, UTM, OG colors)
build.py generator; python3 build.py [--check|--drafts]
scripts/import_article.py import any HTML article as a draft
scripts/import_x_articles.py import X articles as drafts
scripts/og_image.py auto-generate OG cards from post titles
templates/ base.html, home.html, post.html, tag.html
assets/ style.css, fonts, icons, vendor/chart.umd.min.js
assets/images/ hero images live here
content/posts/ YYYY-MM-DD-<slug>.md source files
public/ build output (gitignored)
Config
config.py is the single source of truth. If it does not exist, the build
falls back to config.example.py defaults (with a printed note), so a fresh
fork builds with zero setup. To customize: copy config.example.py to
config.py and edit the values.
Key fields: SITE_URL, SITE_NAME, SITE_TAGLINE, CTA_URL, CTA_TEXT,
OG_* colors, UTM_SOURCE.
Writing a post
Create content/posts/YYYY-MM-DD-<slug>.md:
---
title: "Post Title"
slug: post-title
date: 2026-01-15
author: Author Name
description: "Short SEO summary."
tags: [tag1, tag2]
draft: true
og_image: /assets/images/hero.png
---
Then python3 build.py. Drafts render to public/drafts/<slug>/ and are
excluded from homepage, RSS, sitemap. Set draft: false to publish.
Frontmatter fields
| Field | Required | Notes |
|---|---|---|
| title | yes | Wrap in quotes if it contains : or # |
| slug | no | Defaults to filename slug |
| date | no | Defaults to filename date |
| author | no | Defaults to config.DEFAULT_AUTHOR |
| description | no | Falls back to first 160 chars of body |
| tags | no | YAML list [a, b] |
| draft | no | true = excluded from public surfaces |
| og_image | no | Custom OG image path; auto-generated if omitted |
| source_url | no | Original URL if imported |
Body
Standard markdown after the ---. Supports headings, bold, italic, links,
images, code blocks, blockquotes, lists, tables, horizontal rules.
For raw HTML articles (imported), the body can contain inline <style>
and <script> blocks. The importer scopes CSS under .sr-article.
Importing articles
From any HTML URL
python3 scripts/import_article.py "URL" \
--date YYYY-MM-DD --slug S --title "T" \
--author "Author" --tags a,b --description "..."
The importer fetches the page, extracts body + styles + scripts, scopes
CSS under .sr-article, drops the original <h1>, rewrites Chart.js CDN
to vendored copy, and writes a draft markdown file.
From X articles
python3 scripts/import_x_articles.py
Reads twitter_articles_5_raw.json (or similar export), downloads cover
images, and writes formatted markdown posts.
Building
python3 build.py --check # validate only, write nothing
python3 build.py # full build (drafts excluded from public surfaces)
python3 build.py --drafts # full build (drafts visible on homepage, marked [draft])
Output goes to public/. The build is deterministic.
OG images
If a post has no og_image in frontmatter, scripts/og_image.py generates
a 1200x630 PNG card with the post title, date, and brand wordmark. Colors
come from config.py (OG_* fields). A default card is also generated for
the site homepage.
UTM analytics
Every outbound link (nav CTA, footer, article CTA) is tagged at build time:
utm_source=config.UTM_SOURCE(default:blog)utm_medium=nav,footer, orarticle_ctautm_content= post slug (only on article CTAs)utm_campaign=config.UTM_CAMPAIGN(optional)
Internal links, assets, RSS, and JSON-LD are never tagged. This lets analytics platforms attribute outbound clicks to specific articles without any client-side setup.
Previewing
Serve public/ with any static server:
python3 -m http.server 8000 -d public/
On Starchild, use the preview system to serve the built site and get a shareable URL.
Publishing
The public/ directory is static HTML. Deploy to any static host:
- GitHub Pages
- Netlify / Vercel
- Any static file host (rsync, SCP)
- Starchild community publish (via
community-publishskill)
Validation checklist
Before publishing a post:
python3 build.py --checkpasses with no errors- Post has a title and description in frontmatter
draft: falseis set- Hero image exists at the path specified in
og_image(if set) python3 build.pycompletes successfully- Preview the post at
/posts/<slug>/to verify rendering - Check
/feed.xmland/sitemap.xmlinclude the post
Troubleshooting
- YAML parse error: usually an unquoted title containing
:or#. Wrap in double quotes. - Charts blank: a CDN
<script src>slipped back in. Confirm the built page referencesvendor/chart.umd.min.js. - Wrong fonts on imported article: re-run the importer (it forces white bg + blog fonts). Do not patch
public/by hand. - Mojibake: re-run the importer; it decodes UTF-8 via
apparent_encoding. - "config.py not found" note during build: not an error; the build used example defaults. Run
cp config.example.py config.pyand edit to customize. - Telegram / X link preview shows no image despite
og:imagebeing set: hero image was saved as RGBA PNG. Telegram's crawler silently rejects alpha-channel PNGs with no fallback. Fix:Image.open(path).convert('RGB').save(path, format='PNG'). Verify:struct.unpack('B', open(path,'rb').read()[25:26])[0] == 2(color type 2 = RGB). Also addog:image:secure_urlandog:image:typemeta tags for full Telegram/X card support. - On Starchild, use
preview(action="serve")to get a shareable/preview/<id>/URL — never exposelocalhost:<port>to users. Web users see the Preview panel; on Telegram/WeChat use thecommunity-publishskill'spublish_preview()for a public URL. Protected slugs (1892-starchild-blog,1892-docs) must never be stopped or overwritten.