cdp-toolkit 1.10.0 → 1.11.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/CHANGELOG.md +20 -0
- package/README.md +29 -7
- package/dist/cli.js +902 -13
- package/dist/mcp.js +340 -14
- package/package.json +2 -1
- package/src/bidi/driver.ts +190 -28
- package/src/bidi/marionette.ts +276 -0
- package/src/bidi/session-lease.ts +265 -0
- package/src/cli.ts +10 -0
- package/src/install/browser.ts +211 -0
- package/src/install/harness.ts +224 -0
- package/src/install/wizard.ts +412 -0
- package/src/mcp.ts +1 -1
package/dist/mcp.js
CHANGED
|
@@ -7081,6 +7081,252 @@ async function connectBidiSessionUrl(wsUrl, opts = {}) {
|
|
|
7081
7081
|
return conn;
|
|
7082
7082
|
}
|
|
7083
7083
|
|
|
7084
|
+
// src/bidi/session-lease.ts
|
|
7085
|
+
import { mkdir as mkdir12, readFile as readFile8, unlink as unlink5, writeFile as writeFile9 } from "node:fs/promises";
|
|
7086
|
+
import { join as join10 } from "node:path";
|
|
7087
|
+
var DEFAULT_SESSION_WAIT_MS = 1e4;
|
|
7088
|
+
var DEFAULT_POLL_MS = 250;
|
|
7089
|
+
function safeEndpoint(endpoint) {
|
|
7090
|
+
return endpoint.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
7091
|
+
}
|
|
7092
|
+
function sessionSlotFile(endpoint) {
|
|
7093
|
+
return join10(leaseDir(), `ff-session-${safeEndpoint(endpoint)}.json`);
|
|
7094
|
+
}
|
|
7095
|
+
function sessionWaitMs() {
|
|
7096
|
+
const raw = Number(process.env.CDP_FIREFOX_SESSION_WAIT_MS);
|
|
7097
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_SESSION_WAIT_MS;
|
|
7098
|
+
}
|
|
7099
|
+
|
|
7100
|
+
class SessionSlotBusyError extends Error {
|
|
7101
|
+
endpoint;
|
|
7102
|
+
holderPid;
|
|
7103
|
+
holderLabel;
|
|
7104
|
+
constructor(endpoint, holder) {
|
|
7105
|
+
const who = holder ? `'${holder.label}' (pid ${holder.pid})` : "another live process";
|
|
7106
|
+
super(`Firefox BiDi session slot for endpoint '${endpoint}' is held by ${who}. ` + `Firefox serves exactly ONE WebDriver-BiDi session per browser instance, and this slot is held ` + `by a LIVE process, so it cannot be taken. Wait for that process to release it and retry, or point ` + `this server at a different endpoint/browser.`);
|
|
7107
|
+
this.name = "SessionSlotBusyError";
|
|
7108
|
+
this.endpoint = endpoint;
|
|
7109
|
+
if (holder) {
|
|
7110
|
+
this.holderPid = holder.pid;
|
|
7111
|
+
this.holderLabel = holder.label;
|
|
7112
|
+
}
|
|
7113
|
+
}
|
|
7114
|
+
}
|
|
7115
|
+
async function readSessionSlot(endpoint) {
|
|
7116
|
+
let raw;
|
|
7117
|
+
try {
|
|
7118
|
+
raw = await readFile8(sessionSlotFile(endpoint), "utf8");
|
|
7119
|
+
} catch (err) {
|
|
7120
|
+
if (err.code === "ENOENT")
|
|
7121
|
+
return;
|
|
7122
|
+
throw err;
|
|
7123
|
+
}
|
|
7124
|
+
try {
|
|
7125
|
+
const rec = JSON.parse(raw);
|
|
7126
|
+
if (typeof rec?.pid === "number" && typeof rec?.endpoint === "string" && typeof rec?.label === "string") {
|
|
7127
|
+
return rec;
|
|
7128
|
+
}
|
|
7129
|
+
} catch {}
|
|
7130
|
+
return;
|
|
7131
|
+
}
|
|
7132
|
+
async function acquireSessionSlot(endpoint, opts) {
|
|
7133
|
+
const waitMs = opts.waitMs ?? sessionWaitMs();
|
|
7134
|
+
const pollMs = opts.pollMs !== undefined && opts.pollMs > 0 ? opts.pollMs : DEFAULT_POLL_MS;
|
|
7135
|
+
const createdAt = opts.now ?? Date.now();
|
|
7136
|
+
const file = sessionSlotFile(endpoint);
|
|
7137
|
+
const record2 = {
|
|
7138
|
+
endpoint,
|
|
7139
|
+
pid: process.pid,
|
|
7140
|
+
label: opts.label,
|
|
7141
|
+
createdAt
|
|
7142
|
+
};
|
|
7143
|
+
if (opts.marionettePort !== undefined)
|
|
7144
|
+
record2.marionettePort = opts.marionettePort;
|
|
7145
|
+
const handle = { endpoint, pid: process.pid, createdAt };
|
|
7146
|
+
const payload = JSON.stringify(record2);
|
|
7147
|
+
await mkdir12(leaseDir(), { recursive: true });
|
|
7148
|
+
const deadline = Date.now() + waitMs;
|
|
7149
|
+
const maxAttempts = Math.max(100, Math.ceil(waitMs / pollMs) + 100);
|
|
7150
|
+
let attempts = 0;
|
|
7151
|
+
let lastLiveHolder;
|
|
7152
|
+
while (true) {
|
|
7153
|
+
if (attempts >= maxAttempts)
|
|
7154
|
+
throw new SessionSlotBusyError(endpoint, lastLiveHolder);
|
|
7155
|
+
attempts++;
|
|
7156
|
+
try {
|
|
7157
|
+
await writeFile9(file, payload, { flag: "wx" });
|
|
7158
|
+
return { handle };
|
|
7159
|
+
} catch (err) {
|
|
7160
|
+
if (err.code !== "EEXIST")
|
|
7161
|
+
throw err;
|
|
7162
|
+
}
|
|
7163
|
+
const holder = await readSessionSlot(endpoint);
|
|
7164
|
+
if (!holder)
|
|
7165
|
+
continue;
|
|
7166
|
+
if (!isPidAlive(holder.pid)) {
|
|
7167
|
+
await unlink5(file).catch(() => {
|
|
7168
|
+
return;
|
|
7169
|
+
});
|
|
7170
|
+
try {
|
|
7171
|
+
await writeFile9(file, payload, { flag: "wx" });
|
|
7172
|
+
return { handle, staleHolder: holder };
|
|
7173
|
+
} catch (err) {
|
|
7174
|
+
if (err.code !== "EEXIST")
|
|
7175
|
+
throw err;
|
|
7176
|
+
continue;
|
|
7177
|
+
}
|
|
7178
|
+
}
|
|
7179
|
+
lastLiveHolder = holder;
|
|
7180
|
+
const remaining = deadline - Date.now();
|
|
7181
|
+
if (remaining <= 0)
|
|
7182
|
+
throw new SessionSlotBusyError(endpoint, holder);
|
|
7183
|
+
await sleep2(Math.min(pollMs, remaining));
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
7186
|
+
async function releaseSessionSlot(handle) {
|
|
7187
|
+
const current = await readSessionSlot(handle.endpoint);
|
|
7188
|
+
if (!current)
|
|
7189
|
+
return { released: false };
|
|
7190
|
+
if (current.pid !== process.pid || current.createdAt !== handle.createdAt)
|
|
7191
|
+
return { released: false };
|
|
7192
|
+
await unlink5(sessionSlotFile(handle.endpoint)).catch(() => {
|
|
7193
|
+
return;
|
|
7194
|
+
});
|
|
7195
|
+
return { released: true };
|
|
7196
|
+
}
|
|
7197
|
+
function sleep2(ms) {
|
|
7198
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7199
|
+
}
|
|
7200
|
+
|
|
7201
|
+
// src/bidi/marionette.ts
|
|
7202
|
+
import { Socket } from "node:net";
|
|
7203
|
+
var DEFAULT_MARIONETTE_PORT = 2828;
|
|
7204
|
+
var DEFAULT_TIMEOUT_MS3 = 4000;
|
|
7205
|
+
var HOST = "127.0.0.1";
|
|
7206
|
+
var COLON = 58;
|
|
7207
|
+
var DELETE_SESSION_FRAME = encodeFrame([0, 1, "WebDriver:DeleteSession", {}]);
|
|
7208
|
+
function defaultMarionettePort() {
|
|
7209
|
+
const raw = Number(process.env.CDP_FIREFOX_MARIONETTE_PORT);
|
|
7210
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MARIONETTE_PORT;
|
|
7211
|
+
}
|
|
7212
|
+
function encodeFrame(json) {
|
|
7213
|
+
const body = Buffer.from(JSON.stringify(json), "utf8");
|
|
7214
|
+
const prefix = Buffer.from(`${body.length}:`, "ascii");
|
|
7215
|
+
return Buffer.concat([prefix, body]);
|
|
7216
|
+
}
|
|
7217
|
+
|
|
7218
|
+
class MarionetteProtocolError extends Error {
|
|
7219
|
+
constructor(message) {
|
|
7220
|
+
super(message);
|
|
7221
|
+
this.name = "MarionetteProtocolError";
|
|
7222
|
+
}
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
class FrameDecoder {
|
|
7226
|
+
buf = Buffer.alloc(0);
|
|
7227
|
+
push(chunk) {
|
|
7228
|
+
this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);
|
|
7229
|
+
}
|
|
7230
|
+
next() {
|
|
7231
|
+
const colon = this.buf.indexOf(COLON);
|
|
7232
|
+
if (colon === -1) {
|
|
7233
|
+
this.assertDigitsPrefix(this.buf.length);
|
|
7234
|
+
return;
|
|
7235
|
+
}
|
|
7236
|
+
if (colon === 0)
|
|
7237
|
+
throw new MarionetteProtocolError("empty frame length prefix");
|
|
7238
|
+
this.assertDigitsPrefix(colon);
|
|
7239
|
+
const len = Number.parseInt(this.buf.toString("ascii", 0, colon), 10);
|
|
7240
|
+
const start = colon + 1;
|
|
7241
|
+
const end = start + len;
|
|
7242
|
+
if (this.buf.length < end)
|
|
7243
|
+
return;
|
|
7244
|
+
const payload = this.buf.toString("utf8", start, end);
|
|
7245
|
+
this.buf = this.buf.subarray(end);
|
|
7246
|
+
try {
|
|
7247
|
+
return JSON.parse(payload);
|
|
7248
|
+
} catch {
|
|
7249
|
+
throw new MarionetteProtocolError(`frame body was not valid JSON (${len} bytes): ${payload.slice(0, 80)}`);
|
|
7250
|
+
}
|
|
7251
|
+
}
|
|
7252
|
+
assertDigitsPrefix(upTo) {
|
|
7253
|
+
for (let i = 0;i < upTo; i++) {
|
|
7254
|
+
const b = this.buf[i];
|
|
7255
|
+
if (b < 48 || b > 57) {
|
|
7256
|
+
throw new MarionetteProtocolError(`non-digit byte 0x${b.toString(16)} in frame length prefix`);
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
function isSlotFreedReply(frame) {
|
|
7262
|
+
if (!Array.isArray(frame))
|
|
7263
|
+
return false;
|
|
7264
|
+
const err = frame[2];
|
|
7265
|
+
if (err === null)
|
|
7266
|
+
return true;
|
|
7267
|
+
if (typeof err === "object" && err !== null && err.error === "invalid session id") {
|
|
7268
|
+
return true;
|
|
7269
|
+
}
|
|
7270
|
+
return false;
|
|
7271
|
+
}
|
|
7272
|
+
function forceClearOrphanSession(marionettePort, opts = {}) {
|
|
7273
|
+
const timeoutMs = opts.timeoutMs !== undefined && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0 ? opts.timeoutMs : DEFAULT_TIMEOUT_MS3;
|
|
7274
|
+
return new Promise((resolve) => {
|
|
7275
|
+
if (!Number.isFinite(marionettePort) || marionettePort <= 0 || marionettePort > 65535) {
|
|
7276
|
+
resolve(false);
|
|
7277
|
+
return;
|
|
7278
|
+
}
|
|
7279
|
+
const sock = new Socket;
|
|
7280
|
+
const decoder = new FrameDecoder;
|
|
7281
|
+
let settled = false;
|
|
7282
|
+
let handshakeSeen = false;
|
|
7283
|
+
let commandSent = false;
|
|
7284
|
+
const settle = (value) => {
|
|
7285
|
+
if (settled)
|
|
7286
|
+
return;
|
|
7287
|
+
settled = true;
|
|
7288
|
+
clearTimeout(timer);
|
|
7289
|
+
sock.removeAllListeners();
|
|
7290
|
+
sock.destroy();
|
|
7291
|
+
resolve(value);
|
|
7292
|
+
};
|
|
7293
|
+
const timer = setTimeout(() => settle(false), timeoutMs);
|
|
7294
|
+
if (typeof timer.unref === "function")
|
|
7295
|
+
timer.unref();
|
|
7296
|
+
sock.on("error", () => settle(false));
|
|
7297
|
+
sock.on("close", () => settle(false));
|
|
7298
|
+
sock.on("data", (chunk) => {
|
|
7299
|
+
if (settled)
|
|
7300
|
+
return;
|
|
7301
|
+
try {
|
|
7302
|
+
decoder.push(chunk);
|
|
7303
|
+
for (;; ) {
|
|
7304
|
+
const frame = decoder.next();
|
|
7305
|
+
if (frame === undefined)
|
|
7306
|
+
break;
|
|
7307
|
+
if (!handshakeSeen) {
|
|
7308
|
+
handshakeSeen = true;
|
|
7309
|
+
continue;
|
|
7310
|
+
}
|
|
7311
|
+
settle(isSlotFreedReply(frame));
|
|
7312
|
+
return;
|
|
7313
|
+
}
|
|
7314
|
+
if (handshakeSeen && !commandSent) {
|
|
7315
|
+
commandSent = true;
|
|
7316
|
+
sock.write(DELETE_SESSION_FRAME);
|
|
7317
|
+
}
|
|
7318
|
+
} catch {
|
|
7319
|
+
settle(false);
|
|
7320
|
+
}
|
|
7321
|
+
});
|
|
7322
|
+
try {
|
|
7323
|
+
sock.connect(marionettePort, HOST);
|
|
7324
|
+
} catch {
|
|
7325
|
+
settle(false);
|
|
7326
|
+
}
|
|
7327
|
+
});
|
|
7328
|
+
}
|
|
7329
|
+
|
|
7084
7330
|
// src/bidi/snapshot.ts
|
|
7085
7331
|
var STAMP_ATTR = "data-cdp-uid";
|
|
7086
7332
|
async function takeStampedSnapshot(evaluate) {
|
|
@@ -7657,6 +7903,22 @@ var BIDI_CAPABILITIES = new Set([
|
|
|
7657
7903
|
"locate.xpath"
|
|
7658
7904
|
]);
|
|
7659
7905
|
var connections = new Map;
|
|
7906
|
+
var slotHandles = new Map;
|
|
7907
|
+
var connecting = new Map;
|
|
7908
|
+
async function releaseSlotOnDialFailure(endpoint, owned) {
|
|
7909
|
+
if (owned)
|
|
7910
|
+
return;
|
|
7911
|
+
const h = slotHandles.get(endpoint);
|
|
7912
|
+
if (h) {
|
|
7913
|
+
await releaseSessionSlot(h).catch(() => {
|
|
7914
|
+
return;
|
|
7915
|
+
});
|
|
7916
|
+
slotHandles.delete(endpoint);
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
function orphanNotClearedError(endpoint) {
|
|
7920
|
+
return driverError2("disconnected", `Firefox holds an orphaned WebDriver BiDi session on ${endpoint} that could not be auto-cleared. ` + `Marionette recovery needs Firefox launched with --marionette (default port 2828, or set ` + `CDP_FIREFOX_MARIONETTE_PORT); otherwise restart Firefox to free the slot. (A client killed without ` + `session.end can leave a session Firefox does not reap.)`);
|
|
7921
|
+
}
|
|
7660
7922
|
async function getConnection(endpoint, timeoutMs) {
|
|
7661
7923
|
const existing = connections.get(endpoint);
|
|
7662
7924
|
if (existing && existing.isOpen) {
|
|
@@ -7665,17 +7927,68 @@ async function getConnection(endpoint, timeoutMs) {
|
|
|
7665
7927
|
}
|
|
7666
7928
|
if (existing)
|
|
7667
7929
|
connections.delete(endpoint);
|
|
7668
|
-
const
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7930
|
+
const inflight = connecting.get(endpoint);
|
|
7931
|
+
if (inflight) {
|
|
7932
|
+
const conn = await inflight;
|
|
7933
|
+
conn.retain();
|
|
7934
|
+
return conn;
|
|
7935
|
+
}
|
|
7936
|
+
const create = (async () => {
|
|
7937
|
+
const mport = defaultMarionettePort();
|
|
7938
|
+
const owned = slotHandles.has(endpoint);
|
|
7939
|
+
if (!owned) {
|
|
7940
|
+
let acq;
|
|
7941
|
+
try {
|
|
7942
|
+
acq = await acquireSessionSlot(endpoint, { label: defaultLabel(), marionettePort: mport });
|
|
7943
|
+
} catch (e) {
|
|
7944
|
+
if (e instanceof SessionSlotBusyError) {
|
|
7945
|
+
const who = e.holderLabel ? `'${e.holderLabel}' (pid ${e.holderPid})` : "another live process";
|
|
7946
|
+
throw driverError2("disconnected", `Firefox's single WebDriver BiDi session on ${endpoint} is held by a LIVE process ${who}. ` + `Firefox serves exactly ONE BiDi session per browser instance, so this server cannot take it ` + `while that process is running. Wait for it to release the session and retry, or point this ` + `server at a different endpoint/browser.`);
|
|
7947
|
+
}
|
|
7948
|
+
throw e;
|
|
7949
|
+
}
|
|
7950
|
+
slotHandles.set(endpoint, acq.handle);
|
|
7951
|
+
if (acq.staleHolder) {
|
|
7952
|
+
await forceClearOrphanSession(acq.staleHolder.marionettePort ?? mport);
|
|
7953
|
+
}
|
|
7674
7954
|
}
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7955
|
+
const dialOpts = {
|
|
7956
|
+
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
7957
|
+
capabilities: { alwaysMatch: { unhandledPromptBehavior: { default: "ignore" } } }
|
|
7958
|
+
};
|
|
7959
|
+
let conn;
|
|
7960
|
+
try {
|
|
7961
|
+
conn = await connectBidiSessionUrl(endpoint, dialOpts);
|
|
7962
|
+
} catch (e) {
|
|
7963
|
+
if (e instanceof BidiError && /maximum number of active sessions/i.test(e.message)) {
|
|
7964
|
+
const cleared = await forceClearOrphanSession(mport);
|
|
7965
|
+
if (!cleared) {
|
|
7966
|
+
await releaseSlotOnDialFailure(endpoint, owned);
|
|
7967
|
+
throw orphanNotClearedError(endpoint);
|
|
7968
|
+
}
|
|
7969
|
+
try {
|
|
7970
|
+
conn = await connectBidiSessionUrl(endpoint, dialOpts);
|
|
7971
|
+
} catch (e2) {
|
|
7972
|
+
await releaseSlotOnDialFailure(endpoint, owned);
|
|
7973
|
+
if (e2 instanceof BidiError && /maximum number of active sessions/i.test(e2.message)) {
|
|
7974
|
+
throw orphanNotClearedError(endpoint);
|
|
7975
|
+
}
|
|
7976
|
+
throw e2;
|
|
7977
|
+
}
|
|
7978
|
+
} else {
|
|
7979
|
+
await releaseSlotOnDialFailure(endpoint, owned);
|
|
7980
|
+
throw e;
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
7983
|
+
connections.set(endpoint, conn);
|
|
7984
|
+
return conn;
|
|
7985
|
+
})();
|
|
7986
|
+
connecting.set(endpoint, create);
|
|
7987
|
+
try {
|
|
7988
|
+
return await create;
|
|
7989
|
+
} finally {
|
|
7990
|
+
connecting.delete(endpoint);
|
|
7991
|
+
}
|
|
7679
7992
|
}
|
|
7680
7993
|
var bidiBeaconScripts = new Map;
|
|
7681
7994
|
async function safeTitle(conn, contextId) {
|
|
@@ -8441,14 +8754,27 @@ class BidiBrowserDriver {
|
|
|
8441
8754
|
if (key.startsWith(`${this.endpoint}:`))
|
|
8442
8755
|
bidiBeaconScripts.delete(key);
|
|
8443
8756
|
}
|
|
8757
|
+
connecting.delete(this.endpoint);
|
|
8444
8758
|
const conn = connections.get(this.endpoint);
|
|
8445
|
-
if (!conn)
|
|
8759
|
+
if (!conn) {
|
|
8760
|
+
await this.releaseSessionSlotIfHeld();
|
|
8446
8761
|
return;
|
|
8762
|
+
}
|
|
8447
8763
|
connections.delete(this.endpoint);
|
|
8448
8764
|
await conn.send("session.end", {}).catch(() => {
|
|
8449
8765
|
return;
|
|
8450
8766
|
});
|
|
8451
8767
|
conn.dispose();
|
|
8768
|
+
await this.releaseSessionSlotIfHeld();
|
|
8769
|
+
}
|
|
8770
|
+
async releaseSessionSlotIfHeld() {
|
|
8771
|
+
const h = slotHandles.get(this.endpoint);
|
|
8772
|
+
if (!h)
|
|
8773
|
+
return;
|
|
8774
|
+
await releaseSessionSlot(h).catch(() => {
|
|
8775
|
+
return;
|
|
8776
|
+
});
|
|
8777
|
+
slotHandles.delete(this.endpoint);
|
|
8452
8778
|
}
|
|
8453
8779
|
}
|
|
8454
8780
|
function createFirefoxDriver(port, opts) {
|
|
@@ -8463,7 +8789,7 @@ import { spawn as spawn3 } from "node:child_process";
|
|
|
8463
8789
|
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
8464
8790
|
import { createConnection, createServer } from "node:net";
|
|
8465
8791
|
import { tmpdir } from "node:os";
|
|
8466
|
-
import { join as
|
|
8792
|
+
import { join as join11 } from "node:path";
|
|
8467
8793
|
function resolveBinary() {
|
|
8468
8794
|
const candidates = [
|
|
8469
8795
|
process.env.FIREFOX_BIN,
|
|
@@ -8541,7 +8867,7 @@ async function launchFirefox(opts = {}) {
|
|
|
8541
8867
|
const binary = resolveBinary();
|
|
8542
8868
|
const port = await pickPort(opts.port);
|
|
8543
8869
|
const isThrowaway = opts.profilePath === undefined;
|
|
8544
|
-
const profilePath = opts.profilePath ?? mkdtempSync(
|
|
8870
|
+
const profilePath = opts.profilePath ?? mkdtempSync(join11(tmpdir(), "cdp-toolkit-ff-profile-"));
|
|
8545
8871
|
const args = ["--profile", profilePath, "--remote-debugging-port", String(port), "--no-remote"];
|
|
8546
8872
|
if (opts.headless)
|
|
8547
8873
|
args.push("--headless");
|
|
@@ -8892,7 +9218,7 @@ var FIREFOX_TOOLS = {
|
|
|
8892
9218
|
};
|
|
8893
9219
|
|
|
8894
9220
|
// src/mcp.ts
|
|
8895
|
-
var VERSION = "1.
|
|
9221
|
+
var VERSION = "1.11.0";
|
|
8896
9222
|
var { kind: BROWSER, endpoint: FIREFOX_ENDPOINT } = resolveBackend(process.argv.slice(2));
|
|
8897
9223
|
var AVAILABILITY = toolAvailability(BROWSER);
|
|
8898
9224
|
var AVAILABLE_NAMES = new Set(AVAILABILITY.available);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-toolkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "A lightweight chrome-devtools-mcp alternative: an MCP server + CLI that drives the tabs you name, one target per call over one direct WebSocket, with a bounded timeout on every call, so a stuck page can't wedge your agent or force a /mcp restart. Chrome over CDP and Firefox over WebDriver BiDi, with tools a backend can't run absent from tools/list rather than throwing mid-task. 45 tools including built-in network mocking, a cookie group, tab leases with human-activity detection, real HTML5 drag-and-drop, raw mouse/scroll dispatch, downloads, permission grants, and tab-to-video screen recording so several agents can drive one browser without stealing each other's tabs or a human's. No Puppeteer, no all-target fan-out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"mcp:smoke": "bun run ./test/mcp-smoke.ts",
|
|
80
80
|
"firefox:smoke": "bun run ./test/firefox-smoke.ts",
|
|
81
81
|
"firefox:attach:smoke": "bun run ./test/firefox-attach-smoke.ts",
|
|
82
|
+
"firefox:coord:smoke": "bun run ./test/firefox-session-coord-smoke.ts",
|
|
82
83
|
"lease:smoke": "bun run ./test/lease-smoke.ts",
|
|
83
84
|
"staleness:smoke": "bun run ./test/staleness-smoke.ts",
|
|
84
85
|
"input:smoke": "bun run ./test/input-smoke.ts",
|