0%

Claude Code 源码解析 (5):技能系统的设计哲学

Claude Code 源码解析 (5):技能系统的设计哲学

导读: 这是 Claude Code 20 个功能特性源码解析系列的第 5 篇,深入分析技能系统 (SkillTool) 的设计哲学。


📋 目录

  1. 问题引入:为什么需要技能系统?
  2. 技术原理:技能系统的核心架构
  3. 设计思想:为什么这样设计
  4. 解决方案:完整实现详解
  5. OpenClaw 最佳实践
  6. 总结

问题引入:为什么需要技能系统?

痛点场景

场景 1:重复操作繁琐

1
2
3
4
5
6
7
8
9
10
11
用户每天都要做:
1. 查看天气
2. 检查日历
3. 阅读新闻摘要

每天都要重复说:
"查看今天天气"
"检查今天日历"
"阅读新闻摘要"

→ 为什么不能一键完成?

场景 2:复杂流程难复用

1
2
3
4
5
6
7
8
9
10
11
发布博客的完整流程:
1. 创建新文章
2. 编写内容
3. 添加 Front Matter
4. Git 提交
5. 推送仓库
6. 创建 MR
7. 等待审核
8. 合并发布

每次都重新说一遍?太麻烦了!

场景 3:最佳实践难传承

1
2
3
4
5
6
7
资深开发者知道:
- 代码审查 Checklist
- 发布前验证步骤
- 故障排查流程

新人不知道这些经验
→ 如何固化并传承?

核心问题

设计 AI 助手的技能系统时,面临以下挑战:

  1. 复用性问题

    • 常用操作如何一键执行?
    • 复杂流程如何封装?
  2. 灵活性问题

    • 技能如何支持参数?
    • 如何组合多个技能?
  3. 可发现性问题

    • 用户如何知道有哪些技能?
    • 如何快速找到需要的技能?
  4. 可扩展性问题

    • 用户如何自定义技能?
    • 技能如何共享和分发?

Claude Code 用 SkillTool + SKILL.md 规范解决了这些问题。


技术原理:技能系统的核心架构

整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
┌─────────────────────────────────────────────────────────────┐
│ 用户请求 │
│ "帮我发布博客" │
└─────────────────────┬───────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ SkillTool (技能工具) │
│ - 技能发现 │
│ - 技能加载 │
│ - 参数绑定 │
│ - 执行调度 │
└─────────────┬───────────────────────────────────────────────┘


┌─────────────────────────┐
│ 技能注册表 │
│ ┌─────────────────┐ │
│ │ hexo-new │ │
│ │ hexo-publish │ │
│ │ blog-deploy │ │
│ │ morning-check │ │
│ └─────────────────┘ │
└─────────────────────────┘


┌─────────────────────────┐
│ 技能定义 (SKILL.md) │
│ - 触发条件 │
│ - 参数定义 │
│ - 执行脚本 │
│ - 输出格式 │
└─────────────────────────┘


┌─────────────────────────┐
│ 执行器 │
│ - Script 执行器 │
│ - Prompt 执行器 │
│ - API 执行器 │
└─────────────────────────┘

技能定义规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# SKILL.md - 技能定义文件

---
name: skill-name # 技能名称
description: 技能描述 # 简短说明
version: 1.0.0 # 版本号
author: author-name # 作者
---

# 技能说明

## 触发条件

- 关键词:`keyword1`, `keyword2`
- 命令:`/skill-command`
- 模式:`pattern.*`

## 参数

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| --title | string | 是 | - | 文章标题 |
| --category | string | 否 | "技术" | 文章分类 |

## 使用方式

```bash
openclaw run skill-name --title "标题" --category "分类"

执行脚本

1
2
3
4
5
// scripts/skill-name.js
module.exports = async function(params) {
// 执行逻辑
return { success: true, output: '结果' };
};

依赖技能

  • skill-dependency-1
  • skill-dependency-2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

### 技能注册表

```typescript
interface SkillRegistry {
// 技能元数据
name: string;
description: string;
version: string;
author?: string;

// 触发条件
triggers: {
keywords?: string[];
commands?: string[];
patterns?: RegExp[];
};

// 参数定义
parameters: SkillParameter[];

// 执行器
executor: {
type: 'script' | 'prompt' | 'api';
script?: string;
prompt?: string;
api?: string;
};

// 依赖
dependencies?: string[];

// 状态
enabled: boolean;
loadedAt?: Date;
}

