
Xiaohongshu Publisher
- 260 installs
- 10 repo stars
- Updated January 16, 2026
- iamzifei/red-publisher-skill
xiaohongshu-publisher is an agent skill at version 3.0.0 that publishes Markdown notes and up to 18 images to Xiaohongshu Creator Platform via agent-browser CDP mode for developers automating CN social-commerce content d
About
xiaohongshu-publisher is an agent skill from iamzifei/red-publisher-skill version 3.0.0 that publishes images and notes to Xiaohongshu (Little Red Book) using agent-browser with Chrome DevTools Protocol. Launch Chrome with --remote-debugging-port=9222, log in once at creator.xiaohongshu.com, then run /xiaohongshu-publisher against a Markdown note file. The skill parses title, body, and hashtags from Markdown, uploads up to 18 images, fills creator form fields, and saves drafts by default—never auto-publishing unless explicitly instructed. Developers reach for xiaohongshu-publisher when batching social-commerce posts, shop-linked launches, or CN market distribution from agent pipelines without repeated QR login flows. Python helpers cover Markdown parsing and clipboard operations alongside the agent-browser --cdp 9222 command interface.
- Xiaohongshu post formatting
- CN social-commerce captions
- Image and hashtag packs
- Shop-link publishing flow
- Regional channel checklist
Xiaohongshu Publisher by the numbers
- 260 all-time installs (skills.sh)
- +3 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #879 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/iamzifei/red-publisher-skill --skill xiaohongshu-publisherAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 260 |
|---|---|
| repo stars | ★ 10 |
| Last updated | January 16, 2026 |
| Repository | iamzifei/red-publisher-skill ↗ |
How do you automate Xiaohongshu note publishing?
Format, caption, and publish posts to Xiaohongshu (Little Red Book) from agent workflows—images, hashtags, and shop links—for CN social-commerce launches.
Who is it for?
Developers distributing CN social-commerce content who want CDP-based Xiaohongshu publishing from logged-in Chrome without manual creator UI clicks each time.
Skip if: Teams not targeting Xiaohongshu, workflows that cannot run Chrome with remote debugging on port 9222, or use cases requiring immediate auto-publish without explicit confirmation.
When should I use this skill?
The developer asks to publish or draft a Xiaohongshu note, batch-upload images to creator platform, or automate Little Red Book content from Markdown.
What you get
Xiaohongshu creator drafts or published notes with uploaded images, parsed titles, captions, hashtags, and optional shop links from Markdown source files.
- Xiaohongshu draft or published notes
- Uploaded image sets
By the numbers
- Skill version 3.0.0
- Uploads up to 18 images per note
- Connects via Chrome DevTools Protocol on port 9222
Files
Xiaohongshu Publisher (小红书发布器)
Publish images and notes to Xiaohongshu (小红书) Creator Platform using agent-browser with CDP (Chrome DevTools Protocol) mode.
Architecture: CDP Mode
This skill uses CDP mode - connecting to an existing browser instance where the user is already logged in. This approach:
- Eliminates QR code scanning - User logs in once in their browser
- Leverages existing session - Uses the browser's cookies and auth state
- More stable - No need to manage auth state files
- agent-browser CLI - Simple command-line interface with
--cdpflag
⚠️ IMPORTANT: Draft Mode by Default
This skill ALWAYS saves notes as DRAFT by default. It will NEVER auto-publish.
Only click the "发布" (publish) button if the user EXPLICITLY requests immediate publishing with phrases like:
- "直接发布" / "立即发布" / "马上发布"
- "publish now" / "publish directly" / "publish immediately"
- "不要草稿,直接发" / "不存草稿"
If unsure, ALWAYS save as draft and let user review before publishing.
Prerequisites
1. Launch Chrome with Remote Debugging
Before using this skill, the user must launch Chrome with remote debugging enabled:
macOS:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222Or create an alias in ~/.zshrc or ~/.bashrc:
alias chrome-debug='/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222'Then simply run: chrome-debug
2. Login to Xiaohongshu
In the Chrome browser (with debug port), navigate to:
https://creator.xiaohongshu.com/publish/publishLogin using QR code scan with Xiaohongshu App. This only needs to be done once - the session persists in the browser.
Note: After login, you can minimize the browser window to the Dock. The browser must stay running (not closed) for CDP to work, but you don't need to see it.
3. Python Dependencies
pip install Pillow pyobjc-framework-Cocoaagent-browser CDP Commands Reference
All commands use the --cdp 9222 flag to connect to the existing Chrome browser:
# Navigation
npx agent-browser --cdp 9222 open <url> # Navigate to URL
npx agent-browser --cdp 9222 snapshot -i # Get page snapshot with element refs
# Element Interaction
npx agent-browser --cdp 9222 click @e5 # Click element by ref
npx agent-browser --cdp 9222 fill @e2 "text" # Fill input field
npx agent-browser --cdp 9222 type @e3 "text" # Type text into element
npx agent-browser --cdp 9222 upload @e4 "/path/to/file.jpg" # Upload file
# Keyboard & Wait
npx agent-browser --cdp 9222 press Enter # Press key
npx agent-browser --cdp 9222 wait 2000 # Wait milliseconds
npx agent-browser --cdp 9222 wait --text "发布" # Wait for text
# Screenshot
npx agent-browser --cdp 9222 screenshot # Take screenshotImportant:
- Element refs (like @e5) come from
snapshot -ioutput. Always take a snapshot before interacting. - The
--cdp 9222flag connects to the browser's debug port instead of launching a new browser. - Do NOT use the
closecommand as it would close the user's browser!
Scripts
Located in ~/.claude/skills/xiaohongshu-publisher/scripts/:
parse_note.py
Parse Markdown and extract structured data for Xiaohongshu notes:
python parse_note.py <markdown_file> [--output json]Returns JSON with: title, content, images (list of paths), tags
copy_to_clipboard.py
Copy image to system clipboard for pasting:
python copy_to_clipboard.py image /path/to/image.jpg [--quality 80]Workflow
Phase 0: Verify Browser Connection
CRITICAL: First verify that the CDP browser is running and connected.
1. Check if browser is accessible:
npx agent-browser --cdp 9222 snapshot -i- If connection fails, tell user: "请先启动带调试端口的 Chrome 浏览器"
2. Provide startup command if needed:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222Phase 1: Check Login Status
1. Navigate to creator page (if not already there):
npx agent-browser --cdp 9222 open "https://creator.xiaohongshu.com/publish/publish"2. Take snapshot to check login status:
npx agent-browser --cdp 9222 snapshot -i- Look for "上传图片" button = logged in
- Look for QR code or login form = need to login
3. If login required:
- Tell user: "请在浏览器中扫码登录小红书,登录后告诉我"
- Wait for user confirmation
- Take new snapshot to verify login success
Phase 2: Parse Content
If user provides a markdown file, parse it:
python ~/.claude/skills/xiaohongshu-publisher/scripts/parse_note.py /path/to/note.mdOutput JSON:
{
"title": "Note Title",
"content": "Note description/content text...",
"images": ["/path/to/img1.jpg", "/path/to/img2.jpg"],
"tags": ["tag1", "tag2"]
}Phase 3: Upload Images
1. Take snapshot to get current element refs:
npx agent-browser --cdp 9222 snapshot -i2. Find the upload input element (look for file input or "上传图片" area)
3. Upload images:
# Single image
npx agent-browser --cdp 9222 upload @e<ref> "/path/to/image1.jpg"
# Multiple images (comma-separated)
npx agent-browser --cdp 9222 upload @e<ref> "/path/to/img1.jpg,/path/to/img2.jpg,/path/to/img3.jpg"4. Wait for uploads to complete:
npx agent-browser --cdp 9222 wait 30005. Take new snapshot after upload completes
Phase 4: Fill Title and Content
1. Find title input field from snapshot (placeholder usually contains "标题")
2. Fill title:
npx agent-browser --cdp 9222 fill @e<title_ref> "Your Note Title"- Title limit: ~20 characters recommended
3. Find content/description field (placeholder usually contains "描述" or "正文")
4. Fill content:
npx agent-browser --cdp 9222 fill @e<content_ref> "Your note content here..."- Content limit: ~1000 characters max
Phase 5: Add Tags (Optional)
If tags are provided:
1. Find tag input area from snapshot
2. Add each tag:
npx agent-browser --cdp 9222 click @e<add_tag_ref>
npx agent-browser --cdp 9222 fill @e<tag_input_ref> "tag1"
npx agent-browser --cdp 9222 press Enter- Repeat for each tag (max 5 recommended)
Phase 6: Save as Draft (Default) or Publish
Default Action: Save as Draft
1. Find "存草稿" button from snapshot
2. Click draft button:
npx agent-browser --cdp 9222 click @e<draft_button_ref>3. Verify success - take snapshot or wait for confirmation
Only If User Explicitly Requests Publishing
ONLY if user said "直接发布", "立即发布", or "publish now":
1. Find "发布" button from snapshot
2. Click publish button:
npx agent-browser --cdp 9222 click @e<publish_button_ref>When in doubt, ALWAYS save as draft.
Phase 7: Verify and Report
1. Take final snapshot to verify success:
npx agent-browser --cdp 9222 snapshot -i2. Report to user:
- Draft saved: "草稿已保存!请在小红书 App 中预览和发布。"
- Published: "笔记已发布成功!"
Example Flows
Example 1: Basic Image Publish
User: "发布这些图片到小红书: /path/to/photo1.jpg, /path/to/photo2.jpg, 标题是'周末好去处'"
# 1. Verify connection and check login
npx agent-browser --cdp 9222 snapshot -i
# 2. Navigate to publish page (if needed)
npx agent-browser --cdp 9222 open "https://creator.xiaohongshu.com/publish/publish"
# 3. Take snapshot, verify "上传图片" visible
npx agent-browser --cdp 9222 snapshot -i
# 4. Upload images
npx agent-browser --cdp 9222 upload @e<ref> "/path/to/photo1.jpg,/path/to/photo2.jpg"
npx agent-browser --cdp 9222 wait 3000
# 5. Take new snapshot, fill title
npx agent-browser --cdp 9222 snapshot -i
npx agent-browser --cdp 9222 fill @e<title_ref> "周末好去处"
# 6. Save as draft
npx agent-browser --cdp 9222 click @e<draft_ref>Example 2: Markdown File Publish
User: "把这个 markdown 发到小红书: /path/to/note.md"
# 1. Parse markdown
python ~/.claude/skills/xiaohongshu-publisher/scripts/parse_note.py /path/to/note.md
# 2. Extract: title, content, images, tags from JSON output
# 3. Verify connection
npx agent-browser --cdp 9222 snapshot -i
# 4. Upload all images from parsed result
npx agent-browser --cdp 9222 upload @e<ref> "/path/to/img1.jpg,/path/to/img2.jpg"
# 5. Fill title and content
npx agent-browser --cdp 9222 fill @e<title_ref> "parsed title"
npx agent-browser --cdp 9222 fill @e<content_ref> "parsed content"
# 6. Add tags if present
npx agent-browser --cdp 9222 fill @e<tag_ref> "tag1"
npx agent-browser --cdp 9222 press Enter
# 7. Save as draft
npx agent-browser --cdp 9222 click @e<draft_ref>Example 3: Direct Publish
User: "直接发布这些图到小红书,不用草稿"
# [Same as above until Step 6]
# Find and click "发布" button (NOT 存草稿)
npx agent-browser --cdp 9222 click @e<publish_ref>Critical Rules
1. 🚨 NEVER AUTO-PUBLISH - ALWAYS save as draft by default 2. 🔌 ALWAYS USE --cdp 9222 - Every command must include this flag 3. ❌ NEVER USE close COMMAND - Would close user's browser! 4. 📸 TAKE SNAPSHOTS FREQUENTLY - Page state changes, always get fresh refs 5. ⏳ WAIT AFTER UPLOADS - Give time for images to process 6. 🔄 HANDLE LOGIN GRACEFULLY - Guide user to login in browser if needed 7. 📝 RESPECT CONTENT LIMITS - Title ~20 chars, Content ~1000 chars 8. 🖼️ IMAGE LIMITS - 1-18 images per note
Troubleshooting
CDP Connection Failed
If snapshot fails to connect:
1. Verify Chrome is running with debug port:
lsof -i :92222. If not running, start Chrome:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=92223. Check for port conflicts - another process might be using 9222
Session Expired
If previously logged in but now seeing login page:
1. Tell user to re-login in browser: "您的登录状态已过期,请在浏览器中重新扫码登录"
2. Wait for user confirmation
3. Take new snapshot to verify
Upload Failed
- Check image file exists and is valid format (jpg, png, gif, webp)
- Check file size (Xiaohongshu has limits)
- Try uploading one at a time
- Take screenshot to see actual error:
npx agent-browser --cdp 9222 screenshot error.pngElement Not Found
- Page structure may have changed
- Always take a fresh snapshot before interacting
- Look for similar elements with different refs
Why CDP Mode with agent-browser?
Advantages:
1. No QR code scanning per session - Login persists in browser 2. User's own browser - Existing cookies and preferences 3. Simple CLI - Same agent-browser commands, just add --cdp 9222 4. No MCP server - Direct CLI invocation 5. Reliable auth - Browser handles session management
Trade-offs:
1. Requires browser running - User must start Chrome with debug port 2. Single browser instance - One session at a time 3. Manual first login - User does initial QR scan in browser
Supported Content
| Type | Details |
|---|---|
| Images | JPG, PNG, GIF, WebP (1-18 images) |
| Title | Up to ~20 characters recommended |
| Content | Up to ~1000 characters |
| Tags | Up to 5 tags recommended |
#!/usr/bin/env python3
"""
Copy image to system clipboard for Xiaohongshu (小红书) note publishing.
Supports:
- Image files (jpg, png, gif, webp) - copies as image data
- Optional image compression before copying
Usage:
# Copy image to clipboard
python copy_to_clipboard.py /path/to/image.jpg
# Copy image with compression (quality 0-100)
python copy_to_clipboard.py /path/to/image.jpg --quality 80
macOS Requirements:
pip install Pillow pyobjc-framework-Cocoa
"""
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 main():
parser = argparse.ArgumentParser(description='Copy image to clipboard for Xiaohongshu')
parser.add_argument('path', help='Path to image file')
parser.add_argument('--quality', '-q', type=int, default=None,
help='JPEG quality (1-100), enables compression')
parser.add_argument('--max-width', type=int, default=2000,
help='Max width for resize')
parser.add_argument('--max-height', type=int, default=2000,
help='Max height for resize')
args = parser.parse_args()
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_macos(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)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Parse Markdown or text content for Xiaohongshu (小红书) note publishing.
Extracts:
- Title (from first H1/H2 or first line)
- Content (body text, without images)
- Images (list of image paths)
- Tags (from #hashtag format)
Usage:
python parse_note.py <file> [--output json]
python parse_note.py --title "标题" --content "内容" --images "img1.jpg,img2.jpg"
Output (JSON):
{
"title": "Note Title",
"content": "Note content text...",
"images": ["/path/to/img1.jpg", "/path/to/img2.jpg"],
"tags": ["tag1", "tag2"]
}
"""
import argparse
import json
import os
import re
import sys
from pathlib import Path
def extract_title(text: str) -> tuple[str, str]:
"""Extract title from first H1, H2, or first non-empty line.
Returns:
(title, text_without_title_line)
"""
lines = text.strip().split('\n')
title = ""
title_line_idx = None
for idx, line in enumerate(lines):
stripped = line.strip()
if not stripped:
continue
# H1 - use as title and remove
if stripped.startswith('# '):
title = stripped[2:].strip()
title_line_idx = idx
break
# H2 - use as title and remove
if stripped.startswith('## '):
title = stripped[3:].strip()
title_line_idx = idx
break
# First non-empty, non-image line
if not stripped.startswith('!['):
title = stripped[:50] # Xiaohongshu title is short
title_line_idx = idx
break
# Remove title line from text
if title_line_idx is not None:
lines.pop(title_line_idx)
text = '\n'.join(lines)
return title, text
def extract_images(text: str, base_path: Path) -> tuple[list[str], str]:
"""Extract image paths and return text without image references.
Returns:
(image_paths, text_without_images)
"""
images = []
# Find all markdown images
img_pattern = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)')
def process_image(match):
img_path = match.group(2)
# Skip URLs, only process local files
if img_path.startswith(('http://', 'https://')):
return ''
# Resolve relative paths
if not os.path.isabs(img_path):
full_path = str(base_path / img_path)
else:
full_path = img_path
# Only add if file exists
if os.path.exists(full_path):
images.append(full_path)
else:
print(f"Warning: Image not found: {full_path}", file=sys.stderr)
return '' # Remove from text
clean_text = img_pattern.sub(process_image, text)
return images, clean_text
def extract_tags(text: str) -> tuple[list[str], str]:
"""Extract hashtags from text.
Returns:
(tags, text_without_tags)
"""
tags = []
# Find all hashtags (Chinese and English)
# Match #tag or #标签 format, but not ## headers
tag_pattern = re.compile(r'(?<!#)#([^\s#]+)')
matches = tag_pattern.findall(text)
for tag in matches:
if tag and tag not in tags:
tags.append(tag)
# Remove tag markers from text (keep the tag word itself)
clean_text = tag_pattern.sub(r'\1', text)
return tags, clean_text
def clean_content(text: str) -> str:
"""Clean up content text for Xiaohongshu.
- Remove markdown formatting
- Collapse multiple newlines
- Trim whitespace
"""
# Remove H2/H3 markers but keep text
text = re.sub(r'^#{2,}\s*', '', text, flags=re.MULTILINE)
# Remove bold/italic markers
text = re.sub(r'\*\*(.+?)\*\*', r'\1', text)
text = re.sub(r'\*(.+?)\*', r'\1', text)
# Remove links but keep text
text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text)
# Remove blockquote markers
text = re.sub(r'^>\s*', '', text, flags=re.MULTILINE)
# Remove list markers
text = re.sub(r'^[-*]\s+', '', text, flags=re.MULTILINE)
text = re.sub(r'^\d+\.\s+', '', text, flags=re.MULTILINE)
# Collapse multiple newlines to double
text = re.sub(r'\n{3,}', '\n\n', text)
# Trim
text = text.strip()
return text
def parse_file(filepath: str) -> dict:
"""Parse a file and return structured data."""
path = Path(filepath)
base_path = path.parent
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Extract title first
title, content = extract_title(content)
# Extract images
images, content = extract_images(content, base_path)
# Extract tags
tags, content = extract_tags(content)
# Clean content
content = clean_content(content)
return {
"title": title,
"content": content,
"images": images,
"tags": tags[:5], # Xiaohongshu recommends max 5 tags
"source_file": str(path.absolute())
}
def parse_args_content(title: str, content: str, images_str: str, tags_str: str) -> dict:
"""Parse content from command line arguments."""
images = []
if images_str:
for img in images_str.split(','):
img = img.strip()
if os.path.exists(img):
images.append(os.path.abspath(img))
else:
print(f"Warning: Image not found: {img}", file=sys.stderr)
tags = []
if tags_str:
tags = [t.strip() for t in tags_str.split(',') if t.strip()]
return {
"title": title or "",
"content": content or "",
"images": images,
"tags": tags[:5]
}
def main():
parser = argparse.ArgumentParser(description='Parse content for Xiaohongshu notes')
parser.add_argument('file', nargs='?', help='Markdown/text file to parse')
parser.add_argument('--title', '-t', help='Note title')
parser.add_argument('--content', '-c', help='Note content')
parser.add_argument('--images', '-i', help='Comma-separated image paths')
parser.add_argument('--tags', help='Comma-separated tags')
parser.add_argument('--output', choices=['json'], default='json',
help='Output format (default: json)')
args = parser.parse_args()
if args.file:
if not os.path.exists(args.file):
print(f"Error: File not found: {args.file}", file=sys.stderr)
sys.exit(1)
result = parse_file(args.file)
elif args.title or args.content or args.images:
result = parse_args_content(args.title, args.content, args.images, args.tags)
else:
print("Error: Provide either a file or --title/--content/--images", file=sys.stderr)
sys.exit(1)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == '__main__':
main()
Related skills
How it compares
Pick xiaohongshu-publisher over generic browser MCP skills when you need Xiaohongshu-specific Markdown parsing, multi-image upload, and draft-safe publishing defaults.
FAQ
Does xiaohongshu-publisher auto-publish notes?
xiaohongshu-publisher version 3.0.0 always saves notes as drafts by default and never auto-publishes. Developers must explicitly request publish-now or equivalent phrasing to move a draft live on Xiaohongshu Creator Platform.
How does xiaohongshu-publisher connect to the browser?
xiaohongshu-publisher uses agent-browser with the --cdp 9222 flag to attach to an existing Chrome session started with --remote-debugging-port=9222. Login persists in the browser after one-time authentication at creator.xiaohongshu.com.