browser-cookie-bridge 1.3.0 → 1.4.1
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 +24 -6
- package/extension-template/manifest.json +1 -1
- package/macos-app/Info.plist +2 -2
- package/macos-app/Sources/BraveCodexSyncApp/BraveCodexSyncApp.swift +12 -0
- package/macos-app/Sources/BraveCodexSyncApp/SyncModel.swift +124 -3
- package/package.json +2 -1
- package/src/cli.js +4 -2
- package/src/config.js +3 -0
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<img src="https://img.shields.io/badge/local--first-cloud%20optional-C68B3C" alt="Local-first, cloud optional" />
|
|
17
17
|
<img src="https://img.shields.io/github/stars/apoorvdarshan/browser-cookie-bridge?logo=github&color=C68B3C" alt="GitHub stars" />
|
|
18
18
|
<a href="https://www.producthunt.com/products/browser-cookie-bridge"><img src="https://img.shields.io/badge/Product%20Hunt-View%20launch-DA552F?logo=producthunt&logoColor=white" alt="Browser Cookie Bridge on Product Hunt" /></a>
|
|
19
|
+
<a href="https://tally.so/r/J9j0D4"><img src="https://img.shields.io/badge/Windows-Join%20waitlist-8E2735?logo=windows&logoColor=white" alt="Join the Browser Cookie Bridge Windows waitlist" /></a>
|
|
19
20
|
<img src="https://img.shields.io/badge/license-MIT-3DA639" alt="MIT License" />
|
|
20
21
|
</p>
|
|
21
22
|
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
<a href="CONTRIBUTING.md">Contribute</a> ·
|
|
29
30
|
<a href="https://github.com/apoorvdarshan/browser-cookie-bridge/issues/new?template=bug_report.yml">Report a bug</a> ·
|
|
30
31
|
<a href="https://www.producthunt.com/products/browser-cookie-bridge">Product Hunt</a> ·
|
|
32
|
+
<a href="https://tally.so/r/J9j0D4">Windows waitlist</a> ·
|
|
31
33
|
<a href="#support">Support</a>
|
|
32
34
|
</p>
|
|
33
35
|
|
|
@@ -44,7 +46,7 @@
|
|
|
44
46
|
|
|
45
47
|
---
|
|
46
48
|
|
|
47
|
-
> **Version 1.
|
|
49
|
+
> **Version 1.4.0 adds an optional one-click Codex restart flow.** When enabled for a manual sync, Browser Cookie Bridge force quits Codex, waits for its database to close, performs the verified local transfer, preserves the imported/skipped/failed counts, and reopens Codex only after success. The option is off by default.
|
|
48
50
|
|
|
49
51
|
## Why Browser Cookie Bridge
|
|
50
52
|
|
|
@@ -52,11 +54,16 @@ Signing into the same sites across several browsers is repetitive. Export files
|
|
|
52
54
|
|
|
53
55
|
Browser Cookie Bridge gives those browser profiles a small, native control panel. Local browser and Codex transfers stay on the Mac. If you explicitly select Browserless Cloud and click Upload, the app can instead send authenticated profile state to your own Browserless account.
|
|
54
56
|
|
|
57
|
+
## Windows waitlist
|
|
58
|
+
|
|
59
|
+
Browser Cookie Bridge currently ships for macOS. A separate Windows app is being explored, with demand guiding whether it moves into active development. If you use Windows, [join the early-access waitlist](https://tally.so/r/J9j0D4) and tell us which browsers and transfer paths matter to you.
|
|
60
|
+
|
|
55
61
|
## Features
|
|
56
62
|
|
|
57
63
|
- 🍪 **Cookie and session transfer** — cookies are enabled by default, including supported domain, path, expiry, security, `SameSite`, and partition attributes.
|
|
58
64
|
- 🌐 **Seven Chromium browsers** — Brave, Chrome, Edge, Arc, Vivaldi, Opera, and Perplexity Comet can be sources or destinations.
|
|
59
65
|
- ✨ **ChatGPT Codex import** — merge selected local browser data into Codex's built-in browser; Codex is destination-only.
|
|
66
|
+
- 🔁 **Optional Codex restart** — manual Sync can force quit a running Codex instance and reopen it only after a successful local transfer; off by default.
|
|
60
67
|
- ☁️ **Optional Browserless upload** — create or refresh a Browserless authenticated profile with cookies, local storage, and IndexedDB; see a local size preflight, live progress, cancellation, and post-upload verification.
|
|
61
68
|
- 🕘 **Background automation** — sync when you sign in, at a fixed daily time, or whenever you choose.
|
|
62
69
|
- ◉ **Native menu-bar app** — closing the window removes the Dock icon while the helper continues running.
|
|
@@ -91,7 +98,7 @@ The DMG is self-contained. Node.js 24+ and Xcode Command Line Tools are required
|
|
|
91
98
|
- **[Apple silicon DMG](https://github.com/apoorvdarshan/browser-cookie-bridge/releases/latest/download/Browser-Cookie-Bridge-arm64.dmg)** — M1, M2, M3, M4, and newer
|
|
92
99
|
- **[Intel DMG](https://github.com/apoorvdarshan/browser-cookie-bridge/releases/latest/download/Browser-Cookie-Bridge-x64.dmg)** — Intel-based Macs
|
|
93
100
|
|
|
94
|
-
Open the DMG, drag **Browser Cookie Bridge** onto **Applications**, then open it from Applications.
|
|
101
|
+
Open the DMG, drag **Browser Cookie Bridge** onto **Applications**, then open it from Applications. Current DMGs are checksum-verified, Developer ID-signed, and Apple-notarized for a standard macOS first-launch experience. The app does not require Node.js, Xcode, Terminal, or an administrator password.
|
|
95
102
|
|
|
96
103
|
### Via npm
|
|
97
104
|
|
|
@@ -125,11 +132,11 @@ The final command compiles the native SwiftUI app, enables **Open at login**, **
|
|
|
125
132
|
No browser extension is needed for this path.
|
|
126
133
|
|
|
127
134
|
1. Select a source browser and **ChatGPT Codex** as the destination.
|
|
128
|
-
2. Quit Codex completely
|
|
135
|
+
2. Quit Codex completely, or enable **Restart Codex automatically**. Closing only its browser panel is not enough.
|
|
129
136
|
3. Choose **Cookies** and, optionally, **History URLs**.
|
|
130
|
-
4. Press **Sync now** and reopen Codex after the success message.
|
|
137
|
+
4. Press **Sync now** and reopen Codex after the success message if automatic restart is off.
|
|
131
138
|
|
|
132
|
-
|
|
139
|
+
By default, an open Codex blocks the transfer. The optional **Restart Codex automatically** setting applies only to a manual Sync: it force quits Codex, waits for its database to close, and reopens Codex only after the transfer succeeds. Scheduled and login syncs never force quit it.
|
|
133
140
|
|
|
134
141
|
### Browser → browser
|
|
135
142
|
|
|
@@ -171,6 +178,7 @@ The official CLI records the Browserless upload-disclaimer acceptance timestamp
|
|
|
171
178
|
| **Sync at login** | Runs once whenever you sign in; on by default |
|
|
172
179
|
| **Open at login** | Starts the background app after macOS login; on by default |
|
|
173
180
|
| **Show in menu bar** | Keeps sync, status, updates, and support actions close at hand |
|
|
181
|
+
| **Restart Codex automatically** | Force quits Codex for a manual sync and reopens it only after success; off by default |
|
|
174
182
|
| **Check for updates** | Finds a newer GitHub release, verifies its DMG, and offers install + relaunch |
|
|
175
183
|
|
|
176
184
|
Automation uses the saved source, destination, and data choices for local transfers. A scheduled Codex sync safely exits without making changes when Codex is open. Browserless cloud uploads are always manual and require an explicit upload action.
|
|
@@ -180,7 +188,7 @@ Automation uses the saved source, destination, and data choices for local transf
|
|
|
180
188
|
```bash
|
|
181
189
|
browser-cookie-bridge install-app [--no-open]
|
|
182
190
|
browser-cookie-bridge setup [--hour 9] [--minute 0] [--no-schedule]
|
|
183
|
-
browser-cookie-bridge preferences --source brave --target codex --cookies on --history off
|
|
191
|
+
browser-cookie-bridge preferences --source brave --target codex --cookies on --history off --auto-restart-codex off
|
|
184
192
|
browser-cookie-bridge sync [--timeout 300] [--allow-cloud-upload]
|
|
185
193
|
browser-cookie-bridge browserless-preflight
|
|
186
194
|
browser-cookie-bridge doctor
|
|
@@ -255,6 +263,16 @@ The product landing page, documentation overview, Privacy Policy, and Terms are
|
|
|
255
263
|
npm run web
|
|
256
264
|
```
|
|
257
265
|
|
|
266
|
+
## Star History
|
|
267
|
+
|
|
268
|
+
<a href="https://github.com/apoorvdarshan/browser-cookie-bridge/stargazers">
|
|
269
|
+
<picture>
|
|
270
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://cookiebridge.apoorvdarshan.com/api/star-history.svg?theme=dark&v=1" />
|
|
271
|
+
<source media="(prefers-color-scheme: light)" srcset="https://cookiebridge.apoorvdarshan.com/api/star-history.svg?theme=light&v=1" />
|
|
272
|
+
<img alt="Browser Cookie Bridge GitHub star history" src="https://cookiebridge.apoorvdarshan.com/api/star-history.svg?theme=light&v=1" />
|
|
273
|
+
</picture>
|
|
274
|
+
</a>
|
|
275
|
+
|
|
258
276
|
## License
|
|
259
277
|
|
|
260
278
|
[MIT](LICENSE) © 2026 [Apoorv Darshan](https://github.com/apoorvdarshan)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Browser Cookie Bridge",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.1",
|
|
5
5
|
"description": "Transfers selected browser data locally between supported browsers and into ChatGPT Codex.",
|
|
6
6
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5o6lDkXzU/23Jm2QRk4mfuxNnr3VlEIjRHcZr93Gdo7PI7asHJQhigaMGT6UOz0lkaTn1F+TGIPlsJo9GNBSsxbCfxWoBSXcFQFrPolY5LEfKaMtZQQ6eb4UAnp+bbUwb1den4On0piWU47Yzl6i0Jcdg4EG5H1kCSF/6nJsbKgF7OTaR/3drULu40yRvflzSXRhly3UDO7c5mgHJsaMwVV+VILsxbcPOoRINjuo3zDC737HFs67dpf38xF3tjSVL0HURAWKKobMvfABiDfVEm5O0Lvf+57d1Ib12QLT2qtVXNDy3iYwX+w2S0wH2vIMsfvoFPlRdmeqft70kZOqXwIDAQAB",
|
|
7
7
|
"permissions": ["alarms", "cookies", "history"],
|
package/macos-app/Info.plist
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
|
11
11
|
<key>CFBundleName</key><string>Browser Cookie Bridge</string>
|
|
12
12
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
13
|
-
<key>CFBundleShortVersionString</key><string>1.
|
|
14
|
-
<key>CFBundleVersion</key><string>
|
|
13
|
+
<key>CFBundleShortVersionString</key><string>1.4.1</string>
|
|
14
|
+
<key>CFBundleVersion</key><string>29</string>
|
|
15
15
|
<key>LSMinimumSystemVersion</key><string>13.5</string>
|
|
16
16
|
<key>NSHighResolutionCapable</key><true/>
|
|
17
17
|
</dict>
|
|
@@ -578,6 +578,18 @@ struct PreferencesPanel: View {
|
|
|
578
578
|
PreferenceRow(icon: "key.slash", color: .secondary, title: "Passwords", detail: "Browser extensions cannot access passwords", muted: true) {
|
|
579
579
|
FixedBadge("Unavailable")
|
|
580
580
|
}
|
|
581
|
+
if model.selectedTargetID == "codex" {
|
|
582
|
+
RowDivider()
|
|
583
|
+
PreferenceRow(
|
|
584
|
+
icon: "arrow.clockwise.circle",
|
|
585
|
+
color: Theme.accent,
|
|
586
|
+
title: "Restart Codex automatically",
|
|
587
|
+
detail: "Force quit before manual sync; reopen only after success"
|
|
588
|
+
) {
|
|
589
|
+
Toggle("", isOn: Binding(get: { model.autoRestartCodex }, set: { model.setAutoRestartCodex($0) }))
|
|
590
|
+
.labelsHidden().toggleStyle(.switch).tint(Theme.active).disabled(model.isWorking || model.isSyncing)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
581
593
|
}
|
|
582
594
|
|
|
583
595
|
SectionLabel(title: "Automation", detail: "Runs in the background", separated: true)
|
|
@@ -77,6 +77,7 @@ final class SyncModel: ObservableObject {
|
|
|
77
77
|
@Published var openAtLogin = false
|
|
78
78
|
@Published var menuBarEnabled = true
|
|
79
79
|
@Published var autoCheckUpdates = true
|
|
80
|
+
@Published var autoRestartCodex = false
|
|
80
81
|
@Published var isCheckingForUpdates = false
|
|
81
82
|
@Published var isInstallingUpdate = false
|
|
82
83
|
@Published var availableUpdateVersion: String?
|
|
@@ -128,7 +129,7 @@ final class SyncModel: ObservableObject {
|
|
|
128
129
|
var targetName: String {
|
|
129
130
|
isBrowserlessTarget ? "Browserless Cloud" : selectedTargetBrowser?.name ?? "ChatGPT Codex"
|
|
130
131
|
}
|
|
131
|
-
var codexBlocked: Bool { selectedTargetID == "codex" && codexRunning }
|
|
132
|
+
var codexBlocked: Bool { selectedTargetID == "codex" && codexRunning && !autoRestartCodex }
|
|
132
133
|
var browserlessBlocked: Bool {
|
|
133
134
|
isBrowserlessTarget && (!browserlessConfigured || sourceBrowserRunning || selectedSourceID == "comet")
|
|
134
135
|
}
|
|
@@ -261,6 +262,7 @@ final class SyncModel: ObservableObject {
|
|
|
261
262
|
historyEnabled = config.imports?.history ?? false
|
|
262
263
|
menuBarEnabled = config.ui?.menuBar ?? true
|
|
263
264
|
autoCheckUpdates = config.ui?.autoCheckUpdates ?? true
|
|
265
|
+
autoRestartCodex = config.ui?.autoRestartCodex ?? false
|
|
264
266
|
browserlessProfileName = config.browserless?.profileName ?? "browser-cookie-bridge"
|
|
265
267
|
browserlessRegion = config.browserless?.region ?? "sfo"
|
|
266
268
|
browserlessOnlyDomains = (config.browserless?.onlyDomains ?? []).joined(separator: ", ")
|
|
@@ -347,6 +349,11 @@ final class SyncModel: ObservableObject {
|
|
|
347
349
|
if enabled { checkForUpdates() }
|
|
348
350
|
}
|
|
349
351
|
|
|
352
|
+
func setAutoRestartCodex(_ enabled: Bool) {
|
|
353
|
+
autoRestartCodex = enabled
|
|
354
|
+
persistPreferences(successMessage: enabled ? "Automatic Codex restart enabled" : "Automatic Codex restart disabled")
|
|
355
|
+
}
|
|
356
|
+
|
|
350
357
|
func checkForUpdates(showAlert: Bool = false) {
|
|
351
358
|
guard !isCheckingForUpdates, !isInstallingUpdate else { return }
|
|
352
359
|
guard let url = URL(string: "https://api.github.com/repos/apoorvdarshan/browser-cookie-bridge/releases/latest") else { return }
|
|
@@ -449,6 +456,68 @@ final class SyncModel: ObservableObject {
|
|
|
449
456
|
}
|
|
450
457
|
return
|
|
451
458
|
}
|
|
459
|
+
if selectedTargetID == "codex" && codexRunning && autoRestartCodex {
|
|
460
|
+
forceQuitCodexThenSync(showMenuBarAlert: showMenuBarAlert)
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
startSync(showMenuBarAlert: showMenuBarAlert, reopenCodexOnSuccess: false)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private func forceQuitCodexThenSync(showMenuBarAlert: Bool) {
|
|
467
|
+
isSyncing = true
|
|
468
|
+
uploadCanceling = false
|
|
469
|
+
state = .syncing
|
|
470
|
+
primaryStatus = "Closing Codex for sync"
|
|
471
|
+
secondaryStatus = "Force quitting ChatGPT Codex and waiting for its browser database to close…"
|
|
472
|
+
let applications = NSWorkspace.shared.runningApplications.filter {
|
|
473
|
+
$0.bundleIdentifier == "com.openai.codex"
|
|
474
|
+
}
|
|
475
|
+
guard !applications.isEmpty, applications.allSatisfy({ $0.forceTerminate() }) else {
|
|
476
|
+
finishCodexPreparationFailure(
|
|
477
|
+
message: "macOS could not force quit ChatGPT Codex. Quit it manually, then try again.",
|
|
478
|
+
showMenuBarAlert: showMenuBarAlert
|
|
479
|
+
)
|
|
480
|
+
return
|
|
481
|
+
}
|
|
482
|
+
waitForCodexToQuit(attemptsRemaining: 50, showMenuBarAlert: showMenuBarAlert)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private func waitForCodexToQuit(attemptsRemaining: Int, showMenuBarAlert: Bool) {
|
|
486
|
+
let stillRunning = NSWorkspace.shared.runningApplications.contains {
|
|
487
|
+
$0.bundleIdentifier == "com.openai.codex"
|
|
488
|
+
}
|
|
489
|
+
if !stillRunning {
|
|
490
|
+
codexRunning = false
|
|
491
|
+
secondaryStatus = "Codex is closed — waiting briefly for its database to be released…"
|
|
492
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { [weak self] in
|
|
493
|
+
self?.startSync(showMenuBarAlert: showMenuBarAlert, reopenCodexOnSuccess: true)
|
|
494
|
+
}
|
|
495
|
+
return
|
|
496
|
+
}
|
|
497
|
+
guard attemptsRemaining > 0 else {
|
|
498
|
+
finishCodexPreparationFailure(
|
|
499
|
+
message: "ChatGPT Codex did not close within 10 seconds. Quit it manually, then try again.",
|
|
500
|
+
showMenuBarAlert: showMenuBarAlert
|
|
501
|
+
)
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
|
505
|
+
self?.waitForCodexToQuit(attemptsRemaining: attemptsRemaining - 1, showMenuBarAlert: showMenuBarAlert)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private func finishCodexPreparationFailure(message: String, showMenuBarAlert: Bool) {
|
|
510
|
+
isSyncing = false
|
|
511
|
+
state = .error
|
|
512
|
+
primaryStatus = "Could not close Codex"
|
|
513
|
+
secondaryStatus = message
|
|
514
|
+
updateEndpointRunningStatus()
|
|
515
|
+
if showMenuBarAlert {
|
|
516
|
+
postNativeAlert(title: primaryStatus, message: secondaryStatus, kind: .error)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private func startSync(showMenuBarAlert: Bool, reopenCodexOnSuccess: Bool) {
|
|
452
521
|
isSyncing = true
|
|
453
522
|
uploadCanceling = false
|
|
454
523
|
state = .syncing
|
|
@@ -507,7 +576,13 @@ final class SyncModel: ObservableObject {
|
|
|
507
576
|
: "Keep both browsers open and check the extensions")
|
|
508
577
|
}
|
|
509
578
|
self.updateEndpointRunningStatus()
|
|
510
|
-
if
|
|
579
|
+
if success && reopenCodexOnSuccess {
|
|
580
|
+
self.reopenCodexAfterSuccessfulSync(
|
|
581
|
+
partial: partial,
|
|
582
|
+
syncSummary: self.secondaryStatus,
|
|
583
|
+
showMenuBarAlert: showMenuBarAlert
|
|
584
|
+
)
|
|
585
|
+
} else if showMenuBarAlert {
|
|
511
586
|
self.postNativeAlert(
|
|
512
587
|
title: self.primaryStatus,
|
|
513
588
|
message: self.secondaryStatus,
|
|
@@ -517,6 +592,44 @@ final class SyncModel: ObservableObject {
|
|
|
517
592
|
}
|
|
518
593
|
}
|
|
519
594
|
|
|
595
|
+
private func reopenCodexAfterSuccessfulSync(partial: Bool, syncSummary: String, showMenuBarAlert: Bool) {
|
|
596
|
+
let transferResult = syncSummary.replacingOccurrences(
|
|
597
|
+
of: "Reopen Codex to use the updated sessions. ",
|
|
598
|
+
with: ""
|
|
599
|
+
)
|
|
600
|
+
guard let codexURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.openai.codex") else {
|
|
601
|
+
state = .warning
|
|
602
|
+
primaryStatus = "Codex sessions updated, but Codex was not reopened"
|
|
603
|
+
secondaryStatus = "\(transferResult)\n\nCodex could not be found. Open it manually to use the updated sessions."
|
|
604
|
+
if showMenuBarAlert {
|
|
605
|
+
postNativeAlert(title: primaryStatus, message: secondaryStatus, kind: .warning)
|
|
606
|
+
}
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
NSWorkspace.shared.openApplication(at: codexURL, configuration: NSWorkspace.OpenConfiguration()) { [weak self] _, error in
|
|
610
|
+
Task { @MainActor in
|
|
611
|
+
guard let self else { return }
|
|
612
|
+
if let error {
|
|
613
|
+
self.state = .warning
|
|
614
|
+
self.primaryStatus = "Codex sessions updated, but Codex was not reopened"
|
|
615
|
+
self.secondaryStatus = "\(transferResult)\n\nCodex could not be reopened: \(error.localizedDescription)"
|
|
616
|
+
} else {
|
|
617
|
+
self.codexRunning = true
|
|
618
|
+
self.state = partial ? .warning : .success
|
|
619
|
+
self.primaryStatus = partial ? "Codex sync completed with warnings" : "Codex sessions updated"
|
|
620
|
+
self.secondaryStatus = "\(transferResult)\n\nChatGPT Codex reopened successfully."
|
|
621
|
+
}
|
|
622
|
+
if showMenuBarAlert {
|
|
623
|
+
self.postNativeAlert(
|
|
624
|
+
title: self.primaryStatus,
|
|
625
|
+
message: self.secondaryStatus,
|
|
626
|
+
kind: self.state == .success ? .information : .warning
|
|
627
|
+
)
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
520
633
|
func cancelSync() {
|
|
521
634
|
guard isBrowserlessTarget, isSyncing, !uploadCanceling else { return }
|
|
522
635
|
uploadCanceling = true
|
|
@@ -627,6 +740,7 @@ final class SyncModel: ObservableObject {
|
|
|
627
740
|
"--history", historyEnabled ? "on" : "off",
|
|
628
741
|
"--menu-bar", menuBarEnabled ? "on" : "off",
|
|
629
742
|
"--auto-check-updates", autoCheckUpdates ? "on" : "off",
|
|
743
|
+
"--auto-restart-codex", autoRestartCodex ? "on" : "off",
|
|
630
744
|
"--browserless-profile", browserlessProfileName,
|
|
631
745
|
"--browserless-region", browserlessRegion,
|
|
632
746
|
"--browserless-domains", browserlessOnlyDomains,
|
|
@@ -827,7 +941,13 @@ final class SyncModel: ObservableObject {
|
|
|
827
941
|
$0.bundleIdentifier == selectedBrowser.bundleIdentifier
|
|
828
942
|
}
|
|
829
943
|
guard !isSyncing else { return }
|
|
830
|
-
if selectedTargetID == "codex" && codexRunning {
|
|
944
|
+
if selectedTargetID == "codex" && codexRunning && autoRestartCodex {
|
|
945
|
+
if state == .ready || primaryStatus == "Quit Codex before syncing" {
|
|
946
|
+
state = .ready
|
|
947
|
+
primaryStatus = "Ready to sync and restart Codex"
|
|
948
|
+
secondaryStatus = "Sync will force quit Codex and reopen it only after a successful transfer"
|
|
949
|
+
}
|
|
950
|
+
} else if selectedTargetID == "codex" && codexRunning {
|
|
831
951
|
state = .warning
|
|
832
952
|
primaryStatus = "Quit Codex before syncing"
|
|
833
953
|
secondaryStatus = "Close ChatGPT Codex completely so its local cookie database can be updated safely"
|
|
@@ -948,6 +1068,7 @@ private struct AppConfig: Decodable {
|
|
|
948
1068
|
let menuBar: Bool?
|
|
949
1069
|
let openAtLogin: Bool?
|
|
950
1070
|
let autoCheckUpdates: Bool?
|
|
1071
|
+
let autoRestartCodex: Bool?
|
|
951
1072
|
}
|
|
952
1073
|
|
|
953
1074
|
struct BrowserlessSettings: Decodable {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-cookie-bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Local-first cookie and session transfer for macOS with optional Browserless upload",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "node --test test/*.test.js",
|
|
11
|
+
"test:web-worker": "node --test web/worker/*.test.mjs",
|
|
11
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
13
|
"release:check": "node scripts/check-release-version.js",
|
|
13
14
|
"build:app": "node bin/brave-codex-cookie-sync.js install-app --no-open",
|
package/src/cli.js
CHANGED
|
@@ -45,7 +45,7 @@ Commands:
|
|
|
45
45
|
setup [--hour 9] [--minute 0] [--no-schedule]
|
|
46
46
|
install-app [--no-open]
|
|
47
47
|
bootstrap-bundled --app-path /Applications/Browser Cookie Bridge.app
|
|
48
|
-
preferences --source brave --target codex --cookies on --history off --menu-bar on --auto-check-updates on
|
|
48
|
+
preferences --source brave --target codex --cookies on --history off --menu-bar on --auto-check-updates on --auto-restart-codex off
|
|
49
49
|
sync [--timeout 300] [--allow-cloud-upload]
|
|
50
50
|
browserless-preflight
|
|
51
51
|
doctor
|
|
@@ -128,12 +128,13 @@ function preferences(args) {
|
|
|
128
128
|
menuBar: booleanFlag(args, "--menu-bar", existing.ui?.menuBar === true),
|
|
129
129
|
openAtLogin: booleanFlag(args, "--open-at-login", existing.ui?.openAtLogin !== false),
|
|
130
130
|
autoCheckUpdates: booleanFlag(args, "--auto-check-updates", existing.ui?.autoCheckUpdates !== false),
|
|
131
|
+
autoRestartCodex: booleanFlag(args, "--auto-restart-codex", existing.ui?.autoRestartCodex === true),
|
|
131
132
|
browserlessProfileName: stringFlag(args, "--browserless-profile", existing.browserless?.profileName || "browser-cookie-bridge"),
|
|
132
133
|
browserlessRegion: stringFlag(args, "--browserless-region", existing.browserless?.region || "sfo"),
|
|
133
134
|
browserlessOnlyDomains: optionalStringFlag(args, "--browserless-domains", (existing.browserless?.onlyDomains || []).join(",")),
|
|
134
135
|
});
|
|
135
136
|
console.log(
|
|
136
|
-
`Saved: source=${config.sourceBrowser}, target=${config.targetBrowser}, cookies=${config.imports.cookies ? "on" : "off"}, history=${config.imports.history ? "on" : "off"}, menu-bar=${config.ui.menuBar ? "on" : "off"}, open-at-login=${config.ui.openAtLogin ? "on" : "off"}, auto-check-updates=${config.ui.autoCheckUpdates ? "on" : "off"}`,
|
|
137
|
+
`Saved: source=${config.sourceBrowser}, target=${config.targetBrowser}, cookies=${config.imports.cookies ? "on" : "off"}, history=${config.imports.history ? "on" : "off"}, menu-bar=${config.ui.menuBar ? "on" : "off"}, open-at-login=${config.ui.openAtLogin ? "on" : "off"}, auto-check-updates=${config.ui.autoCheckUpdates ? "on" : "off"}, auto-restart-codex=${config.ui.autoRestartCodex ? "on" : "off"}`,
|
|
137
138
|
);
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -435,6 +436,7 @@ function setAppLogin(enabled) {
|
|
|
435
436
|
menuBar: existing.ui?.menuBar === true,
|
|
436
437
|
openAtLogin: enabled,
|
|
437
438
|
autoCheckUpdates: existing.ui?.autoCheckUpdates !== false,
|
|
439
|
+
autoRestartCodex: existing.ui?.autoRestartCodex === true,
|
|
438
440
|
browserlessProfileName: existing.browserless?.profileName,
|
|
439
441
|
browserlessRegion: existing.browserless?.region,
|
|
440
442
|
browserlessOnlyDomains: existing.browserless?.onlyDomains,
|
package/src/config.js
CHANGED
|
@@ -50,6 +50,7 @@ export function installConfig({ home, hour = 9, minute = 0, nodePath = process.e
|
|
|
50
50
|
menuBar: existing.ui?.menuBar !== false,
|
|
51
51
|
openAtLogin: existing.ui?.openAtLogin !== false,
|
|
52
52
|
autoCheckUpdates: existing.ui?.autoCheckUpdates !== false,
|
|
53
|
+
autoRestartCodex: existing.ui?.autoRestartCodex === true,
|
|
53
54
|
},
|
|
54
55
|
browserless: {
|
|
55
56
|
profileName: cleanProfileName(existing.browserless?.profileName) || "browser-cookie-bridge",
|
|
@@ -77,6 +78,7 @@ export function updatePreferences({
|
|
|
77
78
|
menuBar,
|
|
78
79
|
openAtLogin,
|
|
79
80
|
autoCheckUpdates,
|
|
81
|
+
autoRestartCodex,
|
|
80
82
|
browserlessProfileName,
|
|
81
83
|
browserlessRegion,
|
|
82
84
|
browserlessOnlyDomains,
|
|
@@ -98,6 +100,7 @@ export function updatePreferences({
|
|
|
98
100
|
menuBar: Boolean(menuBar),
|
|
99
101
|
openAtLogin: Boolean(openAtLogin),
|
|
100
102
|
autoCheckUpdates: Boolean(autoCheckUpdates),
|
|
103
|
+
autoRestartCodex: Boolean(autoRestartCodex),
|
|
101
104
|
};
|
|
102
105
|
const region = browserlessRegion ?? config.browserless?.region ?? "sfo";
|
|
103
106
|
if (!["sfo", "lon", "ams"].includes(region)) throw new Error("Browserless region must be sfo, lon, or ams");
|