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/src/cli.js CHANGED
@@ -5,18 +5,24 @@ import { installApp } from "./app-installer.js";
5
5
  import { createBroker } from "./broker.js";
6
6
  import {
7
7
  CODEX_RUNNING_ERROR,
8
- directImportToCodex,
9
- isCodexRunning,
8
+ CURSOR_RUNNING_ERROR,
9
+ directImportToEmbeddedBrowser,
10
+ isDirectTargetRunning,
10
11
  } from "./codex-direct-import.js";
11
12
  import { isChromiumBrowserRunning, readChromiumProfile } from "./chromium-reader.js";
12
13
  import { uploadBrowserlessProfile } from "./browserless.js";
13
14
  import { inspectBrowserlessProfile } from "./browserless-preflight.js";
15
+ import {
16
+ grokBotSummary,
17
+ writeGrokBotBundle,
18
+ } from "./grok-bot-export.js";
14
19
  import { installConfig, installRuntime, readConfig, updatePreferences } from "./config.js";
15
20
  import {
16
21
  braveCookiePaths,
17
22
  appLoginLaunchAgentPath,
18
23
  codexCookiePaths,
19
24
  configPath,
25
+ cursorCookiePaths,
20
26
  installedAppPath,
21
27
  installedExtensionDir,
22
28
  launchAgentPath,
@@ -40,14 +46,14 @@ import { performUpdate, startDetachedUpdate } from "./updater.js";
40
46
 
41
47
  const HELP = `browser-cookie-bridge
42
48
 
43
- Local cookie and session transfer between Chromium browsers and into ChatGPT Codex.
49
+ Local cookie and session transfer between Chromium browsers and embedded app browsers.
44
50
 
45
51
  Commands:
46
52
  setup [--hour 9] [--minute 0] [--no-schedule]
47
53
  install-app [--no-open] [--replace-system-from-source]
48
54
  bootstrap-bundled --app-path /Applications/Browser Cookie Bridge.app
49
55
  preferences --source brave --target codex --cookies on --history off --site-storage off --menu-bar on --auto-check-updates on --auto-restart-codex off --auto-restart-both off
50
- sync [--timeout 300] [--allow-cloud-upload]
56
+ sync [--timeout 300] [--allow-cloud-upload] [--output /path/GrokBot-Import.bcbx]
51
57
  browserless-preflight
52
58
  doctor
53
59
  enable-login-sync
@@ -135,6 +141,7 @@ function preferences(args) {
135
141
  browserlessProfileName: stringFlag(args, "--browserless-profile", existing.browserless?.profileName || "browser-cookie-bridge"),
136
142
  browserlessRegion: stringFlag(args, "--browserless-region", existing.browserless?.region || "sfo"),
137
143
  browserlessOnlyDomains: optionalStringFlag(args, "--browserless-domains", (existing.browserless?.onlyDomains || []).join(",")),
144
+ grokBotOnlyDomains: optionalStringFlag(args, "--grok-bot-domains", (existing.grokBot?.onlyDomains || []).join(",")),
138
145
  });
139
146
  console.log(
140
147
  `Saved: source=${config.sourceBrowser}, target=${config.targetBrowser}, cookies=${config.imports.cookies ? "on" : "off"}, history=${config.imports.history ? "on" : "off"}, site-storage=${config.imports.siteStorage ? "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"}, auto-restart-both=${config.ui.autoRestartBoth ? "on" : "off"}`,
@@ -251,13 +258,17 @@ function setup(args) {
251
258
 
252
259
  console.log("Setup complete.");
253
260
  console.log(`Pinned runtime: ${runtime}`);
254
- if (config.targetBrowser === "codex") {
261
+ if (isDirectTarget(config.targetBrowser)) {
262
+ const targetName = directTargetName(config.targetBrowser);
255
263
  console.log(`Source browser: ${config.sourceBrowser} (read locally; no extension required)`);
256
- console.log("Target integration: direct local Codex merge (Codex must be closed)");
264
+ console.log(`Target integration: direct local ${targetName} browser merge (${targetName} must be closed)`);
257
265
  } else if (config.targetBrowser === "browserless") {
258
266
  console.log(`Source browser: ${config.sourceBrowser} (captured locally by the Browserless CLI)`);
259
267
  console.log(`Cloud destination: Browserless ${config.browserless?.region || "sfo"} / ${config.browserless?.profileName || "browser-cookie-bridge"}`);
260
268
  console.log("Browserless uploads are manual-only and require --allow-cloud-upload.");
269
+ } else if (config.targetBrowser === "grok-bot") {
270
+ console.log(`Source browser: ${config.sourceBrowser} (read locally; no extension required)`);
271
+ console.log("Target integration: encrypted Grok Bot transfer file (.bcbx); attach it to any Grok Bot and run the bundled importer.");
261
272
  } else {
262
273
  console.log(`Source extension (${config.sourceBrowser}): ${installedExtensionDir(undefined, config.sourceBrowser)}`);
263
274
  console.log(`Target extension (${config.targetBrowser}): ${installedExtensionDir(undefined, config.targetBrowser)}`);
@@ -265,8 +276,8 @@ function setup(args) {
265
276
  }
266
277
  if (plist) console.log(`Daily schedule: ${pad(hour)}:${pad(minute)} (${plist})`);
267
278
  console.log(`Broker port: 127.0.0.1:${config.port}`);
268
- console.log(config.targetBrowser === "codex"
269
- ? "Codex is backed up and validated before its local browser data is changed."
279
+ console.log(isDirectTarget(config.targetBrowser)
280
+ ? `${directTargetName(config.targetBrowser)} is backed up and validated before its local browser data is changed.`
270
281
  : "Cookie values are transferred in memory and are not written to logs or disk.");
271
282
  }
272
283
 
@@ -275,7 +286,40 @@ async function sync(args, { signal } = {}) {
275
286
  const config = readConfig();
276
287
  const target = config.targetBrowser || "codex";
277
288
  const seconds = integerFlag(args, "--timeout", target === "browserless" ? 900 : 300, 5, 3600);
278
- const isCodexTarget = target === "codex";
289
+ const directTarget = isDirectTarget(target);
290
+ if (target === "grok-bot") {
291
+ if (config.imports?.cookies === false) {
292
+ throw new Error("Grok Bot export requires cookies. Turn on Cookies and try again.");
293
+ }
294
+ if (config.imports?.history === true || config.imports?.siteStorage === true) {
295
+ throw new Error("Grok Bot export supports cookie sessions only.");
296
+ }
297
+ const outputPath = stringFlag(args, "--output", "");
298
+ if (!outputPath) throw new Error("Grok Bot export requires --output /path/GrokBot-Import.bcbx");
299
+ const source = config.sourceBrowser || "brave";
300
+ const payload = readChromiumProfile({
301
+ browser: source,
302
+ imports: { cookies: true, history: false },
303
+ });
304
+ console.log(`Read ${payload.cookies.length} of ${payload.cookieStats.total} cookies (${payload.cookieStats.skipped} unavailable) from ${source}.`);
305
+ const result = writeGrokBotBundle({
306
+ outputPath,
307
+ cookies: payload.cookies,
308
+ sourceBrowser: source,
309
+ onlyDomains: config.grokBot?.onlyDomains || [],
310
+ });
311
+ result.sourceCookieSkipped = payload.cookieStats.skipped;
312
+ console.log(grokBotSummary(result));
313
+ console.log(`BCB_GROK_RESULT ${JSON.stringify({
314
+ outputPath: result.outputPath,
315
+ passphrase: result.passphrase,
316
+ cookieCount: result.cookieCount,
317
+ domainCount: result.domainCount,
318
+ domains: result.domains,
319
+ prompt: result.prompt,
320
+ })}`);
321
+ return result;
322
+ }
279
323
  if (target === "browserless") {
280
324
  if (!args.includes("--allow-cloud-upload")) {
281
325
  throw new Error("Browserless cloud uploads are manual-only. Start one from the app or pass --allow-cloud-upload explicitly.");
@@ -306,27 +350,39 @@ async function sync(args, { signal } = {}) {
306
350
  console.log(result.summary);
307
351
  return result;
308
352
  }
309
- if (isCodexTarget) {
310
- if (isCodexRunning()) throw new Error(CODEX_RUNNING_ERROR);
353
+ if (directTarget) {
354
+ if (isDirectTargetRunning({ target })) {
355
+ throw new Error(target === "cursor" ? CURSOR_RUNNING_ERROR : CODEX_RUNNING_ERROR);
356
+ }
357
+ if (target === "cursor" && config.imports?.history === true) {
358
+ throw new Error("Cursor browser does not expose a compatible history store. Turn off History URLs and try again.");
359
+ }
360
+ if (target === "cursor" && config.imports?.siteStorage === true) {
361
+ throw new Error("Cursor browser full site-data import is not supported yet. Turn off Full site data and try again.");
362
+ }
363
+ if (target === "cursor" && config.imports?.cookies === false) {
364
+ throw new Error("Cursor import currently supports cookies only. Turn on Cookies and try again.");
365
+ }
311
366
  const siteStorage = config.imports?.siteStorage === true;
312
- const source = requireClosedCodexSource({
367
+ const source = requireClosedDirectTargetSource({
313
368
  sourceBrowser: config.sourceBrowser,
314
369
  siteStorage,
370
+ targetName: directTargetName(target),
315
371
  });
316
372
  const payload = readChromiumProfile({
317
373
  browser: source,
318
374
  imports: config.imports || { cookies: true, history: false },
319
375
  });
320
376
  console.log(`Read ${payload.cookies.length} of ${payload.cookieStats.total} cookies (${payload.cookieStats.skipped} unavailable) and ${payload.history.length} history URLs from ${source}.`);
321
- const result = directImportToCodex({
377
+ const result = directImportToEmbeddedBrowser({
378
+ target,
322
379
  cookies: payload.cookies,
323
380
  history: payload.history,
324
381
  sourceProfilePath: payload.profilePath,
325
382
  siteStorage,
326
383
  sourceCookieSkipped: payload.cookieStats.skipped,
327
- codexRunning: false,
328
384
  });
329
- console.log(directCodexSummary(result));
385
+ console.log(directTargetSummary(result));
330
386
  return result;
331
387
  }
332
388
  const broker = createBroker({
@@ -358,10 +414,19 @@ export function requireClosedCodexSource({
358
414
  sourceBrowser,
359
415
  siteStorage,
360
416
  runningCheck = isChromiumBrowserRunning,
417
+ }) {
418
+ return requireClosedDirectTargetSource({ sourceBrowser, siteStorage, runningCheck, targetName: "Codex" });
419
+ }
420
+
421
+ export function requireClosedDirectTargetSource({
422
+ sourceBrowser,
423
+ siteStorage,
424
+ targetName,
425
+ runningCheck = isChromiumBrowserRunning,
361
426
  }) {
362
427
  const source = sourceBrowser || "brave";
363
428
  if (siteStorage && runningCheck({ browser: source })) {
364
- throw new Error(`Quit ${source} completely before importing full site data into Codex.`);
429
+ throw new Error(`Quit ${source} completely before importing full site data into ${targetName}.`);
365
430
  }
366
431
  return source;
367
432
  }
@@ -382,6 +447,11 @@ function emitBrowserlessProgress(event) {
382
447
  }
383
448
 
384
449
  export function directCodexSummary(result) {
450
+ return directTargetSummary({ ...result, targetName: "Codex" });
451
+ }
452
+
453
+ export function directTargetSummary(result) {
454
+ const targetName = result.targetName || "embedded browser";
385
455
  const imported = result.imported + result.historyImported;
386
456
  const skipped = result.skipped + result.historySkipped;
387
457
  const failures = result.failed + result.historyFailed;
@@ -389,11 +459,12 @@ export function directCodexSummary(result) {
389
459
  const siteStorage = result.siteStorageImported > 0
390
460
  ? ` Full site data: ${result.siteStorageImported} storage area${result.siteStorageImported === 1 ? "" : "s"} replaced from the source profile.`
391
461
  : "";
392
- if (failures > 0 || unavailable > 0) {
462
+ const backupWarning = result.backupCleanupWarning ? ` ${result.backupCleanupWarning}` : "";
463
+ if (failures > 0 || unavailable > 0 || backupWarning) {
393
464
  const sourceNote = unavailable > 0 ? ` ${unavailable} source cookie${unavailable === 1 ? " was" : "s were"} unreadable or unsupported.` : "";
394
- return `Direct Codex sync completed with warnings: ${imported} imported, ${skipped} skipped, ${failures} failed.${sourceNote}${siteStorage} Backup: ${result.backupPath}`;
465
+ return `Direct ${targetName} sync completed with warnings: ${imported} imported, ${skipped} skipped, ${failures} failed.${sourceNote}${siteStorage}${backupWarning} Backup: ${result.backupPath}`;
395
466
  }
396
- return `Direct Codex sync complete: ${imported} imported and ${skipped} skipped.${siteStorage} Reopen Codex to use the updated sessions. Backup: ${result.backupPath}`;
467
+ return `Direct ${targetName} sync complete: ${imported} imported and ${skipped} skipped.${siteStorage} Reopen ${targetName} to use the updated sessions. Backup: ${result.backupPath}`;
397
468
  }
398
469
 
399
470
  export function transferSummary(result) {
@@ -409,6 +480,7 @@ function doctor() {
409
480
  const home = os.homedir();
410
481
  const brave = existing(braveCookiePaths(home));
411
482
  const codex = existing(codexCookiePaths(home));
483
+ const cursor = existing(cursorCookiePaths(home));
412
484
  console.log(`Platform: ${process.platform} ${process.arch}`);
413
485
  console.log(`Node: ${process.version}`);
414
486
  console.log(`Configuration: ${status(configPath(home))}`);
@@ -419,10 +491,12 @@ function doctor() {
419
491
  console.log(`Selected target: ${target}`);
420
492
  console.log(`Source extension: ${status(installedExtensionDir(home, source))}`);
421
493
  console.log(
422
- target === "codex"
423
- ? "Target integration: direct local Codex merge (Codex must be closed)"
494
+ isDirectTarget(target)
495
+ ? `Target integration: direct local ${directTargetName(target)} browser merge (${directTargetName(target)} must be closed)`
424
496
  : target === "browserless"
425
497
  ? `Target integration: optional Browserless cloud upload (${config?.browserless?.region || "sfo"}); manual only`
498
+ : target === "grok-bot"
499
+ ? "Target integration: encrypted Grok Bot transfer file (.bcbx); manual only"
426
500
  : `Target extension: ${status(installedExtensionDir(home, target))}`,
427
501
  );
428
502
  console.log(`Daily schedule: ${status(launchAgentPath(home))}`);
@@ -431,8 +505,10 @@ function doctor() {
431
505
  console.log(`Desktop app: ${status(installedAppPath(home))}`);
432
506
  console.log(`Brave cookie stores detected: ${brave.length}`);
433
507
  console.log(`Codex cookie stores detected: ${codex.length}`);
508
+ console.log(`Cursor browser cookie stores detected: ${cursor.length}`);
434
509
  for (const file of brave) console.log(` Brave: ${file}`);
435
510
  for (const file of codex) console.log(` Codex: ${file}`);
511
+ for (const file of cursor) console.log(` Cursor: ${file}`);
436
512
  console.log("No cookie names, domains, values, or encryption keys were read.");
437
513
  }
438
514
 
@@ -445,10 +521,12 @@ function enableLoginSync() {
445
521
  });
446
522
  console.log(`Login sync enabled: ${plist}`);
447
523
  const config = readConfig();
448
- console.log(config.targetBrowser === "codex"
449
- ? "A sync starts when you sign in and updates Codex only when Codex is closed."
524
+ console.log(isDirectTarget(config.targetBrowser)
525
+ ? `A sync starts when you sign in and updates ${directTargetName(config.targetBrowser)} only when it is closed.`
450
526
  : config.targetBrowser === "browserless"
451
527
  ? "Browserless uploads remain manual-only; login sync will not send data to the cloud."
528
+ : config.targetBrowser === "grok-bot"
529
+ ? "Grok Bot transfer files remain manual-only; login sync will not create cloud bundles."
452
530
  : "A sync starts when you sign in and waits up to five minutes for both browser extensions.");
453
531
  }
454
532
 
@@ -471,6 +549,7 @@ function setAppLogin(enabled) {
471
549
  browserlessProfileName: existing.browserless?.profileName,
472
550
  browserlessRegion: existing.browserless?.region,
473
551
  browserlessOnlyDomains: existing.browserless?.onlyDomains,
552
+ grokBotOnlyDomains: existing.grokBot?.onlyDomains,
474
553
  });
475
554
  if (enabled) {
476
555
  const appPath = installedAppPath();
@@ -486,6 +565,14 @@ function remove() {
486
565
  console.log(removeSchedule() ? "Daily schedule removed." : "No daily schedule was installed.");
487
566
  }
488
567
 
568
+ function isDirectTarget(target) {
569
+ return target === "codex" || target === "cursor";
570
+ }
571
+
572
+ function directTargetName(target) {
573
+ return target === "cursor" ? "Cursor" : "Codex";
574
+ }
575
+
489
576
  function assertMacOS() {
490
577
  if (process.platform !== "darwin") throw new Error("This release supports macOS only.");
491
578
  }