Claude Code 源码分析第 48 课 · 第 07
第 48 课

桌面应用

Claude Code 如何将终端桥接到 Claude Desktop、与 IDE 集成、通过本机消息传递与浏览器对话以及获取计算机使用功能 — 所有这些都来自一个 CLI 二进制文件。

01 Overview

Claude Code 是一个终端应用程序,但它具有令人惊讶的桌面集成深度:它可以将正在运行的会话移交给 Claude Desktop,自动检测从其启动的 IDE(VS Code、Cursor、JetBrains 系列),通过本机消息传递主机连接 Chrome 扩展,并公开完整的计算机使用 MCP 服务器。 所有这些都是作为不共享全局状态的离散子系统实现的——每个子系统仅在满足其条件时才激活。

覆盖源文件
commands/desktop/index.ts · commands/desktop/desktop.tsx · components/DesktopHandoff.tsx · components/DesktopUpsell/DesktopUpsellStartup.tsx · utils/desktopDeepLink.ts · utils/claudeDesktop.ts · utils/ide.ts · utils/claudeInChrome/ · utils/computerUse/ · entrypoints/cli.tsx

有四个不同的集成层,每个层都有自己的检测逻辑、激活路径和后备行为:

第 1 层

克劳德桌面切换

The /desktop 命令刷新会话并通过深层链接 URL 在桌面应用程序中打开它。

第 2 层

IDE集成

通过读取以下文件中的锁定文件自动检测 VS Code、Cursor、Windsurf 和 14+ JetBrains IDE ~/.claude/ide/.

第 3 层

克劳德在 Chrome 中

安装本机消息传递主机清单,以便浏览器扩展可以直接调用 Claude Code 工具。

第 4 层

计算机使用 MCP

由 Max/Pro 订阅和 GrowthBook 标志控制; 生成一个单独的 MCP 服务器来驱动操作系统。

02 The /desktop 命令和深层链接

命令注册

The /desktop 命令(别名 /app)住在 commands/desktop/index.ts。 其可用性被锁定 claude-ai 产品 — 它不会出现在 SDK 或企业控制台版本中。 该命令还在运行时对平台进行门控检查:

// commands/desktop/index.ts
function isSupportedPlatform(): boolean {
  if (process.platform === 'darwin') return true
  if (process.platform === 'win32' && process.arch === 'x64') return true
  return false
}

const desktop = {
  type: 'local-jsx',
  name: 'desktop',
  aliases: ['app'],
  description: 'Continue the current session in Claude Desktop',
  availability: ['claude-ai'],
  isEnabled: isSupportedPlatform,
  get isHidden() { return !isSupportedPlatform() },
  load: () => import('./desktop.js'),
}

启用后,该命令将呈现 DesktopHandoff 反应/墨水组件。 在 Windows 上仅支持 x64; ARM Windows 和 Linux 被排除。

切换状态机

DesktopHandoff in components/DesktopHandoff.tsx 是一个具有六个状态的小型状态机:

stateDiagram-v2 [*] --> checking checking --> prompt_download : not-installed or version too old checking --> flushing : ready prompt_download --> [*] : user picks N or dismisses flushing --> opening : session flushed opening --> success : deep link opened opening --> error : OS failed to open URL success --> [*] : 500ms then onDone() error --> [*] : keypress

所需的最低桌面版本被硬编码为 MIN_DESKTOP_VERSION = '1.1.2396' in utils/desktopDeepLink.ts。 较旧的安装会被捕获,并在移交继续之前提示用户进行更新。

深层链接建设

启动切换的实际 URL 是由 buildDesktopDeepLink:

// utils/desktopDeepLink.ts
function buildDesktopDeepLink(sessionId: string): string {
  // dev builds use 'claude-dev://', prod uses 'claude://'
  const protocol = isDevMode() ? 'claude-dev' : 'claude'
  const url = new URL(`${protocol}://resume`)
  url.searchParams.set('session', sessionId)
  url.searchParams.set('cwd', getCwd())
  return url.toString()
}

网址格式为 claude://resume?session=<uuid>&cwd=<path>。 克劳德桌面注册 claude:// 通过 Electron 的协议处理程序 setAsDefaultProtocolClient。 在开发中,使用AppleScript代替 open 因为 dev Electron 二进制文件注册的是它自己而不是应用程序包:

// macOS dev mode workaround
const { code } = await execFileNoThrow('osascript', [
  '-e',
  `tell application "Electron" to open location "${deepLinkUrl}"`,
])

特定于平台的安装检测

在打开深层链接之前,代码会验证应用程序是否确实使用适合操作系统的方法安装:

macOS

路径检查

Checks /Applications/Claude.app 存在。 版本读取方式 defaults read …/Info.plist CFBundleShortVersionString.

Linux

xdg-mime 查询

Runs xdg-mime query default x-scheme-handler/claude 并检查标准输出是否为非空(单独的退出代码是不可靠的)。

Windows

注册表查询

Queries HKEY_CLASSES_ROOT\claude via reg query。 版本发现于 %LOCALAPPDATA%\AnthropicClaude\app-*\.

会话刷新:秘密成分

在打开深层链接之前, flushing 国家呼吁 flushSessionStorage()。 这会强制将所有缓冲的对话写入磁盘 ~/.claude/projects/ 以便 Claude Desktop 在打开时可以读取完整的会话记录 claude://resume 网址。 如果没有刷新,桌面应用程序将打开一个不完整的会话。

03 启动追加销售对话框

与显式分开 /desktop 命令,Claude Code 可以在启动时主动建议迁移到桌面应用程序。 逻辑存在于 components/DesktopUpsell/DesktopUpsellStartup.tsx.

当对话框出现时

功能 shouldShowDesktopUpsellStartup() 控制对话框:

export function shouldShowDesktopUpsellStartup(): boolean {
  if (!isSupportedPlatform()) return false
  // Requires GrowthBook flag 'tengu_desktop_upsell'.enable_startup_dialog
  if (!getDesktopUpsellConfig().enable_startup_dialog) return false
  const config = getGlobalConfig()
  if (config.desktopUpsellDismissed) return false   // "Never" was chosen
  if ((config.desktopUpsellSeenCount ?? 0) >= 3) return false  // capped at 3 showings
  return true
}

该对话框提供三个选项: 在 Claude Code 桌面中打开 (触发完整的切换流程), 现在不要 (增加看到的计数器),并且 永远不要再问 (sets desktopUpsellDismissed: true in ~/.claude/config.json via saveGlobalConfig)。 该功能由控制 tengu_desktop_upsell GrowthBook 实验,默认情况下处于禁用状态。

配置持久化
Both desktopUpsellDismissed and desktopUpsellSeenCount 存储在全局配置中 ~/.claude/config.json。 删除该文件会重置抑制状态。
04 从 Claude Desktop 导入 MCP 服务器

Claude Code 可以从 Claude Desktop 读取 MCP 服务器配置并将其导入。 该实用程序位于 utils/claudeDesktop.ts,它知道每个平台上的配置文件位置:

// utils/claudeDesktop.ts
export async function getClaudeDesktopConfigPath(): Promise<string> {
  if (platform === 'macos') {
    return join(homedir(), 'Library', 'Application Support', 'Claude',
                'claude_desktop_config.json')
  }
  // WSL: tries USERPROFILE, then scans /mnt/c/Users/* for AppData/Roaming/Claude/
}

解析该文件以提取 mcpServers 地图。 每个条目均经过验证 McpStdioServerConfigSchema() (Zod)在被接受之前。 这 MCPServerDesktopImportDialog 组件呈现一个多选列表,以便用户可以选择将哪些服务器复制到其 Claude Code 配置中。

WSL路径转换
在 WSL 上,Windows 路径如下 C:\Users\moiz\AppData\Roaming\Claude 被转换为 /mnt/c/Users/moiz/AppData/Roaming/Claude 通过剥离驱动器盘符并预先添加 /mnt/c.
05 IDE集成

当 Claude Code 在嵌入 IDE 的终端内运行时,它会检测 IDE 并调整行为 - 例如,使用 IDE 的文件打开 API 而不是系统编辑器,或通过更快的通道路由“辅助查询”(快速问题)。 检测发生在 utils/ide.ts.

支持的 IDE

The supportedIdeConfigs 映射到 ide.ts 涵盖两个家庭,由 ideKind field:

VS 代码系列

