# fanqie-author-publish

Adopt this skill: https://proskills.md/skills/automation/owlco001-fanqie-author-publish

Page: https://proskills.md/skills/automation/owlco001-fanqie-author-publish
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: baidu-author-publish/SKILL.md
size: 1240
sha: c7e850a8c53b4b8cd8bc7f5bda7ee6d538f0d407
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/SKILL.md

```text
# baidu-author-publish

## 用途

这个技能用于通过浏览器自动化，在百度作家平台后台完成章节的准备、检查和发布。

## 适用场景

- 准备百度作家平台章节草稿
- 检查章节是否可以发布
- 在确认后发布已准备好的章节
- 核对本地章节计划与平台最近章节状态

## 安全规则

- 不要要求用户在聊天里粘贴密码、短信验证码或 Cookie。
- 没有用户在当前会话里的明确确认，不要执行最终发布。
- 任何发布动作前，优先执行“保存草稿”。
- 如果页面布局、编辑器行为或关键按钮文案与预期不一致，立即停止并说明变化点。
- 如果出现验证码、风控或身份校验，立即停止并让用户手动处理。

## 标准流程

1. 打开百度作家平台后台并确认已登录
2. 进入目标作品并打开章节编辑页
3. 填入章节标题和正文
4. 验证内容是否真正出现在编辑器中
5. 保存草稿并截图
6. 检查是否满足发布条件
7. 仅在人工确认后执行最终发布
8. 核对最近章节状态

## 最小执行约定

- `pending`
- `draft_loaded`
- `draft_saved`
- `awaiting_confirmation`
- `published_verified`
- `failed_needs_review`
```

## File: qimao-author-publish/SKILL.md
size: 1222
sha: 24146eacf5fc8f1ee7b5d4996f3e4329da3834ae
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/SKILL.md

```text
# qimao-author-publish

## 用途

这个技能用于通过浏览器自动化，在七猫小说作者后台完成章节的准备、检查和发布。

## 适用场景

- 准备七猫章节草稿
- 检查章节是否可以发布
- 在确认后发布已准备好的章节
- 核对本地章节计划与平台最近章节状态

## 安全规则

- 不要要求用户在聊天里粘贴密码、短信验证码或 Cookie。
- 没有用户在当前会话里的明确确认，不要执行最终发布。
- 任何发布动作前，优先执行“保存草稿”。
- 如果页面布局、编辑器行为或关键按钮文案与预期不一致，立即停止并说明变化点。
- 如果出现验证码、风控或身份校验，立即停止并让用户手动处理。

## 标准流程

1. 打开七猫作者后台并确认已登录
2. 进入目标作品并打开章节编辑页
3. 填入章节标题和正文
4. 验证内容是否真正出现在编辑器中
5. 保存草稿并截图
6. 检查是否满足发布条件
7. 仅在人工确认后执行最终发布
8. 核对最近章节状态

## 最小执行约定

- `pending`
- `draft_loaded`
- `draft_saved`
- `awaiting_confirmation`
- `published_verified`
- `failed_needs_review`
```

## File: SKILL.md
size: 5495
sha: 18dfbc085638c3b89d75a799d74e7e12c88d1ea7
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/SKILL.md

```text
# fanqie-author-publish

## 用途

这个技能用于通过浏览器自动化，在番茄小说作者后台完成章节的准备、检查和发布。

这个技能默认采用人工确认参与的工作流：
- 从本地文件或粘贴内容中读取章节内容
- 打开番茄作者后台并进入目标作品
- 填写章节标题和正文
- 优先保存草稿
- 在最终发布前要求用户明确确认

不要尝试无人值守登录。在执行任何发布动作前，用户应当已经在受控浏览器配置中登录好番茄作者账号。

## 适用场景

当用户需要以下操作时，使用这个技能：
- 准备番茄章节草稿
- 检查章节是否可以发布
- 在确认后发布已准备好的章节
- 核对本地章节计划与平台最近章节状态

以下情况不要使用这个技能：
- 用户还没有登录番茄作者后台
- 用户要求完全无人值守的批量发布
- 平台当前出现验证码、风控、身份校验等提示

## 输入要求

执行前需要收集或确认以下信息：
- 目标作品名
- 章节标题
- 章节正文
- 目标动作：准备草稿、检查、发布、核对
- 可选发布时间
- 可选本地源文件路径

如果缺少必要信息，只问一个能够推动下一步的短问题。

## 安全规则

- 不要要求用户在聊天里粘贴密码、短信验证码或 Cookie。
- 没有用户在当前会话里的明确确认，不要执行最终发布。
- 任何发布动作前，优先执行“保存草稿”。
- 如果页面布局、编辑器行为或关键按钮文案与预期不一致，立即停止并说明变化点。
- 如果出现验证码、风控或身份校验，立即停止并让用户手动处理。
- 在最终发布前，必须明确复述将要发布的作品和章节。
- 每执行一个关键浏览器动作后，都要先验证页面状态，再继续下一步。

## 标准流程

### 1. 准备草稿

当用户要求准备章节或保存草稿时，走这个流程。

步骤：
1. 确认目标作品、章节标题和章节正文。
2. 在浏览器工具中打开番茄作者后台。
3. 确认用户当前处于已登录状态。
4. 进入正确的作品，并打开章节编辑页。
5. 填入章节标题和正文。
6. 确认内容已经正确出现在编辑器中。
7. 保存为草稿。
8. 截图或保存页面快照用于确认。
9. 清楚报告成功或失败结果。

输出中应包含：
- 作品名
- 章节标题
- 是否已保存草稿
- 是否有警告
- 截图或状态摘要

### 2. 检查发布条件

当用户要求发布前做最终检查时，走这个流程。

检查项：
- 当前作品是否正确
- 章节标题是否存在
- 章节正文是否已加载
- 是否存在明显空字段
- 如果平台可见，草稿保存状态是否正常
- 如果平台可见，发布按钮是否存在且可点击

返回一个简短结论：
- 可以发布
- 不能发布，并说明阻塞原因

### 3. 发布章节

只有在用户已经明确确认发布时，才走这个流程。

步骤：
1. 重新说明目标作品和章节标题。
2. 如果当前会话中还没有明确确认，再次请求确认。
3. 点击最终发布动作。
4. 等待成功状态、跳转结果或确认提示。
5. 截图或保存页面快照。
6. 报告是否已验证发布成功。

如果发布结果不明确：
- 不要重复点击发布按钮
- 检查章节列表或成功提示
- 报告当前不确定状态，并询问用户如何继续

### 4. 核对最近发布状态

当用户要求把本地计划和平台状态进行比对时，走这个流程。

步骤：
1. 打开目标作品的章节列表。
2. 检查最近章节标题和状态。
3. 与提供的本地章节列表或源文件进行对比。
4. 报告以下类型的不一致：
- 缺少章节
- 标题重复
- 草稿未发布
- 出现了意外的已发布章节

## 浏览器操作原则

执行浏览器操作时：
- 优先使用稳定、可见的文字定位，不要依赖脆弱的位置假设
- 等待编辑器完全加载后再输入
- 输入或粘贴完成后，要确认内容确实出现在编辑器中，尤其是富文本编辑器
- 在关键节点截图：编辑器加载完成、草稿保存完成、发布确认完成

如果浏览器工具支持页面快照，在执行破坏性动作前优先保存快照。

## 失败处理

任何步骤失败时：
1. 立即停止流程。
2. 记录最后一个已确认成功的步骤。
3. 如果可以，立刻截图。
4. 告诉用户具体失败点，以及需要用户做什么手动处理。

常见失败场景：
- 登录失效
- 找不到目标作品
- 编辑器没有接收正文内容
- 保存草稿按钮缺失或不可点击
- 出现验证码或风控提示
- 发布确认没有出现

## 响应风格

输出要短、直接、可执行。

示例：
- `《作品名》 / 第12章 已保存草稿，可继续检查。`
- `发布被阻止：番茄后台登录已失效。`
- `《作品名》 / 第12章 已准备完成，确认后可继续发布。`

## 建议触发语句

以下请求可以视为这个技能的触发信号：
- `准备番茄章节草稿`
- `把这章发到番茄草稿箱`
- `检查这章能不能发布`
- `发布这章番茄章节`
- `核对最近几章发布状态`

## 最小执行约定

将流程状态统一视为以下之一：
- `pending`
- `draft_loaded`
- `draft_saved`
- `awaiting_confirmation`
- `published_verified`
- `failed_needs_review`

每次运行结束时，都必须明确说明当前状态。
```

## File: skills/baidu-publisher/SKILL.md
size: 932
sha: 5152d77f494230a903f019090589636ff6a05e05
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/baidu-publisher/SKILL.md

```text
---
name: baidu-publisher
description: 自动在百度作家平台后台创建章节、填写标题正文、保存草稿或发布章节。适用于网文章节上传、草稿保存、批量发布前的浏览器自动化任务。
allowed-tools: exec, read, write
---

# baidu-publisher

用于百度作家平台后台的章节自动化发布。

## 输入参数

- `BOOK_ID`: 书籍 ID
- `TITLE`: 章节标题
- `BODY_PATH`: 正文文件路径
- `AUTO_PUBLISH`: 是否自动发布，`true` / `false`
- `MIN_WORDS`: 最低字数要求

## 执行原则

1. 进入百度作家平台后台并确认已登录。
2. 进入目标作品并打开章节编辑器。
3. 填写标题后必须验证输入框回显。
4. 填写正文后必须验证编辑器内容和字数同步。
5. 保存草稿后必须等待保存状态出现。
6. 若 `AUTO_PUBLISH=true`，则继续执行发布并等待终态。
7. 失败时必须截图并说明卡点。
```

## File: skills/fanqie-publisher/SKILL.md
size: 2393
sha: 13f43eccd8a2d7196e60127d8a7a032c7c3aee6f
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/fanqie-publisher/SKILL.md

```text
---
name: fanqie-publisher
description: 自动在番茄小说作者后台创建章节、填写标题正文、保存草稿或发布章节。适用于网文章节上传、草稿保存、批量发布前的浏览器自动化任务。
allowed-tools: exec, read, write
---

# fanqie-publisher

用于番茄小说作者后台的章节自动化发布。

## 适用场景

当用户提出以下需求时使用：

- 发布番茄小说章节
- 保存番茄草稿
- 自动上传章节正文
- 在番茄作者后台新建章节
- 批量发布网文章节
- 通过浏览器自动化操作番茄小说后台

## 输入参数

- `BOOK_ID`: 书籍 ID
- `TITLE`: 章节标题
- `BODY_PATH`: 正文文件路径
- `AUTO_PUBLISH`: 是否自动发布，`true` / `false`
- `MIN_WORDS`: 最低字数要求

## 执行原则

1. 优先尝试进入番茄章节编辑器：
   - `.../publish/?enter_from=newdraft`
   - `.../publish/?enter_from=newchapter`

2. 必须确认页面已进入编辑器：
   - 有标题输入框
   - 有正文编辑区

3. 如果未登录：
   - 不要擅自输入账号密码
   - 直接停止并汇报“需要登录”

4. 填写标题后必须验证输入框回显。

5. 填写正文后必须验证：
   - 编辑器中已存在内容
   - 页面“正文字数”已同步

6. 保存草稿后必须等待：
   - `已保存`
   - `保存成功`
   - `草稿已保存`
   等任一状态。

7. 若 `AUTO_PUBLISH=true`：
   - 继续点击发布
   - 处理确认弹窗
   - 等待终态：
     - `发布成功`
     - `已发布`
     - `审核中`
     - `待审核`
     - 或跳出 publish 页面进入章节管理页

8. 失败时必须截图并说明卡点。

## 发布前约束

在正式发布章节前：
1. 先向用户做简短确认
2. 清理当前上下文，或切换到干净执行环境
3. 再执行浏览器自动化发布

如仅保存草稿，则可直接执行。

## 输出结果

返回：

- `openedUrl`
- `currentUrl`
- `titleVerified`
- `wordCount`
- `draftSaved`
- `published`
- `publishSignal`
- `screenshots`
- `note`

## 推荐执行方式

优先使用 ACP 浏览器代理或 agent-browser 执行。
每次页面状态变化后重新 snapshot，不复用旧元素引用。

## 推荐触发词

- `发布番茄章节`
- `上传这一章到番茄`
- `保存番茄草稿`
- `在番茄后台新建章节`
- `自动发番茄`
- `把这章发到番茄小说`
```

## File: skills/qimao-publisher/SKILL.md
size: 926
sha: 4e447be74533864fd733b0d05419f17c1b0f147e
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/qimao-publisher/SKILL.md

```text
---
name: qimao-publisher
description: 自动在七猫小说作者后台创建章节、填写标题正文、保存草稿或发布章节。适用于网文章节上传、草稿保存、批量发布前的浏览器自动化任务。
allowed-tools: exec, read, write
---

# qimao-publisher

用于七猫小说作者后台的章节自动化发布。

## 输入参数

- `BOOK_ID`: 书籍 ID
- `TITLE`: 章节标题
- `BODY_PATH`: 正文文件路径
- `AUTO_PUBLISH`: 是否自动发布，`true` / `false`
- `MIN_WORDS`: 最低字数要求

## 执行原则

1. 进入七猫作者后台并确认已登录。
2. 进入目标作品并打开章节编辑器。
3. 填写标题后必须验证输入框回显。
4. 填写正文后必须验证编辑器内容和字数同步。
5. 保存草稿后必须等待保存状态出现。
6. 若 `AUTO_PUBLISH=true`，则继续执行发布并等待终态。
7. 失败时必须截图并说明卡点。
```

## File: baidu-author-publish/README.md
size: 1780
sha: a5aec3f9277411e068f1c303af62a94c31cadfc6
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/README.md

````text
# baidu-author-publish

一个面向百度作家平台的半自动发布技能包，目标运行环境为 Linux + OpenClaw。

当前版本是与 `fanqie-author-publish` 对齐的精简骨架，适合继续做真实页面联调与页面定位校准。

## 目标

- 读取章节内容
- 打开百度作家平台后台
- 自动填入章节标题和正文
- 优先保存草稿
- 人工确认后再执行最终发布

## 目录结构

```text
baidu-author-publish/
├─ README.md
├─ SKILL.md
├─ manifest.json
├─ browser-workflow.md
├─ execution-policy.md
├─ selectors.md
├─ input.schema.json
├─ output.schema.json
├─ chapter.template.json
├─ chapter.example.json
├─ chapters.batch.template.json
├─ chapters.batch.example.json
├─ publish-log.template.md
├─ test-cases.md
├─ real-site-calibration-checklist.md
├─ setup-linux.sh
├─ validate-input.sh
├─ logs/
└─ screenshots/
```

## 说明

- 输入输出结构与 `fanqie-author-publish` 保持一致，便于多平台复用。
- 当前仍需根据百度作家平台真实页面补充按钮文案、编辑器行为和发布校验规则。
- 建议先完成真实页面联调，再启用正式发布流程。

## 登录态保持说明

不建议提取、导出或复用站点 Cookie。

推荐做法是：
- 在 Linux 主机上为百度作家平台使用固定浏览器 profile
- 用这个 profile 手动登录一次
- 后续始终复用同一个 profile 运行 OpenClaw 浏览器流程

推荐目录：

```text
/home/author/browser-profiles/baidu-author/
```

示例启动方式：

```bash
chromium --user-data-dir=/home/author/browser-profiles/baidu-author
```

或者：

```bash
google-chrome --user-data-dir=/home/author/browser-profiles/baidu-author
```
````

## File: qimao-author-publish/README.md
size: 1759
sha: 811c68a65ccda1053822673e50785da72fdd27a5
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/README.md

````text
# qimao-author-publish

一个面向七猫小说作者后台的半自动发布技能包，目标运行环境为 Linux + OpenClaw。

当前版本是与 `fanqie-author-publish` 对齐的精简骨架，适合继续做真实页面联调与页面定位校准。

## 目标

- 读取章节内容
- 打开七猫作者后台
- 自动填入章节标题和正文
- 优先保存草稿
- 人工确认后再执行最终发布

## 目录结构

```text
qimao-author-publish/
├─ README.md
├─ SKILL.md
├─ manifest.json
├─ browser-workflow.md
├─ execution-policy.md
├─ selectors.md
├─ input.schema.json
├─ output.schema.json
├─ chapter.template.json
├─ chapter.example.json
├─ chapters.batch.template.json
├─ chapters.batch.example.json
├─ publish-log.template.md
├─ test-cases.md
├─ real-site-calibration-checklist.md
├─ setup-linux.sh
├─ validate-input.sh
├─ logs/
└─ screenshots/
```

## 说明

- 输入输出结构与 `fanqie-author-publish` 保持一致，便于多平台复用。
- 当前仍需根据七猫作者后台真实页面补充按钮文案、编辑器行为和发布校验规则。
- 建议先完成真实页面联调，再启用正式发布流程。

## 登录态保持说明

不建议提取、导出或复用站点 Cookie。

推荐做法是：
- 在 Linux 主机上为七猫作者后台使用固定浏览器 profile
- 用这个 profile 手动登录一次
- 后续始终复用同一个 profile 运行 OpenClaw 浏览器流程

推荐目录：

```text
/home/author/browser-profiles/qimao/
```

示例启动方式：

```bash
chromium --user-data-dir=/home/author/browser-profiles/qimao
```

或者：

```bash
google-chrome --user-data-dir=/home/author/browser-profiles/qimao
```
````

## File: README.md
size: 11991
sha: 0d668c271475bdea6a008a4814ceb8db1ad8a5c0
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/README.md

