Add baoyu-skills package

This commit is contained in:
2026-09-13 07:37:15 +08:00
commit 832004bd6f
782 changed files with 251339 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
# API Credential Setup
Guided setup when `WECHAT_APP_ID` / `WECHAT_APP_SECRET` are missing. Invoked by Step 2 of the article-posting workflow.
## Detection
Look for the credentials in this order:
1. Env vars `WECHAT_APP_ID` / `WECHAT_APP_SECRET`
2. `<cwd>/.baoyu-skills/.env` with `WECHAT_APP_ID=...`
3. `$HOME/.baoyu-skills/.env` with `WECHAT_APP_ID=...`
If none are present, run the guided setup below.
## Guided Setup
Show this message to the user and ask where to save:
```
WeChat API credentials not found.
To obtain credentials:
1. Visit https://mp.weixin.qq.com
2. Go to: 开发 → 基本配置
3. Copy AppID and AppSecret
Where to save?
A) Project-level: .baoyu-skills/.env (this project only)
B) User-level: ~/.baoyu-skills/.env (all projects)
```
After they choose a location, collect the values (prefer a user-input tool, fall back to a numbered prompt per the User Input Tools rule in SKILL.md) and append:
```
WECHAT_APP_ID=<user_input>
WECHAT_APP_SECRET=<user_input>
```
## Multi-Account Variant
If the user has multiple accounts configured (`accounts:` block in EXTEND.md), use prefixed keys instead — see `multi-account.md` → "Credential Resolution".

View File

@@ -0,0 +1,95 @@
# Article Posting (文章发表)
Post markdown articles to WeChat Official Account with full formatting support.
## Usage
```bash
# Post markdown article
${BUN_X} ./scripts/wechat-article.ts --markdown article.md
# With theme
${BUN_X} ./scripts/wechat-article.ts --markdown article.md --theme grace
# Disable bottom citations for ordinary external links
${BUN_X} ./scripts/wechat-article.ts --markdown article.md --no-cite
# With explicit options
${BUN_X} ./scripts/wechat-article.ts --markdown article.md --author "作者名" --summary "摘要"
```
## Parameters
| Parameter | Description |
|-----------|-------------|
| `--markdown <path>` | Markdown file to convert and post |
| `--theme <name>` | Theme: default, grace, simple, modern |
| `--no-cite` | Keep ordinary external links inline instead of converting them to bottom citations |
| `--title <text>` | Override title (auto-extracted from markdown) |
| `--author <name>` | Author name |
| `--summary <text>` | Article summary |
| `--html <path>` | Pre-rendered HTML file (alternative to markdown) |
| `--profile <dir>` | Chrome profile directory |
## Markdown Format
```markdown
---
title: Article Title
author: Author Name
---
# Title (becomes article title)
Regular paragraph with **bold** and *italic*.
## Section Header
![Image description](./image.png)
- List item 1
- List item 2
> Blockquote text
[Link text](https://example.com)
```
Markdown mode converts ordinary external links into bottom citations by default for WeChat-friendly output. Use `--no-cite` to disable that behavior.
## Image Handling
1. **Parse**: Images in markdown are replaced with `WECHATIMGPH_N`
2. **Render**: HTML is generated with placeholders in text
3. **Paste**: HTML content is pasted into WeChat editor
4. **Replace**: For each placeholder:
- Find and select the placeholder text
- Scroll into view
- Press Backspace to delete the placeholder
- Paste the image from clipboard
## Scripts
| Script | Purpose |
|--------|---------|
| `wechat-article.ts` | Main article publishing script |
| `md-to-wechat.ts` | Markdown to HTML with placeholders |
| `md/render.ts` | Markdown rendering with themes |
## Example Session
```
User: /post-to-wechat --markdown ./article.md
Claude:
1. Parses markdown, finds 5 images
2. Generates HTML with placeholders
3. Opens Chrome, navigates to WeChat editor
4. Pastes HTML content
5. For each image:
- Selects WECHATIMGPH_1
- Scrolls into view
- Presses Backspace to delete
- Pastes image
6. Reports: "Article composed with 5 images."
```

View File

