AI新手教程11:Skill的介绍及使用

从打字员到超级助手

之前我们学会了用Claude Code做项目,你可能已经体验到了AI编程的强大。但你有没有发现一个问题:

每次做类似的事情,都要重新描述一遍需求。

比如你想让AI帮你:

  • 写Git提交信息
  • 审查代码
  • 生成项目文档
  • 同步文件

这些任务你可能每天都要做好几次,但每次都要打一长段话告诉AI该怎么做,太累了!

这就像你每次点外卖,都要跟店家说:

“我要一份红烧牛肉面,不要香菜,多加辣椒,面要硬一点,汤要少一点……”

有没有办法把这些常用操作变成一键调用?

有的,这就是今天要讲的Skill


什么是Skill?

一句话解释

Skill = AI的快捷指令

就像你手机上的快捷指令App,Skill把复杂的操作封装成一个简单的命令。

官方定义

Skill指的是可复用的"能力模块",把工具/API/脚本与提示封装成标准接口,让AI按需调用完成特定任务。

它强调:

  • 清晰的输入输出
  • 依赖与版本管理
  • 可测试、可更新
  • 把通用AI变成面向业务的专业助手

通俗理解

没有Skill的时候:

你:请帮我审查这段代码,检查以下几点:
1. 是否有性能问题
2. 是否有安全漏洞
3. 代码风格是否符合规范
4. 是否有重复代码
5. 变量命名是否清晰
6. 注释是否完整
……(还要继续描述10行)

有了Skill之后:

你:/review

AI自动按照预设的标准审查代码,并给出详细报告。

差别看出来了吗? 从几百字变成一个命令,这就是Skill的威力。


Skill、提示词、MCP的区别

很多人会问:Skill、提示词(Prompt)、MCP这三个概念有什么区别?

概念对比

对比项 提示词(Prompt) Skill MCP
本质 文本指令 封装好的能力模块 连接外部工具的协议
复用性 低,每次要重新输入 高,定义一次重复使用 高,一次配置持续使用
复杂度 简单 中等 复杂
能力范围 仅文本处理 文本 + 简单脚本 文本 + 外部系统交互
学习门槛 最低 中等 较高

用比喻说明

提示词 = 口头指令

  • 你每次都要口头告诉AI做什么
  • 适合一次性、临时的任务
  • 例如:“帮我翻译这段话”

Skill = 工作流程

  • 把常用的指令固化成标准流程
  • 适合重复性、标准化的任务
  • 例如:"/commit"自动生成Git提交信息

MCP = 外部系统

  • 让AI连接到外部工具和数据源
  • 适合需要访问外部系统的任务
  • 例如:连接Obsidian读写笔记、连接数据库查询数据

三者的关系

提示词 → 最基础的交互方式
    ↓
Skill → 封装了提示词 + 简单逻辑
    ↓
MCP → Skill + 外部系统的能力

形象地说:

  • 提示词 = 你自己做饭
  • Skill = 用微波炉加热速食
  • MCP = 连接外卖平台点餐

什么时候用哪个?

用提示词,如果:

  • 任务简单、一次性
  • 需求灵活多变
  • 不需要复用

用Skill,如果:

  • 任务重复性高
  • 有标准化流程
  • 想提高效率

用MCP,如果:

  • 需要访问外部数据(数据库、API、文件系统等)
  • 需要和其他软件交互(Obsidian、浏览器等)
  • 需要实时获取信息

实际案例对比:

场景一:翻译一段话

  • 用提示词:“请翻译这段话”(最简单)

场景二:每天翻译很多文档

  • 用Skill:"/translate"(标准化翻译流程)

场景三:自动翻译Obsidian笔记并保存

  • 用MCP:连接Obsidian,自动读取、翻译、保存(最强大)

Skill的基本使用

怎么查看已有的Skill?

在Claude Code或Codex中,输入:

/skill

会列出所有可用的Skill。

PixPin_2026-01-23_11-22-37

怎么使用Skill?

方法一:直接执行

输入 /skill,然后用Tab键选中想用的Skill,按回车执行。

PixPin_2026-01-23_11-22-55

方法二:带参数执行

选中Skill后,继续输入你的具体需求:

/translate 把这段代码的注释翻译成英文

常用的内置Skill

