browser-cookie-bridge 1.5.4 → 1.5.6

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,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Browser Cookie Bridge",
4
- "version": "1.5.4",
4
+ "version": "1.5.6",
5
5
  "description": "Transfers selected browser data locally between supported browsers and embedded app browsers.",
6
6
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5o6lDkXzU/23Jm2QRk4mfuxNnr3VlEIjRHcZr93Gdo7PI7asHJQhigaMGT6UOz0lkaTn1F+TGIPlsJo9GNBSsxbCfxWoBSXcFQFrPolY5LEfKaMtZQQ6eb4UAnp+bbUwb1den4On0piWU47Yzl6i0Jcdg4EG5H1kCSF/6nJsbKgF7OTaR/3drULu40yRvflzSXRhly3UDO7c5mgHJsaMwVV+VILsxbcPOoRINjuo3zDC737HFs67dpf38xF3tjSVL0HURAWKKobMvfABiDfVEm5O0Lvf+57d1Ib12QLT2qtVXNDy3iYwX+w2S0wH2vIMsfvoFPlRdmeqft70kZOqXwIDAQAB",
7
7
  "permissions": ["alarms", "cookies", "history"],
@@ -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.5.4</string>
14
- <key>CFBundleVersion</key><string>41</string>
13
+ <key>CFBundleShortVersionString</key><string>1.5.6</string>
14
+ <key>CFBundleVersion</key><string>43</string>
15
15
  <key>LSMinimumSystemVersion</key><string>13.5</string>
16
16
  <key>NSHighResolutionCapable</key><true/>
17
17
  </dict>
@@ -34,6 +34,7 @@ struct BraveCodexSyncApp: App {
34
34
  final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
35
35
  weak var model: SyncModel?
36
36
  private weak var mainWindow: NSWindow?
37
+ private var grokBotResultPanel: NSPanel?
37
38
  private var statusItem: NSStatusItem?
38
39
  private var syncMenuItem: NSMenuItem?
39
40
  private var updateMenuItem: NSMenuItem?
@@ -63,6 +64,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
63
64
  name: .syncStateChanged,
64
65
  object: nil
65
66
  )
67
+ NotificationCenter.default.addObserver(
68
+ self,
69
+ selector: #selector(showMainWindowNotification(_:)),
70
+ name: .showMainWindow,
71
+ object: nil
72
+ )
73
+ NotificationCenter.default.addObserver(
74
+ self,
75
+ selector: #selector(presentGrokBotResultNotification(_:)),
76
+ name: .presentGrokBotResult,
77
+ object: nil
78
+ )
66
79
  }
67
80
 
68
81
  func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { false }
@@ -186,8 +199,56 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
186
199
  }
187
200
  }
188
201
 
202
+ @objc private func showMainWindowNotification(_ notification: Notification) {
203
+ showMainWindow()
204
+ }
205
+
206
+ @objc private func presentGrokBotResultNotification(_ notification: Notification) {
207
+ guard let model else { return }
208
+ showMainWindow()
209
+ grokBotResultPanel?.close()
210
+ grokBotResultPanel = nil
211
+ DispatchQueue.main.async { [weak self] in
212
+ guard let self, let model = self.model else { return }
213
+ self.presentGrokBotResultPanel(model: model)
214
+ }
215
+ }
216
+
217
+ private func presentGrokBotResultPanel(model: SyncModel) {
218
+ let panel = NSPanel(
219
+ contentRect: NSRect(x: 0, y: 0, width: 520, height: 520),
220
+ styleMask: [.titled, .closable, .fullSizeContentView],
221
+ backing: .buffered,
222
+ defer: false
223
+ )
224
+ panel.title = "Grok Bot transfer ready"
225
+ panel.titlebarAppearsTransparent = false
226
+ panel.isFloatingPanel = true
227
+ panel.level = .modalPanel
228
+ panel.collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary]
229
+ panel.isReleasedWhenClosed = false
230
+ panel.center()
231
+ let dismissPanel = { [weak self] in
232
+ self?.grokBotResultPanel?.close()
233
+ self?.grokBotResultPanel = nil
234
+ }
235
+ panel.contentView = NSHostingView(
236
+ rootView: GrokBotResultSheet(onDone: dismissPanel).environmentObject(model)
237
+ )
238
+ panel.makeKeyAndOrderFront(nil)
239
+ NSApp.activate(ignoringOtherApps: true)
240
+ grokBotResultPanel = panel
241
+ }
242
+
189
243
  private func showMainWindow() {
190
244
  NSApp.setActivationPolicy(.regular)
245
+ if mainWindow == nil || mainWindow?.isVisible == false {
246
+ let candidate = NSApp.windows.first(where: { $0.canBecomeMain && $0.title == "Browser Cookie Bridge" })
247
+ ?? NSApp.windows.first(where: { $0.canBecomeMain })
248
+ ?? NSApp.windows.first
249
+ mainWindow = candidate
250
+ mainWindow?.delegate = self
251
+ }
191
252
  mainWindow?.makeKeyAndOrderFront(nil)
192
253
  NSApp.activate(ignoringOtherApps: true)
193
254
  }