````text
# fanqie-author-publish

一个面向番茄小说作者后台的半自动发布技能包，目标运行环境为 Linux + OpenClaw。

目标不是一开始就做全自动无人值守发布，而是先做一个更稳的流程：
- 读取章节内容
- 打开番茄作者后台
- 自动填入章节标题和正文
- 优先保存草稿
- 人工确认后再执行最终发布

## 适用场景

适合这些需求：
- 把本地章节快速录入到番茄作者后台
- 自动保存草稿，减少重复复制粘贴
- 发布前做一次结构化检查
- 核对后台最近章节状态和本地计划是否一致
- 批量准备多个章节草稿

不适合这些需求：
- 完全无人值守批量发布
- 自动处理验证码、风控、短信验证
- 在未登录番茄作者后台的情况下直接执行

## 目录结构

```text
fanqie-author-publish/
├─ README.md
├─ CHANGELOG.md
├─ SKILL.md
├─ manifest.json
├─ browser-workflow.md
├─ execution-policy.md
├─ selectors.md
├─ input.schema.json
├─ output.schema.json
├─ chapter.template.json
├─ chapter.example.json
├─ chapters.batch.template.json
├─ chapters.batch.example.json
├─ publish-log.template.md
├─ test-cases.md
├─ real-site-calibration-checklist.md
├─ setup-linux.sh
├─ logs/
│  ├─ .gitkeep
│  └─ example-log.md
└─ screenshots/
```

## 核心文件说明

### `manifest.json`

技能元信息文件，定义：
- 技能名
- 平台
- Linux 运行要求
- 支持动作
- 所需工具
- 安全约束
- 批量策略

### `CHANGELOG.md`

版本更新日志，记录每个版本新增的能力与说明。

### `SKILL.md`

技能主说明文件，定义：
- 技能用途
- 触发场景
- 输入要求
- 安全规则
- 草稿准备、发布检查、正式发布、状态核对等流程

### `browser-workflow.md`

浏览器执行清单，定义自动化过程中每个阶段的：
- 操作步骤
- 成功条件
- 失败条件
- 停止条件
- 截图检查点

### `execution-policy.md`

执行策略文件，定义：
- Linux 运行约定
- 超时策略
- 重试策略
- 发布策略
- 批量策略
- 日志策略
- 截图策略
- 最小输出约定

### `selectors.md`

页面定位策略文件，定义：
- 如何识别后台首页
- 如何定位作品列表、章节列表、编辑器
- 如何识别保存草稿按钮和发布按钮
- 如何判断成功、失败和异常

### `input.schema.json`

输入 Schema，用于校验：
- 单章节输入
- 批量章节输入
- Linux 风格路径
- 动作合法性

### `output.schema.json`

输出 Schema，用于统一运行结果的机器可读结构。

### `chapter.template.json`

单章节输入模板。

### `chapter.example.json`

单章节示例文件，可直接用于测试。

### `chapters.batch.template.json`

批量章节输入模板。

### `chapters.batch.example.json`

批量章节示例文件，适合测试批量草稿流程。

### `publish-log.template.md`

发布日志模板，用于统一记录每次运行结果。

### `test-cases.md`

测试用例文件，覆盖：
- 单章草稿成功
- 发布前检查成功
- 发布成功
- 批量草稿成功
- 登录失效、验证码、重复标题、保存不明确、发布不明确等失败路径

### `real-site-calibration-checklist.md`

真实页面联调清单，用于第一次接入番茄后台时做页面校准。

### `setup-linux.sh`

Linux 初始化脚本草案，用于：
- 创建目录
- 检查关键文件
- 初始化 `logs/` 和 `screenshots/`
- 给出下一步联调入口

### `examples/playwright-fanqie-publish-example.js`

一个基于 Playwright 的番茄发布流程示例脚本，覆盖：
- 直达新建草稿 / 新建章节入口
- 标题写入
- ProseMirror 正文写入
- 正文字数同步等待
- 存草稿校验
- 可选的发布入口检测

### `skills/fanqie-publisher/`

一个面向番茄章节发布任务的 skill 三件套，包含：
- `SKILL.md`
- `templates/task-template.txt`
- `scripts/prepare-task.js`

适合把番茄发布流程从临时 prompt 整理成可复用任务模板。

### `skills/qimao-publisher/`

一个面向七猫章节发布任务的 skill 三件套，结构与 `fanqie-publisher` 对齐，便于后续复用与联调。

### `skills/baidu-publisher/`

一个面向百度作家平台章节发布任务的 skill 三件套，结构与 `fanqie-publisher` 对齐，便于后续复用与联调。

## 输入文件说明

### 单章节输入

推荐使用 `chapter.template.json`。

示例：

```json
{
  "book_name": "作品名",
  "chapter_title": "第12章 夜雨将至",
  "chapter_body": "这里是正文内容。\n\n第二段内容。",
  "action": "prepare_draft",
  "publish_time": "",
  "source_file": "/home/author/novel/chapter12.md",
  "notes": "发布前检查结尾伏笔。"
}
```

### 批量章节输入

推荐使用 `chapters.batch.template.json`。

示例结构：

```json
{
  "book_name": "作品名",
  "default_action": "prepare_draft",
  "chapters": [
    {
      "chapter_title": "第12章 标题示例",
      "chapter_body": "这里填写第12章正文。",
      "publish_time": "",
      "source_file": "/home/author/novel/book/chapter12.md",
      "notes": "发布前检查段落与结尾。"
    }
  ]
}
```

## 建议动作枚举

建议在单章节或批量章节数据中使用以下动作值：

- `prepare_draft`
- `review`
- `publish`
- `reconcile`

## Linux 环境建议

如果在 Linux 环境下运行 OpenClaw，建议：
- 所有 JSON、Markdown 文件统一使用 UTF-8 编码
- 使用 LF 换行
- 本地章节路径统一使用 Linux 风格，例如 `/home/author/novel/...`
- 日志放在 `logs/` 目录
- 截图放在 `screenshots/` 目录
- 浏览器使用独立 profile，避免和日常账号混用

推荐部署目录：

```text
/home/author/openclaw-skills/fanqie-author-publish/
```

## Linux 初始化

把技能目录复制到 Linux 后，可以执行：

```bash
bash setup-linux.sh
```

如果需要指定父目录：

```bash
bash setup-linux.sh /home/author/openclaw-skills
```

这个脚本会：
- 创建技能目录
- 创建 `logs/` 和 `screenshots/`
- 检查关键文件是否存在
- 输出下一步联调建议

## 效果展示

以下截图用于展示当前技能包在番茄作者后台联调过程中的页面效果：

![页面截图 1](docs/images/screenshot-1.png)

![页面截图 2](docs/images/screenshot-2.png)

![页面截图 3](docs/images/screenshot-3.png)

![页面截图 4](docs/images/screenshot-4.png)

## 推荐工作流

### 1. 准备章节数据

如果只处理单章：
- 复制 `chapter.template.json`
- 或直接参考 `chapter.example.json`

如果要批量准备多章：
- 复制 `chapters.batch.template.json`
- 或直接参考 `chapters.batch.example.json`

### 2. 手动登录番茄作者后台

在浏览器中手动登录目标作者账号。

建议：
- 使用专门的浏览器配置文件
- 不要把密码、Cookie、短信验证码交给代理

### 2.1 登录态保持说明

不建议提取、导出或复用站点 Cookie。

推荐做法是：
- 在 Linux 主机上为番茄作者后台使用固定浏览器 profile
- 用这个 profile 手动登录一次
- 后续始终复用同一个 profile 运行 OpenClaw 浏览器流程

推荐目录：

```text
/home/author/browser-profiles/fanqie/
```

示例启动方式：

```bash
chromium --user-data-dir=/home/author/browser-profiles/fanqie
```

或者：

```bash
google-chrome --user-data-dir=/home/author/browser-profiles/fanqie
```

操作建议：
1. 用固定 profile 启动浏览器
2. 手动登录番茄作者后台
3. 完成短信验证、验证码或安全确认
4. 关闭浏览器后，再用同一个 profile 重新打开，确认登录态仍在
5. 让 OpenClaw 绑定这个固定 profile，而不是新建临时浏览器会话

为了尽量保持登录稳定：
- 固定浏览器版本
- 固定 profile
- 尽量固定网络出口和设备环境
- 不要频繁切换 IP、时区、UA 或分辨率
- 不要把登录敏感信息放进自动化流程

### 3. 执行真实页面联调

第一次接入真实页面时，不要先做正式发布。

先按以下文件进行联调：

```text
real-site-calibration-checklist.md
```

联调重点：
- 后台首页识别
- 作品列表定位
- 作品管理页定位
- 章节列表定位
- 编辑器定位
- 草稿保存成功信号
- 发布确认弹窗文案
- 发布结果核对方式

### 4. 回填真实页面信息

联调完成后，优先更新：
- `selectors.md`
- `browser-workflow.md`
- `execution-policy.md`
- `test-cases.md`

### 5. 跑最小测试集

建议优先跑：
- `TC-001` 单章草稿保存成功
- `TC-002` 发布前检查成功
- `TC-005` 未登录时正确停止
- `TC-006` 验证码/风控时正确停止
- `TC-009` 编辑器未接受正文时正确停止
- `TC-011` 发布结果不明确时正确停止

### 6. 人工确认后发布

只有在当前会话里得到明确确认后，才执行最终发布。

建议使用口令式确认，例如：
- `确认发布这章`
- `继续发布`

## 推荐日志用法

每次执行后，建议至少记录以下内容：
- 当前时间
- 作品名和章节标题
- 执行动作
- 当前状态
- 是否保存草稿成功
- 是否发布成功
- 是否出现异常
- 截图路径

推荐方式：
- 复制 `publish-log.template.md`
- 存到 `logs/` 目录下
- 文件名可按时间和章节命名，例如：

```text
logs/2026-04-03_第12章_夜雨将至.md
```

## 安全建议

- 永远先保存草稿，再考虑正式发布
- 不要自动处理验证码和风控
- 如果页面结构变化，立即停止而不是盲点按钮
- 发布动作只执行一次，避免重复点击
- 每个关键节点尽量截图
- 不要在聊天中暴露账号敏感信息
- 不要导出、分享或跨环境搬运站点 Cookie

## 当前定位

这不是零散笔记，而是一套已经可以迁移到 Linux / OpenClaw 环境中的技能包骨架。

## 相关技能包

当前仓库除了 `fanqie-author-publish` 外，还包含以下同规格平台骨架：

- `qimao-author-publish`：面向七猫小说作者后台
- `baidu-author-publish`：面向百度作家平台

这些目录当前都采用统一的 Linux/OpenClaw 技能包结构，方便共享输入格式、日志格式和联调方法。
其中 `qimao-author-publish` 和 `baidu-author-publish` 都是基于 `fanqie-author-publish` 的结构派生出来的同规格技能骨架，用于快速扩展到其他作者平台。

## 多平台导航

## 派生关系简表

| 技能目录 | 目标平台 | 当前状态 | 来源关系 |
|---|---|---|---|
| `fanqie-author-publish` | 番茄小说作者后台 | 主模板，资料最完整 | 原始模板 |
| `qimao-author-publish` | 七猫小说作者后台 | 已建立同规格骨架 | 基于番茄结构派生 |
| `baidu-author-publish` | 百度作家平台 | 已建立同规格骨架 | 基于番茄结构派生 |

### `fanqie-author-publish`

- 平台：番茄小说作者后台
- 状态：已补充截图展示与较完整的联调资料
- 适合作为当前主模板继续迭代

### `qimao-author-publish`

- 平台：七猫小说作者后台
- 状态：已建立同规格技能骨架
- 来源：基于 `fanqie-author-publish` 派生
- 下一步重点：根据真实页面补按钮文案、编辑器类型和发布校验逻辑

### `baidu-author-publish`

- 平台：百度作家平台
- 状态：已建立同规格技能骨架
- 来源：基于 `fanqie-author-publish` 派生
- 下一步重点：根据真实页面补后台入口、章节编辑器与保存/发布信号

它已经定义了：
- 技能元信息
- 输入输出结构
- 浏览器流程
- 执行策略
- 页面定位策略
- 测试用例
- 联调清单
- Linux 初始化方式

## 后续可扩展方向

后面如果要继续完善，可以增加：
- `validate-input.sh`
- 自动生成发布日志的脚本
- 更细的页面选择规则
- 与 OpenClaw 运行时更直接的动作映射
- 批量任务的恢复与续跑机制
````

## File: skills/fanqie-publisher/README.md
size: 606
sha: 177525fadad464cdf7afbd0116eb50cef6238e17
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/fanqie-publisher/README.md

````text
# fanqie-publisher

一个可复用的番茄章节发布 skill，用于把临时 prompt 转成结构化任务模板。

## 包含内容

- `SKILL.md`：技能定义与执行规则
- `templates/task-template.txt`：参数化任务模板
- `scripts/prepare-task.js`：任务单渲染脚本

## 用法示例

```bash
BOOK_ID=7624670035260230680 \
TITLE='第一章 穿越天津卫' \
BODY_PATH='/tmp/ch1-clean.txt' \
AUTO_PUBLISH=false \
MIN_WORDS=20 \
node skills/fanqie-publisher/scripts/prepare-task.js /tmp/fanqie-task.txt
```

生成后可把 `/tmp/fanqie-task.txt` 交给浏览器代理或 ACP 执行。
````

## File: baidu-author-publish/browser-workflow.md
size: 470
sha: ff540d0a665a751596f105be4dd8c04ece9efa17
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/browser-workflow.md

```text
# 百度作家平台浏览器工作流

## 前置条件

- 用户已经登录百度作家平台后台
- 目标章节内容已经准备好
- 当前浏览器会话使用的是正确的作者账号
- 当前没有验证码或风控提示

## 工作流阶段

1. 打开百度作家平台后台
2. 定位目标作品
3. 打开章节列表或章节编辑器
4. 填写标题和正文
5. 保存草稿
6. 检查发布条件
7. 在人工确认后发布
8. 核对最近章节状态
```

## File: baidu-author-publish/chapter.example.json
size: 409
sha: 22f1a33bdb4c2cdd772f3ef3586e15ec500ca527
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/chapter.example.json

```text
{
  "book_name": "夜行者日志",
  "chapter_title": "第12章 夜雨将至",
  "chapter_body": "雨是半夜落下来的。\n\n林砚被窗沿上的滴水声吵醒时，整座旧城还陷在断电后的黑暗里。",
  "action": "prepare_draft",
  "publish_time": "",
  "source_file": "/home/author/novel/baidu/夜行者日志/第12章_夜雨将至.md",
  "notes": "发布前检查分段是否正常。"
}
```

## File: baidu-author-publish/chapter.template.json
size: 306
sha: 56271c9becba6837ccb9cd58e2535157f7a0d4cd
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/chapter.template.json

```text
{
  "book_name": "作品名",
  "chapter_title": "第12章 标题示例",
  "chapter_body": "这里填写章节正文。\n\n如果有分段，直接换行即可。",
  "action": "prepare_draft",
  "publish_time": "",
  "source_file": "/home/author/novel/baidu/chapter12.md",
  "notes": "可选备注。"
}
```

## File: baidu-author-publish/chapters.batch.example.json
size: 823
sha: ec49e808fcd940263763b68c2698e9e4257328f2
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/chapters.batch.example.json

```text
{
  "book_name": "夜行者日志",
  "default_action": "prepare_draft",
  "chapters": [
    {
      "chapter_title": "第12章 夜雨将至",
      "chapter_body": "雨是半夜落下来的。\n\n林砚被窗沿上的滴水声吵醒时，整座旧城还陷在断电后的黑暗里。",
      "publish_time": "",
      "source_file": "/home/author/novel/baidu/夜行者日志/第12章_夜雨将至.md",
      "notes": "发布前检查分段是否正常。"
    },
    {
      "chapter_title": "第13章 旧巷来客",
      "chapter_body": "巷口的脚步声最终停在了楼下。\n\n林砚屏住呼吸，听见有人轻轻敲了三下铁门。",
      "publish_time": "",
      "source_file": "/home/author/novel/baidu/夜行者日志/第13章_旧巷来客.md",
      "notes": "注意本章结尾停顿。"
    }
  ]
}
```

## File: baidu-author-publish/chapters.batch.template.json
size: 348
sha: f27c4a4a3e372823f3e60b169db257d15c4bbc0b
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/chapters.batch.template.json

```text
{
  "book_name": "作品名",
  "default_action": "prepare_draft",
  "chapters": [
    {
      "chapter_title": "第12章 标题示例",
      "chapter_body": "这里填写第12章正文。",
      "publish_time": "",
      "source_file": "/home/author/novel/baidu/book/chapter12.md",
      "notes": "发布前检查段落与结尾。"
    }
  ]
}
```

## File: baidu-author-publish/execution-policy.md
size: 547
sha: a2c3d17167f53a184b5c1b1e3f9d3d514413475d
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/execution-policy.md

```text
# 执行策略

- 操作系统：Linux
- 文件编码：UTF-8
- 换行：LF
- 浏览器：使用独立作者账号 profile
- 不依赖提取、导出或复用站点 Cookie
- 使用固定浏览器 profile 持久化登录态
- 首次登录由用户手动完成
- 任何发布前必须先保存草稿
- 只有在当前会话中得到明确确认后，才允许点击最终发布
- 非破坏性动作最多重试 2 次
- 破坏性动作默认只执行 1 次
- 出现验证码、风控、账号不匹配、页面结构明显变化时必须立即停止
```

