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 通过工具系统被调用。源码里的 SkillTool(src/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. 权限检查在执行前进行, disableModelInvocation可以阻止模型自动调用 -
2. 返回的 allowedTools会限制后续调用能用哪些工具 -
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 里导出一个对象,包含 metadata、init、activate、deactivate 这些属性和方法。
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。
夜雨聆风
