From 68eb4d408805232c066af08a1a5b91d536f3ff3e Mon Sep 17 00:00:00 2001 From: ciltocruz Date: Thu, 24 Sep 2026 14:49:26 +0200 Subject: [PATCH 1/3] feat(browser): support additional Chrome channels and Chromium --- README.md | 2 +- .../src-tauri/src/commands/browser_import.rs | 4 + docs/PROVIDERS.md | 2 +- rust/src/browser/detection.rs | 119 ++++++++++++++++-- rust/src/browser/wsl_paths.rs | 100 ++++++++++----- 5 files changed, 179 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 2e052ad2f9..653e7f21ad 100755 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Win-CodexBar is a Windows system-tray app for keeping AI coding-tool usage visib - **Tray-first workflow** with a compact provider grid, usage cards, refresh action, settings shortcut, and quit control. - **Provider settings** for source selection, credentials, cookie import, token accounts, API keys, regions, and tray-display preferences. - **Windows credential protection** for app-managed API keys, manual cookies, and token accounts, using user-scoped DPAPI where available. -- **Browser cookie import** for Chrome, Edge, Brave, and Firefox, kept opt-in per provider. +- **Browser cookie import** for Chrome Stable, Beta, Dev, Canary, Chrome for Testing, Chromium, Edge, Brave, and Firefox, kept opt-in per provider. - **Installed local CLI** for scripting usage, cost, config, diagnostics, and loopback integrations. - **Installer + portable builds** with WebView2 runtime bootstrap, VC++ runtime bootstrap, and SHA-256 checksum files. diff --git a/apps/desktop-tauri/src-tauri/src/commands/browser_import.rs b/apps/desktop-tauri/src-tauri/src/commands/browser_import.rs index 40d8ff79a8..6e70a0eb32 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/browser_import.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/browser_import.rs @@ -92,6 +92,10 @@ fn browser_type_key(bt: codexbar::browser::detection::BrowserType) -> &'static s use codexbar::browser::detection::BrowserType; match bt { BrowserType::Chrome => "chrome", + BrowserType::ChromeBeta => "chrome-beta", + BrowserType::ChromeDev => "chrome-dev", + BrowserType::ChromeCanary => "chrome-canary", + BrowserType::ChromeForTesting => "chrome-for-testing", BrowserType::Edge => "edge", BrowserType::Brave => "brave", BrowserType::Arc => "arc", diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 237f7dab3d..3f5758d5e2 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -40,7 +40,7 @@ Auth resolution helpers in `rust/src/providers/` commonly try: explicit settings ## Cookie-backed providers -Windows browser import: Chrome, Edge, Brave (DPAPI + AES-GCM), Firefox (SQLite). +Windows browser import: Chrome Stable, Beta, Dev, Canary, Chrome for Testing, Chromium, Edge, Brave (DPAPI + AES-GCM), and Firefox (SQLite). Chrome channels are detected separately and keep distinct import choices. Settings → **Providers** → provider detail → choose browser → Import. Manual cookie header paste is the fallback (required under WSL for Chromium DPAPI). Details: [COOKIES.md](./COOKIES.md). diff --git a/rust/src/browser/detection.rs b/rust/src/browser/detection.rs index 0dc39f27d7..3b90c4a633 100755 --- a/rust/src/browser/detection.rs +++ b/rust/src/browser/detection.rs @@ -16,6 +16,10 @@ use crate::wsl; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BrowserType { Chrome, + ChromeBeta, + ChromeDev, + ChromeCanary, + ChromeForTesting, Edge, Brave, Arc, @@ -28,6 +32,10 @@ impl BrowserType { pub fn all() -> &'static [BrowserType] { &[ BrowserType::Chrome, + BrowserType::ChromeBeta, + BrowserType::ChromeDev, + BrowserType::ChromeCanary, + BrowserType::ChromeForTesting, BrowserType::Edge, BrowserType::Brave, BrowserType::Arc, @@ -45,6 +53,10 @@ impl BrowserType { pub fn display_name(&self) -> &'static str { match self { BrowserType::Chrome => "Google Chrome", + BrowserType::ChromeBeta => "Google Chrome Beta", + BrowserType::ChromeDev => "Google Chrome Dev", + BrowserType::ChromeCanary => "Google Chrome Canary", + BrowserType::ChromeForTesting => "Chrome for Testing", BrowserType::Edge => "Microsoft Edge", BrowserType::Brave => "Brave", BrowserType::Arc => "Arc", @@ -52,6 +64,44 @@ impl BrowserType { BrowserType::Chromium => "Chromium", } } + + /// Resolve a browser's Windows AppData/Local profile root. + pub fn user_data_dir_under(&self, appdata_local: &Path) -> Option { + match self { + BrowserType::Chrome => Some( + appdata_local + .join("Google") + .join("Chrome") + .join("User Data"), + ), + BrowserType::ChromeBeta => Some( + appdata_local + .join("Google") + .join("Chrome Beta") + .join("User Data"), + ), + BrowserType::ChromeDev => Some( + appdata_local + .join("Google") + .join("Chrome Dev") + .join("User Data"), + ), + BrowserType::ChromeCanary => Some( + appdata_local + .join("Google") + .join("Chrome SxS") + .join("User Data"), + ), + BrowserType::ChromeForTesting => Some( + appdata_local + .join("Google") + .join("Chrome for Testing") + .join("User Data"), + ), + BrowserType::Chromium => Some(appdata_local.join("Chromium").join("User Data")), + _ => None, + } + } } /// A detected browser installation @@ -142,12 +192,12 @@ impl BrowserDetector { && let Some(appdata_local) = wsl::windows_appdata_local() { let path = match browser_type { - BrowserType::Chrome => Some( - appdata_local - .join("Google") - .join("Chrome") - .join("User Data"), - ), + BrowserType::Chrome + | BrowserType::ChromeBeta + | BrowserType::ChromeDev + | BrowserType::ChromeCanary + | BrowserType::ChromeForTesting + | BrowserType::Chromium => browser_type.user_data_dir_under(&appdata_local), BrowserType::Edge => Some( appdata_local .join("Microsoft") @@ -161,7 +211,6 @@ impl BrowserDetector { .join("User Data"), ), BrowserType::Arc => Some(appdata_local.join("Arc").join("User Data")), - BrowserType::Chromium => Some(appdata_local.join("Chromium").join("User Data")), BrowserType::Firefox => wsl::windows_appdata_roaming() .map(|roaming| roaming.join("Mozilla").join("Firefox").join("Profiles")), }; @@ -176,10 +225,14 @@ impl BrowserDetector { let app_data = dirs::data_dir()?; let path = match browser_type { - BrowserType::Chrome => local_app_data - .join("Google") - .join("Chrome") - .join("User Data"), + BrowserType::Chrome + | BrowserType::ChromeBeta + | BrowserType::ChromeDev + | BrowserType::ChromeCanary + | BrowserType::ChromeForTesting + | BrowserType::Chromium => browser_type + .user_data_dir_under(&local_app_data) + .expect("matched a Chromium browser"), BrowserType::Edge => local_app_data .join("Microsoft") .join("Edge") @@ -189,7 +242,6 @@ impl BrowserDetector { .join("Brave-Browser") .join("User Data"), BrowserType::Arc => local_app_data.join("Arc").join("User Data"), - BrowserType::Chromium => local_app_data.join("Chromium").join("User Data"), BrowserType::Firefox => app_data.join("Mozilla").join("Firefox").join("Profiles"), }; @@ -268,6 +320,49 @@ impl BrowserDetector { mod tests { use super::*; + #[test] + fn chrome_channels_resolve_to_distinct_windows_profile_roots() { + let appdata = Path::new("C:/Users/test/AppData/Local"); + let channels = [ + ( + BrowserType::Chrome, + "Google/Chrome/User Data", + "Google Chrome", + ), + ( + BrowserType::ChromeBeta, + "Google/Chrome Beta/User Data", + "Google Chrome Beta", + ), + ( + BrowserType::ChromeDev, + "Google/Chrome Dev/User Data", + "Google Chrome Dev", + ), + ( + BrowserType::ChromeCanary, + "Google/Chrome SxS/User Data", + "Google Chrome Canary", + ), + ( + BrowserType::ChromeForTesting, + "Google/Chrome for Testing/User Data", + "Chrome for Testing", + ), + (BrowserType::Chromium, "Chromium/User Data", "Chromium"), + ]; + + for (browser, relative_path, display_name) in channels { + assert_eq!( + browser.user_data_dir_under(appdata).unwrap(), + appdata.join(relative_path), + "wrong profile root for {display_name}" + ); + assert_eq!(browser.display_name(), display_name); + assert!(BrowserType::all().contains(&browser)); + } + } + #[test] fn test_browser_detection() { let browsers = BrowserDetector::detect_all(); diff --git a/rust/src/browser/wsl_paths.rs b/rust/src/browser/wsl_paths.rs index 42d93f17d4..316da4c394 100644 --- a/rust/src/browser/wsl_paths.rs +++ b/rust/src/browser/wsl_paths.rs @@ -29,45 +29,38 @@ impl WslBrowserDetector { let mut browsers = Vec::new(); - let candidates: &[(BrowserType, PathBuf)] = &[ - ( - BrowserType::Chrome, - appdata_local - .join("Google") - .join("Chrome") - .join("User Data"), - ), - ( - BrowserType::Edge, - appdata_local - .join("Microsoft") - .join("Edge") - .join("User Data"), - ), - ( - BrowserType::Brave, - appdata_local - .join("BraveSoftware") - .join("Brave-Browser") - .join("User Data"), - ), - ( - BrowserType::Arc, - appdata_local.join("Arc").join("User Data"), - ), - ( - BrowserType::Chromium, - appdata_local.join("Chromium").join("User Data"), - ), - ]; + let candidates = windows_browser_candidates(&appdata_local); + let candidates: Vec<(BrowserType, PathBuf)> = candidates + .into_iter() + .chain([ + ( + BrowserType::Edge, + appdata_local + .join("Microsoft") + .join("Edge") + .join("User Data"), + ), + ( + BrowserType::Brave, + appdata_local + .join("BraveSoftware") + .join("Brave-Browser") + .join("User Data"), + ), + ( + BrowserType::Arc, + appdata_local.join("Arc").join("User Data"), + ), + ]) + .collect(); for (browser_type, user_data_dir) in candidates { if user_data_dir.exists() { - let profiles = detect_chromium_profiles(user_data_dir); + let profiles = detect_chromium_profiles(&user_data_dir); if !profiles.is_empty() { browsers.push(DetectedBrowser { - browser_type: *browser_type, - user_data_dir: user_data_dir.clone(), + browser_type, + user_data_dir, profiles, }); } @@ -95,6 +88,24 @@ impl WslBrowserDetector { } } +fn windows_browser_candidates(appdata_local: &std::path::Path) -> Vec<(BrowserType, PathBuf)> { + [ + BrowserType::Chrome, + BrowserType::ChromeBeta, + BrowserType::ChromeDev, + BrowserType::ChromeCanary, + BrowserType::ChromeForTesting, + BrowserType::Chromium, + ] + .into_iter() + .filter_map(|browser_type| { + browser_type + .user_data_dir_under(appdata_local) + .map(|path| (browser_type, path)) + }) + .collect() +} + fn detect_chromium_profiles(user_data_dir: &PathBuf) -> Vec { let mut profiles = Vec::new(); @@ -151,6 +162,27 @@ fn detect_firefox_profiles(profiles_dir: &PathBuf) -> Vec { mod tests { use super::*; + #[test] + fn windows_profile_roots_include_every_chrome_channel_and_chromium() { + let appdata = PathBuf::from("/mnt/c/Users/test/AppData/Local"); + let expected = [ + (BrowserType::Chrome, "Google/Chrome/User Data"), + (BrowserType::ChromeBeta, "Google/Chrome Beta/User Data"), + (BrowserType::ChromeDev, "Google/Chrome Dev/User Data"), + (BrowserType::ChromeCanary, "Google/Chrome SxS/User Data"), + ( + BrowserType::ChromeForTesting, + "Google/Chrome for Testing/User Data", + ), + (BrowserType::Chromium, "Chromium/User Data"), + ]; + + let actual = windows_browser_candidates(&appdata); + for (browser_type, relative_path) in expected { + assert!(actual.contains(&(browser_type, appdata.join(relative_path)))); + } + } + #[test] fn test_wsl_browser_detection() { let browsers = WslBrowserDetector::detect_all(); From eec2c4747c9794c9580a69c966c481b696a10b61 Mon Sep 17 00:00:00 2001 From: ciltocruz Date: Thu, 24 Sep 2026 14:59:55 +0200 Subject: [PATCH 2/3] refactor(browser): derive WSL browser candidates from shared types --- rust/src/browser/wsl_paths.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/rust/src/browser/wsl_paths.rs b/rust/src/browser/wsl_paths.rs index 316da4c394..fef31d4781 100644 --- a/rust/src/browser/wsl_paths.rs +++ b/rust/src/browser/wsl_paths.rs @@ -89,21 +89,15 @@ impl WslBrowserDetector { } fn windows_browser_candidates(appdata_local: &std::path::Path) -> Vec<(BrowserType, PathBuf)> { - [ - BrowserType::Chrome, - BrowserType::ChromeBeta, - BrowserType::ChromeDev, - BrowserType::ChromeCanary, - BrowserType::ChromeForTesting, - BrowserType::Chromium, - ] - .into_iter() - .filter_map(|browser_type| { - browser_type - .user_data_dir_under(appdata_local) - .map(|path| (browser_type, path)) - }) - .collect() + BrowserType::all() + .iter() + .copied() + .filter_map(|browser_type| { + browser_type + .user_data_dir_under(appdata_local) + .map(|path| (browser_type, path)) + }) + .collect() } fn detect_chromium_profiles(user_data_dir: &PathBuf) -> Vec { From cb68c7db3acaa75b325bcc9add0f6c5d55124095 Mon Sep 17 00:00:00 2001 From: ciltocruz Date: Thu, 24 Sep 2026 17:21:33 +0200 Subject: [PATCH 3/3] docs(browser): document WSL profile candidate helpers --- rust/src/browser/wsl_paths.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/src/browser/wsl_paths.rs b/rust/src/browser/wsl_paths.rs index fef31d4781..6c70fa2234 100644 --- a/rust/src/browser/wsl_paths.rs +++ b/rust/src/browser/wsl_paths.rs @@ -17,6 +17,7 @@ use super::detection::{BrowserProfile, BrowserType, DetectedBrowser}; pub struct WslBrowserDetector; impl WslBrowserDetector { + /// Detect Windows browsers visible from WSL via /mnt/c/ paths. pub fn detect_all() -> Vec { if !wsl::is_wsl() { return Vec::new(); @@ -88,6 +89,9 @@ impl WslBrowserDetector { } } +/// Collect profile roots for every browser that resolves one under the given +/// AppData/Local directory. Browsers without a shared-root mapping (Edge, +/// Brave, Arc, Firefox) are skipped here and added separately by the caller. fn windows_browser_candidates(appdata_local: &std::path::Path) -> Vec<(BrowserType, PathBuf)> { BrowserType::all() .iter() @@ -100,6 +104,7 @@ fn windows_browser_candidates(appdata_local: &std::path::Path) -> Vec<(BrowserTy .collect() } +/// Detect Chromium-based browser profiles fn detect_chromium_profiles(user_data_dir: &PathBuf) -> Vec { let mut profiles = Vec::new();