mcp-browser-extension 0.9.27 → 0.9.28
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 +24 -3
- package/SETUP.md +10 -0
- package/dist/src/access.d.ts +2 -0
- package/dist/src/access.js +4 -0
- package/dist/src/cli.js +14 -2
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +15 -0
- package/dist/src/mcp/server.d.ts +1 -1
- package/dist/src/mcp/server.js +10 -3
- package/extension-dist/background.js +5 -0
- package/extension-dist/popup.html +1 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -82,8 +82,8 @@ or load from the folder the server unpacks.
|
|
|
82
82
|
### Up and running in one paste
|
|
83
83
|
|
|
84
84
|
Hand this to your AI agent (Claude Code, Cursor, Windsurf, anything MCP) and it
|
|
85
|
-
installs the server, wires it into the client, and walks you through
|
|
86
|
-
|
|
85
|
+
installs the server, wires it into the client, and walks you through installing
|
|
86
|
+
the extension, verifying your email, and pairing Chrome:
|
|
87
87
|
|
|
88
88
|
```text
|
|
89
89
|
Set up MCP Browser Extension on this machine by fetching and following
|
|
@@ -140,6 +140,15 @@ By default everything is **deny-all** (no domains, no eval, no mutations). Grant
|
|
|
140
140
|
exactly what you need with `--allow-domain <glob>` (repeatable), `--enable-mutations`,
|
|
141
141
|
`--enable-downloads`, `--enable-uploads`, `--unsafe-enable-eval`, or `--unsafe-all-domains`.
|
|
142
142
|
|
|
143
|
+
Email verification is on by default. For a trusted, local unattended setup, you
|
|
144
|
+
can opt out with `--no-email-verification` (or set
|
|
145
|
+
`MCP_BROWSER_EXTENSION_EMAIL_VERIFICATION=0`). This skips the email prompt and
|
|
146
|
+
the Firebase access service, so no email or device grant is sent there. The
|
|
147
|
+
local MCP client can then use the browser within the configured domain and
|
|
148
|
+
capability policy without an account check. Treat every process that can launch
|
|
149
|
+
or reach this local server as trusted. This option is rejected with `--tunnel`
|
|
150
|
+
and does not turn off telemetry or the npm update check.
|
|
151
|
+
|
|
143
152
|
> `--enable-uploads` permits `upload_file` (setting local file(s) on a page's file
|
|
144
153
|
> `<input>`). It is **off by default** because sending local files to a page is an
|
|
145
154
|
> exfiltration risk; it is also gated by the destination-domain allowlist. Pair it
|
|
@@ -162,7 +171,19 @@ explicitly (and is never written to disk).
|
|
|
162
171
|
One click, no Developer mode, and **Chrome keeps it updated for you**. This is
|
|
163
172
|
the way to install it.
|
|
164
173
|
|
|
165
|
-
**3.
|
|
174
|
+
**3. Verify your email.** In your MCP client, ask the agent to call
|
|
175
|
+
`chrome_status`. On this first browser tool call, the client shows an email
|
|
176
|
+
prompt. Enter an address you can access, then open the sign-in link sent to
|
|
177
|
+
that address. The waiting tool reports **Email verified** and continues.
|
|
178
|
+
|
|
179
|
+
If the tool returns `ACCESS_PENDING`, ask the agent to call `chrome_status`
|
|
180
|
+
again to keep waiting on the same link. You do not need another email or a
|
|
181
|
+
verification code. The MCP client must support interactive input prompts
|
|
182
|
+
(MCP elicitation). If no prompt appears, enable interactive prompts in your
|
|
183
|
+
client and retry; installing the extension alone does not activate access.
|
|
184
|
+
|
|
185
|
+
**4. Click Connect if Chrome is not already paired.** A fresh install opens the
|
|
186
|
+
extension's Settings with a
|
|
166
187
|
**Connect** button (it is in the toolbar popup too). Click it, allow the one
|
|
167
188
|
permission Chrome asks for, and you are paired: the server registers a small
|
|
168
189
|
helper with Chrome every time it starts, and the button asks it for the port and
|
package/SETUP.md
CHANGED
|
@@ -59,6 +59,12 @@ if they want the agent to click, type, and navigate (almost always yes). Add
|
|
|
59
59
|
`--persist-token` so the pairing survives restarts. Leave every other gate off
|
|
60
60
|
unless they ask.
|
|
61
61
|
|
|
62
|
+
Email verification stays enabled by default. Only if the human asks for a
|
|
63
|
+
trusted local or unattended setup, add `--no-email-verification`. It skips the
|
|
64
|
+
email prompt and Firebase access check, while the domain allowlist and other
|
|
65
|
+
capability gates still apply. Do not combine it with `--tunnel` or use it when
|
|
66
|
+
untrusted clients can reach the local server.
|
|
67
|
+
|
|
62
68
|
**Claude Code** (registers for every project on this machine):
|
|
63
69
|
|
|
64
70
|
```
|
|
@@ -129,6 +135,10 @@ the result includes **Email verified** and the requested browser action continue
|
|
|
129
135
|
The user does not need to send another chat message. Progress updates and status
|
|
130
136
|
notifications are also sent to clients that display them.
|
|
131
137
|
|
|
138
|
+
If the human explicitly configured `--no-email-verification`, there is no email
|
|
139
|
+
step; continue to pairing. This option is intended only for trusted local
|
|
140
|
+
clients and cannot be combined with a public tunnel.
|
|
141
|
+
|
|
132
142
|
Each verification wait lasts up to 45 seconds. If the result says
|
|
133
143
|
`ACCESS_PENDING`, call `chrome_status` again to continue waiting on the same
|
|
134
144
|
link. Expired or denied verification returns a clear failure; browser actions
|
package/dist/src/access.d.ts
CHANGED
|
@@ -18,4 +18,6 @@ declare function result(message: string): {
|
|
|
18
18
|
};
|
|
19
19
|
/** Returns null when authorized, otherwise a user-facing tool error. */
|
|
20
20
|
export declare function requireAccess(server: Server, dataDir: string, request?: AccessRequest): Promise<ReturnType<typeof result> | null>;
|
|
21
|
+
/** Explicit local-only opt-out; the CLI rejects it when a public tunnel is enabled. */
|
|
22
|
+
export declare const bypassEmailVerification: typeof requireAccess;
|
|
21
23
|
export {};
|
package/dist/src/access.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bypassEmailVerification = void 0;
|
|
3
4
|
exports.setAccessPollIntervalForTesting = setAccessPollIntervalForTesting;
|
|
4
5
|
exports.requireAccess = requireAccess;
|
|
5
6
|
/** Email-link access gate for the MCP tool surface. */
|
|
@@ -209,6 +210,9 @@ async function requireAccess(server, dataDir, request = {}) {
|
|
|
209
210
|
completionToReport.delete(dataDir);
|
|
210
211
|
return outcome ? result(outcome.message) : null;
|
|
211
212
|
}
|
|
213
|
+
/** Explicit local-only opt-out; the CLI rejects it when a public tunnel is enabled. */
|
|
214
|
+
const bypassEmailVerification = async () => null;
|
|
215
|
+
exports.bypassEmailVerification = bypassEmailVerification;
|
|
212
216
|
async function check(server, dataDir, request) {
|
|
213
217
|
const state = loadOrCreate(dataDir);
|
|
214
218
|
try {
|
package/dist/src/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ const compat_1 = require("./compat");
|
|
|
14
14
|
const node_fs_1 = require("node:fs");
|
|
15
15
|
const node_path_1 = require("node:path");
|
|
16
16
|
const config_1 = require("./config");
|
|
17
|
+
const access_1 = require("./access");
|
|
17
18
|
const tasks_1 = require("./bridge/tasks");
|
|
18
19
|
const manager_1 = require("./executor/manager");
|
|
19
20
|
const select_1 = require("./executor/select");
|
|
@@ -225,6 +226,7 @@ async function main() {
|
|
|
225
226
|
// silent from the first line rather than from wherever the first log happened
|
|
226
227
|
// to sit after startup.
|
|
227
228
|
(0, server_2.setLogLevel)(cfg.logLevel);
|
|
229
|
+
const accessGuard = cfg.noEmailVerification ? access_1.bypassEmailVerification : access_1.requireAccess;
|
|
228
230
|
if (cfg.showHelp) {
|
|
229
231
|
process.stdout.write(config_1.HELP_TEXT);
|
|
230
232
|
// The catalog is what `--tools` takes, so `--help` has to name it.
|
|
@@ -250,6 +252,9 @@ async function main() {
|
|
|
250
252
|
});
|
|
251
253
|
if (handedOff !== null)
|
|
252
254
|
process.exit(handedOff);
|
|
255
|
+
if (cfg.noEmailVerification) {
|
|
256
|
+
(0, server_2.logErr)('SECURITY: hosted email verification is disabled; only trusted local MCP clients should connect. The browser-domain allowlist and other safety gates remain active.');
|
|
257
|
+
}
|
|
253
258
|
// Before the bridge binds a port or writes a handshake: a typo'd `--tools`
|
|
254
259
|
// name should fail as a plain startup error, not leave a half-started server.
|
|
255
260
|
(0, tools_1.setToolAllowlist)(cfg.tools);
|
|
@@ -449,7 +454,7 @@ async function main() {
|
|
|
449
454
|
});
|
|
450
455
|
// The resolved policy shapes the advertised catalog: a capability that is off
|
|
451
456
|
// means its tools are never described to the model.
|
|
452
|
-
await (0, server_2.startMcpServer)(version(), cfg.policy);
|
|
457
|
+
await (0, server_2.startMcpServer)(version(), cfg.policy, accessGuard);
|
|
453
458
|
}
|
|
454
459
|
/**
|
|
455
460
|
* `--http` / `--tunnel`: a standalone server for clients on other machines.
|
|
@@ -459,7 +464,14 @@ async function serveRemote(cfg, dataDir, stopBridge) {
|
|
|
459
464
|
const key = (0, key_1.resolveRemoteKey)(dataDir, { rotate: cfg.rotateRemoteKey });
|
|
460
465
|
if (cfg.rotateRemoteKey)
|
|
461
466
|
(0, server_2.logErr)('remote key rotated: every earlier connector URL stops working.');
|
|
462
|
-
const http = await (0, http_1.startHttpMcp)({
|
|
467
|
+
const http = await (0, http_1.startHttpMcp)({
|
|
468
|
+
port: cfg.httpPort,
|
|
469
|
+
key,
|
|
470
|
+
version: version(),
|
|
471
|
+
policy: cfg.policy,
|
|
472
|
+
log: (m) => (0, server_2.logErr)(m),
|
|
473
|
+
accessGuard: cfg.noEmailVerification ? access_1.bypassEmailVerification : undefined,
|
|
474
|
+
});
|
|
463
475
|
let tunnel = null;
|
|
464
476
|
let shuttingDown = false;
|
|
465
477
|
const shutdown = () => {
|
package/dist/src/config.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface CliConfig {
|
|
|
38
38
|
persistToken: boolean;
|
|
39
39
|
/** `--no-telemetry`: send no anonymous usage statistics (see src/telemetry.ts). */
|
|
40
40
|
noTelemetry: boolean;
|
|
41
|
+
/** `--no-email-verification`: bypass the hosted access gate for trusted local use. */
|
|
42
|
+
noEmailVerification: boolean;
|
|
41
43
|
/** `--no-site-grants`: the allowlist is exactly what the flags say; the
|
|
42
44
|
* extension's Options page cannot add sites at runtime. */
|
|
43
45
|
noSiteGrants: boolean;
|
package/dist/src/config.js
CHANGED
|
@@ -82,6 +82,7 @@ function parseArgs(argv) {
|
|
|
82
82
|
let printPairing = false;
|
|
83
83
|
let persistToken = false;
|
|
84
84
|
let noTelemetry = false;
|
|
85
|
+
let noEmailVerification = ['0', 'false', 'off'].includes(((0, compat_1.runtimeEnv)('EMAIL_VERIFICATION') ?? '').toLowerCase());
|
|
85
86
|
let noSiteGrants = false;
|
|
86
87
|
let noAutoUpdate = false;
|
|
87
88
|
let noNativeHost = false;
|
|
@@ -192,6 +193,9 @@ function parseArgs(argv) {
|
|
|
192
193
|
case '--no-telemetry':
|
|
193
194
|
noTelemetry = true;
|
|
194
195
|
break;
|
|
196
|
+
case '--no-email-verification':
|
|
197
|
+
noEmailVerification = true;
|
|
198
|
+
break;
|
|
195
199
|
case '--no-site-grants':
|
|
196
200
|
noSiteGrants = true;
|
|
197
201
|
break;
|
|
@@ -234,6 +238,11 @@ function parseArgs(argv) {
|
|
|
234
238
|
cdpFallback = false;
|
|
235
239
|
cdpEndpoint = undefined;
|
|
236
240
|
prefer = 'extension';
|
|
241
|
+
// The local verification bypass is intended for trusted processes on this
|
|
242
|
+
// machine. Do not expose it through a public Cloudflare tunnel.
|
|
243
|
+
if (noEmailVerification && tunnel) {
|
|
244
|
+
throw new Error('--no-email-verification cannot be used with --tunnel');
|
|
245
|
+
}
|
|
237
246
|
// File first, then flags win.
|
|
238
247
|
const policy = (0, policy_1.resolvePolicy)({ ...policyFile, ...policyFlags });
|
|
239
248
|
// Uploads must be confined to a directory — refuse to start with uploads enabled
|
|
@@ -271,6 +280,7 @@ function parseArgs(argv) {
|
|
|
271
280
|
printPairing,
|
|
272
281
|
persistToken,
|
|
273
282
|
noTelemetry,
|
|
283
|
+
noEmailVerification,
|
|
274
284
|
noSiteGrants,
|
|
275
285
|
noAutoUpdate,
|
|
276
286
|
noNativeHost,
|
|
@@ -375,6 +385,11 @@ Connection:
|
|
|
375
385
|
MCP_BROWSER_EXTENSION_TELEMETRY=0 or DO_NOT_TRACK=1. Only counts are
|
|
376
386
|
ever sent (version, OS, tool calls, error codes) — never
|
|
377
387
|
URLs, page content or arguments.
|
|
388
|
+
--no-email-verification Skip the hosted email/device access gate for trusted local use.
|
|
389
|
+
Email and device data are not sent to the access service.
|
|
390
|
+
Same as MCP_BROWSER_EXTENSION_EMAIL_VERIFICATION=0.
|
|
391
|
+
Cannot be combined with --tunnel; the domain allowlist,
|
|
392
|
+
deny-all defaults, and other safety gates still apply.
|
|
378
393
|
|
|
379
394
|
Backend:
|
|
380
395
|
This build is EXTENSION-ONLY — it drives ONLY your real Chrome via the paired
|
package/dist/src/mcp/server.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare const SERVER_INSTRUCTIONS: string;
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function createServer(version?: string, policy?: Policy, accessGuard?: typeof requireAccess): McpServer;
|
|
29
29
|
/** Start over stdio. Idempotent. */
|
|
30
|
-
export declare function startMcpServer(version?: string, policy?: Policy): Promise<void>;
|
|
30
|
+
export declare function startMcpServer(version?: string, policy?: Policy, accessGuard?: typeof requireAccess): Promise<void>;
|
|
31
31
|
/** Stop and release the transport. Idempotent, best-effort. */
|
|
32
32
|
export declare function stopMcpServer(): Promise<void>;
|
|
33
33
|
/** The MCP client this session serves, as it named itself at initialize (for analytics). */
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -43,6 +43,10 @@ exports.SERVER_INSTRUCTIONS = "MCP Browser Extension drives the user's real, sig
|
|
|
43
43
|
'toolbar icon (its popup has an Allow button). It applies at once, with no restart. You cannot approve a site yourself. ' +
|
|
44
44
|
"Work in your own tab: tab_new opens one in the background, and calls without a tabId go to it, never to the tab the user is on. " +
|
|
45
45
|
"To act on the user's current page, pass its tabId from tabs_list.";
|
|
46
|
+
const LOCAL_BYPASS_INSTRUCTIONS = 'Email verification is disabled by the local operator for this server. Only trusted local MCP clients should connect; the browser-domain allowlist and other capability safety gates remain active. ' +
|
|
47
|
+
'Start with chrome_status. If it reports no paired browser (or any tool fails with NO_BACKEND), give the user the steps in its `setup` field. ' +
|
|
48
|
+
'If a tool fails with POLICY_DENIED because a site is not allowed, ask the user to allow it from the extension\'s toolbar icon. ' +
|
|
49
|
+
"Work in your own tab: tab_new opens one in the background. To act on the user's current page, pass its tabId from tabs_list.";
|
|
46
50
|
/** Default version reported when no explicit version is passed in (legacy callers/tests). */
|
|
47
51
|
const DEFAULT_VERSION = SERVER_VERSION;
|
|
48
52
|
let server = null;
|
|
@@ -56,7 +60,10 @@ let transport = null;
|
|
|
56
60
|
* full catalog is advertised, which is what the tests want.
|
|
57
61
|
*/
|
|
58
62
|
function createServer(version = DEFAULT_VERSION, policy, accessGuard = access_1.requireAccess) {
|
|
59
|
-
const srv = new mcp_js_1.McpServer({ name: SERVER_NAME, version }, {
|
|
63
|
+
const srv = new mcp_js_1.McpServer({ name: SERVER_NAME, version }, {
|
|
64
|
+
capabilities: { tools: {}, logging: {} },
|
|
65
|
+
instructions: accessGuard === access_1.bypassEmailVerification ? LOCAL_BYPASS_INSTRUCTIONS : exports.SERVER_INSTRUCTIONS,
|
|
66
|
+
});
|
|
60
67
|
const advertised = (0, tools_1.registerTools)(srv, policy, accessGuard);
|
|
61
68
|
watchInitialize(srv, advertised);
|
|
62
69
|
// `McpServer` wraps the low-level `Server`, which owns the `onerror` hook.
|
|
@@ -88,12 +95,12 @@ function watchInitialize(srv, advertised) {
|
|
|
88
95
|
};
|
|
89
96
|
}
|
|
90
97
|
/** Start over stdio. Idempotent. */
|
|
91
|
-
async function startMcpServer(version = DEFAULT_VERSION, policy) {
|
|
98
|
+
async function startMcpServer(version = DEFAULT_VERSION, policy, accessGuard = access_1.requireAccess) {
|
|
92
99
|
if (server) {
|
|
93
100
|
(0, log_1.logErr)('startMcpServer called but already running; ignoring.');
|
|
94
101
|
return;
|
|
95
102
|
}
|
|
96
|
-
const srv = createServer(version, policy);
|
|
103
|
+
const srv = createServer(version, policy, accessGuard);
|
|
97
104
|
const tx = new stdio_js_1.StdioServerTransport();
|
|
98
105
|
try {
|
|
99
106
|
await srv.connect(tx);
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
constructor(deps) {
|
|
50
50
|
this.deps = deps;
|
|
51
51
|
}
|
|
52
|
+
deps;
|
|
52
53
|
ws = null;
|
|
53
54
|
state = "idle";
|
|
54
55
|
isConnected() {
|
|
@@ -1070,6 +1071,7 @@
|
|
|
1070
1071
|
super(message);
|
|
1071
1072
|
this.code = code;
|
|
1072
1073
|
}
|
|
1074
|
+
code;
|
|
1073
1075
|
};
|
|
1074
1076
|
var DOWNLOAD_TIMEOUT_MS = 12e4;
|
|
1075
1077
|
function waitForDownloadComplete(id) {
|
|
@@ -1480,6 +1482,7 @@
|
|
|
1480
1482
|
constructor(getPolicy = () => null) {
|
|
1481
1483
|
this.getPolicy = getPolicy;
|
|
1482
1484
|
}
|
|
1485
|
+
getPolicy;
|
|
1483
1486
|
/**
|
|
1484
1487
|
* Resolve `frameId` / `allFrames` params into the frame ids an injection may
|
|
1485
1488
|
* touch, dropping any whose own URL the policy does not allow for this method.
|
|
@@ -2121,6 +2124,7 @@
|
|
|
2121
2124
|
if (!HANDLED.has(m)) throw new Error(`router drift: no handler for wire method "${m}"`);
|
|
2122
2125
|
}
|
|
2123
2126
|
}
|
|
2127
|
+
deps;
|
|
2124
2128
|
async dispatch(cmd) {
|
|
2125
2129
|
try {
|
|
2126
2130
|
const policy = this.deps.getPolicy();
|
|
@@ -2318,6 +2322,7 @@
|
|
|
2318
2322
|
constructor(getPolicy) {
|
|
2319
2323
|
this.getPolicy = getPolicy;
|
|
2320
2324
|
}
|
|
2325
|
+
getPolicy;
|
|
2321
2326
|
/** One lock for all border state: parallel batch ops touching the same tab
|
|
2322
2327
|
* must not both inject (removeCSS takes out one copy, not both). */
|
|
2323
2328
|
lock = new KeyedMutex();
|
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
|
|
64
64
|
main { padding: 4px 12px 12px; display: grid; gap: 10px; }
|
|
65
65
|
.card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 12px; box-shadow: var(--shadow); }
|
|
66
|
-
.card.connect { color: #0f172a; background: color-mix(in oklch, oklch(0.58 0.19 255) 7%, white); border-color: color-mix(in oklch, oklch(0.58 0.19 255) 25%, #e6e8ec); }
|
|
67
66
|
h2 { display: flex; align-items: center; justify-content: space-between; margin: 0 0 8px; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
|
|
68
67
|
.count { font-size: 11px; letter-spacing: 0; text-transform: none; padding: 1px 7px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); color: var(--muted); }
|
|
69
68
|
|
|
@@ -118,7 +117,7 @@
|
|
|
118
117
|
ol { margin: 2px 0 12px; padding-left: 18px; color: var(--muted); }
|
|
119
118
|
ol li { margin: 3px 0; }
|
|
120
119
|
code { font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; padding: 1px 5px; border-radius: 5px; background: var(--panel); border: 1px solid var(--line); color: var(--fg); }
|
|
121
|
-
.connect .note { margin: 0 0 10px; color:
|
|
120
|
+
.connect .note { margin: 0 0 10px; color: var(--fg); }
|
|
122
121
|
#connect { width: 100%; padding: 10px 12px; font-size: 13.5px; font-weight: 600; }
|
|
123
122
|
#connect.secondary { background: var(--panel); color: var(--fg); border-color: var(--line); box-shadow: none; padding: 7px 12px; font-size: 12.5px; font-weight: 500; }
|
|
124
123
|
.connect .error { margin: 8px 0 0; color: var(--warn); }
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-browser-extension",
|
|
3
3
|
"mcpName": "io.github.Mehmoodqureshi/mcp-browser-extension",
|
|
4
|
-
"version": "0.9.
|
|
5
|
-
"description": "Drive your real Chrome browser over MCP
|
|
4
|
+
"version": "0.9.28",
|
|
5
|
+
"description": "Drive your real Chrome browser over MCP — real logins, real cookies. A stdio MCP server (CLI) plus an MV3 extension, driving Chrome via chrome.scripting/chrome.tabs. Multi-tab batch automation, accessibility snapshots, deny-all security by default.",
|
|
6
6
|
"author": "Mehmood Ur Rehman Qureshi",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"homepage": "https://mcp-browser-extension.com",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@types/chrome": "^0.0.287",
|
|
71
71
|
"@types/node": "^20.17.10",
|
|
72
72
|
"@types/ws": "^8.5.13",
|
|
73
|
-
"esbuild": "^0.
|
|
73
|
+
"esbuild": "^0.28.2",
|
|
74
74
|
"playwright": "^1.49.1",
|
|
75
75
|
"rimraf": "^5.0.10",
|
|
76
76
|
"typescript": "^5.7.2"
|