dsh-wechat-article
English | 简体中文
Turn Markdown into a WeChat Official Account (公众号) draft from inside DeepSeek Harness — inline-style HTML, a 2.35:1 cover card, and a draft-box push that reads itself back. Zero dependencies.
This is an article production chain, not an IM bridge.
dsh-wechat(a different, unrelated package) connects an iLink bot to dsh for chat. This plugin never touches messages — it takes a Markdown file and produces a publishable article draft.
What it does
| Tool | Purpose |
|---|---|
wechat_draft |
Markdown → WeChat-editor-safe HTML: every rule inline, no <style>, no class, images centred with captions, tables wrapped for horizontal scroll |
wechat_cover |
900x383 (2.35:1) cover card PNG — kicker, wrapped title, subtitle, four-colour chip row |
wechat_push |
Create a draft in the 草稿箱 with a generated cover, then read it back and verify title + content length |
The chain ends at the draft box. Nothing in this plugin publishes: 发表 is a decision for the human who owns the account.
Install
dsh plugin --profile web add dsh-wechat-article
Or straight from GitHub:
dsh plugin --profile web add github:alitrack/dsh-wechat-article
Credentials
wechat_draft and wechat_cover need no credentials at all. wechat_push needs an AppID/AppSecret pair with the 草稿箱 permission (a verified 订阅号/服务号):
# option A — environment
export DSH_WECHAT_APPID=wx...
export DSH_WECHAT_APPSECRET=...
# option B — local file (recommended for a desktop agent)
mkdir -p ~/.dsh-wechat
printf '{"appId":"wx...","appSecret":"..."}' > ~/.dsh-wechat/credentials.json
chmod 600 ~/.dsh-wechat/credentials.json
Overrides: DSH_WECHAT_CREDENTIALS points at a different file; DSH_WECHAT_CHROME pins the Chrome/Chromium binary used for covers.
Credentials are never accepted as tool arguments. That is deliberate: tool arguments travel through the model's context, the session transcript and the tool-call log. If the model could pass an AppSecret, the secret would end up in all three.
WeChat also requires the calling IP to be whitelisted (公众号后台 → 开发 → 基本配置 → IP白名单). A 40164 error means exactly that, and the plugin says so instead of echoing invalid ip.
Usage
Ask the agent, or call the tools directly:
1. wechat_draft markdown_path=/ws/article.md out_path=/ws/out/article.html
2. wechat_cover title="一条命令推到草稿箱" out_path=/ws/out/cover.png
3. wechat_push title="…" markdown_path=/ws/article.md
wechat_push also accepts html= (the fragment wechat_draft returned), thumb_media_id= (an existing cover), or cover_path=; with none of them it generates and uploads a cover itself. dry_run=true validates titles, digests, limits and the cover without touching the network.
Supported Markdown
Headings, paragraphs, emphasis, inline code, fenced code (with a language label), blockquotes, ordered/unordered lists with two levels of nesting, GFM tables with alignment, images, ---, and the :::tip / :::info / :::warning / :::danger callout syntax. Frontmatter (title, author, digest, summary) feeds the draft's metadata. Anything outside this subset degrades to text rather than disappearing.
Two cover renderers
- headless Chrome/Chromium (auto-detected) — real font stack, so CJK titles render. Snap-packaged Chromium works: the renderer stages its files where a confined snap can actually write, and verifies the PNG's dimensions before accepting it.
- built-in rasteriser (
lib/png.js+lib/font5x7.js) — always available, no Chrome needed. Draws the full card, but only has glyphs for digits and uppercase Latin; a CJK title comes back with aCJK_TEXT_NOT_RENDEREDwarning instead of boxes.
Both produce a real 900x383 PNG, and both stay under WeChat's 64KB thumb-material cap.
Security model
- Workspace confinement. Every path is absolute and must resolve inside the calling session's workspace (
exec.agent.session.header.cwd)...traversal, symlinks pointing outside, absolute paths outside the root, directories and missing files are rejected with distinct error codes. - No publish path. Only
draft/addanddraft/getare implemented;freepublishis not. - Idempotent pushes. The same title+content hash is pushed once per workspace — a retrying agent cannot flood the draft box. State lives in
<workspace>/.dsh-wechat/state.json(0600) and is capped at 200 entries. - Rate limiting. Consecutive pushes are spaced by at least 2s, and the plugin never retries in a loop on
45009. - Read-back verification. A 200 from
draft/addmeans "accepted", not "correct". The plugin immediately fetches the draft and compares the title and the visible text length (5% tolerance, because WeChat rewrites HTML);readback_okisfalsewith a warning whenever they disagree. It never reports success it has not seen. - Secret hygiene. Error text is scrubbed of AppSecrets and access tokens before it reaches the model; the AppID is masked to
wx12***cd.
Editorial pre-flight
wechat_draft reports warnings, never edits:
- no images at all (a wall of text),
- images not hosted on
mmbiz.qpic.cn(the editor strips external images on save), - external
<a href>links (unverified accounts lose them on publish), - tables wider than four columns,
- placeholder tokens (
[[待写]],TODO,待补), - numeric consistency: the same quantity written two ways (
1,234vs1234), a unit whose values span ≥1000x (usually 人 vs 人次, or 总数 vs 子集), large numbers with no unit, and numbers that appear only once and therefore cannot cross-check anything.
The numbers check is a checklist generator, not a fact checker — it surfaces candidates, the author decides.
Local development
npm install --no-save @deepseek-ai/dsh-tools@0.0.1-rc.1
node --test # 77 unit tests
node tests/e2e/keyless.mjs # contract + guards, no model, no network
YAML overlays avoid pnpm entirely during development:
# local-overlay.yml
- insert:
- id: dsh-wechat-article
name: '/absolute/path/to/dsh-wechat-article/index.js'
npx @deepseek-ai/dsh@0.2.1-rc.2 --patch ./local-overlay.yml "把 article.md 排成公众号 HTML"
Full headless e2e (real dsh runtime + a local OpenAI-compatible model, so no API key): bash tests/e2e/run_e2e.sh.
Version gate
dsh refuses to load a plugin whose peerDependencies do not match the runtime. This plugin declares @deepseek-ai/dsh-tools: ^0.0.1-rc.1 and is tested against dsh 0.2.1-rc.2. The exemption is a profile-local compatibility.json mapping an exact name@version to exact dsh versions — dsh plugin allow-version is not a real command, despite the error message suggesting it.
Limitations (honest list)
- Drafts only. No publish, no comment settings management beyond the create-time flags, no material library management.
- Images are not uploaded. Article images must already live on WeChat's CDN; only the cover is uploaded (as thumb material). An image-upload helper is the obvious next step.
- No syntax highlighting. Code blocks get one dark palette, not per-token colours.
- CJK covers need Chrome. Without it you get a clean card with an ASCII title and an explicit warning.
- Chinese-only account model. WeChat's draft API limits (64-char title, 16-char author, 120-char digest, 1MB content) are enforced locally so failures surface before a network call.
License
MIT.
No comments yet. Be the first to write one.