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
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
|
-
|
|
9
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
269
|
-
?
|
|
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
|
|
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 (
|
|
310
|
-
if (
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
423
|
-
?
|
|
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
|
|
449
|
-
?
|
|
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
|
}
|