interface SkillParameter {
name: string;
type: 'string' | 'number' | 'boolean' | 'array';
required: boolean;
default?: any;
description?: string;
choices?: string[]; // 可选值
}

技能加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class SkillLoader {
private skillDirs: string[];
private registry: Map<string, SkillRegistry>;

constructor(config: SkillLoaderConfig) {
this.skillDirs = config.skillDirs || [
'~/.openclaw/skills',
'~/.openclaw/agents/*/skills',
];
this.registry = new Map();
}

async loadAll(): Promise<void> {
for (const dir of this.skillDirs) {
await this.loadFromDir(dir);
}
}

private async loadFromDir(dir: string): Promise<void> {
const skillDirs = await this.findSkillDirs(dir);

for (const skillDir of skillDirs) {
try {
const skill = await this.loadSkill(skillDir);
this.registry.set(skill.name, skill);
console.log(`[SkillLoader] Loaded: ${skill.name}`);
} catch (error) {
console.error(`[SkillLoader] Failed to load ${skillDir}:`, error);
}
}
}

private async loadSkill(skillDir: string): Promise<SkillRegistry> {
// 1. 读取 SKILL.md
const skillMdPath = path.join(skillDir, 'SKILL.md');
const skillMdContent = await fs.promises.readFile(skillMdPath, 'utf-8');

// 2. 解析 Front Matter
const frontMatter = this.parseFrontMatter(skillMdContent);
const content = this.parseContent(skillMdContent);

// 3. 构建技能定义
const skill: SkillRegistry = {
name: frontMatter.name,
description: frontMatter.description,
version: frontMatter.version || '1.0.0',
author: frontMatter.author,

triggers: this.parseTriggers(content),
parameters: this.parseParameters(content),
executor: this.parseExecutor(content, skillDir),
dependencies: this.parseDependencies(content),

enabled: true,
loadedAt: new Date(),
};

return skill;
}

private parseFrontMatter(content: string): any {
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) {
throw new Error('Invalid SKILL.md: missing front matter');
}

return yaml.parse(match[1]);
}
}

技能执行器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
interface SkillExecutor {
execute(skill: SkillRegistry, params: Record<string, any>): Promise<SkillResult>;
}

// 脚本执行器
class ScriptExecutor implements SkillExecutor {
async execute(skill: SkillRegistry, params: Record<string, any>): Promise<SkillResult> {
const scriptPath = skill.executor.script!;

// 1. 加载脚本
const script = await import(scriptPath);

// 2. 验证参数
this.validateParams(skill.parameters, params);

// 3. 执行脚本
const result = await script.default(params);

return {
success: result.success,
output: result.output,
source: 'script',
};
}

private validateParams(parameters: SkillParameter[], params: Record<string, any>): void {
for (const param of parameters) {
if (param.required && !(param.name in params)) {
throw new Error(`Missing required parameter: ${param.name}`);
}
}
}
}

// Prompt 执行器
class PromptExecutor implements SkillExecutor {
constructor(private llm: LLMClient) {}

async execute(skill: SkillRegistry, params: Record<string, any>): Promise<SkillResult> {
const promptTemplate = skill.executor.prompt!;

// 1. 填充模板
const prompt = this.interpolate(promptTemplate, params);

// 2. 调用 LLM
const response = await this.llm.generate(prompt);

return {
success: true,
output: response.content,
source: 'prompt',
};
}

private interpolate(template: string, params: Record<string, any>): string {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
return params[key] !== undefined ? params[key] : '';
});
}
}

// API 执行器
class ApiExecutor implements SkillExecutor {
async execute(skill: SkillRegistry, params: Record<string, any>): Promise<SkillResult> {
const apiUrl = skill.executor.api!;

// 1. 构建请求
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});

