SuperAPI AI Directory

publish-x-article

Writing & research

View repository

Updated 10-15 13:00

About this skill

Publish X Article

Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images.

Credits

This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work.

Interactive Setup: Ask Subscription Type

IMPORTANT: Before processing the article, ask the user about their X subscription type if not already known.

Prompt the User

Before publishing, I need to know your X subscription type to handle formatting correctly:

1. **X Premium** - Basic tier ($8/month)
2. **X Premium+** - Plus tier ($16/month)

Which subscription do you have? (Premium / Premium+)


For Chinese users:

Before publishing, I need to know your X subscription type to handle the format correctly:

  1. X Premium - Basic ($8/month)
  2. X Premium+ - Premium ($16/month)

Which version are you using? (Premium / Premium+)


### Remember the Answer

Once the user answers, remember their subscription type for the rest of the session. Don't ask again unless they explicitly want to change it.

## X Subscription Feature Comparison

| Feature | X Premium | X Premium+ |
|---------|-----------|------------|
| H1 headers (`#`) | Title only | Title only |
| H2 headers (`##`) | Yes | Yes |
| H3+ headers (`###`, etc.) | **No** | Yes |
| Markdown tables | **No** | Yes |
| Mermaid diagrams | **No** | **No** (not supported by X) |
| Code blocks | Blockquotes | Blockquotes |
| Bold, italic, links | Yes | Yes |
| Lists | Yes | Yes |
| Blockquotes | Yes | Yes |
| Images | Yes | Yes |

### Pre-Processing Required by Subscription

**X Premium (Basic):**
- Convert H3+ headers → H2 or bold
- Convert tables → PNG images
- Convert mermaid → PNG images

**X Premium+ (Plus):**
- Keep H3+ headers as-is
- Keep tables as-is (rendered natively)
- Convert mermaid → PNG images (still not supported)

## Prerequisites

- Playwright MCP for browser automation
- User logged into X with Premium subscription
- Python 3.9+ with dependencies:
  - macOS: `pip install Pillow pyobjc-framework-Cocoa markdown`
  - Windows: `pip install Pillow pywin32 clip-util markdown`
- diagram-to-image skill (for converting tables/mermaid to PNG via diagramless.xyz API)

## Scripts

Located in `~/.claude/skills/publish-x-article/scripts/`:

### parse_markdown.py
Parse Markdown and extract structured data:
```bash
python parse_markdown.py <markdown_file> [--output json|html] [--html-only]

Returns JSON with: title, cover_image, content_images (with block_index for positioning), html, total_blocks

copy_to_clipboard.py

Copy image or HTML to system clipboard:

# Copy image (with optional compression)
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]

# Copy HTML for rich text paste
python copy_to_clipboard.py html --file /path/to/content.html

diagram-to-image.mjs (from diagram-to-image skill)

Convert Mermaid diagrams and Markdown tables to PNG images via diagramless.xyz API:

node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table.md -o /tmp/table.png
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/diagram.mmd -o /tmp/diagram.png --theme ocean

Pre-Processing: Handle Unsupported Elements

Before publishing, scan the Markdown for unsupported elements and convert them to images.

Step 0: Analyze Content for Limitations

# Check the markdown file for unsupported elements
cat /path/to/article.md

Look for:

  1. Deep headers (H3+): ###, ####, etc.
  2. Markdown tables: Lines with | characters forming table structure
  3. Mermaid code blocks: ```mermaid

Converting Unsupported Elements to Images

1. Markdown Tables → PNG

When a table is detected:

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

# 2. Convert to image via diagramless.xyz API
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table.md -o /tmp/table-001.png

# 3. Replace table in markdown with image reference
# ![Table description](/tmp/table-001.png)

2. Mermaid Diagrams → PNG

When a mermaid block is detected:

# 1. Extract mermaid to temp file
cat > /tmp/diagram.mmd << 'MERMAID_EOF'
flowchart TD
    A[Start] --> B[Process]
    B --> C[End]
MERMAID_EOF

