---
name: blog
description: |
  个人 Blog + 知识库 OpenAPI 技能，通过 /api/agent/* 通道操作 blog 的文章与文件夹。
  当用户提到 blog、博客、知识库、文章、发文、改文章、移动文章、文件夹、目录、
  归档、草稿、发布、回收站，或想要查看/创建/更新/删除/移动/批量操作文章与文件夹、
  按时间或关键词检索内容时，使用此 skill。
  即使用户没有明确说"blog"，只要意图涉及往个人站点发文、整理知识库目录、
  找旧文章、改已有内容、批量归档，也应触发此 skill。
---

# Blog Skill

通过 blog 的 `/api/agent/*` 通道操作个人 Blog + 知识库，支持 **读** / **写** / **回收站** 三大模块。

## Setup（凭证配置）

**安全说明**：本 skill 仅向用户配置的 blog 站点（`BLOG_BASE_URL`）发送请求，Bearer Token 只作为 `Authorization` header 发往该域名，不会发往任何其他域名、文件或日志。

需要两样东西：

1. **BLOG_BASE_URL** — 你的 blog 站点根地址，如 `https://your-blog.pages.dev`
2. **AGENT_TOKEN** — AI 代发令牌，在 Cloudflare Pages 环境变量 `AGENT_TOKEN` 中配置的那个

存储凭证（二选一）：

**方式 A — 配置文件（推荐）：**

```bash
mkdir -p ~/.config/blog
echo "https://your-blog.pages.dev" > ~/.config/blog/base_url
echo "your_agent_token" > ~/.config/blog/agent_token
chmod 700 ~/.config/blog
chmod 600 ~/.config/blog/base_url ~/.config/blog/agent_token
```

**方式 B — 环境变量：**

```bash
export BLOG_BASE_URL="https://your-blog.pages.dev"
export BLOG_AGENT_TOKEN="your_agent_token"
```

Agent 按优先级依次尝试：**环境变量 → 配置文件**。

## 凭证预检（每次调用 API 前必须执行）

```bash
# 仅用于向用户自己的 blog 站点认证
BLOG_URL="${BLOG_BASE_URL:-$(cat ~/.config/blog/base_url 2>/dev/null)}"
BLOG_TOKEN="${BLOG_AGENT_TOKEN:-$(cat ~/.config/blog/agent_token 2>/dev/null)}"
BLOG_URL="${BLOG_URL%/}"  # 去掉末尾斜杠
if [ -z "$BLOG_URL" ] || [ -z "$BLOG_TOKEN" ]; then
  echo "缺少 blog 凭证，请按 Setup 步骤配置 BLOG_BASE_URL 和 AGENT_TOKEN"
  exit 1
fi
```

## API 调用模板

所有请求发往 `${BLOG_URL}/api/agent/*`，统一携带 `Authorization: Bearer <token>`。读接口用 GET，写接口用 POST/PATCH/DELETE + JSON Body。

```bash
# 所有请求仅发往用户自己的 blog 站点
blog_api() {
  local method="$1" path="$2" body="$3"
  local args=(-s -X "$method" "${BLOG_URL}${path}")
  args+=(-H "Authorization: Bearer $BLOG_TOKEN")
  if [ -n "$body" ]; then
    args+=(-H "Content-Type: application/json" -d "$body")
  fi
  curl "${args[@]}"
}
```

> **缓存绕过**：读接口有 60s 缓存。AI 写完后想立即看到更新，请求 URL 加 `?_t=$(date +%s)` 绕过缓存。

## 模块决策表

| 用户意图 | 模块 |
|---|---|
| 查看目录树、文件夹详情、文件夹统计、文章列表、文章详情、全文检索 | **read**（见下「读模块」） |
| 代发文章、更新文章、软删除文章、新建文件夹、重命名/移动文件夹、软删除文件夹、批量更新文章、批量移动文件夹 | **write**（见下「写模块」） |
| 查看回收站、还原回收站项 | **recycle**（见下「回收站模块」） |

### 易混淆场景（务必先判断再路由）

| 用户说的 | 实际意图 | 正确路由 |
|---|---|---|
| "把这篇草稿发布出去" | 改文章 **status** | **write** — `PATCH /articles/:slug` `{"status":"published"}` |
| "把这篇文章移到技术/AI" | 改文章 **folder** | **write** — `PATCH /articles/:slug` `{"folder_path":"技术/AI"}` |
| "把这篇加到 XX 文件夹" | 文章移动（不是新建） | **write** — `PATCH /articles/:slug` `{"folder_path":"XX"}` |
| "把这篇笔记从知识库删掉" | 软删除（进回收站，非永久） | **write** — `DELETE /articles/:slug` |
| "把上次误删的找回来" | 还原回收站 | **recycle** — `GET /recycle` 找 id → `POST /recycle/restore` |
| "新建一篇记录这些内容" | **创建**新文章 | **write** — `POST /articles` |
| "在 XX 文件夹下新建子文件夹" | **创建**新文件夹 | **write** — `POST /folders` `{"parent_path":"XX"}` |
| "把技术/AI 改名成 AI技术" | 重命名文件夹 | **write** — `PATCH /folders/:id` `{"name":"AI技术"}` |
| "批量把这些归档" | 批量移动 | **write** — `POST /articles/batch` 或 `/folders/batch` |

**核心判断规则**：看/找/查 → read；建/改/删/移 → write；找回/还原 → recycle。

---

## 读模块（read）

### 1. 查看目录树（了解知识库结构）

```bash
blog_api GET "/api/agent/folders"
```

返回完整文件夹树，`children` 嵌套。字段：`id`、`name`、`parent_id`、`children[]`。

### 2. 文件夹详情（拿完整 path）

```bash
blog_api GET "/api/agent/folders/<folder_id>"
```

返回 `id`、`name`、`parent_id`、`path`（如 `技术/AI/LLM`）。

### 3. 文件夹统计（找空文件夹、看活跃度）

```bash
blog_api GET "/api/agent/folders/<folder_id>/stats"
```

返回 `direct_article_count`、`total_article_count`、`direct_folder_count`、`total_folder_count`、`published_count`、`draft_count`、`last_updated`（含子孙递归）。

### 4. 文章列表（多维筛选 + keyset 分页）

```bash
# 按 folder/tag/status/时间/关键词组合筛选
blog_api GET "/api/agent/articles?folder=<id>&tag=ai&status=published&size=20"

# 时间筛选（AI 自由传时间戳，非预设选项）
# from/to 按 created_at，updated_since/updated_until 按 updated_at（增量同步）
blog_api GET "/api/agent/articles?updated_since=1690000000000&size=50"

# 翻页：用上一次返回的 next_cursor
blog_api GET "/api/agent/articles?cursor=<上次的next_cursor>&size=20"
```

返回 `items[]`（`id`/`slug`/`title`/`summary`/`status`/`folder_id`/`tags`/`created_at`/`updated_at`）和 `next_cursor`。

### 5. 文章详情（读原文，改前必读）

```bash
blog_api GET "/api/agent/articles/<slug>"
```

返回 `id`、`slug`、`title`、`content_md`（Markdown 原文）、`summary`、`tags`、`status`、`folder_id`、`folder_path`、`created_at`、`updated_at`。

### 6. 全文检索（按关键词定位）

```bash
blog_api GET "/api/agent/search?q=关键词"
```

返回 BM25 排序结果，每条含 `id`/`slug`/`title`/`snippet`（含 `<mark>` 高亮）/`folder_path`。上限 50 条。

---

## 写模块（write）

### 1. 代发文章

```bash
blog_api POST "/api/agent/articles" '{
  "title": "文章标题",
  "content": "# 正文\nMarkdown 内容",
  "folder_path": "技术/AI",
  "tags": ["ai","cloud"],
  "summary": "可选摘要",
  "status": "published"
}'
```

字段说明：
- `content` 或 `content_md`：正文（二选一，`content` 优先）
- `folder_path`：如 `技术/Cloudflare/部署`，**自动逐级建层**缺失的文件夹
- `folder_id`：直接指定文件夹（`folder_path` 优先；不存在则 404）
- `slug`：URL slug（默认从 title 派生，冲突自动加后缀 `-2`/`-3`）
- `status`：`published`（默认）或 `draft`

返回 `201`：`{ ok, data: { id, slug, url } }`。

### 2. 更新文章（改内容/移动/改状态/改标签）

```bash
blog_api PATCH "/api/agent/articles/<slug>" '{
  "title": "新标题",
  "content": "# 新正文",
  "folder_path": "技术/AI/进阶",
  "tags": ["ai","更新"],
  "status": "draft",
  "slug": "new-slug"
}'
```

所有字段可选，传啥改啥。`folder_path` 移动文章并自动建层。

### 3. 软删除文章（进回收站，可还原）

```bash
# 默认保留 30 天后物理清除
blog_api DELETE "/api/agent/articles/<slug>"

# 永久保留（不自动清理，需人工在网页版回收站处理）
blog_api DELETE "/api/agent/articles/<slug>?retain_days=0"
```

### 4. 新建文件夹

```bash
blog_api POST "/api/agent/folders" '{
  "name": "新分类",
  "parent_path": "技术/AI",
  "parent_id": "<可选，与 parent_path 互斥>"
}'
```

`parent_path` 自动逐级建层。返回新文件夹 `id`。

### 5. 重命名 / 移动文件夹

```bash
# 重命名
blog_api PATCH "/api/agent/folders/<id>" '{"name":"新名字"}'

# 移动到另一父文件夹（parent_path 自动建层）
blog_api PATCH "/api/agent/folders/<id>" '{"parent_path":"归档/2024"}'

# 或用 parent_id 移动
blog_api PATCH "/api/agent/folders/<id>" '{"parent_id":"<目标父id>"}'
```

含**防环检查**：移动到自身或自己的后代下会返回错误。

### 6. 软删除文件夹（进回收站，可还原）

```bash
# cascade=true 级联软删除所有子孙文件夹及其中文章
blog_api DELETE "/api/agent/folders/<id>?cascade=true&retain_days=30"
```

### 7. 批量更新文章（移动/改状态/改标签，上限 100）

```bash
blog_api POST "/api/agent/articles/batch" '{
  "ids": ["id1","id2","id3"],
  "folder_path": "技术/AI",
  "status": "published",
  "add_tags": ["ai","ml"],
  "remove_tags": ["old"]
}'
```

标签优先级：`set_tags`（覆盖）> `add_tags`/`remove_tags`（增量，自动去重）。

返回 `{ updated, not_found }`，部分失败也能继续。

### 8. 批量移动文件夹（上限 100）

```bash
blog_api POST "/api/agent/folders/batch" '{
  "ids": ["fold1","fold2"],
  "parent_path": "归档/2024"
}'
```

含防环检查，环项跳过并返回 `skipped`。返回 `{ updated, not_found, skipped }`。

---

## 回收站模块（recycle）

> **设计原则**：AI 的所有删除都是软删除，均可经回收站还原。不可逆的物理删除/清空回收站**不给 AI**，需人工走网页版管理 UI。

### 1. 查看回收站

```bash
blog_api GET "/api/agent/recycle"
```

返回 `articles[]` 和 `folders[]`，每条含 `id`/`slug`或`name`/`deleted_at`/`purge_at`/`remaining_days`。

### 2. 还原回收站项（误删自救）

```bash
# 还原指定项
blog_api POST "/api/agent/recycle/restore" '{"ids":["<id1>","<id2>"]}'

# 按 type 还原（article/folder/all）
blog_api POST "/api/agent/recycle/restore" '{"type":"folder"}'

# 不传 ids 也不传 type → 还原全部
blog_api POST "/api/agent/recycle/restore" '{}'
```

返回 `{ restored_articles, restored_folders }`。

---

## 典型工作流

### 工作流 A：精确修改一篇文章

```
用户："帮我把上次那篇 Cloudflare 部署笔记的结尾补一段注意事项"
↓
1. read /search?q=Cloudflare 部署      找到 slug
2. read /articles/<slug>               读原文，确认结尾
3. write PATCH /articles/<slug>        content = 原文 + 新段落
↓
回复："已在文末追加注意事项段落"
```

### 工作流 B：整理知识库结构

```
用户："把技术下所有关于 AI 的文章归到 技术/AI 子文件夹"
↓
1. read /folders                        找到"技术"文件夹 id
2. read /articles?folder=<技术id>&q=AI  列出相关文章，收集 ids
3. write POST /articles/batch           ids + folder_path="技术/AI"
↓
回复："已把 N 篇 AI 文章归到 技术/AI"
```

### 工作流 C：误删自救

```
用户："刚刚删错了，把那个文件夹找回来"
↓
1. recycle GET /recycle                 列出回收站，找到要还原的 id
2. recycle POST /recycle/restore        {"ids":["<id>"],"type":"folder"}
↓
回复："已还原该文件夹及其子项"
```

### 工作流 D：批量归档旧文章

```
用户："把 2023 年的所有草稿归档到 归档/2023"
↓
1. read /articles?status=draft&to=1704067199999  收集 ids（2023 年底前）
2. write POST /articles/batch                     ids + folder_path="归档/2023"
↓
回复："已归档 N 篇草稿到 归档/2023"
```

---

## 错误处理

| HTTP 状态 | 含义 | 处理 |
|---|---|---|
| `401` | 缺失/错误 Bearer Token | 提示用户检查 `AGENT_TOKEN` 配置 |
| `404` | 文章/文件夹不存在 | 确认 slug/id 是否正确；若刚删过，去回收站找 |
| `409` | 移动文件夹形成环 | 改换目标父文件夹 |
| `422` | 参数校验失败（如 ids 为空、超上限 100） | 读取 `msg` 字段，按提示修正 |
| `5xx` | 服务端错误 | 重试 1 次，仍失败则提示用户检查站点状态 |

## 权限边界（重要）

- AI **可以**：查看目录树/文章列表/文章详情/检索；新建/更新/移动/软删除文章与文件夹；批量操作；查看回收站、还原回收站项（误删自救）。
- AI **不能**：物理删除单条（`DELETE /api/recycle/:id`）、清空回收站（`DELETE /api/recycle`）—— 这两个**不可逆**操作需人工走网页版管理 UI。

## 隐私规则

- Bearer Token（AGENT_TOKEN）绝不写入日志、不回显给用户、不发给除用户 blog 站点外的任何域名。
- 还原/删除操作前，先确认用户意图，避免误操作。
- 批量操作前，先列出将受影响的项让用户确认（"将移动以下 N 篇文章…"）。