// 2. 解析响应
const result = await response.json();

return {
success: result.success,
output: result.output,
source: 'api',
};
}
}

技能组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class SkillComposer {
// 组合多个技能
async compose(
name: string,
steps: SkillStep[]
): Promise<SkillRegistry> {
return {
name,
description: `组合技能:${steps.length} 个步骤`,
version: '1.0.0',

triggers: {},
parameters: this.mergeParameters(steps),

executor: {
type: 'script',
script: this.generateComposerScript(steps),
},

enabled: true,
};
}

private mergeParameters(steps: SkillStep[]): SkillParameter[] {
const allParams = new Map<string, SkillParameter>();

for (const step of steps) {
const skill = this.registry.get(step.skill);
if (skill) {
for (const param of skill.parameters) {
allParams.set(param.name, param);
}
}
}

return Array.from(allParams.values());
}

private generateComposerScript(steps: SkillStep[]): string {
const script = `
module.exports = async function(params) {
const results = [];

${steps.map((step, i) => `
// 步骤 ${i + 1}: ${step.skill}
const result${i} = await executeSkill('${step.skill}', {
${step.params ? Object.entries(step.params).map(([k, v]) => `${k}: params.${k}`).join(',\n ') : ''}
});
results.push(result${i});
`).join('\n')}

return {
success: results.every(r => r.success),
output: results.map(r => r.output).join('\\n'),
results,
};
};
`.trim();

return script;
}
}

// 使用示例
const morningRoutine = await composer.compose('morning-routine', [
{ skill: 'check-weather' },
{ skill: 'check-calendar' },
{ skill: 'read-news', params: { category: 'tech' } },
]);

技能触发器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class SkillTrigger {
private triggers: TriggerRule[] = [];

// 添加触发规则
addRule(rule: TriggerRule): void {
this.triggers.push(rule);
}

// 检查是否触发
check(input: string): SkillTriggerResult | null {
for (const rule of this.triggers) {
const match = this.matchRule(input, rule);
if (match) {
return {
skill: rule.skill,
matched: true,
params: match.params,
confidence: match.confidence,
};
}
}

return null;
}

private matchRule(input: string, rule: TriggerRule): MatchResult | null {
// 1. 关键词匹配
if (rule.keywords) {
for (const keyword of rule.keywords) {
if (input.toLowerCase().includes(keyword.toLowerCase())) {
return {
params: this.extractParams(input, rule),
confidence: 0.7,
};
}
}
}

// 2. 命令匹配
if (rule.commands) {
for (const command of rule.commands) {
if (input.startsWith(command)) {
return {
params: this.extractParams(input, rule),
confidence: 0.9,
};
}
}
}

// 3. 正则匹配
if (rule.patterns) {
for (const pattern of rule.patterns) {
const match = input.match(pattern);
if (match) {
return {
params: this.extractParamsFromMatch(match, rule),
confidence: 0.95,
};
}
}
}

return null;
}
}

interface TriggerRule {
skill: string;
keywords?: string[];
commands?: string[];
patterns?: RegExp[];
}

设计思想:为什么这样设计

思想 1:约定优于配置

问题: 技能配置太复杂,用户难以入手。

解决: SKILL.md 规范。

1
2
3
4
5
6
7
8
# 简单的技能定义
---
name: hello
description: 打招呼
---

## 使用方式
/openclaw run hello --name "John"

设计智慧:

好的规范让用户无需思考就能上手。

对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 复杂配置 (不推荐)
skill:
name: hello
description: 打招呼
version: 1.0.0
author: John
triggers:
- type: keyword
value: hello
- type: command
value: /hello
parameters:
- name: name
type: string
required: true
executor:
type: script
path: ./scripts/hello.js
permissions:
- read
timeout: 30

# 简单配置 (推荐)
---
name: hello
description: 打招呼
---

/openclaw run hello --name "John"

思想 2:技能即代码

核心理念:

技能应该像代码一样可版本控制、可复用、可组合。