## File: baidu-author-publish/input.schema.json
size: 2033
sha: 9cdce9c7045bdb890ab603bb5a3bf3ea318a5d20
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/input.schema.json

```text
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/baidu-author-publish/input.schema.json",
  "title": "baidu-author-publish input schema",
  "oneOf": [
    { "$ref": "#/$defs/singleChapterInput" },
    { "$ref": "#/$defs/batchChapterInput" }
  ],
  "$defs": {
    "action": {
      "type": "string",
      "enum": ["prepare_draft", "review", "publish", "reconcile"]
    },
    "singleChapterInput": {
      "type": "object",
      "additionalProperties": false,
      "required": ["book_name", "chapter_title", "chapter_body", "action"],
      "properties": {
        "book_name": { "type": "string", "minLength": 1 },
        "chapter_title": { "type": "string", "minLength": 1 },
        "chapter_body": { "type": "string", "minLength": 1 },
        "action": { "$ref": "#/$defs/action" },
        "publish_time": { "type": "string", "default": "" },
        "source_file": { "type": "string", "default": "" },
        "notes": { "type": "string", "default": "" }
      }
    },
    "batchChapterInput": {
      "type": "object",
      "additionalProperties": false,
      "required": ["book_name", "default_action", "chapters"],
      "properties": {
        "book_name": { "type": "string", "minLength": 1 },
        "default_action": { "$ref": "#/$defs/action" },
        "chapters": {
          "type": "array",
          "minItems": 1,
          "maxItems": 5,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["chapter_title", "chapter_body"],
            "properties": {
              "chapter_title": { "type": "string", "minLength": 1 },
              "chapter_body": { "type": "string", "minLength": 1 },
              "action": { "$ref": "#/$defs/action" },
              "publish_time": { "type": "string", "default": "" },
              "source_file": { "type": "string", "default": "" },
              "notes": { "type": "string", "default": "" }
            }
          }
        }
      }
    }
  }
}
```

## File: baidu-author-publish/logs/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/logs/.gitkeep

```text

```

## File: baidu-author-publish/manifest.json
size: 646
sha: 6a9a873211299c67e62a8a96f35639130ec06157
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/manifest.json

```text
{
  "name": "baidu-author-publish",
  "display_name": "百度作家平台发布技能",
  "version": "0.1.0",
  "description": "一个用于百度作家平台后台的半自动发布技能包，支持准备草稿、发布前检查、人工确认后发布，以及章节状态核对。",
  "language": "zh-CN",
  "platform": "OpenClaw",
  "runtime": {
    "os": ["linux"],
    "encoding": "UTF-8",
    "line_endings": "LF"
  },
  "risk_level": "medium",
  "human_in_the_loop": true,
  "supported_actions": ["prepare_draft", "review", "publish", "reconcile"],
  "required_tools": ["browser"],
  "optional_tools": ["files", "screenshots", "logs"]
}
```

## File: baidu-author-publish/output.schema.json
size: 1302
sha: f65908a541b379d732af3217b69788e8719e6691
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/output.schema.json

```text
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/baidu-author-publish/output.schema.json",
  "title": "baidu-author-publish output schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["state", "action", "book_name", "result", "warning", "log_path", "screenshot_paths"],
  "properties": {
    "state": {
      "type": "string",
      "enum": ["pending", "backend_opened", "book_selected", "editor_opened", "draft_loaded", "draft_saved", "awaiting_confirmation", "published_verified", "failed_needs_review"]
    },
    "action": {
      "type": "string",
      "enum": ["prepare_draft", "review", "publish", "reconcile"]
    },
    "book_name": { "type": "string", "minLength": 1 },
    "chapter_title": { "type": "string", "default": "" },
    "result": { "type": "string", "minLength": 1 },
    "warning": { "type": "string", "default": "" },
    "blocking_reason": { "type": "string", "default": "" },
    "requires_manual_action": { "type": "boolean", "default": false },
    "confirmation_required": { "type": "boolean", "default": false },
    "log_path": { "type": "string" },
    "screenshot_paths": { "type": "array", "items": { "type": "string" }, "default": [] },
    "next_step": { "type": "string", "default": "" }
  }
}
```

## File: baidu-author-publish/publish-log.template.md
size: 478
sha: 9e02a8d044ce629fd7fdba45b3baa61ab55d668d
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/publish-log.template.md

```text
# 发布记录模板

- 记录时间：`YYYY-MM-DD HH:MM:SS`
- 平台：`百度作家平台`
- 作品名：`作品名`
- 章节标题：`章节标题`
- 动作：`prepare_draft / review / publish / reconcile`
- 当前状态：`pending / draft_loaded / draft_saved / awaiting_confirmation / published_verified / failed_needs_review`
- 结果：`成功 / 失败 / 部分完成`
- 结果说明：`这里写执行结果`
- 截图：`screenshots/...`
- 异常说明：`如有则写`
```

## File: baidu-author-publish/real-site-calibration-checklist.md
size: 4431
sha: 6915a711ff419eee931c7bb7aee042f105e40048
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/real-site-calibration-checklist.md

````text
# 百度作家平台真实页面联调清单

这个清单用于第一次把 `baidu-author-publish` 接到 Linux / OpenClaw / 百度作家平台真实页面时做联调。

目标不是直接发布，而是先把页面结构、关键文案、成功信号、失败信号校准清楚，再回填到：
- `selectors.md`
- `browser-workflow.md`
- `execution-policy.md`

## 联调前准备

开始前确认：
- Linux 环境下 OpenClaw 已能调用浏览器工具
- 使用独立作者账号浏览器 profile
- 百度作家平台已手动登录
- `logs/` 可写
- `screenshots/` 可写
- 准备一份测试章节，避免用正式发布内容做第一次联调

建议准备：
- 一部测试作品
- 1 到 2 章测试草稿文本
- 一份联调日志

## 联调顺序

建议按这个顺序进行：
1. 后台首页识别
2. 作品列表定位
3. 作品管理页定位
4. 章节列表定位
5. 编辑器定位
6. 草稿保存验证
7. 发布按钮和确认弹窗验证
8. 发布结果核对验证

## 1. 后台首页识别

### 需要确认的内容

- 登录后的后台首页 URL
- 页面标题文字
- 侧边栏或顶部导航中的关键文案
- 如何区分“已登录后台”和“登录页”

### 需要记录

- 后台首页可见关键文案
- 登录页可见关键文案
- 是否存在作者昵称或账号显示区域

## 2. 作品列表定位

### 需要确认的内容

- 作品列表页 URL 或导航路径
- 页面上作品标题的展示位置
- 是否存在搜索框
- 作品卡片上的“管理”“进入”“详情”等按钮真实文案

### 需要记录

- 搜索框是否可用
- 标题匹配是否支持精确搜索
- 如果作品名重复，页面如何区分

## 3. 作品管理页定位

### 需要确认的内容

- 进入某个作品后的页面标题
- 是否显示当前作品名
- “章节管理”入口的真实文案
- 是否还有“内容管理”“作品设置”等相近入口

### 需要记录

- 章节管理入口位置
- 如果有多个相似入口，哪个才是正确入口

## 4. 章节列表定位

### 需要确认的内容

- 章节列表页是否显示标题列和状态列
- “新建章节”按钮真实文案
- 已有草稿的“编辑”入口文案
- 是否能直接看到“草稿”“已发布”等状态

### 需要记录

- 标题列位置
- 状态列真实文案
- 重复标题在界面上如何表现

## 5. 编辑器定位

### 需要确认的内容

- 标题输入框的真实标签或 placeholder
- 正文区域是否是普通输入框、富文本、`contenteditable` 还是 iframe
- 正文输入后能否立即读取到内容
- 编辑器是否会自动格式化或截断内容

### 需要记录

- 标题输入框定位方式
- 正文编辑器定位方式
- 输入正文后最稳的验证方法
- 是否存在延迟加载或首输失效问题

## 6. 草稿保存验证

### 需要确认的内容

- “保存草稿”按钮真实文案
- 点击后是否出现 toast
- toast 文案是什么
- 如果没有 toast，在哪能看到已保存状态

### 需要记录

- 最可靠的成功判断信号
- 没有成功提示时的备选验证路径
- 保存失败时的错误提示文案

## 7. 发布按钮和确认弹窗验证

### 需要确认的内容

- 发布按钮真实文案
- 是否存在确认弹窗
- 确认弹窗标题和按钮文案
- 是否存在二次确认或风险提示

### 需要记录

- 发布前是否要求章节完整性检查
- 确认按钮真实文案
- 是否存在“取消”“继续”“确认发布”等多种按钮

## 8. 发布结果核对验证

### 需要确认的内容

- 发布成功后的 toast 或提示文案
- 发布成功后页面是否跳转
- 回章节列表后状态如何显示
- 是否有发布时间或更新时间字段

### 需要记录

- 最可靠的发布成功判断信号
- 发布结果不明确时的人工核对路径

## 联调记录模板

```text
校准项：
页面名称：
实际文案：
实际按钮文字：
成功信号：
失败信号：
截图路径：
需要更新的文件：
```

## 联调完成标准

满足以下条件后，可以进入真实试跑：

- 已确认后台首页和登录页区分信号
- 已确认目标作品定位方式
- 已确认章节编辑器真实类型
- 已确认保存草稿成功信号
- 已确认发布按钮和确认弹窗真实文案
- 已确认发布结果核对方式
- `selectors.md` 已回填真实文案
- `test-cases.md` 可据此执行最小验收
````

## File: baidu-author-publish/screenshots/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/screenshots/.gitkeep

```text

```

## File: baidu-author-publish/selectors.md
size: 535
sha: e6440c00077950bfd91d6eb023a4e137cdf7e374
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/selectors.md

```text
# 页面定位与选择策略

## 总体原则

- 优先使用明确可见的按钮或标题文字
- 优先定位作品名、章节管理、保存草稿、发布等关键文案
- 不要依赖脆弱的 class 哈希或位置猜测

## 需要联调确认的区域

- 作者后台首页识别信号
- 作品列表页中的作品卡片与管理入口
- 章节列表页中的新建章节按钮
- 标题输入框和正文编辑区域
- 保存草稿按钮文案
- 发布按钮和确认弹窗文案
- 成功保存与成功发布的提示文案
```

## File: baidu-author-publish/setup-linux.sh
size: 327
sha: 33a6d514d3ace5ffdeb23d085a62ea96201e40e6
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/setup-linux.sh

```text
#!/usr/bin/env bash

set -euo pipefail

BASE_DIR="${1:-$HOME/openclaw-skills}"
SKILL_DIR="$BASE_DIR/baidu-author-publish"

mkdir -p "$SKILL_DIR/logs"
mkdir -p "$SKILL_DIR/screenshots"

echo "初始化完成: $SKILL_DIR"
echo "下一步：手动登录百度作家平台，并按 real-site-calibration-checklist.md 做联调。"
```

## File: baidu-author-publish/test-cases.md
size: 436
sha: 4a1af0de18071761102096e718cc11bfb16e2a3a
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/test-cases.md

```text
# 测试用例

- `TC-001` 单章草稿保存成功
- `TC-002` 发布前检查成功
- `TC-003` 单章发布成功
- `TC-004` 批量草稿准备成功
- `TC-005` 未登录后台时正确停止
- `TC-006` 出现验证码或风控时正确停止
- `TC-007` 找不到目标作品时正确停止
- `TC-008` 标题重复时正确停止
- `TC-009` 编辑器未接受正文时正确停止
- `TC-010` 保存草稿结果不明确时正确停止
```

## File: baidu-author-publish/validate-input.sh
size: 331
sha: dcedb0e963ded852c1899a164d4022877fb622bf
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/baidu-author-publish/validate-input.sh

```text
#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCHEMA_FILE="$SCRIPT_DIR/input.schema.json"
INPUT_FILE="${1:-}"

if [[ -z "$INPUT_FILE" ]]; then
  echo "用法: bash validate-input.sh /path/to/input.json"
  exit 1
fi

npx --yes ajv-cli validate -s "$SCHEMA_FILE" -d "$INPUT_FILE"
```

## File: browser-workflow.md
size: 5060
sha: 226ca6fe50e3adc4ae57c66aa2577768add94c81
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/browser-workflow.md

````text
# 番茄浏览器工作流

## 目标

这个文档定义了 `fanqie-author-publish` 技能在浏览器侧的执行清单。

它用于指导 OpenClaw 的浏览器自动化流程，确保步骤清晰、可验证、可中断。

## 前置条件

开始前必须满足：
- 用户已经登录番茄作者后台
- 目标章节内容已经从 JSON 文件或粘贴内容中准备好
- 当前浏览器会话使用的是正确的作者账号
- 当前没有验证码或风控提示

任何一个前置条件不满足，都要停止并报告原因。

## 必需输入

- `book_name`
- `chapter_title`
- `chapter_body`
- `action`

可选输入：
- `publish_time`
- `notes`
- `source_file`

## 工作流状态

- `pending`
- `backend_opened`
- `book_selected`
- `editor_opened`
- `draft_loaded`
- `draft_saved`
- `awaiting_confirmation`
- `published_verified`
- `failed_needs_review`

## 阶段 1：打开后台

1. 打开番茄作者后台地址。
2. 等待页面完全加载。
3. 确认当前页面不是登录页。
4. 如果页面可见账号信息，确认当前账号与预期作者账号一致。

成功条件：
- 已看到作者后台首页或工作台

失败条件：
- 出现登录页
- 出现验证码
- 后台页面加载失败

## 阶段 2：定位目标作品

1. 找到作品列表、书籍列表或创作管理入口。
2. 进入作品列表。
3. 搜索或人工可见定位 `book_name`。
4. 打开匹配作品的管理页。

成功条件：
- 当前作品页明确匹配 `book_name`

失败条件：
- 没有找到匹配作品
- 存在多个相似作品且无法明确区分

## 阶段 3：打开章节编辑器

1. 找到章节管理入口。
2. 打开章节列表或章节编辑页。
3. 根据 `action` 点击新建章节或编辑现有草稿。
4. 等待编辑器完全加载。

成功条件：
- 可以看到标题输入框和章节编辑区域

失败条件：
- 编辑器没有加载完成
- 编辑器处于只读状态
- 页面发生意外跳转

## 阶段 4：填写章节内容

1. 如有必要，先清空章节标题输入框。
2. 输入 `chapter_title`。
3. 聚焦章节正文编辑器。
4. 填入 `chapter_body`。
5. 短暂等待后确认正文确实显示在编辑器中。

重点检查：
- 标题输入框中是否为预期标题
- 编辑器中是否存在非空正文
- 正文是否出现截断或缺失

成功条件：
- 标题和正文都正确显示

失败条件：
- 标题没有成功写入
- 正文没有出现在编辑器中
- 正文内容部分缺失

## 阶段 5：保存草稿

这个阶段用于 `prepare_draft`，也用于任何最终发布前的预保存动作。

1. 定位“保存草稿”按钮。
2. 点击一次保存草稿。
3. 等待成功提示、状态标签、跳转结果或列表更新。
4. 截图保存。

成功条件：
- 出现草稿保存成功提示，或在章节列表中能看到草稿状态

失败条件：
- 保存按钮不可点击
- 出现错误提示
- 等待后没有任何状态变化

## 阶段 6：检查发布条件

用于 `review`，也用于 `publish` 之前的最终检查。

检查：
- `book_name` 正确
- `chapter_title` 正确
- 正文存在
- 如果平台显示草稿状态，草稿已存在
- 如果平台显示发布按钮，发布按钮状态正常

返回以下之一：
- `ready to publish`
- `not ready`

如果不能发布，必须明确指出阻塞项。

## 阶段 7：发布

只有满足以下条件时才进入此阶段：
- `action` 为 `publish`
- 用户在当前会话中已明确确认最终发布

步骤：
1. 再次确认 `book_name` 和 `chapter_title`。
2. 找到最终发布按钮。
3. 只点击一次发布。
4. 如果出现确认弹窗，核对文案后只确认一次。
5. 等待发布成功结果。
6. 截图保存。
7. 如果可以，返回章节列表并确认章节状态为已发布。

成功条件：
- 出现成功提示，或章节状态清楚显示为已发布

失败条件：
- 找不到发布按钮
- 确认弹窗内容异常
- 等待超时且没有成功状态

## 阶段 8：核对最近章节

当 `action` 为 `reconcile` 时，执行这个阶段。

1. 打开 `book_name` 对应的章节列表。
2. 读取最近章节标题和状态。
3. 与提供的本地章节列表进行比对。
4. 输出不一致项。

不一致类型包括：
- 平台缺少章节
- 标题重复
- 仍然是草稿
- 出现意外的额外章节

## 停止条件

出现以下任一情况时，必须立即停止：
- 登录页
- 验证码或人工校验
- 风控警告
- 账号不匹配
- 页面文案或结构与预期流程明显不一致

## 截图节点

在这些节点建议截图：
- 后台首页加载完成
- 目标作品页面打开
- 编辑器填写完成
- 草稿保存完成
- 发布成功或失败

## 最小输出约定

每次运行都应输出：
- `state`
- `book_name`
- `chapter_title`
- `action`
- `result`
- `warning`
- 截图状态

示例输出：

```text
state: draft_saved
book_name: 作品名
chapter_title: 第12章 标题示例
action: prepare_draft
result: 已成功保存草稿。
warning: 无。
```
````

## File: CHANGELOG.md
size: 4866
sha: 2f37009ccb4d60e259fed062470d18eb6d8de233
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/CHANGELOG.md

