skills/publish-substack-article/SKILL.md
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).
npx skillsauth add sugarforever/01coder-agent-skills publish-substack-articleInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Publish Markdown content to Substack post editor, converting Markdown to HTML and pasting as rich text. Saves as draft for user review before publishing.
mcp__chrome-devtools__*)mcp__playwright__*)markdown package (pip install markdown)copy_to_clipboard.py script (shared from publish-zsxq-article skill)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.
https://{publication}.substack.com/publishhttps://{publication}.substack.com/publish/post/{postId}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:
SUBSTACK_PUBLICATION (optional, for users who publish repeatedly to the same publication).请提供 Substack publication subdomain(例如 verysmallwoods.substack.com 里的
verysmallwoods)。
Do not proceed to Step 3 (Navigate) without a resolved publication subdomain.
The Substack post editor uses Tiptap (ProseMirror-based WYSIWYG editor).
textbox "title" (placeholder: "Title")textbox "Add a subtitle…".ProseMirror (Tiptap editor, "Start writing...")button "Saved" (auto-saves)button "Preview"button "Continue" (publish flow - DO NOT USE)button "Settings" (title, description, thumbnail)When "Settings" or "File Settings" is open:
textbox "Add a title..."textbox "Add a description..."Bold, Italic, Strikethrough, Code, Link, Image, Audio, Video, Quote, Lists (bullet/ordered), Button, More (Code block, Divider, Footnote, LaTeX, etc.)
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:
markdown librarycopy_to_clipboard.py htmlWhy HTML paste?
fill tool → Content treated as plain text, no formattingKnown 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.
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:
Detect tables in the Markdown file (lines with | forming table structure)
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
Note the position of each table in the article for later insertion (after which heading/paragraph)
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.
Read the Markdown file and extract:
title field, or H1 header # Title, or filenameexcerpt or description fieldUse 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-reference links (e.g., English version link)
# Adjust pattern as needed for your articles
content = re.sub(r'^> .* available at.*\n\n?', '', content, flags=re.MULTILINE)
# Convert to HTML
html = markdown.markdown(content, extensions=['tables', 'fenced_code'])
# Write to temp file
with open('/tmp/substack_article.html', 'w') as f:
f.write(html)
IMPORTANT: Do NOT use nl2br extension - it converts single newlines to <br> tags, causing extra line breaks in the editor.
Navigate to the Substack dashboard and create a new post:
# Navigate to Substack dashboard
navigate to: https://{publication}.substack.com/publish
If not logged in, prompt user to log in:
请先登录 Substack,登录完成后告诉我。
Please log in to Substack first, then let me know.
From the dashboard, create a new text post:
Alternatively, if the editor is already open with an empty post, proceed directly.
textbox "title")textbox "Add a subtitle…")click: title textbox
fill/type: article title
click: subtitle textbox
fill/type: article subtitle
CRITICAL: Do NOT use fill tool - it inserts plain text without formatting.
python3 /path/to/copy_to_clipboard.py html --file /tmp/substack_article.html
Click the editor content area (.ProseMirror or paragraph element inside it)
Press Cmd+V to paste:
press_key: Meta+v (macOS)
press_key: Control+v (Windows/Linux)
This triggers Tiptap's HTML paste handler, which renders the content as rich text with proper formatting.
If the article had tables converted to images in Step 0, insert them now:
Navigate to the correct position in the editor — click on the paragraph or empty line where the table should appear (after the relevant heading/text)
Click the Image toolbar button (button "Image") — a dropdown menu appears with options: Image, Gallery, Stock photos, Generate image
Click "Image" menuitem from the dropdown — a file chooser dialog opens
Upload the image via file chooser:
browser_file_upload with the image pathupload_file with the image pathImportant notes:
/tmp/, copy it to the project directory firstAfter pasting:
The editor auto-saves, so no explicit save action is needed.
草稿已保存到 Substack。请在 Substack 中预览并手动发布。
Draft saved to Substack. Please preview and publish manually.
Post URL: https://{publication}.substack.com/publish/post/{postId}
User: "把 /path/to/my-article.md 发布到 Substack"
0. Pre-process tables (if any)
- Detect Markdown tables
- Create styled HTML for each table
- Render to screenshots (open in browser, screenshot, close tab)
- Remove table Markdown from content
- Note insertion positions
1. Read /path/to/my-article.md
- Extract title from frontmatter or H1
- Extract subtitle from frontmatter excerpt
- Get full Markdown content (with tables removed)
2. Convert Markdown to HTML
- Strip frontmatter
- Use markdown.markdown() with ['tables', 'fenced_code']
- Write to /tmp/substack_article.html
3. Navigate to Substack dashboard or new post
4. Check if logged in
- If not, prompt user to login
5. Fill title and subtitle
6. Copy HTML to clipboard + Paste
- python3 copy_to_clipboard.py html --file /tmp/substack_article.html
- Click editor content area
- Press Cmd+V
7. Insert table images at correct positions
- For each table: click position → Image button → Image menuitem → file upload
8. Verify draft saved
- Check "Saved" status
9. Report success
- "草稿已保存,请手动预览并发布"
nl2br extension - Causes double line breaksSUBSTACK_PUBLICATION env var, or user prompt before any navigation (see Publication Resolution)If you see raw HTML tags or unformatted text:
fill tool instead of clipboard pastecopy_to_clipboard.py + Cmd+V method (see Step 6)Substack's Tiptap editor does not support HTML tables. They collapse into inline plain text.
If page shows login prompt:
请先登录 Substack: https://{publication}.substack.com
登录完成后告诉我。
If editor elements are not visible:
If copy_to_clipboard.py fails:
pip install pyobjc-framework-Cocoa (macOS)| Element | Selector/Identifier | Description |
|---------|---------------------|-------------|
| Title input | textbox "title" | Post title |
| Subtitle input | textbox "Add a subtitle…" | Post subtitle |
| Content area | .ProseMirror (Tiptap editor) | Post content |
| Save status | button "Saved" | Auto-save indicator |
| Preview button | button "Preview" | Preview post |
| Continue button | button "Continue" | DO NOT USE - starts publish flow |
| Settings button | button "Settings" | Open settings sidebar |
| Exit button | button "Exit" | Exit editor |
| Image button | button "Image" | Opens image upload dropdown |
| Image menuitem | menuitem "Image" | Opens file chooser for image upload |
| Author button | button "{PublicationName}" | Author/publication selector |
Markdown file
↓ (Python markdown library)
HTML string
↓ (copy_to_clipboard.py)
System clipboard (text/html + text/plain)
↓ (Cmd+V keyboard shortcut)
Tiptap ProseMirror editor
↓ (auto-save)
Substack draft
The following Markdown elements are correctly rendered after HTML conversion and paste:
| Markdown Element | Substack Support | Notes | |-----------------|-----------------|-------| | Headings (H2-H6) | Yes | H1 not recommended (title is separate) | | Bold / Italic | Yes | | | Inline code | Yes | | | Code blocks | Yes | Syntax highlighting may vary | | Links | Yes | | | Blockquotes | Yes | | | Bullet lists | Yes | | | Ordered lists | Yes | | | Horizontal rules | Yes | | | Tables | No → Image | Convert via diagram-to-image skill, upload as image | | Images | Manual | Upload via Image toolbar button → file chooser |
tools
Design typography-driven video cover images using HTML/CSS + Chrome DevTools screenshot. Generates covers in all needed aspect ratios - 16:9 (YouTube), 16:10 (Bilibili), 9:16 and 3:4 (抖音/视频号 竖屏短视频) - with big readable text. Different from `cover-image` (AI hand-drawn aesthetic) - this is precise typography control via code. Use when user asks for "视频封面", "thumbnail", "做封面", "cover design", "缩略图", "横屏/竖屏封面", "抖音封面", "视频号封面".
data-ai
Produce slides-driven narration videos (口播视频) where each slide maps 1:1 to one voiceover section. Orchestrates a slides-generating skill (PPT, chosen from whatever is available) and `video-planner` (script + publishing materials) with a method-focused production workflow. Use when user wants to make a video that uses slides to explain a topic - e.g. 发布解读 / 产品评测 / 行业观察 / 技术解读 / 趋势分析. Triggers on "做一期视频 + PPT", "slides 视频", "发布解读视频", "深度讲解视频", or similar requests for structured narration videos.
development
Review one completed Claude Code session and propose a skill to create, update, or reuse so similar work goes faster next time. Use when the user asks to "mine a session for skills", "what skill can be created or updated from the session where I…", "extract a skill from this chat", or to review a past session for reusable workflows. Operates on exported session markdown from claude-session-manager. Not for exporting/converting sessions (use claude-session-manager) and not for writing blogs or TODOs from sessions.
tools
Manage local Codex session transcripts, including listing candidate sessions, exporting full or selected sessions to organized Markdown, inspecting archived sessions, and summarizing tool-call history. Use when the user asks to scan, parse, archive, inspect, recover, summarize, manage, or convert Codex sessions, `~/.codex/sessions` data, `~/.codex/archived_sessions` data, `.jsonl` transcripts, tool-call history, or hard-to-read Codex conversation logs.