目录

Claude Code 命令系统源码解读

Claude Code 命令系统源码解读

注意:本文档分析的是 Claude Code (claude-code-main) 的命令系统源码。


1. 命令系统概述

Claude Code 的命令系统是一个分层架构,包含三大核心概念:

概念说明
Command命令的元数据定义(名称、描述、类型、来源等)
Tool实际执行工具(Read、Write、Bash 等),与 AI 模型交互
Skill基于 Command 的 prompt 模板,可被 AI 模型调用

核心文件:

  • src/commands.ts - 命令注册中心
  • src/types/command.ts - 命令类型定义
  • src/utils/processUserInput/processSlashCommand.tsx - 命令解析与执行
  • src/utils/slashCommandParsing.ts - Slash 命令解析
  • src/Tool.ts - 工具基类与 ToolDef 定义
  • src/tools/* - 各种 Tool 实现

2. 命令类型体系

2.1 Command 类型定义

// src/types/command.ts

export type Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)

命令分为三种类型:

2.1.1 PromptCommand (提示词命令)

type PromptCommand = {
  type: 'prompt'
  progressMessage: string      // 进度显示消息
  contentLength: number         // 内容长度(用于 token 估算)
  argNames?: string[]          // 参数名称
  allowedTools?: string[]      // 允许使用的工具
  model?: string               // 模型覆盖
  source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
  disableNonInteractive?: boolean
  hooks?: HooksSettings         // 钩子设置
  skillRoot?: string            // 技能资源目录
  context?: 'inline' | 'fork'   // 执行上下文
  agent?: string                // Fork 时使用的 Agent 类型
  effort?: EffortValue          // 努力级别
  paths?: string[]               // 适用的文件路径模式
  getPromptForCommand(args: string, context: ToolUseContext): Promise<ContentBlockParam[]>
}

示例命令: /commit, /review, /help

2.1.2 LocalCommand (本地命令)

type LocalCommand = {
  type: 'local'
  supportsNonInteractive: boolean
  load: () => Promise<LocalCommandModule>  // 延迟加载
}

示例命令: /clear, /compact

2.1.3 LocalJSXCommand (本地 JSX 命令)

type LocalJSXCommand = {
  type: 'local-jsx'
  load: () => Promise<LocalJSXCommandModule>  // 延迟加载,返回 React 组件
}

示例命令: /config, /theme, /help

2.2 Command 基础属性

type CommandBase = {
  availability?: CommandAvailability[]  // 可用性要求
  description: string
  hasUserSpecifiedDescription?: boolean
  isEnabled?: () => boolean               // 启用状态检查
  isHidden?: boolean                      // 是否隐藏
  name: string
  aliases?: string[]                      // 命令别名
  argumentHint?: string                   // 参数提示
  whenToUse?: string                      // 使用场景描述
  version?: string
  disableModelInvocation?: boolean         // 禁用模型调用
  userInvocable?: boolean                 // 用户是否可以调用
  loadedFrom?: 'commands_DEPRECATED' | 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp'
  kind?: 'workflow'                       // 工作流类型
  immediate?: boolean                     // 立即执行(跳过队列)
  isSensitive?: boolean                   // 敏感命令(参数会被编辑)
  userFacingName?: () => string           // 显示名称
}

3. 命令注册与解析

3.1 命令注册中心 (commands.ts)

所有内置命令在 src/commands.ts 中集中注册:

// src/commands.ts

// 内置命令列表(静态导入)
const COMMANDS = memoize((): Command[] => [
  addDir,
  advisor,
  agents,
  branch,
  // ... 更多命令
])

// 命令加载(包含动态来源)
const loadAllCommands = memoize(async (cwd: string): Promise<Command[]> => {
  const [
    { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills },
    pluginCommands,
    workflowCommands,
  ] = await Promise.all([
    getSkills(cwd),           // 技能目录、插件技能、捆绑技能
    getPluginCommands(),      // 插件命令
    getWorkflowCommands?.(cwd) ?? Promise.resolve([]),  // 工作流命令
  ])

  return [
    ...bundledSkills,
    ...builtinPluginSkills,
    ...skillDirCommands,
    ...workflowCommands,
    ...pluginCommands,
    ...pluginSkills,
    ...COMMANDS(),  // 内置命令
  ]
})

命令来源优先级:

  1. bundled - 捆绑技能
  2. builtinPluginSkills - 内置插件技能
  3. skillDirCommands - 用户技能目录
  4. workflowCommands - 工作流命令
  5. pluginCommands - 插件命令
  6. pluginSkills - 插件技能
  7. COMMANDS() - 内置命令

3.2 Slash 命令解析

// src/utils/slashCommandParsing.ts

export type ParsedSlashCommand = {
  commandName: string
  args: string
  isMcp: boolean
}

export function parseSlashCommand(input: string): ParsedSlashCommand | null {
  const trimmedInput = input.trim()
  
  if (!trimmedInput.startsWith('/')) {
    return null
  }

  const withoutSlash = trimmedInput.slice(1)
  const words = withoutSlash.split(' ')

  if (!words[0]) {
    return null
  }

  let commandName = words[0]
  let isMcp = false
  let argsStartIndex = 1

  // 检查 MCP 命令格式: /mcp:tool (MCP) arg1 arg2
  if (words.length > 1 && words[1] === '(MCP)') {
    commandName = commandName + ' (MCP)'
    isMcp = true
    argsStartIndex = 2
  }

  const args = words.slice(argsStartIndex).join(' ')

  return { commandName, args, isMcp }
}

3.3 命令查找

// src/commands.ts

export function findCommand(
  commandName: string,
  commands: Command[],
): Command | undefined {
  return commands.find(
    _ =>
      _.name === commandName ||
      getCommandName(_) === commandName ||  // 支持 userFacingName
      _.aliases?.includes(commandName),      // 支持别名
  )
}

export function getCommand(commandName: string, commands: Command[]): Command {
  const command = findCommand(commandName, commands)
  if (!command) {
    throw ReferenceError(`Command ${commandName} not found...`)
  }
  return command
}

4. 命令执行流程

4.1 主入口: processSlashCommand

// src/utils/processUserInput/processSlashCommand.tsx

export async function processSlashCommand(
  inputString: string,
  precedingInputBlocks: ContentBlockParam[],
  imageContentBlocks: ContentBlockParam[],
  attachmentMessages: AttachmentMessage[],
  context: ProcessUserInputContext,
  setToolJSX: SetToolJSXFn,
  uuid?: string,
  isAlreadyProcessing?: boolean,
  canUseTool?: CanUseToolFn,
): Promise<ProcessUserInputBaseResult> {
  // 1. 解析 slash 命令
  const parsed = parseSlashCommand(inputString)
  if (!parsed) {
    // 不是有效的 slash 命令,作为普通用户输入处理
    return { messages: [createUserMessage({...})], shouldQuery: true }
  }

  const { commandName, args: parsedArgs, isMcp } = parsed

  // 2. 检查命令是否存在
  if (!hasCommand(commandName, context.options.commands)) {
    // 命令不存在,检查是否是文件路径
    if (looksLikeCommand(commandName)) {
      return { messages: [createUserMessage({ content: `Unknown skill: ${commandName}` })], shouldQuery: false }
    }
    // 可能是普通输入,包含 slash
  }

  // 3. 获取命令消息
  const result = await getMessagesForSlashCommand(commandName, parsedArgs, ...)

  // 4. 返回结果
  return {
    messages: result.messages,
    shouldQuery: result.shouldQuery,
    // ...
  }
}

4.2 命令类型分发

// src/utils/processUserInput/processSlashCommand.tsx

async function getMessagesForSlashCommand(...) {
  const command = getCommand(commandName, context.options.commands)

  try {
    switch (command.type) {
      case 'local-jsx': {
        // JSX 命令:返回 React 组件显示
        return new Promise<SlashCommandResult>(resolve => {
          let doneWasCalled = false
          const onDone = (result?: string, options?: {...}) => {
            doneWasCalled = true
            // 处理完成回调
            void resolve({
              messages: [...],
              shouldQuery: options?.shouldQuery ?? false,
              command,
            })
          }
          // 加载并执行命令
          void command.load().then(mod => mod.call(onDone, context, args))
        })
      }

      case 'local': {
        // 本地命令:直接执行
        const mod = await command.load()
        const result = await mod.call(args, context)
        // 处理结果
        if (result.type === 'compact') {
          // 压缩处理
          return buildPostCompactMessages(result.compactionResult)
        }
        return { messages: [userMessage, createCommandInputMessage(...)], shouldQuery: false }
      }

      case 'prompt': {
        // 提示词命令:检查是否需要 fork
        if (command.context === 'fork') {
          return await executeForkedSlashCommand(command, args, ...)
        }
        return await getMessagesForPromptSlashCommand(command, args, ...)
      }
    }
  } catch (e) {
    // 错误处理
  }
}

4.3 Fork 子代理执行

// 对于 context: 'fork' 的命令,在子代理中执行

async function executeForkedSlashCommand(
  command: CommandBase & PromptCommand,
  args: string,
  context: ProcessUserInputContext,
  precedingInputBlocks: ContentBlockParam[],
  setToolJSX: SetToolJSXFn,
  canUseTool: CanUseToolFn,
): Promise<SlashCommandResult> {
  const agentId = createAgentId()

  // 准备 fork 上下文
  const { skillContent, modifiedGetAppState, baseAgent, promptMessages } =
    await prepareForkedCommandContext(command, args, context)

  // 运行子代理
  const agentMessages: Message[] = []
  for await (const message of runAgent({
    agentDefinition,
    promptMessages,
    toolUseContext: { ...context, getAppState: modifiedGetAppState },
    canUseTool,
    isAsync: false,
    querySource: 'agent:custom',
    model: command.model as ModelAlias | undefined,
    availableTools: context.options.tools,
  })) {
    agentMessages.push(message)
  }

  const resultText = extractResultText(agentMessages, 'Command completed')

  return {
    messages: [
      createUserMessage({ content: `/${getCommandName(command)} ${args}`.trim() }),
      createUserMessage({ content: `<local-command-stdout>\n${resultText}\n</local-command-stdout>` }),
    ],
    shouldQuery: false,
  }
}

5. 内置命令实现

5.1 示例: /help 命令 (LocalJSX 类型)

// src/commands/help/index.ts

const help = {
  type: 'local-jsx',
  name: 'help',
  description: 'Show help and available commands',
  load: () => import('./help.js'),  // 延迟加载实现
} satisfies Command

export default help

5.2 示例: /clear 命令 (Local 类型)

// src/commands/clear/index.ts

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

export default clear

5.3 示例: /commit 命令 (Prompt 类型)

// src/commands/commit.ts

const command = {
  type: 'prompt',
  name: 'commit',
  description: 'Create a git commit',
  allowedTools: [                   // 限制可使用的工具
    'Bash(git add:*)',
    'Bash(git status:*)',
    'Bash(git commit:*)',
  ],
  contentLength: 0,
  progressMessage: 'creating commit',
  source: 'builtin',
  async getPromptForCommand(_args, context) {
    // 获取 prompt 内容
    const promptContent = getPromptContent()
    // 执行 shell 命令替换 !`command` 语法
    const finalContent = await executeShellCommandsInPrompt(promptContent, context, '/commit')
    return [{ type: 'text', text: finalContent }]
  },
} satisfies Command

export default command

6. 工具系统 (Tools)

6.1 Tool 定义

// src/Tool.ts

export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  name: string
  aliases?: string[]
  searchHint?: string
  call(
    args: z.infer<Input>,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
    onProgress?: ToolCallProgress<P>,
  ): Promise<ToolResult<Output>>
  description(input: z.infer<Input>, options: {...}): Promise<string>
  readonly inputSchema: Input
  readonly outputSchema: Output
  // ... 更多可选方法
}

6.2 核心内置工具

工具名文件路径功能
Readsrc/tools/FileReadTool/FileReadTool.ts读取文件
Writesrc/tools/FileWriteTool/FileWriteTool.ts写入文件
Editsrc/tools/FileEditTool/FileEditTool.ts编辑文件
Bashsrc/tools/BashTool/BashTool.tsx执行 shell 命令
Grepsrc/tools/GrepTool/GrepTool.ts内容搜索
Globsrc/tools/GlobTool/GlobTool.ts文件模式匹配
Tasksrc/tools/AgentTool/AgentTool.ts代理任务
NotebookEditsrc/tools/NotebookEditTool/NotebookEditTool.tsJupyter 笔记本编辑
WebFetchsrc/tools/WebFetchTool/WebFetchTool.ts网页获取
WebSearchsrc/tools/WebSearchTool/WebSearchTool.ts网页搜索
MCPToolsrc/tools/MCPTool/MCPTool.tsMCP 协议工具
SkillToolsrc/tools/SkillTool/SkillTool.ts技能调用

6.3 工具与命令的关系

┌─────────────────────────────────────────────────────────────────┐
│                        Command (命令)                            │
├─────────────────────────────────────────────────────────────────┤
│  type: 'prompt'                                                 │
│  ├── 被 SkillTool 调用                                          │
│  ├── 展开为 prompt 内容                                         │
│  └── 示例: /commit, /review                                     │
│                                                                 │
│  type: 'local' / 'local-jsx'                                    │
│  ├── 直接执行,不涉及 AI 模型                                    │
│  └── 示例: /clear, /config, /help                               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                         Tool (工具)                              │
├─────────────────────────────────────────────────────────────────┤
│  被 AI 模型调用                                                 │
│  ├── 有 inputSchema/outputSchema                                │
│  ├── 支持权限检查                                                │
│  ├── 支持进度报告                                               │
│  └── 示例: Read, Write, Bash, Edit                              │
└─────────────────────────────────────────────────────────────────┘

关键区别:

  • Command: 用户直接调用的slash命令,定义元数据和执行逻辑
  • Tool: AI 模型调用的工具,有输入输出schema

6.4 SkillTool: 命令与工具的桥梁

// src/tools/SkillTool/SkillTool.ts

export const SkillTool = buildTool({
  name: 'Skill',
  
  async call({ skill, args }, context, canUseTool, parentMessage, onProgress) {
    // 1. 解析技能名称
    const commandName = skill.replace(/^\//, '')

    // 2. 查找命令
    const commands = await getAllCommands(context)
    const command = findCommand(commandName, commands)

    // 3. 检查是否为 fork 类型
    if (command?.type === 'prompt' && command.context === 'fork') {
      return executeForkedSkill(command, commandName, args, context, ...)
    }

    // 4. 处理 prompt 命令
    const processedCommand = await processPromptSlashCommand(
      commandName,
      args || '',
      commands,
      context,
    )

    // 5. 返回结果和上下文修改
    return {
      data: { success: true, commandName, allowedTools },
      newMessages: processedCommand.messages,
      contextModifier(ctx) {
        // 修改上下文(如添加工具权限)
        return modifiedContext
      },
    }
  },
})

7. 自定义命令支持

7.1 技能目录加载

// src/skills/loadSkillsDir.ts

export async function getSkillDirCommands(cwd: string): Promise<Command[]> {
  // 从用户的 .claude/commands 或 .claude/skills 目录加载
  const skillsPath = getSkillsPath(source, 'commands')
  
  // 扫描目录中的 .md 文件
  const markdownFiles = await loadMarkdownFilesForSubdir(skillsPath)
  
  // 解析每个文件的 frontmatter 和内容
  for (const file of markdownFiles) {
    const { frontmatter, content } = parseFrontmatter(file.content)
    
    // 创建 PromptCommand
    const command: PromptCommand = {
      type: 'prompt',
      name: frontmatter.name || extractNameFromFilename(file.path),
      description: frontmatter.description || extractDescriptionFromMarkdown(content),
      source: 'commands_DEPRECATED' as SettingSource,
      async getPromptForCommand(args, context) {
        // 替换 $ARGUMENTS 等变量
        const processedContent = substituteArguments(content, args)
        return [{ type: 'text', text: processedContent }]
      },
    }
  }
}

7.2 自定义命令结构

用户可以在 .claude/commands/ 目录下创建 markdown 文件:

---
name: my-custom-command
description: A custom command for XYZ
allowedTools:
  - Bash(git:*) 
---

## My Custom Command

This command does XYZ with $ARGUMENTS.

Steps:
1. First step
2. Second step

7.3 插件命令

// src/utils/plugins/loadPluginCommands.ts

export async function getPluginCommands(): Promise<Command[]> {
  // 加载插件目录下的 commands/
  // 插件命令结构与用户自定义命令相同
  // 来源标记为 'plugin'
}

8. 命令执行安全机制

8.1 可用性检查

// src/commands.ts

export function meetsAvailabilityRequirement(cmd: Command): boolean {
  if (!cmd.availability) return true
  
  for (const a of cmd.availability) {
    switch (a) {
      case 'claude-ai':
        if (isClaudeAISubscriber()) return true
        break
      case 'console':
        // 仅限直接 API 用户
        if (!isClaudeAISubscriber() && !isUsing3PServices() && isFirstPartyAnthropicBaseUrl())
          return true
        break
    }
  }
  return false
}

8.2 启用状态检查

// src/types/command.ts

export function isCommandEnabled(cmd: CommandBase): boolean {
  return cmd.isEnabled?.() ?? true
}

8.3 远程安全命令

// src/commands.ts

export const REMOTE_SAFE_COMMANDS: Set<Command> = new Set([
  session, exit, clear, help, theme, color, vim, cost, usage, copy, btw,
  feedback, plan, keybindings, statusline, stickers, mobile,
])

export const BRIDGE_SAFE_COMMANDS: Set<Command> = new Set([
  compact, clear, cost, summary, releaseNotes, files,
])

export function isBridgeSafeCommand(cmd: Command): boolean {
  if (cmd.type === 'local-jsx') return false  // 阻塞 JSX 命令
  if (cmd.type === 'prompt') return true      // prompt 类型默认安全
  return BRIDGE_SAFE_COMMANDS.has(cmd)
}

9. 命令缓存与失效

// src/commands.ts

export function clearCommandsCache(): void {
  clearCommandMemoizationCaches()  // 清除命令缓存
  clearPluginCommandCache()         // 清除插件命令缓存
  clearPluginSkillsCache()           // 清除插件技能缓存
  clearSkillCaches()                // 清除技能缓存
}

10. 总结

Claude Code 的命令系统设计要点:

  1. 分层类型: Command 定义元数据,Tool 定义执行能力,两者通过 SkillTool 桥接

  2. 三种执行模式:

    • prompt: 展开为 prompt 供 AI 处理
    • local: 直接执行,返回结果
    • local-jsx: 渲染 React 组件
  3. 多来源: 内置 → 插件 → 工作流 → 用户目录

  4. 延迟加载: 所有 locallocal-jsx 命令使用动态 import

  5. 安全机制: 可用性检查、启用状态、远程安全列表

  6. 权限控制: allowedTools 限制命令可使用的工具


参考文件

文件说明
src/commands.ts命令注册中心
src/types/command.ts命令类型定义
src/utils/processUserInput/processSlashCommand.tsx命令执行主流程
src/utils/slashCommandParsing.tsSlash 命令解析
src/Tool.ts工具基类
src/tools/SkillTool/SkillTool.ts技能工具实现
src/tools/BashTool/BashTool.tsxBash 工具示例
src/skills/loadSkillsDir.ts技能加载逻辑