@@ -0,0 +1,224 @@
---
name: first-time-setup
description: First-time setup flow for baoyu-post-to-wechat preferences
---
# First-Time Setup
## Overview
When no EXTEND.md is found, guide user through preference setup.
**BLOCKING OPERATION**: This setup MUST complete before ANY other workflow steps. Do NOT:
- Ask about content or files to publish
- Ask about themes or publishing methods
- Proceed to content conversion or publishing
ONLY ask the questions in this setup flow, save EXTEND.md, then continue.
## Setup Flow
```
No EXTEND.md found
|
v
+---------------------+
| AskUserQuestion |
| (all questions) |
+---------------------+
|
v
+---------------------+
| Create EXTEND.md |
+---------------------+
|
v
Continue to Step 1
```
## Questions
**Language**: Use user's input language or saved language preference.
Use AskUserQuestion with ALL questions in ONE call:
### Question 1: Default Theme
```yaml
header: "Theme"
question: "Default theme for article conversion?"
options:
- label: "default (Recommended)"
description: "Classic layout - centered title with border, white-on-color H2 (default: blue)"
- label: "grace"
description: "Elegant - text shadows, rounded cards, refined blockquotes (default: purple)"
- label: "simple"
description: "Minimal modern - asymmetric rounded corners, clean whitespace (default: green)"
- label: "modern"
description: "Large rounded corners, pill headings, spacious (default: orange)"
```
### Question 2: Default Color
```yaml
header: "Color"
question: "Default color preset? (theme default if not set)"
options:
- label: "Theme default (Recommended)"
description: "Use the theme's built-in default color"
- label: "blue"
description: "#0F4C81 经典蓝"
- label: "red"
description: "#A93226 中国红"
- label: "green"
description: "#009874 翡翠绿"
```
Note: User can choose "Other" to type any preset name (vermilion, yellow, purple, sky, rose, olive, black, gray, pink, orange) or hex value.
### Question 3: Default Publishing Method
```yaml
header: "Method"
question: "Default publishing method?"
options:
- label: "api (Recommended)"
description: "Fast, requires API credentials (AppID + AppSecret)"
- label: "browser"
description: "Slow, requires Chrome and login session"
- label: "remote-api"
description: "Fast, tunnels WeChat API calls through SSH to a server whose IP is on the WeChat allowlist"
```
If the user selects `remote-api`, prompt for `remote_publish_host` and (optionally) `remote_publish_user`, `remote_publish_identity_file`. These can also be filled in later by editing EXTEND.md.
### Question 4: Default Author
```yaml
header: "Author"
question: "Default author name for articles?"
options:
- label: "No default"
description: "Leave empty, specify per article"
```
Note: User will likely choose "Other" to type their author name.
### Question 5: Open Comments
```yaml
header: "Comments"
question: "Enable comments on articles by default?"
options:
- label: "Yes (Recommended)"
description: "Allow readers to comment on articles"
- label: "No"
description: "Disable comments by default"
```
### Question 6: Fans-Only Comments
```yaml
header: "Fans only"
question: "Restrict comments to followers only?"
options:
- label: "No (Recommended)"
description: "All readers can comment"
- label: "Yes"
description: "Only followers can comment"
```
### Question 7: Save Location
```yaml
header: "Save"
question: "Where to save preferences?"
options:
- label: "Project (Recommended)"
description: ".baoyu-skills/ (this project only)"
- label: "User"
description: "~/.baoyu-skills/ (all projects)"
```
## Save Locations
| Choice | Path | Scope |
|--------|------|-------|
| Project | `.baoyu-skills/baoyu-post-to-wechat/EXTEND.md` | Current project |
| User | `~/.baoyu-skills/baoyu-post-to-wechat/EXTEND.md` | All projects |
## After Setup
1. Create directory if needed
2. Write EXTEND.md
3. Confirm: "Preferences saved to [path]"
4. Continue to Step 0 (load the saved preferences)
## EXTEND.md Template
### Single Account (Default)
```md
default_theme: [default/grace/simple/modern]
default_color: [preset name, hex, or empty for theme default]
default_publish_method: [api/browser/remote-api]
default_author: [author name or empty]
need_open_comment: [1/0]
only_fans_can_comment: [1/0]
chrome_profile_path:
# Remote API publishing — only fill in if default_publish_method is remote-api
# or you plan to pass --remote on the CLI.
remote_publish_host:
remote_publish_user:
remote_publish_port:
remote_publish_identity_file:
remote_publish_known_hosts_file:
remote_publish_strict_host_key_checking:
remote_publish_connect_timeout:
remote_publish_proxy_jump:
```
Raw `ssh` / `scp` options are intentionally not supported; only the typed keys above are honored. Authentication is SSH key only.
### Multi-Account
```md
default_theme: [default/grace/simple/modern]
default_color: [preset name, hex, or empty for theme default]
accounts:
- name: [display name]
alias: [short key, e.g. "baoyu"]
default: true
default_publish_method: [api/browser/remote-api]
default_author: [author name]
need_open_comment: [1/0]
only_fans_can_comment: [1/0]
app_id: [WeChat App ID, optional]
app_secret: [WeChat App Secret, optional]
# Remote API publishing (optional, per-account override of globals)
remote_publish_host:
remote_publish_user:
remote_publish_identity_file:
- name: [second account name]
alias: [short key, e.g. "ai-tools"]
default_publish_method: [api/browser/remote-api]
default_author: [author name]
need_open_comment: [1/0]
only_fans_can_comment: [1/0]
```
## Adding More Accounts Later
After initial setup, users can add accounts by editing EXTEND.md:
1. Add an `accounts:` block with list items
2. Move per-account settings (author, publish method, comments) into each account entry
3. Keep global settings (theme, color) at the top level
4. Each account needs a unique `alias` (used for CLI `--account` arg and Chrome profile naming)
5. Set `default: true` on the primary account
## Modifying Preferences Later
Users can edit EXTEND.md directly or delete it to trigger setup again.

