Skip to content

插件技术参考

本文提供 Claude Code 插件系统的完整技术规范。内容涵盖:五类插件组件(skills、agents、hooks、MCP servers、LSP servers)的结构与配置 schema;plugin.json 所有字段(含 userConfig 用户配置、channels 消息频道、monitors 后台监控);两个路径变量(CLAUDE_PLUGIN_ROOT 和 CLAUDE_PLUGIN_DATA)的用法与持久数据目录模式;插件安装作用域(user/project/local/managed);插件缓存机制与文件解析规则;CLI 命令(install/uninstall/enable/disable/update);以及常见问题调试清单。

想安装插件?查看发现并安装插件。想创建插件?查看创建插件。想分发插件?查看插件市场

插件是一个自包含目录,包含扩展 Claude Code 的组件:skills、agents、hooks、MCP servers 和 LSP servers。


插件组件参考

Skills

插件通过 skills 为 Claude Code 添加 /name 快捷方式,你或 Claude 都可以调用。

位置:插件根目录的 skills/commands/ 目录

文件格式:skills 是包含 SKILL.md 的子目录;commands 是平铺的 .md 文件

技能结构

skills/
├── pdf-processor/
│   ├── SKILL.md
│   ├── reference.md(可选)
│   └── scripts/(可选)
└── code-reviewer/
    └── SKILL.md

集成行为

  • 安装插件后,skills 和 commands 自动发现
  • Claude 根据任务上下文可自动调用
  • Skill 可在 SKILL.md 旁包含支撑文件

详情参见技能

Agents

插件可提供针对特定任务的子代理,Claude 在合适的时机自动调用。

位置:插件根目录的 agents/ 目录

文件格式:描述代理能力的 Markdown 文件

markdown
---
name: agent-name
description: 该代理的专长以及 Claude 何时调用它
model: sonnet
effort: medium
maxTurns: 20
disallowedTools: Write, Edit
---

代理的详细系统提示,描述其角色、专长和行为。

支持的 frontmatter 字段:namedescriptionmodeleffortmaxTurnstoolsdisallowedToolsskillsmemorybackgroundisolation(仅支持 "worktree")。

出于安全原因,插件代理不支持 hooksmcpServerspermissionMode

集成要点

  • Agents 出现在 /agents 界面
  • Claude 根据任务上下文可自动调用
  • 用户也可手动调用
  • 插件代理与内置 Claude 代理并存

详情参见子代理

Hooks

插件可提供响应 Claude Code 事件的事件处理器。

位置hooks/hooks.json 或内联在 plugin.json

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format-code.sh"
          }
        ]
      }
    ]
  }
}

插件 hooks 响应与用户定义 hooks 相同的生命周期事件:

事件触发时机
SessionStart会话开始或恢复时
UserPromptSubmit提交提示时,Claude 处理前
PreToolUse工具调用执行前,可阻止
PermissionRequest出现权限对话框时
PermissionDenied工具调用被自动模式分类器拒绝时。返回 {retry: true} 可告知模型重试被拒绝的工具调用
PostToolUse工具调用成功后
PostToolUseFailure工具调用失败后
NotificationClaude Code 发送通知时
SubagentStart子代理被生成时
SubagentStop子代理完成时
TaskCreated任务通过 TaskCreate 创建时
TaskCompleted任务被标记为完成时
StopClaude 完成响应时
StopFailure由于 API 错误导致对话轮结束时。输出和退出码被忽略
TeammateIdle代理团队中的队友即将进入空闲状态时
InstructionsLoadedCLAUDE.md 或 .claude/rules/*.md 文件加载到上下文时
ConfigChange会话中配置文件发生变化时
CwdChanged工作目录变更时(如 Claude 执行 cd 命令)。适合与 direnv 等工具配合做响应式环境管理
FileChanged被监视的文件在磁盘上发生变化时。matcher 字段指定要监视的文件名
WorktreeCreate通过 --worktreeisolation: "worktree" 创建 worktree 时。替换默认 git 行为
WorktreeRemove移除 worktree 时(会话退出或子代理完成时)
PreCompact上下文压缩前
PostCompact上下文压缩完成后
ElicitationMCP server 在工具调用中请求用户输入时
ElicitationResult用户响应 MCP elicitation 后、响应发回 server 前
SessionEnd会话终止时

Hook 类型command(执行 shell 命令)、http(POST 请求)、prompt(LLM 评估)、agent(带工具的验证代理)

MCP 服务器

插件可捆绑 MCP 服务器,将 Claude Code 与外部工具和服务连接。

位置.mcp.json 或内联在 plugin.json

json
{
  "mcpServers": {
    "plugin-database": {
      "command": "${CLAUDE_PLUGIN_ROOT}/servers/db-server",
      "args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config.json"],
      "env": {
        "DB_PATH": "${CLAUDE_PLUGIN_ROOT}/data"
      }
    },
    "plugin-api-client": {
      "command": "npx",
      "args": ["@company/mcp-server", "--plugin-mode"],
      "cwd": "${CLAUDE_PLUGIN_ROOT}"
    }
  }
}

集成行为

  • 插件启用后 MCP 服务器自动启动
  • 作为标准 MCP 工具出现在 Claude 工具集中
  • 与 Claude 现有工具无缝集成
  • 插件服务器可独立于用户 MCP 服务器配置

LSP 服务器

插件可提供 Language Server Protocol(LSP)服务器,让 Claude 在处理代码库时获得实时代码智能。

LSP 集成提供:

  • 即时诊断:每次编辑后立即看到错误和警告
  • 代码导航:跳转到定义、查找引用、悬停信息
  • 语言感知:代码符号的类型信息和文档

位置.lsp.json 或内联在 plugin.json

json
{
  "go": {
    "command": "gopls",
    "args": ["serve"],
    "extensionToLanguage": {
      ".go": "go"
    }
  }
}

必填字段

字段说明
command要执行的 LSP 二进制文件(必须在 PATH 中)
extensionToLanguage文件扩展名到语言标识符的映射

可选字段

字段说明
argsLSP 服务器的命令行参数
transport通信传输:stdio(默认)或 socket
env启动服务器时设置的环境变量
initializationOptions初始化时传给服务器的选项
settings通过 workspace/didChangeConfiguration 传递的设置
workspaceFolder服务器的工作区目录路径
startupTimeout等待服务器启动的最长时间(毫秒)
shutdownTimeout等待优雅关闭的最长时间(毫秒)
restartOnCrash服务器崩溃后是否自动重启
maxRestarts放弃前的最大重启次数

可用 LSP 插件

插件语言服务器安装命令
pyright-lspPyright(Python)pip install pyrightnpm install -g pyright
typescript-lspTypeScript Language Servernpm install -g typescript-language-server typescript
rust-lsprust-analyzer参见 rust-analyzer 安装文档

先安装语言服务器,再从市场安装对应插件。


插件安装作用域

安装插件时选择作用域,决定插件在哪里可用和谁能使用:

作用域设置文件使用场景
user~/.claude/settings.json跨所有项目的个人插件(默认)
project.claude/settings.json通过版本控制共享的团队插件
local.claude/settings.local.json项目专属插件,已 gitignore
managed托管设置管理员安装的插件(只读,只能更新)

插件使用与其他 Claude Code 配置相同的作用域系统。安装说明和作用域标志参见安装插件。作用域完整说明参见配置作用域


插件清单 Schema

.claude-plugin/plugin.json 定义插件的元数据和配置。清单是可选的——省略时,Claude Code 从默认位置自动发现组件,并从目录名推断插件名。只有在需要提供元数据或自定义组件路径时才需要清单。

完整 Schema

json
{
  "name": "plugin-name",
  "version": "1.2.0",
  "description": "简短插件描述",
  "author": {
    "name": "Author Name",
    "email": "author@example.com",
    "url": "https://github.com/author"
  },
  "homepage": "https://docs.example.com/plugin",
  "repository": "https://github.com/author/plugin",
  "license": "MIT",
  "keywords": ["keyword1", "keyword2"],
  "skills": "./custom/skills/",
  "commands": ["./custom/commands/special.md"],
  "agents": "./custom/agents/",
  "hooks": "./config/hooks.json",
  "mcpServers": "./mcp-config.json",
  "outputStyles": "./styles/",
  "lspServers": "./.lsp.json",
  "monitors": "./monitors.json"
}

必填字段

有清单时,name 是唯一必填字段。

字段类型说明示例
namestring唯一标识符(kebab-case,无空格)。用于组件命名空间,如插件 plugin-dev 的代理 agent-creator 在 UI 中显示为 plugin-dev:agent-creator"deployment-tools"

元数据字段

字段类型说明示例
versionstring语义化版本。若在市场条目中也设置了版本,plugin.json 优先。两处只需设置一处"2.1.0"
descriptionstring插件用途的简短说明"Deployment automation tools"
authorobject作者信息(nameemailurl{"name": "Dev Team"}
homepagestring文档 URL"https://docs.example.com"
repositorystring源码 URL"https://github.com/user/plugin"
licensestring许可证标识符"MIT""Apache-2.0"
keywordsarray发现标签["deployment", "ci-cd"]

组件路径字段

字段类型说明示例
skillsstring|array包含 <name>/SKILL.md 的技能目录自定义路径(替换默认 skills/"./custom/skills/"
commandsstring|array扁平 .md 技能文件或目录的自定义路径(替换默认 commands/"./custom/cmd.md"["./cmd1.md"]
agentsstring|array代理文件自定义路径(替换默认 agents/"./custom/agents/reviewer.md"
hooksstring|array|objecthooks 配置路径或内联配置"./my-extra-hooks.json"
mcpServersstring|array|objectMCP 配置路径或内联配置"./my-extra-mcp-config.json"
outputStylesstring|arrayoutput style 文件/目录自定义路径(替换默认 output-styles/"./styles/"
lspServersstring|array|objectLanguage Server Protocol 配置(代码跳转、引用查找等)"./.lsp.json"
monitorsstring|array|object后台 Monitor 配置,插件启用或调用插件中的 skill 时自动激活"./monitors.json"
userConfigobject启用插件时提示用户输入的可配置值。见用户配置见下文
channelsarray消息频道声明(Telegram、Slack、Discord 风格)。见 Channels见下文

用户配置

userConfig 字段声明插件启用时 Claude Code 向用户提示的配置项。替代让用户手动编辑 settings.json

json
{
  "userConfig": {
    "api_endpoint": {
      "description": "团队 API 端点",
      "sensitive": false
    },
    "api_token": {
      "description": "API 认证 token",
      "sensitive": true
    }
  }
}

Keys 必须是有效标识符。每个值可用 ${user_config.KEY} 替换语法,在 MCP、LSP 服务器配置、hook 命令以及(非 sensitive 值)skill 和 agent 内容中使用。值同时作为 CLAUDE_PLUGIN_OPTION_<KEY> 环境变量导出给插件子进程。非 sensitive 值存储在 settings.jsonpluginConfigs[<plugin-id>].options 中;sensitive 值存储在系统钥匙串(或无钥匙串时的 ~/.claude/.credentials.json)。钥匙串存储与 OAuth token 共享,总容量约 2KB,sensitive 值请保持简短。

Channels

channels 字段让插件声明一个或多个消息频道,将内容注入到对话中。每个频道绑定到插件提供的 MCP 服务器:

json
{
  "channels": [
    {
      "server": "telegram",
      "userConfig": {
        "bot_token": { "description": "Telegram bot token", "sensitive": true },
        "owner_id": { "description": "你的 Telegram 用户 ID", "sensitive": false }
      }
    }
  ]
}

server 字段必填,必须与插件 mcpServers 中的 key 匹配。可选的 per-channel userConfig 使用与顶层相同的 schema,让插件在启用时提示输入 bot token 或 owner ID。

路径行为规则

对于 skillscommandsagentsoutputStylesmonitors,自定义路径替换默认路径,而非追加。指定了 skills 就不再扫描默认 skills/ 目录;指定了 monitors 就不再加载默认 monitors/monitors.json

hooksmcpServerslspServers 的语义不同,多个来源会合并处理。

  • 所有路径必须相对于插件根目录,并以 ./ 开头
  • 自定义路径的组件使用相同的命名和命名空间规则
  • 可以数组形式指定多个路径
  • 要保留默认目录同时追加路径,把默认目录也包含进数组:"skills": ["./skills/", "./extras/"]
  • 当 skill 路径指向直接包含 SKILL.md 的目录(如 "skills": ["./"]),frontmatter 中的 name 字段决定调用名,未设置时用目录名作为后备

环境变量

Claude Code 提供两个路径变量,在 skill 内容、agent 内容、hook 命令、MCP/LSP 配置中均可内联替换,也会作为环境变量导出给 hook 进程和 MCP/LSP 服务器子进程。

${CLAUDE_PLUGIN_ROOT}:插件安装目录的绝对路径。用于引用插件捆绑的脚本、二进制文件和配置文件。插件更新后此路径会变,不要在此目录写入需跨更新保留的文件。

${CLAUDE_PLUGIN_DATA}:跨更新持久化的插件数据目录。用于已安装的依赖(如 node_modules 或 Python 虚拟环境)、生成的代码、缓存以及其他需要跨版本保留的文件。首次引用时自动创建。

json
{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/process.sh"
          }
        ]
      }
    ]
  }
}

持久数据目录

${CLAUDE_PLUGIN_DATA} 解析为 ~/.claude/plugins/data/{id}/,其中 {id} 是插件标识符(a-zA-Z0-9_- 以外的字符替换为 -)。以 formatter@my-marketplace 安装的插件,目录为 ~/.claude/plugins/data/formatter-my-marketplace/

常见用法:安装语言依赖并跨会话复用。数据目录跨插件版本存续,单靠目录存在检测无法发现依赖清单变更。推荐模式:对比捆绑的清单与数据目录中的副本,不同时重新安装。以下 SessionStart hook 在首次运行和插件更新带来新 package.json 时安装 node_modules

json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\""
          }
        ]
      }
    ]
  }
}

diff 在副本缺失或与捆绑版不同时返回非零,覆盖了首次运行和依赖变更更新两种场景。若 npm install 失败,末尾的 rm 删除已复制的清单,使下次会话重试。MCP 服务器脚本可引用持久化的 node_modules

json
{
  "mcpServers": {
    "routines": {
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/server.js"],
      "env": {
        "NODE_PATH": "${CLAUDE_PLUGIN_DATA}/node_modules"
      }
    }
  }
}

从最后一个作用域卸载插件时,数据目录自动删除。/plugin 界面会显示目录大小并在删除前提示。CLI 默认删除;用 --keep-data 可保留(如测试新版本后重装时使用)。


插件缓存与文件解析

插件通过两种方式指定:

  • 通过 claude --plugin-dir,仅在当前会话有效
  • 通过市场安装,在未来会话中持久可用

出于安全和验证目的,Claude Code 将市场插件复制到本地插件缓存~/.claude/plugins/cache),而不是就地使用。每个安装版本是缓存中的独立目录。更新或卸载插件后,旧版本目录被标记为孤儿并在 7 天后自动清理。宽限期让已加载旧版本的并发 Claude Code 会话继续运行,不会出错。Claude 的 Glob 和 Grep 工具搜索时跳过孤儿版本目录,不会返回过期的插件代码。

路径遍历限制

已安装插件无法引用其目录之外的文件。安装后,遍历到插件根目录之外的路径(如 ../shared-utils)无法工作,因为这些外部文件没有被复制到缓存。

处理外部依赖

需要访问目录外文件时,可在插件目录内创建指向外部文件的符号链接。符号链接在缓存中被保留(不解引用),运行时指向目标。以下命令在插件目录内创建指向共享工具目录的链接:

bash
ln -s /path/to/shared-utils ./shared-utils

插件目录结构

标准插件布局

完整的插件遵循以下结构:

enterprise-plugin/
├── .claude-plugin/           # 元数据目录(可选)
│   └── plugin.json           # 插件清单
├── skills/                   # Skills
│   ├── code-reviewer/
│   │   └── SKILL.md
│   └── pdf-processor/
│       ├── SKILL.md
│       └── scripts/
├── commands/                 # Skills(扁平 .md 格式)
│   ├── status.md
│   └── logs.md
├── agents/                   # 子代理定义
│   ├── security-reviewer.md
│   ├── performance-tester.md
│   └── compliance-checker.md
├── output-styles/            # Output style 定义
│   └── terse.md
├── monitors/                 # 后台监控配置
│   └── monitors.json
├── hooks/                    # Hook 配置
│   ├── hooks.json            # 主 hook 配置
│   └── security-hooks.json  # 附加 hooks
├── bin/                      # 插件可执行文件(加入 Bash 工具的 PATH)
│   └── my-tool               # 在 Bash 工具调用中可用裸命令调用
├── settings.json             # 插件默认设置
├── .mcp.json                 # MCP 服务器定义
├── .lsp.json                 # LSP 服务器配置
├── scripts/                  # Hook 和工具脚本
│   ├── security-scan.sh
│   ├── format-code.py
│   └── deploy.js
├── LICENSE                   # 许可证文件
└── CHANGELOG.md              # 版本历史

警告.claude-plugin/ 只放 plugin.json。所有其他目录(commands/agents/skills/hooks/)必须在插件根目录,而非 .claude-plugin/ 内。

文件位置参考

组件默认位置用途
清单.claude-plugin/plugin.json插件元数据和配置(可选)
Skillsskills/包含 <name>/SKILL.md 结构的技能
Commandscommands/扁平 Markdown 格式的技能(新插件推荐用 skills/
Agentsagents/子代理 Markdown 文件
Output stylesoutput-styles/Output style 定义
Hookshooks/hooks.jsonHook 配置
MCP 服务器.mcp.jsonMCP 服务器定义
LSP 服务器.lsp.json语言服务器配置
Monitorsmonitors/monitors.json后台监控配置
可执行文件bin/加入 Bash 工具 PATH 的可执行文件,插件启用时可在 Bash 工具调用中用裸命令调用
默认设置settings.json插件启用时应用的默认配置。目前只支持 agentsubagentStatusLine key

CLI 命令参考

plugin install

从可用市场安装插件。

bash
claude plugin install <plugin> [options]

参数<plugin> 为插件名或 plugin-name@marketplace-name(指定市场)

选项说明默认
-s, --scope <scope>安装作用域:userprojectlocaluser

--scope project 将插件写入 .claude/settings.jsonenabledPlugins,克隆项目仓库的所有人都能用。

bash
# 安装到 user 作用域(默认)
claude plugin install formatter@my-marketplace

# 安装到 project 作用域(团队共享)
claude plugin install formatter@my-marketplace --scope project

# 安装到 local 作用域(gitignored)
claude plugin install formatter@my-marketplace --scope local

plugin uninstall

移除已安装的插件。

bash
claude plugin uninstall <plugin> [options]
选项说明默认
-s, --scope <scope>从指定作用域卸载:userprojectlocaluser
--keep-data保留插件的持久数据目录

别名removerm

从最后一个作用域卸载时,默认删除 ${CLAUDE_PLUGIN_DATA} 目录。测试新版本后需重装时,用 --keep-data 保留数据。

plugin enable

启用已禁用的插件。

bash
claude plugin enable <plugin> [options]
选项说明默认
-s, --scope <scope>在指定作用域启用:userprojectlocaluser

plugin disable

禁用插件而不卸载。

bash
claude plugin disable <plugin> [options]
选项说明默认
-s, --scope <scope>在指定作用域禁用:userprojectlocaluser

plugin update

更新插件到最新版本。

bash
claude plugin update <plugin> [options]
选项说明默认
-s, --scope <scope>更新指定作用域的插件:userprojectlocalmanageduser

调试与开发工具

调试命令

bash
claude --debug

可以看到:哪些插件正在加载、插件清单中的错误、skill/agent/hook 注册情况、MCP 服务器初始化状态。

故障排查题

问题原因解决方案
插件无法加载plugin.json 无效运行 claude plugin validate/plugin validate 检查清单、frontmatter 和 hooks.json 的语法错误
Skills 未出现目录结构错误确保 skills/commands/ 在插件根目录,而非 .claude-plugin/
Hooks 未触发脚本没有执行权限运行 chmod +x script.sh
MCP 服务器失败缺少 ${CLAUDE_PLUGIN_ROOT}对所有插件路径使用此变量
路径错误使用了绝对路径所有路径必须相对且以 ./ 开头
LSP Executable not found in $PATH语言服务器未安装安装对应二进制文件(如 npm install -g typescript-language-server typescript

错误示例

清单验证错误

  • Invalid JSON syntax: Unexpected token } in JSON at position 142:检查缺失逗号、多余逗号或未引号字符串
  • Plugin has an invalid manifest file at .claude-plugin/plugin.json. Validation errors: name: Required:必填字段缺失
  • Plugin has a corrupt manifest file at .claude-plugin/plugin.json. JSON parse error:...:JSON 语法错误

插件加载错误

  • Warning: No commands found in plugin my-plugin custom directory: ./cmds.:命令路径存在但没有有效的命令文件
  • Plugin directory not found at path: ./plugins/my-plugin.:marketplace.json 中的 source 路径指向不存在的目录
  • Plugin my-plugin has conflicting manifests: both plugin.json and marketplace entry specify components.:移除重复的组件定义,或删除市场条目中的 strict: false

Hook 排查

Hook 脚本未执行

  1. 检查脚本可执行权限:chmod +x ./scripts/your-script.sh
  2. 验证 shebang 行:第一行应为 #!/bin/bash#!/usr/bin/env bash
  3. 检查路径使用了 ${CLAUDE_PLUGIN_ROOT}"command": "${CLAUDE_PLUGIN_ROOT}/scripts/your-script.sh"
  4. 手动测试脚本:./scripts/your-script.sh

Hook 未在期望事件触发

  1. 确认事件名大小写正确(区分大小写):PostToolUse 而非 postToolUse
  2. 检查 matcher 模式匹配目标工具:"matcher": "Write|Edit"
  3. 确认 hook 类型有效:commandhttppromptagent

MCP 服务器排查

服务器未启动

  1. 检查命令是否存在且可执行
  2. 确认所有路径使用了 ${CLAUDE_PLUGIN_ROOT} 变量
  3. claude --debug 查看初始化错误
  4. 在 Claude Code 外手动测试服务器

服务器工具未出现

  1. 确认服务器在 .mcp.jsonplugin.json 中正确配置
  2. 验证服务器正确实现了 MCP 协议
  3. 检查调试输出中是否有连接超时

版本管理

遵循语义化版本:

json
{
  "name": "my-plugin",
  "version": "2.1.0"
}

版本格式MAJOR.MINOR.PATCH

  • MAJOR:破坏性变更(不兼容的 API 变更)
  • MINOR:新功能(向后兼容的新增)
  • PATCH:Bug 修复(向后兼容的修复)

最佳实践

  • 第一个稳定版本从 1.0.0 开始
  • 分发变更前在 plugin.json 中更新版本
  • CHANGELOG.md 记录变更历史
  • 测试阶段用预发布版本:如 2.0.0-beta.1

相关资源


常见问题

Q: plugin.json 清单是必须的吗?

不是必须的。省略时 Claude Code 从默认位置自动发现组件(skills/commands/agents/ 等),并从目录名推断插件名。只有需要自定义元数据、指定非默认路径或配置 userConfig/channels 等高级功能时才需要清单。

Q: ${CLAUDE_PLUGIN_ROOT}${CLAUDE_PLUGIN_DATA} 有什么区别?

${CLAUDE_PLUGIN_ROOT} 指向插件当前安装版本的目录(只读,更新后路径变更);${CLAUDE_PLUGIN_DATA} 是跨更新持久化的数据目录(可读写,更新后路径不变)。脚本和配置文件用 ROOT,依赖(node_modules)和运行时数据用 DATA。

Q: 插件组件加载出来了但找不到正确位置,如何排查?

运行 claude --debug 查看插件加载详情,检查是否有"No commands found"或"Plugin directory not found"错误。最常见问题是目录结构错误:skills/agents/hooks/ 必须在插件根目录,不能放在 .claude-plugin/ 内。