大部分AI编程工具都自带一些常用Skill:

Skill 作用 示例
/commit 自动生成Git提交信息 /commit
/review 审查代码质量 /review
/fix 修复代码错误 /fix
/test 生成测试用例 /test
/doc 生成文档 /doc
/refactor 重构代码 /refactor

这些内置Skill已经能解决80%的日常需求。


创建自己的Skill

如果内置Skill不够用,你可以创建自己的Skill。

两种创建方法

方法一:让AI帮你创建(推荐)

在Codex中自带了一个创建Skill的Skill(很绕,但很强大)。

操作步骤:

  1. 输入 /skill
  2. 找到"Create Skill"相关的选项
  3. 选中后,告诉AI你想要什么Skill

示例:

/create-skill

我想要一个翻译Skill,功能是:
1. 自动检测代码中的中文注释
2. 翻译成英文
3. 保持代码格式不变

PixPin_2026-01-23_11-23-29

AI会自动帮你创建Skill文件,并放到正确的位置。

方法二:手动创建

如果你想更深入了解Skill的结构,可以手动创建。

Skill的文件结构:

每个Skill是一个文件夹,里面必须包含 SKILL.md 文件:

my-skill/
  ├── SKILL.md          # Skill的说明和配置
  └── scripts/          # 可选:辅助脚本
      └── helper.py

SKILL.md的基本格式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
---
name: 技能名称
description: 技能描述
---

# 详细说明

这里写详细的使用说明和实现逻辑。

## 参数

- `--param1`: 参数1说明
- `--param2`: 参数2说明

## 示例

使用示例代码

后面我们会详细介绍如何创建一个完整的Skill。


安装别人的Skill

GitHub上有很多现成的Skill,你可以直接下载使用。

找到Skill文件夹

Codex的Skill位置:

  • Mac/Linux:~/.codex/skills/
  • Windows:%USERPROFILE%\.codex\skills\

Claude Code的Skill位置:

  • Mac/Linux:~/.claude/skills/
  • Windows:%USERPROFILE%\.claude\skills\

安装步骤

  1. 打开Skill文件夹

如果文件夹不存在,可以手动创建:

1
2
3
4
5
# Mac/Linux
mkdir -p ~/.codex/skills

# Windows (PowerShell)
New-Item -Path "$env:USERPROFILE\.codex\skills" -ItemType Directory -Force

PixPin_2026-01-23_11-24-12

  1. 下载Skill文件

从GitHub或其他来源下载Skill文件夹。

  1. 复制到Skill目录

把整个Skill文件夹复制到对应的skills目录。

PixPin_2026-01-23_11-24-36

  1. 重启工具

注意: Codex目前不支持热更新,需要退出重启才能看到新Skill。Claude Code通常可以自动识别。

验证安装

重启后,输入 /skill 查看列表,确认新Skill已经出现。


实战案例——创建一个同步Skill

问题场景

如果你同时使用Codex和Claude Code(很多人都这样),会遇到一个麻烦:

两个工具的Skill要分别管理,非常不方便。

  • Codex的Skill在 ~/.codex/skills/
  • Claude Code的Skill在 ~/.claude/skills/

每次你在Codex创建了一个好用的Skill,还要手动复制到Claude的文件夹,太繁琐了!

这时候我们就可以创建一个Skill,自动同步两个文件夹的内容,用skill来管理skill

这个Skill会:

  1. 检查两边的Skill差异
  2. 报告哪些Skill需要同步
  3. 经过你确认后,自动同步

我们只需要把需求告诉AI即可 PixPin_2026-02-10_10-29-42

下面是AI帮你做的事情,请注意,这些步骤都是它自动的,不用你任何操作! 放在这里只是演示

第一步:创建Skill文件夹

在Codex或Claude Code的skills目录下,创建一个新文件夹:

1
mkdir ~/.codex/skills/codex-claude-skill-sync

第二步:创建SKILL.md

在文件夹中创建 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
name: codex-claude-skill-sync
description: 同步Codex与Claude技能
---

# Codex/Claude Skill Sync

## 概述

用于检查并同步 Codex 与 Claude 的技能目录,保持两边一致。默认只报告差异,获得用户确认后再执行同步。

## 工作流

1. 运行差异报告(不修改):

   `python3 scripts/sync_skills.py`

