# Wechat Autopublish

> Use when the user wants to publish a WeChat personal-subscription article from a topic, Feishu doc, notes, or existing draft. Turn research or source material into a ready-to-publish WeChat article and, when credentials allow, push it to the WeChat Official Account draft box with title/digest/cover/author prepared.

- Skill: `tyrantlucifer/wechat-autopublish` (Agent Skill, multi-file: 73 files)
- Install (CLI): `npx skillmds@latest add tyrantlucifer/wechat-autopublish`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tyrantlucifer/wechat-autopublish/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- License: MIT
- Author: TyrantLucifer (https://skillmd.com/u/tyrantlucifer)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tyrantlucifer/wechat-autopublish

---


# WeChat Autopublish

## Overview

This skill turns source material into a polished WeChat Official Account article for a **personal subscription account**.

The core value is not generic writing advice. The skill is designed as a professional publishing pipeline for the owner's personal brand, with a strong bias toward:

- Feishu doc → WeChat article conversion
- topic → ready-to-publish WeChat article
- practical / technical content rather than empty thought-leadership copy

It should:

1. Accept one of these inputs:
   - a topic
   - a Feishu/Lark document
   - local Markdown/notes
   - reference links
   - an existing draft

2. Produce one of these final outputs:
   - **WeChat draft box article** (preferred)
   - **ready-to-paste publishing package** (fallback)
   - **draft review package** (when explicitly requested)

3. Default to a professional technical blog workflow instead of generic content generation.
4. Support explicit **copy-transfer mode** when the source article is already strong enough to be published as a mostly intact WeChat copy, especially when the user asks to preserve both the detailed content and the original images.

The skill should keep the workflow stable, reusable, and focused on publishing, not brainstorming.

## When to Use

Use this skill when:

- the user wants to publish or convert a Feishu doc into a WeChat article
- the user gives a topic and wants a publish-ready WeChat article
- the user asks to push content into the WeChat draft box
- the user says things like "公众号", "推文", "公号稿", "草稿箱", "发布文章"
- the user wants a repeatable publishing workflow rather than one-off writing help
- the user provides an internal document, design note, or technical summary and wants it transformed into external-facing WeChat content
- the user wants the agent to handle the full publishing job rather than just write a draft

The three most important trigger patterns are:
- **Feishu doc publishing mode**: publish this Feishu doc as a WeChat article
- **Topic content-building mode**: take this topic and build a publish-ready WeChat article for me
- **Copy-transfer mode**: the source article is already detailed and should be published as a near-complete copy with images retained, not condensed into a fresh rewrite

Do **not** use this skill for:

- general article writing without WeChat publishing intent
- enterprise service-account publishing workflows
- short social posts unrelated to the WeChat Official Account
- lightweight brainstorming that clearly does not need a publishing pipeline

## Default Profile

This is a personal-brand publishing skill, not a generic content generator.

### Personal voice baseline

The default writing style should follow the owner's blog voice at https://tyrantlucifer.com/ .

Based on representative posts such as `笔记工作流的最佳实践` and `Fluss - 面向分析的实时流存储初探`, the blog's recurring style has these traits:

- writes in first person, but keeps the focus on the topic rather than self-dramatization
- opens by stating the real background, problem, or motivation in a few sentences
- prefers a clean problem-oriented or evaluation-oriented structure instead of fluffy narrative
- often uses a flow like: motivation/context → concept/objective → architecture or selection criteria → concrete examples or setup notes → verification/takeaways
- uses comparison, step reasoning, and decision explanation when introducing a new tool or workflow
- keeps paragraphs short and readable, usually 2-4 sentences
- frequently uses section titles that directly describe the current stage or question
- uses concrete nouns, real components, and real scenarios instead of abstract inspirational language
- preserves important English names exactly when discussing open-source projects, systems, or technical terms
- uses first-person observation to add practical judgment, not to perform emotion
- prefers clear lists, structured evaluation points, architecture breakdowns, and workflow summaries
- values clarity, practicality, reproducibility, and reader utility over rhetorical flourish
- tends to explain choices and trade-offs rather than just stack buzzwords

Style fingerprint:
**Write like the blog owner: a senior big-data / infrastructure engineer who explains things in a calm, practical, structured, example-aware, and opinionated way. The article should feel like a real practitioner's technical blog post, not AI promotional copy.**

Source posts sampled for this profile:
- `笔记工作流的最佳实践`
- `Fluss - 面向分析的实时流存储初探`
- `SeaTunnel连接器V1到V2的架构演进与探究`

User-operating preferences for this skill (hard-won corrections from live use):

- **One canonical copy of publishing scripts lives inside this skill**, under `scripts/wechat_publisher/`. Do not propose wrapper shells that merely call an external checkout.
- When the user asks for “搬运” / copy / migration mode, **preserve the source document's structure and detail first**. Do not aggressively rewrite into a short editorial unless the user explicitly asks for that mode.
- When migrating Feishu docs to WeChat, treat **callout blocks and whiteboard figures as first-class content**, not decorative noise. They must be converted or migrated explicitly.
- When figures exist, **migrate unique images intentionally**. Avoid naive duplication that inflates figure count and produces messy layout.
- Publishing output should be **actionable and concrete**: title, digest, author, cover, migrated images, HTML quality, fallback path, and next action. Avoid long theoretical explanations when the user wants execution.
- **Quality gate comes after migration completeness**, not before. First make the copy faithful; then polish for publishing readability.
- The user expects **active review of the HTML conversion path** whenever rich Feishu content is involved, especially callouts, figures, tables, and appendix image placement.

### Publishing defaults

Unless the user overrides:

- account: `main`
- preferred publishing mode: **draft box**
- article scale: roughly 2500-5000 words for long-form
- output language: Chinese
- structure: problem-oriented or workflow-oriented
- style: professional technical blog, not marketing copy
- translation rule: if source material is in English, publish in Chinese with original terms preserved where useful

## Execution backend

This skill manages its own publishing scripts internally:

- `scripts/wechat_publisher/`

This is the correct integration shape for this user. Do **not** vendor the entire upstream project tree, do **not** create a wrapper shell that merely calls an external checkout, and do **not** leave publishing scripts split across two unrelated locations.

The required integration pattern is:

1. extract only the necessary publishing scripts into `scripts/wechat_publisher/`
2. keep required publishing assets together with those scripts when the scripts depend on them
3. keep `wechat-publisher.yaml` with the extracted scripts, not at unrelated outer paths
4. expose thin shell entrypoints under `scripts/wp-*.sh` that call the internal scripts directly

If the underlying publishing code expects its config at `parent.parent / wechat-publisher.yaml`, adjust the internal copy so the config lookup matches the skill's actual layout instead of assuming the old upstream directory shape.

## Known converter pitfalls

### lark-cli table output missing pipe delimiters

When fetching Feishu docs via `lark-cli docs +fetch`, exported Markdown tables may be missing leading/trailing `|`. Normalize them before gzh-design rendering so table structure is unambiguous and source fidelity can be checked.

**Fix**: After fetching, normalize table rows by adding `|` at start and end:

```python
lines = content.split('\n')
new_lines = []
for line in lines:
    if '|' in line and not line.strip().startswith('```'):
        if line.count('|') >= 2:
            if not line.strip().startswith('|'):
                line = '|' + line
            if not line.strip().endswith('|'):
                line = line + '|'
    new_lines.append(line)
### Known converter pitfalls

#### Use `gzh-design` as the canonical Markdown → HTML renderer

`gzh-design` is the **default and mandatory renderer** for new publishing runs. The internal `html_converter.py` is retained only as a legacy emergency fallback; do not choose it merely because it is deterministic or already wired into `publish.py`.

Canonical path:
1. normalize Feishu/Word/PDF/notes into clean WeChat-ready Markdown;
2. load `gzh-design` and read its `theme-index.md`, the selected theme library, and `common-components.md`;
3. render a pure `<section>…</section>` body fragment with inline styles and `<span leaf="">` text wrappers;
4. run `gzh-design/scripts/validate_gzh_html.py` until **ERROR=0 and WARNING=0**;
5. run `scripts/wp-html.sh <rendered.html>` for the copy-ready preview;
6. publish the rendered HTML with `scripts/wp-publish.sh <rendered.html> --title ...`, which uploads local/external HTML images to WeChat CDN before draft creation.

Theme behavior:
- if the user specifies a gzh-design theme, use it directly;
- otherwise recommend from the article type; in full-auto publishing, choose automatically and report the reason;
- do not mix components from different gzh-design themes;
- preserve the normalized Markdown as the content source of truth and the gzh-design HTML as the publishing artifact.

Legacy fallback is allowed only when `gzh-design` is unavailable or its component assets are damaged. The fallback must be explicit in the final report and uses `wp-html-legacy-md.sh` / `wp-publish-legacy-md.sh`.

**Mandatory HTML quality gate before publishing:**
1. gzh-design validator reports 0 ERROR and 0 WARNING;
2. no literal `**` remains in HTML;
3. no `src=""` appears;
4. no raw `<grid>`, `<callout>`, `<whiteboard>`, Mermaid code, or `flowchart`/`mindmap` remains;
5. expected links retain `href="https://..."`;
6. every expected figure has a non-empty local/CDN image source;
7. after draft creation, `draft/get` confirms content and figures survived WeChat sanitization.

### Table rows MUST start and end with `|`

`html_converter.py` detects table rows by checking `stripped.startswith("|")`. Markdown tables like:

```
**维度** | **传统数据湖** | **多模态数据湖**
---|---|---
数据类型 | 结构化/半结构化 | 任意模态
```

will NOT be recognized as tables. They must be:

```
|**维度** | **传统数据湖** | **多模态数据湖**|
|---|---|---|
|数据类型 | 结构化/半结构化 | 任意模态|
```

Always normalize table rows to start/end with `|` before passing to the HTML converter.

### Table cells: `flush_table()` must call `process_inline()` on cell contents

The original `flush_table()` in `html_converter.py` inserted raw cell text into `<th>`/`<td>` tags without processing inline Markdown (`**bold**`, links, etc.). Fix: call `process_inline(c)` on each cell value.

### Table headers: strip `**bold**` markers to avoid yellow highlight

`process_inline()` converts `**text**` to `<strong>` with yellow highlight background (`#fff18a`). In table headers (`<th>` with dark blue background), this creates unreadable text. Fix: strip `**` markers from header cells before calling `process_inline()`:

```python
def clean_th_text(t):
    t = re.sub(r'\*\*([^*]+)\*\*', r'\1', t)
    return process_inline(t)
```

#### Table rows must start AND end with `|` (critical)

`html_converter.py` detects table rows via `stripped.startswith("|")`. Rows without leading `|` are treated as plain paragraphs and break the entire table.

See "Markdown tables MUST start and end with `|`" section above for the fix script, or `references/table-format-fix.md` for a standalone fixer script.

#### Table cell inline markdown needs explicit `process_inline()` call

**Fixed in 2026-07-01:** `flush_table()` now calls `process_inline()` on both `<th>` and `<td>` cell contents. Previously, `**bold**` in table cells rendered as literal asterisks.

#### Table header `**bold**` causes yellow highlight on dark background

**Fixed in 2026-07-01:** Table headers now strip `**bold**` markers before processing, since the `strong` style's yellow `#fff18a` background clashes with the dark blue `#0b1530` header background.

#### Feishu `<grid>` / `<column>` blocks need manual preprocessing

The `strong` style in `html_converter.py` includes `background: #fff18a` (yellow highlight). When table header cells contain `**bold**` markers, the yellow highlight appears on the dark blue `#0b1530` th background, making text unreadable.

**Fix**: In `flush_table()`, strip `**bold**` markers from table header (th) cells before passing to `process_inline()`, since `<th>` already has `font-weight: 600`:

```python
def clean_th_text(t):
    t = re.sub(r'\*\*([^*]+)\*\*', r'\1', t)
    return process_inline(t)
```

Apply to the `i == 0` (header row) branch only. Body `<td>` cells should still use `process_inline()` to preserve inline markdown formatting.

### Table cell markdown not rendered

The original `flush_table()` passed cell content directly into `<td>` without calling `process_inline()`, so `**bold**` markers appeared as raw text. The fix is to call `process_inline(c)` on all non-header cell content.

### Cover image: picsum.photos seeds produce irrelevant images

Using `picsum.photos/seed/<word>/900/383` does NOT guarantee the seed word relates to the image content. Seeds like `datalake`, `technology`, `database` all produce random photos (vegetables, landscapes, etc.).

**Better approach**:
1. Use a known-good tech image URL (e.g., from Pixabay/Unsplash with direct link)
2. If needed, resize with `ffmpeg -i input.jpg -vf "scale=900:383" output.jpg`
3. Or generate with `image_generate` tool when available

### `Feishu `<grid>` / `<column>` blocks need manual preprocessing

`normalize_for_wechat.py` handles `<callout>`, `<whiteboard>`, `<title>`, `<p>` blocks — but **not** `<grid>` / `<column>` layout blocks that Feishu exports for multi-column content (e.g. pros/cons comparison).

Preprocessing pattern:
```python
import re
grid_re = re.compile(r'<grid>(.*?)</grid>', re.S | re.I)
col_re = re.compile(r'<column[^>]*>(.*?)</column>', re.S | re.I)

def replace_grid(m):
    cols = col_re.findall(m.group(1))
    return "\n\n---\n\n".join(c.strip() for c in cols) + "\n" if cols else m.group(1)

text = grid_re.sub(replace_grid, text)
```

Run this **before** `normalize_for_wechat.py`.

### Markdown tables MUST start and end with `|`

`html_converter.py` detects table rows via `stripped.startswith("|")`. When converting Feishu docs or writing markdown manually, every table row (including the header and separator) **must** start and end with `|`:

```markdown
|列A|列B|列C|
|---|---|---|
|值1|值2|值3|
```

Without leading `|`, rows render as plain paragraphs. Use a fixer script:
```python
# fix_table_pipes.py — add missing leading/trailing | to table rows
import re
lines = content.split('\n')
for i, line in enumerate(lines):
    if '|' in line and not line.strip().startswith('```'):
        if line.count('|') >= 2:
            if not line.strip().startswith('|'):
                lines[i] = '|' + line
            if not line.strip().endswith('|'):
                lines[i] = lines[i] + '|'
```

### `flush_table()` must call `process_inline()` on cell contents

The original `html_converter.py` passed raw cell text into `<td>`/`<th>` tags without inline markdown processing. `**bold**` in table cells rendered as literal asterisks.

**Fix applied (2026-07-01):** `flush_table()` now calls `process_inline(c)` for both `<th>` and `<td>` cells.

### Table headers: strip `**bold**` to avoid yellow highlight conflict

When `process_inline()` processes `**text**`, it applies the `strong` style which includes `background: #fff18a` (yellow highlight). In table headers (`<th>` with `background: #0b1530; color: #ffffff`), this yellow highlight makes text unreadable.

**Fix applied (2026-07-01):** Table header cells use `clean_th_text()` that strips `**bold**` markers before `process_inline()`, since `<th>` is already bold by default.

```python
def flush_table():
    ...
    if i == 0:  # header row
        def clean_th_text(t):
            t = re.sub(r'\*\*([^*]+)\*\*', r'\1', t)
            return process_inline(t)
        cells_html = "".join(f'<th ...>{clean_th_text(c)}</th>' for c in cells)
    else:  # body rows
        cells_html = "".join(f'<td ...>{process_inline(c)}</td>' for c in cells)
```

### Cover image generation: use the skill's built-in pipeline (FIRST CHOICE)

**⚠️ CRITICAL ORDER: Always try `generate_image.py` FIRST.**

Do NOT try `image_generate` tool (FAL), picsum, pixabay, or any external image source before checking the skill's own `generate_image.py` script. The script reads API credentials directly from `wechat-publisher.yaml` and works even when `image_generate` tool reports missing keys.

Correct cover image priority:
1. User-supplied cover image
2. `generate_image.py` script (reads config from `wechat-publisher.yaml`)
3. If that configured provider/model is unavailable, use the active `image_generate` tool when available
4. Article inline images (reuse the most representative one)
5. Branded placeholder template
6. Last resort: stock photo (warn user it's temporary)

After generation, always inspect the image visually, then inspect the **actual file dimensions** before resizing or cropping. Do not trust a tool's reported nominal size: backends may return a different aspect ratio. For WeChat covers, verify the final 900×383 artifact again after conversion.

**Do NOT** jump to picsum/pixabay/random images just because `image_generate` tool reports `FAL_KEY` missing — the skill has its own image generation backend configured in `wechat-publisher.yaml`.

### Sensitive info masking — do NOT assume values are truncated

Hermes tools (`read_file`, `terminal` output, `session_search`) automatically mask sensitive values like API keys. A displayed value like `sk-701...2b99` is **NOT truncated** — it is the full key being masked at the display layer for security.

**Do NOT:**
- Assume the key in the config file is broken or incomplete
- Ask the user to "provide the full key" when it's already there
- Waste rounds trying to "fix" a value that is already correct

**Do:**
- Just call the script that reads the config directly (`generate_image.py`, `wp-token.sh`, etc.) — they read the actual file, not the masked display
- If the script fails with 401, then investigate the key — but trust the file, not the display

Do not infer that a credential file is incomplete from masked tool output. Let the local script read the file directly, then diagnose only from the real API response.

The skill has a complete image generation pipeline:

1. `generate_image.py` — unified entry point, reads `wechat-publisher.yaml`
2. `openai_image_gen.py` — OpenAI-compatible API backend
3. Config keys in `wechat-publisher.yaml`:
   - `image_generation.generator` (set to `"openai"`)
   - `image_generation.openai.api_key`
   - `image_generation.openai.base_url`
   - `image_generation.openai.image_model`

**Usage:**
```bash
cd scripts/wechat_publisher
python3 generate_image.py --generator openai \
  -p "描述封面图内容的详细prompt" \
  --image /tmp/cover.jpg \
  --size "1792x1024" \
  --quality "high"
```

**Pitfall:** If the API key in `wechat-publisher.yaml` is truncated or invalid, the script will return 401. Always verify the key is complete before attempting generation. If generation fails, report the error clearly and ask the user to provide the correct key — do NOT silently fall back to unrelated stock images.

### `--html` mode cover behavior

`publish.py --html <gzh-fragment>` always requires `--title`. `--cover` is optional only when the HTML contains a resolvable first image, which the publisher can reuse as cover; otherwise provide a real cover explicitly. Never download a random Picsum image silently.

**⚠️ Lorem Picsum random images are often irrelevant** — seeds like `database`, `technology` return random photos (vegetables, landscapes, architecture) that have nothing to do with the topic. Random Picsum is acceptable only as a "won't crash" fallback. When the user sees an irrelevant cover, they will complain.

Better fallback strategies (in priority order):
1. Ask the user to supply a cover image
2. If the article has inline images, reuse the most representative one
3. Generate a cover via the configured image backend (OpenAI / Gemini)
4. Use a solid-color SVG or a simple branded template image
5. Last resort: Picsum random — but warn the user it's a placeholder

### `html_converter.py` table cells must call `process_inline()` for markdown rendering

The `flush_table()` function in `html_converter.py` originally inserted table cell content raw — without calling `process_inline()`. This meant `**bold**`, `[links](url)`, and other inline markdown inside table cells rendered as literal text.

**Fix applied (2026-07)**: Changed `flush_table()` to call `process_inline()` on every cell:

```python
# Before (broken):
cells_html = "".join(f'<th style="...">{c}</th>' for c in cells)
# After (fixed):
cells_html = "".join(f'<th style="...">{process_inline(c)}</th>' for c in cells)
```

Same for `<td>` cells. The patch is applied to the skill's copy of `html_converter.py`. If you replace or update the file, verify this fix is present.

**Validation**: after conversion, check the HTML output — if `**text**` appears as literal asterisks inside `<td>` or `<th>` tags, the fix was lost.

### Mermaid / flowchart code blocks must be rendered to images before gzh-design

Before publishing any article containing Mermaid blocks:
1. search the normalized Markdown for fenced `mermaid` blocks or `flowchart`/`mindmap` syntax;
2. render each diagram to a PNG;
3. replace the fenced block with `![流程图说明](/absolute/path/to/diagram.png)`;
4. render the resulting Markdown with gzh-design and verify no Mermaid source remains in the HTML;
5. publish the clean HTML through `wp-publish.sh`; it uploads the local image source to WeChat CDN.

If the server lacks Chinese fonts, download/use a local CJK font and reference it via `@font-face` when rendering diagrams with Playwright; otherwise Chinese labels become square glyphs in the generated image.

### `html_converter.py` requires tables to start and end with `|`

The table detection in `html_converter.py` uses this check:
```python
if "|" in stripped and stripped.startswith("|"):
```

Markdown tables that lack leading/trailing `|` (e.g. `**维度** | **传统** | **多模态**` instead of `|**维度** | **传统** | **多模态**|`) will **not** be detected as tables — they pass through as plain `<p>` text.

This commonly happens when:
- Feishu doc exports produce bare pipe-separated rows without `|` boundaries
- Content is hand-written with `|` as separator but no leading `|`

**Fix**: normalize table rows before `normalize_for_wechat.py`:
```python
import re
def fix_table_pipes(text: str) -> str:
    """Ensure markdown table rows start and end with |."""
    lines = text.split('\n')
    result = []
    for line in lines:
        if '|' in line and not line.strip().startswith('```'):
            if line.count('|') >= 2:
                if not line.strip().startswith('|'):
                    line = '|' + line
                if not line.strip().endswith('|'):
                    line = line + '|'
        result.append(line)
    return '\n'.join(result)
```

This fix is available as `scripts/fix_table_pipes.py`.

**Validation**: after conversion, grep the HTML for `<table` — if absent and the source had tables, the pipe fix was missed.

Use the skill's wrapper scripts for operations:
- `scripts/wp-list-accounts.sh`
- `scripts/wp-token.sh`
- `scripts/wp-html.sh`
- `scripts/wp-ai-score.sh`
- `scripts/wp-publish.sh`
- `scripts/wp-bootstrap.sh`
- `scripts/wp-run.sh`

### Cover image generation

When `image_generate` tool is unavailable (no FAL_KEY), use the skill's built-in `generate_image.py` script:

```bash
cd ~/.hermes/skills/social-media/wechat-autopublish/scripts/wechat_publisher
python3 generate_image.py --generator openai \
  -p "A modern data lake architecture diagram..." \
  --image /tmp/cover.jpg \
  --size "1792x1024" --quality "high"
```

The script reads API config from `wechat-publisher.yaml` (`image_generation.openai.*`).
If the API key is truncated or invalid, the script will fail with 401 — check the key before retrying.

When image generation is unavailable (no API key, API errors), fall back to a stock image from pixabay/picsum. Always inform the user about the fallback.

Image-generation integration notes are in `references/wechat-image-generation.md`.

Rendering, integration shape, and next-upgrade research are in `references/wechat-rendering-and-integration-notes.md`.

### WeChat justified text stretches Chinese spacing

Do **not** use `text-align: justify` for normal WeChat article paragraphs. In WeChat preview/WebView, Chinese-English mixed lines can be expanded aggressively, creating large gaps between Chinese characters and English terms. Use:

```css
text-align: left;
letter-spacing: 0.15px;
word-spacing: 0;
```

This applies to the default converter styles and the `refined-blue` theme. Verify the actual draft HTML returned by `draft/get`, not only the pre-upload HTML.

### WeChat sanitizes multi-line blockquotes

Do not render Feishu callouts as one `<blockquote>` joined by `<br>`. WeChat's draft sanitizer can keep only the first line, silently deleting the callout body. Render each callout as a styled `<section>` containing one `<p>` per source line. After draft creation/update, call `draft/get` and verify that every callout still contains its body text.

## List rendering strategy

WeChat Official Account HTML does not reliably support nested list indentation via `ul/ol/li` + CSS.

Therefore:
- use a standard Markdown renderer for correctness
- then convert nested lists into **WeChat-safe list components** using `p` + `span`
- preserve indentation through explicit left padding
- preserve numbering/bullets through explicit styled spans

This avoids duplicated numbering and unstable nested-list rendering.

## Preconditions

Before attempting draft-box publishing, confirm:

1. the WeChat account is a personal subscription account
2. `AppID` is available
3. `AppSecret` is available
4. the server/public IP is added to the WeChat IP whitelist
5. the publishing toolchain is configured inside `scripts/wechat_publisher/wechat-publisher.yaml`
6. image generation configuration is understood: `image_generation.openai.*` is used for OpenAI-compatible image backends, `image_generation.gemini_proxy.*` for Gemini-style backends, and `image_generation.generator` selects the active path
7. if a generated cover is required, the active image backend is configured before publishing

If any precondition fails, fall back to the publishing package workflow rather than stopping completely.

Operational lesson from actual use:
- when the user asks how to configure YAML, answer in execution-project terms first
- clarify that secrets / provider keys / account credentials belong in the execution project config
- clarify whether image generation is currently wired into this skill's publishing flow before giving configuration advice

If any precondition fails, fall back to the publishing package workflow rather than stopping completely.

## Core Principle

This skill should always separate **content production** from **account-specific publishing mechanics**.

A stronger publishing principle should also be enforced:

- **all inputs must first be normalized into WeChat-ready Markdown**
- **Markdown → HTML is the single main rendering path**
- source-specific preprocessing (Feishu docs, rich exports, mixed tags) belongs in the Markdown normalization layer, not inside the HTML renderer
- preserve source value where appropriate; do not blindly compress detailed source documents when the user wants publication-grade content

This keeps the publishing pipeline stable and makes the rendering core reusable across different input types.

Two primary operating modes should be supported explicitly:

1. **Feishu doc publishing mode**
   - Use when the user gives a Feishu doc URL/token and wants it published as a WeChat article.
   - The Feishu doc is treated as the main source of truth.
   - The skill should extract, restructure, de-internalize, and convert the doc into publishable WeChat content.
   - This mode should be used when the user wants a publishing-grade rewrite or restructure.

2. **Copy-transfer mode**
   - Use when the user wants the source article transferred to WeChat as faithfully as possible.
   - Prefer preserving original structure, detail density, figures, and appendices over aggressive rewriting.
   - This mode is especially appropriate when the source article is already mature and the user says things like “搬运一下”“图也得搬一下”“不能太精简”.
   - The goal is publishable fidelity, not fresh content reconstruction.

3. **Topic content-building mode**
   - Use when the user gives only a topic, outline, or rough idea.
   - The skill should build the article end-to-end: define the job, research, outline, draft, humanize, format, and package for publishing.

Support files for execution:
- `references/gzh-design-integration.md` — canonical renderer contract and artifact boundary
- `references/wechat-publisher-execution.md`
- `references/feishu-wechat-conversion.md`
- `references/html-publishing-quality-gate.md` — renderer failure signatures and the pre/post-publish verification gate
- `templates/wechat-publisher.yaml.example`
- `templates/feishu-doc-intake.md`
- `templates/topic-intake.md`
- `templates/final-package.md`

Recommended abstraction:

1. **Source intake**
2. **Research / evidence gathering**
3. **Structuring**
4. **Drafting**
5. **Humanization / anti-AI flattening**
6. **WeChat formatting**
7. **Publishing execution via wechat-publisher**
8. **Output packaging / fallback packaging**

This skill should treat `wechat-publisher` as the execution backend, not duplicate its publishing logic.

### Copy-transfer mode: Feishu doc → WeChat copy

Use this mode when the source article should remain close to the original, especially when the user wants the original detail and diagrams preserved.

Default behavior:
- preserve section hierarchy and detail density
- preserve figures, appendices, tables, code blocks, callouts, and examples
- perform only necessary publishing cleanup instead of fresh rewriting
- rewrite only where internal phrasing, internal context, or platform formatting needs adjustment
- export embedded Feishu whiteboard/diagram/media content as images when possible and include them in the article

Important copy-transfer rules:
1. do not compress a detailed document into a summary just because it is long
2. do not strip diagrams unless they truly cannot be recovered or published
3. do not replace an already-strong structure with a generic article skeleton
4. treat user signals like “搬运一下” and “图也得搬一下” as explicit instructions to preserve content volume and figures
5. if the user criticizes a version as “太精简”, the next version must restore detail and figures first

Completion criteria:
- the published WeChat draft is a faithful transfer of the source article
- figures are migrated wherever technically possible
- the article retains the original depth unless the user explicitly asks for condensation

When the source is a Feishu document:

- load the document first
- identify the original goal, audience, and structure
- convert internal phrasing into public-facing WeChat phrasing where needed
- preserve technical accuracy, original names, diagrams or examples worth keeping, and practical substance
- do not over-summarize; reshape the document into a publishable article instead of reducing it to a shallow overview

#### Feishu conversion rules

When loading a Feishu document, the `feishu_doc_read` tool will fail with "Feishu client not available" outside of Feishu comment contexts. Reliable fallback:

```bash
~/.local/bin/lark-cli docs +fetch --doc <token-or-url> --doc-format markdown --format json -q '.data.document.content'
```

Note: The `feishu_doc_read` tool is only available when Hermes is running inside a Feishu gateway context. In all other contexts (terminal, cron jobs, Desktop), use the lark-cli fallback above.

1. preserve the technical spine of the document, not the internal presentation order
2. keep real components, real flows, real constraints, and real examples whenever they are valuable to an external reader
3. remove internal assumptions that an outside reader cannot understand or does not care about
4. rewrite internal system names, internal project references, or internal shorthand unless they are essential to the story
5. convert note-like or memo-like structure into a deliberate article structure
6. if the document is too linear, too flat, or too internal, rebuild it into a stronger skeleton before drafting
7. avoid flattening the document into a summary; the goal is publishing reconstruction, not compression
8. make the final article feel like the author deliberately wrote it for WeChat readers, not like a sanitized internal export
9. if the source Feishu doc contains `<callout>` blocks, convert them explicitly to WeChat-safe structures before HTML conversion; do not leave raw Feishu-specific tags in the publishing payload
10. if the source Feishu doc contains embedded whiteboard/export figures, migrate them explicitly as images and deduplicate references before publishing

#### Feishu structural rebuilding heuristics

Rebuild the skeleton when the source doc shows any of these patterns:

- the document is essentially raw notes or bullet-heavy internal thinking
- the structure is too chronological without a clear publishing payoff
- the internal ordering buries the most publishable points
- the document mixes audience types or mixes unrelated concerns
- the original structure would make a WeChat reader drop off early

Preferred rebuilt shapes:

- problem → background → core approach → practical examples → pitfalls → conclusion
- objective → design/workflow → implementation notes → lessons → next steps
- question/context → architecture → execution details → verification → recommendation

Completion criteria:
- the Feishu doc has been processed as primary source material
- internal phrasing has been converted where needed without destroying technical value
- the final article reads like an intentional WeChat article rather than a raw internal doc repost

### Topic → content building

When the source is only a topic:

- define the publishing job before generating content
- search for current and reliable material only as much as needed
- choose an article structure deliberately
- build a skeleton before long-form writing
- draft the article with real technical substance
- then humanize and package it

#### Cover image prompt strategy

When generating cover images, the prompt must extract **specific structural information from the article content**, not use generic abstract descriptions.

**Bad prompt (generic):**
> "A minimalist technical architecture diagram cover image. Dark navy blue background with abstract glowing network nodes..."

**Good prompt (content-specific):**
> Extract the article's core structure — layer names, component names, design pillars, key concepts — and describe them explicitly in the prompt. For example, for an architecture article, describe the actual layers (e.g., "Layer 1 Multi-Entry with CLI/Web/API, Layer 2 Core Runtime with Session/Scheduler/Executor"), the title text, subtitle, color scheme per layer, and layout.

The cover image generation pipeline:
1. read the article content
2. extract key structural elements (title, layers, components, design pillars)
3. build a detailed prompt that describes these elements visually
4. call `generate_image.py --generator openai`
5. verify the generated image visually before publishing

#### Image generation strategy

This skill should treat image generation as a configurable publishing capability, not as a hard requirement.

Current integration status:
- the `wechat-publisher` execution project has image generation support and config keys
- this skill has wired execution scripts for publishing / HTML conversion / AI-score checking
- image generation is not yet embedded as a required default step in this skill's publishing loop

Default recommendation:
- publishing should succeed even when image generation is disabled or unconfigured
- if image generation is unavailable, fall back to text-first layout or manual cover selection
- if image generation is enabled, separate the flows clearly:
  - cover image
  - in-article images
  - fallback behavior when image generation partially fails

When advising on YAML configuration, clarify these distinct blocks:
- account credentials
- publishing behavior
- image generation provider keys / model settings
- integration tokens

This prevents confusion between "what is needed to publish" and "what is needed to auto-generate images".

#### Topic selection rules

Prefer these topic angles for the owner's personal brand:

- practical workflow / practice sharing
- architecture or system design breakdown
- toolchain setup, evaluation, or comparison
- engineering pitfalls and debugging notes
- open-source project analysis from a practitioner view
- data infrastructure, data integration, data synchronization, or adjacent engineering topics

Avoid these angles unless explicitly requested:

- shallow news reposting without analysis
- motivational or generic industry commentary
- purely promotional tool introduction without practitioner value

#### Topic-building quality checks

Before drafting, confirm the chosen structure answers these questions:

1. what is the article actually giving the reader?
2. why is this structure the right one for the topic?
3. where are the concrete examples or practitioner judgments?
4. what will the reader be able to do after reading the article?

Completion criteria:
- the topic has been shaped into a clear article job
- the output follows a deliberate structure rather than generic expansion

## Workflow

### Step 1: Clarify the source type

The skill should first identify the input type:

- topic only
- Feishu document
- Markdown / notes
- reference links
- existing draft

Completion criteria:
- source type is identified
- publishing account is confirmed
- intent is clear: publish now, produce draft, or produce review package

If the source is a Feishu doc, load it directly with Feishu doc tools when possible and treat it as the primary source of truth.
If the source is only a topic, confirm the minimum missing context before research.

### Step 2: Define the article job

Before writing, define:

- target reader
- article goal
- article type
- expected structure

Common article types:

- practical tutorial
- architecture / workflow explanation
- toolchain evaluation
- troubleshooting / case study
- industry or technical trend analysis
- document-to-article conversion

Completion criteria:
- the article job is written in one short paragraph
- the chosen structure is explicit

### Step 3: Collect research material

Do research only after the job is defined.

Gather:
- official docs / release notes / blog posts
- real commands, configs, APIs, and behaviors
- high-quality recent references
- concrete examples or deployment notes
- only relevant discussion when it adds practitioner perspective

Do not pad research with generic background.

Completion criteria:
- key facts are collected
- sources are available
- reusable examples are identified

### Step 4: Build the skeleton

Before full drafting, generate a skeleton:

- title candidates
- summary / digest
- section outline
- per-section angle
- expected diagrams / examples
- cover concept

Prefer skeletons with:
- a sharp opening
- 3-6 sections
- uneven section length if needed
- a clear ending that adds judgment or next-step advice

Completion criteria:
- skeleton exists and can be reviewed before long-form writing

### Step 5: Draft the

…(truncated)
