Home / All Skills / Content & Media Creation / x-article-publisher
Content & Media Creation · wshuyi/x-article-publisher-skill

x-article-publisher

Publish Markdown articles to X (Twitter) Articles editor with proper formatting. Use when user wants to publish a Markdown file/URL to X Articles, or mentions "publish to X", "post article to Twitter", "X article", or wants help with X Premium article publishing. Handles cover image upload and converts Markdown to rich text automatically.

L4 High-Privilege · Graded auth & full auditV1 Commit-PinnedExecute CommandNetwork AccessWrite FileDual-pass Audit
Author wshuyiGitHub wshuyi/x-article-publisher-skill ↗Stars 867License MIT(仓库根 LICENSE:Copyright (c) 2024 wshuyi;gh api spdx=MIT)Commit eb3197d1d8
Control levels describe skill capabilities and suggested control measures; they do not represent quality or safety certifications, nor do they dictate usability. Usage decisions rest entirely with users and administrators. Different agent hosts apply varying constraints; do not assume hosts provide automatic protection. Third-party skill copyrights belong to their original authors. Use at your own risk.

1How It Works & Mechanism

Three-stage pipeline: A local Python script parses the Markdown file into structured data (JSON + HTML ready for pasting), the host-provided Playwright MCP browser tool injects the content into the X Articles editor, and the process stops strictly at drafting; separation of concerns is maintained between the script and browser layers, with the script making no network calls.

README.md
Markdown File ↓ Python parsing Structured Data (title, images with block_index, HTML) ↓ Playwright MCP X Articles Editor (browser automation) ↓ Draft Saved (never auto-publishes)
The architecture diagram at the start of the README illustrates the entire mechanism of this skill: parse, inject, and stop at drafts—no backend server, no X API.

Block-level segmentation is the foundation for all positioning: split_into_blocks scans line-by-line, splitting code blocks (wrapped with ___CODE_BLOCK_START/END, including fallback for unclosed blocks), '---' horizontal dividers, '#' / '>' headings and blockquotes, and standalone images into independent blocks, while merging consecutive lines into paragraph blocks.

skills/x-article-publisher/scripts/parse_markdown.py
def split_into_blocks(markdown: str) -> list[str]: """Split markdown into logical blocks (paragraphs, headers, quotes, code blocks, etc.)."""
Implemented purely with standard library regex/state machines without third-party Markdown parsing libraries; the block sequence serves as the coordinate system for the subsequent block_index.

Images and dividers are stripped from the body text during the parsing stage: extract_images_and_dividers evaluates each block against ___DIVIDER___ or image regex, records block_index as the count of cleaned blocks collected so far, and takes the first 80 characters of the preceding block's last line as after_text; only the stripped body text proceeds to HTML conversion.

skills/x-article-publisher/scripts/parse_markdown.py
block_index = len(clean_blocks) after_text = "" if clean_blocks: prev_block = clean_blocks[-1].strip() lines = [l for l in prev_block.split('\n') if l.strip()] after_text = lines[-1][:80] if lines else ""
The index carries the semantics of 'insert after this block'; after_text is strictly for manual verification.

Title extraction follows clear priority and deduplication rules: extract_title checks sequentially for the first H1, the first H2, and the first non-empty, non-image line; if the title is derived from an H1, that line is removed from the Markdown to avoid duplicating the title in the body.

skills/x-article-publisher/scripts/parse_markdown.py
# Remove H1 title line from markdown to avoid duplication
Corresponds to SKILL.md Critical Rule 7: 'H1 is used solely as the title and does not enter the body text'.

Rich text conversion relies on order-sensitive regex substitutions rather than a Markdown library: code blocks are converted to <blockquote> (since X does not support <pre><code>), followed by H2/H3→h2/h3, **→strong, *→em, [text](url)→a, >→blockquote, and - / 1.→li wrapped in ul; finally, paragraphs are split by blank lines, internal newlines converted to <br>, and wrapped in <p>.

skills/x-article-publisher/scripts/parse_markdown.py
# Convert to blockquote format since X Articles doesn't support <pre><code>
Execution order is crucial: code block markers must be processed first, or internal * and * characters will be mistakenly parsed as emphasis syntax.

Clipboard writing uses native OS clipboard APIs rather than generic third-party libraries: the macOS branch uses pyobjc's NSPasteboard, calling clearContents before writing the HTML type via setData_forType_ alongside a plain text fallback via setString_forType_, ensuring Cmd+V into the X editor retains H2, bold, links, and other rich formatting.

skills/x-article-publisher/scripts/copy_to_clipboard.py
pasteboard.setData_forType_(ns_data, NSPasteboardTypeHTML) # Also set plain text version pasteboard.setString_forType_(html, NSPasteboardTypeString)
The Windows implementation uses clip-util's Clipboard()["html"]; neither branch relies on pyperclip.

Images can optionally be compressed and packaged into platform-native formats before clipboard ingestion: compress_image opens the image with Pillow, converts to RGB if necessary, applies thumbnail constraints up to (2000,2000), and writes to BytesIO at the specified JPEG quality; the Windows branch further saves as BMP, strips the 14-byte BITMAPFILEHEADER, and inserts the raw DIB payload into CF_DIB.

