命令系统
如何在 REPL 内键入、注册、组装成管道、在输入时处理以及分派斜杠命令
1. 什么是命令?
Every /something 您输入的 Claude Code 是 Command — 一个 TypeScript 对象,它携带元数据(名称、描述、可用性)以及三种执行策略之一(本地、本地 jsx 或提示)。 命令位于 src/commands.ts 和 src/commands/ 目录树。
顶级类型是受歧视联合:
export type Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)
判别式是 type 字段 — 每个命令对象必须准确声明三个字符串文字之一: "local", "local-jsx", 或者 "prompt".
COMMANDS(),加上从技能目录、插件、工作流程和 MCP 服务器加载的任意数量的动态 — 所有这些都在运行时通过 getCommands(cwd).
2. 三种命令类型
Local
纯 TypeScript 函数。 在当前进程中同步运行。 返回一个 LocalCommandResult - 任何一个 {type:'text'}, {type:'compact'}, 或者 {type:'skip'}.
JSX 位置
将 React/Ink 组件渲染到终端 TUI 中。 返回一个 ReactNode 通过 call(onDone, context, args) 签名。 阻止桥接/远程模式。
Prompt
扩展到发送到模型的文本内容块。 声明 getPromptForCommand(args, context) 返回 ContentBlockParam[]。 为技能、工作流程和内置代理流程提供支持。
类型决策树
深潜: local - 这 /compact command
注册于 src/commands/compact/index.ts:
const compact = { type: 'local', name: 'compact', description: 'Clear conversation history but keep a summary in context', isEnabled: () => !isEnvTruthy(process.env.DISABLE_COMPACT), supportsNonInteractive: true, argumentHint: '<optional custom summarization instructions>', load: () => import('./compact.js'), } satisfies Command
The load() 模式对于本地命令来说是通用的——它将繁重的实现模块推迟到实际调用命令之前,从而保持快速启动。 该模块必须导出 { call: LocalCommandCall }:
export const call: LocalCommandCall = async (args, context) => { // args = trimmed string after "/compact" // context = ToolUseContext + REPL state const customInstructions = args.trim() // ... runs compaction, returns: return { type: 'compact', compactionResult, displayText } }
The LocalCommandCall 签名总是收到 (args: string, context: LocalJSXCommandContext) — 一个原始参数字符串(命令名称后面的所有内容)和一个丰富的上下文对象,其中包括消息、setMessages、选项和中止控制器。
深潜: local-jsx - 这 /help command
注册于 src/commands/help/index.ts:
const help = { type: 'local-jsx', name: 'help', description: 'Show help and available commands', load: () => import('./help.js'), } satisfies Command
加载的模块导出 { call: LocalJSXCommandCall }:
export const call: LocalJSXCommandCall = async ( onDone, { options: { commands } }, ) => { return <HelpV2 commands={commands} onClose={onDone} /> }
请注意 颠倒论证顺序 与本地命令相比: (onDone, context, args)。 这 onDone 回调接受可选的字符串结果加上选项,例如 shouldQuery, display, 和 nextInput。 什么时候 onDone() 触发后,REPL 会拆除组件并恢复正常输入。
The /model 命令使用相同的模式,但为其描述添加了一个计算的 getter,因此它始终反映当前选定的模型名称:
export default { type: 'local-jsx', name: 'model', get description() { return `Set the AI model (currently ${renderModelName(getMainLoopModel())})` }, argumentHint: '[model]', get immediate() { return shouldInferenceConfigCommandBeImmediate() }, load: () => import('./model.js'), }
深潜: prompt - 这 /commit command
提示命令定义 getPromptForCommand 而不是 load。 他们返回一个数组 ContentBlockParam 命令触发时成为第一个用户转动的对象:
const command = { type: 'prompt', name: 'commit', description: 'Create a git commit', allowedTools: ['Bash(git add:*)', 'Bash(git status:*)', 'Bash(git commit:*)'], contentLength: 0, // 0 = dynamic (computed at call time) progressMessage: 'creating commit', source: 'builtin', async getPromptForCommand(_args, context) { const promptContent = getPromptContent() const finalContent = await executeShellCommandsInPrompt( promptContent, context, '/commit' ) return [{ type: 'text', text: finalContent }] }, } satisfies Command
The executeShellCommandsInPrompt 助手扫描提示 !`shell cmd` 反引号模式并在文本到达模型之前将其替换为实时 shell 输出。 就是这样 /commit 内联当前 git status, git diff HEAD,最近登录会自动提示。
allowedTools 限制模型在此命令执行期间可以调用哪些工具 - 一个安全层,可防止模型调用声明列表之外的任何内容。
3. CommandBase 合约
所有这三种类型都有一个共同的基础。 最重要的领域:
| Field | Type | Description |
|---|---|---|
name | string | 斜杠命令名称,例如 "compact"。 用户类型 /compact. |
description | string | 显示在预先输入和 /help。 可以是动态描述的吸气剂。 |
aliases | string[]? | 替代名称。 /clear 也回应 /reset and /new. |
isEnabled | () => boolean | 功能标志或环境变量保护。 每一次都被称为新鲜 getCommands() pass. |
isHidden | boolean? | 隐藏预输入和帮助 UI,同时仍允许调用。 |
availability | CommandAvailability[]? | 授权门: 'claude-ai' (订阅者)或 'console' (API 密钥用户)。 |
argumentHint | string? | 预先输入命令名称后显示的灰色提示,例如 [model]. |
immediate | boolean? | If true,命令运行时无需等待任何正在进行的 AI 请求停止。 |
loadedFrom | string? | 来源标签: 'skills', 'plugin', 'bundled', 'commands_DEPRECATED', 'mcp'. |
whenToUse | string? | 面向模型的使用提示(来自 SKILL.md frontmatter)。 控制技能工具的可见性。 |
isSensitive | boolean? | 如果为真,则从对话历史记录中编辑命令参数。 |
availability 是在功能标志之前运行的静态身份验证提供程序检查 - 它控制 谁能看到 命令。 isEnabled() 是对功能标志和环境变量的运行时检查 - 它控制 该命令现在是否处于活动状态。 两者都必须通过命令才能出现在 getCommands().
4. 注册流程
命令通过多级汇编管道到达 REPL src/commands.ts:
第 1 阶段:静态核心命令 (COMMANDS())
记忆函数返回约 80 个内置命令的规范列表 - /help, /model, /clear, /compact, /commit, /review等等。它被声明为函数(而不是常量),以便在启动时配置可读后执行:
const COMMANDS = memoize((): Command[] => [ addDir, advisor, agents, branch, btw, clear, compact, config, cost, diff, ... // feature-flagged commands are spread conditionally: ...(ultraplan ? [ultraplan] : []), ...(!isUsing3PServices() ? [logout, login()] : []), ])
第 2 阶段:动态命令源 (loadAllCommands)
所有非静态源都是并行加载和合并的。 合并顺序是确定性的,对于重复数据删除很重要:
const loadAllCommands = memoize(async (cwd: string) => { const [ { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills }, pluginCommands, workflowCommands, ] = await Promise.all([ getSkills(cwd), getPluginCommands(), getWorkflowCommands ? getWorkflowCommands(cwd) : Promise.resolve([]), ]) return [ ...bundledSkills, // lowest index → highest priority in dedup ...builtinPluginSkills, ...skillDirCommands, ...workflowCommands, ...pluginCommands, ...pluginSkills, ...COMMANDS(), // built-ins last ] })
第三阶段:过滤和动态技能插入(getCommands)
每次致电 getCommands(cwd) 重新运行可用性并针对记忆的命令池进行 isEnabled 检查。 动态技能(在文件操作期间发现)插入到内置命令块之前:
export async function getCommands(cwd: string): Promise<Command[]> { const allCommands = await loadAllCommands(cwd) const dynamicSkills = getDynamicSkills() const baseCommands = allCommands.filter( _ => meetsAvailabilityRequirement(_) && isCommandEnabled(_) ) // Insert dynamic skills at the right position... }
四种技能来源及其起源
技能(从 Markdown 文件加载的提示类型命令)从四个地方到达: getSkills(cwd):
- skillDirCommands — 加载自
.claude/skills/在项目或用户主页中。 这些是您或您的团队编写的 SKILL.md 文件。 - pluginSkills - 已安装插件中捆绑的技能(例如
/plugin install frontend-design@claude-plugins-official). - bundledSkills — 技能编译到 Claude Code 二进制文件本身中(在启动时通过同步注册
getBundledSkills()). - builtinPluginSkills - 来自始终启用的内置插件的技能,通过
getBuiltinPluginSkillCommands().
如果任何源加载失败,则会捕获并记录错误,但其余部分继续 - 技能加载故意是非致命的。
INTERNAL_ONLY_COMMANDS — 内部构建门
命令子集仅存在于 Anthropic 内部版本中。 它们被声明在 INTERNAL_ONLY_COMMANDS 并且仅附加到 COMMANDS() when process.env.USER_TYPE === 'ant':
export const INTERNAL_ONLY_COMMANDS = [ backfillSessions, breakCache, bughunter, commit, commitPushPr, mockLimits, bridgeKick, // ...many more ].filter(Boolean) // Inside COMMANDS(): ...(!process.env.IS_DEMO && process.env.USER_TYPE === 'ant' ? INTERNAL_ONLY_COMMANDS : [])
这意味着像这样的命令 /commit and /bughunter 实际上,公共二进制文件中不存在它们 - 它们被 Bun 的捆绑程序消除了死代码。
5. 输入处理
当用户提交以以下内容开头的文本时 /,REPL 在任何内容接触模型之前运行查找和分派。 关键帮手住在最底层 src/commands.ts:
查找功能
// Returns the first Command matching by name, userFacingName, or alias export function findCommand(commandName: string, commands: Command[]): Command | undefined // Presence check (wraps findCommand) export function hasCommand(commandName: string, commands: Command[]): boolean // Throws ReferenceError listing all available commands if not found export function getCommand(commandName: string, commands: Command[]): Command
The findCommand 匹配器按顺序检查三件事: _.name === commandName, getCommandName(_) === commandName (面向用户的覆盖),以及 _.aliases?.includes(commandName)。 这就是为什么 /reset and /new 两者都会触发清除命令。
参数传递
命令名称(已修剪)之后的所有内容都是 参数字符串。 没有框架级参数解析——每个命令负责解释自己的参数。 /compact focus on the database layer delivers "focus on the database layer" 到紧凑处理程序,该处理程序将其传递到汇总模型,如下所示 customInstructions.
提示中的 Shell 命令替换
提示命令通常内联嵌入 shell 输出。 这 executeShellCommandsInPrompt 实用程序扫描提示文本 !`shell command` 模式并在提示到达模型之前将其替换为实时输出。 /commit 使用它来自动注入 git status, git diff HEAD,以及最近的日志输出:
const PROMPT = `## 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...`
UI 与模型的描述格式
The formatDescriptionWithSource(cmd) 实用程序为面向用户的表面(提前输入、帮助屏幕)添加出处注释,而不会污染面向模型的描述。 来自名为“frontend-design”的插件的技能显示为 "(frontend-design) Polish and refine UI components" 在自动完成中,但模型只看到 "Polish and refine UI components".
6. REPL 集成
REPL(读取-评估-打印循环)是交互式会话的核心。 它通过渲染 launchRepl in src/replLauncher.tsx,它会延迟加载 App and REPL 墨水成分。 命令根据其类型以三个不同的调度路径流过 REPL:
processSlash命令流程
在 REPL 内部,斜杠命令处理遵循以下顺序:
The immediate 命令上的标志绕过了正常的“等待任何正在进行的 AI 请求停止”行为。 /status sets immediate: true 因此,即使长时间生成正在运行,您也可以检查连接状态。
按类型处理结果
| Type | Dispatch | Post-dispatch |
|---|---|---|
local |
await cmd.load() then call(args, ctx) |
如果结果是 {type:'compact'}, REPL 替换消息。 如果 {type:'text'},添加系统消息。 如果 {type:'skip'},什么也不做。 |
local-jsx |
await cmd.load() 然后渲染返回 ReactNode |
REPL 安装组件。 什么时候 onDone(result, opts) 触发:卸载,可选地附加结果,可选地调用模型,如果 opts.shouldQuery = true. |
prompt |
await cmd.getPromptForCommand(args, ctx) |
Returned ContentBlockParam[] 成为第一条用户留言。 REPL 进入查询模式,就像用户键入该文本一样。 progressMessage 显示在状态行中。 |
onDone 回调合约 (local-jsx)
The LocalJSXCommandOnDone 回调比看起来更丰富。 完整签名:
type LocalJSXCommandOnDone = ( result?: string, options?: { display?: 'skip' | 'system' | 'user' // default: 'user' shouldQuery?: boolean // send messages to model after done metaMessages?: string[] // model-visible but hidden from UI nextInput?: string // pre-fill the input box submitNextInput?: boolean // auto-submit nextInput } ) => void
这意味着 JSX 命令可以在关闭时自动将预先编写的消息注入到模型管道中 - 例如 模型选择器可以在模型选择后自动提交确认。
非交互模式(无头CLI)
当 Claude Code 在非交互(无头)模式下运行时(例如 claude -p "..."), 本地命令可以声明 supportsNonInteractive: true 保持可用。 /compact and /cost 两者都这样做。 由于没有终端 TUI,JSX 命令在无头模式下始终被阻止。
提示命令声明 disableNonInteractive: true 当它们依赖于交互状态(例如活动会话消息)时。 执行繁重模型工作的内置提示命令通常在两种模式下都可以工作。
7. 可用性和功能门控
命令有两个独立的门层,在命令出现在 REPL 中之前,两者都必须经过:
授权提供商门
检查者 meetsAvailabilityRequirement(cmd)。 两个可能的值: 'claude-ai' (需要 OAuth 订户)或 'console' (需要直接 API 密钥用户)。 没有此字段的命令将无条件通过。 每一次都重新评估 getCommands() 调用 so auth 之后更改 /login 立即生效。
运行时功能门
一个功能 () => boolean。 可以通过读取 GrowthBook 标志 feature('FLAG_NAME')、环境变量、平台检查或任何其他运行时条件。 每次过滤都被称为新鲜。 命令不带 isEnabled 默认为 true.
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': if (!isClaudeAISubscriber() && !isUsing3PServices() && isFirstPartyAnthropicBaseUrl()) return true; break } } return false }
功能标记命令
仅当 Bun 捆绑包功能标志处于活动状态时,才存在多个命令。 在构建时,如果该功能关闭,Bun 的死代码消除会删除整个 require 链:
const ultraplan = feature('ULTRAPLAN') ? require('./commands/ultraplan.js').default : null const voiceCommand = feature('VOICE_MODE') ? require('./commands/voice/index.js').default : null // Then in COMMANDS(): ...(ultraplan ? [ultraplan] : []), ...(voiceCommand ? [voiceCommand] : []),
8. 缓存管理
命令加载的成本很高——它涉及磁盘 I/O、YAML 解析、动态导入和 MCP 往返。 三层记忆可快速重复调用:
| Cache | Key | 它存储什么 |
|---|---|---|
COMMANDS() |
无(单例) | 静态内置命令列表。 清除者 clearCommandMemoizationCaches(). |
loadAllCommands |
cwd string |
合并给定工作目录的所有来源的命令池。 |
getSkillToolCommands |
cwd string |
符合 SkillTool 模型调用条件的筛选提示命令。 |
针对不同的失效场景,存在两种粒度明确的函数:
// Clears command memoization only — does NOT clear skill file caches. // Use when dynamic skills are added mid-session. export function clearCommandMemoizationCaches(): void { loadAllCommands.cache?.clear?.() getSkillToolCommands.cache?.clear?.() getSlashCommandToolSkills.cache?.clear?.() clearSkillIndexCache?.() // outer search index must also be cleared } // Full reset: command + plugin + skill file caches. // Use when plugins are installed/removed or skills dir changes. export function clearCommandsCache(): void { clearCommandMemoizationCaches() clearPluginCommandCache() clearPluginSkillsCache() clearSkillCaches() }
meetsAvailabilityRequirement and isCommandEnabled 是故意的 not 已记忆——每次都会对它们进行重新评估 getCommands() 称呼。 这确保了 /login 或者 GrowthBook 标志翻转立即生效,无需缓存清除。
9. 桥接和远程模式
Claude Code 可以在 远程模式 (通过浏览器/移动设备访问)并且可以通过 bridge (远程控制协议)。 两种模式都限制可用的命令。
远程安全命令(--remote 标志)
当 Claude Code 开头时 --remote,仅命令 REMOTE_SAFE_COMMANDS 在 CCR 初始化消息到达之前可用。 这些命令仅影响本地 TUI 状态,不会影响文件系统、git、shell 或 IDE:
export const REMOTE_SAFE_COMMANDS: Set<Command> = new Set([ session, // Shows QR code / URL for remote session exit, // Exit the TUI clear, // Clear screen help, // Show help theme, // Change terminal theme cost, // Show session cost plan, // Plan mode toggle // ...more ])
桥接安全命令
桥接器(移动/Web 客户端)通过远程控制连接发送斜杠命令。 这 isBridgeSafeCommand 谓词决定执行哪些谓词而不是默默地删除:
export function isBridgeSafeCommand(cmd: Command): boolean { if (cmd.type === 'local-jsx') return false // always blocked (renders Ink UI) if (cmd.type === 'prompt') return true // always safe (expands to text) return BRIDGE_SAFE_COMMANDS.has(cmd) // 'local' needs explicit opt-in }
规则很直观: local-jsx 命令呈现桥客户端无法显示的终端 UI,因此它们始终被阻止。 prompt 命令仅生成文本,因此它们始终是安全的。 清楚的 local 命令必须明确列出在 BRIDGE_SAFE_COMMANDS - 默认情况下它们被阻止。 允许名单包括 /compact, /clear, /cost, /files,以及其他一些。
10. 要点
-
🔵
每个斜杠命令都是一个
Command对象——一个CommandBase受歧视联盟 三种执行类型:local(纯TS函数),local-jsx(墨水成分),以及prompt(注入模型上下文的文本)。 -
⚡
Both
localandlocal-jsx命令使用 延迟加载 viaload: () => import('./cmd.js')— 在实际调用命令之前不会导入实现模块,从而保持快速的启动时间。 -
🔀
The 注册管道 通过合并捆绑技能、插件技能、技能目录命令、工作流程、插件命令和静态内置命令
loadAllCommands(cwd),然后过滤availabilityandisEnabled在每一个getCommands()call. -
🛡️
两个独立的门 控制可见性:
availability(静态身份验证提供者检查,每次调用重新评估)和isEnabled()(运行时功能标志检查)。 两者都必须通过。 两者都不会被记住,因此身份验证更改会立即生效。 -
🌐
桥接和远程模式 应用第三个过滤器。
local-jsx命令始终通过桥被阻止(它们呈现终端 UI)。prompt命令始终是允许的。local命令需要明确选择加入BRIDGE_SAFE_COMMANDS. -
📝
提示命令使用 外壳替换 via
!`cmd`在到达模型之前将实时 shell 输出嵌入到提示中的模式 - 这就是如何/commitauto-injectsgit status,git diff HEAD和最近的日志,无需粘贴它们。 -
🔒
仅供内部使用的命令(
/commit,/bughunter等)被包裹在INTERNAL_ONLY_COMMANDSand dead-code-eliminated 在公共二进制文件中构建时由 Bun 生成 - 它们在用户安装的版本中根本不存在。
11. 测验
Q1. 您想要添加一个新的斜线命令,该命令打开交互式菜单以选择 GitHub PR 进行审核。 您应该使用哪种类型?
Q2。 用户安装新插件并立即输入 /plugin-command。 未找到该命令。 最可能的原因是什么?
Q3。 关于哪一个说法 REMOTE_SAFE_COMMANDS vs BRIDGE_SAFE_COMMANDS 是正确的吗?
Q4。 一个 prompt 命令集 allowedTools: ['Bash(git add:*)', 'Bash(git commit:*)']。 What does this do?
Q5. 目的是什么 !`shell command` 提示命令的模板字符串中的模式?