光标、风帆冲浪、vscode

通过进程名称检测,例如 Cursor Helper, Code Helper。 传输:HTTP/WebSocket 到本地端口。

JetBrains 家族

intellij、pycharm、webstorm、phpstorm、rubymine、clion、goland、rider、datagrip、appcode、dataspell、aqua、网关、舰队、androidstudio

总共 15 个 IDE。 传输:通过锁定文件通告的端口进行 SSE 或 WebSocket。

基于锁定文件的发现

IDE 插件将锁定文件写入 ~/.claude/ide/<port>.lock 当他们开始时。 Claude Code 读取这些内容以查找哪些 IDE 正在运行以及它们打开了哪些工作区文件夹:

// utils/ide.ts — lockfile JSON schema
type LockfileJsonContent = {
  workspaceFolders?: string[]
  pid?: number
  ideName?: string
  transport?: 'ws' | 'sse'
  runningInWindows?: boolean
  authToken?: string
}

锁定文件按修改时间排序(最新的在前),因此优先选择最近关注的 IDE。 过时的锁会被跳过——代码调用 isProcessRunning(pid) via process.kill(pid, 0) 在信任锁文件之前验证 PID 是否仍然存在。

终端检测快捷方式

在扫描锁定文件之前,代码会检查 TERM_PROGRAM 环境变量(存储在 env.terminal)。 如果它与已知的 IDE 进程名称匹配(例如, vscode, cursor), isSupportedTerminal() returns true 立即执行,无需任何文件系统 I/O。

Windows-in-WSL 路径转换

当 IDE 在本机 Windows 中运行但 Claude Code 在 WSL 中运行时,文件路径需要转换。 utils/idePathConversion.ts provides WindowsToWSLConverter 这改变了 C:\Users\moiz\project into /mnt/c/Users/moiz/project, 和 checkWSLDistroMatch 在接受跨界连接之前验证两个进程是否都针对相同的 WSL 分布。

06 Chrome 中的 Claude — 原生消息传递

Claude Code 可以充当 Chrome(或 Chromium)扩展程序的本机消息传递主机。 这允许浏览器扩展从网页调用 Claude Code 工具。 该子系统位于 utils/claudeInChrome/.

入口点标志

两个特殊的 CLI 标志在最顶部处理 entrypoints/cli.tsx,在任何正常启动之前:

// entrypoints/cli.tsx
if (process.argv[2] === '--claude-in-chrome-mcp') {
  // Runs the MCP server that the Chrome extension connects to
  await runClaudeInChromeMcpServer()
  return
} else if (process.argv[2] === '--chrome-native-host') {
  // Acts as the Chrome native messaging host (stdin/stdout protocol)
  await runChromeNativeHost()
  return
}

本机消息传递主机清单

Chrome 需要一个 JSON 清单文件,将主机标识符映射到二进制路径。 Claude Code 在启动时自动安装此清单 shouldEnableClaudeInChrome() 返回真:

const NATIVE_HOST_IDENTIFIER = 'com.anthropic.claude_code_browser_extension'

// In bundled (native binary) mode, a wrapper script is created that calls:
// `"${process.execPath}" --chrome-native-host`
// because native host manifests cannot contain CLI arguments directly.
const execCommand = `"${process.execPath}" --chrome-native-host`
void createWrapperScript(execCommand)
  .then(manifestBinaryPath => installChromeNativeHostManifest(manifestBinaryPath))

支持的浏览器

The CHROMIUM_BROWSERS 映射到 utils/claudeInChrome/common.ts 涵盖多个基于 Chromium 的浏览器,每个浏览器都具有特定于平台的数据路径和本机消息传递主机目录:

export const CHROMIUM_BROWSERS: Record<ChromiumBrowser, BrowserConfig> = {
  chrome:  { macos: { nativeMessagingPath: ['Library', 'Application Support',
                      'Google', 'Chrome', 'NativeMessagingHosts'] }, ... },
  // Also: chromium, brave, edge, opera, vivaldi, arc ...
}

激活条件

