基于 Claude Code v2.1.88 源码分析 核心文件:
src/types/command.ts,src/commands.ts,src/utils/processUserInput/
Claude Code 的命令系统建立在一个精心设计的 TypeScript 类型体系之上。所有命令共享 CommandBase 基础类型,通过判别式联合 (discriminated union) 分为三种执行模式。
type CommandBase = {
availability?: CommandAvailability[] // 认证/平台门控
description: string // 用户可见描述
hasUserSpecifiedDescription?: boolean // 是否有用户自定义描述
isEnabled?: () => boolean // 动态启用/禁用 (默认 true)
isHidden?: boolean // 从 typeahead/help 中隐藏 (默认 false)
name: string // 唯一标识名
aliases?: string[] // 别名列表
isMcp?: boolean // 是否为 MCP 命令
argumentHint?: string // 参数提示 (灰色显示)
whenToUse?: string // 技能规范中的使用场景描述
version?: string // 命令/技能版本
disableModelInvocation?: boolean // 禁止模型调用此命令
userInvocable?: boolean // 用户是否可直接 /name 调用
loadedFrom?: 'commands_DEPRECATED' | 'skills' | 'plugin'
| 'managed' | 'bundled' | 'mcp' // 来源标记
kind?: 'workflow' // 工作流命令标识
immediate?: boolean // 立即执行,不等待队列停顿
isSensitive?: boolean // 参数脱敏 (历史记录中显示 ***)
userFacingName?: () => string // 显示名 (可能与 name 不同)
}生成提示词内容注入到对话中,由模型执行实际操作。适用于需要模型推理的复杂任务。
type PromptCommand = {
type: 'prompt'
progressMessage: string // 执行中的进度文本
contentLength: number // 内容长度 (用于 token 估算)
argNames?: string[] // 参数名列表
allowedTools?: string[] // 授予模型的额外工具权限
model?: string // 指定模型别名
source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
pluginInfo?: { pluginManifest: PluginManifest; repository: string }
context?: 'inline' | 'fork' // inline=内联展开, fork=子代理执行
agent?: string // fork 模式下的代理类型
effort?: EffortValue // 推理深度
paths?: string[] // glob 匹配模式,限定可见性
hooks?: HooksSettings // 技能专用钩子
skillRoot?: string // 技能资源根目录
getPromptForCommand(
args: string,
context: ToolUseContext
): Promise<ContentBlockParam[]> // 核心: 生成注入到对话的内容
}关键特性:
context: 'fork'模式下命令在独立子代理中运行,拥有独立 token 预算allowedTools可为技能临时授权额外工具 (如/commit授权Bash(git add:*))paths实现延迟发现: 仅当模型操作匹配路径的文件后,技能才变为可见
在 Node.js 进程中同步执行,返回文本结果。不渲染 UI 组件。
type LocalCommand = {
type: 'local'
supportsNonInteractive: boolean // 是否支持 -p 模式
load: () => Promise<LocalCommandModule>
}
type LocalCommandModule = {
call: (args: string, context: LocalJSXCommandContext) => Promise<LocalCommandResult>
}
type LocalCommandResult =
| { type: 'text'; value: string } // 文本输出
| { type: 'compact'; compactionResult: CompactionResult; displayText?: string } // 压缩操作
| { type: 'skip' } // 静默完成渲染 React/Ink 组件到终端,支持完整的交互式 UI (列表选择、表单输入等)。
type LocalJSXCommand = {
type: 'local-jsx'
load: () => Promise<LocalJSXCommandModule>
}
type LocalJSXCommandModule = {
call: (
onDone: LocalJSXCommandOnDone,
context: ToolUseContext & LocalJSXCommandContext,
args: string
) => Promise<React.ReactNode>
}
type LocalJSXCommandOnDone = (
result?: string,
options?: {
display?: 'skip' | 'system' | 'user' // 结果显示方式
shouldQuery?: boolean // 完成后是否触发模型查询
metaMessages?: string[] // 模型可见但用户隐藏的附加消息
nextInput?: string // 链式命令输入
submitNextInput?: boolean // 自动提交下一条输入
}
) => voidtype Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)// 解析用户可见名称,优先使用 userFacingName()
function getCommandName(cmd: CommandBase): string {
return cmd.userFacingName?.() ?? cmd.name
}
// 判断命令是否启用,默认 true
function isCommandEnabled(cmd: CommandBase): boolean {
return cmd.isEnabled?.() ?? true
}type CommandAvailability =
| 'claude-ai' // claude.ai OAuth 订阅用户 (Pro/Max/Team/Enterprise)
| 'console' // Console API Key 用户 (直连 api.anthropic.com)availability 与 isEnabled 是两个独立维度:
| 维度 | 含义 | 评估时机 |
|---|---|---|
availability |
谁能用 (认证/平台要求,静态) | 每次 getCommands() 调用 |
isEnabled() |
现在开不开 (功能标志/环境变量,动态) | 每次 getCommands() 调用 |
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':
// 排除 3P (Bedrock/Vertex/Foundry) 和自定义 base URL 用户
if (!isClaudeAISubscriber() && !isUsing3PServices()
&& isFirstPartyAnthropicBaseUrl())
return true
break
}
}
return false
}| 命令 | availability | 说明 |
|---|---|---|
/upgrade |
['claude-ai'] |
升级到 Max 计划 |
/usage |
['claude-ai'] |
查看计划用量 |
/voice |
['claude-ai'] |
语音模式 |
/chrome |
['claude-ai'] |
Chrome 扩展设置 |
/desktop |
['claude-ai'] |
桌面应用跳转 |
/mobile |
无 (全平台) | 移动端 QR 码 |
/install-github-app |
['claude-ai', 'console'] |
GitHub Actions 安装 |
/fast |
['claude-ai', 'console'] |
快速模式切换 |
/install-slack-app |
['claude-ai'] |
Slack 应用安装 |
/web-setup |
['claude-ai'] |
Web 远程环境设置 |
┌─────────────────────────────────────────────────┐
│ loadAllCommands(cwd) │
│ (memoized, 仅首次执行加载) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ bundledSkills │ │builtinPlugin │ │
│ │ (内置技能) │ │ Skills │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────┴──────┐ ┌───────┴───────┐ │
│ │ skillDir │ │ workflow │ │
│ │ Commands │ │ Commands │ │
│ │(.claude/ │ │(WORKFLOW_ │ │
│ │ skills/) │ │ SCRIPTS flag) │ │
│ └──────┬──────┘ └───────┬───────┘ │
│ │ │ │
│ ┌──────┴──────┐ ┌───────┴───────┐ │
│ │ plugin │ │ plugin │ │
│ │ Commands │ │ Skills │ │
│ └──────┬──────┘ └───────┬───────┘ │
│ │ │ │
│ └───────┬──────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ COMMANDS() │ ◄── 内置命令列表 │
│ └──────────────┘ │
└─────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ getCommands(cwd) │
│ (每次调用都重新过滤) │
│ │
│ 1. loadAllCommands(cwd) ← memoized │
│ 2. getDynamicSkills() ← 文件操作中发现的技能 │
│ 3. filter: │
│ - meetsAvailabilityRequirement() │
│ - isCommandEnabled() │
│ 4. dedupe dynamic skills │
│ 5. 返回最终命令列表 │
└─────────────────────────────────────────────────┘
为什么不 memoize getCommands: 认证状态可以在会话中改变 (如用户执行 /login),所以 availability 和 isEnabled 检查必须每次重新评估。
使用 lodash/memoize 延迟初始化,因为底层函数在模块加载时读取配置会报错:
const COMMANDS = memoize((): Command[] => [
addDir, advisor, agents, branch, btw, chrome, clear, color,
compact, config, copy, desktop, context, contextNonInteractive,
cost, diff, doctor, effort, exit, fast, files, heapDump,
help, ide, init, keybindings, installGitHubApp, installSlackApp,
mcp, memory, mobile, model, outputStyle, remoteEnv, plugin,
pr_comments, releaseNotes, reloadPlugins, rename, resume,
session, skills, stats, status, statusline, stickers, tag,
theme, feedback, review, ultrareview, rewind, securityReview,
terminalSetup, upgrade, extraUsage, extraUsageNonInteractive,
rateLimitOptions, usage, usageReport, vim,
// Feature-gated commands (条件包含)
...(webCmd ? [webCmd] : []),
...(forkCmd ? [forkCmd] : []),
...(buddy ? [buddy] : []),
...(proactive ? [proactive] : []),
...(briefCommand ? [briefCommand] : []),
...(assistantCommand ? [assistantCommand] : []),
...(bridge ? [bridge] : []),
...(voiceCommand ? [voiceCommand] : []),
thinkback, thinkbackPlay, permissions, plan, privacySettings,
hooks, exportCommand, sandboxToggle,
...(!isUsing3PServices() ? [logout, login()] : []), // 3P 用户不显示登录/登出
passes, tasks,
...(workflowsCmd ? [workflowsCmd] : []),
...(torch ? [torch] : []),
// Internal-only: 仅 Anthropic 员工可见
...(process.env.USER_TYPE === 'ant' && !process.env.IS_DEMO
? INTERNAL_ONLY_COMMANDS : []),
])所有 local 和 local-jsx 命令使用 load() 延迟导入:
// 命令注册只声明元数据,不加载实现
const compact = {
type: 'local',
name: 'compact',
description: 'Clear conversation history...',
load: () => import('./compact.js'), // 用户调用时才加载
} satisfies Command这种模式确保启动时只解析元数据,实际模块在首次调用时按需加载。
用户输入 "/compact some args"
│
▼
┌──────────────────────────────┐
│ processUserInput() │
│ processUserInput.ts │
│ │
│ 1. 解析输入类型 (string/ │
│ ContentBlockParam[]) │
│ 2. 处理图片/粘贴内容 │
│ 3. Bridge 安全检查 │
│ 4. Ultraplan 关键词检测 │
│ 5. 识别 "/" 前缀 │
│ → processSlashCommand() │
└──────────┬───────────────────┘
│
▼
┌──────────────────────────────┐
│ processSlashCommand() │
│ processSlashCommand.tsx │
│ │
│ 1. parseSlashCommand() │
│ → {commandName, args, │
│ isMcp} │
│ 2. hasCommand() 检查 │
│ 3. looksLikeCommand() 判断 │
│ (排除文件路径误识别) │
│ 4. getMessagesForSlash...()│
└──────────┬───────────────────┘
│
▼
┌──────────────────────────────┐
│ getMessagesForSlashCommand() │
│ │
│ 1. getCommand() 解析命令 │
│ 2. recordSkillUsage() 追踪 │
│ 3. userInvocable 检查 │
│ 4. switch (command.type): │
│ ├─ 'local-jsx' ──────► │
│ │ load() → mod.call() │
│ │ → setToolJSX(jsx) │
│ │ → onDone() 回调 │
│ ├─ 'local' ──────────► │
│ │ load() → mod.call() │
│ │ → LocalCommandResult│
│ └─ 'prompt' ─────────► │
│ ├─ fork? ──► executeForkedSlashCommand()
│ │ (子代理运行)
│ └─ inline? ► getMessagesForPromptSlashCommand()
│ (生成提示词注入对话)
└──────────────────────────────┘
// src/utils/slashCommandParsing.ts
type ParsedSlashCommand = {
commandName: string
args: string
isMcp: boolean
}
function parseSlashCommand(input: string): ParsedSlashCommand | null {
// "/search foo bar" → { commandName: 'search', args: 'foo bar', isMcp: false }
// "/mcp:tool (MCP) arg1" → { commandName: 'mcp:tool (MCP)', args: 'arg1', isMcp: true }
}case 'local-jsx': {
return new Promise<SlashCommandResult>(resolve => {
let doneWasCalled = false
const onDone = (result?, options?) => {
doneWasCalled = true
// display: 'skip' → 无消息
// display: 'system' → 命令输入/输出消息 (不发送到模型)
// default → 用户消息 (发送到模型)
resolve({ messages, shouldQuery: options?.shouldQuery ?? false, command })
}
command.load()
.then(mod => mod.call(onDone, context, args))
.then(jsx => {
if (doneWasCalled) return // onDone 在 call() 中已触发
setToolJSX({
jsx,
shouldHidePromptInput: true, // 隐藏输入框
isLocalJSXCommand: true, // 标记为 JSX 命令
isImmediate: command.immediate // 立即命令不等队列
})
})
})
}case 'local': {
const mod = await command.load()
const result = await mod.call(args, context)
switch (result.type) {
case 'skip': return { messages: [], shouldQuery: false }
case 'compact': // 特殊处理: 重建消息列表
case 'text': return { messages: [userMsg, commandOutputMsg], shouldQuery: false }
}
}case 'prompt': {
if (command.context === 'fork') {
return executeForkedSlashCommand(...) // 子代理路径
}
return getMessagesForPromptSlashCommand(...) // 内联路径
}内联路径生成的消息结构:
ProgressMessage-- 显示 "Loading /command..."- 调用
command.getPromptForCommand(args, context)获取内容 - 注册技能钩子 (
registerSkillHooks) - 添加技能调用记录 (
addInvokedSkill) - 构建
UserMessage带命令标签和技能内容 - 设置
shouldQuery: true触发模型响应
context: 'fork' 的命令在子代理中运行:
┌─────────────────────────────────────────┐
│ executeForkedSlashCommand() │
│ │
│ 1. prepareForkedCommandContext() │
│ - 获取技能内容 │
│ - 创建隔离的 getAppState │
│ - 选择代理定义 │
│ │
│ 2. Kairos 模式? │
│ ├─ 是: 后台 fire-and-forget │
│ │ - 等待 MCP settle │
│ │ - runAgent() 异步 │
│ │ - enqueuePendingNotification() │
│ │ - 立即返回空消息 │
│ └─ 否: 同步执行 │
│ - runAgent() 带进度 UI │
│ - 实时更新 setToolJSX() │
│ - 返回结果文本 │
└─────────────────────────────────────────┘
--remote 模式下仅允许不依赖本地文件系统/shell 的命令:
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,
])通过 Remote Control (手机/Web 客户端) 发送的命令需要额外安全检查:
function isBridgeSafeCommand(cmd: Command): boolean {
if (cmd.type === 'local-jsx') return false // 渲染 Ink UI,阻止
if (cmd.type === 'prompt') return true // 展开为文本,安全
return BRIDGE_SAFE_COMMANDS.has(cmd) // local 型需要显式允许
}
const BRIDGE_SAFE_COMMANDS: Set<Command> = new Set([
compact, clear, cost, summary, releaseNotes, files,
])原则:
prompt类型天然安全 (只生成文本)local-jsx类型全部阻止 (需要终端渲染)local类型需要显式列入白名单
使用 bun:bundle 的 feature() 实现编译时死代码消除:
import { feature } from 'bun:bundle'
const proactive = feature('PROACTIVE') || feature('KAIROS')
? require('./commands/proactive.js').default : null
const voiceCommand = feature('VOICE_MODE')
? require('./commands/voice/index.js').default : null| Feature Flag | 命令 | 说明 |
|---|---|---|
PROACTIVE / KAIROS |
/proactive |
主动建议模式 |
KAIROS / KAIROS_BRIEF |
/brief |
简报模式 |
KAIROS |
/assistant |
助手模式 |
BRIDGE_MODE |
/bridge |
Bridge 连接管理 |
DAEMON + BRIDGE_MODE |
/remote-control-server |
远程控制服务器 |
VOICE_MODE |
/voice |
语音模式 |
HISTORY_SNIP |
/force-snip |
强制历史剪切 |
WORKFLOW_SCRIPTS |
/workflows |
工作流管理 |
CCR_REMOTE_SETUP |
/web-setup |
Web 远程环境设置 |
EXPERIMENTAL_SKILL_SEARCH |
(清除索引缓存) | 技能搜索实验功能 |
KAIROS_GITHUB_WEBHOOKS |
/subscribe-pr |
PR Webhook 订阅 |
ULTRAPLAN |
/ultraplan |
超级计划 (CCR 远端执行) |
TORCH |
/torch |
Torch 调试工具 |
UDS_INBOX |
/peers |
对等通信 |
FORK_SUBAGENT |
/fork |
子代理分叉 |
BUDDY |
/buddy |
Buddy 协作模式 |
MCP_SKILLS |
(MCP 技能过滤) | MCP 技能发现 |
COORDINATOR_MODE |
(协调器分发逻辑) | 工作者协调 |
INTERNAL_ONLY_COMMANDS 仅在 USER_TYPE === 'ant' 且非 demo 模式下注入:
const INTERNAL_ONLY_COMMANDS = [
backfillSessions, // 会话回填
breakCache, // 缓存清除
bughunter, // Bug 猎手
commit, // Git 提交
commitPushPr, // 提交+推送+PR
ctx_viz, // 上下文可视化
goodClaude, // (已 stub)
issue, // 创建 Issue
initVerifiers, // 初始化验证器
forceSnip?, // 强制历史剪切
mockLimits, // 模拟限流
bridgeKick, // Bridge 踢出
version, // 版本信息
ultraplan?, // 超级计划
subscribePr?, // PR 订阅
resetLimits, // 重置限流
resetLimitsNonInteractive,
onboarding, // 引导流程
share, // 分享会话
summary, // 会话摘要
teleport, // 远程传送
antTrace, // 追踪调试
perfIssue, // 性能 Issue
env, // 环境变量查看
oauthRefresh, // OAuth 刷新
debugToolCall, // 工具调用调试
agentsPlatform, // 代理平台
autofixPr, // 自动修复 PR
]const agentsPlatform =
process.env.USER_TYPE === 'ant'
? require('./commands/agents-platform/index.js').default
: null此命令使用运行时环境变量检查而非 feature(),因为它需要在非 Bun 构建环境中也能正确排除。
| 命令 | 别名 | 类型 | 说明 |
|---|---|---|---|
/clear |
local |
清除对话历史 | |
/compact |
local |
压缩对话历史,保留摘要 | |
/resume |
/continue |
local-jsx |
恢复之前的对话 |
/rename |
local-jsx |
重命名当前对话 | |
/branch |
/fork |
local-jsx |
在当前位置创建对话分支 |
/rewind |
/checkpoint |
local |
回退代码和/或对话到先前状态 |
/export |
local-jsx |
导出对话到文件或剪贴板 | |
/tag |
local-jsx |
为会话添加可搜索标签 |
| 命令 | 类型 | 说明 |
|---|---|---|
/model |
local-jsx |
切换 AI 模型 |
/effort |
local-jsx |
设置推理深度 |
/plan |
local-jsx |
启用/查看计划模式 |
/advisor |
local |
配置 Advisor 双模型审查 |
| 命令 | 别名 | 类型 | 说明 |
|---|---|---|---|
/config |
/settings |
local-jsx |
打开配置面板 |
/memory |
local-jsx |
编辑 CLAUDE.md 记忆文件 | |
/permissions |
/allowed-tools |
local-jsx |
管理工具权限规则 |
/privacy-settings |
local-jsx |
查看/更新隐私设置 | |
/hooks |
local-jsx |
查看钩子配置 | |
/keybindings |
local |
打开快捷键配置文件 | |
/sandbox |
local-jsx |
切换沙箱模式 |
| 命令 | 类型 | 说明 |
|---|---|---|
/review |
prompt |
审查 Pull Request (本地 gh 执行) |
/ultrareview |
local-jsx |
深度 Bug 搜索 (CCR 远端,10-20 分钟) |
/security-review |
prompt |
安全审查当前分支变更 |
/diff |
local-jsx |
查看未提交更改和每轮差异 |
/pr-comments |
prompt |
获取 GitHub PR 评论 |
| 命令 | 类型 | 说明 |
|---|---|---|
/context |
local-jsx / local |
可视化当前上下文使用量 (彩色网格) |
/files |
local |
列出当前上下文中的所有文件 |
/add-dir |
local-jsx |
添加新的工作目录 |
/cost |
local |
显示会话费用 |
/status |
local-jsx |
显示会话状态 |
/stats |
local-jsx |
显示 Claude Code 使用统计 |
| 命令 | 类型 | 说明 |
|---|---|---|
/mcp |
local-jsx |
管理 MCP 服务器 (immediate=true) |
/skills |
local-jsx |
列出可用技能 |
/agents |
local-jsx |
管理代理配置 |
/plugin |
local-jsx |
管理插件 |
/reload-plugins |
local |
激活挂起的插件更改 |
| 命令 | 别名 | 类型 | 说明 |
|---|---|---|---|
/help |
local-jsx |
显示帮助和可用命令 | |
/theme |
local-jsx |
更改主题 | |
/color |
local-jsx |
更改代理颜色 | |
/vim |
local |
切换 Vim/Normal 编辑模式 | |
/exit |
/quit |
local-jsx |
退出 REPL |
/copy |
local-jsx |
复制最后一条消息 | |
/stickers |
local |
订购 Claude Code 贴纸 | |
/statusline |
prompt |
状态栏开关 |
| 命令 | 别名 | 类型 | 说明 |
|---|---|---|---|
/doctor |
local-jsx |
诊断和验证安装设置 | |
/terminal-setup |
local-jsx |
终端设置 | |
/ide |
local-jsx |
管理 IDE 集成 | |
/heapdump |
local |
JS 堆转储到 ~/Desktop |
| 命令 | 别名 | 类型 | 说明 |
|---|---|---|---|
/feedback |
/bug |
local-jsx |
提交反馈/Bug 报告 |
/btw |
local-jsx |
快速备注 | |
/release-notes |
local |
查看更新日志 |
| 命令 | 类型 | 说明 |
|---|---|---|
/init |
prompt |
生成 CLAUDE.md 和项目配置 |
/output-style |
local-jsx |
已废弃: 使用 /config |
| 命令 | 类型 | 条件 |
|---|---|---|
/login |
local-jsx |
非 3P 服务用户 |
/logout |
local-jsx |
非 3P 服务用户 |
| 命令 | 别名 | 类型 | 说明 |
|---|---|---|---|
/passes |
local-jsx |
Pass 管理 | |
/tasks |
/bashes |
local-jsx |
后台任务管理 |
/insights |
prompt |
生成使用分析报告 (懒加载,113KB) | |
/think-back |
local-jsx |
2025 年度回顾 | |
/thinkback-play |
local |
播放回顾动画 | |
/remote-env |
local-jsx |
配置远程环境默认值 | |
/rate-limit-options |
local-jsx |
速率限制选项 |
| 命令 | availability | 类型 | 说明 |
|---|---|---|---|
/upgrade |
claude-ai |
local-jsx |
升级到 Max 计划 |
/usage |
claude-ai |
local-jsx |
查看计划用量 |
/extra-usage |
(动态) | local-jsx / local |
超额用量配置 |
/fast |
claude-ai, console |
local-jsx |
快速模式切换 |
/chrome |
claude-ai |
local-jsx |
Chrome Beta 设置 |
/desktop |
claude-ai |
local-jsx |
桌面应用跳转 |
/mobile |
全平台 | local-jsx |
移动端 QR 码 |
/voice |
claude-ai |
local |
语音模式 |
/install-github-app |
claude-ai, console |
local-jsx |
GitHub Actions |
/install-slack-app |
claude-ai |
local |
Slack 应用 |
/web-setup |
claude-ai |
local-jsx |
Web 远程环境 |
/session |
/remote |
local-jsx |
远程会话 (remote 模式) |
这些是以技能格式注册的内置命令,存放在 src/skills/bundled/:
| 技能 | 说明 |
|---|---|
batch |
批量操作 |
claude-api |
Claude API 使用指导 |
claude-in-chrome |
Chrome 扩展指导 |
debug |
调试辅助 |
keybindings |
快捷键配置 (技能版) |
loop |
循环执行 |
lorem-ipsum |
占位文本 |
remember |
记忆管理 |
schedule |
远程代理调度 |
simplify |
代码简化 |
skillify |
技能创建 |
stuck |
卡住时的帮助 |
update-config |
配置更新 |
verify |
验证检查 |
最简单的 prompt 命令 -- 生成提示词让模型执行 PR 审查:
const review: Command = {
type: 'prompt',
name: 'review',
description: 'Review a pull request',
progressMessage: 'reviewing pull request',
contentLength: 0, // 动态内容,长度不确定
source: 'builtin',
async getPromptForCommand(args): Promise<ContentBlockParam[]> {
return [{ type: 'text', text: `
You are an expert code reviewer...
1. If no PR number, run gh pr list
2. If PR number provided, run gh pr view + gh pr diff
3. Analyze and review...
PR number: ${args}
` }]
},
}带 allowedTools 和 shell 命令预执行的 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 status` 等 shell 模板
// executeShellCommandsInPrompt 会展开 !`...` 为实际输出
const finalContent = await executeShellCommandsInPrompt(promptContent, ...)
return [{ type: 'text', text: finalContent }]
},
} satisfies Command返回结构化结果的本地命令:
// 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
// compact.ts -- 实际实现
export const call: LocalCommandCall = async (args, context) => {
const compactionResult = await performCompaction(...)
return {
type: 'compact', // 特殊结果类型,触发消息重建
compactionResult,
displayText: 'Context compacted successfully',
}
}渲染交互式配置面板:
// index.ts
const config = {
aliases: ['settings'],
type: 'local-jsx',
name: 'config',
description: 'Open config panel',
load: () => import('./config.js'),
} satisfies Command
// config.ts (简化)
export const call: LocalJSXCommandCall = async (onDone, context, args) => {
// 返回 React/Ink JSX 组件
return <ConfigPanel
onDone={(result) => onDone(result, { display: 'system' })}
context={context}
/>
}根据运行模式动态启用/隐藏:
const session = {
type: 'local-jsx',
name: 'session',
aliases: ['remote'],
description: 'Show remote session URL and QR code',
isEnabled: () => getIsRemoteMode(), // 仅 remote 模式启用
get isHidden() {
return !getIsRemoteMode() // 非 remote 模式从列表中隐藏
},
load: () => import('./session.js'),
} satisfies Command对超大模块 (113KB) 使用代理模式延迟加载:
const usageReport: Command = {
type: 'prompt',
name: 'insights',
description: 'Generate a report analyzing your Claude Code sessions',
contentLength: 0,
progressMessage: 'analyzing your sessions',
source: 'builtin',
async getPromptForCommand(args, context) {
// 首次调用时才加载 113KB 模块
const real = (await import('./commands/insights.js')).default
if (real.type !== 'prompt') throw new Error('unreachable')
return real.getPromptForCommand(args, context)
},
}同一功能根据交互/非交互模式注册为不同类型:
export const extraUsage = {
type: 'local-jsx',
name: 'extra-usage',
description: 'Configure extra usage...',
isEnabled: () => isExtraUsageAllowed() && !getIsNonInteractiveSession(),
load: () => import('./extra-usage.js'),
} satisfies Command
export const extraUsageNonInteractive = {
type: 'local',
name: 'extra-usage', // 同名! getCommands 时只有一个生效
supportsNonInteractive: true,
description: 'Configure extra usage...',
isEnabled: () => isExtraUsageAllowed() && getIsNonInteractiveSession(),
get isHidden() { return !getIsNonInteractiveSession() },
load: () => import('./extra-usage-noninteractive.js'),
} satisfies Command模型可通过 SkillTool 调用的命令:
const getSkillToolCommands = memoize(async (cwd: string): Promise<Command[]> => {
const allCommands = await getCommands(cwd)
return allCommands.filter(cmd =>
cmd.type === 'prompt' &&
!cmd.disableModelInvocation &&
cmd.source !== 'builtin' &&
(cmd.loadedFrom === 'bundled' ||
cmd.loadedFrom === 'skills' ||
cmd.loadedFrom === 'commands_DEPRECATED' ||
cmd.hasUserSpecifiedDescription ||
cmd.whenToUse)
)
})const getSlashCommandToolSkills = memoize(async (cwd: string): Promise<Command[]> => {
const allCommands = await getCommands(cwd)
return allCommands.filter(cmd =>
cmd.type === 'prompt' &&
cmd.source !== 'builtin' &&
(cmd.hasUserSpecifiedDescription || cmd.whenToUse) &&
(cmd.loadedFrom === 'skills' ||
cmd.loadedFrom === 'plugin' ||
cmd.loadedFrom === 'bundled' ||
cmd.disableModelInvocation) // 模型不可调用 = 仅用户可调用
)
})命令系统使用多层缓存,需要在特定时机清除:
// 仅清除命令 memoization (动态技能添加时)
function clearCommandMemoizationCaches(): void {
loadAllCommands.cache?.clear?.()
getSkillToolCommands.cache?.clear?.()
getSlashCommandToolSkills.cache?.clear?.()
clearSkillIndexCache?.() // 外层索引也要清
}
// 完全清除 (插件/技能文件变更时)
function clearCommandsCache(): void {
clearCommandMemoizationCaches()
clearPluginCommandCache()
clearPluginSkillsCache()
clearSkillCaches()
}function findCommand(commandName: string, commands: Command[]): Command | undefined {
return commands.find(_ =>
_.name === commandName ||
getCommandName(_) === commandName || // userFacingName
_.aliases?.includes(commandName)
)
}// 区分命令名和文件路径
function looksLikeCommand(commandName: string): boolean {
return !/[^a-zA-Z0-9:\-_]/.test(commandName)
}输入 /var/log/syslog 不会被误识别为命令,因为包含 / 等非法字符。额外检查文件系统 (stat) 确认不是文件路径。
用户界面和模型提示使用不同的描述格式:
function formatDescriptionWithSource(cmd: Command): string {
// workflow → "(workflow)" 后缀
// plugin → "(pluginName)" 前缀 或 "(plugin)" 后缀
// bundled → "(bundled)" 后缀
// user/project/enterprise → "(Personal)" 等来源标注
// builtin/mcp → 原始描述
}| 设计决策 | 原因 |
|---|---|
| 三类型判别联合 | 清晰分离 UI 渲染/文本处理/模型交互三种执行模式 |
load() 懒加载 |
启动时只解析元数据,减少初始化开销 |
availability + isEnabled 双维度 |
分离静态平台约束和动态功能开关 |
memoize + 每次过滤 |
平衡加载开销和认证状态实时性 |
| Bridge/Remote 白名单 | 精确控制跨平台命令安全边界 |
context: 'fork' |
隔离长时间运行的技能,避免阻塞主对话 |
| Feature flags 编译时消除 | 外部构建中完全移除内部功能代码 |
immediate 标记 |
关键命令 (如 /mcp) 绕过队列等待,立即执行 |