分析对象:Easel(
ZJU-REAL/Easel),基于 commit765f5a6(2026-09-10 10:52:51 +0800,main)。许可 Apache-2.0。版本号pyproject.toml写 0.1.0,easel/__init__.py仍是0.0.1——两处还没对齐。 代码规模(sparse 检出,跳过assets/宣传视频):705 个文件。真正干活的代码是easel/9 个 Python 文件 / 768 行 +web/app.py2 509 行 + 前端web/frontend/src约 5 238 行 TS/TSX + 112 个 OpenClaw SKILL + 44 个skills/shared/scripts/*.py。测试 650 行。仓库很大(GitHub 上约 300MB+),绝大部分是展示素材。 社区体量:733 star / 90 fork / 2 open issue(2026-09-10 GitHub API)。仓库 2026-08-28 创建——分析当天大约两周。浙大 REAL Lab + 北大 OpenDCAI。主页 zju-real.github.io/Easel。 一句话定位:面向国内社媒创作者的开源内容工作台。它自己不写 Agent 主循环,把 OpenClaw 当成执行引擎,用账号画像、112 个可执行 SKILL、真实浏览器发布,把「发现 → 策划 → 创作 → 发布 → 归因」焊成一条能落盘、能真发的流水线。 读者对象:读过本系列任一份分析的读者。请先换轴:前面多数项目在回答「怎么让 AI 写代码 / 干活」;Open Design 回答「怎么不写循环、把别人的 CLI 当引擎做设计」;MiroFish 回答「怎么让一群 AI 把未来预演一遍」。Easel 坐在 Open Design 同一侧——宿主——但客体不是设计文件,是小红书卡片、短视频、知乎回答,以及它们发到真实平台之后的数据。
目录
第一部分 · 它是什么 1. 项目概览:两周 700 星,它到底在卖什么 2. 技术栈全解:一张薄整合层 + 一个借来的引擎 + 一座技能库 3. 五层工作流:产品叙事,不是运行时调度器
第二部分 · 宿主怎么接住引擎 4. 三入口单一真相源:chat / skill / web 5. Prompt 四层栈:SOUL → AGENTS → CONTEXT → SKILL 6. sync.sh:workspace 是剧本,项目根才是工地 7. 超时、代理、doctor:把环境做成可检查的产品
第三部分 · 技能即产品 8. SKILL-SPEC:三层加载与 200 行上限 9. 112 个 Skill:按层看完这座库 10. outputs 契约:内容是项目,不是聊天记录 11. 代表作:xhs-note-creator 怎么把一张笔记做出来
第四部分 · 真发与工作台 12. 两道闸门:content_guard 硬拦,persona_gate 只提醒 13. 登录与一键发布:Playwright + 短信墙 14. SSE 对话:断线不杀、双锁、uuid5、thinking 自愈 15. 前端工作台:11 页 SPA 与跨标签会话
第五部分 · 评价 16. 真正新的东西 17. 工程质量:亮点与硬伤 18. 对做新产品的十二条启发 19. 它在本系列里的位置 20. 🔍 源码指路表
第 1 章 项目概览:两周 700 星,它到底在卖什么
1.1 先看一组反差数字
| 指标 | 数值 |
|---|---|
| Star | 733(分析当日) |
| Fork | 90 |
| 仓库年龄 | 约 13 天(2026-08-28 → 2026-09-10) |
| Python 整合包 | 768 行 / 9 个文件 |
| Web 后端 | web/app.py 单文件 2 509 行 |
| 前端 | 约 5 238 行 TS/TSX |
| Skill | 112 个(discover 9 / plan 16 / produce 50 / publish 20 / attribute 11 / general 6) |
| 共享脚本 | 44 个 skills/shared/scripts/*.py |
| 许可 | Apache-2.0 |
| 主页 | https://zju-real.github.io/Easel/ |
Python 包只有 768 行,Skill 有 112 个。 这组数字本身就是架构声明:
Easel 的产品不在「再写一个 Agent 循环」,而在「给一个现成的 Agent 配上社媒领域的手、规矩和工位」。
README 开篇那句最重要:
你的私人、持续进化的社媒运营助手。从一个想法开始,完成发现、策划、创作、发布与复盘。
六个平台写进能力表:小红书、抖音、快手、知乎、B 站、微信视频号。研究愿景写得很直白——把实验室里的社交智能,接到创作者每天真的要发的那条内容上。
1.2 它明确不是什么
| 不是 | 是 |
|---|---|
| 又一个编程 Agent | 社媒内容工作台 |
| 自己实现的 ReAct 循环 | OpenClaw 的 profile = easel 隔离工作区 |
| 「功能清单」式的 skill 目录 | 带可运行脚本、产物落盘、真发闸门的技能库 |
| 全局一份 USER.md / MEMORY.md | 每个请求自包含的画像前缀 + 按画像隔离的 memory.md |
| 聊天记录里的半成品 | outputs/<人类可读主题>/ 的项目目录 |
README 还写了两条使用警告,值得当成产品约束读:
- 推荐 Web 前端,CLI 只是入口之一。
- 谨慎自动发布到小红书——平台可能检测自动化,有验证、限流、风控。预览 + 人确认后再发。
1.3 谁在做、向谁致谢
浙大 REAL Lab + 北大 OpenDCAI。docs/ACKNOWLEDGMENTS.md 把 112 个 Skill 的来源摊开:卡片模板明确写了借鉴 nexu-io/open-design,去 AI 味借鉴「说人话」,模板复用借鉴 fabric……每个 Skill 目录另有 EASEL-META.md 记来源。这很诚实:技能库是汇编 + 自研,不是从零发明社媒方法论。工程贡献在把它们收成同一套契约(frontmatter、outputs、闸门、画像)。
🧠 一句话:Easel 卖的是「会记住你账号的内容搭档」,工程上它是 OpenClaw 的垂直宿主。本系列里,Open Design 把 25 个 CLI 当引擎做设计;Easel 把 1 个 OpenClaw 当引擎做社媒。
第 2 章 技术栈全解:一张薄整合层 + 一个借来的引擎 + 一座技能库
2.1 装配图
flowchart TB
subgraph UI["① 你能看到的"]
WEB["React 工作台
对话 / 技能库 / 内容库 / 账号 / 画像
热点 / 日历 / 发布 / 复盘"]
CLI["easel chat / skill / doctor"]
end
subgraph HOST["② 整合层(Easel 自己写的)"]
PY["easel/ 768 行
persona · timeouts · CLI"]
API["web/app.py 2509 行
SSE · 登录 · 发布 · 产物树"]
SS["skills/shared/scripts 44 个
output_paths · manifest · content_guard"]
end
subgraph ENGINE["③ 借来的引擎"]
OC["openclaw --profile easel
gateway :18789"]
WS["~/.openclaw/workspace-easel/
SOUL.md + AGENTS.md + skills 副本"]
end
subgraph DISK["④ 项目磁盘(唯一真相)"]
SK["skills/openclaw/ 112 SKILL"]
PF["profiles/ 六维画像"]
OUT["outputs/ 内容项目"]
ENV[".env 模型与媒体 Key"]
end
WEB --> API
CLI --> PY
API -->|subprocess + 画像前缀| OC
PY -->|subprocess + 画像前缀| OC
OC --> WS
WS -.symlink.-> PF
WS -.symlink.-> OUT
OC -->|"cd 到项目根再跑脚本"| SK
SK --> SS
SS --> OUT
2.2 完整技术栈
| 层 | 技术 | 源码位置 |
|---|---|---|
| CLI | argparse 子命令:chat / doctor / gateway / ping / skill / web |
easel/cli.py:124-171 |
| 画像 | 六维 md 拼接 + 消息前缀 + 每轮 TURN_REMINDER | easel/persona.py |
| 超时 | TIMEOUT_PRODUCE=7200 / TIMEOUT_DIRECT=300 / TIMEOUT_CHAT=7200 |
easel/timeouts.py:11-13 |
| Web | FastAPI + SSE(sse-starlette)+ CORS * |
web/app.py:190-191 |
| 前端 | React + Vite,11 个页面组件 | web/frontend/src/App.tsx、Sidebar.tsx:11 |
| 引擎 | 全局 openclaw CLI,profile 名写死 "easel" |
easel/cli.py:31、web/app.py:46 |
| Gateway | 127.0.0.1:18789/healthz |
easel/commands/doctor.py:75-81 |
| 发布 | Playwright Chromium + biliup + 各平台脚本 | pyproject.toml:20-21、web/app.py:1996-2053 |
| 媒体 | Pillow / OpenCV / faster-whisper / edge-tts / rembg / librosa | pyproject.toml:18-21 |
| 运行时 | Python ≥3.10、Node ≥22.19、FFmpeg | easel/commands/doctor.py:148-158 |
pyproject.toml:25-26 的 CLI 入口只有一行:easel = "easel.cli:main"。Web 不是 Python 包里的 ASGI 应用,而是 easel web 再 subprocess 去跑 web/app.py(easel/cli.py:157-166)。
2.3 被否方案,从注释里读出来
整合层非常薄,但注释里全是「我们试过、翻过车、才收敛到这里」:
| 被否方案 | 为什么否 | 现在怎么做 |
|---|---|---|
全局 USER.md 写当前画像 |
并发会话互相覆盖 | 画像作为消息内联(easel/persona.py:1-7) |
全局 MEMORY.md 承载账号知识 |
跨画像污染 | sync 时把 workspace 的 MEMORY.md 清空(openclaw/sync.sh:101-103) |
| 三入口各写各的超时 | 同一任务经 CLI 能跑完、经 Web 被掐断 | easel/timeouts.py:1-8 |
| 三入口各写各的画像逻辑 | 行为不一致 | 收敛到 easel/persona.py |
openclaw.json5 当生效配置 |
setup 从不应用它,会和真实配置漂移 | 文件自己在头部警告(openclaw/openclaw.json5:2-5) |
OpenClaw 默认 thinking=high |
thinking 块存进历史时丢签名,Bedrock 回放 400 | 默认 low + session_heal 清洗(web/app.py:51-54) |
只靠 --session-key 续会话 |
空闲约 24h 绑定过期,隔天历史全丢 | 用 uuid5 钉死 --session-id(web/app.py:823-833) |
🧠 一句话:Easel 的 Python 代码少,是因为它把循环外包了;它真正花力气的地方,是把外包之后必然出现的竞态、超时、会话丢失、密钥泄露,一个一个焊死。
第 3 章 五层工作流:产品叙事,不是运行时调度器
3.1 五层写在哪
产品层(README)和 Agent 层(AGENTS.md)用的是同一套词:
| 层 | README 说法 | AGENTS.md 说法 | Skill layer 字段 |
|---|---|---|---|
| 发现 | 热点与机会 | 热点、爆款、二创机会 | discover(9) |
| 策划 | 选题、标题、脚本、排期 | 选题、脚本、分镜、封面构思 | plan(16) |
| 创作 / 制作 | 图文、音频、视频 | 任何要创建文件的任务,写入 outputs/ |
produce(50) |
| 发布 | 检查、适配、真发 | 合规、平台适配、登录、真发 | publish(20) |
| 归因 | 表现回流画像 | 数据、评论、Profile 回流 | attribute(11) |
| (横切) | 工作台基础 | — | general(6) |
openclaw/workspace/AGENTS.md:41 写得很克制:
清晰单层任务直接执行;跨两层以上或明显多步骤任务先给简短 Plan。一个任务可组合多个 SKILL,但不要运行无关层。
没有一个 Python 类叫 WorkflowEngine 按层调度。五层是写给模型看的分工规则,落地靠两样东西:
- 每个 SKILL.md 的 YAML
layer:(路由与文档) - 跨层时
manifest.py在outputs/<主题>/.easel.json里记「产物路径 + 一句结论」(docs/SKILL-SPEC.md:136-149)
3.2 纵向编排的薄索引
AGENTS.md 对跨层的规定(openclaw/workspace/AGENTS.md:43-45):
- 跨两层以上才建 manifest;单层不建
- 每层完成或失败都登记
- 下游用
latest/read取上游,不重新推导、不整块转发 - 完整载荷写文件,关键决策写
brief.md
docs/SKILL-SPEC.md:157-159 把原则说得更狠:manifest 只当薄索引,summary 一行给编排层路由,outputs[] 指路径,不复制内容。
这和编程 Agent 把中间结果堆在对话里,是相反的哲学:对话是控制面,磁盘是数据面。
sequenceDiagram
participant U as 用户
participant A as OpenClaw Agent
participant M as manifest.py
participant D as outputs/主题/
U->>A: 帮我做一条小红书并排期发布
A->>A: Plan:discover → plan → produce → publish
A->>D: 写 brief.md / 卡片 / 文案
A->>M: record --layer produce --outputs card_1.png,...
A->>M: latest --layer produce
M-->>A: 路径 + 一句结论
A->>D: 发布脚本 --exec
A->>M: meta --status published
3.3 制作层的特殊地位
AGENTS.md 把「制作」定义成凡是要创建文件的任务(openclaw/workspace/AGENTS.md:31),不只是「好看的视频」。超时也按这个定义给预算:chat 入口按制作层 7200 秒,因为对话中途可能生视频(easel/timeouts.py:7-13)。
制作流程固定五步(openclaw/workspace/AGENTS.md:53-63):凝练 Profile → 明确规格 → 按 SKILL 产出 → 自检 → 仅失败时返工一次。制作层不读 platforms.md,原样遵守 preferences 红线。这是故意的:做内容时先把东西做对,平台适配留给发布层。
第 4 章 三入口单一真相源:chat / skill / web
4.1 三个入口,同一句话
| 入口 | 怎么进 OpenClaw | 画像怎么注入 | 超时 |
|---|---|---|---|
easel chat |
openclaw --profile easel chat --session ... --message <prefix> |
选画像后,前缀作为初始消息 | TIMEOUT_CHAT(easel/cli.py:103-117) |
easel skill |
openclaw agent --session-key skill-<ts> --message "请执行 /<skill>…" |
persona_prefix 拼在指令前 |
一律 TIMEOUT_PRODUCE(easel/commands/skill.py:156-164) |
Web /api/chat/stream |
同上 openclaw agent,外加 --session-id 钉死 transcript |
chat_turn_message = 前缀 + 用户原文 + TURN_REMINDER |
TIMEOUT_CHAT(web/app.py:1080-1084) |
Web /api/skill |
run_agent_sync |
_persona_prefix + 请执行 /… |
TIMEOUT_PRODUCE(web/app.py:1453-1463) |
CLI chat 不加 TURN_REMINDER,Web 对话才加。easel/persona.py:72-76 解释了为什么:SKILL 单跑不必加;对话会长,系统提示会衰减。
4.2 画像前缀长什么样
我当前使用的画像是「科技数码达人」。本会话的账号长期记忆仅使用 profiles/科技数码达人/memory.md,不要使用工作区全局 MEMORY.md 作为账号记忆。
实现:easel/persona.py:58-69。六维文件顺序写死(identity / style / audience / platforms / preferences / memory,easel/persona.py:16-20)。load_profile_text 按这个顺序拼接,其它 .md 追加——但注入进 OpenClaw 的并不是全文,只是「去读哪个目录」的指针。真正凝练由 AGENTS.md 要求模型自己去读 easel-profiles/<名>/。
4.3 每轮行为提醒:用近因效应对抗指令衰减
TURN_REMINDER(easel/persona.py:77-83)拼在用户消息末尾,并标明「内部提醒、勿复述」。内容就三句:
- 动手前先查技能库,别凭记忆裸做
- 五层都由你自己把成品写到
outputs/ - 问账号先查登录态;对外文案不许泄密钥、也不许自曝「AI 生成」
注释把动机写得很清楚(easel/persona.py:72-76):AGENTS.md / SOUL.md 只在会话开头新鲜,长对话后面模型会「凭记忆裸做」。把最关键的反射放末尾,成本极低。
这是本系列里很少被写成代码的一件事:系统提示会衰减,所以把承重规则再喂一遍,而且喂在用户话后面。
4.4 skill 入口如何消化输入
easel/commands/skill.py:48-66:输入可能是文本,也可能是路径。图片改写成「请处理这个图片:绝对路径」;音视频/PDF/压缩包只传路径,避免 read_text 把二进制当 UTF-8 炸;普通文本文件才读进消息。过长的字符串(超过文件名上限)Path.is_file 会抛 OSError,被当成文本——tests/test_core.py:71-74 专门锁了这个坑。
Skill 名允许省略 skill- 前缀(easel/commands/skill.py:39-45),Web find_skill 同一套逻辑(web/app.py:213-219)。
第 5 章 Prompt 四层栈:SOUL → AGENTS → CONTEXT → SKILL
docs/prompt-stack.md 把组合顺序写死。对照 Open Design 的「20 层按缓存频率分带」,Easel 只分四层,而且常驻层刻意不写具体 skill 名。
5.1 Layer 1 — SOUL.md(人格,30 行)
openclaw/workspace/SOUL.md:你是创作者的「搭子」——懂策略,也能上手。能力总览按五层写「能做什么」,不写 skill 名,避免像某个平台下架那样过时(docs/prompt-stack.md:25)。沟通风格:中文、给 2–3 个选项、对外文案绝不暴露工具痕迹。
5.2 Layer 2 — AGENTS.md(业务宪法,110 行)
这是最重要的文件。核心执行规则六条(openclaw/workspace/AGENTS.md:5-12):
- 先路由 SKILL——精确匹配 → 最接近 → 才用通用能力
- 先到项目根——第一个脚本前必须
cd到 sync 注入的绝对路径 - 不在 workspace 跑项目副本——禁止从 OpenClaw workspace 的
shared/跑脚本 - 查现有信息再提问——登录态、画像、历史产物先查
- 付费操作先确认——生图/生视频/音乐先给范围和费用
- 真实产物才算完成——计划、空壳、提示词不算
另外还有一整节「配置检查」:模型 Key 只能以项目根 .env 为准;env / printenv 看不到未 export 的值;workspace ls -a 也看不到项目根 .env——二者都不能用来宣称缺配置(openclaw/workspace/AGENTS.md:16-23)。这是被真实翻车逼出来的:Agent 在错误的 cwd 里喊「你没配 Key」,用户其实配好了。
5.3 Layer 3 — CONTEXT.md(半静态路径)
由 sync.sh 生成,含项目绝对路径。注释强调:本 claude 版本不支持 --cwd,所以必须先 cd 再跑脚本(openclaw/sync.sh:128-137)。
5.4 Layer 4 — SKILL(按需)
触发时加载 SKILL.md,references 按引用再读,scripts 执行时调用、代码不进 prompt(docs/prompt-stack.md:36-39)。这和 Claude Code 的 skill 三层披露是同一类想法,Easel 把它写进了自己的 SKILL-SPEC。
flowchart LR
S["SOUL.md
人格 · 类目级能力"] --> A["AGENTS.md
分工 · 编排 · 安全"]
A --> C["CONTEXT.md
项目根绝对路径"]
C --> K["SKILL.md
被触发才加载"]
K --> R["references/
领域知识按需"]
K --> SC["scripts/
不进 prompt"]
P["画像前缀
每条消息"] -.-> A
T["TURN_REMINDER
仅 Web 对话末尾"] -.-> K
第 6 章 sync.sh:workspace 是剧本,项目根才是工地
6.1 隔离 profile 的真实路径
workspace → ~/.openclaw/workspace-easel/
config → ~/.openclaw-easel/openclaw.json
(openclaw/sync.sh:6-8)
--profile easel 把 Easel 和用户机器上可能存在的其它 OpenClaw 工作区切开。
6.2 同步做了什么
- 删除源里已经没有的 SKILL(
openclaw/sync.sh:30-38) - 整目录
cp -r每个 SKILL 到 workspace/skills(openclaw/sync.sh:46-53) - 复制
skills/shared/到 workspace/shared(给 SKILL 里../../shared/相对引用) - 复制 AGENTS.md / SOUL.md,再追加「运行时项目根」绝对路径(
openclaw/sync.sh:77-93) - 删掉残留 USER.md,清空 MEMORY.md(
openclaw/sync.sh:97-103) - symlink
easel-profiles→ 项目profiles/,outputs→ 项目outputs/(openclaw/sync.sh:106-124) - 写
CONTEXT.md
symlink 那段写了个真实坑:必须先 rm 再 ln -s,否则 ln -sf 会跟着旧链接走进目标目录造成循环(openclaw/sync.sh:107)。
6.3 为什么脚本必须在项目根跑
manifest.py:58-61 自己招认:脚本会被 sync 拍平复制到 workspace/shared/scripts/,那里 __file__ 少一层 skills/,上溯会算成 ~/.openclaw,产物写错地方。所以 EASEL_ROOT 环境变量不可省。CLI 和 Web 都在 _proxy_env 里 setdefault("EASEL_ROOT", 项目根)(easel/cli.py:36、web/app.py:303)。
AGENTS.md 同步后追加的那段,等于每轮都把「去哪 cd」焊进系统提示。workspace 里那份 shared/ 只给模型读文档用,不是执行 cwd。
6.4 openclaw.json5 是一份不会生效的模板
文件头写得毫不留情(openclaw/openclaw.json5:2-5):
本文件仅作参考,实际配置由 setup.sh 通过
openclaw config set直接写入~/.openclaw-easel/openclaw.json,此模板从不被 setup.sh 应用。
Gateway 端口 18789、模型 anthropic/claude-sonnet-4-6 只是示意。读源码的人如果把 json5 当运行时配置,会错。 这是一种少见的诚实:把「可能漂移的模板」标成危险品,而不是假装它是单一真相源。
第 7 章 超时、代理、doctor:把环境做成可检查的产品
7.1 超时三常数
TIMEOUT_PRODUCE = 7200 # 生视频 / 多镜合成
TIMEOUT_DIRECT = 300 # 发现 / 策划 / 发布 / 归因
TIMEOUT_CHAT = TIMEOUT_PRODUCE
(easel/timeouts.py:11-13)
chat 按制作层给预算,是因为用户在对话里随时可能说「做成视频」。skill CLI 一律用制作层上界,简单粗暴,避免再按 layer 分支出错。
7.2 代理:保护内网直连
_proxy_env 把 EASEL_PROXY 填进 http_proxy / https_proxy,同时 no_proxy 包含 localhost、127.0.0.1、*.xiaohongshu.com、*.devops.xiaohongshu.com、10.*(easel/cli.py:33-40)。这是一张实验室内网痕迹:小红书内部域名要直连,外网走代理。content_guard 稍后会把这些内部域名当成 BLOCK 级泄露。
7.3 doctor 检查清单
easel doctor(easel/commands/doctor.py:143-207)按产品依赖逐项打勾:
Python ≥3.10 / venv / Node ≥22.19 / FFmpeg / openclaw 在 PATH / fastapi·uvicorn·sse_starlette·multipart / 前端 dist/index.html / Playwright Chromium / .env 里任一认证通道 / gateway :18789/healthz / ~/.openclaw/workspace-easel/skills/ 非空 / 关键文件存在。
认证通道是「任一即可」(easel/commands/doctor.py:92-140):ANTHROPIC_API_KEY,或 EASEL_LLM_API_KEY + BASE_URL,或 ANTHROPIC_AUTH_TOKEN + BASE_URL,或 OPENAI_API_KEY,或火山 MaaS。占位符 REPLACE_ME 不算配好。
easel ping 再做一次真连通:healthz + 让 agent 说 PONG(easel/commands/ping.py:48-78)。doctor 是静态存在性,ping 才是权威。
第 8 章 SKILL-SPEC:三层加载与 200 行上限
docs/SKILL-SPEC.md 是技能库的接口规范 v0.3。
8.1 目录与三层
skills/
├── openclaw/ 五层 SKILL,OpenClaw 直接执行
└── shared/ 跨 SKILL 脚本与配置
skill-xxx/
├── SKILL.md 必须,< 200 行,只写「怎么做」
├── references/ 领域知识,按需加载
├── scripts/ 运行时调用,代码不进 prompt
└── tests/ test1.prompt / test1.expected
| 层 | 加载时机 | token |
|---|---|---|
| Metadata(name, description, layer) | 常驻,用于路由 | 极小 |
| Instructions(SKILL.md 主体) | 被触发 | 中等 |
| Resources(references + scripts) | 执行中按需 | 按需 |
frontmatter 只保留三个常规字段:name / description / layer。禁止 version、allowed-tools、tags 等(docs/SKILL-SPEC.md:73-78)。description 必须用中文写清能力、触发说法、与相邻 SKILL 的边界。
校验两条命令:scripts/validate_skills.py(frontmatter、资源链接、输出与发布安全契约)和 scripts/validate_skill_commands.py(SKILL 里的 Python 命令 vs 脚本 argparse,防路径漂移)。
8.2 设计约束五条
独立可调、无 Profile 也能用、接口稳定、SKILL.md 精简、泛化不给具体 case(docs/SKILL-SPEC.md:167-173)。
「无 Profile 也能用」决定了产品可以先玩起来再慢慢建画像;「独立可调」决定了 112 个目录可以并行改,而不是一张大网。
8.3 和编程 Agent 的 skill 有何不同
Claude Code / Codex 的 skill 多半是「教模型怎么用工具」。Easel 的 skill 自己就是一条制作流水线:SKILL.md 里写着要跑哪条 python skills/.../scripts/foo.py,产物路径被 output_paths.py 门禁,发布前被 content_guard.py 扫描。Skill 不是说明书,是带执行器的 SOP。
第 9 章 112 个 Skill:按层看完这座库
数量以 docs/skill-function-mapping.md 与检出目录为准,frontmatter layer 统计:
| 层 | 数量 | 这一层在干什么 |
|---|---|---|
| general | 6 | 产物管理、批量处理、登录账号查询、画像构建/管理、模板库 |
| discover | 9 | 热搜、竞品、内容缺口、跨平台差异、节日、行业资讯、RSS、算法更新、UGC |
| plan | 16 | 定位、受众、人设、选题矩阵、评分、日历、钩子、大纲、分镜、直播、商单 |
| produce | 50 | 文字 / 视觉 / 音频 / 视频 / 小说 / 短剧 / 论文解读——半壁江山 |
| publish | 20 | 六平台上传、跨平台分发、排期、质量门禁、评论回复、短链、通知 |
| attribute | 11 | 数据、评论洞察、ROI、复盘、画像记忆 |
制作层 50 个不是「50 种文案」,而是把 ffmpeg、TTS、生图、生视频、字幕、绿幕、卡点、相册……收成 Agent 可调的原子。共享脚本层才是真正的多媒体 SDK。
Web 侧 get_skills()(web/app.py:270-286)扫 skills/openclaw/*/SKILL.md,解析 description/layer,并对少数 Skill 标 needsApi(生图/生视频/音乐/克隆/短剧/论文解读,web/app.py:142-170)。前端技能库页用这张表决定要不要感叹号「去配 Key」。
SKILL_API_REQUIREMENTS 和 skills/shared/scripts/model_registry.py 共用同一真相——tests/test_core.py:33-39 断言 Web 的 provider 列表等于注册表,序列化结果里不得出现密钥。
9.1 制作层里值得单独点名的几类
| 类型 | 代表 Skill | 特点 |
|---|---|---|
| 平台总入口 | xhs-note-creator |
小红书图文/视频的 SOP,强制去 AI 化、走 card-design |
| 一键出片 | auto-short-video |
文案→配图/AI 视频→配音→字幕→BGM→合成 |
| 长内容 | novel-writer / paper-explainer / short-drama |
文件化状态、跨章一致性、剧集圣经 |
| 确定性媒体 | image-editing / video-editing / audio-editing |
不靠模型,靠 OpenCV/ffmpeg |
| 去 AI 味 | text-polisher |
七轮扫描 + 中文 AI 标记表,被其它 Skill 引用为权威源 |
xhs-note-creator 的 SKILL.md 明确写:整套笔记用本 SKILL;仅渲染卡片用 card-xiaohongshu;其它平台用 social-content(skills/openclaw/xhs-note-creator/SKILL.md:4-6)。边界写在 description 里,这就是路由的全部机制——没有 Python 路由器。
第 10 章 outputs 契约:内容是项目,不是聊天记录
10.1 目录规约
outputs/<人类可读主题>/
├── note.md / final.mp4 / card_1.png 成品,放项目根
├── assets/ 中间件:帧、切片、草稿
└── .easel.json 展示头 + steps[](隐藏)
禁止:泛名(xhs / test / tmp / 主题……,output_paths.py:25-29);成品散落 outputs/ 根;内容写入 _ 系统目录。系统目录白名单:_analytics _debug _inbox _login _probe _profile_build _publish _scratch _sessions(output_paths.py:30-33)。
validate_output_path(output_paths.py:41-75)是所有新脚本的门禁:解析到项目根、必须在 outputs/ 下、内容至少两级路径、泛名直接抛 OutputPathError。系统写入必须 allow_system=True 且 top 在白名单。
10.2 manifest 的两份工作
skills/shared/scripts/manifest.py:
- 展示头(
meta):title / platform / kind / status / tags / cover / deliverables——给前端内容库做卡片 - steps[](
record):layer / skill / at / status(done|failed) / outputs[] / upstream[] / summary——给下游编排
kind ∈ article / xhs-note / video / cards / poster / audio / other;项目 status ∈ draft / ready / published。atomic_write 用 mkstemp + os.replace(manifest.py:107-118)。
Web _read_project_meta(web/app.py:505-520)只取展示字段,不把 steps 泄露给内容库 UI。封面解析:声明的 cover → 首个成品媒体 → 目录里第一张图/视频。
10.3 附件隔离
用户上传进 outputs/_inbox/,按会话隔离。本轮消息附「系统附件清单」,只允许用清单里的路径,禁止扫描 inbox 其它文件(openclaw/workspace/AGENTS.md:91、web/app.py:790-795)。纳入项目时复制到 outputs/<项目>/assets/,inbox 原件保留以便重试。
这是发布类 Agent 特有的威胁模型:inbox 里可能有别的会话刚上传的素材,模型如果 ls 一下就串味。
第 11 章 代表作:xhs-note-creator 怎么把一张笔记做出来
小红书是 Easel 的主场。这个 SKILL 把「笔记」定义成 3–9 张 3:4 卡片或 15–90 秒竖屏分镜,长文只是中间产物(skills/openclaw/xhs-note-creator/SKILL.md:14-19)。
工作流(不可跳步):
- Intake:主题 / 形态 / 素材 / 风格——一次问完
0.5 卖点公式:稀缺性 × 实用性 × 可感知 - 有素材则
analyze_material.py出清单 - 观点类必须外部参考,核心数据 ≥2 源
- 写 2000–4000 字长文原稿,先给用户确认
- 强制去 AI 化——规则不在本 SKILL 维护副本,统一走
text-polisher的 references(SKILL.md:73-79) - 视觉走
card-design九种风格,渲染走card-xiaohongshu - 质检、落
outputs/、登记 manifest
爆款五原则里有一句很关键:视觉不能糙。想要「素人/手账」也要选 card-design 的手账贴纸风格,而不是真的用糙 t2i 加大 emoji(SKILL.md:25)。这是 Open Design 那套「反 AI 味」在社媒卡片上的落地,ACKNOWLEDGMENTS 也承认卡片模板借鉴了 open-design。
脚本目录里有 validate_meta.py、analyze_material.py、normalize_slug.py、crop_watermark.py、text_on_image.py、collage_3x4.py——模型负责判断和文案,像素级操作交给确定性脚本。
第 12 章 两道闸门:content_guard 硬拦,persona_gate 只提醒
这是 Easel 最值得抄的产品决策之一:会真发到公开平台的 Agent,必须假设模型会把内部设置写进文案。
12.1 content_guard:fail-closed,退出码 7
skills/shared/scripts/content_guard.py 头部把事故写出来了(:3-7):过去从生成到 --exec 之间没有任何过滤,密钥、内部 URL、代理 IP、「由 AI 生成」一旦发出去就删不掉。
两级强制(content_guard.py:97-105):
| 级别 | 类别 | 真发时 |
|---|---|---|
| BLOCK | api-key / env-value / internal-host / proxy-ip / internal-path / env-name | exit 7,改稿重发 |
| WARN | ai-disclosure / model-name | 只告警。论文解读里「Claude」可能是正文 |
扫描是双路:正则表 + .env 里敏感键的字面值(content_guard.py:141-176)。命中片段在报告里打码(_mask / _snippet),避免日志二次泄露。dry-run 全部只告警;放行硬拦必须显式 --allow-unsafe。AGENTS.md 要求:除非用户明确要求,不要用这个开关绕过(openclaw/workspace/AGENTS.md:72)。
正则里能看到实验室拓扑:maas.devops.xiaohongshu.com、webide-gateway.devops.xiaohongshu.com、/mnt/tidal-alsh01、happyhorse 生视频内部名(content_guard.py:50-85)。闸门既是通用产品,也是这份代码从内部工具开源出来时的脱敏层。
12.2 persona_gate:人设检查永不阻断发布
发布层 SKILL 执行前,有 Profile 就跑 skill-persona-check(LLM 打分),再交给 persona_gate.py check 把分数变成 pass/warn。阈值 80(persona_gate.py:29)。退出码永远是 0(persona_gate.py:10-11, 54):publish_allowed: true。
AGENTS.md 把理由说死(openclaw/workspace/AGENTS.md:74-78):人设检查只提醒;用户已明确要发就继续,不额外索要确认。内容安全与平台合规仍独立硬拦。
record 把评分写进 manifest 的 publish 步,便于事后审计。
🧠 一句话:密钥泄露是不可逆的公共事故,所以硬拦;「这条不很符合人设」是创作判断,所以只提醒。两道闸门的力度反着来,是对「自动化发布」责任边界的正确划分。
flowchart TD
C[待发文案] --> G{content_guard}
G -->|BLOCK 命中| X[exit 7 改稿]
G -->|通过或仅 WARN| P{有画像?}
P -->|是| S[skill-persona-check LLM 评分]
S --> PG[persona_gate classify]
PG -->|pass / warn 都继续| E[--exec 真发]
P -->|否| E
第 13 章 登录与一键发布:Playwright + 短信墙
13.1 六平台登录后端
LOGIN_RUNNERS(web/app.py:98-105):
| 前端 key | 显示名 | backend |
|---|---|---|
| xiaohongshu | 小红书 | xhs |
| douyin | 抖音 | douyin |
| kuaishou | 快手 | web + wp=kuaishou |
| weixin-channels | 微信视频号 | web + wp=weixin-channels |
| zhihu | 知乎 | web + wp=zhihu |
| bilibili | B 站 | biliup |
浏览器 profile 在 ~/.easel-browser-profiles(web/app.py:82)。登录状态写 outputs/_login/。whoami 真校验要起 headless 浏览器,进程内缓存 600 秒(web/app.py:93-96),避免账号页和工作台重复开浏览器。
13.2 /api/publish/{platform}
二次确认在前端。后端拼出 --exec 命令(web/app.py:1996-2053):
- 小红书:
xhs_publish.py publish或publish-video - 抖音:同样结构,但异步——可能触发短信墙,状态写
outputs/_publish/douyin.json,验证码写douyin.code,前端轮询到sms_required弹框 - B 站:直接
biliup upload,默认分区 tid=36「知识」,无标签兜底「日常」 - 其它:
web_publisher.py --platform <wp>
约束:有的平台必须带媒体;图和视频不能同时发;视频号类只能发视频。超时 600 秒。每次发布追加 outputs/_publish.log。
发布走 _publish_env(),和对话代理环境分开,避免把代理打到平台域名上把登录态搞丢。
13.3 为什么 README 要你小心小红书
自动化操作会被风控。Easel 选择「能真发」而不是「只给文案」——这是产品差异化,也是合规与账号安全的代价。代码把 dry-run、预览、人确认、content_guard 叠在 --exec 前面,但挡不住平台侧的行为检测。这不是 bug,是宿主选择进入真实世界之后必须写在包装上的警告。
第 14 章 SSE 对话:断线不杀、双锁、uuid5、thinking 自愈
web/app.py 里的对话实现,是这份薄整合层里最「系统」的一段。注释密度极高,几乎每条都对应一次线上事故。
14.1 问题 1:CLI 不流式
openclaw agent 会把模型输出缓冲到结束才打印(web/app.py:1028-1032)。解法:设 OPENCLAW_RAW_STREAM=1 + 每轮独立 jsonl 路径,后端 tail 文件,把 assistant_text_stream 的 text_delta 转成 SSE token,thinking 转成 thinking。每轮独立文件,无并发串扰。
14.2 问题 2:代理会掐断 SSE,但制作要跑很久
supervisor(跑 openclaw)和 forward(给浏览器)拆开(web/app.py:1037-1039, 1352-1374):
- 客户端断开只结束 forward,不取消 supervisor
- 完整结果落
outputs/_sessions/<sk>.json,前端用/api/chat/last取回 - 另有
/api/chat/jobs/{turn_id}/stream按 jsonl 续 SSE(web/app.py:986-1021) - 只有用户点停止才
terminate进程(web/app.py:1408-1415);断线不杀
长任务时 WebIDE 代理掐 SSE,是注释里点名的场景(web/app.py:946-947)。
14.3 问题 3:同一会话两个 openclaw = takeover 崩溃
OpenClaw 并发写同一 session 会抛 EmbeddedAttemptSessionTakeoverError,表现是「答一半停在冒号」(web/app.py:808-811)。双层锁:
- 进程内
asyncio.Lock按 session-key(web/app.py:812-820) - 跨进程
fcntl.flock(Windows 走msvcrt.locking)(web/app.py:841-896)
不同会话仍可并行。拿不到 flock 就返回「正在另一个窗口运行」。前端还有 BroadcastChannel 探测跨标签占用(第 15 章)。
14.4 问题 4:隔天再问就忘了
OpenClaw 靠 --session-key 解析 transcript,空闲超过约 24h(threadBindings.idleHours)绑定过期,会新起空 transcript(web/app.py:823-827)。解法:用固定命名空间做 uuid5,同一 web sessionId 永远同一 --session-id(web/app.py:828-833),绕开 key→绑定过期。
14.5 问题 5:thinking 块丢签名,回放 400
OpenClaw 把 Claude thinking 存进 jsonl 时丢了 signature,内网 Bedrock 回放校验失败(scripts/session_heal.py:3-6、web/app.py:51-54)。每轮 spawn 前 _heal_openclaw_session 删 thinking / redacted_thinking 块和空消息。默认 EASEL_THINKING_LEVEL=low 减少产生量。
14.6 问题 6:「答完了」其实没答完
收尾检测(web/app.py:1267-1290)不把「已经吐了字」当成成功。正常收尾的唯一标志是 raw 流最后一个事件为 assistant_message_end。否则追加一段给用户看的警告:触顶截断、进程被杀、停在 tool_use、流被掐、正文停在中文冒号(用户实测「所有莫名停止都停在冒号」)。
每次收尾还往 outputs/_debug/chat-stream.jsonl 打一行诊断(web/app.py:1313-1333):rc、stop_reason、last_ev、token/thinking 字数、是否误收了别的 session 的 raw 事件。
flowchart TB
B[浏览器] -->|POST /api/chat/stream| F[forward 生成器]
F -->|SSE token/thinking/activity| B
S[supervisor 后台任务] -->|client_q| F
S --> L[asyncio 锁 + flock]
L --> P[openclaw agent 子进程]
P --> RAW[每轮 jsonl raw stream]
S -->|tail| RAW
S -->|原子写| T[outputs/_sessions/sk.json]
B -.断线.-> F
S -.断线后继续.-> T
B -->|/api/chat/last| T
第 15 章 前端工作台:11 页 SPA 与跨标签会话
15.1 页面
Sidebar.tsx:11 的 Page 类型:
dashboard | chat | trends | ideas | calendar | publish | breakdown | skills | outputs | accounts | profile
侧栏只放六个主导航(工作台 / 对话 / 技能库 / 内容库 / 账号 / 画像),热点、选题、日历、发布、复盘收进工作台 SubNav,避免侧栏爆炸(Sidebar.tsx:31-39)。
App.tsx 把流式状态放在根组件,切页不卸载、不丢流(web/frontend/src/App.tsx:164 注释)。这是对「用户去内容库看一眼生成物、对话还在跑」的正确结构。
15.2 跨标签:BroadcastChannel + flock 兜底
挂载逻辑(App.tsx:55-125):
- 同标签刷新:
sessionStorage记着本标签会话 → 续上 - 新标签:用 BroadcastChannel 问「谁在用上次活跃会话」;有人应答
owned就开新会话,避免两个窗口撞同一 OpenClaw session - 不支持 BroadcastChannel:退回旧逻辑,后端 flock 仍能挡住崩溃
Onboarding:没有任何画像且没看过引导 → 推荐配置向导(App.tsx:144-147)。还处理了一次品牌更名:旧 localStorage key 从 postcraft_onboarding_seen 迁到 easel_onboarding_seen(用 ['post','craft'].join('') 躲开简单的字面扫描,App.tsx:36)。
15.3 API 客户端
web/frontend/src/lib/api.ts 用当前 pathname 推 BASE,方便子路径部署。错误优先展示 FastAPI detail。类型里把 .easel.json 展示头和产物树节点写全,和后端 _read_project_meta / _build_output_node 对齐。
第 16 章 真正新的东西
在本系列坐标系里,Easel 不是「又一个更强的循环」。它新在五件事:
16.1 垂直宿主,而且宿主只接一个引擎
Open Design 的宿主接 25 个 CLI,适配器即数据。Easel 只接 OpenClaw,把差异化全部做在 SKILL + 画像 + 真发闸门。更窄,所以能把社媒 SOP 做深(112 个、带脚本、带契约)。
16.2 对话是控制面,项目目录是数据面
编程 Agent 的产物经常活在 diff 和聊天里。Easel 强制 outputs/<主题>/ + 成品/中间件分离 + 薄 manifest。这是内容团队能理解的「一个选题一个文件夹」,不是工程师的 session 日志。
16.3 画像是请求的一部分,不是全局文件
否掉 USER.md 之后,并发会话不再互踩。记忆按 profiles/<名>/memory.md 隔离,全局 MEMORY.md 被同步脚本保持为空。这是多账号运营的正确内存模型。
16.4 真发之前的分级闸门
content_guard 的 BLOCK/WARN 分级、persona_gate 永不阻断、.env 字面值扫描——针对的是「Agent 会把内部世界说出去」这个发布场景特有的事故,编程 Agent 的沙箱解决不了。
16.5 把 OpenClaw 的坑当成产品表面来修
thinking 丢签名、24h session 绑定过期、并发 takeover、SSE 被代理掐断、指令衰减、cwd 跑错导致误报缺 Key——这些都不是「调用 CLI」就能自动好的。Easel 的 2509 行后端,一大半是在为上游引擎做适配器式的伤口包扎。这和 Open Design 为 25 个 CLI 写解析器,是同一类工作。
第 17 章 工程质量:亮点与硬伤
17.1 亮点
| 点 | 证据 |
|---|---|
| 三入口收敛单一真相源 | persona.py、timeouts.py,注释写明历史分叉 |
| 确定性脚本带 selftest | content_guard / persona_gate / manifest / output_paths |
| 原子写 | manifest os.replace;turn 结果 .tmp 再 replace |
| 测试锁契约 | tests/test_core.py 覆盖输入分类、路径安全、注册表脱敏、persona_gate |
| Skill 来源可追溯 | ACKNOWLEDGMENTS + 每 Skill 的 EASEL-META.md |
| 配置模板标明无效 | openclaw.json5 头部警告 |
| 诊断日志 | chat-stream.jsonl 把「莫名停下」分类 |
17.2 硬伤与代价
web/app.py单文件 2509 行。 路由、锁、SSE、登录、发布、产物树、画像 CRUD 全挤在一起。对两周的仓库可以理解,对后续贡献者不友好。- 版本号分裂。
pyproject.toml:7的0.1.0vseasel/__init__.py:3的0.0.1。 - 引擎不在仓库里。 OpenClaw 是
npm i -g openclaw。分析无法核对循环本身;Easel 的正确性依赖上游 CLI 的 flags(--session-id、OPENCLAW_RAW_STREAM)继续存在。 - json5 模板与真实配置漂移是已知的,但新人仍会被文件名骗。
- Skill 质量必然不均。 112 个目录,有的是完整流水线(xhs-note-creator),有的更接近提示词包装。
validate_skills.py能锁格式,锁不住「这个 SOP 在真实运营里好不好用」。 - 测试只有 650 行,且明确只测整合层纯函数(
tests/test_core.py:1-4)。发布脚本、Playwright、OpenClaw 交互是测试盲区——真发路径最危险的部分反而最难测。 - CORS
allow_origins=["*"](web/app.py:191)对本地工作台够用,不是能暴露到公网的形状。 - 小红书自动发布的风控写进了 README,但代码路径仍然提供
--exec。产品把选择权交给用户,也把风险交给用户。 - sync 是整目录复制,不是增量。112 个 Skill 每次全量 cp,workspace 和源可能在 Agent 跑着的时候被覆盖。
- TURN_REMINDER 每轮追加,长对话的 token 成本线性涨;换来的是路由命中率。这是公开的取舍(
easel/persona.py:72-76)。
第 18 章 对做新产品的十二条启发
- 先决定循环归谁。 如果领域 SOP 比通用推理更值钱,就别写循环,写宿主。
- 宿主的核心工作是包扎引擎伤口,不是
subprocess.run一行搞定。 - 领域产物用文件系统当一等公民,别让它们活在聊天记录里。
- 跨步骤传递用薄索引 + 路径,不要口传全文。
- 会发到公网的文本,必须有确定性扫描。 提示词里的「不要泄露」不够。
- 闸门分级:不可逆事故硬拦,审美/人设只提醒。
- 多租户记忆不要放全局文件。 画像是请求的一部分。
- 长对话要假定系统提示会衰减,把承重规则用近因再喂一遍。
- SSE 与长任务解耦:断线不杀,结果落盘,前端来取。
- 同一会话同一时刻只允许一个引擎进程——锁要跨到进程外。
- 环境检查做成产品命令(doctor / ping),不要只写 README。
- 把「这份文件不生效」写在文件头上。 漂移的模板比没有模板更危险。
第 19 章 它在本系列里的位置
flowchart LR
subgraph LOOP["自己写循环"]
CC["Claude Code / Codex / dsh
通用:写代码、干活"]
MF["MiroFish
垂直:仿真预演"]
end
subgraph HOST["不写循环 · 宿主"]
OD["Open Design
垂直:设计文件"]
EA["Easel
垂直:社媒内容 + 真发"]
end
CC -.skill 思想.-> EA
OD -.卡片视觉 / 宿主形态.-> EA
| 项目 | 问题 | 和 Easel 的关系 |
|---|---|---|
| Open Design | 不写循环,接 25 个 CLI 做设计 | 最近亲缘:都是宿主。Easel 只接 1 个引擎,技能库更垂直;卡片视觉还借鉴了它 |
| OpenWorker | 通用任务同事,收件箱 + 审批 | 都面向「非编程工作」;OW 强调人不在时的恢复,Easel 强调内容项目与真发 |
| MiroFish | 仿真,不干活 | 同为非编程;一个预演世界,一个生产内容 |
| Claude Code / Codex | 写循环的编程 Agent | Easel 把它们那种 skill 思想用在社媒 SOP 上,循环本身外包 |
| DeepSeek Harness | 循环也是插件 | 对照:dsh 把可替换做到极致;Easel 把可替换让给上游,自己锁死领域契约 |
谱系补丁:前七家编程 Agent → OpenWorker 同事 → Open Design 设计宿主 → MiroFish 仿真 → Easel 社媒宿主。它让「不写主循环」这条路线从设计工具走进了国内创作者每天要面对的六个平台。
配套阅读:从零构建社媒内容工作台(Easel 系) · 交互实验台 Easel-解析.html
第 20 章 🔍 源码指路表
全部对照 commit 765f5a6。
| 你想看 | 去这里 |
|---|---|
| CLI 入口与 chat 注入画像 | easel/cli.py:49-121 |
| 三入口画像前缀 / TURN_REMINDER | easel/persona.py:1-99 |
| 超时常量 | easel/timeouts.py |
| skill 路由与二进制输入 | easel/commands/skill.py:31-164 |
| doctor / ping / gateway | easel/commands/doctor.py、ping.py、gateway.py |
| Prompt 栈说明 | docs/prompt-stack.md |
| SKILL 规范 | docs/SKILL-SPEC.md |
| 112 Skill 能力地图 | docs/skill-function-mapping.md |
| Agent 宪法 | openclaw/workspace/AGENTS.md |
| 人格 | openclaw/workspace/SOUL.md |
| 同步与 symlink | openclaw/sync.sh |
| 「此配置不生效」 | openclaw/openclaw.json5:1-35 |
| SSE supervisor / 双锁 / uuid5 | web/app.py:808-1401 |
| 停止 vs 断线 | web/app.py:1408-1415 |
| Skill HTTP | web/app.py:1453-1463 |
| 产物树与展示头 | web/app.py:488-545 |
| 登录表 | web/app.py:98-105 |
| 一键发布 | web/app.py:1996-2068 |
| 前端跨标签 | web/frontend/src/App.tsx:55-125 |
| 页面枚举 | web/frontend/src/components/Sidebar.tsx:11-39 |
| 路径门禁 | skills/shared/scripts/output_paths.py |
| 层间契约 | skills/shared/scripts/manifest.py |
| 出站扫描 | skills/shared/scripts/content_guard.py |
| 人设提醒 | skills/shared/scripts/persona_gate.py |
| thinking 自愈 | scripts/session_heal.py |
| 小红书 SOP | skills/openclaw/xhs-note-creator/SKILL.md |
| 依赖与版本 | pyproject.toml |
| 整合层测试 | tests/test_core.py |
| Skill 致谢 | docs/ACKNOWLEDGMENTS.md |