@openschool_01/skills 0.1.1 → 0.1.3
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/bin/openschool-skills.js +7 -0
- package/bundled-skills/human-writing/package.json +12 -0
- package/bundled-skills/human-writing/skill/OPENSCHOOL-META.md +7 -0
- package/bundled-skills/human-writing/skill/SKILL.md +57 -0
- package/bundled-skills/human-writing/skill/references/rewrite-rules.md +22 -0
- package/bundled-skills/nano-banana-pro/package.json +12 -0
- package/bundled-skills/nano-banana-pro/skill/OPENSCHOOL-META.md +7 -0
- package/bundled-skills/nano-banana-pro/skill/SKILL.md +62 -0
- package/bundled-skills/nano-banana-pro/skill/references/prompt-rules.md +22 -0
- package/bundled-skills/wechat-assistant/package.json +12 -0
- package/bundled-skills/wechat-assistant/skill/OPENSCHOOL-META.md +19 -0
- package/bundled-skills/wechat-assistant/skill/SKILL.md +109 -0
- package/bundled-skills/wechat-assistant/skill/assets/wechat-article-template.html +26 -0
- package/bundled-skills/wechat-assistant/skill/config/wechat-style-config.json +7 -0
- package/bundled-skills/wechat-assistant/skill/references/title-guidelines.md +48 -0
- package/bundled-skills/wechat-assistant/skill/references/writing-rules.md +40 -0
- package/bundled-skills/wechat-assistant/skill/scripts/audit_article_quality.py +160 -0
- package/bundled-skills/wechat-assistant/skill/scripts/render_wechat_html.py +171 -0
- package/bundled-skills/xiaohongshu-assistant/package.json +12 -0
- package/bundled-skills/xiaohongshu-assistant/skill/OPENSCHOOL-META.md +14 -0
- package/bundled-skills/xiaohongshu-assistant/skill/SKILL.md +100 -0
- package/bundled-skills/xiaohongshu-assistant/skill/references/title-guidelines.md +36 -0
- package/bundled-skills/xiaohongshu-assistant/skill/references/writing-rules.md +33 -0
- package/package.json +2 -1
- package/registry/skills-registry.json +62 -0
package/bin/openschool-skills.js
CHANGED
|
@@ -11,6 +11,7 @@ const tar = require("tar");
|
|
|
11
11
|
const CLI_ROOT = path.resolve(__dirname, "..");
|
|
12
12
|
const REPO_ROOT = path.resolve(CLI_ROOT, "..", "..");
|
|
13
13
|
const BUNDLED_REGISTRY_PATH = path.join(CLI_ROOT, "registry", "skills-registry.json");
|
|
14
|
+
const BUNDLED_SKILLS_ROOT = path.join(CLI_ROOT, "bundled-skills");
|
|
14
15
|
const WORKSPACE_REGISTRY_PATH = path.join(REPO_ROOT, "registry", "skills-registry.json");
|
|
15
16
|
const NPM_EXECUTABLE = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
16
17
|
|
|
@@ -116,6 +117,12 @@ function writeSourceMeta(targetDir, meta) {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
function getLocalSkillDirectory(entry) {
|
|
120
|
+
const bundledSkillDir = path.join(BUNDLED_SKILLS_ROOT, entry.slug, "skill");
|
|
121
|
+
|
|
122
|
+
if (fs.existsSync(bundledSkillDir)) {
|
|
123
|
+
return bundledSkillDir;
|
|
124
|
+
}
|
|
125
|
+
|
|
119
126
|
if (!entry.localPackageDir) {
|
|
120
127
|
return null;
|
|
121
128
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Human Writing
|
|
2
|
+
|
|
3
|
+
适用于把 AI 生成的生硬文案,改成更像真人写的版本。
|
|
4
|
+
|
|
5
|
+
当前版本专注 4 件事:
|
|
6
|
+
|
|
7
|
+
1. 减少模板味和说明书腔
|
|
8
|
+
2. 增加真实判断和自然转场
|
|
9
|
+
3. 保留原信息,但让表达更顺
|
|
10
|
+
4. 让文案更适合内容平台直接使用
|
|
11
|
+
|
|
12
|
+
## 什么时候用
|
|
13
|
+
|
|
14
|
+
当用户要做这些事时使用:
|
|
15
|
+
|
|
16
|
+
- 让 AI 文案更像真人写的
|
|
17
|
+
- 给公众号文章去掉 AI 味
|
|
18
|
+
- 给小红书文案去掉模板感
|
|
19
|
+
- 把结构保留但把语气写活
|
|
20
|
+
|
|
21
|
+
## 推荐工作流
|
|
22
|
+
|
|
23
|
+
### 1. 先判断目标平台
|
|
24
|
+
|
|
25
|
+
优先确认:
|
|
26
|
+
|
|
27
|
+
- 是公众号
|
|
28
|
+
- 小红书
|
|
29
|
+
- 视频口播
|
|
30
|
+
- 普通说明文
|
|
31
|
+
|
|
32
|
+
### 2. 再判断要保留什么
|
|
33
|
+
|
|
34
|
+
优先保留:
|
|
35
|
+
|
|
36
|
+
- 核心结论
|
|
37
|
+
- 关键信息
|
|
38
|
+
- 必要步骤
|
|
39
|
+
|
|
40
|
+
优先改掉:
|
|
41
|
+
|
|
42
|
+
- 套话
|
|
43
|
+
- 重复解释
|
|
44
|
+
- 机械转场
|
|
45
|
+
- 空泛词
|
|
46
|
+
|
|
47
|
+
### 3. 输出时默认给两版
|
|
48
|
+
|
|
49
|
+
- 精修版
|
|
50
|
+
- 更口语一点的版本
|
|
51
|
+
|
|
52
|
+
## 输出要求
|
|
53
|
+
|
|
54
|
+
如果用户没有特别说明,默认补一段简短说明:
|
|
55
|
+
|
|
56
|
+
- 这次主要改掉了什么
|
|
57
|
+
- 哪一版更适合当前平台
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Nano Banana Pro
|
|
2
|
+
|
|
3
|
+
适用于把一段需求整理成更适合生图模型执行的成品提示词。
|
|
4
|
+
|
|
5
|
+
当前版本专注 4 件事:
|
|
6
|
+
|
|
7
|
+
1. 把模糊需求补成完整画面描述
|
|
8
|
+
2. 统一画风、构图、比例和细节层级
|
|
9
|
+
3. 生成主提示词、备选提示词和负面提示词
|
|
10
|
+
4. 让图片更适合海报、封面、宣传图这类场景
|
|
11
|
+
|
|
12
|
+
当前版本不负责:
|
|
13
|
+
|
|
14
|
+
- 直接调用生图网站
|
|
15
|
+
- 自动下载图片
|
|
16
|
+
- 自动排版进公众号或小红书
|
|
17
|
+
|
|
18
|
+
## 什么时候用
|
|
19
|
+
|
|
20
|
+
当用户要做这些事时使用:
|
|
21
|
+
|
|
22
|
+
- 生成海报图提示词
|
|
23
|
+
- 生成封面图提示词
|
|
24
|
+
- 生成产品宣传图提示词
|
|
25
|
+
- 优化一段生图需求
|
|
26
|
+
|
|
27
|
+
## 推荐工作流
|
|
28
|
+
|
|
29
|
+
### 1. 先明确用途
|
|
30
|
+
|
|
31
|
+
至少确认:
|
|
32
|
+
|
|
33
|
+
- 这张图要用在哪里
|
|
34
|
+
- 主要对象是什么
|
|
35
|
+
- 希望什么风格
|
|
36
|
+
- 想强调什么信息
|
|
37
|
+
|
|
38
|
+
### 2. 再补全关键维度
|
|
39
|
+
|
|
40
|
+
默认补齐这些信息:
|
|
41
|
+
|
|
42
|
+
- 画面主体
|
|
43
|
+
- 构图方式
|
|
44
|
+
- 色彩方向
|
|
45
|
+
- 比例
|
|
46
|
+
- 背景与氛围
|
|
47
|
+
- 细节限制
|
|
48
|
+
|
|
49
|
+
### 3. 默认输出三部分
|
|
50
|
+
|
|
51
|
+
- 主提示词
|
|
52
|
+
- 2 到 3 条可选变体
|
|
53
|
+
- 负面提示词
|
|
54
|
+
|
|
55
|
+
## 输出要求
|
|
56
|
+
|
|
57
|
+
如果用户没指定,默认一起给:
|
|
58
|
+
|
|
59
|
+
- 中文主提示词
|
|
60
|
+
- 英文高质量版提示词
|
|
61
|
+
- 负面提示词
|
|
62
|
+
- 一句使用建议
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 公众号助手
|
|
2
|
+
|
|
3
|
+
适用于把一个选题整理成可发布到微信公众号的完整内容稿。
|
|
4
|
+
|
|
5
|
+
当前版本专注 4 件事:
|
|
6
|
+
|
|
7
|
+
1. 明确选题和读者视角
|
|
8
|
+
2. 生成更像公众号风格的标题方向
|
|
9
|
+
3. 写出 Markdown 正文
|
|
10
|
+
4. 渲染成微信公众号可用的 HTML
|
|
11
|
+
|
|
12
|
+
这不是一个“全自动发布器”。当前版本不负责:
|
|
13
|
+
|
|
14
|
+
- 生图
|
|
15
|
+
- 封面生成
|
|
16
|
+
- 微信草稿箱发布
|
|
17
|
+
|
|
18
|
+
## 什么时候用
|
|
19
|
+
|
|
20
|
+
当用户要做这些事时使用:
|
|
21
|
+
|
|
22
|
+
- 写公众号文章
|
|
23
|
+
- 把直播内容整理成公众号稿
|
|
24
|
+
- 生成公众号标题
|
|
25
|
+
- 输出微信公众号排版版式的 HTML
|
|
26
|
+
- 检查文章是否适合公众号发布
|
|
27
|
+
|
|
28
|
+
## 推荐工作流
|
|
29
|
+
|
|
30
|
+
### 1. 先收集输入
|
|
31
|
+
|
|
32
|
+
至少确认这 4 个信息:
|
|
33
|
+
|
|
34
|
+
- 文章主题
|
|
35
|
+
- 目标读者
|
|
36
|
+
- 读者当前最常见的问题
|
|
37
|
+
- 这篇文章承诺解决什么
|
|
38
|
+
|
|
39
|
+
### 2. 先给标题方向
|
|
40
|
+
|
|
41
|
+
优先输出 3 到 5 个标题,并解释它们分别适合:
|
|
42
|
+
|
|
43
|
+
- 偏方法拆解
|
|
44
|
+
- 偏观点判断
|
|
45
|
+
- 偏案例复盘
|
|
46
|
+
|
|
47
|
+
标题规则见:
|
|
48
|
+
|
|
49
|
+
- `references/title-guidelines.md`
|
|
50
|
+
|
|
51
|
+
### 3. 再写正文
|
|
52
|
+
|
|
53
|
+
正文默认要求:
|
|
54
|
+
|
|
55
|
+
- 使用 4 到 6 个二级标题
|
|
56
|
+
- 先给判断,再展开解释
|
|
57
|
+
- 至少包含 1 个真实场景
|
|
58
|
+
- 至少包含 1 个常见误区
|
|
59
|
+
- 至少包含 1 组可执行动作
|
|
60
|
+
- 结尾自然引向下一篇或下一步动作
|
|
61
|
+
|
|
62
|
+
写作约束见:
|
|
63
|
+
|
|
64
|
+
- `references/writing-rules.md`
|
|
65
|
+
|
|
66
|
+
### 4. 渲染 HTML
|
|
67
|
+
|
|
68
|
+
正文 Markdown 完成后,运行:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python scripts/render_wechat_html.py --input "<article.md>" --output "<article>.html"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 5. 做一次质量检查
|
|
75
|
+
|
|
76
|
+
如果用户在意发布质量,再运行:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python scripts/audit_article_quality.py --input "<article.md>"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 目录说明
|
|
83
|
+
|
|
84
|
+
- `references/title-guidelines.md`
|
|
85
|
+
标题风格和筛选规则
|
|
86
|
+
- `references/writing-rules.md`
|
|
87
|
+
正文写作规则和结构要求
|
|
88
|
+
- `assets/wechat-article-template.html`
|
|
89
|
+
微信 HTML 模板
|
|
90
|
+
- `config/wechat-style-config.json`
|
|
91
|
+
样式配置
|
|
92
|
+
- `scripts/render_wechat_html.py`
|
|
93
|
+
Markdown 转微信 HTML
|
|
94
|
+
- `scripts/audit_article_quality.py`
|
|
95
|
+
质量检查脚本
|
|
96
|
+
|
|
97
|
+
## 交付要求
|
|
98
|
+
|
|
99
|
+
默认交付:
|
|
100
|
+
|
|
101
|
+
- 3 到 5 个标题备选
|
|
102
|
+
- 1 篇 Markdown 正文
|
|
103
|
+
- 1 个 HTML 文件
|
|
104
|
+
|
|
105
|
+
如果脚本已经执行,还要明确告诉用户:
|
|
106
|
+
|
|
107
|
+
- Markdown 文件路径
|
|
108
|
+
- HTML 文件路径
|
|
109
|
+
- 质量检查结果
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>{{TITLE}}</title>
|
|
7
|
+
<style>
|
|
8
|
+
{{STYLE}}
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<main class="wx-wrap">
|
|
13
|
+
<article class="wx-article">
|
|
14
|
+
<header class="wx-header">
|
|
15
|
+
<h1 class="wx-title">{{TITLE}}</h1>
|
|
16
|
+
{{SUBTITLE_BLOCK}}
|
|
17
|
+
</header>
|
|
18
|
+
<section class="wx-content">
|
|
19
|
+
{{CONTENT}}
|
|
20
|
+
</section>
|
|
21
|
+
{{CTA_BLOCK}}
|
|
22
|
+
{{FOOTER_BLOCK}}
|
|
23
|
+
</article>
|
|
24
|
+
</main>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# 标题规则
|
|
2
|
+
|
|
3
|
+
公众号标题优先追求“清楚、可信、能打开”,不是追求夸张。
|
|
4
|
+
|
|
5
|
+
## 默认输出 3 类标题
|
|
6
|
+
|
|
7
|
+
### 1. 观点判断型
|
|
8
|
+
|
|
9
|
+
适合表达一个明确判断。
|
|
10
|
+
|
|
11
|
+
示例结构:
|
|
12
|
+
|
|
13
|
+
- 为什么我现在更建议先做 X,再做 Y
|
|
14
|
+
- 真正拉开差距的,不是 X,而是 Y
|
|
15
|
+
|
|
16
|
+
### 2. 方法拆解型
|
|
17
|
+
|
|
18
|
+
适合教程、流程、经验总结。
|
|
19
|
+
|
|
20
|
+
示例结构:
|
|
21
|
+
|
|
22
|
+
- 我现在做公众号内容,基本只用这 4 步
|
|
23
|
+
- 从选题到成稿,我现在固定这样写公众号
|
|
24
|
+
|
|
25
|
+
### 3. 复盘案例型
|
|
26
|
+
|
|
27
|
+
适合直播复盘、项目拆解、过程型内容。
|
|
28
|
+
|
|
29
|
+
示例结构:
|
|
30
|
+
|
|
31
|
+
- 这场直播做完后,我把整个流程又重搭了一遍
|
|
32
|
+
- 我复盘了今天的内容链路,发现问题不在写作本身
|
|
33
|
+
|
|
34
|
+
## 标题筛选规则
|
|
35
|
+
|
|
36
|
+
- 不要震惊体
|
|
37
|
+
- 不要故意制造焦虑
|
|
38
|
+
- 不要只有概念没有对象
|
|
39
|
+
- 要让读者一眼知道这篇内容大概讲什么
|
|
40
|
+
- 最好能带一个具体场景、对象或判断
|
|
41
|
+
|
|
42
|
+
## 输出建议
|
|
43
|
+
|
|
44
|
+
默认输出 3 到 5 个标题,并补一句:
|
|
45
|
+
|
|
46
|
+
- 哪个更适合当前主题
|
|
47
|
+
- 哪个更适合偏增长
|
|
48
|
+
- 哪个更适合偏专业感
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 写作规则
|
|
2
|
+
|
|
3
|
+
## 默认结构
|
|
4
|
+
|
|
5
|
+
1. 开头先给判断
|
|
6
|
+
2. 解释为什么今天值得讲这件事
|
|
7
|
+
3. 讲清楚这件事到底是什么
|
|
8
|
+
4. 讲清楚为什么大家容易做错
|
|
9
|
+
5. 给出一套能直接执行的方法
|
|
10
|
+
6. 用边界和下一步收尾
|
|
11
|
+
|
|
12
|
+
## 文字要求
|
|
13
|
+
|
|
14
|
+
- 用连续叙述,不写成培训提纲
|
|
15
|
+
- 默认 1600 到 2800 字
|
|
16
|
+
- 使用 4 到 6 个 `##` 二级标题
|
|
17
|
+
- 每节至少两段自然段
|
|
18
|
+
- 列表尽量少,只在确实需要步骤时使用
|
|
19
|
+
|
|
20
|
+
## 内容要求
|
|
21
|
+
|
|
22
|
+
- 至少 1 个真实场景
|
|
23
|
+
- 至少 1 个常见误区
|
|
24
|
+
- 至少 1 组可执行动作
|
|
25
|
+
- 至少 1 个明确判断
|
|
26
|
+
- 结尾不能只剩 CTA
|
|
27
|
+
|
|
28
|
+
## 风格要求
|
|
29
|
+
|
|
30
|
+
- 专业,但不要端着
|
|
31
|
+
- 有判断,但不要喊口号
|
|
32
|
+
- 可以有第一人称经验
|
|
33
|
+
- 不要低质口癖
|
|
34
|
+
- 不要泛泛而谈“提效、闭环、赋能”
|
|
35
|
+
|
|
36
|
+
## 输出约束
|
|
37
|
+
|
|
38
|
+
- 正文用 Markdown
|
|
39
|
+
- 重要判断句用 `**加粗**`
|
|
40
|
+
- 如果有命令、配置、提示词,放进 fenced code block
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Heuristic quality gate for WeChat article markdown."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
BAD_SLANG = [
|
|
13
|
+
"家人们",
|
|
14
|
+
"绝绝子",
|
|
15
|
+
"冲就完了",
|
|
16
|
+
"yyds",
|
|
17
|
+
"哈哈哈",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
TRANSITIONS = [
|
|
21
|
+
"先说结论",
|
|
22
|
+
"更关键的是",
|
|
23
|
+
"换句话说",
|
|
24
|
+
"问题在于",
|
|
25
|
+
"所以",
|
|
26
|
+
"接下来",
|
|
27
|
+
"最后",
|
|
28
|
+
"这意味着",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
SCENE_MARKERS = [
|
|
32
|
+
"比如",
|
|
33
|
+
"例如",
|
|
34
|
+
"我自己",
|
|
35
|
+
"我实测",
|
|
36
|
+
"这周",
|
|
37
|
+
"上周",
|
|
38
|
+
"项目里",
|
|
39
|
+
"直播间",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
ACTION_MARKERS = [
|
|
43
|
+
"可以先",
|
|
44
|
+
"建议你",
|
|
45
|
+
"下一步",
|
|
46
|
+
"按这三步",
|
|
47
|
+
"立刻",
|
|
48
|
+
"先做",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def normalize(markdown: str) -> str:
|
|
53
|
+
markdown = re.sub(r"```[\s\S]*?```", "", markdown)
|
|
54
|
+
markdown = re.sub(r"!\[[^\]]*]\([^)]+\)", "", markdown)
|
|
55
|
+
return markdown
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def split_paragraphs(markdown: str) -> list[str]:
|
|
59
|
+
chunks = re.split(r"\n\s*\n", markdown)
|
|
60
|
+
paragraphs = []
|
|
61
|
+
for chunk in chunks:
|
|
62
|
+
text = chunk.strip()
|
|
63
|
+
if not text:
|
|
64
|
+
continue
|
|
65
|
+
if text.startswith("#") or text.startswith(">") or text == "---":
|
|
66
|
+
continue
|
|
67
|
+
if re.match(r"^\s*(?:[-*]|\d+\.)\s+", text):
|
|
68
|
+
continue
|
|
69
|
+
paragraphs.append(text)
|
|
70
|
+
return paragraphs
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def count_chinese_chars(text: str) -> int:
|
|
74
|
+
return len(re.findall(r"[\u4e00-\u9fff]", text))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def build_report(markdown: str) -> dict:
|
|
78
|
+
text = normalize(markdown)
|
|
79
|
+
lines = text.splitlines()
|
|
80
|
+
non_empty = [line for line in lines if line.strip()]
|
|
81
|
+
list_lines = [line for line in lines if re.match(r"^\s*(?:[-*]|\d+\.)\s+", line)]
|
|
82
|
+
h2_headings = [line for line in lines if re.match(r"^\s*##\s+", line)]
|
|
83
|
+
paragraphs = split_paragraphs(text)
|
|
84
|
+
|
|
85
|
+
zh_chars = count_chinese_chars(text)
|
|
86
|
+
list_ratio = (len(list_lines) / len(non_empty)) if non_empty else 0
|
|
87
|
+
short_paragraphs = [paragraph for paragraph in paragraphs if count_chinese_chars(re.sub(r"\s+", "", paragraph)) < 60]
|
|
88
|
+
|
|
89
|
+
transition_hits = sum(text.count(item) for item in TRANSITIONS)
|
|
90
|
+
scene_hits = sum(text.count(item) for item in SCENE_MARKERS)
|
|
91
|
+
action_hits = sum(text.count(item) for item in ACTION_MARKERS)
|
|
92
|
+
slang_hits = [item for item in BAD_SLANG if item in text]
|
|
93
|
+
|
|
94
|
+
has_what = ("是什么" in text) or ("这件事是" in text) or ("你可以把它理解成" in text)
|
|
95
|
+
has_why = ("为什么" in text) or ("原因" in text) or ("问题在于" in text)
|
|
96
|
+
has_how = ("怎么做" in text) or ("可以先" in text) or ("建议你" in text) or ("下一步" in text)
|
|
97
|
+
has_next = ("下一篇" in text) or ("下篇" in text) or ("下一步" in text)
|
|
98
|
+
punctuation_burst = bool(re.search(r"[!?!?]{3,}", text))
|
|
99
|
+
|
|
100
|
+
suggestions: list[str] = []
|
|
101
|
+
if zh_chars < 1200:
|
|
102
|
+
suggestions.append("正文偏短,建议补到至少 1200 个中文字符。")
|
|
103
|
+
if list_ratio > 0.2:
|
|
104
|
+
suggestions.append("列表密度偏高,建议改写成更多自然段。")
|
|
105
|
+
if len(h2_headings) < 4:
|
|
106
|
+
suggestions.append("结构层级偏少,建议补足 4 到 6 个二级标题。")
|
|
107
|
+
if transition_hits < 4:
|
|
108
|
+
suggestions.append("章节承接偏弱,可以增加“先说结论、问题在于、换句话说”这类过渡句。")
|
|
109
|
+
if scene_hits < 1:
|
|
110
|
+
suggestions.append("缺少真实场景,建议补一个直播、项目或实测片段。")
|
|
111
|
+
if action_hits < 1:
|
|
112
|
+
suggestions.append("缺少可执行动作,建议补一组能直接照做的步骤。")
|
|
113
|
+
if slang_hits:
|
|
114
|
+
suggestions.append(f"存在口语化过强表达:{', '.join(slang_hits)}。")
|
|
115
|
+
if not has_next:
|
|
116
|
+
suggestions.append("结尾缺少下一步引导,建议加一句自然收束。")
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
"metrics": {
|
|
120
|
+
"zh_chars": zh_chars,
|
|
121
|
+
"h2_count": len(h2_headings),
|
|
122
|
+
"paragraph_count": len(paragraphs),
|
|
123
|
+
"short_paragraph_count": len(short_paragraphs),
|
|
124
|
+
"list_line_count": len(list_lines),
|
|
125
|
+
"list_ratio": round(list_ratio, 3),
|
|
126
|
+
"transition_hits": transition_hits,
|
|
127
|
+
"scene_hits": scene_hits,
|
|
128
|
+
"action_hits": action_hits,
|
|
129
|
+
"slang_hits": slang_hits,
|
|
130
|
+
"punctuation_burst": punctuation_burst
|
|
131
|
+
},
|
|
132
|
+
"gates": {
|
|
133
|
+
"logic": "PASS" if has_what and has_why and has_how and len(h2_headings) >= 4 else "FAIL",
|
|
134
|
+
"humanity": "PASS" if scene_hits >= 1 and action_hits >= 1 else "FAIL",
|
|
135
|
+
"language": "PASS" if not slang_hits and not punctuation_burst else "FAIL",
|
|
136
|
+
"publish_ready": "PASS" if has_next and zh_chars >= 1200 and list_ratio <= 0.2 else "FAIL"
|
|
137
|
+
},
|
|
138
|
+
"suggestions": suggestions
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main() -> None:
|
|
143
|
+
parser = argparse.ArgumentParser()
|
|
144
|
+
parser.add_argument("--input", required=True)
|
|
145
|
+
parser.add_argument("--output", default="")
|
|
146
|
+
args = parser.parse_args()
|
|
147
|
+
|
|
148
|
+
markdown = Path(args.input).read_text(encoding="utf-8")
|
|
149
|
+
report = build_report(markdown)
|
|
150
|
+
output = json.dumps(report, ensure_ascii=False, indent=2)
|
|
151
|
+
print(output)
|
|
152
|
+
|
|
153
|
+
if args.output:
|
|
154
|
+
output_path = Path(args.output)
|
|
155
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
output_path.write_text(output, encoding="utf-8")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
main()
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render markdown into WeChat-friendly article HTML."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import html
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
DEFAULT_STYLE = """
|
|
14
|
+
body { margin: 0; background: #ffffff; color: #111111; font-family: "PingFang SC", "Microsoft YaHei", sans-serif; }
|
|
15
|
+
.wx-wrap { max-width: 720px; margin: 0 auto; padding: 24px 18px 48px; box-sizing: border-box; }
|
|
16
|
+
.wx-title { margin: 0 0 12px; font-size: 28px; line-height: 1.35; font-weight: 700; color: #111111; }
|
|
17
|
+
.wx-subtitle { margin: 0 0 20px; font-size: 15px; line-height: 1.8; color: #666666; }
|
|
18
|
+
.wx-content p, .wx-content li { margin: 0 0 14px; font-size: 16px; line-height: 1.8; color: #111111; text-align: justify; text-justify: inter-ideograph; word-break: break-word; }
|
|
19
|
+
.wx-content h2, .wx-content h3 { margin: 26px 0 10px; font-size: 20px; line-height: 1.6; font-weight: 700; color: rgb(0,128,255); }
|
|
20
|
+
.wx-content strong { color: rgb(0,128,255); font-weight: 700; }
|
|
21
|
+
.wx-content ul, .wx-content ol { margin: 0 0 14px 1.4em; padding: 0; }
|
|
22
|
+
.wx-content pre { margin: 0 0 18px; padding: 14px 16px; background: #f7f8fa; border-left: 3px solid rgba(0,128,255,.45); overflow-x: auto; border-radius: 6px; }
|
|
23
|
+
.wx-content code { font-size: 14px; line-height: 1.7; color: #333333; font-family: "Consolas", "Menlo", monospace; white-space: pre-wrap; }
|
|
24
|
+
.wx-sep { margin: 12px 0 16px; font-size: 16px; line-height: 1.8; color: #999999; text-align: center; }
|
|
25
|
+
.wx-cta { margin-top: 28px; padding-top: 16px; border-top: 1px solid #ececec; }
|
|
26
|
+
.wx-cta p { margin: 0; font-size: 15px; line-height: 1.8; color: #444444; }
|
|
27
|
+
.wx-footer { margin-top: 44px; padding-top: 18px; border-top: 1px solid #f0f0f0; }
|
|
28
|
+
.wx-footer p { margin: 0; font-size: 13px; line-height: 1.9; color: #a0a0a0; text-align: center; }
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_style(config_path: Path | None) -> str:
|
|
33
|
+
if not config_path or not config_path.exists():
|
|
34
|
+
return DEFAULT_STYLE
|
|
35
|
+
|
|
36
|
+
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
37
|
+
body_font_size = int(config.get("bodyFontSize", 16))
|
|
38
|
+
body_line_height = float(config.get("bodyLineHeight", 1.8))
|
|
39
|
+
content_width = int(config.get("contentWidth", 720))
|
|
40
|
+
accent = str(config.get("accentColor", "rgb(0,128,255)"))
|
|
41
|
+
text = str(config.get("textColor", "#111111"))
|
|
42
|
+
|
|
43
|
+
return DEFAULT_STYLE.replace("720px", f"{content_width}px").replace(
|
|
44
|
+
"rgb(0,128,255)", accent
|
|
45
|
+
).replace("#111111", text).replace("font-size: 16px; line-height: 1.8;", f"font-size: {body_font_size}px; line-height: {body_line_height};")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def convert_inline(text: str) -> str:
|
|
49
|
+
text = re.sub(r"!\[\[[^\]]+\]\]", "", text)
|
|
50
|
+
text = html.escape(text)
|
|
51
|
+
text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
|
|
52
|
+
text = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", text)
|
|
53
|
+
text = re.sub(r"\*([^*]+)\*", r"<em>\1</em>", text)
|
|
54
|
+
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'<a href="\2">\1</a>', text)
|
|
55
|
+
return text
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def markdown_to_blocks(content: str) -> tuple[str, str, bool]:
|
|
59
|
+
lines = content.splitlines()
|
|
60
|
+
title = ""
|
|
61
|
+
blocks: list[str] = []
|
|
62
|
+
in_code = False
|
|
63
|
+
code_lines: list[str] = []
|
|
64
|
+
paragraph_lines: list[str] = []
|
|
65
|
+
has_cta = False
|
|
66
|
+
|
|
67
|
+
def flush_paragraph() -> None:
|
|
68
|
+
nonlocal paragraph_lines, has_cta
|
|
69
|
+
if not paragraph_lines:
|
|
70
|
+
return
|
|
71
|
+
text = re.sub(r"\s+", " ", " ".join(line.strip() for line in paragraph_lines if line.strip())).strip()
|
|
72
|
+
paragraph_lines = []
|
|
73
|
+
if not text:
|
|
74
|
+
return
|
|
75
|
+
if any(keyword in text for keyword in ["留言", "评论", "下一篇", "下篇", "领取"]):
|
|
76
|
+
has_cta = True
|
|
77
|
+
blocks.append(f"<p>{convert_inline(text)}</p>")
|
|
78
|
+
|
|
79
|
+
for raw_line in lines:
|
|
80
|
+
line = raw_line.rstrip()
|
|
81
|
+
stripped = line.strip()
|
|
82
|
+
|
|
83
|
+
if stripped.startswith("```"):
|
|
84
|
+
flush_paragraph()
|
|
85
|
+
if in_code:
|
|
86
|
+
blocks.append("<pre><code>" + html.escape("\n".join(code_lines)) + "</code></pre>")
|
|
87
|
+
code_lines = []
|
|
88
|
+
in_code = False
|
|
89
|
+
else:
|
|
90
|
+
in_code = True
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if in_code:
|
|
94
|
+
code_lines.append(line)
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
if not stripped:
|
|
98
|
+
flush_paragraph()
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
if not title and stripped.startswith("# "):
|
|
102
|
+
title = stripped[2:].strip()
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if stripped.startswith("## "):
|
|
106
|
+
flush_paragraph()
|
|
107
|
+
blocks.append(f"<h2>{convert_inline(stripped[3:])}</h2>")
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if stripped.startswith("### "):
|
|
111
|
+
flush_paragraph()
|
|
112
|
+
blocks.append(f"<h3>{convert_inline(stripped[4:])}</h3>")
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
if re.match(r"^\d+\.\s+", stripped):
|
|
116
|
+
flush_paragraph()
|
|
117
|
+
blocks.append("<ol><li>" + convert_inline(re.sub(r"^\d+\.\s+", "", stripped)) + "</li></ol>")
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
if re.match(r"^[-*]\s+", stripped):
|
|
121
|
+
flush_paragraph()
|
|
122
|
+
blocks.append("<ul><li>" + convert_inline(re.sub(r"^[-*]\s+", "", stripped)) + "</li></ul>")
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
if stripped == "---":
|
|
126
|
+
flush_paragraph()
|
|
127
|
+
blocks.append('<p class="wx-sep">···</p>')
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
paragraph_lines.append(stripped)
|
|
131
|
+
|
|
132
|
+
flush_paragraph()
|
|
133
|
+
return title, "\n".join(blocks), has_cta
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def render_html(markdown: str, title_override: str, style_config_path: Path | None) -> str:
|
|
137
|
+
title, body, has_cta = markdown_to_blocks(markdown)
|
|
138
|
+
title = title_override or title or "公众号文章"
|
|
139
|
+
template_path = Path(__file__).resolve().parent.parent / "assets" / "wechat-article-template.html"
|
|
140
|
+
template = template_path.read_text(encoding="utf-8")
|
|
141
|
+
cta = "" if has_cta else '<footer class="wx-cta"><p>如果这篇内容对你有帮助,下一步可以继续把同主题素材整理成系列文章。</p></footer>'
|
|
142
|
+
footer = '<footer class="wx-footer"><p>由 OpenSchool 严选技能整理输出</p></footer>'
|
|
143
|
+
subtitle_block = ""
|
|
144
|
+
|
|
145
|
+
output = template.replace("{{TITLE}}", html.escape(title))
|
|
146
|
+
output = output.replace("{{SUBTITLE_BLOCK}}", subtitle_block)
|
|
147
|
+
output = output.replace("{{STYLE}}", load_style(style_config_path).strip())
|
|
148
|
+
output = output.replace("{{CONTENT}}", body)
|
|
149
|
+
output = output.replace("{{CTA_BLOCK}}", cta)
|
|
150
|
+
output = output.replace("{{FOOTER_BLOCK}}", footer)
|
|
151
|
+
return output
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main() -> None:
|
|
155
|
+
parser = argparse.ArgumentParser()
|
|
156
|
+
parser.add_argument("--input", required=True)
|
|
157
|
+
parser.add_argument("--output", required=True)
|
|
158
|
+
parser.add_argument("--title", default="")
|
|
159
|
+
parser.add_argument("--style-config", default="")
|
|
160
|
+
args = parser.parse_args()
|
|
161
|
+
|
|
162
|
+
style_config_path = Path(args.style_config) if args.style_config else Path(__file__).resolve().parent.parent / "config" / "wechat-style-config.json"
|
|
163
|
+
markdown = Path(args.input).read_text(encoding="utf-8")
|
|
164
|
+
output = render_html(markdown, args.title, style_config_path)
|
|
165
|
+
output_path = Path(args.output)
|
|
166
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
output_path.write_text(output, encoding="utf-8")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
main()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 小红书助手
|
|
2
|
+
|
|
3
|
+
适用于把一个主题、直播片段、公众号原稿或观点素材,整理成更适合小红书发布的内容。
|
|
4
|
+
|
|
5
|
+
当前版本专注 4 件事:
|
|
6
|
+
|
|
7
|
+
1. 明确小红书切角
|
|
8
|
+
2. 生成更像平台风格的标题方向
|
|
9
|
+
3. 输出可直接发布的正文
|
|
10
|
+
4. 补齐标签、封面文案和评论区引导
|
|
11
|
+
|
|
12
|
+
当前版本不负责:
|
|
13
|
+
|
|
14
|
+
- 生图
|
|
15
|
+
- 封面出图
|
|
16
|
+
- 自动发布
|
|
17
|
+
|
|
18
|
+
## 什么时候用
|
|
19
|
+
|
|
20
|
+
当用户要做这些事时使用:
|
|
21
|
+
|
|
22
|
+
- 把公众号内容改成小红书
|
|
23
|
+
- 写小红书标题
|
|
24
|
+
- 生成小红书正文
|
|
25
|
+
- 做一版更适合平台传播的内容重写
|
|
26
|
+
|
|
27
|
+
## 推荐工作流
|
|
28
|
+
|
|
29
|
+
### 1. 先收集输入
|
|
30
|
+
|
|
31
|
+
至少确认:
|
|
32
|
+
|
|
33
|
+
- 原始主题或素材
|
|
34
|
+
- 目标读者
|
|
35
|
+
- 这次最想强调的结论
|
|
36
|
+
- 希望走哪种内容逻辑
|
|
37
|
+
|
|
38
|
+
### 2. 先定传播逻辑
|
|
39
|
+
|
|
40
|
+
默认优先在这几类里选一个主逻辑:
|
|
41
|
+
|
|
42
|
+
- 误区避坑
|
|
43
|
+
- 保姆级步骤
|
|
44
|
+
- 强观点判断
|
|
45
|
+
- 案例复盘
|
|
46
|
+
- 清单速看
|
|
47
|
+
|
|
48
|
+
一次只保留一个主逻辑,不要把一篇小红书写成什么都想讲的说明文。
|
|
49
|
+
|
|
50
|
+
### 3. 再出标题
|
|
51
|
+
|
|
52
|
+
默认输出 5 个标题备选,并覆盖:
|
|
53
|
+
|
|
54
|
+
- 警告拦截型
|
|
55
|
+
- 数字步骤型
|
|
56
|
+
- 观点判断型
|
|
57
|
+
|
|
58
|
+
标题规则见:
|
|
59
|
+
|
|
60
|
+
- `references/title-guidelines.md`
|
|
61
|
+
|
|
62
|
+
### 4. 再写正文
|
|
63
|
+
|
|
64
|
+
正文默认要求:
|
|
65
|
+
|
|
66
|
+
- 开头前两句就给钩子和结论
|
|
67
|
+
- 用短段落,控制阅读压力
|
|
68
|
+
- 优先写误区、建议、步骤和判断
|
|
69
|
+
- 结尾补评论区互动问题
|
|
70
|
+
|
|
71
|
+
写作规则见:
|
|
72
|
+
|
|
73
|
+
- `references/writing-rules.md`
|
|
74
|
+
|
|
75
|
+
### 5. 最终补齐发布要素
|
|
76
|
+
|
|
77
|
+
默认交付:
|
|
78
|
+
|
|
79
|
+
- 5 个标题备选
|
|
80
|
+
- 1 篇可直接发布的正文
|
|
81
|
+
- 3 条封面文案
|
|
82
|
+
- 8 到 12 个标签
|
|
83
|
+
- 3 条评论区引导
|
|
84
|
+
|
|
85
|
+
## 目录说明
|
|
86
|
+
|
|
87
|
+
- `references/title-guidelines.md`
|
|
88
|
+
标题方向规则
|
|
89
|
+
- `references/writing-rules.md`
|
|
90
|
+
正文改写规则
|
|
91
|
+
|
|
92
|
+
## 交付要求
|
|
93
|
+
|
|
94
|
+
如果没有特别说明,默认用中文直接输出:
|
|
95
|
+
|
|
96
|
+
- 标题备选
|
|
97
|
+
- 正文
|
|
98
|
+
- 封面文案
|
|
99
|
+
- 标签建议
|
|
100
|
+
- 评论区引导
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 标题规则
|
|
2
|
+
|
|
3
|
+
小红书标题优先追求“停下来、看下去、愿意点开”,不是写成说明书标题。
|
|
4
|
+
|
|
5
|
+
## 默认输出 5 个标题
|
|
6
|
+
|
|
7
|
+
至少覆盖这三类:
|
|
8
|
+
|
|
9
|
+
### 1. 警告拦截型
|
|
10
|
+
|
|
11
|
+
示例:
|
|
12
|
+
|
|
13
|
+
- 别一上来就这样做
|
|
14
|
+
- 新手最容易踩坑的,其实是这一步
|
|
15
|
+
|
|
16
|
+
### 2. 数字步骤型
|
|
17
|
+
|
|
18
|
+
示例:
|
|
19
|
+
|
|
20
|
+
- 我现在只用这 3 步写小红书
|
|
21
|
+
- 1 分钟看懂这件事到底怎么做
|
|
22
|
+
|
|
23
|
+
### 3. 观点判断型
|
|
24
|
+
|
|
25
|
+
示例:
|
|
26
|
+
|
|
27
|
+
- 真正拉开差距的,不是会不会写,而是会不会切角
|
|
28
|
+
- 很多人不是不会做内容,是一开始就写偏了
|
|
29
|
+
|
|
30
|
+
## 标题筛选规则
|
|
31
|
+
|
|
32
|
+
- 优先 14 到 20 字
|
|
33
|
+
- 一眼能看懂在讲什么
|
|
34
|
+
- 不要空泛“干货、分享、教程”
|
|
35
|
+
- 可以有轻度情绪,但不要过度夸张
|
|
36
|
+
- 优先带对象、场景、结果或判断
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 写作规则
|
|
2
|
+
|
|
3
|
+
## 默认结构
|
|
4
|
+
|
|
5
|
+
1. 开头两句完成钩子和结论
|
|
6
|
+
2. 中间用 3 到 5 个短段落展开
|
|
7
|
+
3. 优先写误区、建议、步骤、判断
|
|
8
|
+
4. 结尾用一句总结加一个互动问题收尾
|
|
9
|
+
|
|
10
|
+
## 内容要求
|
|
11
|
+
|
|
12
|
+
- 正文尽量控制在 280 到 520 字
|
|
13
|
+
- 复杂主题也尽量不要超过 1000 字
|
|
14
|
+
- 每段 1 到 3 行
|
|
15
|
+
- 一次只保留一个主逻辑
|
|
16
|
+
- 至少有一个可直接执行动作
|
|
17
|
+
|
|
18
|
+
## 风格要求
|
|
19
|
+
|
|
20
|
+
- 更像真实用户分享
|
|
21
|
+
- 少用说明文腔调
|
|
22
|
+
- 少铺背景,多给结论
|
|
23
|
+
- 少讲大道理,多给具体判断
|
|
24
|
+
|
|
25
|
+
## 输出要求
|
|
26
|
+
|
|
27
|
+
默认输出:
|
|
28
|
+
|
|
29
|
+
- 5 个标题
|
|
30
|
+
- 1 版正文
|
|
31
|
+
- 3 条封面文案
|
|
32
|
+
- 8 到 12 个标签
|
|
33
|
+
- 3 条评论区互动引导
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openschool_01/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "OpenSchool curated skills installer CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
+
"bundled-skills",
|
|
11
12
|
"registry",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
@@ -532,5 +532,67 @@
|
|
|
532
532
|
],
|
|
533
533
|
"sourceType": "official",
|
|
534
534
|
"localPackageDir": "packages/skills/xiaohongshu-assistant"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"slug": "nano-banana-pro",
|
|
538
|
+
"name": "Nano Banana Pro",
|
|
539
|
+
"tagline": "把海报、封面和宣传图需求整理成更稳的生图提示词。",
|
|
540
|
+
"category": "content",
|
|
541
|
+
"badge": "OpenSchool 推荐",
|
|
542
|
+
"status": "已上线",
|
|
543
|
+
"difficulty": "进阶",
|
|
544
|
+
"installCommand": "npx @openschool_01/skills install nano-banana-pro",
|
|
545
|
+
"packageName": "@openschool_01/skill-nano-banana-pro",
|
|
546
|
+
"summary": "适合做海报、封面、宣传图和产品视觉的提示词整理,把模糊需求补成更容易出图的一版完整提示词。",
|
|
547
|
+
"outcomes": [
|
|
548
|
+
"主提示词生成",
|
|
549
|
+
"风格变体提示词",
|
|
550
|
+
"负面提示词",
|
|
551
|
+
"封面图需求整理"
|
|
552
|
+
],
|
|
553
|
+
"fitFor": [
|
|
554
|
+
"公众号封面",
|
|
555
|
+
"小红书配图",
|
|
556
|
+
"宣传海报",
|
|
557
|
+
"需要稳定生图提示词的内容团队"
|
|
558
|
+
],
|
|
559
|
+
"updatedAt": "2026-04-10",
|
|
560
|
+
"prerequisites": [
|
|
561
|
+
"当前版本是提示词版,不直接调用生图网站",
|
|
562
|
+
"适合先产出成品提示词再去即梦、可灵、Midjourney 等工具使用"
|
|
563
|
+
],
|
|
564
|
+
"sourceType": "official",
|
|
565
|
+
"localPackageDir": "packages/skills/nano-banana-pro"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
"slug": "human-writing",
|
|
569
|
+
"name": "Human Writing",
|
|
570
|
+
"tagline": "把 AI 文案改成更像真人写的版本,少一点模板味,多一点判断感。",
|
|
571
|
+
"category": "content",
|
|
572
|
+
"badge": "OpenSchool 推荐",
|
|
573
|
+
"status": "已上线",
|
|
574
|
+
"difficulty": "进阶",
|
|
575
|
+
"installCommand": "npx @openschool_01/skills install human-writing",
|
|
576
|
+
"packageName": "@openschool_01/skill-human-writing",
|
|
577
|
+
"summary": "适合给公众号、小红书、口播稿和说明文去掉 AI 味,保留信息结构,但把表达变得更顺、更像真人写的。",
|
|
578
|
+
"outcomes": [
|
|
579
|
+
"去 AI 味改写",
|
|
580
|
+
"平台语气调优",
|
|
581
|
+
"两版改写输出",
|
|
582
|
+
"套话与空话清理"
|
|
583
|
+
],
|
|
584
|
+
"fitFor": [
|
|
585
|
+
"公众号润色",
|
|
586
|
+
"小红书文案优化",
|
|
587
|
+
"视频口播改写",
|
|
588
|
+
"需要更自然表达的内容团队"
|
|
589
|
+
],
|
|
590
|
+
"updatedAt": "2026-04-10",
|
|
591
|
+
"prerequisites": [
|
|
592
|
+
"适合基于已有文案精修",
|
|
593
|
+
"当前版本不负责事实核查,只负责表达优化"
|
|
594
|
+
],
|
|
595
|
+
"sourceType": "official",
|
|
596
|
+
"localPackageDir": "packages/skills/human-writing"
|
|
535
597
|
}
|
|
536
598
|
]
|