实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
技能文件结构:
skills/
├── hexo-new/
│ ├── SKILL.md # 技能定义
│ ├── scripts/
│ │ └── hexo-new.js # 执行脚本
│ └── README.md # 使用说明
├── hexo-publish/
│ ├── SKILL.md
│ └── scripts/
│ └── publish.js
└── morning-check/
├── SKILL.md
└── scripts/
└── check.js

版本控制:

1
2
3
4
5
6
# 技能可以 Git 管理
cd ~/.openclaw/skills
git init
git add hexo-new/
git commit -m "Add hexo-new skill"
git push origin main

思想 3:组合优于继承

问题: 技能之间如何复用?

解决: 技能组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义基础技能
const checkWeather = {
name: 'check-weather',
executor: { type: 'api', api: 'https://api.weather.com/...' },
};

const checkCalendar = {
name: 'check-calendar',
executor: { type: 'api', api: 'https://calendar.google.com/...' },
};

// 组合为新技能
const morningCheck = await composer.compose('morning-check', [
{ skill: 'check-weather' },
{ skill: 'check-calendar' },
]);

// 使用
await executeSkill('morning-check');

设计智慧:

小组件组合成大功能,而不是写一个巨型技能。

思想 4:触发即执行

问题: 每次都要说 /run skill-name 太麻烦。

解决: 智能触发。

1
2
3
4
5
6
7
8
用户输入:"今天天气怎么样"

触发器匹配:
- 关键词:"天气" → check-weather 技能
- 置信度:0.85
- 自动执行

AI:今天北京晴,气温 25°C...

触发优先级:

1
2
3
4
1. 明确命令 (/run skill-name) → 100% 触发
2. 技能命令 (/skill-command) → 95% 触发
3. 关键词匹配 → 70-90% 触发
4. 语义匹配 → 50-70% 触发

思想 5:参数自解释

问题: 用户不知道技能需要什么参数。

解决: 参数自解释 + 自动补全。

1
2
3
4
5
6
7
8
9
interface SkillParameter {
name: string;
type: 'string' | 'number' | 'boolean';
required: boolean;
default?: any;
description?: string;
choices?: string[]; // 可选值
example?: string; // 示例
}

使用体验:

1
2
3
4
5
6
7
8
9
用户:"/openclaw run hexo-new"

AI:创建新文章需要提供:
--title <string> (必填): 文章标题
--category <string> (可选,默认"技术"): 文章分类
--tags <array> (可选): 标签列表

示例:
/openclaw run hexo-new --title "我的文章" --category "技术" --tags '["AI", "Agent"]'

解决方案:完整实现详解

SkillTool 核心实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
export class SkillTool extends Tool {
name = 'skill';
description = '管理和执行技能';

inputSchema = {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['list', 'run', 'install', 'uninstall', 'info'],
description: '操作类型',
},
name: {
type: 'string',
description: '技能名称',
},
params: {
type: 'object',
description: '技能参数',
},
},
required: ['action'],
};

async execute(input: SkillInput, context: ToolContext): Promise<ToolResult> {
switch (input.action) {
case 'list':
return this.listSkills();

case 'run':
return this.runSkill(input.name!, input.params || {}, context);

case 'install':
return this.installSkill(input.name!);

case 'uninstall':
return this.uninstallSkill(input.name!);

case 'info':
return this.getSkillInfo(input.name!);

default:
return {
success: false,
error: `Unknown action: ${input.action}`,
};
}
}

private async runSkill(
name: string,
params: Record<string, any>,
context: ToolContext
): Promise<ToolResult> {
// 1. 查找技能
const skill = this.registry.get(name);
if (!skill) {
return {
success: false,
error: `Skill not found: ${name}`,
};
}

// 2. 检查依赖
if (skill.dependencies) {
for (const dep of skill.dependencies) {
if (!this.registry.has(dep)) {
return {
success: false,
error: `Missing dependency: ${dep}`,
};
}
}
}

// 3. 选择执行器
const executor = this.getExecutor(skill.executor.type);

// 4. 执行技能
try {
const result = await executor.execute(skill, params);

return {
success: result.success,
output: result.output,
skill: name,
};
} catch (error) {
return {
success: false,
error: error.message,
};
}
}

