Claude Code는 터미널 애플리케이션이지만 데스크톱 통합 범위가 꽤 넓습니다. 실행 중인 세션을 Claude Desktop으로 넘길 수 있고, 어떤 IDE에서 실행됐는지 자동 감지할 수 있으며 (VS Code, Cursor, JetBrains 계열), Chrome 확장을 native messaging host로 연결하고, 완전한 computer-use 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
통합 계층은 네 가지로 나뉘며, 각 계층마다 감지 로직, 활성화 경로, 폴백 동작이 다릅니다.
Claude Desktop 핸드오프
/desktop 명령이 세션을 flush한 뒤 deep link URL로 데스크톱 앱에서 엽니다.
IDE 통합
~/.claude/ide/의 lockfile을 읽어 VS Code, Cursor, Windsurf, 14개 이상의 JetBrains IDE를 자동 감지합니다.
Chrome 안의 Claude
브라우저 확장이 Claude Code 도구를 직접 호출할 수 있도록 native messaging host manifest를 설치합니다.
Computer Use MCP
Max/Pro 구독과 GrowthBook 플래그로 제한되며, OS를 제어하는 별도 MCP 서버를 실행합니다.
/desktop 명령과 Deep Link명령 등록
/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: '현재 세션을 Claude Desktop에서 이어서 열기',
availability: ['claude-ai'],
isEnabled: isSupportedPlatform,
get isHidden() { return !isSupportedPlatform() },
load: () => import('./desktop.js'),
}
활성화되면 이 명령은 DesktopHandoff React/Ink 컴포넌트를 렌더링합니다.
Windows는 x64만 지원하고, ARM Windows와 Linux는 제외됩니다.
핸드오프 상태 머신
components/DesktopHandoff.tsx의 DesktopHandoff는
여섯 개 상태를 가진 작은 상태 머신입니다.
최소 필요 데스크톱 버전은 utils/desktopDeepLink.ts에
MIN_DESKTOP_VERSION = '1.1.2396'로 하드코딩되어 있습니다.
더 오래된 설치는 미리 감지되고, 핸드오프를 진행하기 전에 업데이트 안내가 표시됩니다.
Deep link 구성
핸드오프를 실제로 시작하는 URL은 buildDesktopDeepLink가 만듭니다.
// utils/desktopDeepLink.ts
function buildDesktopDeepLink(sessionId: string): string {
// dev 빌드는 'claude-dev://', prod는 '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()
}
URL 형식은 claude://resume?session=<uuid>&cwd=<path>입니다.
Claude Desktop은 Electron의 setAsDefaultProtocolClient로
claude:// 프로토콜 핸들러를 등록합니다. 개발 환경에서는 앱 번들이 아니라
dev Electron 바이너리가 자신을 등록하기 때문에 open 대신 AppleScript를 씁니다.
// macOS dev 모드 우회책
const { code } = await execFileNoThrow('osascript', [
'-e',
`tell application "Electron" to open location "${deepLinkUrl}"`,
])
플랫폼별 설치 감지
deep link를 열기 전에, 코드는 OS에 맞는 방법으로 앱이 실제 설치되어 있는지 확인합니다.
경로 확인
/Applications/Claude.app 존재 여부를 확인합니다. 버전은 defaults read …/Info.plist CFBundleShortVersionString로 읽습니다.
xdg-mime 조회
xdg-mime query default x-scheme-handler/claude를 실행하고 stdout이 비어 있지 않은지 확인합니다. 종료 코드만으로는 신뢰할 수 없습니다.
레지스트리 조회
reg query로 HKEY_CLASSES_ROOT\claude를 조회합니다. 버전은 %LOCALAPPDATA%\AnthropicClaude\app-*\에서 찾습니다.
세션 flush, 핵심 재료
deep link를 열기 전에 flushing 상태는
flushSessionStorage()를 호출합니다. 이렇게 하면 버퍼에 남아 있던 모든 대화 turn이
~/.claude/projects/ 아래 디스크로 기록되고, Claude Desktop이
claude://resume URL을 열 때 전체 세션 기록을 읽을 수 있습니다.
flush가 없으면 데스크톱 앱은 불완전한 세션을 열게 됩니다.
명시적인 /desktop 명령과 별개로, Claude Code는 시작할 때
데스크톱 앱으로 옮기라고 먼저 제안할 수 있습니다. 이 로직은
components/DesktopUpsell/DesktopUpsellStartup.tsx에 있습니다.
다이얼로그가 나타나는 시점
shouldShowDesktopUpsellStartup() 함수가 이 다이얼로그를 제어합니다.
export function shouldShowDesktopUpsellStartup(): boolean {
if (!isSupportedPlatform()) return false
// GrowthBook 플래그 'tengu_desktop_upsell'.enable_startup_dialog 가 필요
if (!getDesktopUpsellConfig().enable_startup_dialog) return false
const config = getGlobalConfig()
if (config.desktopUpsellDismissed) return false // "다시 묻지 않기" 선택됨
if ((config.desktopUpsellSeenCount ?? 0) >= 3) return false // 최대 3번까지만 표시
return true
}
이 다이얼로그는 세 가지 선택지를 제공합니다. Claude Code Desktop에서 열기는
전체 핸드오프 흐름을 시작하고, 나중에는 본 횟수 카운터를 늘리며,
다시 묻지 않기는 saveGlobalConfig를 통해
~/.claude/config.json에 desktopUpsellDismissed: true를 기록합니다.
이 기능은 tengu_desktop_upsell GrowthBook 실험으로 제어되며,
기본값은 비활성화입니다.
desktopUpsellDismissed와 desktopUpsellSeenCount는 모두
전역 설정 파일 ~/.claude/config.json에 저장됩니다.
이 파일을 지우면 숨김 상태도 초기화됩니다.
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: 먼저 USERPROFILE을 시도하고, 그다음 /mnt/c/Users/* 아래의 AppData/Roaming/Claude/ 를 스캔
}
이 파일을 파싱해서 mcpServers 맵을 추출합니다. 각 항목은 수용하기 전에
McpStdioServerConfigSchema() (Zod)로 검증됩니다.
MCPServerDesktopImportDialog 컴포넌트는 다중 선택 목록을 렌더링해,
사용자가 Claude Code 설정으로 복사할 서버를 골라 담을 수 있게 합니다.
C:\Users\moiz\AppData\Roaming\Claude 같은 Windows 경로를,
드라이브 문자를 제거하고 /mnt/c를 앞에 붙여
/mnt/c/Users/moiz/AppData/Roaming/Claude로 바꿉니다.
Claude Code가 IDE 안에 내장된 터미널에서 실행되면, 어떤 IDE인지 감지해서 동작을 조정합니다.
예를 들어 시스템 편집기 대신 IDE의 파일 열기 API를 쓰거나,
"side queries" 같은 빠른 질문을 더 빠른 채널로 보낼 수 있습니다.
감지는 utils/ide.ts에서 이뤄집니다.
지원 IDE
ide.ts의 supportedIdeConfigs 맵은 두 계열을 다루며,
ideKind 필드로 구분합니다.
cursor, windsurf, vscode
Cursor Helper, Code Helper 같은 프로세스 이름으로 감지합니다. 전송 방식은 로컬 포트의 HTTP/WebSocket입니다.
intellij, pycharm, webstorm, phpstorm, rubymine, clion, goland, rider, datagrip, appcode, dataspell, aqua, gateway, fleet, androidstudio
총 15개 IDE를 지원합니다. 전송 방식은 lockfile에 기록된 포트를 통한 SSE 또는 WebSocket입니다.
Lockfile 기반 발견
IDE 플러그인은 시작할 때 ~/.claude/ide/<port>.lock에 lockfile을 씁니다.
Claude Code는 이를 읽어 어떤 IDE가 실행 중인지, 어떤 워크스페이스 폴더를 열고 있는지 알아냅니다.
// utils/ide.ts, lockfile JSON 스키마
type LockfileJsonContent = {
workspaceFolders?: string[]
pid?: number
ideName?: string
transport?: 'ws' | 'sse'
runningInWindows?: boolean
authToken?: string
}
lockfile은 수정 시간 기준으로 정렬되며, 가장 최근에 포커스를 받은 IDE가 우선됩니다.
오래된 lock은 건너뜁니다. 코드는 process.kill(pid, 0)를 통한
isProcessRunning(pid) 호출로 PID가 아직 살아 있는지 확인한 뒤에만 lockfile을 신뢰합니다.
터미널 감지 지름길
lockfile을 스캔하기 전에, 코드는 TERM_PROGRAM 환경 변수를 확인합니다.
이 값은 env.terminal에 저장됩니다. 값이 알려진 IDE 프로세스 이름
예를 들어 vscode, cursor와 일치하면,
isSupportedTerminal()은 파일시스템 I/O 없이 즉시 true를 반환합니다.
Windows in WSL 경로 변환
IDE는 네이티브 Windows에서 실행되고 Claude Code는 WSL 안에서 실행되는 경우,
파일 경로를 변환해야 합니다. utils/idePathConversion.ts의
WindowsToWSLConverter는 C:\Users\moiz\project를
/mnt/c/Users/moiz/project로 바꾸고,
checkWSLDistroMatch는 경계를 넘는 연결을 허용하기 전에 두 프로세스가
같은 WSL 배포판을 가리키는지 검증합니다.
Claude Code는 Chrome 또는 Chromium 확장의 native messaging host 역할을 할 수 있습니다.
덕분에 브라우저 확장이 웹페이지에서 Claude Code 도구를 호출할 수 있습니다.
이 서브시스템은 utils/claudeInChrome/ 아래에 있습니다.
Entrypoint 플래그
두 개의 특수 CLI 플래그는 일반 시작 절차보다 앞선,
entrypoints/cli.tsx 최상단에서 처리됩니다.
// entrypoints/cli.tsx
if (process.argv[2] === '--claude-in-chrome-mcp') {
// Chrome 확장이 연결하는 MCP 서버를 실행
await runClaudeInChromeMcpServer()
return
} else if (process.argv[2] === '--chrome-native-host') {
// Chrome native messaging host 역할 수행 (stdin/stdout 프로토콜)
await runChromeNativeHost()
return
}
Native messaging host manifest
Chrome은 host 식별자를 바이너리 경로에 매핑하는 JSON manifest 파일을 요구합니다.
Claude Code는 shouldEnableClaudeInChrome()가 true를 반환하면,
시작 시 이 manifest를 자동으로 설치합니다.
const NATIVE_HOST_IDENTIFIER = 'com.anthropic.claude_code_browser_extension'
// 번들된 상태(네이티브 바이너리)에서는 다음을 호출하는 wrapper script를 만듭니다.
// `"${process.execPath}" --chrome-native-host`
// native host manifest에는 CLI 인자를 직접 넣을 수 없기 때문입니다.
const execCommand = `"${process.execPath}" --chrome-native-host`
void createWrapperScript(execCommand)
.then(manifestBinaryPath => installChromeNativeHostManifest(manifestBinaryPath))
지원 브라우저
utils/claudeInChrome/common.ts의 CHROMIUM_BROWSERS 맵은
여러 Chromium 계열 브라우저를 다룹니다. 각 브라우저마다 플랫폼별 데이터 경로와
native messaging host 디렉터리가 정의되어 있습니다.
export const CHROMIUM_BROWSERS: Record<ChromiumBrowser, BrowserConfig> = {
chrome: { macos: { nativeMessagingPath: ['Library', 'Application Support',
'Google', 'Chrome', 'NativeMessagingHosts'] }, ... },
// 여기에 chromium, brave, edge, opera, vivaldi, arc 등도 포함
}
활성화 조건
shouldEnableClaudeInChrome()는 우선순위 순으로 다음을 확인합니다.
- 비대화형 세션(SDK/CI)에서는 기본적으로 비활성화
--chrome/--no-chromeCLI 플래그가 모든 것보다 우선CLAUDE_CODE_ENABLE_CFC환경 변수~/.claude/config.json의claudeInChromeDefaultEnabled필드- 자동 활성화: 대화형 세션 + 확장이 이미 설치됨 +
tengu_chrome_auto_enableGrowthBook 플래그
가장 강력하고 제한도 가장 많은 데스크톱 통합은 computer use입니다. 스크린샷을 찍고, 마우스를 움직이고, 키 입력을 보내고, 임의의 데스크톱 앱과 상호작용할 수 있습니다. 이 기능은 "Chicago" / "Malort"라는 코드네임의 별도 MCP 서버로 노출됩니다.
Entrypoint 플래그
// entrypoints/cli.tsx
if (feature('CHICAGO_MCP') && process.argv[2] === '--computer-use-mcp') {
await runComputerUseMcpServer()
return
}
feature('CHICAGO_MCP') 호출은 빌드 타임 dead-code-elimination 게이트입니다.
이 플래그를 켜지 않은 외부 빌드에서는 이 분기 전체가 제거됩니다.
접근 게이트
utils/computerUse/gates.ts는 층층이 쌓인 게이트 검사를 구현합니다.
export function getChicagoEnabled(): boolean {
// monorepo 접근 권한이 있는 Ant 직원은 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
}
// 외부 사용자는 Max 또는 Pro 구독 + GrowthBook 게이트가 필요
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 // 스크린샷 전에 터미널 숨기기
autoTargetDisplay: boolean
clipboardGuard: boolean
}
터미널 bundle ID 인식
Claude Code는 창이 없는 터미널 앱이기 때문에, computer-use 패키지는 자신을 어떤 터미널 에뮬레이터가 호스팅하는지 알아야 합니다.
그래야 스크린샷에서 그 창을 제외하고, 자기 자신의 키보드 입력을 실수로 막지 않을 수 있습니다.
utils/computerUse/common.ts는 macOS LaunchServices가 설정하는
__CFBundleIdentifier와, 잘 알려진 터미널을 위한 정적 fallback 테이블로 이를 해결합니다.
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와 normalized는 첫 읽기 시점에 고정됩니다.
세션 도중 GrowthBook 값이 바뀌면, 모델은 한 좌표계를 기준으로 생각하고 실행기는 다른 좌표계로 변환해 오동작할 수 있기 때문입니다.
데스크톱 관련 서브시스템은 모두 entrypoints/cli.tsx 최상단의 특수 인자 검사로 CLI에 연결됩니다.
모듈 로드나 설정 초기화보다도 앞서 실행됩니다.
덕분에 native host 모드의 코드 경로를 가볍게 유지할 수 있습니다.
feature CHICAGO_MCP 필요] B -->|--daemon-worker| F[runDaemonWorker] B -->|remote-control / rc| G[bridgeMain] B -->|일반 모드| H[전체 CLI 부팅] C --> Z[종료] D --> Z E --> Z F --> Z G --> Z
각 fast path는 핸들러가 끝나자마자 즉시 반환하므로, host/server 모드에서는 일반 부팅 파이프라인 오버헤드가 전혀 없습니다. 이 점은 Chrome native messaging에서 특히 중요합니다. Chrome은 필요할 때 host 바이너리를 실행하고, 밀리초 단위 안에 응답하길 기대합니다.
Claude Code는 두 가지 배포 방식을 제공합니다. npm으로 설치하는 JavaScript 번들과,
미리 컴파일된 네이티브 바이너리입니다. utils/nativeInstaller/의 네이티브 설치기는
npm 설치와 함께 이 네이티브 바이너리의 생명주기를 관리합니다.
디렉터리 레이아웃
# XDG 규약을 따르는 레이아웃
~/.local/share/claude/versions/ # 영구 저장, 설치된 버전마다 디렉터리 하나
~/.cache/claude/staging/ # 임시 저장, atomic rename 전 다운로드 대상
~/.local/state/claude/locks/ # 업데이트 조정을 위한 PID 기반 lock 파일
~/.local/bin/claude # symlink -> 현재 활성 버전
설치기는 VERSION_RETENTION_COUNT = 2개 버전을 디스크에 유지하고,
업데이트가 성공하면 더 오래된 버전을 지웁니다. 업데이트는 PID 기반 lockfile로 멀티 프로세스 안전하게 처리되며,
stale timeout은 7일이라 노트북 절전도 버틸 수 있습니다.
플랫폼 타기팅
설치기 모듈의 getPlatform()은 darwin-arm64, linux-x64,
linux-x64-musl, win32-x64 같은 문자열을 만듭니다.
musl 변형은 Alpine Linux나 Docker 환경을 위해 envDynamic.isMuslEnvironment()로 자동 감지합니다.
utils/sessionStoragePortable.ts에는 의도적으로 내부 의존성이 없는 순수 Node.js 유틸리티가 들어 있습니다.
이 파일은 VS Code 확장 패키지의 packages/claude-vscode/src/common-host/sessionStorage.ts와
완전히 같은 형태로 공유되며, CLI와 IDE 플러그인 양쪽에서 같은 코드로 세션 기록을 읽습니다.
(터미널)"] subgraph Desktop["Claude Desktop (Electron)"] DP["claude://resume
deep link 핸들러"] DS["세션 리더
(~/.claude/projects/)"] end subgraph IDE["IDE 플러그인 (VS Code / JetBrains)"] LF["~/.claude/ide/*.lock"] RP["로컬 HTTP/WS
RPC 서버"] end subgraph Browser["Chrome 확장"] NM["Native Messaging
com.anthropic.claude_code_browser_extension"] MCP["MCP 서버
(--claude-in-chrome-mcp)"] end subgraph OS["OS (macOS)"] CU["Computer Use MCP
(--computer-use-mcp)
스크린샷, 마우스, 키보드"] end CLI -->|"/desktop 명령\nflushSession + URL 열기"| DP DP --> DS CLI -->|"lockfile 읽기\n자동 감지"| LF LF --> RP CLI -->|"manifest 설치\n필요 시 실행"| NM NM --> MCP CLI -->|"Max/Pro + GB 플래그"| CU
핵심 요약
/desktop명령은 6개 상태를 가진 React 머신으로, 세션 기록을 flush하고claude://resume?session=…&cwd=…URL을 만든 뒤 OS의 URL 열기 기능으로 데스크톱 앱에 넘깁니다.- 데스크톱 설치 감지는 OS마다 다릅니다. macOS는 경로 확인, Linux는
xdg-mime, Windows는 레지스트리 조회를 사용합니다. - IDE 감지는 VS Code와 JetBrains 플러그인이 쓰는
~/.claude/ide/<port>.lock파일과, 이를 보완하는TERM_PROGRAM환경 변수를 사용합니다. - Chrome 안의 Claude는 Chrome의 native messaging 프로토콜로 동작합니다. JSON manifest가
com.anthropic.claude_code_browser_extension식별자를--chrome-native-host로 CLI를 호출하는 wrapper script에 연결합니다. - Computer use는 세 겹으로 제한됩니다. 빌드 타임 feature flag
CHICAGO_MCP, Max/Pro 구독 검사, GrowthBook 실험 키tengu_malort_pedway입니다. - 모든 데스크톱 모드 fast path, 즉
--chrome-native-host,--claude-in-chrome-mcp,--computer-use-mcp는 시작 오버헤드를 최소화하기 위해 어떤 설정이나 분석 초기화보다 먼저 처리됩니다. utils/sessionStoragePortable.ts는 CLI 전용 모듈을 끌어오지 않고 VS Code 확장 패키지와 공유할 수 있도록 의도적으로 의존성이 없습니다.
이해도 점검
/desktop 핸드오프가 진행되기 위해 필요한 최소 Claude Desktop 버전은 무엇인가요?DesktopHandoff 컴포넌트는 왜 deep link를 열기 전에 flushSessionStorage()를 호출할까요?openDeepLink는 왜 open 명령 대신 AppleScript를 사용할까요?