```text
# Changelog

All notable changes to this project will be documented in this file.

## [0.1.4] - 2026-04-04

### Added

- Added `skills/qimao-publisher/` as a reusable Qimao chapter publishing skill package.
- Added `skills/baidu-publisher/` as a reusable Baidu author platform chapter publishing skill package.
- Added `skills/fanqie-publisher/README.md` with usage guidance for the Fanqie publisher skill.
- Added README entries for the Qimao and Baidu reusable publisher skill packages.

### Notes

- The repository now contains reusable publisher skill packages for Fanqie, Qimao, and Baidu author workflows.
- `qimao-publisher` and `baidu-publisher` mirror the Fanqie skill structure to keep cross-platform task generation consistent.

## [0.1.3] - 2026-04-04

### Added

- Added `examples/playwright-fanqie-publish-example.js` as a Playwright-based Fanqie publishing flow example.
- Added `skills/fanqie-publisher/` as a reusable Fanqie chapter publishing skill package.
- Added `skills/fanqie-publisher/SKILL.md` for task definition and execution rules.
- Added `skills/fanqie-publisher/templates/task-template.txt` for parameterized browser task generation.
- Added `skills/fanqie-publisher/scripts/prepare-task.js` for rendering task files from environment variables.
- Added README entries for the Playwright example and the reusable Fanqie publisher skill.

### Notes

- This release turns the Fanqie browser workflow into a reusable skill structure instead of keeping it only as ad hoc prompt guidance.
- The example script and skill template are intended to support both draft-saving and optional publish flows.

## [0.1.2] - 2026-04-04

### Added

- Added persistent browser profile login guidance to `fanqie-author-publish/README.md`.
- Added login-state handling policy to `fanqie-author-publish/execution-policy.md`.
- Added matching persistent profile login guidance to `qimao-author-publish` and `baidu-author-publish` README files.
- Added matching login-state handling policy to `qimao-author-publish` and `baidu-author-publish` execution policy files.

### Notes

- The project now consistently recommends persistent browser profiles instead of extracting or reusing site cookies.
- Manual login remains the expected workflow for all supported platforms.
- This guidance is aligned across Fanqie, Qimao, and Baidu author platform skill packages.

## [0.1.1] - 2026-04-04

### Added

- Added `qimao-author-publish` as a Fanqie-derived skill skeleton for Qimao author workflows.
- Added `baidu-author-publish` as a Fanqie-derived skill skeleton for Baidu author workflows.
- Added Linux/OpenClaw-aligned manifests, schemas, workflow files, setup scripts, and validation scripts for both new platform packages.
- Expanded `qimao-author-publish/real-site-calibration-checklist.md` into a full first-time calibration checklist.
- Expanded `baidu-author-publish/real-site-calibration-checklist.md` into a full first-time calibration checklist.
- Added multi-platform navigation and derived-skill notes to the main `README.md`.

### Notes

- `fanqie-author-publish` remains the primary template and the most complete reference package.
- `qimao-author-publish` and `baidu-author-publish` are intentionally derived from the Fanqie package structure to accelerate cross-platform expansion.
- Both derived packages still require real-site calibration before production use.

## [0.1.0] - 2026-04-04

### Added

- Initial `fanqie-author-publish` OpenClaw skill package structure for Linux deployments.
- Core skill documentation in `SKILL.md`.
- End-to-end browser execution guide in `browser-workflow.md`.
- Linux-oriented execution rules in `execution-policy.md`.
- Page targeting and selector strategy in `selectors.md`.
- Input validation schema in `input.schema.json`.
- Output result schema in `output.schema.json`.
- Skill metadata manifest in `manifest.json`.
- Single chapter input template in `chapter.template.json`.
- Single chapter example input in `chapter.example.json`.
- Batch chapter input template in `chapters.batch.template.json`.
- Batch chapter example input in `chapters.batch.example.json`.
- Publish log template in `publish-log.template.md`.
- Example log entry in `logs/example-log.md`.
- Real-site calibration checklist in `real-site-calibration-checklist.md`.
- Test coverage plan in `test-cases.md`.
- Linux setup helper script in `setup-linux.sh`.
- Input validation helper script in `validate-input.sh`.
- Persistent placeholder directories for `logs/` and `screenshots/`.
- Project README covering Linux/OpenClaw deployment and workflow.

### Notes

- The package is designed for a human-in-the-loop workflow.
- Manual login is expected in the target browser profile.
- Final publish actions require explicit confirmation.
- Real-site calibration is still required before production use against the Fanqie author backend.
```

## File: chapter.example.json
size: 1030
sha: a09ec53a5f9ad25f3f6b6c1f275da9e69c4f08f1
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/chapter.example.json

```text
{
  "book_name": "夜行者日志",
  "chapter_title": "第12章 夜雨将至",
  "chapter_body": "雨是半夜落下来的。\n\n林砚被窗沿上的滴水声吵醒时，整座旧城还陷在断电后的黑暗里。\n\n他摸黑坐起身，先听见了风，再听见楼下巷口传来的脚步声。那脚步声很慢，像是在故意等人听见。\n\n桌上的旧手机没有信号，电量也只剩最后一格。屏幕亮起又熄灭的瞬间，他看见窗玻璃上倒映出一个模糊的人影。\n\n可他很清楚，自己房间里只有他一个人。\n\n他没有立刻回头，只是伸手摸向枕边那把折叠刀。冰凉的金属贴上掌心时，他忽然想起白天在书店里收到的那张纸条。\n\n纸条上只有一句话：今晚别关灯。\n\n而现在，整栋楼的灯都灭了。",
  "action": "prepare_draft",
  "publish_time": "",
  "source_file": "/home/author/novel/夜行者日志/第12章_夜雨将至.md",
  "notes": "发布前检查分段是否正常，确认结尾悬念保留。"
}
```

## File: chapter.template.json
size: 381
sha: 71e97febef9a1cc1a3b750bcd6239b7ac6ed0c93
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/chapter.template.json

```text
{
  "book_name": "作品名",
  "chapter_title": "第12章 标题示例",
  "chapter_body": "这里填写章节正文。\n\n建议保留自然换行，不要手动插入多余空格。\n\n如果有分段，直接换行即可。",
  "action": "prepare_draft",
  "publish_time": "",
  "source_file": "",
  "notes": "可选备注，例如：本章需要发布前再检查结尾。"
}
```

## File: chapters.batch.example.json
size: 1730
sha: 561c7211e21e1bffdc7f17daff90ce1c70da02ef
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/chapters.batch.example.json

```text
{
  "book_name": "夜行者日志",
  "default_action": "prepare_draft",
  "chapters": [
    {
      "chapter_title": "第12章 夜雨将至",
      "chapter_body": "雨是半夜落下来的。\n\n林砚被窗沿上的滴水声吵醒时，整座旧城还陷在断电后的黑暗里。\n\n他摸黑坐起身，先听见了风，再听见楼下巷口传来的脚步声。那脚步声很慢，像是在故意等人听见。",
      "publish_time": "",
      "source_file": "/home/author/novel/夜行者日志/第12章_夜雨将至.md",
      "notes": "发布前检查分段是否正常。"
    },
    {
      "chapter_title": "第13章 旧巷来客",
      "chapter_body": "巷口的脚步声最终停在了楼下。\n\n林砚屏住呼吸，听见有人轻轻敲了三下铁门。那不是熟人会用的节奏，却像是某种约定好的暗号。\n\n几秒后，门外传来一个低哑的女声。\n\n她只说了四个字。\n\n她说，开灯，别看。",
      "publish_time": "",
      "source_file": "/home/author/novel/夜行者日志/第13章_旧巷来客.md",
      "notes": "注意本章结尾的悬念停顿。"
    },
    {
      "chapter_title": "第14章 纸条的背面",
      "chapter_body": "他没有开门。\n\n可下一秒，一张被雨水打湿的纸条从门缝底下慢慢塞了进来。\n\n林砚盯着那张纸，迟迟没有弯腰去捡。直到窗外一道闪电照亮房间，他才看见纸条背面还写着另一句话。\n\n那句话比前一张更短，也更让人后背发冷。\n\n这次，轮到你了。",
      "publish_time": "",
      "source_file": "/home/author/novel/夜行者日志/第14章_纸条的背面.md",
      "notes": "确认本章末尾单独成段显示。"
    }
  ]
}
```

## File: chapters.batch.template.json
size: 709
sha: 8020f7a32684cc32c460967d8ca4e0cbba66e750
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/chapters.batch.template.json

```text
{
  "book_name": "作品名",
  "default_action": "prepare_draft",
  "chapters": [
    {
      "chapter_title": "第12章 标题示例",
      "chapter_body": "这里填写第12章正文。\n\n分段可以直接保留自然换行。",
      "publish_time": "",
      "source_file": "/home/author/novel/book/chapter12.md",
      "notes": "发布前检查段落与结尾。"
    },
    {
      "chapter_title": "第13章 标题示例",
      "chapter_body": "这里填写第13章正文。\n\n如果是批量准备草稿，可以按顺序填写多个章节。",
      "publish_time": "",
      "source_file": "/home/author/novel/book/chapter13.md",
      "notes": "确认本章与上一章衔接。"
    }
  ]
}
```

## File: docs/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/.gitkeep

```text

```

## File: docs/images/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/.gitkeep

```text

```

## File: docs/images/screenshot-1.png
size: 152943
sha: dec815178ff50762e52b68791cb75556bcb775a8
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-1.png

Content is not inlined in the catalog. Read the upstream file: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-1.png

## File: docs/images/screenshot-2.png
size: 113839
sha: d149ba1a3f668d90f5e382303a7dd31cddea918b
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-2.png

Content is not inlined in the catalog. Read the upstream file: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-2.png

## File: docs/images/screenshot-3.png
size: 118373
sha: 71416612d7601bbf28cae4e00b9c61f8cb43e466
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-3.png

Content is not inlined in the catalog. Read the upstream file: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-3.png

## File: docs/images/screenshot-4.png
size: 55875
sha: 6fd8cba15315a0cee8d41a06bfc13f3376cb033d
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-4.png

Content is not inlined in the catalog. Read the upstream file: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/docs/images/screenshot-4.png

## File: examples/playwright-fanqie-publish-example.js
size: 9584
sha: 829018b06348fb62af59f010e404c76861d8b870
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/examples/playwright-fanqie-publish-example.js

```text
import fs from 'node:fs/promises';

const BOOK_ID = '7624670035260230680';
const TITLE = '第一章 穿越天津卫';
const BODY_PATH = '/tmp/ch1-clean.txt';

// 生产建议默认 false，确认稳定后再改 true
const AUTO_PUBLISH = false;

// 测试时可设 20；正式发章节建议按平台要求调高
const MIN_WORDS = 20;

const NEW_DRAFT_URL = `https://fanqienovel.com/main/writer/${BOOK_ID}/publish/?enter_from=newdraft`;
const NEW_CHAPTER_URL = `https://fanqienovel.com/main/writer/${BOOK_ID}/publish/?enter_from=newchapter`;

const titleLocator = page.locator('input.serial-editor-input-hint-area[placeholder="请输入标题"]');
const editorLocator = page.locator('.serial-editor-container .ProseMirror[contenteditable="true"]').first();
const saveDraftBtn = page.locator('button.auto-editor-save-btn');
const headerLocator = page.locator('.publish-header');
const statusLocator = page.locator('.publish-maintain-info-status');
const bodyLocator = page.locator('body');
const toastLocator = page.locator('.semi-toast, .semi-notification, .toast, [class*="toast"]');

function nowTag() {
  const d = new Date();
  const pad = n => String(n).padStart(2, '0');
  return [
    d.getFullYear(),
    pad(d.getMonth() + 1),
    pad(d.getDate()),
    '-',
    pad(d.getHours()),
    pad(d.getMinutes()),
    pad(d.getSeconds())
  ].join('');
}

async function shot(name) {
  const path = `/tmp/${name}-${nowTag()}.png`;
  await page.screenshot({ path, fullPage: true });
  console.log('screenshot =', path);
  return path;
}

function extractWordCount(text) {
  const m = text.match(/正文字数\s*(\d+)/);
  return m ? Number(m[1]) : null;
}

async function getHeaderStatus() {
  const headerText = await headerLocator.innerText().catch(() => '');
  const statusText = await statusLocator.innerText().catch(() => '');
  return { headerText, statusText };
}

async function openEditorPage() {
  for (const url of [NEW_DRAFT_URL, NEW_CHAPTER_URL]) {
    await page.goto(url, { waitUntil: 'domcontentloaded' });
    await page.waitForTimeout(1800);

    const titleVisible = await titleLocator.first().isVisible().catch(() => false);
    const editorVisible = await editorLocator.first().isVisible().catch(() => false);

    console.log('try url =', url);
    console.log({
      titleVisible,
      editorVisible,
      currentUrl: page.url()
    });

    if (titleVisible && editorVisible) {
      return url;
    }
  }

  await shot('fanqie-open-editor-failed');
  throw new Error('未能进入番茄章节编辑器页面');
}

async function verifyEditorReady() {
  const titleVisible = await titleLocator.first().isVisible().catch(() => false);
  const editorVisible = await editorLocator.first().isVisible().catch(() => false);

  if (!titleVisible || !editorVisible) {
    await shot('fanqie-editor-missing');
    throw new Error('编辑器关键元素不存在或不可见');
  }
}

async function fillReactInput(locator, value) {
  await locator.waitFor({ state: 'visible', timeout: 10000 });
  await locator.click();

  await locator.evaluate((el, nextValue) => {
    const setter = Object.getOwnPropertyDescriptor(
      HTMLInputElement.prototype,
      'value'
    )?.set;

    if (!setter) throw new Error('HTMLInputElement.value setter not found');

    el.focus();
    setter.call(el, nextValue);
    el.dispatchEvent(new Event('input', { bubbles: true }));
    el.dispatchEvent(new Event('change', { bubbles: true }));
  }, value);

  await page.waitForTimeout(200);

  // 逼 React / 表单状态真正更新
  await locator.press('End').catch(() => {});
  await locator.press(' ').catch(() => {});
  await locator.press('Backspace').catch(() => {});
  await locator.blur().catch(() => {});
}

async function verifyTitle(expected) {
  const actual = await titleLocator.inputValue().catch(() => '');
  console.log('titleValue =', actual);

  if (actual !== expected) {
    await shot('fanqie-title-failed');
    throw new Error(`标题未成功写入，当前值: ${actual}`);
  }
}

async function fillProseMirror(locator, text) {
  await locator.waitFor({ state: 'visible', timeout: 10000 });
  await locator.click();
  await page.waitForTimeout(300);

  await page.keyboard.press('Control+A').catch(() => {});
  await page.keyboard.press('Backspace').catch(() => {});
  await page.waitForTimeout(300);

  // 优先尝试粘贴
  await page.evaluate(async value => {
    await navigator.clipboard.writeText(value);
  }, text);

  await page.keyboard.press('Control+V').catch(() => {});
  await page.waitForTimeout(1500);

  const editorText = await locator.evaluate(el => (el.innerText || '').trim()).catch(() => '');

  // 粘贴失败时退回逐段输入
  if (!editorText || editorText.length < 20) {
    console.log('paste fallback -> type mode');

    await locator.click();
    await page.waitForTimeout(200);

    const lines = text.replace(/\r\n/g, '\n').split('\n');
    for (const line of lines) {
      if (line === '') {
        await page.keyboard.press('Enter');
      } else {
        await page.keyboard.type(line, { delay: 10 });
        await page.keyboard.press('Enter');
      }
    }

    await page.waitForTimeout(1200);
  }
}

async function waitForWordCount(min = 20, timeout = 15000) {
  const start = Date.now();

  while (Date.now() - start < timeout) {
    const headerText = await headerLocator.innerText().catch(() => '');
    const count = extractWordCount(headerText);

    console.log('headerText =', headerText);
    console.log('wordCount =', count);

    if (count !== null && count >= min) {
      return count;
    }

    await page.waitForTimeout(500);
  }

  await shot('fanqie-wordcount-timeout');
  throw new Error('等待正文字数同步超时');
}

async function verifyEditorContent() {
  const editorText = await editorLocator.evaluate(el => (el.innerText || '').trim()).catch(() => '');
  console.log('editorText preview =', editorText.slice(0, 200));

  if (!editorText || editorText.length < 20) {
    await shot('fanqie-editor-empty');
    throw new Error('正文疑似未成功写入，编辑器内容过短');
  }

  return editorText;
}

async function waitForSaved(timeout = 15000) {
  const start = Date.now();

  while (Date.now() - start < timeout) {
    const { headerText, statusText } = await getHeaderStatus();
    const combined = `${headerText}\n${statusText}`;

    console.log('save status =', combined);

    if (/已保存|保存成功|草稿已保存/.test(combined)) {
      return { headerText, statusText };
    }

    const toastText = await toastLocator.first().innerText().catch(() => '');
    if (toastText) {
      console.log('toast =', toastText);
    }

    await page.waitForTimeout(500);
  }

  await shot('fanqie-save-timeout');
  throw new Error('等待保存成功状态超时');
}

async function saveDraftAndVerify() {
  await bodyLocator.click().catch(() => {});
  await page.waitForTimeout(400);

  await saveDraftBtn.waitFor({ state: 'visible', timeout: 10000 });

  const disabled = await saveDraftBtn.isDisabled().catch(() => false);
  console.log('saveDraft disabled =', disabled);

  if (disabled) {
    await shot('fanqie-save-disabled');
    throw new Error('存草稿按钮当前不可用');
  }

  await saveDraftBtn.click();
  return await waitForSaved();
}

