# 第三阶段 · 模块三 · 第四节:自定义命令开发
如何开发一个自定义命令?命令的完整结构是什么?命令与工具的区别和使用场景是什么?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ 命令系统 │
│ │
│ 自定义命令开发 ← 本节 │
│ ├── Command 接口 ──> 命令定义 │
│ ├── LocalCommandCall ──> 命令实现 │
│ └── 与工具的区别 ──> 使用场景 │
└─────────────────────────────────────────────────────────────────────┘
源码位置:`src/commands/clear/index.ts` 第 17 行
import type { Command } from '../../commands.js'
const clear = {
type: 'local',
name: 'clear',
description: 'Clear conversation history and free up context',
aliases: ['reset', 'new'],
supportsNonInteractive: false,
load: () => import('./clear.js'),
} satisfies Command
源码位置:`src/commands/compact/index.ts`
const compact = {
type: 'local', // 命令类型
name: 'compact', // 命令名(唯一标识)
description: // 命令描述(用户看到的帮助文本)
'Clear conversation history but keep a summary in context. Optional: /compact [instructions for summarization]',
isEnabled?: () => boolean, // 是否启用(可选,默认 true)
supportsNonInteractive?: boolean, // 是否支持非交互模式
argumentHint?: string, // 参数提示
aliases?: string[], // 命令别名
load?: () => Promise<void>, // 延迟加载
} satisfies Command
问 1:type: 'local' 是什么意思?
// 命令类型有三种:
type CommandType = 'local' | 'remote' | 'mcp'
// local:本地命令,随 Claude Code 启动加载
// remote:远程命令,通过网络获取
// mcp:MCP 协议命令,来自 MCP 服务器
问 2:为什么要用 load 延迟加载?
// 源码位置:src/commands/clear/index.ts
// 减少启动时间
// 只有当用户实际调用命令时才加载实现代码
load: () => import('./clear.js'),
// 对比:
// ❌ 直接导入:import { call } from './clear.js'
// ✅ 延迟导入:load: () => import('./clear.js')
源码位置:`src/commands/clear/clear.ts`
import type { LocalCommandCall } from '../../types/command.js'
export const call: LocalCommandCall = async (_, context) => {
await clearConversation(context)
return { type: 'text', value: '' }
}
// 命令执行函数的类型签名
type LocalCommandCall = (
args: string, // 用户输入的参数
context: CommandContext // 执行上下文
) => Promise<CommandResult>
// 返回值类型
type CommandResult =
| { type: 'text'; value: string }
| { type: 'error'; value: string }
| { type: 'stop'; value: string } // 停止程序
// 命令执行时可访问的上下文
interface CommandContext {
messages: Message[] // 对话历史
abortController: AbortController // 可取消操作
getAppState: () => AppState // 获取应用状态
setAppState: (f: (prev: AppState) => AppState) => void // 修改状态
}
问 1:命令可以修改对话历史吗?
// 可以,通过 context
export const call: LocalCommandCall = async (_, context) => {
// 清空对话
await clearConversation(context)
return { type: 'text', value: 'Conversation cleared' }
}
问 2:命令可以返回什么?
// 文本响应
return { type: 'text', value: 'Done!' }
// 错误响应
return { type: 'error', value: 'Something went wrong' }
// 停止程序
return { type: 'stop', value: 'Goodbye!' }
src/commands/
├── my-custom/
│ ├── index.ts ← 命令定义
│ └── my-custom.ts ← 命令实现
源码位置:`src/commands/compact/index.ts`
// index.ts
import type { Command } from '../../commands.js'
const myCustom = {
type: 'local',
name: 'my-custom',
description: 'My custom command description',
// 命令参数提示
argumentHint: '<required-param> [optional-param]',
// 是否支持非交互模式
supportsNonInteractive: true,
// 延迟加载实现
load: () => import('./my-custom.js'),
} satisfies Command
export default myCustom
// my-custom.ts
import type { LocalCommandCall } from '../../types/command.js'
export const call: LocalCommandCall = async (args, context) => {
// 1. 解析参数
const params = parseArgs(args)
// 2. 验证参数
if (!params.required) {
return {
type: 'error',
value: 'Missing required parameter'
}
}
// 3. 执行业务逻辑
const result = await doSomething(params)
// 4. 返回结果
return {
type: 'text',
value: result,
}
}
function parseArgs(args: string): Record<string, string> {
const parts = args.split(/\s+/)
return {
required: parts[0] || '',
optional: parts[1] || '',
}
}
源码位置:`src/commands.ts` 第 1-50 行
// 1. 导入命令
import myCustom from './commands/my-custom/index.js'
// 2. 添加到命令列表
const commands = [
helpCommand,
compactCommand,
clearCommand,
myCustom, // ← 添加你的命令
// ...
]
// 通过 aliases 设置别名
const clear = {
type: 'local',
name: 'clear',
aliases: ['reset', 'new'], // 用户可以用 /reset 或 /new
description: '...',
load: () => import('./clear.js'),
} satisfies Command
| 方面 | 命令 | 工具 |
| 触发方式 | 用户输入 `/cmd` | AI 模型决定调用 |
| 上下文 | 修改会话状态 | 操作文件/执行命令 |
| 调用时机 | 用户主动 | AI 决策 |
| 返回值 | 消息/停止 | 结构化结果 |
| 典型用途 | /clear, /compact | Read, Write, Bash |
问 1:什么时候用命令,什么时候用工具?
// 用命令的场景:
// - 修改会话状态(/clear, /compact)
// - 用户主动操作(/help, /status)
// - 控制程序行为(/exit, /model)
// 用工具的场景:
// - AI 需要执行操作(读文件、运行命令)
// - 返回结构化数据
// - 后台自动化任务
答案:
// 源码位置:src/commands/compact/compact.ts
export const call: LocalCommandCall = async (args, context) => {
// args 是字符串,需要解析
const customInstructions = args.trim() // "/compact aggressive" → "aggressive"
if (!customInstructions) {
// 无参数处理
return await defaultCompact(context)
} else {
// 有参数处理
return await compactWithInstructions(context, customInstructions)
}
}
答案:
// 理论上可以,但不推荐
// 命令是用户操作,工具是 AI 操作
// 混用会增加复杂度
// 如果确实需要,可以在命令中调用工具
export const call: LocalCommandCall = async (args, context) => {
// 读取文件
const readTool = getTool('Read')
const result = await readTool.execute(
{ file_path: args },
context // 传递 ToolUseContext
)
return { type: 'text', value: result }
}
答案:
// 方法 1:添加日志
export const call: LocalCommandCall = async (args, context) => {
console.log('[DEBUG] my-custom called with args:', args)
try {
const result = await doSomething(args)
console.log('[DEBUG] result:', result)
return { type: 'text', value: result }
} catch (error) {
console.error('[ERROR]', error)
return { type: 'error', value: error.message }
}
}
// 方法 2:使用 verbose 模式
claude --verbose
# 会显示所有命令调用
| 文件 | 核心内容 |
| `src/commands/compact/index.ts` | 命令定义示例 |
| `src/commands/clear/index.ts` | 简洁命令示例 |
| `src/types/command.ts` | LocalCommandCall 类型 |
下一节我们将进入 第四阶段:上下文管理:
- mutableMessages 状态管理
- 上下文压缩机制
- 工具调用与上下文的关系
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*