// Name: Chrome Profiles // Description: List Chrome profiles and copy their path to clipboard/open in Finder // Author: Strajk import '@johnlindquist/kit' import { Choice } from '@johnlindquist/kit/types'; const chromeAppSupportPaths = [ home('Library/Application Support/Google/Chrome/'), home('Library/Application Support/Google/Chrome Canary/'), home('Library/Application Support/Google/Chrome Dev/'), ] let choices: Choice[] = [] for (const chromeAppSupportPath of chromeAppSupportPaths) { if (!await pathExists(chromeAppSupportPath)) continue const subdirs = await readdir(chromeAppSupportPath) for (const subdir of subdirs) { const profilePath = path.join(chromeAppSupportPath, subdir) const profilePreferencesPath = path.join(profilePath, 'Preferences') if (!await pathExists(profilePreferencesPath)) continue try { const preferencesRaw = await readFile(profilePreferencesPath, 'utf-8') const preferencesJson = JSON.parse(preferencesRaw) const useful = pickUsefulFromPreferences(preferencesJson) let title = useful.accountEmail || useful.profileName title += ` (created ${useful.profileCreationTime ? useful.profileCreationTime.toISOString().split('T')[0] : ''})` let description = profilePath let tag = '' if (chromeAppSupportPath.includes('Google/Chrome Canary')) { tag = 'Canary' } else if (chromeAppSupportPath.includes('Google/Chrome Beta')) { tag = 'Beta' } else if (chromeAppSupportPath.includes('Google/Chrome Dev')) { tag = 'Dev' } else if (chromeAppSupportPath.includes('Google/Chrome')) { tag = 'Stable' } let className = '' if (isUnnamedProfile(title)) { className = 'text-gray-400' } choices.push({ value: profilePath, // full path always as a value img: useful.pictureUrl || useful.avatarUrl, name: title, description, tag, nameClassName: className, }) } catch (e) { console.warn(`Error parsing profile at ${profilePreferencesPath}`, e) } } } if (choices.length === 0) { await div("No Chrome profiles found") exit() } // Sort choices: regular profiles alphabetically first, then "Person X" and "Guest" profiles choices.sort((a, b) => { const aIsUnnamed = isUnnamedProfile(a.name) const bIsUnnamed = isUnnamedProfile(b.name) // If both are Unnamed profiles or both are not, sort alphabetically if ((aIsUnnamed && bIsUnnamed) || (!aIsUnnamed && !bIsUnnamed)) { return a.name.localeCompare(b.name) } // Put Unnamed profiles at the end return aIsUnnamed ? 1 : -1 }) let selectedProfile = await arg({ placeholder: 'Select a profile', enter: 'Copy Path', actions: [ { name: 'Open in Finder', onAction: async (input, { focused }) => { await revealFile(focused.value) }, }, ] }, choices) if (selectedProfile) { await clipboard.writeText(selectedProfile) notify(`Copied Profile path to clipboard`) } // HELPERS // === function isUnnamedProfile(title: string) { return title.match(/Person \d+|Guest/) } // Converts Windows FILETIME timestamp (100-nanosecond intervals since January 1, 1601) // to Unix timestamp (seconds since January 1, 1970) // See: https://learn.microsoft.com/en-us/windows/win32/sysinfo/file-times function windowsTimestampToDate(windowsTime: number): Date { try { const n = Number(windowsTime) / 1e6 - 11644473600; return new Date(n * 1000); } catch (e) { console.warn(`Error converting Windows timestamp ${windowsTime} to Date`, e); return undefined } } function pickUsefulFromPreferences(preferencesJson: any) { return { accountEmail: preferencesJson.account_info?.[0]?.email, accountName: preferencesJson.account_info?.[0]?.full_name, pictureUrl: preferencesJson.account_info?.[0]?.picture_url, avatarUrl: avatarIdToUrl(preferencesJson.profile?.avatar_index), profileName: preferencesJson.profile?.name, profileCreationTime: windowsTimestampToDate(preferencesJson.profile?.creation_time), lastEngagementTime: windowsTimestampToDate(preferencesJson.profile?.last_engagement_time), extensionsCount: Object.keys(preferencesJson.extensions.install_signature?.ids || {}).length, } } function chromeImageUrl(name: "stable" | "canary" | "dev" | "beta") { let url = `https://raw.githubusercontent.com/chromium/chromium/refs/heads/main/chrome/app/theme/default_100_percent/common/` if (name === "stable") { url += "product_logo_16.png" } else if (name === "canary") { url += "product_logo_32_canary.png" } else if (name === "dev") { url += "product_logo_32_dev.png" } else { url += "product_logo_32_beta.png" } return url } function avatarIdToUrl(avatarId: number) { // from https://gitlab.developers.cam.ac.uk/jz448/browser-android-tabs/-/blob/base-75.0.3770.67-brave-ads/chrome/app/theme/theme_resources.grd const mapIdToFile = { 0: 'profile_avatar_generic.png', 1: 'profile_avatar_generic_aqua.png', 2: 'profile_avatar_generic_blue.png', 3: 'profile_avatar_generic_green.png', 4: 'profile_avatar_generic_orange.png', 5: 'profile_avatar_generic_purple.png', 6: 'profile_avatar_generic_red.png', 7: 'profile_avatar_generic_yellow.png', 8: 'profile_avatar_secret_agent.png', 9: 'profile_avatar_superhero.png', 10: 'profile_avatar_volley_ball.png', 11: 'profile_avatar_businessman.png', 12: 'profile_avatar_ninja.png', 13: 'profile_avatar_alien.png', 14: 'profile_avatar_awesome.png', 15: 'profile_avatar_flower.png', 16: 'profile_avatar_pizza.png', 17: 'profile_avatar_soccer.png', 18: 'profile_avatar_burger.png', 19: 'profile_avatar_cat.png', 20: 'profile_avatar_cupcake.png', 21: 'profile_avatar_dog.png', 22: 'profile_avatar_horse.png', 23: 'profile_avatar_margarita.png', 24: 'profile_avatar_note.png', 25: 'profile_avatar_sun_cloud.png', 26: 'profile_avatar_placeholder.png', 27: 'modern_avatars/origami/avatar_cat.png', 28: 'modern_avatars/origami/avatar_corgi.png', 29: 'modern_avatars/origami/avatar_dragon.png', 30: 'modern_avatars/origami/avatar_elephant.png', 31: 'modern_avatars/origami/avatar_fox.png', 32: 'modern_avatars/origami/avatar_monkey.png', 33: 'modern_avatars/origami/avatar_panda.png', 34: 'modern_avatars/origami/avatar_penguin.png', 35: 'modern_avatars/origami/avatar_pinkbutterfly.png', 36: 'modern_avatars/origami/avatar_rabbit.png', 37: 'modern_avatars/origami/avatar_unicorn.png', 38: 'modern_avatars/illustration/avatar_basketball.png', 39: 'modern_avatars/illustration/avatar_bike.png', 40: 'modern_avatars/illustration/avatar_bird.png', 41: 'modern_avatars/illustration/avatar_cheese.png', 42: 'modern_avatars/illustration/avatar_football.png', 43: 'modern_avatars/illustration/avatar_ramen.png', 44: 'modern_avatars/illustration/avatar_sunglasses.png', 45: 'modern_avatars/illustration/avatar_sushi.png', 46: 'modern_avatars/illustration/avatar_tamagotchi.png', 47: 'modern_avatars/illustration/avatar_vinyl.png', 48: 'modern_avatars/abstract/avatar_avocado.png', 49: 'modern_avatars/abstract/avatar_cappuccino.png', 50: 'modern_avatars/abstract/avatar_icecream.png', 51: 'modern_avatars/abstract/avatar_icewater.png', 52: 'modern_avatars/abstract/avatar_melon.png', 53: 'modern_avatars/abstract/avatar_onigiri.png', 54: 'modern_avatars/abstract/avatar_pizza.png', 55: 'modern_avatars/abstract/avatar_sandwich.png' } const file = mapIdToFile[avatarId] return `https://raw.githubusercontent.com/chromium/chromium/refs/heads/main/chrome/app/theme/default_100_percent/common/${file}` }