async function findPublishEntry() {
  const candidates = [
    page.getByRole('button', { name: /下一步|发布/i }),
    page.locator('button:has-text("下一步"), button:has-text("发布")'),
    page.locator('a:has-text("下一步"), a:has-text("发布")')
  ];

  for (const locator of candidates) {
    const count = await locator.count().catch(() => 0);
    if (count > 0) {
      const first = locator.first();
      const visible = await first.isVisible().catch(() => false);
      if (visible) return first;
    }
  }

  return null;
}

async function publishIfEnabled() {
  if (!AUTO_PUBLISH) {
    console.log('AUTO_PUBLISH = false，跳过自动发布');
    return;
  }

  const publishEntry = await findPublishEntry();
  if (!publishEntry) {
    await shot('fanqie-publish-entry-missing');
    throw new Error('未找到“下一步”或“发布”入口');
  }

  const disabled = await publishEntry.isDisabled?.().catch(() => false);
  console.log('publishEntry disabled =', disabled);

  if (disabled) {
    await shot('fanqie-publish-entry-disabled');
    throw new Error('发布入口当前不可用');
  }

  await publishEntry.click();
  await page.waitForTimeout(2000);

  const confirmBtn = page.getByRole('button', { name: /确认发布|发布|确定/i }).first();
  const confirmVisible = await confirmBtn.isVisible().catch(() => false);

  if (confirmVisible) {
    await confirmBtn.click();
    await page.waitForTimeout(3000);
  }

  const pageText = await page.locator('body').innerText().catch(() => '');
  console.log('publish page text preview =', pageText.slice(0, 300));

  await shot('fanqie-after-publish');
}

async function main() {
  const bodyText = await fs.readFile(BODY_PATH, 'utf8');

  const openedUrl = await openEditorPage();
  console.log('opened editor via =', openedUrl);

  await verifyEditorReady();

  await fillReactInput(titleLocator, TITLE);
  await verifyTitle(TITLE);

  await fillProseMirror(editorLocator, bodyText);
  await verifyEditorContent();

  const wordCount = await waitForWordCount(MIN_WORDS);
  console.log('final wordCount =', wordCount);

  const saveResult = await saveDraftAndVerify();
  console.log('saveResult =', saveResult);

  await publishIfEnabled();

  console.log('流程执行完成。');
}

await main();
```

## File: execution-policy.md
size: 5027
sha: 47a7544afbd9db83b14a1bd5b2a731d06077bd62
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/execution-policy.md

````text
# 执行策略

这个文件定义 `fanqie-author-publish` 在 OpenClaw 和 Linux 环境中的执行约束，目标是让浏览器自动化尽可能稳定、可恢复、可审计。

## 运行环境

- 操作系统：Linux
- 文件编码：UTF-8
- 换行：LF
- 浏览器：使用独立作者账号 profile
- 技能目录：建议放在固定可读写路径下

## 登录态策略

- 不依赖提取、导出或复用站点 Cookie
- 使用固定浏览器 profile 持久化登录态
- 首次登录由用户在该 profile 中手动完成
- OpenClaw 后续始终复用同一个 profile
- 如果登录失效，只允许用户手动重新登录，不要尝试自动处理验证码、短信校验或安全验证

推荐 profile 路径：

```text
/home/author/browser-profiles/fanqie/
```

推荐原则：
- 固定设备环境
- 固定浏览器版本
- 尽量固定网络出口
- 避免高频异常操作触发风控

推荐目录结构：

```text
/home/author/openclaw-skills/fanqie-author-publish/
├─ README.md
├─ SKILL.md
├─ chapter.template.json
├─ chapter.example.json
├─ chapters.batch.template.json
├─ chapters.batch.example.json
├─ browser-workflow.md
├─ publish-log.template.md
├─ execution-policy.md
├─ logs/
└─ screenshots/
```

## 输入策略

- 单章输入使用 `chapter.template.json`
- 批量输入使用 `chapters.batch.template.json`
- 如果存在 `source_file`，优先将其视为来源记录，而不是运行时必须读取的唯一路径
- 缺少 `book_name`、`chapter_title`、`chapter_body` 时，不允许继续执行

## 状态策略

流程状态统一为：
- `pending`
- `backend_opened`
- `book_selected`
- `editor_opened`
- `draft_loaded`
- `draft_saved`
- `awaiting_confirmation`
- `published_verified`
- `failed_needs_review`

任何阶段退出时，都必须输出当前状态。

## 超时策略

建议默认超时：
- 打开后台首页：30 秒
- 页面跳转：20 秒
- 章节编辑器加载：30 秒
- 保存草稿结果等待：20 秒
- 发布结果等待：30 秒

如果超时：
- 不要盲目重复点击
- 先检查页面是否已发生有效变化
- 若状态不明，截图并停止

## 重试策略

允许重试的动作：
- 打开页面
- 页面刷新
- 定位非破坏性入口

不允许自动重试的动作：
- 保存草稿按钮重复点击超过 1 次
- 发布按钮重复点击
- 弹窗确认重复点击

推荐规则：
- 非破坏性动作最多重试 2 次
- 破坏性动作默认只执行 1 次

## 发布策略

- 任何发布前必须先保存草稿
- 只有在当前会话中得到明确确认后，才允许点击最终发布
- 如果发布后结果不明确，禁止再次点击发布，先去章节列表核对状态

## 批量策略

批量处理建议默认按顺序执行。

推荐规则：
- 一次批量最多处理 3 到 5 章
- 当前一章失败时，默认停止后续章节
- 如需跳过失败项继续执行，必须由用户明确要求
- 默认不允许同标题重复写入，遇到重复标题应停止并报告

## 页面验证策略

每个关键步骤后都必须做验证。

优先级从高到低：
1. 明确成功提示文案
2. 明确状态标签变化
3. 章节列表中可见状态变化
4. URL 或页面标题变化
5. 截图人工可确认

如果前 4 项都无法确认，不要假定动作成功。

## 停止策略

出现以下情况时必须立即停止：
- 登录失效
- 验证码
- 风控提示
- 账号不匹配
- 页面结构明显变化
- 编辑器无法写入正文
- 保存或发布结果无法确认

## 日志策略

每次运行后，建议都写一份日志到 `logs/` 目录。

日志至少包含：
- 时间
- 作品名
- 章节标题
- 动作
- 当前状态
- 结果说明
- 警告信息
- 截图路径
- 是否需要人工处理

推荐命名：

```text
logs/2026-04-03_第12章_夜雨将至.md
```

## 截图策略

截图建议统一放到 `screenshots/` 目录。

建议截图节点：
- 后台首页成功打开
- 目标作品页面成功打开
- 编辑器已填入内容
- 草稿保存完成
- 发布成功或失败

推荐命名：

```text
screenshots/2026-04-03_editor-filled.png
screenshots/2026-04-03_draft-saved.png
screenshots/2026-04-03_publish-result.png
```

## 人工确认策略

以下动作必须人工确认后才能继续：
- 最终发布
- 遇到重复标题但用户仍想继续
- 遇到结构变化但用户想尝试手动指定路径

建议确认话术：
- `确认发布这章`
- `继续发布`
- `跳过这一章继续下一章`

## 最小输出约定

每次运行至少输出：
- `state`
- `book_name`
- `chapter_title`
- `action`
- `result`
- `warning`
- `log_path`
- `screenshot_paths`

示例：

```text
state: draft_saved
book_name: 夜行者日志
chapter_title: 第12章 夜雨将至
action: prepare_draft
result: 已成功保存草稿。
warning: 无。
log_path: logs/2026-04-03_第12章_夜雨将至.md
screenshot_paths:
  - screenshots/2026-04-03_editor-filled.png
  - screenshots/2026-04-03_draft-saved.png
```
````

## File: input.schema.json
size: 3362
sha: 421cd3dfad536484c8d0e4f9350c2a7336f176c8
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/input.schema.json

```text
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/fanqie-author-publish/input.schema.json",
  "title": "fanqie-author-publish input schema",
  "description": "Schema for single-chapter and batch chapter inputs used by the fanqie-author-publish skill.",
  "oneOf": [
    {
      "$ref": "#/$defs/singleChapterInput"
    },
    {
      "$ref": "#/$defs/batchChapterInput"
    }
  ],
  "$defs": {
    "action": {
      "type": "string",
      "enum": [
        "prepare_draft",
        "review",
        "publish",
        "reconcile"
      ]
    },
    "linuxPath": {
      "type": "string",
      "minLength": 1,
      "pattern": "^/.*"
    },
    "nonEmptyText": {
      "type": "string",
      "minLength": 1
    },
    "optionalText": {
      "type": "string"
    },
    "publishTime": {
      "type": "string",
      "description": "Optional publish time. Leave empty when not scheduling.",
      "default": ""
    },
    "singleChapterInput": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "book_name",
        "chapter_title",
        "chapter_body",
        "action"
      ],
      "properties": {
        "book_name": {
          "$ref": "#/$defs/nonEmptyText"
        },
        "chapter_title": {
          "$ref": "#/$defs/nonEmptyText"
        },
        "chapter_body": {
          "$ref": "#/$defs/nonEmptyText"
        },
        "action": {
          "$ref": "#/$defs/action"
        },
        "publish_time": {
          "$ref": "#/$defs/publishTime"
        },
        "source_file": {
          "oneOf": [
            {
              "$ref": "#/$defs/linuxPath"
            },
            {
              "type": "string",
              "maxLength": 0
            }
          ],
          "default": ""
        },
        "notes": {
          "$ref": "#/$defs/optionalText",
          "default": ""
        }
      }
    },
    "batchChapter": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "chapter_title",
        "chapter_body"
      ],
      "properties": {
        "chapter_title": {
          "$ref": "#/$defs/nonEmptyText"
        },
        "chapter_body": {
          "$ref": "#/$defs/nonEmptyText"
        },
        "action": {
          "$ref": "#/$defs/action"
        },
        "publish_time": {
          "$ref": "#/$defs/publishTime",
          "default": ""
        },
        "source_file": {
          "oneOf": [
            {
              "$ref": "#/$defs/linuxPath"
            },
            {
              "type": "string",
              "maxLength": 0
            }
          ],
          "default": ""
        },
        "notes": {
          "$ref": "#/$defs/optionalText",
          "default": ""
        }
      }
    },
    "batchChapterInput": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "book_name",
        "default_action",
        "chapters"
      ],
      "properties": {
        "book_name": {
          "$ref": "#/$defs/nonEmptyText"
        },
        "default_action": {
          "$ref": "#/$defs/action"
        },
        "chapters": {
          "type": "array",
          "minItems": 1,
          "maxItems": 5,
          "items": {
            "$ref": "#/$defs/batchChapter"
          }
        }
      }
    }
  }
}
```

## File: logs/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/logs/.gitkeep

```text

```

## File: logs/example-log.md
size: 1615
sha: cc8eda1c8760143ff2ce1147f41e7841ee46147d
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/logs/example-log.md

````text
# 发布记录

## 基本信息

- 记录时间：`2026-04-03 21:30:00`
- 操作人：`owlco001`
- 作品名：`夜行者日志`
- 章节标题：`第12章 夜雨将至`
- 动作：`prepare_draft`
- 当前状态：`draft_saved`

## 输入来源

- 源文件路径：`/home/author/novel/夜行者日志/第12章_夜雨将至.md`
- 章节数据文件：`chapter.example.json`
- 备注：`发布前检查分段是否正常，确认结尾悬念保留。`

## 执行结果

- 结果：`成功`
- 结果说明：

```text
已打开目标作品后台，章节标题和正文填写成功，草稿保存成功，当前等待人工确认发布。
```

- 警告信息：

```text
无
```

## 浏览器检查点

- 后台首页打开：`是`
- 目标作品定位成功：`是`
- 编辑器打开成功：`是`
- 标题填写成功：`是`
- 正文填写成功：`是`
- 草稿保存成功：`是`
- 发布成功：`未执行`

## 截图与证据

- 截图 1：`screenshots/backend-home.png`
- 截图 2：`screenshots/editor-filled.png`
- 截图 3：`screenshots/draft-saved.png`
- 成功提示文案：`草稿保存成功`
- 失败提示文案：`无`

## 异常与阻塞

- 是否出现登录失效：`否`
- 是否出现验证码：`否`
- 是否出现风控：`否`
- 是否出现页面结构变化：`否`
- 具体异常说明：

```text
无异常。当前流程已完成草稿保存，等待人工确认后继续发布。
```

## 后续动作

- 是否需要人工复查：`是`
- 是否允许继续发布：`是`
- 下一步建议：

```text
1. 人工检查正文格式和分段
2. 确认无误后执行最终发布
```
````

## File: manifest.json
size: 1551
sha: b5434b919749585005e81fbdd60e1b392ba1fd00
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/manifest.json

```text
{
  "name": "fanqie-author-publish",
  "display_name": "番茄作者发布技能",
  "version": "0.1.0",
  "description": "一个用于番茄小说作者后台的半自动发布技能包，支持准备草稿、发布前检查、人工确认后发布，以及章节状态核对。",
  "language": "zh-CN",
  "platform": "OpenClaw",
  "runtime": {
    "os": [
      "linux"
    ],
    "encoding": "UTF-8",
    "line_endings": "LF"
  },
  "risk_level": "medium",
  "human_in_the_loop": true,
  "supported_actions": [
    "prepare_draft",
    "review",
    "publish",
    "reconcile"
  ],
  "required_tools": [
    "browser"
  ],
  "optional_tools": [
    "files",
    "screenshots",
    "logs"
  ],
  "entrypoints": {
    "skill": "SKILL.md",
    "workflow": "browser-workflow.md",
    "execution_policy": "execution-policy.md",
    "selectors": "selectors.md",
    "input_schema": "input.schema.json",
    "output_schema": "output.schema.json",
    "readme": "README.md"
  },
  "examples": {
    "single": "chapter.example.json",
    "batch": "chapters.batch.example.json",
    "log": "logs/example-log.md"
  },
  "directories": {
    "logs": "logs",
    "screenshots": "screenshots"
  },
  "safety": {
    "manual_login_required": true,
    "explicit_publish_confirmation_required": true,
    "stop_on_captcha": true,
    "stop_on_risk_control": true,
    "stop_on_account_mismatch": true,
    "save_draft_before_publish": true
  },
  "batch_policy": {
    "max_items": 5,
    "default_stop_on_failure": true,
    "allow_duplicate_titles": false
  }
}
```

## File: output.schema.json
size: 2701
sha: 29e5a27fd1de2cc3253ddf7445a774c04c990a05
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/output.schema.json

```text
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/fanqie-author-publish/output.schema.json",
  "title": "fanqie-author-publish output schema",
  "description": "Machine-readable run result schema for the fanqie-author-publish skill.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "state",
    "action",
    "book_name",
    "result",
    "warning",
    "log_path",
    "screenshot_paths"
  ],
  "properties": {
    "state": {
      "type": "string",
      "enum": [
        "pending",
        "backend_opened",
        "book_selected",
        "editor_opened",
        "draft_loaded",
        "draft_saved",
        "awaiting_confirmation",
        "published_verified",
        "failed_needs_review"
      ]
    },
    "action": {
      "type": "string",
      "enum": [
        "prepare_draft",
        "review",
        "publish",
        "reconcile"
      ]
    },
    "book_name": {
      "type": "string",
      "minLength": 1
    },
    "chapter_title": {
      "type": "string",
      "default": ""
    },
    "result": {
      "type": "string",
      "minLength": 1
    },
    "warning": {
      "type": "string",
      "default": ""
    },
    "blocking_reason": {
      "type": "string",
      "default": ""
    },
    "requires_manual_action": {
      "type": "boolean",
      "default": false
    },
    "confirmation_required": {
      "type": "boolean",
      "default": false
    },
    "log_path": {
      "type": "string",
      "pattern": "^(logs/|/).+"
    },
    "screenshot_paths": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^(screenshots/|/).+"
      },
      "default": []
    },
    "evidence": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "default": []
    },
    "timestamp": {
      "type": "string",
      "description": "ISO 8601 timestamp for the run result, if available.",
      "default": ""
    },
    "next_step": {
      "type": "string",
      "default": ""
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "action": {
            "const": "publish"
          }
        },
        "required": [
          "action"
        ]
      },
      "then": {
        "required": [
          "chapter_title",
          "confirmation_required"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "state": {
            "const": "failed_needs_review"
          }
        },
        "required": [
          "state"
        ]
      },
      "then": {
        "required": [
          "blocking_reason",
          "requires_manual_action"
        ]
      }
    }
  ]
}
```

## File: publish-log.template.md
size: 2582
sha: 8fe6c68d1e2111eb036f0363425c8bfaec04568b
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/publish-log.template.md

````text
# 发布记录模板

## 基本信息

- 记录时间：`YYYY-MM-DD HH:MM:SS`
- 操作人：`作者账号 / 操作人名称`
- 作品名：`作品名`
- 章节标题：`章节标题`
- 动作：`prepare_draft / review / publish / reconcile`
- 当前状态：`pending / draft_loaded / draft_saved / awaiting_confirmation / published_verified / failed_needs_review`

## 输入来源

- 源文件路径：`/home/author/novel/chapter.md`
- 章节数据文件：`chapter.example.json`
- 备注：`可选备注`

## 执行结果

- 结果：`成功 / 失败 / 部分完成`
- 结果说明：

```text
这里写本次执行的简要结果，例如：
已打开目标作品，章节内容已填入，草稿保存成功，等待人工确认发布。
```

- 警告信息：

```text
无
```

## 浏览器检查点

- 后台首页打开：`是 / 否`
- 目标作品定位成功：`是 / 否`
- 编辑器打开成功：`是 / 否`
- 标题填写成功：`是 / 否`
- 正文填写成功：`是 / 否`
- 草稿保存成功：`是 / 否`
- 发布成功：`是 / 否 / 未执行`

## 截图与证据

- 截图 1：`路径或说明`
- 截图 2：`路径或说明`
- 截图 3：`路径或说明`
- 成功提示文案：`如有则填写`
- 失败提示文案：`如有则填写`

## 异常与阻塞

- 是否出现登录失效：`是 / 否`
- 是否出现验证码：`是 / 否`
- 是否出现风控：`是 / 否`
- 是否出现页面结构变化：`是 / 否`
- 具体异常说明：

```text
如果失败或中断，在这里写明最后一个成功步骤，以及当前阻塞点。
```

## 后续动作

- 是否需要人工复查：`是 / 否`
- 是否允许继续发布：`是 / 否`
- 下一步建议：

```text
例如：
1. 重新登录番茄作者后台
2. 检查章节末尾分段
3. 人工确认后再执行发布
```

## 示例

```text
记录时间：2026-04-03 21:30:00
操作人：owlco001
作品名：夜行者日志
章节标题：第12章 夜雨将至
动作：prepare_draft
当前状态：draft_saved

