# 第五阶段 · 模块五 · 第四节:自定义插件开发
如何从零开发一个插件?插件的项目结构是什么?如何打包和发布插件?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ 插件系统 │
│ │
│ 自定义插件开发 ← 本节 │
│ ├── 创建项目 │
│ ├── 编写 manifest │
│ └── 打包发布 │
└─────────────────────────────────────────────────────────────────────┘
my-plugin/
├── plugin.json ← 插件清单(必需)
├── package.json ← Node.js 配置
├── index.ts ← 入口文件
├── commands/ ← 命令目录
│ └── hello.ts
├── tools/ ← 工具目录
│ └── greet.ts
├── hooks/ ← 钩子目录
│ └── index.ts
└── README.md ← 文档
# 创建目录
mkdir my-plugin && cd my-plugin
# 初始化 package.json
npm init -y
# 安装依赖
npm install typescript @claude-code/sdk
# 初始化 TypeScript
npx tsc --init
{
"name": "my-plugin",
"version": "1.0.0",
"description": "A useful plugin for Claude Code",
"author": "Your Name",
"license": "MIT",
"commands": [
{
"name": "hello",
"description": "Say hello",
"path": "./commands/hello.ts"
}
],
"tools": [
{
"name": "greet",
"description": "Greet someone",
"path": "./tools/greet.ts"
}
],
"hooks": {
"path": "./hooks/index.ts"
},
"compatibility": {
"claude": ">=3.0.0"
}
}
| 字段 | 必需 | 说明 |
| name | ✓ | 插件名称,唯一标识 |
| version | ✓ | 语义版本 |
| description | ✓ | 简短描述 |
| commands | 自定义命令列表 | |
| tools | 自定义工具列表 | |
| hooks | 钩子配置 | |
| compatibility | Claude Code 版本要求 |
// commands/hello.ts
import type { Command } from '../../commands.js';
const helloCommand: Command = {
type: 'local',
name: 'hello',
description: 'Say hello to someone',
async getPromptForCommand(args: string) {
const name = args || 'World';
return {
type: 'text',
content: `Hello, ${name}! 👋`,
};
},
};
export default helloCommand;
// commands/with-args.ts
const withArgsCommand: Command = {
type: 'local',
name: 'greet',
description: 'Greet with custom message',
async getPromptForCommand(args: string) {
// 解析参数
const parts = args.split(' ');
const name = parts[0];
const style = parts[1] || 'formal';
const greetings = {
formal: `Good day, ${name}.`,
casual: `Hey ${name}!`,
friendly: `Hi ${name}! How are you?`,
};
return {
type: 'text',
content: greetings[style] || greetings.formal,
};
},
};
// tools/greet.ts
import type { Tool, ToolUseContext } from '../../Tool.js';
const greetTool: Tool = {
name: 'greet',
description: 'Generate a greeting message',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Name to greet' },
style: {
type: 'string',
enum: ['formal', 'casual', 'friendly'],
default: 'friendly',
},
},
required: ['name'],
},
async execute(input: { name: string; style?: string }, context: ToolUseContext) {
const { name, style = 'friendly' } = input;
const greetings = {
formal: `Good day, ${name}. I hope this message finds you well.`,
casual: `Hey ${name}!`,
friendly: `Hi ${name}! 🎉 How's it going?`,
};
return greetings[style];
},
};
export default greetTool;
// 工具默认需要用户授权
// 可以在 manifest 中声明权限
{
"tools": [
{
"name": "greet",
"path": "./tools/greet.ts",
"permission": "granted" // 自动授权
}
]
}
// hooks/index.ts
export async function preQuery(params: QueryParams): Promise<void> {
console.log('Before query:', params.messages.length, 'messages');
}
export async function postQuery(result: QueryResult): Promise<void> {
console.log('After query, result:', result.type);
}
export async function preTool(toolCall: ToolCall): Promise<void> {
console.log('Before tool:', toolCall.name);
}
export async function postTool(result: ToolResult): Promise<void> {
console.log('After tool, result:', result.content.slice(0, 50));
}
type PluginHooks = {
// 在发送查询前调用
preQuery?: (params: QueryParams) => Promise<void>;
// 在收到回复后调用
postQuery?: (result: QueryResult) => Promise<void>;
// 在工具执行前调用
preTool?: (toolCall: ToolCall) => Promise<void>;
// 在工具执行后调用
postTool?: (result: ToolResult) => Promise<void>;
};
# 链接到本地开发目录
claude plugin link ./my-plugin
# 验证插件
claude plugin validate ./my-plugin
# 查看插件列表
claude plugin list
// 在插件中添加日志
import { logForDebugging } from '../../utils/debug.js';
export async function preQuery(params: QueryParams): Promise<void> {
logForDebugging('preQuery called', { messageCount: params.messages.length });
}
# 打包插件
npm run build
# 创建发布包
mkdir dist
cp -r plugin.json package.json dist/
cp -r commands tools hooks dist/
# 登录市场
claude marketplace login
# 发布插件
claude plugin publish ./dist
# 设置标签
claude plugin tag my-plugin --tags util,productivity
// 语义版本 (SemVer)
// major.minor.patch
// 1.0.0 - 初始版本
// 1.0.1 - 修复bug
// 1.1.0 - 新功能(向后兼容)
// 2.0.0 - 破坏性变更
答案:
// 可以,插件有完全的网络访问权限
export async function fetchData(): Promise<string> {
const response = await fetch('https://api.example.com/data');
return response.text();
}
// 但要注意:
// 1. API 密钥不要硬编码
// 2. 使用环境变量存储敏感信息
// 3. 处理网络错误
答案:
// 捕获并记录错误
export async function preQuery(params: QueryParams): Promise<void> {
try {
// 操作
} catch (error) {
logForDebugging('preQuery error', { error: error.message });
// 不要抛出,否则会中断流程
}
}
答案:
// 不能直接修改
// 但可以通过 appendSystemPrompt 影响
// 在 manifest 中声明
{
"systemPromptAppend": "You are working with my custom plugin..."
}
// 或在钩子中
export async function preQuery(params: QueryParams): Promise<void> {
params.appendSystemPrompt = 'Additional context...';
}
| 资源 | 说明 |
| Claude Code SDK | https://docs.anthropic.com/claude-code/sdk |
| 插件市场 | https://claude.com/plugins |
| 插件模板 | https://github.com/anthropics/claude-code-plugin-template |
下一节我们将进入 第六章:Hooks 系统:
- Hooks 的类型
- 预置 Hooks
- 自定义 Hooks
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*