View File

@@ -0,0 +1,94 @@
# Image-Text Posting (贴图发表, formerly 图文)
Post image-text messages with multiple images to WeChat Official Account.
> **Note**: WeChat has renamed "图文" to "贴图" in the Official Account menu (as of 2026).
## Usage
```bash
# Post with images and markdown file (title/content extracted automatically)
${BUN_X} ./scripts/wechat-browser.ts --markdown source.md --images ./images/
# Post with explicit title and content
${BUN_X} ./scripts/wechat-browser.ts --title "标题" --content "内容" --image img1.png --image img2.png
# Save as draft
${BUN_X} ./scripts/wechat-browser.ts --markdown source.md --images ./images/ --submit
```
## Parameters
| Parameter | Description |
|-----------|-------------|
| `--markdown <path>` | Markdown file for title/content extraction |
| `--images <dir>` | Directory containing images (sorted by name) |
| `--title <text>` | Article title (max 20 chars, auto-compressed if too long) |
| `--content <text>` | Article content (max 1000 chars, auto-compressed if too long) |
| `--image <path>` | Single image file (can be repeated) |
| `--submit` | Save as draft (default: preview only) |
| `--profile <dir>` | Chrome profile directory |
## Auto Title/Content from Markdown
When using `--markdown`, the script:
1. **Parses frontmatter** for title and author:
```yaml
---
title: 文章标题
author: 作者名
---
```
2. **Falls back to H1** if no frontmatter title:
```markdown
# 这将成为标题
```
3. **Compresses title** to 20 characters if too long:
- Original: "如何在一天内彻底重塑你的人生"
- Compressed: "一天彻底重塑你的人生"
4. **Extracts first paragraphs** as content (max 1000 chars)
## Image Directory Mode
When using `--images <dir>`:
- All PNG/JPG files in directory are uploaded
- Files are sorted alphabetically by name
- Naming convention: `01-cover.png`, `02-content.png`, etc.
## Constraints
| Field | Max Length | Notes |
|-------|------------|-------|
| Title | 20 chars | Auto-compressed if longer |
| Content | 1000 chars | Auto-compressed if longer |
| Images | 9 max | WeChat limit |
## Example Session
```
User: /post-to-wechat --markdown ./article.md --images ./xhs-images/
Claude:
1. Parses markdown meta:
- Title: "如何在一天内彻底重塑你的人生" → "一天内重塑你的人生"
- Author: from frontmatter or default
2. Extracts content from first paragraphs
3. Finds 7 images in xhs-images/
4. Opens Chrome, navigates to WeChat "图文" editor
5. Uploads all images
6. Fills title and content
7. Reports: "Image-text posted with 7 images."
```
## Scripts
| Script | Purpose |
|--------|---------|
| `wechat-browser.ts` | Main image-text posting script |
| `cdp.ts` | Chrome DevTools Protocol utilities |
| `copy-to-clipboard.ts` | Clipboard operations |

View File