@@ -241,9 +302,6 @@ struct ContentView: View {
241
302
  .sheet(isPresented: $model.showingBrowserlessSetup) {
242
303
  BrowserlessSetupSheet().environmentObject(model)
243
304
  }
244
- .sheet(isPresented: $model.showingGrokBotResult) {
245
- GrokBotResultSheet().environmentObject(model)
246
- }
247
305
  }
248
306
 
249
307
  private var header: some View {
@@ -1170,6 +1228,7 @@ enum Theme {
1170
1228
  struct GrokBotResultSheet: View {
1171
1229
  @EnvironmentObject private var model: SyncModel
1172
1230
  @Environment(\.dismiss) private var dismiss
1231
+ var onDone: (() -> Void)?
1173
1232
 
1174
1233
  var body: some View {
1175
1234
  VStack(alignment: .leading, spacing: 16) {
@@ -1228,7 +1287,10 @@ struct GrokBotResultSheet: View {
1228
1287
  NSPasteboard.general.setString(model.grokBotPrompt, forType: .string)
1229
1288
  }
1230
1289
  Spacer()
1231
- Button("Done") { dismiss() }
1290
+ Button("Done") {
1291
+ onDone?()
1292
+ dismiss()
1293
+ }
1232
1294
  .keyboardShortcut(.defaultAction)
1233
1295
  }
1234
1296
  .controlSize(.regular)
@@ -8,6 +8,8 @@ extension Notification.Name {
8
8
  static let nativeAlert = Notification.Name("BraveCodexSync.nativeAlert")
9
9
  static let updateStateChanged = Notification.Name("BraveCodexSync.updateStateChanged")
10
10
  static let syncStateChanged = Notification.Name("BraveCodexSync.syncStateChanged")
11
+ static let showMainWindow = Notification.Name("BraveCodexSync.showMainWindow")
12
+ static let presentGrokBotResult = Notification.Name("BraveCodexSync.presentGrokBotResult")
11
13
  }
12
14
 
13
15
  struct NativeAlert {
@@ -99,7 +101,6 @@ final class SyncModel: ObservableObject {
99
101
  @Published var browserlessOnlyDomains = ""
100
102
  @Published var grokBotOnlyDomains = ""
101
103
  @Published var showingBrowserlessSetup = false
102
- @Published var showingGrokBotResult = false
103
104
  @Published var grokBotPrompt = ""
104
105
  @Published var grokBotOutputPath = ""
105
106
  @Published var browserlessAssessment: BrowserlessProfileAssessment?
@@ -118,6 +119,16 @@ final class SyncModel: ObservableObject {
118
119
  private var appLoginAgent: URL { home.appending(path: "Library/LaunchAgents/com.apoorvdarshan.brave-codex-cookie-sync.app-login.plist") }
119
120
  private var endpointStatusTimer: Timer?
120
121
  private var updateTimer: Timer?
122
+ private var activeGrokBotOutputPath: String?
123
+
124
+ private static let grokBotFallbackPrompt = """
125
+ On your Grok Bot cloud computer only — do not access my local Mac and do not print cookie values.
126
+
127
+ 1. Save the attached GrokBot-Import.bcbx to the cloud computer.
128
+ 2. Unzip it: unzip -o GrokBot-Import.bcbx -d bcb-import && cd bcb-import
129
+ 3. Run: node import.mjs
130
+ 4. Report only how many cookies were imported per domain, then delete the bcb-import folder and any copies of the bundle.
131
+ """
121
132
  private var didCheckAfterLaunch = false
122
133
  private var didConsumeUpdateResult = false
123
134
  private var assessedBrowserID: String?
@@ -725,7 +736,10 @@ final class SyncModel: ObservableObject {
725
736
  var environment: [String: String] = [:]
726
737
  var arguments = ["sync", "--timeout", isBrowserlessTarget ? "900" : "300"]
727
738
  if let grokBotOutputPath {
739
+ activeGrokBotOutputPath = grokBotOutputPath
728
740
  arguments.append(contentsOf: ["--output", grokBotOutputPath])
741
+ } else {
742
+ activeGrokBotOutputPath = nil
729
743
  }
730
744
  if isBrowserlessTarget {
731
745
  guard let token = BrowserlessCredentialStore.read() else {
@@ -758,10 +772,14 @@ final class SyncModel: ObservableObject {
758
772
  self.secondaryStatus = "No cloud profile was changed; temporary profile data was removed"
759
773
  } else if success {
760
774
  self.state = partial ? .warning : .success
761
- if self.isGrokBotTarget, let result = self.parseGrokBotResult(from: output) {
762
- self.grokBotPrompt = result.prompt
763
- self.grokBotOutputPath = result.outputPath
764
- self.showingGrokBotResult = true
775
+ if self.isGrokBotTarget {
776
+ let parsed = self.parseGrokBotResult(from: output)
777
+ let outputPath = parsed?.outputPath ?? self.activeGrokBotOutputPath ?? grokBotOutputPath ?? self.grokBotOutputPath
778
+ let prompt = parsed?.prompt ?? Self.grokBotFallbackPrompt
779
+ if !outputPath.isEmpty {
780
+ self.presentGrokBotResultSheet(prompt: prompt, outputPath: outputPath)
781
+ }
782
+ self.activeGrokBotOutputPath = nil
765
783
  self.primaryStatus = partial ? "Grok Bot transfer created with warnings" : "Grok Bot transfer file ready"
766
784
  self.secondaryStatus = self.lastMeaningfulLine(output) ?? "Attach the .bcbx file to any Grok Bot and paste the prompt"
767
785
  } else {
@@ -1183,6 +1201,15 @@ final class SyncModel: ObservableObject {
1183
1201
  return try? JSONDecoder().decode(GrokBotResultPayload.self, from: data)
1184
1202
  }
1185
1203
 
1204
+ private func presentGrokBotResultSheet(prompt: String, outputPath: String) {
1205
+ grokBotPrompt = prompt
1206
+ grokBotOutputPath = outputPath
1207
+ NSPasteboard.general.clearContents()
1208
+ NSPasteboard.general.setString(prompt, forType: .string)
1209
+ NotificationCenter.default.post(name: .showMainWindow, object: nil)
1210
+ NotificationCenter.default.post(name: .presentGrokBotResult, object: nil)
1211
+ }
1212
+
1186
1213
  private func updateEndpointRunningStatus() {
1187
1214
  let wasDirectTargetRunning = directTargetRunning
1188
1215
  codexRunning = NSWorkspace.shared.runningApplications.contains {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-cookie-bridge",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Local-first cookie and session transfer for macOS with optional Browserless upload",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -13,6 +13,7 @@ import { isChromiumBrowserRunning, readChromiumProfile } from "./chromium-reader
13
13
  import { uploadBrowserlessProfile } from "./browserless.js";
14
14
  import { inspectBrowserlessProfile } from "./browserless-preflight.js";
15
15
  import {
16
+ formatGrokBotResultLine,
16
17
  grokBotSummary,
17
18
  writeGrokBotBundle,
18
19
  } from "./grok-bot-export.js";
@@ -310,13 +311,10 @@ async function sync(args, { signal } = {}) {
310
311
  });
311
312
  result.sourceCookieSkipped = payload.cookieStats.skipped;
312
313
  console.log(grokBotSummary(result));
313
- console.log(`BCB_GROK_RESULT ${JSON.stringify({
314
+ console.log(formatGrokBotResultLine({
314
315
  outputPath: result.outputPath,
315
- cookieCount: result.cookieCount,
316
- domainCount: result.domainCount,
317
- domains: result.domains,
318
316
  prompt: result.prompt,
319
- })}`);
317
+ }));
320
318
  return result;
321
319
  }
322
320
  if (target === "browserless") {
@@ -157,6 +157,11 @@ export function grokBotSummary(result) {
157
157
  return `Grok Bot transfer file created: ${result.cookieCount} cookies across ${domainNote} from ${result.sourceBrowser}.${skipped} Attach ${path.basename(result.outputPath)} to any Grok Bot and paste the prompt. The bundle includes the decryption key—treat the file as credentials and do not share it.`;
158
158
  }
159
159
 
160
+ /** Machine-readable sync result for the macOS app (keep small — no domain lists). */
161
+ export function formatGrokBotResultLine({ outputPath, prompt }) {
162
+ return `BCB_GROK_RESULT ${JSON.stringify({ outputPath, prompt })}`;
163
+ }
164
+
160
165
  export function filterCookies(cookies, onlyDomains = []) {
161
166
  const domains = normalizeDomainFilters(onlyDomains);
162
167
  if (!domains.length) return cookies;