# 2. Convert to image via diagramless.xyz API
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/diagram.mmd -o /tmp/diagram-001.png

# 3. Replace mermaid block in markdown with image reference
# ![Diagram description](/tmp/diagram-001.png)

3. Deep Headers (H3+) → Simplified Structure

Goal: Preserve the article's logical structure and readability while working within X Premium's H2-only limitation.

Guidelines for the AI:

When you encounter H3, H4, or deeper headers in a Premium user's article, think about what the author intended:

  • If the header introduces a distinct subtopic under a section, convert it to bold text as a paragraph opener. This maintains visual hierarchy without breaking X's formatting.

  • If the header is a major section that happens to be H3, consider promoting it to H2 — but only if this doesn't create a flat, meaningless structure. The article should still flow logically.

  • If there's a deep hierarchy (H2 → H3 → H4), flatten thoughtfully:

    • Keep H2 as H2
    • Convert H3 to bold paragraph
    • Convert H4 to italic or just merge into the paragraph naturally
  • Preserve meaning over structure. A header like ### Why This Matters might become a bold lead-in: **Why does this matter?** followed by the content. Use your judgment.

  • Read the content. If an H3 header is just "Example" or "Note", it might work better as a blockquote or inline emphasis rather than a standalone bold line.

The goal is not mechanical conversion — it's creating an article that reads well on X while honoring the author's intent.

Pre-Processing Workflow

Before publishing, read through the article and prepare it for X:

  1. Understand the article's structure — Read it first. What are the main sections? How does the author use headers to organize ideas?

  2. Handle tables and mermaid diagrams — These need to become images. Extract each one, convert to PNG, and note where they should be inserted.

  3. Adapt headers for the subscription tier:

    • For Premium+ users: Keep headers as-is (H3+ supported)
    • For Premium users: Thoughtfully restructure H3+ headers while preserving the article's flow and intent (see guidelines above)
  4. Create the modified markdown — Save your adapted version to a temp file, ready for parsing.

The goal is an article that reads naturally on X, not a mechanically transformed document.

Main Workflow

Strategy: "Text First, Images Second, Dividers Last"

For articles with images and dividers, paste ALL text content first, then insert images and dividers at correct positions using block index.

  1. Pre-process: Convert tables/mermaid to images, flatten deep headers
  2. Parse modified Markdown with Python script → get title, images, dividers (all with block_index), HTML
  3. Navigate to X Articles editor
  4. Upload cover image (first image)
  5. Fill title
  6. Copy HTML to clipboard (Python) → Paste with Cmd+V
  7. Insert content images at positions specified by block_index
  8. Insert dividers at positions specified by block_index (via Insert > Divider menu)
  9. Save as draft (NEVER auto-publish)

Efficiency Guidelines

Goal: Minimize the waiting time between operations to achieve a smooth automated experience.

1. Avoid unnecessary browser_snapshot

Most browser operations (click, type, press_key, etc.) will include the page state in their returned result.Do notcall browser_snapshotseparately after each operation; just use the page state returned by the operation directly.

❌ 错误做法:
browser_click → browser_snapshot → 分析 → browser_click → browser_snapshot → ...

✅ 正确做法:
browser_click → 从返回结果中获取页面状态 → browser_click → ...

2. Avoid unnecessary browser_wait_for

Use browser_wait_for:

  • Wait for image upload to complete (textGone="正在上传媒体")
  • Wait for the page to initially load (very rare cases)

Do notuse browser_wait_for to wait for buttons or input boxes to appear - they are immediately available after the page finishes loading.

3. Execute independent operations in parallel

When two operations have no dependency relationship, you can call multiple tools in parallel in the same message:

✅ 可以并行:
- 填写标题 (browser_type) + 复制HTML到剪贴板 (Bash)
- 解析Markdown生成JSON + 生成HTML文件

❌ 不能并行(有依赖):
- 必须先点击create才能上传封面图
- 必须先粘贴内容才能插入图片

4. Execute browser operations consecutively

The page state returned by each browser operation contains all the element references needed. Use these references directly for the next operation:

