Help the user write a WeChat Official Account article.Light polish, no rewriting. Automatically generate the cover image and explanatory images, and push the draft with one command.
⚠️ Sole style authority: STYLE.md (must-read before writing / polishing)
Before writing or polishing each article, first read the STYLE.mdin the same directory, and follow it strictly. That is the style definition file maintained by Wang Jianshuo himself—tone DNA, length, bolding, layout, and the red-line checklist are all there. This SKILL.md only coversmechanism and workflow(scripts, images, HTML conversion, publishing).
When STYLE.md and this file conflict on style,STYLE.md prevails. To adjust style = change STYLE.md, not here, and certainly not in the draft.
Below, only the mechanism-related writing constraints are kept:
- Word count(STYLE.md sets the budget; here is the counting command):
python3 -c "import re; t=open('article.md').read(); t=re.sub(r'\!\[.*?\]\(.*?\)','',t); print(len(re.findall(r'[一-鿿]',t)) + len(re.findall(r'[A-Za-z]+',t)))"
- Mechanism for bold and red:
upload-draft.shRender**...**as red bold (usage see STYLE.md "bold and red"). - Mechanism for Pangu spacing:
upload-draft.shAutomatically runscripts/pangu.py, Claude does not need to add spaces manually. - How to write commands / code as standalone paragraphs(rules see STYLE.md; here is a copyable style):
- Preferred—light background code block (raw HTML block, the whole paragraph on one line, no blank lines inside):
<section style="background:#f6f8fa;border-radius:6px;padding:14px 16px;overflow-x:auto;font-family:Menlo,Consolas,monospace;font-size:14px;line-height:1.8;color:#24292e;">npm install -g xxx<br>xxx run</section> - Or fenced
```bashblock, the script converts it into a standalone<p><code>…</code></p>。
- Preferred—light background code block (raw HTML block, the whole paragraph on one line, no blank lines inside):
For articles introducing a skill: the installation method must be attached at the end
Trigger condition: this article is introducing / recommending a specific Claude Code skill.
Prerequisite—confirm the skill has been published: Wang Jianshuo's own wjs-* skill is automatically pushed by ~/.claude/skills-publish-hook.sh to github.com/jianshuo/claude-skills, confirm with gh api repos/jianshuo/claude-skills/contents/<skill-name> . For other people's skills, first confirm they are in a public git repo.
Attach the following passage at the end(<SKILL_NAME> replace with the actual name):
## 安装方法
不用复制命令。打开你用的 AI agent——Claude Code、Codex、Kimi Code、OpenClaw 都可以,对它说一句:
> 安装 https://github.com/jianshuo/claude-skills/blob/main/<SKILL_NAME>/SKILL.md
它会自己 fetch、放到 skill 目录里、提示你重启对话。
用 Hermes 的话直接命令行:
\`\`\`bash
hermes skills install https://github.com/jianshuo/claude-skills/blob/main/<SKILL_NAME>/SKILL.md
\`\`\`
装完之后,对 agent 说一句「<一句最自然的触发语,紧扣这个 skill 的入口>」,就能用。
Rules:
- This passagedoes not count toward the 800–1000 word budget
- Use
github.com/<owner>/<repo>/blob/main/<path>for URLs—browsers can view it directly, and LLM agents can also extract markdown from the blob URL - List Hermes separatelycommand line, because it is a registry CLI rather than a chat agent
- Write the final trigger phrase according to the current skill's actual entry point,do not omit it
- Usually placed last; if there is
## 后注then place it before the postscript
Workflow
Step 0: Receive input
Input forms: complete draft / scattered ideas / long passage without paragraph breaks / voice transcription (may contain typos). If too scattered,ask one question: "Do you want to write one article, or several independent ideas?"—ask only this once.
Step 1: Light polish
- Fix typos ("的得地", homophones, repeated characters like "我我")
- One paragraph every 1–3 sentences
- Make minimal changes to awkward phrasing; if the tone changes after editing, better not to change it
- Punctuation: full-width for Chinese, spaces between English / numbers
- Keep the original opening and ending
Step 1.5: Privacy scan (hard gate before finalizing)
Trigger: after polishing, right before the text is finalized, run it on every article.Scan sentence by sentence for personal privacy information—specific names, restaurant / shop / venue names, precise locations / addresses, phone / email / WeChat ID, etc.—thesemust not go into the final published version, generalize, replace, or delete. For judgment boundaries, replacement examples, and details such as keeping city-level place names, see STYLE.md"10. Privacy red lines"。
Add the hits to the delivery change list (marked "Privacy"); if there are none at all, also say "Privacy scan: none." List anything uncertain and ask the user; do not delete intentional details on your own.
Step 2: Title candidates
Give 3 candidates: A) straightforward type; B) story type; C) the most flavorful sentence from the original. No clickbait, exaggeration, "shocking," or "must-read."
Step 3: Summary (50–80 characters)
Hook the reader, spark clicks—use a "curiosity gap": throw out the most counterintuitive / most tension-filled point in the article (conclusion, cost, turning point, number), buthold something back, keep the answer in the body, make people feel they have to click in. Still in Wang Jianshuo's tone (specific, sincere, restrained in the way it only tells half), the hook comes from something real, not adjectives;absolutely noclickbait, exaggeration, or marketing speak. Not a copy of the first paragraph. For rules and examples, see STYLE.md "6. Titles and summaries."
Step 4: Illustrations (two per article, generated automatically without asking the user)
- Header image cover.png — strictly 2.35:1(900×383), strong typography, strong composition, text-led
- Explanatory image illustration.png — aspect ratio determined by the content(model's choice), flat cartoon, with labels and flow
~/.claude/skills/wjs-publishing-wechat/scripts/gen-cover-ai.sh <article-folder> ["目标字词"]
~/.claude/skills/wjs-publishing-wechat/scripts/gen-illustration.sh <article-folder>
- When no second parameter is passed, take from
meta.jsonthetitle; it is recommended to pick core concept words (1–4 characters) - Internally goes through
gpt-image-2-skillthe--provider codex, requires~/.codex/auth.json - The header image is automatically cropped to 900×383; the explanatory image is not cropped
The explanatory image must be referenced in markdown——article.md must have  one line, otherwise it will not be visible in the draft.
⚠️ In the body, except for cover.png / illustration.png , images are not automatically uploaded to the CDN. For each local screenshot provided by the user (such as img-xxx.png), first md2wechat upload_image img-xxx.png --json get data.wechat_url, then replace article.md the local path in. Verification:grep -c mmbiz content.html = number of body images,grep -c 'img-' content.html = 0。
Default insertion position:after the final landing point of the body(if there is ## 后注 place before the postscript; if there is ## 安装方法 place before the installation method).
Never write a lead-in for the explanatory image—do not write things like "here is the whole thing drawn out" or "as shown in the figure"; let the image speak for itself. See [[no-illustration-caption]] for details.
Safety net:
illustration.pngexists butarticle.mdis not referenced,upload-draft.shautomatically inserts and rewrites, idempotent.
Step 5: Output file package
In the working directory (default ~/wechat-publish/articles/) Create:
articles/2026-05-09-{slug}/
├── article.md # 润色后的 markdown
├── cover.png # 题图 900×383
├── illustration.png # 解释图
├── meta.json # { title, summary, author, date, slug }
└── original.md # 用户原始输入
{slug}: Pinyin initials + keywords, within 30 characters.
Step 5.5: Choose title before publishing (hard gate, must wait for user reply)
Trigger: the file package is ready, right before running upload-draft.sh .This step cannot be skipped, and you cannot decide on your own—you must stop, send the title options to the user, and wait for the user to reply with a letter before continuing.
Give 4 candidates, formatted as a clear list:
- A) Original title—i.e., the existing
meta.jsonintitle, listed as-is (so the user knows what "no change" looks like) - B / C / D) Three with more impact—more attention-grabbing and with more tension than the original title, but still observing Wang Jianshuo's red lines:no clickbait, no exaggeration, no "shocking / must-read / subversive", no marketing tone. Impact comes from real substance (counterintuitive conclusions, specific numbers, sharp contrasts, hooks that leave things half-said), not piles of adjectives.
After sending the candidates, say: "Reply A to use the original title, or B/C/D to choose a new one, or give one yourself." Thenstop。
After the user chooses:
- If B/C/D or self-authored → use
python3to change themeta.jsonintitleto the selected one (cover.pnghas already been generated; there is no need to redraw the cover image because of a title change, unless the user requests it) - Choose A or no reply → title unchanged
- Then continue to Step 6 publish
Step 6: Publish (upload-draft.sh)
~/.claude/skills/wjs-publishing-wechat/scripts/upload-draft.sh <workspace>/articles/YYYY-MM-DD-{slug}
What the script does:
- Run
pangu.pyto add Pangu spacing md2wechat upload_image cover.png→ whenthumb_media_idillustration.pngexists but is not referenced, automatically insert it and upload to get the CDN URL (idempotent safety net)- Inject the "Recent Articles" link list(
build-recent-articles.py, on by default, see the next section) - From
article.mdgeneratecontent.html(conversion rules below) - Install
draft.json, callcreate_draftor (publish.jsonwhen there isdraft_media_id)draft/updateupdate in place - Automatically open the draft: on macOS use
scripts/open-draft-edit.sh—read the session token of the logged-in browser, look upappmsgid、by title and go directly to the edit page for this article(if not logged in, fall back to opening the homepage); on Linux openmp.weixin.qq.comhomepage.WECHAT_PUBLISH_NO_OPEN=1turn off - Automatically
git add / commit / pushthe article directory to origin
End-of-article "Recent Articles" list (automatic)
On every publish,upload-draft.sh automatically appends at the end of the article arecently published articleslink list (5 by default), using the WeChat editor's native article hyperlink format (mp_article_text_link + data-linktype="2", only /s/ links from the same account are clickable).
Mechanism = local ledger + offline rendering, publishing step zero-network, deterministic, runnable offline:
- Ledger: each article's
publish.jsoninpermalink(https://mp.weixin.qq.com/s/…)+published_at。 - Rendering:
build-recent-articles.py <article-folder>scans articles in the same-level*/publish.jsonthat havepermalink, takes the most recent N articles in reverse order ofpublished_at(excluding this one), inarticle.mdof<!--RECENT_ARTICLES_START/END-->within the marker areaIdempotent rewriteA raw-HTML block (content.htmlThe builder passes it through as-is; WeChat ignores comments). If the ledger is empty, it simply won't attach anything—it never errors and never blocks publishing. - Run/preview separately:
build-recent-articles.py <folder> --print(print only, don't write);--count Nchange the count. - Turn off:
WECHAT_PUBLISH_NO_RECENT=1; change the count:WECHAT_RECENT_COUNT=8。
Where the ledger comes from — backfill-permalinks.py(run occasionally):permalink It can only be obtained after an article is officially published, from the WeChat Official Account backend (draft → publish is clicked manually and does not flow back). This script calls the web backend appmsgpublish(published list) to pull the entire account,matches exactly by titleand writes permalink/published_at back to each publish.json. Exact match only—published titles are often different from meta.title , and fuzzy matching would write the wrong links; anything that doesn't match will be listed for you to handle manually.
~/.claude/skills/wjs-publishing-wechat/scripts/backfill-permalinks.py [articles根目录] [--dry-run]
Authentication uses theweb session(not the publishing API's access_token); the cookie expires in a few hours, so this is "occasional maintenance" rather than something to run on every publish. Provide any one of three values: ① environment variable WECHAT_MP_COOKIE / WECHAT_MP_TOKEN / WECHAT_MP_FAKEID; ② session file (default ~/.config/wjs-wechat/mp-session.env, containing export with the same three variable names). How to capture: log in to mp.weixin.qq.com → Content Management → Published → in DevTools Network find the appmsgpublish request → Copy as cURL,token/fakeid is in the URL,Cookie is in the request headers.
upload-draft.shWhen publishing, if the above credentials/session file are detected, it willfirst try its best to refresh the ledgerand then inject (non-fatal; if expired, skip); if not configured, it will just use the existing local ledger. Once the session file is configured, publishing becomes "automatic backfill + automatic attachment of recent articles" in one step.
article.md writing constraints(affects how Claude writes; other HTML details are handled by the script):
- Supports
<p>/<h2>/<h3>/<img>/<strong>/<em>/<code>/<ul>/<ol>/<li>/ pipe table - Raw HTML block passthrough: blocks starting with
<are output as-is; the entire section must be one block,no blank lines inside - Multiple lines within a paragraph →
<br>line breaks(for parallelism / parallel short sentences):Hard rule: the end of a line must never be a comma ","; line-break boundaries may only fall after sentence-ending punctuation (。?!)
Environment variables:
WECHAT_PUBLISH_FORCE_NEW=1— force creating a new draft (do not reusedraft_media_id)WECHAT_PUBLISH_NO_OPEN=1— do not automatically open the browserWECHAT_PUBLISH_NO_PUSH=1— do not automatically pushWECHAT_PUBLISH_NO_RECENT=1— do not inject the "Recent Articles" list at the endWECHAT_RECENT_COUNT=N— how many articles to list in "Recent Articles" (default 5)WECHAT_MP_COOKIE/WECHAT_MP_TOKEN/WECHAT_MP_FAKEIDorWECHAT_MP_SESSION(session file path, default~/.config/wjs-wechat/mp-session.env) — if configured, automatically refresh the permalink ledger when publishing
Prerequisites:
md2wechatCLI installed andWECHAT_APPID+WECHAT_SECRETconfigured (md2wechat config show)- the current public IP is in the WeChat Official Account backend whitelist: mp.weixin.qq.com → Settings and Development → Basic Configuration → IP Whitelist. If missed, it will
errcode=40164
Common errcodes:40164 IP not in whitelist | 45004 summary is empty / too short | 40007 old draft_media_id was deleted (the script automatically falls back to creating a new one).
After success, go to the Draft Box → mobile preview → publish.
Polishing guidance / paragraphing / red lines / change list
All moved to STYLE.md—typo patterns, paragraphing rules, red line checklist, and providing a change list before delivery are all there. Read STYLE.md before polishing and follow it.
Depends on external skills:gpt-image-2-skill(cover/illustration generation) + /md2wechat(upload + draft)。
Completion criteria
-
articles/YYYY-MM-DD-{slug}/folder exists - contains article.md, cover.png, illustration.png, meta.json, original.md
- meta.json has all fields
- privacy scan done before finalizing(Step 1.5)—personal privacy information such as names / restaurants / locations has been generalized or removed, and hits are included in the change list
- Before publishing, A (original title) + 3 more impactful candidates were provided, and the title was finalized according to the user's choice(Step 5.5)
- draft is visible in the mp.weixin.qq.com backend
- the user did not say "revise again"