2. 用中文向用户汇报差异并等待明确同意后再执行。
3. 同意后执行同步:

   `python3 scripts/sync_skills.py --apply`

4. 遇到冲突(同一修改时间但内容不同)时暂停,询问用户选择保留哪一侧。

## 规则

- 默认目录:
  - Codex: `/Users/你的用户名/.codex/skills`
  - Claude: `/Users/你的用户名/.claude/skills`
- 仅处理顶层且包含 `SKILL.md` 的目录,跳过隐藏目录与 `.system`
- 以目录内最新修改时间判断哪一侧更新
- 同步时删除目标技能目录后再整体拷贝来源目录

## 参数

- `--apply` 执行同步(默认只报告)
- `--codex <path>` 覆盖 Codex 目录
- `--claude <path>` 覆盖 Claude 目录
- `--prefer codex|claude` 当修改时间相同但内容不同,用指定一侧覆盖(需用户明确授权)

重要: 把上面的路径改成你自己的实际路径!

第三步:创建脚本文件

在Skill文件夹下创建 scripts 目录,然后创建 sync_skills.py

1
mkdir ~/.codex/skills/codex-claude-skill-sync/scripts

PixPin_2026-01-23_11-25-07

sync_skills.py 的完整代码:

  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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#!/usr/bin/env python3
"""
Compare and sync skill folders between Codex and Claude.

Default behavior is report-only. Use --apply to perform sync.
"""

from __future__ import annotations

import argparse
import hashlib
import os
from datetime import datetime
from pathlib import Path
import shutil
import sys

DEFAULT_CODEX = Path("/Users/你的用户名/.codex/skills")
DEFAULT_CLAUDE = Path("/Users/你的用户名/.claude/skills")

IGNORE_DIR_NAMES = {".git", ".idea", ".vscode", "__pycache__", ".pytest_cache", ".mypy_cache"}
IGNORE_FILE_NAMES = {".DS_Store"}
TIME_EPSILON = 1.0


def format_time(timestamp: float) -> str:
    return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")


def list_skill_dirs(root: Path) -> tuple[dict[str, Path], list[str]]:
    if not root.exists():
        raise FileNotFoundError(f"Root path does not exist: {root}")
    if not root.is_dir():
        raise NotADirectoryError(f"Root path is not a directory: {root}")

    skills: dict[str, Path] = {}
    ignored: list[str] = []
    for entry in sorted(root.iterdir(), key=lambda p: p.name):
        if not entry.is_dir():
            continue
        if entry.name.startswith("."):
            ignored.append(entry.name)
            continue
        if not (entry / "SKILL.md").is_file():
            continue
        skills[entry.name] = entry
    return skills, ignored


def dir_state(path: Path) -> tuple[str, float, int]:
    hasher = hashlib.sha256()
    latest_mtime = path.stat().st_mtime
    file_count = 0

    for root, dirs, files in os.walk(path):
        dirs[:] = [d for d in dirs if d not in IGNORE_DIR_NAMES]
        dirs.sort()
        files = sorted(f for f in files if f not in IGNORE_FILE_NAMES)

        rel_dir = os.path.relpath(root, path)
        if rel_dir == ".":
            rel_dir = ""
        hasher.update(f"D|{rel_dir}\n".encode())

        try:
            latest_mtime = max(latest_mtime, os.stat(root).st_mtime)
        except FileNotFoundError:
            continue

        for name in files:
            file_path = Path(root) / name
            rel_path = os.path.relpath(file_path, path)

            if file_path.is_symlink():
                try:
                    target = os.readlink(file_path)
                except OSError:
                    target = ""
                hasher.update(f"L|{rel_path}\n{target}\n".encode())
                try:
                    latest_mtime = max(latest_mtime, file_path.lstat().st_mtime)
                except FileNotFoundError:
                    pass
                continue

            if not file_path.is_file():
                continue

            stat = file_path.stat()
            latest_mtime = max(latest_mtime, stat.st_mtime)
            file_count += 1
            hasher.update(f"F|{rel_path}\n{stat.st_size}\n".encode())

            with open(file_path, "rb") as handle:
                for chunk in iter(lambda: handle.read(1024 * 1024), b""):
                    hasher.update(chunk)

    return hasher.hexdigest(), latest_mtime, file_count