# 理想流程(每步直接执行,不额外等待):
browser_navigate → 从返回状态找create按钮 → browser_click(create)
→ 从返回状态找上传按钮 → browser_click(上传) → browser_file_upload
→ 从返回状态找应用按钮 → browser_click(应用)
→ 从返回状态找标题框 → browser_type(标题)
→ 点击编辑器 → browser_press_key(Meta+v)
→ ...

5. Do preparatory work in advance

Before starting browser operations, complete all preparatory work first:

  1. Scan for unsupported elements (tables, Mermaid, deep headings)
  2. Convert tables/Mermaid to images
  3. Parse Markdown to obtain JSON data
  4. Generate HTML files to /tmp/
  5. Record information such as title, cover_image, content_images, etc.

In this way, the browser operation phase can be executed consecutively without stopping midway to process data.

Step 0: Read and Adapt the Article

First, read the article. Understand what it's about, how it's structured, and what the author is trying to communicate.

Preserve the Original

IMPORTANT: Never modify the user's original file. If adaptations are needed:

  1. Save the adapted version as a copy (e.g., /tmp/article_adapted.md or alongside the original as article_for_x.md)
  2. Tell the user what was changed and where both files are:
    I've adapted your article for X Premium. Here's what changed:
    - Converted 2 tables to images
    - Restructured 3 H3 headers to bold text for better flow
    
    Original preserved: /path/to/article.md
    Adapted version: /path/to/article_for_x.md
    
  3. Proceed with the adapted copy for publishing

This ensures the user can review the changes and keeps their original work intact.

Adaptation Based on Subscription

For Premium Users (Basic Tier)

Ask yourself:

  • Are there tables? → Convert each to a PNG image
  • Are there mermaid diagrams? → Convert each to a PNG image
  • Are there H3+ headers? → Restructure them thoughtfully (see header guidelines above)

Create a modified version of the markdown that will work within Premium's limitations while preserving readability.

For Premium+ Users

Ask yourself:

  • Are there mermaid diagrams? → Convert to PNG (still not supported on any tier)
  • Tables and H3+ headers can stay as-is

Converting Elements to Images

# Tables → PNG (auto-detected as table)
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table-1.md -o /tmp/table-1.png

# Mermaid → PNG (auto-detected as mermaid)
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/diagram-1.mmd -o /tmp/diagram-1.png

Replace these elements in the markdown with image references, positioning them where the original element was.

Step 1: Parse Markdown (Python)

Use parse_markdown.py to extract all structured data:

python ~/.claude/skills/publish-x-article/scripts/parse_markdown.py /path/to/modified_article.md

Output JSON:

{
  "title": "Article Title",
  "cover_image": "/path/to/first-image.jpg",
  "content_images": [
    {"path": "/tmp/table-1.png", "block_index": 5, "after_text": "context..."},
    {"path": "/tmp/mermaid-1.png", "block_index": 12, "after_text": "another context..."}
  ],
  "dividers": [
    {"block_index": 7, "after_text": "context before divider..."},
    {"block_index": 15, "after_text": "another context..."}
  ],
  "html": "<p>Content...</p><h2>Section</h2>...",
  "total_blocks": 45
}

