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(), // 内置命令
]
})命令来源优先级:
bundled- 捆绑技能builtinPluginSkills- 内置插件技能skillDirCommands- 用户技能目录workflowCommands- 工作流命令pluginCommands- 插件命令pluginSkills- 插件技能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 help5.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 clear5.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 command6. 工具系统 (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 核心内置工具
| 工具名 | 文件路径 | 功能 |
|---|---|---|
| Read | src/tools/FileReadTool/FileReadTool.ts | 读取文件 |
| Write | src/tools/FileWriteTool/FileWriteTool.ts | 写入文件 |
| Edit | src/tools/FileEditTool/FileEditTool.ts | 编辑文件 |
| Bash | src/tools/BashTool/BashTool.tsx | 执行 shell 命令 |
| Grep | src/tools/GrepTool/GrepTool.ts | 内容搜索 |
| Glob | src/tools/GlobTool/GlobTool.ts | 文件模式匹配 |
| Task | src/tools/AgentTool/AgentTool.ts | 代理任务 |
| NotebookEdit | src/tools/NotebookEditTool/NotebookEditTool.ts | Jupyter 笔记本编辑 |
| WebFetch | src/tools/WebFetchTool/WebFetchTool.ts | 网页获取 |
| WebSearch | src/tools/WebSearchTool/WebSearchTool.ts | 网页搜索 |
| MCPTool | src/tools/MCPTool/MCPTool.ts | MCP 协议工具 |
| SkillTool | src/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 step7.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 的命令系统设计要点:
分层类型:
Command定义元数据,Tool定义执行能力,两者通过SkillTool桥接三种执行模式:
prompt: 展开为 prompt 供 AI 处理local: 直接执行,返回结果local-jsx: 渲染 React 组件
多来源: 内置 → 插件 → 工作流 → 用户目录
延迟加载: 所有
local和local-jsx命令使用动态 import安全机制: 可用性检查、启用状态、远程安全列表
权限控制:
allowedTools限制命令可使用的工具
参考文件
| 文件 | 说明 |
|---|---|
src/commands.ts | 命令注册中心 |
src/types/command.ts | 命令类型定义 |
src/utils/processUserInput/processSlashCommand.tsx | 命令执行主流程 |
src/utils/slashCommandParsing.ts | Slash 命令解析 |
src/Tool.ts | 工具基类 |
src/tools/SkillTool/SkillTool.ts | 技能工具实现 |
src/tools/BashTool/BashTool.tsx | Bash 工具示例 |
src/skills/loadSkillsDir.ts | 技能加载逻辑 |