
装过插件的 ZCode 用户很多,会发插件的极少。这事跟 App Store 一个理:人人都会下载,上架是另一门手艺。
少的根源在于没路。你攒的技能、配好的钩子,一直没有体面的分发路子,ZCode 把这活儿收编了,方案是插件:把要发的东西打成一个文件夹,对方点一下安装就齐活。
这篇两端都讲:怎么装别人的,怎么发自己的。
插件是什么
一句话:把技能、命令、子智能体、钩子、MCP 服务打成一个可安装的文件夹。
目录长这样:
my-plugin/
├── .zcode-plugin/
│ └── plugin.json ← 清单,唯一必需的东西
├── skills/ ← 技能,一个子目录装一个
├── commands/ ← 斜杠命令,一个 .md 文件一个
├── agents/ ← 子智能体
├── hooks/
│ └── hooks.json ← 钩子
└── .mcp.json ← MCP 服务声明五种货,前面几篇都是散着用的:技能放技能目录,钩子写进 config.json,各管各的。插件等于集装箱,把这些货装进一个箱,再配一份箱单。箱单里唯一必填的字段是 name,其余全可选。官方市场里最简的 skill-creator,箱单就六个字段:
{
"name": "skill-creator",
"version": "0.1.0",
"description": "Create, edit, and iterate local ZCode skills.",
"author": { "name": "Z.ai" },
"license": "MIT",
"skills": "skills"
}name 的规矩是 ^[a-z0-9][a-z0-9._-]{0,127}$:小写字母或数字开头,点、横线、下划线随便用,最长 128。version 缺省算 0.0.0,建议老实写语义化版本,后面「检查更新」拿它对账。
commands、skills、hooks、mcpServers、agents 这几个字段写目录路径即可,字符串或数组都行。hooks 有个默认约定:放在 hooks/hooks.json 会被自动发现,清单里再声明一遍属于画蛇添足,文档原话是不要重复指向同一文件。.mcp.json 里声明的服务装进插件后自动带命名空间,plugin:插件名:服务名,两个插件各带一个同名服务也打不起架来;type 字段能省,写了 command 默认 stdio,写了 url 默认 http。
顺手提两个字段。userConfig 让用户安装时自己改配置,比如官方 ios-simulator 插件的「默认模拟器型号」,各人填各人的,不用动你的文件。dependencies 声明插件依赖,写 name@market,能引同一市场里的,也能跨市场引,跨市场要自己这边开白名单:在 marketplace.json 顶层写 allowCrossMarketplaceDependenciesOn,把目标市场的名字列进去。
不知道目录怎么组织,官方市场里有个 example-plugin 模板插件,清单、命令、技能、钩子、MCP 一样不缺,官方原话是「新建插件时可直接复制」。抄它起步。
装别人的:市场
设置 -> 插件就是商店。官方市场是内置的,我同步那会儿收录 11 个,browser-use、document-skills、skill-creator 都在。
装一个试试:卡片上点「安装」,状态依次变「安装中…」「已安装」,装完默认启用。想看装了什么,点搜索框下面的「已安装」图标带直达详情,它右边的齿轮进「管理已安装」,名称、版本、来源、组件数量都列着,开关启停,「检查更新」和卸载也在这一页。
停用和卸载是两回事。停用只是暂时不生效,组件立即从会话移除,再开就回来;卸载是移出列表。有个细节做得贴心:内置官方插件卸载时会记一个屏蔽标记,之后应用升级也不会自动装回来,不想要的东西不会阴魂不散。
加第三方市场走右上角「创建」-> 添加插件市场,来源三种:GitHub 仓库(写 owner/repo 或链接)、git URL、本地目录或清单文件。加之前 ZCode 先校验市场,过了之后这批插件按市场名分组,出现在「个人」段。市场源的管理在搜索框上方的齿轮里:每个市场收录多少、上次更新什么时候,能单独刷新,也能移除,官方市场除外,它只能刷新。
有件事得单独说:格式上 ZCode 做了开放兼容。清单除了 .zcode-plugin/plugin.json,也认 .claude-plugin/plugin.json,优先级次之;${CLAUDE_PLUGIN_ROOT} 这类变量名原样可用。所以 Claude Code 官方市场(GitHub 上的 anthropics/claude-plugins-official,我同步那会儿收录 255 个)填个仓库地址就能挂上。我日常在用的 superpowers 就是从那儿装的,技能和钩子在 ZCode 里都正常跑。ZCode 自家市场管主力,开放格式让别家的箱也装得进,这是工程上的选择。
连着 SSH 或 WSL 远程工作区干活的补一句:本机插件默认不跟过去,工作区标题栏「同步」下拉里有「同步 Plugin」。
发自己的:打一个插件
轮到自己的箱了。装两样最常见的货:Skill 那篇写过的 code-review-checklist 技能,加 Hooks 那篇的自动格式化 hook。
team-toolkit/
├── .zcode-plugin/
│ └── plugin.json
├── skills/
│ └── code-review-checklist/
│ └── SKILL.md
└── hooks/
├── hooks.json
└── format.mjs箱单:
{
"name": "team-toolkit",
"version": "0.1.0",
"description": "团队工具箱:代码审查清单,加改完文件自动格式化",
"author": { "name": "your-name" },
"license": "MIT",
"skills": "skills"
}技能从技能目录整个搬进 skills/ 下就行,SKILL.md 一个字不用改。要留神的只有一处:插件内技能必须单层目录,skills/code-review-checklist/SKILL.md 这样,嵌套在分组目录里的不被识别。Skill 那篇提过这个坑,搬家时最容易再踩一遍。
钩子有两个关键改动,先看配置:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "process",
"command": "node",
"args": ["${ZCODE_PLUGIN_ROOT}/hooks/format.mjs"],
"timeoutMs": 10000
}
]
}
]
}
}改动一,结构变了。config.json 里写钩子是 enabled 和 events 两层包装,插件的 hooks.json 没这俩,事件名直接开张。照着上一篇把配置原样搬过来,一个钩子都不会响,这是最阴的坑,踩坑节还会再提。
改动二,路径变量换了。${ZCODE_PROJECT_DIR} 指向当前项目,插件脚本不该跟某个项目绑定,改用 ${ZCODE_PLUGIN_ROOT},指向插件自己的安装目录,脚本跟着箱子走。format.mjs 本体不用动,它执行时拿的 cwd 来自会话所在项目,prettier 照样从当前项目的 node_modules 里找。
顺带一个锦上添花的字段:statusMessage,写一句人话,当前存在设置页里展示,还不是钩子运行时的实时提示,example-plugin 模板里有示范。
发出去:建个市场
插件打好了,本地先跑通。建一个目录装市场和插件:
my-market/
├── marketplace.json
└── team-toolkit/
└── ……{
"name": "my-market",
"plugins": [
{
"name": "team-toolkit",
"source": "./team-toolkit",
"description": "团队工具箱",
"version": "0.1.0"
}
]
}设置 -> 插件 -> 创建 -> 添加插件市场,选本地目录,文档特意强调本地路径要真实存在。校验通过后 team-toolkit 出现在「个人」段,安装启用。之后改了插件代码不用重装,到市场源面板把该市场刷新一下就生效。
要发给团队,把市场推上 git 仓库:
team-plugins/ ← 推到 GitHub 的仓库
├── marketplace.json
└── plugins/
└── team-toolkit/
└── ……{
"name": "team-plugins",
"pluginRoot": "plugins",
"plugins": [
{ "name": "team-toolkit", "source": "./team-toolkit", "version": "0.1.0" }
]
}pluginRoot 是基准目录,设了之后相对 source 都从它起算,每条不用重复写 ./plugins/。队友在设置里添加这个仓库地址,你的全部插件一次到手。
source 一共七种写法,日常用得到的就三种:相对路径管本地调试,github 管仓库(能带 ref 和 path),git 管任意 URL。剩下的 directory、file、url、npm 留给特殊场景,官方市场自己走的是 url 加 zip 包加 sha256 校验的路子。
发新版本的动作是:改代码,plugin.json 的 version 提一位,marketplace.json 里那条的 version 也提一位。为什么两处都要动,踩坑节说。
踩坑
按踩中概率排。
版本是双头的。「检查更新」比新旧,最新版取自 marketplace.json 里的 version,已安装版取自插件自己的 plugin.json。发版只改了 plugin.json,队友那边永远显示没有更新,他手动卸载重装才能拿到新版。自建市场发版,两个文件都要提版本。检测走的是本地缓存,怀疑结果不准,先把市场源刷新一遍再看。
插件里的 hooks.json 结构不一样。就是前面说的那两层包装的差异,再提一遍因为它最阴。另外插件启用后,钩子只进新会话,正在跑的会话不热更新,跟改 config.json 要重开会话是同一个脾气。
敏感配置填不进去。userConfig 里标了 sensitive: true 的项,比如 api_key,界面上会提示「该值需要安全存储接入后才能配置」,当前没法在界面直接填。敏感值只能换个地方用:在 MCP 声明里写 ${user_config.键} 引用。
有的字段只是登记。清单里写 channels、lspServers、outputStyles、settings,运行时仅登记、不执行,诊断里会提示。不影响其他组件加载,但别指望插件带这些功能。
剩下的都是零碎规矩。命令文件名要匹配 ^[a-z0-9][a-z0-9_:-]{0,63}$;SKILL.md 的 description 上限 1024 字符,超了整个技能被丢弃;frontmatter 里非白名单的字段,比如 homepage,直接忽略,写了也白写。
还有两个环境类的。插件页要先打开工作区,没开会提示「打开一个工作区以管理插件」。官方目录的内容放在 CDN 和 GitHub 上,网络不畅时列表、详情、安装都可能加载失败,列表不对先点右上角刷新。
最后是安全,上一篇的提醒在这里要加倍:启用插件等于授信,第三方插件带的钩子和官方的一样照跑本地进程、读环境变量,装之前把它的 hooks.json 和脚本过一眼,不放心就别装。这条对任何市场都成立,官方市场也不例外。
四件套到齐
前三样攒的是你自己的生产力,第四样让它流动起来。装插件用的是别人攒的经验,发插件是把你攒的经验变成别人机器上能跑的东西。同事再来要技能,发他一个市场地址,让他自己点安装。
从 example-plugin 复制起步,十来分钟能把你最常被讨要的那个技能装箱。装完检查一处:description 写清楚什么时候用得上,这行字决定你的插件是被人用起来,还是在列表里吃灰。
关注我们,获取AI知识与资源
夜雨聆风