导语:本文是对 Claude Code (Anthropic 官方 CLI 工具) 的源码还原与深度分析。该项目使用 TypeScript + React + Ink 构建,共计 1,884 个 TS/TSX 文件,运行在 Bun 运行时之上。本文将从整体架构、启动流程、运行模式以及核心数据流四个维度,带你硬核拆解这款强大的 AI CLI 工具。

参考仓库(源码地址):https://github.com/ChinaSiro/claude-code-sourcemap


第一章 整体架构

1.1 目录结构

项目的核心逻辑集中在 src/ 目录下,各模块职责划分非常清晰:

src/
├── main.tsx        # 主入口 (~800KB),CLI 解析 + 启动流程
├── entrypoints/    # 多种启动模式 (CLI/SDK/Sandbox)
├── state/          # Zustand 风格全局状态管理 (AppStateStore)
├── tools/          # 43 个工具实现 (Bash, FileEdit, Grep, Agent 等)
├── commands/       # 101 个 CLI 命令 (commit, review, config 等)
├── components/     # 144 个 React 终端 UI 组件
├── ink/            # 定制增强版 Ink 终端 UI 框架
├── services/       # 36 个服务模块 (MCP, OAuth, API, LSP 等)
├── hooks/          # React hooks + AI 行为钩子系统
├── tasks/          # 后台任务管理 (Shell/Agent/Teammate/Workflow 等)
├── utils/          # 329 个工具函数/模块
├── bridge/         # 桌面端集成 (Desktop/远程控制)
├── skills/         # AI 技能系统
├── vim/            # Vim 模式支持
├── voice/          # 语音模式
└── coordinator/    # 多 Agent 编排

1.2 核心设计

设计方面 具体实现
运行时 基于 Bun,利用 feature() 做编译期死代码消除。
UI 框架 React + 深度定制 Ink,支持焦点管理、动画、点击与滚动。
状态管理 不可变 AppState (DeepImmutable),Zustand 风格 store。
工具系统 统一 Tool 接口,内置/MCP/LSP 工具共用同一抽象层。
权限控制 完整的权限检查流程,支持适配符规则和逐工具控制。
功能门控 大量 Feature Flag (如 KAIROS, COORDINATOR_MODE, VOICE_MODE 等)。
渲染优化 源码中可见 React Compiler 编译器优化标记,大幅提升终端渲染性能。

1.3 关键文件速览

  • main.tsx - 主入口,负责启动编排。
  • Tool.ts - 工具基础接口定义。
  • tools.ts - 工具注册表。
  • query.ts - 查询引擎(整个系统的心脏),API 调用核心,约 1700 行。
  • QueryEngine.ts - 查询执行引擎 (SDK/headless 封装)。
  • commands.ts - 命令注册。
  • context.ts - 系统与用户上下文构建。

第二章 启动流程

整个启动过程像流水线一样依次执行,主要分为 5 个阶段:

2.1 第一阶段:模块加载期的并行预热 (main.tsx:1-20)

  • profileCheckpoint('main_tsx_entry') - 记录时间戳
  • startMdmRawRead() - 并行读取 MDM 企业配置 (plutil/reg)
  • startKeychainPrefetch() - 并行读取 macOS 钥匙串 (OAuth token)

💡 设计思路:利用 JS import 语句求值的 ~135ms,提前启动耗时的子进程。这些进程在后台跑,等后面需要结果时再 await,这是一种非常巧妙的启动性能优化手段。

2.2 第二阶段:main() 函数 (main.tsx:585-854)

主要做两件事:

  1. 安全设置:防止 Windows PATH 劫持、注册 SIGINT/exit 处理。
  2. 特殊启动模式分流:根据 argv 判断是否走特殊路径:
    • cc:// URL - Direct Connect 模式
    • --handle-uri - Deep Link 协议处理
    • claude assistant - Kairos 助手模式
    • claude ssh - SSH 远程模式
    • 如果都不是,则进入标准的 run() 流程。

2.3 第三阶段:run() - Commander CLI 解析 (main.tsx:884-1006)

Commander 初始化并注册 preAction hook (每个命令执行前运行):

  • await MDM + Keychain 预取完成
  • await init() - 核心初始化
  • 加载 analytics sinks
  • 运行数据迁移 (migrations)
  • 加载远程设置 + 策略限制 (非阻塞)

