
X Publish
- 77 installs
- 316 repo stars
- Updated February 21, 2026
- kangarooking/x-skills
Helps with ai & agent building tasks.
About
x-publish is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- x-publish
- AI & Agent Building
- AI-coding skill
X Publish by the numbers
- 77 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #5,358 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/kangarooking/x-skills --skill x-publishAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 77 |
|---|---|
| repo stars | ★ 316 |
| Last updated | February 21, 2026 |
| Repository | kangarooking/x-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
X Publish
Publish tweets and threads to X draft using Playwright browser automation.
Prerequisites
- Playwright MCP for browser automation
- User logged into X (Twitter)
- Python 3.9+ with dependencies:
- macOS:
pip install Pillow pyobjc-framework-Cocoa - Windows:
pip install Pillow pywin32
Critical Rules
1. NEVER auto-publish - Only save to draft 2. User must be logged in - Prompt to login if not 3. Verify content before saving - Double-check tweet text
Content Types
Short Tweet
Single tweet ≤280 characters
Thread
Multiple tweets connected (3-10 tweets)
Scripts
copy_to_clipboard.py
Copy text to system clipboard for paste operation:
# Copy text to clipboard
python scripts/copy_to_clipboard.py text "Tweet content here"
# Copy from file
python scripts/copy_to_clipboard.py text --file /tmp/tweet.txtWorkflow
Short Tweet
Step 1: Prepare Content
# Save tweet to temp file
echo "Tweet content" > /tmp/tweet.txt
# Copy to clipboard
python scripts/copy_to_clipboard.py text --file /tmp/tweet.txtStep 2: Open Compose
browser_navigate: https://x.com/compose/postStep 3: Paste Content
browser_snapshot → Find tweet textbox
browser_click: textbox
browser_press_key: Meta+vStep 4: Save Draft
browser_click: X (close button)
browser_click: "Save" or "保存" in dialogStep 5: Verify
Report: "Draft saved. Please review at https://x.com/compose/drafts"Thread
Step 1: Prepare Content Parse thread into individual tweets:
### 1/5
First tweet content...
### 2/5
Second tweet content...Step 2: Open Compose
browser_navigate: https://x.com/compose/postStep 3: Add First Tweet
python scripts/copy_to_clipboard.py text "First tweet content"browser_click: textbox
browser_press_key: Meta+vStep 4: Add More Tweets For each additional tweet:
browser_click: "Add another post" / "添加" button
browser_press_key: Meta+v (after copying next tweet)Step 5: Save Draft
browser_click: X (close button)
browser_click: "Save" in dialogStep 6: Verify
Report: "Thread draft saved ({n} tweets). Review at https://x.com/compose/drafts"Efficiency Guidelines
Avoid Unnecessary Waits
❌ browser_snapshot after every action
✅ Use action return values for next stepParallel Preparation
✅ Prepare all tweet content before browser operations
✅ Copy to clipboard while navigatingSequential Execution
Navigate → Paste first tweet → Add tweet → Paste → ... → SaveElement References
Common elements in X compose:
| Element | Description | Typical ref pattern |
|---|---|---|
| Tweet textbox | Main input area | textbox with "What's happening" |
| Add tweet button | "+" or "添加" | button near compose area |
| Close button | X icon | button top-left |
| Save draft | In close dialog | "Save" / "保存" button |
| Drafts link | View saved drafts | link to /compose/drafts |
Example Flows
Short Tweet Example
User: /x-publish "Claude 4.5发布了,extended thinking是真正的游戏规则改变者。"
# 1. Copy to clipboard
python scripts/copy_to_clipboard.py text "Claude 4.5发布了,extended thinking是真正的游戏规则改变者。"# 2. Navigate and paste
browser_navigate: https://x.com/compose/post
browser_snapshot → find textbox
browser_click: textbox
browser_press_key: Meta+v
# 3. Close and save
browser_click: close button (X)
browser_click: "Save" button
# 4. Report
"Draft saved! Review at: https://x.com/compose/drafts"Thread Example
User: /x-publish (with thread content from x-create)
Thread:
### 1/3
First point about AI...
### 2/3
Second point...
### 3/3
Conclusion...Execution:
# Prepare all tweets
tweet1="First point about AI..."
tweet2="Second point..."
tweet3="Conclusion..."# Navigate
browser_navigate: https://x.com/compose/post
# Tweet 1
python copy_to_clipboard.py text "$tweet1"
browser_click: textbox
browser_press_key: Meta+v
# Tweet 2
python copy_to_clipboard.py text "$tweet2"
browser_click: "Add another post" button
browser_press_key: Meta+v
# Tweet 3
python copy_to_clipboard.py text "$tweet3"
browser_click: "Add another post" button
browser_press_key: Meta+v
# Save
browser_click: close button
browser_click: "Save"
# Report
"Thread draft saved (3 tweets). Review at: https://x.com/compose/drafts"Error Handling
Not Logged In
If login page detected:
→ "Please log in to X first, then run /x-publish again"Character Limit
If tweet > 280 chars:
→ Split into thread or truncate with warningNetwork Error
If page fails to load:
→ Retry once, then report errorIntegration
After publishing:
推文已保存到草稿箱!
- 类型: {short/thread}
- 条数: {n}
- 草稿链接: https://x.com/compose/drafts
请手动审核后发布。Append a machine-readable block for hooks/state ingestion:
PUBLISH_JSON
{
"schema_version": "x_skills.publish.v1",
"timestamp": "{timestamp}",
"draft_saved": true,
"post_type": "short|thread",
"count": 1,
"draft_url": "https://x.com/compose/drafts"
}(Optional) Append event for feedback loop:
python ~/.claude/skills/x-create/scripts/x_state.py event --event publish.saved_to_draft --payload-json '{"post_type":"short","count":1,"draft_saved":true}'#!/usr/bin/env python3
"""
Copy text to system clipboard for X tweet publishing.
Usage:
# Copy text directly
python copy_to_clipboard.py text "Tweet content here"
# Copy text from file
python copy_to_clipboard.py text --file /tmp/tweet.txt
Requirements:
macOS: pip install pyobjc-framework-Cocoa
Windows: pip install pyperclip
"""
import argparse
import os
import sys
def copy_text_to_clipboard_macos(text: str) -> bool:
"""Copy text to macOS clipboard using AppKit."""
try:
from AppKit import NSPasteboard, NSPasteboardTypeString
pasteboard = NSPasteboard.generalPasteboard()
pasteboard.clearContents()
pasteboard.setString_forType_(text, 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 text: {e}", file=sys.stderr)
return False
def copy_text_to_clipboard_windows(text: str) -> bool:
"""Copy text to Windows clipboard."""
try:
import pyperclip
pyperclip.copy(text)
return True
except ImportError:
# Fallback to win32clipboard
try:
import win32clipboard
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(text, win32clipboard.CF_UNICODETEXT)
finally:
win32clipboard.CloseClipboard()
return True
except ImportError as e:
print(f"Error: Missing dependency: {e}", file=sys.stderr)
print("Install with: pip install pyperclip", file=sys.stderr)
return False
except Exception as e:
print(f"Error copying text: {e}", file=sys.stderr)
return False
def copy_text_to_clipboard_linux(text: str) -> bool:
"""Copy text to Linux clipboard using xclip or xsel."""
try:
import subprocess
# Try xclip first
try:
process = subprocess.Popen(
['xclip', '-selection', 'clipboard'],
stdin=subprocess.PIPE
)
process.communicate(text.encode('utf-8'))
return process.returncode == 0
except FileNotFoundError:
pass
# Try xsel
try:
process = subprocess.Popen(
['xsel', '--clipboard', '--input'],
stdin=subprocess.PIPE
)
process.communicate(text.encode('utf-8'))
return process.returncode == 0
except FileNotFoundError:
print("Error: Neither xclip nor xsel found. Install one of them.", file=sys.stderr)
return False
except Exception as e:
print(f"Error copying text: {e}", file=sys.stderr)
return False
def copy_text_to_clipboard(text: str) -> bool:
"""Copy text to clipboard (cross-platform)."""
if sys.platform == 'darwin':
return copy_text_to_clipboard_macos(text)
elif sys.platform == 'win32':
return copy_text_to_clipboard_windows(text)
elif sys.platform.startswith('linux'):
return copy_text_to_clipboard_linux(text)
else:
print(f"Error: Unsupported platform: {sys.platform}", file=sys.stderr)
return False
def main():
parser = argparse.ArgumentParser(description='Copy text to clipboard for X publishing')
subparsers = parser.add_subparsers(dest='type', required=True)
# Text subcommand
text_parser = subparsers.add_parser('text', help='Copy text to clipboard')
text_parser.add_argument('content', nargs='?', help='Text content')
text_parser.add_argument('--file', '-f', help='Read text from file')
args = parser.parse_args()
if args.type == 'text':
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:
text = f.read()
elif args.content:
text = args.content
else:
# Read from stdin
text = sys.stdin.read()
success = copy_text_to_clipboard(text)
if success:
char_count = len(text)
preview = text[:50] + '...' if len(text) > 50 else text
preview = preview.replace('\n', ' ')
print(f"Text copied to clipboard ({char_count} chars)")
print(f"Preview: {preview}")
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()