有不少人, 一旦将Claude Code源码打开, 其最先出现的反应便是, 从src/main.tsx起始, 而后一路朝着下方翻去。此行为本身并无差错, 然而却极易致使自身阅读时陷入混乱状态。原因在于, 你最先碰到的并非“核心业务”, 而是数量众多的入口装配, 以及初始化内容, 还有副作用相关部分, 另外还有特性开关, 以及信任校验这方面的内容, 甚至还有UI启动逻辑。对于Claude Code这样的AI CLI项目, 更可靠的读法并非按照文件大小生硬去读, 而是依照“入口装配层, 命令层, REPL/UI状态层, 对话主循环层, 工具/扩展层”来构建阅读地图。
它不打算一开始就着手做逐行翻译, 也不着急地深入拆解实现,它要先处理一个更具实际性的问题, 若你仅有10分钟, Claude Code这份源码该从何处去读, 为何要这般去读, 后续每一篇又会顺着哪条线路持续往下推进。
在此予以说明, 本文依旧是依据当下能够见到的, claude code泄露的源码快照来进行写作的, 仅仅针对源码能够证实的启动链路展开讨论, 并不会延伸至仓库外面的构建、发布以及线上部署的细节部分。
本篇看点如果你时间很少,先记住这句话
最值得先抓住的并非某个巨型文件, 而是这5条主线, 它们属于Claude Code , 是这样的 , 是这样的 , 就这样。
程序该如何启动, 命令要怎样组织, 终端 UI 以及状态是如何运转的, 用户输入怎样才能进入, QueryEngine 工具和扩展能力又该怎么接进来。
先把这 5 条线看清,再往里钻,阅读效率会高很多。
一图先看懂:Claude Code 的源码地图

