🍎 List Chrome profiles and copy their path to clipboard/open in Finder

I'm using a lot of profiles and often need their path for playwright/chrome extension testing, so I've made a script :) Feel free to adjust to your needs.

image

Open chrome-profiles in Script Kit

// 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}`
}