乐于分享
好东西不私藏

ZCode 插件实战:装插件是入门,发插件才是本事

ZCode 插件实战:装插件是入门,发插件才是本事

装过插件的 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 和脚本过一眼,不放心就别装。这条对任何市场都成立,官方市场也不例外。

四件套到齐

手段
管什么
生效方式
AGENTS.md
项目约定
每个会话都加载
Skill
任务方法
用到才加载
Hook
强制规矩
流程上拦截
Plugin
打包分发
装到别人机器上

前三样攒的是你自己的生产力,第四样让它流动起来。装插件用的是别人攒的经验,发插件是把你攒的经验变成别人机器上能跑的东西。同事再来要技能,发他一个市场地址,让他自己点安装。

从 example-plugin 复制起步,十来分钟能把你最常被讨要的那个技能装箱。装完检查一处:description 写清楚什么时候用得上,这行字决定你的插件是被人用起来,还是在列表里吃灰。

———— END ————

  关注我们,获取AI知识与资源  

细说说DeepSeek开源的Agent框架deepseek-harness

智谱 ZCode 上手实录:我决定卸载Claude和Codex

Agentic Loop 在智谱 ZCode 里是怎么跑起来的

ZCode 远程能力全解析:远程开发、远程控制与机器人通道

ZCode 定时任务和闲时任务让我真正省下来时间