def build_plan(
    codex_skills: dict[str, Path],
    claude_skills: dict[str, Path],
    codex_root: Path,
    claude_root: Path,
    prefer: str | None,
) -> tuple[list[dict], list[str], list[dict]]:
    actions: list[dict] = []
    identical: list[str] = []
    conflicts: list[dict] = []

    all_names = sorted(set(codex_skills) | set(claude_skills))
    for name in all_names:
        codex_path = codex_skills.get(name)
        claude_path = claude_skills.get(name)

        if codex_path and not claude_path:
            actions.append(
                {
                    "name": name,
                    "src": codex_path,
                    "dst": claude_root / name,
                    "reason": "only in codex",
                    "direction": "codex -> claude",
                }
            )
            continue
        if claude_path and not codex_path:
            actions.append(
                {
                    "name": name,
                    "src": claude_path,
                    "dst": codex_root / name,
                    "reason": "only in claude",
                    "direction": "claude -> codex",
                }
            )
            continue

        if not codex_path or not claude_path:
            continue

        codex_hash, codex_mtime, _ = dir_state(codex_path)
        claude_hash, claude_mtime, _ = dir_state(claude_path)

        if codex_hash == claude_hash:
            identical.append(name)
            continue

        time_delta = codex_mtime - claude_mtime
        if abs(time_delta) <= TIME_EPSILON:
            if prefer == "codex":
                actions.append(
                    {
                        "name": name,
                        "src": codex_path,
                        "dst": claude_path,
                        "reason": "same mtime, prefer codex",
                        "direction": "codex -> claude",
                        "codex_mtime": codex_mtime,
                        "claude_mtime": claude_mtime,
                    }
                )
            elif prefer == "claude":
                actions.append(
                    {
                        "name": name,
                        "src": claude_path,
                        "dst": codex_path,
                        "reason": "same mtime, prefer claude",
                        "direction": "claude -> codex",
                        "codex_mtime": codex_mtime,
                        "claude_mtime": claude_mtime,
                    }
                )
            else:
                conflicts.append(
                    {
                        "name": name,
                        "codex_mtime": codex_mtime,
                        "claude_mtime": claude_mtime,
                    }
                )
            continue

        if time_delta > 0:
            actions.append(
                {
                    "name": name,
                    "src": codex_path,
                    "dst": claude_path,
                    "reason": "codex newer",
                    "direction": "codex -> claude",
                    "codex_mtime": codex_mtime,
                    "claude_mtime": claude_mtime,
                }
            )
        else:
            actions.append(
                {
                    "name": name,
                    "src": claude_path,
                    "dst": codex_path,
                    "reason": "claude newer",
                    "direction": "claude -> codex",
                    "codex_mtime": codex_mtime,
                    "claude_mtime": claude_mtime,
                }
            )

    return actions, identical, conflicts


def print_report(
    actions: list[dict],
    identical: list[str],
    conflicts: list[dict],
    codex_root: Path,
    claude_root: Path,
    apply: bool,
    ignored_codex: list[str],
    ignored_claude: list[str],
) -> None:
    print("Skill sync report")
    print(f"Codex: {codex_root}")
    print(f"Claude: {claude_root}")

    if ignored_codex:
        print(f"Ignored in Codex: {', '.join(sorted(ignored_codex))}")
    if ignored_claude:
        print(f"Ignored in Claude: {', '.join(sorted(ignored_claude))}")

    print("\nPlanned sync actions:")
    if not actions:
        print("- none")
    else:
        for item in actions:
            codex_mtime = item.get("codex_mtime")
            claude_mtime = item.get("claude_mtime")
            details = []
            if codex_mtime is not None:
                details.append(f"codex mtime: {format_time(codex_mtime)}")
            if claude_mtime is not None:
                details.append(f"claude mtime: {format_time(claude_mtime)}")
            detail_text = f" ({', '.join(details)})" if details else ""
            print(f"- {item['name']}: {item['direction']} [{item['reason']}]" + detail_text)

    print("\nConflicts:")
    if not conflicts:
        print("- none")
    else:
        for item in conflicts:
            print(
                f"- {item['name']}: same mtime but different content "
                f"(codex {format_time(item['codex_mtime'])}, claude {format_time(item['claude_mtime'])})"
            )

    print(f"\nUp-to-date skills: {len(identical)}")

    if not apply:
        print("\nDry run only. Re-run with --apply to sync.")