shouldEnableClaudeInChrome() 按优先顺序检查:

  1. 默认情况下在非交互式 (SDK/CI) 会话中禁用
  2. --chrome / --no-chrome CLI 标志覆盖所有内容
  3. CLAUDE_CODE_ENABLE_CFC 环境变量
  4. claudeInChromeDefaultEnabled 领域在 ~/.claude/config.json
  5. 自动启用:交互式会话 + 已安装扩展 + tengu_chrome_auto_enable 成长书旗帜
07 计算机使用 MCP — 操作系统级自动化

最强大(也是最封闭)的桌面集成是计算机的使用——截取屏幕截图、移动鼠标​​、敲击键盘以及与任意桌面应用程序交互的能力。 它作为一个单独的 MCP 服务器公开,代号为“Chicago”/“Malort”。

入口点标志

// entrypoints/cli.tsx
if (feature('CHICAGO_MCP') && process.argv[2] === '--computer-use-mcp') {
  await runComputerUseMcpServer()
  return
}

The feature('CHICAGO_MCP') call 是构建时死代码消除门:整个分支将从不启用该标志的外部构建中删除。

出入闸门

utils/computerUse/gates.ts 实现分层门检查:

export function getChicagoEnabled(): boolean {
  // Ant employees with monorepo access are excluded unless ALLOW_ANT_COMPUTER_USE_MCP=1
  if (process.env.USER_TYPE === 'ant' && process.env.MONOREPO_ROOT_DIR
      && !isEnvTruthy(process.env.ALLOW_ANT_COMPUTER_USE_MCP)) {
    return false
  }
  // External users need Max or Pro subscription + GrowthBook gate
  return hasRequiredSubscription() && readConfig().enabled
}

GrowthBook实验的关键是 tengu_malort_pedway。 它运送一个 ChicagoConfig 包含细粒度子门的对象:

type ChicagoConfig = CuSubGates & {
  enabled: boolean
  coordinateMode: 'pixels' | 'normalized'
  pixelValidation: boolean
  clipboardPasteMultiline: boolean
  mouseAnimation: boolean
  hideBeforeAction: boolean   // hide terminal before screenshot
  autoTargetDisplay: boolean
  clipboardGuard: boolean
}

终端捆绑ID感知

由于 Claude Code 是一个没有窗口的终端应用程序,因此计算机使用包需要知道哪个终端模拟器正在托管它,以便它可以从屏幕截图中排除该窗口,并避免意外阻止其自己的键盘输入。 utils/computerUse/common.ts 通过解决这个问题 __CFBundleIdentifier (由 macOS LaunchServices 设置),具有知名终端的静态后备表:

const TERMINAL_BUNDLE_ID_FALLBACK = {
  'iTerm.app':    'com.googlecode.iterm2',
  'Apple_Terminal': 'com.apple.Terminal',
  'ghostty':      'com.mitchellh.ghostty',
  'WarpTerminal': 'dev.warp.Warp-Stable',
  'vscode':       'com.microsoft.VSCode',
  // ...
}

坐标模式(pixels vs normalized)在第一次读取时被冻结 - 否则,实时的 GrowthBook 翻转会导致模型在一个坐标空间中思考,而执行器在另一个坐标空间中进行转换。

08 CLI 入口点:桌面快速路径

所有与桌面相关的子系统都通过最顶部的特殊参数检查挂接到 CLI 中。 entrypoints/cli.tsx,在任何模块加载或配置初始化之前。 这使本机主机模式的代码路径保持精简:

flowchart TD A[claude argv] --> B{argv[2] == ?} B -->|--claude-in-chrome-mcp| C[runClaudeInChromeMcpServer] B -->|--chrome-native-host| D[runChromeNativeHost] B -->|--computer-use-mcp| E[runComputerUseMcpServer
feature CHICAGO_MCP required] B -->|--daemon-worker| F[runDaemonWorker] B -->|remote-control / rc| G[bridgeMain] B -->|normal| H[full CLI boot] C --> Z[exit] D --> Z E --> Z F --> Z G --> Z

每个快速路径在其处理程序完成后立即返回,因此主机/服务器模式下的正常引导管道的开销为零。 这对于 Chrome 原生消息传递很重要 - Chrome 按需生成主机二进制文件,并期望它在几毫秒内做出响应。

09 本机安装程序

Claude Code 提供两种分发模式:npm 安装的 JavaScript 捆绑包和预编译的本机二进制文件。 本机安装程序位于 utils/nativeInstaller/ 与 npm 安装一起管理本机二进制文件的生命周期。

