Community코딩 & 개발github.com

X 아티클 퍼블리셔

Markdown 아티클을 X (Twitter) 아티클 편집기에 서식과 함께 발행합니다. 사용자가 Markdown 파일/URL을 X에 발행하려고 하거나, 'X에 발행', '트위터에 글 게시', 'X 아티클'을 언급하거나, X Premium 아티클 발행 도움을 요청할 때 사용합니다. 커버 이미지 업로드와 Markdown을 리치 텍스트로 자동 변환합니다.

X 아티클 퍼블리셔란 무엇인가요?

이 스킬은 Markdown으로 작성된 아티클을 X(트위터) 아티클 편집기에 발행하는 자동화 도구입니다. Playwright MCP를 활용한 브라우저 자동화를 통해 Markdown을 리치 텍스트로 변환하고, 커버 이미지 업로드와 내용 내 이미지/구분선 삽입 등을 처리합니다. Python 스크립트를 사용하여 Markdown을 파싱하고, HTML을 클립보드에 복사한 후 브라우저에 붙여넣기 하는 방식으로 서식을 유지합니다. 주요 기능으로는 표를 PNG로 변환(table_to_image.py), Mermaid 다이어그램을 이미지로 변환(mmdc), 이미지/HTML 클립보드 복사(copy_to_clipboard.py) 등이 있습니다. 효율적인 실행을 위해 '문서 먼저, 이미지 다음, 구분선 마지막' 전략을 사용하며, block_index 역순으로 이미지와 구분선을 삽입하여 위치 오류를 방지합니다. 절대 자동 발행하지 않고 초안만 저장하며, 사용자가 수동으로 검토하고 발행하도록 합니다.

지원 대상✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/wshuyi/x-article-publisher-skill/tree/main/skills/x-article-publisher

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

미리보기

스킬 README에서

Table
Diagram

문서

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
  • 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]

반환되는 JSON: title, cover_image, content_images, dividers (block_index 포함), html, total_blocks

copy_to_clipboard.py

이미지 또는 HTML을 시스템 클립보드에 복사합니다 (크로스 플랫폼):

# 이미지 복사 (선택적 압축)
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]

# 리치 텍스트 붙여넣기를 위한 HTML 복사
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

# 표를 임시 파일로 추출한 후 변환
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/table.md /tmp/table.png
# Markdown의 표를 다음으로 대체: ![Table](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/tmp/table.png)

Mermaid 다이어그램 → PNG

# mermaid 블록을 .mmd 파일로 추출한 후 변환
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.png -b white -s 2
# mermaid 블록을 다음으로 대체: ![Diagram](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/tmp/diagram.png)

구분선 (---)

구분선은 parse_markdown.py에 의해 자동으로 감지되어 dividers 배열에 출력됩니다. X 아티클의 Insert > Divider 메뉴를 통해 삽입해야 합니다 (HTML <hr> 태그는 X에서 무시됨).

워크플로우

전략: "본문 우선, 이미지 다음, 구분선 마지막"

이미지와 구분선이 있는 문서의 경우, 먼저 모든 텍스트 콘텐츠를 붙여넣은 다음 block_index를 사용하여 올바른 위치에 이미지와 구분선을 삽입합니다.

  1. (선택 사항) 전처리: 표/mermaid를 이미지로 변환
  2. Python 스크립트로 Markdown 파싱 → title, images, dividers (block_index 포함), HTML 획득
  3. X 아티클 편집기로 이동
  4. 커버 이미지 업로드 (첫 번째 이미지)
  5. 제목 입력
  6. HTML을 클립보드에 복사 (Python) → Cmd+V로 붙여넣기
  7. block_index로 지정된 위치에 내용 이미지 삽입
  8. block_index로 지정된 위치에 구분선 삽입 (Insert > Divider 메뉴를 통해)
  9. 초안으로 저장 (절대 자동 발행하지 않음)

효율성 지침

목표: 작업 간 대기 시간을 최소화하여 원활한 자동화 경험을 제공합니다.

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. 사전 작업 미리 수행