skills/x-article-publisher/scripts/copy_to_clipboard.py
data = output.getvalue()[14:] # Skip BITMAPFILEHEADER
This inline comment clarifies why the Windows path must manually strip the BMP file header—the clipboard expects bare DIB data.

Table-to-image rendering is fully self-drawn: table_to_image.py parses Markdown tables independently (recognizing :--- alignment markers), measures column widths via Pillow ImageDraw's textbbox, renders rounded borders, header backgrounds, and row dividers, and searches truetype fonts across macOS (sfns/pingfang/stheiti) → Linux (dejavu) → Windows (arial/msyh), falling back to bitmap fonts only if all fail.

skills/x-article-publisher/scripts/table_to_image.py
for path in font_paths: try: return ImageFont.truetype(path, size) except (OSError, IOError): continue
The font candidates include PingFang and Microsoft YaHei (C:/Windows/Fonts/msyh.ttc), indicating deliberate support for Chinese tables.

2Core Capabilities

01Markdown block-level parsing and block_index coordinate output (title / cover image / content images / dividers / total_blocks)
02Markdown-to-HTML rich text conversion (H2/H3, bold, italic, links, blockquotes, ordered/unordered lists, code blocks downgraded to blockquotes)
03System clipboard rich text writing (macOS NSPasteboard HTML+String; Windows clip-util html)
04System clipboard image writing with optional compression (--quality triggers JPEG compression, --max-width/--max-height limits to 2000x2000)
05Markdown table-to-PNG rendering (with header background, rounded borders, column alignment, and --scale high-DPI scaling)
06Divider (---) detection and independent indexing for agent insertion via X's Insert > Divider menu
07Image path fallback search: if not found locally, searches ~/Downloads, ~/Desktop, and ~/Pictures by filename, reporting substitutions to stderr
08Title and frontmatter processing: skips YAML frontmatter automatically, extracts H1 as title, and strips it from body text

3External Dependencies

KindDependency / Endpoint
clipython3(解释器,脚本 shebang 与文档命令均为 python3/python)
climmdc(@mermaid-js/mermaid-cli,Mermaid→PNG,需 npm 全局安装)
clipkill(排障步骤里杀 Playwright MCP 相关进程)
packagePillow(image 压缩、BMP/DIB 封装、表格自绘)
packagepyobjc-framework-Cocoa(macOS AppKit/Foundation,NSPasteboard)
packagepywin32(win32clipboard/CF_DIB)与 clip-util(Windows HTML 剪贴板)
networkPlaywright MCP(宿主浏览器自动化工具集 browser_navigate/browser_click/browser_press_key 等;skill 不自带浏览器驱动)
networkX Articles 编辑器(被自动化访问的站点,读取用户已登录会话)
apiX (Twitter) Premium Plus 订阅(Articles 功能的准入前提,非 API 调用)

4Recommended Governance & Evidence

Suggested Controls for Safe Operation
For Agent Runtime
Graded/per-use authorization, sandbox recommended
For Administrators
Approval workflow, rollback support, full audit
Evidence & Triggers (File Paths & Excerpts)
  • scripts/: Executes local script or command: python3 skills/x-article-publisher/scripts/parse_markdown.py <markdown_file
  • 网络端点: Declares external network access: Script layer: None—none of the three scripts make requests/urllib.request/httpx/socket calls
  • 本地工作目录: File read/write and artifact output: /tmp/article_html.html (SKILL.md instructs agent to write via shell redirection)
Audit Notes: Orange tier. Three source code evidences: ① Explicitly relies on the user's pre-authenticated session—SKILL.md states 'If login needed, prompt user to log in manually.', and docs/GUIDE.md Section 3.4 Notes specifies '2. **Logged in to X**: Ensure your X account is logged in within the browser', meaning automation runs entirely under user credentials and constitutes substantive use of session/cookie state (though the skill itself does not read them); ② Relies on third-party libraries and global npm packages without version pinning—plugin.json specifies Python dependencies on Pillow and pyobjc-framework-Cocoa, SKILL.md specifies 'npm install -g @mermaid-js/mermaid-cli', and the repo lacks requirements.txt; ③ Modifies user environment state—copy_to_clipboard.py uses 'pasteboard.clearContents()' and 'win32clipboard.EmptyClipboard()' to clear and overwrite the clipboard, table_to_image.py executes "img.save(output, 'PNG')" to write files, and docs/GUIDE.md provides commands to read ~/.claude/settings.json and kill processes via pkill. Prevented from Red tier: scripts do not use eval/exec/subprocess/os.system (no arbitrary code execution surface), contain no TLS downgrade, anti-scraping bypasses, or bulk scraping; the sole network target is X's official domain initiated via the host MCP browser.