结果：成功
结果说明：已进入目标作品后台，标题和正文填写完成，草稿保存成功。
警告信息：无

后台首页打开：是
目标作品定位成功：是
编辑器打开成功：是
标题填写成功：是
正文填写成功：是
草稿保存成功：是
发布成功：未执行

是否出现登录失效：否
是否出现验证码：否
是否出现风控：否
是否出现页面结构变化：否

是否需要人工复查：是
是否允许继续发布：是
下一步建议：人工检查正文格式后，再执行最终发布。
```
````

## File: qimao-author-publish/browser-workflow.md
size: 446
sha: 01c8a20c4c0406ec7b9057c6bddbb5a0eed6e2d8
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/browser-workflow.md

```text
# 七猫浏览器工作流

## 前置条件

- 用户已经登录七猫作者后台
- 目标章节内容已经准备好
- 当前浏览器会话使用的是正确的作者账号
- 当前没有验证码或风控提示

## 工作流阶段

1. 打开七猫作者后台
2. 定位目标作品
3. 打开章节列表或章节编辑器
4. 填写标题和正文
5. 保存草稿
6. 检查发布条件
7. 在人工确认后发布
8. 核对最近章节状态
```

## File: qimao-author-publish/chapter.example.json
size: 409
sha: 1211466b882655fc30584bcd7e583c7cd8ef7c09
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/chapter.example.json

```text
{
  "book_name": "夜行者日志",
  "chapter_title": "第12章 夜雨将至",
  "chapter_body": "雨是半夜落下来的。\n\n林砚被窗沿上的滴水声吵醒时，整座旧城还陷在断电后的黑暗里。",
  "action": "prepare_draft",
  "publish_time": "",
  "source_file": "/home/author/novel/qimao/夜行者日志/第12章_夜雨将至.md",
  "notes": "发布前检查分段是否正常。"
}
```

## File: qimao-author-publish/chapter.template.json
size: 306
sha: 6c9901e6ad1d04c5dedb3fe3e17060acc54ee184
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/chapter.template.json

```text
{
  "book_name": "作品名",
  "chapter_title": "第12章 标题示例",
  "chapter_body": "这里填写章节正文。\n\n如果有分段，直接换行即可。",
  "action": "prepare_draft",
  "publish_time": "",
  "source_file": "/home/author/novel/qimao/chapter12.md",
  "notes": "可选备注。"
}
```

## File: qimao-author-publish/chapters.batch.example.json
size: 823
sha: 6a1f7ff78149ae7696b6ce91c18cdb334db4d96c
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/chapters.batch.example.json

```text
{
  "book_name": "夜行者日志",
  "default_action": "prepare_draft",
  "chapters": [
    {
      "chapter_title": "第12章 夜雨将至",
      "chapter_body": "雨是半夜落下来的。\n\n林砚被窗沿上的滴水声吵醒时，整座旧城还陷在断电后的黑暗里。",
      "publish_time": "",
      "source_file": "/home/author/novel/qimao/夜行者日志/第12章_夜雨将至.md",
      "notes": "发布前检查分段是否正常。"
    },
    {
      "chapter_title": "第13章 旧巷来客",
      "chapter_body": "巷口的脚步声最终停在了楼下。\n\n林砚屏住呼吸，听见有人轻轻敲了三下铁门。",
      "publish_time": "",
      "source_file": "/home/author/novel/qimao/夜行者日志/第13章_旧巷来客.md",
      "notes": "注意本章结尾停顿。"
    }
  ]
}
```

## File: qimao-author-publish/chapters.batch.template.json
size: 348
sha: 68887b1baf7bbc9f90aee3efb6c2a088e7655104
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/chapters.batch.template.json

```text
{
  "book_name": "作品名",
  "default_action": "prepare_draft",
  "chapters": [
    {
      "chapter_title": "第12章 标题示例",
      "chapter_body": "这里填写第12章正文。",
      "publish_time": "",
      "source_file": "/home/author/novel/qimao/book/chapter12.md",
      "notes": "发布前检查段落与结尾。"
    }
  ]
}
```

## File: qimao-author-publish/execution-policy.md
size: 547
sha: a2c3d17167f53a184b5c1b1e3f9d3d514413475d
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/execution-policy.md

```text
# 执行策略

- 操作系统：Linux
- 文件编码：UTF-8
- 换行：LF
- 浏览器：使用独立作者账号 profile
- 不依赖提取、导出或复用站点 Cookie
- 使用固定浏览器 profile 持久化登录态
- 首次登录由用户手动完成
- 任何发布前必须先保存草稿
- 只有在当前会话中得到明确确认后，才允许点击最终发布
- 非破坏性动作最多重试 2 次
- 破坏性动作默认只执行 1 次
- 出现验证码、风控、账号不匹配、页面结构明显变化时必须立即停止
```

## File: qimao-author-publish/input.schema.json
size: 2033
sha: 9a507d81d455075f4651ea1a78e37aacc36a6796
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/input.schema.json

```text
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/qimao-author-publish/input.schema.json",
  "title": "qimao-author-publish input schema",
  "oneOf": [
    { "$ref": "#/$defs/singleChapterInput" },
    { "$ref": "#/$defs/batchChapterInput" }
  ],
  "$defs": {
    "action": {
      "type": "string",
      "enum": ["prepare_draft", "review", "publish", "reconcile"]
    },
    "singleChapterInput": {
      "type": "object",
      "additionalProperties": false,
      "required": ["book_name", "chapter_title", "chapter_body", "action"],
      "properties": {
        "book_name": { "type": "string", "minLength": 1 },
        "chapter_title": { "type": "string", "minLength": 1 },
        "chapter_body": { "type": "string", "minLength": 1 },
        "action": { "$ref": "#/$defs/action" },
        "publish_time": { "type": "string", "default": "" },
        "source_file": { "type": "string", "default": "" },
        "notes": { "type": "string", "default": "" }
      }
    },
    "batchChapterInput": {
      "type": "object",
      "additionalProperties": false,
      "required": ["book_name", "default_action", "chapters"],
      "properties": {
        "book_name": { "type": "string", "minLength": 1 },
        "default_action": { "$ref": "#/$defs/action" },
        "chapters": {
          "type": "array",
          "minItems": 1,
          "maxItems": 5,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["chapter_title", "chapter_body"],
            "properties": {
              "chapter_title": { "type": "string", "minLength": 1 },
              "chapter_body": { "type": "string", "minLength": 1 },
              "action": { "$ref": "#/$defs/action" },
              "publish_time": { "type": "string", "default": "" },
              "source_file": { "type": "string", "default": "" },
              "notes": { "type": "string", "default": "" }
            }
          }
        }
      }
    }
  }
}
```

## File: qimao-author-publish/logs/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/logs/.gitkeep

```text

```

## File: qimao-author-publish/manifest.json
size: 640
sha: ddd1f419bd6222716f4779ae233960f2e8d09a63
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/manifest.json

```text
{
  "name": "qimao-author-publish",
  "display_name": "七猫作者发布技能",
  "version": "0.1.0",
  "description": "一个用于七猫小说作者后台的半自动发布技能包，支持准备草稿、发布前检查、人工确认后发布，以及章节状态核对。",
  "language": "zh-CN",
  "platform": "OpenClaw",
  "runtime": {
    "os": ["linux"],
    "encoding": "UTF-8",
    "line_endings": "LF"
  },
  "risk_level": "medium",
  "human_in_the_loop": true,
  "supported_actions": ["prepare_draft", "review", "publish", "reconcile"],
  "required_tools": ["browser"],
  "optional_tools": ["files", "screenshots", "logs"]
}
```

## File: qimao-author-publish/output.schema.json
size: 1302
sha: 8daa5fe37ed953293527733ac48867c4f0055f57
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/output.schema.json

```text
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.local/qimao-author-publish/output.schema.json",
  "title": "qimao-author-publish output schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["state", "action", "book_name", "result", "warning", "log_path", "screenshot_paths"],
  "properties": {
    "state": {
      "type": "string",
      "enum": ["pending", "backend_opened", "book_selected", "editor_opened", "draft_loaded", "draft_saved", "awaiting_confirmation", "published_verified", "failed_needs_review"]
    },
    "action": {
      "type": "string",
      "enum": ["prepare_draft", "review", "publish", "reconcile"]
    },
    "book_name": { "type": "string", "minLength": 1 },
    "chapter_title": { "type": "string", "default": "" },
    "result": { "type": "string", "minLength": 1 },
    "warning": { "type": "string", "default": "" },
    "blocking_reason": { "type": "string", "default": "" },
    "requires_manual_action": { "type": "boolean", "default": false },
    "confirmation_required": { "type": "boolean", "default": false },
    "log_path": { "type": "string" },
    "screenshot_paths": { "type": "array", "items": { "type": "string" }, "default": [] },
    "next_step": { "type": "string", "default": "" }
  }
}
```

## File: qimao-author-publish/publish-log.template.md
size: 472
sha: b55113110e17d034b2c02c5740cf18e6cc7ed92c
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/publish-log.template.md

```text
# 发布记录模板

- 记录时间：`YYYY-MM-DD HH:MM:SS`
- 平台：`七猫小说`
- 作品名：`作品名`
- 章节标题：`章节标题`
- 动作：`prepare_draft / review / publish / reconcile`
- 当前状态：`pending / draft_loaded / draft_saved / awaiting_confirmation / published_verified / failed_needs_review`
- 结果：`成功 / 失败 / 部分完成`
- 结果说明：`这里写执行结果`
- 截图：`screenshots/...`
- 异常说明：`如有则写`
```

## File: qimao-author-publish/real-site-calibration-checklist.md
size: 4419
sha: 21ba35cd4bfcc28287967220e8b30482a8225efc
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/real-site-calibration-checklist.md

````text
# 七猫真实页面联调清单

这个清单用于第一次把 `qimao-author-publish` 接到 Linux / OpenClaw / 七猫作者后台真实页面时做联调。

目标不是直接发布，而是先把页面结构、关键文案、成功信号、失败信号校准清楚，再回填到：
- `selectors.md`
- `browser-workflow.md`
- `execution-policy.md`

## 联调前准备

开始前确认：
- Linux 环境下 OpenClaw 已能调用浏览器工具
- 使用独立作者账号浏览器 profile
- 七猫作者后台已手动登录
- `logs/` 可写
- `screenshots/` 可写
- 准备一份测试章节，避免用正式发布内容做第一次联调

建议准备：
- 一部测试作品
- 1 到 2 章测试草稿文本
- 一份联调日志

## 联调顺序

建议按这个顺序进行：
1. 后台首页识别
2. 作品列表定位
3. 作品管理页定位
4. 章节列表定位
5. 编辑器定位
6. 草稿保存验证
7. 发布按钮和确认弹窗验证
8. 发布结果核对验证

## 1. 后台首页识别

### 需要确认的内容

- 登录后的后台首页 URL
- 页面标题文字
- 侧边栏或顶部导航中的关键文案
- 如何区分“已登录后台”和“登录页”

### 需要记录

- 后台首页可见关键文案
- 登录页可见关键文案
- 是否存在作者昵称或账号显示区域

## 2. 作品列表定位

### 需要确认的内容

- 作品列表页 URL 或导航路径
- 页面上作品标题的展示位置
- 是否存在搜索框
- 作品卡片上的“管理”“进入”“详情”等按钮真实文案

### 需要记录

- 搜索框是否可用
- 标题匹配是否支持精确搜索
- 如果作品名重复，页面如何区分

## 3. 作品管理页定位

### 需要确认的内容

- 进入某个作品后的页面标题
- 是否显示当前作品名
- “章节管理”入口的真实文案
- 是否还有“内容管理”“作品设置”等相近入口

### 需要记录

- 章节管理入口位置
- 如果有多个相似入口，哪个才是正确入口

## 4. 章节列表定位

### 需要确认的内容

- 章节列表页是否显示标题列和状态列
- “新建章节”按钮真实文案
- 已有草稿的“编辑”入口文案
- 是否能直接看到“草稿”“已发布”等状态

### 需要记录

- 标题列位置
- 状态列真实文案
- 重复标题在界面上如何表现

## 5. 编辑器定位

### 需要确认的内容

- 标题输入框的真实标签或 placeholder
- 正文区域是否是普通输入框、富文本、`contenteditable` 还是 iframe
- 正文输入后能否立即读取到内容
- 编辑器是否会自动格式化或截断内容

### 需要记录

- 标题输入框定位方式
- 正文编辑器定位方式
- 输入正文后最稳的验证方法
- 是否存在延迟加载或首输失效问题

## 6. 草稿保存验证

### 需要确认的内容

- “保存草稿”按钮真实文案
- 点击后是否出现 toast
- toast 文案是什么
- 如果没有 toast，在哪能看到已保存状态

### 需要记录

- 最可靠的成功判断信号
- 没有成功提示时的备选验证路径
- 保存失败时的错误提示文案

## 7. 发布按钮和确认弹窗验证

### 需要确认的内容

- 发布按钮真实文案
- 是否存在确认弹窗
- 确认弹窗标题和按钮文案
- 是否存在二次确认或风险提示

### 需要记录

- 发布前是否要求章节完整性检查
- 确认按钮真实文案
- 是否存在“取消”“继续”“确认发布”等多种按钮

## 8. 发布结果核对验证

### 需要确认的内容

- 发布成功后的 toast 或提示文案
- 发布成功后页面是否跳转
- 回章节列表后状态如何显示
- 是否有发布时间或更新时间字段

### 需要记录

- 最可靠的发布成功判断信号
- 发布结果不明确时的人工核对路径

## 联调记录模板

```text
校准项：
页面名称：
实际文案：
实际按钮文字：
成功信号：
失败信号：
截图路径：
需要更新的文件：
```

## 联调完成标准

满足以下条件后，可以进入真实试跑：

- 已确认后台首页和登录页区分信号
- 已确认目标作品定位方式
- 已确认章节编辑器真实类型
- 已确认保存草稿成功信号
- 已确认发布按钮和确认弹窗真实文案
- 已确认发布结果核对方式
- `selectors.md` 已回填真实文案
- `test-cases.md` 可据此执行最小验收
````

## File: qimao-author-publish/screenshots/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/screenshots/.gitkeep

```text

```

## File: qimao-author-publish/selectors.md
size: 535
sha: e6440c00077950bfd91d6eb023a4e137cdf7e374
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/selectors.md

```text
# 页面定位与选择策略

## 总体原则

- 优先使用明确可见的按钮或标题文字
- 优先定位作品名、章节管理、保存草稿、发布等关键文案
- 不要依赖脆弱的 class 哈希或位置猜测

## 需要联调确认的区域

- 作者后台首页识别信号
- 作品列表页中的作品卡片与管理入口
- 章节列表页中的新建章节按钮
- 标题输入框和正文编辑区域
- 保存草稿按钮文案
- 发布按钮和确认弹窗文案
- 成功保存与成功发布的提示文案
```

## File: qimao-author-publish/setup-linux.sh
size: 327
sha: 1c9e22a762e91c5cb3948d88f3b08062aa23fcc1
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/setup-linux.sh

```text
#!/usr/bin/env bash

set -euo pipefail

BASE_DIR="${1:-$HOME/openclaw-skills}"
SKILL_DIR="$BASE_DIR/qimao-author-publish"

mkdir -p "$SKILL_DIR/logs"
mkdir -p "$SKILL_DIR/screenshots"

echo "初始化完成: $SKILL_DIR"
echo "下一步：手动登录七猫作者后台，并按 real-site-calibration-checklist.md 做联调。"
```

## File: qimao-author-publish/test-cases.md
size: 436
sha: 4a1af0de18071761102096e718cc11bfb16e2a3a
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/test-cases.md

```text
# 测试用例

