wjs-tweeting-from-articles
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesewjs-tweeting-from-articles
wjs-tweeting-from-articles
每天一条 X tweet,灵感直接从最近发的公众号文章里抠。文章是源头,tweet 是更短的萃取。
One daily X tweet, with inspiration directly extracted from recently published WeChat Official Account articles. The article is the source, and the tweet is a more concise distillation.
Core Principle
Core Principle
文章已经过了一轮王建硕式的精炼,tweet 只是再短一格的版本。 不要重新构思——从 article.md 里直接抠最 quotable 的一句 / 一段,按 X 的节奏切。
一天最多一条,一篇文章只推一次。 状态文件 记录哪些 article 已经推过;不重复。
state/history.jsonl真发,不暂存。 用户确认后立刻 。失败原文 dump 给用户重试。
xurl POSTThe article has already gone through Wang Jianshuo-style refinement; the tweet is just an even shorter version. Do not re-conceptualize — directly extract the most quotable sentence/paragraph from article.md and adapt it to X's rhythm.
Maximum one tweet per day, one tweet per article. The state file records which articles have been tweeted; no repetition.
state/history.jsonlPost immediately, no draft. After user confirmation, send via immediately. If it fails, dump the original content to the user for retry.
xurl POSTWhen This Skill Fires
When This Skill Fires
- 用户说「今天的 tweet」/「发一条 X」/「从文章里发推」
- 用户跑
/wjs-tweeting-from-articles - 用户设了 之后每天自动调
/schedule daily /wjs-tweeting-from-articles
- User says "Today's tweet" / "Post an X tweet" / "Tweet from an article"
- User runs
/wjs-tweeting-from-articles - User sets up for automatic daily execution
/schedule daily /wjs-tweeting-from-articles
When NOT to use
When NOT to use
- 用户要发的内容和最近的公众号文章无关——直接 即可
xurl POST /2/tweets - 用户要发的是产品 / skill 推广——用 或
/publish-skill/wjs-promoting-skills
- The content the user wants to post is unrelated to recent WeChat Official Account articles — directly use
xurl POST /2/tweets - The user wants to promote a product/skill — use or
/publish-skill/wjs-promoting-skills
Workflow
Workflow
Step 1: 挑今天要推的文章
Step 1: Select today's article to tweet
bash
scripts/pick-next-article.sh输出最新一篇还没推过的文章的 folder 路径(最近一周内,按日期倒序找第一个未推的)。已经全推完 → 退出 0、空输出,告诉用户「最近 7 天的文章都推过了,今天 rest day」。
如果用户想推特定那一篇:跳过此脚本,直接用 。
<folder>bash
scripts/pick-next-article.shOutputs the folder path of the latest article that hasn't been tweeted yet (within the last week, find the first untweeted one in reverse chronological order). If all have been tweeted → exit with code 0, empty output, and inform the user "All articles from the last 7 days have been tweeted, today is a rest day".
If the user wants to tweet a specific article: skip this script and directly use .
<folder>Step 2: 读文章 + 草拟 3 条 tweet 候选
Step 2: Read article + draft 3 tweet candidates
读 ,按下面三个角度各起草一条:
<folder>/article.md| 角度 | 选材 | 例子 |
|---|---|---|
| A · 金句 | 从文中挑一句最 quotable 的,可以加一句铺垫 | "笔头钝了,写不出锋——蘸点墨,在砚台上转两圈,重新有了尖。" |
| B · 比喻 | 文章的核心比喻 + 一句把比喻落到读者头上 | "写 prompt 跟画画一样,是手感活儿。每天都得写点。" |
| C · 反差 | 「不是 X,是 Y」式的认知翻转 | "不是变聪明,是手感来了。" |
长度硬约束:tweet ≤ 280 字符(X 限制;中文每字算 2)——所以中文 tweet 实际 ≤ 140 字。留 buffer 到 120 字以内比较稳。
风格:保留王建硕语气——平实、家常比喻、不写营销腔。不要加 hashtags、不要加 @、不要加 emoji(除非原文有)。不要加 mp.weixin 链接(默认);如果用户问要不要带链接,提示可以 reply 里附。
Read , draft one candidate from each of the following three angles:
<folder>/article.md| Angle | Content Selection | Example |
|---|---|---|
| A · Quote | Pick the most quotable sentence from the article, can add a lead-in | "When your pen tip goes blunt and can't write sharply — dip it in ink, twist it twice on the inkstone, and it regains its sharpness." |
| B · Metaphor | The core metaphor of the article + a sentence that connects it to the reader | "Writing prompts is like painting — it's a matter of muscle memory. You have to write a little every day." |
| C · Contrast | Cognitive flip in the form of "It's not X, it's Y" | "It's not about getting smarter, it's about getting into the groove." |
Hard length constraint: Tweet ≤ 280 characters (X's limit; each Chinese character counts as 2) — so actual Chinese tweets ≤ 140 characters. Keeping it within 120 characters is safer for buffer.
Style: Retain Wang Jianshuo's tone — plain, down-to-earth metaphors, no marketing jargon. Do not add hashtags, @ mentions, or emojis (unless present in the original text). Do not add mp.weixin links (by default); if the user asks about adding links, suggest attaching them in a reply.
Step 3: 让用户挑一条
Step 3: Let the user select one
用 给出 A/B/C 三条候选 + 「四选其他」。用户选一条之后进入 Step 4。
AskUserQuestionUse to present the three A/B/C candidates plus a "Fourth option: other". After the user selects one, proceed to Step 4.
AskUserQuestionStep 4: 真发 X
Step 4: Post to X for real
bash
TWEET_TEXT='<picked text>'
JSON=$(jq -nc --arg text "$TWEET_TEXT" '{text:$text}')
resp=$(xurl -X POST -d "$JSON" /2/tweets)bash
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
jq -r '.data.id'Don't use jq -r '.data.id'
here — X API returns raw newlines in the echoed
jq -r '.data.id'text
field, which strict jq rejects with "control characters must be escaped".
texttext
field, which strict jq rejects with "control characters must be escaped".
textGrep the id directly instead.
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"
成功 → 拿到 tweet_id + URL,告诉用户。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"
If successful → get the tweet_id + URL and inform the user.Step 5: 记录到 history
Step 5: Record to history
bash
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"angleABCotherbash
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"angleABCotherStep 6: 收尾
Step 6: Wrap up
告诉用户:
- tweet URL
- 哪篇文章(slug)
- 哪个 angle
- 今天的 history 行已经写入
Inform the user:
- Tweet URL
- Which article (slug)
- Which angle was used
- Today's history entry has been written
Inputs
Inputs
/wjs-tweeting-from-articles # 自动挑最近一篇没推过的
/wjs-tweeting-from-articles <article-folder> # 显式指定
/wjs-tweeting-from-articles --dry-run # 草稿不发/wjs-tweeting-from-articles # Automatically select the latest untweeted article
/wjs-tweeting-from-articles <article-folder> # Explicitly specify an article
/wjs-tweeting-from-articles --dry-run # Draft only, no postingFile Layout
File Layout
~/.claude/skills/wjs-tweeting-from-articles/
├── SKILL.md
├── scripts/
│ └── pick-next-article.sh # 找最近一篇未推的 article folder
└── state/
├── .gitignore # 屏蔽 history.jsonl 不被推到 public repo
└── history.jsonl # 每条 tweet 一行 JSON record~/.claude/skills/wjs-tweeting-from-articles/
├── SKILL.md
├── scripts/
│ └── pick-next-article.sh # Find the latest untweeted article folder
└── state/
├── .gitignore # Exclude history.jsonl from public repo
└── history.jsonl # One JSON record per tweet, line-separatedDaily 自动化(可选)
Daily Automation (Optional)
要每天自动跑:
bash
/schedule daily 09:00 /wjs-tweeting-from-articles或写 cron。但默认不自动跑——每天人工确认 angle 选哪条更稳。
To run automatically every day:
bash
/schedule daily 09:00 /wjs-tweeting-from-articlesOr use cron. But by default, do not run automatically — manually confirming which angle to choose each day is more reliable.
Anti-Patterns
Anti-Patterns
| 不要 | 原因 |
|---|---|
| 加 hashtags(#AI #prompt) | 王建硕的 X 风格不用 hashtag,加了变营销腔 |
| 同一篇文章推两条 | 一篇一推;如果文章特别长 / 多核心,下次跑时把它从 history 删一行重推 |
| tweet 里塞 mp.weixin 链接 | 默认不带;想带就放 reply 里 |
| 把 3 条候选都发出去 | 用户挑 1 条;不挑 = 跳过今天 |
| 凭空生造一条不在文章里的 tweet | 灵感必须从 article.md 抠;这条 skill 的价值就是「文章是源」 |
| LLM 自己改原文风格做"提炼" | 王建硕原文已经够紧;直接抠 > 重写 |
| Do Not | Reason |
|---|---|
| Add hashtags (#AI #prompt) | Wang Jianshuo's X style doesn't use hashtags; adding them makes it sound like marketing |
| Tweet twice from the same article | One tweet per article; if the article is particularly long/multi-core, delete one entry from history and retweet next time |
| Include mp.weixin links in the tweet | Do not include by default; if you want to add it, put it in a reply |
| Post all 3 candidates | The user selects 1; if no selection is made, skip today |
| Create a tweet from scratch that's not in the article | Inspiration must be extracted from article.md; the value of this skill is that "the article is the source" |
| Let LLM rewrite the original text in its own style for "refinement" | Wang Jianshuo's original text is already concise enough; direct extraction is better than rewriting |
Dependencies
Dependencies
- xurl:能返回用户名(auth OK)
xurl whoami - jq:解析 xurl 返回的 JSON
- 存在 :源文章
~/code/wechat-publish/articles/YYYY-MM-DD-*/article.md
- xurl: can return the username (authentication OK)
xurl whoami - jq: Parse JSON returned by xurl
- Presence of : Source articles
~/code/wechat-publish/articles/YYYY-MM-DD-*/article.md