5Second-Pass Verification

  • [ok] 全部 evidence.quote 逐字存在性(Python 子串校验,覆盖 SKILL.md/两个 README/GUIDE/三个脚本/plugin.json/LICENSE) — 所有引用均为原文精确子串,未发现 paraphrase 冒充 quote;含反斜杠的正则引用按源码原样保留。
  • [ok] 网络调用反查(requests|urllib|httpx|socket|aiohttp|curl|wget|api.x.com|api.twitter) — 仅 parse_markdown.py 的 `import urllib.parse` 与 `urllib.parse.unquote(img_path)` 命中,用途是 URL 解码文件名;无任何 HTTP 客户端。
  • [discrepancy] 凭证/密钥反查(token|api_key|environ|getenv|.env|cookie|oauth|Bearer) — 脚本层零命中,确认无凭证读取;但文档层 docs/GUIDE.md 指示 `cat ~/.claude/settings.json | grep playwright`,会读取可能含 API key 的 Claude Code 配置文件——第一遍未计入,已在 security.credential_reads 补记。
  • [ok] 隐藏文件与额外可执行体反查(find 全清单 + .gitignore) — 仓库仅 13 个非 .git 条目:3 个 py 脚本、SKILL.md、GUIDE.md、两个 README、plugin.json、LICENSE、.gitignore、skills/、docs/、.claude-plugin/;无隐藏脚本、无二进制、无混淆内容。
  • [ok] 外部依赖调用点复核(Pillow / pyobjc / pywin32 / clip-util / mmdc / pkill / Playwright MCP) — Pillow 在 copy_to_clipboard.py 与 table_to_image.py 的 import 行可定位;mmdc 与 pkill 只出现在 SKILL.md 文档指令中,仓库无脚本调用它们;Playwright MCP 只在 plugin.json 与 SKILL.md 工具名中出现,skill 不自带驱动。
  • [discrepancy] 功能声明 vs 实际能力:frontmatter 宣称 'publish a Markdown file/URL to X Articles' — 夸大:parse_markdown.py 只有 `with open(filepath, 'r', encoding='utf-8') as f:` 与 `os.path.exists(args.file)`,仅接受本地文件,全仓库无 URL 抓取分支。若 agent 照字面接受 URL 输入,会自行引入不受控的网页内容。
  • [discrepancy] 定位策略一致性:SKILL.md Step 6 推荐 after_text 文本搜索 vs Critical Rules 第 5 条与 README 声称的 block_index — SKILL.md 自身矛盾:Step 6 标题写 '(Text Search Positioning)' 且正文称『推荐方法: 使用 after_text 文字搜索定位,比 block_index 更直观可靠』,而同一文件 Critical Rules 写『5. Block index positioning - Use block_index for precise image/divider placement』,README 又称 v1.1 已用 block_index 替换文本匹配。agent 执行时选错策略会导致图片错位。
  • [discrepancy] 版本一致性:plugin.json version 与 README/changelog 版本号、日期 — plugin.json 为 version 1.1.0,README 顶部却写 v1.2.0;changelog 条目为 v1.2.0 (2025-01)、v1.1.0 (2025-12)、v1.0.0 (2025-12),v1.2.0 的月份早于 v1.1.0,与 pin commit 日期 2026-01-25 也对不上,疑为文档笔误。

6Verdict & Scenarios

  • Zero official API dependencies, bypassing X developer account requirements and OAuth approvals
  • The only irreversible action (publishing) is explicitly excluded from automation
  • Deterministic spatial positioning without relying on fuzzy text matching
  • Core parsing script has zero third-party dependencies, making it easy to audit and reuse
  • Self-drawn table-to-image rendering with support for Chinese tables
  • Best for: Ideal for individuals with X Premium Plus who write in Markdown and prefer having an agent manage browser interactions: transforms an English or Chinese Markdown article containing titles, images, blockquotes, and lists into an X Articles draft, with formatting and image placement computed by script while leaving final review and publishing to humans. Also serves well as a reference implementation for semi-automated 'local deterministic transformation + MCP browser delivery' pipelines. Offers the most complete experience on macOS (plugin.json targets macos only); rich text clipboard fidelity on Windows depends on clip-util.
    Not for: Not suitable for full one-click publishing workflows (intentionally stops at drafts); not suitable for Linux users (copy_to_clipboard explicitly raises Unsupported platform, marked incomplete in README); not suitable for accounts without X Premium Plus; not suitable for articles containing complex nested syntax, native tables, or math formulas (tables and Mermaid require manual image pre-rendering, code blocks degrade to blockquotes); not suitable for directly ingesting untrusted external Markdown requiring high fidelity (lacks HTML escaping, positioning documentation contains internal contradictions); not suitable for teams requiring end-to-end publish auditing, as clipboard and browser session actions leave no audit trail or replay logs.
    Install Agent-Ready
    ① Verified Mirror 2026-09-15
    Option A · Direct DownloadDownload x-article-publisher.tar.gz
    sha256: 61755757ff05c8f7…
    Option B · Copy Guide for Agent
    install.json
    Agents automatically download and verify mirrors from this JSON guide.
    ② Upstream GitHub
    GitHub Repo ↗
    Pinned to commit eb3197d1d8.