private getExecutor(type: string): SkillExecutor {
switch (type) {
case 'script':
return new ScriptExecutor();
case 'prompt':
return new PromptExecutor(this.llm);
case 'api':
return new ApiExecutor();
default:
throw new Error(`Unknown executor type: ${type}`);
}
}
}

技能注册表实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class SkillRegistryManager {
private registry: Map<string, SkillRegistry> = new Map();
private loader: SkillLoader;
private trigger: SkillTrigger;

constructor(config: SkillRegistryConfig) {
this.loader = new SkillLoader(config);
this.trigger = new SkillTrigger();
}

async initialize(): Promise<void> {
// 1. 加载所有技能
await this.loader.loadAll();

// 2. 注册到注册表
for (const skill of this.loader.skills) {
this.register(skill);
}

// 3. 设置触发器
this.setupTriggers();
}

register(skill: SkillRegistry): void {
this.registry.set(skill.name, skill);

// 添加触发规则
if (skill.triggers) {
this.trigger.addRule({
skill: skill.name,
keywords: skill.triggers.keywords,
commands: skill.triggers.commands,
patterns: skill.triggers.patterns,
});
}
}

get(name: string): SkillRegistry | undefined {
return this.registry.get(name);
}

list(): SkillRegistry[] {
return Array.from(this.registry.values());
}

search(query: string): SkillRegistry[] {
const lowerQuery = query.toLowerCase();

return this.list().filter(skill =>
skill.name.toLowerCase().includes(lowerQuery) ||
skill.description.toLowerCase().includes(lowerQuery)
);
}

private setupTriggers(): void {
// 为每个技能设置触发规则
for (const skill of this.registry.values()) {
if (skill.triggers) {
this.trigger.addRule({
skill: skill.name,
keywords: skill.triggers.keywords,
commands: skill.triggers.commands,
});
}
}
}
}

技能示例:Hexo 新文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# ~/.openclaw/skills/hexo-new/SKILL.md

---
name: hexo-new
description: 创建新的 Hexo 博客文章
version: 1.0.0
author: John
---

# Hexo 新文章

创建新的 Hexo 博客文章,自动添加 Front Matter。

## 触发条件

- 关键词:`hexo-new`, `新建文章`, `创建文章`
- 命令:`/hexo-new`

## 参数

| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| --title | string | 是 | - | 文章标题 |
| --category | string | 否 | "技术" | 文章分类 |
| --tags | array | 否 | [] | 标签列表 |
| --draft | boolean | 否 | false | 是否为草稿 |

## 使用方式

```bash
openclaw run hexo-new --title "我的文章" --category "技术" --tags '["AI", "Agent"]'

执行脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// scripts/hexo-new.js
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');

module.exports = async function(params) {
const { title, category, tags, draft } = params;

// 1. 使用 hexo 命令创建文章
const hexoCmd = draft ? 'hexo new draft' : 'hexo new post';

return new Promise((resolve, reject) => {
exec(`${hexoCmd} "${title}"`, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}

// 2. 获取文章路径
const filePath = `source/_posts/${title.replace(/\s+/g, '-')}.md`;

// 3. 添加 Front Matter
const frontMatter = `---
title: ${title}
date: ${new Date().toISOString().split('T')[0]}
categories: [${category}]
tags: [${tags.join(', ')}]
author: John
---

`;

// 4. 写入文件
const fullPath = path.join(process.cwd(), filePath);
const content = fs.readFileSync(fullPath, 'utf-8');
fs.writeFileSync(fullPath, frontMatter + content);

resolve({
success: true,
output: `文章已创建:${filePath}`,
filePath,
});
});
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

### 技能示例:晨间检查

```markdown
# ~/.openclaw/skills/morning-check/SKILL.md

---
name: morning-check
description: 晨间例行检查(天气 + 日历 + 新闻)
version: 1.0.0
author: John
---

# 晨间检查

一键获取天气、日历事件和新闻摘要。

## 触发条件