目录布局

# XDG-compliant layout
~/.local/share/claude/versions/   # permanent — one dir per installed version
~/.cache/claude/staging/          # temporary — download target before atomic rename
~/.local/state/claude/locks/      # PID-based lock files for update coordination
~/.local/bin/claude               # symlink → active version

安装程序保留 VERSION_RETENTION_COUNT = 2 磁盘上的版本,成功更新后删除旧版本。 通过基于 PID 的锁定文件,更新是多进程安全的,具有 7 天的过时超时(足够长的时间以保证笔记本电脑睡眠)。

平台定位

getPlatform() 在安装程序模块中生成类似的字符串 darwin-arm64, linux-x64, linux-x64-musl, win32-x64。 musl 变体是通过以下方式自动检测的 envDynamic.isMuslEnvironment() 适用于 Alpine Linux / Docker 环境。

便携式会话存储
utils/sessionStoragePortable.ts 包含故意不含内部依赖性的纯Node.js实用程序。 该文件与 VS Code 扩展包逐字共享,位于 packages/claude-vscode/src/common-host/sessionStorage.ts — 相同的代码读取 CLI 和 IDE 插件中的会话记录。
10 完整的整合地图
graph LR CLI["Claude Code CLI
(terminal)"] subgraph Desktop["Claude Desktop (Electron)"] DP["claude://resume
deep link handler"] DS["Session reader
(~/.claude/projects/)"] end subgraph IDE["IDE Plugin (VS Code / JetBrains)"] LF["~/.claude/ide/*.lock"] RP["Local HTTP/WS
RPC server"] end subgraph Browser["Chrome Extension"] NM["Native Messaging
com.anthropic.claude_code_browser_extension"] MCP["MCP Server
(--claude-in-chrome-mcp)"] end subgraph OS["OS (macOS)"] CU["Computer Use MCP
(--computer-use-mcp)
screenshots, mouse, keyboard"] end CLI -->|"/desktop command\nflushSession + open URL"| DP DP --> DS CLI -->|"reads lockfiles\nauto-detects"| LF LF --> RP CLI -->|"installs manifest\nspawns on demand"| NM NM --> MCP CLI -->|"Max/Pro + GB flag"| CU

要点

  • The /desktop 命令是一个 6 状态 React 机器,它刷新会话记录,构建一个 claude://resume?session=…&cwd=… URL,然后通过操作系统本机 URL 打开将其交给桌面应用程序。
  • 桌面安装检测特定于操作系统:macOS 上的路径检查, xdg-mime 在 Linux 上,在 Windows 上进行注册表查询。
  • IDE检测用途 ~/.claude/ide/<port>.lock 由 VS Code 和 JetBrains 插件编写的文件,并由 TERM_PROGRAM 环境变量。
  • Chrome 中的 Claude 通过 Chrome 的本机消息传递协议进行工作 — JSON 清单映射标识符 com.anthropic.claude_code_browser_extension 到调用 CLI 的包装器脚本 --chrome-native-host.
  • 计算机的使用是三重门控的:构建时功能标志(CHICAGO_MCP)、Max/Pro 订阅检查和 GrowthBook 实验密钥 tengu_malort_pedway.
  • 所有桌面模式快速路径(--chrome-native-host, --claude-in-chrome-mcp, --computer-use-mcp)在任何配置或分析初始化之前进行处理,以将启动开销降至最低。
  • utils/sessionStoragePortable.ts 有意无依赖关系,因此可以与 VS Code 扩展包共享,而无需引入 CLI-only 模块。

知识检查

Q1. 所需的最低 Claude Desktop 版本是多少 /desktop 切换继续吗?
Q2. 为什么 DesktopHandoff 组件调用 flushSessionStorage() 在打开深层链接之前?
Q3. 在 macOS 开发模式下,为什么 openDeepLink 使用AppleScript代替 open 命令?
Q4. 在读取任何锁定文件之前,Claude Code 如何检测它正在运行的 IDE?
Q5. Chrome 集成中用于 Claude 的本机消息传递主机标识符是什么?
Q6. 计算机使用的坐标模式是“初读时冻结”。 为什么?
0/6