browser-cookie-bridge 1.4.7 → 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.
- package/README.md +55 -27
- package/extension-template/manifest.json +2 -2
- package/macos-app/Info.plist +2 -2
- package/macos-app/Resources/BrowserIcons/grok-bot.png +0 -0
- package/macos-app/Sources/BraveCodexSyncApp/BraveCodexSyncApp.swift +157 -17
- package/macos-app/Sources/BraveCodexSyncApp/SyncModel.swift +183 -30
- package/package.json +4 -3
- package/src/cli.js +110 -23
- package/src/codex-direct-import.js +265 -104
- package/src/config.js +34 -4
- package/src/crc32.js +19 -0
- package/src/grok-bot-export.js +199 -0
- package/src/grok-bot-importer.mjs +178 -0
- package/src/paths.js +6 -1
- package/src/zip-store.js +92 -0
|
@@ -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
|
|
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
|
|
136
|
-
selectedTargetID == "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
273
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
620
|
-
|
|
621
|
-
? "
|
|
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.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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.
|
|
668
|
-
? "Quit
|
|
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
|
-
|
|
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
|
|
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
|
|
1217
|
+
} else if isDirectTarget && directTargetRunning {
|
|
1086
1218
|
state = .warning
|
|
1087
|
-
primaryStatus = "Quit
|
|
1088
|
-
secondaryStatus = "Close
|
|
1089
|
-
} else if
|
|
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 = "
|
|
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.
|
|
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": [
|