Posts text, images, videos, and long-form articles to X via real Chrome browser (bypasses anti-bot detection).
When user invokes this skill without specifying what to publish (e.g., just says "发到 X" or uses slash command):
Clean Chrome CDP processes first (see CDP Cleanup below)
Get current active file,按优先级依次尝试:
优先级 1:<current_note> 标签(Claudian 注入,最准确)
检查用户消息末尾是否有 <current_note> 标签,直接提取路径,无需执行任何命令。
优先级 2:workspace.json(本地 fallback)
jq -r '.lastOpenFiles[] | select(endswith(".md"))' .obsidian/workspace.json | head -1
优先级 3:Obsidian CLI(最后手段)
obsidian file active
Read the file content using Read tool
Auto-detect publishing type:
是否长文: true
是否长文: true → Publish as X Article (long-form)是否长文: false, 是否长文 absent, or no frontmatter → Publish as Regular Post (short-form)Inform user of detected type and proceed with publishing
Execute appropriate workflow:
x-article.ts (handles Obsidian conversion internally)md-to-post.ts → Publish with x-post.ts
Execute publishing scripts via Bash background process (REQUIRED - do NOT use Agent run_in_background=true):
& with log redirected to vault: bun ... > .temp/x-article-output.log 2>&1 &
run_in_background=true — the output file lands in /private/tmp/ which is blocked by vault permission hooks, causing TaskOutput to fail and triggering a duplicate execution.temp/x-article-output.log with sleep N && cat .temp/x-article-output.log | tail -30
Success Detection: Check .temp/x-article-output.log for success markers:
Image upload verified occurrences matching expected image countPost composed (preview mode) or Browser remains open
Article composed or Browser remains open
sleep 15 && cat .temp/x-article-output.log | tail -30 to pollExample:
User: "发到 X"
AI: ✓ Detected current file: Articles/news/my-article.md
✓ Found `是否文章: true` → Publishing as X Article
[proceeds with article publishing workflow]
Run this before every publishing script to prevent "Chrome debug port not ready" errors. Do it automatically — no need to ask the user.
pkill -f "Chrome.*remote-debugging-port"; pkill -f "Chromium.*remote-debugging-port"; sleep 2
X Articles vs Regular Posts:
| Feature | X Articles | Regular Posts |
|---|---|---|
| Content | Rich text (Markdown) | Plain text only |
| Formatting | ✅ Bold, italic, headers, lists | ❌ All stripped |
| Code blocks | ✅ Syntax highlighting | ❌ Not supported |
| Images | ✅ Multiple images | ✅ Max 4 images |
| Length | Long-form (unlimited) | Short (280 chars) |
| Requirements | X Premium | Free |
| Script | x-article.ts |
x-post.ts |
| Conversion | md-to-article.ts |
md-to-post.ts |
When to use:
For Obsidian users who want to publish the currently active article:
bash ${SKILL_DIR}/scripts/publish-active.sh
This automatically:
Important: All scripts are located in the scripts/ subdirectory of this skill.
Agent Execution Instructions:
SKILL_DIR
${SKILL_DIR}/scripts/<script-name>.ts
${SKILL_DIR} in this document with the actual path${BUN_X} runtime: if bun installed → bun; if npx available → npx -y bun; else suggest installing bunScript Reference:
| Script | Purpose |
|---|---|
| Publishing Scripts | |
scripts/x-post.ts |
Publish regular posts (text + images, max 4) |
scripts/x-video.ts |
Publish video posts (text + video) |
scripts/x-quote.ts |
Publish quote tweet with comment |
scripts/x-article.ts |
Publish X Articles (rich text + images + code) |
| Conversion Scripts | |
scripts/md-to-post.ts |
Convert Obsidian Markdown → plain text + images (for Posts) |
scripts/md-to-article.ts |
Convert Obsidian Markdown → HTML + images (for Articles) |
| Utilities | |
scripts/publish-active.sh |
One-command publish for active Obsidian file |
bun runtimejq tool (brew install jq on macOS)Before first use, suggest running the environment check:
${BUN_X} ${SKILL_DIR}/scripts/check-paste-permissions.ts
Checks: Chrome, Bun, Accessibility permissions, clipboard, paste keystroke.
If any check fails, provide fix guidance per item:
| Check | Fix |
|---|---|
| Chrome | Install Chrome or set X_BROWSER_CHROME_PATH env var |
| Bun runtime | brew install oven-sh/bun/bun (macOS) or npm install -g bun |
| Accessibility (macOS) | System Settings → Privacy & Security → Accessibility → enable terminal app |
| Paste keystroke (Linux) | Install xdotool (X11) or ydotool (Wayland) |
For Obsidian users, this skill can automatically detect the currently active file and convert Obsidian-specific syntax.
Quick workflow:
# One-command publish
bash ${SKILL_DIR}/scripts/publish-active.sh
Manual workflow:
# Step 1: Get active file (workspace.json method, 39x faster)
ACTIVE_FILE=$(jq -r '.lastOpenFiles[0]' .obsidian/workspace.json)
# Step 2: Publish (x-article.ts handles Obsidian conversion internally)
bun ${SKILL_DIR}/scripts/x-article.ts "$ACTIVE_FILE"
For detailed Obsidian integration, see references/obsidian-integration.md:
For Obsidian syntax conversion, see references/obsidian-conversion.md:
![[]] image syntaxText + up to 4 images. Plain text only (all Markdown formatting stripped).
Step 1: CDP cleanup (see CDP Cleanup section)
Step 2: Convert Markdown to plain text + images
# Extract content from Markdown file
# Supports both standard Markdown  and Obsidian ![[path]] image syntax
# --output-dir keeps downloaded images inside the vault (.temp) to avoid permission issues
bun ${SKILL_DIR}/scripts/md-to-post.ts "Articles/my-post.md" --output-dir ".temp/x-post" > .temp/post-content.json
TEXT=$(jq -r '.text' .temp/post-content.json)
IMAGES=$(jq -r '.images[]' .temp/post-content.json)
Image Syntax Support:

![[path/to/image.png]]
 or ![[https://example.com/image.jpg]]
Step 2.5: 提取封面图作为第一张图
从文件 frontmatter 中读取 封面 属性,若存在则将其路径作为第一张图片:
# 提取 封面 属性(支持 ![[path]] 和裸路径两种格式)
COVER_RAW=$(grep -m1 '^封面:' "Articles/my-post.md" | sed 's/^封面: *//' | tr -d '"')
# 解析 Obsidian wikilink 格式 ![[path]] → path
COVER_PATH=$(echo "$COVER_RAW" | sed 's/^!\[\[//;s/\]\]$//')
# 组合图片列表:封面优先,正文图片补充(总数上限 4 张)
IMAGE_ARGS=""
if [ -n "$COVER_PATH" ]; then
IMAGE_ARGS="--image \"$COVER_PATH\""
fi
for img in $IMAGES; do
IMAGE_ARGS="$IMAGE_ARGS --image \"$img\""
done
封面 属性为空或不存在,则跳过,仅使用正文图片Step 3: Publish post
eval "${BUN_X} ${SKILL_DIR}/scripts/x-post.ts \"$TEXT\" $IMAGE_ARGS"
${BUN_X} ${SKILL_DIR}/scripts/x-post.ts "Hello!" --image ./photo.png
Parameters:
| Parameter | Description |
|---|---|
<text> |
Post content (plain text, positional) |
--image <path> |
Image file (repeatable, max 4) |
--profile <dir> |
Custom Chrome profile |
Content Processing:
Browser Behavior:
--submit flag to auto-publish (closes after 2 seconds)AI Success Detection (for background execution):
Image upload verified in output matching expected image countPost composed (preview mode) or Browser remains open
1. Know you're uploading 3 images
2. Wait 10-15s for uploads
3. Check output: grep "Image upload verified" | wc -l
4. If count == 3 → Report success immediately
Text + video file.
Step 1: CDP cleanup (see CDP Cleanup section)
Step 2: Publish video post
${BUN_X} ${SKILL_DIR}/scripts/x-video.ts "Check this out!" --video ./clip.mp4
Parameters:
| Parameter | Description |
|---|---|
<text> |
Post content (positional) |
--video <path> |
Video file (MP4, MOV, WebM) |
--profile <dir> |
Custom Chrome profile |
Limits: Regular 140s max, Premium 60min. Processing: 30-60s.
Quote an existing tweet with comment.
Step 1: CDP cleanup (see CDP Cleanup section)
Step 2: Publish quote tweet
${BUN_X} ${SKILL_DIR}/scripts/x-quote.ts https://x.com/user/status/123 "Great insight!"
Parameters:
| Parameter | Description |
|---|---|
<tweet-url> |
URL to quote (positional) |
<comment> |
Comment text (positional, optional) |
--profile <dir> |
Custom Chrome profile |
Long-form Markdown articles (requires X Premium).
Step 1: CDP cleanup (see CDP Cleanup section)
Step 2: Publish article
${BUN_X} ${SKILL_DIR}/scripts/x-article.ts article.md
${BUN_X} ${SKILL_DIR}/scripts/x-article.ts article.md --cover ./cover.jpg
Parameters:
| Parameter | Description |
|---|---|
<markdown> |
Markdown file (positional) |
--cover <path> |
Cover image |
--title <text> |
Override title |
Frontmatter: title / 标题, cover_image / 封面 / 配图 supported in YAML front matter.
Title Resolution (for X Articles):
# 优先读 frontmatter 的 `标题` 属性,为空则 fallback 到文件名
TITLE=$(obsidian property:read name="标题" path="$NOTE_PATH" 2>/dev/null | tr -d '[:space:]')
if [ -z "$TITLE" ]; then
TITLE=$(basename "$NOTE_PATH" .md)
fi
# 传入脚本
${BUN_X} ${SKILL_DIR}/scripts/x-article.ts "$NOTE_PATH" --title "$TITLE"
Note: Script opens browser with article filled in. User reviews and publishes manually.
Code blocks are automatically extracted from Markdown and inserted into X Articles editor. Supports all languages (JavaScript, Python, TypeScript, Rust, Go, Shell, etc.). No manual action required.
Common issues:
md-to-post.ts downloads images to system /tmp by default, which may be blocked by vault-restricted hooks. Fix: always pass --output-dir ".temp/x-post" to keep images inside the vault:
bun ${SKILL_DIR}/scripts/md-to-post.ts "Articles/my-post.md" --output-dir ".temp/x-post"
For detailed troubleshooting, see references/troubleshooting.md.
references/obsidian-integration.md - Obsidian file detection and integrationreferences/obsidian-conversion.md - Converting Obsidian syntax to standard Markdownreferences/regular-posts.md - Regular posts workflow and troubleshootingreferences/articles.md - X Articles detailed guidereferences/troubleshooting.md - Common issues and solutionsreferences/browser-automation-lessons.md - Browser automation patterns and lessons learned (CDP, DraftJS, background tabs)Custom configurations via EXTEND.md. Check these paths (priority order):
.libukai-skills/obsidian-to-x/EXTEND.md (project directory)$HOME/.libukai-skills/obsidian-to-x/EXTEND.md (user home)EXTEND.md Supports: Default Chrome profile
--submit flag)