Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
7.1 kB · 236 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237import { readdir } from 'fs/promises'import { join } from 'path'import { coerce as semverCoerce } from 'semver'import { getSessionId } from '../bootstrap/state.js'import { getCwd } from './cwd.js'import { logForDebugging } from './debug.js'import { execFileNoThrow } from './execFileNoThrow.js'import { pathExists } from './file.js'import { gte as semverGte } from './semver.js'
const MIN_DESKTOP_VERSION = '1.1.2396'
function isDevMode(): boolean { if ((process.env.NODE_ENV as string) === 'development') { return true }
// Local builds from build directories are dev mode even with NODE_ENV=production const pathsToCheck = [process.argv[1] || '', process.execPath || ''] const buildDirs = [ '/build-ant/', '/build-ant-native/', '/build-external/', '/build-external-native/', ]
return pathsToCheck.some(p => buildDirs.some(dir => p.includes(dir)))}
/** * Builds a deep link URL for Claude Desktop to resume a CLI session. * Format: claude://resume?session={sessionId}&cwd={cwd} * In dev mode: claude-dev://resume?session={sessionId}&cwd={cwd} */function buildDesktopDeepLink(sessionId: string): string { 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()}
/** * Check if Claude Desktop app is installed. * On macOS, checks for /Applications/Claude.app. * On Linux, checks if xdg-open can handle claude:// protocol. * On Windows, checks if the protocol handler exists. * In dev mode, always returns true (assumes dev Desktop is running). */async function isDesktopInstalled(): Promise<boolean> { // In dev mode, assume the dev Desktop app is running if (isDevMode()) { return true }
const platform = process.platform
if (platform === 'darwin') { // Check for Claude.app in /Applications return pathExists('/Applications/Claude.app') } else if (platform === 'linux') { // Check if xdg-mime can find a handler for claude:// // Note: xdg-mime returns exit code 0 even with no handler, so check stdout too const { code, stdout } = await execFileNoThrow('xdg-mime', [ 'query', 'default', 'x-scheme-handler/claude', ]) return code === 0 && stdout.trim().length > 0 } else if (platform === 'win32') { // On Windows, try to query the registry for the protocol handler const { code } = await execFileNoThrow('reg', [ 'query', 'HKEY_CLASSES_ROOT\\claude', '/ve', ]) return code === 0 }
return false}
/** * Detect the installed Claude Desktop version. * On macOS, reads CFBundleShortVersionString from the app plist. * On Windows, finds the highest app-X.Y.Z directory in the Squirrel install. * Returns null if version cannot be determined. */async function getDesktopVersion(): Promise<string | null> { const platform = process.platform
if (platform === 'darwin') { const { code, stdout } = await execFileNoThrow('defaults', [ 'read', '/Applications/Claude.app/Contents/Info.plist', 'CFBundleShortVersionString', ]) if (code !== 0) { return null } const version = stdout.trim() return version.length > 0 ? version : null } else if (platform === 'win32') { const localAppData = process.env.LOCALAPPDATA if (!localAppData) { return null } const installDir = join(localAppData, 'AnthropicClaude') try { const entries = await readdir(installDir) const versions = entries .filter(e => e.startsWith('app-')) .map(e => e.slice(4)) .filter(v => semverCoerce(v) !== null) .sort((a, b) => { const ca = semverCoerce(a)! const cb = semverCoerce(b)! return ca.compare(cb) }) return versions.length > 0 ? versions[versions.length - 1]! : null } catch { return null } }
return null}
export type DesktopInstallStatus = | { status: 'not-installed' } | { status: 'version-too-old'; version: string } | { status: 'ready'; version: string }
/** * Check Desktop install status including version compatibility. */export async function getDesktopInstallStatus(): Promise<DesktopInstallStatus> { const installed = await isDesktopInstalled() if (!installed) { return { status: 'not-installed' } }
let version: string | null try { version = await getDesktopVersion() } catch { // Best effort — proceed with handoff if version detection fails return { status: 'ready', version: 'unknown' } }
if (!version) { // Can't determine version — assume it's ready (dev mode or unknown install) return { status: 'ready', version: 'unknown' } }
const coerced = semverCoerce(version) if (!coerced || !semverGte(coerced.version, MIN_DESKTOP_VERSION)) { return { status: 'version-too-old', version } }
return { status: 'ready', version }}
/** * Opens a deep link URL using the platform-specific mechanism. * Returns true if the command succeeded, false otherwise. */async function openDeepLink(deepLinkUrl: string): Promise<boolean> { const platform = process.platform logForDebugging(`Opening deep link: ${deepLinkUrl}`)
if (platform === 'darwin') { if (isDevMode()) { // In dev mode, `open` launches a bare Electron binary (without app code) // because setAsDefaultProtocolClient registers just the Electron executable. // Use AppleScript to route the URL to the already-running Electron app. const { code } = await execFileNoThrow('osascript', [ '-e', `tell application "Electron" to open location "${deepLinkUrl}"`, ]) return code === 0 } const { code } = await execFileNoThrow('open', [deepLinkUrl]) return code === 0 } else if (platform === 'linux') { const { code } = await execFileNoThrow('xdg-open', [deepLinkUrl]) return code === 0 } else if (platform === 'win32') { // On Windows, use cmd /c start to open URLs const { code } = await execFileNoThrow('cmd', [ '/c', 'start', '', deepLinkUrl, ]) return code === 0 }
return false}
/** * Build and open a deep link to resume the current session in Claude Desktop. * Returns an object with success status and any error message. */export async function openCurrentSessionInDesktop(): Promise<{ success: boolean error?: string deepLinkUrl?: string}> { const sessionId = getSessionId()
// Check if Desktop is installed const installed = await isDesktopInstalled() if (!installed) { return { success: false, error: 'Claude Desktop is not installed. Install it from https://claude.ai/download', } }
// Build and open the deep link const deepLinkUrl = buildDesktopDeepLink(sessionId) const opened = await openDeepLink(deepLinkUrl)
if (!opened) { return { success: false, error: 'Failed to open Claude Desktop. Please try opening it manually.', deepLinkUrl, } }
return { success: true, deepLinkUrl }}