- 关键词:`morning`, `晨间`, `早安`, `今天怎么样`
- 命令:`/morning-check`

## 参数

无参数

## 使用方式

```bash
openclaw run morning-check

执行脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// scripts/morning-check.js
module.exports = async function(params) {
const results = [];

// 1. 检查天气
const weather = await executeSkill('check-weather');
results.push(`🌤️ 天气:${weather.output}`);

// 2. 检查日历
const calendar = await executeSkill('check-calendar');
results.push(`📅 日程:${calendar.output}`);

// 3. 阅读新闻
const news = await executeSkill('read-news', { category: 'tech' });
results.push(`📰 新闻:${news.output}`);

return {
success: true,
output: results.join('\n\n'),
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

---

## OpenClaw 最佳实践

### 实践 1:创建技能目录

```bash
# 创建技能目录
mkdir -p ~/.openclaw/skills/hexo-new/scripts
cd ~/.openclaw/skills/hexo-new

# 创建 SKILL.md
vim SKILL.md

# 创建执行脚本
vim scripts/hexo-new.js

# 测试技能
openclaw run hexo-new --title "测试文章"

实践 2:技能市场

1
2
3
4
5
6
7
8
9
10
11
# 浏览可用技能
openclaw run skill --action list

# 搜索技能
openclaw run skill --action search --query "blog"

# 安装技能
openclaw run skill --action install hexo-new

# 查看技能信息
openclaw run skill --action info hexo-new

实践 3:技能组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ~/.openclaw/config/skill-compositions.yaml

compositions:
- name: blog-workflow
description: 博客发布完整流程
steps:
- skill: hexo-new
params:
title: "{{title}}"
category: "{{category}}"
- skill: hexo-publish
wait: true
- skill: create-mr
params:
branch: "content-{{date}}"

- name: dev-routine
description: 开发例行检查
steps:
- skill: git-status
- skill: run-tests
- skill: check-coverage

实践 4:技能共享

1
2
3
4
5
6
7
8
9
# 打包技能
cd ~/.openclaw/skills/hexo-new
tar -czf hexo-new.tar.gz SKILL.md scripts/

# 发布到技能市场
openclaw skill publish hexo-new.tar.gz

# 分享技能
openclaw skill share hexo-new --to @colleague

实践 5:技能版本管理

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看技能版本
openclaw skill versions hexo-new

# 输出:
hexo-new 版本历史:
1.0.0 (当前) - 初始版本
0.9.0 - Beta 版本

# 回滚到旧版本
openclaw skill rollback hexo-new --version 0.9.0

# 发布新版本
openclaw skill release hexo-new --version 1.1.0 --message "添加草稿支持"

总结

核心要点

  1. 约定优于配置 - SKILL.md 规范简化配置
  2. 技能即代码 - 可版本控制、可复用
  3. 组合优于继承 - 小组件组合成大功能
  4. 触发即执行 - 智能触发减少输入
  5. 参数自解释 - 自动补全和提示

设计智慧

好的技能系统让用户”想说即做”。

Claude Code 的技能系统设计告诉我们:

  • 简单规范降低使用门槛
  • 技能组合提升复用性
  • 智能触发改善用户体验
  • 版本管理保证可维护性

技能分类

类别 示例 数量
博客管理 hexo-new, hexo-publish 5
日常检查 morning-check, weather 3
开发工具 git-status, run-tests 10
数据处理 csv-to-json, data-clean 8
系统管理 backup, cleanup 5

下一步

  • 创建技能目录
  • 编写第一个技能
  • 配置触发器
  • 发布到技能市场

系列文章:

  • [1] Bash 命令执行的安全艺术 (已发布)
  • [2] 差异编辑的设计艺术 (已发布)
  • [3] 文件搜索的底层原理 (已发布)
  • [4] 多 Agent 协作的架构设计 (已发布)
  • [5] 技能系统的设计哲学 (本文)
  • [6] MCP 协议集成的完整指南 (待发布)

上一篇: Claude Code 源码解析 (4):多 Agent 协作的架构设计


关于作者: John,OpenClaw 平台开发者,专注 AI 助手架构设计与实现。