DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

alitrack /

alitrack/dsh-wechat-article

Verified

Markdown -> WeChat Official Account draft from inside DeepSeek Harness (dsh): inline-style HTML, 900x383 cover, draft push with read-back. Zero dependencies.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@dee163f1

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.

license dsh node

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, upload local/data: images to the WeChat CDN (media/uploadimg, deduplicated by content hash) and rewrite their src to mmbiz.qpic.cn, then read the draft 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.

The credential gate

wechat_push is only registered when credentials resolve. Without them the plugin mounts wechat_draft and wechat_cover alone (both are pure local work — no network), and adds a system-prompt section naming the exact fix. The reason is the same reason dsh's own patches disable rows: a tool that can only ever return WECHAT_NOT_CONFIGURED still costs the model a turn to discover that, and it competes for attention with the tools that do work. An absent tool plus one sentence in the prompt beats a tool that always fails.

# What it looks like on a machine with no credentials:
#   wechat_draft ✓   wechat_cover ✓   wechat_push ✗ (not registered)
# and the model is told: set DSH_WECHAT_APPID/DSH_WECHAT_APPSECRET or write
# ~/.dsh-wechat/credentials.json, then restart dsh.

# Escape hatch — register push anyway (useful for a dry_run check in CI):
export DSH_WECHAT_GATE=off

DSH_WECHAT_GATE=off restores the old always-registered behaviour, in which a real push fails with a structured WECHAT_NOT_CONFIGURED error instead of disappearing — dry_run: true still validates content, limits and cover without touching the network. The gate is evaluated once at mount time, so adding credentials requires a dsh restart (dsh has no hot reload for a plugin's tool surface).

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

  1. 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.
  2. 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 a CJK_TEXT_NOT_RENDERED warning 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/add and draft/get are implemented; freepublish is 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.
  • Deduplicated image uploads. Content images are uploaded via media/uploadimg (they do not consume the permanent-material quota) and cached by the sha256 of their bytes, so re-pushing a revised draft does not re-upload unchanged figures. Missing or unresolvable image paths are reported as warnings, never silently dropped.
  • 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/add means "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_ok is false with 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,234 vs 1234), 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.
  • Remote images are not fetched. Local paths and data: URLs are uploaded automatically; http(s) images must already be on mmbiz.qpic.cn — download the file and reference it locally if not. upload_images=false opts out of uploading entirely.
  • 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.

—/ 5

No ratings yet

Verified DSH bundle

Commit dee163f15382

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout