// Name: Open Chrome Windows by Profile // Description: Select Chrome profiles and URL sets to open multiple new windows. // Author: tayiorbeii // GitHub: tayiorbeii import "@johnlindquist/kit" type ChromeProfile = { dir: string name: string } type WindowConfig = { profileDir: string profileName: string urls: string[] } const getChromeUserDataDir = async (): Promise<string> => { if (isMac) { return home("Library", "Application Support", "Google", "Chrome") } else if (isWin) { return home("AppData", "Local", "Google", "Chrome", "User Data") } else { // linux const chromeDir = home(".config", "google-chrome") if (await pathExists(chromeDir)) return chromeDir return home(".config", "chromium") } } const getChromeProfiles = async (): Promise<ChromeProfile[]> => { const userDataDir = await getChromeUserDataDir() const localStatePath = path.join(userDataDir, "Local State") let profiles: ChromeProfile[] = [] try { const localStateRaw = await readFile(localStatePath, "utf8") const localState = JSON.parse(localStateRaw) const cache = localState?.profile?.info_cache || {} profiles = Object.keys(cache).map(dir => ({ dir, name: cache[dir]?.name || dir, })) } catch { // Fallback: list directories try { const entries = await readdir(userDataDir) for (const entry of entries) { if (entry === "Default" || entry.startsWith("Profile ")) { profiles.push({ dir: entry, name: entry }) } } } catch { // ignore } } // Ensure "Default" appears first if present profiles.sort((a, b) => { if (a.dir === "Default") return -1 if (b.dir === "Default") return 1 return a.name.localeCompare(b.name) }) // Deduplicate by dir const seen = new Set<string>() profiles = profiles.filter(p => { if (seen.has(p.dir)) return false seen.add(p.dir) return true }) // If we still have nothing, offer a minimal set if (profiles.length === 0) { profiles = [ { dir: "Default", name: "Default" }, { dir: "Profile 1", name: "Profile 1" }, { dir: "Profile 2", name: "Profile 2" }, ] } return profiles } const findChromeBinary = async (): Promise<string> => { if (isMac) { const candidates = [ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", ] for (const c of candidates) { if (await pathExists(c)) return c } const w = which("google-chrome") if (w) return w.toString() } else if (isWin) { const candidates = [ path.join(process.env["ProgramFiles"] || "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"), path.join(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "Google", "Chrome", "Application", "chrome.exe"), home("AppData", "Local", "Google", "Chrome", "Application", "chrome.exe"), ] for (const c of candidates) { try { if (await pathExists(c)) return c } catch {} } const w = which("chrome") || which("google-chrome") if (w) return w.toString() } else { const w = which("google-chrome") || which("chromium") || which("chromium-browser") if (w) return w.toString() } throw new Error("Could not locate Chrome binary") } const openChromeWindow = async (chromePath: string, profileDir: string, urls: string[]) => { const args = [`--profile-directory=${profileDir}`, "--new-window", ...urls] try { const child = spawn(chromePath, args, { detached: true, stdio: "ignore", }) child.unref() } catch (e) { warn(`Failed to launch Chrome window for profile ${profileDir}: ${e}`) } } const normalizeUrls = (lines: string[]): string[] => { return lines .map(l => l.trim()) .filter(l => !!l) .map(l => { if (/^[a-zA-Z]+:\/\//.test(l)) return l if (l.startsWith("www.")) return `https://${l}` // Treat as search if there's a space if (/\s/.test(l)) return `https://www.google.com/search?q=${encodeURIComponent(l)}` return `https://${l}` }) } const profiles = await getChromeProfiles() const chromePath = await findChromeBinary() const configs: WindowConfig[] = [] while (true) { const profileDir = await arg<string>( { placeholder: "Select a Chrome profile", enter: "Select", }, profiles.map(p => ({ name: `${p.name} (${p.dir})`, value: p.dir, })) ) const urlsText = await editor({ value: "", language: "plaintext", placeholder: "Enter one URL per line (or search terms). Example:\nhttps://news.ycombinator.com\nhttps://github.com\nmail.google.com", shortcuts: [ { name: "Done", key: `${cmd}+s`, onPress: (input: string) => submit(input), bar: "right", }, ], }) const urls = normalizeUrls(urlsText.split("\n")) if (urls.length === 0) { const retry = await arg("No URLs entered. What next?", ["Re-enter URLs", "Cancel this window"]) if (retry === "Re-enter URLs") continue // Cancel and ask to add another or finish } else { const selectedProfile = profiles.find(p => p.dir === profileDir) configs.push({ profileDir, profileName: selectedProfile?.name || profileDir, urls, }) } const next = await arg("Add another window or launch?", ["Add another window", "Launch"]) if (next === "Launch") break } if (configs.length === 0) { await notify("No windows to open.") exit() } await notify(`Opening ${configs.length} Chrome window${configs.length > 1 ? "s" : ""}...`) for (const c of configs) { await openChromeWindow(chromePath, c.profileDir, c.urls) } await hide()