第 12 章:Skill 与扩展体系
前面几章我们学了工具(MCP)、钩子、权限、多 Agent……这些都是在 Python 代码里 定义的扩展。 但 Claude Code 还有一套 文件驱动 的扩展体系——Slash Commands、Skills、Plugins—— 用 Markdown 文件就能给 Claude 加功能,不用写一行 Python。 本章教你怎么通过 SDK 把这些扩展集成进你的项目。
1. 扩展体系概览
Claude Code 的扩展体系分四种,先看全景图:
| 类型 | 定义位置 | 用途 | SDK 加载方式 |
|---|---|---|---|
| Slash Commands | .claude/commands/*.md |
可重用的提示词模板,类似快捷指令 | setting_sources |
| Skills | .claude/skills/*/SKILL.md |
专业知识 + 脚本,给 Claude 特定领域的能力 | setting_sources |
| Plugins | plugin-dir/.claude-plugin/ |
把 Commands + Skills 打包分发 | plugins |
| Agents | 代码中 AgentDefinition 或 .claude/agents/*.md |
专业子代理,处理特定任务 | agents 参数 |
一句话总结:Commands 是"快捷指令",Skills 是"专业技能",Plugins 是"技能包",Agents 是"专家团队"。
2. Slash Commands — 可重用的指令模板
2.1 什么是 Slash Commands?
Slash Commands 就是你在终端里用 / 开头触发的快捷指令。比如 /commit 自动生成提交信息,/review 自动审查代码。
它们的本质是 Markdown 模板 ——你把一段精心设计的提示词存成 .md 文件,放到 .claude/commands/ 目录下,Claude 就会自动识别并注册为可用命令。
2.2 创建你的第一个 Command
在项目根目录下创建 .claude/commands/review.md:
---
description: 审查代码变更,找出潜在问题
allowed-tools: Bash(git diff:*), Read, Glob, Grep
argument-hint: "<文件路径或留空审查全部>"
---
## 任务
审查以下代码变更,找出:
1. 潜在的 Bug
2. 安全隐患
3. 性能问题
4. 代码风格问题
## 当前变更
!`git diff HEAD`
## 目标文件
$ARGUMENTS
## 要求
- 用中文回答
- 每个问题给出具体的改进建议
- 严重程度分级:🔴 严重 / 🟡 警告 / 🟢 建议
文件格式说明:
- Front matter(
---之间的部分): description:命令描述,在/help中显示allowed-tools:这个命令允许使用的工具argument-hint:参数提示$ARGUMENTS:会被替换为用户输入的参数(如/review src/main.py中的src/main.py)!`command`:动态执行命令并把结果注入模板(如!`git diff HEAD`会被替换为实际的 diff 输出)
2.3 再来一个:翻译命令
创建 .claude/commands/translate.md:
---
description: 将文本翻译成指定语言
argument-hint: "<目标语言> <文本或文件路径>"
---
## 任务
将以下内容翻译成用户指定的语言。
## 翻译内容
$ARGUMENTS
## 要求
- 保持原文的格式和结构
- 技术术语保留英文,括号内标注翻译
- 代码块不翻译
- 如果输入是文件路径,先读取文件内容再翻译
2.4 在 SDK 中加载 Commands
关键配置:setting_sources。它告诉 SDK 从哪里加载扩展:
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, SystemMessage
options = ClaudeAgentOptions(
# 从项目目录加载 commands 和 skills
setting_sources=["project"],
# 工作目录——SDK 会在这里找 .claude/ 目录
cwd="/path/to/your-project",
)
| 来源 | 加载路径 | 说明 |
|---|---|---|
"user" |
~/.claude/ |
你的全局设置,所有项目共享 |
"project" |
项目目录/.claude/ |
项目级设置,团队共享 |
"local" |
项目目录/.claude-local/ |
本地设置,不提交到 Git |
可以组合使用:
# 同时加载全局和项目设置
setting_sources=["user", "project"]
# 加载所有来源
setting_sources=["user", "project", "local"]
# 不设置 = 不加载任何预定义命令/技能(默认行为)
setting_sources=None
2.5 检测已加载的 Commands
SDK 启动时会通过 SystemMessage 告诉你加载了哪些命令:
import asyncio
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, SystemMessage
async def list_commands():
options = ClaudeAgentOptions(
setting_sources=["project"],
cwd="/path/to/your-project",
)
async with ClaudeSDKClient(options) as client:
# 连接时会收到 init 消息
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
# 从 init 数据中提取已加载的命令
commands = msg.data.get("slash_commands", [])
print(f"已加载 {len(commands)} 个命令:")
for cmd in commands:
print(f" /{cmd}")
break
asyncio.run(list_commands())
3. Skills — 给 Claude 专业技能
3.1 什么是 Skills?
如果说 Commands 是"快捷指令",那 Skills 就是"专业知识库"。
Skills 让 Claude 拥有特定领域的专业能力。比如一个"代码分析"Skill 可以包含: - 代码分析的方法论和最佳实践 - 辅助脚本(Python/Bash) - 参考文档
Skills 存放在 .claude/skills/ 目录下,每个 Skill 一个子目录。
3.2 Skill 的目录结构
.claude/skills/
└── code-analyzer/
├── SKILL.md # 技能描述文件(必需)
└── scripts/ # 辅助脚本(可选)
└── analyze.py
3.3 创建一个代码分析 Skill
创建 .claude/skills/code-analyzer/SKILL.md:
---
name: code-analyzer
description: "分析 Python 代码质量,给出优化建议"
---
# 代码分析技能
## 激活触发器
当对话中提到以下关键词时,自动使用这个技能:
- "分析代码"、"代码质量"
- "代码审查"、"code review"
- "优化建议"、"重构"
## 分析维度
1. **代码复杂度**
- 函数长度(超过 50 行标记为过长)
- 嵌套深度(超过 3 层标记为过深)
- 圈复杂度(超过 10 标记为过高)
2. **命名规范**
- 变量名是否有意义(避免 a, b, tmp)
- 函数名是否表达行为(动词开头)
- 类名是否表达概念(名词)
3. **错误处理**
- 是否有裸 except
- 是否忽略了异常
- 关键操作是否有 try/except
4. **性能**
- 是否在循环中做了不必要的操作
- 是否可以用生成器替代列表
- 是否有 N+1 查询问题
## 输出格式
分析完成后,按以下格式输出:
### 代码质量评分:X/10
#### 发现的问题
| 严重程度 | 位置 | 问题描述 | 建议 |
|---------|------|---------|------|
#### 亮点
列出代码中做得好的地方。
3.4 Skill vs Command 的区别
| 对比项 | Slash Command | Skill |
|---|---|---|
| 触发方式 | 用户手动输入 /命令名 |
Claude 自动识别并激活 |
| 本质 | 提示词模板 | 领域知识库 |
| 参数 | 支持 $ARGUMENTS |
无参数,靠上下文触发 |
| 适合场景 | 重复性操作(提交、审查) | 专业领域知识(分析、诊断) |
4. Plugins — 打包与分发
4.1 什么是 Plugin?
当你积累了一堆好用的 Commands 和 Skills,想分享给团队或其他项目用?Plugin 就是 Commands + Skills 的打包格式。
4.2 Plugin 的目录结构
my-plugin/
├── .claude-plugin/
│ └── plugin.json # 插件元信息(必需)
├── commands/ # 斜杠命令(可选)
│ ├── lint.md
│ └── format.md
├── skills/ # 技能(可选)
│ └── best-practices/
│ └── SKILL.md
└── agents/ # 代理(可选)
└── reviewer.md
4.3 创建 plugin.json
{
"name": "code-quality",
"description": "代码质量工具包:lint、format、最佳实践",
"version": "1.0.0",
"author": {
"name": "Your Name"
}
}
4.4 在 SDK 中加载插件
通过 plugins 配置加载:
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
plugins=[
{
"type": "local",
"path": str(Path(__file__).parent / "my-plugin"),
}
],
)
可以同时加载多个插件:
options = ClaudeAgentOptions(
plugins=[
{"type": "local", "path": "/path/to/code-quality-plugin"},
{"type": "local", "path": "/path/to/doc-helper-plugin"},
],
# 同时加载项目自己的 commands 和 skills
setting_sources=["project"],
)
4.5 检测已加载的插件
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
plugins = msg.data.get("plugins", [])
for p in plugins:
print(f" 插件: {p.get('name')} (路径: {p.get('path')})")
5. 实战:为 MiniClaw 集成 Skill 系统
我们来给前几章构建的 MiniClaw 项目加上 Skill 扩展能力。
5.1 创建项目目录结构
在 MiniClaw 项目中创建:
miniclaw/
├── .claude/
│ ├── commands/
│ │ ├── review.md # /review 命令
│ │ └── explain.md # /explain 命令
│ └── skills/
│ └── python-expert/
│ └── SKILL.md # Python 专家技能
├── engine.py
├── cli.py
└── ...
5.2 修改引擎配置
在 engine.py 中启用 setting_sources:
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
class ChatEngine:
def __init__(self, project_dir: str = "."):
self.options = ClaudeAgentOptions(
# 加载项目中定义的 commands 和 skills
setting_sources=["project"],
# 设置工作目录为项目根目录
cwd=project_dir,
# 其他配置...
max_turns=10,
)
5.3 在 CLI 中展示可用命令
async def show_available_commands(client: ClaudeSDKClient):
"""显示所有可用的斜杠命令(包括自定义的)"""
info = await client.get_server_info()
if info:
commands = info.get("slash_commands", [])
if commands:
print("可用的斜杠命令:")
for cmd in commands:
print(f" /{cmd}")
5.4 完整集成示例
import asyncio
from pathlib import Path
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
SystemMessage,
TextBlock,
)
async def main():
project_dir = Path(__file__).parent
options = ClaudeAgentOptions(
setting_sources=["project"],
cwd=str(project_dir),
max_turns=10,
)
print("MiniClaw + Skill 系统")
print("=" * 50)
async with ClaudeSDKClient(options) as client:
# 打印已加载的扩展信息
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
commands = msg.data.get("slash_commands", [])
if commands:
print(f"\n已加载 {len(commands)} 个斜杠命令:")
for cmd in commands:
print(f" /{cmd}")
break
# 交互式聊天
while True:
user_input = input("\n你: ").strip()
if user_input in ("/quit", "/exit", ""):
print("再见!")
break
await client.query(user_input)
print("Claude: ", end="", flush=True)
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(block.text, end="", flush=True)
elif isinstance(msg, ResultMessage):
print()
print(f" [{msg.duration_ms}ms | ${msg.total_cost_usd or 0:.4f}]")
asyncio.run(main())
6. 最佳实践
Commands 设计原则
- 单一职责:每个命令只做一件事
- 参数灵活:用
$ARGUMENTS支持可选参数 - 动态上下文:用
!`command`注入当前状态 - 限制工具:用
allowed-tools约束命令的能力范围
Skills 设计原则
- 明确的触发条件:在 SKILL.md 中列出激活关键词
- 结构化知识:用 Markdown 标题和列表组织知识
- 带示例:给 Claude 看具体的分析示例
- 附带脚本:复杂操作用 Python 脚本辅助
setting_sources 使用建议
| 场景 | 推荐配置 |
|---|---|
| 个人学习 | ["user"] |
| 团队项目 | ["user", "project"] |
| CI/CD | ["project"](只加载项目级配置) |
| 完全隔离 | None(默认,不加载任何预定义扩展) |
本章完整示例代码
以下是本章涉及的完整示例文件,可以直接复制运行。
skill_loader.py — 加载并展示可用扩展
import asyncio
from pathlib import Path
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
SystemMessage,
TextBlock,
)
async def main():
"""演示如何通过 setting_sources 加载并展示可用的 Commands 和 Skills。"""
# 指向包含 .claude/ 目录的示例项目
sample_project = Path(__file__).parent / "sample-project"
options = ClaudeAgentOptions(
# 加载项目级设置(commands + skills)
setting_sources=["project"],
# 工作目录指向示例项目
cwd=str(sample_project),
max_turns=1,
)
print("=" * 50)
print("Skill 系统加载器")
print("=" * 50)
async with ClaudeSDKClient(options) as client:
# 读取初始化消息,获取已加载的扩展信息
async for msg in client.receive_response():
if isinstance(msg, SystemMessage):
if msg.subtype == "init":
print(f"\n初始化完成!")
# 打印已加载的斜杠命令
commands = msg.data.get("slash_commands", [])
print(f"\n斜杠命令 ({len(commands)} 个):")
if commands:
for cmd in commands:
print(f" /{cmd}")
else:
print(" (无)")
# 打印已加载的插件
plugins = msg.data.get("plugins", [])
print(f"\n插件 ({len(plugins)} 个):")
if plugins:
for p in plugins:
print(f" - {p.get('name', '未知')}")
else:
print(" (无)")
break
# 测试:让 Claude 使用已加载的技能
print("\n" + "-" * 50)
print("测试:让 Claude 分析一段代码")
print("-" * 50)
await client.query("请分析这段代码的质量:\n\ndef f(x):\n if x>0:\n if x>10:\n if x>100:\n return 'big'\n return 'medium'\n return 'small'\n return 'negative'\n")
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"\nClaude: {block.text}")
elif isinstance(msg, ResultMessage):
print(f"\n[耗时: {msg.duration_ms}ms]")
if __name__ == "__main__":
asyncio.run(main())
运行方式:
python examples/skill_loader.py
plugin_demo.py — 创建和加载插件
import asyncio
import json
import tempfile
from pathlib import Path
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
SystemMessage,
TextBlock,
)
def create_demo_plugin(plugin_dir: Path) -> None:
"""在指定目录创建一个演示插件。"""
# 1. 创建插件元信息
plugin_meta_dir = plugin_dir / ".claude-plugin"
plugin_meta_dir.mkdir(parents=True, exist_ok=True)
plugin_json = {
"name": "demo-helper",
"description": "演示插件:包含一个 /greet 命令",
"version": "1.0.0",
"author": {"name": "Tutorial"},
}
(plugin_meta_dir / "plugin.json").write_text(
json.dumps(plugin_json, indent=2, ensure_ascii=False),
encoding="utf-8",
)
# 2. 创建一个斜杠命令
commands_dir = plugin_dir / "commands"
commands_dir.mkdir(exist_ok=True)
greet_md = """\
---
description: 用指定的风格打招呼
argument-hint: "<风格:正式/搞笑/诗意>"
---
## 任务
请用 $ARGUMENTS 的风格,和用户打个招呼。
如果没有指定风格,就用友好的方式打招呼。
要求用中文回答。
"""
(commands_dir / "greet.md").write_text(greet_md, encoding="utf-8")
print(f"已创建演示插件: {plugin_dir}")
print(f" - 插件名: {plugin_json['name']}")
print(f" - 命令: /greet")
async def main():
"""演示如何创建和加载一个本地插件。"""
print("=" * 50)
print("Plugin 插件演示")
print("=" * 50)
# 在临时目录创建插件
with tempfile.TemporaryDirectory() as tmpdir:
plugin_dir = Path(tmpdir) / "demo-helper"
create_demo_plugin(plugin_dir)
# 通过 plugins 配置加载
options = ClaudeAgentOptions(
plugins=[
{
"type": "local",
"path": str(plugin_dir),
}
],
max_turns=1,
)
print()
async with ClaudeSDKClient(options) as client:
# 检查初始化信息
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
plugins = msg.data.get("plugins", [])
commands = msg.data.get("slash_commands", [])
print(f"已加载 {len(plugins)} 个插件, {len(commands)} 个命令")
break
# 使用插件提供的 /greet 命令
print("\n调用 /greet 命令(搞笑风格)...")
await client.query("/greet 搞笑")
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"\nClaude: {block.text}")
elif isinstance(msg, ResultMessage):
print(f"\n[耗时: {msg.duration_ms}ms]")
if __name__ == "__main__":
asyncio.run(main())
运行方式:
python examples/plugin_demo.py
小结
本章我们学习了 Claude Code 的文件驱动扩展体系:
- Slash Commands:在
.claude/commands/中用 Markdown 定义可重用的指令模板,支持参数($ARGUMENTS)和动态注入(!`command`) - Skills:在
.claude/skills/中用 SKILL.md 定义领域知识库,Claude 会根据上下文自动激活 - Plugins:把 Commands + Skills 打包成可分发的插件,通过
plugins配置加载 - setting_sources:控制从哪些来源(user/project/local)加载扩展,是连接文件系统和 SDK 的桥梁
和前面章节学到的代码扩展(MCP 工具、钩子、AgentDefinition)的关系:
| 扩展方式 | 定义方式 | 适合场景 |
|---|---|---|
| MCP 工具(第 5 章) | Python 代码 | 需要执行逻辑、访问数据 |
| 钩子(第 6 章) | Python 代码 | 拦截、审计、修改行为 |
| AgentDefinition(第 11 章) | Python 代码 | 专业子代理 |
| Slash Commands | Markdown 文件 | 可重用提示词 |
| Skills | Markdown + 脚本 | 领域知识 |
| Plugins | 目录打包 | 跨项目分享 |
它们不是互斥的,而是互补的。 一个完整的项目通常会同时使用代码扩展和文件扩展:代码扩展处理"做什么",文件扩展处理"怎么想"。
完整代码
以下是本章所有示例文件的完整可运行代码。
examples/skill_loader.py
"""Skill 系统加载器 — 展示如何加载并查看可用的 Commands 和 Skills。
通过 setting_sources 配置加载项目中定义的扩展,
然后打印可用的斜杠命令和插件信息。
运行方式:
python examples/skill_loader.py
"""
import asyncio
from pathlib import Path
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
SystemMessage,
TextBlock,
)
async def main():
"""演示如何通过 setting_sources 加载并展示可用的 Commands 和 Skills。"""
# 指向包含 .claude/ 目录的示例项目
sample_project = Path(__file__).parent / "sample-project"
options = ClaudeAgentOptions(
# 加载项目级设置(commands + skills)
setting_sources=["project"],
# 工作目录指向示例项目
cwd=str(sample_project),
max_turns=1,
)
print("=" * 50)
print("Skill 系统加载器")
print("=" * 50)
async with ClaudeSDKClient(options) as client:
# 读取初始化消息,获取已加载的扩展信息
async for msg in client.receive_response():
if isinstance(msg, SystemMessage):
if msg.subtype == "init":
print(f"\n初始化完成!")
# 打印已加载的斜杠命令
commands = msg.data.get("slash_commands", [])
print(f"\n斜杠命令 ({len(commands)} 个):")
if commands:
for cmd in commands:
print(f" /{cmd}")
else:
print(" (无)")
# 打印已加载的插件
plugins = msg.data.get("plugins", [])
print(f"\n插件 ({len(plugins)} 个):")
if plugins:
for p in plugins:
print(f" - {p.get('name', '未知')}")
else:
print(" (无)")
break
# 测试:让 Claude 使用已加载的技能
print("\n" + "-" * 50)
print("测试:让 Claude 分析一段代码")
print("-" * 50)
await client.query(
"请分析这段代码的质量:\n\n"
"def f(x):\n"
" if x>0:\n"
" if x>10:\n"
" if x>100:\n"
" return 'big'\n"
" return 'medium'\n"
" return 'small'\n"
" return 'negative'\n"
)
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"\nClaude: {block.text}")
elif isinstance(msg, ResultMessage):
print(f"\n[耗时: {msg.duration_ms}ms]")
if __name__ == "__main__":
asyncio.run(main())
examples/plugin_demo.py
"""Plugin 插件演示 — 创建和加载一个本地插件。
在临时目录中动态创建一个包含 /greet 命令的插件,
然后通过 SDK 的 plugins 配置加载并使用它。
运行方式:
python examples/plugin_demo.py
"""
import asyncio
import json
import tempfile
from pathlib import Path
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ClaudeSDKClient,
ResultMessage,
SystemMessage,
TextBlock,
)
def create_demo_plugin(plugin_dir: Path) -> None:
"""在指定目录创建一个演示插件。"""
# 1. 创建插件元信息
plugin_meta_dir = plugin_dir / ".claude-plugin"
plugin_meta_dir.mkdir(parents=True, exist_ok=True)
plugin_json = {
"name": "demo-helper",
"description": "演示插件:包含一个 /greet 命令",
"version": "1.0.0",
"author": {"name": "Tutorial"},
}
(plugin_meta_dir / "plugin.json").write_text(
json.dumps(plugin_json, indent=2, ensure_ascii=False),
encoding="utf-8",
)
# 2. 创建一个斜杠命令
commands_dir = plugin_dir / "commands"
commands_dir.mkdir(exist_ok=True)
greet_md = """\
---
description: 用指定的风格打招呼
argument-hint: "<风格:正式/搞笑/诗意>"
---
## 任务
请用 $ARGUMENTS 的风格,和用户打个招呼。
如果没有指定风格,就用友好的方式打招呼。
要求用中文回答。
"""
(commands_dir / "greet.md").write_text(greet_md, encoding="utf-8")
print(f"已创建演示插件: {plugin_dir}")
print(f" - 插件名: {plugin_json['name']}")
print(f" - 命令: /greet")
async def main():
"""演示如何创建和加载一个本地插件。"""
print("=" * 50)
print("Plugin 插件演示")
print("=" * 50)
# 在临时目录创建插件
with tempfile.TemporaryDirectory() as tmpdir:
plugin_dir = Path(tmpdir) / "demo-helper"
create_demo_plugin(plugin_dir)
# 通过 plugins 配置加载
options = ClaudeAgentOptions(
plugins=[
{
"type": "local",
"path": str(plugin_dir),
}
],
max_turns=1,
)
print()
async with ClaudeSDKClient(options) as client:
# 检查初始化信息
async for msg in client.receive_response():
if isinstance(msg, SystemMessage) and msg.subtype == "init":
plugins = msg.data.get("plugins", [])
commands = msg.data.get("slash_commands", [])
print(f"已加载 {len(plugins)} 个插件, {len(commands)} 个命令")
break
# 使用插件提供的 /greet 命令
print("\n调用 /greet 命令(搞笑风格)...")
await client.query("/greet 搞笑")
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(f"\nClaude: {block.text}")
elif isinstance(msg, ResultMessage):
print(f"\n[耗时: {msg.duration_ms}ms]")
if __name__ == "__main__":
asyncio.run(main())