- `TC-001` 单章草稿保存成功
- `TC-002` 发布前检查成功
- `TC-003` 单章发布成功
- `TC-004` 批量草稿准备成功
- `TC-005` 未登录后台时正确停止
- `TC-006` 出现验证码或风控时正确停止
- `TC-007` 找不到目标作品时正确停止
- `TC-008` 标题重复时正确停止
- `TC-009` 编辑器未接受正文时正确停止
- `TC-010` 保存草稿结果不明确时正确停止
```

## File: qimao-author-publish/validate-input.sh
size: 331
sha: dcedb0e963ded852c1899a164d4022877fb622bf
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/qimao-author-publish/validate-input.sh

```text
#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCHEMA_FILE="$SCRIPT_DIR/input.schema.json"
INPUT_FILE="${1:-}"

if [[ -z "$INPUT_FILE" ]]; then
  echo "用法: bash validate-input.sh /path/to/input.json"
  exit 1
fi

npx --yes ajv-cli validate -s "$SCHEMA_FILE" -d "$INPUT_FILE"
```

## File: real-site-calibration-checklist.md
size: 6459
sha: a28856d2b2a675cf37b80cbe1ae8bab845a05201
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/real-site-calibration-checklist.md

````text
# 真实页面联调清单

这个清单用于第一次把 `fanqie-author-publish` 接到 Linux / OpenClaw / 番茄作者后台真实页面时做联调。

目标不是直接发布，而是先把页面结构、关键文案、成功信号、失败信号校准清楚，再回填到：
- `selectors.md`
- `browser-workflow.md`
- `execution-policy.md`

## 联调前准备

开始前确认：
- Linux 环境下 OpenClaw 已能调用浏览器工具
- 使用独立作者账号浏览器 profile
- 番茄作者后台已手动登录
- `logs/` 可写
- `screenshots/` 可写
- 准备一份测试章节，避免用正式发布内容做第一次联调

建议准备：
- 一篇测试用作品
- 1 到 2 章测试草稿文本
- 一个专门用于联调的日志文件

## 联调顺序

建议按这个顺序进行：
1. 后台首页识别
2. 作品列表定位
3. 作品管理页定位
4. 章节列表定位
5. 编辑器定位
6. 草稿保存验证
7. 发布按钮和确认弹窗验证
8. 发布结果核对验证

## 1. 后台首页识别

### 需要确认的内容

- 登录后的后台首页 URL
- 页面标题文字
- 侧边栏或顶部导航中的关键文案
- 如何区分“已登录后台”和“登录页”

### 需要截图

- 后台首页完整截图
- 登录页截图

### 需要记录

- 后台首页可见关键文案
- 登录页可见关键文案
- 是否存在账号昵称或作者名显示区域

### 回填位置

- `selectors.md` 的“作者后台首页”部分
- `test-cases.md` 的未登录和正常登录判断

## 2. 作品列表定位

### 需要确认的内容

- 作品列表页 URL 或导航路径
- 页面上作品标题的展示位置
- 是否存在搜索框
- 作品卡片上的“管理”“进入”“详情”等按钮真实文案

### 需要截图

- 作品列表页截图
- 单个作品卡片截图

### 需要记录

- 搜索框是否可用
- 标题匹配时是否支持精确匹配
- 如果作品名重复，页面如何区分

### 回填位置

- `selectors.md` 的“作品列表页”部分

## 3. 作品管理页定位

### 需要确认的内容

- 进入某个作品后的页面标题
- 是否显示当前作品名
- “章节管理”入口的真实文案
- 是否还有“内容管理”“作品设置”等相近入口

### 需要截图

- 作品管理页完整截图
- 章节管理入口截图

### 需要记录

- 章节管理入口位置
- 如果有多个相似入口，哪个才是正确入口

### 回填位置

- `selectors.md` 的“作品管理页”部分
- `browser-workflow.md` 的阶段 2、阶段 3

## 4. 章节列表定位

### 需要确认的内容

- 章节列表页是否显示标题列和状态列
- “新建章节”按钮真实文案
- 已有草稿的“编辑”入口文案
- 是否能直接看到“草稿”“已发布”等状态

### 需要截图

- 章节列表页截图
- 新建章节按钮截图
- 一条草稿记录截图

### 需要记录

- 标题列位置
- 状态列真实文案
- 重复标题在界面上如何表现

### 回填位置

- `selectors.md` 的“章节列表页”部分
- `test-cases.md` 的重复标题、章节状态核对相关用例

## 5. 编辑器定位

### 需要确认的内容

- 标题输入框的真实标签
- 正文区域是否是普通输入框、富文本、`contenteditable` 还是 iframe
- 正文输入后能否立即读取到内容
- 编辑器是否会自动格式化或截断内容

### 需要截图

- 空编辑器截图
- 标题已填入截图
- 正文已填入截图

### 需要记录

- 标题输入框定位方式
- 正文编辑器定位方式
- 输入正文后最稳的验证方法
- 是否存在延迟加载或首输失效问题

### 回填位置

- `selectors.md` 的“章节编辑器”部分
- `execution-policy.md` 的页面验证策略
- `test-cases.md` 的正文输入失败场景

## 6. 草稿保存验证

### 需要确认的内容

- “保存草稿”按钮真实文案
- 点击后是否出现 toast
- toast 文案是什么
- 如果没有 toast，在哪能看到已保存状态

### 需要截图

- 保存草稿按钮截图
- 保存成功 toast 截图
- 草稿保存后章节列表状态截图

### 需要记录

- 最可靠的成功判断信号
- 没有成功提示时的备选验证路径
- 保存失败时的错误提示文案

### 回填位置

- `selectors.md` 的“保存草稿按钮”部分
- `execution-policy.md` 的页面验证策略
- `test-cases.md` 的草稿保存成功和保存不明确用例

## 7. 发布按钮和确认弹窗验证

### 需要确认的内容

- 发布按钮真实文案
- 是否存在确认弹窗
- 确认弹窗标题和按钮文案
- 是否存在二次确认或风险提示

### 需要截图

- 发布按钮截图
- 确认弹窗截图

### 需要记录

- 发布前是否要求章节完整性检查
- 确认按钮真实文案
- 是否存在“取消”“继续”“确认发布”等多种按钮

### 回填位置

- `selectors.md` 的“发布按钮与确认弹窗”部分
- `execution-policy.md` 的发布策略

## 8. 发布结果核对验证

### 需要确认的内容

- 发布成功后的 toast 或提示文案
- 发布成功后页面是否跳转
- 回章节列表后状态如何显示
- 是否有发布时间或更新时间字段

### 需要截图

- 发布成功提示截图
- 发布后章节列表截图

### 需要记录

- 最可靠的发布成功判断信号
- 发布结果不明确时的人工核对路径

### 回填位置

- `selectors.md` 的“成功信号优先级”部分
- `test-cases.md` 的发布成功和发布不明确用例

## 联调记录模板

每完成一项，建议记录：

```text
校准项：
页面名称：
实际文案：
实际按钮文字：
成功信号：
失败信号：
截图路径：
需要更新的文件：
```

## 联调完成标准

满足以下条件后，可以进入真实试跑：

- 已确认后台首页和登录页区分信号
- 已确认目标作品定位方式
- 已确认章节编辑器真实类型
- 已确认保存草稿成功信号
- 已确认发布按钮和确认弹窗真实文案
- 已确认发布结果核对方式
- `selectors.md` 已回填真实文案
- `test-cases.md` 可据此执行最小验收

## 联调后建议动作

完成第一次联调后，建议立刻：
1. 更新 `selectors.md`
2. 更新 `execution-policy.md` 中的验证优先级
3. 更新 `test-cases.md` 中的成功/失败判定
4. 在 `logs/` 中保存一份联调日志

这样下一次再接真实页面时，不需要重新摸索关键点位。
````

## File: screenshots/.gitkeep
size: 0
sha: e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/screenshots/.gitkeep

```text

```

## File: selectors.md
size: 8509
sha: 96bb093f55a8461c02f2420908f03695b7e6a034
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/selectors.md

```text
# 页面定位与选择策略

这个文件定义 `fanqie-author-publish` 在浏览器自动化中的页面定位策略。

目标不是写死某个 CSS 选择器，而是优先使用更稳的定位方法：
- 可见文字
- 页面区域语义
- 关键按钮名称
- 状态提示
- 后备定位策略

这样即使页面局部结构发生变化，仍然有机会保持可用。

## 总体原则

优先级从高到低：
1. 明确可见的按钮或标题文字
2. 明确的页面区域名称
3. 表单标签和输入框邻近关系
4. 状态提示文案
5. 结构性后备方法

不要优先依赖：
- 深层 CSS 路径
- 易变化的 class 名
- 第几个按钮这类位置假设
- 没有文案上下文的纯图标按钮

## 页面 1：作者后台首页

### 目标

确认已经进入番茄作者后台，而不是普通站点首页或登录页。

### 优先定位信号

- 页面上出现“作者后台”“创作平台”“工作台”“作品管理”这类后台入口文字
- 页面存在作品列表、创作管理、章节管理等后台功能区
- 页面右上或页面头部存在当前作者账号信息

### 成功判断

满足以下任意一项即可：
- 能看到“作品管理”或同类入口
- 能看到当前作者已登录后的工作台区域
- 能进入作品列表页

### 失败判断

- 出现“登录”“手机号登录”“验证码登录”等登录页文字
- 出现身份验证、验证码、风险提示

### 后备策略

- 从侧边栏查找与“创作”“作品”“章节”相关入口
- 从页面主区域查找卡片式的作品管理入口

## 页面 2：作品列表页

### 目标

找到目标作品 `book_name`。

### 优先定位信号

- 可见作品卡片
- 可见作品标题文字
- 页面存在“作品管理”“我的作品”“作品列表”等文案

### 定位策略

1. 优先在搜索框输入 `book_name`，如果页面有搜索能力
2. 如果没有搜索框，遍历可见作品卡片文字
3. 定位到与 `book_name` 完全一致的作品
4. 打开该作品的管理入口

### 成功判断

- 当前打开页面的作品标题与 `book_name` 一致

### 失败判断

- 没有找到目标作品
- 找到多个近似作品但无法唯一确认

### 后备策略

- 允许使用部分标题匹配，但必须在点击前再次确认完整作品名
- 如果存在“进入管理”“管理作品”“查看详情”等多个按钮，优先选与目标作品同一卡片内的按钮

## 页面 3：作品管理页

### 目标

从作品管理页进入章节相关功能。

### 优先定位信号

- 页面标题中出现作品名
- 页面存在“章节管理”“作品设置”“数据中心”等管理模块

### 定位策略

1. 在页面导航、侧边栏或页签中寻找“章节管理”
2. 如果没有完全一致文案，寻找“章节”“内容管理”“目录管理”等相近文案
3. 进入章节列表或章节编辑页面

### 成功判断

- 看到章节列表、新建章节按钮或章节编辑入口

### 失败判断

- 无法定位章节管理入口
- 页面跳转到了与章节无关的功能区

## 页面 4：章节列表页

### 目标

找到“新建章节”或已有草稿。

### 优先定位信号

- “新建章节”“新增章节”“写新章节”按钮
- 章节标题列表
- 章节状态列，如“草稿”“已发布”

### 定位策略

#### 新建草稿场景

1. 找到“新建章节”或等效按钮
2. 点击进入章节编辑器

#### 编辑已有草稿场景

1. 在章节列表中查找 `chapter_title`
2. 如果找到同名草稿，优先进入编辑而不是新建
3. 若存在重复标题，停止并报告

### 成功判断

- 已进入编辑器，或章节列表明确显示目标草稿状态

### 失败判断

- 找不到新建入口
- 找不到目标章节
- 存在重复标题且无法判断应该编辑哪一条

### 后备策略

- 如果页面上按钮文字不同，优先找“新建”“写作”“编辑”这类动作词
- 如果列表中状态列不明显，可结合标题行与操作按钮判断

## 页面 5：章节编辑器

### 目标

正确定位标题输入框和正文编辑区域。

### 优先定位信号

- 标题输入框附近有“章节标题”“标题”文案
- 正文编辑区域附近有“正文”“内容”“请输入正文”等提示
- 页面存在“保存草稿”“发布”等操作按钮

### 标题输入框定位策略

1. 优先找带“标题”“章节标题”标签的输入框
2. 如果有多个输入框，优先选择靠近正文编辑器上方的输入框
3. 输入后立刻读取当前值，确认标题已成功写入

### 正文编辑器定位策略

1. 优先找大面积可输入区域
2. 优先匹配带“正文”“内容”提示的编辑区
3. 如果是富文本或 `contenteditable` 区域，输入后必须再次读取或截图确认内容存在
4. 如果页面使用 iframe 编辑器，先进入 iframe 再操作

### 成功判断

- 标题输入框中已显示 `chapter_title`
- 正文区域中存在非空内容，并且开头段落可见

### 失败判断

- 标题输入后被自动清空
- 正文粘贴后未显示
- 正文仅显示部分内容
- 编辑器只读或不可聚焦

### 后备策略

- 如果直接输入失败，允许尝试一次粘贴式输入
- 如果富文本编辑器行为异常，停止并记录当前截图

## 页面 6：保存草稿按钮

### 目标

只点击一次保存草稿，并验证结果。

### 优先定位信号

- “保存草稿”按钮
- 等效文案，例如“保存”“存草稿”

### 定位策略

1. 优先找页面顶部或底部操作区中的“保存草稿”
2. 如果同时存在“发布”和“保存草稿”，优先选择包含“草稿”文字的按钮
3. 只点击一次

### 成功判断

满足以下任意一项：
- 页面出现“保存成功”“草稿保存成功”等提示
- 返回章节列表后能看到目标章节状态为草稿
- 页面明显保留了已保存状态

### 失败判断

- 出现错误提示
- 按钮灰掉且不可点击
- 点击后长时间没有任何状态变化

### 后备策略

- 若无明显 toast，可回章节列表验证状态
- 如果状态仍不明确，不要再次点击，直接停止并报告

## 页面 7：发布按钮与确认弹窗

### 目标

在用户明确确认后执行最终发布，并验证结果。

### 优先定位信号

- “发布”按钮
- 确认弹窗中的“确认发布”“确定”“继续发布”文案

### 定位策略

1. 发布前再次确认标题和作品名
2. 找到“发布”按钮
3. 只点击一次
4. 如果出现确认弹窗，核对文案后只确认一次

### 成功判断

满足以下任意一项：
- 出现“发布成功”提示
- 返回章节列表后显示状态为已发布
- 页面中可见已发布状态标签

### 失败判断

- 没有找到发布按钮
- 弹窗文案与预期不符
- 结果不明确且无状态变化

### 后备策略

- 如果发布后页面无提示，优先去章节列表核对
- 禁止再次点击发布来“试一试”

## 页面 8：章节状态核对页

### 目标

对比本地计划章节与平台上的真实状态。

### 优先定位信号

- 章节标题列表
- 状态标签，如“草稿”“已发布”
- 更新时间、发布时间等辅助列

### 定位策略

1. 逐项匹配标题
2. 读取每条记录对应状态
3. 记录重复标题、缺失标题、状态异常项

### 成功判断

- 能输出一份明确的状态比对结果

### 失败判断

- 无法读取章节标题
- 状态列缺失或无法识别

## 成功信号优先级

动作完成后的成功信号优先按以下顺序判断：

1. 明确成功提示文案
2. 章节状态标签变化
3. 列表中可见新增或状态更新
4. 页面标题或 URL 明显变化
5. 截图可人工确认

如果前 4 项都不存在，不要默认动作已成功。

## 禁止策略

以下做法禁止使用：
- 根据按钮位置猜测“右上角第一个就是发布”
- 仅凭颜色判断按钮功能
- 重复点击保存或发布以碰运气
- 在作品名或章节名不完全确认时继续点击

## 建议补充实测信息

真正接入 OpenClaw 前，建议根据番茄后台实际页面再补这些内容：
- 实际页面标题文案
- 章节编辑器是否为 iframe 或 `contenteditable`
- 保存成功 toast 的准确文案
- 发布确认弹窗的准确文案
- 章节列表的状态字段实际名称

这些补充越具体，浏览器代理执行时越稳定。
```

## File: setup-linux.sh
size: 1871
sha: 37e3ce8ea3ed059f2066d9c3e6a20c36c1d792e3
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/setup-linux.sh

```text
#!/usr/bin/env bash

set -euo pipefail

SKILL_NAME="fanqie-author-publish"
BASE_DIR="${1:-$HOME/openclaw-skills}"
SKILL_DIR="$BASE_DIR/$SKILL_NAME"

REQUIRED_FILES=(
  "README.md"
  "SKILL.md"
  "manifest.json"
  "browser-workflow.md"
  "execution-policy.md"
  "selectors.md"
  "input.schema.json"
  "output.schema.json"
  "chapter.template.json"
  "chapter.example.json"
  "chapters.batch.template.json"
  "chapters.batch.example.json"
  "publish-log.template.md"
  "test-cases.md"
  "real-site-calibration-checklist.md"
)

echo "[1/5] 初始化目录"
mkdir -p "$SKILL_DIR"
mkdir -p "$SKILL_DIR/logs"
mkdir -p "$SKILL_DIR/screenshots"

echo "[2/5] 检查技能目录"
if [[ ! -d "$SKILL_DIR" ]]; then
  echo "技能目录不存在: $SKILL_DIR" >&2
  exit 1
fi

echo "[3/5] 检查关键文件"
missing=0
for file in "${REQUIRED_FILES[@]}"; do
  if [[ ! -f "$SKILL_DIR/$file" ]]; then
    echo "缺少文件: $SKILL_DIR/$file"
    missing=1
  fi
done

if [[ "$missing" -ne 0 ]]; then
  cat <<EOF

检测到关键文件缺失。

请先把本地技能包完整复制到：
  $SKILL_DIR

