关于 publish-x-article
publish-x-article 是将 Markdown 文章整理并发布到 X Articles 编辑器的技能,会按订阅类型处理格式限制,并通过浏览器自动化和剪贴板插入内容。它适用于需要保留文章结构并处理表格、图表等不支持元素的发布流程。
主要功能
- 01解析 Markdown 并生成富文本 HTML
- 02按订阅类型调整标题与表格
- 03将表格和 Mermaid 图转换为图片
- 04通过浏览器与剪贴板填充编辑器
- 05按区块位置插入图片和分隔线
适用场景
- 01将 Markdown 文章整理到 X Articles
- 02为 X Premium 用户转换不支持的格式
- 03在文章中按原位置插入图片与分隔线
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sugarforever/ 01coder-agent-skills --skill publish-x-article安装后在 Claude Code 中输入/publish-x-article确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
scripts/copy_to_clipboard.pyparse_markdown.pytable_to_image.py详细介绍
发布 X 文章
将 Markdown 内容发布到 X(Twitter)文章编辑器,并通过富文本转换保留格式。自动处理 X Premium 的限制,将不受支持的元素转换为图片。
致谢
此技能受 wshuyi/x-article-publisher-skill 启发,并基于该项目构建。感谢原作者奠定了基础。
交互式设置:询问订阅类型
重要:如果尚不清楚用户的 X 订阅类型,请在处理文章前询问用户。
提示用户
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+)
面向中文用户:
在发布之前,我需要了解您的 X 订阅类型以正确处理格式:
1. **X Premium** - 基础版 ($8/月)
2. **X Premium+** - 高级版 ($16/月)
您使用的是哪个版本?(Premium / Premium+)
记住答案
用户回答后,在本次会话的剩余时间内记住其订阅类型。除非用户明确表示要更改,否则不要再次询问。
X 订阅功能对比
| 功能 | X Premium | X Premium+ |
|---|---|---|
H1 标题(#) | 仅标题 | 仅标题 |
H2 标题(##) | 支持 | 支持 |
H3 及更深层级标题(### 等) | 不支持 | 支持 |
| Markdown 表格 | 不支持 | 支持 |
| Mermaid 图表 | 不支持 | 不支持(X 不支持) |
| 代码块 | 引用块 | 引用块 |
| 粗体、斜体、链接 | 支持 | 支持 |
| 列表 | 支持 | 支持 |
| 引用块 | 支持 | 支持 |
| 图片 | 支持 | 支持 |
根据订阅类型进行预处理
X Premium(基础版):
- 将 H3 及更深层级标题转换为 H2 或粗体
- 将表格转换为 PNG 图片
- 将 Mermaid 图表转换为 PNG 图片
X Premium+(高级版):
- 保留 H3 及更深层级标题
- 保留表格(原生渲染)
- 将 Mermaid 图表转换为 PNG 图片(仍不受支持)
前提条件
- 用于浏览器自动化的 Playwright MCP
- 用户已使用 Premium 订阅登录 X
- Python 3.9+ 及依赖项:
- macOS:
pip install Pillow pyobjc-framework-Cocoa markdown - Windows:
pip install Pillow pywin32 clip-util markdown
- macOS:
- diagram-to-image 技能(通过 diagramless.xyz API 将表格/Mermaid 转换为 PNG)
脚本
位于 ~/.claude/skills/publish-x-article/scripts/:
parse_markdown.py
解析 Markdown 并提取结构化数据:
python parse_markdown.py <markdown_file> [--output json|html] [--html-only]
返回包含以下内容的 JSON:title、cover_image、content_images(包含用于定位的 block_index)、html、total_blocks
copy_to_clipboard.py
将图片或 HTML 复制到系统剪贴板:
# 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(来自 diagram-to-image 技能)
通过 diagramless.xyz API 将 Mermaid 图表和 Markdown 表格转换为 PNG 图片:
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
预处理:处理不受支持的元素
发布前,扫描 Markdown 中不受支持的元素,并将其转换为图片。
步骤 0:分析内容是否有限制元素
# Check the markdown file for unsupported elements
cat /path/to/article.md
查找:
- 深层级标题(H3+):
###、####等。 - Markdown 表格:包含
|字符且构成表格结构的行 - Mermaid 代码块:
```mermaid
将不受支持的元素转换为图片
1. Markdown 表格 → PNG
检测到表格时:
# 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
# 
2. Mermaid 图表 → PNG
检测到 Mermaid 代码块时:
# 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
# 
3. 深层标题(H3+)→ 简化结构
**目标:**在符合 X Premium 仅支持 H2 的限制下,保留文章的逻辑结构和可读性。
AI 指南:
当你在 Premium 用户的文章中遇到 H3、H4 或更深层级的标题时,请思考作者的意图:
-
如果标题在某个章节下引出一个独立子主题,将其转换为段落开头的粗体文本。这样既能保留视觉层级,又不会破坏 X 的格式。
-
**如果某个 H3 实际上是主要章节,**可以考虑将其提升为 H2,但前提是这样做不会让结构变得扁平、缺乏意义。文章仍应保持逻辑流畅。
-
如果存在深层级结构(H2 → H3 → H4),请有选择地简化:
- 保留 H2
- 将 H3 转换为粗体段落
- 将 H4 转换为斜体,或自然地并入段落
-
**保留含义优先于保留结构。**像
### Why This Matters这样的标题可以改成粗体引导语:**这为什么重要?**,后面接正文。请自行判断。 -
**阅读正文。**如果 H3 标题只是“示例”或“注意”,将其处理为引用块或行内强调,可能比单独一行粗体文字更合适。
目标不是机械转换,而是在尊重作者意图的同时,让文章在 X 上读起来自然。
预处理工作流
发布前,请通读文章并为其在 X 上发布做好准备:
-
了解文章结构——先阅读文章。主要章节有哪些?作者如何用标题组织思路?
-
处理表格和 Mermaid 图表——这些内容需要转换为图片。提取每个表格和图表,将其转换为 PNG,并记录插入位置。
-
根据订阅等级调整标题:
- 对于 Premium+ 用户:保留原有标题(支持 H3+)
- 对于 Premium 用户:妥善调整 H3+ 标题,同时保留文章的流畅度和原意(参见上述指南)
-
创建修改后的 Markdown——将调整后的版本保存到临时文件,以便解析。
目标是让文章在 X 上读起来自然,而不是机械地转换文档。
主要工作流
策略:“先文后图后分割线”
对于包含图片和分割线的文章,先粘贴所有文本内容,再根据 block_index 在正确位置插入图片和分割线。
- 预处理:将表格/Mermaid 图表转换为图片,简化深层标题
- 使用 Python 脚本解析修改后的 Markdown → 获取标题、图片、分割线(均含 block_index)和 HTML
- 打开 X Articles 编辑器
- 上传封面图片(第一张图片)
- 填写标题
- 将 HTML 复制到剪贴板(Python)→ 使用 Cmd+V 粘贴
- 在 block_index 指定的位置插入正文图片
- 在 block_index 指定的位置插入分割线(通过“插入 > 分割线”菜单)
- 保存为草稿(切勿自动发布)
高效执行原则
目标:最大限度减少操作之间的等待时间,实现流畅的自动化体验。
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. 提前完成准备工作
开始浏览器操作前,先完成所有准备工作:
- 扫描不支持的元素(表格、Mermaid、深层标题)
- 将表格/Mermaid 转换为图片
- 解析 Markdown 并获取 JSON 数据
- 在 /tmp/ 中生成 HTML 文件
- 记录 title、cover_image、content_images 等信息
这样,在浏览器操作阶段就可以连续执行,无需中途停下来处理数据。
步骤 0:阅读并调整文章
**首先,阅读文章。**了解文章主题、结构,以及作者想要传达的内容。
保留原文
**重要提示:**切勿修改用户的原始文件。如果需要调整:
- 将调整后的版本另存为副本(例如
/tmp/article_adapted.md,或与原文件放在一起并命名为article_for_x.md) - 告知用户修改了什么,以及两个文件的位置:
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 - 使用调整后的副本进行发布
这样可以确保用户能够查看修改内容,并保留原始作品。
根据订阅方案进行调整
对于 Premium 用户(Basic Tier)
请考虑:
- 有表格吗?→ 将每个表格转换为 PNG 图片
- 有 mermaid 图表吗?→ 将每个图表转换为 PNG 图片
- 有 H3 及更高级别的标题吗?→ 根据需要重新组织(参见上面的标题指南)
创建一个经过修改的 Markdown 版本,使其符合 Premium 的限制,同时保持易读性。
对于 Premium+ 用户
请考虑:
- 有 mermaid 图表吗?→ 转换为 PNG(所有等级目前都不支持)
- 表格和 H3 及更高级别的标题可以保留原样
将元素转换为图片
# 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
在 Markdown 中将这些元素替换为图片引用,并放在原元素所在的位置。
步骤 1:解析 Markdown(Python)
使用 parse_markdown.py 提取所有结构化数据:
python ~/.claude/skills/publish-x-article/scripts/parse_markdown.py /path/to/modified_article.md
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
}
主要字段:
block_index:应在此索引处的块元素之后插入图片/分隔线(从 0 开始计数)total_blocks:HTML 中块元素的总数after_text:仅供参考/调试,不用于定位dividers:分隔线位置数组(必须通过 X 菜单插入 Markdown---,不能使用 HTML<hr>)
将 HTML 保存到临时文件,以供剪贴板使用:
python parse_markdown.py modified_article.md --html-only > /tmp/article_html.html
步骤 2:打开 X Articles 编辑器
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 后才会出现,否则会导致超时。
如果需要登录,请提示用户手动登录。
步骤 3:上传封面图片
- 点击“添加照片或视频”按钮
- 使用 browser_file_upload 上传封面图片(路径来自 JSON 输出)
- 确认图片已上传
步骤 4:填写标题
- 查找占位符为“添加标题”的文本框
- 使用 browser_type 输入标题(来自 JSON 输出)
步骤 5:粘贴文本内容(Python 剪贴板)
使用 Python 将 HTML 复制到系统剪贴板,然后粘贴:
# Copy HTML to clipboard
python ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py html --file /tmp/article_html.html
然后在浏览器中:
browser_click on editor textbox
browser_press_key: Meta+v
这样可以保留所有富文本格式(H2、粗体、链接、列表)。
步骤 6:插入内容图片(按块索引定位)
**关键改进:**使用 block_index 精确定位,而非依赖文字匹配。
定位原理
粘贴 HTML 后,编辑器中的内容由一系列块元素(段落、标题、引用等)构成。每张图片的 block_index 表示应在第 N 个块元素之后插入该图片。
操作步骤
- 获取所有块元素:使用 browser_snapshot 获取编辑器内容,找到文本框下的所有子元素
- 按索引定位:根据
block_index点击对应的块元素 - 粘贴图片:将图片复制到剪贴板后粘贴
对于每张内容图片(来自 content_images 数组):
# 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
反向插入
**注意:**每插入一张图片后,后续图片的实际位置都会偏移。建议按 block_index 从大到小的顺序插入图片。
如果有 3 张图片,block_index 分别为 5、12、27:
- 先插入
block_index=27的图片 - 再插入
block_index=12的图片 - 最后插入
block_index=5的图片
步骤 6.5:插入分隔线(通过菜单)
**重要:**Markdown 中的 --- 分隔线不能通过粘贴 HTML <hr> 标签插入(X Articles 会忽略它)。必须通过 X Articles 的 Insert 菜单插入。
为什么需要特殊处理
X Articles 有自己的原生分隔线元素,只能通过 Insert > Divider 菜单插入。HTML <hr> 标签会被完全忽略。
操作步骤
对于每条分隔线(来自 dividers 数组),按 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
反向插入
与图片一样,按 block_index 从大到小的顺序插入分隔线,避免位置偏移。
图片与分隔线的插入顺序
建议先插入所有图片,再插入所有分隔线。两者都按 block_index 从大到小的顺序插入:
- 插入所有图片(从最大
block_index开始) - 插入所有分隔线(从最大
block_index开始)
步骤 7:保存草稿
- 确认内容已粘贴(查看字数指示器)
- 草稿会自动保存;如有需要,也可以点击 Save 按钮
- 点击“预览”以确认格式
- 报告:“草稿已保存。请检查并手动发布。”
关键规则
- 绝不发布——只保存草稿
- 先预处理——解析前转换表格、mermaid 和深层标题
- 第一张图片作为封面——将第一张图片上传为封面图片
- 富文本转换——粘贴前始终将 Markdown 转换为 HTML
- 使用剪贴板 API——通过剪贴板粘贴,以正确保留格式
- 按区块索引定位——使用 block_index 精确放置图片和分隔线
- 按逆序插入——按照从最大到最小的 block_index 顺序插入图片和分隔线
- 处理 H1 标题——H1 仅用作标题,不包含在正文中
- 通过菜单插入分隔线——Markdown
---必须通过 Insert > Divider 菜单插入(HTML<hr>会被忽略)
支持的格式(预处理后)
| 元素 | 支持情况 | 备注 |
|---|---|---|
H2 (##) | 原生支持 | 章节标题 |
粗体 (**) | 原生支持 | 强调 |
斜体 (*) | 原生支持 | 强调 |
链接 ([](url)) | 原生支持 | 超链接 |
| 有序列表 | 原生支持 | 1. 2. 3. |
| 无序列表 | 原生支持 | - 项目符号 |
引用块 (>) | 原生支持 | 引用文本 |
| 代码块 | 转换 | → 引用块 |
| 表格 | 转换 | → PNG 图片 |
| Mermaid | 转换 | → PNG 图片 |
| H3 及更深层级标题 | 转换 | → H2 或粗体 |
分隔线 (---) | 通过菜单插入 | → Insert > Divider |
示例流程
用户:“将 /path/to/article.md 发布到 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
- 导航到 https://x.com/compose/articles
- 点击创建,上传封面图片(仅封面使用 browser_file_upload)
- 填写标题(来自 JSON:
title) - 复制并粘贴 HTML:
然后:browser_press_key Meta+vpython ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py html --file /tmp/article_html.html - 对每张正文图片(包括转换后的表格/mermaid PNG),按照 block_index 的逆序操作:
python copy_to_clipboard.py image /path/to/img.jpg --quality 85- 点击
block_index位置的区块元素 - browser_press_key Meta+v
- 等待上传完成
- 点击
- 在预览中检查
- “草稿已保存。请检查并手动发布。”
最佳实践
为什么用 block_index 而非文字匹配?
- 精确定位:不依赖文字内容,即使多处文字相似也能正确定位
- 可靠性:索引是确定性的,不会因为文字相似而混淆
- 便于调试:仍保留
after_text供人工核验
为什么用 Python 而非浏览器内 JavaScript?
- 本地处理更可靠:Python 直接操作系统剪贴板,不受浏览器沙盒限制
- 图片压缩:上传前压缩图片(--quality 85),减少上传时间
- 代码复用:脚本固定不变,无需每次重新编写转换逻辑
- 便于调试:脚本可单独测试,问题易于定位
等待策略
关键理解:browser_wait_for 的 textGone 参数会在文字消失时立即返回,time 只是最大等待时间,不是固定等待时间。
# 正确用法:短 time 值,条件满足立即返回
browser_wait_for textGone="正在上传媒体" time=2
# 错误用法:固定长时间等待
browser_wait_for time=5 # 无条件等待5秒,浪费时间
封面图 vs 正文图片
- 封面图:使用 browser_file_upload(因为有专门的上传按钮)
- 正文图片:使用 Python 剪贴板 + 粘贴(效率更高)
故障排除
表格渲染不正确
- 确保已安装 Pillow:
pip install pillow - 检查表格 Markdown 语法是否有效
Mermaid 转换失败
- 检查 diagramless.xyz 是否可访问
- 检查 mermaid 语法是否有效
- 尝试指定类型:
--type mermaid
深层标题仍然显示
- 手动将
###展平为##或**bold** - 重新运行预处理
图片上传超时
- 使用
--quality 70压缩图片 - 使用更短的
browser_wait_for time=2