本教程带你在 30 分钟内从零创建一个完整的 Claude Code 自定义 Skill——ask-db:用户用一句中文提问,Claude 自动生成 SQL、在只读安全沙箱里执行、再用自然语言回答。你将掌握 2026 版 Skills 的全部核心能力:frontmatter 触发、动态上下文注入(!command`)、scripts/工程化与${CLAUDE_SKILL_DIR}、渐进式披露的参考文件、参数传递与allowed-tools`。适合已用过 Claude Code、想把"重复粘贴的查库流程"固化成可复用能力的开发者与数据分析师。
${CLAUDE_SKILL_DIR}、动态注入、skill-creator 评测等特性都需要较新版本;命令行执行 claude --version 确认。sqlite3,无需 pip install。SKILL.md 里把 schema 实时拉进上下文。sales.db。/skill-name。很多人上手就想造 Skill,但 Skills 官方文档给了一个清晰的判据——当你在对话里反复粘贴同一段指令、检查清单或多步流程时,或者当 CLAUDE.md 里某一节从"事实陈述"长成了一段"操作步骤"时,就该抽成 Skill。区别在于生命周期:CLAUDE.md 的内容常驻上下文,而 Skill 的正文只在被触发时才加载,长篇参考材料平时几乎零成本。
把"用中文查数据库"这件事和另外两种扩展方式做个对照,你能清楚为什么它最适合做成 Skill:
| 维度 | CLAUDE.md | Skill(本教程) | MCP 服务器 |
|---|---|---|---|
| 加载时机 | 常驻 | 触发/手动调用时 | 工具被调用时 |
| 适合内容 | 事实、约定 | 多步流程 + 脚本 + 参考材料 | 高频原子工具、跨会话状态 |
| 改造成本 | 改文件即生效 | 改文件即生效(live change detection) | 写服务进程、定协议 |
| 查库场景 | 太重,浪费 token | ✅ 恰好 | 适合"查库存"这类高频原子调用 |
我们的场景是"理解一句中文 → 编排多步(生成 SQL、安全执行、组织回答)",这正是 Skill 的甜区。高频的"查某个 SKU 库存"那种点查,才更值得做成 MCP 工具。理解这条边界,是写好 Skill 的前提。
Skills 遵循开放的 Agent Skills 标准,每个 Skill 就是一个目录,入口是 SKILL.md。我们把它放在个人级目录,这样所有项目都能用:
mkdir -p ~/.claude/skills/ask-db/scripts
mkdir -p ~/.claude/skills/ask-db/reference
最终结构长这样:
ask-db/
├── SKILL.md # 入口:frontmatter + 编排指令(必须,<500 行)
├── scripts/
│ └── run_query.py # 只读查询执行器(被运行,不进上下文)
└── reference/
├── schema.md # 完整表结构说明(按需加载)
└── safety_rules.md # SQL 安全红线(按需加载)
预期结果:目录创建成功。Claude Code 会实时监听
~/.claude/skills/的文件变化——新建目录、改SKILL.md在当前会话内立即生效,无需重启。注意:若创建的是会话启动时不存在的顶层 skills 目录,才需要重启一次让它被纳入监听。常见问题:报错"No such file"——多半是
~没展开,用echo $HOME确认绝对路径。Windows/PowerShell 环境把内联 shell 默认从 bash 换成 powershell 需设CLAUDE_CODE_USE_POWERSHELL_TOOL=1。
SKILL.md 顶部是 YAML frontmatter(--- 之间),告诉 Claude 何时用这个 Skill;下面是 markdown 正文,告诉它怎么做。先写 frontmatter:
---
description: 用中文自然语言查询 SQLite 数据库并给出人类可读的回答。当用户想"查一下/统计/算一下"数据库里的数据、问销售/库存/用户等问题、或要求生成 SQL 时使用。不用于修改数据。
when_to_use: 触发词包括"查一下""帮我统计""有多少""按…分组""上个月/最近"等数据提问,以及"对 sales.db 跑个查询"。
argument-hint: "[数据问题]"
allowed-tools: Bash(python3 *) Bash(sqlite3 *)
---
这里有几条 2026 版的关键规则,踩中才能精准触发:
description 把核心用例放第一句。description 和 when_to_use 拼接后在技能列表里会被截断到 1,536 个字符,而且 Claude 是靠它做语义匹配决定是否自动加载的——第一句写清"做什么 + 何时用",比堆关键词有效。when_to_use 补充触发短语,而不是往 description 里塞。两者分工不同:前者是"能力 + 场景",后者是"例子请求"。本教程末尾"已覆盖主题"里那些泛泛而谈的标题,多半就是 description 写得太宽。disable-model-invocation: true。这个字段会让 Claude 永不自动触发、只能你手动 /ask-db。我们希望用户说"查一下上月销量"就自动用,所以保留默认。allowed-tools 预批准权限:列出 Bash(python3 *) 后,Claude 跑查询脚本时不再每次弹窗问你要不要批准。注意,它只是"免询问",并不限制可用工具范围。预期结果:保存后在会话里输入
/应能看到ask-db出现在补全列表;说"帮我查一下数据"应能被自动触发。常见问题一:Skill 不触发——八成是
description太抽象。把"数据库助手"改成"用中文查 SQLite 并回答数据问题"这种带动词和场景的句子。 常见问题二:触发太频繁——description写了"回答任何问题",Claude 就会在无关对话里也加载它。把范围收窄到"数据/统计/SQL"。 常见问题三:description 被截断——精简到 1500 字符内,把长例子挪进reference/。
NL-to-SQL 最大的难点是 Claude 得先知道库里有哪些表、哪些列。动态上下文注入(!command`)正好解决:在SKILL.md` 正文里写一行 shell,Claude Code 会先跑这条命令、把输出原地替换进正文,Claude 看到的就是真实 schema 而非命令本身。
在 SKILL.md 正文区写下骨架(完整版见第六步):
## 当前数据库结构
!`sqlite3 sales.db ".schema" 2>/dev/null || echo "(未找到 sales.db,请向用户确认数据库路径)"`
> 上面是实时拉取的表结构。若为空,先问用户数据库文件在哪,或参考 `reference/schema.md` 的约定。
语法要点:
!command只在 `!` 处于**行首或紧跟空白**时才被识别。写成 `KEY=!`cmd 会被当字面量、不执行。```!:
markdown
```!
sqlite3 sales.db ".tables"
sqlite3 sales.db "SELECT COUNT(*) FROM customers"
```!cmd`` 不会被再展开——天然安全。"disableSkillShellExecution": true,每条命令会被替换成 [shell command execution disabled by policy]。预期结果:触发 Skill 时,Claude 的 prompt 里直接是
CREATE TABLE customers (...),而不是一条 sqlite3 命令。常见问题:schema 很大、几十张表——别全塞进
SKILL.md,那会让 Skill 正文每轮都占上下文。改为只注入.tables列表,把详细结构放进reference/schema.md(下一步),让 Claude 按需读。
Skill 正文应该简洁(官方建议 <500 行),重活交给 scripts/ 里的脚本——脚本是被执行的,不进上下文,零 token 成本。我们写一个只读查询执行器,这是整个 Skill 的安全基石。保存到 ~/.claude/skills/ask-db/scripts/run_query.py:
#!/usr/bin/env python3
"""只读 SQLite 查询执行器:供 ask-db skill 调用。
安全设计:
1. 以 mode=ro 只读方式打开数据库,物理上无法写入。
2. 只允许 SELECT/WITH/EXPLAIN/PRAGMA 开头的查询,拒绝一切 DDL/DML。
3. 只执行单条语句(sqlite3 遇到多余语句会抛错),防止语句堆叠。
4. 强制行数上限与执行时限,防止全表扫描拖垮机器。
"""
import sqlite3, sys, time, re, json
MAX_ROWS = 500
TIMEOUT_MS = 5000
ALLOW_PREFIX = ("select", "with", "explain", "pragma")
FORBIDDEN = re.compile(
r"\b(insert|update|delete|drop|alter|create|truncate|attach|detach|"
r"replace|merge|vacuum|reindex)\b", re.IGNORECASE)
def run(db_path: str, sql: str) -> None:
sql = sql.strip().rstrip(";")
if not sql:
print(json.dumps({"error": "空查询"}, ensure_ascii=False)); return
first = sql.split(None, 1)[0].lower()
if not sql.lower().startswith(ALLOW_PREFIX) or FORBIDDEN.search(sql):
print(json.dumps({"error": "仅允许只读 SELECT 查询"}, ensure_ascii=False)); return
# 以只读 URI 打开;找不到库会直接报错
con = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True, timeout=1)
con.row_factory = sqlite3.Row
deadline = [time.monotonic()]
def progress():
if (time.monotonic() - deadline[0]) * 1000 > TIMEOUT_MS:
return 1 # 中止查询
return 0
con.set_progress_handler(progress, 1000) # 每处理 1000 个虚拟机指令检查一次
wrapped = f"SELECT * FROM ({sql}) AS _q LIMIT {MAX_ROWS}"
try:
cur = con.execute(wrapped) # execute 只跑首条语句,多语句会抛错
rows = cur.fetchall()
out = {"columns": [d[0] for d in cur.description],
"rows": [list(r) for r in rows],
"rowcount": len(rows),
"truncated": len(rows) >= MAX_ROWS}
print(json.dumps(out, ensure_ascii=False, default=str))
except sqlite3.Error as e:
print(json.dumps({"error": f"SQL 执行失败: {e}"}, ensure_ascii=False))
finally:
con.close()
if __name__ == "__main__":
if len(sys.argv) != 3:
print(json.dumps({"error": "用法: run_query.py <db> <sql>"}, ensure_ascii=False))
sys.exit(2)
run(sys.argv[1], sys.argv[2])
四个安全层次是刻意的:只读连接是物理兜底;前缀白名单 + 关键词黑名单是语义校验;单语句执行防堆叠注入;行数/时限防资源耗尽。即使 Claude 生成了 DROP TABLE,这里也会被拦下。
路径用 ${CLAUDE_SKILL_DIR} 引用,无论 Skill 装在个人、项目还是插件级都正确:
python3 ${CLAUDE_SKILL_DIR}/scripts/run_query.py sales.db "SELECT ...;"
预期结果:手动跑
python3 scripts/run_query.py sales.db "SELECT 1"返回{"columns":["1"],"rows":[[1]],...};跑DELETE FROM t返回{"error":"仅允许只读 SELECT 查询"}。常见问题一:
${CLAUDE_SKILL_DIR}没展开——它是 Skills 提供的字符串替换变量,只在 Skill 正文/动态注入里有效,别在普通对话或子代理里用。 常见问题二:子查询包裹SELECT * FROM (...) LIMIT N报错——极少数含特定窗口函数的语句会有歧义,让 Claude 换写法即可,安全优先。 常见问题三:查询慢——set_progress_handler会在超时后中止并报错,提示 Claude 加WHERE或建索引。
现在把 frontmatter、动态注入、脚本调用串成一条清晰的工作流。这是完整 SKILL.md 正文:
# ask-db:中文问数据库
你是只读数据分析师。面对用户的数据问题,按以下步骤工作:
## 1. 确认数据源
默认库为 `sales.db`(当前工作目录)。schema 见上方注入块;若为空,先问用户库文件路径。
详细字段含义见 reference/schema.md(仅当注入的 schema 不够清晰时再读)。
## 2. 把问题翻译成只读 SQL
- 只生成 SELECT/WITH 开头的查询;禁止任何写操作。
- 列名用双引号、字符串用单引号;日期用 `DATE('now','-1 month')` 等。
- 复杂查询先在脑中验证聚合粒度,避免笛卡尔积。
## 3. 执行(必须走安全脚本,禁止直接 sqlite3 写命令)
用 ${CLAUDE_SKILL_DIR}/scripts/run_query.py 跑你的 SQL:
python3 ${CLAUDE_SKILL_DIR}/scripts/run_query.py
脚本会返回 JSON:`columns`/`rows`/`rowcount`/`truncated` 或 `error`。
若 `error`,修正 SQL 重跑;若 `truncated:true`,告知用户结果被截断到 500 行并建议加条件。
## 4. 用自然语言回答
不要把原始 JSON 丢给用户。用表格/要点呈现结论,先给答案再给关键数字,必要时附上你用的 SQL。
## 红线
- 永不绕过 run_query.py 直接执行写语句。
- 安全规则的细节见 reference/safety_rules.md(有争议时再查)。
注意第 4 步那句话——"skill 看起来在影响行为"和"它真的影响了"是两回事。Skill 正文被调用后作为一条消息留在会话里整个任务期间有效,Claude 不会每轮重读文件。所以把"贯穿任务的红线"写成常驻指令(如"必须走脚本"),把一次性步骤写清楚,行为才会稳定。
补两个轻量参考文件。reference/schema.md:
# sales.db 表结构约定
- customers(id, name, city, signup_date)
- products(id, name, category, price)
- orders(id, customer_id, order_date, total) — total 为订单金额
- order_items(order_id, product_id, qty, unit_price) — 明细
时间字段统一存为 ISO 字符串 'YYYY-MM-DD'。
reference/safety_rules.md:把脚本里那四层防护用中文复述一遍,供 Claude 在用户质疑"为什么不让我 UPDATE"时引用。这两个文件默认不加载,只有 SKILL.md 里引用它们、且 Claude 判断需要时才被读入——这就是渐进式披露,长参考材料平时零成本。
先做两轮人工冒烟(务必在新会话里测,老会话有编写时的残留上下文会掩盖缺陷):
ask-db、是否走脚本。/ask-db 按城市统计订单数,看 $ARGUMENTS 是否被正确传入问题。手动验证技巧:分别"开 Skill 跑一次"和"关掉 Skill 跑一次"对比结果——这是官方推荐的基线对照法。
接着上自动化评测。skill-creator 插件把"开/关对照"循环自动化了:
/plugin install skill-creator@claude-plugins-official
# 若提示找不到,先:
/plugin marketplace update claude-plugins-official
/reload-plugins
装好后对 Claude 说"用 skill-creator 评测我的 ask-db skill"。它会:
evals/evals.json 里存测试用例(prompt + 输入文件 + 期望行为);grading.json;benchmark.json,让你看到加这个 Skill 到底提升了多少通过率、付出了多少 token;description 调整。预期结果:
benchmark.json里"有 Skill"通过率显著高于"无 Skill",且 token 开销可接受。常见问题:评测显示"命中率低、该触发没触发"——回到第三步,把
description/when_to_use里缺的触发短语补上重测。
从零建一个库并提问。先造数据:
sqlite3 sales.db <<'SQL'
CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT, city TEXT, signup_date TEXT);
CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, category TEXT, price REAL);
CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INT, order_date TEXT, total REAL);
CREATE TABLE order_items(order_id INT, product_id INT, qty INT, unit_price REAL);
INSERT INTO customers VALUES (1,'张三','上海','2024-01-15'),(2,'李四','北京','2025-05-20');
INSERT INTO products VALUES (1,'无线鼠标','外设',99),(2,'机械键盘','外设',399),(3,'显示器','显示',1299);
INSERT INTO orders VALUES (1,1,'2026-05-28',498),(2,2,'2026-05-30',399),(3,1,'2026-06-02',1299);
INSERT INTO order_items VALUES (1,1,1,99),(1,2,1,399),(2,2,1,399),(3,3,1,1299);
SQL
在 sales.db 所在目录启动 claude,直接问:
上个月(2026-05)销量金额最高的三个商品是哪些?
Claude 会自动触发 ask-db,注入 sales.db 的 schema,生成形如:
SELECT p.name, SUM(oi.qty*oi.unit_price) AS revenue
FROM order_items oi JOIN products p ON p.id=oi.product_id
JOIN orders o ON o.id=oi.order_id
WHERE o.order_date LIKE '2026-05%'
GROUP BY p.name ORDER BY revenue DESC LIMIT 3;
经 run_query.py 执行后,你会得到一段自然语言结论(例如"机械键盘 798 元居首,其次……"),而非原始 JSON。
整个过程的工作流:
flowchart TD
A[用户中文提问] --> B{Claude 语义匹配<br/>description/when_to_use}
B -->|命中| C[加载 ask-db Skill 正文]
C --> D["动态注入 !sqlite3 .schema<br/>拿到真实表结构"]
D --> E[Claude 生成只读 SELECT SQL]
E --> F["运行 scripts/run_query.py<br/>只读+白名单+行数时限"]
F --> G{执行结果}
G -->|error| E
G -->|JSON rows| H[Claude 组织自然语言回答]
H --> I[表格/要点交付给用户]
G -->|truncated| J[提示加 WHERE 重试]
J --> E
当库很大、查询很多时,主会话上下文会被 schema 和中间结果撑爆。给 frontmatter 加两行,让整个 Skill 在隔离子代理里跑、只把结论回传:
context: fork
agent: Explore
context: fork 让 SKILL.md 正文成为驱动子代理的 prompt;agent: Explore 用内置的只读探索代理(它跳过 CLAUDE.md 和 git status,上下文极小)。注意:fork 只适合"有明确任务"的 Skill;若是纯约定类("用这些 SQL 规范")放进 fork,子代理拿到规范却没有可执行任务,会空手返回。
~/.claude/skills/ask-db/,你所有项目可用。.claude/skills/ask-db/ 到版本控制,团队 clone 即用(首次需接受 workspace 信任对话框,因为 allowed-tools 在信任后才生效——审阅项目 Skill 再信任,别盲目信任来路不明的仓库)。.claude-plugin/plugin.json,它就变成 <name>@skills-dir 插件,还能捆绑 hooks、agents、MCP 服务器,通过 marketplace 分发。/code-review)。code.claude.com/docs/en/skills!command`、${CLAUDE_SKILL_DIR}、context: fork`、skill-creator 评测流程[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