然后重新执行：
  bash "$SKILL_DIR/setup-linux.sh"
EOF
  exit 1
fi

echo "[4/5] 写入目录占位文件"
touch "$SKILL_DIR/logs/.gitkeep"
touch "$SKILL_DIR/screenshots/.gitkeep"

echo "[5/5] 输出后续步骤"
cat <<EOF

初始化完成。

技能目录：
  $SKILL_DIR

建议下一步：
1. 确认 OpenClaw 可以访问这个目录
2. 使用独立浏览器 profile 手动登录番茄作者后台
3. 先用以下文件做单章测试：
   $SKILL_DIR/chapter.example.json
4. 按以下清单做真实页面联调：
   $SKILL_DIR/real-site-calibration-checklist.md
5. 把运行日志写入：
   $SKILL_DIR/logs/
6. 把截图保存到：
   $SKILL_DIR/screenshots/

如果你还没复制技能包内容，可先执行：
  rsync -av ./fanqie-author-publish/ "$SKILL_DIR/"

EOF
```

## File: skills/baidu-publisher/scripts/prepare-task.js
size: 641
sha: def6d54ed4ed2e3550c3d362f83e05f81280ba68
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/baidu-publisher/scripts/prepare-task.js

```text
import fs from 'node:fs/promises';

const templatePath = new URL('../templates/task-template.txt', import.meta.url);
const outputPath = process.argv[2] || '/tmp/baidu-task.txt';

const vars = {
  BOOK_ID: process.env.BOOK_ID || '',
  TITLE: process.env.TITLE || '',
  BODY_PATH: process.env.BODY_PATH || '',
  AUTO_PUBLISH: process.env.AUTO_PUBLISH || 'false',
  MIN_WORDS: process.env.MIN_WORDS || '20'
};

const template = await fs.readFile(templatePath, 'utf8');
const output = template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '');

await fs.writeFile(outputPath, output, 'utf8');
console.log(`Task written to ${outputPath}`);
```

## File: skills/baidu-publisher/templates/task-template.txt
size: 942
sha: 7b8224bd98128dca1e2c68a1490c6f81d99ae1ba
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/baidu-publisher/templates/task-template.txt

```text
任务：在百度作家平台后台为指定书籍创建章节，填写标题与正文，保存草稿；如 AUTO_PUBLISH=true，则继续发布。

参数：
- BOOK_ID: {{BOOK_ID}}
- TITLE: {{TITLE}}
- BODY_PATH: {{BODY_PATH}}
- AUTO_PUBLISH: {{AUTO_PUBLISH}}
- MIN_WORDS: {{MIN_WORDS}}

执行要求：

1. 打开百度作家平台后台并定位到 `{{BOOK_ID}}` 对应作品。
2. 进入新建章节或草稿编辑器页面。
3. 读取 `{{BODY_PATH}}` 作为正文内容。
4. 填写标题并确认回显为 `{{TITLE}}`。
5. 填写正文并确认编辑器存在正文内容，且页面字数已同步，且 `>= {{MIN_WORDS}}`。
6. 点击保存草稿并等待保存成功信号。
7. 如果 `AUTO_PUBLISH=true`，继续点击发布并等待成功或审核状态。
8. 失败时截图并报告具体卡点。

输出格式：
- currentUrl:
- titleVerified:
- wordCount:
- draftSaved:
- published:
- publishSignal:
- screenshots:
- note:
```

## File: skills/fanqie-publisher/scripts/prepare-task.js
size: 644
sha: d6fab207fe9f80d0d305ec859404c6ab36c8a704
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/fanqie-publisher/scripts/prepare-task.js

```text
import fs from 'node:fs/promises';

const templatePath = new URL('../templates/task-template.txt', import.meta.url);
const outputPath = process.argv[2] || '/tmp/fanqie-task.txt';

const vars = {
  BOOK_ID: process.env.BOOK_ID || '',
  TITLE: process.env.TITLE || '',
  BODY_PATH: process.env.BODY_PATH || '',
  AUTO_PUBLISH: process.env.AUTO_PUBLISH || 'false',
  MIN_WORDS: process.env.MIN_WORDS || '20'
};

const template = await fs.readFile(templatePath, 'utf8');

const output = template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '');

await fs.writeFile(outputPath, output, 'utf8');

console.log(`Task written to ${outputPath}`);
```

## File: skills/fanqie-publisher/templates/task-template.txt
size: 1908
sha: d9e3353861047b16e36cae6dbcd0506f0e18475f
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/fanqie-publisher/templates/task-template.txt

```text
任务：在番茄小说作者后台为指定书籍创建章节，填写标题与正文，保存草稿；如 AUTO_PUBLISH=true，则继续发布。

参数：
- BOOK_ID: {{BOOK_ID}}
- TITLE: {{TITLE}}
- BODY_PATH: {{BODY_PATH}}
- AUTO_PUBLISH: {{AUTO_PUBLISH}}
- MIN_WORDS: {{MIN_WORDS}}

执行要求：

1. 先访问以下两个入口，哪个能进入编辑器就使用哪个：
   - https://fanqienovel.com/main/writer/{{BOOK_ID}}/publish/?enter_from=newdraft
   - https://fanqienovel.com/main/writer/{{BOOK_ID}}/publish/?enter_from=newchapter

2. 成功进入编辑器的判定：
   - 页面存在标题输入框，placeholder 类似“请输入标题”
   - 页面存在正文富文本编辑区域

3. 如果未登录：
   - 立即停止
   - 返回“需要登录”

4. 读取 `{{BODY_PATH}}` 作为正文内容。

5. 填写标题：
   - 设置为 `{{TITLE}}`
   - 填完后再次读取并验证标题完全一致

6. 填写正文：
   - 优先尝试粘贴
   - 失败则退回键盘输入
   - 填写后确认编辑区已有正文内容
   - 并确认页面字数已同步，且 `>= {{MIN_WORDS}}`

7. 保存草稿：
   - 点击“保存草稿”或等效按钮
   - 等待出现“已保存 / 保存成功 / 草稿已保存”等提示
   - 保存截图

8. 如果 `AUTO_PUBLISH=false`：
   - 到此结束
   - 返回保存结果

9. 如果 `AUTO_PUBLISH=true`：
   - 继续寻找“发布 / 立即发布 / 提交”按钮
   - 如出现确认弹窗则继续确认
   - 等待出现：
     - 发布成功
     - 已发布
     - 审核中
     - 待审核
     - 或跳转到非 publish 页面
   - 保存截图

10. 注意：
   - 每次页面变化后重新 snapshot
   - 不依赖旧元素引用
   - 失败时截图并报告具体失败步骤

输出格式：
- openedUrl:
- currentUrl:
- titleVerified:
- wordCount:
- draftSaved:
- published:
- publishSignal:
- screenshots:
- note:
```

## File: skills/qimao-publisher/scripts/prepare-task.js
size: 641
sha: 340740e981ff051c7e5d2b0689c9b3be0adf0ff0
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/qimao-publisher/scripts/prepare-task.js

```text
import fs from 'node:fs/promises';

const templatePath = new URL('../templates/task-template.txt', import.meta.url);
const outputPath = process.argv[2] || '/tmp/qimao-task.txt';

const vars = {
  BOOK_ID: process.env.BOOK_ID || '',
  TITLE: process.env.TITLE || '',
  BODY_PATH: process.env.BODY_PATH || '',
  AUTO_PUBLISH: process.env.AUTO_PUBLISH || 'false',
  MIN_WORDS: process.env.MIN_WORDS || '20'
};

const template = await fs.readFile(templatePath, 'utf8');
const output = template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '');

await fs.writeFile(outputPath, output, 'utf8');
console.log(`Task written to ${outputPath}`);
```

## File: skills/qimao-publisher/templates/task-template.txt
size: 936
sha: 5f9371bd7bef04b6dbd32f8fde333cbc811aa9c7
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/skills/qimao-publisher/templates/task-template.txt

```text
任务：在七猫小说作者后台为指定书籍创建章节，填写标题与正文，保存草稿；如 AUTO_PUBLISH=true，则继续发布。

参数：
- BOOK_ID: {{BOOK_ID}}
- TITLE: {{TITLE}}
- BODY_PATH: {{BODY_PATH}}
- AUTO_PUBLISH: {{AUTO_PUBLISH}}
- MIN_WORDS: {{MIN_WORDS}}

执行要求：

1. 打开七猫作者后台并定位到 `{{BOOK_ID}}` 对应作品。
2. 进入新建章节或草稿编辑器页面。
3. 读取 `{{BODY_PATH}}` 作为正文内容。
4. 填写标题并确认回显为 `{{TITLE}}`。
5. 填写正文并确认编辑器存在正文内容，且页面字数已同步，且 `>= {{MIN_WORDS}}`。
6. 点击保存草稿并等待保存成功信号。
7. 如果 `AUTO_PUBLISH=true`，继续点击发布并等待成功或审核状态。
8. 失败时截图并报告具体卡点。

输出格式：
- currentUrl:
- titleVerified:
- wordCount:
- draftSaved:
- published:
- publishSignal:
- screenshots:
- note:
```

## File: test-cases.md
size: 6645
sha: 26d653525e818b53b3a7e8964501f563577c6af4
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/test-cases.md

```text
# 测试用例

这个文件用于验证 `fanqie-author-publish` 在 Linux / OpenClaw 环境中的可执行性。

目标不是一次性覆盖所有细节，而是先覆盖最常见、最关键的成功路径和失败路径。

## 测试前提

执行测试前，默认满足以下条件：
- OpenClaw 已可调用浏览器工具
- 使用独立的作者账号浏览器 profile
- 技能目录已可读写
- 输入文件为 UTF-8 编码
- 日志目录和截图目录可写

建议测试顺序：
1. 单章草稿成功路径
2. 单章发布前检查路径
3. 单章发布确认路径
4. 批量草稿路径
5. 异常与停止路径

## 用例 1：单章草稿保存成功

- 编号：`TC-001`
- 类型：成功路径
- 输入：`chapter.example.json`
- 动作：`prepare_draft`

预期步骤：
- 打开番茄作者后台
- 定位目标作品
- 打开章节编辑器
- 填入标题和正文
- 点击保存草稿

预期结果：
- 输出 `state: draft_saved`
- `result` 明确说明草稿已保存
- 生成日志文件
- 至少生成 1 到 2 张关键截图

失败判定：
- 标题或正文未成功写入
- 保存草稿后没有任何成功信号

## 用例 2：发布前检查成功

- 编号：`TC-002`
- 类型：成功路径
- 输入：已存在草稿的单章数据
- 动作：`review`

预期步骤：
- 打开目标作品的章节编辑页或章节列表
- 检查标题、正文、草稿状态、发布按钮状态

预期结果：
- 输出 `ready to publish` 或等价成功描述
- `state` 不应是失败态
- 如需人工确认，`confirmation_required` 为 `true`

失败判定：
- 无法确认作品或章节
- 页面上缺少关键字段导致无法判断是否可发布

## 用例 3：单章发布成功

- 编号：`TC-003`
- 类型：成功路径
- 输入：已准备好草稿的单章数据
- 动作：`publish`

前置条件：
- 用户已在当前会话明确确认发布

预期步骤：
- 再次确认作品名和章节标题
- 点击发布
- 如有弹窗，只确认一次
- 回到章节列表或看到成功提示

预期结果：
- 输出 `state: published_verified`
- `result` 明确说明已发布
- 日志文件中包含发布结果

失败判定：
- 重复点击发布
- 发布后无成功信号，且仍被错误标记为已发布

## 用例 4：批量草稿准备成功

- 编号：`TC-004`
- 类型：成功路径
- 输入：`chapters.batch.example.json`
- 动作：`prepare_draft`

预期步骤：
- 按顺序逐章处理
- 每章都完成标题填写、正文填写、草稿保存

预期结果：
- 每章都有单独结果记录
- 出现失败前，不应跳过未处理章节
- 总处理数量不超过 schema 限制

失败判定：
- 批量顺序错乱
- 某章失败后仍无说明地继续执行后续章节

## 用例 5：未登录后台

- 编号：`TC-005`
- 类型：失败路径
- 输入：任意合法单章输入
- 动作：`prepare_draft`

场景：
- 打开的是登录页，未处于作者后台登录状态

预期结果：
- 立即停止
- 输出 `state: failed_needs_review`
- `blocking_reason` 指向登录失效或未登录
- `requires_manual_action` 为 `true`

失败判定：
- 在登录页继续尝试查找作品或输入内容

## 用例 6：出现验证码或风控

- 编号：`TC-006`
- 类型：失败路径
- 输入：任意合法输入
- 动作：任意

场景：
- 页面出现验证码、身份校验、风控提示

预期结果：
- 立即停止
- 输出 `state: failed_needs_review`
- 日志中记录验证码或风控
- 不继续任何点击

失败判定：
- 继续尝试自动处理验证码
- 在风控页面上继续操作

## 用例 7：找不到目标作品

- 编号：`TC-007`
- 类型：失败路径
- 输入：不存在的 `book_name`
- 动作：`prepare_draft`

预期结果：
- 停止在作品定位阶段
- 输出明确错误：未找到目标作品
- 记录最后成功步骤为后台已打开或作品列表已打开

失败判定：
- 在未确认作品时误点其他作品进入

## 用例 8：章节标题重复

- 编号：`TC-008`
- 类型：失败路径
- 输入：与平台已有章节同名的标题
- 动作：`prepare_draft` 或 `publish`

预期结果：
- 停止并报告标题重复
- 默认不继续覆盖或重复发布
- `requires_manual_action` 为 `true`

失败判定：
- 未经确认直接覆盖已有内容
- 在重复标题下继续发布

## 用例 9：编辑器未接受正文

- 编号：`TC-009`
- 类型：失败路径
- 输入：合法单章数据
- 动作：`prepare_draft`

场景：
- 标题已输入，但正文编辑器未显示内容或仅显示部分内容

预期结果：
- 停止在正文写入阶段
- 输出 `state: failed_needs_review`
- 截图保留编辑器当前状态

失败判定：
- 未确认正文是否存在就继续保存草稿

## 用例 10：保存草稿结果不明确

- 编号：`TC-010`
- 类型：失败路径
- 输入：合法单章数据
- 动作：`prepare_draft`

场景：
- 点击保存草稿后没有 toast，也没有明确状态变化

预期结果：
- 不重复点击保存草稿
- 尝试通过章节列表或页面状态确认
- 若仍不明确，则停止并报告结果不明确

失败判定：
- 连续多次点击保存草稿

## 用例 11：发布结果不明确

- 编号：`TC-011`
- 类型：失败路径
- 输入：合法单章数据
- 动作：`publish`

场景：
- 点击发布后没有成功提示，也没有明显跳转或状态变化

预期结果：
- 不再次点击发布
- 优先去章节列表检查状态
- 若仍无法确认，停止并标记人工复查

失败判定：
- 为了“确认一下”再次点击发布

## 用例 12：批量处理中途失败

- 编号：`TC-012`
- 类型：失败路径
- 输入：批量章节数据
- 动作：`prepare_draft`

场景：
- 第 2 章处理时出现错误

预期结果：
- 默认停止后续章节
- 输出当前失败章节和阻塞原因
- 保留前面已成功章节的结果记录

失败判定：
- 未说明原因就继续处理第 3 章之后的内容

## 最低验收标准

至少满足以下条件，才能认为第一版技能包可在 Linux / OpenClaw 环境中试运行：

- `TC-001` 通过
- `TC-002` 通过
- `TC-003` 在人工确认场景下通过
- `TC-005`、`TC-006`、`TC-009`、`TC-011` 能正确停止
- 日志和截图路径可正常生成
- 输入输出均能通过 schema 校验

## 建议记录方式

每次跑测试时，建议补充：
- 测试时间
- 使用的输入文件
- 实际结果
- 是否符合预期
- 失败截图路径
- 是否需要调整 `selectors.md` 或 `execution-policy.md`

这样后面可以持续迭代这套技能包，而不是每次从头排查。
```

## File: validate-input.sh
size: 1014
sha: c2ade76348cd7fd7f34a441cc503838d2330728e
upstream: https://github.com/owlco001/fanqie-author-publish/blob/HEAD/validate-input.sh

```text
#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCHEMA_FILE="$SCRIPT_DIR/input.schema.json"
INPUT_FILE="${1:-}"

if [[ -z "$INPUT_FILE" ]]; then
  cat <<EOF
用法：
  bash validate-input.sh /path/to/input.json

说明：
  - 用于校验单章节或批量章节 JSON 输入
  - 依赖 Node.js 和 npx
  - 使用 input.schema.json 进行校验
EOF
  exit 1
fi

if [[ ! -f "$INPUT_FILE" ]]; then
  echo "输入文件不存在: $INPUT_FILE" >&2
  exit 1
fi

if [[ ! -f "$SCHEMA_FILE" ]]; then
  echo "Schema 文件不存在: $SCHEMA_FILE" >&2
  exit 1
fi

if ! command -v node >/dev/null 2>&1; then
  echo "未检测到 node，请先安装 Node.js。" >&2
  exit 1
fi

if ! command -v npx >/dev/null 2>&1; then
  echo "未检测到 npx，请先安装 npm / npx。" >&2
  exit 1
fi

echo "开始校验输入文件..."
echo "Schema: $SCHEMA_FILE"
echo "Input : $INPUT_FILE"

npx --yes ajv-cli validate -s "$SCHEMA_FILE" -d "$INPUT_FILE"

echo
echo "校验通过。"
```