def apply_actions(actions: list[dict]) -> None:
    for item in actions:
        src = Path(item["src"])
        dst = Path(item["dst"])

        if dst.exists():
            if dst.is_dir():
                shutil.rmtree(dst)
            else:
                dst.unlink()

        shutil.copytree(src, dst, symlinks=True)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Sync Codex and Claude skill folders")
    parser.add_argument("--codex", type=Path, default=DEFAULT_CODEX, help="Codex skill root")
    parser.add_argument("--claude", type=Path, default=DEFAULT_CLAUDE, help="Claude skill root")
    parser.add_argument("--apply", action="store_true", help="Apply sync actions")
    parser.add_argument(
        "--prefer",
        choices=["codex", "claude"],
        help="Break ties when mtimes are equal",
    )
    return parser.parse_args()


def main() -> int:
    args = parse_args()

    try:
        codex_skills, ignored_codex = list_skill_dirs(args.codex)
        claude_skills, ignored_claude = list_skill_dirs(args.claude)
    except (FileNotFoundError, NotADirectoryError) as exc:
        print(str(exc), file=sys.stderr)
        return 2

    actions, identical, conflicts = build_plan(
        codex_skills,
        claude_skills,
        args.codex,
        args.claude,
        args.prefer,
    )
    print_report(
        actions,
        identical,
        conflicts,
        args.codex,
        args.claude,
        args.apply,
        ignored_codex,
        ignored_claude,
    )

    if args.apply and actions:
        apply_actions(actions)
        print("\nSync complete.")
    elif args.apply and not actions:
        print("\nNo changes to apply.")

    if conflicts and not args.prefer:
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

重要: 记得修改开头的路径:

1
2
DEFAULT_CODEX = Path("/Users/你的用户名/.codex/skills")
DEFAULT_CLAUDE = Path("/Users/你的用户名/.claude/skills")

改成你实际的路径。

使用同步Skill

第一步:查看差异

在Claude Code或Codex中输入:

/codex-claude-skill-sync

AI会自动运行脚本,报告两边的Skill差异。

第二步:确认同步

如果你同意同步,告诉AI:

同意,请执行同步

AI会运行 python3 scripts/sync_skills.py --apply 完成同步。

搞定! 以后你在任意一边创建或修改Skill,只需要运行一次这个同步Skill,两边就会保持一致。


Skill进阶技巧

技巧一:组合使用Skill

多个Skill可以串联使用:

/review 然后 /fix 修复发现的问题

AI会先审查代码,然后根据审查结果自动修复。

技巧二:自定义Skill参数

很多Skill支持参数:

/commit --type feat --scope api

会生成符合特定格式的提交信息。

技巧三:Skill模板

你可以创建Skill模板,快速生成新Skill:

  1. 复制一个现有Skill文件夹
  2. 修改SKILL.md
  3. 保存即可

技巧四:团队共享Skill

把Skill文件夹放到Git仓库,团队成员可以共享:

1
git clone https://github.com/your-team/skills.git ~/.codex/skills/team-skills

总结

今天学到了什么:

  1. Skill是什么:可复用的能力模块,把复杂操作变成简单命令
  2. Skill vs 提示词 vs MCP:三者的区别和适用场景
  3. 如何使用Skill/skill 查看和调用
  4. 如何创建Skill:让AI帮忙或手动创建
  5. 如何安装Skill:复制到对应文件夹
  6. 实战案例:创建同步Skill,解决多工具管理问题

核心要点:

  • Skill让AI从"打字员"升级为"专业助手"
  • 常用操作应该封装成Skill
  • 好的Skill可以节省90%的时间

下期预告

下一篇我们会介绍MCP:

MCP深度解析——让AI连接整个世界

MCP(Model Context Protocol)是AI工具的"插件系统",可以让Claude Code或Codex:

  • 连接Obsidian,自动整理笔记
  • 连接浏览器,自动操作网页
  • 连接数据库,智能查询数据
  • 连接任何你想连接的工具

这才是AI编程的最终形态!

敬请期待!

如果觉得有帮助,记得关注这个系列!

使用 Hugo 构建
主题 StackJimmy 设计