用 Claude Agent Skills 实现博客一键发布:从繁琐流程到自然语言交互
- Published on
- ...
- Authors

- Name
- Huashan
- @herohuashan
引言:从多步骤到一句话
想象一下这两种发布博客的体验:
传统方式(即使有自动化脚本):
./check_sensitive.sh
python publish.py \
-f "ready/article.md" \
-t "文章标题" \
-c tech \
--tags "标签1,标签2" \
-d "文章描述"
python sync.py
# 4. 预览
cd blog && hugo server -D
# 5. 部署
cd blog && hugo && git add . && git commit -m "..." && git push
使用 Claude Agent Skills:
我:帮我发布这篇文章
Claude:
✅ 已检查敏感信息
✅ 已发布文章到 Hugo
✅ 已同步到 Obsidian
✅ 已构建并部署
🎉 发布完成!文章已上线。
这就是 AI Agent 的魔力。
本文将分享我如何借助 Claude Agent Skills,将 Obsidian 到 Hugo Blog 的复杂发布流程封装为一句话交互,实现真正的"一键发布"。
更重要的是: 所有相关文件(Skill 定义、Python 脚本、配置文件)都集中在一个文件夹里,复制一个文件夹就能移植到任何项目,真正做到"开箱即用"。
第一部分:理解 Claude Agent Skills
什么是 Claude Agent Skills?
Claude Agent Skills 是 Claude Code 提供的一个强大特性,允许用户将复杂的工作流封装为可复用的"技能",然后通过自然语言交互来触发执行。
核心概念
Skills(技能)
- 本质是一个包含详细指令的 Markdown 文件
- 描述了 Claude 应该如何完成特定任务
- 可以调用系统工具(文件操作、命令执行等)
- 支持复杂的决策逻辑和错误处理
Skill Prompt(技能提示词)
- 详细描述任务的步骤和要求
- 定义输入参数和输出格式
- 包含错误处理和边界情况处理逻辑
自然语言触发
- 用户用日常语言描述需求
- Claude 自动识别并执行相应的 Skill
- 无需记忆复杂的命令行参数
技术架构
用户请求("帮我发布文章")
↓
Claude 自然语言理解
↓
匹配相应 Skill
↓
加载 Skill Prompt
↓
执行具体步骤
├─ 读取文件
├─ 运行命令
├─ 处理数据
└─ 返回结果
↓
友好的用户反馈
为什么选择 Agent Skills?
对比其他自动化方案
| 方案 | 优势 | 劣势 |
|---|---|---|
| Shell 脚本 | 轻量、快速 | 需要记忆命令、参数复杂、不灵活 |
| Python 脚本 | 功能强大、易扩展 | 仍需命令行操作、交互性差 |
| Obsidian 插件 | 与编辑器深度集成 | 开发复杂、需要 TypeScript |
| Claude Skills | 自然语言交互、智能决策 | 需要 Claude Code |
Agent Skills 的独特优势:
- 自然交互:无需记忆命令,用人话描述需求即可
- 智能决策:能根据上下文自动处理边界情况
- 灵活适应:可以处理"发布这篇文章"、"发布 ready 目录下最新的文章"等不同表述
- 错误处理:自动检测问题并给出建议
- 可组合:一个 Skill 可以调用多个底层工具
第二部分:底层技术实现
在封装 Agent Skills 之前,我们需要先构建底层的自动化工具。这一部分是技术基础。
内容创作者的困境
作为一个技术博主,我面临着典型的内容管理挑战:
- Obsidian 是我的主力笔记工具,用于日常记录、知识管理、想法沉淀
- Hugo Blog 是我的对外发布平台,用于分享技术文章
但当我想把 Obsidian 中的某篇笔记发布到博客时,需要经历一个繁琐的流程:复制内容、添加 Front Matter、处理图片、转换链接、移动文件......整个流程至少需要 10-15 分钟。
双系统并存的核心挑战
1. Front Matter 格式差异
Obsidian(YAML):
---
Date: 2024-11-13
tags:
- 标签1
- 标签2
project: 项目名
---
特点:
- 使用
---包裹 - 字段灵活,无强制要求
- 支持嵌套结构
- 大小写不敏感
Hugo(TOML):
+++
title = "文章标题"
date = "2024-11-13"
lastmod = "2024-11-13"
author = ["geekhuashan"]
draft = false
categories = ["tech"]
tags = ["标签1", "标签2"]
description = "文章描述"
+++
特点:
- 使用
+++包裹 - 必须包含特定字段(title, date, author, categories)
- 严格的类型要求(数组、字符串、布尔值)
- 需要符合 TOML 规范
2. 图片链接差异
Obsidian Wiki-style:


特点:
- 简洁的 Wiki 语法
- 自动从
Attachment/目录查找 - 支持空格和特殊字符
Hugo 标准 Markdown:


特点:
- 标准 Markdown 语法
- 需要明确的路径(相对于
static/) - 需要 alt 文本(用于 SEO 和可访问性)
- 文件名建议使用连字符,避免空格
3. 文件组织差异
Obsidian 目录结构:
Main/
├── 07 blog idea/ # 博客草稿(混合存放)
│ ├── draft/ # 写作中的草稿
│ ├── ready/ # 待发布队列
│ └── published/ # 已发布备份
└── Attachment/ # 所有附件统一存放
├── Mac mini.png
└── ...
Hugo 目录结构:
blog/
├── content/posts/ # 按分类组织文章
│ ├── tech/
│ ├── read/
│ ├── life/
│ └── project/
└── static/ # 按分类组织图片
├── tech/
├── read/
└── life/
4. 兼容性矩阵
| 特性 | Obsidian | Hugo | 需要转换 |
|---|---|---|---|
| Front Matter | YAML | TOML | ✅ 是 |
| 图片链接 |  |  | ✅ 是 |
| 图片路径 | 相对路径 | 绝对路径 | ✅ 是 |
| 文件名空格 | 支持 | 不建议 | ✅ 是 |
| Wiki 链接 | [xxx](/xxx) | [xxx](/xxx) | ❌ 否(Hugo 已支持) |
| Markdown 语法 | 标准 + 扩展 | 标准 | ❌ 否 |
第一步:自动化发布工具(v1.0)
技术方案设计
基于上述分析,我决定开发一个 Python 脚本来自动化整个发布流程。
核心功能规划
草稿扫描与选择
- 自动扫描
ready/队列目录 - 显示所有可用草稿及修改时间
- 支持交互式选择
- 自动扫描
Front Matter 转换
- 解析 YAML Front Matter
- 提取现有字段(如 Date, tags)
- 交互式收集缺失字段(title, category, description)
- 生成完整的 TOML Front Matter
图片处理
- 提取所有
和引用 - 在
Attachment/目录中递归查找图片 - 标准化文件名(去空格、小写化)
- 复制到
blog/static/**category**/ - 生成图片路径映射表
- 提取所有
链接转换
- 替换
为 - 保持 Wiki 链接
[article](/article)不变(Hugo 支持) - 处理边界情况(文件名中的特殊字符)
- 替换
文件发布
- 组合 Front Matter 和正文
- 生成带日期前缀的文件名(
2025-11-14_slug.md) - 保存到
blog/content/posts/**category**/ - 从
ready/队列删除已发布文件
技术栈选择
Python 3.x
- 理由:跨平台、生态丰富、适合脚本开发
- 核心库:
pathlib(路径操作)、re(正则匹配)、shutil(文件复制) - 第三方库:
pyyaml(YAML 解析)、questionary(交互式 CLI,可选)
YAML 配置文件
- 理由:可读性好、易于维护
- 用途:路径映射、默认值、图片命名规则
核心实现细节
1. Front Matter 转换
核心挑战: YAML 到 TOML 的类型映射
def generate_toml_front_matter(self, metadata: Dict) -> str:
"""生成 TOML Front Matter"""
toml_lines = ["+++"]
# 字符串字段 - 需要引号包裹
toml_lines.append(f'title = "**metadata["title"]**"')
toml_lines.append(f'date = "**metadata["date"]**"')
toml_lines.append(f'lastmod = "**metadata["lastmod"]**"')
# 数组字段 - 需要方括号和引号
toml_lines.append(f'author = ["**metadata["author"]**"]')
toml_lines.append(f'categories = ["**metadata["category"]**"]')
# 标签数组 - 动态生成
if metadata['tags']:
tags_str = ", ".join([f'"**tag**"' for tag in metadata['tags']])
toml_lines.append(f'tags = [**tags_str**]')
# 布尔字段 - 小写 true/false
toml_lines.append(f'draft = **str(metadata["draft"]).lower()**')
# 描述 - 需要转义引号
if metadata['description']:
desc = metadata['description'].replace('"', '\\"')
toml_lines.append(f'description = "**desc**"')
toml_lines.append("+++")
return "\n".join(toml_lines)
关键点:
- TOML 字符串必须用双引号包裹
- 数组语法:
["item1", "item2"] - 布尔值:小写
true/false(不是 Python 的True/False) - 特殊字符转义:描述中可能包含引号
2. 图片提取与处理
核心挑战: 支持两种图片引用格式
def extract_image_references(self, body: str) -> List[str]:
"""提取文章中的所有图片引用"""
# 匹配 Obsidian Wiki-style: 
wiki_pattern = r'!\[\[([^\]]+)\]\]'
wiki_images = re.findall(wiki_pattern, body)
# 匹配标准 Markdown 本地图片: 
md_pattern = r'!\[([^\]]*)\]\((?!http)([^\)]+)\)'
md_images = [match[1] for match in re.findall(md_pattern, body)]
return wiki_images + md_images
图片文件名标准化:
def normalize_image_filename(self, filename: str) -> str:
"""标准化图片文件名"""
# 处理空格:Mac mini.png → mac-mini.png
if config['remove_spaces']:
filename = filename.replace(' ', '-')
# 转换为小写:Test.PNG → test.png
if config['lowercase']:
name, ext = os.path.splitext(filename)
filename = name.lower() + ext.lower()
return filename
实际效果:
Mac mini.png→mac-mini.pngPasted image 20230320223703.png→pasted-image-20230320223703.pngTest File.JPG→test-file.jpg
3. 链接替换
核心挑战: 精确匹配并替换,避免误伤
def replace_image_links(self, body: str, image_mapping: Dict[str, str], category: str) -> str:
"""替换图片链接为 Hugo 格式"""
result = body
for original_name, new_name in image_mapping.items():
# 替换 Wiki-style 链接
wiki_pattern = rf'!\[\[**re.escape(original_name)**\]\]'
hugo_link = f''
result = re.sub(wiki_pattern, hugo_link, result)
# 替换标准 Markdown 链接(保留原有 alt 文本)
md_pattern = rf'!\[([^\]]*)\]\(**re.escape(original_name)**\)'
hugo_link_with_alt = rf''
result = re.sub(md_pattern, hugo_link_with_alt, result)
return result
关键点:
- 使用
re.escape()处理文件名中的特殊字符(如括号、空格) - 保留标准 Markdown 链接的 alt 文本
- 生成绝对路径
/category/image.png(Hugo 要求)
使用效果演示
发布流程:
$ python publish.py
============================================================
Obsidian → Hugo Blog 自动发布工具
============================================================
🔍 正在扫描草稿...
📝 可用的草稿文件:
[1] 实验室自动化:从传统到智能的转型之路.md (修改于: 2024-08-19 09:23)
[2] tailscale-derp-guide.md (修改于: 2024-06-07 16:52)
选择要发布的草稿: 2
✅ 已选择: tailscale-derp-guide.md
📋 请提供发布信息:
文章标题 [Tailscale 完全指南]: Tailscale完全指南:从自建DERP服务器到Mac子网路由
选择分类:
❯ tech
read
life
project
标签 (逗号分隔): Tailscale, DERP, Mac, 子网路由, 网络, 服务器部署
文章描述 (用于 SEO): 一篇关于如何自建Tailscale DERP服务器以及配置Mac作为子网路由的详细指南
🖼️ 正在处理图片...
找到 3 张图片
✅ 复制图片: Mac mini.png -> mac-mini.png
✅ 复制图片: Pasted image 20230320223703.png -> pasted-image-20230320223703.png
✅ 复制图片: tailscale-diagram.png -> tailscale-diagram.png
📝 正在生成文章...
============================================================
✅ 发布完成!
============================================================
📍 文章位置: blog/content/posts/tech/2025-11-14_tailscale-derp-guide.md
📍 分类: tech
📍 标签: Tailscale, DERP, Mac, 子网路由, 网络, 服务器部署
💡 下一步:
1. 同步: python sync.py
2. 预览: cd blog && hugo server -D
3. 部署: cd blog && git push
性能数据(v1.0)
| 指标 | 手动流程 | 自动化工具 | 提升 |
|---|---|---|---|
| 发布耗时 | 10-15 分钟 | 1-2 分钟 | 83% |
| 图片处理 | 手动查找、复制、重命名 | 自动完成 | 100% |
| Front Matter | 手动编写 | 自动生成 | 100% |
| 链接转换 | 手动查找替换 | 自动批量替换 | 100% |
| 错误率 | ~20%(漏图片、错路径) | <5% | 75% |
第二步:双向同步(v2.0)
单向发布的局限性
v1.0 版本的自动化发布工具极大地提升了发布效率,但很快我就发现了新的问题:
单向发布导致的内容管理困境:
- 修改不同步:有时我会在 Hugo Blog 中直接修改文章(比如通过 GitHub Web 快速修正错误),但这些修改不会同步回 Obsidian
- 备份不完整:Obsidian 的备份只在发布时更新,如果在 Hugo 中修改了文章,Obsidian 中的版本就过时了
- 管理混乱:已发布文章没有按分类组织,难以管理
- 双重维护:需要在两边分别维护同一篇文章,容易出错
我需要的不是单向发布,而是双向同步。
双向同步系统设计
需求分析
按分类组织
- Obsidian 的
published/目录应该按照 tech/read/life/project 分类组织 - 与 Hugo Blog 的
content/posts/目录结构保持一致
- Obsidian 的
智能同步检测
- 自动检测哪些文章需要同步
- 基于文件修改时间判断同步方向
- 避免不必要的文件复制
灵活的同步方向
- 支持双向同步(Obsidian ↔ Hugo)
- 支持单向同步(仅 Obsidian → Hugo 或 Hugo → Obsidian)
- 用户可以根据实际情况选择
同步策略
核心原则:以修改时间为准
# 伪代码
for each file in all_files:
if file exists only in Obsidian:
action = "新增到 Hugo"
elif file exists only in Hugo:
action = "新增到 Obsidian"
elif obsidian_mtime > hugo_mtime:
action = "更新 Hugo"
elif hugo_mtime > obsidian_mtime:
action = "更新 Obsidian"
else:
action = "已同步,跳过"
时间比较精度:
- 使用秒级精度(
stat().st_mtime) - 时间差大于 1 秒才认为有修改(避免文件系统精度问题)
目录结构改造
改造前(v1.0):
Main/07 blog idea/
├── draft/
├── ready/
├── published/ # 扁平结构
│ ├── article-1.md
│ ├── article-2.md
│ └── ...
└── *.md
改造后(v2.0):
Main/07 blog idea/
├── draft/
├── ready/
├── published/ # 按分类组织
│ ├── tech/ # 技术类
│ ├── read/ # 阅读类
│ ├── life/ # 生活类
│ └── project/ # 项目类
└── *.md
核心实现
1. 文件扫描与比较
def scan_files(self, directory: Path, category: str) -> Dict[str, Path]:
"""扫描指定目录下的文章文件"""
files = {}
category_dir = directory / category
if category_dir.exists():
for file in category_dir.glob("*.md"):
if file.is_file():
files[file.name] = file
return files
def compare_files(self, obsidian_files: Dict[str, Path],
hugo_files: Dict[str, Path]) -> Tuple[List, List, List]:
"""
比较 Obsidian 和 Hugo 的文件
返回:(需要同步到 Hugo 的, 需要同步到 Obsidian 的, 冲突的)
"""
to_hugo = [] # Obsidian 更新 → Hugo
to_obsidian = [] # Hugo 更新 → Obsidian
conflicts = [] # 两边都修改过的文件
# Obsidian 中有但 Hugo 中没有,或 Obsidian 更新
for filename, obs_path in obsidian_files.items():
if filename not in hugo_files:
to_hugo.append((filename, obs_path, None, 'new'))
else:
hugo_path = hugo_files[filename]
obs_mtime = self.get_file_mtime(obs_path)
hugo_mtime = self.get_file_mtime(hugo_path)
# 时间差超过 1 秒才认为有修改
time_diff = abs(obs_mtime - hugo_mtime)
if time_diff > 1:
if obs_mtime > hugo_mtime:
to_hugo.append((filename, obs_path, hugo_path, 'update'))
else:
to_obsidian.append((filename, hugo_path, obs_path, 'update'))
# Hugo 中有但 Obsidian 中没有
for filename, hugo_path in hugo_files.items():
if filename not in obsidian_files:
to_obsidian.append((filename, hugo_path, None, 'new'))
return to_hugo, to_obsidian, conflicts
2. 同步执行
def sync_file(self, source: Path, target: Path, action: str):
"""同步单个文件"""
try:
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target) # 保留文件元数据
return True
except Exception as e:
print(f"❌ 同步失败 **source.name**: **e**")
return False
def sync_category(self, category: str, direction: str = 'both') -> Dict:
"""
同步指定分类的文章
direction: 'to_hugo', 'to_obsidian', 'both'
"""
print(f"\n📂 同步分类: **category**")
print("-" * 60)
# 扫描文件
obsidian_files = self.scan_files(self.published_folder, category)
hugo_files = self.scan_files(self.hugo_content, category)
print(f"Obsidian: **len(obsidian_files)** 篇文章")
print(f"Hugo Blog: **len(hugo_files)** 篇文章")
# 比较文件
to_hugo, to_obsidian, conflicts = self.compare_files(obsidian_files, hugo_files)
stats = **
'to_hugo': 0,
'to_obsidian': 0,
'skipped': 0,
'failed': 0
**
# 同步到 Hugo
if direction in ['to_hugo', 'both'] and to_hugo:
print(f"\n📤 需要同步到 Hugo: **len(to_hugo)** 篇")
for filename, source, target, action in to_hugo:
action_text = "新增" if action == 'new' else "更新"
target_path = self.hugo_content / category / filename
if self.sync_file(source, target_path, action):
print(f" ✅ **action_text**: **filename**")
stats['to_hugo'] += 1
else:
stats['failed'] += 1
# 同步到 Obsidian
if direction in ['to_obsidian', 'both'] and to_obsidian:
print(f"\n📥 需要同步到 Obsidian: **len(to_obsidian)** 篇")
for filename, source, target, action in to_obsidian:
action_text = "新增" if action == 'new' else "更新"
target_path = self.published_folder / category / filename
if self.sync_file(source, target_path, action):
print(f" ✅ **action_text**: **filename**")
stats['to_obsidian'] += 1
else:
stats['failed'] += 1
return stats
命令行接口
# 查看同步状态
python sync.py --status
# 双向同步
python sync.py
python sync.py --direction both
# 单向同步
python sync.py --direction to_hugo
python sync.py --direction to_obsidian
# 预览模式
python sync.py --dry-run
# 交互式
python sync.py -i
使用效果
初次同步:从 Hugo 同步到 Obsidian
我的 Hugo Blog 中已有 37 篇文章,首次运行同步脚本:
$ python sync.py --direction to_obsidian
============================================================
Obsidian ↔ Hugo Blog 双向同步工具
============================================================
同步方向: Hugo → Obsidian
📂 同步分类: tech
------------------------------------------------------------
Obsidian: 0 篇文章
Hugo Blog: 20 篇文章
📥 需要同步到 Obsidian: 20 篇
✅ 新增: ai-agent-seo-optimization.md
✅ 新增: tailscale-derp-guide.md
✅ 新增: github-to-cloudflare-pages.md
... (共 20 篇)
📂 同步分类: read
------------------------------------------------------------
Obsidian: 0 篇文章
Hugo Blog: 5 篇文章
📥 需要同步到 Obsidian: 5 篇
✅ 新增: Getting things done.md
... (共 5 篇)
============================================================
✅ 同步完成!
============================================================
📊 同步统计:
📤 Obsidian → Hugo: 0 篇
📥 Hugo → Obsidian: 37 篇
总计: 37 篇文章已同步
日常使用:双向同步
场景 1:在 Hugo 中快速修正错误
发现文章有个错别字,通过 GitHub Web 快速修改:
# 同步回 Obsidian
$ python sync.py --direction to_obsidian
📂 同步分类: tech
📥 需要同步到 Obsidian: 1 篇
✅ 更新: obsidian-to-hugo-automation.md
场景 2:定期双向同步
每周运行一次,确保两边始终一致:
# 双向同步
$ python sync.py
📂 同步分类: tech
Obsidian: 20 篇文章
Hugo Blog: 21 篇文章
📥 需要同步到 Obsidian: 1 篇
✅ 新增: new-article.md
📂 同步分类: read
Obsidian: 6 篇文章
Hugo Blog: 5 篇文章
📤 需要同步到 Hugo: 1 篇
✅ 新增: new-book-review.md
性能数据(v2.0)
| 指标 | 数据 |
|---|---|
| 同步 37 篇文章 | ~2 秒 |
| 状态查看 | ~0.5 秒 |
| 单个文件同步 | ~10ms |
| 内存占用 | ~15MB |
第三步:功能完善(v3.0)
在基础功能完善后,我又根据实际使用需求,添加了几个重要功能。
1. Unsplash 封面图集成
问题: 许多技术文章没有配图,显得单调
解决方案: 集成 Unsplash API,自动为无图片文章添加高质量封面图
实现思路
- 检测文章是否包含图片
- 如果无图片,根据标题/标签生成搜索关键词
- 调用 Unsplash API 搜索相关图片
- 展示 10 张图片供用户选择
- 下载选中图片到
static/**category**/cover.jpg - 在 Front Matter 添加
image字段 - 在文章末尾添加摄影师署名(符合 Unsplash 使用条款)
配置
编辑 publish_config.yaml:
unsplash:
access_key: "YOUR_ACCESS_KEY_HERE"
enabled: true
使用效果
$ python publish.py
💡 文章中没有图片,可以添加 Unsplash 封面图
🔍 正在 Unsplash 搜索封面图: "programming"...
✅ 找到 10 张图片
📸 可选图片:
[1] Modern workspace with laptop - by John Doe
[2] Abstract technology background - by Jane Smith
[3] Code on screen - by Developer X
...
选择图片 (1-10, 0=跳过): 1
✅ 封面图已下载: blog/static/tech/cover.jpg
📝 已添加摄影师署名
搜索关键词策略
智能生成,优先级:
- 标签中的英文词 → 如
Python,Docker - 标题中的英文词 → 如
Guide,Tutorial - 分类默认词 →
tech→"technology programming"
2. 敏感信息安全检查
问题: 技术文章中可能包含敏感信息(API Keys、公司名称、IP 地址等)
解决方案: 提供自动化的敏感信息检查脚本
检查清单
- ✓ 搜索 "key"、"token"、"password" - 检查 API keys
- ✓ 搜索 "BASF"、"巴斯夫" - 检查公司名称
- ✓ 搜索 "license" - 检查许可证信息
- ✓ 检查 IP 地址(192.168.x.x)
- ✓ 检查代码块中的配置文件
- ✓ 检查截图中的敏感信息
使用方式
# 发布前自动检查
./check_sensitive.sh
脱敏方法
- API Key:
sk-xxxxx→sk-***[已隐藏]*** - 公司名:
BASF上海研发中心→某化工企业研发中心 - 服务器:
192.168.1.100→192.168.x.x
3. 非交互式发布支持
问题: 交互式发布不适合批量操作和脚本集成
解决方案: 支持完整的命令行参数
使用方式
python publish.py \
-f "ready/article.md" \
-t "文章标题" \
-c tech \
--tags "标签1,标签2,标签3" \
-s "article-slug" \
-d "文章描述(100-160字符)"
可用参数:
-f, --file: 文章路径(必需)-t, --title: 标题-c, --category: 分类(tech/read/life/project)--tags: 标签(逗号分隔)-s, --slug: 英文 slug(可选,自动生成)-d, --description: SEO 描述-y, --non-interactive: 非交互模式(使用默认值)
自动生成特性:
- 自动生成带日期前缀的文件名(
2025-11-14_slug.md) - 自动生成英文 slug(基于标题智能转换)
- 自动生成 lastmod 字段(当前日期)
第三部分:Agent Skills 封装(★核心创新★)
有了底层的 Python 脚本后,最重要的一步就是将其封装为 Claude Agent Skill,实现真正的"一键发布"。
设计思路:从命令行到自然语言
传统方式的问题
- 需要记忆多个命令和参数
- 需要按顺序执行多个步骤
- 容易遗漏某个步骤(比如忘记运行 sync.py)
- 出错时需要手动诊断和修复
Agent Skills 的目标
- 用户只需要说"帮我发布文章"
- Claude 自动理解用户意图
- 自动执行所有必要步骤
- 智能处理错误和边界情况
- 给出清晰的反馈
Skill 核心能力
一个完整的发布 Skill 需要具备:
1. 理解意图
- "发布这篇文章" → 发布当前打开的文件
- "发布 ready 目录下最新的文章" → 扫描并选择最新文章
- "发布关于 XX 的文章" → 根据标题搜索匹配
2. 自动化流程
- 安全检查(敏感信息扫描)
- 文章发布(调用 publish.py)
- 双向同步(调用 sync.py)
- 预览/部署(可选)
3. 智能决策
- 自动从文章内容提取元数据
- 智能生成标题、描述、标签
- 自动选择合适的分类
- 处理图片引用
4. 错误处理
- 检测并修复常见问题
- 给出清晰的错误提示
- 提供解决建议
Skill 文件结构
我的 obsidian-hugo-publisher Skill 文件结构(所有文件集中在一起,方便移植):
~/.claude/
└── skills/
└── obsidian-hugo-publisher/
├── SKILL.md # 主 Skill 定义
├── WORKFLOW.md # 发布工作流
├── COMMANDS.md # 命令参考
├── publish.py # 发布脚本
├── sync.py # 同步脚本
├── unsplash_cover.py # Unsplash 封面图模块
├── publish_config.yaml # 配置文件
└── check_sensitive.sh # 敏感信息检查脚本
重要说明:
- 📍 Skills 应该放在用户主目录
~/.claude/skills/而不是项目目录 - 📍 这是 Claude Code 的全局 Skills 目录
- 📍 所有项目都可以共享使用这些 Skills
设计优势:
- ✅ 所有相关文件集中在一个目录
- ✅ 一键复制即可移植到其他项目
- ✅ 便于版本管理和分享
- ✅ 全局可用,任何项目都能调用
Skill Prompt 核心内容
完整发布流程定义
## 当用户请求发布文章时
### 步骤 1:理解用户意图
- 如果用户指定了文件,使用该文件
- 如果用户说"这篇文章",使用当前打开的文件
- 如果没有指定,扫描 ready/ 目录并让用户选择
### 步骤 2:安全检查(必须执行)
- 运行:`.claude/skills/obsidian-hugo-publisher/check_sensitive.sh`
- 检查是否包含敏感信息:
- API keys, tokens, passwords
- 公司名称(BASF、巴斯夫)
- IP 地址、服务器信息
- 如果发现敏感信息,**停止发布**并提示用户
### 步骤 3:提取或询问元数据
- 尝试从文章内容自动提取:
- 标题(从 YAML Front Matter 或第一个 # 标题)
- 现有标签
- 分类(从文件路径或 project 字段推断)
- 如果缺失,智能询问用户:
- 标题(必需)
- 分类:tech/read/life/project
- 标签(逗号分隔)
- SEO 描述
### 步骤 4:执行发布
运行发布命令(使用 skill 目录下的脚本):
```bash
python .claude/skills/obsidian-hugo-publisher/publish.py \
-f "ready/article.md" \
-t "文章标题" \
-c tech \
--tags "标签1,标签2,标签3" \
-d "文章描述"
步骤 5:同步到 Obsidian(关键)
发布后必须运行同步:
python .claude/skills/obsidian-hugo-publisher/sync.py
步骤 6:询问用户下一步
- 是否需要本地预览? →
cd blog && hugo server -D - 是否需要部署? →
cd blog && hugo && git add . && git commit && git push
### 使用体验演示
#### 场景 1:发布指定文章
我:帮我发布 ready 目录下关于 Obsidian Hugo 的文章
Claude: 🔍 正在扫描 ready 目录... 找到文章:2025-11-14_obsidian-hugo-complete-publishing-system.md
📖 正在读取文章内容... 已提取标题:用 Claude Agent Skills 实现博客一键发布 已识别分类:tech 已提取标签:Claude, Agent Skills, AI, 自动化
🔒 正在检查敏感信息... ✅ 未发现敏感信息
请补充 SEO 描述(100-160字符):
借助 Claude Agent Skills 将复杂发布流程封装为一句话交互
📤 正在发布文章... ✅ 文章已发布到:blog/content/posts/tech/2025-11-14_obsidian-hugo-claude-agent-skills.md
🔄 正在同步到 Obsidian... ✅ 已同步到:Main/07 blog idea/published/tech/
🎉 发布完成!
下一步选项:
- 本地预览(hugo server -D)
- 构建并部署(hugo && git push)
- 完成
请选择:
#### 场景 2:一句话完整发布
我:帮我发布文章并部署
Claude: [执行完整流程...] ✅ 已检查敏感信息 ✅ 已发布文章 ✅ 已同步到 Obsidian ✅ 已构建站点 ✅ 已部署到 GitHub
🎉 文章已上线!
#### 场景 3:智能错误处理
我:帮我发布文章
Claude: 🔍 正在扫描 ready 目录... 找到文章:sensitive-article.md
🔒 正在检查敏感信息... ⚠️ 发现敏感信息!
问题 1:第 42 行包含可能的 API Key sk-proj-xxxxxxxxxxxxx
问题 2:第 156 行包含公司名称 BASF上海研发中心
❌ 发布已停止
建议修复:
- 将 API Key 替换为:sk-[已隐藏]
- 将公司名称替换为:某化工企业研发中心
修复后重新运行发布。
### 对比:传统方式 vs Agent Skills
| 维度 | 传统 Python 脚本 | Agent Skills |
| -------------- | --------------------------------- | ----------------------- |
| **触发方式** | `python publish.py -f ... -t ...` | "帮我发布文章" |
| **参数记忆** | 需要记忆所有参数 | 无需记忆,自然对话 |
| **元数据输入** | 必须在命令行指定 | 智能提取 + 交互式补充 |
| **错误处理** | 手动查看错误信息并修复 | 自动检测 + 给出修复建议 |
| **流程遗漏** | 容易忘记某个步骤(如 sync) | 自动执行完整流程 |
| **灵活性** | 固定参数 | 理解多种表述方式 |
| **学习成本** | 需要学习命令行参数 | **零学习成本** |
| **体验** | 工具化、机械 | 对话式、自然 |
### 技术实现细节
#### Skill 如何调用 Python 脚本
Claude Agent 通过 `Bash` 工具执行系统命令:
- 使用 Bash 工具运行 `python .claude/skills/obsidian-hugo-publisher/publish.py` 并传递参数
- 解析命令输出,提取成功/失败信息
- 如果失败,分析错误信息并给出建议
**路径设计:** 所有脚本都放在 skill 目录下,确保:
- ✅ 便于移植(只需复制一个文件夹)
- ✅ 便于版本管理
- ✅ 避免路径混乱
#### Skill 如何智能提取元数据
1. 使用 Read 工具读取文章内容
2. 使用正则表达式解析 YAML Front Matter
3. 提取第一个 # 标题作为备选标题
4. 分析内容关键词,推荐相关标签
5. 提取第一段作为描述候选
#### Skill 如何处理用户输入
Claude 的自然语言理解能力:
- "发布这篇文章" → 使用当前打开的文件
- "发布 XXX 文章" → 在 ready/ 目录搜索标题匹配的文件
- "发布最新的文章" → 按修改时间排序,选择最新的
- "发布关于 AI 的文章" → 搜索标题/内容包含 "AI" 的文章
---
## 完整使用指南
### 方式 1:使用 Agent Skills(★推荐★)
这是最简单的方式,只需要和 Claude 对话即可完成整个发布流程。
#### 最简单的发布
我:帮我发布文章
Claude 会:
1. 扫描 ready 目录
2. 让你选择要发布的文章
3. 自动检查敏感信息
4. 智能提取元数据(标题、标签等)
5. 询问缺失的信息(分类、描述)
6. 执行发布和同步
7. 询问是否需要预览或部署
**全程只需要回答几个简单问题,其他都自动完成!**
#### 指定文章发布
我:帮我发布 ready 目录下关于 AI 的文章
Claude 会自动搜索标题匹配的文章并发布。
#### 完整发布并部署
我:帮我发布文章并部署到线上
Claude 会执行完整流程:发布 → 同步 → 构建 → 部署,全自动。
**对比:**
- **Agent Skills 方式**:1 句话,3-5 次交互,零学习成本
- **传统方式**:5 个命令,需要记忆所有参数
---
### 方式 2:使用 Python 脚本(传统方式)
如果不想使用 Agent Skills,也可以直接运行 Python 脚本:
#### 1️⃣ 安全检查
```bash
./check_sensitive.sh
2️⃣ 发布文章
python publish.py \
-f "ready/article.md" \
-t "文章标题" \
-c tech \
--tags "标签1,标签2,标签3" \
-d "文章描述"
3️⃣ 同步
发布后必须运行同步,将文章从 blog/content/posts/ 同步到 published/:
python sync.py
同步关系:
blog/content/posts/→published/(主要方向)published/是从 blog 同步的备份
5️⃣ 本地预览
cd blog
hugo server -D
访问 http://localhost:1313 检查文章效果。
6️⃣ 构建和部署
cd blog
hugo
git add .
git commit -m "发布新文章:[文章标题]"
git push origin main
命令速查表
发布相关
# 发布单篇文章(交互式)
python publish.py
# 查看待发布队列
ls -lh "Main/07 blog idea/ready/"
# 查看已发布文章
ls -lh "Main/07 blog idea/published/"
同步相关
# 双向同步(推荐,发布后必须运行)
python sync.py
# 查看同步状态(不执行同步)
python sync.py --status
# 单向同步:Hugo → Obsidian
python sync.py --direction to_obsidian
# 单向同步:Obsidian → Hugo
python sync.py --direction to_hugo
# 交互式同步
python sync.py --interactive
# 预览模式(查看会同步哪些文件)
python sync.py --dry-run
预览和部署
# 本地预览(包含草稿)
cd blog && hugo server -D
# 构建静态站点
cd blog && hugo
# 提交并推送
cd blog && git add . && git commit -m "发布新文章" && git push
工作流最佳实践
✅ 应该做的
发布前检查敏感信息(重要)
./check_sensitive.sh # 自动检查敏感信息- 搜索并隐藏 API keys、license keys
- 替换公司名称(BASF → 某化工企业)
- 检查截图中的敏感信息
发布后立即同步
python publish.py # 发布 python sync.py # 同步(关键步骤)使用描述性图片文件名
- ✅
docker-setup-diagram.png - ❌
IMG_1234.png
- ✅
精心编写 SEO 元数据
- 标题:简洁,包含关键词,50-60 字符
- 描述:100-160 字符,概括核心内容
- 标签:3-5 个相关标签
保持 ready/ 队列清空
ready/是临时队列,不是长期存放处- 写完就发布
❌ 避免做的
发布前不检查敏感信息(危险)
- ⚠️ 可能泄露 API keys、密码、license
- ⚠️ 可能暴露公司内部信息(BASF等)
- ⚠️ 可能违反保密协议
- 始终运行
./check_sensitive.sh进行检查
发布后不运行 sync.py
published/不会自动更新- 必须通过 sync.py 同步
不要将文章长期存放在 ready/ 中
- 在
draft/中修改 - 完成后移动到
ready/
- 在
不要使用中文图片文件名
- 可能导致 URL 编码问题
完整工作流示例
场景 1:发布一篇新文章
# 1. 将文章从 draft/ 移动到 ready/
mv "Main/07 blog idea/draft/my-article.md" "Main/07 blog idea/ready/"
# 2. 安全检查(必做)
./check_sensitive.sh
# 3. 发布文章
python publish.py
# 选择文章,填写元数据...
# 4. 同步到 Obsidian(重要)
python sync.py
# 5. 预览
cd blog && hugo server -D
# 6. 部署
cd blog
hugo
git add .
git commit -m "发布新文章:我的文章标题"
git push
场景 2:在 Hugo 中修改了文章
如果在 blog/content/posts/ 中直接编辑了文章,同步回 Obsidian:
python sync.py --direction to_obsidian
场景 3:检查发布状态
# 查看待发布队列
ls -lh "Main/07 blog idea/ready/"
# 查看同步状态
python sync.py --status
# 查看 Hugo 文章
ls -lh "blog/content/posts/tech/"
技术总结
技术亮点
1. Front Matter 智能转换
挑战: YAML 和 TOML 的类型系统差异 解决: 严格遵循 TOML 规范,特别注意引号、数组、布尔值格式
2. 图片文件名处理
挑战: 文件名中的空格和特殊字符 解决: 使用 re.escape() 转义特殊字符,确保正则匹配准确
3. 时间戳精度处理
挑战: 不同文件系统的时间戳精度不同 解决: 使用 1 秒容差,避免误判
time_diff = abs(obs_mtime - hugo_mtime)
if time_diff > 1: # 只有差异大于 1 秒才认为有修改
# 进行同步
4. 目录自动创建
挑战: 目标目录可能不存在 解决: 使用 mkdir(parents=True, exist_ok=True)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
5. 元数据保留
挑战: 保持文件修改时间一致 解决: 使用 shutil.copy2() 而不是 shutil.copy()
shutil.copy2(source, target) # 保留修改时间等元数据
完整性能数据
| 功能 | 手动操作 | 自动化工具 | 提升 |
|---|---|---|---|
| 发布单篇文章 | 10-15 分钟 | 1-2 分钟 | 83% |
| 同步 37 篇文章 | N/A(手动不现实) | ~2 秒 | ∞ |
| 图片处理 | 手动逐个复制 | 自动批量 | 100% |
| Front Matter | 手动编写 | 自动生成 | 100% |
| 错误率 | ~20% | <5% | 75% |
投资回报率
开发总耗时: 约 12 小时
- v1.0 自动化发布:6 小时
- v2.0 双向同步:4 小时
- v3.0 功能完善:2 小时
回报计算:
- 假设每月发布 4 篇文章
- 每篇节省 10 分钟
- 每月节省 40 分钟
- 18 个月即可回本(12 小时 ÷ 40 分钟/月)
隐性收益:
- 心理负担减轻,发布积极性提升
- 发布频率从每月 2 篇提升至每月 4-5 篇
- 内容管理更规范,两边始终保持一致
- 降低了错误率,提升了内容质量
未来展望
潜在改进方向
1. 冲突检测与解决
目前的版本只是简单地以修改时间为准,未来可以实现:
- 三方合并:类似 Git 的合并策略
- 手动选择:检测到冲突时,让用户手动选择保留哪一方
- diff 显示:显示两边的差异,辅助决策
2. 增量同步
目前每次都会扫描所有文件,可以优化为:
- 记录同步历史:使用 SQLite 数据库记录上次同步时间
- 只检查修改过的文件:减少文件系统操作
- 哈希校验:使用文件哈希而不是时间戳判断是否修改
3. 实时同步
使用文件监控实现实时同步:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class SyncHandler(FileSystemEventHandler):
def on_modified(self, event):
# 自动触发同步
pass
4. Obsidian 插件集成
使用 Obsidian Shell Commands 插件,设置快捷键一键发布:
# 命令配置
cd /Users/huashan/Documents/Obsidian && python publish.py
# 绑定快捷键
Cmd+Shift+P
效果:在 Obsidian 中编辑文章时,按快捷键即可触发发布流程。
5. 图片压缩
集成 Pillow 库,自动压缩图片:
from PIL import Image
def compress_image(source_path: Path, target_path: Path, quality: int = 85):
"""压缩图片"""
with Image.open(source_path) as img:
# 转换 RGBA 为 RGB(PNG → JPG)
if img.mode == 'RGBA':
img = img.convert('RGB')
# 压缩并保存
img.save(target_path, optimize=True, quality=quality)
收益: 减少图片体积 50-70%,提升博客加载速度。
6. AI 辅助元数据生成
使用 AI 自动生成:
- SEO 友好的文章描述
- 相关标签推荐
- 英文 slug 智能生成
- 图片 alt 文本生成(提升 SEO 和可访问性)
总结
三层架构:从底层到交互
这个项目展示了一个完整的自动化发布系统的三层架构:
第一层:底层工具(Python 脚本)
✅ Front Matter 转换(YAML → TOML) ✅ 图片提取与处理 ✅ 文件名标准化 ✅ 链接转换 ✅ 双向同步 ✅ 安全检查 ✅ 封面图集成
价值:将手动流程自动化
第二层:命令行接口
✅ 参数化发布命令 ✅ 灵活的配置系统 ✅ 友好的交互式 CLI ✅ 详细的进度反馈
价值:提供可编程的接口
第三层:Agent Skills(★核心创新★)
✅ 一句话触发完整流程 ✅ 智能理解用户意图 ✅ 自动提取和推断元数据 ✅ 智能错误处理和建议 ✅ 零学习成本
价值:将工具变成对话伙伴
效率提升对比
| 方案 | 耗时 | 步骤数 | 学习成本 | 错误率 |
|---|---|---|---|---|
| 手动操作 | 10-15 分钟 | 10+ 个步骤 | 中 | ~20% |
| Python 脚本 | 2-3 分钟 | 5 个命令 | 高 | ~5% |
| Agent Skills | 1-2 分钟 | 1 句话 | 零 | <3% |
关键洞察:AI Agent 的真正价值
不仅仅是自动化,更是交互方式的革命:
从命令到对话
- 传统:需要记忆命令、参数、顺序
- Agent:用人话说需求即可
从被动到主动
- 传统:工具等待指令
- Agent:理解意图、主动询问、智能决策
从工具到伙伴
- 传统:你需要懂工具的语言
- Agent:工具懂你的语言
从降低门槛到消除门槛
- 传统:简化操作流程
- Agent:直接说出想法
最终成果
效率提升:
- 发布耗时:10-15 分钟 → 1 句话(90%+ 提升)
- 同步 37 篇文章:手动不现实 → 2 秒(无限提升)
- 错误率:20% → <3%(85% 降低)
- 学习成本:需要学习命令行 → 零学习成本
体验提升:
- 心理负担几乎为零,想发就发
- 发布频率显著提升
- 内容管理更规范
- 创作流程完全不被打断
这就是 AI Agent 的真正价值:不仅是节省时间,更是让人机交互回归自然,让技术真正为人服务。
延伸思考
Claude Agent Skills 的应用场景
这个发布系统只是一个例子。Agent Skills 可以应用到更多场景:
开发工作流
- "帮我创建一个新的 React 组件"
- "运行测试并修复所有失败的用例"
- "重构这个函数,提升可读性"
数据处理
- "分析这份 CSV 文件,生成可视化报告"
- "从这些日志中提取错误信息"
- "合并这两个数据库的数据"
内容管理
- "整理我的笔记,按主题分类"
- "生成本周的工作总结"
- "将这篇文章翻译成英文"
运维自动化
- "检查服务器状态并生成报告"
- "备份所有数据库"
- "部署最新版本到生产环境"
设计 Agent Skills 的最佳实践
- 清晰的职责边界:一个 Skill 应该专注于一个明确的任务域
- 完整的错误处理:预见并处理所有可能的异常情况
- 智能的默认值:减少用户输入,提升体验
- 友好的反馈:让用户清楚知道 Agent 在做什么
- 可组合性:Skill 可以调用其他 Skill 或工具
- 便于移植:将所有相关文件(脚本、配置、文档)集中在一个目录
- ✅ 一键复制即可使用
- ✅ 便于版本管理和分享
- ✅ 降低使用门槛
AI 工作流的未来
从这个项目可以看到:
- AI 不是替代工具,而是增强工具
- 最好的 AI Agent 是隐形的:用户感觉不到在"用工具",而是在"完成任务"
- 自然语言是未来的 API:描述需求比调用接口更自然
项目文件
完整文件结构
所有文件集中在一个 skill 目录,便于移植和分享:
~/.claude/
└── skills/
└── obsidian-hugo-publisher/
├── SKILL.md # 主 Skill 定义
├── WORKFLOW.md # 发布工作流
├── COMMANDS.md # 命令参考
├── publish.py # 发布脚本
├── sync.py # 同步脚本
├── unsplash_cover.py # Unsplash 封面图模块
├── publish_config.yaml # 配置文件
└── check_sensitive.sh # 敏感信息检查脚本
文件说明:
Skill 定义文件:
SKILL.md- Claude Agent 的核心指令文件,定义如何执行发布任务WORKFLOW.md- 详细的发布工作流程说明COMMANDS.md- 命令参考和使用指南
可执行脚本:
publish.py- Python 发布脚本,处理 Front Matter 转换、图片处理等sync.py- Python 同步脚本,实现 Obsidian 和 Hugo 的双向同步unsplash_cover.py- Unsplash 封面图模块,自动获取和下载高质量封面图check_sensitive.sh- Bash 脚本,检查敏感信息
配置文件:
publish_config.yaml- 配置文件,包含路径、默认值、Unsplash API key 等
快速开始
方式 1:使用 Agent Skills(★推荐★)
步骤:
安装 Claude Code
- 访问 https://claude.ai/claude-code
- 下载并安装 Claude Code
复制 Skill 文件夹
# 将整个 skill 文件夹复制到你的 Obsidian 根目录 cp -r obsidian-hugo-publisher ~/.claude/skills/ # 或者直接 git clone 到 .claude/skills/ 目录配置 publish_config.yaml
paths: obsidian_root: "/Users/你的用户名/Documents/Obsidian" blog_root: "/Users/你的用户名/blog" ready_folder: "Main/07 blog idea/ready" published_folder: "Main/07 blog idea/published" unsplash: access_key: "你的_UNSPLASH_ACCESS_KEY" enabled: true开始使用
- 在 Claude Code 中打开你的 Obsidian 目录
- 直接说:"帮我发布文章"
- 就这么简单!✨
优势:
- ✅ 一键复制,即刻可用
- ✅ 所有依赖文件都在一起
- ✅ 便于更新和维护
- ✅ 零学习成本
方式 2:使用 Python 脚本(传统方式)
如果不想使用 Agent Skills,也可以直接运行 Python 脚本:
安装依赖
pip install pyyaml requests pillow配置文件 编辑
.claude/skills/obsidian-hugo-publisher/publish_config.yaml运行脚本
python .claude/skills/obsidian-hugo-publisher/publish.py
移植到其他项目
只需要 3 步:
复制整个 skill 文件夹
cp -r .claude/skills/obsidian-hugo-publisher /path/to/new/project/.claude/skills/修改配置文件 更新
publish_config.yaml中的路径开始使用 在新项目中对 Claude 说"帮我发布文章"
就是这么简单! 所有相关文件都在一个文件夹里,真正做到"一次配置,到处使用"。
延伸阅读
Claude Agent Skills 相关
自动化工具开发
- [github-to-cloudflare-pages](/从 GitHub Pages 到 Cloudflare Pages 的迁移实践)
- tailscale-derp-guide
技术文档
更新日志:
- 2025-11-14:v1.0 完成 Python 自动化发布工具开发
- 2025-11-14:v2.0 完成双向同步功能开发
- 2025-11-14:v3.0 添加 Unsplash 封面图、敏感信息检查、非交互式发布
- 2025-11-14:v4.0 封装为 Claude Agent Skills,实现一键发布
- 2025-11-14:发布完整技术分享文章

关键词:Claude Agent Skills, AI Agent, 自动化工作流, Obsidian, Hugo, 自然语言交互, 博客发布
Photo by Juan Encalada on Unsplash
相关推荐
AI 时代的 SEO 优化 - 从搜索引擎到 AI 代理
AI 正在改变信息获取方式。本文介绍如何针对 AI 代理(ChatGPT、Perplexity)优化博客,包括 Schema.org 结构化数据、FAQ 标记和 robots.txt 配置。
双向链接系统演示 - Backlinks
演示 Hugo 博客中实现的双向链接功能,灵感来自 Obsidian 和 Roam Research 的知识管理方式。
爱心浇水系统 - AI 再利用环保小发明
和晞晞一起设计的环保浇水装置,将 AI 技术与废物利用结合,解决出差期间植物无人浇水的难题。