One X tweet per day, inspiredDirectly pulled from a recently published WeChat Official Account articleThe article is the source, the tweet is a shorter extraction.
Core Principle
The article has already gone through a round of Wang Jianshuo-style refinement; the tweet is just a version one notch shorter. Don't rethink it—pull the most quotable sentence / passage directly from article.md, and cut it to X's rhythm.
At most one per day, each article is only tweeted once. State file state/history.jsonl records which articles have already been tweeted; no repeats. (Exception: batch scheduling mode—queue up multiple articles at once and auto-post one every N hours, see below.)
Actually post, don't draft. Immediately after the user confirms, xurl POST. On failure, dump the original text to the user to retry.
When This Skill Fires
- The user says "today's tweet" / "post an X" / "tweet from an article"
- The user runs
/wjs-tweeting-from-articles - The user has set up
/schedule daily /wjs-tweeting-from-articlesto auto-call every day after
When NOT to use
- The content the user wants to postis unrelated to the recent WeChat Official Account articles—just
xurl POST /2/tweetsis fine - What the user wants to post is a product / skill promotion—use
/publish-skillor/wjs-promoting-skills
Workflow
Step 1: Pick today's article to tweet
scripts/pick-next-article.sh
Output the folder path of the newest articlethat hasn't been tweeted yet(within the last week, find the first untweeted one in reverse date order). If all have been tweeted → exit 0, empty output, tell the user "all articles from the last 7 days have been tweeted, today is a rest day".
If the user wants to tweeta specificone: skip this script, directly use <folder>。
Step 2: Read the article + draft 3 tweet candidates
Read <folder>/article.md, draft one for each of the followingthree angles:
| Angle | Material | Example |
|---|---|---|
| A · Punchline | Pick the most quotable sentence from the text, you can add a lead-in sentence | "The pen tip has gone dull, it can't write a sharp edge—dip it in some ink, turn it twice on the inkstone, and it has a point again." |
| B · Metaphor | The article's core metaphor + one sentence that lands the metaphor on the reader | "Writing prompts is like painting, it's a hands-on craft. You have to write some every day." |
| C · Contrast | A "it's not X, it's Y"-style cognitive flip | "It's not that you got smarter, it's that the feel came." |
Hard length constraint:tweet ≤ 280 characters(X limit; each Chinese character counts as 2)—so a Chinese tweet is actually ≤ 140 characters。keep a buffer to within 120 charactersto be safer.
Style: keep the Wang Jianshuo tone—plain, homely metaphors, no marketing speak.Don'tadd hashtags, don't add @, don't add emoji (unless the original has them).Don'tadd an mp.weixin link (by default); if the user asks whether to include a link, suggest it can be attached in a reply.
Step 3: Let the user pick one
Use AskUserQuestion Give three candidates A/B/C + "fourth option: other". After the user picks one, proceed to Step 4.
Step 4: Actually post to X
TWEET_TEXT='<picked text>'
JSON=$(jq -nc --arg text "$TWEET_TEXT" '{text:$text}')
resp=$(xurl -X POST -d "$JSON" /2/tweets)
# Don't use `jq -r '.data.id'` here — X API returns raw newlines in the echoed
# `text` field, which strict jq rejects with "control characters must be escaped".
# Grep the id directly instead.
TWEET_ID=$(printf '%s' "$resp" | grep -oE '"id":"[0-9]+"' | head -1 | sed -E 's/.*"([0-9]+)".*/\1/')
[[ -n "$TWEET_ID" ]] || { echo "POST failed: $resp"; exit 1; }
echo "https://x.com/jianshuo/status/$TWEET_ID"
Success → get tweet_id + URL, tell the user.
Step 5: Record to history
HIST="$HOME/.claude/skills/wjs-tweeting-from-articles/state/history.jsonl"
SLUG=$(basename "$FOLDER")
jq -nc --arg date "$(date +%F)" --arg slug "$SLUG" --arg angle "$ANGLE" \
--arg tweet_id "$TWEET_ID" --arg text "$TWEET_TEXT" \
'{date:$date,slug:$slug,angle:$angle,tweet_id:$tweet_id,text:$text,status:"posted"}' \
>> "$HIST"
angle ∈ A / B / C / other。
Step 6: Wrap up
Tell the user:
- tweet URL
- Which article (slug)
- Which angle
- Today's history row has been written
Inputs
/wjs-tweeting-from-articles # 自动挑最近一篇没推过的
/wjs-tweeting-from-articles <article-folder> # 显式指定
/wjs-tweeting-from-articles --dry-run # 草稿不发
File Layout
~/.claude/skills/wjs-tweeting-from-articles/
├── SKILL.md
├── scripts/
│ ├── pick-next-article.sh # 找最近一篇未推的 article folder
│ └── post-next-from-queue.sh # 批量排期模式:每次发队列里下一条,自节流
└── state/
├── .gitignore # 屏蔽 history.jsonl 不被推到 public repo
├── history.jsonl # 每条 tweet 一行 JSON record
├── queue-<DATE>.tsv # 批量模式:待发队列 idx/slug/text
├── queue-cursor # 批量模式:下一条序号
└── last-post-epoch # 批量模式:上次发出的时间戳(节流用)
Batch scheduling mode (schedule multiple articles at once, one every N hours)
Need to postmany articles(typical source:wjs-mining-articles digs a dozen-plus articles out of one long conversation), use this mode—posting them all in a row will get flagged as spam by X, so queue them up and post automatically at fixed intervals.
Why not use AskUserQuestion to pick an angle article by article: a dozen-plus articles × A/B/C is too much. For each article, directly extractonethe most quotable line (≤120 characters, Wang Jianshuo's tone, no hashtag/emoji/link, with pangu spacing), list the full text for the user to review in one pass + set the cadence, and schedule after confirmation.
Mechanism(scripts/post-next-from-queue.sh + an hourly cron, the script throttles itself):
# 1. 队列文件:state/queue-<DATE>.tsv,每行 idx<TAB>slug<TAB>text
# 2. cursor: state/queue-cursor(下一条序号);last-post-epoch(上次发的时间)
# 3. 立刻发第一条(FORCE 绕过节流,顺便验证 xurl 能发):
FORCE=1 bash scripts/post-next-from-queue.sh
# 4. 装 cron:每小时跑,脚本只在距上次 ≥ MIN_GAP(默认 4h)才真发:
( crontab -l 2>/dev/null | grep -v post-next-from-queue.sh; \
echo "5 * * * * /bin/bash $HOME/.claude/skills/wjs-tweeting-from-articles/scripts/post-next-from-queue.sh >/dev/null 2>&1" ) | crontab -
Key script points: single-machine lock (prevent concurrency), on post failure do not advance the cursor (retry next hour), only on successful post write history.jsonl and advance,when the queue is finished, automatically delete its own cron entry. To change the interval use MIN_GAP(seconds).
Daily automation (optional)
To run automatically every day:
/schedule daily 09:00 /wjs-tweeting-from-articles
Or write a cron. Butby default does notrun automatically—manually confirming which angle to pick each day is more reliable.
Anti-Patterns
| Do not | Reason |
|---|---|
| Add hashtags (#AI #prompt) | Wang Jianshuo's X style doesn't use hashtags; adding them makes it sound marketing-y |
| Post two tweets from the same article | One article, one tweet; if the article is especially long / has multiple cores, next run delete one row for it from history and repost |
| Stuff an mp.weixin link into the tweet | By default don't include it; if you want to, put it in a reply |
| Post all 3 candidates | The user picks 1; not picking = skip today |
| Invent a tweet out of thin air that isn't in the article | Inspirationmustbe extracted from article.md; the value of this skill is precisely "the article is the source" |
| Have the LLM rewrite the original's style to "distill" it | Wang Jianshuo's original is already tight enough; direct extraction > rewriting |
Dependencies
- xurl:
xurl whoamican return the username (auth OK) - jq: parse the JSON returned by xurl
- Exists in
~/code/wechat-publish/articles/YYYY-MM-DD-*/article.md: source article