CMM Publishing
cmm-publishing is the publishing and draft-routing layer in the CMM content
factory.
It consumes content_package, optional visual_package, and optional
video_package, then produces a publish_package with platform support,
metadata, asset mapping, approval gates, and execution route. After explicit
confirmation, an approved backend can create a draft or browser-review state.
Boundary
Use this skill for:
- Assembling a
publish_package. - Deciding whether the target should be
draft_box,browser_review, orschedule_plan,manual_upload, orunsupported. - Preparing platform metadata, asset mapping, and checklist output.
- Running backend preflight before platform execution.
- Choosing a safer fallback when the preferred backend is unavailable.
- Selecting WeChat, X, Weibo, Xiaohongshu, or social scheduling workflows only as approved execution backends.
- Recording publishing gaps for platforms not yet supported.
Do not use this skill to:
- Write the content draft.
- Generate images or render videos.
- Click
Publish,Post,发布, or any externally visible submit action by default.
Publish Boundary
This skill assembles a publish_package and prepares drafts / manual-upload
instructions. Built-in publish paths: WeChat draft box API
(scripts/wechat_draft.py), WeChat browser draft fallback
(scripts/wechat_browser_draft.mjs), X (scripts/x_publish.mjs), Weibo
(scripts/weibo_publish.mjs), and Douyin (scripts/douyin_publish.py). Browser
paths default to fill-only: the assistant fills the content and stops before
final public publishing, and the user clicks publish themselves in the
browser (the assistant does not auto-click, even on confirmation — see
Confirmation Rules).
Live auto-publish to platforms not yet built in (e.g. Xiaohongshu
live-fill) remains an optional plugin, never routed to by default. If a
platform-specific gap appears, record it in platform_gaps and update CMM
contracts or docs.
Input
Preferred input:
content_package:
platform: ""
format: ""
title: ""
body_markdown: ""
summary: ""
sources_used: []
publish_notes: ""
status: draft
visual_package:
asset_paths: []
status: draft
video_package:
render_path: ""
cover_path: ""
publish_copy: ""
status: draft
Read references/publish-package-contract.md for exact output fields. Read
references/publishing-method.md for support matrix, metadata, approval rules,
backend preflight, and fallback behavior. Read
references/wechat-quality-gate.md before WeChat draft creation; use
templates/wechat-layout-package-template.md for the WeChat layout handoff.
Workflow
- Validate available packages.
- Resolve target platform.
- Choose target mode:
draft_box: API or platform draft when supported.browser_review: browser opens with content filled, user reviews manually.schedule_plan: planning/calendar output only.unsupported: platform gap recorded.
- Build
publish_package. - Add metadata, checklist, platform gaps, and approval requirements.
- Run preflight for the selected backend before platform execution:
- runtime dependencies
- credentials or logged-in state
- API/IP allowlist constraints where detectable
- required cover/image/video assets
- backend capability gaps
- If preflight fails, either choose a documented fallback or set
publish_status: blockedwith a concreteplatform_gapsitem. When both "repair preferred backend" and "fallback backend" are plausible, do not switch automatically. Report the reason, whether any draft/assets were created, present the decision options, and wait for the user to choose. - Ask for explicit confirmation before invoking any platform skill unless the user already requested draft/browser/manual preparation in the current turn. For WeChat long-form outputs coming from CMM Content Factory, the local preview gate outranks same-turn draft intent: do not invoke the WeChat draft-box backend until the user has seen and confirmed the rendered preview and then confirmed draft-box creation.
- Route to an approved backend only after confirmation or same-turn draft intent where no stricter preview gate applies.
- Stop before any final public post unless the user explicitly confirms the final publish action in the same turn.
Routing
Default output is a publish package + draft / manual-upload instructions. X, Weibo, Douyin, and WeChat draft are built-in (review-before-publish by default). The right-hand column marks platforms whose live publish still needs the optional live-publish plugin; without the relevant runtime, follow the manual-upload path.
| Platform | CMM target (default) | Live publish (built-in / optional plugin) |
|---|---|---|
| WeChat Official Account | draft_box via built-in scripts/wechat_draft.py; browser fallback via scripts/wechat_browser_draft.mjs when API/IP allowlist blocks | — (draft/fill paths are built-in) |
| X / Twitter | built-in browser publish via scripts/x_publish.mjs (raw CDP, spawns native Chrome, persistent login, default review-before-publish); see references/x-publish.md. Falls back to manual upload. | — (built-in) |
built-in browser publish via scripts/weibo_publish.mjs (raw CDP, spawns native Chrome, persistent login, default review-before-publish); see references/weibo-publish.md. Falls back to manual upload. | — (built-in) | |
| Xiaohongshu / Rednote image note | publish package + manual upload | optional browser-publish plugin |
| Xiaohongshu / Rednote video note | publish package + manual upload | optional browser-publish plugin |
| Cross-platform calendar | schedule_plan (plan only) | optional scheduler plugin |
| Douyin | built-in browser publish via sau (scripts/douyin_publish.py, default --review-before-publish); see references/douyin-publish.md. Falls back to manual_upload if sau unavailable. | — (built-in) |
| Bilibili | manual_upload or unsupported | none until verified |
publish_package
publish_package:
platform: ""
target: draft_box
title: ""
body_ref: ""
body_markdown: ""
asset_refs: []
video_ref: ""
metadata: {}
approval_status: pending
publish_status: not_started
route: ""
approval_gate:
draft_creation: pending
final_publish: required_same_turn
backend_preflight: []
fallback_history: []
readiness_gate: {}
checklist: []
platform_gaps: []
external_result: {}
Confirmation Rules
- Building
publish_packagedoes not require confirmation. - If the user explicitly asks for a platform draft, draft box, browser review,
or manual-upload package in the current turn, set
approval_status: target_intent_recordeduntil any stricter upstream preview gate is satisfied. After preview confirmation and explicit draft-box/browser confirmation, setapproval_status: approved_for_draftand allow draft preparation. - Invoking a browser/API/platform skill requires explicit confirmation unless the current user request already clearly asks for draft/browser/manual preparation and no stricter preview gate applies.
- Fill-only by default — the user clicks the final publish button themselves. For every browser-driven platform, the assistant fills the draft/composer/creator backend, stops at the publish button, and hands the open browser back to the user to publish. The assistant does not click the final publish button by default, even when the user confirms — the publish click is the user's action in the browser. (Rationale: an async submit / re-render at publish time has corrupted posts before, e.g. body text dropped on click.) The assistant fills, then screenshots or reports state for the user to review and publish.
--auto-publish/--submitand any auto-click switch are used only when the user, in the current turn, explicitly asks for automatic publishing; otherwise always stop at the publish button.- Never use
--submitor click final publish buttons by default. - If a routed legacy skill has a stricter confirmation rule, follow the stricter rule.
Readiness Gate
| Gate | Requirement |
|---|---|
content_ready | Draft exists and is not blocked. |
asset_ready | Required cover/images/video are present or gap is explicit. |
metadata_ready | Title, summary, tags, cover, and account/platform notes are prepared where relevant. |
approval_ready | Draft creation and final publish approvals are separated. |
platform_supported | Platform route is supported, manual, or explicitly unsupported. |
Platform Notes
- Prefer draft-box behavior.
- Covers are required for many article flows.
- API/IP allowlist or browser login may be required.
- Preflight API credentials and IP allowlist before doing full API draft work when possible.
- If API fails with IP allowlist or token permission errors, fall back to the
built-in browser route (
scripts/wechat_browser_draft.mjs) when Chrome/login requirements pass. - Browser draft creation may not set the cover image through all backends. If
the selected backend cannot confirm cover insertion, record an
asset_gapand include cover path inchecklist.
X
- Built-in publish via
scripts/x_publish.mjs(CMM-native, raw CDP, zero npm deps — uses Node ≥21 built-in WebSocket). Spawns native Chrome with a persistent profile (~/.cmm/chrome-x); the first run prompts a one-time x.com login (plain browser, so Google/X SSO is not blocked), reused thereafter. No stored credentials.--cdp URLcan attach to an already-running debug-port Chrome. - Text is injected via CDP
Input.insertText(real keyboard input: writes X's Draft.js EditorState so it survives re-render;execCommand('insertText')only touched the DOM and got wiped on publish — seereferences/x-publish.md). Needs the tab in front + a real click to focus. Media via CDPDOM.setFileInputFiles. - Fill-only: fill the composer, stop at the Post button; the user clicks Post
themselves in the browser (
--auto-publishonly on explicit same-turn request). - See
references/x-publish.md. Falls back tomanual_uploadif Chrome/debug-port/login is unavailable.
- Built-in publish via
scripts/weibo_publish.mjs(CMM-native, raw CDP, zero npm deps). Spawns native Chrome with a persistent profile (~/.cmm/chrome-weibo); first run prompts a one-time weibo.com login. No stored credentials. - Fills the home composer
<textarea>via CDPInput.insertText; images/videos (≤18 total) via CDPDOM.setFileInputFiles. - Fill-only: fill the composer, stop at the Send button; the user clicks Send
themselves in the browser (
--auto-publishonly on explicit same-turn request). - See
references/weibo-publish.md. Falls back tomanual_uploadif Chrome/debug-port/login is unavailable.
Xiaohongshu / Rednote
Preflight 检查(发布前必做)
| 项目 | 限制 |
|---|---|
| 标题 | ≤ 20 字 |
| 正文(去掉末尾话题标签行后) | ≤ 950 字(平台上限 1000,留安全边距) |
| 图片数量 | 1–9 张 |
| 图片格式 | jpg / png / webp,单张 ≤ 32MB |
发布流程(图文笔记,已验证可用)
Step 1 — 直接导航到上传图文页
https://creator.xiaohongshu.com/publish/publish?from=menu&target=image
不要通过「发布笔记」下拉菜单点击,直接用 URL 跳转。
Step 2 — 核对账号
用 get_page_text 或 javascript_tool 读页面右上角账号名,确认是目标账号后再继续。账号不对让用户先切换,不要继续填写内容。
Step 3 — 上传图片
当前限制:Chrome 扩展 file_upload 工具只允许上传会话已共享的路径,本地 Documents 目录不在范围内。
现行操作:通知用户从 Finder(提前 open 图片目录)手动将图片拖入上传区,按顺序(封面在前)。
待解方案:在 Claude 桌面应用设置里将 ~/.cmm/uploads/ 加入 connected folders,生图流水线自动 cp 到该目录后可全自动上传。
Step 4 — 填写标题
用 find 定位标题 input(placeholder: "填写标题会有更多赞哦"),用 computer.type 或 form_input 填入。
Step 5 — 填写正文
正文区是 contenteditable div,用 javascript_tool 注入:
const div = document.querySelectorAll('[contenteditable="true"]')[0];
div.focus();
document.execCommand('selectAll');
document.execCommand('insertText', false, bodyText);
话题标签直接写在正文末尾(#话题 格式),小红书会自动解析。
注入完成后用 javascript_tool 读取页面字符计数器(X/1000)确认字数正确。
Step 6 — 停在发布按钮前
所有内容填完后停止,不点「发布」。告知用户内容已就绪,由用户自行确认并点击发布。
平台限制记录
- 无内置发布脚本,不支持 API draft box。
- 图片上传路径限制(见 Step 3)为当前已知 gap,记录在
platform_gaps。 - Final public publish 必须用户同步确认,不得代为点击。
Douyin (built-in) and Bilibili
- Do not pretend final public publishing is complete without confirmation.
- Douyin: built-in browser publish via
sau(seereferences/douyin-publish.md,scripts/douyin_publish.py). Preflight gates: account selected, login/cookie valid, video/image + title/tags prepared. Default--review-before-publish(fill then stop at the publish button; the user clicks publish themselves in the open browser — do not auto-click even on confirmation). Ifsauis unavailable, fall back tomanual_uploadwith a complete package. - Keep generated content/assets ready for manual upload even when login is not ready.
- For Bilibili, output
target: manual_uploadwhen a complete manual package can be prepared; otherwise outputtarget: unsupported. - Include a
platform_gapsitem explaining missing login, missing account, unsupported backend, or manual-upload requirements.
Stop Conditions
Stop and ask before:
- Calling platform APIs.
- Opening browser automation for logged-in platforms.
- Uploading images or videos.
- Clicking any final publish/post button.
- Scheduling external posts.