x-article-publisher について
x-article-publisherは、Markdownの記事をX Articlesエディター向けに整えるスキルです。書式や画像、区切り線を配置し、自動公開せず下書きとして保存します。
主な機能
- 01Markdownのタイトル、画像、区切り線の解析
- 02MarkdownからリッチテキストHTMLへの変換
- 03クリップボード経由のテキストと画像の貼り付け
- 04表とMermaid図の画像化
- 05指定位置への画像と区切り線の挿入
- 06下書き保存と書式のプレビュー
活用シーン
- 01Markdown記事のX Articles向け準備
- 02記事内の表やMermaid図の変換
- 03カバー画像や本文画像の適切な位置への挿入
MCP 対応
Playwright MCPを使い、X Articlesエディターをブラウザー操作します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshuyi/ x-article-publisher-skill --skill x-article-publisherインストール後、Claude Code で/x-article-publisherを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
scripts/copy_to_clipboard.pyparse_markdown.pytable_to_image.py詳細
X 記事パブリッシャー
Markdown のコンテンツを X(Twitter)の記事エディターに公開します。リッチテキスト変換により書式を保持します。
前提条件
- ブラウザー自動化用の Playwright MCP
- Premium Plus サブスクリプションで X にログインしていること
- 依存関係を含む 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
スクリプト
~/.claude/skills/x-article-publisher/scripts/ にあります:
parse_markdown.py
Markdown を解析し、構造化データを抽出します:
python parse_markdown.py <markdown_file> [--output json|html] [--html-only]
title、cover_image、content_images、dividers(配置用の block_index を含む)、html、total_blocks を含む JSON を返します。
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
table_to_image.py
Markdown の表を PNG 画像に変換します:
python table_to_image.py <input.md> <output.png> [--scale 2]
X 記事が表のネイティブ表示に対応していない場合や、一貫したスタイルにする場合に使用します。
前処理(任意)
公開前に Markdown をスキャンし、変換が必要な要素を確認します:
表 → PNG
# Extract table to temp file, then convert
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/table.md /tmp/table.png
# Replace table in markdown with: 
Mermaid 図 → PNG
# Extract mermaid block to .mmd file, then convert
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
# Replace mermaid block with: 
区切り線(---)
区切り線は parse_markdown.py によって自動検出され、dividers 配列に出力されます。X では HTML の <hr> タグが無視されるため、X 記事の Insert > Divider メニューから挿入する必要があります。
ワークフロー
方針:「テキスト、画像、区切り線の順」
画像と区切り線を含む記事では、まずテキストコンテンツをすべて貼り付け、その後、block index を使って正しい位置に画像と区切り線を挿入します。
- (任意) 前処理:表や Mermaid 図を画像に変換
- Python スクリプトで Markdown を解析 → タイトル、画像、block_index 付きの dividers、HTML を取得
- X 記事エディターに移動
- カバー画像(最初の画像)をアップロード
- タイトルを入力
- HTML をクリップボードにコピー(Python)→ Cmd+V で貼り付け
- block_index で指定された位置にコンテンツ画像を挿入
- block_index で指定された位置に区切り線を挿入(Insert > Divider メニューから)
- 下書きとして保存(自動公開は絶対にしない)
効率的な実行原則
目標:操作間の待ち時間を最小限に抑え、スムーズな自動化を実現します。
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. 独立した操作を並列実行する
2 つの操作に依存関係がない場合は、同じメッセージ内で複数のツールを並列に呼び出せます:
✅ 可以并行:
- 填写标题 (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 などの情報を記録
こうすることで、ブラウザー操作の段階を中断せずに連続して実行できます。
ステップ 1:Markdown を解析する(Python)
parse_markdown.py を使用して、すべての構造化データを抽出します:
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /path/to/article.md
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
}
主なフィールド:
block_index:このインデックス(0 始まり)のブロック要素の後に画像を挿入しますtotal_blocks:HTML 内のブロック要素の合計数after_text:参照/デバッグ用にのみ保持されます。配置には使用しませんexists:画像ファイルが見つかったかどうか(false の場合、アップロードは失敗します)original_path:Markdown から解決されたパス(自動検索の前)path:使用する実際のパス(自動検索された場合、original_path と異なることがあります)missing_images:どこにも見つからなかった画像の数
クリップボード用に HTML を一時ファイルへ保存します:
python parse_markdown.py article.md --html-only > /tmp/article_html.html
ステップ 2:X 記事エディターを開く
ブラウザーエラーの処理
Error: Browser is already in use エラーが発生した場合:
# 方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles
# 方案2:如果 browser_close 无效(锁定),提示用户手动关闭 Chrome
# 方案3:使用已有标签页,直接导航
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 をクリックした後、エディター要素が表示されるのを待ちます
# 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:カバー画像をアップロードする
- 「写真または動画を追加」ボタンをクリックします
- カバー画像のパス(JSON 出力から取得)を指定して browser_file_upload を使用します
- 画像がアップロードされたことを確認します
ステップ 4:タイトルを入力する
- プレースホルダーが「タイトルを追加」のテキストボックスを探します
- browser_type を使用してタイトル(JSON 出力から取得)を入力します
ステップ 5:テキストコンテンツを貼り付ける(Python クリップボード)
Python を使って HTML をシステムクリップボードにコピーしてから、貼り付けます:
# Copy HTML to clipboard
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、太字、リンク、リスト)が保持されます。
ステップ 6:コンテンツ画像を挿入する(テキスト検索による位置指定)
推奨方法:block_index より直感的で信頼性の高い、after_text によるテキスト検索で位置を指定します。
位置指定の仕組み
各画像の after_text フィールドには、その直前の段落の末尾のテキスト(最大80文字)が記録されています。エディターでそのテキストを含む段落を検索し、クリックしてから画像を挿入します。
操作手順
各コンテンツ画像(content_images 配列から)について、block_index の大きい順に:
# 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. 在 browser_snapshot 中搜索包含 after_text 的段落
# 找到该段落的 ref
# 3. Click the paragraph containing after_text
browser_click: element="paragraph with target text", ref=<paragraph_ref>
# 4. **关键步骤**: 按 End 键移动光标到行尾
# 这一步非常重要!避免点击到段落中的链接导致位置偏移
browser_press_key: End
# 5. Paste image
browser_press_key: Meta+v
# 6. Wait for upload (only use textGone, no time parameter)
browser_wait_for textGone="正在上传媒体"
End キーを押す必要がある理由
問題:段落にリンクが含まれている場合(例:[リンクテキスト](url))、段落をクリックすると次のようなことが起きる場合があります:
- リンク編集ポップアップが表示される
- カーソルが段落の末尾ではなくリンク内に配置される
解決策:段落をクリックしたら、すぐに End キーを押します:
- カーソルが段落の末尾に移動するようにします
- リンクによる干渉を防ぎます
- 画像がその段落の後に正しく挿入されます
位置指定の方法
browser_snapshot が返す構造の中から、after_text のキーワードを検索します:
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 の画像を挿入します
大きい順に挿入することで、位置がずれる問題を防げます。
ステップ 6.5:区切り線を挿入する(メニューを使用)
重要:Markdown の --- 区切り線は、HTML の <hr> タグを貼り付けても挿入できません(X Articles はこれを無視します)。X Articles の Insert メニューから挿入する必要があります。
操作手順
各区切り線(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 (Add Media button)
browser_click on "Insert" or "添加媒体" button
# 3. Click Divider menu item
browser_click on "Divider" or "分割线" menuitem
# Divider is inserted at cursor position
画像との挿入順序
最初にすべての画像を挿入し、次にすべての区切り線を挿入することをおすすめします。どちらも block_index の大きい順に挿入します:
- すべての画像を挿入します(block_index が最大のものから開始)
- すべての区切り線を挿入します(block_index が最大のものから開始)
ステップ 7:下書きを保存する
- コンテンツが貼り付けられたことを確認します(文字数インジケーターを確認)
- 下書きは自動保存されます。必要に応じて保存ボタンをクリックします
- 「プレビュー」をクリックして書式を確認します
- 次のように報告します:「下書きを保存しました。内容を確認し、手動で公開してください。」
重要なルール
- 絶対に公開しない — 下書きの保存のみ行います
- 最初の画像 = カバー — 最初の画像をカバー画像としてアップロードします
- リッチテキストへの変換 — 貼り付ける前に、必ず Markdown を HTML に変換します
- クリップボード API を使用する — 適切な書式を保持するため、クリップボード経由で貼り付けます
- block_index による位置指定 — 画像や区切り線を正確な位置に配置するには block_index を使用します
- 逆順で挿入する — 画像と区切り線は、block_index の大きい順に挿入します
- H1 タイトルの扱い — H1 はタイトルとしてのみ使用し、本文には含めません
- メニューから区切り線を挿入する — Markdown の
---は Insert > Divider メニューから挿入する必要があります(HTML の<hr>は無視されます)
対応している書式
| 要素 | 対応状況 | 備考 |
|---|---|---|
H2 (##) | ネイティブ | セクション見出し |
太字 (**) | ネイティブ | 強調 |
斜体 (*) | ネイティブ | 強調 |
リンク ([](url)) | ネイティブ | ハイパーリンク |
| 順序付きリスト | ネイティブ | 1. 2. 3. |
| 順序なしリスト | ネイティブ | - 箇条書き |
引用 (>) | ネイティブ | 引用テキスト |
| コードブロック | 変換 | → 引用 |
| テーブル | 変換 | → PNG 画像(table_to_image.py を使用) |
| Mermaid | 変換 | → PNG 画像(mmdc を使用) |
区切り線 (---) | メニューから挿入 | → Insert > Divider |
操作例
ユーザー:「/path/to/article.md を 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
- https://x.com/compose/articles に移動します
- カバー画像をアップロードします(カバーのみ browser_file_upload を使用)
- タイトルを入力します(JSON の
titleから取得) - HTML をコピーして貼り付けます:
次に:browser_press_key Meta+vpython ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html - 各コンテンツ画像について、block_index の逆順に:
python copy_to_clipboard.py image /path/to/img.jpg --quality 85block_indexの位置にあるブロック要素をクリックします- browser_press_key Meta+v
- アップロードが完了するまで待ちます
- プレビューで確認します
- 「下書きを保存しました。確認して、手動で公開してください。」
ベストプラクティス
なぜテキスト検索ではなく block_index を使うのですか?
- 正確な位置指定:テキストの内容に依存しないため、似たテキストが複数箇所にあっても正しく位置を指定できます
- 信頼性:インデックスは確定的なため、似たテキストによって混同されることがありません
- デバッグが容易:
after_textは人による確認用に引き続き保持されます
なぜブラウザー内の JavaScript ではなく Python を使うのですか?
- ローカルでの処理のほうが信頼性が高い:Python はシステムクリップボードを直接操作するため、ブラウザーのサンドボックスによる制限を受けません
- 画像の圧縮:アップロード前に画像を圧縮(--quality 85)し、アップロード時間を短縮します
- コードの再利用:スクリプトは固定されており、変換ロジックを毎回書き直す必要がありません
- デバッグが容易:スクリプトを個別にテストできるため、問題の特定が容易です
等待策略
重要发现: Playwright MCP 的 browser_wait_for 实际行为是 先等待 time 秒,再检查条件,而非轮询!
// 实际执行的代码:
await new Promise(f => setTimeout(f, time * 1000)); // 先固定等待
await page.getByText("xxx").waitFor({ state: 'hidden' }); // 再检查
正确用法:
- ✅ 只用
textGone,不设time:让 Playwright 自己轮询等待 - ✅ 只用
time:固定等待指定秒数 - ❌ 同时用
textGone+time:会先等 time 秒再检查,浪费时间
# 推荐:只用 textGone,让它自动等待条件满足
browser_wait_for textGone="正在上传媒体"
# 或者:用 browser_snapshot 轮询检查状态
# 每次操作后检查返回的页面状态,无需额外等待
图片插入效率
每张图片的浏览器操作从5步减少到2步:
- 旧: 点击 → 添加媒体 → 媒体 → 添加照片 → file_upload
- 新: 点击段落 → Meta+v
封面图 vs 内容图
- 封面图: 使用 browser_file_upload(因为有专门的上传按钮)
- 内容图: 使用 Python 剪贴板 + 粘贴(更高效)
故障排除
MCP 连接问题
如果 Playwright MCP 工具不可用(报错 No such tool available 或 Not connected):
方案1:重新连接 MCP(推荐)
执行 /mcp 命令,选择 playwright,选择 Restart
方案2:清理残留进程后重连
# 杀掉所有残留的 playwright 进程
pkill -f "mcp-server-playwright"
pkill -f "@playwright/mcp"
# 然后执行 /mcp 重新连接
配置文件位置: ~/.claude/mcp_servers.json
浏览器错误处理
如果遇到 Error: Browser is already in use 错误:
# 方案1:先关闭浏览器再重新打开
browser_close
browser_navigate: https://x.com/compose/articles
# 方案2:杀掉 Chrome 进程
pkill -f "Chrome.*--remote-debugging"
# 然后重新 navigate
图片位置偏移
如果图片插入位置不正确(特别是点击含链接的段落时):
原因: 点击段落时可能误触链接,导致光标位置错误
解决方案: 点击后必须按 End 键移动光标到行尾
# 正确流程
1. browser_click 点击目标段落
2. browser_press_key: End # 关键步骤!
3. browser_press_key: Meta+v # 粘贴图片
4. browser_wait_for textGone="正在上传媒体"
图片路径找不到
如果 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' instead
JSON 字段说明:
original_path: Markdown 中指定的路径(解析后的绝对路径)path: 实际使用的路径(如果自动搜索成功,会不同于 original_path)exists:true表示找到文件,false表示未找到(上传会失败)
如果仍然找不到:
- 检查 JSON 输出中的
missing_images字段 - 手动将图片复制到 Markdown 文件同目录的
assets/子目录 - 或修改 Markdown 中的图片路径为绝对路径