
Publish Zsxq Article
- 446 installs
- 130 repo stars
- Updated June 19, 2026
- sugarforever/01coder-agent-skills
publish-zsxq-article is an agent skill that publishes local Markdown files as Zsxq knowledge-planet article drafts via browser automation for developers distributing technical content to Chinese paid communities.
About
publish-zsxq-article is a browser-automation agent skill from sugarforever/01coder-agent-skills that saves Markdown articles as drafts on Zsxq (知识星球) without auto-publishing. The eight-step workflow resolves groupId, navigates to wx.zsxq.com/article, switches to Milkdown Markdown mode, fills a 60-character title, inserts content via ClipboardEvent paste—not fill—and clicks 保存 while never clicking 发布. It strips YAML frontmatter, H1 titles, HTML comments, and horizontal rules that break Milkdown parsing, and enforces a 100,000-character body limit. Playwright MCP is preferred over Chrome DevTools MCP. Prerequisites include a logged-in Zsxq session and ZSXQ_GROUP_ID or user-supplied groupId.
- publish-zsxq-article
Publish Zsxq Article by the numbers
- 446 all-time installs (skills.sh)
- +11 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #974 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/sugarforever/01coder-agent-skills --skill publish-zsxq-articleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 446 |
|---|---|
| repo stars | ★ 130 |
| Last updated | June 19, 2026 |
| Repository | sugarforever/01coder-agent-skills ↗ |
How do you publish Markdown drafts to Zsxq automatically?
Use publish-zsxq-article for development tasks
Who is it for?
Developers who write Markdown technical content and routinely publish drafts to Zsxq communities with browser MCP and an existing login.
Skip if: Teams not using Zsxq, fully automated publishing without human review, or environments lacking Playwright or Chrome DevTools MCP.
When should I use this skill?
User asks to publish Markdown to Zsxq, 知识星球, or zsxq article, or provides a .md file path for draft upload.
What you get
Zsxq article editor draft with title, rendered Markdown body, optional tags, and user prompt to preview and publish manually.
- Zsxq article draft
- Rendered Markdown in Milkdown editor
- Draft save confirmation for manual publish
By the numbers
- 100000-character Zsxq article body limit documented
- Eight-step main workflow from content prep through draft save
Files
Publish Zsxq Article
Publish Markdown content to Zsxq (知识星球) article editor in Markdown mode, saving as draft for user review before publishing.
Prerequisites
- Browser automation MCP (either one):
- Chrome DevTools MCP (
mcp__chrome-devtools__*) - Playwright MCP (
mcp__playwright__*) - User logged into Zsxq (知识星球)
Browser MCP Tool Mapping
This skill works with both Chrome DevTools MCP and Playwright MCP:
| Action | Chrome DevTools MCP | Playwright MCP |
|---|---|---|
| Navigate | navigate_page | browser_navigate |
| Take snapshot | take_snapshot | browser_snapshot |
| Take screenshot | take_screenshot | browser_take_screenshot |
| Click element | click | browser_click |
| Fill text | fill | browser_type |
| Upload file | upload_file | browser_file_upload |
| Press key | press_key | browser_press_key |
| Evaluate JS | evaluate_script | browser_evaluate |
Priority: Default to Playwright MCP. Use Chrome DevTools MCP only when Playwright MCP is unavailable.
Detection: At runtime, prefer mcp__playwright__browser_navigate. Fall back to mcp__chrome-devtools__navigate_page only if Playwright tools are not available.
Key URLs
- Login page:
https://wx.zsxq.com/login - Article editor:
https://wx.zsxq.com/article?groupId={groupId}
Group ID Resolution
The Zsxq group ID is required to navigate to the article editor (the groupId= URL parameter). Do not hardcode a default.
Before any navigation, resolve the group ID in this order:
1. Skill argument — if the user invoked the skill with a group ID, use it. 2. Environment variable — check ZSXQ_GROUP_ID (optional, for users who publish repeatedly to the same group). 3. Prompt the user if neither is available:
请提供知识星球的 group ID(在星球 URL 里 groupId= 之后那串数字)。Do not proceed to Step 2 (Navigate) without a resolved group ID.
Editor Interface
The Zsxq article editor has two modes:
Rich Text Mode (Default)
- Standard WYSIWYG editor with formatting toolbar
- Click "切换到 Markdown 模式 (内测)" to switch
Markdown Mode (Preferred)
- Uses Milkdown (ProseMirror-based WYSIWYG Markdown editor)
- Renders Markdown as formatted content (headings, bold, links, lists)
- Click "切换到富文本模式" to switch back
IMPORTANT: Content Insertion Method The Milkdown editor requires content to be inserted via paste event, NOT direct fill:
filltool → Content treated as plain text, Markdown NOT rendered- Paste event → Milkdown parses Markdown and renders it properly
Key Elements in Markdown Mode
- Title input: textbox "请在这里输入标题"
- Content area: ProseMirror editor (
.ProseMirrorclass) - Save button: "保存" (saves as draft)
- Preview button: "预览"
- Publish button: "发布" (DO NOT USE - always save as draft)
- Tags: "添加标签"
Main Workflow
Step 1: Prepare Content
Read the Markdown file and extract:
- Title: from YAML frontmatter
titlefield, or H1 header# Title, or filename - Content: Markdown body with the following stripped:
- YAML frontmatter (
---delimited block at the top) - H1 title line (already used as the article title)
- HTML comments (
<!-- ... -->) - Horizontal rules (`---`) — Milkdown's paste handler misparses
---, causing subsequent headings and formatting to break (e.g.,##rendered as bold**text instead of H2). Remove all---lines before pasting.
# Read the markdown file
cat /path/to/article.mdStep 2: Navigate to Article Editor
# Navigate to the article editor with the resolved group ID (see Group ID Resolution above)
navigate_page: https://wx.zsxq.com/article?groupId={groupId}If not logged in, the page will redirect to login. Prompt user to log in manually:
请先登录知识星球,登录完成后告诉我。
Login URL: https://wx.zsxq.com/loginStep 3: Switch to Markdown Mode
After page loads, check if already in Markdown mode by looking for "切换到富文本模式" text.
If in Rich Text mode (shows "切换到 Markdown 模式"): 1. Click "切换到 Markdown 模式 (内测)" 2. Confirm the dialog by clicking "确定"
// Check current mode
const switchBtn = document.querySelector('[class*="switch"]');
if (switchBtn && switchBtn.innerText.includes('切换到 Markdown')) {
// Need to switch to Markdown mode
}Step 4: Fill Title
1. Find the title textbox with placeholder "请在这里输入标题" 2. Click to focus 3. Type the title
click: title textbox
fill: title textbox with article titleStep 5: Insert Markdown Content (via Paste Event)
CRITICAL: Do NOT use `fill` tool - it inserts plain text without Markdown rendering.
Instead, use evaluate_script to simulate a paste event:
// Simulate paste event to trigger Milkdown's Markdown parsing
() => {
const markdownContent = `YOUR_MARKDOWN_CONTENT_HERE`;
const editorEl = document.querySelector('.ProseMirror');
if (!editorEl) return { error: 'Editor not found' };
// Focus the editor
editorEl.focus();
// Create and dispatch paste event
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', markdownContent);
const pasteEvent = new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
clipboardData: clipboardData
});
editorEl.dispatchEvent(pasteEvent);
return { success: true, charCount: markdownContent.length };
}This method: 1. Creates a ClipboardEvent with the Markdown content 2. Dispatches it to the ProseMirror editor 3. Milkdown's paste handler parses and renders the Markdown
Step 6: Add Tags (Optional)
1. Click "添加标签" 2. Enter tag text 3. Confirm
Step 7: Save as Draft
IMPORTANT: Always save as draft, NEVER click "发布" (Publish)
1. Click "保存" button to save as draft 2. Verify save was successful
click: "保存" buttonStep 8: Verify and Report
After saving: 1. Check for success message or draft status 2. Report to user:
草稿已保存。请在知识星球中预览并手动发布。
Draft saved. Please review in Zsxq and publish manually.Complete Example Flow
User: "把 /path/to/my-article.md 发布到知识星球"
1. Read /path/to/my-article.md
- Extract title from frontmatter or H1
- Strip frontmatter, H1 title, HTML comments, and horizontal rules (`---`)
- Get cleaned content
2. Navigate to https://wx.zsxq.com/article?groupId={resolved groupId}
3. Check if logged in
- If not, prompt user to login
4. Switch to Markdown mode if needed
- Click "切换到 Markdown 模式 (内测)"
- Confirm dialog
5. Fill title
- Click title input
- Use `fill` tool to set title text
6. Insert content via paste event
- Use `evaluate_script` to simulate paste event
- This triggers Milkdown to parse and render Markdown
7. Save as draft
- Click "保存"
8. Report success
- "草稿已保存,请手动预览并发布"Critical Rules
1. NEVER click "发布" - Only save as draft using "保存" 2. Always use Markdown mode - Switch if in Rich Text mode 3. Check login status - Prompt user to login if needed 4. Preserve original file - Never modify the source Markdown file 5. Report completion - Tell user the draft is saved and needs manual review 6. Resolve group ID first - Never hardcode a group ID. Resolve via skill argument, ZSXQ_GROUP_ID env var, or user prompt before any navigation (see Group ID Resolution) 7. Prefer Playwright MCP - Default to Playwright MCP; only use Chrome DevTools MCP when Playwright is unavailable
Troubleshooting
Markdown Not Rendering (Shows Raw Syntax)
If you see raw Markdown syntax like **bold** or [link](url) instead of rendered formatting:
- Cause: Content was inserted using
filltool instead of paste event - Solution: Use the
evaluate_scriptmethod to simulate a paste event (see Step 5)
The Milkdown editor only parses Markdown when content is pasted, not when directly set.
Horizontal Rules (---) Break Formatting
If headings appear as bold text with ** markers, or formatting is garbled after a --- line:
- Cause: Milkdown's paste handler misparses
---(horizontal rule), corrupting subsequent Markdown elements - Solution: Strip all
---lines from the content before pasting. The article can use headings for section separation instead.
Login Required
If page redirects or shows login prompt:
请先登录知识星球: https://wx.zsxq.com/login
登录完成后告诉我。Content Too Long
Zsxq has a 100,000 character limit. If content exceeds:
文章内容超过100000字符限制,请考虑拆分文章。Switch Mode Dialog
When switching to Markdown mode, a confirmation dialog appears:
- Message: "确定要切换编辑器?当前内容将不会同步至新编辑器"
- Click "确定" to confirm
Editor Not Loading
If editor elements are not visible: 1. Wait for page to fully load 2. Take a new snapshot 3. If still not loading, refresh the page
Element Reference
| Element | Selector/Identifier | Description |
|---|---|---|
| Title input | textbox "请在这里输入标题" | Article title (max 60 chars) |
| Content area | .ProseMirror (Milkdown editor) | Markdown content (max 100000 chars) |
| Save button | "保存" | Save as draft |
| Preview button | "预览" | Preview article |
| Publish button | "发布" | DO NOT USE |
| Mode switch | "切换到 Markdown 模式" / "切换到富文本模式" | Toggle editor mode |
| Tags | "添加标签" | Add article tags |
| Word count | "正文字数:X /100000" | Character counter |
Image Upload
Image upload works in both Rich Text mode and Markdown mode using the upload_file tool.
Prerequisites
- Check image file size first: if > 500KB, compress to WebP or reduce quality
- Use
ls -la /path/to/image.pngto check file size
Image Upload Workflow
Image upload works with the image button in both editor modes:
1. Take snapshot to find the image button ref/uid
- Rich Text mode:
button "image" - Markdown mode:
generic description="Add image"
2. Upload image
Chrome DevTools MCP:
upload_file:
uid: <image button uid>
filePath: /path/to/image.pngPlaywright MCP:
browser_file_upload:
ref: <image button ref>
paths: ["/path/to/image.png"]3. Verify upload - take screenshot to confirm image appears in editor
Key Elements for Image Upload
| Mode | Image Button | Selector in Snapshot |
|---|---|---|
| Rich Text | button "image" | button "image" |
| Markdown | Add image | generic description="Add image" |
Example Image Upload (Markdown Mode)
# 1. Take verbose snapshot to find image button
take_snapshot(verbose=true)
# 2. Find "Add image" button (e.g., uid=26_59)
# Look for: generic description="Add image"
# 3. Upload image directly to the button
upload_file:
uid: 26_59 # (example uid for "Add image" button)
filePath: /Users/user/Downloads/image.png
# 4. Verify with screenshot
take_screenshotExample Image Upload (Rich Text Mode)
# 1. Take snapshot to find image button
take_snapshot
# 2. Find image button (e.g., uid=12_7)
# Look for: button "image"
# 3. Upload image to the button
upload_file:
uid: 12_7 # (example uid for image button)
filePath: /Users/user/Downloads/image.png
# 4. Verify with screenshot
take_screenshotImage Size Limits
- Maximum recommended: 500KB per image
- For larger images, compress first using tools like ImageMagick or sips:
# Check size
ls -la /path/to/image.png
# Compress if needed (macOS)
sips -s format jpeg -s formatOptions 80 /path/to/image.png --out /path/to/image_compressed.jpgTroubleshooting Image Upload
Image not appearing after upload:
- Take a fresh verbose snapshot to get correct uid for image button
- Verify the image file exists and is accessible
- Check the word count indicator - it should increase after successful upload
Finding the correct button uid:
- Use
take_snapshot(verbose=true)to see element descriptions - In Markdown mode, look for
generic description="Add image" - In Rich Text mode, look for
button "image"
Technical Details
The Zsxq article editor uses two different editors:
Rich Text Mode (Quill)
- Quill: A modern WYSIWYG editor
- Image upload: Works via
upload_filetool tobutton "image" - Toolbar: Standard formatting buttons including image
Markdown Mode (Milkdown)
- Milkdown: A plugin-driven WYSIWYG markdown editor
- ProseMirror: The underlying rich-text editing framework
- Paste handling: Milkdown intercepts paste events and parses Markdown content
- Image upload: Works via
upload_filetool togeneric description="Add image"
This is why the user's workflow via md.bytenote.net works - pasting from any source triggers Milkdown's Markdown parser, resulting in properly rendered content.
#!/usr/bin/env python3
"""
Copy image or HTML to system clipboard for X Articles publishing.
Supports:
- Image files (jpg, png, gif, webp) - copies as image data
- HTML content - copies as rich text for paste
- Optional image compression before copying
Usage:
# Copy image to clipboard
python copy_to_clipboard.py image /path/to/image.jpg
# Copy image with compression (quality 0-100)
python copy_to_clipboard.py image /path/to/image.jpg --quality 80
# Copy HTML to clipboard
python copy_to_clipboard.py html "<p>Hello</p>"
# Copy HTML from file
python copy_to_clipboard.py html --file /path/to/content.html
Requirements:
macOS: pip install Pillow pyobjc-framework-Cocoa
Windows: pip install Pillow pywin32 clip-util
"""
import argparse
import io
import os
import sys
from pathlib import Path
def compress_image(image_path: str, quality: int = 85, max_size: tuple = (2000, 2000)) -> bytes:
"""Compress image and return as bytes."""
from PIL import Image
img = Image.open(image_path)
# Convert to RGB if necessary (for JPEG)
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
# Resize if too large
img.thumbnail(max_size, Image.Resampling.LANCZOS)
# Save to bytes
buffer = io.BytesIO()
img.save(buffer, format='JPEG', quality=quality, optimize=True)
return buffer.getvalue()
def copy_image_to_clipboard_macos(image_path: str, quality: int = None) -> bool:
"""Copy image to macOS clipboard using AppKit."""
try:
from AppKit import NSPasteboard, NSPasteboardTypePNG, NSPasteboardTypeTIFF
from Foundation import NSData
# Compress if quality specified, otherwise use original
if quality:
image_data = compress_image(image_path, quality)
else:
with open(image_path, 'rb') as f:
image_data = f.read()
# Create NSData from image bytes
ns_data = NSData.dataWithBytes_length_(image_data, len(image_data))
# Get pasteboard and clear it
pasteboard = NSPasteboard.generalPasteboard()
pasteboard.clearContents()
# Determine type based on file extension
ext = Path(image_path).suffix.lower()
if ext in ('.png',):
pasteboard.setData_forType_(ns_data, NSPasteboardTypePNG)
else:
# For JPEG and others, use TIFF (more compatible)
from PIL import Image
img = Image.open(io.BytesIO(image_data))
tiff_buffer = io.BytesIO()
img.save(tiff_buffer, format='TIFF')
tiff_data = NSData.dataWithBytes_length_(tiff_buffer.getvalue(), len(tiff_buffer.getvalue()))
pasteboard.setData_forType_(tiff_data, NSPasteboardTypeTIFF)
return True
except ImportError as e:
print(f"Error: Missing dependency: {e}", file=sys.stderr)
print("Install with: pip install Pillow pyobjc-framework-Cocoa", file=sys.stderr)
return False
except Exception as e:
print(f"Error copying image: {e}", file=sys.stderr)
return False
def copy_html_to_clipboard_macos(html: str) -> bool:
"""Copy HTML to macOS clipboard as rich text."""
try:
from AppKit import NSPasteboard, NSPasteboardTypeHTML, NSPasteboardTypeString
from Foundation import NSData
# Get pasteboard and clear it
pasteboard = NSPasteboard.generalPasteboard()
pasteboard.clearContents()
# Set HTML content
html_data = html.encode('utf-8')
ns_data = NSData.dataWithBytes_length_(html_data, len(html_data))
pasteboard.setData_forType_(ns_data, NSPasteboardTypeHTML)
# Also set plain text version
pasteboard.setString_forType_(html, NSPasteboardTypeString)
return True
except ImportError as e:
print(f"Error: Missing dependency: {e}", file=sys.stderr)
print("Install with: pip install pyobjc-framework-Cocoa", file=sys.stderr)
return False
except Exception as e:
print(f"Error copying HTML: {e}", file=sys.stderr)
return False
# ============================================================================
# Windows Implementation
# ============================================================================
def copy_image_to_clipboard_windows(image_path: str, quality: int = None) -> bool:
"""Copy image to Windows clipboard using CF_DIB format.
Uses pywin32's win32clipboard module to set image data in Device Independent
Bitmap (DIB) format, which is the standard Windows clipboard format for images.
"""
try:
import win32clipboard
from PIL import Image
# Load and optionally compress image
if quality:
image_data = compress_image(image_path, quality)
img = Image.open(io.BytesIO(image_data))
else:
img = Image.open(image_path)
# Convert to RGB (required for BMP format)
if img.mode in ('RGBA', 'P', 'LA'):
img = img.convert('RGB')
# Save as BMP to BytesIO, skip 14-byte BITMAPFILEHEADER
output = io.BytesIO()
img.save(output, format='BMP')
data = output.getvalue()[14:] # Skip BITMAPFILEHEADER
output.close()
# Copy to clipboard
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
finally:
win32clipboard.CloseClipboard()
return True
except ImportError as e:
print(f"Error: Missing dependency: {e}", file=sys.stderr)
print("Install with: pip install Pillow pywin32", file=sys.stderr)
return False
except Exception as e:
print(f"Error copying image: {e}", file=sys.stderr)
return False
def copy_html_to_clipboard_windows(html: str) -> bool:
"""Copy HTML to Windows clipboard using clip-util library."""
try:
from clipboard import Clipboard
with Clipboard() as clipboard:
clipboard["html"] = html
return True
except ImportError as e:
print(f"Error: Missing dependency: {e}", file=sys.stderr)
print("Install with: pip install clip-util", file=sys.stderr)
return False
except Exception as e:
print(f"Error copying HTML: {e}", file=sys.stderr)
return False
# ============================================================================
# Platform Detection and Function Selection
# ============================================================================
def copy_image_to_clipboard(image_path: str, quality: int = None) -> bool:
"""Copy image to clipboard (cross-platform)."""
if sys.platform == 'darwin':
return copy_image_to_clipboard_macos(image_path, quality)
elif sys.platform == 'win32':
return copy_image_to_clipboard_windows(image_path, quality)
else:
print(f"Error: Unsupported platform: {sys.platform}", file=sys.stderr)
return False
def copy_html_to_clipboard(html: str) -> bool:
"""Copy HTML to clipboard (cross-platform)."""
if sys.platform == 'darwin':
return copy_html_to_clipboard_macos(html)
elif sys.platform == 'win32':
return copy_html_to_clipboard_windows(html)
else:
print(f"Error: Unsupported platform: {sys.platform}", file=sys.stderr)
return False
def main():
parser = argparse.ArgumentParser(description='Copy to clipboard for X Articles')
subparsers = parser.add_subparsers(dest='type', required=True)
# Image subcommand
img_parser = subparsers.add_parser('image', help='Copy image to clipboard')
img_parser.add_argument('path', help='Path to image file')
img_parser.add_argument('--quality', type=int, default=None,
help='JPEG quality (1-100), enables compression')
img_parser.add_argument('--max-width', type=int, default=2000,
help='Max width for resize')
img_parser.add_argument('--max-height', type=int, default=2000,
help='Max height for resize')
# HTML subcommand
html_parser = subparsers.add_parser('html', help='Copy HTML to clipboard')
html_parser.add_argument('content', nargs='?', help='HTML content')
html_parser.add_argument('--file', '-f', help='Read HTML from file')
args = parser.parse_args()
if args.type == 'image':
if not os.path.exists(args.path):
print(f"Error: Image not found: {args.path}", file=sys.stderr)
sys.exit(1)
success = copy_image_to_clipboard(args.path, args.quality)
if success:
print(f"Image copied to clipboard: {args.path}")
if args.quality:
print(f" (compressed with quality={args.quality})")
sys.exit(0 if success else 1)
elif args.type == 'html':
if args.file:
if not os.path.exists(args.file):
print(f"Error: File not found: {args.file}", file=sys.stderr)
sys.exit(1)
with open(args.file, 'r', encoding='utf-8') as f:
html = f.read()
elif args.content:
html = args.content
else:
# Read from stdin
html = sys.stdin.read()
success = copy_html_to_clipboard(html)
if success:
print(f"HTML copied to clipboard ({len(html)} chars)")
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()
Related skills
How it compares
Use publish-zsxq-article over generic browser skills when targeting Zsxq Milkdown quirks like paste-only rendering and horizontal-rule stripping.
FAQ
Does publish-zsxq-article click the Zsxq publish button?
publish-zsxq-article never clicks 发布. The skill saves drafts with 保存 only, then tells the user to preview and publish manually inside Zsxq after review.
Why must publish-zsxq-article use paste instead of fill?
publish-zsxq-article dispatches a ClipboardEvent to the Milkdown ProseMirror editor because fill inserts plain text. Paste triggers Markdown parsing so headings, bold, and links render correctly.