做一个 Claude Code 插件,分享给团队:把 skill、子代理和 hook 打成一包
插件是一个带清单文件的目录,把 skill、子代理、hook、MCP 服务器装在一起,可以整包分享和安装。本站把本系列前面做的三样东西打成了一个插件,实测了校验、单次加载、建本地市场、按项目安装和移除。要留意的一个结果:把 skills 目录放错位置,校验照样通过,但 skill 不会加载。
本站已实测,测试日期:2026-10-12。
本页目录(12 节)
这篇教程解决一个问题:你在一个项目里配好了一套 skill、子代理和 hook,想让同事也用上,或者想在自己的几个项目里都用上,怎么办。
把 .claude/ 目录复制来复制去是一个办法,但改了一处就要到处同步。Claude Code 的做法是插件:一个目录,里面按固定的位置放 skill、子代理、hook、MCP 服务器,再加一个清单文件说明它叫什么。Claude Code 把整个目录当成一个单元来加载,所以能整包分享、安装、更新。
本站把本系列前面几篇做的三样东西打成了一个插件,从零走到“别人能装上”。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-12 |
| 版本 | Claude Code 2.1.296 |
| 系统 | Ubuntu 24.04 |
| 模型 | Opus 5.5 |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
| 示例仓库 | Express 的源码,v5.3.0,带一份 CLAUDE.md |
什么时候才需要插件
官方文档的判断很直接:skill、子代理、hook、MCP 服务器放在项目或者个人目录里本来就能用。只服务一个项目、或者只有你自己用的时候,保持原样就行。
下面三种情况才值得做成插件:
- 要分享给同事;
- 要装进好几个项目;
- 要发布带版本号的更新。
搬进插件之后有两处变化:
- 文件的位置:都放在插件自己的目录下,分别是
skills/、agents/、hooks/hooks.json、.mcp.json。 - 名字:插件里的 skill 和子代理会带上插件名做前缀,比如
/express-kit:commit-message。这样两个插件各有一个同名的 skill 也不会冲突。
1建目录,写清单
我们的插件叫 express-kit,装三样东西:一个写提交说明的 skill,子代理那篇里的测试评审员,一个记录改动文件的 hook。
express-kit/
├── .claude-plugin/
│ └── plugin.json 清单
├── skills/
│ └── commit-message/
│ └── SKILL.md
├── agents/
│ └── test-reviewer.md
└── hooks/
└── hooks.json
清单是 .claude-plugin/plugin.json:
{
"name": "express-kit",
"description": "团队在这个仓库上用的一套约定:提交说明的格式、测试评审员、改动记录",
"version": "0.1.0",
"author": {
"name": "chunduai demo"
}
}
四个字段,按官方文档:
| 字段 | 说明 |
|---|---|
name |
必填。插件的标识,也是它里面每个 skill 和子代理的前缀。不能有空格 |
description |
别人在 /plugin 里看到的那句介绍 |
version |
可选。写了的话,装的人会停在这个版本,直到你改它 |
author |
署名。里面的 name 必填,email 和 url 可选 |
2放进三样东西
skill 放在 skills/<名字>/SKILL.md,写法和普通的 skill 完全一样(见写第一个 skill):
---
name: commit-message
description: 按团队的格式写提交说明。用户要提交、或者要一条提交说明时使用。
---
根据用户描述的改动(没有描述就看 `git diff --staged`),写一条提交说明:
- 用英文,只写一行,不超过 72 个字符,结尾不加句号。
- 以类型开头,只能是 `fix:`、`docs:`、`test:`、`refactor:` 之一。
- 类型后面用祈使句说明做了什么,不写为什么。
- 只输出这一行,不要解释,不要代码块。
子代理 放在 agents/ 下,一个 Markdown 文件一个,头部和正文与项目里的写法相同。我们直接用了之前那个只读的测试评审员。
hook 放在 hooks/hooks.json。最外层是一个 "hooks" 键,里面的结构和设置文件里的 hooks 一模一样:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' >> /tmp/c7-edited-files.log" }
]
}
]
}
}
这个 hook 很简单:每次它改完或写完一个文件,把路径记进一个日志。hook 怎么写见Hooks 实战。
已经在项目的 .claude/ 下有这些文件的,不用重写,复制过去就行:cp -r .claude/skills express-kit/,子代理同理;hook 则是把设置文件里的 hooks 对象整个复制进 hooks/hooks.json。
最容易放错的地方
.claude-plugin/ 里只放 plugin.json 一个文件。 skill、子代理、hook 都放在插件的根目录下,和 .claude-plugin/ 并排,不是放在它里面。
这一条官方文档用警告框强调了,我们专门试了放错会怎样,结果见下一节。
3校验
claude plugin validate ./express-kit
Validating plugin manifest: /home/ccdemo/demo/c7-plugin/express-kit/.claude-plugin/plugin.json
✔ Validation passed
我们故意弄错了两处,看它查不查得出。
名字里带空格,查得出,退出码非零:
✘ Found 1 error:
❯ name: Plugin name cannot contain spaces. Use kebab-case (e.g., "my-plugin")
✘ Validation failed
把 skills/ 放进了 .claude-plugin/ 里面,查不出:
✔ Validation passed
校验通过了,但加载之后会话里登记的东西是这样的:
{"plugins":["express-kit", ...],"skills":[],"agents":["express-kit:test-reviewer"]}
插件在,子代理在,skill 一个都没有,也没有任何报错。
所以校验通过只说明清单和各个文件的头部写得合法,不说明东西都加载上了。校验之后一定要实际加载一次,看清单。
4不安装,先加载一次试试
--plugin-dir 让一个插件只在这一次会话里生效,不写任何设置,适合开发时用:
claude --plugin-dir ../express-kit
我们用 -p 配合流式输出,看会话启动时登记了什么(写法见把 Claude Code 接进脚本和 CI):
{
"plugins": [
{"name": "express-kit", "path": "/home/ccdemo/demo/c7-plugin/express-kit"},
{"name": "cc-plugin-agents-md", "path": "builtin"},
...
],
"skills": ["express-kit:commit-message"],
"agents": ["express-kit:test-reviewer"]
}
三样逐个验证。
skill,用带前缀的名字调用:
$ claude --plugin-dir ../express-kit -p "/express-kit:commit-message 我把 lib/utils.js 里的 var 都换成了 const 和 let,行为没有变"
refactor: replace var with const and let in lib/utils.js
注意:CLAUDE.md 规定提交类型只能用 `fix:`、`docs:` 或 `test:`,但这个技能也允许用 `refactor:`。
这次改动不影响行为,三种类型都不贴切,所以我用了 `refactor:`……
第一行是我们要的。后面多出来的一段值得注意:这个仓库的 CLAUDE.md 里写着提交类型只有三种,而插件的 skill 允许四种。两处的规矩对不上,它两份都读到了,于是多说了一段。
这是做插件时真会碰到的事:插件是装进别人项目里的,那个项目有自己的 CLAUDE.md。 插件里的约定和项目的约定冲突时,它不会默默挑一个,结果也就不是你设想的那一行干净的输出。给多个项目用的插件,约定要么写得足够通用,要么在说明里讲清楚以谁为准。
不加 --plugin-dir 跑同一条命令,它会告诉你没有这个命令:
这个会话里没有安装 `/express-kit:commit-message` 命令,它没有运行。我直接按项目约定帮你写提交说明。
hook,让它改一个文件,然后看日志:
$ cat /tmp/c7-edited-files.log
/home/ccdemo/demo/c7-plugin/app/Readme.md
hook 触发了。
子代理在登记的清单里是 express-kit:test-reviewer,点名时用这个带前缀的名字。这次我们只确认了它登记上了,没有再派它干一次活。
开发时改了插件的文件,在会话里输入 /reload-plugins 就能重新加载,不用退出。这一条我们没有试。
用 claude plugin list 也能看到单次加载的插件,前提是把 --plugin-dir 写在前面:
$ claude --plugin-dir ../express-kit plugin list
Session-only plugins (--plugin-dir / --plugin-url):
❯ express-kit@inline
Version: 0.1.0
Path: /home/ccdemo/demo/c7-plugin/express-kit
Status: ✔ loaded
5建一个市场,让别人能装
把插件交到别人手里,官方文档给了三条路:
| 做法 | 适合 |
|---|---|
| 直接把目录或者 zip 包发给对方 | 只给几个人,不需要发布 |
| 放进你自己的市场 | 团队或组织。对方登记一次市场,按名字安装,之后能收到更新 |
| 提交到 Anthropic 的目录 | 面向所有人,要经过审核 |
团队里用,走第二条。市场也只是一个目录,里面有一个 .claude-plugin/marketplace.json,列出有哪些插件、各自去哪取。
team-marketplace/
├── .claude-plugin/
│ └── marketplace.json
└── plugins/
└── express-kit/ 把插件整个复制进来
{
"name": "team-marketplace",
"description": "团队内部的插件",
"owner": {
"name": "chunduai demo"
},
"plugins": [
{
"name": "express-kit",
"source": "./plugins/express-kit",
"description": "提交说明的格式、测试评审员、改动记录"
}
]
}
两条规矩,官方文档说大多数装不上的情况都出在这两处:
source的相对路径从市场的根目录算起,也就是包含.claude-plugin/的那个目录,不是从.claude-plugin/里面算。路径里不能有..。- 条目里的
name要和插件自己plugin.json里的name一样。 前者是安装时用的名字,后者是 skill 的前缀。两个不一样的话,别人照着 skill 前缀上的名字去装,会提示在市场里找不到这个插件。
校验同一条命令,这次指向市场的目录:
claude plugin validate ./team-marketplace
Validating marketplace manifest: /home/ccdemo/demo/c7-plugin/team-marketplace/.claude-plugin/marketplace.json
✔ Validation passed
6登记市场,安装
先在自己机器上把整个流程走一遍,这也是你的同事之后要走的流程。
登记市场:
claude plugin marketplace add ./team-marketplace
Adding marketplace…✔ Successfully added marketplace: team-marketplace (declared in user settings)
安装。安装用的标识是“插件名@市场名”:
claude plugin install express-kit@team-marketplace --scope project
Installing plugin "express-kit@team-marketplace"...✔ Successfully installed plugin: express-kit@team-marketplace (scope: project)
安装范围
--scope 决定谁能用上这个插件,以及记在哪个设置文件里:
| 范围 | 谁能用 | 记在哪 |
|---|---|---|
| 用户(默认) | 你,这台机器上的所有项目 | ~/.claude/settings.json |
项目(--scope project) |
在这个仓库里干活的所有人 | .claude/settings.json,要提交 |
本地(--scope local) |
你,只在这个仓库里 | .claude/settings.local.json |
我们选了项目范围。装完之后项目的 .claude/settings.json 里多了这一段:
{
"enabledPlugins": {
"express-kit@team-marketplace": true
}
}
官方文档对项目范围有一句要注意的说明:把这一段提交进仓库,是给同事启用了这个插件,但不会把插件下载到他们的机器上。每个同事还得自己登记市场、运行一次安装。
装上之后看什么
$ claude plugin list
Installed plugins:
❯ express-kit@team-marketplace
Version: 0.1.0
Read from: /home/ccdemo/demo/c7-plugin/team-marketplace/plugins/express-kit
Scope: project
Status: ✔ enabled
更有用的是 details,它列出插件里实际被识别出来的东西,以及每样占多少上下文:
$ claude plugin details express-kit
express-kit 0.1.0
Description: 提交说明的格式、测试评审员、改动记录
Source: express-kit@team-marketplace
Component inventory
Skills (1) commit-message
Agents (1) test-reviewer
Hooks (1) PostToolUse (harness-only — no model context cost)
MCP servers (0)
LSP servers (0)
Projected token cost
Always-on: ~134 tok added to every session
Per-component (rounded)
component always-on on-invoke
commit-message ~70 ~270
test-reviewer ~60 ~160
前面“skill 放错位置”的问题,在这里一眼就能看出来:Skills 后面的数字对不对。
下面那张表回答的是装一个插件的代价:这个插件让每次会话多带约 134 个 token(skill 和子代理的名字加说明),正文在用到时才加载。hook 标着不占模型的上下文,因为它是 Claude Code 自己执行的,不经过模型。装别人的插件之前,也可以用这条命令看看它会往每次会话里加多少东西。
验证
在这个项目里,不加任何参数:
$ claude -p "/express-kit:commit-message 给 req.subdomains 补了一个 IPv6 主机名的测试"
test: add IPv6 hostname case for req.subdomains
换到另一个没装它的项目,会话里就没有这个 skill。项目范围起作用了。
7放到团队够得着的地方
上面的市场还在你自己的硬盘上。要给团队用,把市场的目录推到一个 git 仓库,私有的也可以。同事那边:
claude plugin marketplace add 你的组织/仓库名
claude plugin install express-kit@team-marketplace
GitHub 上的仓库可以用“组织/仓库”的简写,别的 git 托管用完整的仓库地址。这一步我们没有实测,实测环境没有可以推送的远程仓库。
插件本身不一定要放在市场的目录里。source 可以指向别处,文档列的常用写法:
source |
用在 |
|---|---|
"./plugins/express-kit" |
插件就在市场的目录里 |
{ "source": "github", "repo": "你的组织/express-kit" } |
插件自己是一个 GitHub 仓库 |
{ "source": "git-subdir", "url": "你的组织/monorepo", "path": "tools/express-kit" } |
插件是某个仓库里的一个子目录 |
有一种错误校验查不出来:source 指向的目录不存在。文档说明这种情况校验会通过,到安装时才报 Source path does not exist。所以发给别人之前,自己先装一遍。
不要了怎么撤
claude plugin marketplace remove team-marketplace
✔ Successfully removed marketplace: team-marketplace
Also uninstalled 1 plugin from this marketplace:
express-kit@team-marketplace
The removal also deletes their saved options, secrets and data where it can.
移除市场会连带卸载从它装的插件。之后 claude plugin list 显示没有已安装的插件,项目设置里的 enabledPlugins 也清空了。
这一趟花了多少
校验、登记、安装、查看、移除这些命令都不调用模型,不花钱。花钱的是验证 skill 和 hook 的那几次 claude -p,合计约 1 美元,是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。
这次实测没有覆盖到的
- 市场没有推到远程仓库。 同事从 git 仓库登记和安装、收到更新、私有仓库的访问,都按官方文档写的。
- 插件里的 MCP 服务器、LSP 服务器、
userConfig配置项没有做。 - 子代理只确认了登记上,没有实际派出去。
/reload-plugins、--plugin-url、claude plugin init、把插件放在~/.claude/skills/下每次自动加载,都没有试。- 会话里的
/plugin面板没有试,全程用的是 shell 里的claude plugin命令。 - 版本更新没有试。 改了
version之后装过的人怎么收到,文档在“维护市场”那一页。 claude plugin eval(衡量插件有没有真的改变它的行为)没有试。- 只跑了一次。
下一步
- 插件里能放的几样东西各管什么:CLAUDE.md、skill、hook、子代理、MCP、插件各管什么。
- 大仓库里用插件集中管理约定:大仓库和 monorepo 里用 Claude Code。
- 插件和 mod 的区别:Mods 是什么。