브라우저 작업을 시작하기 전에 모든 사전 작업을 완료하세요:

  1. Markdown 파싱하여 JSON 데이터 획득
  2. HTML 파일을 /tmp/에 생성
  3. 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": "기사 제목",
  "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": "컨텍스트..."},
    {"path": "/path/to/img3.jpg", "original_path": "/md/dir/assets/img3.jpg", "exists": true, "block_index": 12, "after_text": "다른..."}
  ],
  "html": "<p>콘텐츠...</p><h2>섹션</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

중요: 페이지가 로드되면 초안 목록이 표시되며 편집기가 아닙니다. 다음이 필요합니다:

  1. 페이지 로드 완료 대기: browser_snapshot으로 페이지 상태 확인
  2. 즉시 "create" 버튼 클릭: "제목 추가"와 같은 편집기 요소는 create를 클릭한 후에만 나타나므로 기다리지 마세요
  3. 편집기 로드 대기: 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단계: 커버 이미지 업로드

  1. "사진 또는 비디오 추가" 버튼 클릭
  2. 커버 이미지 경로로 browser_file_upload 사용 (JSON 출력에서)
  3. 이미지 업로드 확인

4단계: 제목 입력

  • "제목 추가" 플레이스홀더가 있는 텍스트 상자 찾기
  • browser_type을 사용하여 제목 입력 (JSON 출력에서)

5단계: 텍스트 콘텐츠 붙여넣기 (Python 클립보드)

Python을 사용하여 HTML을 시스템 클립보드에 복사한 다음 붙여넣습니다:

# 클립보드에 HTML 복사
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단계: 내용 이미지 삽입 (텍스트 검색 위치 지정)

권장 방법: after_text 텍스트 검색을 사용한 위치 지정이 block_index보다 더 직관적이고 안정적입니다.

위치 지정 원리

각 이미지의 after_text 필드는 이전 단락의 끝 텍스트 (최대 80자)를 기록합니다. 편집기에서 해당 텍스트가 포함된 단락을 검색하고 클릭한 후 이미지를 삽입합니다.

작업 단계

각 내용 이미지 (content_images 배열)에 대해 block_index가 큰 순서대로:

# 1. 이미지를 클립보드에 복사 (압축 포함)
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. after_text를 포함하는 단락 클릭
browser_click: element="target text가 있는 단락", ref=<paragraph_ref>

# 4. **중요 단계**: End 키를 눌러 커서를 줄 끝으로 이동
#    이 단계는 매우 중요합니다! 단락 내 링크를 클릭하여 위치가 이동하는 것을 방지합니다
browser_press_key: End

# 5. 이미지 붙여넣기
browser_press_key: Meta+v

# 6. 업로드 대기 (textGone만 사용, 시간 매개변수 없음)
browser_wait_for textGone="미디어 업로드 중"

End 키가 필요한 이유는?

