browser-cookie-bridge 1.2.0 → 1.4.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 +14 -7
- package/bin/brave-codex-cookie-sync.js +10 -2
- package/extension-template/manifest.json +1 -1
- package/macos-app/Info.plist +2 -2
- package/macos-app/Sources/BraveCodexSyncApp/BraveCodexSyncApp.swift +152 -31
- package/macos-app/Sources/BraveCodexSyncApp/SyncModel.swift +339 -15
- package/package.json +2 -2
- package/src/browserless-preflight.js +113 -0
- package/src/browserless.js +184 -11
- package/src/cli.js +40 -6
- package/src/config.js +3 -0
package/src/browserless.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
1
3
|
import path from "node:path";
|
|
2
4
|
import { spawn } from "node:child_process";
|
|
3
5
|
import { projectRoot } from "./paths.js";
|
|
@@ -13,7 +15,10 @@ export async function uploadBrowserlessProfile({
|
|
|
13
15
|
onlyDomains = [],
|
|
14
16
|
token = process.env.BROWSERLESS_TOKEN,
|
|
15
17
|
root = projectRoot(),
|
|
16
|
-
runner =
|
|
18
|
+
runner = runBrowserlessCLI,
|
|
19
|
+
signal,
|
|
20
|
+
timeoutMs = 15 * 60 * 1000,
|
|
21
|
+
onProgress = () => {},
|
|
17
22
|
} = {}) {
|
|
18
23
|
if (!SUPPORTED_SOURCES.has(browser)) {
|
|
19
24
|
throw new Error(`${browser === "comet" ? "Comet" : browser} is not supported by Browserless profile capture yet.`);
|
|
@@ -34,7 +39,15 @@ export async function uploadBrowserlessProfile({
|
|
|
34
39
|
DO_NOT_TRACK: "1",
|
|
35
40
|
};
|
|
36
41
|
const client = ["--region", region, "--json"];
|
|
37
|
-
|
|
42
|
+
onProgress({ phase: "validating", fraction: 0.10, detail: "Checking the destination profile…" });
|
|
43
|
+
const shown = await runner(
|
|
44
|
+
cliPath,
|
|
45
|
+
["profile", "show", profileName.trim(), ...client],
|
|
46
|
+
environment,
|
|
47
|
+
runnerPath,
|
|
48
|
+
{ signal, timeoutMs: Math.min(timeoutMs, 60_000) },
|
|
49
|
+
);
|
|
50
|
+
throwForInterruptedRun(shown, timeoutMs);
|
|
38
51
|
const operation = shown.status === 0 ? "refresh" : isMissingProfile(shown.output) ? "upload" : null;
|
|
39
52
|
if (!operation) throw new Error(lastLine(shown.output) || "Browserless could not validate the cloud profile.");
|
|
40
53
|
|
|
@@ -48,8 +61,28 @@ export async function uploadBrowserlessProfile({
|
|
|
48
61
|
"--auto-fit",
|
|
49
62
|
];
|
|
50
63
|
for (const domain of onlyDomains) capture.push("--only-domain", domain);
|
|
51
|
-
const
|
|
52
|
-
|
|
64
|
+
const parseProgress = progressParser(onProgress);
|
|
65
|
+
const result = await runner(cliPath, capture, environment, runnerPath, {
|
|
66
|
+
signal,
|
|
67
|
+
timeoutMs,
|
|
68
|
+
onOutput: parseProgress,
|
|
69
|
+
});
|
|
70
|
+
parseProgress("\n");
|
|
71
|
+
throwForInterruptedRun(result, timeoutMs);
|
|
72
|
+
if (result.status !== 0) throw new Error(actionableFailure(result.output));
|
|
73
|
+
|
|
74
|
+
onProgress({ phase: "verifying", fraction: 0.97, detail: "Verifying the cloud profile…" });
|
|
75
|
+
const verified = await runner(
|
|
76
|
+
cliPath,
|
|
77
|
+
["profile", "show", profileName.trim(), ...client],
|
|
78
|
+
environment,
|
|
79
|
+
runnerPath,
|
|
80
|
+
{ signal, timeoutMs: Math.min(timeoutMs, 60_000) },
|
|
81
|
+
);
|
|
82
|
+
throwForInterruptedRun(verified, timeoutMs);
|
|
83
|
+
if (verified.status !== 0) {
|
|
84
|
+
throw new Error(`The upload finished, but Browserless could not verify the cloud profile: ${lastLine(verified.output) || "profile lookup failed"}`);
|
|
85
|
+
}
|
|
53
86
|
|
|
54
87
|
const details = parseJSON(result.output);
|
|
55
88
|
const cookies = details?.cookieCount;
|
|
@@ -59,28 +92,126 @@ export async function uploadBrowserlessProfile({
|
|
|
59
92
|
: "";
|
|
60
93
|
return {
|
|
61
94
|
operation,
|
|
95
|
+
verified: true,
|
|
62
96
|
profileName: details?.name || profileName.trim(),
|
|
63
97
|
cookieCount: cookies,
|
|
64
98
|
originCount: origins,
|
|
65
|
-
|
|
99
|
+
droppedOriginCount: droppedOrigins(result.output),
|
|
100
|
+
failedOriginCount: failedOrigins(result.output),
|
|
101
|
+
summary: uploadSummary({ operation, details, fallbackName: profileName.trim(), counts, output: result.output }),
|
|
66
102
|
};
|
|
67
103
|
}
|
|
68
104
|
|
|
69
|
-
function
|
|
105
|
+
export function runBrowserlessCLI(cliPath, args, environment, runnerPath, {
|
|
106
|
+
signal,
|
|
107
|
+
timeoutMs = 15 * 60 * 1000,
|
|
108
|
+
onOutput = () => {},
|
|
109
|
+
} = {}) {
|
|
70
110
|
return new Promise((resolve, reject) => {
|
|
111
|
+
const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "browser-cookie-bridge-browserless-"));
|
|
112
|
+
fs.chmodSync(temporaryRoot, 0o700);
|
|
71
113
|
const child = spawn(process.execPath, [runnerPath, cliPath, ...args], {
|
|
72
|
-
env: environment,
|
|
114
|
+
env: { ...environment, TMPDIR: temporaryRoot },
|
|
73
115
|
stdio: ["ignore", "pipe", "pipe"],
|
|
116
|
+
detached: process.platform !== "win32",
|
|
74
117
|
});
|
|
75
118
|
let stdout = "";
|
|
76
119
|
let stderr = "";
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
120
|
+
let interruption = null;
|
|
121
|
+
let settled = false;
|
|
122
|
+
const append = (target, chunk) => {
|
|
123
|
+
const text = String(chunk);
|
|
124
|
+
if (target === "stdout") stdout += text;
|
|
125
|
+
else stderr += text;
|
|
126
|
+
onOutput(text);
|
|
127
|
+
};
|
|
128
|
+
const terminate = (reason) => {
|
|
129
|
+
if (settled || interruption) return;
|
|
130
|
+
interruption = reason;
|
|
131
|
+
try {
|
|
132
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, "SIGTERM");
|
|
133
|
+
else child.kill("SIGTERM");
|
|
134
|
+
} catch {}
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
137
|
+
try {
|
|
138
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, "SIGKILL");
|
|
139
|
+
else child.kill("SIGKILL");
|
|
140
|
+
} catch {}
|
|
141
|
+
}, 2_000).unref();
|
|
142
|
+
};
|
|
143
|
+
const abort = () => terminate("canceled");
|
|
144
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
145
|
+
if (signal?.aborted) abort();
|
|
146
|
+
const timer = setTimeout(() => terminate("timedOut"), timeoutMs);
|
|
147
|
+
timer.unref();
|
|
148
|
+
child.stdout.on("data", (chunk) => append("stdout", chunk));
|
|
149
|
+
child.stderr.on("data", (chunk) => append("stderr", chunk));
|
|
150
|
+
child.on("error", (error) => {
|
|
151
|
+
settled = true;
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
signal?.removeEventListener("abort", abort);
|
|
154
|
+
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
|
155
|
+
reject(error);
|
|
156
|
+
});
|
|
157
|
+
child.on("close", (status) => {
|
|
158
|
+
settled = true;
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
signal?.removeEventListener("abort", abort);
|
|
161
|
+
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
|
162
|
+
resolve({
|
|
163
|
+
status: interruption === "canceled" ? 130 : interruption === "timedOut" ? 124 : (status ?? 1),
|
|
164
|
+
output: `${stderr}${stdout}`,
|
|
165
|
+
canceled: interruption === "canceled",
|
|
166
|
+
timedOut: interruption === "timedOut",
|
|
167
|
+
});
|
|
168
|
+
});
|
|
81
169
|
});
|
|
82
170
|
}
|
|
83
171
|
|
|
172
|
+
export function progressParser(onProgress) {
|
|
173
|
+
let pending = "";
|
|
174
|
+
let lastPhase = "";
|
|
175
|
+
return (chunk) => {
|
|
176
|
+
pending += chunk;
|
|
177
|
+
const lines = pending.split(/\r\n|\n|\r/);
|
|
178
|
+
pending = lines.pop() ?? "";
|
|
179
|
+
for (const raw of lines) {
|
|
180
|
+
const line = raw.trim();
|
|
181
|
+
let progress = null;
|
|
182
|
+
if (/copying profile data/i.test(line)) {
|
|
183
|
+
progress = { phase: "copying", fraction: 0.18, detail: "Copying profile data into an isolated workspace…" };
|
|
184
|
+
} else if (/launching headless browser/i.test(line)) {
|
|
185
|
+
progress = { phase: "launching", fraction: 0.30, detail: "Launching the temporary browser…" };
|
|
186
|
+
} else if (/waiting for browser to be ready/i.test(line)) {
|
|
187
|
+
progress = { phase: "waiting", fraction: 0.38, detail: "Waiting for the temporary browser…" };
|
|
188
|
+
} else if (/capturing per-origin storage/i.test(line)) {
|
|
189
|
+
progress = { phase: "capturing", fraction: 0.42, detail: "Capturing cookies, local storage, and IndexedDB…" };
|
|
190
|
+
} else if (/^\d+\/\d+\s+/.test(line)) {
|
|
191
|
+
const match = line.match(/^(\d+)\/(\d+)\s+(.+)$/);
|
|
192
|
+
if (match) {
|
|
193
|
+
const current = Number(match[1]);
|
|
194
|
+
const total = Number(match[2]);
|
|
195
|
+
progress = {
|
|
196
|
+
phase: "capturing",
|
|
197
|
+
fraction: total > 0 ? 0.42 + 0.42 * Math.min(current / total, 1) : 0.42,
|
|
198
|
+
current,
|
|
199
|
+
total,
|
|
200
|
+
detail: `Capturing ${match[3]}`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
} else if (/uploading to browserless/i.test(line)) {
|
|
204
|
+
progress = { phase: "uploading", fraction: 0.90, detail: "Uploading the fitted authenticated profile…" };
|
|
205
|
+
}
|
|
206
|
+
if (!progress) continue;
|
|
207
|
+
const identity = `${progress.phase}:${progress.current ?? ""}:${progress.total ?? ""}:${progress.detail}`;
|
|
208
|
+
if (identity === lastPhase) continue;
|
|
209
|
+
lastPhase = identity;
|
|
210
|
+
onProgress(progress);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
84
215
|
function isMissingProfile(output) {
|
|
85
216
|
return /(?:not found|does not exist|404)/i.test(output);
|
|
86
217
|
}
|
|
@@ -106,3 +237,45 @@ function parseJSON(output) {
|
|
|
106
237
|
function lastLine(output) {
|
|
107
238
|
return output.split("\n").map((line) => line.trim()).filter(Boolean).at(-1)?.replace(/^Error:\s*/, "");
|
|
108
239
|
}
|
|
240
|
+
|
|
241
|
+
function throwForInterruptedRun(result, timeoutMs) {
|
|
242
|
+
if (result?.canceled || result?.status === 130) {
|
|
243
|
+
throw new Error("Browserless upload canceled. Temporary profile data was removed.");
|
|
244
|
+
}
|
|
245
|
+
if (result?.timedOut || result?.status === 124) {
|
|
246
|
+
const minutes = Math.max(1, Math.round(timeoutMs / 60_000));
|
|
247
|
+
throw new Error(`Browserless upload timed out after ${minutes} minute${minutes === 1 ? "" : "s"}. Check the connection, close the source browser, and try again with a smaller domain allowlist.`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function droppedOrigins(output) {
|
|
252
|
+
return Number(output.match(/--auto-fit:\s*dropped\s+(\d+)\s+origin/i)?.[1] || 0);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function failedOrigins(output) {
|
|
256
|
+
return Number(output.match(/!\s+(\d+)\s+origin\(s\) failed to capture/i)?.[1] || 0);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function uploadSummary({ operation, details, fallbackName, counts, output }) {
|
|
260
|
+
const dropped = droppedOrigins(output);
|
|
261
|
+
const failed = failedOrigins(output);
|
|
262
|
+
const warnings = [];
|
|
263
|
+
if (dropped > 0) warnings.push(`${dropped} heavy origin${dropped === 1 ? "" : "s"} omitted to fit Browserless's 2 MB cap`);
|
|
264
|
+
if (failed > 0) warnings.push(`${failed} origin${failed === 1 ? "" : "s"} could not be captured`);
|
|
265
|
+
const warning = warnings.length > 0 ? `; ${warnings.join("; ")}` : "";
|
|
266
|
+
return `Browserless profile ${operation === "refresh" ? "updated" : "created"} and verified: ${details?.name || fallbackName}${counts}${warning}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function actionableFailure(output) {
|
|
270
|
+
const detail = lastLine(output) || "Browserless profile upload failed.";
|
|
271
|
+
if (/(?:failed to reach|enotfound|econnreset|econnrefused|network|socket hang up|fetch failed)/i.test(output)) {
|
|
272
|
+
return `Browserless could not be reached. Check your internet connection and region, then try again. ${detail}`;
|
|
273
|
+
}
|
|
274
|
+
if (/(?:profile busy|singletonlock|browser.*running|source browser must be closed)/i.test(output)) {
|
|
275
|
+
return `The source browser is still using this profile. Quit it completely, wait a few seconds, and try again. ${detail}`;
|
|
276
|
+
}
|
|
277
|
+
if (/(?:2 MB|too large|artifact.*cap|payload.*large)/i.test(output)) {
|
|
278
|
+
return `The captured state could not fit Browserless's 2 MB profile cap. Add a domain allowlist for the sites you need, then try again. ${detail}`;
|
|
279
|
+
}
|
|
280
|
+
return detail;
|
|
281
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "./codex-direct-import.js";
|
|
11
11
|
import { readChromiumProfile } from "./chromium-reader.js";
|
|
12
12
|
import { uploadBrowserlessProfile } from "./browserless.js";
|
|
13
|
+
import { inspectBrowserlessProfile } from "./browserless-preflight.js";
|
|
13
14
|
import { installConfig, installRuntime, readConfig, updatePreferences } from "./config.js";
|
|
14
15
|
import {
|
|
15
16
|
braveCookiePaths,
|
|
@@ -44,8 +45,9 @@ Commands:
|
|
|
44
45
|
setup [--hour 9] [--minute 0] [--no-schedule]
|
|
45
46
|
install-app [--no-open]
|
|
46
47
|
bootstrap-bundled --app-path /Applications/Browser Cookie Bridge.app
|
|
47
|
-
preferences --source brave --target codex --cookies on --history off --menu-bar on --auto-check-updates on
|
|
48
|
+
preferences --source brave --target codex --cookies on --history off --menu-bar on --auto-check-updates on --auto-restart-codex off
|
|
48
49
|
sync [--timeout 300] [--allow-cloud-upload]
|
|
50
|
+
browserless-preflight
|
|
49
51
|
doctor
|
|
50
52
|
enable-login-sync
|
|
51
53
|
disable-login-sync
|
|
@@ -55,13 +57,15 @@ Commands:
|
|
|
55
57
|
help
|
|
56
58
|
`;
|
|
57
59
|
|
|
58
|
-
export async function main(argv) {
|
|
60
|
+
export async function main(argv, { signal } = {}) {
|
|
59
61
|
const [command = "help", ...args] = argv;
|
|
60
62
|
switch (command) {
|
|
61
63
|
case "setup":
|
|
62
64
|
return setup(args);
|
|
63
65
|
case "sync":
|
|
64
|
-
return sync(args);
|
|
66
|
+
return sync(args, { signal });
|
|
67
|
+
case "browserless-preflight":
|
|
68
|
+
return browserlessPreflight();
|
|
65
69
|
case "install-app":
|
|
66
70
|
return installDesktopApp(args);
|
|
67
71
|
case "bootstrap-bundled":
|
|
@@ -124,12 +128,13 @@ function preferences(args) {
|
|
|
124
128
|
menuBar: booleanFlag(args, "--menu-bar", existing.ui?.menuBar === true),
|
|
125
129
|
openAtLogin: booleanFlag(args, "--open-at-login", existing.ui?.openAtLogin !== false),
|
|
126
130
|
autoCheckUpdates: booleanFlag(args, "--auto-check-updates", existing.ui?.autoCheckUpdates !== false),
|
|
131
|
+
autoRestartCodex: booleanFlag(args, "--auto-restart-codex", existing.ui?.autoRestartCodex === true),
|
|
127
132
|
browserlessProfileName: stringFlag(args, "--browserless-profile", existing.browserless?.profileName || "browser-cookie-bridge"),
|
|
128
133
|
browserlessRegion: stringFlag(args, "--browserless-region", existing.browserless?.region || "sfo"),
|
|
129
134
|
browserlessOnlyDomains: optionalStringFlag(args, "--browserless-domains", (existing.browserless?.onlyDomains || []).join(",")),
|
|
130
135
|
});
|
|
131
136
|
console.log(
|
|
132
|
-
`Saved: source=${config.sourceBrowser}, target=${config.targetBrowser}, cookies=${config.imports.cookies ? "on" : "off"}, history=${config.imports.history ? "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"}`,
|
|
137
|
+
`Saved: source=${config.sourceBrowser}, target=${config.targetBrowser}, cookies=${config.imports.cookies ? "on" : "off"}, history=${config.imports.history ? "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"}`,
|
|
133
138
|
);
|
|
134
139
|
}
|
|
135
140
|
|
|
@@ -259,11 +264,11 @@ function setup(args) {
|
|
|
259
264
|
: "Cookie values are transferred in memory and are not written to logs or disk.");
|
|
260
265
|
}
|
|
261
266
|
|
|
262
|
-
async function sync(args) {
|
|
267
|
+
async function sync(args, { signal } = {}) {
|
|
263
268
|
assertMacOS();
|
|
264
|
-
const seconds = integerFlag(args, "--timeout", 300, 5, 3600);
|
|
265
269
|
const config = readConfig();
|
|
266
270
|
const target = config.targetBrowser || "codex";
|
|
271
|
+
const seconds = integerFlag(args, "--timeout", target === "browserless" ? 900 : 300, 5, 3600);
|
|
267
272
|
const isCodexTarget = target === "codex";
|
|
268
273
|
if (target === "browserless") {
|
|
269
274
|
if (!args.includes("--allow-cloud-upload")) {
|
|
@@ -271,6 +276,15 @@ async function sync(args) {
|
|
|
271
276
|
}
|
|
272
277
|
const source = config.sourceBrowser || "brave";
|
|
273
278
|
const local = readChromiumProfile({ browser: source, imports: { cookies: false, history: false } });
|
|
279
|
+
emitBrowserlessProgress({ phase: "preflight", fraction: 0.03, detail: "Inspecting the local profile…" });
|
|
280
|
+
const assessment = inspectBrowserlessProfile({ profilePath: local.profilePath });
|
|
281
|
+
emitBrowserlessProgress({
|
|
282
|
+
phase: "preflight-complete",
|
|
283
|
+
fraction: 0.06,
|
|
284
|
+
detail: assessment.summary,
|
|
285
|
+
assessment,
|
|
286
|
+
});
|
|
287
|
+
console.log(`Profile preflight: ${assessment.summary}`);
|
|
274
288
|
console.log(`Preparing ${source} profile ${local.profileName} for an explicit Browserless cloud upload…`);
|
|
275
289
|
const result = await uploadBrowserlessProfile({
|
|
276
290
|
browser: source,
|
|
@@ -278,7 +292,11 @@ async function sync(args) {
|
|
|
278
292
|
profileName: config.browserless?.profileName || "browser-cookie-bridge",
|
|
279
293
|
region: config.browserless?.region || "sfo",
|
|
280
294
|
onlyDomains: config.browserless?.onlyDomains || [],
|
|
295
|
+
timeoutMs: seconds * 1000,
|
|
296
|
+
signal,
|
|
297
|
+
onProgress: emitBrowserlessProgress,
|
|
281
298
|
});
|
|
299
|
+
emitBrowserlessProgress({ phase: "complete", fraction: 1, detail: result.summary });
|
|
282
300
|
console.log(result.summary);
|
|
283
301
|
return result;
|
|
284
302
|
}
|
|
@@ -322,6 +340,21 @@ async function sync(args) {
|
|
|
322
340
|
return result;
|
|
323
341
|
}
|
|
324
342
|
|
|
343
|
+
function browserlessPreflight() {
|
|
344
|
+
assertMacOS();
|
|
345
|
+
const config = readConfig();
|
|
346
|
+
const source = config.sourceBrowser || "brave";
|
|
347
|
+
if (source === "comet") throw new Error("Comet is not supported by Browserless profile capture yet.");
|
|
348
|
+
const local = readChromiumProfile({ browser: source, imports: { cookies: false, history: false } });
|
|
349
|
+
const assessment = inspectBrowserlessProfile({ profilePath: local.profilePath });
|
|
350
|
+
console.log(JSON.stringify({ browser: source, profileName: local.profileName, ...assessment }));
|
|
351
|
+
return assessment;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function emitBrowserlessProgress(event) {
|
|
355
|
+
console.log(`BCB_PROGRESS ${JSON.stringify(event)}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
325
358
|
export function directCodexSummary(result) {
|
|
326
359
|
const imported = result.imported + result.historyImported;
|
|
327
360
|
const skipped = result.skipped + result.historySkipped;
|
|
@@ -403,6 +436,7 @@ function setAppLogin(enabled) {
|
|
|
403
436
|
menuBar: existing.ui?.menuBar === true,
|
|
404
437
|
openAtLogin: enabled,
|
|
405
438
|
autoCheckUpdates: existing.ui?.autoCheckUpdates !== false,
|
|
439
|
+
autoRestartCodex: existing.ui?.autoRestartCodex === true,
|
|
406
440
|
browserlessProfileName: existing.browserless?.profileName,
|
|
407
441
|
browserlessRegion: existing.browserless?.region,
|
|
408
442
|
browserlessOnlyDomains: existing.browserless?.onlyDomains,
|
package/src/config.js
CHANGED
|
@@ -50,6 +50,7 @@ export function installConfig({ home, hour = 9, minute = 0, nodePath = process.e
|
|
|
50
50
|
menuBar: existing.ui?.menuBar !== false,
|
|
51
51
|
openAtLogin: existing.ui?.openAtLogin !== false,
|
|
52
52
|
autoCheckUpdates: existing.ui?.autoCheckUpdates !== false,
|
|
53
|
+
autoRestartCodex: existing.ui?.autoRestartCodex === true,
|
|
53
54
|
},
|
|
54
55
|
browserless: {
|
|
55
56
|
profileName: cleanProfileName(existing.browserless?.profileName) || "browser-cookie-bridge",
|
|
@@ -77,6 +78,7 @@ export function updatePreferences({
|
|
|
77
78
|
menuBar,
|
|
78
79
|
openAtLogin,
|
|
79
80
|
autoCheckUpdates,
|
|
81
|
+
autoRestartCodex,
|
|
80
82
|
browserlessProfileName,
|
|
81
83
|
browserlessRegion,
|
|
82
84
|
browserlessOnlyDomains,
|
|
@@ -98,6 +100,7 @@ export function updatePreferences({
|
|
|
98
100
|
menuBar: Boolean(menuBar),
|
|
99
101
|
openAtLogin: Boolean(openAtLogin),
|
|
100
102
|
autoCheckUpdates: Boolean(autoCheckUpdates),
|
|
103
|
+
autoRestartCodex: Boolean(autoRestartCodex),
|
|
101
104
|
};
|
|
102
105
|
const region = browserlessRegion ?? config.browserless?.region ?? "sfo";
|
|
103
106
|
if (!["sfo", "lon", "ams"].includes(region)) throw new Error("Browserless region must be sfo, lon, or ams");
|