Claude Code 的命令系统是一个类型安全、懒加载、多源合并的架构设计,通过统一的 Command 接口将内置命令、用户技能、插件扩展和 MCP 服务整合为一个协调的执行体系。该系统采用** discriminated union** 模式区分三种命令类型(local、local-jsx、prompt),每种类型对应不同的执行语义和返回值处理策略,同时通过 load() 函数实现按需加载,显著降低启动时的依赖图复杂度。
Command 类型系统:三种执行模式
命令系统的核心是 Command 类型定义,它通过 TypeScript 的 discriminated union 将三种不同的执行模式统一在一个类型系统下:
export type Command = CommandBase &
(PromptCommand | LocalCommand | LocalJSXCommand)
每种命令类型都有明确的职责边界和执行特征,下表对比了三种类型的核心差异:
| 特性维度 | local | local-jsx | prompt |
|---|---|---|---|
| 返回类型 | LocalCommandResult (text/compact/skip) | React.ReactNode | ContentBlockParam[] (发送给模型) |
| 执行语义 | 同步计算,立即返回结果 | 渲染交互式 UI 组件 | 扩展为提示词,触发模型推理 |
| 适用场景 | 简单查询、状态操作、会话管理 | 配置界面、选择器、向导流程 | 技能、工作流、代码生成任务 |
| 模型交互 | ❌ 不触发 API 调用 | ❌ 不触发 API 调用 | ✅ 将提示词发送给模型 |
| 示例命令 | /version, /clear, /cost | /help, /model, /config | /commit, /review, /init |
CommandBase:通用命令元数据
所有命令类型共享的基础元数据定义了命令的标识、可见性和可用性条件:
export type CommandBase = {
name: string // 命令标识符(唯一)
description: string // 用户可见描述
aliases?: string[] // 命令别名
isEnabled?: () => boolean // 动态启用条件(特性开关、环境检查)
isHidden?: boolean // 从自动补全和帮助中隐藏
availability?: CommandAvailability[] // 认证/提供商限制
argumentHint?: string // 参数提示文本
whenToUse?: string // 详细使用场景说明
userInvocable?: boolean // 用户是否可直接调用(默认 true)
disableModelInvocation?: boolean // 禁止模型通过 SkillTool 调用
loadedFrom?: 'skills' | 'plugin' | 'bundled' | 'mcp' // 命令来源
immediate?: boolean // 立即执行,绕过命令队列
isSensitive?: boolean // 参数敏感,在历史记录中脱敏
}
可用性控制 通过 availability 字段实现基于认证提供商的命令过滤,例如 ['claude-ai', 'console'] 表示仅对 claude.ai 订阅用户和直接 API 用户可见,而对 Bedrock/Vertex 用户隐藏。这与 isEnabled() 形成双层过滤:availability 是静态的身份检查,isEnabled() 是动态的状态检查(如特性开关)。
local 命令:轻量级同步操作
local 命令是最简单的命令类型,用于执行不涉及模型交互的同步操作,如查询状态、清理会话、显示信息等。其执行函数签名如下:
export type LocalCommandCall = (
args: string,
context: LocalJSXCommandContext
) => Promise<LocalCommandResult>
export type LocalCommandResult =
| { type: 'text'; value: string }
| { type: 'compact'; compactionResult: CompactionResult; displayText?: string }
| { type: 'skip' }
返回值的 discriminated union 设计允许命令灵活控制输出行为:
text类型返回纯文本,包装在<local-command-stdout>标签中显示compact类型触发特殊的上下文压缩流程,返回压缩结果skip类型跳过所有消息生成,用于静默操作
典型实现示例 — /version 命令展示了 local 命令的最小实现:
const call: LocalCommandCall = async () => {
return {
type: 'text',
value: MACRO.BUILD_TIME
? `${MACRO.VERSION} (built ${MACRO.BUILD_TIME})`
: MACRO.VERSION
}
}
const version = {
type: 'local',
name: 'version',
description: 'Print the version this session is running',
isEnabled: () => process.env.USER_TYPE === 'ant',
supportsNonInteractive: true,
load: () => Promise.resolve({ call })
} satisfies Command
懒加载策略 通过 load() 函数实现,返回包含 call 函数的模块对象。对于简单命令(如 /version),可直接返回 Promise.resolve();对于依赖重模块的命令(如 /clear),则通过 import() 动态加载以减少启动时间:
const clear = {
type: 'local',
name: 'clear',
description: 'Clear conversation history and free up context',
aliases: ['reset', 'new'],
load: () => import('./clear.js') // 动态导入,延迟加载依赖
} satisfies Command
commands/clear/index.ts#L10-L17
local-jsx 命令:交互式 UI 组件
local-jsx 命令返回 React 组件,用于渲染交互式界面元素,如配置面板、模型选择器、帮助屏幕等。其执行函数接收 onDone 回调,允许命令在用户交互完成后异步返回结果:
export type LocalJSXCommandCall = (
onDone: LocalJSXCommandOnDone,
context: ToolUseContext & LocalJSXCommandContext,
args: string
) => Promise<React.ReactNode>
export type LocalJSXCommandOnDone = (
result?: string,
options?: {
display?: 'skip' | 'system' | 'user' // 控制结果显示方式
shouldQuery?: boolean // 是否触发模型查询
metaMessages?: string[] // 模型可见但用户不可见的消息
nextInput?: string // 自动填充的下一个输入
submitNextInput?: boolean // 是否自动提交 nextInput
}
) => void
onDone 回调是 local-jsx 命令的核心控制机制,它允许命令:
- 控制显示方式:
display: 'skip'跳过所有消息,display: 'system'使用系统消息样式,display: 'user'使用用户消息样式 - 触发后续操作:
shouldQuery: true在命令完成后自动触发模型查询 - 注入隐藏上下文:通过
metaMessages向模型提供额外信息而不在对话历史中显示 - 预设下一个输入:
nextInput和submitNextInput实现命令链式调用
执行流程 在命令处理器中通过 Promise 包装 onDone 回调:
case 'local-jsx': {
return new Promise<SlashCommandResult>(resolve => {
const onDone = (result?: string, options?: {...}) => {
// 根据选项构建消息数组
void resolve({
messages: [...], // 根据显示选项构建
shouldQuery: options?.shouldQuery ?? false,
command,
nextInput: options?.nextInput,
submitNextInput: options?.submitNextInput
})
}
void command.load().then(mod => mod.call(onDone, context, args))
.then(jsx => {
if (jsx == null) return
// 将 JSX 组件设置到工具上下文,触发 Ink 渲染
setToolJSX({
jsx,
shouldHidePromptInput: true,
isLocalJSXCommand: true
})
})
})
}
utils/processUserInput/processSlashCommand.tsx#L551-L656
典型实现示例 — /help 命令展示了一个简单的 local-jsx 命令:
export const call: LocalJSXCommandCall = async (onDone, { options: { commands } }) => {
return <HelpV2 commands={commands} onClose={onDone} />
}
组件接收 onDone 作为 onClose 回调,当用户关闭帮助界面时触发,完成命令生命周期。
prompt 命令:模型驱动的技能扩展
prompt 命令是最强大的命令类型,它不直接返回结果,而是生成发送给 AI 模型的提示词,由模型执行复杂任务(如代码生成、代码审查、项目初始化等)。这种设计将命令系统从简单的 CLI 工具提升为技能扩展平台:
export type PromptCommand = {
type: 'prompt'
progressMessage: string // 加载时显示的进度消息
contentLength: number // 提示词长度(用于 token 预算估算)
source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
getPromptForCommand(
args: string,
context: ToolUseContext
): Promise<ContentBlockParam[]> // 生成提示词的核心方法
allowedTools?: string[] // 技能授予的额外工具权限
model?: string // 指定使用的模型
effort?: EffortValue // 任务复杂度指示
context?: 'inline' | 'fork' // 执行上下文:inline=当前会话,fork=子代理
agent?: string // fork 模式下使用的代理类型
hooks?: HooksSettings // 技能注册的钩子
paths?: string[] // 文件路径 glob 模式,限制技能可见性
}
getPromptForCommand 方法是 prompt 命令的核心,它接收用户参数和执行上下文,返回 Anthropic API 的 ContentBlockParam[] 数组(通常是文本块和工具使用块的组合)。这个设计允许技能:
- 动态生成提示词:根据参数和上下文构造不同的指令
- 嵌入上下文信息:读取文件、执行 shell 命令获取实时状态
- 控制工具权限:通过
allowedTools授予模型额外的工具访问权限
典型实现示例 — /commit 命令展示了 prompt 命令的完整实现:
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) {
const promptContent = getPromptContent() // 包含 git 状态和指令
const finalContent = await executeShellCommandsInPrompt(
promptContent,
{
...context,
getAppState() {
const appState = context.getAppState()
return {
...appState,
toolPermissionContext: {
...appState.toolPermissionContext,
alwaysAllowRules: {
...appState.toolPermissionContext.alwaysAllowRules,
command: ALLOWED_TOOLS // 注入工具权限
}
}
}
}
}
)
return [{ type: 'text', text: finalContent }]
}
}
提示词模板 使用特殊的 !\command“ 语法嵌入 shell 命令执行结果:
const promptContent = `
## Context
- Current git status: !\`git status\`
- Current git diff: !\`git diff HEAD\`
- Current branch: !\`git branch --show-current\`
- Recent commits: !\`git log --oneline -10\`
## Your task
Based on the above changes, create a single git commit...
`
executeShellCommandsInPrompt 函数会在运行时解析这些命令并替换为实际输出,实现上下文感知的动态提示词生成。
Fork 模式 是 prompt 命令的高级特性,通过 context: 'fork' 和 agent 字段配置,允许技能在独立的子代理中执行,拥有独立的上下文和 token 预算。这种模式适用于:
- 长时间运行的后台任务(如定时任务、代码审查)
- 需要隔离上下文的操作(避免污染主会话)
- 并行执行多个独立任务
utils/processUserInput/processSlashCommand.tsx#L62-L295
命令注册机制:多源合并与懒加载
Claude Code 的命令注册系统采用多源合并策略,将内置命令、用户技能、插件扩展、工作流脚本和 MCP 服务统一整合,通过懒加载 + 缓存优化性能。
命令源优先级与合并顺序
命令从以下源加载,按优先级从高到低排列(后加载的命令可覆盖同名命令):
const loadAllCommands = memoize(async (cwd: string): Promise<Command[]> => {
const [
{ skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills },
pluginCommands,
workflowCommands
] = await Promise.all([
getSkills(cwd), // 1. 用户技能目录
getPluginCommands(), // 2. 插件命令
getWorkflowCommands(cwd) // 3. 工作流脚本
])
return [
...bundledSkills, // 内置技能(基础)
...builtinPluginSkills, // 内置插件技能
...skillDirCommands, // 用户自定义技能
...workflowCommands, // 工作流脚本
...pluginCommands, // 插件命令
...pluginSkills, // 插件技能
...COMMANDS() // 内置命令(最高优先级,可覆盖前面所有)
]
})
优先级设计遵循”用户覆盖系统”原则:内置命令(COMMANDS())具有最高优先级,允许系统定义的命令覆盖用户或插件的同名命令;用户技能和插件命令处于中间层,可扩展或覆盖基础功能。
内置命令数组 COMMANDS() 使用 lodash 的 memoize 包装,确保只在首次访问时初始化:
const COMMANDS = memoize((): Command[] => [
addDir, advisor, agents, branch, btw, chrome, clear, color,
compact, config, copy, desktop, context, cost, diff, doctor,
effort, exit, fast, files, heapDump, help, ide, init,
keybindings, mcp, memory, mobile, model, outputStyle, plugin,
// ... 更多内置命令
])
export const builtInCommandNames = memoize(
(): Set<string> => new Set(COMMANDS().flatMap(_ => [_.name, ...(_.aliases ?? [])]))
)
getCommands:统一的命令访问接口
getCommands 函数是获取可用命令的唯一入口,它执行以下步骤:
graph TD
A[调用 getCommands cwd] --> B[loadAllCommands: 合并所有命令源]
B --> C[过滤 meetsAvailabilityRequirement]
C --> D[过滤 isCommandEnabled]
D --> E{存在动态技能?}
E -->|是| F[去重并插入到插件技能后]
E -->|否| G[返回 baseCommands]
F --> G
- 加载所有命令源:调用
loadAllCommands(cwd)获取完整命令列表(memoized) - 可用性过滤:调用
meetsAvailabilityRequirement(cmd)检查认证/提供商限制 - 启用状态过滤:调用
isCommandEnabled(cmd)检查特性开关和环境条件 - 动态技能注入:将运行时发现的技能插入到命令列表的适当位置
动态技能是在文件操作过程中发现的技能(如用户在对话中提及某个文件后,系统发现相关技能),它们通过 getDynamicSkills() 获取,并去重后插入到内置命令之前。
缓存失效策略
命令系统实现了多级缓存以优化性能,但也需要精确的缓存失效机制:
export function clearCommandMemoizationCaches(): void {
loadAllCommands.cache?.clear?.() // 清除命令加载缓存
getSkillToolCommands.cache?.clear?.() // 清除技能工具命令缓存
getSlashCommandToolSkills.cache?.clear?.() // 清除斜杠命令技能缓存
clearSkillIndexCache?.() // 清除技能索引缓存(外部模块)
}
export function clearCommandsCache(): void {
clearCommandMemoizationCaches()
clearPluginCommandCache() // 清除插件命令缓存
clearPluginSkillsCache() // 清除插件技能缓存
clearSkillCaches() // 清除技能目录缓存
}
缓存层级:
- loadAllCommands:缓存完整的命令列表,仅在命令源变更时失效
- getSkillToolCommands:缓存模型可调用的技能子集
- SkillIndex:技能搜索索引,在动态技能添加时需要显式清除
命令执行流程:从解析到结果
命令执行流程由 processSlashCommand 函数驱动,它处理用户输入、查找命令、分发执行并返回结果。
执行流程总览
sequenceDiagram
participant User
participant PromptInput
participant processSlashCommand
participant CommandLoader
participant Executor
participant Model
User->>PromptInput: 输入 /command args
PromptInput->>processSlashCommand: 解析输入
processSlashCommand->>processSlashCommand: parseSlashCommand
alt 命令存在
processSlashCommand->>CommandLoader: getCommand commandName
CommandLoader-->>processSlashCommand: Command 对象
alt local 命令
processSlashCommand->>Executor: command.load().call args
Executor-->>processSlashCommand: LocalCommandResult
else local-jsx 命令
processSlashCommand->>Executor: command.load().call onDone args
Executor-->>processSlashCommand: React 组件
processSlashCommand->>PromptInput: setToolJSX jsx
User->>Executor: 关闭 UI
Executor->>processSlashCommand: onDone result
else prompt 命令
processSlashCommand->>Executor: getPromptForCommand args
Executor-->>processSlashCommand: ContentBlockParam[]
processSlashCommand->>Model: 发送提示词
Model-->>processSlashCommand: 模型响应
end
processSlashCommand-->>PromptInput: SlashCommandResult
else 命令不存在
processSlashCommand-->>PromptInput: 错误消息
end
命令解析与查找
解析阶段使用 parseSlashCommand 函数从用户输入中提取命令名和参数:
const parsed = parseSlashCommand(inputString)
// 输入: "/commit add feature"
// 输出: { commandName: 'commit', args: 'add feature', isMcp: false }
utils/processUserInput/processSlashCommand.tsx#L310-L329
查找阶段使用 getCommand 函数在命令列表中查找匹配项,支持名称和别名匹配:
export function findCommand(commandName: string, commands: Command[]): Command | undefined {
return commands.find(_ =>
_.name === commandName ||
getCommandName(_) === commandName ||
_.aliases?.includes(commandName)
)
}
分发执行:类型驱动的多态
getMessagesForSlashCommand 函数是命令执行的核心分发器,根据命令类型调用不同的执行路径:
async function getMessagesForSlashCommand(
commandName: string,
args: string,
setToolJSX: SetToolJSXFn,
context: ProcessUserInputContext,
// ...
): Promise<SlashCommandResult> {
const command = getCommand(commandName, context.options.commands)
switch (command.type) {
case 'local-jsx':
// 执行 React 组件命令
case 'local':
// 执行同步命令
case 'prompt':
// 执行提示词命令
}
}
utils/processUserInput/processSlashCommand.tsx#L525-L777
三种执行路径的详细实现:
1. local-jsx 执行路径
case 'local-jsx': {
return new Promise<SlashCommandResult>(resolve => {
const onDone = (result?: string, options?: {...}) => {
// 构建消息数组,根据 display 选项决定格式
void resolve({
messages: [...],
shouldQuery: options?.shouldQuery ?? false,
command
})
}
void command.load().then(mod => mod.call(onDone, context, args))
.then(jsx => {
if (jsx == null) return
setToolJSX({
jsx,
shouldHidePromptInput: true,
isLocalJSXCommand: true
})
})
})
}
utils/processUserInput/processSlashCommand.tsx#L551-L656
2. local 执行路径
case 'local': {
const userMessage = createUserMessage({
content: prepareUserContent({
inputString: formatCommandInput(command, args),
precedingInputBlocks
})
})
const mod = await command.load()
const result = await mod.call(args, context)
if (result.type === 'skip') {
return { messages: [], shouldQuery: false, command }
}
if (result.type === 'compact') {
// 特殊处理压缩结果
return {
messages: buildPostCompactMessages(...),
shouldQuery: false,
command
}
}
// 文本结果
return {
messages: [
userMessage,
createCommandInputMessage(`<local-command-stdout>${result.value}</local-command-stdout>`)
],
shouldQuery: false,
command
}
}
utils/processUserInput/processSlashCommand.tsx#L657-L722
3. prompt 执行路径
case 'prompt': {
// 检查是否为 fork 模式
if (command.context === 'fork') {
return await executeForkedSlashCommand(
command, args, context, precedingInputBlocks, setToolJSX, canUseTool
)
}
// inline 模式:获取提示词并发送给模型
const contentBlocks = await command.getPromptForCommand(args, context)
return {
messages: [
createUserMessage({
content: formatCommandLoadingMetadata(command, args)
}),
createUserMessage({
content: contentBlocks,
allowedTools: command.allowedTools,
model: command.model,
effort: command.effort
})
],
shouldQuery: true, // 触发模型查询
allowedTools: command.allowedTools,
model: command.model,
command
}
}
utils/processUserInput/processSlashCommand.tsx#L723-L760
结果处理与消息构建
SlashCommandResult 是命令执行的统一返回类型:
type SlashCommandResult = {
messages: Message[] // 要添加到对话的消息数组
shouldQuery: boolean // 是否触发模型查询
command: Command // 执行的命令对象
allowedTools?: string[] // 授予的工具权限(仅 prompt 命令)
model?: string // 指定的模型(仅 prompt 命令)
resultText?: string // 命令结果文本(用于日志)
nextInput?: string // 自动填充的下一个输入
submitNextInput?: boolean // 是否自动提交
}
utils/processUserInput/processSlashCommand.tsx#L49-L51
消息格式化使用 XML 标签标记命令输出,便于解析和显示:
<local-command-stdout>包装正常输出<local-command-stderr>包装错误输出<command-input>包装命令调用记录
实战:创建自定义命令
示例 1:创建简单的 local 命令
创建 src/commands/hello/index.ts:
import type { Command, LocalCommandCall } from '../../types/command.js'
const call: LocalCommandCall = async (args, context) => {
const name = args.trim() || 'World'
return {
type: 'text',
value: `Hello, ${name}! Current directory: ${context.cwd}`
}
}
const hello = {
type: 'local',
name: 'hello',
description: 'Say hello to someone',
argumentHint: '[name]',
supportsNonInteractive: true,
load: () => Promise.resolve({ call })
} satisfies Command
export default hello
然后在 src/commands.ts 中注册:
import hello from './commands/hello/index.js'
const COMMANDS = memoize((): Command[] => [
// ... 其他命令
hello,
])
示例 2:创建交互式 local-jsx 命令
创建 src/commands/greet/index.ts:
import type { Command } from '../../commands.js'
const greet = {
type: 'local-jsx',
name: 'greet',
description: 'Show a greeting dialog',
load: () => import('./greet.js')
} satisfies Command
export default greet
创建 src/commands/greet/greet.tsx:
import * as React from 'react'
import { Box, Text } from 'ink'
import type { LocalJSXCommandCall } from '../../types/command.js'
const GreetingDialog: React.FC<{
onDone: (result?: string) => void
initialName?: string
}> = ({ onDone, initialName }) => {
const [name, setName] = React.useState(initialName || '')
return (
<Box flexDirection="column">
<Text>Enter your name:</Text>
<TextInput
value={name}
onChange={setName}
onSubmit={() => onDone(`Hello, ${name}!`)}
/>
</Box>
)
}
export const call: LocalJSXCommandCall = async (onDone, context, args) => {
return <GreetingDialog onDone={onDone} initialName={args.trim()} />
}
示例 3:创建 prompt 技能
创建 .claude/skills/test-coverage/SKILL.md:
---
name: test-coverage
description: Generate comprehensive test coverage for the specified file
allowedTools:
- Bash(npm test:*)
- Bash(jest:*)
- FileRead
- FileWrite
---
## Task
Generate comprehensive test coverage for the file specified in $ARGUMENTS.
## Steps
1. Read the target file to understand its structure and functions
2. Identify all exported functions, classes, and edge cases
3. Generate a test file that covers:
- Happy path scenarios
- Edge cases and error conditions
- Boundary conditions
- Integration points
4. Run the tests to verify they pass
5. Report the coverage percentage
## Context
- Current file: $ARGUMENTS
- Test framework: !`cat package.json | grep -o '"jest"\|"mocha"\|"vitest"' | head -1`
用户调用 /test-coverage src/utils/format.ts 时,技能会读取文件、生成测试、运行并报告覆盖率。
架构设计原则总结
Claude Code 的命令架构体现了以下设计原则:
- 类型安全的多态:通过 discriminated union 实现编译时类型检查,避免运行时错误
- 懒加载优先:所有命令通过
load()延迟加载,减少启动时间 - 多源合并:统一的注册机制支持内置、用户、插件、MCP 等多种命令源
- 关注点分离:命令定义、加载、执行、结果显示各司其职
- 扩展性:通过 prompt 命令将命令系统扩展为技能平台,支持无限可能
这种架构使 Claude Code 既能作为传统 CLI 工具使用,又能作为 AI 驱动的智能助手运行,实现了命令行界面与 AI 能力的无缝融合。
下一步阅读建议:
- 深入了解核心命令的实现细节,请阅读 核心命令:commit、review、compact 实现解析
- 理解工具系统如何与命令协作,请查看 工具架构:Tool 接口与权限模型
- 探索如何创建自定义技能和插件,请参阅官方文档中的 Skills 和 Plugins 章节