第四节:自定义命令
命令开发与注册

作者:小学子 📚 | 日期:2026年4月2日 | 第三阶段 · 模块三


# 第三阶段 · 模块三 · 第四节:自定义命令开发

核心问题

如何开发一个自定义命令?命令的完整结构是什么?命令与工具的区别和使用场景是什么?


◇ 本节位置


        Claude Code 全局架构
        
        ┌─────────────────────────────────────────────────────────────────────┐
        │  命令系统                                                            │
        │                                                                      │
        │  自定义命令开发 ← 本节                                              │
        │  ├── Command 接口 ──> 命令定义                                      │
        │  ├── LocalCommandCall ──> 命令实现                                  │
        │  └── 与工具的区别 ──> 使用场景                                      │
        └─────────────────────────────────────────────────────────────────────┘
        


一、Command 接口

1.1 命令定义

源码位置:`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
        

1.2 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.3 五问分析

问 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')
        


二、命令实现

2.1 LocalCommandCall

源码位置:`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: '' }
        }
        

2.2 LocalCommandCall 类型


        // 命令执行函数的类型签名
        type LocalCommandCall = (
          args: string,           // 用户输入的参数
          context: CommandContext // 执行上下文
        ) => Promise<CommandResult>
        
        // 返回值类型
        type CommandResult =
          | { type: 'text'; value: string }
          | { type: 'error'; value: string }
          | { type: 'stop'; value: string }  // 停止程序
        

2.3 CommandContext


        // 命令执行时可访问的上下文
        interface CommandContext {
          messages: Message[]              // 对话历史
          abortController: AbortController // 可取消操作
          getAppState: () => AppState     // 获取应用状态
          setAppState: (f: (prev: AppState) => AppState) => void  // 修改状态
        }
        

2.4 五问分析

问 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!' }
        


三、命令开发示例

3.1 创建命令目录


        src/commands/
        ├── my-custom/
        │   ├── index.ts      ← 命令定义
        │   └── my-custom.ts  ← 命令实现
        

3.2 命令定义

源码位置:`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
        

3.3 命令实现


        // 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] || '',
          }
        }
        


四、命令注册

4.1 注册到 commands.ts

源码位置:`src/commands.ts` 第 1-50 行


        // 1. 导入命令
        import myCustom from './commands/my-custom/index.js'
        
        // 2. 添加到命令列表
        const commands = [
          helpCommand,
          compactCommand,
          clearCommand,
          myCustom,  // ← 添加你的命令
          // ...
        ]
        

4.2 命令别名


        // 通过 aliases 设置别名
        const clear = {
          type: 'local',
          name: 'clear',
          aliases: ['reset', 'new'],  // 用户可以用 /reset 或 /new
          description: '...',
          load: () => import('./clear.js'),
        } satisfies Command
        


五、命令 vs 工具

5.1 区别对比

方面命令工具
触发方式用户输入 `/cmd`AI 模型决定调用
上下文修改会话状态操作文件/执行命令
调用时机用户主动AI 决策
返回值消息/停止结构化结果
典型用途/clear, /compactRead, Write, Bash

5.2 五问分析

问 1:什么时候用命令,什么时候用工具?


        // 用命令的场景:
        // - 修改会话状态(/clear, /compact)
        // - 用户主动操作(/help, /status)
        // - 控制程序行为(/exit, /model)
        
        // 用工具的场景:
        // - AI 需要执行操作(读文件、运行命令)
        // - 返回结构化数据
        // - 后台自动化任务
        


六、思考题

思考题 1:如何实现带参数的命令?

答案


        // 源码位置: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)
          }
        }
        


思考题 2:命令可以调用工具吗?

答案


        // 理论上可以,但不推荐
        
        // 命令是用户操作,工具是 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 }
        }
        


思考题 3:如何调试命令?

答案


        // 方法 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 状态管理

- 上下文压缩机制

- 工具调用与上下文的关系


*- 第一轮:□ 事实准确性*

*- 第二轮:□ 深度与洞见*

*- 第三轮:□ 可读性与价值*