browser-cookie-bridge 1.4.6 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import AppKit
2
2
  import Foundation
3
3
  import Security
4
+ import UniformTypeIdentifiers
4
5
 
5
6
  extension Notification.Name {
6
7
  static let menuBarVisibilityChanged = Notification.Name("BraveCodexSync.menuBarVisibilityChanged")
@@ -90,12 +91,18 @@ final class SyncModel: ObservableObject {
90
91
  @Published var selectedSourceID = "brave"
91
92
  @Published var selectedTargetID = "codex"
92
93
  @Published var codexRunning = false
94
+ @Published var cursorRunning = false
93
95
  @Published var sourceBrowserRunning = false
94
96
  @Published var browserlessConfigured = false
95
97
  @Published var browserlessProfileName = "browser-cookie-bridge"
96
98
  @Published var browserlessRegion = "sfo"
97
99
  @Published var browserlessOnlyDomains = ""
100
+ @Published var grokBotOnlyDomains = ""
98
101
  @Published var showingBrowserlessSetup = false
102
+ @Published var showingGrokBotResult = false
103
+ @Published var grokBotPassphrase = ""
104
+ @Published var grokBotPrompt = ""
105
+ @Published var grokBotOutputPath = ""
99
106
  @Published var browserlessAssessment: BrowserlessProfileAssessment?
100
107
  @Published var isInspectingBrowserlessProfile = false
101
108
  @Published var uploadProgress = 0.0
@@ -119,6 +126,8 @@ final class SyncModel: ObservableObject {
119
126
  private var uploadTimer: Timer?
120
127
  private var uploadStartedAt: Date?
121
128
  private var runtimeReady = true
129
+ private var rememberedHistoryEnabled = false
130
+ private var rememberedSiteStorageEnabled = false
122
131
 
123
132
  var selectedBrowser: BrowserChoice {
124
133
  browsers.first(where: { $0.id == selectedSourceID }) ?? browsers[0]
@@ -129,19 +138,33 @@ final class SyncModel: ObservableObject {
129
138
  }
130
139
 
131
140
  var isBrowserlessTarget: Bool { selectedTargetID == "browserless" }
141
+ var isGrokBotTarget: Bool { selectedTargetID == "grok-bot" }
142
+ var isDirectTarget: Bool { selectedTargetID == "codex" || selectedTargetID == "cursor" }
132
143
  var targetName: String {
133
- isBrowserlessTarget ? "Browserless Cloud" : selectedTargetBrowser?.name ?? "ChatGPT Codex"
144
+ if isBrowserlessTarget { return "Browserless Cloud" }
145
+ if isGrokBotTarget { return "Grok Bot" }
146
+ if selectedTargetID == "cursor" { return "Cursor" }
147
+ return selectedTargetBrowser?.name ?? "ChatGPT Codex"
134
148
  }
135
- var codexBlocked: Bool {
136
- selectedTargetID == "codex" && codexRunning && !autoRestartCodex && !(siteStorageEnabled && autoRestartBoth)
149
+ var directTargetRunning: Bool {
150
+ selectedTargetID == "cursor" ? cursorRunning : selectedTargetID == "codex" && codexRunning
151
+ }
152
+ var directTargetBlocked: Bool {
153
+ isDirectTarget && directTargetRunning
154
+ && (selectedTargetID == "cursor" || (!autoRestartCodex && !(siteStorageEnabled && autoRestartBoth)))
137
155
  }
138
156
  var sourceSiteDataBlocked: Bool {
139
- selectedTargetID == "codex" && siteStorageEnabled && sourceBrowserRunning && !autoRestartBoth
157
+ isDirectTarget && siteStorageEnabled && sourceBrowserRunning
158
+ && (selectedTargetID == "cursor" || !autoRestartBoth)
140
159
  }
141
160
  var browserlessBlocked: Bool {
142
161
  isBrowserlessTarget && (!browserlessConfigured || sourceBrowserRunning || selectedSourceID == "comet")
143
162
  }
144
- var syncBlocked: Bool { !runtimeReady || codexBlocked || sourceSiteDataBlocked || browserlessBlocked }
163
+ var cursorHasNoDataSelected: Bool { selectedTargetID == "cursor" && !cookiesEnabled }
164
+ var grokBotHasNoDataSelected: Bool { isGrokBotTarget && !cookiesEnabled }
165
+ var syncBlocked: Bool {
166
+ !runtimeReady || directTargetBlocked || sourceSiteDataBlocked || browserlessBlocked || cursorHasNoDataSelected || grokBotHasNoDataSelected
167
+ }
145
168
  var formattedUploadElapsed: String {
146
169
  let minutes = uploadElapsedSeconds / 60
147
170
  let seconds = uploadElapsedSeconds % 60
@@ -149,7 +172,20 @@ final class SyncModel: ObservableObject {
149
172
  }
150
173
  var sourceIcon: NSImage { browserIcon(selectedBrowser) }
151
174
  var targetIcon: NSImage {
152
- isBrowserlessTarget ? browserlessIcon : selectedTargetBrowser.map(browserIcon) ?? codexIcon
175
+ if isBrowserlessTarget { return browserlessIcon }
176
+ if isGrokBotTarget { return grokBotIcon }
177
+ if selectedTargetID == "cursor" { return cursorIcon }
178
+ return selectedTargetBrowser.map(browserIcon) ?? codexIcon
179
+ }
180
+ var grokBotIcon: NSImage {
181
+ if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.anysphere.sand") {
182
+ return NSWorkspace.shared.icon(forFile: url.path)
183
+ }
184
+ if let url = Bundle.main.url(forResource: "grok-bot", withExtension: "png", subdirectory: "BrowserIcons"),
185
+ let image = NSImage(contentsOf: url) {
186
+ return image
187
+ }
188
+ return NSImage(systemSymbolName: "sparkle", accessibilityDescription: "Grok Bot") ?? NSImage()
153
189
  }
154
190
  var browserlessIcon: NSImage {
155
191
  bundledIcon("browserless")
@@ -161,6 +197,9 @@ final class SyncModel: ObservableObject {
161
197
  ?? chatGPTResource("app.icns")
162
198
  ?? appIcon(bundleIdentifier: "com.openai.codex", fallbackSymbol: "terminal")
163
199
  }
200
+ var cursorIcon: NSImage {
201
+ appIcon(bundleIdentifier: "com.todesktop.230313mzl4w4u92", fallbackSymbol: "cursorarrow.square")
202
+ }
164
203
 
165
204
  func browserIcon(_ browser: BrowserChoice) -> NSImage {
166
205
  if browser.id == "brave",
@@ -264,13 +303,21 @@ final class SyncModel: ObservableObject {
264
303
  let configuredSource = config.sourceBrowser ?? "brave"
265
304
  selectedSourceID = browsers.contains(where: { $0.id == configuredSource }) ? configuredSource : "brave"
266
305
  let configuredTarget = config.targetBrowser ?? "codex"
267
- selectedTargetID = configuredTarget == "codex" || configuredTarget == "browserless" || browsers.contains(where: { $0.id == configuredTarget })
306
+ selectedTargetID = configuredTarget == "codex" || configuredTarget == "cursor" || configuredTarget == "browserless" || configuredTarget == "grok-bot" || browsers.contains(where: { $0.id == configuredTarget })
268
307
  ? configuredTarget
269
308
  : "codex"
270
309
  if selectedTargetID == selectedSourceID { selectedTargetID = "codex" }
271
310
  cookiesEnabled = config.imports?.cookies ?? true
272
- historyEnabled = config.imports?.history ?? false
273
- siteStorageEnabled = config.imports?.siteStorage ?? false
311
+ let loadedHistoryEnabled = config.imports?.history ?? false
312
+ let loadedSiteStorageEnabled = config.imports?.siteStorage ?? false
313
+ rememberedHistoryEnabled = config.rememberedImports?.history ?? loadedHistoryEnabled
314
+ rememberedSiteStorageEnabled = config.rememberedImports?.siteStorage ?? loadedSiteStorageEnabled
315
+ historyEnabled = loadedHistoryEnabled
316
+ siteStorageEnabled = loadedSiteStorageEnabled
317
+ if selectedTargetID == "cursor" || selectedTargetID == "grok-bot" {
318
+ historyEnabled = false
319
+ siteStorageEnabled = false
320
+ }
274
321
  menuBarEnabled = config.ui?.menuBar ?? true
275
322
  autoCheckUpdates = config.ui?.autoCheckUpdates ?? true
276
323
  autoRestartCodex = config.ui?.autoRestartCodex ?? false
@@ -278,6 +325,7 @@ final class SyncModel: ObservableObject {
278
325
  browserlessProfileName = config.browserless?.profileName ?? "browser-cookie-bridge"
279
326
  browserlessRegion = config.browserless?.region ?? "sfo"
280
327
  browserlessOnlyDomains = (config.browserless?.onlyDomains ?? []).joined(separator: ", ")
328
+ grokBotOnlyDomains = (config.grokBot?.onlyDomains ?? []).joined(separator: ", ")
281
329
  }
282
330
  browserlessConfigured = BrowserlessCredentialStore.read() != nil
283
331
  NotificationCenter.default.post(name: .menuBarVisibilityChanged, object: menuBarEnabled)
@@ -302,9 +350,21 @@ final class SyncModel: ObservableObject {
302
350
  }
303
351
 
304
352
  func selectTarget(_ id: String) {
305
- let validTarget = id == "codex" || id == "browserless" || browsers.contains(where: { $0.id == id })
353
+ let validTarget = id == "codex" || id == "cursor" || id == "browserless" || id == "grok-bot" || browsers.contains(where: { $0.id == id })
306
354
  guard validTarget, id != selectedTargetID, id != selectedSourceID else { return }
355
+ let wasCursor = selectedTargetID == "cursor"
356
+ if id == "cursor" && !wasCursor {
357
+ rememberedHistoryEnabled = historyEnabled
358
+ rememberedSiteStorageEnabled = siteStorageEnabled
359
+ }
307
360
  selectedTargetID = id
361
+ if id == "cursor" || id == "grok-bot" {
362
+ historyEnabled = false
363
+ siteStorageEnabled = false
364
+ } else if wasCursor {
365
+ historyEnabled = rememberedHistoryEnabled
366
+ siteStorageEnabled = rememberedSiteStorageEnabled
367
+ }
308
368
  persistPreferences(successMessage: "Import destination changed to \(targetName)")
309
369
  updateEndpointRunningStatus()
310
370
  if id == "browserless" && !browserlessConfigured { showingBrowserlessSetup = true }
@@ -317,12 +377,16 @@ final class SyncModel: ObservableObject {
317
377
  }
318
378
 
319
379
  func setHistoryEnabled(_ enabled: Bool) {
380
+ guard selectedTargetID != "cursor", selectedTargetID != "grok-bot" else { return }
320
381
  historyEnabled = enabled
382
+ rememberedHistoryEnabled = enabled
321
383
  persistPreferences(successMessage: enabled ? "History URL import enabled" : "History import disabled")
322
384
  }
323
385
 
324
386
  func setSiteStorageEnabled(_ enabled: Bool) {
387
+ guard selectedTargetID != "cursor", selectedTargetID != "grok-bot" else { return }
325
388
  siteStorageEnabled = enabled
389
+ rememberedSiteStorageEnabled = enabled
326
390
  persistPreferences(successMessage: enabled ? "Full site-data import enabled" : "Full site-data import disabled")
327
391
  }
328
392
 
@@ -410,7 +474,7 @@ final class SyncModel: ObservableObject {
410
474
  }
411
475
  if self.isVersion(release.version, newerThan: self.currentVersion) {
412
476
  self.availableUpdateVersion = release.version
413
- if !self.codexBlocked && !self.isSyncing {
477
+ if !self.directTargetBlocked && !self.isSyncing {
414
478
  self.state = .ready
415
479
  self.primaryStatus = "Update \(release.version) available"
416
480
  self.secondaryStatus = "Install it now; the app will relaunch automatically"
@@ -460,6 +524,11 @@ final class SyncModel: ObservableObject {
460
524
  }
461
525
  }
462
526
 
527
+ func setGrokBotOnlyDomains(_ value: String) {
528
+ grokBotOnlyDomains = value
529
+ persistPreferences(successMessage: "Grok Bot domain filter updated")
530
+ }
531
+
463
532
  func syncNow(showMenuBarAlert: Bool = false) {
464
533
  guard !isSyncing else {
465
534
  if isBrowserlessTarget {
@@ -478,6 +547,10 @@ final class SyncModel: ObservableObject {
478
547
  }
479
548
  return
480
549
  }
550
+ if isGrokBotTarget {
551
+ startGrokBotExport(showMenuBarAlert: showMenuBarAlert)
552
+ return
553
+ }
481
554
  if selectedTargetID == "codex" && siteStorageEnabled && autoRestartBoth && (sourceBrowserRunning || codexRunning) {
482
555
  forceQuitBothThenSync(showMenuBarAlert: showMenuBarAlert)
483
556
  return
@@ -612,18 +685,43 @@ final class SyncModel: ObservableObject {
612
685
  }
613
686
  }
614
687
 
615
- private func startSync(showMenuBarAlert: Bool, reopenCodexOnSuccess: Bool, reopenSourceOnSuccess: Bool = false) {
688
+ private func startGrokBotExport(showMenuBarAlert: Bool) {
689
+ let panel = NSSavePanel()
690
+ panel.title = "Save Grok Bot transfer file"
691
+ panel.nameFieldStringValue = "GrokBot-Import.bcbx"
692
+ panel.canCreateDirectories = true
693
+ panel.isExtensionHidden = false
694
+ if #available(macOS 12.0, *) {
695
+ panel.allowedContentTypes = [UTType(filenameExtension: "bcbx") ?? .data]
696
+ } else {
697
+ panel.allowedFileTypes = ["bcbx"]
698
+ }
699
+ panel.begin { [weak self] response in
700
+ guard let self else { return }
701
+ guard response == .OK, let url = panel.url else { return }
702
+ self.startSync(showMenuBarAlert: showMenuBarAlert, reopenCodexOnSuccess: false, grokBotOutputPath: url.path)
703
+ }
704
+ }
705
+
706
+ private func startSync(showMenuBarAlert: Bool, reopenCodexOnSuccess: Bool, reopenSourceOnSuccess: Bool = false, grokBotOutputPath: String? = nil) {
616
707
  isSyncing = true
617
708
  uploadCanceling = false
618
709
  state = .syncing
619
- primaryStatus = isBrowserlessTarget ? "Uploading authenticated state" : "Transferring selected data"
620
- secondaryStatus = selectedTargetID == "codex"
621
- ? "Backing up Codex and merging \(selectedBrowser.name) locally…"
710
+ primaryStatus = isGrokBotTarget
711
+ ? "Creating Grok Bot transfer file"
712
+ : isBrowserlessTarget ? "Uploading authenticated state" : "Transferring selected data"
713
+ secondaryStatus = isGrokBotTarget
714
+ ? "Encrypting selected cookie sessions from \(selectedBrowser.name)…"
715
+ : isDirectTarget
716
+ ? "Backing up \(targetName) and merging \(selectedBrowser.name) locally…"
622
717
  : isBrowserlessTarget
623
718
  ? "Sending \(selectedBrowser.name) to Browserless \(browserlessRegion.uppercased()) only for this request…"
624
719
  : "Waiting for \(selectedBrowser.name) and \(targetName)…"
625
720
  var environment: [String: String] = [:]
626
721
  var arguments = ["sync", "--timeout", isBrowserlessTarget ? "900" : "300"]
722
+ if let grokBotOutputPath {
723
+ arguments.append(contentsOf: ["--output", grokBotOutputPath])
724
+ }
627
725
  if isBrowserlessTarget {
628
726
  guard let token = BrowserlessCredentialStore.read() else {
629
727
  isSyncing = false
@@ -655,17 +753,26 @@ final class SyncModel: ObservableObject {
655
753
  self.secondaryStatus = "No cloud profile was changed; temporary profile data was removed"
656
754
  } else if success {
657
755
  self.state = partial ? .warning : .success
658
- self.primaryStatus = self.isBrowserlessTarget
659
- ? (partial ? "Browserless profile uploaded with omissions" : "Browserless profile uploaded")
660
- : self.selectedTargetID == "codex"
661
- ? (partial ? "Codex sync completed with warnings" : "Codex sessions updated")
662
- : (partial ? "Partially synced" : "Transfer complete")
663
- self.secondaryStatus = self.lastMeaningfulLine(output) ?? "\(self.selectedBrowser.name) and \(self.targetName) are up to date"
756
+ if self.isGrokBotTarget, let result = self.parseGrokBotResult(from: output) {
757
+ self.grokBotPassphrase = result.passphrase
758
+ self.grokBotPrompt = result.prompt
759
+ self.grokBotOutputPath = result.outputPath
760
+ self.showingGrokBotResult = true
761
+ self.primaryStatus = partial ? "Grok Bot transfer created with warnings" : "Grok Bot transfer file ready"
762
+ self.secondaryStatus = self.lastMeaningfulLine(output) ?? "Attach the .bcbx file to any Grok Bot and paste the prompt"
763
+ } else {
764
+ self.primaryStatus = self.isBrowserlessTarget
765
+ ? (partial ? "Browserless profile uploaded with omissions" : "Browserless profile uploaded")
766
+ : self.isDirectTarget
767
+ ? (partial ? "\(self.targetName) sync completed with warnings" : "\(self.targetName) sessions updated")
768
+ : (partial ? "Partially synced" : "Transfer complete")
769
+ self.secondaryStatus = self.lastMeaningfulLine(output) ?? "\(self.selectedBrowser.name) and \(self.targetName) are up to date"
770
+ }
664
771
  } else {
665
772
  self.state = .error
666
773
  self.primaryStatus = "Sync did not finish"
667
- self.secondaryStatus = self.lastMeaningfulLine(output) ?? (self.selectedTargetID == "codex"
668
- ? "Quit Codex completely, then try again"
774
+ self.secondaryStatus = self.lastMeaningfulLine(output) ?? (self.isDirectTarget
775
+ ? "Quit \(self.targetName) completely, then try again"
669
776
  : self.isBrowserlessTarget
670
777
  ? "Check the API token, close the source browser, and try again"
671
778
  : "Keep both browsers open and check the extensions")
@@ -867,6 +974,7 @@ final class SyncModel: ObservableObject {
867
974
  "--browserless-profile", browserlessProfileName,
868
975
  "--browserless-region", browserlessRegion,
869
976
  "--browserless-domains", browserlessOnlyDomains,
977
+ "--grok-bot-domains", grokBotOnlyDomains,
870
978
  ]
871
979
  runCLI(arguments) { [weak self] success, output in
872
980
  guard let self else { return }
@@ -876,6 +984,8 @@ final class SyncModel: ObservableObject {
876
984
  self.primaryStatus = successMessage
877
985
  self.secondaryStatus = self.isBrowserlessTarget
878
986
  ? "Cloud uploads run only after you click Upload"
987
+ : self.isGrokBotTarget
988
+ ? "Transfer files are created only when you click Create transfer file"
879
989
  : "This choice is saved for manual and daily syncs"
880
990
  } else {
881
991
  self.state = .error
@@ -1052,14 +1162,32 @@ final class SyncModel: ObservableObject {
1052
1162
  }
1053
1163
 
1054
1164
  private var requiredExtensionIDs: [String] {
1055
- selectedTargetID == "codex" || selectedTargetID == "browserless" ? [] : [selectedSourceID, selectedTargetID]
1165
+ isDirectTarget || selectedTargetID == "browserless" || isGrokBotTarget ? [] : [selectedSourceID, selectedTargetID]
1166
+ }
1167
+
1168
+ private struct GrokBotResultPayload: Decodable {
1169
+ let outputPath: String
1170
+ let passphrase: String
1171
+ let prompt: String
1172
+ }
1173
+
1174
+ private func parseGrokBotResult(from output: String) -> GrokBotResultPayload? {
1175
+ guard let line = output.split(separator: "\n").map(String.init).last(where: { $0.hasPrefix("BCB_GROK_RESULT ") }) else {
1176
+ return nil
1177
+ }
1178
+ let json = line.replacingOccurrences(of: "BCB_GROK_RESULT ", with: "")
1179
+ guard let data = json.data(using: .utf8) else { return nil }
1180
+ return try? JSONDecoder().decode(GrokBotResultPayload.self, from: data)
1056
1181
  }
1057
1182
 
1058
1183
  private func updateEndpointRunningStatus() {
1059
- let wasRunning = codexRunning
1184
+ let wasDirectTargetRunning = directTargetRunning
1060
1185
  codexRunning = NSWorkspace.shared.runningApplications.contains {
1061
1186
  $0.bundleIdentifier == "com.openai.codex"
1062
1187
  }
1188
+ cursorRunning = NSWorkspace.shared.runningApplications.contains {
1189
+ $0.bundleIdentifier == "com.todesktop.230313mzl4w4u92"
1190
+ }
1063
1191
  sourceBrowserRunning = NSWorkspace.shared.runningApplications.contains {
1064
1192
  $0.bundleIdentifier == selectedBrowser.bundleIdentifier
1065
1193
  }
@@ -1076,20 +1204,34 @@ final class SyncModel: ObservableObject {
1076
1204
  state = .warning
1077
1205
  primaryStatus = "Quit \(selectedBrowser.name) before syncing"
1078
1206
  secondaryStatus = "Full site data uses live LevelDB files. Close the source browser completely so they can be copied safely"
1207
+ } else if cursorHasNoDataSelected {
1208
+ state = .warning
1209
+ primaryStatus = "Turn on Cookies to sync with Cursor"
1210
+ secondaryStatus = "Cursor import currently supports cookie sessions only"
1079
1211
  } else if selectedTargetID == "codex" && codexRunning && autoRestartCodex {
1080
1212
  if state == .ready || primaryStatus == "Quit Codex before syncing" {
1081
1213
  state = .ready
1082
1214
  primaryStatus = "Ready to sync and restart Codex"
1083
1215
  secondaryStatus = "Sync will force quit Codex and reopen it only after a successful transfer"
1084
1216
  }
1085
- } else if selectedTargetID == "codex" && codexRunning {
1217
+ } else if isDirectTarget && directTargetRunning {
1086
1218
  state = .warning
1087
- primaryStatus = "Quit Codex before syncing"
1088
- secondaryStatus = "Close ChatGPT Codex completely so its local cookie database can be updated safely"
1089
- } else if selectedTargetID == "codex" && wasRunning && primaryStatus == "Quit Codex before syncing" {
1219
+ primaryStatus = "Quit \(targetName) before syncing"
1220
+ secondaryStatus = "Close \(targetName) completely so its local cookie database can be updated safely"
1221
+ } else if isDirectTarget && wasDirectTargetRunning && primaryStatus.hasPrefix("Quit ") {
1090
1222
  state = .ready
1091
1223
  primaryStatus = "Ready to sync directly"
1092
- secondaryStatus = "Codex is closed — a backup will be created before anything changes"
1224
+ secondaryStatus = "\(targetName) is closed — a backup will be created before anything changes"
1225
+ } else if isGrokBotTarget {
1226
+ if grokBotHasNoDataSelected {
1227
+ state = .warning
1228
+ primaryStatus = "Turn on Cookies to export for Grok Bot"
1229
+ secondaryStatus = "Grok Bot transfer files include cookie sessions only"
1230
+ } else {
1231
+ state = .ready
1232
+ primaryStatus = "Ready to create a Grok Bot transfer file"
1233
+ secondaryStatus = "Creates an encrypted .bcbx bundle with a one-time key and bundled importer"
1234
+ }
1093
1235
  } else if isBrowserlessTarget {
1094
1236
  if selectedSourceID == "comet" {
1095
1237
  state = .warning
@@ -1186,8 +1328,10 @@ private struct AppConfig: Decodable {
1186
1328
  let sourceBrowser: String?
1187
1329
  let targetBrowser: String?
1188
1330
  let imports: Imports?
1331
+ let rememberedImports: RememberedImports?
1189
1332
  let ui: UISettings?
1190
1333
  let browserless: BrowserlessSettings?
1334
+ let grokBot: GrokBotSettings?
1191
1335
 
1192
1336
  struct Schedule: Decodable {
1193
1337
  let hour: Int
@@ -1200,6 +1344,11 @@ private struct AppConfig: Decodable {
1200
1344
  let siteStorage: Bool?
1201
1345
  }
1202
1346
 
1347
+ struct RememberedImports: Decodable {
1348
+ let history: Bool?
1349
+ let siteStorage: Bool?
1350
+ }
1351
+
1203
1352
  struct UISettings: Decodable {
1204
1353
  let menuBar: Bool?
1205
1354
  let openAtLogin: Bool?
@@ -1213,6 +1362,10 @@ private struct AppConfig: Decodable {
1213
1362
  let region: String?
1214
1363
  let onlyDomains: [String]?
1215
1364
  }
1365
+
1366
+ struct GrokBotSettings: Decodable {
1367
+ let onlyDomains: [String]?
1368
+ }
1216
1369
  }
1217
1370
 
1218
1371
  private enum BrowserlessCredentialStore {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-cookie-bridge",
3
- "version": "1.4.6",
3
+ "version": "1.5.0",
4
4
  "description": "Local-first cookie and session transfer for macOS with optional Browserless upload",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "test": "node --test test/*.test.js",
11
11
  "test:web-worker": "node --test web/worker/*.test.mjs",
12
- "check": "node --check bin/brave-codex-cookie-sync.js && node --check src/app-installer.js && node --check src/browserless.js && node --check src/browserless-preflight.js && node --check src/browserless-runner.js && node --check src/broker.js && node --check src/chromium-reader.js && node --check src/cli.js && node --check src/codex-direct-import.js && node --check src/config.js && node --check src/paths.js && node --check src/scheduler.js && node --check src/updater.js && node --check scripts/build-dmg.js && node --check extension-template/background.js && node --check web/server.js && node --check web/script.js",
12
+ "check": "node --check bin/brave-codex-cookie-sync.js && node --check src/app-installer.js && node --check src/browserless.js && node --check src/browserless-preflight.js && node --check src/browserless-runner.js && node --check src/broker.js && node --check src/chromium-reader.js && node --check src/cli.js && node --check src/codex-direct-import.js && node --check src/config.js && node --check src/crc32.js && node --check src/grok-bot-export.js && node --check src/paths.js && node --check src/scheduler.js && node --check src/updater.js && node --check src/zip-store.js && node --check scripts/build-dmg.js && node --check extension-template/background.js && node --check web/server.js && node --check web/script.js",
13
13
  "release:check": "node scripts/check-release-version.js",
14
14
  "build:app": "node bin/brave-codex-cookie-sync.js install-app --no-open --replace-system-from-source",
15
15
  "build:dmg": "node scripts/build-dmg.js",
@@ -40,7 +40,8 @@
40
40
  "chromium",
41
41
  "brave",
42
42
  "browserless",
43
- "codex"
43
+ "codex",
44
+ "cursor"
44
45
  ],
45
46
  "funding": "https://ko-fi.com/apoorvdarshan",
46
47
  "files": [