乐于分享
好东西不私藏

Claude Code 源码揭秘:Commands、Skills、Agents、Plugins,四套扩展体系各管一摊

Claude Code 源码揭秘:Commands、Skills、Agents、Plugins,四套扩展体系各管一摊

💡 阅读前记得关注+星标,及时获取更新推送

「Claude Code 源码揭秘」系列的第九篇,上一篇《Claude Code 源码揭秘:6 个默认agent干活 + 3 个组件管事,多 Agent 协作的工业级实现,不污染上下文》,说的是怎么把任务分出去执行。但只讲了内置的子代理,如果你想给 Claude Code 加点自定义能力呢?

用 Claude Code 的时候,你可能注意到它有好几种扩展方式。

输入 /help 能看到一堆斜杠命令,这是 Commands。项目里放一个 .claude/skills/ 目录,里面扔几个 Markdown 文件,Claude 就能用新技能,这是 Skills。在 .claude/agents/ 目录放子代理定义,能让 Claude 分出独立的上下文去处理复杂任务,这是 Agents。还能装插件,功能更强大,这是 Plugins。

四套东西,看起来功能有重叠,为什么不统一成一套?

翻 src/ 目录下这几个模块的代码,才明白:它们各管一摊,解决的是不同层次的扩展需求。就像你盖房子,有的地方用钉子就够了,有的地方得上螺栓,有的地方要焊死。不是工具越重越好,而是看场景。

我之前做过一个企业级的工作流平台,也遇到过类似的设计抉择。当时我们只做了一套插件系统,结果轻量级需求嫌它太重,复杂需求又嫌它不够灵活。后来拆成了「快捷操作 + 流程模板 + 子代理 + 完整插件」四层,用户反馈好了很多。Claude Code 这套设计,思路是一样的。

先看最轻量的 Commands。

Commands —— 一个函数搞定的斜杠命令

/help/clear/exit… 这些都是 Commands。实现起来非常简单,就是一个接口:

interface SlashCommand {  name: string;  aliases?: string[];       // 别名,比如 /? 是 /help 的别名  description: string;  category: CommandCategory;  execute: (ctx: CommandContext) => Promise<CommandResult> | CommandResult;}

注册也简单,往注册表里塞就行:

commandRegistry.register({  name: 'clear',  aliases: ['cls'],  description: 'Clear conversation history',  category: 'general',  execute: (ctx) => {    ctx.session.clearMessages();    return { success: true };  }});

注册表用单例模式,支持别名的两级查找:

get(name: string) {  // 先直接找  let cmd = this.commands.get(name);  if (cmd) return cmd;  // 找不到,看看是不是别名  const aliasedName = this.aliases.get(name);  if (aliasedName) return this.commands.get(aliasedName);}

Commands 的初始化是延迟加载的,按模块分组注册:

export function initializeCommands(): void {  if (initialized) return;  registerGeneralCommands();      // /help, /clear, /exit  registerSessionCommands();       // 会话管理  registerConfigCommands();        // 配置  registerToolsCommands();         // 工具管理  // ... 其他模块  initialized = true;}

这样做的好处是启动快——不需要的模块不加载。我之前做的那个工作流平台,一开始是启动时把所有插件都加载进来,结果启动要 30 秒。后来改成延迟加载,启动时间砍到 3 秒。

Commands 的设计哲学很清晰:纯 UI 层的快捷操作,不需要复杂的生命周期管理。一个 execute 函数,进去出来,完事。

翻源码能看到,Commands 分三种类型(chunks.100.mjs 第 2145 行):

switch (G.type) {  case "local-jsx": // React组件形式的命令,比如 /login 的认证 UI  case "local":     // 本地函数命令,比如 /clear  case "prompt":    // AI 提示命令,比如 /review}

local 最简单,调一个函数返回字符串。local-jsx 能渲染 React 组件,适合需要交互 UI 的命令。prompt 最有意思,它会生成一段提示词让 AI 处理,还能限制这个命令能用哪些工具。比如 /review 命令,内部会生成代码审查的提示词,然后调用模型。

命令的来源也有三个层次(chunks.100.mjs 第 371 行):

// 命令加载函数J2A = L0(async () => {    let A = Gj2(S4(), "commands"),           // 全局自定义命令目录      B = Gj2(dA(), ".claude", "commands");  // 用户自定义命令 ~/.claude/commands    return [...await e_2(A, B), ...gN5()].filter((I) => I.isEnabled())})
  • • 内置命令:gN5() 数组里预定义的,比如 /help/clear/login
  • • 全局自定义命令:S4() 路径下的 commands 目录
  • • 用户自定义命令:~/.claude/commands 目录

后面会详细讲怎么自己写命令。

Skills —— 带元数据的提示词模板

Skills 本质上是 Markdown 文件 + YAML frontmatter,但它比简单的文本模板要强大。

看源码里的 SkillDefinition 接口(src/tools/skill.ts):

interface SkillDefinition {  name: string;  description: string;  prompt: string;  location: 'user' | 'project' | 'builtin';  filePath?: string;  // frontmatter 解析出来的字段  allowedTools?: string[];           // 允许的工具列表  argumentHint?: string;             // 参数提示  whenToUse?: string;                // 何时使用  version?: string;                  // 版本  model?: string;                    // 模型  disableModelInvocation?: boolean;  // 禁用模型调用}

一个典型的 Skill 文件长这样:

---name: code-reviewdescription: Review code changesallowed-tools: bash,read,grepargument-hint: file path or PR numberwhen-to-use: When user asks to review code or PRversion: 1.0.0model: claude-3-opus---## Code Review SkillWhen reviewing code, focus on:1. Potential bugs2. Performance issues3. Best practices violations

frontmatter 里的字段都有实际用途:

  • • allowed-tools:限制这个 Skill 能用哪些工具,逗号分隔。安全考量——不是每个 Skill 都需要完整的工具权限
  • • argument-hint:告诉用户怎么传参数
  • • when-to-use:让模型知道什么时候该调用这个 Skill
  • • model:指定用哪个模型执行
  • • disable-model-invocation:禁止模型自动调用,只能用户手动触发

这个设计让我想起我们之前做的「提示词仓库」。产品经理写好提示词模板,研发同事直接调用。Claude Code 的 Skills 加了元数据控制,能限制工具权限、指定模型,比简单的提示词模板更安全也更灵活。

Skills 不只是单个文件,可以是完整的目录结构。看看 ~/.claude/skills/ 下面几个典型的 Skill:

plantuml/├── SKILL.md              # 主入口,包含 frontmatter 和提示词├── scripts/              # 辅助脚本│   ├── check_setup.py    # 环境检查脚本│   ├── convert_puml.py   # 转换脚本│   └── ...├── references/           # 参考资料│   ├── sequence_diagrams.md│   ├── class_diagrams.md│   ├── styling_guide.md│   └── ...└── examples/             # 示例code-review-assistant/├── SKILL.md├── scripts/│   └── complexity_checker.py└── ...

SKILL.md 里会引用这些资源:

## Setup VerificationRun the setup checker:python scripts/check_setup.py## Syntax ReferenceConsult references for syntax:- references/toc.md: Navigation hub- references/sequence_diagrams.md: Detailed syntax

Claude 执行这个 Skill 时,会根据提示词里的路径,用 Read 工具读取脚本和参考资料。这样一个复杂的 Skill 就能包含完整的知识库和工具链。

不过源码里的加载逻辑只处理 .md 文件:

function loadSkillsFromPath(dirPath: string, location: string, recursive: boolean): void {  for (const entry of entries) {    if (entry.isDirectory() && recursive) {      loadSkillsFromPath(fullPath, location, recursive);  // 递归进入子目录    } else if (entry.isFile() && entry.name.endsWith('.md')) {      // 只加载 .md 文件作为 Skill 入口      const { metadata, body } = parseFrontmatter(content);      registerSkill({ name, description, prompt: body, ... });    }  }}

所以目录里的 scripts/references/ 这些子目录不会被代码自动加载——它们是被 SKILL.md 的 prompt 内容「引用」的,Claude 在执行时通过 Read 工具去读取这些文件。

这个设计挺聪明的:代码保持简单(只解析 .md 文件),但 Skill 的能力不受限(可以引用任意文件)。

Skills 有三个来源,按优先级从低到高:

builtin(内置)→ user(用户级,~/.claude/skills/)→ project(项目级,./.claude/skills/)

加载的时候按这个顺序,后面的会覆盖前面的:

// 1. 先加载内置(最低优先级)loadSkillsFromPath(builtinSkillsDir, 'builtin', true);// 2. 再加载用户级(中等优先级)loadSkillsFromDirectory(claudeDir, 'user', false);// 3. 最后加载项目级(最高优先级)loadSkillsFromDirectory(projectClaudeDir, 'project', false);

这个设计让你可以在项目里覆盖全局的 Skill。比如你全局有一个 code-review Skill,但某个项目有特殊的代码规范,就在项目里放一个同名的 Skill 覆盖它。

Skills 还有缓存机制,5 分钟 TTL:

const CACHE_TTL = 5 * 60 * 1000;function isCacheExpired(): boolean {  return Date.now() - lastLoadTime > CACHE_TTL;}function ensureSkillsLoaded(): void {  if (!skillsLoaded || isCacheExpired()) {    initializeSkillsAndCommands();  }}

5 分钟内多次调用同一个 Skill,不会重复读文件。但你改了 Skill 文件,最多等 5 分钟就能生效。这是个很实用的平衡。

Skills 通过工具系统被调用。源码里的 SkillToolsrc/tools/skill.ts)实现了完整的权限检查:

class SkillTool extends BaseTool<SkillInput, any> {  async checkPermissions(input: SkillInput): Promise<{    behavior: 'allow' | 'deny' | 'ask';    message?: string;    suggestions?: string[];  }> {    const skillDef = skillRegistry.get(skillName);    if (!skillDef) {      return {        behavior: 'deny',        message: `Skill "${input.skill}" not found`,        suggestions: available.slice(0, 5), // 返回前5个可用技能作为建议      };    }    // 检查是否禁用模型调用    if (skillDef.disableModelInvocation) {      return {        behavior: 'deny',        message: `Skill "${skillDef.name}" has model invocation disabled`,      };    }    return { behavior: 'allow' };  }  async execute(input: SkillInput) {    const skillDef = skillRegistry.get(input.skill);    let skillPrompt = skillDef.prompt;    if (input.args) {      skillPrompt = `${skillPrompt}\n\n**Arguments:**\n${input.args}`;    }    return {      success: true,      output: `<command-message>The "${skillDef.name}" skill is loading</command-message>\n\n<skill name="${skillDef.name}">${skillPrompt}</skill>`,      allowedTools: skillDef.allowedTools,      model: skillDef.model,    };  }}

注意几个设计细节:

  1. 1. 权限检查在执行前进行,disableModelInvocation 可以阻止模型自动调用
  2. 2. 返回的 allowedTools 会限制后续调用能用哪些工具
  3. 3. 输出格式用 <command-message> 和 <skill> 标签包裹,方便 UI 渲染

Agents —— 独立上下文的子代理

Skills 和 Agents 长得很像,都是 Markdown + YAML frontmatter。但它们有个关键区别:Agents 运行在独立的上下文窗口里,不会污染主对话

这个特性让 Agents 特别适合复杂的多步骤任务。比如代码审查,可能要读几十个文件、跑测试、分析依赖关系… 如果这些中间过程都塞进主对话,上下文很快就爆了。用 Agent 的话,它在自己的小房间里干活,完事后只把结论带回来。

社区已经有人整理了 100+ 个开箱即用的 Agents,比如 VoltAgent/awesome-claude-code-subagents,涵盖代码审查、安全扫描、性能分析等各种场景。

Agent 的定义格式和 Skills 几乎一样:

---name: security-auditordescription: When user asks for security review or vulnerability scantools: Read, Grep, Glob, Bash---You are a senior security engineer with expertise in penetration testing.## Audit Process1. Scan for dangerous patterns (SQL injection, XSS, etc.)2. Check authentication and authorization logic3. Review sensitive data handling4. Generate findings report with severity levels## Output Format- Severity: Critical/High/Medium/Low- Location: file:line- Description: What's the issue- Fix: How to remediate

关键字段:

  • • name:Agent 名称,调用时用 subagent_type 参数指定
  • • description:何时使用(很重要,模型据此判断调用时机)
  • • tools:可用工具列表,逗号分隔

注意 Agent 定义里没有 model 字段,它会继承主会话的模型设置。

上下文继承范围

内置代理有四种上下文继承类型:full(全部继承)、summary(压缩继承,30000 tokens 内)、minimal(只继承最近 5 条历史)、isolated(完全隔离)。详细机制参考上一篇《Claude Code 源码揭秘:6 个默认agent干活 + 3 个组件管事,多 Agent 协作的工业级实现,不污染上下文》。

自定义 Agent 目前没有开放 contextInheritance 字段,使用默认的 summary 继承策略(源码 src/agents/context.ts 第 198 行的 DEFAULT_INHERITANCE_CONFIG 定义)。这意味着自定义 Agent 能拿到主对话的部分上下文(最近的历史、文件、工具结果),但不是全部。

自定义 Agent 怎么被调用?

有两种方式:

一是模型自动调用。模型会根据 description 字段判断当前任务是否适合用这个 Agent。比如你定义了 description: When user asks for security review,用户说「帮我检查代码安全」,模型就会自动通过 Task 工具调用这个 Agent。

二是用户显式指定。你可以直接告诉 Claude:「用 security-auditor 这个 agent 帮我审查代码」,Claude 会识别出要用的 Agent 并调用。

调用时,Claude 内部会执行类似这样的操作:

Task({  subagent_type: "security-auditor",  // 你定义的 agent name  prompt: "审查 src/ 目录的代码安全性"})

Agent 会在独立的上下文窗口里执行任务,完成后把结果返回给主对话。

存储位置和 Skills 类似,也是两级:

~/.claude/agents/      # 全局,所有项目可用.claude/agents/        # 项目级,优先级更高

Agents 通过 Task 工具调用。看系统提示词里内置的那些 Agent:

- Bash: Command execution specialist- Explore: Fast agent for exploring codebases- Plan: Software architect agent for designing plans- pr-review-toolkit:code-reviewer: Code review specialist- pr-review-toolkit:silent-failure-hunter: Error handling checker

pr-review-toolkit:xxx 这种命名格式说明是插件注册的 Agent。Plugins 可以通过 context.agents.register() 动态注册 Agent,这比手动放文件更灵活。

Plugins —— 完整的插件生态

如果说 Commands 是钉子,Skills 是螺栓,那 Plugins 就是焊接——最重的武器,也是最灵活的。

一个插件长这样:

export default {  metadata: {    name: 'my-plugin',    version: '1.0.0',    description: 'My awesome plugin',    engines: {      'claude-code': '^2.0.0',      'node': '>=18.0.0'    },    dependencies: {      'another-plugin': '^1.0.0'    }  },  async init(context) {    // 初始化,只执行一次  },  async activate(context) {    // 激活,每次启动都执行    context.tools.register({ ... });    context.commands.register({ ... });    context.hooks.on('beforeMessage', async (msg) => { ... });  },  async deactivate() {    // 清理资源  }};

看到没?完整的生命周期:init → activate → deactivate。还有依赖管理、版本检查。

插件运行在沙箱环境里,只能访问受限的 API:

interface PluginContext {  pluginName: string;  pluginPath: string;  config: PluginConfigAPI;      // 配置存储  logger: PluginLogger;         // 日志  fs: PluginFileSystemAPI;      // 文件系统(受限)  tools: PluginToolAPI;         // 注册工具  commands: PluginCommandAPI;   // 注册命令  skills: PluginSkillAPI;       // 注册 Skill  hooks: PluginHookAPI;         // 钩子}

文件系统 API 是受限的,只能在插件自己的目录里操作:

const fsAPI: PluginFileSystemAPI = {  readFile: async (relativePath) => {    const fullPath = path.join(pluginPath, relativePath);    // 安全检查:不能跳出插件目录    if (!fullPath.startsWith(pluginPath)) {      throw new Error('Access denied: path outside plugin directory');    }    return fs.promises.readFile(fullPath, 'utf-8');  },  // writeFile 同理};

这个路径检查很重要。我之前做安全评估的时候,见过不少插件系统被路径遍历攻击搞穿的案例。插件作者写一个 ../../etc/passwd,如果没有检查,就能读到系统文件了。Claude Code 这里做得很严谨。

依赖管理是另一个亮点。支持 semver 版本规范:

class VersionChecker {  static satisfies(version: string, range: string): boolean {    if (range === '*') return true;    const v = this.parseVersion(version);    // ^1.0.0 - 兼容主版本    if (range.startsWith('^')) {      const r = this.parseVersion(range.slice(1));      return v.major === r.major && this.compareVersion(v, r) >= 0;    }    // ~1.0.0 - 兼容次版本    if (range.startsWith('~')) {      const r = this.parseVersion(range.slice(1));      return v.major === r.major && v.minor === r.minor && v.patch >= r.patch;    }    // ...  }}

加载插件的时候,会做拓扑排序,先加载依赖,再加载自己:

const loadWithDeps = async (name: string): Promise<void> => {  if (loaded.has(name)) return;  // 循环依赖检测  if (loading.has(name)) {    throw new Error(`Circular dependency detected: ${name}`);  }  loading.add(name);  // 先加载依赖  for (const depName of dependencies) {    await loadWithDeps(depName);  }  // 再加载自己  await this.load(name);  loaded.add(name);  loading.delete(name);};

这个拓扑排序的实现很经典,用两个 Set 来检测循环依赖。我之前做构建系统的时候也写过类似的代码,思路是一样的——loading 集合记录「正在加载」的节点,如果递归的时候发现自己已经在 loading 里了,说明有环。

还有热重载支持:

enableHotReload(name: string): void {  const watcher = fs.watch(state.path, { recursive: true }, async (eventType, filename) => {    // 忽略 node_modules 和隐藏文件    if (filename.includes('node_modules') || filename.startsWith('.')) return;    // 只监听 JS/TS 文件    if (!/\.(js|ts|mjs|cjs)$/.test(filename)) return;    // 防抖:500ms 后再重载    clearTimeout(existingTimeout);    setTimeout(() => this.reload(name), 500);  });}

500ms 防抖是个聪明的设计。改代码的时候,IDE 可能会触发多次文件变更事件(保存、格式化、自动修复…),如果每次都重载,太浪费了。等 500ms,确认没有新的变更了,再重载一次。

我之前做过一个前端热更新的工具,最开始没加防抖,结果保存一次文件触发三四次重载,浏览器闪得人头晕。加了 300ms 防抖后就正常了。

四层扩展体系的分工

把四个模块放一起对比:

从下到上,复杂度递增,能力也递增。

Commands 最轻,适合「点一下就完事」的操作。清空聊天记录、显示帮助信息、退出程序… 这些用 Commands 刚好。

Skills 稍重一点,适合「可复用的提示词」。代码审查模板、文档生成模板、特定领域的指令集… 这些用 Skills 很合适。而且 Skills 支持项目级覆盖,同一个技能在不同项目里可以有不同的实现。

Agents 再重一层,适合「需要独立上下文的复杂任务」。代码审查要读几十个文件、安全扫描要跑一堆检查、性能分析要做多轮测试… 这些用 Agents,中间过程不污染主对话。

Plugins 最重,适合「需要完整功能的扩展」。如果你要加一个新的工具、注册新的命令、挂钩子、持久化配置… 那就得用 Plugins。Plugins 还能注册 Commands、Skills、Agents,是整个扩展体系的「万能胶水」。

这种分层设计的好处是:简单的事情简单做,复杂的事情有地方做。不会出现「为了加一个快捷命令,要写一整套插件」的情况。

用户怎么自己写扩展?

自定义 Commands

在 ~/.claude/commands/ 或项目的 .claude/commands/ 目录下创建 Markdown 文件就行。

看源码里的加载逻辑(src/tools/skill.ts):

export function loadSlashCommandsFromDirectory(dir: string): void {  const commandsDir = path.join(dir, 'commands');  const files = fs.readdirSync(commandsDir);  for (const file of files) {    if (file.endsWith('.md')) {      const content = fs.readFileSync(fullPath, 'utf-8');      const name = file.replace('.md', '');      // 解析描述(第一行如果是 HTML 注释)      let description: string | undefined;      const lines = content.split('\n');      if (lines[0]?.startsWith('<!--') && lines[0].endsWith('-->')) {        description = lines[0].slice(4, -3).trim();      }      slashCommandRegistry.set(name, { name, description, content, path: fullPath });    }  }}

文件名就是命令名。创建 deploy.md 后,就可以用 /deploy 来调用。

自定义命令支持参数替换,源码里的实现:

// 替换参数占位符// 替换 $1, $2, ...args.forEach((arg, i) => {  content = content.replace(new RegExp(`\\$${i + 1}`, 'g'), arg);});// 替换 $@ (所有参数)content = content.replace(/\$@/g, args.join(' '));

写个例子:

<!-- Review a specific file -->Please review the file: $1Focus on:- Code quality- Potential bugs- Performance issues

调用 /review src/main.ts$1 会被替换成 src/main.ts。用 $@ 可以获取所有参数。

这个设计挺聪明的——用户不需要写代码,只需要写 Markdown,就能定义自己的快捷命令。

自定义 Skills

在 ~/.claude/skills/ 或项目的 .claude/skills/ 目录下创建 Markdown 文件:

---name: my-reviewdescription: My custom code reviewallowed-tools: read,grepwhen-to-use: When user asks to review codeargument-hint: file path---When reviewing code, check these items:- Error handling- Logging- Input validation

Skills 比 Commands 多了 frontmatter 元数据的支持,可以限制工具权限、指定模型、设置使用条件。

Skills 和 Commands 的区别:

  • • Commands 是「用户主动调用」,输入 /xxx 触发
  • • Skills 是「模型主动调用」,模型判断需要时自动调用 Skill 工具

自定义 Agents

在 ~/.claude/agents/ 或项目的 .claude/agents/ 目录下创建 Markdown 文件:

---name: perf-analyzerdescription: When user asks to analyze performance or optimize codetools: Read, Grep, Bash, Glob---You are a performance optimization specialist.## Analysis Process1. Profile the target code2. Identify bottlenecks3. Suggest optimizations with benchmarks## Tools Usage- Use Bash to run profiling commands- Use Read to examine hot paths- Use Grep to find similar patterns

Agents 和 Skills 的格式一样,但存储位置不同(agents/ vs skills/),调用机制也不同(Task 工具 vs Skill 工具)。

自定义 Plugins

写 Plugin 稍微复杂一点,需要创建一个目录结构:

my-plugin/├── package.json└── index.js

index.js 里导出一个对象,包含 metadatainitactivatedeactivate 这些属性和方法。

Plugin 的门槛最高,但能力也最强——可以注册新工具、挂钩子、持久化状态。

你是否曾困惑:Plugins 可以注册 Skills,自己手动也可以创建 Skills,那到底区别在哪?

手动创建 vs Plugins 创建:同一个东西,两种玩法

有意思的是,Commands 和 Skills 都可以通过两种方式创建:手动放文件,或者通过 Plugins 代码注册。这两种方式能力差别很大。

先看 Commands 的差异:

// 手动创建的 Command(纯 Markdown,只能做参数替换)// ~/.claude/commands/review.md<!-- Review code changes -->Please review: $1Focus on: $@// 通过 Plugin 创建的 Command(有 execute 函数,可执行任意代码)context.commands.register({  name: 'review',  description: 'Review code changes',  usage: 'review <file> [aspects...]',  examples: ['review src/main.ts security performance'],  execute: async (args, context) => {    const file = args[0];    const content = await context.fs.readFile(file);    // 可以调用外部 API、访问数据库、做复杂逻辑...    await someComplexLogic(content);  }});

再看 Skills 的差异。源码里有两个不同的 SkillDefinition 接口:

// 手动创建的 Skill 定义(from src/tools/skill.ts)interface SkillDefinition {  name: string;  description: string;  prompt: string;  location: 'user' | 'project' | 'builtin';  allowedTools?: string[];  // ... 其他 frontmatter 字段}// 通过 Plugin 创建的 Skill 定义(from src/plugins/index.ts)interface SkillDefinition {  name: string;  description: string;  prompt: string;  category?: string;         // 多了分类  examples?: string[];       // 多了示例  parameters?: Array<{       // 多了参数定义!    name: string;    description: string;    required?: boolean;    type?: string;  }>;}

Plugins 方式多了 parameters 字段,可以定义结构化的参数,比手动方式的 $1$@ 高级多了。

还有一个关键区别:生命周期管理。

// Plugins 支持动态注册和卸载const commandsAPI: PluginCommandAPI = {  register: (command) => {    commands.push(command);    this.emit('command:registered', name, command);  // 触发事件  },  unregister: (commandName) => {    commands.splice(index, 1);    this.emit('command:unregistered', name, commandName);  },  getRegistered: () => [...commands],};

手动创建的 Commands/Skills 改了文件要等 5 分钟缓存过期,Plugins 创建的可以实时 unregister() 卸载。

两种方式的核心差异:

  • • 实现方式:手动是声明式(Markdown),Plugins 是命令式(JavaScript)
  • • Commands 能力:手动只能做 $1$@ 参数替换;Plugins 有 execute 函数,可执行任意代码
  • • Skills 能力:手动靠 frontmatter;Plugins 支持 parameters 结构化参数定义
  • • 生命周期:手动没有生命周期,文件存在即生效;Plugins 有 init → activate → deactivate
  • • 动态性:手动改文件等缓存过期;Plugins 支持 unregister() 动态卸载
  • • 沙箱隔离:手动无隔离;Plugins 有受限的 PluginContext

什么时候用哪种?手动创建适合简单的提示词模板,快速试验;Plugins 适合需要执行代码逻辑、依赖管理、钩子能力的场景。打个比方:手动创建是「便签纸」,写两句话贴上去就能用;Plugins 是「APP」,功能强大但要开发。

翻完这部分代码,我最大的感受是:扩展系统不是越统一越好,而是要分层。不同复杂度的需求,用不同重量级的方案。

Claude Code 这套四层体系,给了不同层次的扩展能力:想快捷就用 Commands,想复用提示词就用 Skills,想隔离上下文就用 Agents,想完整控制就用 Plugins。各取所需,互不干扰。

我最后想说一下,最新claude code版本中comands和skills概念已经统一啦,《Claude Code 2.1.2-2.1.7重磅更新:Opus 4.5降临Pro订阅,MCP自动省Token 85%,Skills和Commands概念大统一》。

下一篇聊 Hooks 钩子系统。Plugins 里提到了 hooks.on('beforeMessage', ...),这个钩子机制是怎么实现的?有哪些钩子点?怎么用它来定制 Claude Code 的行为?

本文基于 Claude Code 2.0.76 开源版本源码分析,主要参考 src/commands/(斜杠命令系统)、src/tools/skill.ts(Skills 和自定义命令)、src/tools/task.ts(Agents 子代理)、src/plugins/(插件系统)。可能是实际工具有所出入,社区 Agents 资源参考 VoltAgent/awesome-claude-code-subagents。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Claude Code 源码揭秘:Commands、Skills、Agents、Plugins,四套扩展体系各管一摊

评论 抢沙发

6 + 2 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