其中 init()entrypoints/init.ts 中,完成了配置系统启用、安全环境变量应用、优雅退出清理、mTLS 配置、网络代理配置,以及提前建立 Anthropic API 的 TCP+TLS 连接 (又一个并行优化)。

2.4 第四阶段:setup() + 并行加载 (main.tsx:1903-2030)

并行执行以下三项任务:

  1. setup() - 初始化工作目录、worktree、Session、权限模式。
  2. getCommands() - 加载所有 CLI 命令。
  3. getAgentDefinitions() - 加载 agent 定义。

2.5 第五阶段:分流到 REPL 或 Print 模式

根据是否有 -p/--print 参数进入不同运行模式(详见第三章)。

2.6 启动流程总结图

用户输入 `claude "修个 bug"`
│
▼
[模块加载] MDM/Keychain 并行预热
│
▼
[main()] 安全设置 + 特殊模式分流
│
▼
[run()] Commander 解析命令行参数
│
▼
[preAction] init() -> 配置/网络/安全/API预连接
│
▼
[setup()] 工作目录/Session/权限 + 并行加载 commands/agents
│
▼
[分流] ── -p ──> Print 模式 (QueryEngine 单次执行)
│
└── 交互模式 ──> launchRepl() -> <App><REPL /></App>


第三章 两种运行模式:REPL vs Print

REPL = Read-Eval-Print Loop (术语源自 Lisp):读取 -> 执行 -> 输出 -> 循环。

3.1 模式对比

特性 REPL 模式 (交互式) Print 模式 (非交互/headless)
启动方式 claude claude -p "修个bug"
交互方式 持续对话,等待用户输入 执行一次就退出
UI 渲染 Ink 渲染终端 UI (React 组件树) 无 UI,直接输出文本/JSON
状态管理 React AppState (Zustand store) QueryEngine 类内部
核心入口 launchRepl() -> <App><REPL /></App> QueryEngine.submitMessage()

3.2 代码中的分流点

main.tsx 中,核心判断逻辑如下:

if (isNonInteractiveSession) {
  // Print 模式:用 QueryEngine 单次执行
  QueryEngine.submitMessage(prompt)
} else {
  // REPL 模式:启动 Ink 渲染循环
  await launchRepl(root, appProps, sessionConfig, renderAndRun)
}

replLauncher.tsx 非常简洁(仅 22 行),其核心就是挂载 React 组件:

<App {...appProps}>
  <REPL {...replProps} />
</App>


第四章 核心数据流

整条数据流的核心是一个 AsyncGenerator 循环。用户每发一条消息,就触发一次 query() 调用,它通过 yield 不断向外吐出事件(流式消息、工具结果等),调用方消费这些事件来更新 UI。

4.1 三个核心文件的职责

文件 职责说明 形象比喻
context.ts 构建上下文 (git status, CLAUDE.md, 日期) 食材准备
query.ts 单次对话循环:API调用 -> 工具执行 -> 继续/停止 厨师做菜
QueryEngine.ts 会话级状态管理,封装 query() 给 SDK/headless 用 餐厅前台

4.2 上下文构建 (context.ts)

每次对话开始前,系统构建两种上下文(均使用 memoize 缓存,整个会话只算一次):

  • getSystemContext() -> 包含 git 状态 (分支、最近提交、status)
  • getUserContext() -> 包含 CLAUDE.md 文件内容 + 当前日期

4.3 查询循环 (query.ts) - 系统的心脏

query() 是一个 AsyncGenerator,核心结构是一个 while(true) 循环。下面用伪代码展示其核心分支逻辑:

while (true) {
  // ① 消息预处理 (裁剪大结果、历史裁剪、微压缩、上下文折叠、自动压缩)
  
  // ② 调用 Claude API,流式收集回复
  for await (msg of callModel(...)) {
    yield msg // 流式吐出给 UI
    if (msg 包含 tool_use) needsFollowUp = true
  }

  // ③ 关键分支点
  if (!needsFollowUp) {
    // 模型没有请求工具 -> 任务完成
    return { reason: 'completed' } // <- 唯一的正常退出
  }

  // ④ 模型请求了工具 -> 执行工具
  for await (update of runTools(...)) {
    yield update.message // 工具结果给 UI
  }

  // ⑤ 拼接消息,准备下一轮
  state = {
    messages: [...之前的消息, ...assistant回复, ...工具结果],
    turnCount: nextTurnCount,
    transition: { reason: 'next_turn' },
  }
  // 没有 break/return -> 自动 continue 回到 while(true) 顶部
}