Key fields:

  • block_index: The image/divider should be inserted AFTER block element at this index (0-indexed)
  • total_blocks: Total number of block elements in the HTML
  • after_text: Kept for reference/debugging only, NOT for positioning
  • dividers: Array of divider positions (markdown --- must be inserted via X's menu, not HTML <hr>)

Save HTML to temp file for clipboard:

python parse_markdown.py modified_article.md --html-only > /tmp/article_html.html

Step 2: Open X Articles Editor

browser_navigate: https://x.com/compose/articles

Important: After the page loads, it displays the draft list, not the editor. You need to:

  1. Wait for the page to finish loading: Use browser_snapshot to check the page state
  2. Immediately click the "create" button: Do not wait for editor elements such as "Add title"; they only appear after clicking create
  3. Wait for the editor to load: After clicking create, wait for the editor elements to appear
# 1. 导航到页面
browser_navigate: https://x.com/compose/articles

# 2. 获取页面快照,找到 create 按钮
browser_snapshot

# 3. 点击 create 按钮(通常 ref 类似 "create" 或带有 create 标签)
browser_click: element="create button", ref=<create_button_ref>

# 4. 现在编辑器应该打开了,可以继续上传封面图等操作

Note: Do not use browser_wait_for text="添加标题" to wait for the page to load, because this text only appears after clicking create, which will cause a timeout.

If login needed, prompt user to log in manually.

Step 3: Upload Cover Image

  1. Click "添加照片或视频" button
  2. Use browser_file_upload with the cover image path (from JSON output)
  3. Verify image uploaded

Step 4: Fill Title

  • Find textbox with "添加标题" placeholder
  • Use browser_type to input title (from JSON output)

Step 5: Paste Text Content (Python Clipboard)

Copy HTML to system clipboard using Python, then paste:

# Copy HTML to clipboard
python ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py html --file /tmp/article_html.html

Then in browser:

browser_click on editor textbox
browser_press_key: Meta+v

This preserves all rich text formatting (H2, bold, links, lists).

Step 6: Insert Content Images (Block Index Positioning)

Key improvement: Use block_index for precise positioning instead of relying on text matching.

Positioning principle

After pasting HTML, the content structure in the editor is a series of block elements (paragraphs, headings, quotes, etc.). Each image's block_index indicates that it should be inserted after the Nth block element.

Operation steps

  1. Get all block elements: Use browser_snapshot to get the editor content and find all child elements under the textbox
  2. Locate by index: According to block_index Click the corresponding block element
  3. Paste image: Copy the image to the clipboard and then paste

For each content image (from content_images array):

# 1. Copy image to clipboard (with compression)
python ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py image /path/to/img.jpg --quality 85
# 2. Click the block element at block_index
# Example: if block_index=5, click the 6th block element (0-indexed)
browser_click on the element at position block_index in the editor

# 3. Paste image
browser_press_key: Meta+v

# 4. Wait for upload (use short time, returns immediately when done)
browser_wait_for textGone="正在上传媒体" time=2

Reverse insertion

Note: After each image is inserted, the actual position of subsequent images will shift. It is recommended to insert images in block_index descendingorder.

If there are 3 images, with block_index values of 5, 12, 27 respectively:

  1. First insert the image with block_index=27
  2. Then insert the image with block_index=12
  3. Finally insert the image with block_index=5

Step 6.5: Insert Dividers (Via Menu)

Important: The --- divider in Markdown cannot be pasted via the HTML <hr> tag (X Articles will ignore it). It must be inserted via the X Articles Insert menu.

Why special handling is needed

X Articles has its own native divider element, which can only be inserted via the Insert > Divider menu. The HTML <hr> tag will be completely ignored.

Steps

For each divider (from dividers array), in reverse order of block_index:

# 1. Click the block element at block_index position
browser_click on the element at position block_index in the editor

# 2. Open Insert menu
browser_click on "Insert" button (Add Media button)

# 3. Click Divider menu item
browser_click on "Divider" menuitem

# Divider is inserted at cursor position

Reverse insertion

As with images, insert dividers in block_index descendingorder to avoid position shift issues.

Insertion order with images

It is recommended to insert all images first, then insert all dividers. Both should be in descending block_index order:

  1. Insert all images (starting from the largest block_index)
  2. Insert all dividers (starting from the largest block_index)

Step 7: Save Draft

  1. Verify content pasted (check word count indicator)
  2. Draft auto-saves, or click Save button if needed
  3. Click "Preview" to verify formatting
  4. Report: "Draft saved. Review and publish manually."

Critical Rules

  1. NEVER publish - Only save draft
  2. Pre-process first - Convert tables/mermaid/deep headers before parsing
  3. First image = cover - Upload first image as cover image
  4. Rich text conversion - Always convert Markdown to HTML before pasting
  5. Use clipboard API - Paste via clipboard for proper formatting
  6. Block index positioning - Use block_index for precise image/divider placement
  7. Reverse order insertion - Insert images and dividers from highest to lowest block_index
  8. H1 title handling - H1 is used as title only, not included in body
  9. Dividers via menu - Markdown --- must be inserted via Insert > Divider menu (HTML <hr> is ignored)

Supported Formatting (After Pre-Processing)

Element Support Notes
H2 (##) Native Section headers
Bold (**) Native Strong emphasis
Italic (*) Native Emphasis
Links ([](url)) Native Hyperlinks
Ordered lists Native 1. 2. 3.
Unordered lists Native - bullets
Blockquotes (>) Native Quoted text
Code blocks Converted → Blockquotes
Tables Converted → PNG images
Mermaid Converted → PNG images
H3+ headers Converted → H2 or bold
Dividers (---) Menu insert → Insert > Divider

Example Flow

User: "Publish /path/to/article.md to X"

# Step 0: Analyze content
# Found: 1 table, 1 mermaid diagram, 2 H3 headers

# Step 0.1: Convert table to image
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table-1.md -o /tmp/table-1.png

# Step 0.2: Convert mermaid to image
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/mermaid-1.mmd -o /tmp/mermaid-1.png

# Step 0.3: Create modified markdown with image refs and flattened headers
# Save to /tmp/article_modified.md

# Step 1: Parse modified markdown
python ~/.claude/skills/publish-x-article/scripts/parse_markdown.py /tmp/article_modified.md > /tmp/article.json
python ~/.claude/skills/publish-x-article/scripts/parse_markdown.py /tmp/article_modified.md --html-only > /tmp/article_html.html
  1. Navigate to https://x.com/compose/articles
  2. Click create, upload cover image (browser_file_upload for cover only)
  3. Fill title (from JSON: title)
  4. Copy & paste HTML:
    python ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py html --file /tmp/article_html.html
    
    Then: browser_press_key Meta+v
  5. For each content image (including converted table/mermaid PNGs), in reverse order of block_index:
    python copy_to_clipboard.py image /path/to/img.jpg --quality 85
    
    • Click block element at block_index position
    • browser_press_key Meta+v
    • Wait until upload complete
  6. Verify in preview
  7. "Draft saved. Please review and publish manually."

Best Practices

Why use block_index instead of text matching?

  1. Precise positioning: Does not depend on text content; even if similar text appears in multiple places, it can still be positioned correctly
  2. Reliability: The index is deterministic and will not be confused by similar text
  3. Easy debugging: after_text is still retained for manual verification

Why use Python instead of in-browser JavaScript?

  1. Local processing is more reliable: Python directly operates the system clipboard and is not restricted by the browser sandbox
  2. Image compression: Compress images before uploading (--quality 85) to reduce upload time
  3. Code reuse: The script stays fixed; there is no need to rewrite the conversion logic each time
  4. Easy debugging: The script can be tested separately, making issues easy to locate

Waiting strategy

Key understanding: browser_wait_for 's textGone parameter will returnimmediately,time when the text disappears; it is only the maximum waiting time, not a fixed waiting time.

# 正确用法:短 time 值,条件满足立即返回
browser_wait_for textGone="正在上传媒体" time=2

# 错误用法:固定长时间等待
browser_wait_for time=5  # 无条件等待5秒,浪费时间

Cover image vs content image

  • Cover image: Use browser_file_upload (because there is a dedicated upload button)
  • Content image: Use Python clipboard + paste (more efficient)

Troubleshooting

Table not rendering correctly

  • Ensure Pillow is installed: pip install pillow
  • Check table markdown syntax is valid

Mermaid conversion fails

  • Check that diagramless.xyz is reachable
  • Check mermaid syntax is valid
  • Try with explicit type: --type mermaid

Deep headers still showing

  • Manually flatten ### → ## or **bold**
  • Re-run pre-processing

Image upload timeout

  • Compress images with --quality 70
  • Use shorter browser_wait_for time=2