Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
14 kB · 540 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541import { readdirSync } from 'fs'import { stat } from 'fs/promises'import { homedir, platform, tmpdir, userInfo } from 'os'import { join } from 'path'import { normalizeNameForMCP } from '../../services/mcp/normalization.js'import { logForDebugging } from '../debug.js'import { isFsInaccessible } from '../errors.js'import { execFileNoThrow } from '../execFileNoThrow.js'import { getPlatform } from '../platform.js'import { which } from '../which.js'
export const CLAUDE_IN_CHROME_MCP_SERVER_NAME = 'claude-in-chrome'
// Re-export ChromiumBrowser type for setup.tsexport type { ChromiumBrowser } from './setupPortable.js'
// Import for local useimport type { ChromiumBrowser } from './setupPortable.js'
type BrowserConfig = { name: string macos: { appName: string dataPath: string[] nativeMessagingPath: string[] } linux: { binaries: string[] dataPath: string[] nativeMessagingPath: string[] } windows: { dataPath: string[] registryKey: string useRoaming?: boolean // Opera uses Roaming instead of Local }}
export const CHROMIUM_BROWSERS: Record<ChromiumBrowser, BrowserConfig> = { chrome: { name: 'Google Chrome', macos: { appName: 'Google Chrome', dataPath: ['Library', 'Application Support', 'Google', 'Chrome'], nativeMessagingPath: [ 'Library', 'Application Support', 'Google', 'Chrome', 'NativeMessagingHosts', ], }, linux: { binaries: ['google-chrome', 'google-chrome-stable'], dataPath: ['.config', 'google-chrome'], nativeMessagingPath: ['.config', 'google-chrome', 'NativeMessagingHosts'], }, windows: { dataPath: ['Google', 'Chrome', 'User Data'], registryKey: 'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts', }, }, brave: { name: 'Brave', macos: { appName: 'Brave Browser', dataPath: [ 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser', ], nativeMessagingPath: [ 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser', 'NativeMessagingHosts', ], }, linux: { binaries: ['brave-browser', 'brave'], dataPath: ['.config', 'BraveSoftware', 'Brave-Browser'], nativeMessagingPath: [ '.config', 'BraveSoftware', 'Brave-Browser', 'NativeMessagingHosts', ], }, windows: { dataPath: ['BraveSoftware', 'Brave-Browser', 'User Data'], registryKey: 'HKCU\\Software\\BraveSoftware\\Brave-Browser\\NativeMessagingHosts', }, }, arc: { name: 'Arc', macos: { appName: 'Arc', dataPath: ['Library', 'Application Support', 'Arc', 'User Data'], nativeMessagingPath: [ 'Library', 'Application Support', 'Arc', 'User Data', 'NativeMessagingHosts', ], }, linux: { // Arc is not available on Linux binaries: [], dataPath: [], nativeMessagingPath: [], }, windows: { // Arc Windows is Chromium-based dataPath: ['Arc', 'User Data'], registryKey: 'HKCU\\Software\\ArcBrowser\\Arc\\NativeMessagingHosts', }, }, chromium: { name: 'Chromium', macos: { appName: 'Chromium', dataPath: ['Library', 'Application Support', 'Chromium'], nativeMessagingPath: [ 'Library', 'Application Support', 'Chromium', 'NativeMessagingHosts', ], }, linux: { binaries: ['chromium', 'chromium-browser'], dataPath: ['.config', 'chromium'], nativeMessagingPath: ['.config', 'chromium', 'NativeMessagingHosts'], }, windows: { dataPath: ['Chromium', 'User Data'], registryKey: 'HKCU\\Software\\Chromium\\NativeMessagingHosts', }, }, edge: { name: 'Microsoft Edge', macos: { appName: 'Microsoft Edge', dataPath: ['Library', 'Application Support', 'Microsoft Edge'], nativeMessagingPath: [ 'Library', 'Application Support', 'Microsoft Edge', 'NativeMessagingHosts', ], }, linux: { binaries: ['microsoft-edge', 'microsoft-edge-stable'], dataPath: ['.config', 'microsoft-edge'], nativeMessagingPath: [ '.config', 'microsoft-edge', 'NativeMessagingHosts', ], }, windows: { dataPath: ['Microsoft', 'Edge', 'User Data'], registryKey: 'HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts', }, }, vivaldi: { name: 'Vivaldi', macos: { appName: 'Vivaldi', dataPath: ['Library', 'Application Support', 'Vivaldi'], nativeMessagingPath: [ 'Library', 'Application Support', 'Vivaldi', 'NativeMessagingHosts', ], }, linux: { binaries: ['vivaldi', 'vivaldi-stable'], dataPath: ['.config', 'vivaldi'], nativeMessagingPath: ['.config', 'vivaldi', 'NativeMessagingHosts'], }, windows: { dataPath: ['Vivaldi', 'User Data'], registryKey: 'HKCU\\Software\\Vivaldi\\NativeMessagingHosts', }, }, opera: { name: 'Opera', macos: { appName: 'Opera', dataPath: ['Library', 'Application Support', 'com.operasoftware.Opera'], nativeMessagingPath: [ 'Library', 'Application Support', 'com.operasoftware.Opera', 'NativeMessagingHosts', ], }, linux: { binaries: ['opera'], dataPath: ['.config', 'opera'], nativeMessagingPath: ['.config', 'opera', 'NativeMessagingHosts'], }, windows: { dataPath: ['Opera Software', 'Opera Stable'], registryKey: 'HKCU\\Software\\Opera Software\\Opera Stable\\NativeMessagingHosts', useRoaming: true, // Opera uses Roaming AppData, not Local }, },}
// Priority order for browser detection (most common first)export const BROWSER_DETECTION_ORDER: ChromiumBrowser[] = [ 'chrome', 'brave', 'arc', 'edge', 'chromium', 'vivaldi', 'opera',]
/** * Get all browser data paths to check for extension installation */export function getAllBrowserDataPaths(): { browser: ChromiumBrowser path: string}[] { const platform = getPlatform() const home = homedir() const paths: { browser: ChromiumBrowser; path: string }[] = []
for (const browserId of BROWSER_DETECTION_ORDER) { const config = CHROMIUM_BROWSERS[browserId] let dataPath: string[] | undefined
switch (platform) { case 'macos': dataPath = config.macos.dataPath break case 'linux': case 'wsl': dataPath = config.linux.dataPath break case 'windows': { if (config.windows.dataPath.length > 0) { const appDataBase = config.windows.useRoaming ? join(home, 'AppData', 'Roaming') : join(home, 'AppData', 'Local') paths.push({ browser: browserId, path: join(appDataBase, ...config.windows.dataPath), }) } continue } }
if (dataPath && dataPath.length > 0) { paths.push({ browser: browserId, path: join(home, ...dataPath), }) } }
return paths}
/** * Get native messaging host directories for all supported browsers */export function getAllNativeMessagingHostsDirs(): { browser: ChromiumBrowser path: string}[] { const platform = getPlatform() const home = homedir() const paths: { browser: ChromiumBrowser; path: string }[] = []
for (const browserId of BROWSER_DETECTION_ORDER) { const config = CHROMIUM_BROWSERS[browserId]
switch (platform) { case 'macos': if (config.macos.nativeMessagingPath.length > 0) { paths.push({ browser: browserId, path: join(home, ...config.macos.nativeMessagingPath), }) } break case 'linux': case 'wsl': if (config.linux.nativeMessagingPath.length > 0) { paths.push({ browser: browserId, path: join(home, ...config.linux.nativeMessagingPath), }) } break case 'windows': // Windows uses registry, not file paths for native messaging // We'll use a common location for the manifest file break } }
return paths}
/** * Get Windows registry keys for all supported browsers */export function getAllWindowsRegistryKeys(): { browser: ChromiumBrowser key: string}[] { const keys: { browser: ChromiumBrowser; key: string }[] = []
for (const browserId of BROWSER_DETECTION_ORDER) { const config = CHROMIUM_BROWSERS[browserId] if (config.windows.registryKey) { keys.push({ browser: browserId, key: config.windows.registryKey, }) } }
return keys}
/** * Detect which browser to use for opening URLs * Returns the first available browser, or null if none found */export async function detectAvailableBrowser(): Promise<ChromiumBrowser | null> { const platform = getPlatform()
for (const browserId of BROWSER_DETECTION_ORDER) { const config = CHROMIUM_BROWSERS[browserId]
switch (platform) { case 'macos': { // Check if the .app bundle (a directory) exists const appPath = `/Applications/${config.macos.appName}.app` try { const stats = await stat(appPath) if (stats.isDirectory()) { logForDebugging( `[Claude in Chrome] Detected browser: ${config.name}`, ) return browserId } } catch (e) { if (!isFsInaccessible(e)) throw e // App not found, continue checking } break } case 'wsl': case 'linux': { // Check if any binary exists for (const binary of config.linux.binaries) { if (await which(binary).catch(() => null)) { logForDebugging( `[Claude in Chrome] Detected browser: ${config.name}`, ) return browserId } } break } case 'windows': { // Check if data path exists (indicates browser is installed) const home = homedir() if (config.windows.dataPath.length > 0) { const appDataBase = config.windows.useRoaming ? join(home, 'AppData', 'Roaming') : join(home, 'AppData', 'Local') const dataPath = join(appDataBase, ...config.windows.dataPath) try { const stats = await stat(dataPath) if (stats.isDirectory()) { logForDebugging( `[Claude in Chrome] Detected browser: ${config.name}`, ) return browserId } } catch (e) { if (!isFsInaccessible(e)) throw e // Browser not found, continue checking } } break } } }
return null}
export function isClaudeInChromeMCPServer(name: string): boolean { return normalizeNameForMCP(name) === CLAUDE_IN_CHROME_MCP_SERVER_NAME}
const MAX_TRACKED_TABS = 200const trackedTabIds = new Set<number>()
export function trackClaudeInChromeTabId(tabId: number): void { if (trackedTabIds.size >= MAX_TRACKED_TABS && !trackedTabIds.has(tabId)) { trackedTabIds.clear() } trackedTabIds.add(tabId)}
export function isTrackedClaudeInChromeTabId(tabId: number): boolean { return trackedTabIds.has(tabId)}
export async function openInChrome(url: string): Promise<boolean> { const currentPlatform = getPlatform()
// Detect the best available browser const browser = await detectAvailableBrowser()
if (!browser) { logForDebugging('[Claude in Chrome] No compatible browser found') return false }
const config = CHROMIUM_BROWSERS[browser]
switch (currentPlatform) { case 'macos': { const { code } = await execFileNoThrow('open', [ '-a', config.macos.appName, url, ]) return code === 0 } case 'windows': { // Use rundll32 to avoid cmd.exe metacharacter issues with URLs containing & | > < const { code } = await execFileNoThrow('rundll32', ['url,OpenURL', url]) return code === 0 } case 'wsl': case 'linux': { for (const binary of config.linux.binaries) { const { code } = await execFileNoThrow(binary, [url]) if (code === 0) { return true } } return false } default: return false }}
/** * Get the socket directory path (Unix only) */export function getSocketDir(): string { return `/tmp/claude-mcp-browser-bridge-${getUsername()}`}
/** * Get the socket path (Unix) or pipe name (Windows) */export function getSecureSocketPath(): string { if (platform() === 'win32') { return `\\\\.\\pipe\\${getSocketName()}` } return join(getSocketDir(), `${process.pid}.sock`)}
/** * Get all socket paths including PID-based sockets in the directory * and legacy fallback paths */export function getAllSocketPaths(): string[] { // Windows uses named pipes, not Unix sockets if (platform() === 'win32') { return [`\\\\.\\pipe\\${getSocketName()}`] }
const paths: string[] = [] const socketDir = getSocketDir()
// Scan for *.sock files in the socket directory try { // eslint-disable-next-line custom-rules/no-sync-fs -- ClaudeForChromeContext.getSocketPaths (external @ant/claude-for-chrome-mcp) requires a sync () => string[] callback const files = readdirSync(socketDir) for (const file of files) { if (file.endsWith('.sock')) { paths.push(join(socketDir, file)) } } } catch { // Directory may not exist yet }
// Legacy fallback paths const legacyName = `claude-mcp-browser-bridge-${getUsername()}` const legacyTmpdir = join(tmpdir(), legacyName) const legacyTmp = `/tmp/${legacyName}`
if (!paths.includes(legacyTmpdir)) { paths.push(legacyTmpdir) } if (legacyTmpdir !== legacyTmp && !paths.includes(legacyTmp)) { paths.push(legacyTmp) }
return paths}
function getSocketName(): string { // NOTE: This must match the one used in the Claude in Chrome MCP return `claude-mcp-browser-bridge-${getUsername()}`}
function getUsername(): string { try { return userInfo().username || 'default' } catch { return process.env.USER || process.env.USERNAME || 'default' }}