Skip to content
Content
Skill

/publish-substack-article

Publish Markdown articles to Substack as drafts. Use when user wants to publish a Markdown file to Substack, or mentions "发布到 Substack", "Substack article", "publish to Substack". Handles Markdown-to-HTML conversion and saves as draft (never auto-publish).

BOOST
From plugin
01coder-agent-skills
13723 skills
Install
$ npx -y skills add sugarforever/01coder-agent-skills --skill publish-substack-article --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/publish-substack-article

Context preview

The summary Claude sees to decide when to auto-load this skill.

Publish Markdown articles to Substack as drafts. Use when user wants to publish a Markdown file to Substack, or mentions "发布到 Substack", "Substack article", "publish to Substack". Handles Markdown-to-HTML conversion and saves as draft (never auto-publish).

SKILL.md

publish-substack-article.SKILL.md
name: publish-substack-article
description: Publish Markdown articles to Substack as drafts. Use when user wants to publish a Markdown file to Substack, or mentions "发布到 Substack", "Substack article", "publish to Substack". Handles Markdown-to-HTML conversion and saves as draft (never auto-publish).

Publish Substack Article

Publish Markdown content to Substack post editor, converting Markdown to HTML and pasting as rich text. Saves as draft for user review before publishing.

Prerequisites

  • Browser automation MCP (either one):
  • **Chrome DevTools MCP** (`mcp__chrome-devtools__*`)
  • **Playwright MCP** (`mcp__playwright__*`)
  • User logged into Substack
  • Python 3 with `markdown` package (`pip install markdown`)
  • `copy_to_clipboard.py` script (shared from publish-zsxq-article skill)

Browser MCP Tool Mapping

This skill works with both Chrome DevTools MCP and Playwright MCP:

| Action | Chrome DevTools MCP | Playwright MCP | |--------|---------------------|----------------| | Navigate | `navigate_page` | `browser_navigate` | | Take snapshot | `take_snapshot` | `browser_snapshot` | | Take screenshot | `take_screenshot` | `browser_take_screenshot` | | Click element | `click` | `browser_click` | | Fill text | `fill` | `browser_type` | | Upload file | `upload_file` | `browser_file_upload` | | Press key | `press_key` | `browser_press_key` | | Evaluate JS | `evaluate_script` | `browser_evaluate` |

**Priority**: Default to **Playwright MCP**. Use Chrome DevTools MCP only when Playwright MCP is unavailable.

**Detection**: At runtime, prefer `mcp__playwright__browser_navigate`. Fall back to `mcp__chrome-devtools__navigate_page` only if Playwright tools are not available.

Key URLs

  • Substack dashboard: `https://{publication}.substack.com/publish`
  • Post editor: `https://{publication}.substack.com/publish/post/{postId}`

Publication Resolution

The Substack publication subdomain (e.g., `verysmallwoods` in `verysmallwoods.substack.com`) is required to navigate to the dashboard. **Do not hardcode a default.**

Before any navigation, resolve the publication in this order:

1. **Skill argument** — if the user invoked the skill with a publication subdomain, use it. 2. **Environment variable** — check `SUBSTACK_PUBLICATION` (optional, for users who publish repeatedly to the same publication). 3. **Prompt the user** if neither is available: > 请提供 Substack publication subdomain(例如 verysmallwoods.substack.com 里的 `verysmallwoods`)。

Do not proceed to Step 3 (Navigate) without a resolved publication subdomain.

Editor Interface

The Substack post editor uses **Tiptap** (ProseMirror-based WYSIWYG editor).

Key Elements

  • Title input: `textbox "title"` (placeholder: "Title")
  • Subtitle input: `textbox "Add a subtitle…"`
  • Content area: `.ProseMirror` (Tiptap editor, "Start writing...")
  • Save status: `button "Saved"` (auto-saves)
  • Preview button: `button "Preview"`
  • Continue button: `button "Continue"` (publish flow - DO NOT USE)
  • Settings sidebar: `button "Settings"` (title, description, thumbnail)

Settings Sidebar (left panel)

When "Settings" or "File Settings" is open:

  • Title: `textbox "Add a title..."`
  • Description: `textbox "Add a description..."`
  • Thumbnail: Upload button (3:2 aspect ratio)

Toolbar

Bold, Italic, Strikethrough, Code, Link, Image, Audio, Video, Quote, Lists (bullet/ordered), Button, More (Code block, Divider, Footnote, LaTeX, etc.)

Content Insertion Method

**CRITICAL: Use clipboard paste with HTML content**, NOT direct fill or plain Markdown paste.

The Tiptap editor handles HTML paste natively and renders it as rich content. The workflow is:

1. Convert Markdown to HTML using Python's `markdown` library 2. Copy HTML to system clipboard using `copy_to_clipboard.py html` 3. Focus the editor content area 4. Press Cmd+V (macOS) or Ctrl+V (Windows/Linux) to paste

**Why HTML paste?**

  • `fill` tool → Content treated as plain text, no formatting
  • Plain Markdown paste → Tiptap does NOT parse Markdown on paste
  • HTML paste → Tiptap renders HTML as rich content (headings, code blocks, links, bold, etc.)

**Known limitation**: Substack's editor does NOT support HTML tables. Tables will be collapsed into plain text. See **Step 0: Pre-Processing** for converting tables to images.

Main Workflow

Step 0: Pre-Processing — Convert Tables to Images

Substack does NOT render HTML tables. They collapse into plain text. Any Markdown table must be converted to a PNG image and uploaded separately.

**Workflow:**

1. **Detect tables** in the Markdown file (lines with `|` forming table structure)

2. **Convert each table to PNG** using the diagram-to-image skill:

# Extract table to temp file
cat > /tmp/table1.md << 'TABLE_EOF'
| Column 1 | Column 2 | Column 3 |
|----------|----------|----------|
| Data 1   | Data 2   | Data 3   |
TABLE_EOF

# Convert via diagramless.xyz API (auto-detects as table)
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table1.md -o /tmp/table1.png

3. **Note the position** of each table in the article for later insertion (after which heading/paragraph)

4. **Remove table Markdown** from the content before HTML conversion (so it won't appear as plain text in the pasted content)

**Image upload** happens after pasting the main content — see Step 7.

Step 1: Prepare Content

Read the Markdown file and extract:

  • **Title**: from YAML frontmatter `title` field, or H1 header `# Title`, or filename
  • **Subtitle**: from YAML frontmatter `excerpt` or `description` field
  • **Content**: full Markdown body (strip YAML frontmatter and any cross-reference links)

Step 2: Convert Markdown to HTML

Use Python's `markdown` library with `tables` and `fenced_code` extensions:

import markdown
import re

with open('/path/to/article.md', 'r') as f:
    content = f.read()

# Strip YAML frontmatter
content = re.sub(r'^---\n.*?\n---\n', '', content, flags=re.DOTALL)

# Strip cross-refer
Read more
Ships with01coder-agent-skills

A marketplace of agent skills by VerySmallWoods — content creation, publishing, analytics, and security scanning. Works with Claude Code, Codex, Cursor, Windsurf, and any other coding agent that supports the skills.sh format.

Get the whole plugin

Other skills on 01coder-agent-skills.