@@ -0,0 +1,152 @@
# Multi-Account Support
Details for managing multiple WeChat Official Accounts through one EXTEND.md. SKILL.md only covers single-account flow and the selection prompt — read this file when the user has an `accounts:` block, asks to publish to a specific account, or needs per-account credentials.
## Compatibility
| Condition | Mode | Behavior |
|-----------|------|----------|
| No `accounts` block | Single-account | Original behavior, no changes |
| `accounts` with 1 entry | Single-account | Auto-select, no prompt |
| `accounts` with 2+ entries | Multi-account | Prompt to select before publishing |
| `accounts` with `default: true` | Multi-account | Pre-select default; user can switch |
## EXTEND.md Example
```md
default_theme: default
default_color: blue
accounts:
- name: 宝玉的技术分享
alias: baoyu
default: true
default_publish_method: api
default_author: 宝玉
need_open_comment: 1
only_fans_can_comment: 0
app_id: your_wechat_app_id
app_secret: your_wechat_app_secret
- name: AI工具集
alias: ai-tools
default_publish_method: browser
default_author: AI工具集
need_open_comment: 1
only_fans_can_comment: 0
```
## Per-Account vs Global Keys
**Per-account** (also accepted globally as fallback): `default_publish_method`, `default_author`, `need_open_comment`, `only_fans_can_comment`, `app_id`, `app_secret`, `chrome_profile_path`, `remote_publish_host`, `remote_publish_user`, `remote_publish_port`, `remote_publish_identity_file`, `remote_publish_known_hosts_file`, `remote_publish_strict_host_key_checking`, `remote_publish_connect_timeout`, `remote_publish_proxy_jump`.
**Global-only** (always shared): `default_theme`, `default_color`.
## Account Selection (Step 0.5)
Insert between Step 0 (Load EXTEND.md) and Step 1 (Determine input type):
```
if no accounts block:
→ single-account mode (original behavior)
elif accounts.length == 1:
→ auto-select the only account
elif --account <alias> CLI arg:
→ select matching account
elif one account has default: true:
→ pre-select, display: "Using account: <name> (--account to switch)"
else:
→ prompt user to choose from the list
```
## Credential Resolution (API Method)
For the selected account with alias `{alias}`, try in this order (first hit wins):
1. `app_id` / `app_secret` inline in the EXTEND.md account block
2. Env vars `WECHAT_{ALIAS}_APP_ID` / `WECHAT_{ALIAS}_APP_SECRET` (alias uppercased, hyphens → underscores)
3. `.baoyu-skills/.env` with the prefixed key `WECHAT_{ALIAS}_APP_ID`
4. `~/.baoyu-skills/.env` with the prefixed key
5. Fallback to unprefixed `WECHAT_APP_ID` / `WECHAT_APP_SECRET`
### .env Multi-Account Example
```bash
# Account: baoyu
WECHAT_BAOYU_APP_ID=your_wechat_app_id
WECHAT_BAOYU_APP_SECRET=your_wechat_app_secret
# Account: ai-tools
WECHAT_AI_TOOLS_APP_ID=your_ai_tools_wechat_app_id
WECHAT_AI_TOOLS_APP_SECRET=your_ai_tools_wechat_app_secret
```
## Chrome Profile (Browser Method)
Each account uses an isolated Chrome profile so logins don't collide.
| Source | Path |
|--------|------|
| Account `chrome_profile_path` in EXTEND.md | Use as-is |
| Auto-generated from alias | `{shared_profile_parent}/wechat-{alias}/` |
| Single-account fallback | Shared default profile |
## CLI `--account` Flag
All publishing scripts accept `--account <alias>`:
```bash
${BUN_X} {baseDir}/scripts/wechat-api.ts <file> --theme default --account ai-tools
${BUN_X} {baseDir}/scripts/wechat-article.ts --markdown <file> --theme default --account baoyu
${BUN_X} {baseDir}/scripts/wechat-browser.ts --markdown <file> --images ./photos/ --account baoyu
```
## Remote API Publishing
`wechat-api.ts` supports a `remote-api` mode that tunnels WeChat API calls through an SSH SOCKS5 dynamic port forward to a server whose IP is on WeChat's allowlist. Markdown rendering, image processing, draft assembly, and HTML rewriting still happen locally; only outbound HTTPS calls to `api.weixin.qq.com` traverse the tunnel. No files are written to the remote host and `AppSecret` never leaves the local process. The remote host needs only `sshd` and outbound network access.
### Per-Account Configuration
```md
default_theme: default
default_color: blue
default_publish_method: browser # browser remains the default
accounts:
- name: 宝玉的技术分享
alias: baoyu
default: true
default_publish_method: api
default_author: 宝玉
app_id: your_wechat_app_id
app_secret: your_wechat_app_secret
- name: AI工具集
alias: ai-tools
default_publish_method: remote-api
default_author: AI工具集
app_id: your_ai_tools_app_id
app_secret: your_ai_tools_app_secret
remote_publish_host: ai-tools-server.example.com
remote_publish_user: deploy
remote_publish_port: 22
remote_publish_identity_file: /home/me/.ssh/id_ed25519
remote_publish_known_hosts_file: /home/me/.ssh/known_hosts
remote_publish_strict_host_key_checking: accept-new
```
Account-level `remote_publish_*` values override top-level globals. CLI `--remote-*` flags override both.
### CLI Usage
```bash
# Use the account's default_publish_method (remote-api here):
${BUN_X} {baseDir}/scripts/wechat-api.ts <file> --theme default --account ai-tools
# Force remote mode regardless of default_publish_method:
${BUN_X} {baseDir}/scripts/wechat-api.ts <file> --theme default --account baoyu --remote --remote-host other-server.example.com
```
### Security Notes
- Authentication is SSH key only. Passwords and `ssh-askpass` are not used.
- Only the typed `remote_publish_*` keys are read; raw `ssh` / `scp` options are intentionally not supported.
- The tunnel forwards raw TCP; TLS verification for `api.weixin.qq.com` is still performed end-to-end by the local process.