循环退出条件:

  • 只有 return 才会跳出循环。只要模型还在调工具,循环就不会停。
  • 一个典型的 Agent 任务可能循环几十次:读文件 -> 思考 -> 改文件 -> 运行测试 -> 再改 -> ... -> 最终给出文本回复 -> 退出

退出状态枚举:

  • completed: 正常退出,模型不再调用工具。
  • aborted_streaming: 用户按了 Ctrl+C。
  • max_turns: 达到 --max-turns 限制。
  • prompt_too_long: 上下文太长且压缩失败。
  • hook_stopped: Hook 阻止了继续。

4.4 工具执行 (toolOrchestration.ts)

工具不是逐个执行的,而是通过 partitionToolCalls 分批并行或串行:

  • 批次1: [GrepTool, GlobTool] -> isConcurrencySafe=true -> 并行执行
  • 批次2: [FileEditTool] -> isConcurrencySafe=false -> 串行执行
  • 批次3: [GrepTool] -> isConcurrencySafe=true -> 并行执行

:只读工具 (Grep, Read 等) 可以并行,写操作 (Edit, Bash 等) 必须串行。默认最大并发数为 10。

4.5 QueryEngine (QueryEngine.ts)

QueryEngine 是对 query() 的类封装,管理跨轮次的状态:

class QueryEngine {
  mutableMessages: Message[] // 整个会话的消息历史
  totalUsage: Usage          // 累计 token 用量

  async *submitMessage(prompt) {
    // 1. 构建 systemPrompt, userContext, systemContext
    // 2. 创建 toolUseContext (权限、工具列表等)
    // 3. 调用 query(params) 并 yield 结果
    // 4. 累积消息到 mutableMessages
  }
}

(注:REPL 模式不走 QueryEngine,而是直接调用 query(),状态由 React AppState 管理)

4.6 完整数据流图

用户输入 "帮我修 src/app.ts 的 bug"
│
▼
┌─ context.ts ──────────────────────────────────┐
│ systemContext = { gitStatus }                 │
│ userContext = { claudeMd, date }              │
└───────────────────────────────────────────────┘
│
▼
┌─ query.ts ── while(true) 循环 ────────────────┐
│                                               │
│  [预处理] 压缩/裁剪消息 -> 确保不超 context window │
│       │                                       │
│       ▼                                       │
│  [API 调用] callModel(messages, systemPrompt, tools)
│       │   - 流式返回                           │
│       ▼                                       │
│  [模型回复] "我来看看代码" + tool_use: FileReadTool │
│       │                                       │
│       ▼                                       │
│  [工具执行] runTools() -> 读取 src/app.ts -> 返回文件内容│
│       │                                       │
│       ▼                                       │
│  [拼接消息] messages += [assistant回复, tool_result]│
│       │                                       │
│       ▼ - continue, 回到循环顶部                │
│       │                                       │
│  [API 调用] callModel(更长的 messages)          │
│       │                                       │
│       ▼                                       │
│  [模型回复] "找到 bug 了" + tool_use: FileEditTool │
│       │                                       │
│       ▼                                       │
│  [工具执行] runTools() -> 编辑文件 -> 返回 diff   │
│       │                                       │
│       ▼ - continue                            │
│       │                                       │
│  [API 调用] callModel(...)                     │
│       │                                       │
│       ▼                                       │
│  [模型回复] "已修复!" (无 tool_use)             │
│       │                                       │
│       ▼                                       │
│  [stop hooks] -> return { reason: 'completed' }│
└───────────────────────────────────────────────┘
│
▼
UI 渲染每个 yield 出来的消息


结语:Claude Code 的源码展示了极高的工程素养,无论是从启动时的性能压榨,还是对大模型上下文的精准控制,都非常值得前端和 Node.js 开发者学习。如果你觉得这篇源码解析对你有帮助,欢迎点赞、收藏、评论

后面我将继续分享Claude code源码中《上下文管理》和《SKILL调用》的内容,感兴趣的同学们可以继续关注!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