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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { readChromiumProfile } from "./chromium-reader.js";
|
|
6
|
+
import { createZipArchive, readZipArchive } from "./zip-store.js";
|
|
7
|
+
|
|
8
|
+
export const BUNDLE_EXTENSION = "bcbx";
|
|
9
|
+
export const DEFAULT_BUNDLE_NAME = "GrokBot-Import.bcbx";
|
|
10
|
+
export const GROK_BOT_PROMPT = `On your Grok Bot cloud computer only — do not access my local Mac and do not print cookie values.
|
|
11
|
+
|
|
12
|
+
1. Save the attached GrokBot-Import.bcbx to the cloud computer.
|
|
13
|
+
2. Unzip it: unzip -o GrokBot-Import.bcbx -d bcb-import && cd bcb-import
|
|
14
|
+
3. Run: node import.mjs
|
|
15
|
+
4. When prompted, ask me to take over and type the one-time decryption key privately through Agent Computer.
|
|
16
|
+
5. Report only how many cookies were imported per domain, then delete the bcb-import folder and any copies of the bundle.`;
|
|
17
|
+
|
|
18
|
+
const KDF = { name: "scrypt", N: 16384, r: 8, p: 1, keyLength: 32 };
|
|
19
|
+
const SAME_SITE = {
|
|
20
|
+
unspecified: "unspecified",
|
|
21
|
+
no_restriction: "no_restriction",
|
|
22
|
+
lax: "lax",
|
|
23
|
+
strict: "strict",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function buildGrokBotBundle({
|
|
27
|
+
cookies,
|
|
28
|
+
sourceBrowser,
|
|
29
|
+
onlyDomains = [],
|
|
30
|
+
passphrase = generatePassphrase(),
|
|
31
|
+
now = new Date(),
|
|
32
|
+
importerSource = defaultImporterSource(),
|
|
33
|
+
} = {}) {
|
|
34
|
+
if (!Array.isArray(cookies)) throw new Error("Cookie export requires a cookie list.");
|
|
35
|
+
const filtered = filterCookies(cookies, onlyDomains);
|
|
36
|
+
const exportCookies = filtered.map(normalizeExportCookie).filter(Boolean);
|
|
37
|
+
if (exportCookies.length === 0) {
|
|
38
|
+
throw new Error(onlyDomains.length
|
|
39
|
+
? "No cookies matched the selected domains."
|
|
40
|
+
: "No exportable cookies were found in the source browser.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const salt = crypto.randomBytes(16);
|
|
44
|
+
const iv = crypto.randomBytes(12);
|
|
45
|
+
const key = crypto.scryptSync(passphrase, salt, KDF.keyLength, { N: KDF.N, r: KDF.r, p: KDF.p });
|
|
46
|
+
const payload = Buffer.from(JSON.stringify({ cookies: exportCookies }), "utf8");
|
|
47
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
48
|
+
const encrypted = Buffer.concat([cipher.update(payload), cipher.final(), cipher.getAuthTag()]);
|
|
49
|
+
|
|
50
|
+
const domains = uniqueDomains(exportCookies);
|
|
51
|
+
const manifest = {
|
|
52
|
+
format: "browser-cookie-bridge-grok-bot",
|
|
53
|
+
version: 1,
|
|
54
|
+
createdAt: now.toISOString(),
|
|
55
|
+
sourceBrowser,
|
|
56
|
+
cookieCount: exportCookies.length,
|
|
57
|
+
domainCount: domains.length,
|
|
58
|
+
domains,
|
|
59
|
+
salt: salt.toString("base64url"),
|
|
60
|
+
iv: iv.toString("base64url"),
|
|
61
|
+
authTag: encrypted.subarray(encrypted.length - 16).toString("base64url"),
|
|
62
|
+
kdf: KDF,
|
|
63
|
+
};
|
|
64
|
+
const payloadBody = encrypted.subarray(0, encrypted.length - 16);
|
|
65
|
+
|
|
66
|
+
const archive = createZipArchive([
|
|
67
|
+
{ name: "manifest.json", data: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8") },
|
|
68
|
+
{ name: "payload.enc", data: payloadBody },
|
|
69
|
+
{ name: "import.mjs", data: Buffer.from(importerSource, "utf8") },
|
|
70
|
+
{ name: "PROMPT.txt", data: Buffer.from(`${GROK_BOT_PROMPT}\n`, "utf8") },
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
archive,
|
|
75
|
+
manifest,
|
|
76
|
+
passphrase,
|
|
77
|
+
cookieCount: exportCookies.length,
|
|
78
|
+
domainCount: domains.length,
|
|
79
|
+
domains,
|
|
80
|
+
sourceBrowser,
|
|
81
|
+
bundleName: DEFAULT_BUNDLE_NAME,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function writeGrokBotBundle({
|
|
86
|
+
outputPath,
|
|
87
|
+
cookies,
|
|
88
|
+
sourceBrowser,
|
|
89
|
+
onlyDomains = [],
|
|
90
|
+
passphrase,
|
|
91
|
+
} = {}) {
|
|
92
|
+
const resolved = path.resolve(outputPath || DEFAULT_BUNDLE_NAME);
|
|
93
|
+
if (!resolved.endsWith(`.${BUNDLE_EXTENSION}`)) {
|
|
94
|
+
throw new Error(`Grok Bot bundles must use the .${BUNDLE_EXTENSION} extension.`);
|
|
95
|
+
}
|
|
96
|
+
const bundle = buildGrokBotBundle({ cookies, sourceBrowser, onlyDomains, passphrase });
|
|
97
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
98
|
+
fs.writeFileSync(resolved, bundle.archive);
|
|
99
|
+
fs.chmodSync(resolved, 0o600);
|
|
100
|
+
return {
|
|
101
|
+
outputPath: resolved,
|
|
102
|
+
passphrase: bundle.passphrase,
|
|
103
|
+
cookieCount: bundle.cookieCount,
|
|
104
|
+
domainCount: bundle.domainCount,
|
|
105
|
+
domains: bundle.domains,
|
|
106
|
+
sourceBrowser: bundle.sourceBrowser,
|
|
107
|
+
prompt: GROK_BOT_PROMPT,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function exportGrokBotBundleFromProfile({
|
|
112
|
+
browser,
|
|
113
|
+
onlyDomains = [],
|
|
114
|
+
outputPath,
|
|
115
|
+
passphrase,
|
|
116
|
+
} = {}) {
|
|
117
|
+
const payload = readChromiumProfile({
|
|
118
|
+
browser,
|
|
119
|
+
imports: { cookies: true, history: false },
|
|
120
|
+
});
|
|
121
|
+
const result = writeGrokBotBundle({
|
|
122
|
+
outputPath,
|
|
123
|
+
cookies: payload.cookies,
|
|
124
|
+
sourceBrowser: browser,
|
|
125
|
+
onlyDomains,
|
|
126
|
+
passphrase,
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
...result,
|
|
130
|
+
sourceCookieSkipped: payload.cookieStats.skipped,
|
|
131
|
+
sourceCookieTotal: payload.cookieStats.total,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function parseGrokBotBundle(buffer) {
|
|
136
|
+
const entries = readZipArchive(buffer);
|
|
137
|
+
const manifest = JSON.parse(entries.get("manifest.json").toString("utf8"));
|
|
138
|
+
return {
|
|
139
|
+
manifest,
|
|
140
|
+
payload: entries.get("payload.enc"),
|
|
141
|
+
importer: entries.get("import.mjs")?.toString("utf8") || "",
|
|
142
|
+
prompt: entries.get("PROMPT.txt")?.toString("utf8") || "",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function grokBotSummary(result) {
|
|
147
|
+
const domainNote = result.domainCount === 1 ? "1 domain" : `${result.domainCount} domains`;
|
|
148
|
+
const skipped = result.sourceCookieSkipped
|
|
149
|
+
? ` ${result.sourceCookieSkipped} source cookie${result.sourceCookieSkipped === 1 ? " was" : "s were"} unreadable or unsupported.`
|
|
150
|
+
: "";
|
|
151
|
+
return `Grok Bot transfer file created: ${result.cookieCount} cookies across ${domainNote} from ${result.sourceBrowser}.${skipped} Attach ${path.basename(result.outputPath)} to any Grok Bot, paste the prompt, then enter the one-time key privately when asked.`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function filterCookies(cookies, onlyDomains = []) {
|
|
155
|
+
const domains = normalizeDomainFilters(onlyDomains);
|
|
156
|
+
if (!domains.length) return cookies;
|
|
157
|
+
return cookies.filter((cookie) => cookieMatchesDomains(cookie, domains));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function normalizeDomainFilters(onlyDomains) {
|
|
161
|
+
const items = Array.isArray(onlyDomains) ? onlyDomains : String(onlyDomains || "").split(",");
|
|
162
|
+
return [...new Set(items.map((item) => String(item).trim().toLowerCase()).filter(Boolean))];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function cookieMatchesDomains(cookie, domains) {
|
|
166
|
+
const host = String(cookie?.domain || "").replace(/^\./, "").toLowerCase();
|
|
167
|
+
if (!host) return false;
|
|
168
|
+
return domains.some((domain) => host === domain || host.endsWith(`.${domain}`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizeExportCookie(cookie) {
|
|
172
|
+
if (!cookie || typeof cookie.name !== "string" || typeof cookie.value !== "string") return null;
|
|
173
|
+
if (typeof cookie.domain !== "string") return null;
|
|
174
|
+
const host = cookie.domain.replace(/^\./, "").trim().toLowerCase();
|
|
175
|
+
if (!host || host.includes("/") || host.includes(":")) return null;
|
|
176
|
+
const persistent = !cookie.session && Number.isFinite(cookie.expirationDate) && cookie.expirationDate > 0;
|
|
177
|
+
return {
|
|
178
|
+
name: cookie.name,
|
|
179
|
+
value: cookie.value,
|
|
180
|
+
domain: cookie.hostOnly ? host : `.${host}`,
|
|
181
|
+
path: typeof cookie.path === "string" && cookie.path.startsWith("/") ? cookie.path : "/",
|
|
182
|
+
secure: Boolean(cookie.secure),
|
|
183
|
+
httpOnly: Boolean(cookie.httpOnly),
|
|
184
|
+
sameSite: SAME_SITE[cookie.sameSite] || "unspecified",
|
|
185
|
+
...(persistent ? { expires: Math.trunc(cookie.expirationDate) } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function uniqueDomains(cookies) {
|
|
190
|
+
return [...new Set(cookies.map((cookie) => cookie.domain.replace(/^\./, "").toLowerCase()))].sort();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function generatePassphrase() {
|
|
194
|
+
return crypto.randomBytes(18).toString("base64url");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function defaultImporterSource() {
|
|
198
|
+
return fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "grok-bot-importer.mjs"), "utf8");
|
|
199
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-time Grok Bot cookie importer for Browser Cookie Bridge bundles.
|
|
4
|
+
* Run on the Grok Bot cloud computer only. Never log cookie names or values.
|
|
5
|
+
*/
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import readline from "node:readline/promises";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
12
|
+
|
|
13
|
+
const CDP_PORTS = [9222, 9223, 9224, 9228, 9229, 9400];
|
|
14
|
+
const SAME_SITE = {
|
|
15
|
+
unspecified: "Lax",
|
|
16
|
+
no_restriction: "None",
|
|
17
|
+
lax: "Lax",
|
|
18
|
+
strict: "Strict",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
22
|
+
const bundlePath = path.resolve(argv[0] || "GrokBot-Import.bcbx");
|
|
23
|
+
const bundleDir = path.dirname(bundlePath);
|
|
24
|
+
const manifestPath = path.join(bundleDir, "manifest.json");
|
|
25
|
+
const payloadPath = path.join(bundleDir, "payload.enc");
|
|
26
|
+
if (!fs.existsSync(manifestPath) || !fs.existsSync(payloadPath)) {
|
|
27
|
+
throw new Error("Expected manifest.json and payload.enc next to the bundle. Unzip GrokBot-Import.bcbx first.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
31
|
+
validateManifest(manifest);
|
|
32
|
+
const passphrase = await readPassphrase();
|
|
33
|
+
const cookies = decryptPayload({
|
|
34
|
+
manifest,
|
|
35
|
+
encrypted: fs.readFileSync(payloadPath),
|
|
36
|
+
passphrase,
|
|
37
|
+
});
|
|
38
|
+
const endpoint = await findDevToolsEndpoint();
|
|
39
|
+
const imported = await injectCookies(endpoint.webSocketDebuggerUrl, cookies);
|
|
40
|
+
reportSummary(imported, manifest.domains || []);
|
|
41
|
+
cleanup(bundleDir, bundlePath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function validateManifest(manifest) {
|
|
45
|
+
if (manifest?.format !== "browser-cookie-bridge-grok-bot" || manifest?.version !== 1) {
|
|
46
|
+
throw new Error("Unsupported Browser Cookie Bridge Grok Bot bundle.");
|
|
47
|
+
}
|
|
48
|
+
for (const key of ["salt", "iv", "authTag"]) {
|
|
49
|
+
if (typeof manifest[key] !== "string" || !manifest[key]) {
|
|
50
|
+
throw new Error(`Bundle manifest is missing ${key}.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function decryptPayload({ manifest, encrypted, passphrase }) {
|
|
56
|
+
const key = crypto.scryptSync(
|
|
57
|
+
passphrase,
|
|
58
|
+
Buffer.from(manifest.salt, "base64url"),
|
|
59
|
+
32,
|
|
60
|
+
{ N: manifest.kdf?.N ?? 16384, r: manifest.kdf?.r ?? 8, p: manifest.kdf?.p ?? 1 },
|
|
61
|
+
);
|
|
62
|
+
const decipher = crypto.createDecipheriv(
|
|
63
|
+
"aes-256-gcm",
|
|
64
|
+
key,
|
|
65
|
+
Buffer.from(manifest.iv, "base64url"),
|
|
66
|
+
);
|
|
67
|
+
decipher.setAuthTag(Buffer.from(manifest.authTag, "base64url"));
|
|
68
|
+
const plaintext = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
69
|
+
const payload = JSON.parse(plaintext.toString("utf8"));
|
|
70
|
+
if (!Array.isArray(payload.cookies)) throw new Error("Decrypted bundle did not contain cookies.");
|
|
71
|
+
return payload.cookies;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function findDevToolsEndpoint() {
|
|
75
|
+
for (const port of CDP_PORTS) {
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(1500) });
|
|
78
|
+
if (!response.ok) continue;
|
|
79
|
+
const body = await response.json();
|
|
80
|
+
if (typeof body?.webSocketDebuggerUrl === "string") {
|
|
81
|
+
return { port, webSocketDebuggerUrl: body.webSocketDebuggerUrl, browser: body.Browser || "Chrome" };
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Try the next port.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new Error("No local Chrome DevTools endpoint responded. Run the safe probe first.");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function injectCookies(webSocketDebuggerUrl, cookies) {
|
|
91
|
+
const ws = new WebSocket(webSocketDebuggerUrl);
|
|
92
|
+
await waitForOpen(ws);
|
|
93
|
+
let nextId = 1;
|
|
94
|
+
const cdpCookies = cookies.map(toCdpCookie);
|
|
95
|
+
const chunks = chunk(cdpCookies, 40);
|
|
96
|
+
let imported = 0;
|
|
97
|
+
for (const batch of chunks) {
|
|
98
|
+
const id = nextId++;
|
|
99
|
+
const response = await sendCommand(ws, { id, method: "Storage.setCookies", params: { cookies: batch } });
|
|
100
|
+
if (response.error) throw new Error(`Cookie injection failed: ${response.error.message}`);
|
|
101
|
+
imported += batch.length;
|
|
102
|
+
}
|
|
103
|
+
ws.close();
|
|
104
|
+
return { imported, batches: chunks.length };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toCdpCookie(cookie) {
|
|
108
|
+
const entry = {
|
|
109
|
+
name: cookie.name,
|
|
110
|
+
value: cookie.value,
|
|
111
|
+
domain: cookie.domain,
|
|
112
|
+
path: cookie.path || "/",
|
|
113
|
+
secure: Boolean(cookie.secure),
|
|
114
|
+
httpOnly: Boolean(cookie.httpOnly),
|
|
115
|
+
sameSite: SAME_SITE[cookie.sameSite] || cookie.sameSite || "Lax",
|
|
116
|
+
};
|
|
117
|
+
if (Number.isFinite(cookie.expires) && cookie.expires > 0) entry.expires = cookie.expires;
|
|
118
|
+
return entry;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function chunk(items, size) {
|
|
122
|
+
const groups = [];
|
|
123
|
+
for (let index = 0; index < items.length; index += size) {
|
|
124
|
+
groups.push(items.slice(index, index + size));
|
|
125
|
+
}
|
|
126
|
+
return groups;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function readPassphrase() {
|
|
130
|
+
if (process.env.BCB_IMPORT_KEY?.trim()) return process.env.BCB_IMPORT_KEY.trim();
|
|
131
|
+
const rl = readline.createInterface({ input, output });
|
|
132
|
+
try {
|
|
133
|
+
return (await rl.question("One-time decryption key: ")).trim();
|
|
134
|
+
} finally {
|
|
135
|
+
rl.close();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function waitForOpen(ws) {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
ws.addEventListener("open", () => resolve(), { once: true });
|
|
142
|
+
ws.addEventListener("error", (event) => reject(event.error || new Error("DevTools connection failed")), { once: true });
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function sendCommand(ws, message) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const timeout = setTimeout(() => reject(new Error("DevTools command timed out")), 15000);
|
|
149
|
+
const onMessage = (event) => {
|
|
150
|
+
const payload = JSON.parse(String(event.data));
|
|
151
|
+
if (payload.id !== message.id) return;
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
ws.removeEventListener("message", onMessage);
|
|
154
|
+
resolve(payload);
|
|
155
|
+
};
|
|
156
|
+
ws.addEventListener("message", onMessage);
|
|
157
|
+
ws.send(JSON.stringify(message));
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function reportSummary(result, domains) {
|
|
162
|
+
const domainNote = domains.length ? `${domains.length} selected domain${domains.length === 1 ? "" : "s"}` : "all exported domains";
|
|
163
|
+
console.log(`Imported ${result.imported} cookies across ${domainNote} in ${result.batches} batch${result.batches === 1 ? "" : "es"}.`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function cleanup(bundleDir, bundlePath) {
|
|
167
|
+
for (const name of ["manifest.json", "payload.enc", "import.mjs", "PROMPT.txt"]) {
|
|
168
|
+
fs.rmSync(path.join(bundleDir, name), { force: true });
|
|
169
|
+
}
|
|
170
|
+
fs.rmSync(bundlePath, { force: true });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
174
|
+
main().catch((error) => {
|
|
175
|
+
console.error(error.message || String(error));
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
});
|
|
178
|
+
}
|
package/src/paths.js
CHANGED
|
@@ -11,7 +11,7 @@ export const DEFAULT_PORT = 43128;
|
|
|
11
11
|
export const EXTENSION_ID = "ihanfnkcipmlhmokbcinlkdfcfheofjb";
|
|
12
12
|
export const EXTENSION_ORIGIN = `chrome-extension://${EXTENSION_ID}`;
|
|
13
13
|
export const SOURCE_BROWSERS = ["brave", "chrome", "edge", "arc", "vivaldi", "opera", "comet"];
|
|
14
|
-
export const TARGET_BROWSERS = [...SOURCE_BROWSERS, "codex", "browserless"];
|
|
14
|
+
export const TARGET_BROWSERS = [...SOURCE_BROWSERS, "codex", "cursor", "browserless", "grok-bot"];
|
|
15
15
|
|
|
16
16
|
export function projectRoot() {
|
|
17
17
|
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -91,3 +91,8 @@ export function codexCookiePaths(home = os.homedir()) {
|
|
|
91
91
|
path.join(root, "codex-browser-app", "Cookies"),
|
|
92
92
|
];
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
export function cursorCookiePaths(home = os.homedir()) {
|
|
96
|
+
const root = path.join(home, "Library", "Application Support", "Cursor", "Partitions", "cursor-browser");
|
|
97
|
+
return [path.join(root, "Cookies")];
|
|
98
|
+
}
|
package/src/zip-store.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import crc32 from "./crc32.js";
|
|
2
|
+
|
|
3
|
+
const LOCAL_HEADER_SIGNATURE = 0x04034b50;
|
|
4
|
+
const CENTRAL_DIRECTORY_SIGNATURE = 0x02014b50;
|
|
5
|
+
const END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50;
|
|
6
|
+
|
|
7
|
+
export function createZipArchive(entries) {
|
|
8
|
+
const localParts = [];
|
|
9
|
+
const centralParts = [];
|
|
10
|
+
let offset = 0;
|
|
11
|
+
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const name = Buffer.from(entry.name, "utf8");
|
|
14
|
+
const data = Buffer.isBuffer(entry.data) ? entry.data : Buffer.from(String(entry.data), "utf8");
|
|
15
|
+
const checksum = crc32(data);
|
|
16
|
+
const localHeader = Buffer.alloc(30 + name.length);
|
|
17
|
+
localHeader.writeUInt32LE(LOCAL_HEADER_SIGNATURE, 0);
|
|
18
|
+
localHeader.writeUInt16LE(20, 4);
|
|
19
|
+
localHeader.writeUInt16LE(0, 6);
|
|
20
|
+
localHeader.writeUInt16LE(0, 8);
|
|
21
|
+
localHeader.writeUInt16LE(0, 10);
|
|
22
|
+
localHeader.writeUInt16LE(0, 12);
|
|
23
|
+
localHeader.writeUInt32LE(checksum, 14);
|
|
24
|
+
localHeader.writeUInt32LE(data.length, 18);
|
|
25
|
+
localHeader.writeUInt32LE(data.length, 22);
|
|
26
|
+
localHeader.writeUInt16LE(name.length, 26);
|
|
27
|
+
localHeader.writeUInt16LE(0, 28);
|
|
28
|
+
name.copy(localHeader, 30);
|
|
29
|
+
|
|
30
|
+
const centralHeader = Buffer.alloc(46 + name.length);
|
|
31
|
+
centralHeader.writeUInt32LE(CENTRAL_DIRECTORY_SIGNATURE, 0);
|
|
32
|
+
centralHeader.writeUInt16LE(20, 4);
|
|
33
|
+
centralHeader.writeUInt16LE(20, 6);
|
|
34
|
+
centralHeader.writeUInt16LE(0, 8);
|
|
35
|
+
centralHeader.writeUInt16LE(0, 10);
|
|
36
|
+
centralHeader.writeUInt16LE(0, 12);
|
|
37
|
+
centralHeader.writeUInt16LE(0, 14);
|
|
38
|
+
centralHeader.writeUInt32LE(checksum, 16);
|
|
39
|
+
centralHeader.writeUInt32LE(data.length, 20);
|
|
40
|
+
centralHeader.writeUInt32LE(data.length, 24);
|
|
41
|
+
centralHeader.writeUInt16LE(name.length, 28);
|
|
42
|
+
centralHeader.writeUInt16LE(0, 30);
|
|
43
|
+
centralHeader.writeUInt16LE(0, 32);
|
|
44
|
+
centralHeader.writeUInt16LE(0, 34);
|
|
45
|
+
centralHeader.writeUInt16LE(0, 36);
|
|
46
|
+
centralHeader.writeUInt32LE(0, 38);
|
|
47
|
+
centralHeader.writeUInt32LE(offset, 42);
|
|
48
|
+
name.copy(centralHeader, 46);
|
|
49
|
+
|
|
50
|
+
localParts.push(localHeader, data);
|
|
51
|
+
centralParts.push(centralHeader);
|
|
52
|
+
offset += localHeader.length + data.length;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const centralDirectory = Buffer.concat(centralParts);
|
|
56
|
+
const endRecord = Buffer.alloc(22);
|
|
57
|
+
endRecord.writeUInt32LE(END_OF_CENTRAL_DIRECTORY_SIGNATURE, 0);
|
|
58
|
+
endRecord.writeUInt16LE(0, 4);
|
|
59
|
+
endRecord.writeUInt16LE(0, 6);
|
|
60
|
+
endRecord.writeUInt16LE(entries.length, 8);
|
|
61
|
+
endRecord.writeUInt16LE(entries.length, 10);
|
|
62
|
+
endRecord.writeUInt32LE(centralDirectory.length, 12);
|
|
63
|
+
endRecord.writeUInt32LE(offset, 16);
|
|
64
|
+
endRecord.writeUInt16LE(0, 20);
|
|
65
|
+
|
|
66
|
+
return Buffer.concat([...localParts, centralDirectory, endRecord]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function readZipArchive(buffer) {
|
|
70
|
+
const source = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
71
|
+
const entries = new Map();
|
|
72
|
+
let offset = 0;
|
|
73
|
+
|
|
74
|
+
while (offset + 30 <= source.length) {
|
|
75
|
+
const signature = source.readUInt32LE(offset);
|
|
76
|
+
if (signature === END_OF_CENTRAL_DIRECTORY_SIGNATURE || signature === CENTRAL_DIRECTORY_SIGNATURE) break;
|
|
77
|
+
if (signature !== LOCAL_HEADER_SIGNATURE) {
|
|
78
|
+
throw new Error("Unsupported zip archive for Grok Bot bundle.");
|
|
79
|
+
}
|
|
80
|
+
const compressedSize = source.readUInt32LE(offset + 18);
|
|
81
|
+
const nameLength = source.readUInt16LE(offset + 26);
|
|
82
|
+
const extraLength = source.readUInt16LE(offset + 28);
|
|
83
|
+
const name = source.subarray(offset + 30, offset + 30 + nameLength).toString("utf8");
|
|
84
|
+
const dataStart = offset + 30 + nameLength + extraLength;
|
|
85
|
+
const dataEnd = dataStart + compressedSize;
|
|
86
|
+
entries.set(name, Buffer.from(source.subarray(dataStart, dataEnd)));
|
|
87
|
+
offset = dataEnd;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (entries.size === 0) throw new Error("Grok Bot bundle archive was empty.");
|
|
91
|
+
return entries;
|
|
92
|
+
}
|