문제: 단락에 링크가 포함된 경우 (예: [링크 텍스트](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/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"  # 참고: 단락에 링크가 포함될 수 있음
  ...

역순 삽입 예시

block_index가 각각 5, 12, 27인 이미지 3개가 있는 경우:

  1. 먼저 block_index=27인 이미지 삽입 (after_text 검색 + End + 붙여넣기)
  2. 그런 다음 block_index=12인 이미지 삽입
  3. 마지막으로 block_index=5인 이미지 삽입

큰 인덱스부터 작은 순서로 삽입하면 위치 오프셋 문제를 방지할 수 있습니다.

6.5단계: 구분선 삽입 (메뉴를 통해)

중요: Markdown의 --- 구분선은 HTML <hr> 태그를 통해 붙여넣을 수 없습니다 (X 아티클에서 무시됨). X 아티클의 Insert 메뉴를 통해 삽입해야 합니다.

작업 단계

dividers 배열의 각 구분선에 대해 block_index 역순으로:

# 1. block_index 위치의 블록 요소 클릭
browser_click on the element at position block_index in the editor

# 2. Insert 메뉴 열기 (미디어 추가 버튼)
browser_click on "Insert" or "미디어 추가" button

# 3. Divider 메뉴 항목 클릭
browser_click on "Divider" or "구분선" menuitem

# 구분선이 커서 위치에 삽입됩니다

이미지와의 삽입 순서

모든 이미지를 먼저 삽입한 다음 모든 구분선을 삽입하는 것이 좋습니다. 둘 다 block_index 큰 순서대로 진행합니다:

  1. 모든 이미지 삽입 (가장 큰 block_index부터 시작)
  2. 모든 구분선 삽입 (가장 큰 block_index부터 시작)

7단계: 초안 저장

  1. 콘텐츠가 붙여넣어졌는지 확인 (단어 수 표시기 확인)
  2. 초안은 자동 저장되거나, 필요한 경우 저장 버튼 클릭
  3. "미리보기"를 클릭하여 서식 확인
  4. 보고: "초안이 저장되었습니다. 수동으로 검토하고 발행해 주세요."

중요 규칙

  1. 절대 발행하지 않음 - 초안만 저장
  2. 첫 번째 이미지 = 커버 - 첫 번째 이미지를 커버 이미지로 업로드
  3. 리치 텍스트 변환 - 붙여넣기 전에 항상 Markdown을 HTML로 변환
  4. 클립보드 API 사용 - 올바른 서식을 위해 클립보드를 통해 붙여넣기
  5. Block index 위치 지정 - 정확한 이미지/구분선 배치를 위해 block_index 사용
  6. 역순 삽입 - block_index가 높은 것부터 낮은 순서로 이미지와 구분선 삽입
  7. H1 제목 처리 - H1은 제목으로만 사용되며 본문에 포함되지 않음
  8. 메뉴를 통한 구분선 - 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에 발행해 줘"

# 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
  1. https://x.com/compose/articles로 이동
  2. 커버 이미지 업로드 (커버만 browser_file_upload)
  3. 제목 입력 (JSON에서: title)
  4. HTML 복사 및 붙여넣기:
    python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/article_html.html
    
    그런 다음: browser_press_key Meta+v
  5. 각 내용 이미지에 대해 block_index 역순으로:
    python copy_to_clipboard.py image /path/to/img.jpg --quality 85
    
    • block_index 위치의 블록 요소 클릭
    • browser_press_key Meta+v
    • 업로드가 완료될 때까지 대기
  6. 미리보기에서 확인
  7. "초안이 저장되었습니다. 수동으로 검토하고 발행해 주세요."

모범 사례

block_index를 사용하는 이유 (텍스트 일치 대신)?

  1. 정확한 위치 지정: 텍스트 콘텐츠에 의존하지 않으므로 여러 곳에 유사한 텍스트가 있어도 올바르게 위치 지정 가능
  2. 신뢰성: 인덱스는 결정적이므로 텍스트 유사성으로 인한 혼동이 없음
  3. 디버깅 용이: after_text는 수동 확인을 위해 계속 유지됨

Python을 사용하는 이유 (브라우저 내 JavaScript 대신)?

  1. 로컬 처리의 신뢰성: Python은 브라우저 샌드박스 제한 없이 운영 체제 클립보드를 직접 조작
  2. 이미지 압축: 업로드 전 이미지 압축 (--quality 85)으로 업로드 시간 단축
  3. 코드 재사용: 스크립트가 고정되어 있어 매번 변환 로직을 다시 작성할 필요 없음
  4. 디버깅 용이: 스크립트를 개별적으로 테스트할 수 있어 문제를 쉽게 찾을 수 있음

대기 전략

중요 발견: 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는 다음 위치에서 이미지를 검색합니다:

  1. 원래 Markdown 파일 기준 상대 경로
  2. Markdown 파일의 디렉토리
  3. 절대 경로가 지정된 경우 그대로 사용

수동 확인: missing_images 필드를 확인하여 찾을 수 없는 이미지가 있는지 확인합니다.

해결책: 누락된 이미지의 경로를 수동으로 확인하고 Markdown 파일을 업데이트합니다.

관련 스킬