browser-cookie-bridge 1.4.6 → 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 +57 -32
- 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 -21
- 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/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<strong>Move signed-in sessions locally—or explicitly upload one to Browserless Cloud.</strong>
|
|
8
8
|
|
|
9
|
-
<p>Brave, Chrome, Edge, Arc, Vivaldi, Opera, and Comet · ChatGPT Codex import · optional Browserless authenticated profiles.</p>
|
|
9
|
+
<p>Brave, Chrome, Edge, Arc, Vivaldi, Opera, and Comet · ChatGPT Codex and experimental Cursor browser import · encrypted Grok Bot <code>.bcbx</code> export · optional Browserless authenticated profiles.</p>
|
|
10
10
|
|
|
11
11
|
<p>
|
|
12
12
|
<img src="https://img.shields.io/badge/macOS-13.5%2B-111111?logo=apple&logoColor=white" alt="macOS 13.5+" />
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
<a href="https://www.npmjs.com/package/browser-cookie-bridge"><img src="https://img.shields.io/npm/v/browser-cookie-bridge?logo=npm&color=CB3837" alt="browser-cookie-bridge on npm" /></a>
|
|
17
17
|
<img src="https://img.shields.io/badge/local--first-cloud%20optional-C68B3C" alt="Local-first, cloud optional" />
|
|
18
18
|
<img src="https://img.shields.io/github/stars/apoorvdarshan/browser-cookie-bridge?logo=github&color=C68B3C" alt="GitHub stars" />
|
|
19
|
-
<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>
|
|
20
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>
|
|
21
20
|
<img src="https://img.shields.io/badge/license-MIT-3DA639" alt="MIT License" />
|
|
22
21
|
</p>
|
|
@@ -30,7 +29,6 @@
|
|
|
30
29
|
<a href="#security--privacy">Security</a> ·
|
|
31
30
|
<a href="CONTRIBUTING.md">Contribute</a> ·
|
|
32
31
|
<a href="https://github.com/apoorvdarshan/browser-cookie-bridge/issues/new?template=bug_report.yml">Report a bug</a> ·
|
|
33
|
-
<a href="https://www.producthunt.com/products/browser-cookie-bridge">Product Hunt</a> ·
|
|
34
32
|
<a href="https://tally.so/r/J9j0D4">Windows waitlist</a> ·
|
|
35
33
|
<a href="#support">Support</a>
|
|
36
34
|
</p>
|
|
@@ -42,19 +40,19 @@
|
|
|
42
40
|
|
|
43
41
|
<br />
|
|
44
42
|
|
|
45
|
-
<img src="marketing/
|
|
43
|
+
<img src="marketing/app-screenshot.png" width="712" alt="Browser Cookie Bridge app preview" />
|
|
46
44
|
|
|
47
45
|
</div>
|
|
48
46
|
|
|
49
47
|
---
|
|
50
48
|
|
|
51
|
-
> **Version 1.
|
|
49
|
+
> **Version 1.5.0 adds experimental Cursor browser import and encrypted Grok Bot `.bcbx` export.** Cursor is destination-only and currently imports cookie sessions into its dedicated browser partition. Grok Bot writes a one-time encrypted transfer file with a bundled importer and a one-time key: attach the `.bcbx` file to any Grok Bot, paste the prompt, and enter the key privately when asked. Cookie values must never be logged or pasted into chat. The optional Codex restart flow from 1.4.0 is unchanged and remains off by default.
|
|
52
50
|
|
|
53
51
|
## Why Browser Cookie Bridge
|
|
54
52
|
|
|
55
|
-
Signing into the same sites across several browsers is repetitive. Export files are awkward, password managers do not move active sessions, and
|
|
53
|
+
Signing into the same sites across several browsers is repetitive. Export files are awkward, password managers do not move active sessions, and embedded app browsers do not always offer an import button.
|
|
56
54
|
|
|
57
|
-
Browser Cookie Bridge gives those browser profiles a small, native control panel. Local browser and
|
|
55
|
+
Browser Cookie Bridge gives those browser profiles a small, native control panel. Local browser, Codex, and Cursor transfers stay on the Mac. Grok Bot uses a one-time encrypted transfer file that you attach yourself. If you explicitly select Browserless Cloud and click Upload, the app can instead send authenticated profile state to your own Browserless account.
|
|
58
56
|
|
|
59
57
|
## Windows waitlist
|
|
60
58
|
|
|
@@ -65,15 +63,16 @@ Browser Cookie Bridge currently ships for macOS. A separate Windows app is being
|
|
|
65
63
|
- 🍪 **Cookie and session transfer** — cookies are enabled by default, including supported domain, path, expiry, security, `SameSite`, and partition attributes.
|
|
66
64
|
- 🗃️ **Optional full site data for Codex** — replace Codex's compatible Local Storage, IndexedDB, Session Storage, service-worker, and related origin stores from a closed source profile, with backup and rollback. Off by default.
|
|
67
65
|
- 🌐 **Seven Chromium browsers** — Brave, Chrome, Edge, Arc, Vivaldi, Opera, and Perplexity Comet can be sources or destinations.
|
|
68
|
-
- ✨ **ChatGPT Codex import** — merge
|
|
66
|
+
- ✨ **ChatGPT Codex and experimental Cursor import** — merge cookie sessions into either app's built-in browser; Codex also supports optional history and full site data. Both are destination-only. Cursor import is experimental: it writes only to Cursor's dedicated `Partitions/cursor-browser` profile and refuses unknown schemas.
|
|
67
|
+
- 🤖 **Encrypted Grok Bot transfer (`.bcbx`)** — export cookie sessions into a local encrypted bundle with a one-time key and bundled importer. The destination tile uses the official Grok Bot app icon. Attach the file to any Grok Bot; the importer runs on the cloud computer only. Manual only.
|
|
69
68
|
- 🔁 **Optional Codex restart** — manual Sync can force quit a running Codex instance and reopen it only after a successful local transfer; off by default.
|
|
70
69
|
- 🔄 **Optional two-app restart** — Full site data can force quit the selected source browser and Codex, then reopen only the apps that were running after a successful transfer; off by default.
|
|
71
70
|
- ☁️ **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.
|
|
72
71
|
- 🕘 **Background automation** — sync when you sign in, at a fixed daily time, or whenever you choose.
|
|
73
72
|
- ◉ **Native menu-bar app** — closing the window removes the Dock icon while the helper continues running.
|
|
74
|
-
- 🧯 **Backup and rollback** —
|
|
73
|
+
- 🧯 **Backup and rollback** — the destination app browser's database is backed up, modified on a separate copy, integrity-checked, and restored if replacement fails.
|
|
75
74
|
- ⬆️ **Built-in updates** — checks for GitHub releases, verifies the DMG checksum, installs in place, and relaunches the app.
|
|
76
|
-
- 🔒 **Local-first** — local paths use no account, analytics, cookie logs, or remote relay; the separate Browserless path runs only after explicit selection and confirmation.
|
|
75
|
+
- 🔒 **Local-first** — local paths use no account, analytics, cookie logs, or remote relay; Grok Bot writes an encrypted file you attach yourself; the separate Browserless path runs only after explicit selection and confirmation.
|
|
77
76
|
|
|
78
77
|
## What it transfers
|
|
79
78
|
|
|
@@ -81,7 +80,8 @@ Browser Cookie Bridge currently ships for macOS. A separate Windows app is being
|
|
|
81
80
|
|---|---:|---|
|
|
82
81
|
| **Cookies and sessions** | ✅ Default | Transfers supported cookie values and attributes |
|
|
83
82
|
| **Full site data → Codex** | Optional | Replaces compatible origin storage from a closed Chromium profile; backs up Codex first |
|
|
84
|
-
| **
|
|
83
|
+
| **Encrypted Grok Bot export** | Manual | Cookie sessions only, written to a local `.bcbx` file; history and full site data are excluded |
|
|
84
|
+
| **History URLs** | ◐ Optional | Supported for browser and Codex targets; excluded for Cursor and Grok Bot. Original visit times and page titles cannot be preserved |
|
|
85
85
|
| **Local storage and IndexedDB** | ◐ Optional | Full site data into Codex, or an explicit Browserless authenticated-profile upload |
|
|
86
86
|
| **Passwords** | — Never | Chromium extensions cannot read the browser password store |
|
|
87
87
|
| **Bookmarks, autofill, payments** | — Never | Not requested or accessed |
|
|
@@ -92,7 +92,7 @@ Some websites bind sessions to a specific device or browser and may ask you to s
|
|
|
92
92
|
## Requirements
|
|
93
93
|
|
|
94
94
|
- **macOS 13.5+**
|
|
95
|
-
- A supported Chromium browser,
|
|
95
|
+
- A supported Chromium browser as the source, plus ChatGPT Codex, Cursor, Grok Bot, another supported browser, or optional Browserless Cloud as the destination
|
|
96
96
|
|
|
97
97
|
The DMG is self-contained. Node.js 24+ and Xcode Command Line Tools are required only for npm or source installation.
|
|
98
98
|
|
|
@@ -145,16 +145,40 @@ The final command compiles the native SwiftUI app, enables **Open at login**, **
|
|
|
145
145
|
|
|
146
146
|
## Setup
|
|
147
147
|
|
|
148
|
-
### Browser → ChatGPT Codex
|
|
148
|
+
### Browser → ChatGPT Codex or Cursor
|
|
149
149
|
|
|
150
150
|
No browser extension is needed for this path.
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
2. Quit Codex completely, or enable **Restart Codex automatically**. Closing only its browser panel is not enough.
|
|
154
|
-
3. Choose **Cookies** and, optionally, **History URLs**.
|
|
155
|
-
4. Press **Sync now** and reopen Codex after the success message if automatic restart is off.
|
|
152
|
+
Cursor support is experimental and currently limited to cookie sessions in its dedicated browser partition. The importer validates the audited Cursor schema and fails closed if Cursor changes it.
|
|
156
153
|
|
|
157
|
-
|
|
154
|
+
1. Select a source browser and **ChatGPT Codex** or **Cursor browser** as the destination.
|
|
155
|
+
2. Quit the destination app completely. For Codex, you can instead enable **Restart Codex automatically**. Closing only the app's browser panel is not enough.
|
|
156
|
+
3. Choose **Cookies**. For Codex, you can also choose **History URLs** and **Full site data**. Both are excluded for Cursor until its other browser stores can be validated safely.
|
|
157
|
+
4. Press **Sync now**, then reopen the destination app after the success message.
|
|
158
|
+
|
|
159
|
+
An open destination app blocks the transfer. The optional **Restart Codex automatically** setting applies only to a manual Codex sync: it force quits Codex, waits for its database to close, and reopens Codex only after the transfer succeeds. Cursor must be quit manually. Scheduled and login syncs never force quit either app.
|
|
160
|
+
|
|
161
|
+
### Browser → Grok Bot (encrypted `.bcbx`)
|
|
162
|
+
|
|
163
|
+
No browser extension is needed for this path.
|
|
164
|
+
|
|
165
|
+
Grok Bot cannot receive a local database merge. The app writes a one-time encrypted transfer file that you attach to any Grok Bot.
|
|
166
|
+
|
|
167
|
+
1. Select a source browser and **Grok Bot** as the destination. The tile uses the official Grok Bot app icon.
|
|
168
|
+
2. Keep **Cookies** on. History URLs and Full site data are excluded from Grok Bot transfer files.
|
|
169
|
+
3. Optionally limit the export with **Only these domains**. Leave it blank to include every readable cookie.
|
|
170
|
+
4. Press **Create transfer file** and choose where to save `GrokBot-Import.bcbx`.
|
|
171
|
+
5. The result sheet shows the one-time decryption key and the prompt to paste. Use **Reveal file** or **Copy prompt**. Keep the key off chat logs; do not paste cookie values into chat.
|
|
172
|
+
6. Attach the `.bcbx` file to any Grok Bot, paste the prompt, then enter the one-time key privately through Agent Computer when asked.
|
|
173
|
+
|
|
174
|
+
The bundle is a zip-compatible `.bcbx` file containing `manifest.json`, encrypted `payload.enc`, `import.mjs`, and `PROMPT.txt`. Cookies are encrypted with AES-256-GCM using a scrypt-derived key. The file is written with user-only permissions (`0600`). Daily sync and Sync at login never create Grok Bot bundles.
|
|
175
|
+
|
|
176
|
+
On the Grok Bot cloud computer the bundled importer:
|
|
177
|
+
|
|
178
|
+
1. Expects you to unzip `GrokBot-Import.bcbx` and run `node import.mjs`.
|
|
179
|
+
2. Prompts for the one-time decryption key. Do not print cookie names or values.
|
|
180
|
+
3. Injects cookies into the cloud browser and reports only how many cookies were imported.
|
|
181
|
+
4. Deletes the unzipped files and the bundle copy.
|
|
158
182
|
|
|
159
183
|
### Browser → browser
|
|
160
184
|
|
|
@@ -189,7 +213,7 @@ The official CLI records the Browserless upload-disclaimer acceptance timestamp
|
|
|
189
213
|
| Control | What it does |
|
|
190
214
|
|---|---|
|
|
191
215
|
| **Export from** | Selects the browser whose data will be read |
|
|
192
|
-
| **Import into** | Selects a different browser, ChatGPT Codex, or optional Browserless Cloud |
|
|
216
|
+
| **Import into** | Selects a different browser, ChatGPT Codex, experimental Cursor browser, Grok Bot, or optional Browserless Cloud |
|
|
193
217
|
| **Cookies** | Moves cookies and supported session attributes; on by default |
|
|
194
218
|
| **History URLs** | Adds visited URLs without their original timestamps or titles |
|
|
195
219
|
| **Daily sync** | Runs at one fixed local time; off by default |
|
|
@@ -199,15 +223,15 @@ The official CLI records the Browserless upload-disclaimer acceptance timestamp
|
|
|
199
223
|
| **Restart Codex automatically** | Force quits Codex for a manual sync and reopens it only after success; off by default |
|
|
200
224
|
| **Check for updates** | Finds a newer GitHub release, verifies its DMG, and offers install + relaunch |
|
|
201
225
|
|
|
202
|
-
Automation uses the saved source, destination, and data choices for local transfers. A scheduled
|
|
226
|
+
Automation uses the saved source, destination, and data choices for local transfers. A scheduled embedded-browser sync safely exits without making changes when its destination app is open. Browserless cloud uploads and Grok Bot `.bcbx` exports are always manual.
|
|
203
227
|
|
|
204
228
|
## CLI
|
|
205
229
|
|
|
206
230
|
```bash
|
|
207
231
|
browser-cookie-bridge install-app [--no-open] [--replace-system-from-source]
|
|
208
232
|
browser-cookie-bridge setup [--hour 9] [--minute 0] [--no-schedule]
|
|
209
|
-
browser-cookie-bridge preferences --source brave --target codex --cookies on --history off --site-storage off --auto-restart-codex off --auto-restart-both off
|
|
210
|
-
browser-cookie-bridge sync [--timeout 300] [--allow-cloud-upload]
|
|
233
|
+
browser-cookie-bridge preferences --source brave --target codex --cookies on --history off --site-storage off --auto-restart-codex off --auto-restart-both off [--grok-bot-domains example.com]
|
|
234
|
+
browser-cookie-bridge sync [--timeout 300] [--allow-cloud-upload] [--output /path/GrokBot-Import.bcbx]
|
|
211
235
|
browser-cookie-bridge browserless-preflight
|
|
212
236
|
browser-cookie-bridge doctor
|
|
213
237
|
browser-cookie-bridge enable-login-sync
|
|
@@ -217,26 +241,28 @@ browser-cookie-bridge disable-app-login
|
|
|
217
241
|
browser-cookie-bridge remove-schedule
|
|
218
242
|
```
|
|
219
243
|
|
|
220
|
-
Supported source IDs are `brave`, `chrome`, `edge`, `arc`, `vivaldi`, `opera`, and `comet`. Target IDs are the same plus `codex` and `
|
|
244
|
+
Supported source IDs are `brave`, `chrome`, `edge`, `arc`, `vivaldi`, `opera`, and `comet`. Target IDs are the same plus `codex`, `cursor`, `browserless`, and `grok-bot`. The same browser cannot be both endpoints. Cursor import currently supports cookies only. Grok Bot export requires `--output /path/GrokBot-Import.bcbx` and writes cookies only; `--grok-bot-domains` optionally limits the bundle. The CLI prints a one-time decryption key after export—keep it private and never paste cookie values into chat. Browserless requires `BROWSERLESS_TOKEN` and the explicit `--allow-cloud-upload` flag; the native app supplies the token from Keychain without placing it in the OS command line or app configuration.
|
|
221
245
|
|
|
222
246
|
## How it works
|
|
223
247
|
|
|
224
248
|
| Path | Transfer method |
|
|
225
249
|
|---|---|
|
|
226
250
|
| **Browser → browser** | Unpacked extensions connect to a short-lived broker on IPv4 loopback. Selected data stays in memory and is never written to logs. |
|
|
227
|
-
| **Browser → Codex** | The app reads the selected local Chromium profile, creates a consistent
|
|
251
|
+
| **Browser → Codex / Cursor** | The app reads the selected local Chromium profile, creates a consistent destination SQLite backup, merges cookies into a working copy, validates it, then replaces only the embedded browser's cookie database atomically. Codex also supports optional history and full site data. Cursor import is experimental and limited to the dedicated browser-partition cookie store. |
|
|
252
|
+
| **Browser → Grok Bot** | The app reads the selected local Chromium profile, encrypts cookie sessions into a `.bcbx` bundle with a one-time key, and writes that file locally. You attach it to a Grok Bot; the bundled importer decrypts and injects cookies on the cloud computer only. |
|
|
228
253
|
| **Browser → Browserless** | The bundled official Browserless CLI copies a closed local profile, captures cookies/local storage/IndexedDB, and uploads it directly to the selected Browserless region. |
|
|
229
254
|
|
|
230
255
|
The broker validates a random token and extension origin, limits payload size, and normally exits after five minutes. Only the endpoints selected in the app respond to a transfer.
|
|
231
256
|
|
|
232
257
|
## Security & privacy
|
|
233
258
|
|
|
234
|
-
- Cookie values and history URLs are never logged.
|
|
259
|
+
- Cookie values and history URLs are never logged. Never paste cookie values or Grok Bot decryption keys into chat.
|
|
235
260
|
- Browser-to-browser data is held only in broker memory.
|
|
236
|
-
-
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
261
|
+
- Grok Bot `.bcbx` files are encrypted locally with AES-256-GCM. Treat the bundle and its one-time key as credentials. Enter the key privately through Agent Computer when the importer asks; do not ask Grok Bot to print cookie names or values. The bundled importer reports only import counts, then deletes the unzipped files and the bundle copy.
|
|
262
|
+
- Direct-import backups are stored with user-only permissions under `~/Library/Application Support/BraveCodexCookieSync/backups/codex` or `backups/cursor`; the newest 14 per destination are retained.
|
|
263
|
+
- Full site-data import is opt-in for Codex and requires both Codex and the source browser to be closed. It replaces compatible origin-storage directories rather than attempting an unsafe LevelDB merge; the previous directories are included in the same backup.
|
|
264
|
+
- The app refuses unknown destination database schemas instead of guessing and writes only to Cursor's dedicated `Partitions/cursor-browser` profile, never Cursor's main application cookie store.
|
|
265
|
+
- Direct imports currently store imported cookie values in SQLite's plaintext `value` column with an empty `encrypted_value`. Those imported rows can therefore remain readable to software running as your macOS user until the website refreshes them.
|
|
240
266
|
- Anyone who can use your logged-in macOS account or modify a generated extension may be able to access transferred browser sessions.
|
|
241
267
|
- The optional Browserless destination sends authenticated state to Browserless under their terms and privacy practices. Its API token is stored in macOS Keychain, uploads are manual, and Browserless CLI telemetry is disabled by the app.
|
|
242
268
|
|
|
@@ -271,9 +297,8 @@ If Browser Cookie Bridge is useful to you:
|
|
|
271
297
|
- 🐛 **[Report a bug](https://github.com/apoorvdarshan/browser-cookie-bridge/issues/new?template=bug_report.yml)** — never include cookie values or tokens
|
|
272
298
|
- ☕ **[Support on Ko-fi](https://ko-fi.com/apoorvdarshan)**
|
|
273
299
|
- 𝕏 **Follow [@apoorvdarshan](https://x.com/apoorvdarshan)**
|
|
274
|
-
- 🚀 **[View on Product Hunt](https://www.producthunt.com/products/browser-cookie-bridge)**
|
|
275
300
|
|
|
276
|
-
|
|
301
|
+
The product screenshot and transparent cookie logo live in [`marketing/`](marketing/).
|
|
277
302
|
|
|
278
303
|
## Website
|
|
279
304
|
|
|
@@ -297,4 +322,4 @@ npm run web
|
|
|
297
322
|
|
|
298
323
|
[MIT](LICENSE) © 2026 [Apoorv Darshan](https://github.com/apoorvdarshan)
|
|
299
324
|
|
|
300
|
-
<sub>Not affiliated with Brave, Google, Microsoft, The Browser Company, Vivaldi, Opera, Perplexity, OpenAI, or Browserless. Their names and marks belong to their respective owners.</sub>
|
|
325
|
+
<sub>Not affiliated with Brave, Google, Microsoft, The Browser Company, Vivaldi, Opera, Perplexity, OpenAI, Cursor, xAI, or Browserless. Their names and marks belong to their respective owners.</sub>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Browser Cookie Bridge",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"description": "Transfers selected browser data locally between supported browsers and
|
|
4
|
+
"version": "1.5.0",
|
|
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"],
|
|
8
8
|
"host_permissions": ["<all_urls>", "http://127.0.0.1/*"],
|
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.5.0</string>
|
|
14
|
+
<key>CFBundleVersion</key><string>36</string>
|
|
15
15
|
<key>LSMinimumSystemVersion</key><string>13.5</string>
|
|
16
16
|
<key>NSHighResolutionCapable</key><true/>
|
|
17
17
|
</dict>
|
|
Binary file
|
|
@@ -9,7 +9,6 @@ private enum ProjectLinks {
|
|
|
9
9
|
static let license = URL(string: "https://github.com/apoorvdarshan/browser-cookie-bridge/blob/main/LICENSE")!
|
|
10
10
|
static let koFi = URL(string: "https://ko-fi.com/apoorvdarshan")!
|
|
11
11
|
static let x = URL(string: "https://x.com/apoorvdarshan")!
|
|
12
|
-
static let productHunt = URL(string: "https://www.producthunt.com/products/browser-cookie-bridge")!
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
@main
|
|
@@ -202,7 +201,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
|
|
|
202
201
|
menu.addItem(.separator())
|
|
203
202
|
addLink("Support on Ko-fi", url: ProjectLinks.koFi, to: menu)
|
|
204
203
|
addLink("Follow @apoorvdarshan on X", url: ProjectLinks.x, to: menu)
|
|
205
|
-
addLink("View on Product Hunt", url: ProjectLinks.productHunt, to: menu)
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
private func addLink(_ title: String, url: URL, to menu: NSMenu) {
|
|
@@ -241,6 +239,9 @@ struct ContentView: View {
|
|
|
241
239
|
.sheet(isPresented: $model.showingBrowserlessSetup) {
|
|
242
240
|
BrowserlessSetupSheet().environmentObject(model)
|
|
243
241
|
}
|
|
242
|
+
.sheet(isPresented: $model.showingGrokBotResult) {
|
|
243
|
+
GrokBotResultSheet().environmentObject(model)
|
|
244
|
+
}
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
private var header: some View {
|
|
@@ -293,7 +294,7 @@ struct ContentView: View {
|
|
|
293
294
|
.buttonStyle(.plain)
|
|
294
295
|
.foregroundStyle(.secondary)
|
|
295
296
|
.help("Refresh status")
|
|
296
|
-
if model.
|
|
297
|
+
if !model.isDirectTarget && !model.isBrowserlessTarget && !model.isGrokBotTarget {
|
|
297
298
|
Button("Extension setup…") { showingSetup = true }
|
|
298
299
|
.controlSize(.small)
|
|
299
300
|
}
|
|
@@ -386,17 +387,18 @@ struct SyncPanel: View {
|
|
|
386
387
|
if model.isBrowserlessTarget && model.uploadCanceling { return "Canceling…" }
|
|
387
388
|
if model.isBrowserlessTarget && model.isSyncing { return "Cancel upload" }
|
|
388
389
|
if model.isSyncing { return "Syncing…" }
|
|
389
|
-
if model.
|
|
390
|
+
if model.cursorHasNoDataSelected || model.grokBotHasNoDataSelected { return "Turn on Cookies" }
|
|
391
|
+
if model.directTargetBlocked { return "Close \(model.targetName) first" }
|
|
390
392
|
if model.isBrowserlessTarget && !model.browserlessConfigured { return "Connect Browserless" }
|
|
391
393
|
if model.isBrowserlessTarget && model.selectedSourceID == "comet" { return "Choose another browser" }
|
|
392
394
|
if model.isBrowserlessTarget && model.sourceBrowserRunning { return "Close \(model.selectedBrowser.name) first" }
|
|
393
|
-
return model.isBrowserlessTarget ? "Upload now" : "Sync now"
|
|
395
|
+
return model.isGrokBotTarget ? "Create transfer file" : model.isBrowserlessTarget ? "Upload now" : "Sync now"
|
|
394
396
|
}
|
|
395
397
|
|
|
396
398
|
private var syncButtonIcon: String {
|
|
397
399
|
if model.isBrowserlessTarget && model.isSyncing { return "xmark.circle.fill" }
|
|
398
400
|
if model.syncBlocked { return model.isBrowserlessTarget && !model.browserlessConfigured ? "key.fill" : "xmark.circle.fill" }
|
|
399
|
-
return model.isBrowserlessTarget ? "icloud.and.arrow.up.fill" : "arrow.triangle.2.circlepath"
|
|
401
|
+
return model.isBrowserlessTarget ? "icloud.and.arrow.up.fill" : model.isGrokBotTarget ? "arrow.up.doc.fill" : "arrow.triangle.2.circlepath"
|
|
400
402
|
}
|
|
401
403
|
|
|
402
404
|
}
|
|
@@ -463,6 +465,14 @@ struct TargetPicker: View {
|
|
|
463
465
|
}
|
|
464
466
|
HStack(spacing: 5) {
|
|
465
467
|
ForEach(Array(model.browsers.dropFirst(4))) { browser in targetButton(browser) }
|
|
468
|
+
}
|
|
469
|
+
HStack(spacing: 5) {
|
|
470
|
+
EndpointButton(
|
|
471
|
+
icon: model.cursorIcon,
|
|
472
|
+
name: "Cursor browser",
|
|
473
|
+
selected: model.selectedTargetID == "cursor",
|
|
474
|
+
disabled: model.isWorking || model.isSyncing
|
|
475
|
+
) { model.selectTarget("cursor") }
|
|
466
476
|
EndpointButton(
|
|
467
477
|
icon: model.codexIcon,
|
|
468
478
|
name: "ChatGPT Codex",
|
|
@@ -472,10 +482,16 @@ struct TargetPicker: View {
|
|
|
472
482
|
iconWidth: 66,
|
|
473
483
|
iconHeight: 38
|
|
474
484
|
) { model.selectTarget("codex") }
|
|
485
|
+
EndpointButton(
|
|
486
|
+
icon: model.grokBotIcon,
|
|
487
|
+
name: "Grok Bot",
|
|
488
|
+
selected: model.selectedTargetID == "grok-bot",
|
|
489
|
+
disabled: model.isWorking || model.isSyncing
|
|
490
|
+
) { model.selectTarget("grok-bot") }
|
|
475
491
|
}
|
|
476
492
|
}
|
|
477
493
|
}
|
|
478
|
-
.frame(width:
|
|
494
|
+
.frame(width: 220)
|
|
479
495
|
}
|
|
480
496
|
|
|
481
497
|
private func targetButton(_ browser: BrowserChoice) -> some View {
|
|
@@ -564,12 +580,56 @@ struct PreferencesPanel: View {
|
|
|
564
580
|
.help("Rescan local profile size")
|
|
565
581
|
}
|
|
566
582
|
}
|
|
583
|
+
} else if model.isGrokBotTarget {
|
|
584
|
+
PreferenceRow(
|
|
585
|
+
icon: "network",
|
|
586
|
+
color: Theme.accent,
|
|
587
|
+
title: "Cookies",
|
|
588
|
+
detail: "Encrypted cookie sessions for Grok Bot's shared cloud browser"
|
|
589
|
+
) {
|
|
590
|
+
Toggle("", isOn: Binding(get: { model.cookiesEnabled }, set: { model.setCookiesEnabled($0) }))
|
|
591
|
+
.labelsHidden().toggleStyle(.switch).tint(Theme.active).disabled(model.isWorking)
|
|
592
|
+
}
|
|
593
|
+
RowDivider()
|
|
594
|
+
PreferenceRow(icon: "externaldrive.badge.plus", color: .secondary, title: "Full site data", detail: "Not included in Grok Bot transfer files", muted: true) {
|
|
595
|
+
FixedBadge("Excluded")
|
|
596
|
+
}
|
|
597
|
+
RowDivider()
|
|
598
|
+
PreferenceRow(icon: "clock.arrow.circlepath", color: .secondary, title: "History URLs", detail: "Not included in Grok Bot transfer files", muted: true) {
|
|
599
|
+
FixedBadge("Excluded")
|
|
600
|
+
}
|
|
601
|
+
RowDivider()
|
|
602
|
+
PreferenceRow(icon: "line.3.horizontal.decrease.circle", color: Theme.accent, title: "Only these domains", detail: "Leave blank to export every readable cookie") {
|
|
603
|
+
TextField("example.com, app.example.com", text: Binding(
|
|
604
|
+
get: { model.grokBotOnlyDomains },
|
|
605
|
+
set: { model.setGrokBotOnlyDomains($0) }
|
|
606
|
+
))
|
|
607
|
+
.textFieldStyle(.roundedBorder)
|
|
608
|
+
.frame(width: 220)
|
|
609
|
+
.disabled(model.isWorking || model.isSyncing)
|
|
610
|
+
}
|
|
567
611
|
} else {
|
|
568
|
-
PreferenceRow(
|
|
612
|
+
PreferenceRow(
|
|
613
|
+
icon: "network",
|
|
614
|
+
color: Theme.accent,
|
|
615
|
+
title: "Cookies",
|
|
616
|
+
detail: model.selectedTargetID == "cursor" ? "Experimental direct import into Cursor's browser partition" : "Site sessions and sign-ins"
|
|
617
|
+
) {
|
|
569
618
|
Toggle("", isOn: Binding(get: { model.cookiesEnabled }, set: { model.setCookiesEnabled($0) }))
|
|
570
619
|
.labelsHidden().toggleStyle(.switch).tint(Theme.active).disabled(model.isWorking)
|
|
571
620
|
}
|
|
572
|
-
if model.selectedTargetID == "
|
|
621
|
+
if model.selectedTargetID == "cursor" {
|
|
622
|
+
RowDivider()
|
|
623
|
+
PreferenceRow(
|
|
624
|
+
icon: "externaldrive.badge.plus",
|
|
625
|
+
color: .secondary,
|
|
626
|
+
title: "Full site data",
|
|
627
|
+
detail: "Not enabled for Cursor browser yet",
|
|
628
|
+
muted: true
|
|
629
|
+
) {
|
|
630
|
+
FixedBadge("Excluded")
|
|
631
|
+
}
|
|
632
|
+
} else if model.selectedTargetID == "codex" {
|
|
573
633
|
RowDivider()
|
|
574
634
|
PreferenceRow(
|
|
575
635
|
icon: "externaldrive.badge.plus",
|
|
@@ -582,9 +642,15 @@ struct PreferencesPanel: View {
|
|
|
582
642
|
}
|
|
583
643
|
}
|
|
584
644
|
RowDivider()
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
645
|
+
if model.selectedTargetID == "cursor" {
|
|
646
|
+
PreferenceRow(icon: "clock.arrow.circlepath", color: .secondary, title: "History URLs", detail: "Cursor browser does not expose a history store", muted: true) {
|
|
647
|
+
FixedBadge("Excluded")
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
PreferenceRow(icon: "clock.arrow.circlepath", color: Theme.accent, title: "History URLs", detail: "Original visit times are not preserved") {
|
|
651
|
+
Toggle("", isOn: Binding(get: { model.historyEnabled }, set: { model.setHistoryEnabled($0) }))
|
|
652
|
+
.labelsHidden().toggleStyle(.switch).tint(Theme.active).disabled(model.isWorking)
|
|
653
|
+
}
|
|
588
654
|
}
|
|
589
655
|
RowDivider()
|
|
590
656
|
PreferenceRow(icon: "key.slash", color: .secondary, title: "Passwords", detail: "Browser extensions cannot access passwords", muted: true) {
|
|
@@ -617,8 +683,8 @@ struct PreferencesPanel: View {
|
|
|
617
683
|
}
|
|
618
684
|
|
|
619
685
|
SectionLabel(title: "Automation", detail: "Runs in the background", separated: true)
|
|
620
|
-
PreferenceRow(icon: "clock.badge.checkmark", color: Theme.accent, title: "Daily sync", detail: model.isBrowserlessTarget ? "Unavailable for
|
|
621
|
-
if model.isBrowserlessTarget {
|
|
686
|
+
PreferenceRow(icon: "clock.badge.checkmark", color: Theme.accent, title: "Daily sync", detail: model.isBrowserlessTarget || model.isGrokBotTarget ? "Unavailable for this destination" : (model.dailyEnabled ? "At the selected local time" : "Off")) {
|
|
687
|
+
if model.isBrowserlessTarget || model.isGrokBotTarget {
|
|
622
688
|
FixedBadge("Manual only")
|
|
623
689
|
} else {
|
|
624
690
|
HStack(spacing: 8) {
|
|
@@ -637,8 +703,8 @@ struct PreferencesPanel: View {
|
|
|
637
703
|
}
|
|
638
704
|
}
|
|
639
705
|
RowDivider()
|
|
640
|
-
PreferenceRow(icon: "sunrise.fill", color: Theme.accent, title: "Sync at login", detail: model.isBrowserlessTarget ? "Unavailable for
|
|
641
|
-
if model.isBrowserlessTarget {
|
|
706
|
+
PreferenceRow(icon: "sunrise.fill", color: Theme.accent, title: "Sync at login", detail: model.isBrowserlessTarget || model.isGrokBotTarget ? "Unavailable for this destination" : (model.loginSyncEnabled ? "Once whenever you sign in" : "Off")) {
|
|
707
|
+
if model.isBrowserlessTarget || model.isGrokBotTarget {
|
|
642
708
|
FixedBadge("Manual only")
|
|
643
709
|
} else {
|
|
644
710
|
Toggle("", isOn: Binding(get: { model.loginSyncEnabled }, set: { model.setLoginSyncEnabled($0) }))
|
|
@@ -675,8 +741,6 @@ struct PreferencesPanel: View {
|
|
|
675
741
|
ProjectLinkRow(icon: "heart", title: "Support on Ko-fi", detail: "Sponsor development", url: ProjectLinks.koFi)
|
|
676
742
|
RowDivider()
|
|
677
743
|
ProjectLinkRow(icon: "person.crop.circle.badge.plus", title: "Follow @apoorvdarshan on X", detail: "Developer updates", url: ProjectLinks.x)
|
|
678
|
-
RowDivider()
|
|
679
|
-
ProjectLinkRow(icon: "megaphone", title: "View on Product Hunt", detail: "Follow the launch and leave feedback", url: ProjectLinks.productHunt)
|
|
680
744
|
}
|
|
681
745
|
}
|
|
682
746
|
}
|
|
@@ -867,7 +931,7 @@ struct ExtensionSetupSheet: View {
|
|
|
867
931
|
Button("Open page") { model.openExtensions(for: model.selectedSourceID) }
|
|
868
932
|
Button("Show folder") { model.revealExtension(model.selectedSourceID) }
|
|
869
933
|
}
|
|
870
|
-
if model.
|
|
934
|
+
if !model.isDirectTarget && !model.isGrokBotTarget {
|
|
871
935
|
Divider().padding(.leading, 58)
|
|
872
936
|
SetupEndpointRow(icon: model.targetIcon, title: model.targetName, detail: "Load the destination extension") {
|
|
873
937
|
Button("Open page") { model.openExtensions(for: model.selectedTargetID) }
|
|
@@ -878,8 +942,10 @@ struct ExtensionSetupSheet: View {
|
|
|
878
942
|
.background(Color(nsColor: .controlBackgroundColor).opacity(0.65), in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
|
879
943
|
.overlay(RoundedRectangle(cornerRadius: 12, style: .continuous).stroke(Color.primary.opacity(0.07)))
|
|
880
944
|
|
|
881
|
-
Text(model.
|
|
882
|
-
? "
|
|
945
|
+
Text(model.isDirectTarget
|
|
946
|
+
? "\(model.targetName) uses a direct local merge, so no extension is required. Quit \(model.targetName) before syncing. Password access is never requested."
|
|
947
|
+
: model.isGrokBotTarget
|
|
948
|
+
? "Grok Bot uses a one-time encrypted transfer file, so no extension is required. Create the .bcbx bundle, attach it to any Grok Bot, and paste the prompt."
|
|
883
949
|
: "In both endpoints, enable Developer mode and choose Load unpacked. Password access is never requested.")
|
|
884
950
|
.font(.system(size: 10.5))
|
|
885
951
|
.foregroundStyle(.secondary)
|
|
@@ -1093,3 +1159,73 @@ enum Theme {
|
|
|
1093
1159
|
static let accent = Color(red: 0.46, green: 0.46, blue: 0.48)
|
|
1094
1160
|
static let active = Color(red: 0.54, green: 0.12, blue: 0.18)
|
|
1095
1161
|
}
|
|
1162
|
+
|
|
1163
|
+
struct GrokBotResultSheet: View {
|
|
1164
|
+
@EnvironmentObject private var model: SyncModel
|
|
1165
|
+
@Environment(\.dismiss) private var dismiss
|
|
1166
|
+
|
|
1167
|
+
var body: some View {
|
|
1168
|
+
VStack(alignment: .leading, spacing: 16) {
|
|
1169
|
+
HStack(alignment: .top, spacing: 12) {
|
|
1170
|
+
Image(nsImage: model.grokBotIcon)
|
|
1171
|
+
.resizable()
|
|
1172
|
+
.scaledToFit()
|
|
1173
|
+
.frame(width: 24, height: 24)
|
|
1174
|
+
.frame(width: 40, height: 40)
|
|
1175
|
+
.background(Theme.accent.opacity(0.11), in: RoundedRectangle(cornerRadius: 11, style: .continuous))
|
|
1176
|
+
VStack(alignment: .leading, spacing: 3) {
|
|
1177
|
+
Text("Grok Bot transfer ready")
|
|
1178
|
+
.font(.system(size: 18, weight: .bold, design: .rounded))
|
|
1179
|
+
Text(URL(fileURLWithPath: model.grokBotOutputPath).lastPathComponent)
|
|
1180
|
+
.font(.system(size: 11.5))
|
|
1181
|
+
.foregroundStyle(.secondary)
|
|
1182
|
+
}
|
|
1183
|
+
Spacer()
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
1187
|
+
Text("One-time decryption key")
|
|
1188
|
+
.font(.system(size: 11, weight: .semibold))
|
|
1189
|
+
.foregroundStyle(.secondary)
|
|
1190
|
+
Text(model.grokBotPassphrase)
|
|
1191
|
+
.font(.system(size: 15, weight: .semibold, design: .monospaced))
|
|
1192
|
+
.textSelection(.enabled)
|
|
1193
|
+
.padding(10)
|
|
1194
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1195
|
+
.background(Color.primary.opacity(0.04), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
1199
|
+
Text("Prompt for Grok Bot")
|
|
1200
|
+
.font(.system(size: 11, weight: .semibold))
|
|
1201
|
+
.foregroundStyle(.secondary)
|
|
1202
|
+
ScrollView {
|
|
1203
|
+
Text(model.grokBotPrompt)
|
|
1204
|
+
.font(.system(size: 11))
|
|
1205
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1206
|
+
.textSelection(.enabled)
|
|
1207
|
+
}
|
|
1208
|
+
.frame(height: 180)
|
|
1209
|
+
.padding(10)
|
|
1210
|
+
.background(Color.primary.opacity(0.04), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
HStack {
|
|
1214
|
+
Button("Reveal file") {
|
|
1215
|
+
let url = URL(fileURLWithPath: model.grokBotOutputPath)
|
|
1216
|
+
NSWorkspace.shared.activateFileViewerSelecting([url])
|
|
1217
|
+
}
|
|
1218
|
+
Button("Copy prompt") {
|
|
1219
|
+
NSPasteboard.general.clearContents()
|
|
1220
|
+
NSPasteboard.general.setString(model.grokBotPrompt, forType: .string)
|
|
1221
|
+
}
|
|
1222
|
+
Spacer()
|
|
1223
|
+
Button("Done") { dismiss() }
|
|
1224
|
+
.keyboardShortcut(.defaultAction)
|
|
1225
|
+
}
|
|
1226
|
+
.controlSize(.regular)
|
|
1227
|
+
}
|
|
1228
|
+
.padding(20)
|
|
1229
|
+
.frame(width: 520)
|
|
1230
|
+
}
|
|
1231
|
+
}
|