x-article-publisher
npx skills add https://github.com/zhanlincui/ultimate-agent-skills-collection --skill x-article-publisher
Agent 安装分布
Skill 文档
X Article Publisher
Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion.
Prerequisites
- Playwright MCP for browser automation
- User logged into X with Premium Plus subscription
- Python 3.9+ with dependencies:
pip install Pillow pyobjc-framework-Cocoa
Scripts
Located in ~/.claude/skills/x-article-publisher/scripts/:
parse_markdown.py
Parse Markdown and extract structured data:
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
Workflow
Strategy: “å æåå¾” (Text First, Images Later)
For articles with multiple images, paste ALL text content first, then insert images at correct positions using block index.
- Parse Markdown with Python script â get title, images with block_index, HTML
- Navigate to X Articles editor
- Upload cover image (first image)
- Fill title
- Copy HTML to clipboard (Python) â Paste with Cmd+V
- Insert content images at positions specified by block_index
- Save as draft (NEVER auto-publish)
髿æ§è¡åå (Efficiency Guidelines)
ç®æ : æå°åæä½ä¹é´ççå¾ æ¶é´ï¼å®ç°æµç çèªå¨åä½éªã
1. é¿å ä¸å¿ è¦ç browser_snapshot
大夿°æµè§å¨æä½ï¼click, type, press_key çï¼é½ä¼å¨è¿åç»æä¸å
å«é¡µé¢ç¶æãä¸è¦å¨æ¯æ¬¡æä½ååç¬è°ç¨ browser_snapshotï¼ç´æ¥ä½¿ç¨æä½è¿åç页é¢ç¶æå³å¯ã
â éè¯¯åæ³ï¼
browser_click â browser_snapshot â åæ â browser_click â browser_snapshot â ...
â
æ£ç¡®åæ³ï¼
browser_click â ä»è¿åç»æä¸è·å页é¢ç¶æ â browser_click â ...
2. é¿å ä¸å¿ è¦ç browser_wait_for
åªå¨ä»¥ä¸æ
åµä½¿ç¨ browser_wait_forï¼
- çå¾
å¾çä¸ä¼ 宿ï¼
textGone="æ£å¨ä¸ä¼ åªä½"ï¼ - çå¾ é¡µé¢åå§å è½½ï¼æå°æ°æ åµï¼
ä¸è¦ä½¿ç¨ browser_wait_for æ¥çå¾
æé®æè¾å
¥æ¡åºç° – å®ä»¬å¨é¡µé¢å è½½å®æåç«å³å¯ç¨ã
3. å¹¶è¡æ§è¡ç¬ç«æä½
å½ä¸¤ä¸ªæä½æ²¡æä¾èµå ³ç³»æ¶ï¼å¯ä»¥å¨åä¸ä¸ªæ¶æ¯ä¸å¹¶è¡è°ç¨å¤ä¸ªå·¥å ·ï¼
â
å¯ä»¥å¹¶è¡ï¼
- å¡«åæ é¢ (browser_type) + å¤å¶HTMLå°åªè´´æ¿ (Bash)
- è§£æMarkdownçæJSON + çæHTMLæä»¶
â ä¸è½å¹¶è¡ï¼æä¾èµï¼ï¼
- å¿
é¡»å
ç¹å»createæè½ä¸ä¼ å°é¢å¾
- å¿
é¡»å
ç²è´´å
容æè½æå
¥å¾ç
4. è¿ç»æ§è¡æµè§å¨æä½
æ¯ä¸ªæµè§å¨æä½è¿åç页é¢ç¶æå 嫿æéè¦çå ç´ å¼ç¨ãç´æ¥ä½¿ç¨è¿äºå¼ç¨è¿è¡ä¸ä¸æ¥æä½ï¼
# çæ³æµç¨ï¼æ¯æ¥ç´æ¥æ§è¡ï¼ä¸é¢å¤çå¾
ï¼ï¼
browser_navigate â ä»è¿åç¶ææ¾createæé® â browser_click(create)
â ä»è¿åç¶ææ¾ä¸ä¼ æé® â browser_click(ä¸ä¼ ) â browser_file_upload
â ä»è¿åç¶ææ¾åºç¨æé® â browser_click(åºç¨)
â ä»è¿åç¶ææ¾æ 颿¡ â browser_type(æ é¢)
â ç¹å»ç¼è¾å¨ â browser_press_key(Meta+v)
â ...
5. åå¤å·¥ä½åç½®
å¨å¼å§æµè§å¨æä½ä¹åï¼å 宿ææåå¤å·¥ä½ï¼
- è§£æ Markdown è·å JSON æ°æ®
- çæ HTML æä»¶å° /tmp/
- è®°å½ titleãcover_imageãcontent_images çä¿¡æ¯
è¿æ ·æµè§å¨æä½é¶æ®µå¯ä»¥è¿ç»æ§è¡ï¼ä¸éè¦ä¸éå䏿¥å¤çæ°æ®ã
Step 1: Parse Markdown (Python)
Use parse_markdown.py to extract all structured data:
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md
Output JSON:
{
"title": "Article Title",
"cover_image": "/path/to/first-image.jpg",
"content_images": [
{"path": "/path/to/img2.jpg", "block_index": 5, "after_text": "context for debugging..."},
{"path": "/path/to/img3.jpg", "block_index": 12, "after_text": "another context..."}
],
"html": "<p>Content...</p><h2>Section</h2>...",
"total_blocks": 45
}
Key fields:
block_index: The image should be inserted AFTER block element at this index (0-indexed)total_blocks: Total number of block elements in the HTMLafter_text: Kept for reference/debugging only, NOT for positioning
Save HTML to temp file for clipboard:
python parse_markdown.py article.md --html-only > /tmp/article_html.html
Step 2: Open X Articles Editor
browser_navigate: https://x.com/compose/articles
éè¦: 页é¢å è½½å伿¾ç¤ºè稿å表ï¼ä¸æ¯ç¼è¾å¨ãéè¦ï¼
- çå¾
页é¢å è½½å®æ: 使ç¨
browser_snapshotæ£æ¥é¡µé¢ç¶æ - ç«å³ç¹å» “create” æé®: ä¸è¦çå¾ “æ·»å æ 颔 çç¼è¾å¨å ç´ ï¼å®ä»¬åªæç¹å» create åæåºç°
- çå¾ ç¼è¾å¨å è½½: ç¹å» create åï¼çå¾ ç¼è¾å¨å ç´ åºç°
# 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. ç°å¨ç¼è¾å¨åºè¯¥æå¼äºï¼å¯ä»¥ç»§ç»ä¸ä¼ å°é¢å¾çæä½
注æ: ä¸è¦ä½¿ç¨ browser_wait_for text="æ·»å æ é¢" æ¥çå¾
页é¢å è½½ï¼å 为è¿ä¸ªææ¬åªæå¨ç¹å» create åæåºç°ï¼ä¼å¯¼è´è¶
æ¶ã
If login needed, prompt user to log in manually.
Step 3: Upload Cover Image
- Click “æ·»å ç §çæè§é¢” button
- Use browser_file_upload with the cover image path (from JSON output)
- 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/x-article-publisher/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)
å
³é®æ¹è¿: ä½¿ç¨ block_index 精确å®ä½ï¼èéä¾èµæåå¹é
ã
å®ä½åç
ç²è´´ HTML åï¼ç¼è¾å¨ä¸çå
å®¹ç»æä¸ºä¸ç³»ååå
ç´ ï¼æ®µè½ãæ é¢ãå¼ç¨çï¼ãæ¯å¼ å¾çç block_index 表示å®åºè¯¥æå
¥å¨ç¬¬ N 个åå
ç´ ä¹åã
æä½æ¥éª¤
- è·åææåå ç´ : ä½¿ç¨ browser_snapshot è·åç¼è¾å¨å å®¹ï¼æ¾å° textbox ä¸çææåå ç´
- æç´¢å¼å®ä½: æ ¹æ®
block_indexç¹å»å¯¹åºçåå ç´ - ç²è´´å¾ç: å¤å¶å¾çå°åªè´´æ¿åç²è´´
For each content image (from content_images array):
# 1. Copy image to clipboard (with compression)
python ~/.claude/skills/x-article-publisher/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
å®ä½çç¥
å¨ browser_snapshot è¿åçç»æä¸ï¼ç¼è¾å¨å 容é常æ¯ï¼
textbox [ref=xxx]:
generic [ref=block0]: # block_index 0
- paragraph content
heading [ref=block1]: # block_index 1
- h2 content
generic [ref=block2]: # block_index 2
- paragraph content
...
è¦å¨ block_index=5 åæå
¥å¾çï¼
- æ¾å°ç¼è¾å¨ textbox ä¸ç第 6 个åå ç´ ï¼ç´¢å¼ä»0å¼å§ï¼
- ç¹å»è¯¥å ç´
- ç²è´´å¾ç
注æ: æ¯æå
¥ä¸å¼ å¾çåï¼åç»å¾ççå®é
ä½ç½®ä¼åç§»ã建议æ block_index ä»å¤§å°å°çé¡ºåºæå
¥å¾çï¼è¿æ ·å
æå
¥çå¾çä¸ä¼å½±ååç»å¾ççç´¢å¼ã
ååæå ¥ç¤ºä¾
妿æ3å¼ å¾çï¼block_index åå«ä¸º 5, 12, 27ï¼
- å æå ¥ block_index=27 çå¾ç
- åæå ¥ block_index=12 çå¾ç
- æåæå ¥ block_index=5 çå¾ç
è¿æ ·æ¯æ¬¡æå ¥é½ä¸ä¼å½±ååé¢å·²ç»å®ä½å¥½çä½ç½®ã
Step 7: Save Draft
- Verify content pasted (check word count indicator)
- Draft auto-saves, or click Save button if needed
- Click “é¢è§” to verify formatting
- Report: “Draft saved. Review and publish manually.”
Critical Rules
- NEVER publish – Only save draft
- First image = cover – Upload first image as cover image
- Rich text conversion – Always convert Markdown to HTML before pasting
- Use clipboard API – Paste via clipboard for proper formatting
- Block index positioning – Use block_index for precise image placement
- Reverse order insertion – Insert images from highest to lowest block_index
- H1 title handling – H1 is used as title only, not included in body
Supported Formatting
- H2 headers (## )
- Blockquotes (> )
- Code blocks (
...) – converted to blockquotes since X doesn’t support<pre><code> - Bold text (**)
- Hyperlinks (text)
- Ordered lists (1. 2. 3.)
- Unordered lists (- )
- Paragraphs
Example Flow
User: “Publish /path/to/article.md to X”
# Step 1: Parse Markdown
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md > /tmp/article.json
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md --html-only > /tmp/article_html.html
- Navigate to https://x.com/compose/articles
- Upload cover image (browser_file_upload for cover only)
- Fill title (from JSON:
title) - Copy & paste HTML:
Then: browser_press_key Meta+vpython ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html - For each content image, in reverse order of block_index:
python copy_to_clipboard.py image /path/to/img.jpg --quality 85- Click block element at
block_indexposition - browser_press_key Meta+v
- Wait until upload complete
- Click block element at
- Verify in preview
- “Draft saved. Please review and publish manually.”
Best Practices
为ä»ä¹ç¨ block_index èéæåå¹é ï¼
- 精确å®ä½: ä¸ä¾èµæåå 容ï¼å³ä½¿å¤å¤æåç¸ä¼¼ä¹è½æ£ç¡®å®ä½
- å¯é æ§: ç´¢å¼æ¯ç¡®å®æ§çï¼ä¸ä¼å 为æåç¸ä¼¼èæ··æ·
- è°è¯æ¹ä¾¿:
after_textä»ä¿çç¨äºäººå·¥æ ¸éª
为ä»ä¹ç¨ Python èéæµè§å¨å JavaScriptï¼
- æ¬å°å¤çæ´å¯é : Python ç´æ¥æä½ç³»ç»åªè´´æ¿ï¼ä¸åæµè§å¨æ²çéå¶
- å¾çå缩: ä¸ä¼ åå缩å¾ç (–quality 85)ï¼åå°ä¸ä¼ æ¶é´
- 代ç å¤ç¨: èæ¬åºå®ä¸åï¼æ éæ¯æ¬¡éæ°ç¼å转æ¢é»è¾
- è°è¯æ¹ä¾¿: èæ¬å¯åç¬æµè¯ï¼é®é¢æå®ä½
çå¾ çç¥
å
³é®çè§£: browser_wait_for ç textGone åæ°ä¼å¨æåæ¶å¤±æ¶ç«å³è¿åï¼time åªæ¯æå¤§çå¾
æ¶é´ï¼ä¸æ¯åºå®çå¾
æ¶é´ã
- â ä¿å®çå¾
:
time=5ætime=10ï¼å¦æä¸ä¼ åªé2ç§ï¼å©ä½æ¶é´å ¨æµªè´¹ - â
çé´é轮询:
time=2ï¼æ¡ä»¶æ»¡è¶³ç«å³è¿åï¼æå¤ç2ç§
# æ£ç¡®ç¨æ³ï¼ç time å¼ï¼æ¡ä»¶æ»¡è¶³ç«å³è¿å
browser_wait_for textGone="æ£å¨ä¸ä¼ åªä½" time=2
# éè¯¯ç¨æ³ï¼åºå®é¿æ¶é´çå¾
browser_wait_for time=5 # æ æ¡ä»¶çå¾
5ç§ï¼æµªè´¹æ¶é´
åå: ä¸è¦é¢è®¾”éè¦çå¤ä¹ ”ï¼èæ¯è®¾ç½®ä¸ä¸ªåççæå¤§å¼ï¼è®©æ¡ä»¶æ£æµå°½å¿«è¿åã
å¾çæå ¥æç
æ¯å¼ å¾ççæµè§å¨æä½ä»5æ¥åå°å°2æ¥ï¼
- æ§: ç¹å» â æ·»å åªä½ â åªä½ â æ·»å ç §ç â file_upload
- æ°: ç¹å»æ®µè½ â Meta+v
å°é¢å¾ vs å 容å¾
- å°é¢å¾: ä½¿ç¨ browser_file_uploadï¼å 为æä¸é¨çä¸ä¼ æé®ï¼
- å 容å¾: ä½¿ç¨ Python åªè´´æ¿ + ç²è´´ï¼æ´é«æï¼