以下这样的一个图, 实际上已然表明了一项颇为关键的判断, Claude Code并非仅为“仅是一个能够对模型进行调试的终端程序”这般简易, 与此同时, 它更像是一个将交互、对话、工具以及扩展能力统一进行编排组合起来的终端Agent系统。
为什么这份源码特别容易把人读迷路
针对传统 CLI 项目而言, 一般状况下会存有着相对较为明晰的线性路径, 此路径涵盖: 入口, 参数解析, 命令派发, 执行结果, 退出。Claude Code 的结构并非如此这般。它同时涵盖着:
这便致使一种极为常见的误判情况出现, 即众多人常常会把 main.tsx 错误地当作是“真正的核心所在”。然而, 更贴近实际情况的表述却是, main.tsx 乃是一个进行装配的中心。它的确具有重要性, 不过它的重要性首要体现于“将谁与谁连接起来”这一方面, 而并非在于“把所有业务都书写于其中”。
所以, 这篇文章的目标并非“带着你读完入口文件”, 而是, 先搭建出一张阅读地图, 地图搭建好了之后呢, 然后再去看具体模块, 如此一来, 很多代码自己就会变得有上下文了。
一张表先建立阅读框架主线先看文件要回答的问题为什么值得先看
入口装配
main.tsx, interactiveHelpers.tsx, replLauncher.tsx , 它们是不同的文件, 在项目中发挥着各自的作用, 有着各自的任务, 彼此之间存在着特定的关联。
程序怎么起来、什么时候切进 REPL
帮你知道控制权是怎么流动的
命令系统
commands.ts、commands/*
这个产品到底让用户做什么
帮你建立“可操作面”地图
REPL/UI 状态
屏幕显示相关的/REPL.tsx文件, 状态管理方面的/AppStateStore.ts文件。
终端界面和运行态怎么组织
帮你理解这不是个薄壳 CLI
对话主循环
文件名为QueryEngine为ts后缀, query为ts后缀, processUserInput为ts后缀。
用户输入怎么变成一轮对话
这是整套系统的核心链路
Tool 与扩展
Tool.ts、tools.ts、tools/*
模型到底能做哪些事
这是能力边界,不是附属功能
如果你后面真的要系统读源码,这张表就是第一层索引。
第一条主线:先看入口,但别陷进去
这一条线的核心文件是:
入口层最值得先看的不是“细节”开云app官方最新下载地址,而是“装配关系”
先看一段很关键的源码:
// src/main.tsx:1915-1926
const preSetupCwd = getCwd();
if (process.env.CLAUDE_CODE_ENTRYPOINT !== 'local-agent') {
initBuiltinPlugins();
initBundledSkills();
}
const setupPromise = setup(...);
const commandsPromise = worktreeEnabled ? null : getCommands(preSetupCwd);
const agentDefsPromise = worktreeEnabled ? null : getAgentDefinitionsWithOverrides(preSetupCwd);
await setupPromise;
const [commands, agentDefinitionsResult] = await Promise.all([
commandsPromise ?? getCommands(currentCwd),
agentDefsPromise ?? getAgentDefinitionsWithOverrides(currentCwd),
]);
被这段代码拿来建立第一印象是极为合适的。它表明main.tsx并非仅仅充当“命令行入口”, 而是在对好几类关键初始化动作予以协调:
换而言之, 入口层所关注的重点在于“初始化顺序”以及“启动关键路径”, 并非仅仅是将一条命令发起执行而已。
再看它把控制权交给了谁
相较于第一个关键点而言, 第二个关键点在于, main.tsx 并非始终占据着控制流, 它十分明确地将后续阶段交付给了两个函数。
// src/main.tsx:2236-2239
const setupScreensStart = Date.now();
const onboardingShown = await showSetupScreens(
root,
permissionMode,
allowDangerouslySkipPermissions,
commands,
enableClaudeInChrome,
devChannels
);
// src/main.tsx:3793-3801
await launchRepl(
root,
{ getFpsMetrics, stats, initialState },
{ ...sessionConfig, initialMessages, pendingHookMessages },
renderAndRun
);
这两段有一个很强的阅读提示:
interactiveHelpers.tsx, 它为何值得去这么瞧上一眼? replLauncher.tsx, 它又为何值得去这般看上一眼?
倘若仅仅瞅一眼 main.tsx, 你晓得存在一个 handoff, 然而未必清楚 handoff 确切所指是什么, 到了这个时候就得跟着去瞧一瞧:
// src/replLauncher.tsx:12-18
export async function launchRepl(...) {
const { App } = await import('./components/App.js');
const { REPL } = await import('./screens/REPL.js');
await renderAndRun(
root,
<App {...appProps}>
<REPL {...replProps} />
</App>
);
}
这几行实际上已然足以表明问题所在 , Claude Code 的主界面并非借助字符串拼接所造就的输出层 , 而是一棵实实在在的 React 组件树。换句话讲 , 它的终端界面并非外壳 , 而是系统架构的其中一部分。
这一条线该怎么看
第一条任务的主要脉络, 并非是要你将main.tsx自开头至结尾全部通读完毕, 而是要先弄明白三件事情, 这三件事情分别是:
启动阶段, 初始化了哪些关键模块, 何种步骤, 发生在真正进入 REPL 之前, 程序最终, 在什么地方, 把控制权交给主界面。
容易误读
看懂main.tsx, 就等同于看懂Claude Code, 这两者之间存在着这样一种关联。
更接近事实
Main.tsx, 它更像是那种系统总装图模样, 而真正的核心逻辑, 却是散开分布在它所需要调度的下游那些模块内, 是这样的。
第二条主线:命令系统决定这是不是一个好用的 CLI
这一条线的核心文件是:
commands.ts 不是单纯的注册表
先看 getCommands(cwd):
// src/commands.ts:476-516
export async function getCommands(cwd: string): Promise<Command[]> {
const allCommands = await loadAllCommands(cwd)
const dynamicSkills = getDynamicSkills()
const baseCommands = allCommands.filter(
_ => meetsAvailabilityRequirement(_) && isCommandEnabled(_),
)
if (dynamicSkills.length === 0) {
return baseCommands
}
const baseCommandNames = new Set(baseCommands.map(c => c.name))
const uniqueDynamicSkills = dynamicSkills.filter(
s =>
!baseCommandNames.has(s.name) &&
meetsAvailabilityRequirement(s) &&
isCommandEnabled(s),
)
const builtInNames = new Set(COMMANDS().map(c => c.name))
const insertIndex = baseCommands.findIndex(c => builtInNames.has(c.name))
return [
...baseCommands.slice(0, insertIndex),
...uniqueDynamicSkills,
...baseCommands.slice(insertIndex),
]
}
这段代码很值得读,因为它让你一下子明白命令层在做什么:
因此, commands.ts并非是那种专门用于罗列命令名称之处, 它属于命令装配的层面, 其所管理的是Claude Code的操作方面。
这对阅读有什么实际帮助
要是你往后打算去看某一个 /xxx 命令, 节省时间最有效的办法并非首先进入 commands/ 目录去碰运气, 而是先构建这样一个判断:
有了这样一个判断之后, 你再去阅读具体的命令, 如此一来, 就不太容易将“命令实现”以及“命令装配”混淆成同一件事情了。
这一条线的结论
要是讲 main.tsx 确定程序如何启动, 那么 commands.ts 所决定的便是这个产品究竟能够让用户去做些什么。
容易误读
commands/ 目录很多,说明这里只是在拆文件。
源码事实
getCommands(cwd), 它在做的事情是明确的, 这件事是能力聚合, 同时还涉及启用条件判断, 另外还有动态技能插入。
第三条主线:它本质上是个终端里的 React 应用
这一条线的核心文件是:
不要把 REPL 误会成“套了层界面的命令执行器”
打从你从replLauncher.tsx中得知主界面乃是React组件树起, 接着再瞧screens/REPL.tsx, 于顶部之处便能瞅见那密集的hooks、关乎message的处理、针对权限的逻辑、session的恢复以及与remote/bridge相关的依赖。这般依赖密度自身就是一种信号: REPL并非单薄的视图, 它肩负起了数量可观的交互组织工作。
再看 AppState 的形状开云真人app官方版入口,开云真人app官网入口,感受会更明显:
// src/state/AppStateStore.ts:89+
export type AppState = DeepImmutable<{
settings: SettingsJson
verbose: boolean
mainLoopModel: ModelSetting
toolPermissionContext: ToolPermissionContext
remoteSessionUrl: string | undefined
remoteConnectionStatus: 'connecting' | 'connected' | 'reconnecting' | 'disconnected'
replBridgeEnabled: boolean
}> & {
tasks: { [taskId: string]: TaskState }
mcp: {
clients: MCPServerConnection[]
tools: Tool[]
commands: Command[]
resources: Record<string, ServerResource[]>
}
plugins: {
enabled: LoadedPlugin[]
disabled: LoadedPlugin[]
commands: Command[]
}
}
最能体现这段代码的一点在于, 这里的状态并非“页面状态”, 而是“整场交互的运行情况”, 它涵盖了settings、permission、tasks、mcp、plugins、remote session这些内容。
这意味着什么
这背后的工程取舍也很明确:
所以, 第三条主线真正要确立的是这样的一种认知, Claude Code的终端界面并非属于“显示层”, 它自身实际上就是系统设计的其中一部分。
第四条主线:真正的对话核心在 QueryEngine
这一条线的核心文件是:
为何要说核心相较于main.tsx, 反而更加贴近QueryEngine呢?
先看 QueryEngine.ts 里的类注释和签名:
// src/QueryEngine.ts:180-212
/**
* One QueryEngine per conversation. Each submitMessage() call starts a new
* turn within the same conversation. State (messages, file cache, usage, etc.)
* persists across turns.
*/
export class QueryEngine {
...
async *submitMessage(
prompt: string | ContentBlockParam[],
options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown> {
这段定义已经足够说明它的角色:
这不是“发一次请求”的小工具,而是会话生命周期的组织者。
输入在进入 QueryEngine 之前还会被处理
再看 processUserInput() 的签名:
// src/utils/processUserInput/processUserInput.ts:85+
export async function processUserInput({
input,
preExpansionInput,
mode,
setToolJSX,
context,
pastedContents,
ideSelection,
messages,
setUserInputOnProcessing,
uuid,
isAlreadyProcessing,
querySource,
canUseTool,
skipSlashCommands,
bridgeOrigin,
isMeta,
skipAttachments,
}): Promise<ProcessUserInputBaseResult> {
该函数参数数量众多, 这本身就极具说明性。它所处理的并非仅仅是“传递一段字符串”这般简单, 而是需要进行区分:
因此更为确切的表述是: 并非是将用户输入径直发送给模型, 而是要先历经输入处理, 接着进行命令判断, 随后走入hook, 再对消息予以整理, 之后才会迈进对话的主循环处。
这一条线为什么最值得单独深挖
因为它基本决定了 Claude Code 的“灵魂”:
这一篇, 仅先将位置点明, 并不拓展至实现细节。后续倘若真要深入探究, QueryEngine会是最值得单独另起一篇的部分。
第五条主线:Tool 系统和扩展能力是整套架构的能力边界
这一条线的核心文件是:
获取所有基础工具 , 这最能够显著地将Claude代码 所具备的能力观念给展现出来。
先看这段:
// src/tools.ts:193-250
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
ExitPlanModeV2Tool,
FileReadTool,
FileEditTool,
FileWriteTool,
NotebookEditTool,
WebFetchTool,
TodoWriteTool,
WebSearchTool,
TaskStopTool,
AskUserQuestionTool,
SkillTool,
EnterPlanModeTool,
...(isTodoV2Enabled() ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] : []),
...
ListMcpResourcesTool,
ReadMcpResourceTool,
...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
]
}
这一段一眼就能让人看出两件事:
Claude Code的工具能力具备相当丰富的特性, 并非只是一层简单的薄包装, 其用途的启用或者停用, 会受到环境因素、特性因素、模式因素以及权限上下文因素的影响, 这些因素各自发挥作用, 共同决定工具在特定情形下是否能够被启用。
这究竟表明了什么呢? 这表明 Claude Code 身上体现出的“会做事”这种特质并不是源自于 prompt 之中所突显形成的, 而是从 Tool 抽象化以及能力装配过程里逐渐衍生显现出来的。
Tool.ts 解决的是“能做什么”和“能做到什么程度”
往上另外一层去看Tool.ts, 能够目睹ToolUseContext、ToolPermissionContext这些属于类型的东西。这样种设计具备着至关重要的性质, 原因在于它所蕴含的意义是:
因此, 对于Claude Code而言, Tool系统并非附属层, 它属于能力边界的一部分, 同时也是执行边界的一部分。
为什么这条线对后续阅读特别重要
后面的 MCP、Skills、Plugins, 最终都不会避开这条线, 因为它们可能源于不同出处, 然而落地之际都得回归到统一的命令以及工具编排框架之中。
容易误读
工具只是模型调用外部能力时的补充功能。
更接近事实
Tool抽象这件事, 它自身就是, Claude Code这套系统里, 最为关键的边界定义当中的一个。
要是你仅仅计划花费 30 分钟, 那么建议依照这个顺序来阅读, 第一阶段: 先要去构建地图, 查看 src/main.tsx 里, initBuiltinPlugins()、initBundledSkills()、getCommands()、showSetupScreens()、launchRepl()这些调用点, 查看 src/commands.ts 的 getCommands(cwd), 领会命令层是怎样装配的, 查看 src/tools.ts 的 getAllBaseTools(), 领会能力层是怎样装配的, 第二阶段:接着去寻觅主循环, 查看 src/replLauncher.tsx, 确认 REPL 是如何被挂起来的, 查看 src/state/AppStateStore.ts, 感受状态的复杂度, 查看 src/QueryEngine.ts 的类注释和 submitMessage(), 领会会话主循环是由谁负责的, 查看 src/utils/processUserInput/processUserInput.ts, 领会输入在进入 QueryEngine 之前经历了什么, 第三阶段: 最后再进行下钻, 再回过头去阅读你最感兴趣的子系统: 权限、MCP、插件、Agent、Remote、Bridge。
这个顺序具备这样的好处开云手机入口app下载,即, 你会首先知晓系统层次以及边界, 之后再着手去钻研细节, 如此不易于在巨型文件当中迷失方向。
标签: 源码分析 ClaudeCode CLI开发 React应用 工具系统
还木有评论哦,快来抢沙发吧~