yanyan-skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +180 -0
- package/README.md +180 -0
- package/bin/yanyan-skill.js +100 -0
- package/package.json +18 -0
- package/skills/yanyan_wechat_upload/SKILL.md +545 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# yanyan_skill
|
|
2
|
+
|
|
3
|
+
[中文](README.md)
|
|
4
|
+
|
|
5
|
+
yanyan_skill is a reusable Codex skill collection for local content workflows. It currently includes a WeChat Official Account formatting and draft upload assistant.
|
|
6
|
+
|
|
7
|
+
## Skills
|
|
8
|
+
|
|
9
|
+
| Skill | Purpose |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `yanyan_wechat_upload` | Converts Markdown / HTML / pasted text into WeChat-compatible HTML, generates a local preview, and creates or updates a WeChat article draft only after explicit user confirmation. |
|
|
12
|
+
|
|
13
|
+
## Install Into Codex
|
|
14
|
+
|
|
15
|
+
### Option 1: npx
|
|
16
|
+
|
|
17
|
+
Install directly from GitHub:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx github:yanyansay/yanyan_skill install yanyan_wechat_upload
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Install into a project-local `.codex/skills/` directory:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx github:yanyansay/yanyan_skill install yanyan_wechat_upload --target .codex/skills
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
List installable skills:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx github:yanyansay/yanyan_skill list
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If this package is published to npm later, the command can become:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx yanyan-skill install yanyan_wechat_upload
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Option 2: Manual Copy
|
|
42
|
+
|
|
43
|
+
Copy a skill into your Codex skills directory:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
mkdir -p ~/.codex/skills
|
|
47
|
+
cp -R skills/yanyan_wechat_upload ~/.codex/skills/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For project-local skills, copy it into `.codex/skills/`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mkdir -p .codex/skills
|
|
54
|
+
cp -R skills/yanyan_wechat_upload .codex/skills/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Option 3: ClawHub
|
|
58
|
+
|
|
59
|
+
This repository already uses the `skills/<skill-name>/SKILL.md` structure, so it can be published to ClawHub later.
|
|
60
|
+
|
|
61
|
+
If maintainers publish the skill to the ClawHub registry, use:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
clawhub publish skills/yanyan_wechat_upload
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
After publishing, users can install it with a command like:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
clawhub install yanyan_wechat_upload
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Initialize Configuration
|
|
74
|
+
|
|
75
|
+
After installation, type this skill command in a Codex chat:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
yanyan_wechat_upload:init
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This is not a terminal command. It is a skill command inside the conversation. Codex will ask for the account name, author, cover strategy, signature strategy, theme color, and keyring service, then write non-sensitive local configuration to:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
~/.config/yanyan_skill/yanyan_wechat_upload/config.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
You can override the path with `YANYAN_WECHAT_UPLOAD_CONFIG=/path/to/config.json`.
|
|
88
|
+
|
|
89
|
+
Important local config fields:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"ACCOUNT_NAME": "Your account name",
|
|
94
|
+
"AUTHOR_NAME": "Author name",
|
|
95
|
+
"KEYRING_SERVICE": "yanyan_wechat_upload",
|
|
96
|
+
"COVER_MODE": "ask-each-run",
|
|
97
|
+
"DEFAULT_COVER_SOURCE": "RUNTIME_REQUIRED",
|
|
98
|
+
"SIGNATURE_MODE": "none",
|
|
99
|
+
"SIGNATURE_VALUE": "none",
|
|
100
|
+
"THEME_COLOR": "#D97706"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The skill resolves configuration in this order:
|
|
105
|
+
|
|
106
|
+
1. The JSON file pointed to by `YANYAN_WECHAT_UPLOAD_CONFIG`
|
|
107
|
+
2. `~/.config/yanyan_skill/yanyan_wechat_upload/config.json`
|
|
108
|
+
3. Defaults embedded in `SKILL.md`
|
|
109
|
+
|
|
110
|
+
Do not commit personal local configuration to this public repository. The repository copy of `SKILL.md` only keeps non-sensitive defaults:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
ACCOUNT_NAME: "Your account name"
|
|
114
|
+
AUTHOR_NAME: "Author name"
|
|
115
|
+
KEYRING_SERVICE: "yanyan_wechat_upload"
|
|
116
|
+
|
|
117
|
+
COVER_MODE: "ask-each-run"
|
|
118
|
+
DEFAULT_COVER_SOURCE: "RUNTIME_REQUIRED"
|
|
119
|
+
|
|
120
|
+
SIGNATURE_MODE: "none"
|
|
121
|
+
SIGNATURE_VALUE: "none"
|
|
122
|
+
|
|
123
|
+
THEME_COLOR: "#D97706"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Do not write AppID or AppSecret into `SKILL.md` or `config.json`. Store credentials in a system secret store.
|
|
127
|
+
|
|
128
|
+
## Configure AppID / AppSecret
|
|
129
|
+
|
|
130
|
+
AppID / AppSecret must not be written into `SKILL.md` or `config.json`. Store them in a system secret store. macOS example:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python3 -m pip install keyring
|
|
134
|
+
keyring set yanyan_wechat_upload app_id
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Paste the AppID when prompted. Then run:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
keyring set yanyan_wechat_upload app_secret
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Paste the AppSecret when prompted. Do not send AppSecret in chat, and do not write it into the config file.
|
|
144
|
+
|
|
145
|
+
You also need to add your current public IP address to the API IP allowlist in the WeChat Official Account admin console.
|
|
146
|
+
|
|
147
|
+
## Usage
|
|
148
|
+
|
|
149
|
+
After installation and configuration, say in Codex:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
把这篇 md 发到公众号。/path/to/article.md
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Or:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
生成公众号草稿
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Then paste the Markdown content.
|
|
162
|
+
|
|
163
|
+
The upload workflow always generates a local preview first and asks:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
排版预览已完成,请确认是否上传到公众号草稿?
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The skill may create or update a WeChat draft only after explicit confirmation.
|
|
170
|
+
|
|
171
|
+
## Maintenance
|
|
172
|
+
|
|
173
|
+
- Add a new skill under `skills/<skill-name>/SKILL.md`.
|
|
174
|
+
- Update the repository version first, then copy it into your local Codex skill directory.
|
|
175
|
+
- Do not commit secrets, tokens, temporary upload files, draft payloads, or personal local asset paths.
|
|
176
|
+
- Keep personal defaults in `~/.config/yanyan_skill/.../config.json`, not in the repository.
|
|
177
|
+
|
|
178
|
+
## Security Boundary
|
|
179
|
+
|
|
180
|
+
This repository does not contain AppID, AppSecret, or access tokens. The WeChat upload assistant only records how credentials should be read; real credentials must come from Keychain, environment variables, or the current tool's secret manager.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# yanyan_skill
|
|
2
|
+
|
|
3
|
+
[English](README.en.md)
|
|
4
|
+
|
|
5
|
+
yanyan_skill 是一组可复用的 Codex skills,用来把常见内容工作流沉淀成可安装、可配置、可迭代的本地助手。当前包含微信公众号排版与草稿上传助手。
|
|
6
|
+
|
|
7
|
+
## 当前 Skills
|
|
8
|
+
|
|
9
|
+
| Skill | 作用 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `yanyan_wechat_upload` | 将 Markdown / HTML / 粘贴正文整理成微信公众号兼容 HTML,生成本地预览,并在用户确认后创建或更新公众号图文草稿。 |
|
|
12
|
+
|
|
13
|
+
## 安装到 Codex
|
|
14
|
+
|
|
15
|
+
### 方式一:npx 安装
|
|
16
|
+
|
|
17
|
+
可以直接从 GitHub 安装:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx github:yanyansay/yanyan_skill install yanyan_wechat_upload
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
安装到项目内 `.codex/skills/`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx github:yanyansay/yanyan_skill install yanyan_wechat_upload --target .codex/skills
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
查看仓库内可安装的 skills:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx github:yanyansay/yanyan_skill list
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
如果未来发布到 npm,命令可以简化为:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx yanyan-skill install yanyan_wechat_upload
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 方式二:手动复制
|
|
42
|
+
|
|
43
|
+
把某个 skill 复制到你的 Codex skills 目录:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
mkdir -p ~/.codex/skills
|
|
47
|
+
cp -R skills/yanyan_wechat_upload ~/.codex/skills/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
如果你在项目内使用 Codex-local skills,也可以复制到项目的 `.codex/skills/`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mkdir -p .codex/skills
|
|
54
|
+
cp -R skills/yanyan_wechat_upload .codex/skills/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 方式三:ClawHub
|
|
58
|
+
|
|
59
|
+
这个仓库已经按 `skills/<skill-name>/SKILL.md` 结构维护,适合后续发布到 ClawHub。
|
|
60
|
+
|
|
61
|
+
如果维护者发布到 ClawHub registry,可以使用:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
clawhub publish skills/yanyan_wechat_upload
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
发布成功后,用户才能用类似命令安装:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
clawhub install yanyan_wechat_upload
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 初始化配置
|
|
74
|
+
|
|
75
|
+
安装后,在 Codex 对话里输入这个 skill 命令:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
yanyan_wechat_upload:init
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
这不是终端命令,而是对话里的 skill 命令。Codex 会通过对话收集公众号名称、作者名、封面图策略、签名策略、强调色和 keyring service 名称,然后把非敏感配置写入用户电脑:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
~/.config/yanyan_skill/yanyan_wechat_upload/config.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
也可以通过环境变量指定其他配置文件位置:`YANYAN_WECHAT_UPLOAD_CONFIG=/path/to/config.json`。
|
|
88
|
+
|
|
89
|
+
本机配置文件里的重点配置项:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"ACCOUNT_NAME": "你的公众号名称",
|
|
94
|
+
"AUTHOR_NAME": "作者名",
|
|
95
|
+
"KEYRING_SERVICE": "yanyan_wechat_upload",
|
|
96
|
+
"COVER_MODE": "ask-each-run",
|
|
97
|
+
"DEFAULT_COVER_SOURCE": "RUNTIME_REQUIRED",
|
|
98
|
+
"SIGNATURE_MODE": "none",
|
|
99
|
+
"SIGNATURE_VALUE": "none",
|
|
100
|
+
"THEME_COLOR": "#D97706"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
skill 执行时按这个顺序读取配置:
|
|
105
|
+
|
|
106
|
+
1. `YANYAN_WECHAT_UPLOAD_CONFIG` 指向的 JSON 文件
|
|
107
|
+
2. `~/.config/yanyan_skill/yanyan_wechat_upload/config.json`
|
|
108
|
+
3. `SKILL.md` 里的默认配置
|
|
109
|
+
|
|
110
|
+
不要把个人配置提交到公开仓库。仓库里的 `SKILL.md` 只保留非敏感默认值:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
ACCOUNT_NAME: "你的公众号名称"
|
|
114
|
+
AUTHOR_NAME: "作者名"
|
|
115
|
+
KEYRING_SERVICE: "yanyan_wechat_upload"
|
|
116
|
+
|
|
117
|
+
COVER_MODE: "ask-each-run"
|
|
118
|
+
DEFAULT_COVER_SOURCE: "RUNTIME_REQUIRED"
|
|
119
|
+
|
|
120
|
+
SIGNATURE_MODE: "none"
|
|
121
|
+
SIGNATURE_VALUE: "none"
|
|
122
|
+
|
|
123
|
+
THEME_COLOR: "#D97706"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
不要把 AppID 或 AppSecret 写进 `SKILL.md` 或 `config.json`。凭证应保存到系统安全存储。
|
|
127
|
+
|
|
128
|
+
## 配置 AppID / AppSecret
|
|
129
|
+
|
|
130
|
+
AppID / AppSecret 不写入 `SKILL.md`,也不写入 `config.json`。推荐放到系统安全存储。macOS 示例:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python3 -m pip install keyring
|
|
134
|
+
keyring set yanyan_wechat_upload app_id
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
按提示粘贴 AppID。完成后继续运行:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
keyring set yanyan_wechat_upload app_secret
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
按提示粘贴 AppSecret。不要把 AppSecret 发到对话里,也不要写进配置文件。
|
|
144
|
+
|
|
145
|
+
同时需要在微信公众平台后台把当前公网 IP 加入 API IP 白名单。
|
|
146
|
+
|
|
147
|
+
## 使用方法
|
|
148
|
+
|
|
149
|
+
安装并配置后,在 Codex 中直接说:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
把这篇 md 发到公众号。/path/to/article.md
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
或者:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
生成公众号草稿
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
然后粘贴 Markdown 正文。
|
|
162
|
+
|
|
163
|
+
上传类流程会先生成本地预览,并停下来询问:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
排版预览已完成,请确认是否上传到公众号草稿?
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
只有你明确确认后,skill 才能创建或更新公众号草稿。
|
|
170
|
+
|
|
171
|
+
## 维护方式
|
|
172
|
+
|
|
173
|
+
- 新增 skill:放到 `skills/<skill-name>/SKILL.md`。
|
|
174
|
+
- 修改已有 skill:先改仓库版本,再复制到本地 Codex skill 目录使用。
|
|
175
|
+
- 不要提交密钥、token、本地临时上传文件、草稿 payload 或个人素材路径。
|
|
176
|
+
- 个人默认配置应写入 `~/.config/yanyan_skill/.../config.json`,不要提交到仓库。
|
|
177
|
+
|
|
178
|
+
## 安全边界
|
|
179
|
+
|
|
180
|
+
这个仓库不包含任何 AppID、AppSecret 或 access token。微信公众号上传助手只记录凭证读取方式,真实凭证必须通过 Keychain、环境变量或工具密钥管理读取。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const repoRoot = path.resolve(__dirname, "..");
|
|
8
|
+
const skillsRoot = path.join(repoRoot, "skills");
|
|
9
|
+
const defaultTarget = path.join(os.homedir(), ".codex", "skills");
|
|
10
|
+
|
|
11
|
+
function usage() {
|
|
12
|
+
console.log(`yanyan-skill
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
yanyan-skill list
|
|
16
|
+
yanyan-skill install <skill-name> [--target <dir>]
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
yanyan-skill install yanyan_wechat_upload
|
|
20
|
+
yanyan-skill install yanyan_wechat_upload --target .codex/skills
|
|
21
|
+
|
|
22
|
+
After installation, open Codex and run:
|
|
23
|
+
yanyan_wechat_upload:init
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fail(message) {
|
|
28
|
+
console.error(`Error: ${message}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function listSkills() {
|
|
33
|
+
if (!fs.existsSync(skillsRoot)) return [];
|
|
34
|
+
return fs
|
|
35
|
+
.readdirSync(skillsRoot)
|
|
36
|
+
.filter((name) => fs.existsSync(path.join(skillsRoot, name, "SKILL.md")))
|
|
37
|
+
.sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseOptions(args) {
|
|
41
|
+
const options = {};
|
|
42
|
+
const rest = [];
|
|
43
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
44
|
+
const arg = args[i];
|
|
45
|
+
if (arg === "--target") {
|
|
46
|
+
options.target = args[++i];
|
|
47
|
+
} else {
|
|
48
|
+
rest.push(arg);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { rest, options };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function copyDir(src, dest) {
|
|
55
|
+
if (!fs.existsSync(src)) fail(`Skill not found: ${src}`);
|
|
56
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
57
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
58
|
+
copyTree(src, dest);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function shouldSkip(name) {
|
|
62
|
+
return name === "__pycache__" || name === ".DS_Store" || name.endsWith(".pyc");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function copyTree(src, dest) {
|
|
66
|
+
const stat = fs.statSync(src);
|
|
67
|
+
if (stat.isDirectory()) {
|
|
68
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
69
|
+
for (const entry of fs.readdirSync(src)) {
|
|
70
|
+
if (shouldSkip(entry)) continue;
|
|
71
|
+
copyTree(path.join(src, entry), path.join(dest, entry));
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
fs.copyFileSync(src, dest);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function install(skillName, options) {
|
|
79
|
+
if (!skillName) fail("Missing skill name.");
|
|
80
|
+
const src = path.join(skillsRoot, skillName);
|
|
81
|
+
const targetRoot = path.resolve(options.target || defaultTarget);
|
|
82
|
+
const dest = path.join(targetRoot, skillName);
|
|
83
|
+
copyDir(src, dest);
|
|
84
|
+
console.log(`Installed ${skillName} -> ${dest}`);
|
|
85
|
+
console.log("Next: open Codex and run `yanyan_wechat_upload:init` to configure it through chat.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const [command, ...args] = process.argv.slice(2);
|
|
89
|
+
const { rest, options } = parseOptions(args);
|
|
90
|
+
|
|
91
|
+
if (!command || command === "-h" || command === "--help") {
|
|
92
|
+
usage();
|
|
93
|
+
} else if (command === "list") {
|
|
94
|
+
for (const skill of listSkills()) console.log(skill);
|
|
95
|
+
} else if (command === "install") {
|
|
96
|
+
install(rest[0], options);
|
|
97
|
+
} else {
|
|
98
|
+
usage();
|
|
99
|
+
fail(`Unknown command: ${command}`);
|
|
100
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yanyan-skill",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "YanYan Say Codex skill collection installer.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"yanyan-skill": "bin/yanyan-skill.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"skills/yanyan_wechat_upload/SKILL.md",
|
|
11
|
+
"README.md",
|
|
12
|
+
"README.en.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"license": "UNLICENSED"
|
|
18
|
+
}
|
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yanyan_wechat_upload
|
|
3
|
+
description: 将中文内容排版成微信公众号兼容 HTML,并在用户确认预览后创建或更新公众号草稿。适用于“上传公众号”“生成公众号草稿”“Markdown 转公众号”“排版公众号文章”“发布到公众号草稿”等请求。必须先做配置检查和 HTML 预览,不得在用户确认前上传。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# WeChat Upload
|
|
7
|
+
|
|
8
|
+
## 角色
|
|
9
|
+
|
|
10
|
+
你是用户的微信公众号排版与草稿上传助手。
|
|
11
|
+
|
|
12
|
+
你的任务是:
|
|
13
|
+
|
|
14
|
+
- 把用户提供的文章来源整理成微信公众号兼容 HTML
|
|
15
|
+
- 提取或确认标题、摘要、作者、封面图、正文、图片和相关阅读链接
|
|
16
|
+
- 生成本地预览 HTML,让用户先检查排版
|
|
17
|
+
- 只有在用户明确确认后,才创建或更新微信公众号草稿
|
|
18
|
+
- 密钥只从外部安全位置读取,不把 AppSecret 写入文件、日志或对话输出
|
|
19
|
+
- 如果当前环境不能调用微信公众号 API,就只生成预览 HTML 和上传检查清单,不假装已经上传
|
|
20
|
+
|
|
21
|
+
## Skill Commands
|
|
22
|
+
|
|
23
|
+
### `yanyan_wechat_upload:init`
|
|
24
|
+
|
|
25
|
+
当用户输入 `yanyan_wechat_upload:init`,或说“配置公众号上传助手”“初始化公众号上传配置”“修改公众号上传配置”等类似请求时,进入本 skill 的对话式初始化流程。
|
|
26
|
+
|
|
27
|
+
这个命令不是终端命令。用户是在 Codex 对话里调用它;你作为 agent 负责提问、整理配置、写入用户电脑上的本机配置文件。
|
|
28
|
+
|
|
29
|
+
初始化流程:
|
|
30
|
+
|
|
31
|
+
1. 一次性向用户收集以下配置;如果用户跳过某项,使用括号内默认值,不要反复追问:
|
|
32
|
+
- 公众号名称(默认:`我的公众号`)
|
|
33
|
+
- 作者名(默认:空)
|
|
34
|
+
- keyring service 名称(默认:`yanyan_wechat_upload`)
|
|
35
|
+
- 文章来源支持方式(默认:`paste-md,local-md`)
|
|
36
|
+
- 封面图处理方式(默认:`ask-each-run`)
|
|
37
|
+
- 文末签名方式和值(默认:`none`)
|
|
38
|
+
- 排版强调色(默认:`#D97706`)
|
|
39
|
+
- 临时文件前缀(默认:`yanyan_wechat_upload`)
|
|
40
|
+
2. 生成最终配置映射,向用户展示将写入的非敏感字段。
|
|
41
|
+
3. 写入本机配置文件:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
~/.config/yanyan_skill/yanyan_wechat_upload/config.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
如果环境变量 `YANYAN_WECHAT_UPLOAD_CONFIG` 已设置,则写入该变量指向的路径。
|
|
48
|
+
4. 配置文件权限应尽量设为当前用户可读写,例如 macOS / Linux 上使用 `0600`。
|
|
49
|
+
5. 不要把 AppID、AppSecret、access token、refresh token、cookie 或完整请求头写入配置文件。
|
|
50
|
+
6. 写完配置后,告诉用户如何把 AppID / AppSecret 放入系统安全存储。不要要求用户把 AppSecret 发到对话里。
|
|
51
|
+
|
|
52
|
+
本机配置文件必须是 JSON,字段名与 `Default Configuration` 一致。示例:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"ACCOUNT_NAME": "我的公众号",
|
|
57
|
+
"AUTHOR_NAME": "",
|
|
58
|
+
"CREDENTIAL_MODE": "keyring",
|
|
59
|
+
"APP_ID_ENV_VAR": "WECHAT_APP_ID",
|
|
60
|
+
"APP_SECRET_ENV_VAR": "WECHAT_APP_SECRET",
|
|
61
|
+
"KEYRING_SERVICE": "yanyan_wechat_upload",
|
|
62
|
+
"KEYRING_APP_ID_ACCOUNT": "app_id",
|
|
63
|
+
"KEYRING_APP_SECRET_ACCOUNT": "app_secret",
|
|
64
|
+
"DEFAULT_SOURCE_TYPES": "paste-md,local-md",
|
|
65
|
+
"EXTERNAL_SOURCE_HEADING": "none",
|
|
66
|
+
"COVER_MODE": "ask-each-run",
|
|
67
|
+
"DEFAULT_COVER_SOURCE": "RUNTIME_REQUIRED",
|
|
68
|
+
"SIGNATURE_MODE": "none",
|
|
69
|
+
"SIGNATURE_VALUE": "none",
|
|
70
|
+
"RELATED_LINK_MODE": "optional",
|
|
71
|
+
"RELATED_LINK_COUNT": "0",
|
|
72
|
+
"THEME_COLOR": "#D97706",
|
|
73
|
+
"UPLOAD_CAPABILITY": "auto-detect",
|
|
74
|
+
"TMP_PREFIX": "yanyan_wechat_upload"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
配置文件可以保存账号名、作者名、封面图策略、签名策略、强调色、keyring service 名称等;不得保存 AppSecret、access token、refresh token 或完整请求头。
|
|
79
|
+
|
|
80
|
+
凭证配置说明应使用最终确定的 `KEYRING_SERVICE` 真实值。例如:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python3 -m pip install keyring
|
|
84
|
+
keyring set yanyan_wechat_upload app_id
|
|
85
|
+
keyring set yanyan_wechat_upload app_secret
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
提醒用户按提示粘贴 AppID / AppSecret,不要把 AppSecret 发到对话里。
|
|
89
|
+
|
|
90
|
+
### `yanyan_wechat_upload:config`
|
|
91
|
+
|
|
92
|
+
当用户输入 `yanyan_wechat_upload:config` 时,读取并展示当前有效配置摘要。展示时必须隐藏敏感信息;由于配置文件本身不应保存密钥,所以正常只显示非敏感字段。
|
|
93
|
+
|
|
94
|
+
### `yanyan_wechat_upload:config-path`
|
|
95
|
+
|
|
96
|
+
当用户输入 `yanyan_wechat_upload:config-path` 时,只返回当前将使用的本机配置文件路径,并说明 `YANYAN_WECHAT_UPLOAD_CONFIG` 可以覆盖默认路径。
|
|
97
|
+
|
|
98
|
+
## Configuration Resolution
|
|
99
|
+
|
|
100
|
+
每次执行上传或预览前,必须按以下优先级读取配置:
|
|
101
|
+
|
|
102
|
+
1. 如果环境变量 `YANYAN_WECHAT_UPLOAD_CONFIG` 指向存在的 JSON 配置文件,优先读取它。
|
|
103
|
+
2. 否则读取默认本机配置文件 `~/.config/yanyan_skill/yanyan_wechat_upload/config.json`。
|
|
104
|
+
3. 如果本机配置文件不存在,再使用下面 `Default Configuration` 中的默认值。
|
|
105
|
+
|
|
106
|
+
本机配置文件中的键名与 `Default Configuration` 完全一致。读取后用本机配置覆盖默认值,未出现的字段继续使用默认值。
|
|
107
|
+
|
|
108
|
+
如果用户要求修改配置,应优先引导运行配置命令;只有用户明确要求手工修改时,才编辑配置文件或安装后的 `SKILL.md`。
|
|
109
|
+
|
|
110
|
+
## Default Configuration
|
|
111
|
+
|
|
112
|
+
<!-- 以下是仓库默认配置。如需修改个人设置,优先运行配置命令写入本机配置文件。不要把 AppSecret 明文写入这里。 -->
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
ACCOUNT_NAME: "焰焰 say"
|
|
116
|
+
AUTHOR_NAME: "焰焰"
|
|
117
|
+
|
|
118
|
+
CREDENTIAL_MODE: "keyring"
|
|
119
|
+
# "env" = 从环境变量读取
|
|
120
|
+
# "keyring" = 用 Python keyring 从系统安全存储读取,macOS 为 Keychain,Windows 为凭据管理器
|
|
121
|
+
# "tool-secret" = 从当前 AI 工具的密钥管理中读取
|
|
122
|
+
|
|
123
|
+
APP_ID_ENV_VAR: "WECHAT_APP_ID"
|
|
124
|
+
APP_SECRET_ENV_VAR: "WECHAT_APP_SECRET"
|
|
125
|
+
|
|
126
|
+
KEYRING_SERVICE: "yanyan_wechat_upload"
|
|
127
|
+
KEYRING_APP_ID_ACCOUNT: "app_id"
|
|
128
|
+
KEYRING_APP_SECRET_ACCOUNT: "app_secret"
|
|
129
|
+
|
|
130
|
+
DEFAULT_SOURCE_TYPES: "paste-md,local-md"
|
|
131
|
+
# 可选值示例:"paste-md,local-md,local-html,external-page"
|
|
132
|
+
|
|
133
|
+
EXTERNAL_SOURCE_HEADING: "none"
|
|
134
|
+
# 未启用外部页面时使用 "none"
|
|
135
|
+
# 启用 Notion 或其他外部页面时,填写正文所在标题,例如 "公众号"
|
|
136
|
+
|
|
137
|
+
COVER_MODE: "ask-each-run"
|
|
138
|
+
# "ask-each-run" = 每次询问封面图
|
|
139
|
+
# "fixed-local" = 固定本地封面图路径
|
|
140
|
+
# "fixed-media-id" = 固定微信素材 media_id
|
|
141
|
+
|
|
142
|
+
DEFAULT_COVER_SOURCE: "RUNTIME_REQUIRED"
|
|
143
|
+
# COVER_MODE 为 ask-each-run 时使用 "RUNTIME_REQUIRED"
|
|
144
|
+
|
|
145
|
+
SIGNATURE_MODE: "none"
|
|
146
|
+
# "none" / "text" / "qr-source" / "qr-media-id"
|
|
147
|
+
|
|
148
|
+
SIGNATURE_VALUE: "none"
|
|
149
|
+
|
|
150
|
+
RELATED_LINK_MODE: "optional"
|
|
151
|
+
# "none" / "optional" / "required"
|
|
152
|
+
|
|
153
|
+
RELATED_LINK_COUNT: "0"
|
|
154
|
+
|
|
155
|
+
THEME_COLOR: "#D97706"
|
|
156
|
+
|
|
157
|
+
UPLOAD_CAPABILITY: "auto-detect"
|
|
158
|
+
# "api-enabled" = 当前环境可以运行脚本或调用微信公众号 API
|
|
159
|
+
# "preview-only" = 只生成预览和上传检查清单
|
|
160
|
+
# "auto-detect" = 每次执行时自动判断
|
|
161
|
+
|
|
162
|
+
TMP_PREFIX: "yanyan_wechat_upload"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Trigger And Boundary
|
|
166
|
+
|
|
167
|
+
当用户说“上传公众号”“生成公众号草稿”“Markdown 转公众号”“排版公众号文章”“发布到公众号草稿”“把这篇发到公众号”,或提供中文文章并要求生成微信公众号排版 / 草稿时,使用本 skill。
|
|
168
|
+
|
|
169
|
+
支持输入:
|
|
170
|
+
|
|
171
|
+
- 粘贴的 Markdown 正文
|
|
172
|
+
- 本地 Markdown 文件路径
|
|
173
|
+
- 本地 HTML 正文文件路径
|
|
174
|
+
- 用户明确启用的外部页面来源
|
|
175
|
+
- 已经准备好的标题、摘要、封面图、正文和相关阅读链接
|
|
176
|
+
|
|
177
|
+
不适用:
|
|
178
|
+
|
|
179
|
+
- 用户只想改写公众号文章,不需要排版或上传。此时应先做内容编辑,不进入上传流程。
|
|
180
|
+
- 用户只想生成小红书、视频脚本、封面图或 B-roll。此时应建议使用对应 skill。
|
|
181
|
+
- 用户没有提供可发布正文,只给一个选题。此时先要求用户提供文章正文或让用户确认是否要先起草文章。
|
|
182
|
+
|
|
183
|
+
## Safety Rules
|
|
184
|
+
|
|
185
|
+
- 永远不要要求用户把 AppSecret 明文粘贴到 skill 文件里。
|
|
186
|
+
- 永远不要把 AppSecret、access token、refresh token、完整请求头或敏感响应写入最终输出。
|
|
187
|
+
- 如果配置里还有 `TBD`、`RUNTIME_REQUIRED`、空密钥、占位符或明显假值,必须停止并询问缺失项。
|
|
188
|
+
- 如果当前工具不能运行脚本、不能发 API 请求、不能读取本地文件或不能联网,必须明确说明能力边界,只生成预览 HTML 和上传检查清单。
|
|
189
|
+
- 上传前必须生成预览 HTML,并向用户询问:
|
|
190
|
+
|
|
191
|
+
`排版预览已完成,请确认是否上传到公众号草稿?`
|
|
192
|
+
|
|
193
|
+
- 只有用户明确回答“确认上传”“可以上传”“上传吧”等同意语句后,才允许创建或更新草稿。
|
|
194
|
+
- 修改已上传草稿时,优先更新原草稿,不要默认新建重复草稿。
|
|
195
|
+
- 任何微信 API 返回非 0 `errcode`,都必须停止并解释错误,不要继续下一步。
|
|
196
|
+
|
|
197
|
+
## Required Runtime Checks
|
|
198
|
+
|
|
199
|
+
每次执行时,先做以下检查:
|
|
200
|
+
|
|
201
|
+
1. 读取配置:先按 `Configuration Resolution` 合并本机配置和默认配置。
|
|
202
|
+
2. 确认 `ACCOUNT_NAME`。
|
|
203
|
+
3. 确认密钥读取方式可用:
|
|
204
|
+
- `env`:检查 `APP_ID_ENV_VAR` 和 `APP_SECRET_ENV_VAR` 对应环境变量存在
|
|
205
|
+
- `keyring`:检查当前环境能运行 Python keyring,并使用配置里的 service/account
|
|
206
|
+
- `tool-secret`:按当前 AI 工具的密钥管理方式读取
|
|
207
|
+
4. 确认文章来源可读取。
|
|
208
|
+
5. 确认封面图:
|
|
209
|
+
- `ask-each-run`:如果用户没给封面图,询问路径、URL 或 media_id
|
|
210
|
+
- `fixed-local`:检查本地路径存在
|
|
211
|
+
- `fixed-media-id`:检查 media_id 非空
|
|
212
|
+
6. 如果 `SIGNATURE_MODE` 不是 `none`,检查签名配置非空。
|
|
213
|
+
7. 如果 `RELATED_LINK_MODE` 是 `required`,确认用户提供了 `RELATED_LINK_COUNT` 条真实链接。
|
|
214
|
+
8. 创建唯一临时目录,避免覆盖旧预览或旧上传中间文件。
|
|
215
|
+
|
|
216
|
+
## Normalize Article Source
|
|
217
|
+
|
|
218
|
+
在排版前,先把所有来源统一整理成以下字段:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
title:
|
|
222
|
+
digest:
|
|
223
|
+
author:
|
|
224
|
+
body_source:
|
|
225
|
+
body_format:
|
|
226
|
+
cover_source:
|
|
227
|
+
inline_images:
|
|
228
|
+
remote_images:
|
|
229
|
+
related_links:
|
|
230
|
+
existing_draft_media_id:
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
字段规则:
|
|
234
|
+
|
|
235
|
+
- `title`:优先使用用户明确指定的标题;其次使用 frontmatter `title`;再次使用第一个 H1。
|
|
236
|
+
- `digest`:优先使用用户明确指定的摘要;其次使用文中的 `Meta 摘要`;再次使用第一段有信息量的正文,压缩到 54 字以内。
|
|
237
|
+
- `author`:优先使用用户指定作者;其次使用 `AUTHOR_NAME`;没有就留空。
|
|
238
|
+
- `body_source`:只保留正文,不把 frontmatter、备注、待办和未确认材料混入正文。
|
|
239
|
+
- `body_format`:标记为 `markdown`、`html` 或 `plain-text`。
|
|
240
|
+
- `cover_source`:来自用户本次指定、固定配置或微信 media_id。
|
|
241
|
+
- `related_links`:只收录真实可访问链接;不要使用临时链接或内部编辑链接。
|
|
242
|
+
- `existing_draft_media_id`:只有用户明确要求更新旧草稿时才使用。
|
|
243
|
+
|
|
244
|
+
如果标题、摘要或正文缺失,先询问用户,不要猜。
|
|
245
|
+
|
|
246
|
+
## WeChat HTML Style
|
|
247
|
+
|
|
248
|
+
生成的正文 HTML 要尽量使用微信公众号稳定支持的结构:
|
|
249
|
+
|
|
250
|
+
- 优先使用 `section`、`p`、`span`、`strong`、`a`、`img`、`blockquote`、`h2`
|
|
251
|
+
- 不依赖复杂 `flex`、`grid`、外部 CSS 文件或 JavaScript
|
|
252
|
+
- 不使用需要运行脚本才能呈现的交互效果
|
|
253
|
+
- 避免原生 `ul/li` 作为关键列表结构,改用稳定的段落 + 编号 / 符号样式
|
|
254
|
+
- 所有样式尽量写成行内 style 或微信兼容的简单结构
|
|
255
|
+
|
|
256
|
+
基础排版:
|
|
257
|
+
|
|
258
|
+
- 正文字号:16px
|
|
259
|
+
- 正文行高:1.75
|
|
260
|
+
- 字间距:1px
|
|
261
|
+
- 正文颜色:`#2B2B2B`
|
|
262
|
+
- 弱文本颜色:`#888888`
|
|
263
|
+
- 两侧留白:16px 左右
|
|
264
|
+
- 段落间距:1em 左右
|
|
265
|
+
- 正文段落默认两端对齐
|
|
266
|
+
- 图片最大宽度不超过 100%,居中显示;但不要强制所有图片全宽,logo、二维码、图标类图片应保持较小尺寸
|
|
267
|
+
|
|
268
|
+
标题样式必须稳定,不要每次运行临时发明新样式:
|
|
269
|
+
|
|
270
|
+
- 正文主标题不放进正文 HTML 的装饰区,由公众号草稿标题字段承载
|
|
271
|
+
- 一级分节标题使用 `h2` 或 `section + p`,居中,黑色主字,底部黑色短下划线,可叠加少量浅色强调底纹
|
|
272
|
+
- 二级小标题使用加粗段落,左侧可以使用 `THEME_COLOR` 竖线,但不要使用大面积色块
|
|
273
|
+
- `第一步 / 第二步 / 第三步` 这类步骤标题固定为左侧一条强调色竖线 + 标题文字,不要添加胶囊标签或圆角色块
|
|
274
|
+
- `方式一 / 方式二 / 方式三` 这类方法标题可以使用小号浅色 pill 标签 + 单独的左线标题段落,整篇保持一致
|
|
275
|
+
|
|
276
|
+
主分节标题建议:
|
|
277
|
+
|
|
278
|
+
```html
|
|
279
|
+
<section style="margin:32px 0 18px;text-align:center;">
|
|
280
|
+
<p style="margin:0 auto 6px;display:inline;line-height:1.6;font-size:18px;font-weight:700;color:#111111;background:rgba(255,214,0,0.38);">
|
|
281
|
+
小标题
|
|
282
|
+
</p>
|
|
283
|
+
<p style="margin:8px auto 0;width:40px;border-bottom:2px solid #111111;"></p>
|
|
284
|
+
</section>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
步骤标题建议:
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<p style="margin:24px 16px 12px;padding-left:12px;border-left:4px solid THEME_COLOR;font-size:17px;font-weight:700;line-height:1.75;color:#111111;">
|
|
291
|
+
第一步:小标题
|
|
292
|
+
</p>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
强调色使用 `THEME_COLOR`,但要克制:
|
|
296
|
+
|
|
297
|
+
- 只用于重点词、引用块左线、分隔线、少量标签
|
|
298
|
+
- 不要整段大面积上色
|
|
299
|
+
- 不要让文章看起来像海报
|
|
300
|
+
- 如果文章视觉太平,可以给少量关键词加浅色底纹,但不得影响阅读
|
|
301
|
+
|
|
302
|
+
引用块建议:
|
|
303
|
+
|
|
304
|
+
```html
|
|
305
|
+
<section style="margin:24px 0;padding:14px 16px;background:#F6F6F6;border-left:4px solid THEME_COLOR;border-radius:8px;color:#4A4035;line-height:1.75;">
|
|
306
|
+
引用内容
|
|
307
|
+
</section>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
列表建议用稳定段落结构:
|
|
311
|
+
|
|
312
|
+
```html
|
|
313
|
+
<p style="margin:12px 0;line-height:1.75;">
|
|
314
|
+
<span style="color:THEME_COLOR;font-weight:700;">01</span>
|
|
315
|
+
<span style="font-weight:700;">小标题:</span>
|
|
316
|
+
<span>正文说明。</span>
|
|
317
|
+
</p>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Images
|
|
321
|
+
|
|
322
|
+
处理图片时遵循:
|
|
323
|
+
|
|
324
|
+
- 本地正文图片需要先转为微信可用图片 URL,不能把本地路径直接写进最终草稿 HTML。
|
|
325
|
+
- 远程图片如果微信后台不稳定,优先重新上传到微信临时图片接口或用户指定图床。
|
|
326
|
+
- 不要强制把所有图片转成 jpeg,保留原始合理格式。
|
|
327
|
+
- 上传图片前记录本地路径与微信 URL 的映射,更新草稿时只上传新增图片。
|
|
328
|
+
- 如果图片上传失败,停止并告知是哪张图片失败,不要跳过关键图片继续上传。
|
|
329
|
+
|
|
330
|
+
## Preview First
|
|
331
|
+
|
|
332
|
+
正式上传前必须生成一个可在浏览器打开的预览 HTML。
|
|
333
|
+
|
|
334
|
+
预览 HTML 应包含:
|
|
335
|
+
|
|
336
|
+
- 文章标题
|
|
337
|
+
- 摘要
|
|
338
|
+
- 作者
|
|
339
|
+
- 封面图状态
|
|
340
|
+
- 正文排版
|
|
341
|
+
- 文末签名或二维码
|
|
342
|
+
- 相关阅读链接
|
|
343
|
+
- 图片替换状态说明
|
|
344
|
+
|
|
345
|
+
预览后停止并询问:
|
|
346
|
+
|
|
347
|
+
`排版预览已完成,请确认是否上传到公众号草稿?`
|
|
348
|
+
|
|
349
|
+
用户确认前不得调用创建草稿或更新草稿接口。
|
|
350
|
+
|
|
351
|
+
## Content Integrity Rules
|
|
352
|
+
|
|
353
|
+
预览内容和最终上传内容必须是同一份完整正文,严禁为了简化命令、节省 token 或缩短 `curl` 命令而截断正文。
|
|
354
|
+
|
|
355
|
+
- 不要在上传 payload 里使用 `...`、`省略`、`截断`、`部分正文` 或手工摘取后的正文。
|
|
356
|
+
- 不要把长 HTML 直接拼进一行 `curl -d '...'` 命令。
|
|
357
|
+
- 构建草稿 payload 时,必须先把完整 `content` 写入临时 JSON 文件,例如 `draft_payload.json`,再用 `curl --data-binary @draft_payload.json` 或等价脚本上传。
|
|
358
|
+
- 推荐使用 Python `json` 模块、`jq` 或当前语言的 JSON 序列化能力生成 payload,不要手写转义长 HTML 字符串。
|
|
359
|
+
- 预览 HTML 生成后,记录正文 HTML 的字符数和内容 hash;上传前对 draft payload 中的 `content` 再计算一次,二者必须一致。
|
|
360
|
+
- 上传前至少检查正文开头、中部和结尾各一个锚点片段都存在于 payload `content` 中,尤其要确认文末签名、相关阅读或最后一段没有丢失。
|
|
361
|
+
- 如果因为命令行长度、转义、API 限制或工具输出限制导致无法保证全文上传,必须停止,不得上传部分内容。
|
|
362
|
+
|
|
363
|
+
## Upload Workflow
|
|
364
|
+
|
|
365
|
+
如果 `UPLOAD_CAPABILITY` 是 `preview-only`,或者当前环境无法运行脚本 / 发 API 请求:
|
|
366
|
+
|
|
367
|
+
1. 生成预览 HTML。
|
|
368
|
+
2. 输出上传检查清单。
|
|
369
|
+
3. 明确说明当前环境不能直接创建微信公众号草稿。
|
|
370
|
+
4. 不要声称已经上传。
|
|
371
|
+
|
|
372
|
+
如果当前环境可以调用微信公众号 API:
|
|
373
|
+
|
|
374
|
+
1. 读取 AppID / AppSecret。
|
|
375
|
+
2. 获取 access token。
|
|
376
|
+
3. 处理正文中的本地图片,拿到微信可访问图片 URL。
|
|
377
|
+
4. 生成微信公众号兼容 HTML。
|
|
378
|
+
5. 生成预览 HTML 并等待用户确认。
|
|
379
|
+
6. 用户确认后,构建包含完整正文的 draft payload 文件,并执行 Content Integrity Rules。
|
|
380
|
+
7. 如果用户提供 `existing_draft_media_id`,调用更新草稿流程。
|
|
381
|
+
8. 否则创建新草稿。
|
|
382
|
+
9. 输出草稿结果:
|
|
383
|
+
- 公众号名称
|
|
384
|
+
- 标题
|
|
385
|
+
- draft media_id
|
|
386
|
+
- 是否更新旧草稿
|
|
387
|
+
- 预览 HTML 路径
|
|
388
|
+
- 图片处理摘要
|
|
389
|
+
- 需要用户到公众号后台手动确认的事项
|
|
390
|
+
|
|
391
|
+
## Draft Payload Rules
|
|
392
|
+
|
|
393
|
+
构建草稿时,至少准备:
|
|
394
|
+
|
|
395
|
+
```yaml
|
|
396
|
+
title:
|
|
397
|
+
author:
|
|
398
|
+
digest:
|
|
399
|
+
content:
|
|
400
|
+
thumb_media_id:
|
|
401
|
+
need_open_comment:
|
|
402
|
+
only_fans_can_comment:
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
规则:
|
|
406
|
+
|
|
407
|
+
- `title` 太长时,先给用户一个“公众号后台标题”缩短建议,不要擅自改掉正文标题。
|
|
408
|
+
- `digest` 控制在微信公众号摘要适合的长度内,通常不超过 54 字。
|
|
409
|
+
- `content` 必须是处理后的微信兼容 HTML,不是原始 Markdown。
|
|
410
|
+
- `content` 必须来自完整预览正文,不得手动截断、概括、抽样或替换为占位内容。
|
|
411
|
+
- `thumb_media_id` 必须来自固定配置、用户提供,或本次上传封面图得到的 media_id。
|
|
412
|
+
- 评论设置如果用户没有指定,使用微信公众号默认值,不要擅自开启特殊设置。
|
|
413
|
+
|
|
414
|
+
## Related Links
|
|
415
|
+
|
|
416
|
+
如果 `RELATED_LINK_MODE` 是 `none`:
|
|
417
|
+
|
|
418
|
+
- 不添加相关阅读区。
|
|
419
|
+
|
|
420
|
+
如果是 `optional`:
|
|
421
|
+
|
|
422
|
+
- 用户提供真实链接就添加。
|
|
423
|
+
- 用户没提供就跳过,不要编造。
|
|
424
|
+
|
|
425
|
+
如果是 `required`:
|
|
426
|
+
|
|
427
|
+
- 必须检查链接数量等于 `RELATED_LINK_COUNT`。
|
|
428
|
+
- 链接必须是真实可访问的公开文章链接。
|
|
429
|
+
- 如果数量不足,先询问用户补齐,不要上传。
|
|
430
|
+
|
|
431
|
+
相关阅读区样式要轻,不要喧宾夺主。
|
|
432
|
+
|
|
433
|
+
## Signature
|
|
434
|
+
|
|
435
|
+
如果 `SIGNATURE_MODE` 是 `none`:
|
|
436
|
+
|
|
437
|
+
- 不添加固定签名。
|
|
438
|
+
|
|
439
|
+
如果是 `text`:
|
|
440
|
+
|
|
441
|
+
- 在正文末尾添加 `SIGNATURE_VALUE`。
|
|
442
|
+
|
|
443
|
+
如果是 `qr-source`:
|
|
444
|
+
|
|
445
|
+
- 把二维码图片处理为微信可用图片 URL 后插入。
|
|
446
|
+
|
|
447
|
+
如果是 `qr-media-id`:
|
|
448
|
+
|
|
449
|
+
- 使用固定素材,但仍要在预览中说明使用了固定二维码素材。
|
|
450
|
+
|
|
451
|
+
签名区必须和正文有清楚分隔,但不要做成复杂卡片。
|
|
452
|
+
|
|
453
|
+
## Error Handling
|
|
454
|
+
|
|
455
|
+
遇到以下情况必须停止:
|
|
456
|
+
|
|
457
|
+
- 密钥为空、缺失、占位符或读取失败
|
|
458
|
+
- access token 获取失败
|
|
459
|
+
- 封面图缺失或上传失败
|
|
460
|
+
- 正文为空
|
|
461
|
+
- 图片上传失败且图片是正文关键内容
|
|
462
|
+
- 微信 API 返回非 0 `errcode`
|
|
463
|
+
- 用户没有确认上传
|
|
464
|
+
- 目标草稿 media_id 不存在或更新失败
|
|
465
|
+
|
|
466
|
+
停止时输出:
|
|
467
|
+
|
|
468
|
+
- 失败发生在哪一步
|
|
469
|
+
- 已完成哪些步骤
|
|
470
|
+
- 用户需要补充什么
|
|
471
|
+
- 是否可以从预览继续
|
|
472
|
+
|
|
473
|
+
## Output Format
|
|
474
|
+
|
|
475
|
+
每次执行结束时,按状态输出。
|
|
476
|
+
|
|
477
|
+
### 只完成预览
|
|
478
|
+
|
|
479
|
+
```markdown
|
|
480
|
+
## 公众号排版预览已生成
|
|
481
|
+
|
|
482
|
+
- 公众号:...
|
|
483
|
+
- 标题:...
|
|
484
|
+
- 摘要:...
|
|
485
|
+
- 封面图:已确认 / 待确认
|
|
486
|
+
- 正文图片:... 张,... 张已处理
|
|
487
|
+
- 预览文件:...
|
|
488
|
+
|
|
489
|
+
排版预览已完成,请确认是否上传到公众号草稿?
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### 已上传或已更新草稿
|
|
493
|
+
|
|
494
|
+
```markdown
|
|
495
|
+
## 公众号草稿已生成
|
|
496
|
+
|
|
497
|
+
- 公众号:...
|
|
498
|
+
- 标题:...
|
|
499
|
+
- 操作:新建草稿 / 更新草稿
|
|
500
|
+
- draft media_id:...
|
|
501
|
+
- 预览文件:...
|
|
502
|
+
- 图片处理:...
|
|
503
|
+
- 相关阅读:...
|
|
504
|
+
- 文末签名:...
|
|
505
|
+
|
|
506
|
+
请到公众号后台做最后人工检查,重点看封面、摘要、图片、链接和手机端排版。
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 当前环境不能上传
|
|
510
|
+
|
|
511
|
+
```markdown
|
|
512
|
+
## 当前环境只能生成预览
|
|
513
|
+
|
|
514
|
+
我已经生成公众号兼容 HTML 和上传检查清单,但当前 AI 工具不能直接调用微信公众号 API。
|
|
515
|
+
|
|
516
|
+
- 预览文件:...
|
|
517
|
+
- 需要手动处理:...
|
|
518
|
+
- 下一步:在支持脚本 / API 的环境中继续上传
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Quality Checklist
|
|
522
|
+
|
|
523
|
+
上传前检查:
|
|
524
|
+
|
|
525
|
+
- 标题、摘要、作者正确
|
|
526
|
+
- 摘要不是正文第一长段硬截断
|
|
527
|
+
- 封面图已确认
|
|
528
|
+
- 正文不是原始 Markdown
|
|
529
|
+
- 本地图片没有以本地路径出现在最终 HTML 里
|
|
530
|
+
- 链接是真实公开链接
|
|
531
|
+
- 相关阅读数量符合配置
|
|
532
|
+
- 签名或二维码符合配置
|
|
533
|
+
- HTML 没有依赖 JavaScript
|
|
534
|
+
- 关键结构没有依赖 flex / grid
|
|
535
|
+
- 已生成预览
|
|
536
|
+
- draft payload 使用完整正文,不是预览正文的截断版
|
|
537
|
+
- draft payload 中 `content` 的字符数和 hash 已与预览正文核对
|
|
538
|
+
- 正文开头、中部、结尾锚点片段都存在于 payload `content` 中
|
|
539
|
+
- 上传命令没有把长 HTML 手写进一行 `curl -d`,而是使用 JSON 文件或等价的安全序列化方式
|
|
540
|
+
- 用户已明确确认上传
|
|
541
|
+
- 没有输出 AppSecret、access token 或敏感请求信息
|
|
542
|
+
|
|
543
|
+
## One Sentence Principle
|
|
544
|
+
|
|
545
|
+
先把来源规范成一篇微信可读的稳定 HTML,预览确认后再上传;配置缺失就停,密钥不进文件,不能上传就不要假装上传。
|