x-article-publisher
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseX Article Publisher
X 文章发布器
Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion.
将Markdown内容发布到X(原Twitter)文章编辑器,通过富文本转换保留格式。
Prerequisites
前置条件
- Playwright MCP for browser automation
- User logged into X with Premium Plus subscription
- Python 3.9+ with dependencies:
- macOS:
pip install Pillow pyobjc-framework-Cocoa - Windows:
pip install Pillow pywin32 clip-util
- macOS:
- For Mermaid diagrams:
npm install -g @mermaid-js/mermaid-cli
- 用于浏览器自动化的Playwright MCP
- 已登录X且拥有Premium Plus订阅的用户
- 安装Python 3.9+及对应依赖:
- macOS:
pip install Pillow pyobjc-framework-Cocoa - Windows:
pip install Pillow pywin32 clip-util
- macOS:
- 若需处理Mermaid图表:
npm install -g @mermaid-js/mermaid-cli
Scripts
脚本文件
Located in :
~/.claude/skills/x-article-publisher/scripts/位于 :
~/.claude/skills/x-article-publisher/scripts/parse_markdown.py
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, dividers (with block_index for positioning), html, total_blocks
解析Markdown并提取结构化数据:
bash
python parse_markdown.py <markdown_file> [--output json|html] [--html-only]返回包含以下字段的JSON:标题、cover_image、content_images、dividers(含定位用的block_index)、html、total_blocks
copy_to_clipboard.py
copy_to_clipboard.py
Copy image or HTML to system clipboard (cross-platform):
bash
undefined将图片或HTML复制到系统剪贴板(跨平台):
bash
undefinedCopy image (with optional compression)
复制图片(可选压缩)
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]
Copy HTML for rich text paste
复制HTML用于富文本粘贴
python copy_to_clipboard.py html --file /path/to/content.html
undefinedpython copy_to_clipboard.py html --file /path/to/content.html
undefinedtable_to_image.py
table_to_image.py
Convert Markdown table to PNG image:
bash
python table_to_image.py <input.md> <output.png> [--scale 2]Use when X Articles doesn't support native table rendering or for consistent styling.
将Markdown表格转换为PNG图片:
bash
python table_to_image.py <input.md> <output.png> [--scale 2]当X文章不支持原生表格渲染或需要统一样式时使用。
Pre-Processing (Optional)
预处理(可选)
Before publishing, scan the Markdown for elements that need conversion:
发布前,扫描Markdown中需要转换的元素:
Tables → PNG
表格 → PNG
bash
undefinedbash
undefinedExtract table to temp file, then convert
提取表格到临时文件,然后转换
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/table.md /tmp/table.png
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/table.md /tmp/table.png
Replace table in markdown with: 
将Markdown中的表格替换为:
undefinedundefinedMermaid Diagrams → PNG
Mermaid图表 → PNG
bash
undefinedbash
undefinedExtract mermaid block to .mmd file, then convert
提取Mermaid块到.mmd文件,然后转换
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
Replace mermaid block with: 
将Mermaid块替换为:
undefinedundefinedDividers (---)
分割线(---)
Dividers are automatically detected by and output in the array. They must be inserted via X Articles' Insert > Divider menu (HTML tags are ignored by X).
parse_markdown.pydividers<hr>分割线会被自动检测并输出到数组中。必须通过X文章的插入 > 分割线菜单插入(X会忽略HTML 标签)。
parse_markdown.pydividers<hr>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.
- (Optional) Pre-process: Convert tables/mermaid to images
- Parse Markdown with Python script → get title, images, dividers 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
- Insert dividers at positions specified by block_index (via Insert > Divider menu)
- Save as draft (NEVER auto-publish)
策略:"先文后图后分割线"
对于包含图片和分割线的文章,先粘贴所有文本内容,再根据block_index在正确位置插入图片和分割线。
- (可选)预处理:将表格/Mermaid转换为图片
- 使用Python脚本解析Markdown → 获取标题、图片、含block_index的分割线、HTML
- 导航到X文章编辑器
- 上传封面图(第一张图片)
- 填写标题
- 使用Python将HTML复制到剪贴板 → 按Cmd+V粘贴
- 根据block_index指定的位置插入内容图片
- 根据block_index指定的位置插入分割线(通过插入 > 分割线菜单)
- 保存为草稿(绝不自动发布)
高效执行原则 (Efficiency Guidelines)
高效执行原则
目标: 最小化操作之间的等待时间,实现流畅的自动化体验。
目标: 最小化操作间的等待时间,实现流畅的自动化体验。
1. 避免不必要的 browser_snapshot
1. 避免不必要的browser_snapshot
大多数浏览器操作(click, type, press_key 等)都会在返回结果中包含页面状态。不要在每次操作后单独调用 ,直接使用操作返回的页面状态即可。
browser_snapshot❌ 错误做法:
browser_click → browser_snapshot → 分析 → browser_click → browser_snapshot → ...
✅ 正确做法:
browser_click → 从返回结果中获取页面状态 → browser_click → ...大多数浏览器操作(click、type、press_key等)会在返回结果中包含页面状态。不要在每次操作后单独调用,直接使用操作返回的页面状态即可。
browser_snapshot❌ 错误做法:
browser_click → browser_snapshot → 分析 → browser_click → browser_snapshot → ...
✅ 正确做法:
browser_click → 从返回状态中获取页面信息 → browser_click → ...2. 避免不必要的 browser_wait_for
2. 避免不必要的browser_wait_for
只在以下情况使用 :
browser_wait_for- 等待图片上传完成()
textGone="正在上传媒体" - 等待页面初始加载(极少数情况)
不要使用 来等待按钮或输入框出现 - 它们在页面加载完成后立即可用。
browser_wait_for仅在以下情况使用:
browser_wait_for- 等待图片上传完成()
textGone="正在上传媒体" - 等待页面初始加载(极少数情况)
不要使用等待按钮或输入框出现 - 页面加载完成后它们立即可用。
browser_wait_for3. 并行执行独立操作
3. 并行执行独立操作
当两个操作没有依赖关系时,可以在同一个消息中并行调用多个工具:
✅ 可以并行:
- 填写标题 (browser_type) + 复制HTML到剪贴板 (Bash)
- 解析Markdown生成JSON + 生成HTML文件
❌ 不能并行(有依赖):
- 必须先点击create才能上传封面图
- 必须先粘贴内容才能插入图片当两个操作无依赖关系时,可在同一消息中并行调用多个工具:
✅ 可以并行:
- 填写标题 (browser_type) + 将HTML复制到剪贴板 (Bash)
- 解析Markdown生成JSON + 生成HTML文件
❌ 不能并行(有依赖):
- 必须先点击创建按钮才能上传封面图
- 必须先粘贴内容才能插入图片4. 连续执行浏览器操作
4. 连续执行浏览器操作
每个浏览器操作返回的页面状态包含所有需要的元素引用。直接使用这些引用进行下一步操作:
undefined每个浏览器操作返回的页面状态包含所有需要的元素引用。直接使用这些引用进行下一步操作:
undefined理想流程(每步直接执行,不额外等待):
理想流程(每步直接执行,无额外等待):
browser_navigate → 从返回状态找create按钮 → browser_click(create)
→ 从返回状态找上传按钮 → browser_click(上传) → browser_file_upload
→ 从返回状态找应用按钮 → browser_click(应用)
→ 从返回状态找标题框 → browser_type(标题)
→ 点击编辑器 → browser_press_key(Meta+v)
→ ...
undefinedbrowser_navigate → 从返回状态中找到创建按钮 → browser_click(create)
→ 从返回状态中找到上传按钮 → browser_click(上传) → browser_file_upload
→ 从返回状态中找到应用按钮 → browser_click(应用)
→ 从返回状态中找到标题框 → browser_type(标题)
→ 点击编辑器 → browser_press_key(Meta+v)
→ ...
undefined5. 准备工作前置
5. 准备工作前置
在开始浏览器操作之前,先完成所有准备工作:
- 解析 Markdown 获取 JSON 数据
- 生成 HTML 文件到 /tmp/
- 记录 title、cover_image、content_images 等信息
这样浏览器操作阶段可以连续执行,不需要中途停下来处理数据。
在开始浏览器操作前,先完成所有准备工作:
- 解析Markdown获取JSON数据
- 生成HTML文件到/tmp/
- 记录title、cover_image、content_images等信息
这样浏览器操作阶段可连续执行,无需中途处理数据。
Step 1: Parse Markdown (Python)
步骤1:解析Markdown(Python)
Use to extract all structured data:
parse_markdown.pybash
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.mdOutput JSON:
json
{
"title": "Article Title",
"cover_image": "/path/to/first-image.jpg",
"cover_exists": true,
"content_images": [
{"path": "/path/to/img2.jpg", "original_path": "/md/dir/assets/img2.jpg", "exists": true, "block_index": 5, "after_text": "context..."},
{"path": "/path/to/img3.jpg", "original_path": "/md/dir/assets/img3.jpg", "exists": true, "block_index": 12, "after_text": "another..."}
],
"html": "<p>Content...</p><h2>Section</h2>...",
"total_blocks": 45,
"missing_images": 0
}Key fields:
- : The image should be inserted AFTER block element at this index (0-indexed)
block_index - : Total number of block elements in the HTML
total_blocks - : Kept for reference/debugging only, NOT for positioning
after_text - : Whether the image file was found (if false, upload will fail)
exists - : The path resolved from Markdown (before auto-search)
original_path - : The actual path to use (may differ from original_path if auto-searched)
path - : Count of images not found anywhere
missing_images
Save HTML to temp file for clipboard:
bash
python parse_markdown.py article.md --html-only > /tmp/article_html.html使用提取所有结构化数据:
parse_markdown.pybash
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md输出JSON:
json
{
"title": "Article Title",
"cover_image": "/path/to/first-image.jpg",
"cover_exists": true,
"content_images": [
{"path": "/path/to/img2.jpg", "original_path": "/md/dir/assets/img2.jpg", "exists": true, "block_index": 5, "after_text": "context..."},
{"path": "/path/to/img3.jpg", "original_path": "/md/dir/assets/img3.jpg", "exists": true, "block_index": 12, "after_text": "another..."}
],
"html": "<p>Content...</p><h2>Section</h2>...",
"total_blocks": 45,
"missing_images": 0
}关键字段:
- : 图片应插入到此索引对应的块元素之后(从0开始计数)
block_index - : HTML中的块元素总数
total_blocks - : 仅用于参考/调试,不用于定位
after_text - : 是否找到图片文件(若为false,上传会失败)
exists - : Markdown中解析出的路径(自动搜索前)
original_path - : 实际使用的路径(若自动搜索成功,可能与original_path不同)
path - : 未找到的图片数量
missing_images
将HTML保存到临时文件用于剪贴板:
bash
python parse_markdown.py article.md --html-only > /tmp/article_html.htmlStep 2: Open X Articles Editor
步骤2:打开X文章编辑器
浏览器错误处理
浏览器错误处理
如果遇到 错误:
Error: Browser is already in useundefined若遇到错误:
Error: Browser is already in useundefined方案1:先关闭浏览器再重新打开
方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles
browser_close
browser_navigate: https://x.com/compose/articles
方案2:如果 browser_close 无效(锁定),提示用户手动关闭 Chrome
方案2:若browser_close无效(锁定),提示用户手动关闭Chrome
方案3:使用已有标签页,直接导航
方案3:使用已有标签页,直接导航
browser_tabs action=list # 查看现有标签
browser_navigate: https://x.com/compose/articles # 在当前标签导航
**最佳实践**:每次开始前先用 `browser_tabs action=list` 检查状态,避免创建多余空白标签。browser_tabs action=list # 查看现有标签
browser_navigate: https://x.com/compose/articles # 在当前标签导航
**最佳实践**:每次开始前先用`browser_tabs action=list`检查状态,避免创建多余空白标签。导航到编辑器
导航到编辑器
browser_navigate: https://x.com/compose/articles重要: 页面加载后会显示草稿列表,不是编辑器。需要:
- 等待页面加载完成: 使用 检查页面状态
browser_snapshot - 立即点击 "create" 按钮: 不要等待 "添加标题" 等编辑器元素,它们只有点击 create 后才出现
- 等待编辑器加载: 点击 create 后,等待编辑器元素出现
undefinedbrowser_navigate: https://x.com/compose/articles重要: 页面加载后会显示草稿列表,而非编辑器。需要:
- 等待页面加载完成: 使用检查页面状态
browser_snapshot - 立即点击"创建"按钮: 不要等待“添加标题”等编辑器元素,它们仅在点击创建后才会出现
- 等待编辑器加载: 点击创建后,等待编辑器元素出现
undefined1. 导航到页面
1. 导航到页面
browser_navigate: https://x.com/compose/articles
browser_navigate: https://x.com/compose/articles
2. 获取页面快照,找到 create 按钮
2. 获取页面快照,找到创建按钮
browser_snapshot
browser_snapshot
3. 点击 create 按钮(通常 ref 类似 "create" 或带有 create 标签)
3. 点击创建按钮(通常ref类似"create"或带有create标签)
browser_click: element="create button", ref=<create_button_ref>
browser_click: element="create button", ref=<create_button_ref>
4. 现在编辑器应该打开了,可以继续上传封面图等操作
4. 现在编辑器应该已打开,可继续上传封面图等操作
**注意**: 不要使用 `browser_wait_for text="添加标题"` 来等待页面加载,因为这个文本只有在点击 create 后才出现,会导致超时。
If login needed, prompt user to log in manually.
**注意**: 不要使用`browser_wait_for text="添加标题"`等待页面加载,因为该文本仅在点击创建后才会出现,会导致超时。
若需要登录,提示用户手动登录。Step 3: Upload Cover Image
步骤3:上传封面图
- Click "添加照片或视频" button
- Use browser_file_upload with the cover image path (from JSON output)
- Verify image uploaded
- 点击“添加照片或视频”按钮
- 使用browser_file_upload并传入JSON输出中的封面图路径
- 验证图片上传完成
Step 4: Fill Title
步骤4:填写标题
- Find textbox with "添加标题" placeholder
- Use browser_type to input title (from JSON output)
- 找到占位符为“添加标题”的文本框
- 使用browser_type输入JSON输出中的标题
Step 5: Paste Text Content (Python Clipboard)
步骤5:粘贴文本内容(Python剪贴板)
Copy HTML to system clipboard using Python, then paste:
bash
undefined使用Python将HTML复制到系统剪贴板,然后粘贴:
bash
undefinedCopy HTML to clipboard
将HTML复制到剪贴板
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).python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html
然后在浏览器中:browser_click on editor textbox
browser_press_key: Meta+v
这将保留所有富文本格式(H2、粗体、链接、列表)。Step 6: Insert Content Images (Text Search Positioning)
步骤6:插入内容图片(文本搜索定位)
推荐方法: 使用 文字搜索定位,比 更直观可靠。
after_textblock_index推荐方法: 使用文字搜索定位,比更直观可靠。
after_textblock_index定位原理
定位原理
每张图片的 字段记录了它前一个段落的末尾文字(最多80字符)。在编辑器中搜索包含该文字的段落,点击后插入图片。
after_text每张图片的字段记录了其前一段落的末尾文字(最多80字符)。在编辑器中搜索包含该文字的段落,点击后插入图片。
after_text操作步骤
操作步骤
For each content image (from array), 按 block_index 从大到小的顺序:
content_imagesbash
undefined对于每个内容图片(来自数组),按block_index从大到小的顺序:
content_imagesbash
undefined1. Copy image to clipboard (with compression)
1. 将图片复制到剪贴板(带压缩)
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py image /path/to/img.jpg --quality 85
undefinedpython ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py image /path/to/img.jpg --quality 85
undefined2. 在 browser_snapshot 中搜索包含 after_text 的段落
2. 在browser_snapshot中搜索包含after_text的段落
找到该段落的 ref
找到该段落的ref
3. Click the paragraph containing after_text
3. 点击包含after_text的段落
browser_click: element="paragraph with target text", ref=<paragraph_ref>
browser_click: element="paragraph with target text", ref=<paragraph_ref>
4. 关键步骤: 按 End 键移动光标到行尾
4. 关键步骤: 按End键将光标移动到行尾
这一步非常重要!避免点击到段落中的链接导致位置偏移
这一步非常重要!避免点击到段落中的链接导致位置偏移
browser_press_key: End
browser_press_key: End
5. Paste image
5. 粘贴图片
browser_press_key: Meta+v
browser_press_key: Meta+v
6. Wait for upload (only use textGone, no time parameter)
6. 等待上传完成(仅使用textGone,不设置time参数)
browser_wait_for textGone="正在上传媒体"
undefinedbrowser_wait_for textGone="正在上传媒体"
undefined为什么需要按 End 键?
为什么需要按End键?
问题: 当段落包含链接时(如 ),点击段落可能会:
[链接文字](url)- 触发链接编辑弹窗
- 将光标定位在链接内部而非段落末尾
解决方案: 点击段落后立即按 键:
End- 确保光标移动到段落末尾
- 避免链接干扰
- 图片将正确插入在该段落之后
问题: 当段落包含链接时(如),点击段落可能会:
[链接文字](url)- 触发链接编辑弹窗
- 将光标定位在链接内部而非段落末尾
解决方案: 点击段落后立即按键:
End- 确保光标移动到段落末尾
- 避免链接干扰
- 图片将正确插入到该段落之后
定位策略
定位策略
在 browser_snapshot 返回的结构中,搜索 的关键词:
after_textyaml
textbox [ref=editor]:
generic [ref=p1]:
- StaticText: "元旦假期我在家里翻手机相册..." # 如果 after_text 包含这段文字,点击 p1
heading [ref=h1]:
- StaticText: "演示"
generic [ref=p2]:
- StaticText: "这东西到底有多省事儿?"
- link [ref=link1]: "Claude Code" # 注意:段落可能包含链接
...在browser_snapshot返回的结构中,搜索的关键词:
after_textyaml
textbox [ref=editor]:
generic [ref=p1]:
- StaticText: "元旦假期我在家里翻手机相册..." # 如果after_text包含这段文字,点击p1
heading [ref=h1]:
- StaticText: "演示"
generic [ref=p2]:
- StaticText: "这东西到底有多省事儿?"
- link [ref=link1]: "Claude Code" # 注意:段落可能包含链接
...反向插入示例
反向插入示例
如果有3张图片,block_index 分别为 5, 12, 27:
- 先插入 block_index=27 的图片(after_text 搜索 + End + 粘贴)
- 再插入 block_index=12 的图片
- 最后插入 block_index=5 的图片
从大到小插入可以避免位置偏移问题。
若有3张图片,block_index分别为5、12、27:
- 先插入block_index=27的图片(after_text搜索 + End + 粘贴)
- 再插入block_index=12的图片
- 最后插入block_index=5的图片
从大到小插入可避免位置偏移问题。
Step 6.5: Insert Dividers (Via Menu)
步骤6.5:插入分割线(通过菜单)
重要: Markdown 中的 分割线不能通过 HTML 标签粘贴(X Articles 会忽略它)。必须通过 X Articles 的 Insert 菜单插入。
---<hr>重要: Markdown中的分割线无法通过HTML 标签粘贴(X文章会忽略它)。必须通过X文章的插入菜单插入。
---<hr>操作步骤
操作步骤
For each divider (from array), in reverse order of block_index:
dividersundefined对于每个分割线(来自数组),按block_index从大到小的顺序:
dividersundefined1. Click the block element at block_index position
1. 点击编辑器中block_index位置的块元素
browser_click on the element at position block_index in the editor
browser_click on the element at position block_index in the editor
2. Open Insert menu (Add Media button)
2. 打开插入菜单(添加媒体按钮)
browser_click on "Insert" or "添加媒体" button
browser_click on "Insert" or "添加媒体" button
3. Click Divider menu item
3. 点击分割菜单项
browser_click on "Divider" or "分割线" menuitem
browser_click on "Divider" or "分割线" menuitem
Divider is inserted at cursor position
分割线将插入到光标位置
undefinedundefined与图片的插入顺序
与图片的插入顺序
建议先插入所有图片,再插入所有分割线。两者都按 block_index 从大到小的顺序:
- 插入所有图片(从最大 block_index 开始)
- 插入所有分割线(从最大 block_index 开始)
建议先插入所有图片,再插入所有分割线。两者都按block_index从大到小的顺序:
- 插入所有图片(从最大的block_index开始)
- 插入所有分割线(从最大的block_index开始)
Step 7: Save Draft
步骤7:保存草稿
- 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/divider placement
- Reverse order insertion - Insert images and dividers from highest to lowest block_index
- H1 title handling - H1 is used as title only, not included in body
- Dividers via menu - Markdown must be inserted via Insert > Divider menu (HTML
---is ignored)<hr>
- 绝不自动发布 - 仅保存草稿
- 第一张图片为封面 - 上传第一张图片作为封面图
- 富文本转换 - 发布前务必将Markdown转换为HTML
- 使用剪贴板API - 通过剪贴板粘贴以保证格式正确
- Block index定位 - 使用block_index精确定位图片/分割线位置
- 反向插入顺序 - 按block_index从高到低插入图片和分割线
- H1标题处理 - H1仅用作标题,不包含正文中
- 分割线通过菜单插入 - Markdown 必须通过插入 > 分割线菜单插入(HTML
---会被忽略)<hr>
Supported Formatting
支持的格式
| Element | Support | Notes |
|---|---|---|
H2 ( | Native | Section headers |
Bold ( | Native | Strong emphasis |
Italic ( | Native | Emphasis |
Links ( | Native | Hyperlinks |
| Ordered lists | Native | 1. 2. 3. |
| Unordered lists | Native | - bullets |
Blockquotes ( | Native | Quoted text |
| Code blocks | Converted | → Blockquotes |
| Tables | Converted | → PNG images (use table_to_image.py) |
| Mermaid | Converted | → PNG images (use mmdc) |
Dividers ( | Menu insert | → Insert > Divider |
| 元素 | 支持情况 | 说明 |
|---|---|---|
H2 ( | 原生支持 | 章节标题 |
粗体 ( | 原生支持 | 强调 |
斜体 ( | 原生支持 | 斜体强调 |
链接 ( | 原生支持 | 超链接 |
| 有序列表 | 原生支持 | 1. 2. 3. |
| 无序列表 | 原生支持 | - 项目符号 |
块引用 ( | 原生支持 | 引用文本 |
| 代码块 | 转换支持 | → 块引用 |
| 表格 | 转换支持 | → PNG图片(使用table_to_image.py) |
| Mermaid图表 | 转换支持 | → PNG图片(使用mmdc) |
分割线 ( | 菜单插入 | → 插入 > 分割线 |
Example Flow
示例流程
User: "Publish /path/to/article.md to X"
bash
undefined用户:“将/path/to/article.md发布到X”
bash
undefinedStep 1: Parse Markdown
步骤1:解析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
2. Navigate to https://x.com/compose/articles
3. Upload cover image (browser_file_upload for cover only)
4. Fill title (from JSON: `title`)
5. Copy & paste HTML:
```bash
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.htmlThen: browser_press_key Meta+v
6. For each content image, in reverse order of block_index:
bash
python copy_to_clipboard.py image /path/to/img.jpg --quality 85- Click block element at position
block_index - browser_press_key Meta+v
- Wait until upload complete
- Verify in preview
- "Draft saved. Please review and publish manually."
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
2. 导航到https://x.com/compose/articles
3. 上传封面图(仅对封面图使用browser_file_upload)
4. 填写标题(来自JSON的`title`字段)
5. 复制并粘贴HTML:
```bash
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html然后:browser_press_key Meta+v
6. 对于每个内容图片,按block_index从大到小的顺序:
bash
python copy_to_clipboard.py image /path/to/img.jpg --quality 85- 点击位置的块元素
block_index - browser_press_key Meta+v
- 等待上传完成
- 在预览中验证
- 反馈:“草稿已保存,请审核后手动发布。”
Best Practices
最佳实践
为什么用 block_index 而非文字匹配?
为什么用block_index而非文字匹配?
- 精确定位: 不依赖文字内容,即使多处文字相似也能正确定位
- 可靠性: 索引是确定性的,不会因为文字相似而混淆
- 调试方便: 仍保留用于人工核验
after_text
- 精确定位: 不依赖文字内容,即使多处文字相似也能正确定位
- 可靠性: 索引是确定性的,不会因文字相似而混淆
- 调试方便: 仍保留用于人工核验
after_text
为什么用 Python 而非浏览器内 JavaScript?
为什么用Python而非浏览器内JavaScript?
- 本地处理更可靠: Python 直接操作系统剪贴板,不受浏览器沙盒限制
- 图片压缩: 上传前压缩图片 (--quality 85),减少上传时间
- 代码复用: 脚本固定不变,无需每次重新编写转换逻辑
- 调试方便: 脚本可单独测试,问题易定位
- 本地处理更可靠: Python直接操作系统剪贴板,不受浏览器沙盒限制
- 图片压缩: 上传前压缩图片(--quality 85),减少上传时间
- 代码复用: 脚本固定不变,无需每次重新编写转换逻辑
- 调试方便: 脚本可单独测试,问题易定位
等待策略
等待策略
重要发现: Playwright MCP 的 实际行为是 先等待 time 秒,再检查条件,而非轮询!
browser_wait_forjavascript
// 实际执行的代码:
await new Promise(f => setTimeout(f, time * 1000)); // 先固定等待
await page.getByText("xxx").waitFor({ state: 'hidden' }); // 再检查正确用法:
- ✅ 只用 ,不设
textGone:让 Playwright 自己轮询等待time - ✅ 只用 :固定等待指定秒数
time - ❌ 同时用 +
textGone:会先等 time 秒再检查,浪费时间time
undefined重要发现: Playwright MCP的实际行为是先等待time秒,再检查条件,而非轮询!
browser_wait_forjavascript
// 实际执行的代码:
await new Promise(f => setTimeout(f, time * 1000)); // 先固定等待
await page.getByText("xxx").waitFor({ state: 'hidden' }); // 再检查正确用法:
- ✅ 仅使用,不设置
textGone:让Playwright自动轮询等待time - ✅ 仅使用:固定等待指定秒数
time - ❌ 同时使用+
textGone:会先等待time秒再检查,浪费时间time
undefined推荐:只用 textGone,让它自动等待条件满足
推荐:仅使用textGone,让它自动等待条件满足
browser_wait_for textGone="正在上传媒体"
browser_wait_for textGone="正在上传媒体"
或者:用 browser_snapshot 轮询检查状态
或者:使用browser_snapshot轮询检查状态
每次操作后检查返回的页面状态,无需额外等待
每次操作后检查返回的页面状态,无需额外等待
undefinedundefined图片插入效率
图片插入效率
每张图片的浏览器操作从5步减少到2步:
- 旧: 点击 → 添加媒体 → 媒体 → 添加照片 → file_upload
- 新: 点击段落 → Meta+v
每张图片的浏览器操作从5步减少到2步:
- 旧流程: 点击 → 添加媒体 → 媒体 → 添加照片 → file_upload
- 新流程: 点击段落 → Meta+v
封面图 vs 内容图
封面图 vs 内容图
- 封面图: 使用 browser_file_upload(因为有专门的上传按钮)
- 内容图: 使用 Python 剪贴板 + 粘贴(更高效)
- 封面图: 使用browser_file_upload(因为有专门的上传按钮)
- 内容图: 使用Python剪贴板 + 粘贴(更高效)
故障排除
故障排除
MCP 连接问题
MCP连接问题
如果 Playwright MCP 工具不可用(报错 或 ):
No such tool availableNot connected方案1:重新连接 MCP(推荐)
执行 /mcp 命令,选择 playwright,选择 Restart方案2:清理残留进程后重连
bash
undefined若Playwright MCP工具不可用(报错或):
No such tool availableNot connected方案1:重新连接MCP(推荐)
执行/mcp命令,选择playwright,选择Restart方案2:清理残留进程后重连
bash
undefined杀掉所有残留的 playwright 进程
杀掉所有残留的playwright进程
pkill -f "mcp-server-playwright"
pkill -f "@playwright/mcp"
pkill -f "mcp-server-playwright"
pkill -f "@playwright/mcp"
然后执行 /mcp 重新连接
然后执行/mcp重新连接
**配置文件位置**: `~/.claude/mcp_servers.json`
**配置文件位置**: `~/.claude/mcp_servers.json`浏览器错误处理
浏览器错误处理
如果遇到 错误:
Error: Browser is already in usebash
undefined若遇到错误:
Error: Browser is already in usebash
undefined方案1:先关闭浏览器再重新打开
方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles
browser_close
browser_navigate: https://x.com/compose/articles
方案2:杀掉 Chrome 进程
方案2:杀掉Chrome进程
pkill -f "Chrome.*--remote-debugging"
pkill -f "Chrome.*--remote-debugging"
然后重新 navigate
然后重新导航
undefinedundefined图片位置偏移
图片位置偏移
如果图片插入位置不正确(特别是点击含链接的段落时):
原因: 点击段落时可能误触链接,导致光标位置错误
解决方案: 点击后必须按 End 键移动光标到行尾
undefined若图片插入位置不正确(特别是点击含链接的段落时):
原因: 点击段落时可能误触链接,导致光标位置错误
解决方案: 点击后必须按End键将光标移动到行尾
undefined正确流程
正确流程
- browser_click 点击目标段落
- browser_press_key: End # 关键步骤!
- browser_press_key: Meta+v # 粘贴图片
- browser_wait_for textGone="正在上传媒体"
undefined- browser_click点击目标段落
- browser_press_key: End # 关键步骤!
- browser_press_key: Meta+v # 粘贴图片
- browser_wait_for textGone="正在上传媒体"
undefined图片路径找不到
图片路径找不到
如果 Markdown 中的相对路径图片找不到(如 实际在其他位置):
./assets/image.png自动搜索: 会自动在以下目录搜索同名文件:
parse_markdown.py~/Downloads~/Desktop~/Pictures
stderr 输出示例:
[parse_markdown] Image not found at '/path/to/assets/img.png', using '/Users/xxx/Downloads/img.png' insteadJSON 字段说明:
- : Markdown 中指定的路径(解析后的绝对路径)
original_path - : 实际使用的路径(如果自动搜索成功,会不同于 original_path)
path - :
exists表示找到文件,true表示未找到(上传会失败)false
如果仍然找不到:
- 检查 JSON 输出中的 字段
missing_images - 手动将图片复制到 Markdown 文件同目录的 子目录
assets/ - 或修改 Markdown 中的图片路径为绝对路径
若Markdown中的相对路径图片找不到(如实际在其他位置):
./assets/image.png自动搜索: 会自动在以下目录搜索同名文件:
parse_markdown.py~/Downloads~/Desktop~/Pictures
stderr输出示例:
[parse_markdown] Image not found at '/path/to/assets/img.png', using '/Users/xxx/Downloads/img.png' insteadJSON字段说明:
- : Markdown中指定的路径(解析后的绝对路径)
original_path - : 实际使用的路径(若自动搜索成功,会不同于original_path)
path - :
exists表示找到文件,true表示未找到(上传会失败)false
如果仍然找不到:
- 检查JSON输出中的字段
missing_images - 手动将图片复制到Markdown文件同目录的子目录
assets/ - 或修改Markdown中的图片路径为绝对路径