init claude-code
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
import { readdir } from 'fs/promises'
|
||||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { isFsInaccessible } from '../errors.js'
|
||||
|
||||
export const CHROME_EXTENSION_URL = 'https://claude.ai/chrome'
|
||||
|
||||
// Production extension ID
|
||||
const PROD_EXTENSION_ID = 'fcoeoabgfenejglbffodgkkbkcdhcgfn'
|
||||
// Dev extension IDs (for internal use)
|
||||
const DEV_EXTENSION_ID = 'dihbgbndebgnbjfmelmegjepbnkhlgni'
|
||||
const ANT_EXTENSION_ID = 'dngcpimnedloihjnnfngkgjoidhnaolf'
|
||||
|
||||
function getExtensionIds(): string[] {
|
||||
return process.env.USER_TYPE === 'ant'
|
||||
? [PROD_EXTENSION_ID, DEV_EXTENSION_ID, ANT_EXTENSION_ID]
|
||||
: [PROD_EXTENSION_ID]
|
||||
}
|
||||
|
||||
// Must match ChromiumBrowser from common.ts
|
||||
export type ChromiumBrowser =
|
||||
| 'chrome'
|
||||
| 'brave'
|
||||
| 'arc'
|
||||
| 'chromium'
|
||||
| 'edge'
|
||||
| 'vivaldi'
|
||||
| 'opera'
|
||||
|
||||
export type BrowserPath = {
|
||||
browser: ChromiumBrowser
|
||||
path: string
|
||||
}
|
||||
|
||||
type Logger = (message: string) => void
|
||||
|
||||
// Browser detection order - must match BROWSER_DETECTION_ORDER from common.ts
|
||||
const BROWSER_DETECTION_ORDER: ChromiumBrowser[] = [
|
||||
'chrome',
|
||||
'brave',
|
||||
'arc',
|
||||
'edge',
|
||||
'chromium',
|
||||
'vivaldi',
|
||||
'opera',
|
||||
]
|
||||
|
||||
type BrowserDataConfig = {
|
||||
macos: string[]
|
||||
linux: string[]
|
||||
windows: { path: string[]; useRoaming?: boolean }
|
||||
}
|
||||
|
||||
// Must match CHROMIUM_BROWSERS dataPath from common.ts
|
||||
const CHROMIUM_BROWSERS: Record<ChromiumBrowser, BrowserDataConfig> = {
|
||||
chrome: {
|
||||
macos: ['Library', 'Application Support', 'Google', 'Chrome'],
|
||||
linux: ['.config', 'google-chrome'],
|
||||
windows: { path: ['Google', 'Chrome', 'User Data'] },
|
||||
},
|
||||
brave: {
|
||||
macos: ['Library', 'Application Support', 'BraveSoftware', 'Brave-Browser'],
|
||||
linux: ['.config', 'BraveSoftware', 'Brave-Browser'],
|
||||
windows: { path: ['BraveSoftware', 'Brave-Browser', 'User Data'] },
|
||||
},
|
||||
arc: {
|
||||
macos: ['Library', 'Application Support', 'Arc', 'User Data'],
|
||||
linux: [],
|
||||
windows: { path: ['Arc', 'User Data'] },
|
||||
},
|
||||
chromium: {
|
||||
macos: ['Library', 'Application Support', 'Chromium'],
|
||||
linux: ['.config', 'chromium'],
|
||||
windows: { path: ['Chromium', 'User Data'] },
|
||||
},
|
||||
edge: {
|
||||
macos: ['Library', 'Application Support', 'Microsoft Edge'],
|
||||
linux: ['.config', 'microsoft-edge'],
|
||||
windows: { path: ['Microsoft', 'Edge', 'User Data'] },
|
||||
},
|
||||
vivaldi: {
|
||||
macos: ['Library', 'Application Support', 'Vivaldi'],
|
||||
linux: ['.config', 'vivaldi'],
|
||||
windows: { path: ['Vivaldi', 'User Data'] },
|
||||
},
|
||||
opera: {
|
||||
macos: ['Library', 'Application Support', 'com.operasoftware.Opera'],
|
||||
linux: ['.config', 'opera'],
|
||||
windows: { path: ['Opera Software', 'Opera Stable'], useRoaming: true },
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all browser data paths to check for extension installation.
|
||||
* Portable version that uses process.platform directly.
|
||||
*/
|
||||
export function getAllBrowserDataPathsPortable(): BrowserPath[] {
|
||||
const home = homedir()
|
||||
const paths: BrowserPath[] = []
|
||||
|
||||
for (const browserId of BROWSER_DETECTION_ORDER) {
|
||||
const config = CHROMIUM_BROWSERS[browserId]
|
||||
let dataPath: string[] | undefined
|
||||
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
dataPath = config.macos
|
||||
break
|
||||
case 'linux':
|
||||
dataPath = config.linux
|
||||
break
|
||||
case 'win32': {
|
||||
if (config.windows.path.length > 0) {
|
||||
const appDataBase = config.windows.useRoaming
|
||||
? join(home, 'AppData', 'Roaming')
|
||||
: join(home, 'AppData', 'Local')
|
||||
paths.push({
|
||||
browser: browserId,
|
||||
path: join(appDataBase, ...config.windows.path),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (dataPath && dataPath.length > 0) {
|
||||
paths.push({
|
||||
browser: browserId,
|
||||
path: join(home, ...dataPath),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if the Claude in Chrome extension is installed by checking the Extensions
|
||||
* directory across all supported Chromium-based browsers and their profiles.
|
||||
*
|
||||
* This is a portable version that can be used by both TUI and VS Code extension.
|
||||
*
|
||||
* @param browserPaths - Array of browser data paths to check (from getAllBrowserDataPaths)
|
||||
* @param log - Optional logging callback for debug messages
|
||||
* @returns Object with isInstalled boolean and the browser where the extension was found
|
||||
*/
|
||||
export async function detectExtensionInstallationPortable(
|
||||
browserPaths: BrowserPath[],
|
||||
log?: Logger,
|
||||
): Promise<{
|
||||
isInstalled: boolean
|
||||
browser: ChromiumBrowser | null
|
||||
}> {
|
||||
if (browserPaths.length === 0) {
|
||||
log?.(`[Claude in Chrome] No browser paths to check`)
|
||||
return { isInstalled: false, browser: null }
|
||||
}
|
||||
|
||||
const extensionIds = getExtensionIds()
|
||||
|
||||
// Check each browser for the extension
|
||||
for (const { browser, path: browserBasePath } of browserPaths) {
|
||||
let browserProfileEntries = []
|
||||
|
||||
try {
|
||||
browserProfileEntries = await readdir(browserBasePath, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
} catch (e) {
|
||||
// Browser not installed or path doesn't exist, continue to next browser
|
||||
if (isFsInaccessible(e)) continue
|
||||
throw e
|
||||
}
|
||||
|
||||
const profileDirs = browserProfileEntries
|
||||
.filter(entry => entry.isDirectory())
|
||||
.filter(
|
||||
entry => entry.name === 'Default' || entry.name.startsWith('Profile '),
|
||||
)
|
||||
.map(entry => entry.name)
|
||||
|
||||
if (profileDirs.length > 0) {
|
||||
log?.(
|
||||
`[Claude in Chrome] Found ${browser} profiles: ${profileDirs.join(', ')}`,
|
||||
)
|
||||
}
|
||||
|
||||
// Check each profile for any of the extension IDs
|
||||
for (const profile of profileDirs) {
|
||||
for (const extensionId of extensionIds) {
|
||||
const extensionPath = join(
|
||||
browserBasePath,
|
||||
profile,
|
||||
'Extensions',
|
||||
extensionId,
|
||||
)
|
||||
|
||||
try {
|
||||
await readdir(extensionPath)
|
||||
log?.(
|
||||
`[Claude in Chrome] Extension ${extensionId} found in ${browser} ${profile}`,
|
||||
)
|
||||
return { isInstalled: true, browser }
|
||||
} catch {
|
||||
// Extension not found in this profile, continue checking
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log?.(`[Claude in Chrome] Extension not found in any browser`)
|
||||
return { isInstalled: false, browser: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper that returns just the boolean result
|
||||
*/
|
||||
export async function isChromeExtensionInstalledPortable(
|
||||
browserPaths: BrowserPath[],
|
||||
log?: Logger,
|
||||
): Promise<boolean> {
|
||||
const result = await detectExtensionInstallationPortable(browserPaths, log)
|
||||
return result.isInstalled
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that gets browser paths automatically.
|
||||
* Use this when you don't need to provide custom browser paths.
|
||||
*/
|
||||
export function isChromeExtensionInstalled(log?: Logger): Promise<boolean> {
|
||||
const browserPaths = getAllBrowserDataPathsPortable()
|
||||
return isChromeExtensionInstalledPortable(browserPaths, log)
|
||||
}
|
||||
Reference in New Issue
Block a user