diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 9ca2763..523ea03 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Skills shared by Baoyu for improving daily work efficiency", - "version": "0.4.0" + "version": "0.4.1" }, "plugins": [ { diff --git a/README.md b/README.md index b3145b1..2a7f1f1 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,25 @@ Run the following command in Claude Code: /plugin install content-skills@baoyu-skills ``` +**Option 3: Ask the Agent** + +Simply tell Claude Code: + +> Please install Skills from github.com/JimLiu/baoyu-skills + +## Update Skills + +To update skills to the latest version: + +1. Run `/plugin` in Claude Code +2. Switch to **Marketplaces** tab (use arrow keys or Tab) +3. Select **baoyu-skills** +4. Choose **Update marketplace** + +You can also **Enable auto-update** to get the latest versions automatically. + +![Update Skills](./screenshots/update-plugins.png) + ## Available Skills ### gemini-web diff --git a/README.zh.md b/README.zh.md index 4b2e493..b67564e 100644 --- a/README.zh.md +++ b/README.zh.md @@ -34,6 +34,25 @@ /plugin install content-skills@baoyu-skills ``` +**方式三:告诉 Agent** + +直接告诉 Claude Code: + +> 请帮我安装 github.com/JimLiu/baoyu-skills 中的 Skills + +## 更新技能 + +更新技能到最新版本: + +1. 在 Claude Code 中运行 `/plugin` +2. 切换到 **Marketplaces** 标签页(使用方向键或 Tab) +3. 选择 **baoyu-skills** +4. 选择 **Update marketplace** + +也可以选择 **Enable auto-update** 启用自动更新,每次启动时自动获取最新版本。 + +![更新技能](./screenshots/update-plugins.png) + ## 可用技能 ### gemini-web diff --git a/screenshots/update-plugins.png b/screenshots/update-plugins.png new file mode 100644 index 0000000..73b7f2b Binary files /dev/null and b/screenshots/update-plugins.png differ diff --git a/skills/baoyu-post-to-wechat/SKILL.md b/skills/baoyu-post-to-wechat/SKILL.md index 7f53de4..c35d3d8 100644 --- a/skills/baoyu-post-to-wechat/SKILL.md +++ b/skills/baoyu-post-to-wechat/SKILL.md @@ -7,25 +7,45 @@ description: Post content to WeChat Official Account (微信公众号). Supports Post content to WeChat Official Account using Chrome CDP automation. +## Script Directory + +**Important**: All scripts are located in the `scripts/` subdirectory of this skill. + +**Agent Execution Instructions**: +1. Determine this SKILL.md file's directory path as `SKILL_DIR` +2. Script path = `${SKILL_DIR}/scripts/.ts` +3. Replace all `${SKILL_DIR}` in this document with the actual path + +**Script Reference**: +| Script | Purpose | +|--------|---------| +| `scripts/wechat-browser.ts` | Image-text posts (图文) | +| `scripts/wechat-article.ts` | Full article posting (文章) | +| `scripts/md-to-wechat.ts` | Markdown → WeChat HTML conversion | +| `scripts/copy-to-clipboard.ts` | Copy content to clipboard | +| `scripts/paste-from-clipboard.ts` | Send real paste keystroke | + ## Quick Usage ### Image-Text (图文) - Multiple images with title/content ```bash # From markdown file and image directory -npx -y bun ./scripts/wechat-browser.ts --markdown article.md --images ./images/ +npx -y bun ${SKILL_DIR}/scripts/wechat-browser.ts --markdown article.md --images ./images/ # With explicit parameters -npx -y bun ./scripts/wechat-browser.ts --title "标题" --content "内容" --image img1.png --image img2.png --submit +npx -y bun ${SKILL_DIR}/scripts/wechat-browser.ts --title "标题" --content "内容" --image img1.png --image img2.png --submit ``` ### Article (文章) - Full markdown with formatting ```bash # Post markdown article -npx -y bun ./scripts/wechat-article.ts --markdown article.md --theme grace +npx -y bun ${SKILL_DIR}/scripts/wechat-article.ts --markdown article.md --theme grace ``` +> **Note**: `${SKILL_DIR}` represents this skill's installation directory. Agent replaces with actual path at runtime. + ## References - **Image-Text Posting**: See `references/image-text-posting.md` for detailed image-text posting guide diff --git a/skills/baoyu-post-to-wechat/scripts/paste-from-clipboard.ts b/skills/baoyu-post-to-wechat/scripts/paste-from-clipboard.ts new file mode 100644 index 0000000..8466c08 --- /dev/null +++ b/skills/baoyu-post-to-wechat/scripts/paste-from-clipboard.ts @@ -0,0 +1,194 @@ +import { spawnSync } from 'node:child_process'; +import process from 'node:process'; + +function printUsage(exitCode = 0): never { + console.log(`Send real paste keystroke (Cmd+V / Ctrl+V) to the frontmost application + +This bypasses CDP's synthetic events which websites can detect and ignore. + +Usage: + npx -y bun paste-from-clipboard.ts [options] + +Options: + --retries Number of retry attempts (default: 3) + --delay Delay between retries in ms (default: 500) + --app Target application to activate first (macOS only) + --help Show this help + +Examples: + # Simple paste + npx -y bun paste-from-clipboard.ts + + # Paste to Chrome with retries + npx -y bun paste-from-clipboard.ts --app "Google Chrome" --retries 5 + + # Quick paste with shorter delay + npx -y bun paste-from-clipboard.ts --delay 200 +`); + process.exit(exitCode); +} + +function sleepSync(ms: number): void { + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); +} + +function activateApp(appName: string): boolean { + if (process.platform !== 'darwin') return false; + + // Activate and wait for app to be frontmost + const script = ` + tell application "${appName}" + activate + delay 0.5 + end tell + + -- Verify app is frontmost + tell application "System Events" + set frontApp to name of first application process whose frontmost is true + if frontApp is not "${appName}" then + tell application "${appName}" to activate + delay 0.3 + end if + end tell + `; + const result = spawnSync('osascript', ['-e', script], { stdio: 'pipe' }); + return result.status === 0; +} + +function pasteMac(retries: number, delayMs: number, targetApp?: string): boolean { + for (let i = 0; i < retries; i++) { + // Build script that activates app (if specified) and sends keystroke in one atomic operation + const script = targetApp + ? ` + tell application "${targetApp}" + activate + end tell + delay 0.3 + tell application "System Events" + keystroke "v" using command down + end tell + ` + : ` + tell application "System Events" + keystroke "v" using command down + end tell + `; + + const result = spawnSync('osascript', ['-e', script], { stdio: 'pipe' }); + if (result.status === 0) { + return true; + } + + const stderr = result.stderr?.toString().trim(); + if (stderr) { + console.error(`[paste] osascript error: ${stderr}`); + } + + if (i < retries - 1) { + console.error(`[paste] Attempt ${i + 1}/${retries} failed, retrying in ${delayMs}ms...`); + sleepSync(delayMs); + } + } + return false; +} + +function pasteLinux(retries: number, delayMs: number): boolean { + // Try xdotool first (X11), then ydotool (Wayland) + const tools = [ + { cmd: 'xdotool', args: ['key', 'ctrl+v'] }, + { cmd: 'ydotool', args: ['key', '29:1', '47:1', '47:0', '29:0'] }, // Ctrl down, V down, V up, Ctrl up + ]; + + for (const tool of tools) { + const which = spawnSync('which', [tool.cmd], { stdio: 'pipe' }); + if (which.status !== 0) continue; + + for (let i = 0; i < retries; i++) { + const result = spawnSync(tool.cmd, tool.args, { stdio: 'pipe' }); + if (result.status === 0) { + return true; + } + if (i < retries - 1) { + console.error(`[paste] Attempt ${i + 1}/${retries} failed, retrying in ${delayMs}ms...`); + sleepSync(delayMs); + } + } + return false; + } + + console.error('[paste] No supported tool found. Install xdotool (X11) or ydotool (Wayland).'); + return false; +} + +function pasteWindows(retries: number, delayMs: number): boolean { + const ps = ` + Add-Type -AssemblyName System.Windows.Forms + [System.Windows.Forms.SendKeys]::SendWait("^v") + `; + + for (let i = 0; i < retries; i++) { + const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', ps], { stdio: 'pipe' }); + if (result.status === 0) { + return true; + } + if (i < retries - 1) { + console.error(`[paste] Attempt ${i + 1}/${retries} failed, retrying in ${delayMs}ms...`); + sleepSync(delayMs); + } + } + return false; +} + +function paste(retries: number, delayMs: number, targetApp?: string): boolean { + switch (process.platform) { + case 'darwin': + return pasteMac(retries, delayMs, targetApp); + case 'linux': + return pasteLinux(retries, delayMs); + case 'win32': + return pasteWindows(retries, delayMs); + default: + console.error(`[paste] Unsupported platform: ${process.platform}`); + return false; + } +} + +async function main(): Promise { + const args = process.argv.slice(2); + + let retries = 3; + let delayMs = 500; + let targetApp: string | undefined; + + for (let i = 0; i < args.length; i++) { + const arg = args[i] ?? ''; + if (arg === '--help' || arg === '-h') { + printUsage(0); + } + if (arg === '--retries' && args[i + 1]) { + retries = parseInt(args[++i]!, 10) || 3; + } else if (arg === '--delay' && args[i + 1]) { + delayMs = parseInt(args[++i]!, 10) || 500; + } else if (arg === '--app' && args[i + 1]) { + targetApp = args[++i]; + } else if (arg.startsWith('-')) { + console.error(`Unknown option: ${arg}`); + printUsage(1); + } + } + + if (targetApp) { + console.log(`[paste] Target app: ${targetApp}`); + } + console.log(`[paste] Sending paste keystroke (retries=${retries}, delay=${delayMs}ms)...`); + const success = paste(retries, delayMs, targetApp); + + if (success) { + console.log('[paste] Paste keystroke sent successfully'); + } else { + console.error('[paste] Failed to send paste keystroke'); + process.exit(1); + } +} + +await main(); diff --git a/skills/baoyu-post-to-x/SKILL.md b/skills/baoyu-post-to-x/SKILL.md index 4c24faf..ba2e79a 100644 --- a/skills/baoyu-post-to-x/SKILL.md +++ b/skills/baoyu-post-to-x/SKILL.md @@ -7,26 +7,23 @@ description: Post content and articles to X (Twitter). Supports regular posts wi Post content, images, and long-form articles to X using real Chrome browser (bypasses anti-bot detection). -## Features +## Script Directory -- **Regular Posts**: Text + up to 4 images -- **X Articles**: Publish Markdown files with rich formatting and images (requires X Premium) +**Important**: All scripts are located in the `scripts/` subdirectory of this skill. -## Usage +**Agent Execution Instructions**: +1. Determine this SKILL.md file's directory path as `SKILL_DIR` +2. Script path = `${SKILL_DIR}/scripts/.ts` +3. Replace all `${SKILL_DIR}` in this document with the actual path -```bash -# Post text only -/baoyu-post-to-x "Your post content here" - -# Post with image -/baoyu-post-to-x "Your post content" --image /path/to/image.png - -# Post with multiple images (up to 4) -/baoyu-post-to-x "Your post content" --image img1.png --image img2.png - -# Actually submit the post -/baoyu-post-to-x "Your post content" --submit -``` +**Script Reference**: +| Script | Purpose | +|--------|---------| +| `scripts/x-browser.ts` | Regular posts (text + images) | +| `scripts/x-article.ts` | Long-form article publishing (Markdown) | +| `scripts/md-to-html.ts` | Markdown → HTML conversion | +| `scripts/copy-to-clipboard.ts` | Copy content to clipboard | +| `scripts/paste-from-clipboard.ts` | Send real paste keystroke | ## Prerequisites @@ -34,58 +31,28 @@ Post content, images, and long-form articles to X using real Chrome browser (byp - `bun` installed (for running scripts) - First run: log in to X in the opened browser window -## Quick Start (Recommended) +## References -Use the `x-browser.ts` script directly: +- **Regular Posts**: See `references/regular-posts.md` for manual workflow, troubleshooting, and technical details +- **X Articles**: See `references/articles.md` for long-form article publishing guide + +--- + +## Regular Posts + +Text + up to 4 images. ```bash # Preview mode (doesn't post) -npx -y bun ./scripts/x-browser.ts "Hello from Claude!" --image ./screenshot.png +npx -y bun ${SKILL_DIR}/scripts/x-browser.ts "Hello from Claude!" --image ./screenshot.png # Actually post -npx -y bun ./scripts/x-browser.ts "Hello!" --image ./photo.png --submit +npx -y bun ${SKILL_DIR}/scripts/x-browser.ts "Hello!" --image ./photo.png --submit ``` -The script: -1. Launches real Chrome with anti-detection disabled -2. Uses persistent profile (only need to log in once) -3. Types text and pastes images via CDP -4. Waits 30s for preview (or posts immediately with `--submit`) - -## Manual Workflow - -If you prefer step-by-step control: - -### Step 1: Copy Image to Clipboard - -```bash -npx -y bun ./scripts/copy-to-clipboard.ts image /path/to/image.png -``` - -### Step 2: Use Playwright MCP (if Chrome session available) - -```bash -# Navigate -mcp__playwright__browser_navigate url="https://x.com/compose/post" - -# Get element refs -mcp__playwright__browser_snapshot - -# Type text -mcp__playwright__browser_click element="editor" ref="" -mcp__playwright__browser_type element="editor" ref="" text="Your content" - -# Paste image (after copying to clipboard) -mcp__playwright__browser_press_key key="Meta+v" # macOS -# or -mcp__playwright__browser_press_key key="Control+v" # Windows/Linux - -# Screenshot to verify -mcp__playwright__browser_take_screenshot filename="preview.png" -``` - -## Parameters +> **Note**: `${SKILL_DIR}` represents this skill's installation directory. Agent replaces with actual path at runtime. +**Parameters**: | Parameter | Description | |-----------|-------------| | `` | Post content (positional argument) | @@ -93,42 +60,40 @@ mcp__playwright__browser_take_screenshot filename="preview.png" | `--submit` | Actually post (default: preview only) | | `--profile ` | Custom Chrome profile directory | -## Image Support +--- -- Formats: PNG, JPEG, GIF, WebP -- Max 4 images per post -- Images copied to system clipboard, then pasted via keyboard shortcut +## X Articles -## Example Session +Long-form Markdown articles (requires X Premium). -``` -User: /baoyu-post-to-x "Hello from Claude!" --image ./screenshot.png +```bash +# Preview mode +npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md -Claude: -1. Runs: npx -y bun ./scripts/x-browser.ts "Hello from Claude!" --image ./screenshot.png -2. Chrome opens with X compose page -3. Text is typed into editor -4. Image is copied to clipboard and pasted -5. Browser stays open 30s for preview -6. Reports: "Post composed. Use --submit to post." +# With cover image +npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md --cover ./cover.jpg + +# Publish +npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md --submit ``` -## Troubleshooting +**Parameters**: +| Parameter | Description | +|-----------|-------------| +| `` | Markdown file path (positional argument) | +| `--cover ` | Cover image path | +| `--title ` | Override article title | +| `--submit` | Actually publish (default: preview only) | -- **Chrome not found**: Set `X_BROWSER_CHROME_PATH` environment variable -- **Not logged in**: First run opens Chrome - log in manually, cookies are saved -- **Image paste fails**: Verify clipboard script: `npx -y bun ./scripts/copy-to-clipboard.ts image ` -- **Rate limited**: Wait a few minutes before retrying +**Frontmatter** (optional): +```yaml +--- +title: My Article Title +cover_image: /path/to/cover.jpg +--- +``` -## How It Works - -The `x-browser.ts` script uses Chrome DevTools Protocol (CDP) to: -1. Launch real Chrome (not Playwright) with `--disable-blink-features=AutomationControlled` -2. Use persistent profile directory for saved login sessions -3. Interact with X via CDP commands (Runtime.evaluate, Input.dispatchKeyEvent) -4. Paste images from system clipboard - -This approach bypasses X's anti-automation detection that blocks Playwright/Puppeteer. +--- ## Notes @@ -136,204 +101,3 @@ This approach bypasses X's anti-automation detection that blocks Playwright/Pupp - Always preview before using `--submit` - Browser closes automatically after operation - Supports macOS, Linux, and Windows - ---- - -# X Articles (Long-form Publishing) - -Publish Markdown articles to X Articles editor with rich text formatting and images. - -## X Article Usage - -```bash -# Publish markdown article (preview mode) -/baoyu-post-to-x article /path/to/article.md - -# With custom cover image -/baoyu-post-to-x article article.md --cover ./hero.png - -# With custom title -/baoyu-post-to-x article article.md --title "My Custom Title" - -# Actually publish (not just draft) -/baoyu-post-to-x article article.md --submit -``` - -## Prerequisites for Articles - -- X Premium subscription (required for Articles) -- Google Chrome installed -- `bun` installed - -## Article Script - -Use `x-article.ts` directly: - -```bash -npx -y bun ./scripts/x-article.ts article.md -npx -y bun ./scripts/x-article.ts article.md --cover ./cover.jpg -npx -y bun ./scripts/x-article.ts article.md --submit -``` - -## Markdown Format - -```markdown ---- -title: My Article Title -cover_image: /path/to/cover.jpg ---- - -# Title (becomes article title) - -Regular paragraph text with **bold** and *italic*. - -## Section Header - -More content here. - -![Image alt text](./image.png) - -- List item 1 -- List item 2 - -1. Numbered item -2. Another item - -> Blockquote text - -[Link text](https://example.com) - -\`\`\` -Code blocks become blockquotes (X doesn't support code) -\`\`\` -``` - -## Frontmatter Fields - -| Field | Description | -|-------|-------------| -| `title` | Article title (or uses first H1) | -| `cover_image` | Cover image path or URL | -| `cover` | Alias for cover_image | -| `image` | Alias for cover_image | - -## Image Handling - -1. **Cover Image**: First image or `cover_image` from frontmatter -2. **Remote Images**: Automatically downloaded to temp directory -3. **Placeholders**: Images in content use `[[IMAGE_PLACEHOLDER_N]]` format -4. **Insertion**: Placeholders are found, selected, and replaced with actual images - -## Markdown to HTML Script - -Convert markdown and inspect structure: - -```bash -# Get JSON with all metadata -npx -y bun ./scripts/md-to-html.ts article.md - -# Output HTML only -npx -y bun ./scripts/md-to-html.ts article.md --html-only - -# Save HTML to file -npx -y bun ./scripts/md-to-html.ts article.md --save-html /tmp/article.html -``` - -JSON output: -```json -{ - "title": "Article Title", - "coverImage": "/path/to/cover.jpg", - "contentImages": [ - { - "placeholder": "[[IMAGE_PLACEHOLDER_1]]", - "localPath": "/tmp/x-article-images/img.png", - "blockIndex": 5 - } - ], - "html": "

Content...

", - "totalBlocks": 20 -} -``` - -## Supported Formatting - -| Markdown | HTML Output | -|----------|-------------| -| `# H1` | Title only (not in body) | -| `## H2` - `###### H6` | `

` | -| `**bold**` | `` | -| `*italic*` | `` | -| `[text](url)` | `` | -| `> quote` | `
` | -| `` `code` `` | `` | -| ```` ``` ```` | `
` (X limitation) | -| `- item` | `